From d4c6b3217fe09a2e4580d1db831e2573101bdce8 Mon Sep 17 00:00:00 2001
From: Lauri Eskola <lauri.eskola@acquia.com>
Date: Wed, 8 Nov 2023 11:55:37 +0200
Subject: [PATCH] Issue #2911977 by yash.rode, acbramley, bnjmnm, smustgrave,
 phenaproxima, Wim Leers, chr.fritsch, larowlan: Add Media revision UI

---
 .../block_content/src/Entity/BlockContent.php |   2 +-
 .../tests/src/Functional/MediaTest.php        |   2 +-
 core/modules/media/media.routing.yml          |  15 --
 core/modules/media/src/Entity/Media.php       |  15 +-
 .../media/src/MediaAccessControlHandler.php   |  15 +-
 core/modules/media/src/MediaPermissions.php   |   9 +
 .../src/Functional/MediaRevisionTest.php      | 185 +++++++++++++---
 .../Kernel/MediaAccessControlHandlerTest.php  | 199 +++++++++++++++++-
 8 files changed, 386 insertions(+), 56 deletions(-)

diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php
index 2a2c78475eee..7e652eea8ade 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -177,7 +177,7 @@ public function getInstances() {
   public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
     parent::preSaveRevision($storage, $record);
 
-    if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
+    if (!$this->isNewRevision() && isset($this->original) && empty($record->revision_log_message)) {
       // If we are updating an existing block_content without adding a new
       // revision and the user did not supply a revision log, keep the existing
       // one.
diff --git a/core/modules/jsonapi/tests/src/Functional/MediaTest.php b/core/modules/jsonapi/tests/src/Functional/MediaTest.php
index 58e027af822b..84ad99b7da93 100644
--- a/core/modules/jsonapi/tests/src/Functional/MediaTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MediaTest.php
@@ -65,7 +65,7 @@ class MediaTest extends ResourceTestBase {
   protected function setUpAuthorization($method) {
     switch ($method) {
       case 'GET':
-        $this->grantPermissionsToTestedRole(['view media']);
+        $this->grantPermissionsToTestedRole(['view media', 'view any camelids media revisions']);
         break;
 
       case 'POST':
diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml
index 03a5c5afe861..eaf908fb340e 100644
--- a/core/modules/media/media.routing.yml
+++ b/core/modules/media/media.routing.yml
@@ -1,18 +1,3 @@
-entity.media.revision:
-  path: '/media/{media}/revisions/{media_revision}/view'
-  defaults:
-    _controller: '\Drupal\Core\Entity\Controller\EntityRevisionViewController'
-    _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title'
-  options:
-    parameters:
-      media:
-        type: entity:media
-      media_revision:
-        type: entity_revision:media
-  requirements:
-    _entity_access: 'media_revision.view all revisions'
-    media: \d+
-
 media.oembed_iframe:
   path: '/media/oembed'
   defaults:
diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php
index 967e933063ec..7fcf32b94cda 100644
--- a/core/modules/media/src/Entity/Media.php
+++ b/core/modules/media/src/Entity/Media.php
@@ -39,10 +39,13 @@
  *       "edit" = "Drupal\media\MediaForm",
  *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
  *       "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm",
+ *       "revision-delete" = \Drupal\Core\Entity\Form\RevisionDeleteForm::class,
+ *       "revision-revert" = \Drupal\Core\Entity\Form\RevisionRevertForm::class,
  *     },
  *     "views_data" = "Drupal\media\MediaViewsData",
  *     "route_provider" = {
  *       "html" = "Drupal\media\Routing\MediaRouteProvider",
+ *       "revision" = \Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider::class,
  *     }
  *   },
  *   base_table = "media",
@@ -80,6 +83,9 @@
  *     "delete-multiple-form" = "/media/delete",
  *     "edit-form" = "/media/{media}/edit",
  *     "revision" = "/media/{media}/revisions/{media_revision}/view",
+ *     "revision-delete-form" = "/media/{media}/revision/{media_revision}/delete",
+ *     "revision-revert-form" = "/media/{media}/revision/{media_revision}/revert",
+ *     "version-history" = "/media/{media}/revisions",
  *   }
  * )
  */
@@ -381,18 +387,13 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
   public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
     parent::preSaveRevision($storage, $record);
 
-    $is_new_revision = $this->isNewRevision();
-    if (!$is_new_revision && isset($this->original) && empty($record->revision_log_message)) {
+    if (!$this->isNewRevision() && isset($this->original) && empty($record->revision_log_message)) {
       // If we are updating an existing media item without adding a
       // new revision, we need to make sure $entity->revision_log_message is
       // reset whenever it is empty.
       // Therefore, this code allows us to avoid clobbering an existing log
       // entry with an empty one.
-      $record->revision_log_message = $this->original->revision_log_message->value;
-    }
-
-    if ($is_new_revision) {
-      $record->revision_created = self::getRequestTime();
+      $this->setRevisionLogMessage($this->original->getRevisionLogMessage());
     }
   }
 
diff --git a/core/modules/media/src/MediaAccessControlHandler.php b/core/modules/media/src/MediaAccessControlHandler.php
index f34c638f4602..159cab18d271 100644
--- a/core/modules/media/src/MediaAccessControlHandler.php
+++ b/core/modules/media/src/MediaAccessControlHandler.php
@@ -50,7 +50,6 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
    * {@inheritdoc}
    */
   protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
-    /** @var \Drupal\media\MediaInterface $entity */
     // Allow admin permission to override all operations.
     if ($account->hasPermission($this->entityType->getAdminPermission())) {
       return AccessResult::allowed()->cachePerPermissions();
@@ -120,9 +119,9 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
         return AccessResult::neutral("The following permissions are required: 'delete any media' OR 'delete own media' OR '$type: delete any media' OR '$type: delete own media'.")->cachePerPermissions();
 
       case 'view all revisions':
-        // Perform basic permission checks first.
-        if (!$account->hasPermission('view all media revisions')) {
-          return AccessResult::neutral("The 'view all media revisions' permission is required.")->cachePerPermissions();
+      case 'view revision':
+        if ($account->hasPermission('view any ' . $type . ' media revisions') || $account->hasPermission("view all media revisions")) {
+          return AccessResult::allowed()->cachePerPermissions();
         }
 
         // First check the access to the default revision and finally, if the
@@ -135,6 +134,14 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
         }
         return $access->cachePerPermissions()->addCacheableDependency($entity);
 
+      case 'revert':
+        return AccessResult::allowedIfHasPermission($account, 'revert any ' . $type . ' media revisions')
+          ->cachePerPermissions()->addCacheableDependency($entity);
+
+      case 'delete revision':
+        return AccessResult::allowedIfHasPermission($account, 'delete any ' . $type . ' media revisions')
+          ->cachePerPermissions()->addCacheableDependency($entity);
+
       default:
         return AccessResult::neutral()->cachePerPermissions();
     }
diff --git a/core/modules/media/src/MediaPermissions.php b/core/modules/media/src/MediaPermissions.php
index 6489db7f1674..a8d99a7157b3 100644
--- a/core/modules/media/src/MediaPermissions.php
+++ b/core/modules/media/src/MediaPermissions.php
@@ -82,6 +82,15 @@ protected function buildPermissions(MediaTypeInterface $type) {
       "delete any $type_id media" => [
         'title' => $this->t('%type_name: Delete any media', $type_params),
       ],
+      "view any $type_id media revisions" => [
+        'title' => $this->t('%type_name: View any media revision pages', $type_params),
+      ],
+      "revert any $type_id media revisions" => [
+        'title' => $this->t('Revert %type_name: Revert media revisions', $type_params),
+      ],
+      "delete any $type_id media revisions" => [
+        'title' => $this->t('Delete %type_name: Delete media revisions', $type_params),
+      ],
     ];
   }
 
diff --git a/core/modules/media/tests/src/Functional/MediaRevisionTest.php b/core/modules/media/tests/src/Functional/MediaRevisionTest.php
index 44e02905d6c6..8f57b2206976 100644
--- a/core/modules/media/tests/src/Functional/MediaRevisionTest.php
+++ b/core/modules/media/tests/src/Functional/MediaRevisionTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\Entity\FieldConfig;
+use Drupal\media\Entity\Media;
 use Drupal\media\MediaInterface;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
@@ -20,40 +21,59 @@ class MediaRevisionTest extends MediaFunctionalTestBase {
    */
   protected $defaultTheme = 'stark';
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->createMediaType('test', ['id' => 'test', 'label' => 'test']);
+  }
+
+  /**
+   * Creates a media item.
+   *
+   * @param string $title
+   *   Title of media item.
+   *
+   * @return \Drupal\media\Entity\Media
+   *   A media item.
+   */
+  protected function createMedia(string $title): Media {
+    $media = Media::create([
+      'bundle' => 'test',
+      'name' => $title,
+    ]);
+    $media->save();
+
+    return $media;
+  }
+
   /**
    * Checks media revision operations.
    */
   public function testRevisions() {
     $assert = $this->assertSession();
 
-    /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $media_storage */
-    $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
-
-    // Create a media type and media item.
-    $media_type = $this->createMediaType('test');
-    $media = $media_storage->create([
-      'bundle' => $media_type->id(),
-      'name' => 'Unnamed',
-    ]);
-    $media->save();
+    $media = $this->createMedia('Sample media');
+    $originalRevisionId = $media->getRevisionId();
 
     // You can access the revision page when there is only 1 revision.
-    $this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
+    $this->drupalGet($media->toUrl('revision'));
     $assert->statusCodeEquals(200);
 
     // Create some revisions.
-    $media_revisions = [];
-    $media_revisions[] = clone $media;
     $revision_count = 3;
     for ($i = 0; $i < $revision_count; $i++) {
       $media->revision_log = $this->randomMachineName(32);
       $media = $this->createMediaRevision($media);
-      $media_revisions[] = clone $media;
     }
 
-    // Get the last revision for simple checks.
-    /** @var \Drupal\media\MediaInterface $media */
-    $media = end($media_revisions);
+    // Confirm that the last revision is the default revision.
+    $this->assertTrue($media->isDefaultRevision(), 'Last revision is the default.');
+
+    // Get the original revision for simple checks.
+    $media = \Drupal::entityTypeManager()->getStorage('media')
+      ->loadRevision($originalRevisionId);
 
     // Test permissions.
     $this->drupalLogin($this->nonAdminUser);
@@ -62,18 +82,22 @@ public function testRevisions() {
 
     // Test 'view all media revisions' permission ('view media' permission is
     // needed as well).
-    user_role_revoke_permissions($role->id(), ['view media', 'view all media revisions']);
-    $this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
+    user_role_revoke_permissions($role->id(), [
+      'view media',
+      'view all media revisions',
+    ]);
+    $this->drupalGet($media->toUrl('revision'));
     $assert->statusCodeEquals(403);
-    $this->grantPermissions($role, ['view media', 'view all media revisions']);
-    $this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
+    $this->grantPermissions($role, ['view any test media revisions']);
+    $this->drupalGet($media->toUrl('revision'));
+    $assert->statusCodeEquals(200);
+    user_role_revoke_permissions($role->id(), ['view any test media revisions']);
+    $this->grantPermissions($role, ['view all media revisions']);
+    $this->drupalGet($media->toUrl('revision'));
     $assert->statusCodeEquals(200);
 
     // Confirm the revision page shows the correct title.
     $assert->pageTextContains($media->getName());
-
-    // Confirm that the last revision is the default revision.
-    $this->assertTrue($media->isDefaultRevision(), 'Last revision is the default.');
   }
 
   /**
@@ -209,4 +233,117 @@ protected function assertRevisionCount(EntityInterface $entity, int $expected_re
     $this->assertSame($expected_revisions, (int) $count);
   }
 
+  /**
+   * Creates a media with a revision.
+   *
+   * @param \Drupal\media\Entity\Media $media
+   *   The media object.
+   */
+  private function createMediaWithRevision(Media $media): void {
+    $media->setNewRevision();
+    $media->setName('1st changed title');
+    $media->setRevisionLogMessage('first revision');
+    // Set revision creation time to check the confirmation message while
+    // deleting or reverting a revision.
+    $media->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp());
+    $media->save();
+  }
+
+  /**
+   * Tests deleting a revision.
+   */
+  public function testRevisionDelete(): void {
+    $user = $this->drupalCreateUser([
+      'edit any test media',
+      'view any test media revisions',
+      'delete any test media revisions',
+    ]);
+    $this->drupalLogin($user);
+
+    $media = $this->createMedia('Sample media');
+    $this->createMediaWithRevision($media);
+    $originalRevisionId = $media->getRevisionId();
+
+    // Cannot delete latest revision.
+    $this->drupalGet($media->toUrl('revision-delete-form'));
+    $this->assertSession()->statusCodeEquals(403);
+
+    // Create a new revision.
+    $media->setNewRevision();
+    $media->setRevisionLogMessage('second revision')
+      ->setRevisionCreationTime((new \DateTimeImmutable('12 March 2012 5pm'))->getTimestamp())
+      ->setName('Sample media updated')
+      ->save();
+
+    $this->drupalGet($media->toUrl('version-history'));
+    $this->assertSession()->pageTextContains("First revision");
+    $this->assertSession()->pageTextContains("Second revision");
+    $this->assertSession()->elementsCount('css', 'table tbody tr', 3);
+
+    // Reload the previous revision, and ensure we can delete it in the UI.
+    $revision = \Drupal::entityTypeManager()->getStorage('media')
+      ->loadRevision($originalRevisionId);
+    $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->submitForm([], 'Delete');
+    $this->assertSession()->pageTextNotContains("First revision");
+    $this->assertSession()->pageTextContains("Second revision");
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->addressEquals(sprintf('media/%s/revisions', $media->id()));
+    $this->assertSession()->pageTextContains('Revision from Sun, 01/11/2009 - 16:00 of test 1st changed title has been deleted.');
+    // Check that only two revisions exists, i.e. the original and the latest
+    // revision.
+    $this->assertSession()->elementsCount('css', 'table tbody tr', 2);
+  }
+
+  /**
+   * Tests reverting a revision.
+   */
+  public function testRevisionRevert(): void {
+    /** @var \Drupal\user\UserInterface $user */
+    $user = $this->drupalCreateUser([
+      'edit any test media',
+      'view any test media revisions',
+      'revert any test media revisions',
+    ]);
+    $this->drupalLogin($user);
+
+    $media = $this->createMedia('Initial title');
+    $this->createMediaWithRevision($media);
+    $originalRevisionId = $media->getRevisionId();
+    $originalRevisionLabel = $media->getName();
+
+    // Cannot revert latest revision.
+    $this->drupalGet($media->toUrl('revision-revert-form'));
+    $this->assertSession()->statusCodeEquals(403);
+
+    // Create a new revision.
+    $media->setNewRevision();
+    $media->setRevisionLogMessage('Second revision')
+      ->setRevisionCreationTime((new \DateTimeImmutable('12 March 2012 5pm'))->getTimestamp())
+      ->setName('Sample media updated')
+      ->save();
+
+    $this->drupalGet($media->toUrl('version-history'));
+    $this->assertSession()->pageTextContains("First revision");
+    $this->assertSession()->pageTextContains("Second revision");
+    $this->assertSession()->elementsCount('css', 'table tbody tr', 3);
+
+    // Reload the previous revision, and ensure we can revert to it in the UI.
+    $revision = \Drupal::entityTypeManager()->getStorage('media')
+      ->loadRevision($originalRevisionId);
+    $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->submitForm([], 'Revert');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Copy of the revision from Sun, 01/11/2009 - 16:00');
+    $this->assertSession()->addressEquals(sprintf('media/%s/revisions', $media->id()));
+    $this->assertSession()->pageTextContains(sprintf('test %s has been reverted to the revision from Sun, 01/11/2009 - 16:00.', $originalRevisionLabel));
+    $this->assertSession()->elementsCount('css', 'table tbody tr', 4);
+    $this->drupalGet($media->toUrl('edit-form'));
+    // Check if the title is changed to the reverted revision.
+    $this->assertSession()->pageTextContains('1st changed title');
+  }
+
 }
diff --git a/core/modules/media/tests/src/Kernel/MediaAccessControlHandlerTest.php b/core/modules/media/tests/src/Kernel/MediaAccessControlHandlerTest.php
index a2984c22c8f9..b017783d9609 100644
--- a/core/modules/media/tests/src/Kernel/MediaAccessControlHandlerTest.php
+++ b/core/modules/media/tests/src/Kernel/MediaAccessControlHandlerTest.php
@@ -33,15 +33,18 @@ class MediaAccessControlHandlerTest extends MediaKernelTestBase {
    *   Expected cache contexts.
    * @param string[] $expected_cache_tags
    *   Expected cache tags.
+   * @param bool $is_latest_revision
+   *   If FALSE, the media is historic revision.
    *
    * @covers ::checkAccess
    * @dataProvider providerAccess
    */
-  public function testAccess(array $permissions, array $entity_values, $operation, AccessResultInterface $expected_result, array $expected_cache_contexts, array $expected_cache_tags) {
+  public function testAccess(array $permissions, array $entity_values, string $operation, AccessResultInterface $expected_result, array $expected_cache_contexts, array $expected_cache_tags, bool $is_latest_revision) {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage $entity_storage */
+    $entity_storage = $this->container->get('entity_type.manager')->getStorage('media');
+
     // Set a fixed ID so the type specific permissions match.
-    $media_type = $this->createMediaType('test', [
-      'id' => 'test',
-    ]);
+    $media_type = $this->createMediaType('test', ['id' => 'test']);
 
     $user = $this->createUser($permissions);
 
@@ -53,6 +56,20 @@ public function testAccess(array $permissions, array $entity_values, $operation,
 
     $entity = Media::create($entity_values);
     $entity->save();
+
+    $load_revision_id = NULL;
+    if (!$is_latest_revision) {
+      $load_revision_id = $entity->getRevisionId();
+      // Set up for a new revision to be saved.
+      $entity = $entity_storage->createRevision($entity);
+    }
+    $entity->save();
+
+    // Reload a previous revision.
+    if ($load_revision_id !== NULL) {
+      $entity = $entity_storage->loadRevision($load_revision_id);
+    }
+
     /** @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface $access_handler */
     $access_handler = $this->container->get('entity_type.manager')->getAccessControlHandler('media');
     $this->assertAccess($expected_result, $expected_cache_contexts, $expected_cache_tags, $access_handler->access($entity, $operation, $user, TRUE));
@@ -127,6 +144,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['owner, no permissions / published / update'] = [
       [],
@@ -135,6 +153,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, no permissions / published / delete'] = [
       [],
@@ -143,6 +162,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, no permissions / unpublished / view'] = [
       [],
@@ -151,6 +171,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['owner, no permissions / unpublished / update'] = [
       [],
@@ -159,6 +180,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, no permissions / unpublished / delete'] = [
       [],
@@ -167,6 +189,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
 
     // Check published / unpublished media access for a user not owning the
@@ -178,6 +201,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['not owner, no permissions / published / update'] = [
       [],
@@ -186,6 +210,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, no permissions / published / delete'] = [
       [],
@@ -194,6 +219,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, no permissions / unpublished / view'] = [
       [],
@@ -202,6 +228,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['not owner, no permissions / unpublished / update'] = [
       [],
@@ -210,6 +237,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, no permissions / unpublished / delete'] = [
       [],
@@ -218,6 +246,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
 
     // Check published / unpublished media access for a user owning the media
@@ -229,6 +258,7 @@ public function providerAccess() {
       AccessResult::allowed(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['owner, can view media / published / update'] = [
       ['view media'],
@@ -237,6 +267,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, can view media / published / delete'] = [
       ['view media'],
@@ -245,6 +276,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, can view media / unpublished / view'] = [
       ['view media'],
@@ -253,6 +285,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['owner, can view media / unpublished / update'] = [
       ['view media'],
@@ -261,6 +294,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, can view media / unpublished / delete'] = [
       ['view media'],
@@ -269,6 +303,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
 
     // Check published / unpublished media access for a user not owning the
@@ -280,6 +315,7 @@ public function providerAccess() {
       AccessResult::allowed(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['not owner, can view media / published / update'] = [
       ['view media'],
@@ -288,6 +324,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, can view media / published / delete'] = [
       ['view media'],
@@ -296,6 +333,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, can view media / unpublished / view'] = [
       ['view media'],
@@ -304,6 +342,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['not owner, can view media / unpublished / update'] = [
       ['view media'],
@@ -312,6 +351,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, can view media / unpublished / delete'] = [
       ['view media'],
@@ -320,6 +360,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
 
     // Check published / unpublished media access for a user owning the media
@@ -331,6 +372,7 @@ public function providerAccess() {
       AccessResult::allowed(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['owner, can view own unpublished media / published / update'] = [
       ['view media', 'view own unpublished media'],
@@ -339,6 +381,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, can view own unpublished media / published / delete'] = [
       ['view media', 'view own unpublished media'],
@@ -347,6 +390,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, can view own unpublished media / unpublished / view'] = [
       ['view media', 'view own unpublished media'],
@@ -355,6 +399,7 @@ public function providerAccess() {
       AccessResult::allowed(),
       ['user.permissions', 'user'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['owner, can view own unpublished media / unpublished / update'] = [
       ['view media', 'view own unpublished media'],
@@ -363,6 +408,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['owner, can view own unpublished media / unpublished / delete'] = [
       ['view media', 'view own unpublished media'],
@@ -371,6 +417,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
 
     // Check published / unpublished media access for a user not owning the
@@ -382,6 +429,7 @@ public function providerAccess() {
       AccessResult::allowed(),
       ['user.permissions'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['not owner, can view own unpublished media / published / update'] = [
       ['view media', 'view own unpublished media'],
@@ -390,6 +438,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, can view own unpublished media / published / delete'] = [
       ['view media', 'view own unpublished media'],
@@ -398,6 +447,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, can view own unpublished media / unpublished / view'] = [
       ['view media', 'view own unpublished media'],
@@ -406,6 +456,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions', 'user'],
       ['media:1'],
+      TRUE,
     ];
     $test_data['not owner, can view own unpublished media / unpublished / update'] = [
       ['view media', 'view own unpublished media'],
@@ -414,6 +465,7 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
     ];
     $test_data['not owner, can view own unpublished media / unpublished / delete'] = [
       ['view media', 'view own unpublished media'],
@@ -422,6 +474,145 @@ public function providerAccess() {
       AccessResult::neutral(),
       ['user.permissions'],
       [],
+      TRUE,
+    ];
+    // View all revisions:
+    $test_data['view all revisions:none'] = [
+      [],
+      [],
+      'view all revisions',
+      AccessResult::neutral(),
+      ['user.permissions'],
+      ['media:1'],
+      TRUE,
+    ];
+    $test_data['admins can view all revisions'] = [
+      ['administer media'],
+      [],
+      'view all revisions',
+      AccessResult::allowed(),
+      ['user.permissions'],
+      [],
+      TRUE,
+    ];
+    $test_data['view all revisions with view bundle permission'] = [
+      ['view any test media revisions'],
+      [],
+      'view all revisions',
+      AccessResult::allowed(),
+      ['user.permissions'],
+      [],
+      TRUE,
+    ];
+    // Revert revisions:
+    $test_data['revert a latest revision with no permissions'] = [
+      [],
+      [],
+      'revert',
+      AccessResult::forbidden(),
+      [],
+      [],
+      TRUE,
+    ];
+    $test_data['revert a historical revision with no permissions'] = [
+      [],
+      [],
+      'revert',
+      AccessResult::neutral(),
+      ['user.permissions'],
+      ['media:1'],
+      FALSE,
+    ];
+    $test_data['revert latest revision with administer media permission'] = [
+      ['administer media'],
+      [],
+      'revert',
+      AccessResult::forbidden(),
+      [],
+      [],
+      TRUE,
+    ];
+    $test_data['revert a historical revision with administer media permission'] = [
+      ['administer media'],
+      [],
+      'revert',
+      AccessResult::allowed(),
+      ['user.permissions'],
+      [],
+      FALSE,
+    ];
+    $test_data['revert a latest revision with revert bundle permission'] = [
+      ['revert any test media revisions'],
+      [],
+      'revert',
+      AccessResult::forbidden(),
+      [],
+      [],
+      TRUE,
+    ];
+    $test_data['revert a historical revision with revert bundle permission'] = [
+      ['revert any test media revisions'],
+      [],
+      'revert',
+      AccessResult::allowed(),
+      ['user.permissions'],
+      ['media:1'],
+      FALSE,
+    ];
+    // Delete revisions:
+    $test_data['delete a latest revision with no permission'] = [
+      [],
+      [],
+      'delete revision',
+      AccessResult::forbidden(),
+      [],
+      [],
+      TRUE,
+    ];
+    $test_data['delete a historical revision with no permission'] = [
+      [],
+      [],
+      'delete revision',
+      AccessResult::neutral(),
+      ['user.permissions'],
+      ['media:1'],
+      FALSE,
+    ];
+    $test_data['delete a latest revision with administer media permission'] = [
+      ['administer media'],
+      [],
+      'delete revision',
+      AccessResult::forbidden(),
+      [],
+      [],
+      TRUE,
+    ];
+    $test_data['delete a historical revision with administer media permission'] = [
+      ['administer media'],
+      [],
+      'delete revision',
+      AccessResult::allowed(),
+      ['user.permissions'],
+      [],
+      FALSE,
+    ];
+    $test_data['delete a latest revision with delete bundle permission'] = [
+      ['delete any test media revisions'],
+      [],
+      'delete revision',
+      AccessResult::forbidden(),
+      [],
+      [],
+      TRUE,
+    ];
+    $test_data['delete a historical revision with delete bundle permission'] = [
+      ['delete any test media revisions'],
+      [],
+      'delete revision',
+      AccessResult::allowed(),
+      ['user.permissions'],
+      ['media:1'],
+      FALSE,
     ];
 
     return $test_data;
-- 
GitLab