diff options
Diffstat (limited to 'core/modules/migrate')
8 files changed, 222 insertions, 8 deletions
diff --git a/core/modules/migrate/migrate.api.php b/core/modules/migrate/migrate.api.php index 1e58b0090ff2..5d2af7db180e 100644 --- a/core/modules/migrate/migrate.api.php +++ b/core/modules/migrate/migrate.api.php @@ -156,7 +156,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr if ($migration->id() == 'd6_filter_formats') { $value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField(); if ($value) { - $row->setSourceProperty('settings:my_module:foo', unserialize($value)); + $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE])); } } } @@ -179,7 +179,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr function hook_migrate_MIGRATION_ID_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { $value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField(); if ($value) { - $row->setSourceProperty('settings:my_module:foo', unserialize($value)); + $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE])); } } diff --git a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php index 9adf60b46ffe..30cc28562e8d 100644 --- a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php +++ b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php @@ -65,7 +65,7 @@ class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery if (isset($cached['id'])) { // Explicitly unserialize this to create a new object // instance. - $definitions[$cached['id']] = unserialize($cached['content']); + $definitions[$cached['id']] = unserialize($cached['content'], ['allowed_classes' => FALSE]); } continue; } diff --git a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php index dc70496282f3..a5351c748620 100644 --- a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php +++ b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php @@ -71,7 +71,8 @@ class ConfigEntity extends SqlBase { * {@inheritdoc} */ public function prepareRow(Row $row) { - $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'))); + // @see \Drupal\Core\Config\DatabaseStorage::decode() + $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE])); return parent::prepareRow($row); } diff --git a/core/modules/migrate/src/Row.php b/core/modules/migrate/src/Row.php index 3d961902bcde..2b5e8b2fb4e6 100644 --- a/core/modules/migrate/src/Row.php +++ b/core/modules/migrate/src/Row.php @@ -267,6 +267,32 @@ class Row { } /** + * Tests if a property is an empty destination. + * + * @param string $property + * The name of the property. + * + * @return bool + * TRUE if the property is an empty destination. + */ + public function hasEmptyDestinationProperty(string $property): bool { + return in_array($property, $this->emptyDestinationProperties); + } + + /** + * Removes an empty destination property. + * + * @param string $property + * The name of the empty destination property. + */ + public function removeEmptyDestinationProperty(string $property): void { + $this->emptyDestinationProperties = array_diff( + $this->emptyDestinationProperties, + [$property], + ); + } + + /** * Returns the whole destination array. * * @return array diff --git a/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php b/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php index 6885ba378e9c..84dddc3c1825 100644 --- a/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php +++ b/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php @@ -12,10 +12,8 @@ use Drupal\migrate\Plugin\MigrationInterface; /** * Provides base class for testing migrate messages. - * - * @group migrate */ -class MigrateMessageTestBase extends BrowserTestBase { +abstract class MigrateMessageTestBase extends BrowserTestBase { /** * {@inheritdoc} diff --git a/core/modules/migrate/tests/src/Kernel/RowTest.php b/core/modules/migrate/tests/src/Kernel/RowTest.php new file mode 100644 index 000000000000..1b4adf181c88 --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/RowTest.php @@ -0,0 +1,152 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\migrate\Kernel; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\KernelTests\KernelTestBase; +use Drupal\migrate\Event\MigratePreRowSaveEvent; +use Drupal\migrate\Event\MigrateEvents; +use Drupal\migrate\MigrateExecutable; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Tests the Row class. + * + * @group migrate + */ +class RowTest extends KernelTestBase { + + /** + * The event dispatcher. + */ + protected EventDispatcherInterface $eventDispatcher; + + /** + * The entity type manager. + */ + protected EntityTypeManagerInterface $entityTypeManager; + + /** + * The migration manager. + */ + protected MigrationPluginManagerInterface $migrationManager; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'entity_test', + 'field', + 'migrate', + 'user', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->installEntitySchema('entity_test'); + + $this->eventDispatcher = \Drupal::service('event_dispatcher'); + $this->entityTypeManager = \Drupal::service('entity_type.manager'); + $this->migrationManager = \Drupal::service('plugin.manager.migration'); + + // Create two fields that will be set during migration. + $fields = ['field1', 'field2']; + foreach ($fields as $field) { + $this->entityTypeManager->getStorage('field_storage_config')->create([ + 'entity_type' => 'entity_test', + 'field_name' => $field, + 'type' => 'string', + ])->save(); + $this->entityTypeManager->getStorage('field_config')->create([ + 'entity_type' => 'entity_test', + 'field_name' => $field, + 'bundle' => 'entity_test', + ])->save(); + } + } + + /** + * Tests the destination properties of the Row class. + */ + public function testRowDestinations(): void { + $storage = $this->entityTypeManager->getStorage('entity_test'); + + // Execute a migration that creates an entity with two fields. + $data_rows = [ + ['id' => 1, 'field1' => 'f1value', 'field2' => 'f2value'], + ]; + $ids = ['id' => ['type' => 'integer']]; + $definition = [ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $data_rows, + 'ids' => $ids, + ], + 'process' => [ + 'id' => 'id', + 'field1' => 'field1', + 'field2' => 'field2', + ], + 'destination' => ['plugin' => 'entity:entity_test'], + ]; + $this->executeMigrationImport($definition); + $entity = $storage->load(1); + $this->assertEquals('f1value', $entity->get('field1')->getValue()[0]['value']); + $this->assertEquals('f2value', $entity->get('field2')->getValue()[0]['value']); + + // Execute a second migration that attempts to remove both field values. + // The event listener prevents the removal of the second field. + $data_rows = [ + ['id' => 1, 'field1' => NULL, 'field2' => NULL], + ]; + $definition['source']['data_rows'] = $data_rows; + $this->eventDispatcher->addListener(MigrateEvents::PRE_ROW_SAVE, [$this, 'preventFieldRemoval']); + $this->executeMigrationImport($definition); + + // The first field is now empty but the second field is still set. + $entity = $storage->load(1); + $this->assertTrue($entity->get('field1')->isEmpty()); + $this->assertEquals('f2value', $entity->get('field2')->getValue()[0]['value']); + } + + /** + * The pre-row-save event handler for the second migration. + * + * Checks row destinations and prevents the removal of the second field. + * + * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event + * The migration event. + * @param string $name + * The event name. + */ + public function preventFieldRemoval(MigratePreRowSaveEvent $event, string $name): void { + $row = $event->getRow(); + + // Both fields are empty and their existing values will be removed. + $this->assertFalse($row->hasDestinationProperty('field1')); + $this->assertFalse($row->hasDestinationProperty('field2')); + $this->assertTrue($row->hasEmptyDestinationProperty('field1')); + $this->assertTrue($row->hasEmptyDestinationProperty('field2')); + + // Prevent removal of field 2. + $row->removeEmptyDestinationProperty('field2'); + } + + /** + * Executes a migration import for the given migration definition. + * + * @param array $definition + * The migration definition. + */ + protected function executeMigrationImport(array $definition): void { + $migration = $this->migrationManager->createStubMigration($definition); + (new MigrateExecutable($migration))->import(); + } + +} diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php index ba9ab78cff66..ed223601abb3 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php @@ -14,7 +14,7 @@ use Drupal\Tests\UnitTestCase; /** * Base test class for entity migration destination functionality. */ -class EntityTestBase extends UnitTestCase { +abstract class EntityTestBase extends UnitTestCase { /** * The migration entity. diff --git a/core/modules/migrate/tests/src/Unit/RowTest.php b/core/modules/migrate/tests/src/Unit/RowTest.php index 69fce6f78607..7db1a51db437 100644 --- a/core/modules/migrate/tests/src/Unit/RowTest.php +++ b/core/modules/migrate/tests/src/Unit/RowTest.php @@ -292,6 +292,43 @@ class RowTest extends UnitTestCase { } /** + * Tests checking for and removing destination properties that may be empty. + * + * @covers ::hasEmptyDestinationProperty + * @covers ::removeEmptyDestinationProperty + */ + public function testDestinationOrEmptyProperty(): void { + $row = new Row($this->testValues, $this->testSourceIds); + + // Set a destination. + $row->setDestinationProperty('nid', 2); + $this->assertTrue($row->hasDestinationProperty('nid')); + $this->assertFalse($row->hasEmptyDestinationProperty('nid')); + + // Set an empty destination. + $row->setEmptyDestinationProperty('a_property_with_no_value'); + $this->assertTrue($row->hasEmptyDestinationProperty('a_property_with_no_value')); + $this->assertFalse($row->hasDestinationProperty('a_property_with_no_value')); + + // Removing an empty destination that is not actually empty has no effect. + $row->removeEmptyDestinationProperty('nid'); + $this->assertTrue($row->hasDestinationProperty('nid')); + $this->assertFalse($row->hasEmptyDestinationProperty('nid')); + + // Removing a destination that is actually empty has no effect. + $row->removeDestinationProperty('a_property_with_no_value'); + $this->assertTrue($row->hasEmptyDestinationProperty('a_property_with_no_value')); + + // Remove the empty destination. + $row->removeEmptyDestinationProperty('a_property_with_no_value'); + $this->assertFalse($row->hasEmptyDestinationProperty('a_property_with_no_value')); + + // Removing a destination that does not exist does not throw an error. + $this->assertFalse($row->hasEmptyDestinationProperty('not_a_property')); + $row->removeEmptyDestinationProperty('not_a_property'); + } + + /** * Tests setting/getting multiple destination IDs. */ public function testMultipleDestination(): void { |