summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/toolbar/src/Controller/ToolbarController.php
blob: 8d25465ffd083ced9e2e8230e717a373e34e682d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?php

namespace Drupal\toolbar\Controller;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\toolbar\Ajax\SetSubtreesCommand;

/**
 * Defines a controller for the toolbar module.
 */
class ToolbarController extends ControllerBase implements TrustedCallbackInterface {

  /**
   * Constructs a ToolbarController object.
   *
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   */
  public function __construct(
    protected ?TimeInterface $time = NULL,
  ) {
  }

  /**
   * Returns an AJAX response to render the toolbar subtrees.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The AJAX response containing the rendered toolbar subtrees.
   */
  public function subtreesAjax() {
    [$subtrees] = toolbar_get_rendered_subtrees();
    $response = new AjaxResponse();
    $response->addCommand(new SetSubtreesCommand($subtrees));

    // The Expires HTTP header is the heart of the client-side HTTP caching. The
    // additional server-side page cache only takes effect when the client
    // accesses the callback URL again (e.g., after clearing the browser cache
    // or when force-reloading a Drupal page).
    $max_age = 365 * 24 * 60 * 60;
    $response->setPrivate();
    $response->setMaxAge($max_age);

    $expires = new \DateTime();
    $expires->setTimestamp($this->time->getRequestTime() + $max_age);
    $response->setExpires($expires);

    return $response;
  }

  /**
   * Checks access for the subtree controller.
   *
   * @param string $hash
   *   The hash of the toolbar subtrees.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function checkSubTreeAccess($hash) {
    $expected_hash = _toolbar_get_subtrees_hash()[0];
    return AccessResult::allowedIf($this->currentUser()->hasPermission('access toolbar') && hash_equals($expected_hash, $hash))->cachePerPermissions();
  }

  /**
   * Renders the toolbar's administration tray.
   *
   * @param array $element
   *   A renderable array.
   *
   * @return array
   *   The updated renderable array.
   *
   * @see \Drupal\Core\Render\RendererInterface::render()
   */
  public static function preRenderAdministrationTray(array $element) {
    $menu_tree = \Drupal::service('toolbar.menu_tree');
    // Load the administrative menu. The first level is the "Administration"
    // link. In order to load the children of that link, start and end on the
    // second level.
    $parameters = new MenuTreeParameters();
    $parameters->setMinDepth(2)->setMaxDepth(2)->onlyEnabledLinks();
    // @todo Make the menu configurable in https://www.drupal.org/node/1869638.
    $tree = $menu_tree->load('admin', $parameters);
    $manipulators = [
      ['callable' => 'menu.default_tree_manipulators:checkAccess'],
      ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
      ['callable' => 'toolbar_menu_navigation_links'],
    ];
    $tree = $menu_tree->transform($tree, $manipulators);
    $element['administration_menu'] = $menu_tree->build($tree);
    return $element;
  }

  /**
   * Render API callback: Prepares the subtrees.
   *
   * This function is assigned as a #pre_render callback.
   *
   * @internal
   */
  public static function preRenderGetRenderedSubtrees(array $data) {
    $menu_tree = \Drupal::service('toolbar.menu_tree');
    $renderer = \Drupal::service('renderer');
    // Load the administration menu. The first level is the "Administration"
    // link. In order to load the children of that link and the subsequent two
    // levels, start at the second level and end at the fourth.
    $parameters = new MenuTreeParameters();
    $parameters->setMinDepth(2)->setMaxDepth(4)->onlyEnabledLinks();
    // @todo Make the menu configurable in https://www.drupal.org/node/1869638.
    $tree = $menu_tree->load('admin', $parameters);
    $manipulators = [
      ['callable' => 'menu.default_tree_manipulators:checkAccess'],
      ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
      ['callable' => 'toolbar_menu_navigation_links'],
    ];
    $tree = $menu_tree->transform($tree, $manipulators);
    $subtrees = [];
    // Calculated the combined cacheability of all subtrees.
    $cacheability = CacheableMetadata::createFromRenderArray($data);
    foreach ($tree as $element) {
      /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
      $link = $element->link;
      if ($element->subtree) {
        $subtree = $menu_tree->build($element->subtree);
        $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, $subtree) {
          return $renderer->render($subtree);
        });
        $cacheability = $cacheability->merge(CacheableMetadata::createFromRenderArray($subtree));
      }
      else {
        $output = '';
      }
      // Many routes have dots as route name, while some special ones like
      // <front> have <> characters in them.
      $url = $link->getUrlObject();
      $id = str_replace(['.', '<', '>'], ['-', '', ''], $url->isRouted() ? $url->getRouteName() : $url->getUri());

      $subtrees[$id] = $output;
    }

    // Store the subtrees, along with the cacheability metadata.
    $cacheability->applyTo($data);
    $data['#subtrees'] = $subtrees;

    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['preRenderAdministrationTray', 'preRenderGetRenderedSubtrees'];
  }

}