From 86caf6f37824869b5d0caf2039b1329495dc1c84 Mon Sep 17 00:00:00 2001 From: dpi <9354-danph@users.noreply.drupalcode.org> Date: Mon, 12 Dec 2022 20:39:24 +0000 Subject: [PATCH] Issue #1984588 by dpi, larowlan, AaronMcHale, smustgrave, acbramley: Add Block Content revision UI --- core/core.link_relation_types.yml | 6 + .../Controller/VersionHistoryController.php | 9 +- .../block_content/block_content.install | 24 +++ .../src/BlockContentAccessControlHandler.php | 41 ++-- .../src/BlockContentPermissions.php | 36 +++- .../block_content/src/Entity/BlockContent.php | 10 +- .../BlockContentRevisionDeleteTest.php | 85 +++++++++ .../BlockContentRevisionRevertTest.php | 87 +++++++++ ...BlockContentRevisionVersionHistoryTest.php | 94 +++++++++ .../Kernel/BlockContentAccessHandlerTest.php | 180 +++++++++++++++++- 10 files changed, 550 insertions(+), 22 deletions(-) create mode 100644 core/modules/block_content/tests/src/Functional/BlockContentRevisionDeleteTest.php create mode 100644 core/modules/block_content/tests/src/Functional/BlockContentRevisionRevertTest.php create mode 100644 core/modules/block_content/tests/src/Functional/BlockContentRevisionVersionHistoryTest.php diff --git a/core/core.link_relation_types.yml b/core/core.link_relation_types.yml index 50da734474b..c974fbe25cb 100644 --- a/core/core.link_relation_types.yml +++ b/core/core.link_relation_types.yml @@ -15,6 +15,12 @@ delete-multiple-form: revision: uri: https://drupal.org/link-relations/revision description: A particular version of this resource. +revision-revert-form: + uri: https://drupal.org/link-relations/revision-revert-form + description: A form where a particular version of this resource can be reverted. +revision-delete-form: + uri: https://drupal.org/link-relations/revision-delete-form + description: A form where a particular version of this resource can be deleted. create: uri: https://drupal.org/link-relations/create description: A REST resource URL where a resource of this type can be created. diff --git a/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php b/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php index def6fe1cde7..8d069e5aeab 100644 --- a/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php +++ b/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\RevisionableInterface; use Drupal\Core\Entity\RevisionableStorageInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Link; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Entity\RevisionLogInterface; @@ -155,10 +156,10 @@ class VersionHistoryController extends ControllerBase { $linkText = $revision->access('view label') ? $revision->label() : $this->t('- Restricted access -'); } - $revisionViewLink = $revision->toLink($linkText, 'revision'); - $context['revision'] = $revisionViewLink->getUrl()->access() - ? $revisionViewLink->toString() - : (string) $revisionViewLink->getText(); + $url = $revision->hasLinkTemplate('revision') ? $revision->toUrl('revision') : NULL; + $context['revision'] = $url && $url->access() + ? Link::fromTextAndUrl($linkText, $url)->toString() + : (string) $linkText; $context['message'] = $revision instanceof RevisionLogInterface ? [ '#markup' => $revision->getRevisionLogMessage(), '#allowed_tags' => Xss::getHtmlTagList(), diff --git a/core/modules/block_content/block_content.install b/core/modules/block_content/block_content.install index af22b625274..3a9fb83b7b5 100644 --- a/core/modules/block_content/block_content.install +++ b/core/modules/block_content/block_content.install @@ -5,9 +5,33 @@ * Install, update and uninstall functions for the block_content module. */ +use Drupal\Core\Entity\Form\RevisionDeleteForm; +use Drupal\Core\Entity\Form\RevisionRevertForm; +use Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider; +use Drupal\Core\StringTranslation\TranslatableMarkup; + /** * Implements hook_update_last_removed(). */ function block_content_update_last_removed() { return 8600; } + +/** + * Update entity definition to handle revision routes. + */ +function block_content_update_10100(&$sandbox = NULL): TranslatableMarkup { + $entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager(); + $definition = $entityDefinitionUpdateManager->getEntityType('block_content'); + $routeProviders = $definition->get('route_provider'); + $routeProviders['revision'] = RevisionHtmlRouteProvider::class; + $definition + ->setFormClass('revision-delete', RevisionDeleteForm::class) + ->setFormClass('revision-revert', RevisionRevertForm::class) + ->set('route_provider', $routeProviders) + ->setLinkTemplate('revision-delete-form', '/block/{block_content}/revision/{block_content_revision}/delete') + ->setLinkTemplate('revision-revert-form', '/block/{block_content}/revision/{block_content_revision}/revert') + ->setLinkTemplate('version-history', '/block/{block_content}/revisions'); + $entityDefinitionUpdateManager->updateEntityType($definition); + return \t('Added revision routes to Custom block entity type.'); +} diff --git a/core/modules/block_content/src/BlockContentAccessControlHandler.php b/core/modules/block_content/src/BlockContentAccessControlHandler.php index 1a6a31abd08..d4a82d2158b 100644 --- a/core/modules/block_content/src/BlockContentAccessControlHandler.php +++ b/core/modules/block_content/src/BlockContentAccessControlHandler.php @@ -54,21 +54,34 @@ class BlockContentAccessControlHandler extends EntityAccessControlHandler implem * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { - // Allow view and update access to user with the 'edit any (type) block - // content' permission or the 'administer blocks' permission. - $edit_any_permission = 'edit any ' . $entity->bundle() . ' block content'; - if ($operation === 'view') { - $access = AccessResult::allowedIf($entity->isPublished()) + assert($entity instanceof BlockContentInterface); + $bundle = $entity->bundle(); + $forbidIfNotDefaultAndLatest = fn () => AccessResult::forbiddenIf($entity->isDefaultRevision() && $entity->isLatestRevision()); + $access = match ($operation) { + // Allow view and update access to user with the 'edit any (type) block + // content' permission or the 'administer blocks' permission. + 'view' => AccessResult::allowedIf($entity->isPublished()) ->orIf(AccessResult::allowedIfHasPermission($account, 'administer blocks')) - ->orIf(AccessResult::allowedIfHasPermission($account, $edit_any_permission)); - } - elseif ($operation === 'update') { - $access = AccessResult::allowedIfHasPermission($account, 'administer blocks') - ->orIf(AccessResult::allowedIfHasPermission($account, $edit_any_permission)); - } - else { - $access = parent::checkAccess($entity, $operation, $account); - } + ->orIf(AccessResult::allowedIfHasPermission($account, 'edit any ' . $bundle . ' block content')), + 'update' => AccessResult::allowedIfHasPermission($account, 'administer blocks') + ->orIf(AccessResult::allowedIfHasPermission($account, 'edit any ' . $bundle . ' block content')), + + // Revisions. + 'view all revisions' => AccessResult::allowedIfHasPermissions($account, [ + 'administer blocks', + 'view any ' . $bundle . ' block content history', + ], 'OR'), + 'revert' => AccessResult::allowedIfHasPermissions($account, [ + 'administer blocks', + 'revert any ' . $bundle . ' block content revisions', + ], 'OR')->orIf($forbidIfNotDefaultAndLatest()), + 'delete revision' => AccessResult::allowedIfHasPermissions($account, [ + 'administer blocks', + 'delete any ' . $bundle . ' block content revisions', + ], 'OR')->orIf($forbidIfNotDefaultAndLatest()), + + default => parent::checkAccess($entity, $operation, $account), + }; // Add the entity as a cacheable dependency because access will at least be // determined by whether the block is reusable. diff --git a/core/modules/block_content/src/BlockContentPermissions.php b/core/modules/block_content/src/BlockContentPermissions.php index e6be17d0aad..762379379e3 100644 --- a/core/modules/block_content/src/BlockContentPermissions.php +++ b/core/modules/block_content/src/BlockContentPermissions.php @@ -3,17 +3,40 @@ namespace Drupal\block_content; use Drupal\block_content\Entity\BlockContentType; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\BundlePermissionHandlerTrait; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provide dynamic permissions for blocks of different types. */ -class BlockContentPermissions { +class BlockContentPermissions implements ContainerInjectionInterface { use StringTranslationTrait; use BundlePermissionHandlerTrait; + /** + * Constructs a BlockContentPermissions instance. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * Entity type manager. + */ + public function __construct( + protected EntityTypeManagerInterface $entityTypeManager, + ) { + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + ); + } + /** * Build permissions for each block type. * @@ -21,7 +44,7 @@ class BlockContentPermissions { * The block type permissions. */ public function blockTypePermissions() { - return $this->generatePermissions(BlockContentType::loadMultiple(), [$this, 'buildPermissions']); + return $this->generatePermissions($this->entityTypeManager->getStorage('block_content_type')->loadMultiple(), [$this, 'buildPermissions']); } /** @@ -40,6 +63,15 @@ class BlockContentPermissions { "edit any $type_id block content" => [ 'title' => $this->t('%type_name: Edit any block content', $type_params), ], + "view any $type_id block content history" => [ + 'title' => $this->t('%type_name: View any block content history pages', $type_params), + ], + "revert any $type_id block content revisions" => [ + 'title' => $this->t('%type_name: Revert any block content revisions', $type_params), + ], + "delete any $type_id block content revisions" => [ + 'title' => $this->t('%type_name: Delete any block content revisions', $type_params), + ], ]; } diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index b12536d95d8..dcbe2005b31 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -34,7 +34,12 @@ use Drupal\user\UserInterface; * "add" = "Drupal\block_content\BlockContentForm", * "edit" = "Drupal\block_content\BlockContentForm", * "delete" = "Drupal\block_content\Form\BlockContentDeleteForm", - * "default" = "Drupal\block_content\BlockContentForm" + * "default" = "Drupal\block_content\BlockContentForm", + * "revision-delete" = \Drupal\Core\Entity\Form\RevisionDeleteForm::class, + * "revision-revert" = \Drupal\Core\Entity\Form\RevisionRevertForm::class, + * }, + * "route_provider" = { + * "revision" = \Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider::class, * }, * "translation" = "Drupal\block_content\BlockContentTranslationHandler" * }, @@ -50,6 +55,9 @@ use Drupal\user\UserInterface; * "edit-form" = "/block/{block_content}", * "collection" = "/admin/structure/block/block-content", * "create" = "/block", + * "revision-delete-form" = "/block/{block_content}/revision/{block_content_revision}/delete", + * "revision-revert-form" = "/block/{block_content}/revision/{block_content_revision}/revert", + * "version-history" = "/block/{block_content}/revisions", * }, * translatable = TRUE, * entity_keys = { diff --git a/core/modules/block_content/tests/src/Functional/BlockContentRevisionDeleteTest.php b/core/modules/block_content/tests/src/Functional/BlockContentRevisionDeleteTest.php new file mode 100644 index 00000000000..fb96554072e --- /dev/null +++ b/core/modules/block_content/tests/src/Functional/BlockContentRevisionDeleteTest.php @@ -0,0 +1,85 @@ +drupalLogin($this->adminUser); + $this->drupalPlaceBlock('page_title_block'); + } + + /** + * Tests revision delete. + */ + public function testDeleteForm(): void { + $entity = $this->createBlockContent(save: FALSE) + ->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp()) + ->setRevisionTranslationAffected(TRUE); + $entity->setNewRevision(); + $entity->save(); + $revisionId = $entity->getRevisionId(); + + // Cannot delete latest revision. + $this->drupalGet($entity->toUrl('revision-delete-form')); + $this->assertSession()->statusCodeEquals(403); + + // Create a new latest revision. + $entity + ->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp()) + ->setRevisionTranslationAffected(TRUE) + ->setNewRevision(); + $entity->save(); + + // Reload the entity. + $revision = \Drupal::entityTypeManager()->getStorage('block_content') + ->loadRevision($revisionId); + $this->drupalGet($revision->toUrl('revision-delete-form')); + $this->assertSession()->pageTextContains('Are you sure you want to delete the revision from Sun, 01/11/2009 - 16:00?'); + $this->assertSession()->buttonExists('Delete'); + $this->assertSession()->linkExists('Cancel'); + + $countRevisions = static function (): int { + return (int) \Drupal::entityTypeManager()->getStorage('block_content') + ->getQuery() + ->accessCheck(FALSE) + ->allRevisions() + ->count() + ->execute(); + }; + + $count = $countRevisions(); + $this->submitForm([], 'Delete'); + $this->assertEquals($count - 1, $countRevisions()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals(sprintf('block/%s/revisions', $entity->id())); + $this->assertSession()->pageTextContains(sprintf('Revision from Sun, 01/11/2009 - 16:00 of basic %s has been deleted.', $entity->label())); + $this->assertSession()->elementsCount('css', 'table tbody tr', 1); + } + +} diff --git a/core/modules/block_content/tests/src/Functional/BlockContentRevisionRevertTest.php b/core/modules/block_content/tests/src/Functional/BlockContentRevisionRevertTest.php new file mode 100644 index 00000000000..48ab25c989c --- /dev/null +++ b/core/modules/block_content/tests/src/Functional/BlockContentRevisionRevertTest.php @@ -0,0 +1,87 @@ +drupalLogin($this->adminUser); + $this->drupalPlaceBlock('page_title_block'); + } + + /** + * Tests revision revert. + */ + public function testRevertForm(): void { + $entity = $this->createBlockContent(save: FALSE) + ->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp()) + ->setRevisionTranslationAffected(TRUE); + $entity->setNewRevision(); + $entity->save(); + $revisionId = $entity->getRevisionId(); + + // Cannot revert latest revision. + $this->drupalGet($entity->toUrl('revision-revert-form')); + $this->assertSession()->statusCodeEquals(403); + + // Create a new latest revision. + $entity + ->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp()) + ->setRevisionTranslationAffected(TRUE) + ->setNewRevision(); + $entity->save(); + + // Reload the entity. + $revision = \Drupal::entityTypeManager()->getStorage('block_content') + ->loadRevision($revisionId); + $this->drupalGet($revision->toUrl('revision-revert-form')); + $this->assertSession()->pageTextContains('Are you sure you want to revert to the revision from Sun, 01/11/2009 - 16:00?'); + $this->assertSession()->buttonExists('Revert'); + $this->assertSession()->linkExists('Cancel'); + + $countRevisions = static function (): int { + return (int) \Drupal::entityTypeManager()->getStorage('block_content') + ->getQuery() + ->accessCheck(FALSE) + ->allRevisions() + ->count() + ->execute(); + }; + + $count = $countRevisions(); + $this->submitForm([], 'Revert'); + $this->assertEquals($count + 1, $countRevisions()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals(sprintf('block/%s/revisions', $entity->id())); + $this->assertSession()->pageTextContains(sprintf('basic %s has been reverted to the revision from Sun, 01/11/2009 - 16:00.', $entity->label())); + // Three rows, from the top: the newly reverted revision, the revision from + // 5pm, and the revision from 4pm. + $this->assertSession()->elementsCount('css', 'table tbody tr', 3); + } + +} diff --git a/core/modules/block_content/tests/src/Functional/BlockContentRevisionVersionHistoryTest.php b/core/modules/block_content/tests/src/Functional/BlockContentRevisionVersionHistoryTest.php new file mode 100644 index 00000000000..dd3fad4aac5 --- /dev/null +++ b/core/modules/block_content/tests/src/Functional/BlockContentRevisionVersionHistoryTest.php @@ -0,0 +1,94 @@ +drupalLogin($this->adminUser); + } + + /** + * Tests version history page. + */ + public function testVersionHistory(): void { + $entity = $this->createBlockContent(save: FALSE); + + $entity + ->setInfo('first revision') + ->setRevisionCreationTime((new \DateTimeImmutable('1st June 2020 7am'))->getTimestamp()) + ->setRevisionLogMessage('first revision log') + ->setRevisionUser($this->drupalCreateUser(name: 'first author')) + ->setNewRevision(); + $entity->save(); + + $entity + ->setInfo('second revision') + ->setRevisionCreationTime((new \DateTimeImmutable('2nd June 2020 8am'))->getTimestamp()) + ->setRevisionLogMessage('second revision log') + ->setRevisionUser($this->drupalCreateUser(name: 'second author')) + ->setNewRevision(); + $entity->save(); + + $entity + ->setInfo('third revision') + ->setRevisionCreationTime((new \DateTimeImmutable('3rd June 2020 9am'))->getTimestamp()) + ->setRevisionLogMessage('third revision log') + ->setRevisionUser($this->drupalCreateUser(name: 'third author')) + ->setNewRevision(); + $entity->save(); + + $this->drupalGet($entity->toUrl('version-history')); + $this->assertSession()->elementsCount('css', 'table tbody tr', 3); + + // Order is newest to oldest revision by creation order. + $row1 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1)'); + // Latest revision does not have revert or delete revision operation. + $this->assertSession()->elementNotExists('named', ['link', 'Revert'], $row1); + $this->assertSession()->elementNotExists('named', ['link', 'Delete'], $row1); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision'); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'third revision log'); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', '06/03/2020 - 09:00 by third author'); + + $row2 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2)'); + $this->assertSession()->elementExists('named', ['link', 'Revert'], $row2); + $this->assertSession()->elementExists('named', ['link', 'Delete'], $row2); + $this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision'); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', 'second revision log'); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', '06/02/2020 - 08:00 by second author'); + + $row3 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(3)'); + $this->assertSession()->elementExists('named', ['link', 'Revert'], $row3); + $this->assertSession()->elementExists('named', ['link', 'Delete'], $row3); + $this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision'); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', 'first revision log'); + $this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', '06/01/2020 - 07:00 by first author'); + } + +} diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentAccessHandlerTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentAccessHandlerTest.php index 62f63b5f7ad..3358dfcdc1c 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentAccessHandlerTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentAccessHandlerTest.php @@ -97,11 +97,45 @@ class BlockContentAccessHandlerTest extends KernelTestBase { } /** + * Test block content entity access. + * + * @param string $operation + * The entity operation to test. + * @param bool $published + * Whether the latest revision should be published. + * @param bool $reusable + * Whether the block content should be reusable. Non-reusable blocks are + * typically used in Layout Builder. + * @param array $permissions + * Permissions to grant to the test user. + * @param string|null $parent_access + * Whether the test user has access to the parent entity, valid values are + * 'allowed', 'forbidden', or 'neutral'. Set to NULL to assert parent will + * not be called. + * @param string $expected_access + * The expected access for the user and block content. Valid values are + * 'allowed', 'forbidden', or 'neutral'. + * @param bool $isLatest + * Whether the block content should be the latest revision when checking + * access. If FALSE, multiple revisions will be created, and an older + * revision will be loaded before checking access. + * * @covers ::checkAccess * * @dataProvider providerTestAccess */ - public function testAccess($operation, $published, $reusable, $permissions, $parent_access, $expected_access) { + public function testAccess(string $operation, bool $published, bool $reusable, array $permissions, ?string $parent_access, string $expected_access, bool $isLatest = TRUE) { + /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage */ + $entityStorage = \Drupal::entityTypeManager()->getStorage('block_content'); + + $loadRevisionId = NULL; + if (!$isLatest) { + // Save a historical revision, then setup for a new revision to be saved. + $this->blockEntity->save(); + $loadRevisionId = $this->blockEntity->getRevisionId(); + $this->blockEntity = $entityStorage->createRevision($this->blockEntity); + } + $published ? $this->blockEntity->setPublished() : $this->blockEntity->setUnpublished(); $reusable ? $this->blockEntity->setReusable() : $this->blockEntity->setNonReusable(); @@ -144,6 +178,11 @@ class BlockContentAccessHandlerTest extends KernelTestBase { } $this->blockEntity->save(); + // Reload a previous revision. + if ($loadRevisionId !== NULL) { + $this->blockEntity = $entityStorage->loadRevision($loadRevisionId); + } + $result = $this->accessControlHandler->access($this->blockEntity, $operation, $user, TRUE); switch ($expected_access) { case 'allowed': @@ -387,6 +426,145 @@ class BlockContentAccessHandlerTest extends KernelTestBase { 'neutral', ], ]; + + // View all revisions: + $cases['view all revisions:none'] = [ + 'view all revisions', + TRUE, + TRUE, + [], + NULL, + 'neutral', + ]; + $cases['view all revisions:administer blocks'] = [ + 'view all revisions', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'allowed', + ]; + $cases['view all revisions:view bundle'] = [ + 'view all revisions', + TRUE, + TRUE, + ['view any square block content history'], + NULL, + 'allowed', + ]; + + // Revert revisions: + $cases['revert:none:latest'] = [ + 'revert', + TRUE, + TRUE, + [], + NULL, + 'forbidden', + TRUE, + ]; + $cases['revert:none:historical'] = [ + 'revert', + TRUE, + TRUE, + [], + NULL, + 'neutral', + FALSE, + ]; + $cases['revert:administer blocks:latest'] = [ + 'revert', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'forbidden', + TRUE, + ]; + $cases['revert:administer blocks:historical'] = [ + 'revert', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'allowed', + FALSE, + ]; + $cases['revert:revert bundle:latest'] = [ + 'revert', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'forbidden', + TRUE, + ]; + $cases['revert:revert bundle:historical'] = [ + 'revert', + TRUE, + TRUE, + ['revert any square block content revisions'], + NULL, + 'allowed', + FALSE, + ]; + + // Delete revisions: + $cases['delete revision:none:latest'] = [ + 'delete revision', + TRUE, + TRUE, + [], + NULL, + 'forbidden', + TRUE, + ]; + $cases['delete revision:none:historical'] = [ + 'delete revision', + TRUE, + TRUE, + [], + NULL, + 'neutral', + FALSE, + ]; + $cases['delete revision:administer blocks:latest'] = [ + 'delete revision', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'forbidden', + TRUE, + ]; + $cases['delete revision:administer blocks:historical'] = [ + 'delete revision', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'allowed', + FALSE, + ]; + $cases['delete revision:delete bundle:latest'] = [ + 'delete revision', + TRUE, + TRUE, + ['administer blocks'], + NULL, + 'forbidden', + TRUE, + ]; + $cases['delete revision:delete bundle:historical'] = [ + 'delete revision', + TRUE, + TRUE, + ['delete any square block content revisions'], + NULL, + 'allowed', + FALSE, + ]; + return $cases; }