diff --git a/README.txt b/README.txt index da936bf..f2aa01d 100644 --- a/README.txt +++ b/README.txt @@ -61,7 +61,7 @@ The primary features include: APIs, but they are not needed by most sites and have no bearing on the Open Graph meta tags. -* The Pinterest meta tags may be added by enabling the "Metatag: Pinterest" +* The Pinterest meta tags may be added by enabling the "Metatag: Pinterest" submodule. * Site verification meta tags can be added, e.g. as used by the Google search @@ -230,6 +230,17 @@ the type "html_tag". Extracting the value of the meta tag will depend upon the type of meta tag, e.g. the generator meta tag uses the "content" attribute while the link tag uses the "href" attribute. +Migration +-------------------------------------------------------------------------------- +This module works with Migrate Plus https://www.drupal.org/project/migrate_plus. +If you want to migrate from Drupal 7 to Drupal 8, all you need to do is to add +the field mapping into the respective migrate_plus.migration yml file. i.e. + +process: + field_metatags: field_metatag + +Note: Replace `field_metatags` with the machine name used on the entity type. + DrupalConsole integration -------------------------------------------------------------------------------- diff --git a/metatag.module b/metatag.module index 243a1f5..ccabf92 100644 --- a/metatag.module +++ b/metatag.module @@ -5,6 +5,8 @@ * Contains metatag.module. */ +use Drupal\Component\Plugin\Factory\DefaultFactory; +use Drupal\Component\Utility\Html; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; @@ -13,8 +15,15 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; +use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; +use Drupal\migrate\Plugin\MigrateSourceInterface; +use Drupal\migrate\Plugin\MigrationInterface; +use Drupal\migrate\Row; +use Drupal\node\Plugin\migrate\source\d7\Node; +use Drupal\taxonomy\Plugin\migrate\source\d7\Term; use Drupal\taxonomy\TermInterface; -use Drupal\Component\Utility\Html; +use Drupal\user\Plugin\migrate\source\d7\User; +use Drupal\metatag\MigrateHelper; /** * Implements hook_help(). @@ -614,3 +623,122 @@ function metatag_generate_entity_metatags($entity) { } return $values; } + +/** + * 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)) { + // E.g. d7_node, d7_node_revision. + $source_type = 'node'; + } + elseif (is_a($source, Term::class)) { + // E.g. d7_taxonomy_term. + $source_type = 'taxonomy'; + } + elseif (is_a($source, User::class)) { + // E.g. d7_user. + $source_type = 'user'; + } + else { + // Not supported now, nothing to do. + return; + } + + if ($migration->getDestinationPlugin() instanceof EntityContentBase) { + $entity_type = NULL; + $entity_id = NULL; + $revision_id = NULL; + + // @todo Write a more general version rather than switch statement. + switch ($source_type) { + case 'node': + $entity_type = 'node'; + $entity_id = $row->getSourceProperty('nid'); + $revision_id = $row->getSourceProperty('vid'); + break; + + case 'taxonomy': + $entity_type = 'taxonomy_term'; + $entity_id = $row->getSourceProperty('tid'); + break; + + case 'user': + $entity_type = 'user'; + $entity_id = $row->getSourceProperty('uid'); + break; + } + + /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source */ + /** @var \Drupal\Core\Database\Query\SelectInterface $query */ + $query = $source->getDatabase()->select('metatag', 'm') + ->fields('m', ['data']) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id); + if (!is_null($revision_id)) { + if ($source->getDatabase()->schema()->fieldExists('metatag', 'revision_id')) { + $query->condition('revision_id', $revision_id); + } + } + + $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 $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)) { + continue; + } + + // @todo Skip these values for now, maybe an some version supported + // these? + if (!is_array($data) || empty($data['value'])) { + continue; + } + + // Keep the entire data structure. + $metatags[$d8_metatag_name] = $data; + } + } + + $row->setSourceProperty('field_metatag', serialize($metatags)); + } +} + +/** + * Implements hook_migration_plugins_alter(). + */ +function metatag_migration_plugins_alter(array &$migrations) { + foreach ($migrations as &$migration) { + if (isset($migration['destination']['plugin'])) { + // Follow logic on hook_entity_base_field_info(), and exclude metatag + // itself. + if (in_array($migration['destination']['plugin'], ['entity:comment', 'entity:metatag'])) { + continue; + } + $plugin_definition = \Drupal::service('plugin.manager.migrate.destination') + ->getDefinition($migration['destination']['plugin']); + $destination_plugin = DefaultFactory::getPluginClass($migration['destination']['plugin'], $plugin_definition); + + if (is_subclass_of($destination_plugin, EntityContentBase::class) || $destination_plugin == EntityContentBase::class) { + $migration['process']['field_metatag'] = 'field_metatag'; + } + } + } +} diff --git a/migrations/d7_metatag_field.yml b/migrations/d7_metatag_field.yml new file mode 100644 index 0000000..0b6f0cd --- /dev/null +++ b/migrations/d7_metatag_field.yml @@ -0,0 +1,20 @@ +id: d7_metatag_field +label: Metatag field +migration_tags: + - Drupal 7 +source: + plugin: d7_metatag_field + ignore_map: true + constants: + status: true + langcode: und + field_name: field_metatag + type: metatag +process: + entity_type: entity_type + status: 'constants/status' + langcode: 'constants/langcode' + field_name: 'constants/field_name' + type: 'constants/type' +destination: + plugin: entity:field_storage_config diff --git a/migrations/d7_metatag_field_instance.yml b/migrations/d7_metatag_field_instance.yml new file mode 100644 index 0000000..f3bf586 --- /dev/null +++ b/migrations/d7_metatag_field_instance.yml @@ -0,0 +1,23 @@ +id: d7_metatag_field_instance +label: Metatag field instance +migration_tags: + - Drupal 7 +source: + plugin: d7_metatag_field_instance + source_module: metatag + ignore_map: true + constants: + field_name: field_metatag + label: Metatags +process: + entity_type: entity_type + field_name: 'constants/field_name' + bundle: bundle + label: 'constants/label' +destination: + plugin: entity:field_config +migration_dependencies: + required: + - d7_metatag_field + - d7_node_type + - d7_taxonomy_vocabulary diff --git a/migrations/d7_metatag_field_instance_widget_settings.yml b/migrations/d7_metatag_field_instance_widget_settings.yml new file mode 100644 index 0000000..2d9a5a2 --- /dev/null +++ b/migrations/d7_metatag_field_instance_widget_settings.yml @@ -0,0 +1,21 @@ +id: d7_metatag_field_instance_widget_settings +label: Metatag field instance widget settings +migration_tags: + - Drupal 7 +source: + plugin: d7_metatag_field_instance + source_module: metatag + ignore_map: true + constants: + form_mode: default + field_name: field_metatag +process: + bundle: bundle + form_mode: 'constants/form_mode' + field_name: 'constants/field_name' + entity_type: entity_type +destination: + plugin: component_entity_form_display +migration_dependencies: + required: + - d7_metatag_field_instance 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; + } + +} diff --git a/src/Plugin/migrate/source/d7/MetatagField.php b/src/Plugin/migrate/source/d7/MetatagField.php new file mode 100644 index 0000000..305f239 --- /dev/null +++ b/src/Plugin/migrate/source/d7/MetatagField.php @@ -0,0 +1,42 @@ +select('metatag', 'm') + ->fields('m', ['entity_type']) + ->groupBy('entity_type'); + } + + /** + * {@inheritdoc} + */ + public function fields() { + $fields = ['entity_type' => $this->t('Entity type')]; + return $fields; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids['entity_type']['type'] = 'string'; + return $ids; + } + +} diff --git a/src/Plugin/migrate/source/d7/MetatagFieldInstance.php b/src/Plugin/migrate/source/d7/MetatagFieldInstance.php new file mode 100644 index 0000000..8b4f2eb --- /dev/null +++ b/src/Plugin/migrate/source/d7/MetatagFieldInstance.php @@ -0,0 +1,94 @@ +entityTypeBundleInfo = $entity_type_bundle_info; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $migration, + $container->get('state'), + $container->get('entity.manager'), + $container->get('entity_type.bundle.info') + ); + } + + /** + * {@inheritdoc} + */ + public function query() { + return $this->select('metatag', 'm') + ->fields('m', ['entity_type']) + ->groupBy('entity_type'); + } + + /** + * {@inheritdoc} + */ + public function fields() { + return [ + 'entity_type' => $this->t('Entity type'), + 'bundle' => $this->t('Bundle'), + ]; + } + + /** + * Returns each entity_type/bundle pair. + */ + public function initializeIterator() { + $bundles = []; + foreach (parent::initializeIterator() as $instance) { + $bundle_info = $this->entityTypeBundleInfo + ->getBundleInfo($instance['entity_type']); + foreach (array_keys($bundle_info) as $bundle) { + $bundles[] = [ + 'entity_type' => $instance['entity_type'], + 'bundle' => $bundle, + ]; + } + } + return new \ArrayIterator($bundles); + } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids['entity_type']['type'] = 'string'; + $ids['bundle']['type'] = 'string'; + return $ids; + } + +} diff --git a/tests/fixtures/drupal7.php b/tests/fixtures/drupal7.php new file mode 100644 index 0000000..bf0ec7f --- /dev/null +++ b/tests/fixtures/drupal7.php @@ -0,0 +1,229 @@ +schema()->createTable('metatag', [ + 'fields' => [ + 'entity_type' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ], + 'entity_id' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'revision_id' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'language' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ], + 'data' => [ + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ], + ], + 'primary key' => [ + 'entity_type', + 'entity_id', + 'revision_id', + 'language', + ], + 'indexes' => [ + 'type_revision' => [ + 'entity_type', + 'revision_id', + ], + ], + 'mysql_character_set' => 'utf8', +]); + +$connection->insert('metatag') + ->fields([ + 'entity_type', + 'entity_id', + 'revision_id', + 'language', + 'data', + ]) + ->values([ + 'entity_type' => 'node', + 'entity_id' => '998', + 'revision_id' => '998', + 'language' => 'und', + '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'], + 'canonical' => ['value' => 'the-node'], + ]), + ]) + ->values([ + 'entity_type' => 'user', + 'entity_id' => '2', + 'revision_id' => '0', + 'language' => 'und', + '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'], + 'canonical' => ['value' => 'the-term'], + ]), + ]) + ->execute(); + +$connection->insert('node') + ->fields([ + 'nid', + 'vid', + 'type', + 'language', + 'title', + 'uid', + 'status', + 'created', + 'changed', + 'comment', + 'promote', + 'sticky', + 'tnid', + 'translate', + ]) + ->values([ + 'nid' => '998', + 'vid' => '999', + 'type' => 'test_content_type', + 'language' => 'en', + 'title' => 'An Edited Node', + 'uid' => '2', + 'status' => '1', + 'created' => '1421727515', + 'changed' => '1441032132', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', + 'tnid' => '0', + 'translate' => '0', + ]) + ->execute(); + +$connection->insert('node_revision') + ->fields([ + 'nid', + 'vid', + 'uid', + 'title', + 'log', + 'timestamp', + 'status', + 'comment', + 'promote', + 'sticky', + ]) + ->values([ + 'nid' => '998', + 'vid' => '998', + 'uid' => '1', + 'title' => 'A Node', + 'log' => '', + 'timestamp' => '1441032131', + 'status' => '1', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', + ]) + ->values([ + 'nid' => '998', + 'vid' => '999', + 'uid' => '1', + 'title' => 'An Edited Node', + 'log' => '', + 'timestamp' => '1441032132', + 'status' => '1', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', + ]) + ->execute(); + +$connection->insert('taxonomy_term_data') + ->fields([ + 'tid', + 'vid', + 'name', + 'description', + 'format', + 'weight', + ]) + ->values([ + '152', + '1', + 'A Term', + '', + 'plain_text', + '0', + ]) + ->execute(); + +$connection->insert('system') + ->fields([ + 'filename', + 'name', + 'type', + 'owner', + 'status', + 'bootstrap', + 'schema_version', + 'weight', + 'info', + ]) + ->values([ + 'filename' => 'sites/all/modules/metatag/metatag.module', + 'name' => 'metatag', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'bootstrap' => '0', + 'schema_version' => '7115', + 'weight' => '0', + 'info' => 'a:12:{s:4:"name";s:7:"Metatag";s:11:"description";s:47:"Adds support and an API to implement meta tags.";s:7:"package";s:3:"SEO";s:4:"core";s:3:"7.x";s:12:"dependencies";a:3:{i:0;s:23:"drupal:system (>= 7.40)";i:1;s:13:"ctools:ctools";i:2;s:11:"token:token";}s:9:"configure";s:28:"admin/config/search/metatags";s:5:"files";a:30:{i:0;s:11:"metatag.inc";i:1;s:19:"metatag.migrate.inc";i:2;s:22:"metatag.search_api.inc";i:3;s:25:"tests/metatag.helper.test";i:4;s:23:"tests/metatag.unit.test";i:5;s:30:"tests/metatag.tags_helper.test";i:6;s:23:"tests/metatag.tags.test";i:7;s:23:"tests/metatag.node.test";i:8;s:23:"tests/metatag.term.test";i:9;s:23:"tests/metatag.user.test";i:10;s:35:"tests/metatag.core_tag_removal.test";i:11;s:30:"tests/metatag.bulk_revert.test";i:12;s:34:"tests/metatag.string_handling.test";i:13;s:44:"tests/metatag.string_handling_with_i18n.test";i:14;s:22:"tests/metatag.xss.test";i:15;s:33:"tests/metatag.output_caching.test";i:16;s:24:"tests/metatag.image.test";i:17;s:25:"tests/metatag.locale.test";i:18;s:33:"tests/metatag.node.with_i18n.test";i:19;s:33:"tests/metatag.term.with_i18n.test";i:20;s:35:"tests/metatag.with_i18n_output.test";i:21;s:37:"tests/metatag.with_i18n_disabled.test";i:22;s:35:"tests/metatag.with_i18n_config.test";i:23;s:26:"tests/metatag.with_me.test";i:24;s:29:"tests/metatag.with_media.test";i:25;s:30:"tests/metatag.with_panels.test";i:26;s:32:"tests/metatag.with_profile2.test";i:27;s:34:"tests/metatag.with_search_api.test";i:28;s:44:"tests/metatag.with_workbench_moderation.test";i:29;s:29:"tests/metatag.with_views.test";}s:17:"test_dependencies";a:14:{i:0;s:11:"devel:devel";i:1;s:33:"imagecache_token:imagecache_token";i:2;s:37:"entity_translation:entity_translation";i:3;s:9:"i18n:i18n";i:4;s:5:"me:me";i:5;s:23:"file_entity:file_entity";i:6;s:27:"media:media (>= 2.0, < 3.0)";i:7;s:13:"panels:panels";i:8;s:17:"profile2:profile2";i:9;s:13:"entity:entity";i:10;s:21:"search_api:search_api";i:11;s:41:"workbench_moderation:workbench_moderation";i:12;s:11:"views:views";i:13;s:15:"context:context";}s:5:"mtime";i:1550007449;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + ]) + ->execute(); diff --git a/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php b/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php new file mode 100644 index 0000000..5a117b7 --- /dev/null +++ b/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php @@ -0,0 +1,134 @@ +installSchema('file', ['file_usage']); + $this->installEntitySchema('file'); + $this->container->get('stream_wrapper_manager') + ->registerWrapper('public', PublicStream::class, StreamWrapperInterface::NORMAL); + + $fs = \Drupal::service('file_system'); + // The public file directory active during the test will serve as the + // root of the fictional Drupal 7 site we're migrating. + $fs->mkdir('public://sites/default/files', NULL, TRUE); + file_put_contents('public://sites/default/files/cube.jpeg', str_repeat('*', 3620)); + + /** @var \Drupal\migrate\Plugin\Migration $migration */ + $migration = $this->getMigration('d7_file'); + // Set the source plugin's source_base_path configuration value, which + // would normally be set by the user running the migration. + $source = $migration->getSourceConfiguration(); + $source['constants']['source_base_path'] = $fs->realpath('public://'); + $migration->set('source', $source); + $this->executeMigration($migration); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->loadFixture(__DIR__ . '/../../../../fixtures/drupal7.php'); + + $this->installEntitySchema('node'); + $this->installEntitySchema('comment'); + $this->installEntitySchema('taxonomy_term'); + $this->installConfig(static::$modules); + $this->installSchema('node', ['node_access']); + $this->installSchema('system', ['sequences']); + $this->installEntitySchema('metatag_defaults'); + + $this->executeMigrations([ + 'd7_metatag_field', + 'd7_node_type', + 'd7_taxonomy_vocabulary', + 'd7_metatag_field_instance', + 'd7_metatag_field_instance_widget_settings', + 'd7_user_role', + 'd7_user', + 'd7_comment_type', + 'd7_field', + 'd7_field_instance', + ]); + $this->fileMigrationSetup(); + $this->executeMigrations([ + 'd7_node:test_content_type', + 'd7_node:article', + 'd7_node:forum', + 'd7_node:blog', + 'd7_node_revision:test_content_type', + 'd7_taxonomy_term', + ]); + } + + /** + * Test Metatag migration from Drupal 7 to 8. + */ + public function testMetatag() { + /** @var \Drupal\node\Entity\Node $node */ + $node = Node::load(998); + $this->assertTrue($node instanceof NodeInterface); + $this->assertTrue($node->hasField('field_metatag')); + $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: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: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: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/tests/src/Unit/Migrate/d7/MetatagFieldInstanceTest.php b/tests/src/Unit/Migrate/d7/MetatagFieldInstanceTest.php new file mode 100644 index 0000000..760e70e --- /dev/null +++ b/tests/src/Unit/Migrate/d7/MetatagFieldInstanceTest.php @@ -0,0 +1,87 @@ + 'test', + 'source' => [ + 'plugin' => 'd7_metatag_field_instance', + ], + ]; + + protected $expectedResults = [ + [ + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + ], + [ + 'entity_type' => 'taxonomy_term', + 'bundle' => 'test_vocabulary', + ], + [ + 'entity_type' => 'user', + 'bundle' => 'user', + ], + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->databaseContents['metatag'] = $this->expectedResults; + + $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $state = $this->getMock('Drupal\Core\State\StateInterface'); + $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $entity_type_bundle_info = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeBundleInfo') + ->disableOriginalConstructor() + ->getMock(); + $entity_type_bundle_info->expects($this->any()) + ->method('getBundleInfo') + ->willReturnMap([ + ['node', ['test_content_type' => 'test_content_type']], + ['taxonomy_term', ['test_vocabulary' => 'test_vocabulary']], + ['user', ['user' => 'user']], + ]); + + $migration = $this->getMigration(); + // @todo Replace this. + // $migration->expects($this->any()) + // ->method('getHighWater') + // ->will($this->returnValue(static::ORIGINAL_HIGH_WATER)); + + // Setup the plugin. + $plugin_class = static::PLUGIN_CLASS; + $plugin = new $plugin_class($this->migrationConfiguration['source'], $this->migrationConfiguration['source']['plugin'], [], $migration, $state, $entity_manager, $entity_type_bundle_info); + + // Do some reflection to set the database and moduleHandler. + $plugin_reflection = new \ReflectionClass($plugin); + $database_property = $plugin_reflection->getProperty('database'); + $database_property->setAccessible(TRUE); + $module_handler_property = $plugin_reflection->getProperty('moduleHandler'); + $module_handler_property->setAccessible(TRUE); + + // Set the database and the module handler onto our plugin. + $database_property->setValue($plugin, $this->getDatabase($this->databaseContents + ['test_map' => []])); + $module_handler_property->setValue($plugin, $module_handler); + + $plugin->setStringTranslation($this->getStringTranslationStub()); + $migration->expects($this->any()) + ->method('getSourcePlugin') + ->will($this->returnValue($plugin)); + $this->source = $plugin; + $this->expectedCount = count($this->expectedResults); + } + +} diff --git a/tests/src/Unit/Migrate/d7/MetatagFieldTest.php b/tests/src/Unit/Migrate/d7/MetatagFieldTest.php new file mode 100644 index 0000000..2873418 --- /dev/null +++ b/tests/src/Unit/Migrate/d7/MetatagFieldTest.php @@ -0,0 +1,43 @@ + 'test', + 'source' => [ + 'plugin' => 'd7_metatag_field', + ], + ]; + + protected $expectedResults = [ + [ + 'entity_type' => 'node', + ], + [ + 'entity_type' => 'taxonomy_term', + ], + [ + 'entity_type' => 'user', + ], + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->databaseContents['metatag'] = $this->expectedResults; + parent::setUp(); + } + +}