summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel Catchpole2016-08-18 10:10:39 +0100
committerNathaniel Catchpole2016-08-18 10:15:13 +0100
commita2c42b9159d4ea074dccfe868f6698c903caf206 (patch)
treeaef47f7b4baf4c64f42c8e3b9d3347d29445310a
parentffa0f0dead6c2747659f0552e876611c29b7e07a (diff)
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
(cherry picked from commit 182f0316ed7c461ba1733d6de642681f3d520344)
-rw-r--r--core/modules/node/src/Controller/NodeController.php2
-rw-r--r--core/modules/node/src/Controller/NodeViewController.php73
-rw-r--r--core/modules/node/src/Tests/NodeCacheTagsTest.php10
-rw-r--r--core/modules/node/src/Tests/NodeViewTest.php35
-rw-r--r--core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php12
5 files changed, 121 insertions, 11 deletions
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php
index 7244bdf..7131083 100644
--- a/core/modules/node/src/Controller/NodeController.php
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -127,7 +127,7 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
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;
diff --git a/core/modules/node/src/Controller/NodeViewController.php b/core/modules/node/src/Controller/NodeViewController.php
index f80e65d..fce50e1 100644
--- a/core/modules/node/src/Controller/NodeViewController.php
+++ b/core/modules/node/src/Controller/NodeViewController.php
@@ -4,6 +4,10 @@ namespace Drupal\node\Controller;
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.
@@ -11,33 +15,84 @@ use Drupal\Core\Entity\Controller\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}
*/
public function view(EntityInterface $node, $view_mode = 'full', $langcode = NULL) {
$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;
}
diff --git a/core/modules/node/src/Tests/NodeCacheTagsTest.php b/core/modules/node/src/Tests/NodeCacheTagsTest.php
index 990f00b..66932cc 100644
--- a/core/modules/node/src/Tests/NodeCacheTagsTest.php
+++ b/core/modules/node/src/Tests/NodeCacheTagsTest.php
@@ -41,6 +41,16 @@ class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
+ protected function getDefaultCacheContexts() {
+ $defaults = parent::getDefaultCacheContexts();
+ // @see \Drupal\node\Controller\NodeViewController::view()
+ $defaults[] = 'user.roles:anonymous';
+ return $defaults;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return ['timezone'];
}
diff --git a/core/modules/node/src/Tests/NodeViewTest.php b/core/modules/node/src/Tests/NodeViewTest.php
index 5b2200c..fae056c 100644
--- a/core/modules/node/src/Tests/NodeViewTest.php
+++ b/core/modules/node/src/Tests/NodeViewTest.php
@@ -18,14 +18,49 @@ class NodeViewTest extends NodeTestBase {
$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'));
}
/**
diff --git a/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php
index 363fa92..ad4f59b 100644
--- a/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php
+++ b/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php
@@ -27,7 +27,7 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
$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();
@@ -141,4 +141,14 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
$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'];
+ }
+
}