2015-08-17 17:00:26 -07:00
< ? php
namespace Drupal\system ;
use Drupal\Component\Utility\Unicode ;
use Drupal\Core\Access\AccessManagerInterface ;
2015-09-04 13:20:09 -07:00
use Drupal\Core\Breadcrumb\Breadcrumb ;
2015-08-17 17:00:26 -07:00
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface ;
use Drupal\Core\Config\ConfigFactoryInterface ;
use Drupal\Core\Controller\TitleResolverInterface ;
use Drupal\Core\Link ;
use Drupal\Core\ParamConverter\ParamNotConvertedException ;
use Drupal\Core\Path\CurrentPathStack ;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface ;
use Drupal\Core\Routing\RequestContext ;
use Drupal\Core\Routing\RouteMatch ;
use Drupal\Core\Routing\RouteMatchInterface ;
use Drupal\Core\Session\AccountInterface ;
use Drupal\Core\StringTranslation\StringTranslationTrait ;
use Drupal\Core\Url ;
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException ;
use Symfony\Component\Routing\Exception\MethodNotAllowedException ;
use Symfony\Component\Routing\Exception\ResourceNotFoundException ;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface ;
/**
* Class to define the menu_link breadcrumb builder .
*/
class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
use StringTranslationTrait ;
/**
* The router request context .
*
* @ var \Drupal\Core\Routing\RequestContext
*/
protected $context ;
/**
* The menu link access service .
*
* @ var \Drupal\Core\Access\AccessManagerInterface
*/
protected $accessManager ;
/**
* The dynamic router service .
*
* @ var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
*/
protected $router ;
/**
* The dynamic router service .
*
* @ var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
*/
protected $pathProcessor ;
/**
* Site config object .
*
* @ var \Drupal\Core\Config\Config
*/
protected $config ;
/**
* The title resolver .
*
* @ var \Drupal\Core\Controller\TitleResolverInterface
*/
protected $titleResolver ;
/**
* The current user object .
*
* @ var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser ;
/**
* Constructs the PathBasedBreadcrumbBuilder .
*
* @ param \Drupal\Core\Routing\RequestContext $context
* The router request context .
* @ param \Drupal\Core\Access\AccessManagerInterface $access_manager
* The menu link access service .
* @ param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
* The dynamic router service .
* @ param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
* The inbound path processor .
* @ param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service .
* @ param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver service .
* @ param \Drupal\Core\Session\AccountInterface $current_user
* The current user object .
* @ param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path .
*/
public function __construct ( RequestContext $context , AccessManagerInterface $access_manager , RequestMatcherInterface $router , InboundPathProcessorInterface $path_processor , ConfigFactoryInterface $config_factory , TitleResolverInterface $title_resolver , AccountInterface $current_user , CurrentPathStack $current_path ) {
$this -> context = $context ;
$this -> accessManager = $access_manager ;
$this -> router = $router ;
$this -> pathProcessor = $path_processor ;
$this -> config = $config_factory -> get ( 'system.site' );
$this -> titleResolver = $title_resolver ;
$this -> currentUser = $current_user ;
$this -> currentPath = $current_path ;
}
/**
* { @ inheritdoc }
*/
public function applies ( RouteMatchInterface $route_match ) {
return TRUE ;
}
/**
* { @ inheritdoc }
*/
public function build ( RouteMatchInterface $route_match ) {
2015-09-04 13:20:09 -07:00
$breadcrumb = new Breadcrumb ();
2015-08-17 17:00:26 -07:00
$links = array ();
// General path-based breadcrumbs. Use the actual request path, prior to
// resolving path aliases, so the breadcrumb can be defined by simply
// creating a hierarchy of path aliases.
$path = trim ( $this -> context -> getPathInfo (), '/' );
$path_elements = explode ( '/' , $path );
$exclude = array ();
// Don't show a link to the front-page path.
$front = $this -> config -> get ( 'page.front' );
$exclude [ $front ] = TRUE ;
// /user is just a redirect, so skip it.
// @todo Find a better way to deal with /user.
$exclude [ '/user' ] = TRUE ;
2016-05-04 14:35:41 -07:00
// Add the url.path.parent cache context. This code ignores the last path
// part so the result only depends on the path parents.
$breadcrumb -> addCacheContexts ([ 'url.path.parent' ]);
2015-08-17 17:00:26 -07:00
while ( count ( $path_elements ) > 1 ) {
array_pop ( $path_elements );
// Copy the path elements for up-casting.
$route_request = $this -> getRequestForPath ( '/' . implode ( '/' , $path_elements ), $exclude );
if ( $route_request ) {
$route_match = RouteMatch :: createFromRequest ( $route_request );
2015-09-04 13:20:09 -07:00
$access = $this -> accessManager -> check ( $route_match , $this -> currentUser , NULL , TRUE );
// The set of breadcrumb links depends on the access result, so merge
// the access result's cacheability metadata.
$breadcrumb = $breadcrumb -> addCacheableDependency ( $access );
if ( $access -> isAllowed ()) {
2015-08-17 17:00:26 -07:00
$title = $this -> titleResolver -> getTitle ( $route_request , $route_match -> getRouteObject ());
if ( ! isset ( $title )) {
// Fallback to using the raw path component as the title if the
// route is missing a _title or _title_callback attribute.
$title = str_replace ( array ( '-' , '_' ), ' ' , Unicode :: ucfirst ( end ( $path_elements )));
}
$url = Url :: fromRouteMatch ( $route_match );
$links [] = new Link ( $title , $url );
}
}
}
if ( $path && '/' . $path != $front ) {
// Add the Home link, except for the front page.
$links [] = Link :: createFromRoute ( $this -> t ( 'Home' ), '<front>' );
}
2015-09-04 13:20:09 -07:00
return $breadcrumb -> setLinks ( array_reverse ( $links ));
2015-08-17 17:00:26 -07:00
}
/**
* Matches a path in the router .
*
* @ param string $path
* The request path with a leading slash .
* @ param array $exclude
* An array of paths or system paths to skip .
*
* @ return \Symfony\Component\HttpFoundation\Request
* A populated request object or NULL if the path couldn ' t be matched .
*/
protected function getRequestForPath ( $path , array $exclude ) {
if ( ! empty ( $exclude [ $path ])) {
return NULL ;
}
// @todo Use the RequestHelper once https://www.drupal.org/node/2090293 is
// fixed.
$request = Request :: create ( $path );
// Performance optimization: set a short accept header to reduce overhead in
// AcceptHeaderMatcher when matching the request.
$request -> headers -> set ( 'Accept' , 'text/html' );
// Find the system path by resolving aliases, language prefix, etc.
$processed = $this -> pathProcessor -> processInbound ( $path , $request );
if ( empty ( $processed ) || ! empty ( $exclude [ $processed ])) {
// This resolves to the front page, which we already add.
return NULL ;
}
$this -> currentPath -> setPath ( $processed , $request );
// Attempt to match this path to provide a fully built request.
try {
$request -> attributes -> add ( $this -> router -> matchRequest ( $request ));
return $request ;
}
catch ( ParamNotConvertedException $e ) {
return NULL ;
}
catch ( ResourceNotFoundException $e ) {
return NULL ;
}
catch ( MethodNotAllowedException $e ) {
return NULL ;
}
catch ( AccessDeniedHttpException $e ) {
return NULL ;
}
}
}