From 1fd6c15aef8776764a71c63c9d8a18dfe7378171 Mon Sep 17 00:00:00 2001 From: catch Date: Mon, 4 Mar 2024 10:47:09 +0000 Subject: [PATCH] Issue #3101671 by amateescu, webdrips, smithmilner, s_leu, smustgrave: Add mechanism to have workspaces skip processing entity types (cherry picked from commit 940a069060b8bf7006825d114971db831968d1ba) --- .../Handler/BlockContentWorkspaceHandler.php | 24 +++ .../Handler/DefaultWorkspaceHandler.php | 31 +++ .../Handler/IgnoredWorkspaceHandler.php | 31 +++ .../Handler/WorkspaceHandlerInterface.php | 30 +++ .../workspaces/src/Entity/Workspace.php | 1 + core/modules/workspaces/src/EntityAccess.php | 19 +- .../workspaces/src/EntityOperations.php | 88 +++++---- .../src/EntityQuery/PgsqlQueryFactory.php | 47 +---- .../src/EntityQuery/QueryFactory.php | 32 +--- .../workspaces/src/EntityQuery/QueryTrait.php | 17 +- .../workspaces/src/EntityQuery/Tables.php | 10 +- .../modules/workspaces/src/EntityTypeInfo.php | 74 ++++---- .../EntitySchemaSubscriber.php | 22 +-- ...upportedNewEntitiesConstraintValidator.php | 16 +- .../workspaces/src/ViewsQueryAlter.php | 17 +- .../workspaces/src/WorkspaceInformation.php | 111 +++++++++++ .../src/WorkspaceInformationInterface.php | 73 +++++++ .../workspaces/src/WorkspaceManager.php | 63 ++++--- .../src/WorkspaceManagerInterface.php | 15 ++ .../src/EntityTestRevPubWorkspaceHandler.php | 20 ++ .../workspaces_test/workspaces_test.info.yml | 7 + .../src/Kernel/WorkspaceInformationTest.php | 178 ++++++++++++++++++ .../tests/src/Kernel/WorkspaceTestTrait.php | 2 +- core/modules/workspaces/workspaces.install | 6 +- core/modules/workspaces/workspaces.module | 4 +- .../workspaces/workspaces.services.yml | 12 +- 26 files changed, 736 insertions(+), 214 deletions(-) create mode 100644 core/modules/workspaces/src/Entity/Handler/BlockContentWorkspaceHandler.php create mode 100644 core/modules/workspaces/src/Entity/Handler/DefaultWorkspaceHandler.php create mode 100644 core/modules/workspaces/src/Entity/Handler/IgnoredWorkspaceHandler.php create mode 100644 core/modules/workspaces/src/Entity/Handler/WorkspaceHandlerInterface.php create mode 100644 core/modules/workspaces/src/WorkspaceInformation.php create mode 100644 core/modules/workspaces/src/WorkspaceInformationInterface.php create mode 100644 core/modules/workspaces/tests/modules/workspaces_test/src/EntityTestRevPubWorkspaceHandler.php create mode 100644 core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml create mode 100644 core/modules/workspaces/tests/src/Kernel/WorkspaceInformationTest.php diff --git a/core/modules/workspaces/src/Entity/Handler/BlockContentWorkspaceHandler.php b/core/modules/workspaces/src/Entity/Handler/BlockContentWorkspaceHandler.php new file mode 100644 index 00000000000..4e164cc599a --- /dev/null +++ b/core/modules/workspaces/src/Entity/Handler/BlockContentWorkspaceHandler.php @@ -0,0 +1,24 @@ +isReusable(); + } + +} diff --git a/core/modules/workspaces/src/Entity/Handler/DefaultWorkspaceHandler.php b/core/modules/workspaces/src/Entity/Handler/DefaultWorkspaceHandler.php new file mode 100644 index 00000000000..0016729e7de --- /dev/null +++ b/core/modules/workspaces/src/Entity/Handler/DefaultWorkspaceHandler.php @@ -0,0 +1,31 @@ +entityTypeManager = $entity_type_manager; $this->workspaceManager = $workspace_manager; + $this->workspaceInfo = $workspace_information; } /** @@ -52,7 +62,8 @@ class EntityAccess implements ContainerInjectionInterface { public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), - $container->get('workspaces.manager') + $container->get('workspaces.manager'), + $container->get('workspaces.information') ); } @@ -75,7 +86,7 @@ class EntityAccess implements ContainerInjectionInterface { // Workspaces themselves are handled by their own access handler and we // should not try to do any access checks for entity types that can not // belong to a workspace. - if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity->getEntityType()) || !$this->workspaceManager->hasActiveWorkspace()) { + if (!$this->workspaceInfo->isEntitySupported($entity) || !$this->workspaceManager->hasActiveWorkspace()) { return AccessResult::neutral(); } @@ -102,7 +113,7 @@ class EntityAccess implements ContainerInjectionInterface { // should not try to do any access checks for entity types that can not // belong to a workspace. $entity_type = $this->entityTypeManager->getDefinition($context['entity_type_id']); - if ($entity_type->id() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity_type) || !$this->workspaceManager->hasActiveWorkspace()) { + if (!$this->workspaceInfo->isEntityTypeSupported($entity_type) || !$this->workspaceManager->hasActiveWorkspace()) { return AccessResult::neutral(); } diff --git a/core/modules/workspaces/src/EntityOperations.php b/core/modules/workspaces/src/EntityOperations.php index 128f6915e90..325a6466806 100644 --- a/core/modules/workspaces/src/EntityOperations.php +++ b/core/modules/workspaces/src/EntityOperations.php @@ -4,7 +4,6 @@ namespace Drupal\workspaces; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\RevisionableInterface; use Drupal\Core\Form\FormStateInterface; @@ -42,6 +41,13 @@ class EntityOperations implements ContainerInjectionInterface { */ protected $workspaceAssociation; + /** + * The workspace information service. + * + * @var \Drupal\workspaces\WorkspaceInformationInterface + */ + protected $workspaceInfo; + /** * Constructs a new EntityOperations instance. * @@ -51,11 +57,14 @@ class EntityOperations implements ContainerInjectionInterface { * The workspace manager service. * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association * The workspace association service. + * @param \Drupal\workspaces\WorkspaceInformationInterface $workspace_information + * The workspace information service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, WorkspaceInformationInterface $workspace_information) { $this->entityTypeManager = $entity_type_manager; $this->workspaceManager = $workspace_manager; $this->workspaceAssociation = $workspace_association; + $this->workspaceInfo = $workspace_information; } /** @@ -65,7 +74,8 @@ class EntityOperations implements ContainerInjectionInterface { return new static( $container->get('entity_type.manager'), $container->get('workspaces.manager'), - $container->get('workspaces.association') + $container->get('workspaces.association'), + $container->get('workspaces.information') ); } @@ -77,9 +87,8 @@ class EntityOperations implements ContainerInjectionInterface { public function entityPreload(array $ids, $entity_type_id) { $entities = []; - // Only run if the entity type can belong to a workspace and we are in a - // non-default workspace. - if (!$this->workspaceManager->shouldAlterOperations($this->entityTypeManager->getDefinition($entity_type_id))) { + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + if (!$this->workspaceInfo->isEntityTypeSupported($entity_type) || !$this->workspaceManager->hasActiveWorkspace()) { return $entities; } @@ -87,6 +96,11 @@ class EntityOperations implements ContainerInjectionInterface { // current active workspace. If an entity has multiple revisions set for a // workspace, only the one with the highest ID is returned. if ($tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->workspaceManager->getActiveWorkspace()->id(), $entity_type_id, $ids)) { + // Bail out early if there are no tracked entities of this type. + if (!isset($tracked_entities[$entity_type_id])) { + return $entities; + } + /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */ $storage = $this->entityTypeManager->getStorage($entity_type_id); @@ -109,18 +123,13 @@ class EntityOperations implements ContainerInjectionInterface { * @see hook_entity_presave() */ public function entityPresave(EntityInterface $entity) { - $entity_type = $entity->getEntityType(); - - // Only run if we are not dealing with an entity type provided by the - // Workspaces module, an internal entity type or if we are in a non-default - // workspace. - if ($this->shouldSkipPreOperations($entity_type)) { + if ($this->shouldSkipOperations($entity)) { return; } // Disallow any change to an unsupported entity when we are not in the // default workspace. - if (!$this->workspaceManager->isEntityTypeSupported($entity_type)) { + if (!$this->workspaceInfo->isEntitySupported($entity)) { throw new \RuntimeException('This entity can only be saved in the default workspace.'); } @@ -140,7 +149,7 @@ class EntityOperations implements ContainerInjectionInterface { // Track the workspaces in which the new revision was saved. if (!$entity->isSyncing()) { - $field_name = $entity_type->getRevisionMetadataKey('workspace'); + $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace'); $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id(); } @@ -167,10 +176,7 @@ class EntityOperations implements ContainerInjectionInterface { * @see hook_entity_insert() */ public function entityInsert(EntityInterface $entity) { - /** @var \Drupal\Core\Entity\RevisionableInterface|\Drupal\Core\Entity\EntityPublishedInterface $entity */ - // Only run if the entity type can belong to a workspace and we are in a - // non-default workspace. - if (!$this->workspaceManager->shouldAlterOperations($entity->getEntityType())) { + if ($this->shouldSkipOperations($entity) || !$this->workspaceInfo->isEntitySupported($entity)) { return; } @@ -202,9 +208,7 @@ class EntityOperations implements ContainerInjectionInterface { * @see hook_entity_update() */ public function entityUpdate(EntityInterface $entity) { - // Only run if the entity type can belong to a workspace and we are in a - // non-default workspace. - if (!$this->workspaceManager->shouldAlterOperations($entity->getEntityType())) { + if ($this->shouldSkipOperations($entity) || !$this->workspaceInfo->isEntitySupported($entity)) { return; } @@ -224,18 +228,13 @@ class EntityOperations implements ContainerInjectionInterface { * @see hook_entity_predelete() */ public function entityPredelete(EntityInterface $entity) { - $entity_type = $entity->getEntityType(); - - // Only run if we are not dealing with an entity type provided by the - // Workspaces module, an internal entity type or if we are in a non-default - // workspace. - if ($this->shouldSkipPreOperations($entity_type)) { + if ($this->shouldSkipOperations($entity)) { return; } // Disallow any change to an unsupported entity when we are not in the // default workspace. - if (!$this->workspaceManager->isEntityTypeSupported($entity_type)) { + if (!$this->workspaceInfo->isEntitySupported($entity)) { throw new \RuntimeException('This entity can only be deleted in the default workspace.'); } } @@ -253,17 +252,21 @@ class EntityOperations implements ContainerInjectionInterface { * @see hook_form_alter() */ public function entityFormAlter(array &$form, FormStateInterface $form_state, $form_id) { - /** @var \Drupal\Core\Entity\RevisionableInterface $entity */ $entity = $form_state->getFormObject()->getEntity(); - if (!$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) { + if (!$this->workspaceInfo->isEntitySupported($entity) && !$this->workspaceInfo->isEntityIgnored($entity)) { return; } - // For supported entity types, signal the fact that this form is safe to use - // in a non-default workspace. + // For supported and ignored entity types, signal the fact that this form is + // safe to use in a workspace. // @see \Drupal\workspaces\FormOperations::validateForm() $form_state->set('workspace_safe', TRUE); + // There is nothing more to do for ignored entity types. + if ($this->workspaceInfo->isEntityIgnored($entity)) { + return; + } + // Add an entity builder to the form which marks the edited entity object as // a pending revision. This is needed so validation constraints like // \Drupal\path\Plugin\Validation\Constraint\PathAliasConstraintValidator @@ -295,23 +298,18 @@ class EntityOperations implements ContainerInjectionInterface { } /** - * Determines whether we need to react on pre-save or pre-delete operations. + * Determines whether we need to react on entity operations. * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The entity type to check. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to check. * * @return bool - * Returns TRUE if the pre-save or pre-delete entity operations should not - * be altered in the current request, FALSE otherwise. + * Returns TRUE if entity operations should not be altered, FALSE otherwise. */ - protected function shouldSkipPreOperations(EntityTypeInterface $entity_type) { - // We should not react on pre-save and pre-delete entity operations if one - // of the following conditions are met: - // - the entity type is provided by the Workspaces module; - // - the entity type is internal, which means that it should not affect - // anything in the default (Live) workspace; - // - we are in the default workspace. - return $entity_type->getProvider() === 'workspaces' || $entity_type->isInternal() || !$this->workspaceManager->hasActiveWorkspace(); + protected function shouldSkipOperations(EntityInterface $entity) { + // We should not react on entity operations when the entity is ignored or + // when we're not in a workspace context. + return $this->workspaceInfo->isEntityIgnored($entity) || !$this->workspaceManager->hasActiveWorkspace(); } } diff --git a/core/modules/workspaces/src/EntityQuery/PgsqlQueryFactory.php b/core/modules/workspaces/src/EntityQuery/PgsqlQueryFactory.php index 5b15656538e..f0597ee9241 100644 --- a/core/modules/workspaces/src/EntityQuery/PgsqlQueryFactory.php +++ b/core/modules/workspaces/src/EntityQuery/PgsqlQueryFactory.php @@ -2,52 +2,11 @@ namespace Drupal\workspaces\EntityQuery; -use Drupal\Core\Database\Connection; -use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\Query\QueryBase; -use Drupal\Core\Entity\Query\Sql\pgsql\QueryFactory as BaseQueryFactory; -use Drupal\workspaces\WorkspaceManagerInterface; - /** * Workspaces PostgreSQL-specific entity query implementation. + * + * @internal */ -class PgsqlQueryFactory extends BaseQueryFactory { - - /** - * The workspace manager. - * - * @var \Drupal\workspaces\WorkspaceManagerInterface - */ - protected $workspaceManager; - - /** - * Constructs a PgsqlQueryFactory object. - * - * @param \Drupal\Core\Database\Connection $connection - * The database connection used by the entity query. - * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager - * The workspace manager. - */ - public function __construct(Connection $connection, WorkspaceManagerInterface $workspace_manager) { - $this->connection = $connection; - $this->workspaceManager = $workspace_manager; - $this->namespaces = QueryBase::getNamespaces($this); - } - - /** - * {@inheritdoc} - */ - public function get(EntityTypeInterface $entity_type, $conjunction) { - $class = QueryBase::getClass($this->namespaces, 'Query'); - return new $class($entity_type, $conjunction, $this->connection, $this->namespaces, $this->workspaceManager); - } - - /** - * {@inheritdoc} - */ - public function getAggregate(EntityTypeInterface $entity_type, $conjunction) { - $class = QueryBase::getClass($this->namespaces, 'QueryAggregate'); - return new $class($entity_type, $conjunction, $this->connection, $this->namespaces, $this->workspaceManager); - } +class PgsqlQueryFactory extends QueryFactory { } diff --git a/core/modules/workspaces/src/EntityQuery/QueryFactory.php b/core/modules/workspaces/src/EntityQuery/QueryFactory.php index 7aff90da9b6..0594fbc79ac 100644 --- a/core/modules/workspaces/src/EntityQuery/QueryFactory.php +++ b/core/modules/workspaces/src/EntityQuery/QueryFactory.php @@ -6,34 +6,22 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\Query\QueryBase; use Drupal\Core\Entity\Query\Sql\QueryFactory as BaseQueryFactory; +use Drupal\workspaces\WorkspaceInformationInterface; use Drupal\workspaces\WorkspaceManagerInterface; /** * Workspaces-specific entity query implementation. + * + * @internal */ class QueryFactory extends BaseQueryFactory { - /** - * The workspace manager. - * - * @var \Drupal\workspaces\WorkspaceManagerInterface - */ - protected $workspaceManager; - - /** - * Constructs a QueryFactory object. - * - * Initializes the list of namespaces used to locate query - * classes for different entity types. - * - * @param \Drupal\Core\Database\Connection $connection - * The database connection used by the entity query. - * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager - * The workspace manager. - */ - public function __construct(Connection $connection, WorkspaceManagerInterface $workspace_manager) { + public function __construct( + Connection $connection, + protected readonly WorkspaceManagerInterface $workspaceManager, + protected readonly WorkspaceInformationInterface $workspaceInfo + ) { $this->connection = $connection; - $this->workspaceManager = $workspace_manager; $this->namespaces = QueryBase::getNamespaces($this); } @@ -42,7 +30,7 @@ class QueryFactory extends BaseQueryFactory { */ public function get(EntityTypeInterface $entity_type, $conjunction) { $class = QueryBase::getClass($this->namespaces, 'Query'); - return new $class($entity_type, $conjunction, $this->connection, $this->namespaces, $this->workspaceManager); + return new $class($entity_type, $conjunction, $this->connection, $this->namespaces, $this->workspaceManager, $this->workspaceInfo); } /** @@ -50,7 +38,7 @@ class QueryFactory extends BaseQueryFactory { */ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) { $class = QueryBase::getClass($this->namespaces, 'QueryAggregate'); - return new $class($entity_type, $conjunction, $this->connection, $this->namespaces, $this->workspaceManager); + return new $class($entity_type, $conjunction, $this->connection, $this->namespaces, $this->workspaceManager, $this->workspaceInfo); } } diff --git a/core/modules/workspaces/src/EntityQuery/QueryTrait.php b/core/modules/workspaces/src/EntityQuery/QueryTrait.php index 2630d9e9ee8..415ef4b7a08 100644 --- a/core/modules/workspaces/src/EntityQuery/QueryTrait.php +++ b/core/modules/workspaces/src/EntityQuery/QueryTrait.php @@ -4,10 +4,13 @@ namespace Drupal\workspaces\EntityQuery; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\workspaces\WorkspaceInformationInterface; use Drupal\workspaces\WorkspaceManagerInterface; /** * Provides workspaces-specific helpers for altering entity queries. + * + * @internal */ trait QueryTrait { @@ -18,6 +21,13 @@ trait QueryTrait { */ protected $workspaceManager; + /** + * The workspace information service. + * + * @var \Drupal\workspaces\WorkspaceInformationInterface + */ + protected $workspaceInfo; + /** * Constructs a Query object. * @@ -32,11 +42,14 @@ trait QueryTrait { * List of potential namespaces of the classes belonging to this query. * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager * The workspace manager. + * @param \Drupal\workspaces\WorkspaceInformationInterface $workspace_information + * The workspace information service. */ - public function __construct(EntityTypeInterface $entity_type, $conjunction, Connection $connection, array $namespaces, WorkspaceManagerInterface $workspace_manager) { + public function __construct(EntityTypeInterface $entity_type, $conjunction, Connection $connection, array $namespaces, WorkspaceManagerInterface $workspace_manager, WorkspaceInformationInterface $workspace_information) { parent::__construct($entity_type, $conjunction, $connection, $namespaces); $this->workspaceManager = $workspace_manager; + $this->workspaceInfo = $workspace_information; } /** @@ -54,7 +67,7 @@ trait QueryTrait { // Only alter the query if the active workspace is not the default one and // the entity type is supported. - if ($this->workspaceManager->isEntityTypeSupported($this->entityType) && $this->workspaceManager->hasActiveWorkspace()) { + if ($this->workspaceInfo->isEntityTypeSupported($this->entityType) && $this->workspaceManager->hasActiveWorkspace()) { $active_workspace = $this->workspaceManager->getActiveWorkspace(); $this->sqlQuery->addMetaData('active_workspace_id', $active_workspace->id()); $this->sqlQuery->addMetaData('simple_query', FALSE); diff --git a/core/modules/workspaces/src/EntityQuery/Tables.php b/core/modules/workspaces/src/EntityQuery/Tables.php index 66600211b2a..e67e107bfbb 100644 --- a/core/modules/workspaces/src/EntityQuery/Tables.php +++ b/core/modules/workspaces/src/EntityQuery/Tables.php @@ -13,11 +13,11 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; class Tables extends BaseTables { /** - * The workspace manager. + * The workspace information service. * - * @var \Drupal\workspaces\WorkspaceManagerInterface + * @var \Drupal\workspaces\WorkspaceInformationInterface */ - protected $workspaceManager; + protected $workspaceInfo; /** * Workspace association table array, key is base table name, value is alias. @@ -42,7 +42,7 @@ class Tables extends BaseTables { public function __construct(SelectInterface $sql_query) { parent::__construct($sql_query); - $this->workspaceManager = \Drupal::service('workspaces.manager'); + $this->workspaceInfo = \Drupal::service('workspaces.information'); // The join between the first 'workspace_association' table and base table // of the query is done in @@ -117,7 +117,7 @@ class Tables extends BaseTables { $next_base_table_alias = parent::addNextBaseTable($entity_type, $table, $sql_column, $field_storage); $active_workspace_id = $this->sqlQuery->getMetaData('active_workspace_id'); - if ($active_workspace_id && $this->workspaceManager->isEntityTypeSupported($entity_type)) { + if ($active_workspace_id && $this->workspaceInfo->isEntityTypeSupported($entity_type)) { $this->addWorkspaceAssociationJoin($entity_type->id(), $next_base_table_alias, $active_workspace_id); } diff --git a/core/modules/workspaces/src/EntityTypeInfo.php b/core/modules/workspaces/src/EntityTypeInfo.php index bf9d82fedfa..55083bdc590 100644 --- a/core/modules/workspaces/src/EntityTypeInfo.php +++ b/core/modules/workspaces/src/EntityTypeInfo.php @@ -3,10 +3,13 @@ namespace Drupal\workspaces; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\workspaces\Entity\Handler\BlockContentWorkspaceHandler; +use Drupal\workspaces\Entity\Handler\DefaultWorkspaceHandler; +use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -19,31 +22,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class EntityTypeInfo implements ContainerInjectionInterface { - /** - * The entity type manager service. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * The workspace manager service. - * - * @var \Drupal\workspaces\WorkspaceManagerInterface - */ - protected $workspaceManager; - - /** - * Constructs a new EntityTypeInfo instance. - * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager service. - * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager - * The workspace manager service. - */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) { - $this->entityTypeManager = $entity_type_manager; - $this->workspaceManager = $workspace_manager; + public function __construct( + protected readonly WorkspaceInformationInterface $workspaceInfo + ) { } /** @@ -51,13 +32,12 @@ class EntityTypeInfo implements ContainerInjectionInterface { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity_type.manager'), - $container->get('workspaces.manager') + $container->get('workspaces.information') ); } /** - * Adds the "EntityWorkspaceConflict" constraint to eligible entity types. + * Adds workspace support info to eligible entity types. * * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types * An associative array of all entity type definitions, keyed by the entity @@ -67,15 +47,31 @@ class EntityTypeInfo implements ContainerInjectionInterface { */ public function entityTypeBuild(array &$entity_types) { foreach ($entity_types as $entity_type) { - if ($this->workspaceManager->isEntityTypeSupported($entity_type)) { - $entity_type->addConstraint('EntityWorkspaceConflict'); - $entity_type->setRevisionMetadataKey('workspace', 'workspace'); + if ($entity_type->hasHandlerClass('workspace')) { + continue; + } + + // Revisionable and publishable entity types are always supported. + if ($entity_type->entityClassImplements(EntityPublishedInterface::class) && $entity_type->isRevisionable()) { + $entity_type->setHandlerClass('workspace', DefaultWorkspaceHandler::class); + + // Support for custom blocks has to be determined on a per-entity + // basis. + if ($entity_type->id() === 'block_content') { + $entity_type->setHandlerClass('workspace', BlockContentWorkspaceHandler::class); + } + } + + // Internal entity types are allowed to perform CRUD operations inside a + // workspace. + if ($entity_type->isInternal()) { + $entity_type->setHandlerClass('workspace', IgnoredWorkspaceHandler::class); } } } /** - * Removes the 'latest-version' link template provided by Content Moderation. + * Adds Workspace configuration to appropriate entity types. * * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types * An array of entity types. @@ -84,6 +80,16 @@ class EntityTypeInfo implements ContainerInjectionInterface { */ public function entityTypeAlter(array &$entity_types) { foreach ($entity_types as $entity_type) { + if (!$this->workspaceInfo->isEntityTypeSupported($entity_type)) { + continue; + } + + // Workspace-support status has been declared in the "build" phase, now we + // can use that information and add additional configuration in the + // "alter" phase. + $entity_type->addConstraint('EntityWorkspaceConflict'); + $entity_type->setRevisionMetadataKey('workspace', 'workspace'); + // Non-default workspaces display the active revision on the canonical // route of an entity, so the latest version route is no longer needed. $link_templates = $entity_type->get('links'); @@ -123,7 +129,7 @@ class EntityTypeInfo implements ContainerInjectionInterface { * @see hook_entity_base_field_info() */ public function entityBaseFieldInfo(EntityTypeInterface $entity_type) { - if ($this->workspaceManager->isEntityTypeSupported($entity_type)) { + if ($this->workspaceInfo->isEntityTypeSupported($entity_type)) { $field_name = $entity_type->getRevisionMetadataKey('workspace'); $fields[$field_name] = BaseFieldDefinition::create('entity_reference') ->setLabel(new TranslatableMarkup('Workspace')) diff --git a/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php index 5882d49b776..a7e32b1c147 100644 --- a/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php +++ b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php @@ -9,7 +9,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeListenerInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\workspaces\WorkspaceManagerInterface; +use Drupal\workspaces\WorkspaceInformationInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -35,11 +35,11 @@ class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscr protected $entityLastInstalledSchemaRepository; /** - * The workspace manager. + * The workspace information service. * - * @var \Drupal\workspaces\WorkspaceManagerInterface + * @var \Drupal\workspaces\WorkspaceInformationInterface */ - protected $workspaceManager; + protected $workspaceInfo; /** * Constructs a new EntitySchemaSubscriber. @@ -48,13 +48,13 @@ class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscr * Definition update manager. * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository * Last definitions. - * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager - * The workspace manager. + * @param \Drupal\workspaces\WorkspaceInformationInterface $workspace_information + * The workspace information service. */ - public function __construct(EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager, EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository, WorkspaceManagerInterface $workspace_manager) { + public function __construct(EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager, EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository, WorkspaceInformationInterface $workspace_information) { $this->entityDefinitionUpdateManager = $entityDefinitionUpdateManager; $this->entityLastInstalledSchemaRepository = $entityLastInstalledSchemaRepository; - $this->workspaceManager = $workspace_manager; + $this->workspaceInfo = $workspace_information; } /** @@ -70,7 +70,7 @@ class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscr public function onEntityTypeCreate(EntityTypeInterface $entity_type) { // If the entity type is supported by Workspaces, add the revision metadata // field. - if ($this->workspaceManager->isEntityTypeSupported($entity_type)) { + if ($this->workspaceInfo->isEntityTypeSupported($entity_type)) { $this->addRevisionMetadataField($entity_type); } } @@ -88,13 +88,13 @@ class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscr public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { // If the entity type is now supported by Workspaces, add the revision // metadata field. - if ($this->workspaceManager->isEntityTypeSupported($entity_type) && !$this->workspaceManager->isEntityTypeSupported($original)) { + if ($this->workspaceInfo->isEntityTypeSupported($entity_type) && !$this->workspaceInfo->isEntityTypeSupported($original)) { $this->addRevisionMetadataField($entity_type); } // If the entity type is no longer supported by Workspaces, remove the // revision metadata field. - if ($this->workspaceManager->isEntityTypeSupported($original) && !$this->workspaceManager->isEntityTypeSupported($entity_type)) { + if ($this->workspaceInfo->isEntityTypeSupported($original) && !$this->workspaceInfo->isEntityTypeSupported($entity_type)) { $revision_metadata_keys = $original->get('revision_metadata_keys'); $field_storage_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type->id())[$revision_metadata_keys['workspace']]; $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($field_storage_definition); diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php index feb7e348135..551c4c3836b 100644 --- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php +++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php @@ -4,6 +4,7 @@ namespace Drupal\workspaces\Plugin\Validation\Constraint; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\workspaces\WorkspaceInformationInterface; use Drupal\workspaces\WorkspaceManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; @@ -28,12 +29,20 @@ class EntityReferenceSupportedNewEntitiesConstraintValidator extends ConstraintV */ protected $entityTypeManager; + /** + * The workspace information service. + * + * @var \Drupal\workspaces\WorkspaceInformationInterface + */ + protected $workspaceInfo; + /** * Creates a new EntityReferenceSupportedNewEntitiesConstraintValidator instance. */ - public function __construct(WorkspaceManagerInterface $workspaceManager, EntityTypeManagerInterface $entityTypeManager) { + public function __construct(WorkspaceManagerInterface $workspaceManager, EntityTypeManagerInterface $entityTypeManager, WorkspaceInformationInterface $workspace_information) { $this->workspaceManager = $workspaceManager; $this->entityTypeManager = $entityTypeManager; + $this->workspaceInfo = $workspace_information; } /** @@ -42,7 +51,8 @@ class EntityReferenceSupportedNewEntitiesConstraintValidator extends ConstraintV public static function create(ContainerInterface $container) { return new static( $container->get('workspaces.manager'), - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('workspaces.information') ); } @@ -58,7 +68,7 @@ class EntityReferenceSupportedNewEntitiesConstraintValidator extends ConstraintV $target_entity_type_id = $value->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type'); $target_entity_type = $this->entityTypeManager->getDefinition($target_entity_type_id); - if ($value->hasNewEntity() && !$this->workspaceManager->isEntityTypeSupported($target_entity_type)) { + if ($value->hasNewEntity() && !$this->workspaceInfo->isEntityTypeSupported($target_entity_type)) { $this->context->addViolation($constraint->message, ['%collection_label' => $target_entity_type->getCollectionLabel()]); } } diff --git a/core/modules/workspaces/src/ViewsQueryAlter.php b/core/modules/workspaces/src/ViewsQueryAlter.php index 6077b921470..f5fff263165 100644 --- a/core/modules/workspaces/src/ViewsQueryAlter.php +++ b/core/modules/workspaces/src/ViewsQueryAlter.php @@ -63,6 +63,13 @@ class ViewsQueryAlter implements ContainerInjectionInterface { */ protected $languageManager; + /** + * The workspace information service. + * + * @var \Drupal\workspaces\WorkspaceInformationInterface + */ + protected WorkspaceInformationInterface $workspaceInfo; + /** * An array of tables adjusted for workspace_association join. * @@ -85,14 +92,17 @@ class ViewsQueryAlter implements ContainerInjectionInterface { * The views join plugin manager. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\workspaces\WorkspaceInformationInterface $workspace_information + * The workspace information service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, WorkspaceManagerInterface $workspace_manager, ViewsData $views_data, ViewsHandlerManager $views_join_plugin_manager, LanguageManagerInterface $language_manager) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, WorkspaceManagerInterface $workspace_manager, ViewsData $views_data, ViewsHandlerManager $views_join_plugin_manager, LanguageManagerInterface $language_manager, WorkspaceInformationInterface $workspace_information) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->workspaceManager = $workspace_manager; $this->viewsData = $views_data; $this->viewsJoinPluginManager = $views_join_plugin_manager; $this->languageManager = $language_manager; + $this->workspaceInfo = $workspace_information; $this->adjustedTables = new \WeakMap(); } @@ -106,7 +116,8 @@ class ViewsQueryAlter implements ContainerInjectionInterface { $container->get('workspaces.manager'), $container->get('views.views_data'), $container->get('plugin.manager.views.join'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('workspaces.information') ); } @@ -140,7 +151,7 @@ class ViewsQueryAlter implements ContainerInjectionInterface { $entity_type_definitions = $this->entityTypeManager->getDefinitions(); foreach ($entity_type_ids as $entity_type_id) { - if ($this->workspaceManager->isEntityTypeSupported($entity_type_definitions[$entity_type_id])) { + if ($this->workspaceInfo->isEntityTypeSupported($entity_type_definitions[$entity_type_id])) { $this->alterQueryForEntityType($query, $entity_type_definitions[$entity_type_id]); } } diff --git a/core/modules/workspaces/src/WorkspaceInformation.php b/core/modules/workspaces/src/WorkspaceInformation.php new file mode 100644 index 00000000000..b590d7ce729 --- /dev/null +++ b/core/modules/workspaces/src/WorkspaceInformation.php @@ -0,0 +1,111 @@ +getEntityType(); + + if (!$this->isEntityTypeSupported($entity_type)) { + return FALSE; + } + + $handler = $this->entityTypeManager->getHandler($entity_type->id(), 'workspace'); + return $handler->isEntitySupported($entity); + } + + /** + * {@inheritdoc} + */ + public function isEntityTypeSupported(EntityTypeInterface $entity_type): bool { + if (!isset($this->supported[$entity_type->id()])) { + if ($entity_type->hasHandlerClass('workspace')) { + $supported = !is_a($entity_type->getHandlerClass('workspace'), IgnoredWorkspaceHandler::class, TRUE); + } + else { + // Fallback for cases when entity type info hasn't been altered yet, for + // example when the Workspaces module is being installed. + $supported = $entity_type->entityClassImplements(EntityPublishedInterface::class) && $entity_type->isRevisionable(); + } + + $this->supported[$entity_type->id()] = $supported; + } + + return $this->supported[$entity_type->id()]; + } + + /** + * {@inheritdoc} + */ + public function getSupportedEntityTypes(): array { + $entity_types = []; + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + if ($this->isEntityTypeSupported($entity_type)) { + $entity_types[$entity_type_id] = $entity_type; + } + } + return $entity_types; + } + + /** + * {@inheritdoc} + */ + public function isEntityIgnored(EntityInterface $entity): bool { + $entity_type = $entity->getEntityType(); + + if ($this->isEntityTypeIgnored($entity_type)) { + return TRUE; + } + + if ($entity_type->hasHandlerClass('workspace')) { + $handler = $this->entityTypeManager->getHandler($entity_type->id(), 'workspace'); + return !$handler->isEntitySupported($entity); + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isEntityTypeIgnored(EntityTypeInterface $entity_type): bool { + if (!isset($this->ignored[$entity_type->id()])) { + $this->ignored[$entity_type->id()] = $entity_type->hasHandlerClass('workspace') + && is_a($entity_type->getHandlerClass('workspace'), IgnoredWorkspaceHandler::class, TRUE); + } + + return $this->ignored[$entity_type->id()]; + } + +} diff --git a/core/modules/workspaces/src/WorkspaceInformationInterface.php b/core/modules/workspaces/src/WorkspaceInformationInterface.php new file mode 100644 index 00000000000..af32910ba5b --- /dev/null +++ b/core/modules/workspaces/src/WorkspaceInformationInterface.php @@ -0,0 +1,73 @@ + FALSE, - ]; - /** * The request stack. * @@ -86,6 +76,13 @@ class WorkspaceManager implements WorkspaceManagerInterface { */ protected $workspaceAssociation; + /** + * The workspace information service. + * + * @var \Drupal\workspaces\WorkspaceInformationInterface + */ + protected WorkspaceInformationInterface $workspaceInfo; + /** * The workspace negotiator service IDs. * @@ -119,10 +116,12 @@ class WorkspaceManager implements WorkspaceManagerInterface { * The class resolver. * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association * The workspace association service. - * @param array $negotiator_ids + * @param \Drupal\workspaces\WorkspaceInformationInterface|null $workspace_information + * The workspace information service. + * @param array|null $negotiator_ids * The workspace negotiator service IDs. */ - public function __construct(RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, MemoryCacheInterface $entity_memory_cache, AccountProxyInterface $current_user, StateInterface $state, LoggerInterface $logger, ClassResolverInterface $class_resolver, WorkspaceAssociationInterface $workspace_association, array $negotiator_ids) { + public function __construct(RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, MemoryCacheInterface $entity_memory_cache, AccountProxyInterface $current_user, StateInterface $state, LoggerInterface $logger, ClassResolverInterface $class_resolver, WorkspaceAssociationInterface $workspace_association, protected ?WorkspaceInformationInterface $workspace_information = NULL, array $negotiator_ids = NULL) { $this->requestStack = $request_stack; $this->entityTypeManager = $entity_type_manager; $this->entityMemoryCache = $entity_memory_cache; @@ -131,33 +130,34 @@ class WorkspaceManager implements WorkspaceManagerInterface { $this->logger = $logger; $this->classResolver = $class_resolver; $this->workspaceAssociation = $workspace_association; - $this->negotiatorIds = $negotiator_ids; + + if (!$workspace_information instanceof WorkspaceInformationInterface) { + @trigger_error('Calling ' . __METHOD__ . '() without the $workspace_information argument is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3324297', E_USER_DEPRECATED); + $this->workspaceInfo = \Drupal::service('workspaces.information'); + + // The negotiator IDs are always the last constructor argument. + $this->negotiatorIds = $workspace_information; + } + else { + $this->workspaceInfo = $workspace_information; + $this->negotiatorIds = $negotiator_ids; + } } /** * {@inheritdoc} */ public function isEntityTypeSupported(EntityTypeInterface $entity_type) { - $entity_type_id = $entity_type->id(); - if (!isset($this->supported[$entity_type_id])) { - // Only entity types which are revisionable and publishable can belong - // to a workspace. - $this->supported[$entity_type_id] = $entity_type->entityClassImplements(EntityPublishedInterface::class) && $entity_type->isRevisionable(); - } - return $this->supported[$entity_type_id]; + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\workspaces\WorkspaceInformation::isEntityTypeSupported instead. See https://www.drupal.org/node/3324297', E_USER_DEPRECATED); + return $this->workspaceInfo->isEntityTypeSupported($entity_type); } /** * {@inheritdoc} */ public function getSupportedEntityTypes() { - $entity_types = []; - foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { - if ($this->isEntityTypeSupported($entity_type)) { - $entity_types[$entity_type_id] = $entity_type; - } - } - return $entity_types; + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\workspaces\WorkspaceInformation::getSupportedEntityTypes instead. See https://www.drupal.org/node/3324297', E_USER_DEPRECATED); + return $this->workspaceInfo->getSupportedEntityTypes(); } /** @@ -254,7 +254,7 @@ class WorkspaceManager implements WorkspaceManagerInterface { // Clear the static entity cache for the supported entity types. $cache_tags_to_invalidate = array_map(function ($entity_type_id) { return 'entity.memory_cache:' . $entity_type_id; - }, array_keys($this->getSupportedEntityTypes())); + }, array_keys($this->workspaceInfo->getSupportedEntityTypes())); $this->entityMemoryCache->invalidateTags($cache_tags_to_invalidate); // Clear the static cache for path aliases. We can't inject the path alias @@ -297,7 +297,8 @@ class WorkspaceManager implements WorkspaceManagerInterface { * {@inheritdoc} */ public function shouldAlterOperations(EntityTypeInterface $entity_type) { - return $this->isEntityTypeSupported($entity_type) && $this->hasActiveWorkspace(); + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3324297', E_USER_DEPRECATED); + return $this->workspaceInfo->isEntityTypeSupported($entity_type) && $this->hasActiveWorkspace(); } /** @@ -318,7 +319,7 @@ class WorkspaceManager implements WorkspaceManagerInterface { $workspace_id = reset($deleted_workspace_ids); $all_associated_revisions = []; - foreach (array_keys($this->getSupportedEntityTypes()) as $entity_type_id) { + foreach (array_keys($this->workspaceInfo->getSupportedEntityTypes()) as $entity_type_id) { $all_associated_revisions[$entity_type_id] = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id); } $all_associated_revisions = array_filter($all_associated_revisions); @@ -359,7 +360,7 @@ class WorkspaceManager implements WorkspaceManagerInterface { // request a fresh list of tracked entities. If it is empty, we can go ahead // and remove the deleted workspace ID entry from state. $has_associated_revisions = FALSE; - foreach (array_keys($this->getSupportedEntityTypes()) as $entity_type_id) { + foreach (array_keys($this->workspaceInfo->getSupportedEntityTypes()) as $entity_type_id) { if (!empty($this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id))) { $has_associated_revisions = TRUE; break; diff --git a/core/modules/workspaces/src/WorkspaceManagerInterface.php b/core/modules/workspaces/src/WorkspaceManagerInterface.php index de3bfc4cd71..753657fc464 100644 --- a/core/modules/workspaces/src/WorkspaceManagerInterface.php +++ b/core/modules/workspaces/src/WorkspaceManagerInterface.php @@ -17,6 +17,11 @@ interface WorkspaceManagerInterface { * * @return bool * TRUE if the entity type can belong to a workspace, FALSE otherwise. + * + * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use + * \Drupal\workspaces\WorkspaceInformation::isEntityTypeSupported instead. + * + * @see https://www.drupal.org/node/3324297 */ public function isEntityTypeSupported(EntityTypeInterface $entity_type); @@ -25,6 +30,11 @@ interface WorkspaceManagerInterface { * * @return \Drupal\Core\Entity\EntityTypeInterface[] * The entity types what can belong to workspaces. + * + * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use + * \Drupal\workspaces\WorkspaceInformation::getSupportedEntityTypes instead. + * + * @see https://www.drupal.org/node/3324297 */ public function getSupportedEntityTypes(); @@ -97,6 +107,11 @@ interface WorkspaceManagerInterface { * @return bool * TRUE if the entity operations or queries should be altered in the current * request, FALSE otherwise. + * + * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is no + * replacement. + * + * @see https://www.drupal.org/node/3324297 */ public function shouldAlterOperations(EntityTypeInterface $entity_type); diff --git a/core/modules/workspaces/tests/modules/workspaces_test/src/EntityTestRevPubWorkspaceHandler.php b/core/modules/workspaces/tests/modules/workspaces_test/src/EntityTestRevPubWorkspaceHandler.php new file mode 100644 index 00000000000..94e6aa49329 --- /dev/null +++ b/core/modules/workspaces/tests/modules/workspaces_test/src/EntityTestRevPubWorkspaceHandler.php @@ -0,0 +1,20 @@ +bundle() !== 'ignored_bundle'; + } + +} diff --git a/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml b/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml new file mode 100644 index 00000000000..62886a0b12d --- /dev/null +++ b/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml @@ -0,0 +1,7 @@ +name: 'Workspace Test' +type: module +description: 'Provides supporting code for testing workspaces.' +package: Testing +version: VERSION +dependencies: + - drupal:workspaces diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceInformationTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceInformationTest.php new file mode 100644 index 00000000000..d417880b092 --- /dev/null +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceInformationTest.php @@ -0,0 +1,178 @@ +entityTypeManager = \Drupal::entityTypeManager(); + $this->workspaceInformation = \Drupal::service('workspaces.information'); + $this->state = \Drupal::state(); + + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_revpub'); + $this->installEntitySchema('workspace'); + + $this->installSchema('workspaces', ['workspace_association']); + + // Create a new workspace and activate it. + Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save(); + $this->switchToWorkspace('stage'); + } + + /** + * Tests fully supported entity types. + */ + public function testSupportedEntityTypes() { + // Check a supported entity type. + $entity = $this->entityTypeManager->getStorage('entity_test_revpub')->create(); + + $this->assertTrue($this->workspaceInformation->isEntitySupported($entity)); + $this->assertTrue($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType())); + + $this->assertFalse($this->workspaceInformation->isEntityIgnored($entity)); + $this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType())); + + // Check that supported entity types are tracked in a workspace. This entity + // is published by default, so the second revision will be tracked. + $entity->save(); + $this->assertWorkspaceAssociation(['stage' => [2]], 'entity_test_revpub'); + } + + /** + * Tests an entity type with a custom workspace handler. + */ + public function testCustomSupportEntityTypes() { + $entity_type = clone $this->entityTypeManager->getDefinition('entity_test_revpub'); + $entity_type->setHandlerClass('workspace', EntityTestRevPubWorkspaceHandler::class); + $this->state->set('entity_test_revpub.entity_type', $entity_type); + $this->entityTypeManager->clearCachedDefinitions(); + + $entity = $this->entityTypeManager->getStorage('entity_test_revpub')->create([ + 'type' => 'supported_bundle', + ]); + + $this->assertTrue($this->workspaceInformation->isEntitySupported($entity)); + $this->assertTrue($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType())); + $this->assertFalse($this->workspaceInformation->isEntityIgnored($entity)); + $this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType())); + + // Check that supported entity types are tracked in a workspace. This entity + // is published by default, so the second revision will be tracked. + $entity->save(); + $this->assertWorkspaceAssociation(['stage' => [2]], 'entity_test_revpub'); + + $entity = $this->entityTypeManager->getStorage('entity_test_revpub')->create([ + 'type' => 'ignored_bundle', + ]); + + $this->assertFalse($this->workspaceInformation->isEntitySupported($entity)); + $this->assertTrue($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType())); + $this->assertTrue($this->workspaceInformation->isEntityIgnored($entity)); + $this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType())); + + // Check that an ignored entity can be saved, but won't be tracked. + $entity->save(); + $this->assertWorkspaceAssociation(['stage' => [2]], 'entity_test_revpub'); + } + + /** + * Tests ignored entity types. + */ + public function testIgnoredEntityTypes() { + $entity_type = clone $this->entityTypeManager->getDefinition('entity_test_rev'); + $entity_type->setHandlerClass('workspace', IgnoredWorkspaceHandler::class); + $this->state->set('entity_test_rev.entity_type', $entity_type); + $this->entityTypeManager->clearCachedDefinitions(); + + // Check an ignored entity type. CRUD operations for an ignored entity type + // are allowed in a workspace, but their revisions are not tracked. + $entity = $this->entityTypeManager->getStorage('entity_test_rev')->create(); + $this->assertTrue($this->workspaceInformation->isEntityIgnored($entity)); + $this->assertTrue($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType())); + + $this->assertFalse($this->workspaceInformation->isEntitySupported($entity)); + $this->assertFalse($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType())); + + // Check that ignored entity types are not tracked in a workspace. + $entity->save(); + $this->assertWorkspaceAssociation(['stage' => []], 'entity_test_rev'); + } + + /** + * Tests unsupported entity types. + */ + public function testUnsupportedEntityTypes() { + // Check an unsupported entity type. + $entity_test = $this->entityTypeManager->getDefinition('entity_test'); + $this->assertFalse($entity_test->hasHandlerClass('workspace')); + + $entity = $this->entityTypeManager->getStorage('entity_test')->create(); + $this->assertFalse($this->workspaceInformation->isEntitySupported($entity)); + $this->assertFalse($this->workspaceInformation->isEntityTypeSupported($entity_test)); + + $this->assertFalse($this->workspaceInformation->isEntityIgnored($entity)); + $this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity_test)); + + // Check that unsupported entity types can not be saved in a workspace. + $this->expectException(EntityStorageException::class); + $this->expectExceptionMessage('This entity can only be saved in the default workspace.'); + $entity->save(); + } + +} diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php index 7d6cfdeb99e..0fd6c43b45a 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php @@ -39,7 +39,7 @@ trait WorkspaceTestTrait { // Install the entity schema for supported entity types to ensure that the // 'workspace' revision metadata field gets created. - foreach (array_keys($this->workspaceManager->getSupportedEntityTypes()) as $entity_type_id) { + foreach (array_keys(\Drupal::service('workspaces.information')->getSupportedEntityTypes()) as $entity_type_id) { $this->installEntitySchema($entity_type_id); } diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install index 2c37233f92f..0223678a686 100644 --- a/core/modules/workspaces/workspaces.install +++ b/core/modules/workspaces/workspaces.install @@ -35,11 +35,11 @@ function workspaces_module_preinstall($module) { return; } - /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */ - $workspace_manager = \Drupal::service('workspaces.manager'); + /** @var \Drupal\workspaces\WorkspaceInformationInterface $workspace_info */ + $workspace_info = \Drupal::service('workspaces.information'); $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) { - if ($workspace_manager->isEntityTypeSupported($entity_type)) { + if ($workspace_info->isEntityTypeSupported($entity_type)) { $entity_type->setRevisionMetadataKey('workspace', 'workspace'); $entity_definition_update_manager->updateEntityType($entity_type); } diff --git a/core/modules/workspaces/workspaces.module b/core/modules/workspaces/workspaces.module index d972fd8c978..ab12654ad09 100644 --- a/core/modules/workspaces/workspaces.module +++ b/core/modules/workspaces/workspaces.module @@ -148,7 +148,7 @@ function workspaces_entity_predelete(EntityInterface $entity) { * Implements hook_entity_delete(). */ function workspaces_entity_delete(EntityInterface $entity) { - if (\Drupal::service('workspaces.manager')->isEntityTypeSupported($entity->getEntityType())) { + if (\Drupal::service('workspaces.information')->isEntityTypeSupported($entity->getEntityType())) { \Drupal::service('workspaces.association') ->deleteAssociations(NULL, $entity->getEntityTypeId(), [$entity->id()]); } @@ -158,7 +158,7 @@ function workspaces_entity_delete(EntityInterface $entity) { * Implements hook_entity_revision_delete(). */ function workspaces_entity_revision_delete(EntityInterface $entity) { - if (\Drupal::service('workspaces.manager')->isEntityTypeSupported($entity->getEntityType())) { + if (\Drupal::service('workspaces.information')->isEntityTypeSupported($entity->getEntityType())) { \Drupal::service('workspaces.association') ->deleteAssociations(NULL, $entity->getEntityTypeId(), [$entity->id()], [$entity->getRevisionId()]); } diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml index 64669d92061..e36c996717d 100644 --- a/core/modules/workspaces/workspaces.services.yml +++ b/core/modules/workspaces/workspaces.services.yml @@ -3,10 +3,14 @@ services: autoconfigure: true workspaces.manager: class: Drupal\workspaces\WorkspaceManager - arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver', '@workspaces.association'] + arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver', '@workspaces.association', '@workspaces.information'] tags: - { name: service_id_collector, tag: workspace_negotiator } Drupal\workspaces\WorkspaceManagerInterface: '@workspaces.manager' + workspaces.information: + class: Drupal\workspaces\WorkspaceInformation + arguments: [ '@entity_type.manager' ] + Drupal\workspaces\WorkspaceInformationInterface: '@workspaces.information' workspaces.operation_factory: class: Drupal\workspaces\WorkspaceOperationFactory arguments: ['@entity_type.manager', '@database', '@workspaces.manager', '@workspaces.association', '@cache_tags.invalidator', '@event_dispatcher', '@logger.channel.workspaces'] @@ -41,7 +45,7 @@ services: workspaces.entity_schema_listener: class: Drupal\workspaces\EventSubscriber\EntitySchemaSubscriber - arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.manager'] + arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.information'] workspaces.workspace_subscriber: class: Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber arguments: ['@path_alias.manager', '@path.current', '@router.route_provider', '@workspaces.manager'] @@ -58,7 +62,7 @@ services: workspaces.entity.query.sql: decorates: entity.query.sql class: Drupal\workspaces\EntityQuery\QueryFactory - arguments: ['@database', '@workspaces.manager'] + arguments: ['@database', '@workspaces.manager', '@workspaces.information'] public: false decoration_priority: 50 tags: @@ -66,7 +70,7 @@ services: pgsql.workspaces.entity.query.sql: decorates: pgsql.entity.query.sql class: Drupal\workspaces\EntityQuery\PgsqlQueryFactory - arguments: ['@database', '@workspaces.manager'] + arguments: ['@database', '@workspaces.manager', '@workspaces.information'] public: false decoration_priority: 50