Skip to content
Snippets Groups Projects
Commit d881bd0a authored by catch's avatar catch
Browse files

Issue #2935780 by amateescu, Manuel Garcia, Fabianx, Sam152, kunalkursija:...

Issue #2935780 by amateescu, Manuel Garcia, Fabianx, Sam152, kunalkursija: Remove the concept of a 'live' default workspace
parent 1b6d10de
Branches
Tags
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
Showing
with 242 additions and 148 deletions
......@@ -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\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 @@ protected static function getAccessCondition($entity_type_id, CacheableMetadata
// @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.
......
<?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 @@ public function publish() {
* {@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 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
// 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 @@ public function entityOperationAccess(EntityInterface $entity, $operation, Accou
// Workspaces themselves are handled by their own access handler and we
// should not try to do any access checks for entity types that can not
// belong to a workspace.
if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) {
if ($entity->getEntityTypeId() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity->getEntityType()) || !$this->workspaceManager->hasActiveWorkspace()) {
return AccessResult::neutral();
}
......@@ -102,7 +102,7 @@ public function entityCreateAccess(AccountInterface $account, array $context, $e
// should not try to do any access checks for entity types that can not
// belong to a workspace.
$entity_type = $this->entityTypeManager->getDefinition($context['entity_type_id']);
if ($entity_type->id() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity_type)) {
if ($entity_type->id() === 'workspace' || !$this->workspaceManager->isEntityTypeSupported($entity_type) || !$this->workspaceManager->hasActiveWorkspace()) {
return AccessResult::neutral();
}
......
......@@ -314,8 +314,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, $f
// \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 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, $f
// 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 @@ protected function shouldSkipPreOperations(EntityTypeInterface $entity_type) {
// - 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 @@ public function prepare() {
// Only alter the query if the active workspace is not the default one and
// the entity type is supported.
if ($this->workspaceManager->hasActiveWorkspace() && $this->workspaceManager->isEntityTypeSupported($this->entityType)) {
$active_workspace = $this->workspaceManager->getActiveWorkspace();
if (!$active_workspace->isDefaultWorkspace() && $this->workspaceManager->isEntityTypeSupported($this->entityType)) {
$this->sqlQuery->addMetaData('active_workspace_id', $active_workspace->id());
$this->sqlQuery->addMetaData('simple_query', FALSE);
......
<?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 @@ public function buildForm(array $form, FormStateInterface $form_state) {
}
$active_workspace = $this->workspaceManager->getActiveWorkspace();
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 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#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 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
}
}
/**
* 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 @@ public static function create(ContainerInterface $container) {
* @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;
}
......
<?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 @@ public function setActiveWorkspace(WorkspaceInterface $workspace) {
$this->session->set('active_workspace_id', $workspace->id());
}
/**
* {@inheritdoc}
*/
public function unsetActiveWorkspace() {
$this->session->remove('active_workspace_id');
}
}
......@@ -47,4 +47,9 @@ public function getActiveWorkspace(Request $request);
*/
public function setActiveWorkspace(WorkspaceInterface $workspace);
/**
* Unsets the negotiated workspace.
*/
public function unsetActiveWorkspace();
}
......@@ -50,7 +50,8 @@ public static function create(ContainerInterface $container) {
* {@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 @@ public function validate($entity, Constraint $constraint) {
$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 @@ public static function create(ContainerInterface $container) {
* @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 @@ public static function getLabel() {
* {@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 @@ public function publish();
*
* @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 @@ public function buildRow(EntityInterface $entity) {
$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 @@ public function getDefaultOperations(EntityInterface $entity) {
}
$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 @@ public function getDefaultOperations(EntityInterface $entity) {
];
}
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,
'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 @@ public function render() {
*/
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 @@ protected function offCanvasRender(array &$build) {
$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 @@ protected function offCanvasRender(array &$build) {
$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 @@ protected function offCanvasRender(array &$build) {
];
}
}
// 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,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment