Issue #2885486 by seanB, timmillwood, tstoeckler, Wim Leers: Media entity is revisionable but doesn't have a revision link template
parent
1364bb6341
commit
062c2556f9
|
@ -23,3 +23,7 @@ delete any media:
|
|||
|
||||
create media:
|
||||
title: 'Create media'
|
||||
|
||||
view all media revisions:
|
||||
title: 'View all media revisions'
|
||||
description: 'To view a revision, you also need permission to view the media item.'
|
||||
|
|
|
@ -4,3 +4,18 @@ entity.media.multiple_delete_confirm:
|
|||
_form: '\Drupal\media\Form\MediaDeleteMultipleConfirmForm'
|
||||
requirements:
|
||||
_permission: 'administer media+delete any media'
|
||||
|
||||
entity.media.revision:
|
||||
path: '/media/{media}/revisions/{media_revision}/view'
|
||||
defaults:
|
||||
_controller: '\Drupal\Core\Entity\Controller\EntityViewController::viewRevision'
|
||||
_title_callback: '\Drupal\Core\Entity\Controller\EntityController::title'
|
||||
options:
|
||||
parameters:
|
||||
media:
|
||||
type: entity:media
|
||||
media_revision:
|
||||
type: entity_revision:media
|
||||
requirements:
|
||||
_access_media_revision: 'view'
|
||||
media: \d+
|
||||
|
|
|
@ -2,3 +2,9 @@ services:
|
|||
plugin.manager.media.source:
|
||||
class: Drupal\media\MediaSourceManager
|
||||
parent: default_plugin_manager
|
||||
|
||||
access_check.media.revision:
|
||||
class: Drupal\media\Access\MediaRevisionAccessCheck
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _access_media_revision }
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\media\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\media\MediaInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides an access checker for media item revisions.
|
||||
*
|
||||
* @ingroup media_access
|
||||
*/
|
||||
class MediaRevisionAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The media storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
|
||||
*/
|
||||
protected $mediaStorage;
|
||||
|
||||
/**
|
||||
* The media access control handler.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
|
||||
*/
|
||||
protected $mediaAccess;
|
||||
|
||||
/**
|
||||
* A static cache of access checks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $access = [];
|
||||
|
||||
/**
|
||||
* Constructs a new MediaRevisionAccessCheck.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->mediaStorage = $entity_type_manager->getStorage('media');
|
||||
$this->mediaAccess = $entity_type_manager->getAccessControlHandler('media');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks routing access for the media item revision.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
* @param int $media_revision
|
||||
* (optional) The media item revision ID. If not specified, but $media is,
|
||||
* access is checked for that object's revision.
|
||||
* @param \Drupal\media\MediaInterface $media
|
||||
* (optional) A media item. Used for checking access to a media items
|
||||
* default revision when $media_revision is unspecified. Ignored when
|
||||
* $media_revision is specified. If neither $media_revision nor $media are
|
||||
* specified, then access is denied.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, AccountInterface $account, $media_revision = NULL, MediaInterface $media = NULL) {
|
||||
if ($media_revision) {
|
||||
$media = $this->mediaStorage->loadRevision($media_revision);
|
||||
}
|
||||
$operation = $route->getRequirement('_access_media_revision');
|
||||
return AccessResult::allowedIf($media && $this->checkAccess($media, $account, $operation))->cachePerPermissions()->addCacheableDependency($media);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks media item revision access.
|
||||
*
|
||||
* @param \Drupal\media\MediaInterface $media
|
||||
* The media item to check.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* A user object representing the user for whom the operation is to be
|
||||
* performed.
|
||||
* @param string $op
|
||||
* (optional) The specific operation being checked. Defaults to 'view'.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the operation may be performed, FALSE otherwise.
|
||||
*/
|
||||
public function checkAccess(MediaInterface $media, AccountInterface $account, $op = 'view') {
|
||||
if (!$media || $op !== 'view') {
|
||||
// If there was no media to check against, or the $op was not one of the
|
||||
// supported ones, we return access denied.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Statically cache access by revision ID, language code, user account ID,
|
||||
// and operation.
|
||||
$langcode = $media->language()->getId();
|
||||
$cid = $media->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $op;
|
||||
|
||||
if (!isset($this->access[$cid])) {
|
||||
// Perform basic permission checks first.
|
||||
if (!$account->hasPermission('view all media revisions') && !$account->hasPermission('administer media')) {
|
||||
$this->access[$cid] = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// There should be at least two revisions. If the revision ID of the
|
||||
// given media item and the revision ID of the default revision differ,
|
||||
// then we already have two different revisions so there is no need for a
|
||||
// separate database check.
|
||||
if ($media->isDefaultRevision() && ($this->countDefaultLanguageRevisions($media) == 1)) {
|
||||
$this->access[$cid] = FALSE;
|
||||
}
|
||||
elseif ($account->hasPermission('administer media')) {
|
||||
$this->access[$cid] = TRUE;
|
||||
}
|
||||
else {
|
||||
// First check the access to the default revision and finally, if the
|
||||
// media passed in is not the default revision then access to that, too.
|
||||
$this->access[$cid] = $this->mediaAccess->access($this->mediaStorage->load($media->id()), $op, $account) && ($media->isDefaultRevision() || $this->mediaAccess->access($media, $op, $account));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->access[$cid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of revisions in the default language.
|
||||
*
|
||||
* @param \Drupal\media\MediaInterface $media
|
||||
* The media item for which to to count the revisions.
|
||||
*
|
||||
* @return int
|
||||
* The number of revisions in the default language.
|
||||
*/
|
||||
protected function countDefaultLanguageRevisions(MediaInterface $media) {
|
||||
$entity_type = $media->getEntityType();
|
||||
$count = $this->mediaStorage->getQuery()
|
||||
->allRevisions()
|
||||
->condition($entity_type->getKey('id'), $media->id())
|
||||
->condition($entity_type->getKey('default_langcode'), 1)
|
||||
->count()
|
||||
->execute();
|
||||
return $count;
|
||||
}
|
||||
|
||||
}
|
|
@ -76,6 +76,7 @@ use Drupal\user\UserInterface;
|
|||
* "canonical" = "/media/{media}",
|
||||
* "delete-form" = "/media/{media}/delete",
|
||||
* "edit-form" = "/media/{media}/edit",
|
||||
* "revision" = "/media/{media}/revisions/{media_revision}/view",
|
||||
* "admin-form" = "/admin/structure/media/manage/{media_type}"
|
||||
* }
|
||||
* )
|
||||
|
|
|
@ -4,6 +4,9 @@ namespace Drupal\Tests\media\Functional;
|
|||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\media\MediaInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the revisionability of media entities.
|
||||
|
@ -12,6 +15,62 @@ use Drupal\field\Entity\FieldConfig;
|
|||
*/
|
||||
class MediaRevisionTest extends MediaFunctionalTestBase {
|
||||
|
||||
/**
|
||||
* Checks media revision operations.
|
||||
*/
|
||||
public function testRevisions() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $media_storage */
|
||||
$media_storage = $this->container->get('entity.manager')->getStorage('media');
|
||||
|
||||
// Create a media type and media item.
|
||||
$media_type = $this->createMediaType();
|
||||
$media = $media_storage->create([
|
||||
'bundle' => $media_type->id(),
|
||||
'name' => 'Unnamed',
|
||||
]);
|
||||
$media->save();
|
||||
|
||||
// You can't access the revision page when there is only 1 revision.
|
||||
$this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
|
||||
$assert->statusCodeEquals(403);
|
||||
|
||||
// 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);
|
||||
|
||||
// Test permissions.
|
||||
$this->drupalLogin($this->nonAdminUser);
|
||||
/** @var \Drupal\user\RoleInterface $role */
|
||||
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
|
||||
|
||||
// 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');
|
||||
$assert->statusCodeEquals(403);
|
||||
$this->grantPermissions($role, ['view media', 'view all media revisions']);
|
||||
$this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
|
||||
$assert->statusCodeEquals(200);
|
||||
|
||||
// Confirm the revision page shows the correct title.
|
||||
$assert->pageTextContains($media->label());
|
||||
|
||||
// Confirm that the last revision is the default revision.
|
||||
$this->assertTrue($media->isDefaultRevision(), 'Last revision is the default.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating revisions of a File media item.
|
||||
*/
|
||||
|
@ -43,6 +102,13 @@ class MediaRevisionTest extends MediaFunctionalTestBase {
|
|||
$page->fillField('Name', 'Foobaz');
|
||||
$page->pressButton('Save and keep published');
|
||||
$this->assertRevisionCount($media, 2);
|
||||
|
||||
// Confirm the correct revision title appears on "view revisions" page.
|
||||
$media = $this->container->get('entity_type.manager')
|
||||
->getStorage('media')
|
||||
->loadUnchanged(1);
|
||||
$this->drupalGet("media/" . $media->id() . "/revisions/" . $media->getRevisionId() . "/view");
|
||||
$assert->pageTextContains('Foobaz');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,6 +149,29 @@ class MediaRevisionTest extends MediaFunctionalTestBase {
|
|||
$page->fillField('Name', 'Foobaz');
|
||||
$page->pressButton('Save and keep published');
|
||||
$this->assertRevisionCount($media, 2);
|
||||
|
||||
// Confirm the correct revision title appears on "view revisions" page.
|
||||
$media = $this->container->get('entity_type.manager')
|
||||
->getStorage('media')
|
||||
->loadUnchanged(1);
|
||||
$this->drupalGet("media/" . $media->id() . "/revisions/" . $media->getRevisionId() . "/view");
|
||||
$assert->pageTextContains('Foobaz');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new revision for a given media item.
|
||||
*
|
||||
* @param \Drupal\media\MediaInterface $media
|
||||
* A media object.
|
||||
*
|
||||
* @return \Drupal\media\MediaInterface
|
||||
* A media object with up to date revision information.
|
||||
*/
|
||||
protected function createMediaRevision(MediaInterface $media) {
|
||||
$media->set('name', $this->randomMachineName());
|
||||
$media->setNewRevision();
|
||||
$media->save();
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue