diff --git a/core/lib/Drupal/Core/Revision/RevisionControllerTrait.php b/core/lib/Drupal/Core/Revision/RevisionControllerTrait.php new file mode 100644 index 0000000..ac8b012 --- /dev/null +++ b/core/lib/Drupal/Core/Revision/RevisionControllerTrait.php @@ -0,0 +1,210 @@ +entityManager()->getStorage($this->getRevisionEntityTypeId())->loadRevision($revision_id); + $view_controller = $this->getEntityViewBuilder($this->entityManager, $this->renderer); + $page = $view_controller->view($entity); + unset($page[$this->getRevisionEntityTypeId() . 's'][$entity->id()]['#cache']); + return $page; + } + + /** + * Page title callback for an entity revision. + * + * @param int $revision_id + * The entity revision ID. + * + * @return string + * The page title. + */ + public function revisionPageTitle($revision_id) { + $entity = $this->entityManager()->getStorage($this->getRevisionEntityTypeId())->loadRevision($revision_id); + if ($entity instanceof TimestampedRevisionInterface) { + return $this->t('Revision of %title from %date', array( + '%title' => $entity->label(), + '%date' => format_date($entity->getRevisionCreationTime()) + )); + } + else { + return $this->t('Revision of %title', array( + '%title' => $entity->label(), + )); + } + } + + /** + * 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 hasRevertRevisionPermission(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 hasDeleteRevisionPermission(EntityInterface $entity); + + /** + * Builds a link to revert an entity revision. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to build a revert revision link for. + * @param int $revision_id + * The revision ID of the revert link. + * + * @return array + * A link render array + */ + abstract protected function buildRevertRevisionLink(EntityInterface $entity, $revision_id); + + /** + * Builds a link to delete an entity revision. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to build a delete revision link for. + * @param int $revision_id + * The revision ID of the delete link. + * + * @return array + * A link render array + */ + abstract protected function buildDeleteRevisionLink(EntityInterface $entity, $revision_id); + + /** + * 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\EntityInterface $revision + * Returns a string to provide the details of the revision. + * @param bool $is_current + * TRUE if the revision is the current revision. + * + * @return string + * Revision description. + */ + abstract protected function getRevisionDescription(EntityInterface $revision, $is_current = FALSE); + + /** + * Gets the entity type ID for this revision controller. + * + * @return string + * Entity Type ID for this revision controller. + */ + abstract protected function getRevisionEntityTypeId(); + + /** + * Gets the entity's view controller. + * + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * Entity manager. + * @param \Drupal\Core\Render\RendererInterface $renderer + * Renderer service. + * + * @return \Drupal\Core\Entity\EntityViewBuilderInterface + * A new entity view builder. + */ + abstract protected function getEntityViewBuilder(EntityManagerInterface $entity_manager, RendererInterface $renderer); + + /** + * 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) { + $entity_storage = $this->entityManager()->getStorage($this->getRevisionEntityTypeId()); + + $build = array(); + $build['#title'] = $this->t('Revisions for %title', array('%title' => $entity->label())); + $header = array($this->t('Revision'), $this->t('Operations')); + + $revert_permission = $this->hasRevertRevisionPermission($entity); + $delete_permission = $this->hasDeleteRevisionPermission($entity); + + $rows = array(); + + $vids = $entity_storage->revisionIds($entity); + + foreach (array_reverse($vids) as $vid) { + if ($revision = $entity_storage->loadRevision($vid)) { + $row = array(); + + if ($vid == $entity->getRevisionId()) { + $row[] = $this->getRevisionDetails($revision, TRUE); + $row[] = array('data' => SafeMarkup::placeholder($this->t('current revision')), 'class' => array('revision-current')); + } + else { + $row[] = $this->getRevisionDetails($revision, FALSE); + $links = []; + if ($revert_permission) { + $links['revert'] = $this->buildRevertRevisionLink($entity, $vid); + } + if ($delete_permission) { + $links['delete'] = $this->buildDeleteRevisionLink($entity, $vid); + } + $row[] = array( + 'data' => array( + '#type' => 'operations', + '#links' => $links, + ), + ); + } + + $rows[] = $row; + } + } + + $build[$this->getRevisionEntityTypeId() . '_revisions_table'] = array( + '#theme' => 'table', + '#rows' => $rows, + '#header' => $header, + + ); + + return $build; + } +} diff --git a/core/lib/Drupal/Core/Revision/TimestampedRevisionInterface.php b/core/lib/Drupal/Core/Revision/TimestampedRevisionInterface.php new file mode 100644 index 0000000..d4fa536 --- /dev/null +++ b/core/lib/Drupal/Core/Revision/TimestampedRevisionInterface.php @@ -0,0 +1,33 @@ +entityManager()->getStorage('node')->loadRevision($node_revision); - $node_view_controller = new NodeViewController($this->entityManager, $this->renderer); - $page = $node_view_controller->view($node); - unset($page['nodes'][$node->id()]['#cache']); - return $page; + // We call to the parent so we can retain the {node_revision} entry in our + // routing definition. + return $this->showRevisionTrait($node_revision); } /** @@ -138,8 +145,9 @@ public function revisionShow($node_revision) { * The page title. */ public function revisionPageTitle($node_revision) { - $node = $this->entityManager()->getStorage('node')->loadRevision($node_revision); - return $this->t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->getRevisionCreationTime()))); + // We call to the parent so we can retain the {node_revision} entry in our + // routing definition. + return $this->revisionTraitTitle($node_revision); } /** @@ -152,99 +160,10 @@ public function revisionPageTitle($node_revision) { * An array as expected by drupal_render(). */ public function revisionOverview(NodeInterface $node) { - $account = $this->currentUser(); - $node_storage = $this->entityManager()->getStorage('node'); - $type = $node->getType(); - - $build = array(); - $build['#title'] = $this->t('Revisions for %title', array('%title' => $node->label())); - $header = array($this->t('Revision'), $this->t('Operations')); - - $revert_permission = (($account->hasPermission("revert $type revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update')); - $delete_permission = (($account->hasPermission("delete $type revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete')); - - $rows = array(); - - $vids = $node_storage->revisionIds($node); - - foreach (array_reverse($vids) as $vid) { - $revision = $node_storage->loadRevision($vid); - $username = [ - '#theme' => 'username', - '#account' => $revision->uid->entity, - ]; - - // Use revision link to link to revisions that are not active. - $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short'); - if ($vid != $node->getRevisionId()) { - $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); - } - else { - $link = $node->link($date); - } - - $row = []; - $column = [ - 'data' => [ - '#type' => 'inline_template', - '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}

{{ message }}

{% endif %}', - '#context' => [ - 'date' => $link, - 'username' => $this->renderer->renderPlain($username), - 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()], - ], - ], - ]; - // @todo Simplify once https://www.drupal.org/node/2334319 lands. - $this->renderer->addCacheableDependency($column['data'], $username); - $row[] = $column; - - if ($vid == $node->getRevisionId()) { - $row[0]['class'] = ['revision-current']; - $row[] = [ - 'data' => [ - '#prefix' => '', - '#markup' => $this->t('current revision'), - '#suffix' => '', - ], - 'class' => ['revision-current'], - ]; - } - else { - $links = []; - if ($revert_permission) { - $links['revert'] = [ - 'title' => $this->t('Revert'), - 'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]), - ]; - } - - if ($delete_permission) { - $links['delete'] = [ - 'title' => $this->t('Delete'), - 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]), - ]; - } - - $row[] = [ - 'data' => [ - '#type' => 'operations', - '#links' => $links, - ], - ]; - } - - $rows[] = $row; - } - - $build['node_revisions_table'] = array( - '#theme' => 'table', - '#rows' => $rows, - '#header' => $header, - '#attached' => array( - 'library' => array('node/drupal.node.admin'), - ), - ); + $build = $this->revisionTraitOverview($node); + $build['#attached'] = [ + 'library' => ['node/drupal.node.admin'], + ]; return $build; } @@ -262,4 +181,88 @@ public function addPageTitle(NodeTypeInterface $node_type) { return $this->t('Create @name', array('@name' => $node_type->label())); } + /** + * {@inheritdoc} + */ + protected function hasRevertRevisionPermission(EntityInterface $entity) { + $account = $this->currentUser(); + $bundle = $entity->bundle(); + return (($account->hasPermission("revert $bundle revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $entity->access('update')); + } + + /** + * {@inheritdoc} + */ + protected function hasDeleteRevisionPermission(EntityInterface $entity) { + $account = $this->currentUser(); + $bundle = $entity->bundle(); + return (($account->hasPermission("delete $bundle revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $entity->access('delete')); + } + + /** + * {@inheritdoc} + */ + protected function buildRevertRevisionLink(EntityInterface $entity, $revision_id) { + return [ + 'title' => $this->t('Revert'), + 'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $entity->id(), 'node_revision' => $revision_id]), + ]; + } + + /** + * {@inheritdoc} + */ + protected function buildDeleteRevisionLink(EntityInterface $entity, $revision_id) { + return [ + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $entity->id(), 'node_revision' => $revision_id]), + ]; + } + + /** + * {@inheritdoc} + */ + protected function getRevisionDescription(EntityInterface $revision, $is_current = FALSE) { + $revision_author = $revision->uid->entity; + $username = array( + '#theme' => 'username', + '#account' => $revision_author, + ); + if ($is_current) { + return [ + 'data' => $this->t('!date by !username', [ + '!date' => $revision->link($this->dateFormatter->format($revision->revision_timestamp->value, 'short')), + '!username' => $this->renderer->render($username) + ]) + . (($revision->revision_log->value != '') ? '

' . Xss::filter($revision->revision_log->value) . '

' : ''), + 'class' => ['revision-current'], + ]; + } + else { + return $this->t('!date by !username', [ + '!date' => $this->l($this->dateFormatter->format($revision->revision_timestamp->value, 'short'), new Url('node.revision_show', [ + 'node' => $revision->id(), + 'node_revision' => $revision->getRevisionId(), + ])), + '!username' => $this->renderer->render($username), + ]) + . (($revision->revision_log->value != '') ? '

' . Xss::filter($revision->revision_log->value) . '

' : ''); + + } + } + + /** + * {@inheritdoc} + */ + protected function getRevisionEntityTypeId() { + return 'node'; + } + + /** + * {@inheritdoc} + */ + protected function getEntityViewBuilder(EntityManagerInterface $entity_manager, RendererInterface $renderer) { + return new NodeViewController($entity_manager, $renderer); + } + } diff --git a/core/modules/node/src/NodeInterface.php b/core/modules/node/src/NodeInterface.php index 9610a99..3dd361a 100644 --- a/core/modules/node/src/NodeInterface.php +++ b/core/modules/node/src/NodeInterface.php @@ -7,15 +7,15 @@ namespace Drupal\node; +use Drupal\Core\Revision\TimestampedRevisionInterface; use Drupal\user\EntityOwnerInterface; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\user\UserInterface; /** * Provides an interface defining a node entity. */ -interface NodeInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface { +interface NodeInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface, TimestampedRevisionInterface { /** * Gets the node type. @@ -123,25 +123,6 @@ public function isPublished(); public function setPublished($published); /** - * Gets the node revision creation timestamp. - * - * @return int - * The UNIX timestamp of when this revision was created. - */ - public function getRevisionCreationTime(); - - /** - * Sets the node revision creation timestamp. - * - * @param int $timestamp - * The UNIX timestamp of when this revision was created. - * - * @return \Drupal\node\NodeInterface - * The called node entity. - */ - public function setRevisionCreationTime($timestamp); - - /** * Gets the node revision author. * * @return \Drupal\user\UserInterface