Commit 00c06fcf authored by catch's avatar catch

Issue #2070651 by pwolanin, dawehner, larowlan, tim.plunkett, ParisLiakos:...

Issue #2070651 by pwolanin, dawehner, larowlan, tim.plunkett, ParisLiakos: Introduce a path-based breadcrumb builder, remove the one that's based on {menu_links()}.
parent 96edb782
......@@ -531,6 +531,7 @@ services:
arguments: ['@image.toolkit']
breadcrumb:
class: Drupal\Core\Breadcrumb\BreadcrumbManager
arguments: ['@module_handler']
token:
class: Drupal\Core\Utility\Token
arguments: ['@module_handler']
......
......@@ -146,7 +146,7 @@
*/
/**
* Menu type -- A "normal" menu item that's shown in menu and breadcrumbs.
* Menu type -- A "normal" menu item that's shown in menus.
*
* Normal menu items show up in the menu tree and can be moved/hidden by
* the administrator. Use this for most menu items. It is the default value if
......@@ -158,7 +158,7 @@
* Menu type -- A hidden, internal callback, typically used for API calls.
*
* Callbacks simply register a path so that the correct function is fired
* when the URL is accessed. They do not appear in menus or breadcrumbs.
* when the URL is accessed. They do not appear in menus.
*/
const MENU_CALLBACK = 0x0000;
......@@ -200,7 +200,7 @@
/**
* Menu type -- A task specific to the parent, which is never rendered.
*
* Sibling local tasks are not rendered themselves, but affect the breadcrumb
* Sibling local tasks are not rendered themselves, but affect the active
* trail and need their sibling tasks rendered as tabs.
*/
define('MENU_SIBLING_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
......@@ -437,7 +437,7 @@ function menu_unserialize($data, $map) {
* @param $router_item
* The router item. Usually a router entry from menu_get_item() is either
* modified or set to a different path. This allows the navigation block,
* the page title, the breadcrumb, and the page help to be modified in one
* the page title, the active trail, and the page help to be modified in one
* call.
*/
function menu_set_item($path, $router_item) {
......@@ -898,7 +898,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.
......@@ -1281,7 +1280,7 @@ function menu_tree_get_path($menu_name) {
* @param $only_active_trail
* (optional) Whether to only return the links in the active trail (TRUE)
* instead of all links on every level of the menu link tree (FALSE). Defaults
* to FALSE. Internally used for breadcrumbs only.
* to FALSE.
*
* @return
* An array of menu links, in the order they should be rendered. The array
......@@ -1328,7 +1327,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
// template_preprocess_page(). So in order to not build a giant menu tree
// that needs to be checked for access on all levels, we simply check
// whether we have the menu already in cache, or otherwise, build a minimum
// tree containing the breadcrumb/active trail only.
// tree containing the active trail only.
// @see menu_set_active_trail()
if (!isset($tree[$cid]) && $only_active_trail) {
$cid .= ':trail';
......@@ -1430,8 +1429,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
* - active_trail: An array of mlids, representing the coordinates of the
* currently active menu link.
* - only_active_trail: Whether to only return links that are in the active
* trail. This option is ignored, if 'expanded' is non-empty. Internally
* used for breadcrumbs.
* trail. This option is ignored, if 'expanded' is non-empty.
* - min_depth: The minimum depth of menu links in the resulting tree.
* Defaults to 1, which is the default to build a whole tree for a menu
* (excluding menu container itself).
......@@ -1641,7 +1639,7 @@ function _menu_tree_data(&$links, $parents, $depth) {
$tree = array();
while ($item = array_pop($links)) {
// We need to determine if we're on the path to root so we can later build
// the correct active trail and breadcrumb.
// the correct active trail.
$item['in_active_trail'] = in_array($item['mlid'], $parents);
// Add the current link to the tree.
$tree[$item['mlid']] = array(
......@@ -2441,9 +2439,7 @@ function menu_set_active_item($path) {
*
* Any trail set by this function will only be used for functionality that calls
* menu_get_active_trail(). Drupal core only uses trails set here for
* breadcrumbs and the page title and not for menu trees or page content.
* Additionally, breadcrumbs set by drupal_set_breadcrumb() will override any
* trail set here.
* the page title and not for menu trees or page content.
*
* To affect the trail used by menu trees, use menu_tree_set_path(). To affect
* the page content, use menu_set_active_item() instead.
......@@ -2639,57 +2635,6 @@ function menu_get_active_trail() {
return menu_set_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.
*/
......
......@@ -7,6 +7,8 @@
namespace Drupal\Core\Breadcrumb;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Provides a breadcrumb manager.
*
......@@ -15,6 +17,13 @@
*/
class BreadcrumbManager implements BreadcrumbBuilderInterface {
/**
* The module handler to invoke the alter hook.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Holds arrays of breadcrumb builders, keyed by priority.
*
......@@ -31,6 +40,16 @@ class BreadcrumbManager implements BreadcrumbBuilderInterface {
*/
protected $sortedBuilders;
/**
* Constructs a \Drupal\Core\Breadcrumb\BreadcrumbManager object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* Adds another breadcrumb builder.
*
......@@ -49,25 +68,30 @@ public function addBuilder(BreadcrumbBuilderInterface $builder, $priority) {
* {@inheritdoc}
*/
public function build(array $attributes) {
$breadcrumb = array();
$context = array('builder' => NULL);
// Call the build method of registered breadcrumb builders,
// until one of them returns an array.
foreach ($this->getSortedBuilders() as $builder) {
$breadcrumb = $builder->build($attributes);
if (!isset($breadcrumb)) {
$build = $builder->build($attributes);
if (!isset($build)) {
// The builder returned NULL, so we continue with the other builders.
continue;
}
elseif (is_array($breadcrumb)) {
elseif (is_array($build)) {
// The builder returned an array of breadcrumb links.
return $breadcrumb;
$breadcrumb = $build;
$context['builder'] = $builder;
break;
}
else {
throw new \UnexpectedValueException(format_string('Invalid breadcrumb returned by !class::build().', array('!class' => get_class($builder))));
}
}
// Allow modules to alter the breadcrumb.
$this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $attributes, $context);
// Fall back to an empty breadcrumb.
return array();
return $breadcrumb;
}
/**
......
......@@ -91,7 +91,7 @@ public function on403Html(FlattenException $exception, Request $request) {
$request->query->set('destination', $system_path);
}
$subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
$subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
// The active trail is being statically cached from the parent request to
// the subrequest, like any other static. Unfortunately that means the
......@@ -164,7 +164,7 @@ public function on404Html(FlattenException $exception, Request $request) {
// that and sub-call the kernel rather than using meah().
// @todo The create() method expects a slash-prefixed path, but we store a
// normal system path in the site_404 variable.
$subrequest = Request::create('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all());
$subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
// The active trail is being statically cached from the parent request to
// the subrequest, like any other static. Unfortunately that means the
......
services:
book.breadcrumb:
class: Drupal\book\BookBreadcrumbBuilder
arguments: ['@entity.manager', '@string_translation', '@link_generator', '@access_manager']
tags:
- { name: breadcrumb_builder, priority: 701 }
book.manager:
class: Drupal\book\BookManager
arguments: ['@database', '@entity.manager', '@string_translation', '@config.factory']
......
<?php
/**
* @file
* Contains \Drupal\book\BookBreadcrumbBuilder.
*/
namespace Drupal\book;
use Drupal\Core\Access\AccessManager;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Drupal\node\NodeInterface;
/**
* Provides a breadcrumb builder for nodes in a book.
*/
class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface {
/**
* The menu link storage controller.
*
* @var \Drupal\menu_link\MenuLinkStorageControllerInterface
*/
protected $menuLinkStorage;
/**
* The translation manager service.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface;
*/
protected $translation;
/**
* The link generator service.
*
* @var \Drupal\Core\Utility\LinkGeneratorInterface
*/
protected $linkGenerator;
/**
* The access manager.
*
* @var \Drupal\Core\Access\AccessManager
*/
protected $accessManager;
/**
* Constructs the BookBreadcrumbBuilder.
*
* @param \Drupal\Core\Entity\EntityManager $entity_manager
* The entity manager service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The translation manager service.
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
* The link generator.
* @param \Drupal\Core\Access\AccessManager $access_manager
* The access manager.
*/
public function __construct(EntityManager $entity_manager, TranslationInterface $translation, LinkGeneratorInterface $link_generator, AccessManager $access_manager) {
$this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
$this->translation = $translation;
$this->linkGenerator = $link_generator;
$this->accessManager = $access_manager;
}
/**
* {@inheritdoc}
*/
public function build(array $attributes) {
if (!empty($attributes['node']) && $attributes['node'] instanceof NodeInterface && !empty($attributes['node']->book)) {
$mlids = array();
$links = array($this->linkGenerator->generate($this->t('Home'), '<front>'));
$book = $attributes['node']->book;
$depth = 1;
// We skip the current node.
while (!empty($book['p' . ($depth + 1)])) {
$mlids[] = $book['p' . $depth];
$depth++;
}
$menu_links = $this->menuLinkStorage->loadMultiple($mlids);
if (count($menu_links) > 0) {
$depth = 1;
while (!empty($book['p' . ($depth + 1)])) {
if (!empty($menu_links[$book['p' . $depth]]) && ($menu_link = $menu_links[$book['p' . $depth]])) {
if ($this->accessManager->checkNamedRoute($menu_link->route_name, $menu_link->route_parameters)) {
$links[] = $this->linkGenerator->generate($menu_link->label(), $menu_link->route_name, $menu_link->route_parameters, $menu_link->options);
}
}
$depth++;
}
}
return $links;
}
}
/**
* Translates a string to the current language or to a given language.
*
* See the t() documentation for details.
*/
protected function t($string, array $args = array(), array $options = array()) {
return $this->translation->translate($string, $args, $options);
}
}
......@@ -81,14 +81,20 @@ public function routes(RouteBuildEvent $event) {
}
$route = new Route(
"$path/fields",
array('_form' => '\Drupal\field_ui\FieldOverview') + $defaults,
array(
'_form' => '\Drupal\field_ui\FieldOverview',
'_title' => 'Manage fields',
) + $defaults,
array('_permission' => 'administer ' . $entity_type . ' fields')
);
$collection->add("field_ui.overview_$entity_type", $route);
$route = new Route(
"$path/form-display",
array('_form' => '\Drupal\field_ui\FormDisplayOverview') + $defaults,
array(
'_form' => '\Drupal\field_ui\FormDisplayOverview',
'_title' => 'Manage form display',
) + $defaults,
array('_permission' => 'administer ' . $entity_type . ' form display')
);
$collection->add("field_ui.form_display_overview_$entity_type", $route);
......@@ -106,7 +112,10 @@ public function routes(RouteBuildEvent $event) {
$route = new Route(
"$path/display",
array('_form' => '\Drupal\field_ui\DisplayOverview') + $defaults,
array(
'_form' => '\Drupal\field_ui\DisplayOverview',
'_title' => 'Manage display',
) + $defaults,
array('_permission' => 'administer ' . $entity_type . ' display')
);
$collection->add("field_ui.display_overview_$entity_type", $route);
......
......@@ -17,6 +17,7 @@ filter.admin_overview:
defaults:
_content: '\Drupal\Core\Entity\Controller\EntityListController::listing'
entity_type: 'filter_format'
_title: 'Text formats and editors'
requirements:
_permission: 'administer filters'
......
......@@ -46,12 +46,17 @@ public function processInbound($path, Request $request) {
$rest = preg_replace('|^' . $path_prefix . '|', '', $path);
// Get the image style, scheme and path.
list($image_style, $scheme, $file) = explode('/', $rest, 3);
if (substr_count($rest, '/') >= 2) {
list($image_style, $scheme, $file) = explode('/', $rest, 3);
// Set the file as query parameter.
$request->query->set('file', $file);
// Set the file as query parameter.
$request->query->set('file', $file);
return $path_prefix . $image_style . '/' . $scheme;
return $path_prefix . $image_style . '/' . $scheme;
}
else {
return $path;
}
}
}
......@@ -9,6 +9,7 @@ menu.overview_page:
path: '/admin/structure/menu'
defaults:
_entity_list: 'menu'
_title: 'Menus'
requirements:
_permission: 'administer menu'
......
<?php
/**
* @file
* Contains \Drupal\menu_link\MenuLinkBreadcrumbBuilder.
*/
namespace Drupal\menu_link;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
/**
* Class to define the menu_link breadcrumb builder.
*/
class MenuLinkBreadcrumbBuilder implements BreadcrumbBuilderInterface {
/**
* {@inheritdoc}
*/
public function build(array $attributes) {
// @todo Rewrite the implementation.
// Currently the result always array.
return menu_get_active_breadcrumb();
}
}
......@@ -76,17 +76,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'),
......
......@@ -6,6 +6,8 @@
*/
use Drupal\menu_link\Entity\MenuLink;
use Drupal\menu_link\MenuLinkInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
function menu_link_help($path, $arg) {
switch ($path) {
......@@ -195,3 +197,19 @@ function menu_link_maintain($module, $op, $link_path, $link_title = NULL) {
break;
}
}
/**
* Implements hook_system_breadcrumb_alter().
*/
function menu_link_system_breadcrumb_alter(array &$breadcrumb, array $attributes, array $context) {
// Custom breadcrumb behaviour for editing menu links, we append a link to
// the menu in which the link is found.
if (!empty($attributes[RouteObjectInterface::ROUTE_NAME]) && $attributes[RouteObjectInterface::ROUTE_NAME] == 'menu.link_edit' && !empty($attributes['menu_link'])) {
$menu_link = $attributes['menu_link'];
if (($menu_link instanceof MenuLinkInterface) && !$menu_link->isNew()) {
// Add a link to the menu admin screen.
$menu = entity_load('menu', $menu_link->menu_name);
$breadcrumb[] = Drupal::l($menu->label(), 'menu.menu_edit', array('menu' => $menu->id));
}
}
}
services:
menu_link.breadcrumb:
class: Drupal\menu_link\MenuLinkBreadcrumbBuilder
tags:
- { name: breadcrumb_builder, priority: 0 }
......@@ -31,13 +31,6 @@ public function add(EntityInterface $node_type) {
return node_add($node_type);
}
/**
* @todo Remove node_page_view().
*/
public function viewPage(NodeInterface $node) {
return node_page_view($node);
}
/**
* @todo Remove node_show().
*/
......
<?php
/**
* @file
* Contains \Drupal\node\Controller\NodeView.
*/
namespace Drupal\node\Controller;
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\EntityInterface;
use Drupal\node\NodeInterface;
/**
* Returns responses for Node routes.
*/
class NodeView {
/**
* @todo Remove node_page_view().
*/
public function page(NodeInterface $node) {
return node_page_view($node);
}
/**
* The _title_callback for the node.view route.
*
* @param \Drupal\node\NodeInterface $node
*/
public function pageTitle(NodeInterface $node) {
return String::checkPlain($node->label());
}
}
......@@ -1477,10 +1477,6 @@ function node_page_view(EntityInterface $node) {
$build = node_show($node);
// If there is a menu link to this node, the link becomes the last part
// of the active trail, and the link name becomes the page title.
// Thus, we must explicitly set the page title to be the node title.
$build['#title'] = String::checkPlain($node->label());
return $build;
}
......
......@@ -31,7 +31,8 @@ node.add:
node.view:
path: '/node/{node}'
defaults:
_content: '\Drupal\node\Controller\NodeController::viewPage'
_content: '\Drupal\node\Controller\NodeView::page'
_title_callback: '\Drupal\node\Controller\NodeView::pageTitle'
requirements:
_entity_access: 'node.view'
......@@ -77,6 +78,7 @@ node.overview_types:
defaults:
_content: '\Drupal\Core\Entity\Controller\EntityListController::listing'
entity_type: 'node_type'
_title: 'Content types'
requirements:
_permission: 'administer content types'
......
<?php
/**
* @file
* Contains \Drupal\system\PathBasedBreadcrumbBuilder.
*/
namespace Drupal\system;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Routing\RequestHelper;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Access\AccessManager;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;