first commit
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\toolbar\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command that sets the toolbar subtrees.
|
||||
*/
|
||||
class SetSubtreesCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The toolbar subtrees.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $subtrees;
|
||||
|
||||
/**
|
||||
* Constructs a SetSubtreesCommand object.
|
||||
*
|
||||
* @param array $subtrees
|
||||
* The toolbar subtrees that will be set.
|
||||
*/
|
||||
public function __construct($subtrees) {
|
||||
$this->subtrees = $subtrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'setToolbarSubtrees',
|
||||
'subtrees' => array_map('strval', $this->subtrees),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\toolbar\Controller;
|
||||
|
||||
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\Security\TrustedCallbackInterface;
|
||||
use Drupal\toolbar\Ajax\SetSubtreesCommand;
|
||||
|
||||
/**
|
||||
* Defines a controller for the toolbar module.
|
||||
*/
|
||||
class ToolbarController extends ControllerBase implements TrustedCallbackInterface {
|
||||
|
||||
/**
|
||||
* Returns an AJAX response to render the toolbar subtrees.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
*/
|
||||
public function subtreesAjax() {
|
||||
list($subtrees, $cacheability) = 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(REQUEST_TIME + $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* #pre_render callback for toolbar_get_rendered_subtrees().
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function preRenderGetRenderedSubtrees(array $data) {
|
||||
$menu_tree = \Drupal::service('toolbar.menu_tree');
|
||||
// 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 = new CacheableMetadata();
|
||||
foreach ($tree as $element) {
|
||||
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
|
||||
$link = $element->link;
|
||||
if ($element->subtree) {
|
||||
$subtree = $menu_tree->build($element->subtree);
|
||||
$output = \Drupal::service('renderer')->renderPlain($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'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\toolbar\Element;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Render\Element\RenderElement;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Provides a render element for the default Drupal toolbar.
|
||||
*
|
||||
* @RenderElement("toolbar")
|
||||
*/
|
||||
class Toolbar extends RenderElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
$class = get_class($this);
|
||||
return [
|
||||
'#pre_render' => [
|
||||
[$class, 'preRenderToolbar'],
|
||||
],
|
||||
'#theme' => 'toolbar',
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'toolbar/toolbar',
|
||||
],
|
||||
],
|
||||
// Metadata for the toolbar wrapping element.
|
||||
'#attributes' => [
|
||||
// The id cannot be simply "toolbar" or it will clash with the
|
||||
// simpletest tests listing which produces a checkbox with attribute
|
||||
// id="toolbar".
|
||||
'id' => 'toolbar-administration',
|
||||
'role' => 'group',
|
||||
'aria-label' => $this->t('Site administration toolbar'),
|
||||
],
|
||||
// Metadata for the administration bar.
|
||||
'#bar' => [
|
||||
'#heading' => $this->t('Toolbar items'),
|
||||
'#attributes' => [
|
||||
'id' => 'toolbar-bar',
|
||||
'role' => 'navigation',
|
||||
'aria-label' => $this->t('Toolbar items'),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Toolbar as a structured array ready for drupal_render().
|
||||
*
|
||||
* Since building the toolbar takes some time, it is done just prior to
|
||||
* rendering to ensure that it is built only if it will be displayed.
|
||||
*
|
||||
* @param array $element
|
||||
* A renderable array.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*
|
||||
* @see toolbar_page_top()
|
||||
*/
|
||||
public static function preRenderToolbar($element) {
|
||||
// Get the configured breakpoints to switch from vertical to horizontal
|
||||
// toolbar presentation.
|
||||
$breakpoints = static::breakpointManager()->getBreakpointsByGroup('toolbar');
|
||||
if (!empty($breakpoints)) {
|
||||
$media_queries = [];
|
||||
foreach ($breakpoints as $id => $breakpoint) {
|
||||
$media_queries[$id] = $breakpoint->getMediaQuery();
|
||||
}
|
||||
|
||||
$element['#attached']['drupalSettings']['toolbar']['breakpoints'] = $media_queries;
|
||||
}
|
||||
|
||||
$module_handler = static::moduleHandler();
|
||||
// Get toolbar items from all modules that implement hook_toolbar().
|
||||
$items = $module_handler->invokeAll('toolbar');
|
||||
// Allow for altering of hook_toolbar().
|
||||
$module_handler->alter('toolbar', $items);
|
||||
// Sort the children.
|
||||
uasort($items, ['\Drupal\Component\Utility\SortArray', 'sortByWeightProperty']);
|
||||
|
||||
// Merge in the original toolbar values.
|
||||
$element = array_merge($element, $items);
|
||||
|
||||
// Assign each item a unique ID, based on its key.
|
||||
foreach (Element::children($element) as $key) {
|
||||
$element[$key]['#id'] = Html::getId('toolbar-item-' . $key);
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the breakpoint manager.
|
||||
*
|
||||
* @return \Drupal\breakpoint\BreakpointManagerInterface
|
||||
*/
|
||||
protected static function breakpointManager() {
|
||||
return \Drupal::service('breakpoint.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the module handler.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected static function moduleHandler() {
|
||||
return \Drupal::moduleHandler();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\toolbar\Element;
|
||||
|
||||
use Drupal\Core\Render\Element\RenderElement;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a toolbar item that is wrapped in markup for common styling.
|
||||
*
|
||||
* The 'tray' property contains a renderable array.
|
||||
*
|
||||
* @RenderElement("toolbar_item")
|
||||
*/
|
||||
class ToolbarItem extends RenderElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
$class = get_class($this);
|
||||
return [
|
||||
'#pre_render' => [
|
||||
[$class, 'preRenderToolbarItem'],
|
||||
],
|
||||
'tab' => [
|
||||
'#type' => 'link',
|
||||
'#title' => NULL,
|
||||
'#url' => Url::fromRoute('<front>'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides markup for associating a tray trigger with a tray element.
|
||||
*
|
||||
* A tray is a responsive container that wraps renderable content. Trays
|
||||
* present content well on small and large screens alike.
|
||||
*
|
||||
* @param array $element
|
||||
* A renderable array.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*/
|
||||
public static function preRenderToolbarItem($element) {
|
||||
$id = $element['#id'];
|
||||
|
||||
// Provide attributes for a toolbar item.
|
||||
$attributes = [
|
||||
'id' => $id,
|
||||
];
|
||||
|
||||
// If tray content is present, markup the tray and its associated trigger.
|
||||
if (!empty($element['tray'])) {
|
||||
// Provide attributes necessary for trays.
|
||||
$attributes += [
|
||||
'data-toolbar-tray' => $id . '-tray',
|
||||
'role' => 'button',
|
||||
'aria-pressed' => 'false',
|
||||
];
|
||||
|
||||
// Merge in module-provided attributes.
|
||||
$element['tab'] += ['#attributes' => []];
|
||||
$element['tab']['#attributes'] += $attributes;
|
||||
$element['tab']['#attributes']['class'][] = 'trigger';
|
||||
|
||||
// Provide attributes for the tray theme wrapper.
|
||||
$attributes = [
|
||||
'id' => $id . '-tray',
|
||||
'data-toolbar-tray' => $id . '-tray',
|
||||
];
|
||||
// Merge in module-provided attributes.
|
||||
if (!isset($element['tray']['#wrapper_attributes'])) {
|
||||
$element['tray']['#wrapper_attributes'] = [];
|
||||
}
|
||||
$element['tray']['#wrapper_attributes'] += $attributes;
|
||||
$element['tray']['#wrapper_attributes']['class'][] = 'toolbar-tray';
|
||||
}
|
||||
|
||||
$element['tab']['#attributes']['class'][] = 'toolbar-item';
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\toolbar\Menu;
|
||||
|
||||
use Drupal\Core\Menu\MenuLinkTree;
|
||||
|
||||
/**
|
||||
* Extends MenuLinkTree to add specific theme suggestions for the toolbar.
|
||||
*/
|
||||
class ToolbarMenuLinkTree extends MenuLinkTree {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(array $tree, $level = 0) {
|
||||
if ($level == 0) {
|
||||
if (!$tree) {
|
||||
return [];
|
||||
}
|
||||
$build = parent::build($tree, $level);
|
||||
|
||||
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
|
||||
$first_link = reset($tree)->link;
|
||||
// Get the menu name of the first link.
|
||||
$menu_name = $first_link->getMenuName();
|
||||
// Add a more specific theme suggestion to differentiate this rendered
|
||||
// menu from others.
|
||||
$build['#menu_name'] = $menu_name;
|
||||
$build['#theme'] = 'menu__toolbar__' . strtr($menu_name, '-', '_');
|
||||
return $build;
|
||||
}
|
||||
else {
|
||||
return parent::build($tree, $level);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\toolbar\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Cache policy for the toolbar page cache service.
|
||||
*
|
||||
* This policy allows caching of requests directed to /toolbar/subtrees/{hash}
|
||||
* even for authenticated users.
|
||||
*/
|
||||
class AllowToolbarPath implements RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
// Note that this regular expression matches the end of pathinfo in order to
|
||||
// support multilingual sites using path prefixes.
|
||||
if (preg_match('#/toolbar/subtrees/[^/]+(/[^/]+)?$#', $request->getPathInfo())) {
|
||||
return static::ALLOW;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user