diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php index 1cb43be2340e5d8462a20f62a9a1554a975089a8..588991205360eee3c3e9a1e2207508a6b0900b35 100644 --- a/core/modules/node/src/Controller/NodeController.php +++ b/core/modules/node/src/Controller/NodeController.php @@ -172,53 +172,66 @@ public function revisionOverview(NodeInterface $node) { $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')); + $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 = []; + $row[] = [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}{{ message }}
{% endif %}', + '#context' => [ + 'date' => $link, + 'username' => $this->renderer->render($username), + 'message' => Xss::filter($revision->revision_log->value), + ], + ], + ]; + + if ($vid == $node->getRevisionId()) { + $row[0]['class'] = ['revision-current']; + $row[] = [ + 'data' => SafeMarkup::placeholder($this->t('current revision')), + '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]), + ]; } - 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('entity.node.revision', 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, - ), - ); + + if ($delete_permission) { + $links['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]), + ]; } - $rows[] = $row; + $row[] = [ + 'data' => [ + '#type' => 'operations', + '#links' => $links, + ], + ]; } + + $rows[] = $row; } $build['node_revisions_table'] = array( diff --git a/core/modules/node/src/Tests/NodeRevisionsUiTest.php b/core/modules/node/src/Tests/NodeRevisionsUiTest.php index 6527984f863ab460dc2ad71dbe4fa2679e7447c3..4fdd543f44ed4a5487772d787faea88280c36024 100644 --- a/core/modules/node/src/Tests/NodeRevisionsUiTest.php +++ b/core/modules/node/src/Tests/NodeRevisionsUiTest.php @@ -7,6 +7,8 @@ namespace Drupal\node\Tests; +use Drupal\Core\Url; +use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; /** @@ -16,27 +18,31 @@ */ class NodeRevisionsUiTest extends NodeTestBase { + /** + * @var \Drupal\user\Entity\User + */ + protected $editor; + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - // Create and log in user. - $web_user = $this->drupalCreateUser( - array( - 'administer nodes', - 'edit any page content' - ) - ); - - $this->drupalLogin($web_user); + // Create users. + $this->editor = $this->drupalCreateUser([ + 'administer nodes', + 'edit any page content', + 'view page revisions', + 'access user profiles', + ]); } /** * Checks that unchecking 'Create new revision' works when editing a node. */ function testNodeFormSaveWithoutRevision() { + $this->drupalLogin($this->editor); $node_storage = $this->container->get('entity.manager')->getStorage('node'); // Set page revision setting 'create new revision'. This will mean new @@ -73,6 +79,60 @@ function testNodeFormSaveWithoutRevision() { $node_storage->resetCache(array($node->id())); $node_revision = $node_storage->load($node->id()); $this->assertNotEqual($node_revision->getRevisionId(), $node->getRevisionId(), "After an existing node is saved with 'Create new revision' checked, a new revision is created."); + } + + /** + * Checks HTML double escaping of revision logs. + */ + public function testNodeRevisionDoubleEscapeFix() { + $this->drupalLogin($this->editor); + $nodes = []; + + // Create the node. + $node = $this->drupalCreateNode(); + $username = [ + '#theme' => 'username', + '#account' => $this->editor, + ]; + $editor = \Drupal::service('renderer')->render($username); + + // Get original node. + $nodes[] = clone $node; + + // Create revision with a random title and body and update variables. + $node->title = $this->randomMachineName(); + $node->body = [ + 'value' => $this->randomMachineName(32), + 'format' => filter_default_format(), + ]; + $node->setNewRevision(); + $revision_log = 'Revision message with markup.'; + $node->revision_log->value = $revision_log; + $node->save(); + // Make sure we get revision information. + $node = Node::load($node->id()); + $nodes[] = clone $node; + + $this->drupalGet('node/' . $node->id() . '/revisions'); + + // Assert the old revision message. + $date = format_date($nodes[0]->revision_timestamp->value, 'short'); + $url = new Url('entity.node.revision', ['node' => $nodes[0]->id(), 'node_revision' => $nodes[0]->getRevisionId()]); + $old_revision_message = t('!date by !username', [ + '!date' => \Drupal::l($date, $url), + '!username' => $editor, + ]); + $this->assertRaw($old_revision_message); + + // Assert the current revision message. + $date = format_date($nodes[1]->revision_timestamp->value, 'short'); + $current_revision_message = t('!date by !username', [ + '!date' => $nodes[1]->link($date), + '!username' => $editor, + ]); + $current_revision_message .= '' . $revision_log . '
'; + $this->assertRaw($current_revision_message); } + }