diff --git a/metatag.module b/metatag.module index ec7cb64..ccabf92 100644 --- a/metatag.module +++ b/metatag.module @@ -23,6 +23,7 @@ use Drupal\taxonomy\Plugin\migrate\source\d7\Term; use Drupal\taxonomy\TermInterface; use Drupal\user\Plugin\migrate\source\d7\User; +use Drupal\metatag\MigrateHelper; /** * Implements hook_help(). @@ -627,6 +628,12 @@ function metatag_generate_entity_metatags($entity) { * Implements hook_migrate_prepare_row(). */ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { + // If there's no 'metatag' table in the source, there's no point in + // continuing. + if (!$source->getDatabase()->schema()->tableExists('metatag')) { + return; + } + // @todo Write a more general version rather than hard-coded. // Support a know subset of D7 sources. if (is_a($source, Node::class)) { @@ -645,6 +652,7 @@ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, M // Not supported now, nothing to do. return; } + if ($migration->getDestinationPlugin() instanceof EntityContentBase) { $entity_type = NULL; $entity_id = NULL; @@ -681,25 +689,35 @@ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, M } } - $metatag = []; + $metatags = []; foreach ($query->execute()->fetchCol() as $data_entry) { // Re-shape D7 entries into for D8 entries. // @todo This could live in a migrate process plugin. - foreach (unserialize($data_entry) as $metatag_name => $data) { + foreach (unserialize($data_entry) as $d7_metatag_name => $data) { + // Convert the D7 meta tag name to the D8 equivalent. If this meta tag + // is not recognized, skip it. + $d8_metatag_name = MigrateHelper::mapMetatagD7($d7_metatag_name); + if ($d8_metatag_name === FALSE) { + continue; + } + + // If there's no data for this tag, ignore everything. if (empty($data)) { - // Nothing to do. continue; } + + // @todo Skip these values for now, maybe an some version supported + // these? if (!is_array($data) || empty($data['value'])) { - // @todo Skip these values for now, maybe an some version supported - // these? continue; } + // Keep the entire data structure. - $metatag[$metatag_name] = $data; + $metatags[$d8_metatag_name] = $data; } } - $row->setSourceProperty('field_metatag', serialize($metatag)); + + $row->setSourceProperty('field_metatag', serialize($metatags)); } } diff --git a/tests/fixtures/drupal7.php b/tests/fixtures/drupal7.php index 18e7cc5..bf0ec7f 100644 --- a/tests/fixtures/drupal7.php +++ b/tests/fixtures/drupal7.php @@ -71,28 +71,42 @@ 'entity_id' => '998', 'revision_id' => '998', 'language' => 'und', - 'data' => serialize(['keywords' => ['value' => 'keynoderevision']]), + 'data' => serialize([ + // A very basic meta tag. + 'keywords' => ['value' => 'keynoderevision'], + // A meta tag that changed its tag name in D8. + 'canonical' => ['value' => 'the-node'], + ]), ]) ->values([ 'entity_type' => 'node', 'entity_id' => '998', 'revision_id' => '999', 'language' => 'und', - 'data' => serialize(['keywords' => ['value' => 'keynode']]), + 'data' => serialize([ + 'keywords' => ['value' => 'keynode'], + 'canonical' => ['value' => 'the-node'], + ]), ]) ->values([ 'entity_type' => 'user', 'entity_id' => '2', 'revision_id' => '0', 'language' => 'und', - 'data' => serialize(['keywords' => ['value' => 'keyuser']]), + 'data' => serialize([ + 'keywords' => ['value' => 'keyuser'], + 'canonical' => ['value' => 'the-node'], + ]), ]) ->values([ 'entity_type' => 'taxonomy_term', 'entity_id' => '152', 'revision_id' => '0', 'language' => 'und', - 'data' => serialize(['keywords' => ['value' => 'keytaxonomy2']]), + 'data' => serialize([ + 'keywords' => ['value' => 'keytaxonomy2'], + 'canonical' => ['value' => 'the-term'], + ]), ]) ->execute(); diff --git a/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php b/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php index b23223d..5a117b7 100644 --- a/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php +++ b/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php @@ -111,24 +111,24 @@ public function testMetatag() { $node = Node::load(998); $this->assertTrue($node instanceof NodeInterface); $this->assertTrue($node->hasField('field_metatag')); - $this->assertSame('a:1:{s:8:"keywords";a:1:{s:5:"value";s:7:"keynode";}}', $node->field_metatag->value); + $this->assertSame('a:2:{s:8:"keywords";a:1:{s:5:"value";s:7:"keynode";}s:13:"canonical_url";a:1:{s:5:"value";s:8:"the-node";}}', $node->field_metatag->value); $node = node_revision_load(998); $this->assertTrue($node instanceof NodeInterface); $this->assertTrue($node->hasField('field_metatag')); - $this->assertSame('a:1:{s:8:"keywords";a:1:{s:5:"value";s:15:"keynoderevision";}}', $node->field_metatag->value); + $this->assertSame('a:2:{s:8:"keywords";a:1:{s:5:"value";s:15:"keynoderevision";}s:13:"canonical_url";a:1:{s:5:"value";s:8:"the-node";}}', $node->field_metatag->value); /** @var \Drupal\user\Entity\User $user */ $user = User::load(2); $this->assertTrue($user instanceof UserInterface); $this->assertTrue($user->hasField('field_metatag')); - $this->assertSame('a:1:{s:8:"keywords";a:1:{s:5:"value";s:7:"keyuser";}}', $user->field_metatag->value); + $this->assertSame('a:2:{s:8:"keywords";a:1:{s:5:"value";s:7:"keyuser";}s:13:"canonical_url";a:1:{s:5:"value";s:8:"the-node";}}', $user->field_metatag->value); /** @var \Drupal\taxonomy\Entity\Term $term */ $term = Term::load(152); $this->assertTrue($term instanceof TermInterface); $this->assertTrue($term->hasField('field_metatag')); - $this->assertSame('a:1:{s:8:"keywords";a:1:{s:5:"value";s:12:"keytaxonomy2";}}', $term->field_metatag->value); + $this->assertSame('a:2:{s:8:"keywords";a:1:{s:5:"value";s:12:"keytaxonomy2";}s:13:"canonical_url";a:1:{s:5:"value";s:8:"the-term";}}', $term->field_metatag->value); } } diff --git a/src/MigrateHelper.php b/src/MigrateHelper.php new file mode 100644 index 0000000..fb13bb7 --- /dev/null +++ b/src/MigrateHelper.php @@ -0,0 +1,352 @@ + 'abstract', + 'cache-control' => '', + 'canonical' => 'canonical_url', + 'content-language' => 'content_language', + 'description' => 'description', + 'expires' => '', + 'generator' => 'generator', + 'geo.placename' => 'geo_placename', + 'geo.position' => 'geo_position', + 'geo.region' => 'geo_region', + 'icbm' => 'icbm', + 'image_src' => 'image_src', + 'keywords' => 'keywords', + 'news_keywords' => 'news_keywords', + 'next' => '', + 'original-source' => 'original_source', + 'pragma' => '', + 'prev' => '', + 'rating' => 'rating', + 'referrer' => 'referrer', + 'refresh' => '', + 'revisit-after' => '', + 'rights' => 'rights', + 'robots' => 'robots', + 'set_cookie' => 'set_cookie', + 'shortlink' => 'shortlink', + 'standout' => 'standout', + 'title' => 'title', + // From metatag_app_links.metatag.inc: + 'al:android:package' => '', + 'al:android:url' => '', + 'al:android:class' => '', + 'al:android:app_name' => '', + 'al:ios:url' => '', + 'al:ios:app_store_id' => '', + 'al:ios:app_name' => '', + 'al:ipad:url' => '', + 'al:ipad:app_store_id' => '', + 'al:ipad:app_name' => '', + 'al:iphone:url' => '', + 'al:iphone:app_store_id' => '', + 'al:iphone:app_name' => '', + 'al:windows_phone:url' => '', + 'al:windows_phone:app_id' => '', + 'al:windows_phone:app_name' => '', + 'al:windows:url' => '', + 'al:windows:app_id' => '', + 'al:windows:app_name' => '', + 'al:windows_universal:url' => '', + 'al:windows_universal:app_id' => '', + 'al:windows_universal:app_name' => '', + 'al:web:url' => '', + 'al:web:should_fallback' => '', + // From metatag_dc.metatag.inc: + 'dcterms.title' => '', + 'dcterms.creator' => '', + 'dcterms.subject' => '', + 'dcterms.description' => '', + 'dcterms.publisher' => '', + 'dcterms.contributor' => '', + 'dcterms.date' => '', + 'dcterms.type' => '', + 'dcterms.format' => '', + 'dcterms.identifier' => '', + 'dcterms.source' => '', + 'dcterms.language' => '', + 'dcterms.relation' => '', + 'dcterms.coverage' => '', + 'dcterms.rights' => '', + // From metatag_dc_advanced.metatag.inc: + 'dcterms.abstract' => '', + 'dcterms.accessRights' => '', + 'dcterms.accrualMethod' => '', + 'dcterms.accrualPeriodicity' => '', + 'dcterms.accrualPolicy' => '', + 'dcterms.alternative' => '', + 'dcterms.audience' => '', + 'dcterms.available' => '', + 'dcterms.bibliographicCitation' => '', + 'dcterms.conformsTo' => '', + 'dcterms.created' => '', + 'dcterms.dateAccepted' => '', + 'dcterms.dateCopyrighted' => '', + 'dcterms.dateSubmitted' => '', + 'dcterms.educationLevel' => '', + 'dcterms.extent' => '', + 'dcterms.hasFormat' => '', + 'dcterms.hasPart' => '', + 'dcterms.hasVersion' => '', + 'dcterms.instructionalMethod' => '', + 'dcterms.isFormatOf' => '', + 'dcterms.isPartOf' => '', + 'dcterms.isReferencedBy' => '', + 'dcterms.isReplacedBy' => '', + 'dcterms.isRequiredBy' => '', + 'dcterms.isVersionOf' => '', + 'dcterms.issued' => '', + 'dcterms.license' => '', + 'dcterms.mediator' => '', + 'dcterms.medium' => '', + 'dcterms.modified' => '', + 'dcterms.provenance' => '', + 'dcterms.references' => '', + 'dcterms.replaces' => '', + 'dcterms.requires' => '', + 'dcterms.rightsHolder' => '', + 'dcterms.spatial' => '', + 'dcterms.tableOfContents' => '', + 'dcterms.temporal' => '', + 'dcterms.valid' => '', + // From metatag_facebook.metatag.inc: + 'fb:admins' => '', + 'fb:app_id' => '', + 'fb:pages' => '', + 'shortcut icon' => '', + 'mask-icon' => '', + 'icon_16x16' => '', + 'icon_32x32' => '', + 'icon_96x96' => '', + 'icon_192x192' => '', + 'apple-touch-icon' => '', + 'apple-touch-icon_72x72' => '', + 'apple-touch-icon_76x76' => '', + 'apple-touch-icon_114x114' => '', + 'apple-touch-icon_120x120' => '', + 'apple-touch-icon_144x144' => '', + 'apple-touch-icon_152x152' => '', + 'apple-touch-icon_180x180' => '', + 'apple-touch-icon-precomposed' => '', + 'apple-touch-icon-precomposed_72x72' => '', + 'apple-touch-icon-precomposed_76x76' => '', + 'apple-touch-icon-precomposed_114x114' => '', + 'apple-touch-icon-precomposed_120x120' => '', + 'apple-touch-icon-precomposed_144x144' => '', + 'apple-touch-icon-precomposed_152x152' => '', + 'apple-touch-icon-precomposed_180x180' => '', + // From metatag_google_cse.metatag.inc: + 'thumbnail' => '', + 'department' => '', + 'audience' => '', + 'doc_status' => '', + 'google_rating' => '', + // From metatag_google_plus.metatag.inc: + 'itemtype' => '', + 'itemprop:name' => '', + 'itemprop:description' => '', + 'itemprop:image' => '', + 'author' => '', + 'publisher' => '', + // From metatag_hreflang.metatag.inc: + 'hreflang_xdefault' => '', + // @todo 'hreflang_' . $langcode => '', + // From metatag_mobile.metatag.inc: + 'theme-color' => '', + 'MobileOptimized' => '', + 'HandheldFriendly' => '', + 'viewport' => '', + 'cleartype' => '', + 'amphtml' => '', + 'alternate_handheld' => '', + 'apple-itunes-app' => '', + 'apple-mobile-web-app-capable' => '', + 'apple-mobile-web-app-status-bar-style' => '', + 'apple-mobile-web-app-title' => '', + 'format-detection' => '', + 'ios-app-link-alternative' => '', + 'android-app-link-alternative' => '', + 'android-manifest' => '', + 'x-ua-compatible' => '', + 'application-name' => '', + 'msapplication-allowDomainApiCalls' => '', + 'msapplication-allowDomainMetaTags' => '', + 'msapplication-badge' => '', + 'msapplication-config' => '', + 'msapplication-navbutton-color' => '', + 'msapplication-notification' => '', + 'msapplication-square150x150logo' => '', + 'msapplication-square310x310logo' => '', + 'msapplication-square70x70logo' => '', + 'msapplication-wide310x150logo' => '', + 'msapplication-starturl' => '', + 'msapplication-task' => '', + 'msapplication-task-separator' => '', + 'msapplication-tilecolor' => '', + 'msapplication-tileimage' => '', + 'msapplication-tooltip' => '', + 'msapplication-window' => '', + // From metatag_opengraph.metatag.inc: + 'og:site_name' => '', + 'og:type' => '', + 'og:url' => '', + 'og:title' => '', + 'og:determiner' => '', + 'og:description' => '', + 'og:updated_time' => '', + 'og:see_also' => '', + 'og:image' => '', + 'og:image:url' => '', + 'og:image:secure_url' => '', + 'og:image:type' => '', + 'og:image:width' => '', + 'og:image:height' => '', + 'og:latitude' => '', + 'og:longitude' => '', + 'og:street_address' => '', + 'og:locality' => '', + 'og:region' => '', + 'og:postal_code' => '', + 'og:country_name' => '', + 'og:email' => '', + 'og:phone_number' => '', + 'og:fax_number' => '', + 'og:locale' => '', + 'og:locale:alternate' => '', + 'article:author' => '', + 'article:publisher' => '', + 'article:section' => '', + 'article:tag' => '', + 'article:published_time' => '', + 'article:modified_time' => '', + 'article:expiration_time' => '', + 'profile:first_name' => '', + 'profile:last_name' => '', + 'profile:username' => '', + 'profile:gender' => '', + 'og:audio' => '', + 'og:audio:secure_url' => '', + 'og:audio:type' => '', + 'book:author' => '', + 'book:isbn' => '', + 'book:release_date' => '', + 'book:tag' => '', + 'og:video:url' => '', + 'og:video:secure_url' => '', + 'og:video:width' => '', + 'og:video:height' => '', + 'og:video:type' => '', + 'video:actor' => '', + 'video:actor:role' => '', + 'video:director' => '', + 'video:writer' => '', + 'video:duration' => '', + 'video:release_date' => '', + 'video:tag' => '', + 'video:series' => '', + // From metatag_opengraph_products.metatag.inc: + 'product:price:amount' => '', + 'product:price:currency' => '', + 'product:availability' => '', + 'product:brand' => '', + 'product:upc' => '', + 'product:ean' => '', + 'product:isbn' => '', + 'product:plural_title' => '', + 'product:retailer' => '', + 'product:retailer_title' => '', + 'product:retailer_part_no' => '', + 'product:mfr_part_no' => '', + 'product:size' => '', + 'product:product_link' => '', + 'product:category' => '', + 'product:color' => '', + 'product:material' => '', + 'product:pattern' => '', + 'product:shipping_cost:amount' => '', + 'product:shipping_cost:currency' => '', + 'product:weight:value' => '', + 'product:weight:units' => '', + 'product:shipping_weight:value' => '', + 'product:shipping_weight:units' => '', + 'product:expiration_time' => '', + 'product:condition' => '', + // From metatag_twitter_cards.metatag.inc: + 'twitter:card' => '', + 'twitter:site' => '', + 'twitter:site:id' => '', + 'twitter:creator' => '', + 'twitter:creator:id' => '', + 'twitter:url' => '', + 'twitter:title' => '', + 'twitter:description' => '', + 'twitter:dnt' => '', + 'twitter:image' => '', + 'twitter:image:width' => '', + 'twitter:image:height' => '', + 'twitter:image:alt' => '', + 'twitter:image0' => '', + 'twitter:image1' => '', + 'twitter:image2' => '', + 'twitter:image3' => '', + 'twitter:player' => '', + 'twitter:player:width' => '', + 'twitter:player:height' => '', + 'twitter:player:stream' => '', + 'twitter:player:stream:content_type' => '', + 'twitter:app:country' => '', + 'twitter:app:name:iphone' => '', + 'twitter:app:id:iphone' => '', + 'twitter:app:url:iphone' => '', + 'twitter:app:name:ipad' => '', + 'twitter:app:id:ipad' => '', + 'twitter:app:url:ipad' => '', + 'twitter:app:name:googleplay' => '', + 'twitter:app:id:googleplay' => '', + 'twitter:app:url:googleplay' => '', + 'twitter:label1' => '', + 'twitter:data1' => '', + 'twitter:label2' => '', + 'twitter:data2' => '', + // From metatag_verification.metatag.inc: + 'msvalidate.01' => '', + 'baidu-site-verification' => '', + 'google-site-verification' => '', + 'norton-safeweb-site-verification' => '', + 'p:domain_verify' => '', + 'yandex-verification' => '', + ]; + + // @todo Add drupal_alter(). + + // If the meta tag was known in D7, return the D8 name. + if (isset($d7_tags[$old_tag_name])) { + return $d7_tags[$old_tag_name]; + } + + return FALSE; + } + +}