From 99f8e3e6dbd9da3c2124d2b875caaf147f95f22b Mon Sep 17 00:00:00 2001
From: Daniel Wehner <daniel@tag1consulting.com>
Date: Tue, 8 Dec 2015 17:21:58 +0100
Subject: [PATCH] Add a revision view controller

---
 entity.services.yml                           |  7 ++
 .../EntityRevisionRouteAccessChecker.php      |  4 +-
 src/Controller/RevisionController.php         | 36 ++++++++++
 src/Routing/RevisionRouteProvider.php         | 66 +++++++++++++++++
 tests/Kernel/RevisionBasicUITest.php          | 70 +++++++++++++++++++
 .../src/Entity/EntityWithRevisionRoutes.php   | 58 +++++++++++++++
 6 files changed, 240 insertions(+), 1 deletion(-)
 create mode 100644 src/Controller/RevisionController.php
 create mode 100644 src/Routing/RevisionRouteProvider.php
 create mode 100644 tests/Kernel/RevisionBasicUITest.php
 create mode 100644 tests/modules/entity_module_test/src/Entity/EntityWithRevisionRoutes.php

diff --git a/entity.services.yml b/entity.services.yml
index 0440b03..e105738 100644
--- a/entity.services.yml
+++ b/entity.services.yml
@@ -9,3 +9,10 @@ services:
     class: Drupal\entity\RouteEnhancer\EntityRevisionRouteEnhancer
     tags:
       - { name: route_enhancer, priority: 20 }
+
+
+  access_checker.entity_revision:
+    class: \Drupal\entity\Access\EntityRevisionRouteAccessChecker
+    arguments: ['@entity_type.manager']
+    tags:
+      - { name: access_check, applies_to: _entity_access_revision, needs_request: TRUE }
diff --git a/src/Access/EntityRevisionRouteAccessChecker.php b/src/Access/EntityRevisionRouteAccessChecker.php
index 797aab4..8a4184f 100644
--- a/src/Access/EntityRevisionRouteAccessChecker.php
+++ b/src/Access/EntityRevisionRouteAccessChecker.php
@@ -14,6 +14,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -46,7 +47,8 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
   /**
    * {@inheritdoc}
    */
-  public function access(Route $route, AccountInterface $account, RevisionableInterface $_entity_revision) {
+  public function access(Route $route, AccountInterface $account, Request $request) {
+    $_entity_revision = $request->attributes->get('_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();
diff --git a/src/Controller/RevisionController.php b/src/Controller/RevisionController.php
new file mode 100644
index 0000000..b85ee83
--- /dev/null
+++ b/src/Controller/RevisionController.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity\Controller\RevisionController.
+ */
+
+namespace Drupal\entity\Controller;
+
+use Drupal\Core\Entity\Controller\EntityViewController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides some controllers related with entity revisions.
+ */
+class RevisionController extends EntityViewController {
+
+  /**
+   * Provides a page to render a single entity revision.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $_entity_revision
+   *   The Entity to be rendered. Note this variable is named $_entity_revision
+   *   rather than $entity to prevent collisions with other named placeholders
+   *   in the route.
+   * @param string $view_mode
+   *   (optional) The view mode that should be used to display the entity.
+   *   Defaults to 'full'.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function view(EntityInterface $_entity_revision, $view_mode = 'full') {
+    return parent::view($_entity_revision, $view_mode);
+  }
+
+}
diff --git a/src/Routing/RevisionRouteProvider.php b/src/Routing/RevisionRouteProvider.php
new file mode 100644
index 0000000..71adea8
--- /dev/null
+++ b/src/Routing/RevisionRouteProvider.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity\Routing\RevisionRouteProvider.
+ */
+
+namespace Drupal\entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides revision routes.
+ */
+class RevisionRouteProvider implements EntityRouteProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = new RouteCollection();
+    $entity_type_id = $entity_type->id();
+
+    if ($view_route = $this->getRevisionViewRoute($entity_type)) {
+      $collection->add("entity.$entity_type_id.revision", $view_route);
+    }
+
+    return $collection;
+  }
+
+  /**
+   * Gets the entity revision view route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getRevisionViewRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('revision')) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($entity_type->getLinkTemplate('revision'));
+      $route->addDefaults([
+        '_controller' => '\Drupal\entity\Controller\RevisionController::view',
+        '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
+      ]);
+      $route->addRequirements([
+        '_entity_access_revision' => "$entity_type_id.view",
+      ]);
+      $route->setOption('parameters', [
+        $entity_type->id() => [
+          'type' => 'entity:' . $entity_type->id(),
+        ],
+        $entity_type->id() . '_revision' => [
+          'type' => 'entity_revision:' . $entity_type->id(),
+        ],
+      ]);
+      return $route;
+    }
+  }
+
+}
diff --git a/tests/Kernel/RevisionBasicUITest.php b/tests/Kernel/RevisionBasicUITest.php
new file mode 100644
index 0000000..08aa99e
--- /dev/null
+++ b/tests/Kernel/RevisionBasicUITest.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\entity\Kernel\RevisionBasicUITest.
+ */
+
+namespace Drupal\Tests\entity\Kernel;
+
+use Drupal\entity_module_test\Entity\EntityWithRevisionRoutes;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\Entity\User;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @group entity
+ */
+class RevisionBasicUITest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_module_test', 'system', 'user', 'entity'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('entity_test__rev_routes');
+    $this->installSchema('system', 'router');
+
+    \Drupal::service('router.builder')->rebuild();
+  }
+
+  public function testRevisionView() {
+    $entity = EntityWithRevisionRoutes::create([]);
+    $entity->save();
+
+    $revision = clone $entity;
+    $revision->setNewRevision(TRUE);
+    $revision->isDefaultRevision(FALSE);
+    $revision->save();
+
+    /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
+    $http_kernel = \Drupal::service('http_kernel');
+    $request = Request::create($revision->url('revision'));
+    $response = $http_kernel->handle($request);
+    $this->assertEquals(403, $response->getStatusCode());
+
+    $role = Role::create(['id' => 'test_role']);
+    $role->grantPermission('view all entity_test__rev_routes revisions');
+    $role->grantPermission('administer entity_test__revision_routes');
+    $role->save();
+
+    $user = User::create([
+      'name' => 'Test user',
+    ]);
+    $user->addRole($role->id());
+    \Drupal::service('account_switcher')->switchTo($user);
+
+    $request = Request::create($revision->url('revision'));
+    $response = $http_kernel->handle($request);
+    $this->assertEquals(200, $response->getStatusCode());
+  }
+
+}
diff --git a/tests/modules/entity_module_test/src/Entity/EntityWithRevisionRoutes.php b/tests/modules/entity_module_test/src/Entity/EntityWithRevisionRoutes.php
new file mode 100644
index 0000000..0638e3f
--- /dev/null
+++ b/tests/modules/entity_module_test/src/Entity/EntityWithRevisionRoutes.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_module_test\Entity\EntityWithRevisionRoutes.
+ */
+
+namespace Drupal\entity_module_test\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\entity\EntityKeysFieldsTrait;
+use Drupal\entity\Revision\EntityRevisionLogTrait;
+
+/**
+ * @ContentEntityType(
+ *   id = "entity_test__rev_routes",
+ *   label = @Translation("Entity test with revision routes"),
+ *   handlers = {
+ *     "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage",
+ *     "route_provider" = {
+ *       "revision" = "\Drupal\entity\Routing\RevisionRouteProvider",
+ *     },
+ *   },
+ *   base_table = "entity_test__revision_routes",
+ *   data_table = "entity_test__revision_routes__field_data",
+ *   revision_table = "entity_test__revision_routes__revision",
+ *   revision_data_table = "entity_test__revision_routes__field_revision",
+ *   translatable = TRUE,
+ *   revisionable = TRUE,
+ *   admin_permission = "administer entity_test__revision_routes",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "revision" = "vid",
+ *     "langcode" = "langcode",
+ *   },
+ *   links = {
+ *     "revision" = "/entity_test__rev_routes/{entity_test__rev_routes}/revisions/{entity_test__rev_routes_revision}/view",
+ *   }
+ * )
+ */
+class EntityWithRevisionRoutes extends ContentEntityBase {
+
+  use EntityRevisionLogTrait;
+  use EntityKeysFieldsTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = [];
+
+    $fields += static::entityKeysBaseFieldDefinitions($entity_type);
+
+    return $fields;
+  }
+
+}
-- 
GitLab