Commit 182f0316 authored by catch's avatar catch

Issue #2406533 by alexpott, willzyx, olli, Berdir, DamienMcKenna, Dom., Crell,...

Issue #2406533 by alexpott, willzyx, olli, Berdir, DamienMcKenna, Dom., Crell, Wim Leers, mdrummond, mirzu, enjoyiacm, catch: edit-form, delete-form etc. <link> tags added on /node/{node} are invalid according to W3C Validator
parent 0a8686ac
...@@ -127,7 +127,7 @@ public function add(NodeTypeInterface $node_type) { ...@@ -127,7 +127,7 @@ public function add(NodeTypeInterface $node_type) {
public function revisionShow($node_revision) { public function revisionShow($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision); $node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
$node = $this->entityManager()->getTranslationFromContext($node); $node = $this->entityManager()->getTranslationFromContext($node);
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer); $node_view_controller = new NodeViewController($this->entityManager, $this->renderer, $this->currentUser());
$page = $node_view_controller->view($node); $page = $node_view_controller->view($node);
unset($page['nodes'][$node->id()]['#cache']); unset($page['nodes'][$node->id()]['#cache']);
return $page; return $page;
......
...@@ -4,12 +4,50 @@ ...@@ -4,12 +4,50 @@
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController; use Drupal\Core\Entity\Controller\EntityViewController;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Defines a controller to render a single node. * Defines a controller to render a single node.
*/ */
class NodeViewController extends EntityViewController { class NodeViewController extends EntityViewController {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Creates an NodeViewController object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user. For backwards compatibility this is optional, however
* this will be removed before Drupal 9.0.0.
*/
public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer, AccountInterface $current_user = NULL) {
parent::__construct($entity_manager, $renderer);
$this->currentUser = $current_user ?: \Drupal::currentUser();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('renderer'),
$container->get('current_user')
);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -17,27 +55,44 @@ public function view(EntityInterface $node, $view_mode = 'full', $langcode = NUL ...@@ -17,27 +55,44 @@ public function view(EntityInterface $node, $view_mode = 'full', $langcode = NUL
$build = parent::view($node, $view_mode, $langcode); $build = parent::view($node, $view_mode, $langcode);
foreach ($node->uriRelationships() as $rel) { foreach ($node->uriRelationships() as $rel) {
// Set the node path as the canonical URL to prevent duplicate content. $url = $node->toUrl($rel);
$build['#attached']['html_head_link'][] = array( // Add link relationships if the user is authenticated or if the anonymous
array( // user has access. Access checking must be done for anonymous users to
'rel' => $rel, // avoid traffic to inaccessible pages from web crawlers. For
'href' => $node->url($rel), // authenticated users, showing the links in HTML head does not impact
), // user experience or security, since the routes are access checked when
TRUE, // visited and only visible via view source. This prevents doing
); // potentially expensive and hard to cache access checks on every request.
// This means that the page will vary by user.permissions. We also rely on
// the access checking fallback to ensure the correct cacheability
// metadata if we have to check access.
if ($this->currentUser->isAuthenticated() || $url->access($this->currentUser)) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $url->toString(),
),
TRUE,
);
}
if ($rel == 'canonical') { if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink. // Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array( $build['#attached']['html_head_link'][] = array(
array( array(
'rel' => 'shortlink', 'rel' => 'shortlink',
'href' => $node->url($rel, array('alias' => TRUE)), 'href' => $url->setOption('alias', TRUE)->toString(),
), ),
TRUE, TRUE,
); );
} }
} }
// Given this varies by $this->currentUser->isAuthenticated(), add a cache
// context based on the anonymous role.
$build['#cache']['contexts'][] = 'user.roles:anonymous';
return $build; return $build;
} }
......
...@@ -38,6 +38,16 @@ protected function createEntity() { ...@@ -38,6 +38,16 @@ protected function createEntity() {
return $node; return $node;
} }
/**
* {@inheritdoc}
*/
protected function getDefaultCacheContexts() {
$defaults = parent::getDefaultCacheContexts();
// @see \Drupal\node\Controller\NodeViewController::view()
$defaults[] = 'user.roles:anonymous';
return $defaults;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -18,14 +18,49 @@ public function testHtmlHeadLinks() { ...@@ -18,14 +18,49 @@ public function testHtmlHeadLinks() {
$this->drupalGet($node->urlInfo()); $this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
// Link relations are checked for access for anonymous users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertFalse($result, 'Edit form not present for anonymous users without access.');
$this->drupalLogin($this->createUser(['access content']));
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
// Link relations are present regardless of access for authenticated users.
$result = $this->xpath('//link[@rel = "version-history"]'); $result = $this->xpath('//link[@rel = "version-history"]');
$this->assertEqual($result[0]['href'], $node->url('version-history')); $this->assertEqual($result[0]['href'], $node->url('version-history'));
$result = $this->xpath('//link[@rel = "edit-form"]'); $result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form')); $this->assertEqual($result[0]['href'], $node->url('edit-form'));
// Give anonymous users access to edit the node. Do this through the UI to
// ensure caches are handled properly.
$this->drupalLogin($this->rootUser);
$edit = [
'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE
];
$this->drupalPostForm('admin/people/permissions', $edit, 'Save permissions');
$this->drupalLogout();
// Anonymous user's should now see the edit-form link but not the
// version-history link.
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]'); $result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url()); $this->assertEqual($result[0]['href'], $node->url());
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form'));
} }
/** /**
......
...@@ -27,7 +27,7 @@ public function testEntityUri() { ...@@ -27,7 +27,7 @@ public function testEntityUri() {
$view_mode = $this->selectViewMode($entity_type); $view_mode = $this->selectViewMode($entity_type);
// The default cache contexts for rendered entities. // The default cache contexts for rendered entities.
$entity_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']; $entity_cache_contexts = $this->getDefaultCacheContexts();
// Generate the standardized entity cache tags. // Generate the standardized entity cache tags.
$cache_tag = $this->entity->getCacheTags(); $cache_tag = $this->entity->getCacheTags();
...@@ -143,4 +143,14 @@ public function testEntityUri() { ...@@ -143,4 +143,14 @@ public function testEntityUri() {
$this->assertResponse(404); $this->assertResponse(404);
} }
/**
* Gets the default cache contexts for rendered entities.
*
* @return array
* The default cache contexts for rendered entities.
*/
protected function getDefaultCacheContexts() {
return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment