diff --git a/metatag.module b/metatag.module index b9fad84..1a76129 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,14 @@ 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; /** * Implements hook_help(). @@ -603,3 +611,103 @@ function metatag_generate_entity_metatags($entity) { } return $values; } + +/** + * Implements hook_migrate_prepare_row(). + */ +function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { + // @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)) { + $query->condition('revision_id', $revision_id); + } + + $metatag = []; + 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) { + if (empty($data)) { + // Nothing to do. + continue; + } + if (!is_array($data) || empty($data['value'])) { + // @todo Skip these values for now, maybe an some version supported + // these? + continue; + } + // Use the known 'value' key. + $metatag[$metatag_name] = $data['value']; + } + } + $row->setSourceProperty('field_metatag', serialize($metatag)); + } +} + +/** + * 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/Plugin/migrate/source/d7/MetatagField.php b/src/Plugin/migrate/source/d7/MetatagField.php new file mode 100644 index 0000000..853db0d --- /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 = array('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..8f97b3d --- /dev/null +++ b/src/Plugin/migrate/source/d7/MetatagFieldInstance.php @@ -0,0 +1,95 @@ +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 array( + '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..c3b9946 --- /dev/null +++ b/tests/fixtures/drupal7.php @@ -0,0 +1,189 @@ +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(['keywords' => ['value' => 'keynoderevision']]), + ]) + ->values([ + 'entity_type' => 'node', + 'entity_id' => '998', + 'revision_id' => '999', + 'language' => 'und', + 'data' => serialize(['keywords' => ['value' => 'keynode']]), + ]) + ->values([ + 'entity_type' => 'user', + 'entity_id' => '2', + 'revision_id' => '0', + 'language' => 'und', + 'data' => serialize(['keywords' => ['value' => 'keyuser']]), + ]) + ->values([ + 'entity_type' => 'taxonomy_term', + 'entity_id' => '152', + 'revision_id' => '0', + 'language' => 'und', + 'data' => serialize(['keywords' => ['value' => 'keytaxonomy2']]), + ]) + ->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(); diff --git a/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php b/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php new file mode 100644 index 0000000..59fe2f0 --- /dev/null +++ b/tests/src/Kernel/Migrate/d7/MigrateMetatagTest.php @@ -0,0 +1,107 @@ +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'); + + $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_revision:test_content_type', + 'd7_taxonomy_term', + ]); + } + + /** + * Test Metatag migration from Drupal 7 to 8. + */ + public function testMetatag() { + /** @var Node $node */ + $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); + + $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); + + /** @var 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); + + /** @var 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); + } + +} diff --git a/tests/src/Unit/Migrate/d7/MetatagFieldInstanceTest.php b/tests/src/Unit/Migrate/d7/MetatagFieldInstanceTest.php new file mode 100644 index 0000000..530508d --- /dev/null +++ b/tests/src/Unit/Migrate/d7/MetatagFieldInstanceTest.php @@ -0,0 +1,86 @@ + 'test', + 'source' => array( + 'plugin' => 'd7_metatag_field_instance', + ), + ); + + protected $expectedResults = array( + array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + ), + array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'test_vocabulary', + ), + array( + '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(); + $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'], array(), $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 + array('test_map' => array()))); + $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..0cba8e1 --- /dev/null +++ b/tests/src/Unit/Migrate/d7/MetatagFieldTest.php @@ -0,0 +1,43 @@ + 'test', + 'source' => array( + 'plugin' => 'd7_metatag_field', + ), + ); + + protected $expectedResults = array( + array( + 'entity_type' => 'node', + ), + array( + 'entity_type' => 'taxonomy_term', + ), + array( + 'entity_type' => 'user', + ), + ); + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->databaseContents['metatag'] = $this->expectedResults; + parent::setUp(); + } + +}