diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 4d2a7f6cbf046029fdd728b3b81ebe9d67629735..c7b2937684da87cfbaf87fe1d9cfed737675f6fa 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1454,25 +1454,3 @@ function system_element_info_alter(&$type) { $type['page']['#theme_wrappers']['off_canvas_page_wrapper'] = ['#weight' => -1000]; } } - -/** - * Implements hook_modules_uninstalled(). - */ -function system_modules_uninstalled($modules) { - // @todo Remove this when modules are able to maintain their revision metadata - // keys. - // @see https://www.drupal.org/project/drupal/issues/3074333 - if (!in_array('workspaces', $modules, TRUE)) { - return; - } - - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) { - $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); - if ($revision_metadata_keys && array_key_exists('workspace', $revision_metadata_keys)) { - unset($revision_metadata_keys['workspace']); - $entity_type->set('revision_metadata_keys', $revision_metadata_keys); - $entity_definition_update_manager->updateEntityType($entity_type); - } - } -} diff --git a/core/modules/workspaces/src/Entity/WorkspaceAssociation.php b/core/modules/workspaces/src/Entity/WorkspaceAssociation.php new file mode 100644 index 0000000000000000000000000000000000000000..6c65c81ed024c60b426fee0955dc4fcbf48bfb71 --- /dev/null +++ b/core/modules/workspaces/src/Entity/WorkspaceAssociation.php @@ -0,0 +1,77 @@ +<?php + +namespace Drupal\workspaces\Entity; + +use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; + +/** + * Defines the Workspace association entity. + * + * @ContentEntityType( + * id = "workspace_association", + * label = @Translation("Workspace association"), + * label_collection = @Translation("Workspace associations"), + * label_singular = @Translation("workspace association"), + * label_plural = @Translation("workspace associations"), + * label_count = @PluralTranslation( + * singular = "@count workspace association", + * plural = "@count workspace associations" + * ), + * handlers = { + * "storage" = "Drupal\workspaces\WorkspaceAssociationStorage" + * }, + * base_table = "workspace_association", + * revision_table = "workspace_association_revision", + * internal = TRUE, + * entity_keys = { + * "id" = "id", + * "revision" = "revision_id", + * "uuid" = "uuid", + * } + * ) + * + * @internal + * This entity is marked internal because it should not be used directly to + * alter the workspace an entity belongs to. + */ +class WorkspaceAssociation extends ContentEntityBase { + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['workspace'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(new TranslatableMarkup('workspace')) + ->setDescription(new TranslatableMarkup('The workspace of the referenced content.')) + ->setSetting('target_type', 'workspace') + ->setRequired(TRUE) + ->setRevisionable(TRUE); + + $fields['target_entity_type_id'] = BaseFieldDefinition::create('string') + ->setLabel(new TranslatableMarkup('Content entity type ID')) + ->setDescription(new TranslatableMarkup('The ID of the content entity type associated with this workspace.')) + ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH) + ->setRequired(TRUE) + ->setRevisionable(TRUE); + + $fields['target_entity_id'] = BaseFieldDefinition::create('integer') + ->setLabel(new TranslatableMarkup('Content entity ID')) + ->setDescription(new TranslatableMarkup('The ID of the content entity associated with this workspace.')) + ->setRequired(TRUE) + ->setRevisionable(TRUE); + + $fields['target_entity_revision_id'] = BaseFieldDefinition::create('integer') + ->setLabel(new TranslatableMarkup('Content entity revision ID')) + ->setDescription(new TranslatableMarkup('The revision ID of the content entity associated with this workspace.')) + ->setRequired(TRUE) + ->setRevisionable(TRUE); + + return $fields; + } + +} diff --git a/core/modules/workspaces/src/EntityOperations.php b/core/modules/workspaces/src/EntityOperations.php index 4409162a282b2dcd967bed2286af0f6c14f222b7..6182da200f2cbc7606a7d0071f0e3c90741e67b6 100644 --- a/core/modules/workspaces/src/EntityOperations.php +++ b/core/modules/workspaces/src/EntityOperations.php @@ -34,13 +34,6 @@ class EntityOperations implements ContainerInjectionInterface { */ protected $workspaceManager; - /** - * The workspace association service. - * - * @var \Drupal\workspaces\WorkspaceAssociationInterface - */ - protected $workspaceAssociation; - /** * Constructs a new EntityOperations instance. * @@ -48,13 +41,10 @@ class EntityOperations implements ContainerInjectionInterface { * 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. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) { $this->entityTypeManager = $entity_type_manager; $this->workspaceManager = $workspace_manager; - $this->workspaceAssociation = $workspace_association; } /** @@ -63,8 +53,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Wor public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), - $container->get('workspaces.manager'), - $container->get('workspaces.association') + $container->get('workspaces.manager') ); } @@ -85,13 +74,31 @@ public function entityPreload(array $ids, $entity_type_id) { // Get a list of revision IDs for entities that have a revision set for the // 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)) { + $max_revision_id = 'max_target_entity_revision_id'; + $query = $this->entityTypeManager + ->getStorage('workspace_association') + ->getAggregateQuery() + ->accessCheck(FALSE) + ->allRevisions() + ->aggregate('target_entity_revision_id', 'MAX', NULL, $max_revision_id) + ->groupBy('target_entity_id') + ->condition('target_entity_type_id', $entity_type_id) + ->condition('workspace', $this->workspaceManager->getActiveWorkspace()->id()); + + if ($ids) { + $query->condition('target_entity_id', $ids, 'IN'); + } + + $results = $query->execute(); + + if ($results) { /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */ $storage = $this->entityTypeManager->getStorage($entity_type_id); // Swap out every entity which has a revision set for the current active // workspace. - foreach ($storage->loadMultipleRevisions(array_keys($tracked_entities[$entity_type_id])) as $revision) { + $swap_revision_ids = array_column($results, $max_revision_id); + foreach ($storage->loadMultipleRevisions($swap_revision_ids) as $revision) { $entities[$revision->id()] = $revision; } } @@ -135,10 +142,6 @@ public function entityPresave(EntityInterface $entity) { // become the default revision only when it is replicated to the default // workspace. $entity->isDefaultRevision(FALSE); - - // Track the workspaces in which the new revision was saved. - $field_name = $entity_type->getRevisionMetadataKey('workspace'); - $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id(); } // When a new published entity is inserted in a non-default workspace, we @@ -171,7 +174,7 @@ public function entityInsert(EntityInterface $entity) { return; } - $this->workspaceAssociation->trackEntity($entity, $this->workspaceManager->getActiveWorkspace()); + $this->trackEntity($entity); // When an entity is newly created in a workspace, it should be published in // that workspace, but not yet published on the live workspace. It is first @@ -208,7 +211,7 @@ public function entityUpdate(EntityInterface $entity) { // Only track new revisions. /** @var \Drupal\Core\Entity\RevisionableInterface $entity */ if ($entity->getLoadedRevisionId() != $entity->getRevisionId()) { - $this->workspaceAssociation->trackEntity($entity, $this->workspaceManager->getActiveWorkspace()); + $this->trackEntity($entity); } } @@ -237,6 +240,51 @@ public function entityPredelete(EntityInterface $entity) { } } + /** + * Updates or creates a WorkspaceAssociation entity for a given entity. + * + * If the passed-in entity can belong to a workspace and already has a + * WorkspaceAssociation entity, then a new revision of this will be created with + * the new information. Otherwise, a new WorkspaceAssociation entity is created to + * store the passed-in entity's information. + * + * @param \Drupal\Core\Entity\RevisionableInterface $entity + * The entity to update or create from. + */ + protected function trackEntity(RevisionableInterface $entity) { + // If the entity is not new, check if there's an existing + // WorkspaceAssociation entity for it. + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + if (!$entity->isNew()) { + $workspace_associations = $workspace_association_storage->loadByProperties([ + 'target_entity_type_id' => $entity->getEntityTypeId(), + 'target_entity_id' => $entity->id(), + ]); + + /** @var \Drupal\Core\Entity\ContentEntityInterface $workspace_association */ + $workspace_association = reset($workspace_associations); + } + + // If there was a WorkspaceAssociation entry create a new revision, + // otherwise create a new entity with the type and ID. + if (!empty($workspace_association)) { + $workspace_association->setNewRevision(TRUE); + } + else { + $workspace_association = $workspace_association_storage->create([ + 'target_entity_type_id' => $entity->getEntityTypeId(), + 'target_entity_id' => $entity->id(), + ]); + } + + // Add the revision ID and the workspace ID. + $workspace_association->set('target_entity_revision_id', $entity->getRevisionId()); + $workspace_association->set('workspace', $this->workspaceManager->getActiveWorkspace()->id()); + + // Save without updating the tracked content entity. + $workspace_association->save(); + } + /** * Alters entity forms to disallow concurrent editing in multiple workspaces. * @@ -250,7 +298,7 @@ public function entityPredelete(EntityInterface $entity) { * @see hook_form_alter() */ public function entityFormAlter(array &$form, FormStateInterface $form_state, $form_id) { - /** @var \Drupal\Core\Entity\RevisionableInterface $entity */ + /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $form_state->getFormObject()->getEntity(); if (!$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) { return; @@ -270,7 +318,9 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, $f $form['#entity_builders'][] = [get_called_class(), 'entityFormEntityBuild']; } - if ($workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity)) { + /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + if ($workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity)) { // An entity can only be edited in one workspace. $workspace_id = reset($workspace_ids); diff --git a/core/modules/workspaces/src/EntityTypeInfo.php b/core/modules/workspaces/src/EntityTypeInfo.php index 7a72eb246d26cc8beefb42fc5000599e04bb53e1..5495c7fa4e7c09c31db6484bc881ad7126279341 100644 --- a/core/modules/workspaces/src/EntityTypeInfo.php +++ b/core/modules/workspaces/src/EntityTypeInfo.php @@ -3,10 +3,7 @@ namespace Drupal\workspaces; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Field\BaseFieldDefinition; -use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -69,10 +66,6 @@ public function entityTypeBuild(array &$entity_types) { foreach ($entity_types as $entity_type) { if ($this->workspaceManager->isEntityTypeSupported($entity_type)) { $entity_type->addConstraint('EntityWorkspaceConflict'); - - $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); - $revision_metadata_keys['workspace'] = 'workspace'; - $entity_type->set('revision_metadata_keys', $revision_metadata_keys); } } } @@ -91,30 +84,4 @@ public function fieldInfoAlter(&$definitions) { } } - /** - * Provides custom base field definitions for a content entity type. - * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The entity type definition. - * - * @return \Drupal\Core\Field\FieldDefinitionInterface[] - * An array of field definitions, keyed by field name. - * - * @see hook_entity_base_field_info() - */ - public function entityBaseFieldInfo(EntityTypeInterface $entity_type) { - if ($this->workspaceManager->isEntityTypeSupported($entity_type)) { - $field_name = $entity_type->getRevisionMetadataKey('workspace'); - $fields[$field_name] = BaseFieldDefinition::create('entity_reference') - ->setLabel(new TranslatableMarkup('Workspace')) - ->setDescription(new TranslatableMarkup('Indicates the workspace that this revision belongs to.')) - ->setSetting('target_type', 'workspace') - ->setInternal(TRUE) - ->setTranslatable(FALSE) - ->setRevisionable(TRUE); - - return $fields; - } - } - } diff --git a/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php deleted file mode 100644 index 30fd77aa6445cca0ed96aacd74acaef04a63c299..0000000000000000000000000000000000000000 --- a/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php +++ /dev/null @@ -1,147 +0,0 @@ -<?php - -namespace Drupal\workspaces\EventSubscriber; - -use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface; -use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface; -use Drupal\Core\Entity\EntityTypeEventSubscriberTrait; -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 Symfony\Component\EventDispatcher\EventSubscriberInterface; - -/** - * Defines a class for listening to entity schema changes. - */ -class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface { - - use EntityTypeEventSubscriberTrait; - use StringTranslationTrait; - - /** - * The definition update manager. - * - * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface - */ - protected $entityDefinitionUpdateManager; - - /** - * The last installed schema definitions. - * - * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface - */ - protected $entityLastInstalledSchemaRepository; - - /** - * The workspace manager. - * - * @var \Drupal\workspaces\WorkspaceManagerInterface - */ - protected $workspaceManager; - - /** - * Constructs a new EntitySchemaSubscriber. - * - * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager - * Definition update manager. - * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository - * Last definitions. - * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager - * The workspace manager. - */ - public function __construct(EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager, EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository, WorkspaceManagerInterface $workspace_manager) { - $this->entityDefinitionUpdateManager = $entityDefinitionUpdateManager; - $this->entityLastInstalledSchemaRepository = $entityLastInstalledSchemaRepository; - $this->workspaceManager = $workspace_manager; - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - return static::getEntityTypeEvents(); - } - - /** - * {@inheritdoc} - */ - public function onEntityTypeCreate(EntityTypeInterface $entity_type) { - // Nothing to do here. - } - - /** - * {@inheritdoc} - */ - 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)) { - $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); - - if (!isset($revision_metadata_keys['workspace'])) { - // Bail out if there's an existing field called 'workspace'. - if ($this->entityDefinitionUpdateManager->getFieldStorageDefinition('workspace', $entity_type->id())) { - throw new \RuntimeException("An existing 'workspace' field was found for the '{$entity_type->id()}' entity type. Set the 'workspace' revision metadata key to use a different field name and run this update function again."); - } - - $revision_metadata_keys['workspace'] = 'workspace'; - $entity_type->set('revision_metadata_keys', $revision_metadata_keys); - - // We are only adding a revision metadata key so we don't need to go - // through the entity update process. - $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type); - } - - $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_metadata_keys['workspace'], $entity_type->id(), 'workspaces', $this->getWorkspaceFieldDefinition()); - } - - // 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)) { - $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); - - $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); - unset($revision_metadata_keys['workspace']); - $entity_type->set('revision_metadata_keys', $revision_metadata_keys); - - // We are only removing a revision metadata key so we don't need to go - // through the entity update process. - $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type); - } - } - - /** - * {@inheritdoc} - */ - public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original, array $field_storage_definitions, array $original_field_storage_definitions, array &$sandbox = NULL) { - $this->onEntityTypeUpdate($entity_type, $original); - } - - /** - * {@inheritdoc} - */ - public function onEntityTypeDelete(EntityTypeInterface $entity_type) { - // Nothing to do here. - } - - /** - * Gets the base field definition for the 'workspace' revision metadata field. - * - * @return \Drupal\Core\Field\BaseFieldDefinition - * The base field definition. - */ - protected function getWorkspaceFieldDefinition() { - return BaseFieldDefinition::create('entity_reference') - ->setLabel($this->t('Workspace')) - ->setDescription($this->t('Indicates the workspace that this revision belongs to.')) - ->setSetting('target_type', 'workspace') - ->setInternal(TRUE) - ->setTranslatable(FALSE) - ->setRevisionable(TRUE); - } - -} diff --git a/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php b/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php index 195cac732fd28135267597680714a079850962a9..8086873f9ade48b67d544f764fdbc7b1b9aaaf04 100644 --- a/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php +++ b/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php @@ -2,13 +2,8 @@ namespace Drupal\workspaces\Form; -use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\ContentEntityDeleteForm; -use Drupal\Core\Entity\EntityRepositoryInterface; -use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\workspaces\WorkspaceAssociationInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a form for deleting a workspace. @@ -24,52 +19,14 @@ class WorkspaceDeleteForm extends ContentEntityDeleteForm implements WorkspaceFo */ protected $entity; - /** - * The workspace association service. - * - * @var \Drupal\workspaces\WorkspaceAssociationInterface - */ - protected $workspaceAssociation; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity.repository'), - $container->get('workspaces.association'), - $container->get('entity_type.bundle.info'), - $container->get('datetime.time') - ); - } - - /** - * Constructs a WorkspaceDeleteForm object. - * - * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository - * The entity repository service. - * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association - * The workspace association service to check how many revisions will be - * deleted. - * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info - * The entity type bundle service. - * @param \Drupal\Component\Datetime\TimeInterface $time - * The time service. - */ - public function __construct(EntityRepositoryInterface $entity_repository, WorkspaceAssociationInterface $workspace_association, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) { - parent::__construct($entity_repository, $entity_type_bundle_info, $time); - $this->workspaceAssociation = $workspace_association; - } - /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $form = parent::buildForm($form, $form_state); - $tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->entity->id()); + $source_rev_diff = $this->entityTypeManager->getStorage('workspace_association')->getTrackedEntities($this->entity->id()); $items = []; - foreach (array_keys($tracked_entities) as $entity_type_id => $entity_ids) { - $revision_ids = $this->workspaceAssociation->getAssociatedRevisions($this->entity->id(), $entity_type_id, $entity_ids); + foreach ($source_rev_diff as $entity_type_id => $revision_ids) { $label = $this->entityTypeManager->getDefinition($entity_type_id)->getLabel(); $items[] = $this->formatPlural(count($revision_ids), '1 @label revision.', '@count @label revisions.', ['@label' => $label]); } diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php index 1543b5fc2b30fca4e9499667221522f25b39b2bc..070e89050bb8adae926b7336766230b72be792cf 100644 --- a/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php +++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php @@ -3,7 +3,7 @@ namespace Drupal\workspaces\Plugin\Validation\Constraint; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\workspaces\WorkspaceAssociationInterface; +use Drupal\workspaces\WorkspaceAssociationStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -14,20 +14,20 @@ class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface { /** - * The workspace association service. + * The workspace association storage. * - * @var \Drupal\workspaces\WorkspaceAssociationInterface + * @var \Drupal\workspaces\WorkspaceAssociationStorageInterface */ - protected $workspaceAssociation; + protected $workspaceAssociationStorage; /** * Creates a new DeletedWorkspaceConstraintValidator instance. * - * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association - * The workspace association service. + * @param \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage + * The workspace association storage. */ - public function __construct(WorkspaceAssociationInterface $workspace_association) { - $this->workspaceAssociation = $workspace_association; + public function __construct(WorkspaceAssociationStorageInterface $workspace_association_storage) { + $this->workspaceAssociationStorage = $workspace_association_storage; } /** @@ -35,7 +35,7 @@ public function __construct(WorkspaceAssociationInterface $workspace_association */ public static function create(ContainerInterface $container) { return new static( - $container->get('workspaces.association') + $container->get('entity_type.manager')->getStorage('workspace_association') ); } @@ -49,7 +49,14 @@ public function validate($value, Constraint $constraint) { return; } - if ($this->workspaceAssociation->getTrackedEntities($value->getEntity()->id())) { + $count = $this->workspaceAssociationStorage + ->getQuery() + ->allRevisions() + ->accessCheck(FALSE) + ->condition('workspace', $value->getEntity()->id()) + ->count() + ->execute(); + if ($count) { $this->context->addViolation($constraint->message); } } diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php index 66bb887e7b1243ee6047817035d75063826a6e62..17adc7c0d2d892930b2f3904f0a9758ce2cacca5 100644 --- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php +++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php @@ -4,7 +4,6 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\workspaces\WorkspaceAssociationInterface; use Drupal\workspaces\WorkspaceManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; @@ -29,13 +28,6 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp */ protected $workspaceManager; - /** - * The workspace association service. - * - * @var \Drupal\workspaces\WorkspaceAssociationInterface - */ - protected $workspaceAssociation; - /** * Constructs an EntityUntranslatableFieldsConstraintValidator object. * @@ -43,13 +35,10 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp * 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. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) { $this->entityTypeManager = $entity_type_manager; $this->workspaceManager = $workspace_manager; - $this->workspaceAssociation = $workspace_association; } /** @@ -58,8 +47,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Wor public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), - $container->get('workspaces.manager'), - $container->get('workspaces.association') + $container->get('workspaces.manager') ); } @@ -69,7 +57,9 @@ public static function create(ContainerInterface $container) { public function validate($entity, Constraint $constraint) { /** @var \Drupal\Core\Entity\EntityInterface $entity */ if (isset($entity) && !$entity->isNew()) { - $workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity); + /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + $workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity); $active_workspace = $this->workspaceManager->getActiveWorkspace(); if ($workspace_ids && (!$active_workspace || !in_array($active_workspace->id(), $workspace_ids, TRUE))) { diff --git a/core/modules/workspaces/src/WorkspaceAssociation.php b/core/modules/workspaces/src/WorkspaceAssociation.php deleted file mode 100644 index a67f9546937278b9aaa16adcafe0cbf917cbaae8..0000000000000000000000000000000000000000 --- a/core/modules/workspaces/src/WorkspaceAssociation.php +++ /dev/null @@ -1,163 +0,0 @@ -<?php - -namespace Drupal\workspaces; - -use Drupal\Core\Database\Connection; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Entity\RevisionableInterface; -use Drupal\Core\Entity\Sql\SqlContentEntityStorage; - -/** - * Provides a class for CRUD operations on workspace associations. - */ -class WorkspaceAssociation implements WorkspaceAssociationInterface { - - /** - * The table for the workspace association storage. - */ - const TABLE = 'workspace_association'; - - /** - * The database connection. - * - * @var \Drupal\Core\Database\Connection - */ - protected $database; - - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * Constructs a WorkspaceAssociation object. - * - * @param \Drupal\Core\Database\Connection $connection - * A database connection for reading and writing path aliases. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager for querying revisions. - */ - public function __construct(Connection $connection, EntityTypeManagerInterface $entity_type_manager) { - $this->database = $connection; - $this->entityTypeManager = $entity_type_manager; - } - - /** - * {@inheritdoc} - */ - public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $workspace) { - $this->database->merge(static::TABLE) - ->fields([ - 'target_entity_revision_id' => $entity->getRevisionId(), - ]) - ->keys([ - 'workspace' => $workspace->id(), - 'target_entity_type_id' => $entity->getEntityTypeId(), - 'target_entity_id' => $entity->id(), - ]) - ->execute(); - } - - /** - * {@inheritdoc} - */ - public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entity_ids = NULL) { - $query = $this->database->select(static::TABLE); - $query - ->fields(static::TABLE, ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id']) - ->orderBy('target_entity_revision_id', 'ASC') - ->condition('workspace', $workspace_id); - - if ($entity_type_id) { - $query->condition('target_entity_type_id', $entity_type_id, '='); - - if ($entity_ids) { - $query->condition('target_entity_id', $entity_ids, 'IN'); - } - } - - $tracked_revisions = []; - foreach ($query->execute() as $record) { - $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id; - } - - return $tracked_revisions; - } - - /** - * {@inheritdoc} - */ - public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL) { - /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ - $storage = $this->entityTypeManager->getStorage($entity_type_id); - - // If the entity type is not using core's default entity storage, we can't - // assume the table mapping layout so we have to return only the latest - // tracked revisions. - if (!$storage instanceof SqlContentEntityStorage) { - return $this->getTrackedEntities($workspace_id, $entity_type_id, $entity_ids)[$entity_type_id]; - } - - $entity_type = $storage->getEntityType(); - $table_mapping = $storage->getTableMapping(); - $workspace_field = $table_mapping->getColumnNames($entity_type->get('revision_metadata_keys')['workspace'])['target_id']; - $id_field = $table_mapping->getColumnNames($entity_type->getKey('id'))['value']; - $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value']; - - $query = $this->database->select($entity_type->getRevisionTable(), 'revision'); - $query->leftJoin($entity_type->getBaseTable(), 'base', "revision.$id_field = base.$id_field"); - - $query - ->fields('revision', [$revision_id_field, $id_field]) - ->condition("revision.$workspace_field", $workspace_id) - ->where("revision.$revision_id_field > base.$revision_id_field") - ->orderBy("revision.$revision_id_field", 'ASC'); - - // Restrict the result to a set of entity ID's if provided. - if ($entity_ids) { - $query->condition("revision.$id_field", $entity_ids, 'IN'); - } - - return $query->execute()->fetchAllKeyed(); - } - - /** - * {@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()); - - return $query->execute()->fetchCol(); - } - - /** - * {@inheritdoc} - */ - public function postPublish(WorkspaceInterface $workspace) { - $this->deleteAssociations($workspace->id()); - } - - /** - * {@inheritdoc} - */ - public function deleteAssociations($workspace_id, $entity_type_id = NULL, $entity_ids = NULL) { - $query = $this->database->delete(static::TABLE) - ->condition('workspace', $workspace_id); - - if ($entity_type_id) { - $query->condition('target_entity_type_id', $entity_type_id, '='); - - if ($entity_ids) { - $query->condition('target_entity_id', $entity_ids, 'IN'); - } - } - - $query->execute(); - } - -} diff --git a/core/modules/workspaces/src/WorkspaceAssociationInterface.php b/core/modules/workspaces/src/WorkspaceAssociationInterface.php deleted file mode 100644 index 1c93ca4aaa197bc8320cc7c9465dff9dc34ab64c..0000000000000000000000000000000000000000 --- a/core/modules/workspaces/src/WorkspaceAssociationInterface.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php - -namespace Drupal\workspaces; - -use Drupal\Core\Entity\RevisionableInterface; - -/** - * Defines an interface for the workspace_association service. - * - * The canonical workspace association data is stored in a revision metadata - * field on each entity revision that is tracked by a workspace. - * - * For the purpose of optimizing workspace-specific queries, the default - * implementation of this interface defines a custom 'workspace_association' - * index table which stores only the latest revisions tracked by a workspace. - * - * @internal - */ -interface WorkspaceAssociationInterface { - - /** - * Updates or creates the association for a given entity and a workspace. - * - * @param \Drupal\Core\Entity\RevisionableInterface $entity - * The entity to update or create from. - * @param \Drupal\workspaces\WorkspaceInterface $workspace - * The workspace in which the entity will be tracked. - */ - public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $workspace); - - /** - * Retrieves the entities tracked by a given workspace. - * - * @param string $workspace_id - * The ID of the workspace. - * @param string|null $entity_type_id - * (optional) An entity type ID to filter the results by. Defaults to NULL. - * @param int[]|string[]|null $entity_ids - * (optional) An array of entity IDs to filter the results by. Defaults to - * NULL. - * - * @return array - * Returns a multidimensional array where the first level keys are entity - * type IDs and the values are an array of entity IDs keyed by revision IDs. - */ - public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entity_ids = NULL); - - /** - * Retrieves all content revisions tracked by a given workspace. - * - * Since the 'workspace_association' index table only tracks the latest - * associated revisions, this method retrieves all the tracked revisions by - * querying the entity type's revision table directly. - * - * @param string $workspace_id - * The ID of the workspace. - * @param string $entity_type_id - * An entity type ID to find revisions for. - * @param int[]|string[]|null $entity_ids - * (optional) An array of entity IDs to filter the results by. Defaults to - * NULL. - * - * @return array - * Returns an array where the values are an array of entity IDs keyed by - * revision IDs. - */ - public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL); - - /** - * Gets a list of workspace IDs in which an entity is tracked. - * - * @param \Drupal\Core\Entity\RevisionableInterface $entity - * An entity object. - * - * @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); - - /** - * Triggers clean-up operations after publishing a workspace. - * - * @param \Drupal\workspaces\WorkspaceInterface $workspace - * A workspace entity. - */ - public function postPublish(WorkspaceInterface $workspace); - - /** - * Deletes all the workspace association records for the given workspace. - * - * @param string $workspace_id - * A workspace entity ID. - * @param string|null $entity_type_id - * (optional) The target entity type of the associations to delete. Defaults - * to NULL. - * @param string|null $entity_ids - * (optional) The target entity IDs of the associations to delete. Defaults - * to NULL. - */ - public function deleteAssociations($workspace_id, $entity_type_id = NULL, $entity_ids = NULL); - -} diff --git a/core/modules/workspaces/src/WorkspaceAssociationStorage.php b/core/modules/workspaces/src/WorkspaceAssociationStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..6355e79220e49aa51251fcf1a9bb033e473ce393 --- /dev/null +++ b/core/modules/workspaces/src/WorkspaceAssociationStorage.php @@ -0,0 +1,59 @@ +<?php + +namespace Drupal\workspaces; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\Sql\SqlContentEntityStorage; + +/** + * Defines the storage handler class for the Workspace association entity type. + */ +class WorkspaceAssociationStorage extends SqlContentEntityStorage implements WorkspaceAssociationStorageInterface { + + /** + * {@inheritdoc} + */ + public function postPush(WorkspaceInterface $workspace) { + $this->database + ->delete($this->entityType->getBaseTable()) + ->condition('workspace', $workspace->id()) + ->execute(); + $this->database + ->delete($this->entityType->getRevisionTable()) + ->condition('workspace', $workspace->id()) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function getTrackedEntities($workspace_id, $all_revisions = FALSE) { + $table = $all_revisions ? $this->getRevisionTable() : $this->getBaseTable(); + $query = $this->database->select($table, 'base_table'); + $query + ->fields('base_table', ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id']) + ->orderBy('target_entity_revision_id', 'ASC') + ->condition('workspace', $workspace_id); + + $tracked_revisions = []; + foreach ($query->execute() as $record) { + $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id; + } + + return $tracked_revisions; + } + + /** + * {@inheritdoc} + */ + public function getEntityTrackingWorkspaceIds(EntityInterface $entity) { + $query = $this->database->select($this->getBaseTable(), 'base_table'); + $query + ->fields('base_table', ['workspace']) + ->condition('target_entity_type_id', $entity->getEntityTypeId()) + ->condition('target_entity_id', $entity->id()); + + return $query->execute()->fetchCol(); + } + +} diff --git a/core/modules/workspaces/src/WorkspaceAssociationStorageInterface.php b/core/modules/workspaces/src/WorkspaceAssociationStorageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..24663206e30c6d33e04f75e75af8986c8462dc8c --- /dev/null +++ b/core/modules/workspaces/src/WorkspaceAssociationStorageInterface.php @@ -0,0 +1,48 @@ +<?php + +namespace Drupal\workspaces; + +use Drupal\Core\Entity\ContentEntityStorageInterface; +use Drupal\Core\Entity\EntityInterface; + +/** + * Defines an interface for workspace association entity storage classes. + */ +interface WorkspaceAssociationStorageInterface extends ContentEntityStorageInterface { + + /** + * Triggers clean-up operations after pushing. + * + * @param \Drupal\workspaces\WorkspaceInterface $workspace + * A workspace entity. + */ + public function postPush(WorkspaceInterface $workspace); + + /** + * Retrieves the content revisions tracked by a given workspace. + * + * @param string $workspace_id + * The ID of the workspace. + * @param bool $all_revisions + * (optional) Whether to return all the tracked revisions for each entity or + * just the latest tracked revision. Defaults to FALSE. + * + * @return array + * Returns a multidimensional array where the first level keys are entity + * type IDs and the values are an array of entity IDs keyed by revision IDs. + */ + public function getTrackedEntities($workspace_id, $all_revisions = FALSE); + + /** + * Gets a list of workspace IDs in which an entity is tracked. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity object. + * + * @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(EntityInterface $entity); + +} diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php index b53be3613025a3e12416a50da5fea8a57ea35164..5528988a80baa21e71058e55da8c230c43e1c2c8 100644 --- a/core/modules/workspaces/src/WorkspaceManager.php +++ b/core/modules/workspaces/src/WorkspaceManager.php @@ -83,13 +83,6 @@ class WorkspaceManager implements WorkspaceManagerInterface { */ protected $classResolver; - /** - * The workspace association service. - * - * @var \Drupal\workspaces\WorkspaceAssociationInterface - */ - protected $workspaceAssociation; - /** * The workspace negotiator service IDs. * @@ -121,12 +114,10 @@ class WorkspaceManager implements WorkspaceManagerInterface { * A logger instance. * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver * The class resolver. - * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association - * The workspace association service. * @param array $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, array $negotiator_ids) { $this->requestStack = $request_stack; $this->entityTypeManager = $entity_type_manager; $this->entityMemoryCache = $entity_memory_cache; @@ -134,7 +125,6 @@ public function __construct(RequestStack $request_stack, EntityTypeManagerInterf $this->state = $state; $this->logger = $logger; $this->classResolver = $class_resolver; - $this->workspaceAssociation = $workspace_association; $this->negotiatorIds = $negotiator_ids; } @@ -315,35 +305,67 @@ public function purgeDeletedWorkspacesBatch() { $batch_size = Settings::get('entity_update_batch_size', 50); + /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + // Get the first deleted workspace from the list and delete the revisions - // associated with it, along with the workspace association records. + // associated with it, along with the workspace_association entries. $workspace_id = reset($deleted_workspace_ids); - $tracked_entities = $this->workspaceAssociation->getTrackedEntities($workspace_id); - - $count = 1; - foreach ($tracked_entities as $entity_type_id => $entities) { - $associated_entity_storage = $this->entityTypeManager->getStorage($entity_type_id); - $associated_revisions = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id); - foreach (array_keys($associated_revisions) as $revision_id) { - if ($count > $batch_size) { - continue 2; - } + $workspace_association_ids = $this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size); + if ($workspace_association_ids) { + $workspace_associations = $workspace_association_storage->loadMultipleRevisions(array_keys($workspace_association_ids)); + foreach ($workspace_associations as $workspace_association) { + $associated_entity_storage = $this->entityTypeManager->getStorage($workspace_association->target_entity_type_id->value); // Delete the associated entity revision. - $associated_entity_storage->deleteRevision($revision_id); - $count++; + if ($entity = $associated_entity_storage->loadRevision($workspace_association->target_entity_revision_id->value)) { + if ($entity->isDefaultRevision()) { + $entity->delete(); + } + else { + $associated_entity_storage->deleteRevision($workspace_association->target_entity_revision_id->value); + } + } + + // Delete the workspace_association revision. + if ($workspace_association->isDefaultRevision()) { + $workspace_association->delete(); + } + else { + $workspace_association_storage->deleteRevision($workspace_association->getRevisionId()); + } } - // Delete the workspace association entries. - $this->workspaceAssociation->deleteAssociations($workspace_id, $entity_type_id, $entities); } // The purging operation above might have taken a long time, so we need to - // request a fresh list of tracked entities. If it is empty, we can go ahead - // and remove the deleted workspace ID entry from state. - if (!$this->workspaceAssociation->getTrackedEntities($workspace_id)) { + // request a fresh list of workspace association IDs. If it is empty, we can + // go ahead and remove the deleted workspace ID entry from state. + if (!$this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size)) { unset($deleted_workspace_ids[$workspace_id]); $this->state->set('workspace.deleted', $deleted_workspace_ids); } } + /** + * Gets a list of workspace association IDs to purge. + * + * @param string $workspace_id + * The ID of the workspace. + * @param int $batch_size + * The maximum number of records that will be purged. + * + * @return array + * An array of workspace association IDs, keyed by their revision IDs. + */ + protected function getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size) { + return $this->entityTypeManager->getStorage('workspace_association') + ->getQuery() + ->allRevisions() + ->accessCheck(FALSE) + ->condition('workspace', $workspace_id) + ->sort('revision_id', 'ASC') + ->range(0, $batch_size) + ->execute(); + } + } diff --git a/core/modules/workspaces/src/WorkspaceOperationFactory.php b/core/modules/workspaces/src/WorkspaceOperationFactory.php index 7b761a4a3994c6f33a6c014b9312a7c72432b959..d523365667ac508bab102a48d0860ad6d15f7bd4 100644 --- a/core/modules/workspaces/src/WorkspaceOperationFactory.php +++ b/core/modules/workspaces/src/WorkspaceOperationFactory.php @@ -36,13 +36,6 @@ class WorkspaceOperationFactory { */ protected $workspaceManager; - /** - * The workspace association service. - * - * @var \Drupal\workspaces\WorkspaceAssociationInterface - */ - protected $workspaceAssociation; - /** * Constructs a new WorkspacePublisher. * @@ -50,16 +43,11 @@ class WorkspaceOperationFactory { * The entity type manager. * @param \Drupal\Core\Database\Connection $database * Database connection. - * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager - * The workspace manager service. - * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association - * The workspace association service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager) { $this->entityTypeManager = $entity_type_manager; $this->database = $database; $this->workspaceManager = $workspace_manager; - $this->workspaceAssociation = $workspace_association; } /** @@ -72,7 +60,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Con * A workspace publisher object. */ public function getPublisher(WorkspaceInterface $source) { - return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $this->workspaceAssociation, $source); + return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $source); } } diff --git a/core/modules/workspaces/src/WorkspacePublisher.php b/core/modules/workspaces/src/WorkspacePublisher.php index d38e97383c39be78a01a655ca638260fe81cf987..c7da6b8be782c14ba48409cbad592f58a61eb244 100644 --- a/core/modules/workspaces/src/WorkspacePublisher.php +++ b/core/modules/workspaces/src/WorkspacePublisher.php @@ -37,18 +37,18 @@ class WorkspacePublisher implements WorkspacePublisherInterface { protected $database; /** - * The workspace manager. + * The workspace association storage. * - * @var \Drupal\workspaces\WorkspaceManagerInterface + * @var \Drupal\workspaces\WorkspaceAssociationStorageInterface */ - protected $workspaceManager; + protected $workspaceAssociationStorage; /** - * The workspace association service. + * The workspace manager. * - * @var \Drupal\workspaces\WorkspaceAssociationInterface + * @var \Drupal\workspaces\WorkspaceManagerInterface */ - protected $workspaceAssociation; + protected $workspaceManager; /** * Constructs a new WorkspacePublisher. @@ -59,14 +59,12 @@ class WorkspacePublisher implements WorkspacePublisherInterface { * Database connection. * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager * The workspace manager. - * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association - * The workspace association service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, WorkspaceInterface $source) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceInterface $source) { $this->entityTypeManager = $entity_type_manager; $this->database = $database; + $this->workspaceAssociationStorage = $entity_type_manager->getStorage('workspace_association'); $this->workspaceManager = $workspace_manager; - $this->workspaceAssociation = $workspace_association; $this->sourceWorkspace = $source; } @@ -97,11 +95,6 @@ public function publish() { // revisions. $entity->setSyncing(TRUE); $entity->isDefaultRevision(TRUE); - - // The default revision is not workspace-specific anymore. - $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace'); - $entity->{$field_name}->target_id = NULL; - $entity->original = $default_revisions[$entity->id()]; $entity->save(); } @@ -114,8 +107,9 @@ public function publish() { throw $e; } - // Notify the workspace association that a workspace has been published. - $this->workspaceAssociation->postPublish($this->sourceWorkspace); + // Notify the workspace association storage that a workspace has been + // pushed. + $this->workspaceAssociationStorage->postPush($this->sourceWorkspace); } /** @@ -147,7 +141,7 @@ public function checkConflictsOnTarget() { public function getDifferringRevisionIdsOnTarget() { $target_revision_difference = []; - $tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id()); + $tracked_entities = $this->workspaceAssociationStorage->getTrackedEntities($this->sourceWorkspace->id()); foreach ($tracked_entities as $entity_type_id => $tracked_revisions) { $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); @@ -177,7 +171,7 @@ public function getDifferringRevisionIdsOnTarget() { */ public function getDifferringRevisionIdsOnSource() { // Get the Workspace association revisions which haven't been pushed yet. - return $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id()); + return $this->workspaceAssociationStorage->getTrackedEntities($this->sourceWorkspace->id()); } /** diff --git a/core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php b/core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php deleted file mode 100644 index 7e6276abfead76a94a69250aa7db85f9d06582ef..0000000000000000000000000000000000000000 Binary files a/core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php and /dev/null differ diff --git a/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php b/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php deleted file mode 100644 index 0fffd21e3784cbccac98fc13d5632cc130024aa9..0000000000000000000000000000000000000000 --- a/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php +++ /dev/null @@ -1,105 +0,0 @@ -<?php - -namespace Drupal\Tests\workspaces\Functional\Update; - -use Drupal\FunctionalTests\Update\UpdatePathTestBase; - -/** - * Tests the upgrade path for the Workspaces module. - * - * @group workspaces - * @group Update - * @group legacy - */ -class WorkspacesUpdateTest extends UpdatePathTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = ['workspaces']; - - /** - * {@inheritdoc} - */ - public function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz', - __DIR__ . '/../../../fixtures/update/drupal-8.6.0-workspaces_installed.php', - ]; - } - - /** - * Tests the move of workspace association data to a custom table. - * - * @see workspaces_update_8801() - * @see workspaces_post_update_move_association_data() - */ - public function testWorkspaceAssociationRemoval() { - $database = \Drupal::database(); - - // Check that we have two records in the 'workspace_association' base table - // and three records in its revision table. - $wa_records = $database->select('workspace_association')->countQuery()->execute()->fetchField(); - $this->assertEquals(2, $wa_records); - $war_records = $database->select('workspace_association_revision')->countQuery()->execute()->fetchField(); - $this->assertEquals(3, $war_records); - - // Check that the node entity type does not have a 'workspace' field. - $this->assertNull(\Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('workspace', 'node')); - - $this->runUpdates(); - - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - - // Check that the 'workspace' field has been installed for an entity type - // that was workspace-supported before Drupal 8.7.0. - $this->assertTrue($entity_definition_update_manager->getFieldStorageDefinition('workspace', 'node')); - - // Check that the 'workspace' field has been installed for an entity type - // which became workspace-supported as part of an entity schema update. - $this->assertTrue($entity_definition_update_manager->getFieldStorageDefinition('workspace', 'taxonomy_term')); - - // Check that the 'workspace' revision metadata field has been created only - // in the revision table. - $schema = $database->schema(); - $this->assertTrue($schema->fieldExists('node_revision', 'workspace')); - $this->assertFalse($schema->fieldExists('node', 'workspace')); - $this->assertFalse($schema->fieldExists('node_field_data', 'workspace')); - $this->assertFalse($schema->fieldExists('node_field_revision', 'workspace')); - - // Check that the 'workspace_association' records have been migrated - // properly. - $wa_records = $database->select('workspace_association')->fields('workspace_association')->execute()->fetchAll(\PDO::FETCH_ASSOC); - $expected = [ - [ - 'workspace' => 'stage', - 'target_entity_type_id' => 'node', - 'target_entity_id' => '1', - 'target_entity_revision_id' => '2', - ], - [ - 'workspace' => 'dev', - 'target_entity_type_id' => 'node', - 'target_entity_id' => '8', - 'target_entity_revision_id' => '10', - ], - ]; - $this->assertEquals($expected, $wa_records); - - // Check that the 'workspace_association' revisions has been migrated - // properly to the new 'workspace' revision metadata field. - $revisions = \Drupal::entityTypeManager()->getStorage('node')->loadMultipleRevisions([2, 9, 10]); - $this->assertEquals('stage', $revisions[2]->workspace->target_id); - $this->assertEquals('dev', $revisions[9]->workspace->target_id); - $this->assertEquals('dev', $revisions[10]->workspace->target_id); - - // Check that the 'workspace_association' entity type has been uninstalled. - $this->assertNull($entity_definition_update_manager->getEntityType('workspace_association')); - $this->assertNull($entity_definition_update_manager->getFieldStorageDefinition('id', 'workspace_association')); - $this->assertNull(\Drupal::keyValue('entity.storage_schema.sql')->get('workspace_association.entity_schema_data')); - - // Check that the 'workspace_association_revision' table has been removed. - $this->assertFalse($schema->tableExists('workspace_association_revision')); - } - -} diff --git a/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php b/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php index e4932eca3282882270c3f43f311130d27a082dec..e652e3fc826b45a305b34a4e0b9247d5793d75b8 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php @@ -36,12 +36,6 @@ public function testUninstallingWorkspace() { $this->drupalPostForm(NULL, [], 'Uninstall'); $session->pageTextContains('The selected modules have been uninstalled.'); $session->pageTextNotContains('Workspaces'); - - $this->assertFalse($this->getDatabaseConnection()->schema()->fieldExists('node_revision', 'workspace')); - - // Verify that the revision metadata key has been removed. - $revision_metadata_keys = \Drupal::entityDefinitionUpdateManager()->getEntityType('node')->get('revision_metadata_keys'); - $this->assertArrayNotHasKey('workspace', $revision_metadata_keys); } } diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php index f8a4964eda303780adc9441df240ca04390ba86a..b8065a15c8dd84def910ac37be9b364737c7c7e2 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php @@ -33,6 +33,7 @@ protected function setUp() { $this->installSchema('system', ['sequences']); $this->installEntitySchema('workspace'); + $this->installEntitySchema('workspace_association'); $this->installEntitySchema('user'); // User 1. diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php index c8b1705a7e078911b3e27aaac09cf9e5199e7f36..d7d5710c57010e7ad5c58fe21a380a6fb739970a 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php @@ -7,6 +7,7 @@ use Drupal\Tests\node\Traits\NodeCreationTrait; use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\workspaces\Entity\Workspace; +use Drupal\workspaces\Entity\WorkspaceAssociation; /** * Tests CRUD operations for workspaces. @@ -18,7 +19,6 @@ class WorkspaceCRUDTest extends KernelTestBase { use UserCreationTrait; use NodeCreationTrait; use ContentTypeCreationTrait; - use WorkspaceTestTrait; /** * The entity type manager. @@ -66,7 +66,7 @@ protected function setUp() { $this->installSchema('node', ['node_access']); $this->installEntitySchema('workspace'); - $this->installSchema('workspaces', ['workspace_association']); + $this->installEntitySchema('workspace_association'); $this->installEntitySchema('node'); $this->installConfig(['filter', 'node', 'system']); @@ -91,8 +91,10 @@ public function testDeletingWorkspaces() { ]); $this->setCurrentUser($admin); - /** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */ - $workspace_association = \Drupal::service('workspaces.association'); + /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + /** @var \Drupal\node\NodeStorageInterface $node_storage */ + $node_storage = $this->entityTypeManager->getStorage('node'); // Create a workspace with a very small number of associated node revisions. $workspace_1 = Workspace::create([ @@ -104,12 +106,6 @@ public function testDeletingWorkspaces() { $workspace_1_node_1 = $this->createNode(['status' => FALSE]); $workspace_1_node_2 = $this->createNode(['status' => FALSE]); - - // The 'live' workspace should have 2 revisions now. The initial revision - // for each node. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(2, $live_revisions); - for ($i = 0; $i < 4; $i++) { $workspace_1_node_1->setNewRevision(TRUE); $workspace_1_node_1->save(); @@ -118,17 +114,9 @@ public function testDeletingWorkspaces() { $workspace_1_node_2->save(); } - // The workspace should now track 2 nodes. - $tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id()); - $this->assertCount(2, $tracked_entities['node']); - - // There should still be 2 revisions associated with 'live'. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(2, $live_revisions); - - // The other 8 revisions should be associated with 'workspace_1'. - $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node'); - $this->assertCount(8, $associated_revisions); + // The workspace should have 10 associated node revisions, 5 for each node. + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE); + $this->assertCount(10, $associated_revisions['node']); // Check that we are allowed to delete the workspace. $this->assertTrue($workspace_1->access('delete', $admin)); @@ -137,17 +125,14 @@ public function testDeletingWorkspaces() { // entities and all the node revisions have been deleted as well. $workspace_1->delete(); - // There are no more tracked entities in 'workspace_1'. - $tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id()); - $this->assertEmpty($tracked_entities); - - // There are no more revisions associated with 'workspace_1'. - $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node'); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE); $this->assertCount(0, $associated_revisions); - - // There should still be 2 revisions associated with 'live'. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(2, $live_revisions); + $node_revision_count = $node_storage + ->getQuery() + ->allRevisions() + ->count() + ->execute(); + $this->assertEquals(0, $node_revision_count); // Create another workspace, this time with a larger number of associated // node revisions so we can test the batch purge process. @@ -164,27 +149,16 @@ public function testDeletingWorkspaces() { $workspace_2_node_1->save(); } - // Now there is one entity tracked in 'workspace_2'. - $tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id()); - $this->assertCount(1, $tracked_entities['node']); - - // One revision of this entity is in 'live'. - $live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]); - $this->assertCount(1, $live_revisions); + // The workspace should have 60 associated node revisions. + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE); + $this->assertCount(60, $associated_revisions['node']); - // The other 59 are associated with 'workspace_2'. - $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]); - $this->assertCount(59, $associated_revisions); - - // Delete the workspace and check that we still have 9 revision left to + // Delete the workspace and check that we still have 10 revision left to // delete. $workspace_2->delete(); - $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]); - $this->assertCount(9, $associated_revisions); - // The live revision is also still there. - $live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]); - $this->assertCount(1, $live_revisions); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE); + $this->assertCount(10, $associated_revisions['node']); $workspace_deleted = \Drupal::state()->get('workspace.deleted'); $this->assertCount(1, $workspace_deleted); @@ -203,94 +177,41 @@ public function testDeletingWorkspaces() { // from the "workspace.delete" state entry. \Drupal::service('cron')->run(); - $associated_revisions = $workspace_association->getTrackedEntities($workspace_2->id()); - $this->assertCount(0, $associated_revisions); - - // 'workspace_2 'is empty now. - $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE); $this->assertCount(0, $associated_revisions); - $tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id()); - $this->assertEmpty($tracked_entities); - - // The 3 revisions in 'live' remain. - $live_revisions = $this->getUnassociatedRevisions('node'); - $this->assertCount(3, $live_revisions); + $node_revision_count = $node_storage + ->getQuery() + ->allRevisions() + ->count() + ->execute(); + $this->assertEquals(0, $node_revision_count); $workspace_deleted = \Drupal::state()->get('workspace.deleted'); $this->assertCount(0, $workspace_deleted); } /** - * Tests that deleting a workspace keeps its already published content. + * Tests workspace association validation. + * + * @covers \Drupal\workspaces\Entity\WorkspaceAssociation::validate */ - public function testDeletingPublishedWorkspace() { - $admin = $this->createUser([ - 'administer nodes', - 'create workspace', - 'view any workspace', - 'delete any workspace', - ]); - $this->setCurrentUser($admin); - - $live_workspace = Workspace::create([ - 'id' => 'live', - 'label' => 'Live', - ]); - $live_workspace->save(); + public function testWorkspaceAssociationValidation() { $workspace = Workspace::create([ - 'id' => 'stage', - 'label' => 'Stage', + 'id' => 'gibbon', + 'label' => 'Gibbon', ]); $workspace->save(); - $this->workspaceManager->setActiveWorkspace($workspace); - - // Create a new node in the 'stage' workspace - $node = $this->createNode(['status' => TRUE]); - - // Create an additional workspace-specific revision for the node. - $node->setNewRevision(TRUE); - $node->save(); - - // The node should have 3 revisions now: a default and 2 pending ones. - $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3]); - $this->assertCount(3, $revisions); - $this->assertTrue($revisions[1]->isDefaultRevision()); - $this->assertFalse($revisions[2]->isDefaultRevision()); - $this->assertFalse($revisions[3]->isDefaultRevision()); - - // Publish the workspace, which should mark revision 3 as the default one - // and keep revision 2 as a 'source' draft revision. - $workspace->publish(); - $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3]); - $this->assertFalse($revisions[1]->isDefaultRevision()); - $this->assertFalse($revisions[2]->isDefaultRevision()); - $this->assertTrue($revisions[3]->isDefaultRevision()); - - // Create two new workspace-revisions for the node. - $node->setNewRevision(TRUE); - $node->save(); - $node->setNewRevision(TRUE); - $node->save(); - - // The node should now have 5 revisions. - $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3, 4, 5]); - $this->assertFalse($revisions[1]->isDefaultRevision()); - $this->assertFalse($revisions[2]->isDefaultRevision()); - $this->assertTrue($revisions[3]->isDefaultRevision()); - $this->assertFalse($revisions[4]->isDefaultRevision()); - $this->assertFalse($revisions[5]->isDefaultRevision()); - - // Delete the workspace and check that only the two new pending revisions - // were deleted by the workspace purging process. - $workspace->delete(); - - $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3, 4, 5]); - $this->assertCount(3, $revisions); - $this->assertFalse($revisions[1]->isDefaultRevision()); - $this->assertFalse($revisions[2]->isDefaultRevision()); - $this->assertTrue($revisions[3]->isDefaultRevision()); - $this->assertFalse(isset($revisions[4])); - $this->assertFalse(isset($revisions[5])); + $node = $this->createNode(); + + $workspace_association = WorkspaceAssociation::create([ + 'workspace' => $workspace, + 'target_entity_type_id' => $node->getEntityTypeId(), + 'target_entity_id' => $node->id(), + 'target_entity_revision_id' => $node->getRevisionId(), + ]); + + $violations = $workspace_association->validate(); + $this->assertCount(0, $violations); } } diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php index 1530f3865830743097c2c761d203c4c3d3f68c8b..20a7efd04e30adc75d0cec4cf401851461c09d22 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php @@ -275,7 +275,7 @@ public function testWorkspaces() { ], ]); $test_scenarios['add_published_node_in_stage'] = $revision_state; - $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 7]]; + $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 6, 7]]; // Deploying 'stage' to 'live' should simply make the latest revisions in // 'stage' the default ones in 'live'. @@ -365,9 +365,8 @@ public function testEntityQueryWithoutConditions() { $this->switchToWorkspace('stage'); // Add a workspace-specific revision to a pre-existing node. - $node = $this->entityTypeManager->getStorage('node')->load(2); - $node->title->value = 'stage - 2 - r3 - published'; - $node->save(); + $this->nodes[1]->title->value = 'stage - 2 - r3 - published'; + $this->nodes[1]->save(); $query = $this->entityTypeManager->getStorage('node')->getQuery(); $query->sort('nid'); @@ -810,7 +809,7 @@ protected function assertEntityQuery(array $expected_values, $entity_type_id) { } /** - * Checks the workspace_association records for a test scenario. + * Checks the workspace_association entries for a test scenario. * * @param array $expected * An array of expected values, as defined in ::testWorkspaces(). @@ -818,10 +817,10 @@ protected function assertEntityQuery(array $expected_values, $entity_type_id) { * The ID of the entity type that is being tested. */ protected function assertWorkspaceAssociation(array $expected, $entity_type_id) { - /** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */ - $workspace_association = \Drupal::service('workspaces.association'); + /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); foreach ($expected as $workspace_id => $expected_tracked_revision_ids) { - $tracked_entities = $workspace_association->getTrackedEntities($workspace_id, $entity_type_id); + $tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE); $tracked_revision_ids = isset($tracked_entities[$entity_type_id]) ? $tracked_entities[$entity_type_id] : []; $this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids)); } diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceInternalResourceTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceInternalResourceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..06201ce7446d31c4275a077b912fa3f7d7ca2e8e --- /dev/null +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceInternalResourceTest.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\Tests\workspaces\Kernel; + +use Drupal\Component\Plugin\Exception\PluginNotFoundException; +use Drupal\KernelTests\KernelTestBase; +use Drupal\rest\Entity\RestResourceConfig; +use Drupal\rest\RestResourceConfigInterface; + +/** + * Tests REST module with internal workspace entity types. + * + * @group workspaces + */ +class WorkspaceInternalResourceTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['user', 'serialization', 'rest', 'workspaces']; + + /** + * Tests enabling workspace associations for REST throws an exception. + * + * @see \Drupal\workspaces\Entity\WorkspaceAssociation + */ + public function testCreateWorkspaceAssociationResource() { + $this->expectException(PluginNotFoundException::class); + $this->expectExceptionMessage('The "entity:workspace_association" plugin does not exist.'); + RestResourceConfig::create([ + 'id' => 'entity.workspace_association', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['GET'], + 'formats' => ['json'], + 'authentication' => ['cookie'], + ], + ]) + ->enable() + ->save(); + } + +} diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php index 50cf4704c396d5961c450169a0eb0fcdaefaf4a7..13cff6a3dfebf3dac95c51b25ef7e5b188ed3cd6 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php @@ -35,7 +35,7 @@ protected function initializeWorkspacesModule() { $this->workspaceManager = \Drupal::service('workspaces.manager'); $this->installEntitySchema('workspace'); - $this->installSchema('workspaces', ['workspace_association']); + $this->installEntitySchema('workspace_association'); // Create two workspaces by default, 'live' and 'stage'. $this->workspaces['live'] = Workspace::create(['id' => 'live']); @@ -64,33 +64,4 @@ protected function switchToWorkspace($workspace_id) { \Drupal::service('workspaces.manager')->setActiveWorkspace($workspace); } - /** - * Returns all the revisions which are not associated with any workspace. - * - * @param string $entity_type_id - * An entity type ID to find revisions for. - * @param int[]|string[]|null $entity_ids - * (optional) An array of entity IDs to filter the results by. Defaults to - * NULL. - * - * @return array - * An array of entity IDs, keyed by revision IDs. - */ - protected function getUnassociatedRevisions($entity_type_id, $entity_ids = NULL) { - $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); - - $query = \Drupal::entityTypeManager() - ->getStorage($entity_type_id) - ->getQuery() - ->allRevisions() - ->accessCheck(FALSE) - ->notExists($entity_type->get('revision_metadata_keys')['workspace']); - - if ($entity_ids) { - $query->condition($entity_type->getKey('id'), $entity_ids, 'IN'); - } - - return $query->execute(); - } - } diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install index 2bfc003f0e102871d0d8af9ff9571b4cc9457988..c6ac304b6db36a477bb20594639e99b0cbcdb162 100644 --- a/core/modules/workspaces/workspaces.install +++ b/core/modules/workspaces/workspaces.install @@ -5,8 +5,6 @@ * Contains install, update and uninstall functions for the Workspaces module. */ -use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Field\BaseFieldDefinition; use Drupal\workspaces\Entity\Workspace; /** @@ -34,27 +32,6 @@ function workspaces_requirements($phase) { return $requirements; } -/** - * Implements hook_module_preinstall(). - */ -function workspaces_module_preinstall($module) { - if ($module !== 'workspaces') { - return; - } - - /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */ - $workspace_manager = \Drupal::service('workspaces.manager'); - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) { - $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); - if ($workspace_manager->isEntityTypeSupported($entity_type)) { - $revision_metadata_keys['workspace'] = 'workspace'; - $entity_type->set('revision_metadata_keys', $revision_metadata_keys); - $entity_definition_update_manager->updateEntityType($entity_type); - } - } -} - /** * Implements hook_install(). */ @@ -84,83 +61,3 @@ function workspaces_install() { 'uid' => $owner_id, ])->save(); } - -/** - * Implements hook_schema(). - */ -function workspaces_schema() { - $schema['workspace_association'] = [ - 'description' => 'Stores the association between entity revisions and their workspace.', - 'fields' => [ - 'workspace' => [ - 'type' => 'varchar_ascii', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The workspace ID.', - ], - 'target_entity_type_id' => [ - 'type' => 'varchar_ascii', - 'length' => EntityTypeInterface::ID_MAX_LENGTH, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The ID of the associated entity type.', - ], - 'target_entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The ID of the associated entity.', - ], - 'target_entity_revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The revision ID of the associated entity.', - ], - ], - 'indexes' => [ - 'target_entity_revision_id' => ['target_entity_revision_id'], - ], - 'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'], - ]; - - return $schema; -} - -/** - * Add the 'workspace' revision metadata field on all supported entity types. - */ -function workspaces_update_8801() { - /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */ - $workspace_manager = \Drupal::service('workspaces.manager'); - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type_id => $entity_type) { - if ($workspace_manager->isEntityTypeSupported($entity_type)) { - $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); - - if (!isset($revision_metadata_keys['workspace'])) { - // Bail out if there's an existing field called 'workspace'. - if ($entity_definition_update_manager->getFieldStorageDefinition('workspace', $entity_type_id)) { - throw new \RuntimeException("An existing 'workspace' field was found for the '$entity_type_id' entity type. Set the 'workspace' revision metadata key to use a different field name and run this update function again."); - } - - $revision_metadata_keys['workspace'] = 'workspace'; - $entity_type->set('revision_metadata_keys', $revision_metadata_keys); - $entity_definition_update_manager->updateEntityType($entity_type); - } - - $field_storage = BaseFieldDefinition::create('entity_reference') - ->setLabel(t('Workspace')) - ->setDescription(t('Indicates the workspace that this revision belongs to.')) - ->setSetting('target_type', 'workspace') - ->setInternal(TRUE) - ->setTranslatable(FALSE) - ->setRevisionable(TRUE); - - $entity_definition_update_manager->installFieldStorageDefinition($revision_metadata_keys['workspace'], $entity_type_id, 'workspaces', $field_storage); - } - } - - return t("The 'workspace' revision metadata field has been installed."); -} diff --git a/core/modules/workspaces/workspaces.module b/core/modules/workspaces/workspaces.module index c3b0d0776274589f7b94c4621be88363b90eee45..d47985b223ded51e652cebe5be2d964adc8bbe70 100644 --- a/core/modules/workspaces/workspaces.module +++ b/core/modules/workspaces/workspaces.module @@ -8,7 +8,6 @@ use Drupal\Component\Serialization\Json; use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; @@ -67,15 +66,6 @@ function workspaces_field_info_alter(&$definitions) { ->fieldInfoAlter($definitions); } -/** - * Implements hook_entity_base_field_info(). - */ -function workspaces_entity_base_field_info(EntityTypeInterface $entity_type) { - return \Drupal::service('class_resolver') - ->getInstanceFromDefinition(EntityTypeInfo::class) - ->entityBaseFieldInfo($entity_type); -} - /** * Implements hook_entity_preload(). */ diff --git a/core/modules/workspaces/workspaces.post_update.php b/core/modules/workspaces/workspaces.post_update.php index ac45f3f93285cf1b9b61a7376beab24725123626..0df5add2cb8dde0a926effbd8d899c6e322c3d27 100644 --- a/core/modules/workspaces/workspaces.post_update.php +++ b/core/modules/workspaces/workspaces.post_update.php @@ -5,11 +5,6 @@ * Post update functions for the Workspaces module. */ -use Drupal\Core\Entity\ContentEntityNullStorage; -use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\Sql\SqlContentEntityStorage; -use Drupal\Core\Site\Settings; - /** * Clear caches due to access changes. */ @@ -24,122 +19,3 @@ function workspaces_post_update_remove_default_workspace() { $workspace->delete(); } } - -/** - * Move the workspace association data to an entity field and a custom table. - */ -function workspaces_post_update_move_association_data(&$sandbox) { - $database = \Drupal::database(); - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - $entity_type_manager = \Drupal::entityTypeManager(); - $entity_type = $entity_definition_update_manager->getEntityType('workspace_association'); - - // We can't migrate the workspace association data if the entity type is not - // using its default storage. - if ($entity_type->getHandlerClasses()['storage'] !== 'Drupal\workspaces\WorkspaceAssociationStorage') { - return; - } - - // Since the custom storage class doesn't exist anymore, we have to use core's - // default storage. - $entity_type->setStorageClass(SqlContentEntityStorage::class); - - // If 'progress' is not set, this will be the first run of the batch. - if (!isset($sandbox['progress'])) { - $sandbox['progress'] = 0; - $sandbox['current_id'] = -1; - - // Create a temporary table for the new workspace_association index. - $schema = [ - 'description' => 'Stores the association between entity revisions and their workspace.', - 'fields' => [ - 'workspace' => [ - 'type' => 'varchar_ascii', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The workspace ID.', - ], - 'target_entity_type_id' => [ - 'type' => 'varchar_ascii', - 'length' => EntityTypeInterface::ID_MAX_LENGTH, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The ID of the associated entity type.', - ], - 'target_entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The ID of the associated entity.', - ], - 'target_entity_revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The revision ID of the associated entity.', - ], - ], - 'indexes' => [ - 'target_entity_revision_id' => ['target_entity_revision_id'], - ], - 'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'], - ]; - if ($database->schema()->tableExists('tmp_workspace_association')) { - $database->schema()->dropTable('tmp_workspace_association'); - } - $database->schema()->createTable('tmp_workspace_association', $schema); - - // Copy all the data from the base table of the 'workspace_association' - // entity type to the temporary association table. - $select = $database->select($entity_type->getBaseTable()) - ->fields($entity_type->getBaseTable(), ['workspace', 'target_entity_type_id', 'target_entity_id', 'target_entity_revision_id']); - $database->insert('tmp_workspace_association')->from($select)->execute(); - } - - $table_name = $entity_type->getRevisionTable(); - $revision_field_name = 'revision_id'; - - // Get the next entity association revision records to migrate. - $step_size = Settings::get('entity_update_batch_size', 50); - $workspace_association_records = $database->select($table_name, 't') - ->condition("t.$revision_field_name", $sandbox['current_id'], '>') - ->fields('t') - ->orderBy($revision_field_name, 'ASC') - ->range(0, $step_size) - ->execute() - ->fetchAll(); - - foreach ($workspace_association_records as $record) { - // Set the workspace reference on the tracked entity revision. - /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */ - $revision = $entity_type_manager->getStorage($record->target_entity_type_id)->loadRevision($record->target_entity_revision_id); - $revision->set('workspace', $record->workspace); - $revision->setSyncing(TRUE); - $revision->save(); - - $sandbox['progress']++; - $sandbox['current_id'] = $record->{$revision_field_name}; - } - - // Get an updated count of workspace_association revisions that still need to - // be migrated to the new storage. - $missing = $database->select($table_name, 't') - ->condition("t.$revision_field_name", $sandbox['current_id'], '>') - ->orderBy($revision_field_name, 'ASC') - ->countQuery() - ->execute() - ->fetchField(); - $sandbox['#finished'] = $missing ? $sandbox['progress'] / ($sandbox['progress'] + (int) $missing) : 1; - - // Uninstall the 'workspace_association' entity type and rename the temporary - // table. - if ($sandbox['#finished'] == 1) { - $entity_type->setStorageClass(ContentEntityNullStorage::class); - $entity_definition_update_manager->uninstallEntityType($entity_type); - $database->schema()->dropTable('workspace_association'); - $database->schema()->dropTable('workspace_association_revision'); - - $database->schema()->renameTable('tmp_workspace_association', 'workspace_association'); - } -} diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml index 45f1d50746141b382da9faf3884bdcdf826716a1..9823d5ce2ec9125ac961abb408f1f66f8a3ca9fc 100644 --- a/core/modules/workspaces/workspaces.services.yml +++ b/core/modules/workspaces/workspaces.services.yml @@ -1,17 +1,12 @@ services: 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'] tags: - { name: service_id_collector, tag: workspace_negotiator } workspaces.operation_factory: class: Drupal\workspaces\WorkspaceOperationFactory - arguments: ['@entity_type.manager', '@database', '@workspaces.manager', '@workspaces.association'] - workspaces.association: - class: Drupal\workspaces\WorkspaceAssociation - arguments: ['@database', '@entity_type.manager'] - tags: - - { name: backend_overridable } + arguments: ['@entity_type.manager', '@database', '@workspaces.manager'] workspaces.negotiator.session: class: Drupal\workspaces\Negotiator\SessionWorkspaceNegotiator @@ -30,12 +25,6 @@ services: tags: - { name: access_check, applies_to: _has_active_workspace } - workspaces.entity_schema_listener: - class: Drupal\workspaces\EventSubscriber\EntitySchemaSubscriber - arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.manager'] - tags: - - { name: 'event_subscriber' } - cache_context.workspace: class: Drupal\workspaces\WorkspaceCacheContext arguments: ['@workspaces.manager']