diff --git a/core/lib/Drupal/Core/Revision/RevisionControllerTrait.php b/core/lib/Drupal/Core/Revision/RevisionControllerTrait.php new file mode 100644 index 0000000..edf59de --- /dev/null +++ b/core/lib/Drupal/Core/Revision/RevisionControllerTrait.php @@ -0,0 +1,192 @@ +entityManager()->getStorage($this->revisionEntityType)->loadRevision($revision_id); + $view_controller = new $this->viewControllerClass($this->entityManager, $this->renderer); + $page = $view_controller->view($entity); + if (isset($page[$this->revisionEntityType . 's'][$entity->id()]['#cache'])) { + unset($page[$this->revisionEntityType . '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->revisionEntityType)->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 getRevisionDetails(EntityInterface $revision, $is_current = FALSE); + + /** + * 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->revisionEntityType); + + $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->revisionEntityType . '_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..4b7fe23 --- /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->revisionTraitShow($node_revision); } /** @@ -142,8 +158,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); } /** @@ -156,79 +173,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) { - if ($revision = $node_storage->loadRevision($vid)) { - $row = array(); - - $revision_author = $revision->uid->entity; - - if ($vid == $node->getRevisionId()) { - $username = array( - '#theme' => 'username', - '#account' => $revision_author, - ); - $row[] = array('data' => $this->t('!date by !username', array('!date' => $node->link($this->dateFormatter->format($revision->revision_timestamp->value, 'short')), '!username' => drupal_render($username))) - . (($revision->revision_log->value != '') ? '

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

' : ''), - 'class' => array('revision-current')); - $row[] = array('data' => SafeMarkup::placeholder($this->t('current revision')), 'class' => array('revision-current')); - } - else { - $username = array( - '#theme' => 'username', - '#account' => $revision_author, - ); - $row[] = $this->t('!date by !username', array('!date' => $this->l($this->dateFormatter->format($revision->revision_timestamp->value, 'short'), new Url('node.revision_show', array('node' => $node->id(), 'node_revision' => $vid))), '!username' => drupal_render($username))) - . (($revision->revision_log->value != '') ? '

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

' : ''); - - if ($revert_permission) { - $links['revert'] = array( - 'title' => $this->t('Revert'), - 'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]), - ); - } - - if ($delete_permission) { - $links['delete'] = array( - 'title' => $this->t('Delete'), - 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]), - ); - } - - $row[] = array( - 'data' => array( - '#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; } @@ -246,4 +194,73 @@ 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 getRevisionDetails(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) . '

' : ''); + + } + } + } diff --git a/core/modules/node/src/NodeInterface.php b/core/modules/node/src/NodeInterface.php index 4ca91c7..c1f1928 100644 --- a/core/modules/node/src/NodeInterface.php +++ b/core/modules/node/src/NodeInterface.php @@ -7,6 +7,7 @@ namespace Drupal\node; +use Drupal\Core\Revision\TimestampedRevisionInterface; use Drupal\user\EntityOwnerInterface; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Entity\ContentEntityInterface; @@ -15,7 +16,7 @@ /** * Provides an interface defining a node entity. */ -interface NodeInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface { +interface NodeInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface, TimestampedRevisionInterface { /** * Returns the node type. @@ -123,25 +124,6 @@ public function isPublished(); public function setPublished($published); /** - * Returns 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); - - /** * Returns the node revision author. * * @return \Drupal\user\UserInterface