Skip to content
Snippets Groups Projects
Commit 787c3c82 authored by Daniel Wehner's avatar Daniel Wehner Committed by Bojan Zivanovic
Browse files

Add a param converter + route enhancers for entity revisions

parent de6f659e
No related branches found
No related tags found
No related merge requests found
services:
param_converter.entity_revision:
class: \Drupal\entity\ParamConverter\EntityRevisionParamConverter
arguments: ['@entity_type.manager']
tags:
- { name: paramconverter }
route_enhancer.entity_revision:
class: Drupal\entity\RouteEnhancer\EntityRevisionRouteEnhancer
tags:
- { name: route_enhancer, priority: 20 }
<?php
/**
* @file
* Contains \Drupal\entity\Access\EntityRevisionRouteAccessChecker
*/
namespace Drupal\entity\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Checks access to a entity revision.
*/
class EntityRevisionRouteAccessChecker implements AccessInterface {
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Stores calculated access check results.
*
* @var array
*/
protected $accessCache = array();
/**
* Creates a new EntityRevisionRouteAccessChecker instance.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function access(Route $route, AccountInterface $account, RevisionableInterface $_entity_revision) {
$operation = $route->getRequirement('_entity_access_revision');
list(, $operation) = explode('.', $operation, 2);
return AccessResult::allowedIf($_entity_revision && $this->checkAccess($_entity_revision, $account, $operation))->cachePerPermissions();
}
protected function checkAccess(ContentEntityInterface $entity, AccountInterface $account, $operation = 'view') {
$entity_type = $entity->getEntityType();
$entity_type_id = $entity->getEntityTypeId();
$entity_access = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
/** @var \Drupal\Core\Entity\EntityStorageInterface $entity_storage */
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
$map = [
'view' => "view all $entity_type_id revisions",
'update' => "revert all $entity_type_id revisions",
'delete' => "delete all $entity_type_id revisions",
];
$bundle = $entity->bundle();
$type_map = [
'view' => "view $entity_type_id $bundle revisions",
'update' => "revert $entity_type_id $bundle revisions",
'delete' => "delete $entity_type_id $bundle revisions",
];
if (!$entity || !isset($map[$operation]) || !isset($type_map[$operation])) {
// If there was no node 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 = $entity->language()->getId();
$cid = $entity->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $operation;
if (!isset($this->accessCache[$cid])) {
// Perform basic permission checks first.
if (!$account->hasPermission($map[$operation]) && !$account->hasPermission($type_map[$operation]) && !$account->hasPermission('administer nodes')) {
$this->accessCache[$cid] = FALSE;
return FALSE;
}
if (($admin_permission = $entity_type->getAdminPermission()) && $account->hasPermission($admin_permission)) {
$this->accessCache[$cid] = TRUE;
}
else {
// First check the access to the default revision and finally, if the
// node passed in is not the default revision then access to that, too.
$this->accessCache[$cid] = $entity_access->access($entity_storage->load($entity->id()), $operation, $account) && ($entity->isDefaultRevision() || $entity_access->access($entity, $operation, $account));
}
}
return $this->accessCache[$cid];
}
/**
* Counts the number of revisions in the default language.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
* The entity storage.
*
* @return int
* The number of revisions in the default language.
*/
protected function countDefaultLanguageRevisions(ContentEntityInterface $entity, EntityStorageInterface $entity_storage) {
$entity_type = $entity->getEntityType();
$count = $entity_storage->getQuery()
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id())
->condition($entity_type->getKey('default_langcode'), 1)
->count()
->execute();
return $count;
}
/**
* Resets the access cache.
*
* @return $this
*/
public function resetAccessCache() {
$this->accessCache = [];
return $this;
}
}
<?php
/**
* @file
* Contains \Drupal\entity\ParamConverter\EntityRevisionParamConverter.
*/
namespace Drupal\entity\ParamConverter;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Symfony\Component\Routing\Route;
/**
* Parameter converter for single revisions.
*/
class EntityRevisionParamConverter implements ParamConverterInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Creates a new EntityRevisionParamConverter instance.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
list (, $entity_type_id) = explode(':', $definition['type']);
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
return $entity_storage->loadRevision($value);
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
return isset($definition['type']) && strpos($definition['type'], 'entity_revision:') !== FALSE;
}
}
<?php
/**
* @file
* Contains \Drupal\entity\RouteEnhancer\EntityRevisionRouteEnhancer.
*/
namespace Drupal\entity\RouteEnhancer;
use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Adds _entity_revision to the request attributes, if possible.
*/
class EntityRevisionRouteEnhancer implements RouteEnhancerInterface {
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
// Check whether there is any entity revision parameter.
$parameters = $route->getOption('parameters') ?: [];
foreach ($parameters as $info) {
if (isset($info['type']) && strpos($info['type'], 'entity_revision:') === 0) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request) {
/** @var \Symfony\Component\Routing\Route $route */
$route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
$options = $route->getOptions();
if (isset($options['parameters'])) {
foreach ($options['parameters'] as $name => $details) {
if (!empty($details['type']) && strpos($details['type'], 'entity_revision:') !== FALSE) {
$defaults['_entity_revision'] = $defaults[$name];
break;
}
}
}
return $defaults;
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\entity\Unit\ParamConverter\EntityRevisionParamConverterTest.
*/
namespace Drupal\Tests\entity\Unit\ParamConverter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\entity\ParamConverter\EntityRevisionParamConverter;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\entity\ParamConverter\EntityRevisionParamConverter
* @group entity
*/
class EntityRevisionParamConverterTest extends \PHPUnit_Framework_TestCase {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The tested entity revision param converter.
*
* @var \Drupal\entity\ParamConverter\EntityRevisionParamConverter
*/
protected $converter;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->converter = new EntityRevisionParamConverter($this->prophesize(EntityTypeManagerInterface::class)->reveal());
}
protected function getTestRoute() {
$route = new Route('/test/{test_revision}');
$route->setOption('parameters', [
'test_revision' => [
'type' => 'entity_revision:test',
],
]);
return $route;
}
/**
* @covers ::applies
*/
public function testNonApplyingRoute() {
$route = new Route('/test');
$this->assertFalse($this->converter->applies([], 'test_revision', $route));
}
/**
* @covers ::applies
*/
public function testApplyingRoute() {
$route = $this->getTestRoute();
$this->assertTrue($this->converter->applies($route->getOption('parameters')['test_revision'], 'test_revision', $route));
}
/**
* @covers ::convert
*/
public function testConvert() {
$entity = $this->prophesize(EntityInterface::class)->reveal();
$storage = $this->prophesize(EntityStorageInterface::class);
$storage->loadRevision(1)->willReturn($entity);
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
$entity_type_manager->getStorage('test')->willReturn($storage->reveal());
$converter = new EntityRevisionParamConverter($entity_type_manager->reveal());
$route = $this->getTestRoute();
$converter->convert(1, $route->getOption('parameters')['test_revision'], 'test_revision', ['test_revision' => 1]);
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\Unit\RouteEnhancer\EntityRevisionRouteEnhancerTest.
*/
namespace Drupal\Tests\Unit\RouteEnhancer;
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\RouteEnhancer\EntityRevisionRouteEnhancer;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\entity\RouteEnhancer\EntityRevisionRouteEnhancer
* @group entity
*/
class EntityRevisionRouteEnhancerTest extends \PHPUnit_Framework_TestCase {
/**
* @var \Drupal\entity\RouteEnhancer\EntityRevisionRouteEnhancer
*/
protected $routeEnhancer;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->routeEnhancer = new EntityRevisionRouteEnhancer();
}
/**
* @covers ::applies
*/
public function testAppliesWithNoParameters() {
$route = new Route('/test-path');
$this->assertFalse($this->routeEnhancer->applies($route));
}
/**
* @covers ::applies
*/
public function testAppliesWithNoneRevisionParameters() {
$route = new Route('/test-path/{entity_test}', [], [], ['parameters' => ['entity_test' => ['type' => 'entity:entity_test']]]);
$this->assertFalse($this->routeEnhancer->applies($route));
}
/**
* @covers ::applies
*/
public function testAppliesWithRevisionParameters() {
$route = new Route('/test-path/{entity_test_revision}', [], [], ['parameters' => ['entity_test_revision' => ['type' => 'entity_revision:entity_test']]]);
$this->assertTrue($this->routeEnhancer->applies($route));
}
/**
* @covers ::enhance
*/
public function testEnhanceWithoutEntityRevision() {
$route = new Route('/test-path/{entity_test}', [], [], ['parameters' => ['entity_test' => ['type' => 'entity:entity_test']]]);
$request = Request::create('/test-path/123');
$entity = $this->prophesize(EntityInterface::class);
$defaults = [];
$defaults['entity_test'] = $entity->reveal();
$defaults[RouteObjectInterface::ROUTE_OBJECT] = $route;
$this->assertEquals($defaults, $this->routeEnhancer->enhance($defaults, $request));
}
/**
* @covers ::enhance
*/
public function testEnhanceWithEntityRevision() {
$route = new Route('/test-path/{entity_test_revision}', [], [], ['parameters' => ['entity_test_revision' => ['type' => 'entity_revision:entity_test']]]);
$request = Request::create('/test-path/123');
$entity = $this->prophesize(EntityInterface::class);
$defaults = [];
$defaults['entity_test_revision'] = $entity->reveal();
$defaults[RouteObjectInterface::ROUTE_OBJECT] = $route;
$expected = $defaults;
$expected['_entity_revision'] = $defaults['entity_test_revision'];
$this->assertEquals($expected, $this->routeEnhancer->enhance($defaults, $request));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment