diff --git a/core/modules/workspace/css/workspace.toolbar.css b/core/modules/workspace/css/workspace.toolbar.css index 81ee1f9..9c69720 100644 --- a/core/modules/workspace/css/workspace.toolbar.css +++ b/core/modules/workspace/css/workspace.toolbar.css @@ -4,14 +4,14 @@ */ /* Tab appearance. */ -.toolbar .toolbar-bar .workspace-toolbar-tab.toolbar-tab { +.toolbar .toolbar-bar .workspace-toolbar-tab { float: right; /* LTR */ background-color: #e09600; } -[dir="rtl"] .toolbar .toolbar-bar .workspace-toolbar-tab.toolbar-tab { +[dir="rtl"] .toolbar .toolbar-bar .workspace-toolbar-tab { float: left; } -.toolbar .toolbar-bar .workspace-toolbar-tab.toolbar-tab.is-live { +.toolbar .toolbar-bar .workspace-toolbar-tab--is-default { background-color: #77b259; } @@ -40,15 +40,15 @@ /* Individual workspace links */ .toolbar-horizontal .toolbar-tray .toolbar-menu li + li { - border-left: 1px solid #dddddd; /* LTR */ + border-left: 1px solid #ddd; /* LTR */ } [dir="rtl"] .toolbar-horizontal .toolbar-tray .toolbar-menu li + li { border-left: 0 none; - border-right: 1px solid #dddddd; + border-right: 1px solid #ddd; } .toolbar-horizontal .toolbar-tray .toolbar-menu li:last-child { - border-right: 1px solid #dddddd; /* LTR */ + border-right: 1px solid #ddd; } [dir="rtl"] .toolbar-horizontal .toolbar-tray .toolbar-menu li:last-child { - border-left: 1px solid #dddddd; + border-left: 1px solid #ddd; } diff --git a/core/modules/workspace/src/Entity/Workspace.php b/core/modules/workspace/src/Entity/Workspace.php index 86fcec3..beba88a 100644 --- a/core/modules/workspace/src/Entity/Workspace.php +++ b/core/modules/workspace/src/Entity/Workspace.php @@ -10,7 +10,6 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\user\UserInterface; use Drupal\workspace\WorkspaceInterface; -use Drupal\workspace\WorkspaceManager; /** * The workspace entity class. @@ -54,10 +53,10 @@ * }, * links = { * "add-form" = "/admin/config/workflow/workspace/add", - * "edit-form" = "/admin/config/workflow/workspace/{workspace}/edit", - * "delete-form" = "/admin/config/workflow/workspace/{workspace}/delete", - * "activate-form" = "/admin/config/workflow/workspace/{workspace}/activate", - * "deploy-form" = "/admin/config/workflow/workspace/{workspace}/deploy", + * "edit-form" = "/admin/config/workflow/workspace/manage/{workspace}/edit", + * "delete-form" = "/admin/config/workflow/workspace/manage/{workspace}/delete", + * "activate-form" = "/admin/config/workflow/workspace/manage/{workspace}/activate", + * "deploy-form" = "/admin/config/workflow/workspace/manage/{workspace}/deploy", * "collection" = "/admin/config/workflow/workspace", * }, * ) @@ -144,21 +143,21 @@ public function getRepositoryHandler() { * {@inheritdoc} */ public function isDefaultWorkspace() { - return $this->id() === WorkspaceManager::DEFAULT_WORKSPACE; + return $this->id() === static::DEFAULT_WORKSPACE; } /** * {@inheritdoc} */ - public function setCreatedTime($created) { - return $this->set('created', (int) $created); + public function getCreatedTime() { + return $this->get('created')->value; } /** * {@inheritdoc} */ - public function getStartTime() { - return $this->get('created')->value; + public function setCreatedTime($created) { + return $this->set('created', (int) $created); } /** @@ -199,7 +198,7 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti // be purged on cron. $state = \Drupal::state(); $deleted_workspace_ids = $state->get('workspace.deleted', []); - unset($entities[WorkspaceManager::DEFAULT_WORKSPACE]); + unset($entities[static::DEFAULT_WORKSPACE]); $deleted_workspace_ids += array_combine(array_keys($entities), array_keys($entities)); $state->set('workspace.deleted', $deleted_workspace_ids); diff --git a/core/modules/workspace/src/Entity/WorkspaceAssociation.php b/core/modules/workspace/src/Entity/WorkspaceAssociation.php index de58c45..d49a5f4 100644 --- a/core/modules/workspace/src/Entity/WorkspaceAssociation.php +++ b/core/modules/workspace/src/Entity/WorkspaceAssociation.php @@ -13,6 +13,7 @@ * @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( @@ -52,20 +53,20 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setRevisionable(TRUE) ->addConstraint('workspace', []); - $fields['content_entity_type_id'] = BaseFieldDefinition::create('string') + $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['content_entity_id'] = BaseFieldDefinition::create('integer') + $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['content_entity_revision_id'] = BaseFieldDefinition::create('integer') + $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) diff --git a/core/modules/workspace/src/EntityAccess.php b/core/modules/workspace/src/EntityAccess.php index b4c9405..958c436 100644 --- a/core/modules/workspace/src/EntityAccess.php +++ b/core/modules/workspace/src/EntityAccess.php @@ -75,7 +75,7 @@ public function entityOperationAccess(EntityInterface $entity, $operation, Accou // Workspaces themselves are handled by their own access handler and we // should not try to do any access checks for entity types that can not // belong to a workspace. - if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->entityTypeCanBelongToWorkspaces($entity->getEntityType())) { + if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) { return AccessResult::neutral(); } @@ -102,7 +102,7 @@ public function entityCreateAccess(AccountInterface $account, array $context, $e // should not try to do any access checks for entity types that can not // belong to a workspace. $entity_type = $this->entityTypeManager->getDefinition($context['entity_type_id']); - if ($entity_type->id() === 'workspace' || !$this->workspaceManager->entityTypeCanBelongToWorkspaces($entity_type)) { + if ($entity_type->id() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity_type)) { return AccessResult::neutral(); } diff --git a/core/modules/workspace/src/EntityOperations.php b/core/modules/workspace/src/EntityOperations.php index b33c97c..24887ad 100644 --- a/core/modules/workspace/src/EntityOperations.php +++ b/core/modules/workspace/src/EntityOperations.php @@ -3,8 +3,11 @@ namespace Drupal\workspace; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -14,6 +17,8 @@ */ class EntityOperations implements ContainerInjectionInterface { + use StringTranslationTrait; + /** * The entity type manager service. * @@ -67,15 +72,16 @@ public function entityLoad(array &$entities, $entity_type_id) { // current active workspace. If an entity has multiple revisions set for a // workspace, only the one with the highest ID is returned. $entity_ids = array_keys($entities); - $max_revision_id = 'max_content_entity_revision_id'; + $max_revision_id = 'max_target_entity_revision_id'; $results = $this->entityTypeManager ->getStorage('workspace_association') ->getAggregateQuery() + ->accessCheck(FALSE) ->allRevisions() - ->aggregate('content_entity_revision_id', 'MAX', NULL, $max_revision_id) - ->groupBy('content_entity_id') - ->condition('content_entity_type_id', $entity_type_id) - ->condition('content_entity_id', $entity_ids, 'IN') + ->aggregate('target_entity_revision_id', 'MAX', NULL, $max_revision_id) + ->groupBy('target_entity_id') + ->condition('target_entity_type_id', $entity_type_id) + ->condition('target_entity_id', $entity_ids, 'IN') ->condition('workspace', $this->workspaceManager->getActiveWorkspace()->id()) ->execute(); @@ -87,7 +93,7 @@ public function entityLoad(array &$entities, $entity_type_id) { // https://www.drupal.org/project/drupal/issues/2928888 is resolved. if ($results) { $results = array_filter($results, function ($result) use ($entities, $max_revision_id) { - return $entities[$result['content_entity_id']]->getRevisionId() != $result[$max_revision_id]; + return $entities[$result['target_entity_id']]->getRevisionId() != $result[$max_revision_id]; }); } @@ -140,6 +146,8 @@ public function entityPresave(EntityInterface $entity) { if ($entity->isNew() && $entity->isPublished()) { // Keep track of the publishing status for workspace_entity_insert() and // unpublish the default revision. + // @todo Remove this dynamic property once we have an API for associating + // temporary data with an entity: https://www.drupal.org/node/2896474. $entity->_initialPublished = TRUE; $entity->setUnpublished(); } @@ -167,6 +175,7 @@ public function entityInsert(EntityInterface $entity) { // workspace and create a published pending revision for it. This does not // cause an infinite recursion with ::entityPresave() because at this point // the entity is no longer new. + // @todo Better explain in https://www.drupal.org/node/2962764 if (isset($entity->_initialPublished)) { // Operate on a clone to avoid changing the entity prior to subsequent // hook_entity_insert() implementations. @@ -193,7 +202,7 @@ public function entityUpdate(EntityInterface $entity) { } // Only track new revisions. - /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + /** @var \Drupal\Core\Entity\RevisionableInterface $entity */ if ($entity->getLoadedRevisionId() != $entity->getRevisionId()) { $this->trackEntity($entity); } @@ -217,9 +226,9 @@ protected function trackEntity(EntityInterface $entity) { $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); if (!$entity->isNew()) { $workspace_associations = $workspace_association_storage->loadByProperties([ - 'content_entity_type_id' => $entity->getEntityTypeId(), - 'content_entity_id' => $entity->id(), - ]); + 'target_entity_type_id' => $entity->getEntityTypeId(), + 'target_entity_id' => $entity->id(), + ]); /** @var \Drupal\Core\Entity\ContentEntityInterface $workspace_association */ $workspace_association = reset($workspace_associations); @@ -232,17 +241,55 @@ protected function trackEntity(EntityInterface $entity) { } else { $workspace_association = $workspace_association_storage->create([ - 'content_entity_type_id' => $entity->getEntityTypeId(), - 'content_entity_id' => $entity->id(), + 'target_entity_type_id' => $entity->getEntityTypeId(), + 'target_entity_id' => $entity->id(), ]); } // Add the revision ID and the workspace ID. - $workspace_association->set('content_entity_revision_id', $entity->getRevisionId()); + $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. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $form_id + * The form ID. + * + * @see hook_form_alter() + */ + public function formAlter(array &$form, FormStateInterface $form_state, $form_id) { + $form_object = $form_state->getFormObject(); + if (!$form_object instanceof EntityFormInterface) { + return; + } + + $entity = $form_object->getEntity(); + if (!$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) { + return; + } + + /** @var \Drupal\workspace\WorkspaceAssociationStorageInterface $workspace_association_storage */ + $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + if ($workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity)) { + // An entity can only be edited in one workspace. + $workspace_id = reset($workspace_ids); + + if ($workspace_id !== $this->workspaceManager->getActiveWorkspace()->id()) { + $workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id); + + $form['#markup'] = $this->t('The content is being edited in the %label workspace.', ['%label' => $workspace->label()]); + $form['#access'] = FALSE; + } + } + } + } diff --git a/core/modules/workspace/src/EntityQuery/Query.php b/core/modules/workspace/src/EntityQuery/Query.php index 19479f1..30cdcf2 100644 --- a/core/modules/workspace/src/EntityQuery/Query.php +++ b/core/modules/workspace/src/EntityQuery/Query.php @@ -42,7 +42,7 @@ public function prepare() { // relationship, and, as a consequence, the revision ID field is no longer // a simple SQL field but an expression. $this->sqlFields = []; - $this->sqlExpressions[$revision_field] = "COALESCE(workspace_association.content_entity_revision_id, base_table.$revision_field)"; + $this->sqlExpressions[$revision_field] = "COALESCE(workspace_association.target_entity_revision_id, base_table.$revision_field)"; $this->sqlExpressions[$id_field] = "base_table.$id_field"; } diff --git a/core/modules/workspace/src/EntityQuery/QueryTrait.php b/core/modules/workspace/src/EntityQuery/QueryTrait.php index 6606106..5d34686 100644 --- a/core/modules/workspace/src/EntityQuery/QueryTrait.php +++ b/core/modules/workspace/src/EntityQuery/QueryTrait.php @@ -55,7 +55,7 @@ public function prepare() { // Only alter the query if the active workspace is not the default one and // the entity type is supported. $active_workspace = $this->workspaceManager->getActiveWorkspace(); - if (!$active_workspace->isDefaultWorkspace() && $this->workspaceManager->entityTypeCanBelongToWorkspaces($this->entityType)) { + if (!$active_workspace->isDefaultWorkspace() && $this->workspaceManager->isEntityTypeSupported($this->entityType)) { $this->sqlQuery->addMetaData('active_workspace_id', $active_workspace->id()); $this->sqlQuery->addMetaData('simple_query', FALSE); @@ -63,7 +63,7 @@ public function prepare() { // can properly include live content along with a possible workspace // revision. $id_field = $this->entityType->getKey('id'); - $this->sqlQuery->leftJoin('workspace_association', 'workspace_association', "%alias.content_entity_type_id = '{$this->entityTypeId}' AND %alias.content_entity_id = base_table.$id_field AND %alias.workspace = '{$active_workspace->id()}'"); + $this->sqlQuery->leftJoin('workspace_association', 'workspace_association', "%alias.target_entity_type_id = '{$this->entityTypeId}' AND %alias.target_entity_id = base_table.$id_field AND %alias.workspace = '{$active_workspace->id()}'"); } return $this; diff --git a/core/modules/workspace/src/EntityQuery/Tables.php b/core/modules/workspace/src/EntityQuery/Tables.php index ed0eb01..55fc04f 100644 --- a/core/modules/workspace/src/EntityQuery/Tables.php +++ b/core/modules/workspace/src/EntityQuery/Tables.php @@ -63,6 +63,7 @@ public function addField($field, $type, $langcode) { // method to always pick the revision tables if the field being queried is // revisionable. if ($active_workspace_id = $this->sqlQuery->getMetaData('active_workspace_id')) { + $previous_all_revisions = $this->sqlQuery->getMetaData('all_revisions'); $this->sqlQuery->addMetaData('all_revisions', TRUE); } @@ -70,8 +71,8 @@ public function addField($field, $type, $langcode) { // Restore the 'all_revisions' metadata because we don't want to interfere // with the rest of the query. - if ($active_workspace_id) { - $this->sqlQuery->addMetaData('all_revisions', FALSE); + if (isset($previous_all_revisions)) { + $this->sqlQuery->addMetaData('all_revisions', $previous_all_revisions); } return $alias; @@ -99,7 +100,7 @@ protected function addJoin($type, $table, $join_condition, $langcode, $delta = N if ($id_field === $revision_key || $id_field === 'revision_id') { $workspace_association_table = $this->contentWorkspaceTables[$base_table]; - $join_condition = "{$condition_parts[0]} = COALESCE($workspace_association_table.content_entity_revision_id, {$condition_parts[1]})"; + $join_condition = "{$condition_parts[0]} = COALESCE($workspace_association_table.target_entity_revision_id, {$condition_parts[1]})"; } } } @@ -114,7 +115,7 @@ protected function addNextBaseTable(EntityType $entity_type, $table, $sql_column $next_base_table_alias = parent::addNextBaseTable($entity_type, $table, $sql_column, $field_storage); $active_workspace_id = $this->sqlQuery->getMetaData('active_workspace_id'); - if ($active_workspace_id && $this->workspaceManager->entityTypeCanBelongToWorkspaces($entity_type)) { + if ($active_workspace_id && $this->workspaceManager->isEntityTypeSupported($entity_type)) { $this->addWorkspaceAssociationJoin($entity_type->id(), $next_base_table_alias, $active_workspace_id); } @@ -144,7 +145,7 @@ public function addWorkspaceAssociationJoin($entity_type_id, $base_table_alias, // LEFT join the Workspace association entity's table so we can properly // include live content along with a possible workspace-specific revision. - $this->contentWorkspaceTables[$base_table_alias] = $this->sqlQuery->leftJoin('workspace_association', NULL, "%alias.content_entity_type_id = '$entity_type_id' AND %alias.content_entity_id = $base_table_alias.$id_field AND %alias.workspace = '$active_workspace_id'"); + $this->contentWorkspaceTables[$base_table_alias] = $this->sqlQuery->leftJoin('workspace_association', NULL, "%alias.target_entity_type_id = '$entity_type_id' AND %alias.target_entity_id = $base_table_alias.$id_field AND %alias.workspace = '$active_workspace_id'"); $this->baseTablesEntityType[$base_table_alias] = $entity_type->id(); } diff --git a/core/modules/workspace/src/EntityTypeInfo.php b/core/modules/workspace/src/EntityTypeInfo.php index fa2c4d0..d7995df 100644 --- a/core/modules/workspace/src/EntityTypeInfo.php +++ b/core/modules/workspace/src/EntityTypeInfo.php @@ -3,10 +3,7 @@ namespace Drupal\workspace; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -19,8 +16,6 @@ */ class EntityTypeInfo implements ContainerInjectionInterface { - use StringTranslationTrait; - /** * The entity type manager service. * @@ -69,48 +64,10 @@ public static function create(ContainerInterface $container) { */ public function entityTypeBuild(array &$entity_types) { foreach ($entity_types as $entity_type) { - if ($this->workspaceManager->entityTypeCanBelongToWorkspaces($entity_type)) { + if ($this->workspaceManager->isEntityTypeSupported($entity_type)) { $entity_type->addConstraint('EntityWorkspaceConflict'); } } } - /** - * Alters entity forms to disallow concurrent editing in multiple workspaces. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * @param string $form_id - * The form ID. - * - * @see hook_form_alter() - */ - public function formAlter(array &$form, FormStateInterface $form_state, $form_id) { - $form_object = $form_state->getFormObject(); - if (!$form_object instanceof EntityFormInterface) { - return; - } - - $entity = $form_object->getEntity(); - if (!$this->workspaceManager->entityTypeCanBelongToWorkspaces($entity->getEntityType())) { - return; - } - - /** @var \Drupal\workspace\WorkspaceAssociationStorageInterface $workspace_association_storage */ - $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); - if ($workspace_ids = $workspace_association_storage->isEntityTracked($entity)) { - // An entity can only be edited in one workspace. - $workspace_id = reset($workspace_ids); - - if ($workspace_id !== $this->workspaceManager->getActiveWorkspace()->id()) { - $workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id); - - $form['#markup'] = $this->t('The content is being edited in the %label workspace.', ['%label' => $workspace->label()]); - $form['#access'] = FALSE; - } - } - } - } diff --git a/core/modules/workspace/src/Form/WorkspaceDeleteForm.php b/core/modules/workspace/src/Form/WorkspaceDeleteForm.php index 2d8cbef..4a14bc6 100644 --- a/core/modules/workspace/src/Form/WorkspaceDeleteForm.php +++ b/core/modules/workspace/src/Form/WorkspaceDeleteForm.php @@ -24,7 +24,7 @@ class WorkspaceDeleteForm extends ContentEntityDeleteForm { */ public function buildForm(array $form, FormStateInterface $form_state) { $form = parent::buildForm($form, $form_state); - $source_rev_diff = $this->entity->getRepositoryHandler()->getSourceRevisionDifference(); + $source_rev_diff = $this->entity->getRepositoryHandler()->getDifferringRevisionIdsOnSource(); $items = []; foreach ($source_rev_diff as $entity_type_id => $revision_ids) { $label = $this->entityTypeManager->getDefinition($entity_type_id)->getLabel(); diff --git a/core/modules/workspace/src/Form/WorkspaceDeployForm.php b/core/modules/workspace/src/Form/WorkspaceDeployForm.php index 742042b..bef35c0 100644 --- a/core/modules/workspace/src/Form/WorkspaceDeployForm.php +++ b/core/modules/workspace/src/Form/WorkspaceDeployForm.php @@ -73,8 +73,8 @@ public function form(array $form, FormStateInterface $form_state) { $form['#title'] = $this->t('Deploy %source_label workspace', $args); // List the changes that can be pushed. - if ($source_rev_diff = $repository_handler->getSourceRevisionDifference()) { - $total_count = count($source_rev_diff, COUNT_RECURSIVE) - count($source_rev_diff); + if ($source_rev_diff = $repository_handler->getDifferringRevisionIdsOnSource()) { + $total_count = $repository_handler->getNumberOfChangesOnSource(); $form['deploy'] = [ '#theme' => 'item_list', '#title' => $this->formatPlural($total_count, 'There is @count item that can be deployed from %source_label to %target_label', 'There are @count items that can be deployed from %source_label to %target_label', $args), @@ -87,8 +87,8 @@ public function form(array $form, FormStateInterface $form_state) { } // List the changes that can be pulled. - if ($target_rev_diff = $repository_handler->getTargetRevisionDifference()) { - $total_count = count($target_rev_diff, COUNT_RECURSIVE) - count($target_rev_diff); + if ($target_rev_diff = $repository_handler->getDifferringRevisionIdsOnTarget()) { + $total_count = $repository_handler->getNumberOfChangesOnTarget(); $form['refresh'] = [ '#theme' => 'item_list', '#title' => $this->formatPlural($total_count, 'There is @count item that can be refreshed from %target_label to %source_label', 'There are @count items that can be refreshed from %target_label to %source_label', $args), @@ -117,11 +117,11 @@ public function actions(array $form, FormStateInterface $form_state) { $elements = parent::actions($form, $form_state); unset($elements['delete']); - $repositoy_handler = $this->entity->getRepositoryHandler(); + $repository_handler = $this->entity->getRepositoryHandler(); if (isset($form['deploy'])) { $total_count = $form['deploy']['#total_count']; - $elements['submit']['#value'] = $this->formatPlural($total_count, 'Deploy @count item to @target', 'Deploy @count items to @target', ['@target' => $repositoy_handler->getLabel()]); + $elements['submit']['#value'] = $this->formatPlural($total_count, 'Deploy @count item to @target', 'Deploy @count items to @target', ['@target' => $repository_handler->getLabel()]); $elements['submit']['#submit'] = ['::submitForm', '::deploy']; } else { @@ -135,7 +135,7 @@ public function actions(array $form, FormStateInterface $form_state) { $total_count = $form['refresh']['#total_count']; $elements['refresh'] = [ '#type' => 'submit', - '#value' => $this->formatPlural($total_count, 'Refresh @count item from @target', 'Refresh @count items from @target', ['@target' => $repositoy_handler->getLabel()]), + '#value' => $this->formatPlural($total_count, 'Refresh @count item from @target', 'Refresh @count items from @target', ['@target' => $repository_handler->getLabel()]), '#submit' => ['::submitForm', '::refresh'], ]; } diff --git a/core/modules/workspace/src/Negotiator/DefaultWorkspaceNegotiator.php b/core/modules/workspace/src/Negotiator/DefaultWorkspaceNegotiator.php index a37a108..d4310db 100644 --- a/core/modules/workspace/src/Negotiator/DefaultWorkspaceNegotiator.php +++ b/core/modules/workspace/src/Negotiator/DefaultWorkspaceNegotiator.php @@ -5,7 +5,6 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\workspace\WorkspaceInterface; -use Drupal\workspace\WorkspaceManager; use Symfony\Component\HttpFoundation\Request; /** @@ -50,8 +49,8 @@ public function applies(Request $request) { public function getActiveWorkspace(Request $request) { if (!$this->defaultWorkspace) { $default_workspace = $this->workspaceStorage->create([ - 'id' => WorkspaceManager::DEFAULT_WORKSPACE, - 'label' => Unicode::ucwords(WorkspaceManager::DEFAULT_WORKSPACE), + 'id' => WorkspaceInterface::DEFAULT_WORKSPACE, + 'label' => Unicode::ucwords(WorkspaceInterface::DEFAULT_WORKSPACE), 'target' => '', ]); $default_workspace->enforceIsNew(FALSE); diff --git a/core/modules/workspace/src/Plugin/Block/WorkspaceSwitcherBlock.php b/core/modules/workspace/src/Plugin/Block/WorkspaceSwitcherBlock.php index d47d013..e939715 100644 --- a/core/modules/workspace/src/Plugin/Block/WorkspaceSwitcherBlock.php +++ b/core/modules/workspace/src/Plugin/Block/WorkspaceSwitcherBlock.php @@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\workspace\Form\WorkspaceSwitcherForm; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -71,7 +72,7 @@ public static function create(ContainerInterface $container, array $configuratio */ public function build() { $build = [ - 'form' => $this->formBuilder->getForm('Drupal\workspace\Form\WorkspaceSwitcherForm'), + 'form' => $this->formBuilder->getForm(WorkspaceSwitcherForm::class), '#cache' => [ 'contexts' => $this->entityTypeManager->getDefinition('workspace')->getListCacheContexts(), 'tags' => $this->entityTypeManager->getDefinition('workspace')->getListCacheTags(), diff --git a/core/modules/workspace/src/Plugin/RepositoryHandler/LiveRepositoryHandler.php b/core/modules/workspace/src/Plugin/RepositoryHandler/LiveRepositoryHandler.php index eae879c..fef5942 100644 --- a/core/modules/workspace/src/Plugin/RepositoryHandler/LiveRepositoryHandler.php +++ b/core/modules/workspace/src/Plugin/RepositoryHandler/LiveRepositoryHandler.php @@ -113,13 +113,18 @@ public function push() { $transaction = $this->database->startTransaction(); try { - foreach ($this->getSourceRevisionDifference() as $entity_type_id => $revision_difference) { + // @todo Handle the publishing of a workspace with a batch operation in + // https://www.drupal.org/node/2958752. + foreach ($this->getDifferringRevisionIdsOnSource() as $entity_type_id => $revision_difference) { $entity_revisions = $this->entityTypeManager->getStorage($entity_type_id) ->loadMultipleRevisions(array_keys($revision_difference)); - /** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionableInterface $entity */ + /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $entity */ foreach ($entity_revisions as $entity) { // When pushing workspace-specific revisions to the default workspace // (Live), we simply need to mark them as default revisions. + // @todo Remove this dynamic property once we have an API for + // associating temporary data with an entity: + // https://www.drupal.org/node/2896474. $entity->_isReplicating = TRUE; $entity->isDefaultRevision(TRUE); $entity->save(); @@ -157,7 +162,7 @@ public function checkConflictsOnTarget() { /** * {@inheritdoc} */ - public function getTargetRevisionDifference() { + public function getDifferringRevisionIdsOnTarget() { $target_revision_difference = []; $tracked_entities = $this->workspaceAssociationStorage->getTrackedEntities($this->source); @@ -188,9 +193,25 @@ public function getTargetRevisionDifference() { /** * {@inheritdoc} */ - public function getSourceRevisionDifference() { + public function getDifferringRevisionIdsOnSource() { // Get the Workspace association revisions which haven't been pushed yet. return $this->workspaceAssociationStorage->getTrackedEntities($this->source); } + /** + * {@inheritdoc} + */ + public function getNumberOfChangesOnTarget() { + $total_changes = $this->getDifferringRevisionIdsOnTarget(); + return count($total_changes, COUNT_RECURSIVE) - count($total_changes); + } + + /** + * {@inheritdoc} + */ + public function getNumberOfChangesOnSource() { + $total_changes = $this->getDifferringRevisionIdsOnSource(); + return count($total_changes, COUNT_RECURSIVE) - count($total_changes); + } + } diff --git a/core/modules/workspace/src/Plugin/RepositoryHandler/NullRepositoryHandler.php b/core/modules/workspace/src/Plugin/RepositoryHandler/NullRepositoryHandler.php index 33b720d..ce2d515 100644 --- a/core/modules/workspace/src/Plugin/RepositoryHandler/NullRepositoryHandler.php +++ b/core/modules/workspace/src/Plugin/RepositoryHandler/NullRepositoryHandler.php @@ -40,20 +40,34 @@ public function checkConflictsOnTarget() { /** * {@inheritdoc} */ - public function getTargetRevisionDifference() { + public function getDifferringRevisionIdsOnTarget() { return []; } /** * {@inheritdoc} */ - public function getSourceRevisionDifference() { + public function getDifferringRevisionIdsOnSource() { return []; } /** * {@inheritdoc} */ + public function getNumberOfChangesOnTarget() { + return 0; + } + + /** + * {@inheritdoc} + */ + public function getNumberOfChangesOnSource() { + return 0; + } + + /** + * {@inheritdoc} + */ public function getLabel() { return $this->getPluginDefinition()['label']; } diff --git a/core/modules/workspace/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php b/core/modules/workspace/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php index d7fdb99..695a3383 100644 --- a/core/modules/workspace/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php +++ b/core/modules/workspace/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php @@ -44,13 +44,15 @@ public static function create(ContainerInterface $container) { */ public function validate($value, Constraint $constraint) { /** @var \Drupal\Core\Field\FieldItemListInterface $value */ - if (!isset($value)) { + // This constraint applies only to newly created workspace entities. + if (!isset($value) || !$value->getEntity()->isNew()) { return; } $count = $this->workspaceAssociationStorage ->getQuery() ->allRevisions() + ->accessCheck(FALSE) ->condition('workspace', $value->getEntity()->id()) ->count() ->execute(); diff --git a/core/modules/workspace/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php b/core/modules/workspace/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php index b175932..2807fc6 100644 --- a/core/modules/workspace/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php +++ b/core/modules/workspace/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php @@ -4,6 +4,7 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\workspace\WorkspaceManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -21,13 +22,23 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp protected $entityTypeManager; /** + * The workspace manager service. + * + * @var \Drupal\workspace\WorkspaceManagerInterface + */ + protected $workspaceManager; + + /** * Constructs an EntityUntranslatableFieldsConstraintValidator object. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager. + * The entity type manager service. + * @param \Drupal\workspace\WorkspaceManagerInterface $workspace_manager + * The workspace manager service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) { $this->entityTypeManager = $entity_type_manager; + $this->workspaceManager = $workspace_manager; } /** @@ -35,7 +46,8 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('workspace.manager') ); } @@ -47,8 +59,10 @@ public function validate($entity, Constraint $constraint) { if (isset($entity) && !$entity->isNew()) { /** @var \Drupal\workspace\WorkspaceAssociationStorageInterface $workspace_association_storage */ $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); + $workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity); + $active_workspace = $this->workspaceManager->getActiveWorkspace(); - if ($workspace_ids = $workspace_association_storage->isEntityTracked($entity, FALSE)) { + if ($workspace_ids && !in_array($active_workspace->id(), $workspace_ids, TRUE)) { // An entity can only be edited in one workspace. $workspace_id = reset($workspace_ids); $workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id); diff --git a/core/modules/workspace/src/RepositoryHandlerInterface.php b/core/modules/workspace/src/RepositoryHandlerInterface.php index 21ab5b4..660ab62 100644 --- a/core/modules/workspace/src/RepositoryHandlerInterface.php +++ b/core/modules/workspace/src/RepositoryHandlerInterface.php @@ -82,8 +82,11 @@ public function checkConflictsOnTarget(); * @return array * A multidimensional array of revision identifiers, either the revision ID * or the revision UUID, keyed by entity type IDs. + * + * @todo Update the return values to be only UUIDs and revision UUIDs in + * https://www.drupal.org/node/2958752 */ - public function getTargetRevisionDifference(); + public function getDifferringRevisionIdsOnTarget(); /** * Gets the revision identifiers for items which have changed on the source. @@ -91,7 +94,34 @@ public function getTargetRevisionDifference(); * @return array * A multidimensional array of revision identifiers, either the revision ID * or the revision UUID, keyed by entity type IDs. + * + * @todo Update the return values to be only UUIDs and revision UUIDs in + * https://www.drupal.org/node/2958752 + */ + public function getDifferringRevisionIdsOnSource(); + + /** + * Gets the total number of items which have changed on the target. + * + * This returns the aggregated changes count across all entity types. + * For example, if two nodes and one taxonomy term have changed on the target, + * the return value is 3. + * + * @return int + * The number of differing revisions. + */ + public function getNumberOfChangesOnTarget(); + + /** + * Gets the total number of items which have changed on the source. + * + * This returns the aggregated changes count across all entity types. + * For example, if two nodes and one taxonomy term have changed on the source, + * the return value is 3. + * + * @return int + * The number of differing revisions. */ - public function getSourceRevisionDifference(); + public function getNumberOfChangesOnSource(); } diff --git a/core/modules/workspace/src/ViewsQueryAlter.php b/core/modules/workspace/src/ViewsQueryAlter.php index 6da6ae6..51bb2af 100644 --- a/core/modules/workspace/src/ViewsQueryAlter.php +++ b/core/modules/workspace/src/ViewsQueryAlter.php @@ -120,8 +120,8 @@ public function alterQuery(ViewExecutable $view, QueryPluginBase $query) { $entity_type_definitions = $this->entityTypeManager->getDefinitions(); foreach ($entity_type_ids as $entity_type_id) { - if ($this->workspaceManager->entityTypeCanBelongToWorkspaces($entity_type_definitions[$entity_type_id])) { - $this->alterQueryForEntityType($view, $query, $entity_type_definitions[$entity_type_id]); + if ($this->workspaceManager->isEntityTypeSupported($entity_type_definitions[$entity_type_id])) { + $this->alterQueryForEntityType($query, $entity_type_definitions[$entity_type_id]); } } } @@ -129,16 +129,15 @@ public function alterQuery(ViewExecutable $view, QueryPluginBase $query) { /** * Alters the entity type tables for a Views query. * - * @param \Drupal\views\ViewExecutable $view - * The view object about to be processed. + * This should only be called after determining that this entity type is + * involved in the query, and that a non-default workspace is in use. + * * @param \Drupal\views\Plugin\views\query\Sql $query * The query plugin object for the query. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The entity type definition. */ - protected function alterQueryForEntityType(ViewExecutable $view, Sql $query, EntityTypeInterface $entity_type) { - // This is only called after we determined that this entity type is involved - // in the query, and that a non-default workspace is in use. + protected function alterQueryForEntityType(Sql $query, EntityTypeInterface $entity_type) { /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = $this->entityTypeManager->getStorage($entity_type->id())->getTableMapping(); $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id()); @@ -177,7 +176,7 @@ protected function alterQueryForEntityType(ViewExecutable $view, Sql $query, Ent // Update the join to use our COALESCE. $revision_field = $entity_type->getKey('revision'); $table_info['join']->leftTable = NULL; - $table_info['join']->leftField = "COALESCE($workspace_association_table.content_entity_revision_id, $relationship.$revision_field)"; + $table_info['join']->leftField = "COALESCE($workspace_association_table.target_entity_revision_id, $relationship.$revision_field)"; // Update the join and the table info to our new table name, and to join // on the revision key. @@ -239,8 +238,8 @@ protected function alterQueryForEntityType(ViewExecutable $view, Sql $query, Ent // Now we have to go through our where clauses and modify any of our fields. foreach ($query->where as &$clauses) { foreach ($clauses['conditions'] as &$where_info) { - // Build a matrix of our possible relationships against fields we need to - // switch. + // Build a matrix of our possible relationships against fields we need + // to switch. foreach ($relationships as $relationship) { foreach ($revisionable_fields as $field) { if (is_string($where_info['field']) && $where_info['field'] == "$relationship.$field") { @@ -255,8 +254,8 @@ protected function alterQueryForEntityType(ViewExecutable $view, Sql $query, Ent } } - // @todo Handle $query->orderby, $query->groupby, $query->having, - // $query->count_field. + // @todo Handle $query->orderby, $query->groupby, $query->having and + // $query->count_field in https://www.drupal.org/node/2968165. } /** @@ -282,12 +281,12 @@ protected function ensureWorkspaceAssociationTable($entity_type_id, Sql $query, // Construct the join. $definition = [ 'table' => 'workspace_association', - 'field' => 'content_entity_id', + 'field' => 'target_entity_id', 'left_table' => $relationship, 'left_field' => $table_data['table']['base']['field'], 'extra' => [ [ - 'field' => 'content_entity_type_id', + 'field' => 'target_entity_type_id', 'value' => $entity_type_id, ], [ @@ -318,8 +317,8 @@ protected function ensureWorkspaceAssociationTable($entity_type_id, Sql $query, * The alias of the relationship. */ protected function ensureRevisionTable(EntityTypeInterface $entity_type, Sql $query, $relationship) { - // Get the alias for the 'workspace_association' table we chain off of in the - // COALESCE. + // Get the alias for the 'workspace_association' table we chain off of in + // the COALESCE. $workspace_association_table = $this->ensureWorkspaceAssociationTable($entity_type->id(), $query, $relationship); // Get the name of the revision table and revision key. @@ -333,7 +332,8 @@ protected function ensureRevisionTable(EntityTypeInterface $entity_type, Sql $qu $alias = $query->tables[$relationship][$base_revision_table]['alias']; if (isset($table_queue[$alias]['join']->field) && $table_queue[$alias]['join']->field == $revision_field) { // If this table previously existed, but was not added by us, we need - // to modify the join and make sure that 'workspace_association' comes first. + // to modify the join and make sure that 'workspace_association' comes + // first. if (empty($table_queue[$alias]['join']->workspace_adjusted)) { $table_queue[$alias]['join'] = $this->getRevisionTableJoin($relationship, $base_revision_table, $revision_field, $workspace_association_table); // We also have to ensure that our 'workspace_association' comes before @@ -372,7 +372,7 @@ protected function getRevisionTableJoin($relationship, $table, $field, $workspac 'field' => $field, // Making this explicitly null allows the left table to be a formula. 'left_table' => NULL, - 'left_field' => "COALESCE($workspace_association_table.content_entity_revision_id, $relationship.$field)", + 'left_field' => "COALESCE($workspace_association_table.target_entity_revision_id, $relationship.$field)", ]; /** @var \Drupal\views\Plugin\views\join\JoinPluginInterface $join */ @@ -389,8 +389,7 @@ protected function getRevisionTableJoin($relationship, $table, $field, $workspac * Because Workspace chains possibly pre-existing tables onto the * 'workspace_association' table, we have to ensure that the * 'workspace_association' table appears in the query before the alias it's - * chained on or the SQL is invalid. This uses array_slice() to reconstruct - * the table queue of the query. + * chained on or the SQL is invalid. * * @param \Drupal\views\Plugin\views\query\Sql $query * The SQL query object. diff --git a/core/modules/workspace/src/WorkspaceAccessControlHandler.php b/core/modules/workspace/src/WorkspaceAccessControlHandler.php index 838eb58..4bf9601 100644 --- a/core/modules/workspace/src/WorkspaceAccessControlHandler.php +++ b/core/modules/workspace/src/WorkspaceAccessControlHandler.php @@ -34,7 +34,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter $permission_operation = $operation === 'update' ? 'edit' : $operation; - // Check if the user has permission to access any workspace at all. + // Check if the user has permission to access all workspaces. $access_result = AccessResult::allowedIfHasPermission($account, $permission_operation . ' any workspace'); // Check if it's their own workspace, and they have permission to access diff --git a/core/modules/workspace/src/WorkspaceAssociationStorage.php b/core/modules/workspace/src/WorkspaceAssociationStorage.php index 0f6f8d9..9cc4857 100644 --- a/core/modules/workspace/src/WorkspaceAssociationStorage.php +++ b/core/modules/workspace/src/WorkspaceAssociationStorage.php @@ -27,26 +27,17 @@ public function postPush(WorkspaceInterface $workspace) { /** * {@inheritdoc} */ - public function getTrackedEntities($workspace_id, $all_revisions = FALSE, $group = TRUE) { + 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', ['content_entity_type_id', 'content_entity_id', 'content_entity_revision_id']) - ->orderBy('content_entity_revision_id', 'ASC') + ->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) { - if ($group) { - $tracked_revisions[$record->content_entity_type_id][$record->content_entity_revision_id] = $record->content_entity_id; - } - else { - $tracked_revisions[] = [ - 'entity_type_id' => $record->content_entity_type_id, - 'revision_id' => $record->content_entity_revision_id, - 'entity_id' => $record->content_entity_id, - ]; - } + $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id; } return $tracked_revisions; @@ -55,12 +46,12 @@ public function getTrackedEntities($workspace_id, $all_revisions = FALSE, $group /** * {@inheritdoc} */ - public function isEntityTracked(EntityInterface $entity) { + public function getEntityTrackingWorkspaceIds(EntityInterface $entity) { $query = $this->database->select($this->getBaseTable(), 'base_table'); $query ->fields('base_table', ['workspace']) - ->condition('content_entity_type_id', $entity->getEntityTypeId()) - ->condition('content_entity_id', $entity->id()); + ->condition('target_entity_type_id', $entity->getEntityTypeId()) + ->condition('target_entity_id', $entity->id()); return $query->execute()->fetchCol(); } diff --git a/core/modules/workspace/src/WorkspaceAssociationStorageInterface.php b/core/modules/workspace/src/WorkspaceAssociationStorageInterface.php index b08c035..24a7e32 100644 --- a/core/modules/workspace/src/WorkspaceAssociationStorageInterface.php +++ b/core/modules/workspace/src/WorkspaceAssociationStorageInterface.php @@ -11,7 +11,7 @@ interface WorkspaceAssociationStorageInterface extends ContentEntityStorageInterface { /** - * Marks all workspace association entities pushed for a given workspace. + * Triggers clean-up operations after pushing. * * @param \Drupal\workspace\WorkspaceInterface $workspace * A workspace entity. @@ -26,29 +26,23 @@ public function postPush(WorkspaceInterface $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. - * @param bool $group - * (optional) Whether to group the results by their entity type ID. Defaults - * to TRUE. * * @return array - * Returns an array of entity identifiers which are tracked by a given - * workspace. If the $group parameter is TRUE, 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. If the $group parameter is - * FALSE, returns a single level array containing all the tracked entities. + * 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, $group = TRUE); + public function getTrackedEntities($workspace_id, $all_revisions = FALSE); /** - * Checks if a given entity is tracked in one or multiple workspaces. + * 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's not tracked anywhere. + * array if it is not tracked anywhere. */ - public function isEntityTracked(EntityInterface $entity); + public function getEntityTrackingWorkspaceIds(EntityInterface $entity); } diff --git a/core/modules/workspace/src/WorkspaceInterface.php b/core/modules/workspace/src/WorkspaceInterface.php index d1d37c9..411437c 100644 --- a/core/modules/workspace/src/WorkspaceInterface.php +++ b/core/modules/workspace/src/WorkspaceInterface.php @@ -12,6 +12,11 @@ interface WorkspaceInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface { /** + * The ID of the default workspace. + */ + const DEFAULT_WORKSPACE = 'live'; + + /** * Pushes content from this workspace to the target repository. */ public function push(); @@ -38,6 +43,14 @@ public function getRepositoryHandler(); public function isDefaultWorkspace(); /** + * Gets the workspace creation timestamp. + * + * @return int + * Creation timestamp of the workspace. + */ + public function getCreatedTime(); + + /** * Sets the workspace creation timestamp. * * @param int $timestamp @@ -47,12 +60,4 @@ public function isDefaultWorkspace(); */ public function setCreatedTime($timestamp); - /** - * Returns the workspace creation timestamp. - * - * @return int - * Creation timestamp of the workspace. - */ - public function getStartTime(); - } diff --git a/core/modules/workspace/src/WorkspaceListBuilder.php b/core/modules/workspace/src/WorkspaceListBuilder.php index 44384a9..e819454 100644 --- a/core/modules/workspace/src/WorkspaceListBuilder.php +++ b/core/modules/workspace/src/WorkspaceListBuilder.php @@ -65,7 +65,7 @@ public function buildHeader() { */ public function buildRow(EntityInterface $entity) { /** @var \Drupal\workspace\WorkspaceInterface $entity */ - $row['label'] = $entity->label() . ' (' . $entity->id() . ')'; + $row['label'] = $this->t('@label (@id)', ['@label' => $entity->label(), '@id' => $entity->id()]); $row['owner'] = $entity->getOwner()->getDisplayname(); $active_workspace = $this->workspaceManager->getActiveWorkspace()->id(); $row['status'] = $active_workspace == $entity->id() ? $this->t('Active') : $this->t('Inactive'); diff --git a/core/modules/workspace/src/WorkspaceManager.php b/core/modules/workspace/src/WorkspaceManager.php index ad49cd5..ee41348 100644 --- a/core/modules/workspace/src/WorkspaceManager.php +++ b/core/modules/workspace/src/WorkspaceManager.php @@ -21,11 +21,6 @@ class WorkspaceManager implements WorkspaceManagerInterface { use StringTranslationTrait; /** - * The default workspace ID. - */ - const DEFAULT_WORKSPACE = 'live'; - - /** * An array of entity type IDs that can not belong to a workspace. * * By default, only entity types which are revisionable and publishable can @@ -125,7 +120,7 @@ public function __construct(RequestStack $request_stack, EntityTypeManagerInterf /** * {@inheritdoc} */ - public function entityTypeCanBelongToWorkspaces(EntityTypeInterface $entity_type) { + public function isEntityTypeSupported(EntityTypeInterface $entity_type) { if (!isset($this->blacklist[$entity_type->id()]) && $entity_type->entityClassImplements(EntityPublishedInterface::class) && $entity_type->isRevisionable()) { @@ -141,7 +136,7 @@ public function entityTypeCanBelongToWorkspaces(EntityTypeInterface $entity_type public function getSupportedEntityTypes() { $entity_types = []; foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { - if ($this->entityTypeCanBelongToWorkspaces($entity_type)) { + if ($this->isEntityTypeSupported($entity_type)) { $entity_types[$entity_type_id] = $entity_type; } } @@ -206,7 +201,7 @@ public function setActiveWorkspace(WorkspaceInterface $workspace) { * {@inheritdoc} */ public function shouldAlterOperations(EntityTypeInterface $entity_type) { - return $this->entityTypeCanBelongToWorkspaces($entity_type) && !$this->getActiveWorkspace()->isDefaultWorkspace(); + return $this->isEntityTypeSupported($entity_type) && !$this->getActiveWorkspace()->isDefaultWorkspace(); } /** @@ -214,6 +209,12 @@ public function shouldAlterOperations(EntityTypeInterface $entity_type) { */ public function purgeDeletedWorkspacesBatch() { $deleted_workspace_ids = $this->state->get('workspace.deleted', []); + + // Bail out early if there are no workspaces to purge. + if (empty($deleted_workspace_ids)) { + return; + } + $batch_size = Settings::get('entity_update_batch_size', 50); /** @var \Drupal\workspace\WorkspaceAssociationStorageInterface $workspace_association_storage */ @@ -222,26 +223,19 @@ public function purgeDeletedWorkspacesBatch() { // Get the first deleted workspace from the list and delete the revisions // associated with it, along with the workspace_association entries. $workspace_id = reset($deleted_workspace_ids); - $workspace_association_ids = $workspace_association_storage - ->getQuery() - ->allRevisions() - ->accessCheck(FALSE) - ->condition('workspace', $workspace_id) - ->sort('revision_id', 'ASC') - ->range(0, $batch_size) - ->execute(); + $workspace_association_ids = $this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size); if ($workspace_association_ids) { $workspace_associations = $workspace_association_storage->loadMultipleRevisions(array_keys($workspace_association_ids)); foreach ($workspace_associations as $workspace_association) { - $associated_entity_storage = $this->entityTypeManager->getStorage($workspace_association->content_entity_type_id->value); + $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->content_entity_revision_id->value)) { + 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->content_entity_revision_id->value); + $associated_entity_storage->deleteRevision($workspace_association->target_entity_revision_id->value); } } @@ -255,12 +249,35 @@ public function purgeDeletedWorkspacesBatch() { } } - // Remove the deleted workspace ID entry from state if all its associated - // entities have been purged. - if (!$workspace_association_ids || ($workspace_association_ids && count($workspace_association_ids) < $batch_size)) { + // 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)) { unset($deleted_workspace_ids[$workspace_id]); $this->state->set('workspace.deleted', $deleted_workspace_ids); } } + /** + * Gets a list of workspace association IDs to purge. + * + * @param string $workspace_id + * The ID of the workspace. + * @param int $batch_size + * The maximum number of records that will be purged. + * + * @return array + * An array of workspace association IDs, keyed by their revision IDs. + */ + protected function getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size) { + return $this->entityTypeManager->getStorage('workspace_association') + ->getQuery() + ->allRevisions() + ->accessCheck(FALSE) + ->condition('workspace', $workspace_id) + ->sort('revision_id', 'ASC') + ->range(0, $batch_size) + ->execute(); + } + } diff --git a/core/modules/workspace/src/WorkspaceManagerInterface.php b/core/modules/workspace/src/WorkspaceManagerInterface.php index 596667c..552f316 100644 --- a/core/modules/workspace/src/WorkspaceManagerInterface.php +++ b/core/modules/workspace/src/WorkspaceManagerInterface.php @@ -18,7 +18,7 @@ * @return bool * TRUE if the entity type can belong to a workspace, FALSE otherwise. */ - public function entityTypeCanBelongToWorkspaces(EntityTypeInterface $entity_type); + public function isEntityTypeSupported(EntityTypeInterface $entity_type); /** * Returns an array of entity types that can belong to workspaces. @@ -50,7 +50,7 @@ public function getActiveWorkspace(); public function setActiveWorkspace(WorkspaceInterface $workspace); /** - * Determines whether entity operations or queries should be altered. + * Determines whether runtime entity operations should be altered. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The entity type to check. diff --git a/core/modules/workspace/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php b/core/modules/workspace/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php index 49695f2..b5df89c 100644 --- a/core/modules/workspace/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php +++ b/core/modules/workspace/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php @@ -90,7 +90,7 @@ protected function getExpectedNormalizedEntity() { $author = User::load($this->entity->getOwnerId()); return [ 'created' => [ - $this->formatExpectedTimestampItemValues((int) $this->entity->getStartTime()), + $this->formatExpectedTimestampItemValues((int) $this->entity->getCreatedTime()), ], 'changed' => [ $this->formatExpectedTimestampItemValues($this->entity->getChangedTime()), diff --git a/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php b/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php index 0afbb46..3f95cd3 100644 --- a/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php +++ b/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php @@ -41,7 +41,7 @@ public function testCreateWorkspace() { $entity_list = $etm->getStorage('workspace')->loadByProperties(['label' => 'Bears']); $bears = current($entity_list); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/edit"); $this->assertSession()->statusCodeEquals(403); } @@ -65,7 +65,7 @@ public function testEditOwnWorkspace() { // Now edit that same workspace; We should be able to do so. $bears = Workspace::load('bears'); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/edit"); $this->assertSession()->statusCodeEquals(200); $page = $this->getSession()->getPage(); @@ -82,10 +82,10 @@ public function testEditOwnWorkspace() { $this->createWorkspaceThroughUi('Packers', 'packers'); $packers = Workspace::load('packers'); - $this->drupalGet("/admin/config/workflow/workspace/{$packers->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$packers->id()}/edit"); $this->assertSession()->statusCodeEquals(200); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/edit"); $this->assertSession()->statusCodeEquals(403); } @@ -109,7 +109,7 @@ public function testEditAnyWorkspace() { // Now edit that same workspace; We should be able to do so. $bears = Workspace::load('bears'); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/edit"); $this->assertSession()->statusCodeEquals(200); $page = $this->getSession()->getPage(); @@ -126,10 +126,10 @@ public function testEditAnyWorkspace() { $this->createWorkspaceThroughUi('Packers', 'packers'); $packers = Workspace::load('packers'); - $this->drupalGet("/admin/config/workflow/workspace/{$packers->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$packers->id()}/edit"); $this->assertSession()->statusCodeEquals(200); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/edit"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/edit"); $this->assertSession()->statusCodeEquals(200); } @@ -150,7 +150,7 @@ public function testDeleteOwnWorkspace() { $bears = $this->createWorkspaceThroughUi('Bears', 'bears'); // Now try to delete that same workspace; We should be able to do so. - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/delete"); $this->assertSession()->statusCodeEquals(200); // Now login as a different user and ensure they don't have edit access, @@ -160,10 +160,10 @@ public function testDeleteOwnWorkspace() { $this->drupalLogin($editor2); $packers = $this->createWorkspaceThroughUi('Packers', 'packers'); - $this->drupalGet("/admin/config/workflow/workspace/{$packers->id()}/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$packers->id()}/delete"); $this->assertSession()->statusCodeEquals(200); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/delete"); $this->assertSession()->statusCodeEquals(403); } @@ -184,7 +184,7 @@ public function testDeleteAnyWorkspace() { $bears = $this->createWorkspaceThroughUi('Bears', 'bears'); // Now edit that same workspace; We should be able to do so. - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/delete"); $this->assertSession()->statusCodeEquals(200); // Now login as a different user and ensure they have delete access on both @@ -194,15 +194,15 @@ public function testDeleteAnyWorkspace() { $this->drupalLogin($admin); $packers = $this->createWorkspaceThroughUi('Packers', 'packers'); - $this->drupalGet("/admin/config/workflow/workspace/{$packers->id()}/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$packers->id()}/delete"); $this->assertSession()->statusCodeEquals(200); - $this->drupalGet("/admin/config/workflow/workspace/{$bears->id()}/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/{$bears->id()}/delete"); $this->assertSession()->statusCodeEquals(200); // Check that the default workspace can not be deleted, even by a user with // the "delete any workspace" permission. - $this->drupalGet("/admin/config/workflow/workspace/live/delete"); + $this->drupalGet("/admin/config/workflow/workspace/manage/live/delete"); $this->assertSession()->statusCodeEquals(403); } diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php index fda4731..ede2382 100644 --- a/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php +++ b/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php @@ -39,7 +39,7 @@ public function testSwitchingWorkspaces() { $gravity = $this->createWorkspaceThroughUi('Gravity', 'gravity'); - $this->drupalGet('/admin/config/workflow/workspace/' . $gravity->id() . '/activate'); + $this->drupalGet('/admin/config/workflow/workspace/manage/' . $gravity->id() . '/activate'); $this->assertSession()->statusCodeEquals(200); $page = $this->getSession()->getPage(); diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceTest.php index 97b487f..dbf2d8b 100644 --- a/core/modules/workspace/tests/src/Functional/WorkspaceTest.php +++ b/core/modules/workspace/tests/src/Functional/WorkspaceTest.php @@ -84,7 +84,7 @@ public function testWorkspaceOwner() { $test_workspace = $storage->load('test_workspace'); $this->assertEquals($this->editor1->id(), $test_workspace->getOwnerId()); - $this->drupalPostForm('/admin/config/workflow/workspace/test_workspace/edit', [ + $this->drupalPostForm('/admin/config/workflow/workspace/manage/test_workspace/edit', [ 'uid[0][target_id]' => $this->editor2->getUsername(), ], 'Save'); diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php index 6517c5d..e29f7b0 100644 --- a/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php +++ b/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php @@ -49,11 +49,11 @@ public function testViewOwnWorkspace() { // Load the activate form for the Bears workspace. It should fail because // the workspace belongs to someone else. - $this->drupalGet("admin/config/workflow/workspace/{$bears->id()}/activate"); + $this->drupalGet("admin/config/workflow/workspace/manage/{$bears->id()}/activate"); $this->assertSession()->statusCodeEquals(403); // But editor 2 should be able to activate the Packers workspace. - $this->drupalGet("admin/config/workflow/workspace/{$packers->id()}/activate"); + $this->drupalGet("admin/config/workflow/workspace/manage/{$packers->id()}/activate"); $this->assertSession()->statusCodeEquals(200); } @@ -88,12 +88,12 @@ public function testViewAnyWorkspace() { // Load the activate form for the Bears workspace. This user should be // able to see both workspaces because of the "view any" permission. - $this->drupalGet("admin/config/workflow/workspace/{$bears->id()}/activate"); + $this->drupalGet("admin/config/workflow/workspace/manage/{$bears->id()}/activate"); $this->assertSession()->statusCodeEquals(200); // But editor 2 should be able to activate the Packers workspace. - $this->drupalGet("admin/config/workflow/workspace/{$packers->id()}/activate"); + $this->drupalGet("admin/config/workflow/workspace/manage/{$packers->id()}/activate"); $this->assertSession()->statusCodeEquals(200); } diff --git a/core/modules/workspace/tests/src/Kernel/WorkspaceAccessTest.php b/core/modules/workspace/tests/src/Kernel/WorkspaceAccessTest.php index e4cd10d..7dd4626 100644 --- a/core/modules/workspace/tests/src/Kernel/WorkspaceAccessTest.php +++ b/core/modules/workspace/tests/src/Kernel/WorkspaceAccessTest.php @@ -73,6 +73,10 @@ public function testWorkspaceAccess($operation, $permission) { $this->setCurrentUser($user); $workspace = Workspace::create(['id' => 'oak']); $workspace->save(); + + $this->assertFalse($workspace->access($operation, $user)); + + \Drupal::entityTypeManager()->getAccessControlHandler('workspace')->resetCache(); $role = $this->createRole([$permission]); $user->addRole($role); $this->assertTrue($workspace->access($operation, $user)); diff --git a/core/modules/workspace/tests/src/Kernel/WorkspaceCRUDTest.php b/core/modules/workspace/tests/src/Kernel/WorkspaceCRUDTest.php index 345cd4d..69c13ac 100644 --- a/core/modules/workspace/tests/src/Kernel/WorkspaceCRUDTest.php +++ b/core/modules/workspace/tests/src/Kernel/WorkspaceCRUDTest.php @@ -113,8 +113,8 @@ public function testDeletingWorkspaces() { } // The workspace should have 10 associated node revisions, 5 for each node. - $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE, FALSE); - $this->assertCount(10, $associated_revisions); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE); + $this->assertCount(10, $associated_revisions['node']); // Check that we are allowed to delete the workspace. $this->assertTrue($workspace_1->access('delete', $admin)); @@ -123,7 +123,7 @@ public function testDeletingWorkspaces() { // entities and all the node revisions have been deleted as well. $workspace_1->delete(); - $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE, FALSE); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE); $this->assertCount(0, $associated_revisions); $node_revision_count = $node_storage ->getQuery() @@ -148,15 +148,15 @@ public function testDeletingWorkspaces() { } // The workspace should have 60 associated node revisions. - $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE, FALSE); - $this->assertCount(60, $associated_revisions); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE); + $this->assertCount(60, $associated_revisions['node']); // Delete the workspace and check that we still have 10 revision left to // delete. $workspace_2->delete(); - $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE, FALSE); - $this->assertCount(10, $associated_revisions); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE); + $this->assertCount(10, $associated_revisions['node']); $workspace_deleted = \Drupal::state()->get('workspace.deleted'); $this->assertCount(1, $workspace_deleted); @@ -175,7 +175,7 @@ public function testDeletingWorkspaces() { // from the "workspace.delete" state entry. \Drupal::service('cron')->run(); - $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE, FALSE); + $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE); $this->assertCount(0, $associated_revisions); $node_revision_count = $node_storage ->getQuery() diff --git a/core/modules/workspace/tests/src/Kernel/WorkspaceIntegrationTest.php b/core/modules/workspace/tests/src/Kernel/WorkspaceIntegrationTest.php index 2a269db..eae90d2 100644 --- a/core/modules/workspace/tests/src/Kernel/WorkspaceIntegrationTest.php +++ b/core/modules/workspace/tests/src/Kernel/WorkspaceIntegrationTest.php @@ -356,14 +356,14 @@ public function testWorkspaces() { 7 => 4, ], ]; - $this->assertEquals($expected, $stage_repository_handler->getSourceRevisionDifference()); + $this->assertEquals($expected, $stage_repository_handler->getDifferringRevisionIdsOnSource()); $stage_repository_handler->push(); $this->assertWorkspaceStatus($test_scenarios['push_stage_to_live'], 'node'); $this->assertWorkspaceAssociation($expected_workspace_association['push_stage_to_live'], 'node'); // Check that there are no more revisions to push. - $this->assertEmpty($stage_repository_handler->getSourceRevisionDifference()); + $this->assertEmpty($stage_repository_handler->getDifferringRevisionIdsOnSource()); } /** @@ -520,7 +520,7 @@ protected function assertEntityLoad(array $expected_values, $entity_type_id) { $published_key = $entity_keys['published']; // Check \Drupal\Core\Entity\EntityStorageInterface::loadMultiple(). - /** @var \Drupal\Core\Entity\ContentEntityInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */ + /** @var \Drupal\Core\Entity\EntityInterface[]|\Drupal\Core\Entity\RevisionableInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */ $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_column($expected_default_revisions, $id_key)); foreach ($expected_default_revisions as $expected_default_revision) { $entity_id = $expected_default_revision[$id_key]; @@ -531,7 +531,7 @@ protected function assertEntityLoad(array $expected_values, $entity_type_id) { // Check \Drupal\Core\Entity\EntityStorageInterface::loadUnchanged(). foreach ($expected_default_revisions as $expected_default_revision) { - /** @var \Drupal\Core\Entity\ContentEntityInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */ + /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\Core\Entity\EntityPublishedInterface $entity */ $entity = $this->entityTypeManager->getStorage($entity_type_id)->loadUnchanged($expected_default_revision[$id_key]); $this->assertEquals($expected_default_revision[$revision_key], $entity->getRevisionId()); $this->assertEquals($expected_default_revision[$label_key], $entity->label()); @@ -554,7 +554,7 @@ protected function assertEntityRevisionLoad(array $expected_values, $entity_type $label_key = $entity_keys['label']; $published_key = $entity_keys['published']; - /** @var \Drupal\Core\Entity\ContentEntityInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */ + /** @var \Drupal\Core\Entity\EntityInterface[]|\Drupal\Core\Entity\RevisionableInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */ $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultipleRevisions(array_column($expected_values, $revision_key)); foreach ($expected_values as $expected_revision) { $revision_id = $expected_revision[$revision_key]; @@ -629,11 +629,9 @@ protected function assertWorkspaceAssociation(array $expected, $entity_type_id) /** @var \Drupal\workspace\WorkspaceAssociationStorageInterface $workspace_association_storage */ $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); foreach ($expected as $workspace_id => $expected_tracked_revision_ids) { - $tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE, FALSE); - $tracked_entities = array_filter($tracked_entities, function ($tracked_entity) use ($entity_type_id) { - return $tracked_entity['entity_type_id'] === $entity_type_id; - }); - $this->assertEquals($expected_tracked_revision_ids, array_column($tracked_entities, 'revision_id')); + $tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE); + $tracked_revision_ids = isset($tracked_entities[$entity_type_id]) ? $tracked_entities[$entity_type_id] : []; + $this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids)); } } diff --git a/core/modules/workspace/workspace.module b/core/modules/workspace/workspace.module index acd61ff..dd2ef38 100644 --- a/core/modules/workspace/workspace.module +++ b/core/modules/workspace/workspace.module @@ -47,7 +47,7 @@ function workspace_entity_type_build(array &$entity_types) { */ function workspace_form_alter(&$form, FormStateInterface $form_state, $form_id) { return \Drupal::service('class_resolver') - ->getInstanceFromDefinition(EntityTypeInfo::class) + ->getInstanceFromDefinition(EntityOperations::class) ->formAlter($form, $form_state, $form_id); } @@ -171,7 +171,7 @@ function workspace_toolbar() { ], 'tray' => [ '#heading' => t('Workspaces'), - 'workspaces' => workspace_renderable_links(), + 'workspaces' => workspace_build_renderable_links(), 'configure' => $configure_link, ], '#wrapper_attributes' => [ @@ -186,7 +186,7 @@ function workspace_toolbar() { // Add a special class to the wrapper if we are in the default workspace so we // can highlight it with a different color. if ($active_workspace->isDefaultWorkspace()) { - $items['workspace']['#wrapper_attributes']['class'][] = 'is-live'; + $items['workspace']['#wrapper_attributes']['class'][] = 'workspace-toolbar-tab--is-default'; } return $items; @@ -198,7 +198,7 @@ function workspace_toolbar() { * @return array * A render array containing links to the workspace activation form. */ -function workspace_renderable_links() { +function workspace_build_renderable_links() { $entity_type_manager = \Drupal::entityTypeManager(); /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository */ $entity_repository = \Drupal::service('entity.repository'); diff --git a/core/modules/workspace/workspace.routing.yml b/core/modules/workspace/workspace.routing.yml index 2226501..70a6a22 100644 --- a/core/modules/workspace/workspace.routing.yml +++ b/core/modules/workspace/workspace.routing.yml @@ -7,7 +7,7 @@ entity.workspace.collection: _permission: 'administer workspaces+edit any workspace' entity.workspace.activate_form: - path: '/admin/config/workflow/workspace/{workspace}/activate' + path: '/admin/config/workflow/workspace/manage/{workspace}/activate' defaults: _entity_form: 'workspace.activate' _title: 'Activate Workspace' @@ -17,7 +17,7 @@ entity.workspace.activate_form: _entity_access: 'workspace.view' entity.workspace.deploy_form: - path: '/admin/config/workflow/workspace/{workspace}/deploy' + path: '/admin/config/workflow/workspace/manage/{workspace}/deploy' defaults: _entity_form: 'workspace.deploy' _title: 'Deploy Workspace'