array( * 'block' => array( * 'route_parameters' => array('block' => $entity->id()), * ), * ), * @endcode * In this array, the outer key 'block' defines a "group" for contextual * links, and the inner array provides values for the route's placeholder * parameters (see @ref sec_placeholders above). * * To declare that a defined route should be a contextual link for a * contextual links group, put lines like the following in a * module_name.links.contextual.yml file (in the top-level directory for your * module): * @code * block_configure: * title: 'Configure block' * route_name: 'entity.block.edit_form' * group: 'block' * @endcode * Some notes: * - The first line is the machine name for your contextual link, which usually * matches the machine name of the route (given in the 'route_name' line). * - group: This needs to match the link group defined in the render array. * * Contextual links from other modules can be altered using * hook_contextual_links_alter(). * * @todo Derivatives are in flux for these; when they are more stable, add * documentation here. */ /** * @section Rendering menus * Once you have created menus (that contain menu links), you want to render * them. Drupal provides a block (Drupal\system\Plugin\Block\SystemMenuBlock) to * do so. * * However, perhaps you have more advanced needs and you're not satisfied with * what the menu blocks offer you. If that's the case, you'll want to: * - Instantiate \Drupal\Core\Menu\MenuTreeParameters, and set its values to * match your needs. Alternatively, you can use * MenuLinkTree::getCurrentRouteMenuTreeParameters() to get a typical * default set of parameters, and then customize them to suit your needs. * - Call \Drupal\Core\MenuLinkTree::load() with your menu link tree parameters, * this will return a menu link tree. * - Pass the menu tree to \Drupal\Core\Menu\MenuLinkTree::transform() to apply * menu link tree manipulators that transform the tree. You will almost always * want to apply access checking. The manipulators that you will typically * need can be found in \Drupal\Core\Menu\DefaultMenuTreeManipulators. * - Potentially write a custom menu tree manipulator, see * \Drupal\Core\Menu\DefaultMenuTreeManipulators for examples. This is only * necessary if you want to do things like adding extra metadata to rendered * links to display icons next to them. * - Pass the menu tree to \Drupal\Core\Menu\MenuLinkTree::build(), this will * build a renderable array. * * Combined, that would look like this: * @code * $menu_tree = \Drupal::menuTree(); * $menu_name = 'my_menu'; * * // Build the typical default set of menu tree parameters. * $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name); * * // Load the tree based on this set of parameters. * $tree = $menu_tree->load($menu_name, $parameters); * * // Transform the tree using the manipulators you want. * $manipulators = array( * // Only show links that are accessible for the current user. * array('callable' => 'menu.default_tree_manipulators:checkAccess'), * // Use the default sorting of menu links. * array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), * ); * $tree = $menu_tree->transform($tree, $manipulators); * * // Finally, build a renderable array from the transformed tree. * $menu = $menu_tree->build($tree); * * $menu_html = drupal_render($menu); * @endcode */ /** * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { if (isset($variables['tree']['#heading'])) { $variables['heading'] = $variables['tree']['#heading']; $heading = &$variables['heading']; // Convert a string heading into an array, using a H2 tag by default. if (is_string($heading)) { $heading = array('text' => $heading); } // Merge in default array properties into $heading. $heading += array( 'level' => 'h2', 'attributes' => array(), ); // @todo Remove backwards compatibility for $heading['class']. // https://www.drupal.org/node/2310341 if (isset($heading['class'])) { $heading['attributes']['class'] = $heading['class']; } // Convert the attributes array into an Attribute object. $heading['attributes'] = new Attribute($heading['attributes']); $heading['text'] = String::checkPlain($heading['text']); } if (isset($variables['tree']['#attributes'])) { $variables['attributes'] = new Attribute($variables['tree']['#attributes']); } else { $variables['attributes'] = new Attribute(); } if (!isset($variables['attributes']['class'])) { $variables['attributes']['class'] = array(); } $variables['attributes']['class'][] = 'menu'; $variables['tree'] = $variables['tree']['#children']; } /** * Returns HTML for a menu link and submenu. * * @param $variables * An associative array containing: * - element: Structured array data for a menu link. * * @ingroup themeable */ function theme_menu_link(array $variables) { $element = $variables['element']; $sub_menu = ''; if ($element['#below']) { $sub_menu = drupal_render($element['#below']); } /** @var \Drupal\Core\Url $url */ $url = $element['#url']; $url->setOption('set_active_class', TRUE); $output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url); return '' . $output . $sub_menu . "\n"; } /** * Returns HTML for a single local task link. * * @param $variables * An associative array containing: * - element: A render element containing: * - #link: A menu link array with 'title', 'href', and 'localized_options' * keys. * - #active: A boolean indicating whether the local task is active. * * @ingroup themeable */ function theme_menu_local_task($variables) { $link = $variables['element']['#link']; $link += array( 'localized_options' => array(), ); $link_text = $link['title']; if (!empty($variables['element']['#active'])) { // Add text to indicate active tab for non-visual users. $active = '' . t('(active tab)') . ''; // If the link does not contain HTML already, String::checkPlain() it now. // After we set 'html'=TRUE the link will not be sanitized by l(). if (empty($link['localized_options']['html'])) { $link['title'] = String::checkPlain($link['title']); } $link['localized_options']['html'] = TRUE; $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active)); } $link['localized_options']['set_active_class'] = TRUE; if (!empty($link['href'])) { // @todo - remove this once all pages are converted to routes. $a_tag = l($link_text, $link['href'], $link['localized_options']); } else { $a_tag = \Drupal::l($link_text, $link['route_name'], $link['route_parameters'], $link['localized_options']); } return '' . $a_tag . ''; } /** * Returns HTML for a single local action link. * * @param $variables * An associative array containing: * - element: A render element containing: * - #link: A menu link array with 'title', 'href', and 'localized_options' * keys. * * @ingroup themeable */ function theme_menu_local_action($variables) { $link = $variables['element']['#link']; $link += array( 'href' => '', 'localized_options' => array(), 'route_parameters' => array(), ); $link['localized_options']['attributes']['class'][] = 'button'; $link['localized_options']['attributes']['class'][] = 'button-action'; $link['localized_options']['set_active_class'] = TRUE; $output = '
  • '; // @todo Remove this check and the call to l() when all pages are converted to // routes. // @todo Figure out how to support local actions without a href properly. if ($link['href'] === '' && !empty($link['route_name'])) { $output .= Drupal::l($link['title'], $link['route_name'], $link['route_parameters'], $link['localized_options']); } else { $output .= l($link['title'], $link['href'], $link['localized_options']); } $output .= "
  • "; return $output; } /** * Returns an array containing the names of system-defined (default) menus. */ function menu_list_system_menus() { return array( 'tools' => 'Tools', 'admin' => 'Administration', 'account' => 'User account menu', 'main' => 'Main navigation', 'footer' => 'Footer menu', ); } /** * Returns an array of links to be rendered as the Main menu. */ function menu_main_menu() { $main_links_source = _menu_get_links_source('main_links', 'main'); return menu_navigation_links($main_links_source); } /** * Returns an array of links to be rendered as the Secondary links. */ function menu_secondary_menu() { $main_links_source = _menu_get_links_source('main_links', 'main'); $secondary_links_source = _menu_get_links_source('secondary_links', 'account'); // If the secondary menu source is set as the primary menu, we display the // second level of the primary menu. if ($secondary_links_source == $main_links_source) { return menu_navigation_links($main_links_source, 1); } else { return menu_navigation_links($secondary_links_source, 0); } } /** * Returns the source of links of a menu. * * @param string $name * A string configuration key of menu link source. * @param string $default * Default menu name. * * @return string * Returns menu name, if exist */ function _menu_get_links_source($name, $default) { $config = \Drupal::config('menu_ui.settings'); return \Drupal::moduleHandler()->moduleExists('menu_ui') ? $config->get($name) : $default; } /** * Builds a renderable array for a navigation menu. * * @param string $menu_name * The name of the menu. * @param int $level * Optional, the depth of the menu to be returned. * * @return array * A renderable array. */ function menu_navigation_links($menu_name, $level = 0) { $menu_tree = \Drupal::menuTree(); $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name); $parameters->setMaxDepth($level + 1); $tree = $menu_tree->load($menu_name, $parameters); $manipulators = array( array('callable' => 'menu.default_tree_manipulators:checkAccess'), array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), array('callable' => 'menu.default_tree_manipulators:extractSubtreeOfActiveTrail', 'args' => array($level)), ); $tree = $menu_tree->transform($tree, $manipulators); return $menu_tree->build($tree); } /** * Collects the local tasks (tabs), action links, and the root path. * * @param int $level * The level of tasks you ask for. Primary tasks are 0, secondary are 1. * * @return array * An array containing * - tabs: Local tasks for the requested level. * - actions: Action links for the requested level. * - root_path: The router path for the current page. If the current page is * a default local task, then this corresponds to the parent tab. * * @see hook_menu_local_tasks() * @see hook_menu_local_tasks_alter() */ function menu_local_tasks($level = 0) { $data = &drupal_static(__FUNCTION__); $root_path = &drupal_static(__FUNCTION__ . ':root_path', ''); $empty = array( 'tabs' => array(), 'actions' => array(), 'root_path' => &$root_path, ); if (!isset($data)) { // Look for route-based tabs. $data['tabs'] = array(); $data['actions'] = array(); $route_name = \Drupal::routeMatch()->getRouteName(); if (!empty($route_name)) { $manager = \Drupal::service('plugin.manager.menu.local_task'); $local_tasks = $manager->getTasksBuild($route_name); foreach ($local_tasks as $level => $items) { $data['tabs'][$level] = empty($data['tabs'][$level]) ? $items : array_merge($data['tabs'][$level], $items); } } // Allow modules to dynamically add further tasks. $module_handler = \Drupal::moduleHandler(); foreach ($module_handler->getImplementations('menu_local_tasks') as $module) { $function = $module . '_menu_local_tasks'; $function($data, $route_name); } // Allow modules to alter local tasks. $module_handler->alter('menu_local_tasks', $data, $route_name); } if (isset($data['tabs'][$level])) { return array( 'tabs' => $data['tabs'][$level], 'actions' => $data['actions'], 'root_path' => $root_path, ); } elseif (!empty($data['actions'])) { return array('actions' => $data['actions']) + $empty; } return $empty; } /** * Returns the rendered local tasks at the top level. */ function menu_primary_local_tasks() { $links = menu_local_tasks(0); // Do not display single tabs. return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : ''; } /** * Returns the rendered local tasks at the second level. */ function menu_secondary_local_tasks() { $links = menu_local_tasks(1); // Do not display single tabs. return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : ''; } /** * Returns the rendered local actions at the current level. */ function menu_get_local_actions() { $links = menu_local_tasks(); $route_name = Drupal::routeMatch()->getRouteName(); $manager = \Drupal::service('plugin.manager.menu.local_action'); return $manager->getActionsForRoute($route_name) + $links['actions']; } /** * Returns the router path, or the path for a default local task's parent. */ function menu_tab_root_path() { $links = menu_local_tasks(); return $links['root_path']; } /** * Returns a renderable element for the primary and secondary tabs. */ function menu_local_tabs() { $build = array( '#theme' => 'menu_local_tasks', '#primary' => menu_primary_local_tasks(), '#secondary' => menu_secondary_local_tasks(), ); return !empty($build['#primary']) || !empty($build['#secondary']) ? $build : array(); } /** * Returns HTML for primary and secondary local tasks. * * @param $variables * An associative array containing: * - primary: (optional) An array of local tasks (tabs). * - secondary: (optional) An array of local tasks (tabs). * * @ingroup themeable * @see menu_local_tasks() */ function theme_menu_local_tasks(&$variables) { $output = ''; if (!empty($variables['primary'])) { $variables['primary']['#prefix'] = '

    ' . t('Primary tabs') . '

    '; $variables['primary']['#prefix'] .= ''; $output .= drupal_render($variables['primary']); } if (!empty($variables['secondary'])) { $variables['secondary']['#prefix'] = '

    ' . t('Secondary tabs') . '

    '; $variables['secondary']['#prefix'] .= ''; $output .= drupal_render($variables['secondary']); } return $output; } /** * Clears all cached menu data. * * This should be called any time broad changes * might have been made to the router items or menu links. */ function menu_cache_clear_all() { \Drupal::cache('menu')->invalidateAll(); } /** * @} End of "defgroup menu". */