From ae262e1192e14be8b4677500154899c6169ac551 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Mon, 19 Feb 2018 12:21:13 +0000
Subject: [PATCH] Issue #2899392 by idebr, neclimdul, rgpublic:
 user_hook_toolbar() makes all pages uncacheable

---
 .../Functional/ToolbarCacheContextsTest.php   | 12 +++
 core/modules/user/src/ToolbarLinkBuilder.php  | 86 +++++++++++++++++++
 core/modules/user/user.module                 | 80 ++++++-----------
 core/modules/user/user.services.yml           |  3 +
 4 files changed, 129 insertions(+), 52 deletions(-)
 create mode 100644 core/modules/user/src/ToolbarLinkBuilder.php

diff --git a/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php b/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php
index 26454762ea28..023254547321 100644
--- a/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php
+++ b/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php
@@ -58,6 +58,18 @@ protected function setUp() {
     $this->adminUser2 = $this->drupalCreateUser($this->perms);
   }
 
+  /**
+   * Tests toolbar cache integration.
+   */
+  public function testCacheIntegration() {
+    $this->installExtraModules(['dynamic_page_cache']);
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('test-page');
+    $this->assertSame('MISS', $this->getSession()->getResponseHeader('X-Drupal-Dynamic-Cache'));
+    $this->drupalGet('test-page');
+    $this->assertSame('HIT', $this->getSession()->getResponseHeader('X-Drupal-Dynamic-Cache'));
+  }
+
   /**
    * Tests toolbar cache contexts.
    */
diff --git a/core/modules/user/src/ToolbarLinkBuilder.php b/core/modules/user/src/ToolbarLinkBuilder.php
new file mode 100644
index 000000000000..4f7dedd53c4f
--- /dev/null
+++ b/core/modules/user/src/ToolbarLinkBuilder.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\user;
+
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+
+/**
+ * ToolbarLinkBuilder fills out the placeholders generated in user_toolbar().
+ */
+class ToolbarLinkBuilder {
+
+  use StringTranslationTrait;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $account;
+
+  /**
+   * ToolbarHandler constructor.
+   *
+   * @param \Drupal\Core\Session\AccountProxyInterface $account
+   *   The current user.
+   */
+  public function __construct(AccountProxyInterface $account) {
+    $this->account = $account;
+  }
+
+  /**
+   * Lazy builder callback for rendering toolbar links.
+   *
+   * @return array
+   *   A renderable array as expected by the renderer service.
+   */
+  public function renderToolbarLinks() {
+    $links = [
+      'account' => [
+        'title' => $this->t('View profile'),
+        'url' => Url::fromRoute('user.page'),
+        'attributes' => [
+          'title' => $this->t('User account'),
+        ],
+      ],
+      'account_edit' => [
+        'title' => $this->t('Edit profile'),
+        'url' => Url::fromRoute('entity.user.edit_form', ['user' => $this->account->id()]),
+        'attributes' => [
+          'title' => $this->t('Edit user account'),
+        ],
+      ],
+      'logout' => [
+        'title' => $this->t('Log out'),
+        'url' => Url::fromRoute('user.logout'),
+      ],
+    ];
+    $build = [
+      '#theme' => 'links__toolbar_user',
+      '#links' => $links,
+      '#attributes' => [
+        'class' => ['toolbar-menu'],
+      ],
+      '#cache' => [
+        'contexts' => ['user'],
+      ],
+    ];
+
+    return $build;
+  }
+
+  /**
+   * Lazy builder callback for rendering the username.
+   *
+   * @return array
+   *   A renderable array as expected by the renderer service.
+   */
+  public function renderDisplayName() {
+    return [
+      '#markup' => $this->account->getDisplayName(),
+    ];
+  }
+
+}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 2d7d24a00638..cabe189e5a74 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -9,7 +9,6 @@
 use Drupal\Component\Render\PlainTextOutput;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Asset\AttachedAssetsInterface;
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Render\Element;
@@ -1328,41 +1327,6 @@ function user_cookie_delete($cookie_name) {
 function user_toolbar() {
   $user = \Drupal::currentUser();
 
-  // Add logout & user account links or login link.
-  $links_cache_contexts = [];
-  if ($user->isAuthenticated()) {
-    $links = [
-      'account' => [
-        'title' => t('View profile'),
-        'url' => Url::fromRoute('user.page'),
-        'attributes' => [
-          'title' => t('User account'),
-        ],
-      ],
-      'account_edit' => [
-        'title' => t('Edit profile'),
-        'url' => Url::fromRoute('entity.user.edit_form', ['user' => $user->id()]),
-        'attributes' => [
-          'title' => t('Edit user account'),
-        ],
-      ],
-      'logout' => [
-        'title' => t('Log out'),
-        'url' => Url::fromRoute('user.logout'),
-      ],
-    ];
-    // The "Edit user account" link is per-user.
-    $links_cache_contexts[] = 'user';
-  }
-  else {
-    $links = [
-      'login' => [
-        'title' => t('Log in'),
-        'url' => Url::fromRoute('user.page'),
-      ],
-    ];
-  }
-
   $items['user'] = [
     '#type' => 'toolbar_item',
     'tab' => [
@@ -1374,26 +1338,12 @@ function user_toolbar() {
         'class' => ['toolbar-icon', 'toolbar-icon-user'],
       ],
       '#cache' => [
-        'contexts' => [
-          // Cacheable per user, because the current user's name is shown.
-          'user',
-        ],
+        // Vary cache for anonymous and authenticated users.
+        'contexts' => ['user.roles:anonymous'],
       ],
     ],
     'tray' => [
       '#heading' => t('User account actions'),
-      'user_links' => [
-        '#cache' => [
-          // Cacheable per "authenticated or not", because the links to
-          // display depend on that.
-          'contexts' => Cache::mergeContexts(['user.roles:authenticated'], $links_cache_contexts),
-        ],
-        '#theme' => 'links__toolbar_user',
-        '#links' => $links,
-        '#attributes' => [
-          'class' => ['toolbar-menu'],
-        ],
-      ],
     ],
     '#weight' => 100,
     '#attached' => [
@@ -1403,6 +1353,32 @@ function user_toolbar() {
     ],
   ];
 
+  if ($user->isAnonymous()) {
+    $links = [
+      'login' => [
+        'title' => t('Log in'),
+        'url' => Url::fromRoute('user.page'),
+      ],
+    ];
+    $items['user']['tray']['user_links'] = [
+      '#theme' => 'links__toolbar_user',
+      '#links' => $links,
+      '#attributes' => [
+        'class' => ['toolbar-menu'],
+      ],
+    ];
+  }
+  else {
+    $items['user']['tab']['#title'] = [
+      '#lazy_builder' => ['user.toolbar_link_builder:renderDisplayName', []],
+      '#create_placeholder' => TRUE,
+    ];
+    $items['user']['tray']['user_links'] = [
+      '#lazy_builder' => ['user.toolbar_link_builder:renderToolbarLinks', []],
+      '#create_placeholder' => TRUE,
+    ];
+  }
+
   return $items;
 }
 
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index aede2f462ba6..058daa03bb22 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -68,3 +68,6 @@ services:
     arguments: ['@current_user', '@entity.manager']
     tags:
       - { name: 'context_provider' }
+  user.toolbar_link_builder:
+    class: Drupal\user\ToolbarLinkBuilder
+    arguments: ['@current_user']
-- 
GitLab