Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /**
|
Chris@0
|
4 * @file
|
Chris@0
|
5 * Administration toolbar for quick access to top level administration items.
|
Chris@0
|
6 */
|
Chris@0
|
7
|
Chris@0
|
8 use Drupal\Core\Cache\CacheableMetadata;
|
Chris@0
|
9 use Drupal\Core\Menu\MenuTreeParameters;
|
Chris@0
|
10 use Drupal\Core\Render\Element;
|
Chris@0
|
11 use Drupal\Core\Routing\RouteMatchInterface;
|
Chris@0
|
12 use Drupal\Core\Template\Attribute;
|
Chris@0
|
13 use Drupal\Component\Utility\Crypt;
|
Chris@0
|
14 use Drupal\Core\Url;
|
Chris@0
|
15
|
Chris@0
|
16 /**
|
Chris@0
|
17 * Implements hook_help().
|
Chris@0
|
18 */
|
Chris@0
|
19 function toolbar_help($route_name, RouteMatchInterface $route_match) {
|
Chris@0
|
20 switch ($route_name) {
|
Chris@0
|
21 case 'help.page.toolbar':
|
Chris@0
|
22 $output = '<h3>' . t('About') . '</h3>';
|
Chris@0
|
23 $output .= '<p>' . t('The Toolbar module provides a toolbar for site administrators, which displays tabs and trays provided by the Toolbar module itself and other modules. For more information, see the <a href=":toolbar_docs">online documentation for the Toolbar module</a>.', [':toolbar_docs' => 'https://www.drupal.org/documentation/modules/toolbar']) . '</p>';
|
Chris@0
|
24 $output .= '<h4>' . t('Terminology') . '</h4>';
|
Chris@0
|
25 $output .= '<dl>';
|
Chris@0
|
26 $output .= '<dt>' . t('Tabs') . '</dt>';
|
Chris@0
|
27 $output .= '<dd>' . t('Tabs are buttons, displayed in a bar across the top of the screen. Some tabs execute an action (such as starting Edit mode), while other tabs toggle which tray is open.') . '</dd>';
|
Chris@0
|
28 $output .= '<dt>' . t('Trays') . '</dt>';
|
Chris@0
|
29 $output .= '<dd>' . t('Trays are usually lists of links, which can be hierarchical like a menu. If a tray has been toggled open, it is displayed either vertically or horizontally below the tab bar, depending on the browser width. Only one tray may be open at a time. If you click another tab, that tray will replace the tray being displayed. In wide browser widths, the user has the ability to toggle from vertical to horizontal, using a link at the bottom or right of the tray. Hierarchical menus only have open/close behavior in vertical mode; if you display a tray containing a hierarchical menu horizontally, only the top-level links will be available.') . '</dd>';
|
Chris@0
|
30 $output .= '</dl>';
|
Chris@0
|
31 return $output;
|
Chris@0
|
32 }
|
Chris@0
|
33 }
|
Chris@0
|
34
|
Chris@0
|
35 /**
|
Chris@0
|
36 * Implements hook_theme().
|
Chris@0
|
37 */
|
Chris@0
|
38 function toolbar_theme($existing, $type, $theme, $path) {
|
Chris@0
|
39 $items['toolbar'] = [
|
Chris@0
|
40 'render element' => 'element',
|
Chris@0
|
41 ];
|
Chris@0
|
42 $items['menu__toolbar'] = [
|
Chris@0
|
43 'base hook' => 'menu',
|
Chris@0
|
44 'variables' => ['items' => [], 'attributes' => []],
|
Chris@0
|
45 ];
|
Chris@0
|
46
|
Chris@0
|
47 return $items;
|
Chris@0
|
48 }
|
Chris@0
|
49
|
Chris@0
|
50 /**
|
Chris@0
|
51 * Implements hook_page_top().
|
Chris@0
|
52 *
|
Chris@0
|
53 * Add admin toolbar to the top of the page automatically.
|
Chris@0
|
54 */
|
Chris@0
|
55 function toolbar_page_top(array &$page_top) {
|
Chris@0
|
56 $page_top['toolbar'] = [
|
Chris@0
|
57 '#type' => 'toolbar',
|
Chris@0
|
58 '#access' => \Drupal::currentUser()->hasPermission('access toolbar'),
|
Chris@0
|
59 '#cache' => [
|
Chris@0
|
60 'keys' => ['toolbar'],
|
Chris@0
|
61 'contexts' => ['user.permissions'],
|
Chris@0
|
62 ],
|
Chris@0
|
63 ];
|
Chris@0
|
64 }
|
Chris@0
|
65
|
Chris@0
|
66 /**
|
Chris@0
|
67 * Prepares variables for administration toolbar templates.
|
Chris@0
|
68 *
|
Chris@0
|
69 * Default template: toolbar.html.twig.
|
Chris@0
|
70 *
|
Chris@0
|
71 * @param array $variables
|
Chris@0
|
72 * An associative array containing:
|
Chris@0
|
73 * - element: An associative array containing the properties and children of
|
Chris@0
|
74 * the tray. Properties used: #children, #attributes and #bar.
|
Chris@0
|
75 */
|
Chris@0
|
76 function template_preprocess_toolbar(&$variables) {
|
Chris@0
|
77 $element = $variables['element'];
|
Chris@0
|
78
|
Chris@0
|
79 // Prepare the toolbar attributes.
|
Chris@0
|
80 $variables['attributes'] = $element['#attributes'];
|
Chris@0
|
81 $variables['toolbar_attributes'] = new Attribute($element['#bar']['#attributes']);
|
Chris@0
|
82 $variables['toolbar_heading'] = $element['#bar']['#heading'];
|
Chris@0
|
83
|
Chris@0
|
84 // Prepare the trays and tabs for each toolbar item as well as the remainder
|
Chris@0
|
85 // variable that will hold any non-tray, non-tab elements.
|
Chris@0
|
86 $variables['trays'] = [];
|
Chris@0
|
87 $variables['tabs'] = [];
|
Chris@0
|
88 $variables['remainder'] = [];
|
Chris@0
|
89 foreach (Element::children($element) as $key) {
|
Chris@0
|
90 // Early rendering to collect the wrapper attributes from
|
Chris@0
|
91 // ToolbarItem elements.
|
Chris@0
|
92 if (!empty($element[$key])) {
|
Chris@0
|
93 Drupal::service('renderer')->render($element[$key]);
|
Chris@0
|
94 }
|
Chris@0
|
95 // Add the tray.
|
Chris@0
|
96 if (isset($element[$key]['tray'])) {
|
Chris@0
|
97 $attributes = [];
|
Chris@0
|
98 if (!empty($element[$key]['tray']['#wrapper_attributes'])) {
|
Chris@0
|
99 $attributes = $element[$key]['tray']['#wrapper_attributes'];
|
Chris@0
|
100 }
|
Chris@0
|
101 $variables['trays'][$key] = [
|
Chris@0
|
102 'links' => $element[$key]['tray'],
|
Chris@0
|
103 'attributes' => new Attribute($attributes),
|
Chris@0
|
104 ];
|
Chris@0
|
105 if (array_key_exists('#heading', $element[$key]['tray'])) {
|
Chris@0
|
106 $variables['trays'][$key]['label'] = $element[$key]['tray']['#heading'];
|
Chris@0
|
107 }
|
Chris@0
|
108 }
|
Chris@0
|
109
|
Chris@0
|
110 // Add the tab.
|
Chris@0
|
111 if (isset($element[$key]['tab'])) {
|
Chris@0
|
112 $attributes = [];
|
Chris@0
|
113 // Pass the wrapper attributes along.
|
Chris@0
|
114 if (!empty($element[$key]['#wrapper_attributes'])) {
|
Chris@0
|
115 $attributes = $element[$key]['#wrapper_attributes'];
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 $variables['tabs'][$key] = [
|
Chris@0
|
119 'link' => $element[$key]['tab'],
|
Chris@0
|
120 'attributes' => new Attribute($attributes),
|
Chris@0
|
121 ];
|
Chris@0
|
122 }
|
Chris@0
|
123
|
Chris@0
|
124 // Add other non-tray, non-tab child elements to the remainder variable for
|
Chris@0
|
125 // later rendering.
|
Chris@0
|
126 foreach (Element::children($element[$key]) as $child_key) {
|
Chris@0
|
127 if (!in_array($child_key, ['tray', 'tab'])) {
|
Chris@0
|
128 $variables['remainder'][$key][$child_key] = $element[$key][$child_key];
|
Chris@0
|
129 }
|
Chris@0
|
130 }
|
Chris@0
|
131 }
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 /**
|
Chris@0
|
135 * Implements hook_toolbar().
|
Chris@0
|
136 */
|
Chris@0
|
137 function toolbar_toolbar() {
|
Chris@0
|
138 // The 'Home' tab is a simple link, with no corresponding tray.
|
Chris@0
|
139 $items['home'] = [
|
Chris@0
|
140 '#type' => 'toolbar_item',
|
Chris@0
|
141 'tab' => [
|
Chris@0
|
142 '#type' => 'link',
|
Chris@0
|
143 '#title' => t('Back to site'),
|
Chris@0
|
144 '#url' => Url::fromRoute('<front>'),
|
Chris@0
|
145 '#attributes' => [
|
Chris@0
|
146 'title' => t('Return to site content'),
|
Chris@0
|
147 'class' => ['toolbar-icon', 'toolbar-icon-escape-admin'],
|
Chris@0
|
148 'data-toolbar-escape-admin' => TRUE,
|
Chris@0
|
149 ],
|
Chris@0
|
150 ],
|
Chris@0
|
151 '#wrapper_attributes' => [
|
Chris@0
|
152 'class' => ['home-toolbar-tab'],
|
Chris@0
|
153 ],
|
Chris@0
|
154 '#attached' => [
|
Chris@0
|
155 'library' => [
|
Chris@0
|
156 'toolbar/toolbar.escapeAdmin',
|
Chris@0
|
157 ],
|
Chris@0
|
158 ],
|
Chris@0
|
159 '#weight' => -20,
|
Chris@0
|
160 ];
|
Chris@0
|
161
|
Chris@0
|
162 // To conserve bandwidth, we only include the top-level links in the HTML.
|
Chris@0
|
163 // The subtrees are fetched through a JSONP script that is generated at the
|
Chris@0
|
164 // toolbar_subtrees route. We provide the JavaScript requesting that JSONP
|
Chris@0
|
165 // script here with the hash parameter that is needed for that route.
|
Chris@0
|
166 // @see toolbar_subtrees_jsonp()
|
Chris@0
|
167 list($hash, $hash_cacheability) = _toolbar_get_subtrees_hash();
|
Chris@0
|
168 $subtrees_attached['drupalSettings']['toolbar'] = [
|
Chris@0
|
169 'subtreesHash' => $hash,
|
Chris@0
|
170 ];
|
Chris@0
|
171
|
Chris@0
|
172 // The administration element has a link that is themed to correspond to
|
Chris@0
|
173 // a toolbar tray. The tray contains the full administrative menu of the site.
|
Chris@0
|
174 $items['administration'] = [
|
Chris@0
|
175 '#type' => 'toolbar_item',
|
Chris@0
|
176 'tab' => [
|
Chris@0
|
177 '#type' => 'link',
|
Chris@0
|
178 '#title' => t('Manage'),
|
Chris@0
|
179 '#url' => Url::fromRoute('system.admin'),
|
Chris@0
|
180 '#attributes' => [
|
Chris@0
|
181 'title' => t('Admin menu'),
|
Chris@0
|
182 'class' => ['toolbar-icon', 'toolbar-icon-menu'],
|
Chris@0
|
183 // A data attribute that indicates to the client to defer loading of
|
Chris@0
|
184 // the admin menu subtrees until this tab is activated. Admin menu
|
Chris@0
|
185 // subtrees will not render to the DOM if this attribute is removed.
|
Chris@0
|
186 // The value of the attribute is intentionally left blank. Only the
|
Chris@0
|
187 // presence of the attribute is necessary.
|
Chris@0
|
188 'data-drupal-subtrees' => '',
|
Chris@0
|
189 ],
|
Chris@0
|
190 ],
|
Chris@0
|
191 'tray' => [
|
Chris@0
|
192 '#heading' => t('Administration menu'),
|
Chris@0
|
193 '#attached' => $subtrees_attached,
|
Chris@0
|
194 'toolbar_administration' => [
|
Chris@0
|
195 '#pre_render' => [
|
Chris@0
|
196 'toolbar_prerender_toolbar_administration_tray',
|
Chris@0
|
197 ],
|
Chris@0
|
198 '#type' => 'container',
|
Chris@0
|
199 '#attributes' => [
|
Chris@0
|
200 'class' => ['toolbar-menu-administration'],
|
Chris@0
|
201 ],
|
Chris@0
|
202 ],
|
Chris@0
|
203 ],
|
Chris@0
|
204 '#weight' => -15,
|
Chris@0
|
205 ];
|
Chris@0
|
206 $hash_cacheability->applyTo($items['administration']);
|
Chris@0
|
207
|
Chris@0
|
208 return $items;
|
Chris@0
|
209 }
|
Chris@0
|
210
|
Chris@0
|
211 /**
|
Chris@0
|
212 * Renders the toolbar's administration tray.
|
Chris@0
|
213 *
|
Chris@0
|
214 * @param array $element
|
Chris@0
|
215 * A renderable array.
|
Chris@0
|
216 *
|
Chris@0
|
217 * @return array
|
Chris@0
|
218 * The updated renderable array.
|
Chris@0
|
219 *
|
Chris@16
|
220 * @see \Drupal\Core\Render\RendererInterface::render()
|
Chris@0
|
221 */
|
Chris@0
|
222 function toolbar_prerender_toolbar_administration_tray(array $element) {
|
Chris@0
|
223 $menu_tree = \Drupal::service('toolbar.menu_tree');
|
Chris@0
|
224 // Load the administrative menu. The first level is the "Administration" link.
|
Chris@0
|
225 // In order to load the children of that link, start and end on the second
|
Chris@0
|
226 // level.
|
Chris@0
|
227 $parameters = new MenuTreeParameters();
|
Chris@0
|
228 $parameters->setMinDepth(2)->setMaxDepth(2)->onlyEnabledLinks();
|
Chris@0
|
229 // @todo Make the menu configurable in https://www.drupal.org/node/1869638.
|
Chris@0
|
230 $tree = $menu_tree->load('admin', $parameters);
|
Chris@0
|
231 $manipulators = [
|
Chris@0
|
232 ['callable' => 'menu.default_tree_manipulators:checkAccess'],
|
Chris@0
|
233 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
|
Chris@0
|
234 ['callable' => 'toolbar_menu_navigation_links'],
|
Chris@0
|
235 ];
|
Chris@0
|
236 $tree = $menu_tree->transform($tree, $manipulators);
|
Chris@0
|
237 $element['administration_menu'] = $menu_tree->build($tree);
|
Chris@0
|
238 return $element;
|
Chris@0
|
239 }
|
Chris@0
|
240
|
Chris@0
|
241 /**
|
Chris@0
|
242 * Adds toolbar-specific attributes to the menu link tree.
|
Chris@0
|
243 *
|
Chris@0
|
244 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
Chris@0
|
245 * The menu link tree to manipulate.
|
Chris@0
|
246 *
|
Chris@0
|
247 * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
Chris@0
|
248 * The manipulated menu link tree.
|
Chris@0
|
249 */
|
Chris@0
|
250 function toolbar_menu_navigation_links(array $tree) {
|
Chris@0
|
251 foreach ($tree as $element) {
|
Chris@0
|
252 if ($element->subtree) {
|
Chris@0
|
253 toolbar_menu_navigation_links($element->subtree);
|
Chris@0
|
254 }
|
Chris@0
|
255
|
Chris@0
|
256 // Make sure we have a path specific ID in place, so we can attach icons
|
Chris@0
|
257 // and behaviors to the menu links.
|
Chris@0
|
258 $link = $element->link;
|
Chris@0
|
259 $url = $link->getUrlObject();
|
Chris@0
|
260 if (!$url->isRouted()) {
|
Chris@0
|
261 // This is an unusual case, so just get a distinct, safe string.
|
Chris@0
|
262 $id = substr(Crypt::hashBase64($url->getUri()), 0, 16);
|
Chris@0
|
263 }
|
Chris@0
|
264 else {
|
Chris@0
|
265 $id = str_replace(['.', '<', '>'], ['-', '', ''], $url->getRouteName());
|
Chris@0
|
266 }
|
Chris@0
|
267
|
Chris@0
|
268 // Get the non-localized title to make the icon class.
|
Chris@0
|
269 $definition = $link->getPluginDefinition();
|
Chris@0
|
270
|
Chris@0
|
271 $element->options['attributes']['id'] = 'toolbar-link-' . $id;
|
Chris@0
|
272 $element->options['attributes']['class'][] = 'toolbar-icon';
|
Chris@0
|
273 $element->options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(['.', ' ', '_'], ['-', '-', '-'], $definition['id']));
|
Chris@0
|
274 $element->options['attributes']['title'] = $link->getDescription();
|
Chris@0
|
275 }
|
Chris@0
|
276 return $tree;
|
Chris@0
|
277 }
|
Chris@0
|
278
|
Chris@0
|
279 /**
|
Chris@0
|
280 * Implements hook_preprocess_HOOK() for HTML document templates.
|
Chris@0
|
281 */
|
Chris@0
|
282 function toolbar_preprocess_html(&$variables) {
|
Chris@0
|
283 if (!\Drupal::currentUser()->hasPermission('access toolbar')) {
|
Chris@0
|
284 return;
|
Chris@0
|
285 }
|
Chris@18
|
286 $variables['attributes']['class'][] = 'toolbar-tray-open';
|
Chris@18
|
287 $variables['attributes']['class'][] = 'toolbar-horizontal';
|
Chris@18
|
288 $variables['attributes']['class'][] = 'toolbar-fixed';
|
Chris@18
|
289 $variables['attributes']['class'][] = 'toolbar-loading';
|
Chris@0
|
290 }
|
Chris@0
|
291
|
Chris@0
|
292 /**
|
Chris@0
|
293 * Returns the rendered subtree of each top-level toolbar link.
|
Chris@0
|
294 *
|
Chris@0
|
295 * @return array
|
Chris@0
|
296 * An array with the following key-value pairs:
|
Chris@0
|
297 * - 'subtrees': the rendered subtrees
|
Chris@0
|
298 * - 'cacheability: the associated cacheability.
|
Chris@0
|
299 */
|
Chris@0
|
300 function toolbar_get_rendered_subtrees() {
|
Chris@0
|
301 $data = [
|
Chris@0
|
302 '#pre_render' => ['_toolbar_do_get_rendered_subtrees'],
|
Chris@0
|
303 '#cache' => [
|
Chris@0
|
304 'keys' => [
|
Chris@0
|
305 'toolbar_rendered_subtrees',
|
Chris@0
|
306 ],
|
Chris@0
|
307 ],
|
Chris@0
|
308 '#cache_properties' => ['#subtrees'],
|
Chris@0
|
309 ];
|
Chris@0
|
310 \Drupal::service('renderer')->renderPlain($data);
|
Chris@0
|
311 return [$data['#subtrees'], CacheableMetadata::createFromRenderArray($data)];
|
Chris@0
|
312 }
|
Chris@0
|
313
|
Chris@0
|
314 /**
|
Chris@0
|
315 * #pre_render callback for toolbar_get_rendered_subtrees().
|
Chris@0
|
316 */
|
Chris@0
|
317 function _toolbar_do_get_rendered_subtrees(array $data) {
|
Chris@0
|
318 $menu_tree = \Drupal::service('toolbar.menu_tree');
|
Chris@0
|
319 // Load the administration menu. The first level is the "Administration" link.
|
Chris@0
|
320 // In order to load the children of that link and the subsequent two levels,
|
Chris@0
|
321 // start at the second level and end at the fourth.
|
Chris@0
|
322 $parameters = new MenuTreeParameters();
|
Chris@0
|
323 $parameters->setMinDepth(2)->setMaxDepth(4)->onlyEnabledLinks();
|
Chris@0
|
324 // @todo Make the menu configurable in https://www.drupal.org/node/1869638.
|
Chris@0
|
325 $tree = $menu_tree->load('admin', $parameters);
|
Chris@0
|
326 $manipulators = [
|
Chris@0
|
327 ['callable' => 'menu.default_tree_manipulators:checkAccess'],
|
Chris@0
|
328 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
|
Chris@0
|
329 ['callable' => 'toolbar_menu_navigation_links'],
|
Chris@0
|
330 ];
|
Chris@0
|
331 $tree = $menu_tree->transform($tree, $manipulators);
|
Chris@0
|
332 $subtrees = [];
|
Chris@0
|
333 // Calculated the combined cacheability of all subtrees.
|
Chris@0
|
334 $cacheability = new CacheableMetadata();
|
Chris@0
|
335 foreach ($tree as $element) {
|
Chris@0
|
336 /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
|
Chris@0
|
337 $link = $element->link;
|
Chris@0
|
338 if ($element->subtree) {
|
Chris@0
|
339 $subtree = $menu_tree->build($element->subtree);
|
Chris@0
|
340 $output = \Drupal::service('renderer')->renderPlain($subtree);
|
Chris@0
|
341 $cacheability = $cacheability->merge(CacheableMetadata::createFromRenderArray($subtree));
|
Chris@0
|
342 }
|
Chris@0
|
343 else {
|
Chris@0
|
344 $output = '';
|
Chris@0
|
345 }
|
Chris@0
|
346 // Many routes have dots as route name, while some special ones like <front>
|
Chris@0
|
347 // have <> characters in them.
|
Chris@0
|
348 $url = $link->getUrlObject();
|
Chris@0
|
349 $id = str_replace(['.', '<', '>'], ['-', '', ''], $url->isRouted() ? $url->getRouteName() : $url->getUri());
|
Chris@0
|
350
|
Chris@0
|
351 $subtrees[$id] = $output;
|
Chris@0
|
352 }
|
Chris@0
|
353
|
Chris@0
|
354 // Store the subtrees, along with the cacheability metadata.
|
Chris@0
|
355 $cacheability->applyTo($data);
|
Chris@0
|
356 $data['#subtrees'] = $subtrees;
|
Chris@0
|
357
|
Chris@0
|
358 return $data;
|
Chris@0
|
359 }
|
Chris@0
|
360
|
Chris@0
|
361 /**
|
Chris@0
|
362 * Returns the hash of the per-user rendered toolbar subtrees.
|
Chris@0
|
363 *
|
Chris@0
|
364 * @return string
|
Chris@0
|
365 * The hash of the admin_menu subtrees.
|
Chris@0
|
366 */
|
Chris@0
|
367 function _toolbar_get_subtrees_hash() {
|
Chris@0
|
368 list($subtrees, $cacheability) = toolbar_get_rendered_subtrees();
|
Chris@0
|
369 $hash = Crypt::hashBase64(serialize($subtrees));
|
Chris@0
|
370 return [$hash, $cacheability];
|
Chris@0
|
371 }
|