diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 4e3beff..5000567 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -895,7 +895,6 @@ function _menu_link_translate(&$item, $translate = FALSE) { // current path, and we can take over the argument map for a link like // 'foo/%/bar'. This inheritance is only valid for breadcrumb links. // @see _menu_tree_check_access() - // @see menu_get_active_breadcrumb() elseif ($translate && ($current_router_item = menu_get_item())) { // If $translate is TRUE, then this link is in the active trail. // Only translate paths within the current path. @@ -2586,57 +2585,6 @@ function menu_get_active_trail() { } /** - * Gets the breadcrumb for the current page, as determined by the active trail. - * - * @see menu_set_active_trail() - */ -function menu_get_active_breadcrumb() { - $breadcrumb = array(); - - // No breadcrumb for the front page. - if (drupal_is_front_page()) { - return $breadcrumb; - } - - $item = menu_get_item(); - if (!empty($item['access'])) { - $active_trail = menu_get_active_trail(); - - // Allow modules to alter the breadcrumb, if possible, as that is much - // faster than rebuilding an entirely new active trail. - drupal_alter('menu_breadcrumb', $active_trail, $item); - - // Don't show a link to the current page in the breadcrumb trail. - $end = end($active_trail); - if (Drupal::request()->attributes->get('_system_path') == $end['href']) { - array_pop($active_trail); - } - - // Remove the tab root (parent) if the current path links to its parent. - // Normally, the tab root link is included in the breadcrumb, as soon as we - // are on a local task or any other child link. However, if we are on a - // default local task (e.g., node/%/view), then we do not want the tab root - // link (e.g., node/%) to appear, as it would be identical to the current - // page. Since this behavior also needs to work recursively (i.e., on - // default local tasks of default local tasks), and since the last non-task - // link in the trail is used as page title (see menu_get_active_title()), - // this condition cannot be cleanly integrated into menu_get_active_trail(). - // menu_get_active_trail() already skips all links that link to their parent - // (commonly MENU_DEFAULT_LOCAL_TASK). In order to also hide the parent link - // itself, we always remove the last link in the trail, if the current - // router item links to its parent. - if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { - array_pop($active_trail); - } - - foreach ($active_trail as $parent) { - $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']); - } - } - return $breadcrumb; -} - -/** * Gets the title of the current page, as determined by the active trail. */ function menu_get_active_title() { diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml index a5e9746..71de5a0 100644 --- a/core/modules/book/book.services.yml +++ b/core/modules/book/book.services.yml @@ -1,6 +1,7 @@ services: book.breadcrumb: class: Drupal\book\BookBreadcrumbBuilder + arguments: ['@router.route_provider', '@plugin.manager.entity', '@menu_link.access', '@string_translation'] tags: - { name: breadcrumb_builder, priority: 701 } book.manager: diff --git a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php index ddb0857..277263b 100644 --- a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php +++ b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php @@ -7,22 +7,66 @@ namespace Drupal\book; -use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\menu_link\MenuLinkBreadcrumbBuilder; /** * Provides a breadcrumb builder for blocks. */ -class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface { +class BookBreadcrumbBuilder extends MenuLinkBreadcrumbBuilder { /** * {@inheritdoc} */ public function build(array $attributes) { - // @todo This only works for legacy routes. Once node/% is converted to the - // new router this code will need to be updated. - if (isset($attributes['_drupal_menu_item']['map'][1]->book)) { - // @todo Write an actual implementation. - return menu_get_active_breadcrumb(); + if (!empty($attributes['_system_path']) && isset($attributes['_drupal_menu_item']['map'][1]->book)) { + $mlids = array(); + // @todo Replace with link generator service when + // https://drupal.org/node/2047619 lands. + $links = array(l($this->translation->translate('Home'), '')); + $book = $attributes['_drupal_menu_item']['map'][1]->book; + $depth = 1; + while (!empty($book['p' . $depth])) { + $mlids[] = $book['p' . $depth]; + $depth++; + } + $menu_links = $this->menuLinkStorage->loadMultiple($mlids); + if (count($menu_links) > 0) { + $depth = 1; + while (!empty($book['p' . $depth])) { + if (!empty($menu_links[$book['p' . $depth]]) && ($menu_link = $menu_links[$book['p' . $depth]])) { + if (!empty($menu_link->route_name)) { + try { + if ($route = $this->routeProvider->getRouteByName($menu_link->route_name)) { + try { + if ($this->menuLinkAccess->access($route, $menu_link->link_path, $path_elements)) { + // @todo Replace with link generator service when + // https://drupal.org/node/2047619 lands. + $links[] = l($menu_link->label(), $menu_link->link_path, $menu_link->options); + } + } + catch (NotFoundHttpException $e) { + continue; + } + } + } + catch (RouteNotFoundException $e) { + continue; + } + } + else { + // Legacy hook_menu page callback. + // @todo Remove this once all routes are converted. + if ($item = menu_get_item($menu_link->link_path)) { + if ($item['access']) { + $links[] = l($item['title'], $menu_link->link_path, $item['options']); + } + } + } + } + $depth++; + } + } + return $links; } } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php index 89ea1c2..9ce5cd0 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php @@ -10,10 +10,11 @@ use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; use Drupal\Core\Entity\EntityManager; use Drupal\Core\Routing\RouteProviderInterface; +use Drupal\Core\Routing\RouteCompiler; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\menu_link\MenuLinkAccess; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; /** * Class to define the menu_link breadcrumb builder. @@ -21,13 +22,6 @@ class MenuLinkBreadcrumbBuilder implements BreadcrumbBuilderInterface { /** - * The current request. - * - * @var \Symfony\Component\HttpFoundation\Request - */ - protected $request; - - /** * The route provider service. * * @var \Drupal\Core\Routing\RouteProviderInterface @@ -56,10 +50,15 @@ class MenuLinkBreadcrumbBuilder implements BreadcrumbBuilderInterface { protected $translation; /** + * The menu storage controller. + * + * @var Drupal\Core\Config\Entity\ConfigStorageController + */ + protected $menuStorage; + + /** * Constructs the MenuLinkBreadcrumbBuilder. * - * @param \Symfony\Component\HttpFoundation\Request $request - * The current request. * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider * The route provider service. * @param \Drupal\Core\Entity\EntityManager $entity_manager @@ -69,12 +68,12 @@ class MenuLinkBreadcrumbBuilder implements BreadcrumbBuilderInterface { * @param \Drupal\Core\StringTranslation\TranslationInterface $translation * The translation manager service. */ - public function __construct(Request $request, RouteProviderInterface $route_provider, EntityManager $entity_manager, MenuLinkAccess $menu_link_access, TranslationInterface $translation) { - $this->request = $request; + public function __construct(RouteProviderInterface $route_provider, EntityManager $entity_manager, MenuLinkAccess $menu_link_access, TranslationInterface $translation) { $this->routeProvider = $route_provider; $this->menuLinkStorage = $entity_manager->getStorageController('menu_link'); $this->menuLinkAccess = $menu_link_access; $this->translation = $translation; + $this->menuStorage = $entity_manager->getStorageController('menu'); } /** @@ -82,37 +81,74 @@ public function __construct(Request $request, RouteProviderInterface $route_prov */ public function build(array $attributes) { $links = array(); - if ($system_path = $this->request->get('_system_path')) { - $path_elements = explode('/', $system_path); + + // Custom breadcrumb behaviour for editing menu links, we append a link to + // the menu in which the link is found. + if (!empty($attributes['_route']) && $attributes['_route'] == 'menu_link_edit' && !empty($attributes['menu_link'])) { + $menu_link = $attributes['menu_link']; + if (!$menu_link->isNew()) { + // Add a link to the menu admin screen. + $menu = $this->menuStorage->load($menu_link->menu_name); + $links[] = l($menu->label(), 'admin/structure/menu/manage/' . $menu_link->menu_name); + } + } + + // General path-based breadcrumbs. + if (!empty($attributes['_system_path'])) { + $path_elements = explode('/', $attributes['_system_path']); while (count($path_elements) > 0) { array_pop($path_elements); $pattern = implode('/', $path_elements); - $routes = $this->routeProvider->getRoutesByPattern('/' . $pattern); - if (count($routes) > 0) { + $menu_links = $this->menuLinkStorage->loadByProperties(array( + 'link_path' => $pattern, + )); + if (empty($menu_links)) { + // No matching menu link with identical pattern, but could be a case + // of placeholders. try { - // Take the first one. - $routes = $routes->all(); - $route = reset($routes); - continue; - if ($this->menuLinkAccess->access($route, $pattern, $path_elements) && - ($menu_links = $this->menuLinkStorage->loadMultipleByProperties(array( - 'link_path' => $pattern - )))) { - $menu_link = reset($menu_links); - // @todo Replace with link generator service when - // https://drupal.org/node/2047619 lands. - $links[] = l($menu_link->label(), $pattern, $menu_link->options); + $routes = $this->routeProvider->getRoutesByPattern('/' . $pattern)->all(); + if (!empty($routes)) { + $route = reset($routes); + $parameterized_path = RouteCompiler::getPatternOutline($route->getPath()); + $menu_links = $this->menuLinkStorage->loadByProperties(array( + // Strip the leading '/'. + 'link_path' => substr($parameterized_path, 1), + )); } } - catch (NotFoundHttpException $e) { - continue; - } + catch (NotFoundHttpException $e) {} } - else { - // Legacy hook_menu page callback. - // @todo Remove this once all routes are converted. - if ($item = menu_get_item($pattern)) { - $links[] = l($item['title'], $pattern, $item['options']); + if (count($menu_links) > 0) { + $menu_link = reset($menu_links); + if (!empty($menu_link->route_name)) { + try { + if ($route = $this->routeProvider->getRouteByName($menu_link->route_name)) { + try { + if ($this->menuLinkAccess->access($route, $pattern, $path_elements)) { + // @todo Move _menu_item_localize() to a service. + _menu_item_localize($menu_link, $path_elements, FALSE); + // @todo Replace with link generator service when + // https://drupal.org/node/2047619 lands. + $links[] = l($menu_link->title, $pattern, $menu_link->options); + } + } + catch (NotFoundHttpException $e) { + continue; + } + } + } + catch (RouteNotFoundException $e) { + continue; + } + } + else { + // Legacy hook_menu page callback. + // @todo Remove this once all routes are converted. + if ($item = menu_get_item($pattern)) { + if ($item['access']) { + $links[] = l($item['title'], $pattern, $item['options']); + } + } } } } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php index 9d58de9..8ce0afa 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php @@ -78,17 +78,6 @@ public function form(array $form, array &$form_state) { // item, do it here instead. _menu_link_translate($menu_link); - if (!$menu_link->isNew()) { - // Get the human-readable menu title from the given menu name. - $titles = menu_get_menus(); - $current_title = $titles[$menu_link->menu_name]; - - // Get the current breadcrumb and add a link to that menu's overview page. - $breadcrumb = menu_get_active_breadcrumb(); - $breadcrumb[] = l($current_title, 'admin/structure/menu/manage/' . $menu_link->menu_name); - drupal_set_breadcrumb($breadcrumb); - } - $form['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml index d2d2a9f..1615f8e 100644 --- a/core/modules/menu_link/menu_link.services.yml +++ b/core/modules/menu_link/menu_link.services.yml @@ -5,6 +5,6 @@ services: menu_link.breadcrumb: class: Drupal\menu_link\MenuLinkBreadcrumbBuilder - arguments: ['@request', '@router.route_provider', '@plugin.manager.entity', '@menu_link.access', '@string_translation'] + arguments: ['@router.route_provider', '@plugin.manager.entity', '@menu_link.access', '@string_translation'] tags: - { name: breadcrumb_builder, priority: 0 } diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 6339d1c..4d7aae0 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -940,53 +940,6 @@ function hook_local_task_alter(&$local_tasks) { } /** - * Alter links in the active trail before it is rendered as the breadcrumb. - * - * This hook is invoked by menu_get_active_breadcrumb() and allows alteration - * of the breadcrumb links for the current page, which may be preferred instead - * of setting a custom breadcrumb via drupal_set_breadcrumb(). - * - * Implementations should take into account that menu_get_active_breadcrumb() - * subsequently performs the following adjustments to the active trail *after* - * this hook has been invoked: - * - The last link in $active_trail is removed, if its 'href' is identical to - * the 'href' of $item. This happens, because the breadcrumb normally does - * not contain a link to the current page. - * - The (second to) last link in $active_trail is removed, if the current $item - * is a MENU_DEFAULT_LOCAL_TASK. This happens in order to do not show a link - * to the current page, when being on the path for the default local task; - * e.g. when being on the path node/%/view, the breadcrumb should not contain - * a link to node/%. - * - * Each link in the active trail must contain: - * - title: The localized title of the link. - * - href: The system path to link to. - * - localized_options: An array of options to pass to url(). - * - * @param $active_trail - * An array containing breadcrumb links for the current page. - * @param $item - * The menu router item of the current page. - * - * @see drupal_set_breadcrumb() - * @see menu_get_active_breadcrumb() - * @see menu_get_active_trail() - * @see menu_set_active_trail() - */ -function hook_menu_breadcrumb_alter(&$active_trail, $item) { - // Always display a link to the current page by duplicating the last link in - // the active trail. This means that menu_get_active_breadcrumb() will remove - // the last link (for the current page), but since it is added once more here, - // it will appear. - if (!drupal_is_front_page()) { - $end = end($active_trail); - if ($item['href'] == $end['href']) { - $active_trail[] = $end; - } - } -} - -/** * Alter contextual links before they are rendered. * * This hook is invoked by menu_contextual_links(). The system-determined