Issue #2935780 by amateescu, Manuel Garcia, Fabianx, Sam152, kunalkursija: Remove the concept of a 'live' default workspace
parent
1b6d10debd
commit
d881bd0abb
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workspaces\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Determines access to routes based on the presence of an active workspace.
|
||||
*/
|
||||
class ActiveWorkspaceCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The workspace manager.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ActiveWorkspaceCheck.
|
||||
*
|
||||
* @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
|
||||
* The workspace manager.
|
||||
*/
|
||||
public function __construct(WorkspaceManagerInterface $workspace_manager) {
|
||||
$this->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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workspaces\Form;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a form that switches to the live version of the site.
|
||||
*/
|
||||
class SwitchToLiveForm extends ConfirmFormBase implements WorkspaceFormInterface, ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The workspace manager.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* Constructs a new SwitchToLiveForm.
|
||||
*
|
||||
* @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
|
||||
* The workspace manager.
|
||||
*/
|
||||
public function __construct(WorkspaceManagerInterface $workspace_manager) {
|
||||
$this->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('<current>');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\workspaces\Negotiator;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\workspaces\WorkspaceInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines the default workspace negotiator.
|
||||
*/
|
||||
class DefaultWorkspaceNegotiator implements WorkspaceNegotiatorInterface {
|
||||
|
||||
/**
|
||||
* The workspace storage handler.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $workspaceStorage;
|
||||
|
||||
/**
|
||||
* The default workspace entity.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceInterface
|
||||
*/
|
||||
protected $defaultWorkspace;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->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) {}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,4 +47,9 @@ interface WorkspaceNegotiatorInterface {
|
|||
*/
|
||||
public function setActiveWorkspace(WorkspaceInterface $workspace);
|
||||
|
||||
/**
|
||||
* Unsets the negotiated workspace.
|
||||
*/
|
||||
public function unsetActiveWorkspace();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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' => '<div class="active-workspace__title">' . $this->t('Current workspace:') . '</div>',
|
||||
'#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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ abstract class WorkspaceResourceTestBase extends EntityResourceTestBase {
|
|||
],
|
||||
'revision_id' => [
|
||||
[
|
||||
'value' => 3,
|
||||
'value' => 2,
|
||||
],
|
||||
],
|
||||
'uid' => [
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <em class="placeholder">Vultures</em> 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]'));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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('<front>', ['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('<front>');
|
||||
$this->drupalGet($current_user_url);
|
||||
$web_assert->elementContains('css', '.block-workspace-switcher', 'Stage');
|
||||
|
||||
// Check that WorkspaceCacheContext provides the cache context used to
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\workspaces\Unit;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\workspaces\Access\ActiveWorkspaceCheck;
|
||||
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workspaces\Access\ActiveWorkspaceCheck
|
||||
*
|
||||
* @group workspaces
|
||||
* @group Access
|
||||
*/
|
||||
class ActiveWorkspaceCheckTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The dependency injection container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
Loading…
Reference in New Issue