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) {
public function revisionShow($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
$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);
unset($page['nodes'][$node->id()]['#cache']);
return $page;
......
......@@ -4,12 +4,50 @@
use Drupal\Core\Entity\EntityInterface;
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.
*/
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}
*/
......@@ -17,27 +55,44 @@ public function view(EntityInterface $node, $view_mode = 'full', $langcode = NUL
$build = parent::view($node, $view_mode, $langcode);
foreach ($node->uriRelationships() as $rel) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $node->url($rel),
),
TRUE,
);
$url = $node->toUrl($rel);
// Add link relationships if the user is authenticated or if the anonymous
// user has access. Access checking must be done for anonymous users to
// avoid traffic to inaccessible pages from web crawlers. For
// authenticated users, showing the links in HTML head does not impact
// user experience or security, since the routes are access checked when
// 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') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => $node->url($rel, array('alias' => TRUE)),
'href' => $url->setOption('alias', TRUE)->toString(),
),
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;
}
......
......@@ -38,6 +38,16 @@ protected function createEntity() {
return $node;
}
/**
* {@inheritdoc}
*/
protected function getDefaultCacheContexts() {
$defaults = parent::getDefaultCacheContexts();
// @see \Drupal\node\Controller\NodeViewController::view()
$defaults[] = 'user.roles:anonymous';
return $defaults;
}
/**
* {@inheritdoc}
*/
......
......@@ -18,14 +18,49 @@ public function testHtmlHeadLinks() {
$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"]');
$this->assertEqual($result[0]['href'], $node->url('version-history'));
$result = $this->xpath('//link[@rel = "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"]');
$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() {
$view_mode = $this->selectViewMode($entity_type);
// 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.
$cache_tag = $this->entity->getCacheTags();
......@@ -143,4 +143,14 @@ public function testEntityUri() {
$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