diff --git a/core/modules/jsonapi/jsonapi.module b/core/modules/jsonapi/jsonapi.module index 0e765177080d..d0a58ecf9c84 100644 --- a/core/modules/jsonapi/jsonapi.module +++ b/core/modules/jsonapi/jsonapi.module @@ -321,8 +321,6 @@ function jsonapi_jsonapi_user_filter_access(EntityTypeInterface $entity_type, Ac */ function jsonapi_jsonapi_workspace_filter_access(EntityTypeInterface $entity_type, $published, $owner, AccountInterface $account) { // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess() - // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for - // (isDefaultWorkspace()), so this does not have to. return ([ JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'), JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'), diff --git a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php index 5c6c9c8d4ee2..dd3b0a8e3d12 100644 --- a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php +++ b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php @@ -15,7 +15,6 @@ use Drupal\Core\TypedData\DataReferenceDefinitionInterface; use Drupal\jsonapi\Query\EntityCondition; use Drupal\jsonapi\Query\EntityConditionGroup; use Drupal\jsonapi\Query\Filter; -use Drupal\workspaces\WorkspaceInterface; /** * Adds sufficient access control to collection queries. @@ -306,20 +305,6 @@ class TemporaryQueryGuard { // @see \Drupal\user\UserAccessControlHandler::checkAccess() $specific_condition = new EntityCondition('uid', '0', '!='); break; - - case 'workspace': - // The default workspace is always viewable, no matter what, so if - // the generic condition prevents that, add an OR. - // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess() - if ($generic_condition) { - $specific_condition = new EntityConditionGroup('OR', [ - $generic_condition, - new EntityCondition('id', WorkspaceInterface::DEFAULT_WORKSPACE), - ]); - // The generic condition is now part of the specific condition. - $generic_condition = NULL; - } - break; } // Return a combined condition. diff --git a/core/modules/workspaces/src/Access/ActiveWorkspaceCheck.php b/core/modules/workspaces/src/Access/ActiveWorkspaceCheck.php new file mode 100644 index 000000000000..f649feaddc2d --- /dev/null +++ b/core/modules/workspaces/src/Access/ActiveWorkspaceCheck.php @@ -0,0 +1,50 @@ +workspaceManager = $workspace_manager; + } + + /** + * Checks access. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check against. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function access(Route $route) { + if (!$route->hasRequirement('_has_active_workspace')) { + return AccessResult::neutral(); + } + + $required_value = filter_var($route->getRequirement('_has_active_workspace'), FILTER_VALIDATE_BOOLEAN); + return AccessResult::allowedIf($required_value === $this->workspaceManager->hasActiveWorkspace())->addCacheContexts(['workspace']); + } + +} diff --git a/core/modules/workspaces/src/Entity/Workspace.php b/core/modules/workspaces/src/Entity/Workspace.php index db7721a32904..4ebe6f86a9ed 100644 --- a/core/modules/workspaces/src/Entity/Workspace.php +++ b/core/modules/workspaces/src/Entity/Workspace.php @@ -123,7 +123,8 @@ class Workspace extends ContentEntityBase implements WorkspaceInterface { * {@inheritdoc} */ public function isDefaultWorkspace() { - return $this->id() === static::DEFAULT_WORKSPACE; + @trigger_error('WorkspaceInterface::isDefaultWorkspace() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\workspaces\WorkspaceManager::hasActiveWorkspace() instead. See https://www.drupal.org/node/3071527', E_USER_DEPRECATED); + return FALSE; } /** @@ -150,7 +151,6 @@ class Workspace extends ContentEntityBase implements WorkspaceInterface { // be purged on cron. $state = \Drupal::state(); $deleted_workspace_ids = $state->get('workspace.deleted', []); - 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/workspaces/src/EntityAccess.php b/core/modules/workspaces/src/EntityAccess.php index 52b53cbf74ce..db7b6ed5ffc6 100644 --- a/core/modules/workspaces/src/EntityAccess.php +++ b/core/modules/workspaces/src/EntityAccess.php @@ -75,7 +75,7 @@ class EntityAccess implements ContainerInjectionInterface { // Workspaces themselves are handled by their own access handler and we // should not try to do any access checks for entity types that can not // belong to a workspace. - if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) { + if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity->getEntityType()) || !$this->workspaceManager->hasActiveWorkspace()) { return AccessResult::neutral(); } @@ -102,7 +102,7 @@ class EntityAccess implements ContainerInjectionInterface { // should not try to do any access checks for entity types that can not // belong to a workspace. $entity_type = $this->entityTypeManager->getDefinition($context['entity_type_id']); - if ($entity_type->id() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity_type)) { + if ($entity_type->id() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity_type) || !$this->workspaceManager->hasActiveWorkspace()) { return AccessResult::neutral(); } diff --git a/core/modules/workspaces/src/EntityOperations.php b/core/modules/workspaces/src/EntityOperations.php index f599c1c0311c..6182da200f2c 100644 --- a/core/modules/workspaces/src/EntityOperations.php +++ b/core/modules/workspaces/src/EntityOperations.php @@ -314,8 +314,7 @@ class EntityOperations implements ContainerInjectionInterface { // \Drupal\path\Plugin\Validation\Constraint\PathAliasConstraintValidator // know in advance (before hook_entity_presave()) that the new revision will // be a pending one. - $active_workspace = $this->workspaceManager->getActiveWorkspace(); - if (!$active_workspace->isDefaultWorkspace()) { + if ($this->workspaceManager->hasActiveWorkspace()) { $form['#entity_builders'][] = [get_called_class(), 'entityFormEntityBuild']; } @@ -325,7 +324,8 @@ class EntityOperations implements ContainerInjectionInterface { // An entity can only be edited in one workspace. $workspace_id = reset($workspace_ids); - if ($workspace_id !== $active_workspace->id()) { + $active_workspace = $this->workspaceManager->getActiveWorkspace(); + if ($workspace_id && (!$active_workspace || $workspace_id !== $active_workspace->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()]); @@ -360,7 +360,7 @@ class EntityOperations implements ContainerInjectionInterface { // - the entity type is internal, which means that it should not affect // anything in the default (Live) workspace; // - we are in the default workspace. - return $entity_type->getProvider() === 'workspaces' || $entity_type->isInternal() || $this->workspaceManager->getActiveWorkspace()->isDefaultWorkspace(); + return $entity_type->getProvider() === 'workspaces' || $entity_type->isInternal() || !$this->workspaceManager->hasActiveWorkspace(); } } diff --git a/core/modules/workspaces/src/EntityQuery/QueryTrait.php b/core/modules/workspaces/src/EntityQuery/QueryTrait.php index da906bc7191e..c28ede5a361e 100644 --- a/core/modules/workspaces/src/EntityQuery/QueryTrait.php +++ b/core/modules/workspaces/src/EntityQuery/QueryTrait.php @@ -54,8 +54,8 @@ trait QueryTrait { // 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->isEntityTypeSupported($this->entityType)) { + if ($this->workspaceManager->hasActiveWorkspace() && $this->workspaceManager->isEntityTypeSupported($this->entityType)) { + $active_workspace = $this->workspaceManager->getActiveWorkspace(); $this->sqlQuery->addMetaData('active_workspace_id', $active_workspace->id()); $this->sqlQuery->addMetaData('simple_query', FALSE); diff --git a/core/modules/workspaces/src/Form/SwitchToLiveForm.php b/core/modules/workspaces/src/Form/SwitchToLiveForm.php new file mode 100644 index 000000000000..4cbc5f964cd8 --- /dev/null +++ b/core/modules/workspaces/src/Form/SwitchToLiveForm.php @@ -0,0 +1,79 @@ +workspaceManager = $workspace_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('workspaces.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'switch_to_live_form'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Would you like to switch to the live version of the site?'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('Switch to the live version of the site.'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url(''); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->workspaceManager->switchToLive(); + $this->messenger()->addMessage($this->t('You are now viewing the live version of the site.')); + } + +} diff --git a/core/modules/workspaces/src/Form/WorkspaceSwitcherForm.php b/core/modules/workspaces/src/Form/WorkspaceSwitcherForm.php index 3fc5570c53ca..186aed1b78a7 100644 --- a/core/modules/workspaces/src/Form/WorkspaceSwitcherForm.php +++ b/core/modules/workspaces/src/Form/WorkspaceSwitcherForm.php @@ -81,12 +81,14 @@ class WorkspaceSwitcherForm extends FormBase implements WorkspaceFormInterface { } $active_workspace = $this->workspaceManager->getActiveWorkspace(); - unset($workspace_labels[$active_workspace->id()]); + if ($active_workspace) { + unset($workspace_labels[$active_workspace->id()]); + } $form['current'] = [ '#type' => 'item', '#title' => $this->t('Current workspace'), - '#markup' => $active_workspace->label(), + '#markup' => $active_workspace ? $active_workspace->label() : $this->t('None'), '#wrapper_attributes' => [ 'class' => ['container-inline'], ], @@ -100,13 +102,27 @@ class WorkspaceSwitcherForm extends FormBase implements WorkspaceFormInterface { '#wrapper_attributes' => [ 'class' => ['container-inline'], ], + '#access' => !empty($workspace_labels), ]; - $form['submit'] = [ + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Activate'), + '#button_type' => 'primary', + '#access' => !empty($workspace_labels), ]; + if ($active_workspace) { + $form['actions']['switch_to_live'] = [ + '#type' => 'submit', + '#submit' => ['::submitSwitchToLive'], + '#value' => $this->t('Switch to Live'), + '#limit_validation_errors' => [], + '#button_type' => 'primary', + ]; + } + return $form; } @@ -128,4 +144,12 @@ class WorkspaceSwitcherForm extends FormBase implements WorkspaceFormInterface { } } + /** + * Submit handler for switching to the live version of the site. + */ + public function submitSwitchToLive(array &$form, FormStateInterface $form_state) { + $this->workspaceManager->switchToLive(); + $this->messenger->addMessage($this->t('You are now viewing the live version of the site.')); + } + } diff --git a/core/modules/workspaces/src/FormOperations.php b/core/modules/workspaces/src/FormOperations.php index 6795f20a9f21..8e3334738ac5 100644 --- a/core/modules/workspaces/src/FormOperations.php +++ b/core/modules/workspaces/src/FormOperations.php @@ -56,8 +56,8 @@ class FormOperations implements ContainerInjectionInterface { * @see hook_form_alter() */ public function formAlter(array &$form, FormStateInterface $form_state, $form_id) { - // No alterations are needed in the default workspace. - if ($this->workspaceManager->getActiveWorkspace()->isDefaultWorkspace()) { + // No alterations are needed if we're not in a workspace context. + if (!$this->workspaceManager->hasActiveWorkspace()) { return; } diff --git a/core/modules/workspaces/src/Negotiator/DefaultWorkspaceNegotiator.php b/core/modules/workspaces/src/Negotiator/DefaultWorkspaceNegotiator.php deleted file mode 100644 index 90652fb10ac4..000000000000 --- a/core/modules/workspaces/src/Negotiator/DefaultWorkspaceNegotiator.php +++ /dev/null @@ -1,68 +0,0 @@ -workspaceStorage = $entity_type_manager->getStorage('workspace'); - } - - /** - * {@inheritdoc} - */ - public function applies(Request $request) { - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getActiveWorkspace(Request $request) { - if (!$this->defaultWorkspace) { - $default_workspace = $this->workspaceStorage->create([ - 'id' => WorkspaceInterface::DEFAULT_WORKSPACE, - 'label' => Unicode::ucwords(WorkspaceInterface::DEFAULT_WORKSPACE), - ]); - $default_workspace->enforceIsNew(FALSE); - - $this->defaultWorkspace = $default_workspace; - } - - return $this->defaultWorkspace; - } - - /** - * {@inheritdoc} - */ - public function setActiveWorkspace(WorkspaceInterface $workspace) {} - -} diff --git a/core/modules/workspaces/src/Negotiator/SessionWorkspaceNegotiator.php b/core/modules/workspaces/src/Negotiator/SessionWorkspaceNegotiator.php index 523733d21625..0a0550b4cd7d 100644 --- a/core/modules/workspaces/src/Negotiator/SessionWorkspaceNegotiator.php +++ b/core/modules/workspaces/src/Negotiator/SessionWorkspaceNegotiator.php @@ -78,4 +78,11 @@ class SessionWorkspaceNegotiator implements WorkspaceNegotiatorInterface { $this->session->set('active_workspace_id', $workspace->id()); } + /** + * {@inheritdoc} + */ + public function unsetActiveWorkspace() { + $this->session->remove('active_workspace_id'); + } + } diff --git a/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php b/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php index a3f0e3b87d98..caf6c788fc2a 100644 --- a/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php +++ b/core/modules/workspaces/src/Negotiator/WorkspaceNegotiatorInterface.php @@ -47,4 +47,9 @@ interface WorkspaceNegotiatorInterface { */ public function setActiveWorkspace(WorkspaceInterface $workspace); + /** + * Unsets the negotiated workspace. + */ + public function unsetActiveWorkspace(); + } diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php index 11083df0f631..feb7e3481358 100644 --- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php +++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php @@ -50,7 +50,8 @@ class EntityReferenceSupportedNewEntitiesConstraintValidator extends ConstraintV * {@inheritdoc} */ public function validate($value, Constraint $constraint) { - if ($this->workspaceManager->getActiveWorkspace()->isDefaultWorkspace()) { + // The validator should run only if we are in a active workspace context. + if (!$this->workspaceManager->hasActiveWorkspace()) { return; } diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php index 89691d751cc7..17adc7c0d2d8 100644 --- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php +++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php @@ -62,7 +62,7 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp $workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity); $active_workspace = $this->workspaceManager->getActiveWorkspace(); - if ($workspace_ids && !in_array($active_workspace->id(), $workspace_ids, TRUE)) { + if ($workspace_ids && (!$active_workspace || !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/workspaces/src/ViewsQueryAlter.php b/core/modules/workspaces/src/ViewsQueryAlter.php index 8cc93710c93b..a6e4e0d528e8 100644 --- a/core/modules/workspaces/src/ViewsQueryAlter.php +++ b/core/modules/workspaces/src/ViewsQueryAlter.php @@ -108,8 +108,8 @@ class ViewsQueryAlter implements ContainerInjectionInterface { * @see hook_views_query_alter() */ public function alterQuery(ViewExecutable $view, QueryPluginBase $query) { - // Don't alter any views queries if we're in the default workspace. - if ($this->workspaceManager->getActiveWorkspace()->isDefaultWorkspace()) { + // Don't alter any views queries if we're not in a workspace context. + if (!$this->workspaceManager->hasActiveWorkspace()) { return; } diff --git a/core/modules/workspaces/src/WorkspaceAccessControlHandler.php b/core/modules/workspaces/src/WorkspaceAccessControlHandler.php index 06a58303f151..196657b9e55d 100644 --- a/core/modules/workspaces/src/WorkspaceAccessControlHandler.php +++ b/core/modules/workspaces/src/WorkspaceAccessControlHandler.php @@ -19,19 +19,10 @@ class WorkspaceAccessControlHandler extends EntityAccessControlHandler { */ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { /** @var \Drupal\workspaces\WorkspaceInterface $entity */ - if ($operation === 'delete' && $entity->isDefaultWorkspace()) { - return AccessResult::forbidden()->addCacheableDependency($entity); - } - if ($account->hasPermission('administer workspaces')) { return AccessResult::allowed()->cachePerPermissions(); } - // The default workspace is always viewable, no matter what. - if ($operation == 'view' && $entity->isDefaultWorkspace()) { - return AccessResult::allowed()->addCacheableDependency($entity); - } - $permission_operation = $operation === 'update' ? 'edit' : $operation; // Check if the user has permission to access all workspaces. diff --git a/core/modules/workspaces/src/WorkspaceCacheContext.php b/core/modules/workspaces/src/WorkspaceCacheContext.php index a5b697eedc2d..fb78c9da898b 100644 --- a/core/modules/workspaces/src/WorkspaceCacheContext.php +++ b/core/modules/workspaces/src/WorkspaceCacheContext.php @@ -40,7 +40,7 @@ class WorkspaceCacheContext implements CacheContextInterface { * {@inheritdoc} */ public function getContext() { - return $this->workspaceManager->getActiveWorkspace()->id(); + return $this->workspaceManager->hasActiveWorkspace() ? $this->workspaceManager->getActiveWorkspace()->id() : 'live'; } /** diff --git a/core/modules/workspaces/src/WorkspaceInterface.php b/core/modules/workspaces/src/WorkspaceInterface.php index ce21fc921ef1..0868e4259014 100644 --- a/core/modules/workspaces/src/WorkspaceInterface.php +++ b/core/modules/workspaces/src/WorkspaceInterface.php @@ -13,6 +13,11 @@ interface WorkspaceInterface extends ContentEntityInterface, EntityChangedInterf /** * The ID of the default workspace. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\workspaces\WorkspaceManager::hasActiveWorkspace() instead. + * + * @see https://www.drupal.org/node/3071527 */ const DEFAULT_WORKSPACE = 'live'; @@ -26,6 +31,11 @@ interface WorkspaceInterface extends ContentEntityInterface, EntityChangedInterf * * @return bool * TRUE if this workspace is the default one (e.g 'Live'), FALSE otherwise. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\workspaces\WorkspaceManager::hasActiveWorkspace() instead. + * + * @see https://www.drupal.org/node/3071527 */ public function isDefaultWorkspace(); diff --git a/core/modules/workspaces/src/WorkspaceListBuilder.php b/core/modules/workspaces/src/WorkspaceListBuilder.php index 9fbe60d998f0..07ba02a7c46e 100644 --- a/core/modules/workspaces/src/WorkspaceListBuilder.php +++ b/core/modules/workspaces/src/WorkspaceListBuilder.php @@ -75,7 +75,7 @@ class WorkspaceListBuilder extends EntityListBuilder { $row['data'] = $row['data'] + parent::buildRow($entity); $active_workspace = $this->workspaceManager->getActiveWorkspace(); - if ($entity->id() === $active_workspace->id()) { + if ($active_workspace && $entity->id() === $active_workspace->id()) { $row['class'] = 'active-workspace'; } return $row; @@ -92,7 +92,7 @@ class WorkspaceListBuilder extends EntityListBuilder { } $active_workspace = $this->workspaceManager->getActiveWorkspace(); - if ($entity->id() != $active_workspace->id()) { + if (!$active_workspace || $entity->id() != $active_workspace->id()) { $operations['activate'] = [ 'title' => $this->t('Switch to @workspace', ['@workspace' => $entity->label()]), // Use a weight lower than the one of the 'Edit' operation because we @@ -102,30 +102,17 @@ class WorkspaceListBuilder extends EntityListBuilder { ]; } - if (!$entity->isDefaultWorkspace()) { - $operations['deploy'] = [ - 'title' => $this->t('Deploy content'), - // The 'Deploy' operation should be the default one for the currently - // active workspace. - 'weight' => ($entity->id() == $active_workspace->id()) ? 0 : 20, - 'url' => $entity->toUrl('deploy-form', ['query' => ['destination' => $entity->toUrl('collection')->toString()]]), - ]; - } + $operations['deploy'] = [ + 'title' => $this->t('Deploy content'), + // The 'Deploy' operation should be the default one for the currently + // active workspace. + 'weight' => ($active_workspace && $entity->id() == $active_workspace->id()) ? 0 : 20, + 'url' => $entity->toUrl('deploy-form', ['query' => ['destination' => $entity->toUrl('collection')->toString()]]), + ]; return $operations; } - /** - * {@inheritdoc} - */ - public function load() { - $entities = parent::load(); - // Make the active workspace more visible by moving it first in the list. - $active_workspace = $this->workspaceManager->getActiveWorkspace(); - $entities = [$active_workspace->id() => $entities[$active_workspace->id()]] + $entities; - return $entities; - } - /** * {@inheritdoc} */ @@ -150,34 +137,43 @@ class WorkspaceListBuilder extends EntityListBuilder { */ protected function offCanvasRender(array &$build) { $active_workspace = $this->workspaceManager->getActiveWorkspace(); + if ($active_workspace) { + $active_workspace_classes = [ + 'active-workspace--not-default', + 'active-workspace--' . $active_workspace->id(), + ]; + } + else { + $active_workspace_classes = [ + 'active-workspace--default', + ]; + } + + $collection_url = Url::fromRoute('entity.workspace.collection'); $row_count = count($build['table']['#rows']); $build['active_workspace'] = [ '#type' => 'container', '#weight' => -20, '#attributes' => [ - 'class' => [ - 'active-workspace', - $active_workspace->isDefaultWorkspace() ? 'active-workspace--default' : 'active-workspace--not-default', - 'active-workspace--' . $active_workspace->id(), - ], + 'class' => array_merge(['active-workspace'], $active_workspace_classes), ], 'label' => [ '#type' => 'label', '#prefix' => '
' . $this->t('Current workspace:') . '
', - '#title' => $active_workspace->label(), + '#title' => $active_workspace ? $active_workspace->label() : $this->t('Live'), '#title_display' => '', '#attributes' => ['class' => 'active-workspace__label'], ], 'manage' => [ '#type' => 'link', '#title' => $this->t('Manage workspaces'), - '#url' => $active_workspace->toUrl('collection'), + '#url' => $collection_url, '#attributes' => [ 'class' => ['active-workspace__manage'], ], ], ]; - if (!$active_workspace->isDefaultWorkspace()) { + if ($active_workspace) { $build['active_workspace']['actions'] = [ '#type' => 'container', '#weight' => 20, @@ -198,7 +194,7 @@ class WorkspaceListBuilder extends EntityListBuilder { $build['all_workspaces'] = [ '#type' => 'link', '#title' => $this->t('View all @count workspaces', ['@count' => $row_count]), - '#url' => $active_workspace->toUrl('collection'), + '#url' => $collection_url, '#attributes' => [ 'class' => ['all-workspaces'], ], @@ -207,15 +203,14 @@ class WorkspaceListBuilder extends EntityListBuilder { $items = []; $rows = array_slice($build['table']['#rows'], 0, 5, TRUE); foreach ($rows as $id => $row) { - if ($active_workspace->id() !== $id) { + if (!$active_workspace || $active_workspace->id() !== $id) { $url = Url::fromRoute('entity.workspace.activate_form', ['workspace' => $id], ['query' => $this->getDestinationArray()]); - $default_class = $id === WorkspaceInterface::DEFAULT_WORKSPACE ? 'workspaces__item--default' : 'workspaces__item--not-default'; $items[] = [ '#type' => 'link', '#title' => $row['data']['label'], '#url' => $url, '#attributes' => [ - 'class' => ['use-ajax', 'workspaces__item', $default_class], + 'class' => ['use-ajax', 'workspaces__item', 'workspaces__item--not-default'], 'data-dialog-type' => 'modal', 'data-dialog-options' => Json::encode([ 'width' => 500, @@ -224,6 +219,23 @@ class WorkspaceListBuilder extends EntityListBuilder { ]; } } + + // Add an item for switching to Live. + if ($active_workspace) { + $items[] = [ + '#type' => 'link', + '#title' => $this->t('Live'), + '#url' => Url::fromRoute('workspaces.switch_to_live', [], ['query' => $this->getDestinationArray()]), + '#attributes' => [ + 'class' => ['use-ajax', 'workspaces__item', 'workspaces__item--default'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 500, + ]), + ], + ]; + } + $build['workspaces'] = [ '#theme' => 'item_list', '#items' => $items, diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php index b44e895d172e..5528988a80ba 100644 --- a/core/modules/workspaces/src/WorkspaceManager.php +++ b/core/modules/workspaces/src/WorkspaceManager.php @@ -91,9 +91,9 @@ class WorkspaceManager implements WorkspaceManagerInterface { protected $negotiatorIds; /** - * The current active workspace. + * The current active workspace or FALSE if there is no active workspace. * - * @var \Drupal\workspaces\WorkspaceInterface + * @var \Drupal\workspaces\WorkspaceInterface|false */ protected $activeWorkspace; @@ -160,6 +160,13 @@ class WorkspaceManager implements WorkspaceManagerInterface { return $entity_types; } + /** + * {@inheritdoc} + */ + public function hasActiveWorkspace() { + return $this->getActiveWorkspace() !== FALSE; + } + /** * {@inheritdoc} */ @@ -169,14 +176,17 @@ class WorkspaceManager implements WorkspaceManagerInterface { foreach ($this->negotiatorIds as $negotiator_id) { $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); if ($negotiator->applies($request)) { - if ($this->activeWorkspace = $negotiator->getActiveWorkspace($request)) { + if ($active_workspace = $negotiator->getActiveWorkspace($request)) { break; } } } + + // If no negotiator was able to determine the active workspace, default to + // the live version of the site. + $this->activeWorkspace = $active_workspace ?? FALSE; } - // The default workspace negotiator always returns a valid workspace. return $this->activeWorkspace; } @@ -199,19 +209,35 @@ class WorkspaceManager implements WorkspaceManagerInterface { return $this; } + /** + * {@inheritdoc} + */ + public function switchToLive() { + $this->doSwitchWorkspace(NULL); + + // Unset the active workspace on all negotiators. + foreach ($this->negotiatorIds as $negotiator_id) { + $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); + $negotiator->unsetActiveWorkspace(); + } + + return $this; + } + /** * Switches the current workspace. * - * @param \Drupal\workspaces\WorkspaceInterface $workspace - * The workspace to set as active. + * @param \Drupal\workspaces\WorkspaceInterface|null $workspace + * The workspace to set as active or NULL to switch out of the currently + * active workspace. * * @throws \Drupal\workspaces\WorkspaceAccessException * Thrown when the current user doesn't have access to view the workspace. */ - protected function doSwitchWorkspace(WorkspaceInterface $workspace) { + protected function doSwitchWorkspace($workspace) { // If the current user doesn't have access to view the workspace, they // shouldn't be allowed to switch to it. - if (!$workspace->access('view') && !$workspace->isDefaultWorkspace()) { + if ($workspace && !$workspace->access('view')) { $this->logger->error('Denied access to view workspace %workspace_label for user %uid', [ '%workspace_label' => $workspace->label(), '%uid' => $this->currentUser->id(), @@ -219,7 +245,7 @@ class WorkspaceManager implements WorkspaceManagerInterface { throw new WorkspaceAccessException('The user does not have permission to view that workspace.'); } - $this->activeWorkspace = $workspace; + $this->activeWorkspace = $workspace ?: FALSE; // Clear the static entity cache for the supported entity types. $cache_tags_to_invalidate = array_map(function ($entity_type_id) { @@ -247,11 +273,23 @@ class WorkspaceManager implements WorkspaceManagerInterface { return $result; } + /** + * {@inheritdoc} + */ + public function executeOutsideWorkspace(callable $function) { + $previous_active_workspace = $this->getActiveWorkspace(); + $this->doSwitchWorkspace(NULL); + $result = $function(); + $this->doSwitchWorkspace($previous_active_workspace); + + return $result; + } + /** * {@inheritdoc} */ public function shouldAlterOperations(EntityTypeInterface $entity_type) { - return $this->isEntityTypeSupported($entity_type) && !$this->getActiveWorkspace()->isDefaultWorkspace(); + return $this->isEntityTypeSupported($entity_type) && $this->hasActiveWorkspace(); } /** diff --git a/core/modules/workspaces/src/WorkspaceManagerInterface.php b/core/modules/workspaces/src/WorkspaceManagerInterface.php index 006a2bd86d8a..24d81460a5cf 100644 --- a/core/modules/workspaces/src/WorkspaceManagerInterface.php +++ b/core/modules/workspaces/src/WorkspaceManagerInterface.php @@ -28,6 +28,14 @@ interface WorkspaceManagerInterface { */ public function getSupportedEntityTypes(); + /** + * Determines whether a workspace is active in the current request. + * + * @return bool + * TRUE if a workspace is active, FALSE otherwise. + */ + public function hasActiveWorkspace(); + /** * Gets the active workspace. * @@ -49,6 +57,13 @@ interface WorkspaceManagerInterface { */ public function setActiveWorkspace(WorkspaceInterface $workspace); + /** + * Unsets the active workspace via the workspace negotiators. + * + * @return $this + */ + public function switchToLive(); + /** * Executes the given callback function in the context of a workspace. * @@ -62,6 +77,17 @@ interface WorkspaceManagerInterface { */ public function executeInWorkspace($workspace_id, callable $function); + /** + * Executes the given callback function without any workspace context. + * + * @param callable $function + * The callback to be executed. + * + * @return mixed + * The callable's return value. + */ + public function executeOutsideWorkspace(callable $function); + /** * Determines whether runtime entity operations should be altered. * diff --git a/core/modules/workspaces/src/WorkspacePublisher.php b/core/modules/workspaces/src/WorkspacePublisher.php index 5047eb8f2bfe..c7da6b8be782 100644 --- a/core/modules/workspaces/src/WorkspacePublisher.php +++ b/core/modules/workspaces/src/WorkspacePublisher.php @@ -4,6 +4,7 @@ namespace Drupal\workspaces; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Default implementation of the workspace publisher. @@ -12,6 +13,8 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; */ class WorkspacePublisher implements WorkspacePublisherInterface { + use StringTranslationTrait; + /** * The source workspace entity. * @@ -19,13 +22,6 @@ class WorkspacePublisher implements WorkspacePublisherInterface { */ protected $sourceWorkspace; - /** - * The target workspace entity. - * - * @var \Drupal\workspaces\WorkspaceInterface - */ - protected $targetWorkspace; - /** * The entity type manager. * @@ -70,7 +66,6 @@ class WorkspacePublisher implements WorkspacePublisherInterface { $this->workspaceAssociationStorage = $entity_type_manager->getStorage('workspace_association'); $this->workspaceManager = $workspace_manager; $this->sourceWorkspace = $source; - $this->targetWorkspace = $this->entityTypeManager->getStorage('workspace')->load(WorkspaceInterface::DEFAULT_WORKSPACE); } /** @@ -85,7 +80,7 @@ class WorkspacePublisher implements WorkspacePublisherInterface { try { // @todo Handle the publishing of a workspace with a batch operation in // https://www.drupal.org/node/2958752. - $this->workspaceManager->executeInWorkspace($this->targetWorkspace->id(), function () { + $this->workspaceManager->executeOutsideWorkspace(function () { foreach ($this->getDifferringRevisionIdsOnSource() as $entity_type_id => $revision_difference) { $entity_revisions = $this->entityTypeManager->getStorage($entity_type_id) @@ -128,7 +123,7 @@ class WorkspacePublisher implements WorkspacePublisherInterface { * {@inheritdoc} */ public function getTargetLabel() { - return $this->targetWorkspace->label(); + return $this->t('Live'); } /** diff --git a/core/modules/workspaces/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php b/core/modules/workspaces/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php index 79f24da5a8ea..e05ca3078a3c 100644 --- a/core/modules/workspaces/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php +++ b/core/modules/workspaces/tests/src/Functional/EntityResource/WorkspaceResourceTestBase.php @@ -108,7 +108,7 @@ abstract class WorkspaceResourceTestBase extends EntityResourceTestBase { ], 'revision_id' => [ [ - 'value' => 3, + 'value' => 2, ], ], 'uid' => [ diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceCacheContextTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceCacheContextTest.php index f47ae7f544d6..8945a69176cd 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceCacheContextTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceCacheContextTest.php @@ -79,6 +79,11 @@ class WorkspaceCacheContextTest extends BrowserTestBase { $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys($build['#cache']['contexts'])->getKeys()); $this->assertTrue(in_array('[workspace]=stage', $cid_parts, TRUE)); + + // Test that a cache entry is created. + $cid = implode(':', $cid_parts); + $bin = $build['#cache']['bin']; + $this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.'); } } diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceConcurrentEditingTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceConcurrentEditingTest.php index 996e72decc1c..27676ae06b07 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceConcurrentEditingTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceConcurrentEditingTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\workspaces\Functional; use Drupal\Tests\BrowserTestBase; -use Drupal\workspaces\Entity\Workspace; /** * Tests concurrent edits in different workspaces. @@ -23,19 +22,20 @@ class WorkspaceConcurrentEditingTest extends BrowserTestBase { * Test switching workspace via the switcher block and admin page. */ public function testSwitchingWorkspaces() { + // Create a test node. + $this->createContentType(['type' => 'test', 'label' => 'Test']); + $this->setupWorkspaceSwitcherBlock(); + $permissions = [ 'create workspace', 'edit own workspace', 'view own workspace', - 'bypass entity access own workspace', + 'create test content', + 'edit own test content', ]; - $mayer = $this->drupalCreateUser($permissions); $this->drupalLogin($mayer); - $this->setupWorkspaceSwitcherBlock(); - // Create a test node. - $this->createContentType(['type' => 'test', 'label' => 'Test']); $test_node = $this->createNodeThroughUi('Test node', 'test'); // Check that the user can edit the node. @@ -71,10 +71,9 @@ class WorkspaceConcurrentEditingTest extends BrowserTestBase { $this->assertCount(1, $violations); $this->assertEquals('The content is being edited in the Vultures workspace. As a result, your changes cannot be saved.', $violations->get(0)->getMessage()); - // Switch to the Live workspace and check that the user still can not edit - // the node. - $live = Workspace::load('live'); - $this->switchToWorkspace($live); + // Switch to the Live version of the site and check that the user still can + // not edit the node. + $this->switchToLive(); $this->drupalGet('/node/' . $test_node->id() . '/edit'); $page = $this->getSession()->getPage(); $this->assertFalse($page->hasField('title[0][value]')); diff --git a/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php b/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php index 473a861c3f3f..c70ceeac6688 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php @@ -199,11 +199,6 @@ class WorkspacePermissionsTest extends BrowserTestBase { $this->drupalGet("/admin/config/workflow/workspaces/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/workspaces/manage/live/delete"); - $this->assertSession()->statusCodeEquals(403); } } diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php index ce0b3ed47014..63d7d8609f24 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php @@ -67,14 +67,15 @@ class WorkspaceSwitcherTest extends BrowserTestBase { public function testQueryParameterNegotiator() { $web_assert = $this->assertSession(); // Initially the default workspace should be active. - $web_assert->elementContains('css', '.block-workspace-switcher', 'Live'); + $web_assert->elementContains('css', '.block-workspace-switcher', 'None'); // When adding a query parameter the workspace will be switched. - $this->drupalGet('', ['query' => ['workspace' => 'stage']]); + $current_user_url = \Drupal::currentUser()->getAccount()->toUrl(); + $this->drupalGet($current_user_url, ['query' => ['workspace' => 'stage']]); $web_assert->elementContains('css', '.block-workspace-switcher', 'Stage'); // The workspace switching via query parameter should persist. - $this->drupalGet(''); + $this->drupalGet($current_user_url); $web_assert->elementContains('css', '.block-workspace-switcher', 'Stage'); // Check that WorkspaceCacheContext provides the cache context used to diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php index 0352633ce8ca..a0f887706172 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php @@ -131,14 +131,14 @@ class WorkspaceTest extends BrowserTestBase { $this->drupalLogin($this->editor1); $storage = \Drupal::entityTypeManager()->getStorage('workspace'); - // The current live workspace entity should be revision 1. - $live_workspace = $storage->load('live'); - $this->assertEquals('1', $live_workspace->getRevisionId()); + // The current 'stage' workspace entity should be revision 1. + $stage_workspace = $storage->load('stage'); + $this->assertEquals('1', $stage_workspace->getRevisionId()); - // Re-save the live workspace via the UI to create revision 3. - $this->drupalPostForm($live_workspace->toUrl('edit-form')->toString(), [], 'Save'); - $live_workspace = $storage->loadUnchanged('live'); - $this->assertEquals('3', $live_workspace->getRevisionId()); + // Re-save the 'stage' workspace via the UI to create revision 2. + $this->drupalPostForm($stage_workspace->toUrl('edit-form')->toString(), [], 'Save'); + $stage_workspace = $storage->loadUnchanged('stage'); + $this->assertEquals('2', $stage_workspace->getRevisionId()); } /** diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php b/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php index c50401a32918..bede33a866e2 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php @@ -101,6 +101,19 @@ trait WorkspaceTestUtilities { $session->pageTextContains($workspace->label() . ' is now the active workspace.'); } + /** + * Switches to the live version of the site for subsequent requests. + * + * This assumes that the switcher block has already been setup by calling + * setupWorkspaceSwitcherBlock(). + */ + protected function switchToLive() { + /** @var \Drupal\Tests\WebAssert $session */ + $session = $this->assertSession(); + $this->drupalPostForm(NULL, [], 'Switch to Live'); + $session->pageTextContains('You are now viewing the live version of the site.'); + } + /** * Creates a node by "clicking" buttons. * diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php index 26e1c622b52c..77c24efdcda8 100644 --- a/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php @@ -505,7 +505,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { $storage = $this->entityTypeManager->getStorage($entity_type_id); // Create the entity in the default workspace. - $this->switchToWorkspace('live'); + $this->workspaceManager->switchToLive(); $entity = $storage->createWithSampleValues($entity_type_id); if ($entity_type_id === 'workspace') { $entity->id = 'test'; @@ -536,7 +536,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { $storage = $this->entityTypeManager->getStorage($entity_type_id); // Create the entity in the default workspace. - $this->switchToWorkspace('live'); + $this->workspaceManager->switchToLive(); $entity = $storage->createWithSampleValues($entity_type_id); if ($entity_type_id === 'workspace') { $entity->id = 'test'; @@ -581,7 +581,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { $this->initializeWorkspacesModule(); // Create an entity in the default workspace. - $this->switchToWorkspace('live'); + $this->workspaceManager->switchToLive(); $node = $this->createNode([ 'title' => 'live node 1', ]); @@ -594,12 +594,12 @@ class WorkspaceIntegrationTest extends KernelTestBase { $node->save(); // Switch back to the default workspace and run the baseline assertions. - $this->switchToWorkspace('live'); + $this->workspaceManager->switchToLive(); $storage = $this->entityTypeManager->getStorage('node'); - $this->assertEquals('live', $this->workspaceManager->getActiveWorkspace()->id()); + $this->assertFalse($this->workspaceManager->hasActiveWorkspace()); - $live_node = $storage->loadUnchanged($node->id()); + $live_node = $storage->load($node->id()); $this->assertEquals('live node 1', $live_node->title->value); $result = $storage->getQuery() @@ -611,7 +611,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { $this->workspaceManager->executeInWorkspace('stage', function () use ($node, $storage) { $this->assertEquals('stage', $this->workspaceManager->getActiveWorkspace()->id()); - $stage_node = $storage->loadUnchanged($node->id()); + $stage_node = $storage->load($node->id()); $this->assertEquals('stage node 1', $stage_node->title->value); $result = $storage->getQuery() @@ -622,7 +622,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { // Check that the 'stage' workspace was not persisted by the workspace // manager. - $this->assertEquals('live', $this->workspaceManager->getActiveWorkspace()->id()); + $this->assertFalse($this->workspaceManager->getActiveWorkspace()); } /** @@ -879,7 +879,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { $this->initializeWorkspacesModule(); $node_storage = $this->entityTypeManager->getStorage('node'); - $this->switchToWorkspace('live'); + $this->workspaceManager->switchToLive(); $node = $node_storage->create([ 'title' => 'Foo title', // Use the body field on node as a test case because it requires dedicated @@ -895,7 +895,7 @@ class WorkspaceIntegrationTest extends KernelTestBase { $node->save(); $this->workspaces['stage']->publish(); - $this->switchToWorkspace('live'); + $this->workspaceManager->switchToLive(); $reloaded = $node_storage->load($node->id()); $this->assertEquals('Bar title', $reloaded->title->value); diff --git a/core/modules/workspaces/tests/src/Unit/ActiveWorkspaceCheckTest.php b/core/modules/workspaces/tests/src/Unit/ActiveWorkspaceCheckTest.php new file mode 100644 index 000000000000..34db19b58a48 --- /dev/null +++ b/core/modules/workspaces/tests/src/Unit/ActiveWorkspaceCheckTest.php @@ -0,0 +1,73 @@ +container = new ContainerBuilder(); + $cache_contexts_manager = $this->prophesize(CacheContextsManager::class); + $cache_contexts_manager->assertValidTokens()->willReturn(TRUE); + $cache_contexts_manager->reveal(); + $this->container->set('cache_contexts_manager', $cache_contexts_manager); + \Drupal::setContainer($this->container); + } + + /** + * Provides data for the testAccess method. + * + * @return array + */ + public function providerTestAccess() { + return [ + [[], FALSE, FALSE], + [[], TRUE, FALSE], + [['_has_active_workspace' => 'TRUE'], TRUE, TRUE, ['workspace']], + [['_has_active_workspace' => 'TRUE'], FALSE, FALSE, ['workspace']], + [['_has_active_workspace' => 'FALSE'], TRUE, FALSE, ['workspace']], + [['_has_active_workspace' => 'FALSE'], FALSE, TRUE, ['workspace']], + ]; + } + + /** + * @covers ::access + * @dataProvider providerTestAccess + */ + public function testAccess($requirements, $has_active_workspace, $access, array $contexts = []) { + $route = new Route('', [], $requirements); + + $workspace_manager = $this->prophesize(WorkspaceManagerInterface::class); + $workspace_manager->hasActiveWorkspace()->willReturn($has_active_workspace); + $access_check = new ActiveWorkspaceCheck($workspace_manager->reveal()); + + $access_result = AccessResult::allowedIf($access)->addCacheContexts($contexts); + $this->assertEquals($access_result, $access_check->access($route)); + } + +} diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install index 7bcc18b64103..c6ac304b6db3 100644 --- a/core/modules/workspaces/workspaces.install +++ b/core/modules/workspaces/workspaces.install @@ -54,13 +54,7 @@ function workspaces_install() { // Default to user ID 1 if we could not find any other administrator users. $owner_id = !empty($result) ? reset($result) : 1; - // Create two workspaces by default, 'live' and 'stage'. - Workspace::create([ - 'id' => 'live', - 'label' => 'Live', - 'uid' => $owner_id, - ])->save(); - + // Create a 'stage' workspace by default. Workspace::create([ 'id' => 'stage', 'label' => 'Stage', diff --git a/core/modules/workspaces/workspaces.module b/core/modules/workspaces/workspaces.module index 817edc47da3f..d47985b223de 100644 --- a/core/modules/workspaces/workspaces.module +++ b/core/modules/workspaces/workspaces.module @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; use Drupal\views\Plugin\views\query\QueryPluginBase; use Drupal\views\ViewExecutable; use Drupal\workspaces\EntityAccess; @@ -173,8 +174,8 @@ function workspaces_toolbar() { '#type' => 'toolbar_item', 'tab' => [ '#type' => 'link', - '#title' => $active_workspace->label(), - '#url' => $active_workspace->toUrl('collection', ['query' => \Drupal::destination()->getAsArray()]), + '#title' => $active_workspace ? $active_workspace->label() : t('Live'), + '#url' => Url::fromRoute('entity.workspace.collection', [], ['query' => \Drupal::destination()->getAsArray()]), '#attributes' => [ 'title' => t('Switch workspace'), 'class' => ['use-ajax', 'toolbar-icon', 'toolbar-icon-workspace'], @@ -187,7 +188,7 @@ function workspaces_toolbar() { ], ]), ], - '#cache' => ['tags' => $active_workspace->getCacheTags()], + '#cache' => ['tags' => $active_workspace ? $active_workspace->getCacheTags() : []], ], '#wrapper_attributes' => [ 'class' => ['workspaces-toolbar-tab'], @@ -198,9 +199,9 @@ function workspaces_toolbar() { '#weight' => 500, ]; - // 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()) { + // Add a special class to the wrapper if we don't have an active workspace so + // we can highlight it with a different color. + if (!$active_workspace) { $items['workspace']['#wrapper_attributes']['class'][] = 'workspaces-toolbar-tab--is-default'; } diff --git a/core/modules/workspaces/workspaces.post_update.php b/core/modules/workspaces/workspaces.post_update.php index 2ca0ced399e0..0df5add2cb8d 100644 --- a/core/modules/workspaces/workspaces.post_update.php +++ b/core/modules/workspaces/workspaces.post_update.php @@ -10,3 +10,12 @@ */ function workspaces_post_update_access_clear_caches() { } + +/** + * Remove the default workspace. + */ +function workspaces_post_update_remove_default_workspace() { + if ($workspace = \Drupal::entityTypeManager()->getStorage('workspace')->load('live')) { + $workspace->delete(); + } +} diff --git a/core/modules/workspaces/workspaces.routing.yml b/core/modules/workspaces/workspaces.routing.yml index d6d42bbd5770..4ce63c9716e5 100644 --- a/core/modules/workspaces/workspaces.routing.yml +++ b/core/modules/workspaces/workspaces.routing.yml @@ -25,3 +25,12 @@ entity.workspace.deploy_form: _admin_route: TRUE requirements: _permission: 'administer workspaces' + +workspaces.switch_to_live: + path: '/admin/config/workflow/workspaces/switch-to-live' + defaults: + _form: '\Drupal\workspaces\Form\SwitchToLiveForm' + _title: 'Switch to Live' + requirements: + _user_is_logged_in: 'TRUE' + _has_active_workspace: 'TRUE' diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml index 61b83fd06e55..9823d5ce2ec9 100644 --- a/core/modules/workspaces/workspaces.services.yml +++ b/core/modules/workspaces/workspaces.services.yml @@ -8,11 +8,6 @@ services: class: Drupal\workspaces\WorkspaceOperationFactory arguments: ['@entity_type.manager', '@database', '@workspaces.manager'] - workspaces.negotiator.default: - class: Drupal\workspaces\Negotiator\DefaultWorkspaceNegotiator - arguments: ['@entity_type.manager'] - tags: - - { name: workspace_negotiator, priority: 0 } workspaces.negotiator.session: class: Drupal\workspaces\Negotiator\SessionWorkspaceNegotiator arguments: ['@current_user', '@session', '@entity_type.manager'] @@ -24,6 +19,12 @@ services: tags: - { name: workspace_negotiator, priority: 100 } + access_check.workspaces.active_workspace: + class: Drupal\workspaces\Access\ActiveWorkspaceCheck + arguments: ['@workspaces.manager'] + tags: + - { name: access_check, applies_to: _has_active_workspace } + cache_context.workspace: class: Drupal\workspaces\WorkspaceCacheContext arguments: ['@workspaces.manager']