Skip to content
Snippets Groups Projects
Verified Commit da97aa7e authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3300639 by amateescu, acrazyanimal, smustgrave: Improve and add...

Issue #3300639 by amateescu, acrazyanimal, smustgrave: Improve and add explicit test coverage for the workspace conflict validator

(cherry picked from commit d9a1c6e8)
parent 1cc7dc97
Branches 2.x
Tags 2.5.0
23 merge requests!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9944Issue #3483353: Consider making the createCopy config action optionally fail...,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8597Draft: Issue #3442259 by catch, quietone, dww: Reduce time of Migrate Upgrade tests...,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!8325Update file Sort.php,!8095Expose document root on install,!7930Resolve #3427374 "Taxonomytid viewsargumentdefault plugin",!7627Issue #3439440 by nicxvan, Binoli Lalani, longwave: Remove country support from DateFormatter,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!7384Add constraints to system.advisories,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #135535 passed with warnings
Pipeline: drupal

#135541

    ......@@ -21,6 +21,6 @@ class EntityWorkspaceConflictConstraint extends SymfonyConstraint {
    *
    * @var string
    */
    public $message = 'The content is being edited in the %label workspace. As a result, your changes cannot be saved.';
    public $message = 'The content is being edited in the @label workspace. As a result, your changes cannot be saved.';
    }
    ......@@ -6,62 +6,22 @@
    use Drupal\Core\Entity\EntityTypeManagerInterface;
    use Drupal\workspaces\WorkspaceAssociationInterface;
    use Drupal\workspaces\WorkspaceManagerInterface;
    use Drupal\workspaces\WorkspaceRepositoryInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\Validator\Constraint;
    use Symfony\Component\Validator\ConstraintValidator;
    /**
    * Validates the EntityWorkspaceConflict constraint.
    *
    * @internal
    */
    class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
    /**
    * The entity type manager.
    *
    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
    protected $entityTypeManager;
    /**
    * The workspace manager service.
    *
    * @var \Drupal\workspaces\WorkspaceManagerInterface
    */
    protected $workspaceManager;
    /**
    * The workspace association service.
    *
    * @var \Drupal\workspaces\WorkspaceAssociationInterface
    */
    protected $workspaceAssociation;
    /**
    * The workspace repository service.
    *
    * @var \Drupal\workspaces\WorkspaceRepositoryInterface
    */
    protected $workspaceRepository;
    /**
    * Constructs an EntityUntranslatableFieldsConstraintValidator object.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    * The entity type manager service.
    * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
    * The workspace manager service.
    * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
    * The workspace association service.
    * @param \Drupal\workspaces\WorkspaceRepositoryInterface $workspace_repository
    * The Workspace repository service.
    */
    public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, WorkspaceRepositoryInterface $workspace_repository) {
    $this->entityTypeManager = $entity_type_manager;
    $this->workspaceManager = $workspace_manager;
    $this->workspaceAssociation = $workspace_association;
    $this->workspaceRepository = $workspace_repository;
    }
    public function __construct(
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly WorkspaceManagerInterface $workspaceManager,
    protected readonly WorkspaceAssociationInterface $workspaceAssociation,
    ) {}
    /**
    * {@inheritdoc}
    ......@@ -71,7 +31,6 @@ public static function create(ContainerInterface $container) {
    $container->get('entity_type.manager'),
    $container->get('workspaces.manager'),
    $container->get('workspaces.association'),
    $container->get('workspaces.repository')
    );
    }
    ......@@ -83,22 +42,16 @@ public function validate($entity, Constraint $constraint) {
    if (isset($entity) && !$entity->isNew()) {
    $active_workspace = $this->workspaceManager->getActiveWorkspace();
    // Get the latest revision of the entity in order to check if it's being
    // edited in a different workspace.
    $latest_revision = $this->workspaceManager->executeOutsideWorkspace(function () use ($entity) {
    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
    $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
    return $storage->loadRevision($storage->getLatestRevisionId($entity->id()));
    });
    // If the latest revision of the entity is tracked in a workspace, it can
    // only be edited in that workspace or one of its descendants.
    if ($latest_revision_workspace = $latest_revision->workspace->entity) {
    $descendants_and_self = $this->workspaceRepository->getDescendantsAndSelf($latest_revision_workspace->id());
    // If the entity is tracked in a workspace, it can only be edited in
    // that workspace or one of its descendants.
    if ($tracking_workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity, TRUE)) {
    if (!$active_workspace || !in_array($active_workspace->id(), $tracking_workspace_ids, TRUE)) {
    $first_tracking_workspace_id = reset($tracking_workspace_ids);
    $workspace = $this->entityTypeManager->getStorage('workspace')
    ->load($first_tracking_workspace_id);
    if (!$active_workspace || !in_array($active_workspace->id(), $descendants_and_self, TRUE)) {
    $this->context->buildViolation($constraint->message)
    ->setParameter('%label', $latest_revision_workspace->label())
    ->setParameter('@label', $workspace->label())
    ->addViolation();
    }
    }
    ......
    ......@@ -304,13 +304,33 @@ public function getAssociatedInitialRevisions(string $workspace_id, string $enti
    /**
    * {@inheritdoc}
    */
    public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity) {
    $query = $this->database->select(static::TABLE)
    ->fields(static::TABLE, ['workspace'])
    ->condition('target_entity_type_id', $entity->getEntityTypeId())
    ->condition('target_entity_id', $entity->id());
    public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity, bool $latest_revision = FALSE) {
    $query = $this->database->select(static::TABLE, 'wa')
    ->fields('wa', ['workspace'])
    ->condition('[wa].[target_entity_type_id]', $entity->getEntityTypeId())
    ->condition('[wa].[target_entity_id]', $entity->id());
    // Use a self-join to get only the workspaces in which the latest revision
    // of the entity is tracked.
    if ($latest_revision) {
    $inner_select = $this->database->select(static::TABLE, 'wai')
    ->condition('[wai].[target_entity_type_id]', $entity->getEntityTypeId())
    ->condition('[wai].[target_entity_id]', $entity->id());
    $inner_select->addExpression('MAX([wai].[target_entity_revision_id])', 'max_revision_id');
    $query->join($inner_select, 'waj', '[wa].[target_entity_revision_id] = [waj].[max_revision_id]');
    }
    $result = $query->execute()->fetchCol();
    // Return early if the entity is not tracked in any workspace.
    if (empty($result)) {
    return [];
    }
    return $query->execute()->fetchCol();
    // Return workspace IDs sorted in tree order.
    $tree = $this->workspaceRepository->loadTree();
    return array_keys(array_intersect_key($tree, array_flip($result)));
    }
    /**
    ......
    ......@@ -96,12 +96,15 @@ public function getAssociatedInitialRevisions(string $workspace_id, string $enti
    *
    * @param \Drupal\Core\Entity\RevisionableInterface $entity
    * An entity object.
    * @param bool $latest_revision
    * (optional) Whether to return only the workspaces in which the latest
    * revision of the entity is tracked. Defaults to FALSE.
    *
    * @return string[]
    * An array of workspace IDs where the given entity is tracked, or an empty
    * array if it is not tracked anywhere.
    */
    public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity);
    public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity, bool $latest_revision = FALSE);
    /**
    * Triggers clean-up operations after publishing a workspace.
    ......
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\workspaces\Kernel;
    use Drupal\Core\Entity\EntityInterface;
    use Drupal\Core\Entity\EntityTypeManagerInterface;
    use Drupal\entity_test\Entity\EntityTestMulRevPub;
    use Drupal\KernelTests\KernelTestBase;
    use Drupal\Tests\user\Traits\UserCreationTrait;
    use Drupal\workspaces\Entity\Workspace;
    /**
    * @coversDefaultClass \Drupal\workspaces\Plugin\Validation\Constraint\EntityWorkspaceConflictConstraintValidator
    * @group workspaces
    */
    class EntityWorkspaceConflictConstraintValidatorTest extends KernelTestBase {
    use UserCreationTrait;
    use WorkspaceTestTrait;
    /**
    * {@inheritdoc}
    */
    protected static $modules = [
    'entity_test',
    'path_alias',
    'system',
    'user',
    'workspaces',
    ];
    /**
    * The entity type manager.
    */
    protected EntityTypeManagerInterface $entityTypeManager;
    /**
    * {@inheritdoc}
    */
    protected function setUp(): void {
    parent::setUp();
    $this->entityTypeManager = \Drupal::entityTypeManager();
    $this->installSchema('workspaces', ['workspace_association']);
    $this->installEntitySchema('entity_test_mulrevpub');
    $this->installEntitySchema('workspace');
    $this->installEntitySchema('user');
    $this->createUser();
    }
    /**
    * @covers ::validate
    */
    public function testNewEntitiesAllowedInDefaultWorkspace(): void {
    // Create two top-level workspaces and a second-level one.
    $stage = Workspace::create(['id' => 'stage', 'label' => 'Stage']);
    $stage->save();
    $dev = Workspace::create(['id' => 'dev', 'label' => 'Dev', 'parent' => 'stage']);
    $dev->save();
    $other = Workspace::create(['id' => 'other', 'label' => 'Other']);
    $other->save();
    // Create an entity in Live, and check that the validation is skipped.
    $entity = EntityTestMulRevPub::create();
    $this->assertCount(0, $entity->validate());
    $entity->save();
    $entity = $this->reloadEntity($entity);
    $this->assertCount(0, $entity->validate());
    // Edit the entity in Stage.
    $this->switchToWorkspace('stage');
    $entity->save();
    $entity = $this->reloadEntity($entity);
    $this->assertCount(0, $entity->validate());
    $expected_message = 'The content is being edited in the Stage workspace. As a result, your changes cannot be saved.';
    // Check that the entity can no longer be edited in Live.
    $this->switchToLive();
    $entity = $this->reloadEntity($entity);
    $violations = $entity->validate();
    $this->assertCount(1, $violations);
    $this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
    // Check that the entity can no longer be edited in another top-level
    // workspace.
    $this->switchToWorkspace('other');
    $entity = $this->reloadEntity($entity);
    $violations = $entity->validate();
    $this->assertCount(1, $violations);
    $this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
    // Check that the entity can still be edited in a sub-workspace of Stage.
    $this->switchToWorkspace('dev');
    $entity = $this->reloadEntity($entity);
    $this->assertCount(0, $entity->validate());
    // Edit the entity in Dev.
    $this->switchToWorkspace('dev');
    $entity->save();
    $entity = $this->reloadEntity($entity);
    $this->assertCount(0, $entity->validate());
    $expected_message = 'The content is being edited in the Dev workspace. As a result, your changes cannot be saved.';
    // Check that the entity can no longer be edited in Live.
    $this->switchToLive();
    $entity = $this->reloadEntity($entity);
    $violations = $entity->validate();
    $this->assertCount(1, $violations);
    $this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
    // Check that the entity can no longer be edited in the parent workspace.
    $this->switchToWorkspace('stage');
    $entity = $this->reloadEntity($entity);
    $violations = $entity->validate();
    $this->assertCount(1, $violations);
    $this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
    // Check that the entity can no longer be edited in another top-level
    // workspace.
    $this->switchToWorkspace('other');
    $entity = $this->reloadEntity($entity);
    $violations = $entity->validate();
    $this->assertCount(1, $violations);
    $this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
    }
    /**
    * Reloads the given entity from the storage and returns it.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
    * The entity to be reloaded.
    *
    * @return \Drupal\Core\Entity\EntityInterface
    * The reloaded entity.
    */
    protected function reloadEntity(EntityInterface $entity): EntityInterface {
    $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
    $storage->resetCache([$entity->id()]);
    return $storage->load($entity->id());
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment