diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml index 762e60e..283efd8 100644 --- a/core/modules/book/book.services.yml +++ b/core/modules/book/book.services.yml @@ -1,7 +1,7 @@ services: book.breadcrumb: class: Drupal\book\BookBreadcrumbBuilder - arguments: ['@router.route_provider', '@plugin.manager.entity', '@access_manager', '@string_translation', '@router.dynamic'] + arguments: ['@plugin.manager.entity', '@access_manager', '@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 0056065..80838bc 100644 --- a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php +++ b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php @@ -7,18 +7,72 @@ namespace Drupal\book; -use Drupal\menu_link\MenuLinkBreadcrumbBuilder; +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\Core\Access\AccessManager; +use Symfony\Cmf\Component\Routing\DynamicRouter; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Route; /** - * Provides a breadcrumb builder for blocks. + * Provides a breadcrumb builder for nodes in a book. */ -class BookBreadcrumbBuilder extends MenuLinkBreadcrumbBuilder { +class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface { + + /** + * The menu link storage controller. + * + * @var \Drupal\menu_link\MenuLinkStorageControllerInterface + */ + protected $menuLinkStorage; + + /** + * The menu link access service. + * + * @var \Drupal\Core\Access\AccessManager + */ + protected $accessManager; + + /** + * The translation manager service. + * + * @var \Drupal\Core\StringTranslation\TranslationInterface; + */ + protected $translation; + + + /** + * Constructs the MenuLinkBreadcrumbBuilder. + * + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider + * The route provider service. + * @param \Drupal\Core\Entity\EntityManager $entity_manager + * The entity manager service. + * @param \Drupal\Core\Access\AccessManager $access_manager + * The menu link access service. + * @param \Drupal\Core\StringTranslation\TranslationInterface $translation + * The translation manager service. + * @param \Symfony\Cmf\Component\Routing\DynamicRouter $dynamic_router + * The dynamic router service. + */ + public function __construct(EntityManager $entity_manager, AccessManager $access_manager, TranslationInterface $translation) { + $this->menuLinkStorage = $entity_manager->getStorageController('menu_link'); + $this->accessManager = $access_manager; + $this->translation = $translation; + } /** * {@inheritdoc} */ public function build(array $attributes) { - if (!empty($attributes['_system_path']) && isset($attributes['_drupal_menu_item']['map'][1]->book)) { + // @todo - like \Drupal\forum\ForumBreadcrumbBuilder this depends on the + // legacy non-route node view. It must be updated once that's converted. + if (!empty($attributes['_drupal_menu_item']) && !empty($attributes['_drupal_menu_item']['map'][1]->book)) { $mlids = array(); // @todo Replace with link generator service when // https://drupal.org/node/2047619 lands. @@ -35,28 +89,11 @@ public function build(array $attributes) { $depth = 1; while (!empty($book['p' . ($depth + 1)])) { 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)) { - $parameters = $this->getNamedParameters($route, $path_elements); - if ($this->accessManager->checkNamedRoute($menu_link->route_name, $parameters)) { - // @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 (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']); - } + // Legacy hook_menu page callback. + // @todo change this once thie node view route is converted. + if ($item = menu_get_item($menu_link->link_path)) { + if ($item['access']) { + $links[] = l($menu_link->label(), $menu_link->link_path, $menu_link->options); } } } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php deleted file mode 100644 index 996716e..0000000 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php +++ /dev/null @@ -1,244 +0,0 @@ -routeProvider = $route_provider; - $this->menuLinkStorage = $entity_manager->getStorageController('menu_link'); - $this->accessManager = $access_manager; - $this->translation = $translation; - $this->menuStorage = $entity_manager->getStorageController('menu'); - $this->dynamicRouter = $dynamic_router; - } - - /** - * {@inheritdoc} - */ - public function build(array $attributes) { - $links = array(); - - // 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); - // Copy the path elements for up-casting. - $pattern = implode('/', $path_elements); - $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 { - $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) {} - } - if (count($menu_links) > 0 && ($menu_link = reset($menu_links)) && !empty($menu_link->route_name)) { - try { - if ($route = $this->routeProvider->getRouteByName($menu_link->route_name)) { - $parameters = $this->getNamedParameters($route, $path_elements); - if ($this->accessManager->checkNamedRoute($menu_link->route_name, $parameters)) { - // _menu_item_localize expects an upcasted map. - $map = $path_elements; - $this->upcastParameters($route, $pattern, $map); - // @todo Move _menu_item_localize() to a service. - _menu_item_localize($menu_link, $map, FALSE); - // @todo Replace with link generator service when - // https://drupal.org/node/2047619 lands. - $links[] = l($menu_link->title, $pattern, $menu_link->options); - } - } - } - catch (RouteNotFoundException $e) { - continue; - } - } - else { - // Legacy hook_menu page callback. - // @todo Remove this once all routes are converted. - if ($item = menu_get_item($pattern)) { - // As menu_get_item will match paths with extra parameters, we need - // to ensure that the returned item and the pattern are an exact - // match. - if (substr_count($item['path'], '/') == substr_count($pattern, '/') && $item['access']) { - $links[] = l($item['title'], $pattern, $item['options']); - } - } - } - } - } - // @todo Replace with link generator service when - // https://drupal.org/node/2047619 lands. - $links[] = l($this->translation->translate('Home'), ''); - return array_reverse($links); - } - - /** - * Upcasts the path elements from their raw form. - * - * Path map is of form array('node', 1) when passed in. After upcasting raw - * values are replaced with their actual objects for example array('node', 1) - * would become array('node', \Drupal\node\Entity\Node); - * - * @param \Symfony\Component\Routing\Route $route - * The route object. - * @param string $href - * The request path. - * @param array $map - * The raw path map which is to be upcast. - */ - protected function upcastParameters(Route $route, $href, array &$map) { - $request = Request::create('/' . $href); - $request->attributes->set('_system_path', $href); - // Attempt to match this path to provide a fully built request. - try { - $request->attributes->add($this->dynamicRouter->matchRequest($request)); - } - catch (NotFoundHttpException $e) { - return FALSE; - } - - // Populate the map with any matching values from the request. - $path_bits = explode('/', trim($route->getPath(), '/')); - foreach ($map as $index => $map_item) { - $matches = array(); - // Search for placeholders wrapped by curly braces. For example, a path - // 'foo/{bar}/baz' would return 'bar'. - if (isset($path_bits[$index]) && preg_match('/{(?.*)}/', $path_bits[$index], $matches)) { - // If that placeholder is present on the request attributes, replace the - // placeholder in the map with the value. - if ($request->attributes->has($matches['placeholder'])) { - $map[$index] = $request->attributes->get($matches['placeholder']); - } - } - } - } - - /** - * Returns a named map of placeholder values. - * - * Path map is of form array('node', 1) when passed in. After mapping values - * that correspond with named placeholders in the path are returned, keyed by - * the placeholder name. For example array('node', 1) for path node/{node} - * would be returned as array('node' => 1). - * - * @param \Symfony\Component\Routing\Route $route - * The route object. - * @param array $map - * The path map, eg array('node', 1) for /node/1. - */ - protected function getNamedParameters(Route $route, array $map) { - // Populate the map with any matching values from the request. - $path_bits = explode('/', trim($route->getPath(), '/')); - $named = array(); - foreach ($map as $index => $map_item) { - $matches = array(); - // Search for placeholders wrapped by curly braces. For example, a path - // 'foo/{bar}/baz' would return 'bar'. - if (isset($path_bits[$index]) && preg_match('/{(?.*)}/', $path_bits[$index], $matches)) { - $named[$matches['placeholder']] = $map_item; - } - } - return $named; - } - -} diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml deleted file mode 100644 index 7161f42..0000000 --- a/core/modules/menu_link/menu_link.services.yml +++ /dev/null @@ -1,6 +0,0 @@ -services: - menu_link.breadcrumb: - class: Drupal\menu_link\MenuLinkBreadcrumbBuilder - arguments: ['@router.route_provider', '@plugin.manager.entity', '@access_manager', '@string_translation', '@router.dynamic'] - tags: - - { name: breadcrumb_builder, priority: 0 } diff --git a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php new file mode 100644 index 0000000..0edf725 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php @@ -0,0 +1,241 @@ +request = $request; + $this->routeProvider = $route_provider; + $this->menuLinkStorage = $entity_manager->getStorageController('menu_link'); + $this->accessManager = $access_manager; + $this->translation = $translation; + $this->menuStorage = $entity_manager->getStorageController('menu'); + $this->router = $router; + } + /** + * {@inheritdoc} + */ + public function build(array $attributes) { + $links = array(); + + // 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. + $path_elements = explode('/', trim($this->request->getPathInfo(), '/')); + while (count($path_elements) > 1) { + array_pop($path_elements); + // Copy the path elements for up-casting. + $pattern = implode('/', $path_elements); + $route_request = $this->getRequestForPath($pattern); + if ($route_request) { + $access = FALSE; + $route_name = $route_request->attributes->get('_route'); + if ($route_request->attributes->get('_legacy')) { + // @todo handle access check + $access = TRUE; + } + else { + debug(array_keys($route_request->attributes->all())); + $parameters = array(); //$route_request->attributes->get('_raw_variables'); + $access = $this->accessManager->checkNamedRoute($route_name, $parameters, $route_request); + } + if ($access) { + $links[] = l($pattern, $pattern); + } + } + + } + // @todo Replace with link generator service when + // https://drupal.org/node/2047619 lands. + $links[] = l($this->translation->translate('Home'), ''); + return array_reverse($links); + } + + /** + * Matches a path in the router. + * + * @param string $path + * The request path. + + * @return \Symfony\Component\HttpFoundation\Request + * A populated request object or NULL if the patch couldn't be matched. + */ + protected function getRequestForPath($path) { + $request = Request::create($path); + // Attempt to match this path to provide a fully built request. + try { + $request->attributes->add($this->router->matchRequest($request)); + return $request; + } + catch (NotFoundHttpException $e) { + return NULL; + } + catch (ResourceNotFoundException $e) { + return NULL; + } + } + + /** + * Upcasts the path elements from their raw form. + * + * Path map is of form array('node', 1) when passed in. After upcasting raw + * values are replaced with their actual objects for example array('node', 1) + * would become array('node', \Drupal\node\Entity\Node); + * + * @param \Symfony\Component\Routing\Route $route + * The route object. + * @param string $href + * The request path. + * @param array $map + * The raw path map which is to be upcast. + */ + protected function upcastParameters(Route $route, $href, array &$map) { + $request = Request::create('/' . $href); + $request->attributes->set('_system_path', $href); + // Attempt to match this path to provide a fully built request. + try { + $request->attributes->add($this->dynamicRouter->matchRequest($request)); + } + catch (NotFoundHttpException $e) { + return FALSE; + } + + // Populate the map with any matching values from the request. + $path_bits = explode('/', trim($route->getPath(), '/')); + foreach ($map as $index => $map_item) { + $matches = array(); + // Search for placeholders wrapped by curly braces. For example, a path + // 'foo/{bar}/baz' would return 'bar'. + if (isset($path_bits[$index]) && preg_match('/{(?.*)}/', $path_bits[$index], $matches)) { + // If that placeholder is present on the request attributes, replace the + // placeholder in the map with the value. + if ($request->attributes->has($matches['placeholder'])) { + $map[$index] = $request->attributes->get($matches['placeholder']); + } + } + } + } + + /** + * Returns a named map of placeholder values. + * + * Path map is of form array('node', 1) when passed in. After mapping values + * that correspond with named placeholders in the path are returned, keyed by + * the placeholder name. For example array('node', 1) for path node/{node} + * would be returned as array('node' => 1). + * + * @param \Symfony\Component\Routing\Route $route + * The route object. + * @param array $map + * The path map, eg array('node', 1) for /node/1. + */ + protected function getNamedParameters(Route $route, array $map) { + // Populate the map with any matching values from the request. + $path_bits = explode('/', trim($route->getPath(), '/')); + $named = array(); + foreach ($map as $index => $map_item) { + $matches = array(); + // Search for placeholders wrapped by curly braces. For example, a path + // 'foo/{bar}/baz' would return 'bar'. + if (isset($path_bits[$index]) && preg_match('/{(?.*)}/', $path_bits[$index], $matches)) { + $named[$matches['placeholder']] = $map_item; + } + } + return $named; + } + +} diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index 08d67ad..08e8ed8 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -10,6 +10,11 @@ services: class: Drupal\system\LegacyBreadcrumbBuilder tags: - {name: breadcrumb_builder, priority: 500} + system.breadcrumb.default: + class: Drupal\system\PathBasedBreadcrumbBuilder + arguments: ['@request', '@router.route_provider', '@plugin.manager.entity', '@access_manager', '@string_translation', '@router'] + tags: + - { name: breadcrumb_builder, priority: 0 } path_processor.files: class: Drupal\system\PathProcessor\PathProcessorFiles tags: