Skip to content
Snippets Groups Projects
Commit f6da2d35 authored by Daniel Wehner's avatar Daniel Wehner
Browse files

foo

parent d700dd67
No related branches found
No related tags found
No related merge requests found
<?php
/**
* @file
* Contains \Drupal\entity\Controller\RevisionControllerTrait.
*/
namespace Drupal\entity\Controller;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Defines a trait for common revision UI functionality.
*/
trait RevisionControllerTrait {
use StringTranslationTrait;
/**
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
*/
abstract protected function entityTypeManager();
/**
* @return \Drupal\Core\Language\LanguageManagerInterface
*/
public abstract function languageManager();
/**
* Determines if the user has permission to revert revisions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check revert access for.
*
* @return bool
* TRUE if the user has revert access.
*/
abstract protected function hasRevertRevisionAccess(EntityInterface $entity);
/**
* Determines if the user has permission to delete revisions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check delete revision access for.
*
* @return bool
* TRUE if the user has delete revision access.
*/
abstract protected function hasDeleteRevisionAccess(EntityInterface $entity);
/**
* Builds a link to revert an entity revision.
*
* @param \Drupal\Core\Entity\EntityInterface $entity_revision
* The entity to build a revert revision link for.
*
* @return array A link render array.
* A link render array.
* @internal param int $revision_id The revision ID of the revert link.* The revision ID of the revert link.
*
*/
abstract protected function buildRevertRevisionLink(EntityInterface $entity_revision);
/**
* Builds a link to delete an entity revision.
*
* @param \Drupal\Core\Entity\EntityInterface $entity_revision
* The entity to build a delete revision link for.
*
* @return array A link render array.
* A link render array.
* @internal param int $revision_id The revision ID of the delete link.* The revision ID of the delete link.
*
*/
abstract protected function buildDeleteRevisionLink(EntityInterface $entity_revision);
/**
* Returns a string providing details of the revision.
*
* E.g. Node describes its revisions using {date} by {username}. For the
* non-current revision, it also provides a link to view that revision.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $revision
* @param bool $is_current
* TRUE if the revision is the current revision.
*
* @return string
* Returns a string to provide the details of the revision.
*/
abstract protected function getRevisionDescription(ContentEntityInterface $revision, $is_current = FALSE);
/**
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
*
* @return array
*/
protected function revisionIds(ContentEntityInterface $entity) {
$entity_type = $entity->getEntityTypeId();
$result = $this->entityTypeManager()->getStorage($entity_type)->getQuery()
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id())
->sort($entity_type->getKey('revision'), 'DESC')
->execute();
return array_keys($result);
}
/**
* Generates an overview table of older revisions of an entity.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* An entity object.
*
* @return array
* An array as expected by drupal_render().
*/
public function revisionOverview(ContentEntityInterface $entity) {
$langcode = $this->languageManager()
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId();
/** @var \Drupal\content_entity_base\Entity\Storage\RevisionableStorageInterface $entity_storage */
$entity_storage = $this->entityTypeManager()
->getStorage($entity->getEntityTypeId());
$header = [$this->t('Revision'), $this->t('Operations')];
$rows = [];
$revision_ids = $this->revisionIds($entity);
// @todo Expand the entity storage to load multiple revisions.
$entity_revisions = array_combine($revision_ids, array_map(function($vid) use ($entity_storage) {
return $entity_storage->loadRevision($vid);
}, $revision_ids));
$latest_revision = TRUE;
foreach ($entity_revisions as $revision) {
$row = [];
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)
->isRevisionTranslationAffected()
) {
if ($latest_revision) {
$row[] = $this->getRevisionDescription($revision, TRUE);
$row[] = [
'data' => [
'#prefix' => '<em>',
'#markup' => $this->t('Current revision'),
'#suffix' => '</em>',
],
];
foreach ($row as &$current) {
$current['class'] = ['revision-current'];
}
$latest_revision = FALSE;
}
else {
$row[] = $this->getRevisionDescription($revision, FALSE);
$links = $this->getOperationLinks($revision);
$row[] = [
'data' => [
'#type' => 'operations',
'#links' => $links,
],
];
}
}
$rows[] = $row;
}
$build[$entity->getEntityTypeId() . '_revisions_table'] = [
'#theme' => 'table',
'#rows' => $rows,
'#header' => $header,
];
// We have no clue about caching yet.
$build['#cache']['max-age'] = 0;
return $build;
}
/**
* Get the links of the operations for an entity revision.
*
* @param \Drupal\Core\Entity\EntityInterface $entity_revision
* The entity to build the revision links for.
*
* @return array
* The operation links.
*/
protected function getOperationLinks(EntityInterface $entity_revision) {
$links = [];
$revert_permission = $this->hasRevertRevisionAccess($entity_revision);
$delete_permission = $this->hasDeleteRevisionAccess($entity_revision);
if ($revert_permission) {
$links['revert'] = $this->buildRevertRevisionLink($entity_revision);
}
if ($delete_permission) {
$links['delete'] = $this->buildDeleteRevisionLink($entity_revision);
}
return $links;
}
}
<?php
/**
* @file
* Contains \Drupal\entity\Controller\RevisionOverviewController.
*/
namespace Drupal\entity\Controller;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\Revision\EntityRevisionLogInterface;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class RevisionOverviewController extends ControllerBase {
use RevisionControllerTrait;
/**
* The date formatter.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* Creates a new RevisionOverviewController instance.
*
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter.
*/
public function __construct(DateFormatterInterface $date_formatter) {
$this->dateFormatter = $date_formatter;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('date.formatter'));
}
/**
* {@inheritdoc}
*/
protected function hasDeleteRevisionAccess(EntityInterface $entity) {
return $this->currentUser()->hasPermission("delete all {$entity->id()} revisions");
}
/**
* {@inheritdoc}
*/
protected function buildRevertRevisionLink(EntityInterface $entity_revision) {
return [
'title' => t('Revert'),
'url' => $entity_revision->toUrl('revision-revert'),
];
}
/**
* {@inheritdoc}
*/
protected function buildDeleteRevisionLink(EntityInterface $entity_revision) {
return [
'title' => t('Delete'),
'url' => $entity_revision->toUrl('revision-delete'),
];
}
/**
* {@inheritdoc}
*/
protected function getRevisionDescription(ContentEntityInterface $revision, $is_current = FALSE) {
/** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\user\EntityOwnerInterface|\Drupal\entity\Revision\EntityRevisionLogInterface $revision */
if ($revision instanceof EntityOwnerInterface) {
$username = [
'#theme' => 'username',
'#account' => $revision->getOwner(),
];
}
else {
$username = '';
}
if ($revision instanceof EntityRevisionLogInterface) {
// Use revision link to link to revisions that are not active.
$date = $this->dateFormatter->format($revision->getRevisionCreationTime(), 'short');
if (!$is_current) {
$link = $revision->toLink($date, 'revision');
}
else {
$link = $revision->toLink($date);
}
}
else {
$link = $revision->toLink($revision->label(), 'revision');
}
$markup = '';
if ($revision instanceof EntityRevisionLogInterface) {
$markup = $revision->getRevisionLogMessage();
}
if ($username) {
$template = '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}';
}
else {
$template = '{% trans %} {{ date }} {% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}';
}
$column = [
'data' => [
'#type' => 'inline_template',
'#template' => $template,
'#context' => [
'date' => $link,
'username' => $username,
'message' => ['#markup' => $markup, '#allowed_tags' => Xss::getHtmlTagList()],
],
],
];
return $column;
}
/**
* {@inheritdoc}
*/
protected function hasRevertRevisionAccess(EntityInterface $entity) {
return AccessResult::allowedIfHasPermission($this->currentUser(), "revert all {$entity->getEntityTypeId()} revisions")->orIf(
AccessResult::allowedIfHasPermission($this->currentUser(), "revert {$entity->bundle()} {$entity->getEntityTypeId()} revisions")
);
}
}
...@@ -30,6 +30,10 @@ class RevisionRouteProvider implements EntityRouteProviderInterface { ...@@ -30,6 +30,10 @@ class RevisionRouteProvider implements EntityRouteProviderInterface {
$collection->add("entity.$entity_type_id.revision_revert_form", $view_route); $collection->add("entity.$entity_type_id.revision_revert_form", $view_route);
} }
if ($view_route = $this->getRevisionHistoryRoute($entity_type)) {
$collection->add("entity.$entity_type_id.version_history", $view_route);
}
return $collection; return $collection;
} }
...@@ -97,4 +101,33 @@ class RevisionRouteProvider implements EntityRouteProviderInterface { ...@@ -97,4 +101,33 @@ class RevisionRouteProvider implements EntityRouteProviderInterface {
} }
} }
/**
* Gets the entity revision version history route.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available.
*/
protected function getRevisionHistoryRoute($entity_type) {
if ($entity_type->hasLinkTemplate('version-history')) {
$entity_type_id = $entity_type->id();
$route = new Route($entity_type->getLinkTemplate('revision'));
$route->addDefaults([
'_controller' => '\Drupal\entity\Controller\RevisionControllerTrait::revisionOverview',
'_title' => 'Revisions',
]);
$route->addRequirements([
'_entity_access_revision' => "$entity_type_id.view",
]);
$route->setOption('parameters', [
$entity_type->id() => [
'type' => 'entity:' . $entity_type->id(),
],
]);
return $route;
}
}
} }
...@@ -51,8 +51,9 @@ use Drupal\entity\Revision\RevisionableContentEntityBase; ...@@ -51,8 +51,9 @@ use Drupal\entity\Revision\RevisionableContentEntityBase;
* "canonical" = "/entity_test_enhanced/{entity_test_enhanced}", * "canonical" = "/entity_test_enhanced/{entity_test_enhanced}",
* "revision" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/view", * "revision" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/view",
* "revision-revert-form" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/revert", * "revision-revert-form" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/revert",
* "version-history" = "/entity_test_enhanced/{entity_test_enhanced}/revisions",
* }, * },
* bundle_entity_type = "entity_test_enhanced_bundle" * bundle_entity_type = "entity_test_enhanced_bundle",
* ) * )
*/ */
class EnhancedEntity extends RevisionableContentEntityBase { class EnhancedEntity extends RevisionableContentEntityBase {
......
...@@ -43,6 +43,39 @@ class RevisionBasicUITest extends KernelTestBase { ...@@ -43,6 +43,39 @@ class RevisionBasicUITest extends KernelTestBase {
\Drupal::service('router.builder')->rebuild(); \Drupal::service('router.builder')->rebuild();
} }
public function testRevisionHistory() {
$entity = EnhancedEntity::create([
'name' => 'rev 1',
]);
$entity->save();
$revision = clone $entity;
$revision->name->value = 'rev 2';
$revision->setNewRevision(TRUE);
$revision->isDefaultRevision(FALSE);
$revision->save();
$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_enhanced revisions');
$role->grantPermission('administer entity_test_enhanced');
$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());
}
public function testRevisionView() { public function testRevisionView() {
$entity = EnhancedEntity::create([ $entity = EnhancedEntity::create([
'name' => 'rev 1', 'name' => 'rev 1',
......
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