diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index f3262a811a..7cf1082333 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Core\Session\AccountSwitcherInterface; use Drupal\Core\TypedData\TranslatableInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\migrate\Audit\HighestIdInterface; @@ -17,6 +18,7 @@ use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\migrate\Row; +use Drupal\user\EntityOwnerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -102,6 +104,13 @@ class EntityContentBase extends Entity implements HighestIdInterface, MigrateVal */ protected $fieldTypeManager; + /** + * The account switcher service. + * + * @var \Drupal\Core\Session\AccountSwitcherInterface + */ + protected $accountSwitcher; + /** * Constructs a content entity. * @@ -133,7 +142,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { $entity_type = static::getEntityTypeId($plugin_id); - return new static( + $instance = new static( $configuration, $plugin_id, $plugin_definition, @@ -143,6 +152,8 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity_field.manager'), $container->get('plugin.manager.field.field_type') ); + $instance->accountSwitcher = $container->get('account_switcher'); + return $instance; } /** @@ -184,8 +195,19 @@ public function isEntityValidationRequired(FieldableEntityInterface $entity) { * {@inheritdoc} */ public function validateEntity(FieldableEntityInterface $entity) { + // Entity validation can require the user that owns the entity. Switch to + // use that user during validation. + $switch_user = (($entity instanceof EntityOwnerInterface) && $user = $entity->getOwner()); + if ($switch_user) { + $this->getAccountSwitcher()->switchTo($user); + } + $violations = $entity->validate(); + if ($switch_user) { + $this->getAccountSwitcher()->switchBack(); + } + if (count($violations) > 0) { throw new EntityValidationException($violations); } @@ -375,4 +397,17 @@ public function getHighestId() { return (int) current($values); } + /** + * Gets the account switcher service. + * + * @return \Drupal\Core\Session\AccountSwitcherInterface + * The account switcher service. + */ + protected function getAccountSwitcher(): AccountSwitcherInterface { + if (!isset($this->accountSwitcher)) { + $this->accountSwitcher = \Drupal::service('account_switcher'); + } + return $this->accountSwitcher; + } + } diff --git a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php index c0cf029412..d34253b879 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php @@ -2,10 +2,15 @@ namespace Drupal\Tests\migrate\Kernel; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\filter\Entity\FilterFormat; use Drupal\KernelTests\KernelTestBase; use Drupal\migrate\Event\MigrateEvents; use Drupal\migrate\Event\MigrateIdMapMessageEvent; use Drupal\migrate\MigrateExecutable; +use Drupal\user\Entity\Role; +use Drupal\user\Entity\User; use Drupal\user\Plugin\Validation\Constraint\UserNameConstraint; /** @@ -18,7 +23,16 @@ class MigrateEntityContentValidationTest extends KernelTestBase { /** * {@inheritdoc} */ - protected static $modules = ['migrate', 'system', 'user', 'entity_test']; + protected static $modules = [ + 'entity_test', + 'field', + 'filter', + 'filter_test', + 'migrate', + 'system', + 'text', + 'user', + ]; /** * Messages accumulated during the migration run. @@ -33,9 +47,11 @@ class MigrateEntityContentValidationTest extends KernelTestBase { protected function setUp(): void { parent::setUp(); - $this->installConfig(['system', 'user']); $this->installEntitySchema('user'); + $this->installEntitySchema('user_role'); $this->installEntitySchema('entity_test'); + $this->installSchema('system', ['sequences']); + $this->installConfig(['field', 'filter_test', 'system', 'user']); $this->container ->get('event_dispatcher') @@ -140,6 +156,96 @@ public function test2() { $this->assertArrayNotHasKey(3, $this->messages, 'Fourth message should not exist.'); } + /** + * Tests validation for entities that are instances of EntityOwnerInterface. + */ + public function testEntityOwnerValidation() { + // Filter formats have permissions tied to user, user roles and permissions. + /* @var \Drupal\filter\FilterFormatInterface $filter_test_format */ + $filter_test_format = FilterFormat::load('filter_test'); + + // Create 2 users, an admin user who has filter permission and another who + // does not have said access. + /* @var \Drupal\user\RoleInterface $role */ + $role = Role::create([ + 'id' => 'admin', + 'label' => 'admin', + 'is_admin' => TRUE, + ]); + $role->grantPermission($filter_test_format->getPermissionName()); + $role->save(); + $admin_user = User::create([ + 'name' => 'foobar', + 'mail' => 'foobar@example.com', + ]); + $admin_user->addRole($role->id()); + $admin_user->save(); + $normal_user = User::create([ + 'name' => 'normal user', + 'mail' => 'normal@example.com', + ]); + $normal_user->save(); + + // Add a "body" field with the filter format. + $field_name = mb_strtolower($this->randomMachineName()); + $field_storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'entity_test', + 'type' => 'text', + ]); + $field_storage->save(); + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'entity_test', + ])->save(); + + // Attempt to migrate entities. One owned by the admin user, the other is a + // non-permissioned user. + $definition = [ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => [ + [ + 'id' => 1, + 'uid' => $admin_user->id(), + 'body' => [ + 'value' => 'foo', + 'format' => 'filter_test', + ], + ], + [ + 'id' => 2, + 'uid' => $normal_user->id(), + 'body' => [ + 'value' => 'bar', + 'format' => 'filter_test', + ], + ], + ], + 'ids' => [ + 'id' => ['type' => 'integer'], + ], + ], + 'process' => [ + 'id' => 'id', + 'user_id' => 'uid', + "$field_name/value" => 'body/value', + "$field_name/format" => 'body/format', + ], + 'destination' => [ + 'plugin' => 'entity:entity_test', + 'validate' => TRUE, + ], + ]; + $this->container->get('current_user')->setAccount($normal_user); + $this->runImport($definition); + + // The second, non-permissioned user import should fail validation because + // they do not have access to use "filter_test" filter. + $this->assertSame(sprintf('2: [entity_test: 2]: user_id.0.target_id=This entity (user: %s) cannot be referenced.||%s.0.format=The value you selected is not a valid choice.', $normal_user->id(), $field_name), $this->messages[0], 'First message should have 2 validation errors.'); + $this->assertArrayNotHasKey(1, $this->messages, 'Second message should not exist.'); + } + /** * Reacts to map message event. *