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
parent
65491ce4f8
commit
6caa28c094
|
|
@ -1454,3 +1454,25 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
<?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,6 +34,13 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* The workspace association service.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationInterface
|
||||
*/
|
||||
protected $workspaceAssociation;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityOperations instance.
|
||||
*
|
||||
|
|
@ -41,10 +48,13 @@ 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) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->workspaceManager = $workspace_manager;
|
||||
$this->workspaceAssociation = $workspace_association;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -53,7 +63,8 @@ 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.manager'),
|
||||
$container->get('workspaces.association')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -74,31 +85,13 @@ 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.
|
||||
$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) {
|
||||
if ($tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->workspaceManager->getActiveWorkspace()->id(), $entity_type_id, $ids)) {
|
||||
/** @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.
|
||||
$swap_revision_ids = array_column($results, $max_revision_id);
|
||||
foreach ($storage->loadMultipleRevisions($swap_revision_ids) as $revision) {
|
||||
foreach ($storage->loadMultipleRevisions(array_keys($tracked_entities[$entity_type_id])) as $revision) {
|
||||
$entities[$revision->id()] = $revision;
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +135,10 @@ 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
|
||||
|
|
@ -174,7 +171,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
$this->trackEntity($entity);
|
||||
$this->workspaceAssociation->trackEntity($entity, $this->workspaceManager->getActiveWorkspace());
|
||||
|
||||
// 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
|
||||
|
|
@ -211,7 +208,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
// Only track new revisions.
|
||||
/** @var \Drupal\Core\Entity\RevisionableInterface $entity */
|
||||
if ($entity->getLoadedRevisionId() != $entity->getRevisionId()) {
|
||||
$this->trackEntity($entity);
|
||||
$this->workspaceAssociation->trackEntity($entity, $this->workspaceManager->getActiveWorkspace());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,51 +237,6 @@ 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.
|
||||
*
|
||||
|
|
@ -298,7 +250,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
* @see hook_form_alter()
|
||||
*/
|
||||
public function entityFormAlter(array &$form, FormStateInterface $form_state, $form_id) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
/** @var \Drupal\Core\Entity\RevisionableInterface $entity */
|
||||
$entity = $form_state->getFormObject()->getEntity();
|
||||
if (!$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) {
|
||||
return;
|
||||
|
|
@ -318,9 +270,7 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
$form['#entity_builders'][] = [get_called_class(), 'entityFormEntityBuild'];
|
||||
}
|
||||
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
|
||||
$workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
|
||||
if ($workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity)) {
|
||||
if ($workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity)) {
|
||||
// An entity can only be edited in one workspace.
|
||||
$workspace_id = reset($workspace_ids);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
|
|
@ -66,6 +69,10 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,4 +91,30 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
<?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,8 +2,13 @@
|
|||
|
||||
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.
|
||||
|
|
@ -19,14 +24,52 @@ 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);
|
||||
$source_rev_diff = $this->entityTypeManager->getStorage('workspace_association')->getTrackedEntities($this->entity->id());
|
||||
$tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->entity->id());
|
||||
$items = [];
|
||||
foreach ($source_rev_diff as $entity_type_id => $revision_ids) {
|
||||
foreach (array_keys($tracked_entities) as $entity_type_id => $entity_ids) {
|
||||
$revision_ids = $this->workspaceAssociation->getAssociatedRevisions($this->entity->id(), $entity_type_id, $entity_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\WorkspaceAssociationStorageInterface;
|
||||
use Drupal\workspaces\WorkspaceAssociationInterface;
|
||||
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 storage.
|
||||
* The workspace association service.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationStorageInterface
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationInterface
|
||||
*/
|
||||
protected $workspaceAssociationStorage;
|
||||
protected $workspaceAssociation;
|
||||
|
||||
/**
|
||||
* Creates a new DeletedWorkspaceConstraintValidator instance.
|
||||
*
|
||||
* @param \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage
|
||||
* The workspace association storage.
|
||||
* @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
|
||||
* The workspace association service.
|
||||
*/
|
||||
public function __construct(WorkspaceAssociationStorageInterface $workspace_association_storage) {
|
||||
$this->workspaceAssociationStorage = $workspace_association_storage;
|
||||
public function __construct(WorkspaceAssociationInterface $workspace_association) {
|
||||
$this->workspaceAssociation = $workspace_association;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -35,7 +35,7 @@ class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements
|
|||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('workspace_association')
|
||||
$container->get('workspaces.association')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -49,14 +49,7 @@ class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements
|
|||
return;
|
||||
}
|
||||
|
||||
$count = $this->workspaceAssociationStorage
|
||||
->getQuery()
|
||||
->allRevisions()
|
||||
->accessCheck(FALSE)
|
||||
->condition('workspace', $value->getEntity()->id())
|
||||
->count()
|
||||
->execute();
|
||||
if ($count) {
|
||||
if ($this->workspaceAssociation->getTrackedEntities($value->getEntity()->id())) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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;
|
||||
|
|
@ -28,6 +29,13 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp
|
|||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* The workspace association service.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationInterface
|
||||
*/
|
||||
protected $workspaceAssociation;
|
||||
|
||||
/**
|
||||
* Constructs an EntityUntranslatableFieldsConstraintValidator object.
|
||||
*
|
||||
|
|
@ -35,10 +43,13 @@ 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) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->workspaceManager = $workspace_manager;
|
||||
$this->workspaceAssociation = $workspace_association;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -47,7 +58,8 @@ 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.manager'),
|
||||
$container->get('workspaces.association')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -57,9 +69,7 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp
|
|||
public function validate($entity, Constraint $constraint) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
if (isset($entity) && !$entity->isNew()) {
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
|
||||
$workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
|
||||
$workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity);
|
||||
$workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity);
|
||||
$active_workspace = $this->workspaceManager->getActiveWorkspace();
|
||||
|
||||
if ($workspace_ids && (!$active_workspace || !in_array($active_workspace->id(), $workspace_ids, TRUE))) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
<?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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?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);
|
||||
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<?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,6 +83,13 @@ class WorkspaceManager implements WorkspaceManagerInterface {
|
|||
*/
|
||||
protected $classResolver;
|
||||
|
||||
/**
|
||||
* The workspace association service.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationInterface
|
||||
*/
|
||||
protected $workspaceAssociation;
|
||||
|
||||
/**
|
||||
* The workspace negotiator service IDs.
|
||||
*
|
||||
|
|
@ -114,10 +121,12 @@ 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, array $negotiator_ids) {
|
||||
public function __construct(RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, MemoryCacheInterface $entity_memory_cache, AccountProxyInterface $current_user, StateInterface $state, LoggerInterface $logger, ClassResolverInterface $class_resolver, WorkspaceAssociationInterface $workspace_association, array $negotiator_ids) {
|
||||
$this->requestStack = $request_stack;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityMemoryCache = $entity_memory_cache;
|
||||
|
|
@ -125,6 +134,7 @@ class WorkspaceManager implements WorkspaceManagerInterface {
|
|||
$this->state = $state;
|
||||
$this->logger = $logger;
|
||||
$this->classResolver = $class_resolver;
|
||||
$this->workspaceAssociation = $workspace_association;
|
||||
$this->negotiatorIds = $negotiator_ids;
|
||||
}
|
||||
|
||||
|
|
@ -305,67 +315,35 @@ class WorkspaceManager implements WorkspaceManagerInterface {
|
|||
|
||||
$batch_size = Settings::get('entity_update_batch_size', 50);
|
||||
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
|
||||
$workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
|
||||
|
||||
// Get the first deleted workspace from the list and delete the revisions
|
||||
// associated with it, along with the workspace_association entries.
|
||||
// associated with it, along with the workspace association records.
|
||||
$workspace_id = reset($deleted_workspace_ids);
|
||||
$workspace_association_ids = $this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size);
|
||||
$tracked_entities = $this->workspaceAssociation->getTrackedEntities($workspace_id);
|
||||
|
||||
$count = 1;
|
||||
foreach ($tracked_entities as $entity_type_id => $entities) {
|
||||
$associated_entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$associated_revisions = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id);
|
||||
foreach (array_keys($associated_revisions) as $revision_id) {
|
||||
if ($count > $batch_size) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
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 workspace_association revision.
|
||||
if ($workspace_association->isDefaultRevision()) {
|
||||
$workspace_association->delete();
|
||||
}
|
||||
else {
|
||||
$workspace_association_storage->deleteRevision($workspace_association->getRevisionId());
|
||||
}
|
||||
$associated_entity_storage->deleteRevision($revision_id);
|
||||
$count++;
|
||||
}
|
||||
// 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 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)) {
|
||||
// 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)) {
|
||||
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,6 +36,13 @@ class WorkspaceOperationFactory {
|
|||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* The workspace association service.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationInterface
|
||||
*/
|
||||
protected $workspaceAssociation;
|
||||
|
||||
/**
|
||||
* Constructs a new WorkspacePublisher.
|
||||
*
|
||||
|
|
@ -43,11 +50,16 @@ 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) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->database = $database;
|
||||
$this->workspaceManager = $workspace_manager;
|
||||
$this->workspaceAssociation = $workspace_association;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,7 +72,7 @@ class WorkspaceOperationFactory {
|
|||
* A workspace publisher object.
|
||||
*/
|
||||
public function getPublisher(WorkspaceInterface $source) {
|
||||
return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $source);
|
||||
return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $this->workspaceAssociation, $source);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,13 +36,6 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
|
|||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The workspace association storage.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationStorageInterface
|
||||
*/
|
||||
protected $workspaceAssociationStorage;
|
||||
|
||||
/**
|
||||
* The workspace manager.
|
||||
*
|
||||
|
|
@ -50,6 +43,13 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
|
|||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* The workspace association service.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceAssociationInterface
|
||||
*/
|
||||
protected $workspaceAssociation;
|
||||
|
||||
/**
|
||||
* Constructs a new WorkspacePublisher.
|
||||
*
|
||||
|
|
@ -59,12 +59,14 @@ 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, WorkspaceInterface $source) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, 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;
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +97,11 @@ 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();
|
||||
}
|
||||
|
|
@ -107,9 +114,8 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
|
|||
throw $e;
|
||||
}
|
||||
|
||||
// Notify the workspace association storage that a workspace has been
|
||||
// pushed.
|
||||
$this->workspaceAssociationStorage->postPush($this->sourceWorkspace);
|
||||
// Notify the workspace association that a workspace has been published.
|
||||
$this->workspaceAssociation->postPublish($this->sourceWorkspace);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,7 +147,7 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
|
|||
public function getDifferringRevisionIdsOnTarget() {
|
||||
$target_revision_difference = [];
|
||||
|
||||
$tracked_entities = $this->workspaceAssociationStorage->getTrackedEntities($this->sourceWorkspace->id());
|
||||
$tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id());
|
||||
foreach ($tracked_entities as $entity_type_id => $tracked_revisions) {
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
|
||||
|
|
@ -171,7 +177,7 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
|
|||
*/
|
||||
public function getDifferringRevisionIdsOnSource() {
|
||||
// Get the Workspace association revisions which haven't been pushed yet.
|
||||
return $this->workspaceAssociationStorage->getTrackedEntities($this->sourceWorkspace->id());
|
||||
return $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
BIN
core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php
vendored
Normal file
BIN
core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php
vendored
Normal file
Binary file not shown.
|
|
@ -0,0 +1,105 @@
|
|||
<?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,6 +36,12 @@ class WorkspacesUninstallTest extends BrowserTestBase {
|
|||
$this->drupalPostForm(NULL, [], 'Uninstall');
|
||||
$session->pageTextContains('The selected modules have been uninstalled.');
|
||||
$session->pageTextNotContains('Workspaces');
|
||||
|
||||
$this->assertFalse(\Drupal::database()->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,7 +33,6 @@ class WorkspaceAccessTest extends KernelTestBase {
|
|||
$this->installSchema('system', ['sequences']);
|
||||
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installEntitySchema('workspace_association');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
// User 1.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ 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.
|
||||
|
|
@ -19,6 +18,7 @@ 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->installEntitySchema('workspace_association');
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
$this->installEntitySchema('node');
|
||||
|
||||
$this->installConfig(['filter', 'node', 'system']);
|
||||
|
|
@ -91,10 +91,8 @@ class WorkspaceCRUDTest extends KernelTestBase {
|
|||
]);
|
||||
$this->setCurrentUser($admin);
|
||||
|
||||
/** @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');
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
|
||||
// Create a workspace with a very small number of associated node revisions.
|
||||
$workspace_1 = Workspace::create([
|
||||
|
|
@ -106,6 +104,12 @@ 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();
|
||||
|
|
@ -114,9 +118,17 @@ class WorkspaceCRUDTest extends KernelTestBase {
|
|||
$workspace_1_node_2->save();
|
||||
}
|
||||
|
||||
// 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']);
|
||||
// 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);
|
||||
|
||||
// Check that we are allowed to delete the workspace.
|
||||
$this->assertTrue($workspace_1->access('delete', $admin));
|
||||
|
|
@ -125,14 +137,17 @@ class WorkspaceCRUDTest extends KernelTestBase {
|
|||
// entities and all the node revisions have been deleted as well.
|
||||
$workspace_1->delete();
|
||||
|
||||
$associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE);
|
||||
// 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');
|
||||
$this->assertCount(0, $associated_revisions);
|
||||
$node_revision_count = $node_storage
|
||||
->getQuery()
|
||||
->allRevisions()
|
||||
->count()
|
||||
->execute();
|
||||
$this->assertEquals(0, $node_revision_count);
|
||||
|
||||
// There should still be 2 revisions associated with 'live'.
|
||||
$live_revisions = $this->getUnassociatedRevisions('node');
|
||||
$this->assertCount(2, $live_revisions);
|
||||
|
||||
// Create another workspace, this time with a larger number of associated
|
||||
// node revisions so we can test the batch purge process.
|
||||
|
|
@ -149,16 +164,27 @@ class WorkspaceCRUDTest extends KernelTestBase {
|
|||
$workspace_2_node_1->save();
|
||||
}
|
||||
|
||||
// The workspace should have 60 associated node revisions.
|
||||
$associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
|
||||
$this->assertCount(60, $associated_revisions['node']);
|
||||
// Now there is one entity tracked in 'workspace_2'.
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id());
|
||||
$this->assertCount(1, $tracked_entities['node']);
|
||||
|
||||
// Delete the workspace and check that we still have 10 revision left to
|
||||
// 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.
|
||||
$workspace_2->delete();
|
||||
$associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
|
||||
$this->assertCount(9, $associated_revisions);
|
||||
|
||||
$associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
|
||||
$this->assertCount(10, $associated_revisions['node']);
|
||||
// The live revision is also still there.
|
||||
$live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]);
|
||||
$this->assertCount(1, $live_revisions);
|
||||
|
||||
$workspace_deleted = \Drupal::state()->get('workspace.deleted');
|
||||
$this->assertCount(1, $workspace_deleted);
|
||||
|
|
@ -177,41 +203,94 @@ class WorkspaceCRUDTest extends KernelTestBase {
|
|||
// from the "workspace.delete" state entry.
|
||||
\Drupal::service('cron')->run();
|
||||
|
||||
$associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
|
||||
$associated_revisions = $workspace_association->getTrackedEntities($workspace_2->id());
|
||||
$this->assertCount(0, $associated_revisions);
|
||||
$node_revision_count = $node_storage
|
||||
->getQuery()
|
||||
->allRevisions()
|
||||
->count()
|
||||
->execute();
|
||||
$this->assertEquals(0, $node_revision_count);
|
||||
|
||||
// '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);
|
||||
|
||||
$workspace_deleted = \Drupal::state()->get('workspace.deleted');
|
||||
$this->assertCount(0, $workspace_deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests workspace association validation.
|
||||
*
|
||||
* @covers \Drupal\workspaces\Entity\WorkspaceAssociation::validate
|
||||
* Tests that deleting a workspace keeps its already published content.
|
||||
*/
|
||||
public function testWorkspaceAssociationValidation() {
|
||||
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();
|
||||
$workspace = Workspace::create([
|
||||
'id' => 'gibbon',
|
||||
'label' => 'Gibbon',
|
||||
'id' => 'stage',
|
||||
'label' => 'Stage',
|
||||
]);
|
||||
$workspace->save();
|
||||
$node = $this->createNode();
|
||||
$this->workspaceManager->setActiveWorkspace($workspace);
|
||||
|
||||
$workspace_association = WorkspaceAssociation::create([
|
||||
'workspace' => $workspace,
|
||||
'target_entity_type_id' => $node->getEntityTypeId(),
|
||||
'target_entity_id' => $node->id(),
|
||||
'target_entity_revision_id' => $node->getRevisionId(),
|
||||
]);
|
||||
// Create a new node in the 'stage' workspace
|
||||
$node = $this->createNode(['status' => TRUE]);
|
||||
|
||||
$violations = $workspace_association->validate();
|
||||
$this->assertCount(0, $violations);
|
||||
// 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]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, 6, 7]];
|
||||
$expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 7]];
|
||||
|
||||
// Deploying 'stage' to 'live' should simply make the latest revisions in
|
||||
// 'stage' the default ones in 'live'.
|
||||
|
|
@ -365,8 +365,9 @@ class WorkspaceIntegrationTest extends KernelTestBase {
|
|||
$this->switchToWorkspace('stage');
|
||||
|
||||
// Add a workspace-specific revision to a pre-existing node.
|
||||
$this->nodes[1]->title->value = 'stage - 2 - r3 - published';
|
||||
$this->nodes[1]->save();
|
||||
$node = $this->entityTypeManager->getStorage('node')->load(2);
|
||||
$node->title->value = 'stage - 2 - r3 - published';
|
||||
$node->save();
|
||||
|
||||
$query = $this->entityTypeManager->getStorage('node')->getQuery();
|
||||
$query->sort('nid');
|
||||
|
|
@ -809,7 +810,7 @@ class WorkspaceIntegrationTest extends KernelTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks the workspace_association entries for a test scenario.
|
||||
* Checks the workspace_association records for a test scenario.
|
||||
*
|
||||
* @param array $expected
|
||||
* An array of expected values, as defined in ::testWorkspaces().
|
||||
|
|
@ -817,10 +818,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\WorkspaceAssociationStorageInterface $workspace_association_storage */
|
||||
$workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
foreach ($expected as $workspace_id => $expected_tracked_revision_ids) {
|
||||
$tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE);
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_id, $entity_type_id);
|
||||
$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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
<?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->installEntitySchema('workspace_association');
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
// Create two workspaces by default, 'live' and 'stage'.
|
||||
$this->workspaces['live'] = Workspace::create(['id' => 'live']);
|
||||
|
|
@ -64,4 +64,33 @@ 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,6 +5,8 @@
|
|||
* 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;
|
||||
|
||||
/**
|
||||
|
|
@ -32,6 +34,27 @@ 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().
|
||||
*/
|
||||
|
|
@ -61,3 +84,83 @@ 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,6 +8,7 @@
|
|||
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;
|
||||
|
|
@ -66,6 +67,15 @@ 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,6 +5,11 @@
|
|||
* 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.
|
||||
*/
|
||||
|
|
@ -19,3 +24,122 @@ 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,12 +1,17 @@
|
|||
services:
|
||||
workspaces.manager:
|
||||
class: Drupal\workspaces\WorkspaceManager
|
||||
arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver']
|
||||
arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver', '@workspaces.association']
|
||||
tags:
|
||||
- { name: service_id_collector, tag: workspace_negotiator }
|
||||
workspaces.operation_factory:
|
||||
class: Drupal\workspaces\WorkspaceOperationFactory
|
||||
arguments: ['@entity_type.manager', '@database', '@workspaces.manager']
|
||||
arguments: ['@entity_type.manager', '@database', '@workspaces.manager', '@workspaces.association']
|
||||
workspaces.association:
|
||||
class: Drupal\workspaces\WorkspaceAssociation
|
||||
arguments: ['@database', '@entity_type.manager']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
|
||||
workspaces.negotiator.session:
|
||||
class: Drupal\workspaces\Negotiator\SessionWorkspaceNegotiator
|
||||
|
|
@ -25,6 +30,12 @@ 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