Revert "Issue #3062434 by amateescu, pmelab, blazey, catch, Sam152, Leksat: Track the workspace of a revision in a base field and convert the workspace_association entity type to a custom index"
This reverts commit 17bd9ce08f.
			
			
				merge-requests/55/head
			
			
		
							parent
							
								
									a68ff42323
								
							
						
					
					
						commit
						ade7b950a1
					
				| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
  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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
    // 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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
      // 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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
      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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
    // 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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
   * @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 @@ class EntityOperations implements ContainerInjectionInterface {
 | 
			
		|||
      $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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ class EntityTypeInfo implements ContainerInjectionInterface {
 | 
			
		|||
    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 @@ class EntityTypeInfo implements ContainerInjectionInterface {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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]);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ use Symfony\Component\Validator\ConstraintValidator;
 | 
			
		|||
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 @@ class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements
 | 
			
		|||
   */
 | 
			
		||||
  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 @@ class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements
 | 
			
		|||
      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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ namespace Drupal\workspaces\Plugin\Validation\Constraint;
 | 
			
		|||
 | 
			
		||||
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 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp
 | 
			
		|||
  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 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp
 | 
			
		|||
  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))) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 @@ class WorkspaceManager implements WorkspaceManagerInterface {
 | 
			
		|||
    $this->state = $state;
 | 
			
		||||
    $this->logger = $logger;
 | 
			
		||||
    $this->classResolver = $class_resolver;
 | 
			
		||||
    $this->workspaceAssociation = $workspace_association;
 | 
			
		||||
    $this->negotiatorIds = $negotiator_ids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -315,35 +305,67 @@ class WorkspaceManager implements WorkspaceManagerInterface {
 | 
			
		|||
 | 
			
		||||
    $batch_size = Settings::get('entity_update_batch_size', 50);
 | 
			
		||||
 | 
			
		||||
    // Get the first deleted workspace from the list and delete the revisions
 | 
			
		||||
    // associated with it, along with the workspace association records.
 | 
			
		||||
    $workspace_id = reset($deleted_workspace_ids);
 | 
			
		||||
    $tracked_entities = $this->workspaceAssociation->getTrackedEntities($workspace_id);
 | 
			
		||||
    /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
 | 
			
		||||
    $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
 | 
			
		||||
 | 
			
		||||
    $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;
 | 
			
		||||
    // Get the first deleted workspace from the list and delete the revisions
 | 
			
		||||
    // associated with it, along with the workspace_association entries.
 | 
			
		||||
    $workspace_id = reset($deleted_workspace_ids);
 | 
			
		||||
    $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.
 | 
			
		||||
        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 associated entity revision.
 | 
			
		||||
        $associated_entity_storage->deleteRevision($revision_id);
 | 
			
		||||
        $count++;
 | 
			
		||||
        // 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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ class WorkspaceOperationFactory {
 | 
			
		|||
   *   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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,13 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
 | 
			
		|||
   */
 | 
			
		||||
  protected $database;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The workspace association storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\workspaces\WorkspaceAssociationStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $workspaceAssociationStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The workspace manager.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			@ -43,13 +50,6 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
 | 
			
		|||
   */
 | 
			
		||||
  protected $workspaceManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The workspace association service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\workspaces\WorkspaceAssociationInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $workspaceAssociation;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
 | 
			
		|||
            // 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 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
 | 
			
		|||
      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 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
 | 
			
		|||
  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 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
 | 
			
		|||
   */
 | 
			
		||||
  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());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -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'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,12 +36,6 @@ class WorkspacesUninstallTest extends BrowserTestBase {
 | 
			
		|||
    $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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,7 @@ class WorkspaceAccessTest extends KernelTestBase {
 | 
			
		|||
    $this->installSchema('system', ['sequences']);
 | 
			
		||||
 | 
			
		||||
    $this->installEntitySchema('workspace');
 | 
			
		||||
    $this->installEntitySchema('workspace_association');
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
 | 
			
		||||
    // User 1.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
 | 
			
		|||
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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
    $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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
    ]);
 | 
			
		||||
    $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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
 | 
			
		||||
    $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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
      $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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
    // 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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
      $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']);
 | 
			
		||||
    // The workspace should have 60 associated node revisions.
 | 
			
		||||
    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
 | 
			
		||||
    $this->assertCount(60, $associated_revisions['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 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 @@ class WorkspaceCRUDTest extends KernelTestBase {
 | 
			
		|||
    // from the "workspace.delete" state entry.
 | 
			
		||||
    \Drupal::service('cron')->run();
 | 
			
		||||
 | 
			
		||||
    $associated_revisions = $workspace_association->getTrackedEntities($workspace_2->id());
 | 
			
		||||
    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
 | 
			
		||||
    $this->assertCount(0, $associated_revisions);
 | 
			
		||||
 | 
			
		||||
    // 'workspace_2 'is empty now.
 | 
			
		||||
    $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
 | 
			
		||||
    $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);
 | 
			
		||||
    $node = $this->createNode();
 | 
			
		||||
 | 
			
		||||
    // Create a new node in the 'stage' workspace
 | 
			
		||||
    $node = $this->createNode(['status' => TRUE]);
 | 
			
		||||
    $workspace_association = WorkspaceAssociation::create([
 | 
			
		||||
      'workspace' => $workspace,
 | 
			
		||||
      'target_entity_type_id' => $node->getEntityTypeId(),
 | 
			
		||||
      'target_entity_id' => $node->id(),
 | 
			
		||||
      'target_entity_revision_id' => $node->getRevisionId(),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // 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]));
 | 
			
		||||
    $violations = $workspace_association->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -275,7 +275,7 @@ class WorkspaceIntegrationTest extends KernelTestBase {
 | 
			
		|||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $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 @@ class WorkspaceIntegrationTest extends KernelTestBase {
 | 
			
		|||
    $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 @@ class WorkspaceIntegrationTest extends KernelTestBase {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 @@ class WorkspaceIntegrationTest extends KernelTestBase {
 | 
			
		|||
   *   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));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ trait WorkspaceTestTrait {
 | 
			
		|||
    $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 @@ trait WorkspaceTestTrait {
 | 
			
		|||
    \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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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().
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue