diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 623991a1c192d440c9d505033bee03b77f341bae..40519720aec741111c4304c010a66a8264879fb4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -70,11 +70,6 @@ parameters: count: 2 path: modules/wse_preview/src/Form/WsePreviewLinkForm.php - - - message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#" - count: 1 - path: src/Controller/WseNodeController.php - - message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#" count: 1 diff --git a/src/Controller/VersionHistoryTrait.php b/src/Controller/VersionHistoryTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..59808551f98b03d32d238b4ba41ca3e1618dbeca --- /dev/null +++ b/src/Controller/VersionHistoryTrait.php @@ -0,0 +1,56 @@ +<?php + +namespace Drupal\wse\Controller; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Render\Element; + +/** + * Provides helper methods for the revision overview controllers. + */ +trait VersionHistoryTrait { + + /** + * Adds information about each revision's workspace. + * + * @param array $build + * The render array from the version history controller. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity object. + * @param array|null $revisions + * (optional) An array of revisions that are displayed in the table. + */ + protected function alterRevisionsTable(array &$build, EntityInterface $entity, ?array $revisions = NULL): void { + // Add a column to show the workspace in which a revision has been created. + $keys = Element::children($build); + if (!$revisions) { + $revisions = \Drupal::entityTypeManager() + ->getStorage($entity->getEntityTypeId()) + ->loadMultipleRevisions($keys); + } + + $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace'); + foreach ($keys as $key) { + $revision = current($revisions); + + $label = ''; + if ($workspace = $revision->get($field_name)->entity) { + $label = $workspace->access('view label') ? $workspace->label() : $this->t('- Restricted access -'); + } + + // Insert the workspace label column. + if (isset($build[$key]['data'])) { + $build[$key]['data']['workspace'] = ['data' => $label]; + } + elseif (isset($build[$key][0])) { + $build[$key]['workspace'] = ['data' => $label]; + } + else { + $build[$key]['workspace'] = ['#markup' => $label]; + } + + next($revisions); + } + } + +} diff --git a/src/Controller/WseDiffNodeRevisionController.php b/src/Controller/WseDiffNodeRevisionController.php index 6d178b5084542bec579dd5a12ea839742f500673..5ffa043fec93012fa943507d4f6bc6de27f82e35 100644 --- a/src/Controller/WseDiffNodeRevisionController.php +++ b/src/Controller/WseDiffNodeRevisionController.php @@ -4,14 +4,9 @@ declare(strict_types=1); namespace Drupal\wse\Controller; -use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\diff\Controller\NodeRevisionController; use Drupal\node\NodeInterface; -use Drupal\workspaces\Plugin\Validation\Constraint\EntityWorkspaceConflictConstraint; -use Drupal\workspaces\WorkspaceManagerInterface; use Drupal\wse\Diff\Form\WseRevisionOverviewForm; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** @@ -19,26 +14,6 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; */ class WseDiffNodeRevisionController extends NodeRevisionController { - /** - * The workspace manager. - */ - protected WorkspaceManagerInterface $workspaceManager; - - /** - * The typed data manager. - */ - protected TypedDataManagerInterface $typedDataManager; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container): self { - $instance = parent::create($container); - $instance->workspaceManager = $container->get('workspaces.manager'); - $instance->typedDataManager = $container->get('typed_data_manager'); - return $instance; - } - /** * {@inheritdoc} */ @@ -46,57 +21,8 @@ class WseDiffNodeRevisionController extends NodeRevisionController { if (!$node->access('view')) { throw new AccessDeniedHttpException(); } - $build = $this->formBuilder()->getForm(WseRevisionOverviewForm::class, $node); - - if ($this->workspaceManager->hasActiveWorkspace()) { - $build['node_revisions_table']['#empty'] = $this->t('There are no revisions yet in this workspace.'); - $build['#cache']['contexts'][] = 'workspace'; - } - - // Run the workspace conflict validation constraint when the entity form is - // being built, so we can "disable" it early and display a message to the - // user, instead of allowing them to enter data that can never be saved. - foreach ($node->getTypedData()->getConstraints() as $constraint) { - if ($constraint instanceof EntityWorkspaceConflictConstraint) { - $violations = $this->typedDataManager->getValidator()->validate( - $node->getTypedData(), - $constraint - ); - if ($violations->count()) { - $build['node_revisions_table']['#access'] = FALSE; - $build['submit']['#access'] = FALSE; - $build['workspace_conflict']['#markup'] = $violations->get(0)->getMessage(); - } - } - } - - return $build; - } - - /** - * {@inheritdoc} - */ - public function getRevisionIds(EntityStorageInterface $storage, $entity_id): array { - $query = $storage->getQuery() - ->allRevisions() - ->condition($storage->getEntityType()->getKey('id'), $entity_id) - ->accessCheck(FALSE); - - // When a workspace is active, filter the revisions to those created in that - // workspace. - if ($this->workspaceManager->hasActiveWorkspace()) { - $active_workspace_id = $this->workspaceManager->getActiveWorkspace()->id(); - $query->condition($storage->getEntityType()->getRevisionMetadataKey('workspace'), $active_workspace_id); - } - else { - // Otherwise show only revisions that were not created in a workspace. - $query->notExists($storage->getEntityType()->getRevisionMetadataKey('workspace')); - } - $result = $query->execute(); - $result_array = array_keys($result); - sort($result_array); - return $result_array; + return $this->formBuilder()->getForm(WseRevisionOverviewForm::class, $node); } } diff --git a/src/Controller/WseNodeController.php b/src/Controller/WseNodeController.php index 26378b608a7eba82c74b81787ab296cc97b11e3c..ce2945866cd3e569643508e74565daca46e068af 100644 --- a/src/Controller/WseNodeController.php +++ b/src/Controller/WseNodeController.php @@ -6,30 +6,13 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\node\Controller\NodeController; use Drupal\node\NodeInterface; use Drupal\node\NodeStorageInterface; -use Drupal\workspaces\Plugin\Validation\Constraint\EntityWorkspaceConflictConstraint; -use Drupal\workspaces\WorkspaceManagerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Overrides core's NodeController. */ class WseNodeController extends NodeController implements ContainerInjectionInterface { - /** - * The workspace manager. - * - * @var \Drupal\workspaces\WorkspaceManagerInterface - */ - protected WorkspaceManagerInterface $workspaceManager; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - $instance = parent::create($container); - $instance->workspaceManager = $container->get('workspaces.manager'); - return $instance; - } + use VersionHistoryTrait; /** * {@inheritdoc} @@ -37,54 +20,13 @@ class WseNodeController extends NodeController implements ContainerInjectionInte public function revisionOverview(NodeInterface $node) { $build = parent::revisionOverview($node); - if ($this->workspaceManager->hasActiveWorkspace()) { - $build['node_revisions_table']['#empty'] = $this->t('There are no revisions yet in this workspace.'); - $build['#cache']['contexts'][] = 'workspace'; - } - - // Run the workspace conflict validation constraint when the entity form is - // being built, so we can "disable" it early and display a message to the - // user, instead of allowing them to enter data that can never be saved. - foreach ($node->getTypedData()->getConstraints() as $constraint) { - if ($constraint instanceof EntityWorkspaceConflictConstraint) { - $violations = \Drupal::typedDataManager()->getValidator()->validate( - $node->getTypedData(), - $constraint - ); - if ($violations->count()) { - $build['node_revisions_table']['#access'] = FALSE; - $build['workspace_conflict']['#markup'] = $violations->get(0)->getMessage(); - } - } - } + $storage = $this->entityTypeManager()->getStorage('node'); + assert($storage instanceof NodeStorageInterface); + $revisions = $storage->loadMultipleRevisions($this->getRevisionIds($node, $storage)); + $this->alterRevisionsTable($build['node_revisions_table']['#rows'], $node, $revisions); + $build['node_revisions_table']['#header']['workspace'] = $this->t('Workspace'); return $build; } - /** - * {@inheritdoc} - */ - protected function getRevisionIds(NodeInterface $node, NodeStorageInterface $node_storage) { - $query = $node_storage->getQuery() - ->accessCheck(TRUE) - ->allRevisions() - ->condition($node->getEntityType()->getKey('id'), $node->id()) - ->sort($node->getEntityType()->getKey('revision'), 'DESC') - ->pager(50); - - // When a workspace is active, filter the revisions to those created in that - // workspace. - if ($this->workspaceManager->hasActiveWorkspace()) { - $active_workspace_id = $this->workspaceManager->getActiveWorkspace()->id(); - $query->condition($node->getEntityType()->getRevisionMetadataKey('workspace'), $active_workspace_id); - } - else { - // Otherwise show only revisions that were not created in a workspace. - $query->notExists($node->getEntityType()->getRevisionMetadataKey('workspace')); - } - - $result = $query->execute(); - return array_keys($result); - } - } diff --git a/src/Controller/WseVersionHistoryController.php b/src/Controller/WseVersionHistoryController.php index 04472d9ba68b029413989de7beb73fc93c2138a8..0c2d79aa77d20b5622627874c8f978bc78f1209f 100644 --- a/src/Controller/WseVersionHistoryController.php +++ b/src/Controller/WseVersionHistoryController.php @@ -4,72 +4,13 @@ namespace Drupal\wse\Controller; use Drupal\Core\Entity\Controller\VersionHistoryController; use Drupal\Core\Entity\RevisionableInterface; -use Drupal\Core\Entity\RevisionableStorageInterface; -use Drupal\Core\Language\LanguageInterface; -use Drupal\workspaces\WorkspaceManagerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Overrides core's VersionHistoryController. */ class WseVersionHistoryController extends VersionHistoryController { - /** - * The workspace manager. - * - * @var \Drupal\workspaces\WorkspaceManagerInterface - */ - protected WorkspaceManagerInterface $workspaceManager; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - $instance = parent::create($container); - $instance->workspaceManager = $container->get('workspaces.manager'); - return $instance; - } - - /** - * {@inheritdoc} - */ - protected function loadRevisions(RevisionableInterface $entity) { - $entityType = $entity->getEntityType(); - $translatable = $entityType->isTranslatable(); - $entityStorage = $this->entityTypeManager->getStorage($entity->getEntityTypeId()); - assert($entityStorage instanceof RevisionableStorageInterface); - - $query = $entityStorage->getQuery() - ->accessCheck(FALSE) - ->allRevisions() - ->condition($entityType->getKey('id'), $entity->id()) - ->sort($entityType->getKey('revision'), 'DESC') - ->pager(self::REVISIONS_PER_PAGE); - - // When a workspace is active, filter the revisions to those created in that - // workspace. - if ($this->workspaceManager->hasActiveWorkspace()) { - $active_workspace_id = $this->workspaceManager->getActiveWorkspace()->id(); - $query->condition($entity->getEntityType()->getRevisionMetadataKey('workspace'), $active_workspace_id); - } - else { - // Otherwise show only revisions that were not created in a workspace. - $query->notExists($entity->getEntityType()->getRevisionMetadataKey('workspace')); - } - - $result = $query->execute(); - - $currentLangcode = $this->languageManager - ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT) - ->getId(); - foreach ($entityStorage->loadMultipleRevisions(array_keys($result)) as $revision) { - // Only show revisions that are affected by the language that is being - // displayed. - if (!$translatable || ($revision->hasTranslation($currentLangcode) && $revision->getTranslation($currentLangcode)->isRevisionTranslationAffected())) { - yield ($translatable ? $revision->getTranslation($currentLangcode) : $revision); - } - } - } + use VersionHistoryTrait; /** * {@inheritdoc} @@ -77,10 +18,8 @@ class WseVersionHistoryController extends VersionHistoryController { protected function revisionOverview(RevisionableInterface $entity): array { $build = parent::revisionOverview($entity); - if ($this->workspaceManager->hasActiveWorkspace()) { - $build['entity_revisions_table']['#empty'] = $this->t('There are no revisions yet in this workspace.'); - $build['#cache']['contexts'][] = 'workspace'; - } + $this->alterRevisionsTable($build['entity_revisions_table']['#rows'], $entity); + $build['entity_revisions_table']['#header']['workspace'] = $this->t('Workspace'); return $build; } diff --git a/src/Diff/Form/WseRevisionOverviewForm.php b/src/Diff/Form/WseRevisionOverviewForm.php old mode 100755 new mode 100644 index f8bb7e2ae5f630927d43137669ac15654f576977..69fc25644e9e37ac54942ee132e90733d5e7dd52 --- a/src/Diff/Form/WseRevisionOverviewForm.php +++ b/src/Diff/Form/WseRevisionOverviewForm.php @@ -4,56 +4,32 @@ declare(strict_types=1); namespace Drupal\wse\Diff\Form; +use Drupal\Core\Form\FormStateInterface; use Drupal\diff\Form\RevisionOverviewForm; -use Drupal\node\NodeInterface; -use Drupal\workspaces\WorkspaceManagerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\node\NodeStorageInterface; +use Drupal\wse\Controller\VersionHistoryTrait; /** * Provides a form for revision overview page. */ class WseRevisionOverviewForm extends RevisionOverviewForm { - /** - * The workspace manager. - */ - protected WorkspaceManagerInterface $workspaceManager; + use VersionHistoryTrait; /** * {@inheritdoc} */ - public static function create(ContainerInterface $container): self { - $instance = parent::create($container); - $instance->workspaceManager = $container->get('workspaces.manager'); - return $instance; - } + public function buildForm(array $form, FormStateInterface $form_state, $node = NULL): array { + $form = parent::buildForm($form, $form_state, $node); - /** - * {@inheritdoc} - */ - protected function getRevisionIds(NodeInterface $node): array { - $query = $this->entityTypeManager->getStorage('node')->getQuery() - ->condition($node->getEntityType()->getKey('id'), $node->id()) - ->pager($this->config->get('general_settings.revision_pager_limit')) - ->allRevisions() - ->sort($node->getEntityType()->getKey('revision'), 'DESC') - // Access to the content has already been verified. Disable query-level - // access checking so that revisions for unpublished content still - // appear. - ->accessCheck(FALSE); - - // When a workspace is active, filter the revisions to those created in that - // workspace. - if ($this->workspaceManager->hasActiveWorkspace()) { - $active_workspace_id = $this->workspaceManager->getActiveWorkspace()->id(); - $query->condition($node->getEntityType()->getRevisionMetadataKey('workspace'), $active_workspace_id); - } - else { - // Otherwise show only revisions that were not created in a workspace. - $query->notExists($node->getEntityType()->getRevisionMetadataKey('workspace')); - } - - return \array_keys($query->execute()); + $storage = $this->entityTypeManager->getStorage('node'); + assert($storage instanceof NodeStorageInterface); + // @phpstan-ignore-next-line + $revisions = $storage->loadMultipleRevisions($this->getRevisionIds($node)); + $this->alterRevisionsTable($form['node_revisions_table'], $node, $revisions); + $form['node_revisions_table']['#header']['workspace'] = $this->t('Workspace'); + + return $form; } } diff --git a/wse.module b/wse.module index d2dbfddbac395b5c895cabd330b06b84e5ac20a5..f00074919814eafb4ad77e64a77ac800cdc9db2f 100644 --- a/wse.module +++ b/wse.module @@ -8,6 +8,7 @@ use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\SortArray; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; @@ -16,6 +17,7 @@ use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler; use Drupal\workspaces\WorkspaceInterface; @@ -429,3 +431,27 @@ function wse_field_info_alter(&$definitions) { $definitions['entity_reference']['constraints']['WseEntityReferenceSupportedNewEntities'] = []; } } + +/** + * Implements hook_entity_access(). + */ +function wse_entity_access(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface { + if ( + !\Drupal::service('workspaces.information')->isEntitySupported($entity) + || !in_array($operation, ['revert revision', 'revert', 'delete revision']) + ) { + return AccessResult::neutral(); + } + + /** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */ + $workspace_association = \Drupal::service('workspaces.association'); + $tracking_workspace_ids = $workspace_association->getEntityTrackingWorkspaceIds($entity, TRUE); + if ($tracking_workspace_id = reset($tracking_workspace_ids)) { + $active_workspace = \Drupal::service('workspaces.manager')->getActiveWorkspace(); + if (!$active_workspace || $active_workspace->id() != $tracking_workspace_id) { + return AccessResult::forbidden()->addCacheContexts(['workspace']); + } + } + + return AccessResult::neutral()->addCacheContexts(['workspace']); +}