class VersionHistoryController

Same name in other branches
  1. 11.x core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php \Drupal\Core\Entity\Controller\VersionHistoryController

Provides a controller showing revision history for an entity.

This controller is agnostic to any entity type by using \Drupal\Core\Entity\RevisionLogInterface.

Hierarchy

Expanded class hierarchy of VersionHistoryController

2 files declare their use of VersionHistoryController
RevisionHtmlRouteProvider.php in core/lib/Drupal/Core/Entity/Routing/RevisionHtmlRouteProvider.php
RevisionVersionHistoryTest.php in core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTest.php

File

core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php, line 26

Namespace

Drupal\Core\Entity\Controller
View source
class VersionHistoryController extends ControllerBase {
    const REVISIONS_PER_PAGE = 50;
    
    /**
     * Constructs a new VersionHistoryController.
     *
     * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
     *   The entity type manager.
     * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
     *   The language manager.
     * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
     *   The date formatter service.
     * @param \Drupal\Core\Render\RendererInterface $renderer
     *   The renderer.
     */
    public function __construct(EntityTypeManagerInterface $entityTypeManager, LanguageManagerInterface $languageManager, DateFormatterInterface $dateFormatter, RendererInterface $renderer) {
        $this->entityTypeManager = $entityTypeManager;
        $this->languageManager = $languageManager;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container) {
        return new static($container->get('entity_type.manager'), $container->get('language_manager'), $container->get('date.formatter'), $container->get('renderer'));
    }
    
    /**
     * Generates an overview table of revisions for an entity.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
     *   The route match.
     *
     * @return array
     *   A render array.
     */
    public function __invoke(RouteMatchInterface $routeMatch) : array {
        $entityTypeId = $routeMatch->getRouteObject()
            ->getOption('entity_type_id');
        $entity = $routeMatch->getParameter($entityTypeId);
        return $this->revisionOverview($entity);
    }
    
    /**
     * Builds a link to revert an entity revision.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $revision
     *   The entity to build a revert revision link for.
     *
     * @return array|null
     *   A link to revert an entity revision, or NULL if the entity type does not
     *   have an a route to revert an entity revision.
     */
    protected function buildRevertRevisionLink(RevisionableInterface $revision) : ?array {
        if (!$revision->hasLinkTemplate('revision-revert-form')) {
            return NULL;
        }
        $url = $revision->toUrl('revision-revert-form');
        // @todo Merge in cacheability after
        // https://www.drupal.org/project/drupal/issues/2473873.
        if (!$url->access()) {
            return NULL;
        }
        return [
            'title' => $this->t('Revert'),
            'url' => $url,
        ];
    }
    
    /**
     * Builds a link to delete an entity revision.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $revision
     *   The entity to build a delete revision link for.
     *
     * @return array|null
     *   A link render array.
     */
    protected function buildDeleteRevisionLink(RevisionableInterface $revision) : ?array {
        if (!$revision->hasLinkTemplate('revision-delete-form')) {
            return NULL;
        }
        $url = $revision->toUrl('revision-delete-form');
        // @todo Merge in cacheability after
        // https://www.drupal.org/project/drupal/issues/2473873.
        if (!$url->access()) {
            return NULL;
        }
        return [
            'title' => $this->t('Delete'),
            'url' => $url,
        ];
    }
    
    /**
     * Get a description of the revision.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $revision
     *   The entity revision.
     *
     * @return array
     *   A render array describing the revision.
     */
    protected function getRevisionDescription(RevisionableInterface $revision) : array {
        $context = [];
        if ($revision instanceof RevisionLogInterface) {
            // Use revision link to link to revisions that are not active.
            [
                'type' => $dateFormatType,
                'format' => $dateFormatFormat,
            ] = $this->getRevisionDescriptionDateFormat($revision);
            $linkText = $this->dateFormatter
                ->format($revision->getRevisionCreationTime(), $dateFormatType, $dateFormatFormat);
            // @todo Simplify this when https://www.drupal.org/node/2334319 lands.
            $username = [
                '#theme' => 'username',
                '#account' => $revision->getRevisionUser(),
            ];
            $context['username'] = $this->renderer
                ->render($username);
        }
        else {
            $linkText = $revision->access('view label') ? $revision->label() : $this->t('- Restricted access -');
        }
        $url = $revision->hasLinkTemplate('revision') ? $revision->toUrl('revision') : NULL;
        $context['revision'] = $url && $url->access() ? Link::fromTextAndUrl($linkText, $url)->toString() : (string) $linkText;
        $context['message'] = $revision instanceof RevisionLogInterface ? [
            '#markup' => $revision->getRevisionLogMessage(),
            '#allowed_tags' => Xss::getHtmlTagList(),
        ] : '';
        return [
            'data' => [
                '#type' => 'inline_template',
                '#template' => isset($context['username']) ? '{% trans %} {{ revision }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}' : '{% trans %} {{ revision }} {% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}',
                '#context' => $context,
            ],
        ];
    }
    
    /**
     * Date format to use for revision description dates.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $revision
     *   The revision in context.
     *
     * @return array
     *   An array with keys 'type' and optionally 'format' suitable for passing
     *   to date formatter service.
     */
    protected function getRevisionDescriptionDateFormat(RevisionableInterface $revision) : array {
        return [
            'type' => 'short',
            'format' => '',
        ];
    }
    
    /**
     * Generates revisions of an entity relevant to the current language.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $entity
     *   The entity.
     *
     * @return \Generator|\Drupal\Core\Entity\RevisionableInterface
     *   Generates revisions.
     */
    protected function loadRevisions(RevisionableInterface $entity) {
        $entityType = $entity->getEntityType();
        $translatable = $entityType->isTranslatable();
        $entityStorage = $this->entityTypeManager
            ->getStorage($entity->getEntityTypeId());
        assert($entityStorage instanceof RevisionableStorageInterface);
        $result = $entityStorage->getQuery()
            ->accessCheck(FALSE)
            ->allRevisions()
            ->condition($entityType->getKey('id'), $entity->id())
            ->sort($entityType->getKey('revision'), 'DESC')
            ->pager(self::REVISIONS_PER_PAGE)
            ->execute();
        $currentLangcode = $this->languageManager
            ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
            ->getId();
        foreach ($entityStorage->loadMultipleRevisions(array_keys($result)) as $revision) {
            // Only show revisions that are affected by the language that is being
            // displayed.
            if (!$translatable || $revision->hasTranslation($currentLangcode) && $revision->getTranslation($currentLangcode)
                ->isRevisionTranslationAffected()) {
                (yield $translatable ? $revision->getTranslation($currentLangcode) : $revision);
            }
        }
    }
    
    /**
     * Generates an overview table of revisions of an entity.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $entity
     *   A revisionable entity.
     *
     * @return array
     *   A render array.
     */
    protected function revisionOverview(RevisionableInterface $entity) : array {
        $build['entity_revisions_table'] = [
            '#theme' => 'table',
            '#header' => [
                'revision' => [
                    'data' => $this->t('Revision'),
                ],
                'operations' => [
                    'data' => $this->t('Operations'),
                ],
            ],
        ];
        foreach ($this->loadRevisions($entity) as $revision) {
            $build['entity_revisions_table']['#rows'][$revision->getRevisionId()] = $this->buildRow($revision);
        }
        $build['pager'] = [
            '#type' => 'pager',
        ];
        (new CacheableMetadata())->addCacheableDependency($entity)
            ->addCacheContexts([
            'languages:language_content',
        ])
            ->applyTo($build);
        return $build;
    }
    
    /**
     * Builds a table row for a revision.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $revision
     *   An entity revision.
     *
     * @return array
     *   A table row.
     */
    protected function buildRow(RevisionableInterface $revision) : array {
        $row = [];
        $rowAttributes = [];
        $row['revision']['data'] = $this->getRevisionDescription($revision);
        $row['operations']['data'] = [];
        // Revision status.
        if ($revision->isDefaultRevision()) {
            $rowAttributes['class'][] = 'revision-current';
            $row['operations']['data']['status']['#markup'] = $this->t('<em>Current revision</em>');
        }
        // Operation links.
        $links = $this->getOperationLinks($revision);
        if (count($links) > 0) {
            $row['operations']['data']['operations'] = [
                '#type' => 'operations',
                '#links' => $links,
            ];
        }
        return [
            'data' => $row,
        ] + $rowAttributes;
    }
    
    /**
     * Get operations for an entity revision.
     *
     * @param \Drupal\Core\Entity\RevisionableInterface $revision
     *   The entity to build revision links for.
     *
     * @return array
     *   An array of operation links.
     */
    protected function getOperationLinks(RevisionableInterface $revision) : array {
        // Removes links which are inaccessible or not rendered.
        return array_filter([
            $this->buildRevertRevisionLink($revision),
            $this->buildDeleteRevisionLink($revision),
        ]);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 2
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 1
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 1
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 2
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 1
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 1
ControllerBase::redirect protected function Returns a redirect response object for the specified route.
ControllerBase::state protected function Returns the state storage service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 16
MessengerTrait::messenger public function Gets the messenger. 16
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 2
RedirectDestinationTrait::getDestinationArray protected function Prepares a &#039;destination&#039; URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
VersionHistoryController::buildDeleteRevisionLink protected function Builds a link to delete an entity revision.
VersionHistoryController::buildRevertRevisionLink protected function Builds a link to revert an entity revision.
VersionHistoryController::buildRow protected function Builds a table row for a revision.
VersionHistoryController::create public static function Instantiates a new instance of the implementing class using autowiring. Overrides AutowireTrait::create
VersionHistoryController::getOperationLinks protected function Get operations for an entity revision.
VersionHistoryController::getRevisionDescription protected function Get a description of the revision.
VersionHistoryController::getRevisionDescriptionDateFormat protected function Date format to use for revision description dates.
VersionHistoryController::loadRevisions protected function Generates revisions of an entity relevant to the current language.
VersionHistoryController::revisionOverview protected function Generates an overview table of revisions of an entity.
VersionHistoryController::REVISIONS_PER_PAGE constant
VersionHistoryController::__construct public function Constructs a new VersionHistoryController.
VersionHistoryController::__invoke public function Generates an overview table of revisions for an entity.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.