Revert "Issue #3370946 by kunal.sachdev, lauriii, tim.plunkett, omkar.podey, alexpott, ckrina, smustgrave, larowlan, rkoller, hooroomoo, duadua: Page title should contextualize the local navigation"
This reverts commit ad606a8fb7
.
merge-requests/7287/head
parent
7f98626a9a
commit
4a34406a88
|
@ -1079,14 +1079,6 @@ services:
|
|||
class: Drupal\Core\Utility\LinkGenerator
|
||||
arguments: ['@url_generator', '@module_handler', '@renderer']
|
||||
Drupal\Core\Utility\LinkGeneratorInterface: '@link_generator'
|
||||
request_generator:
|
||||
class: Drupal\Core\Utility\RequestGenerator
|
||||
arguments: ['@path_processor_manager', '@path.current', '@router']
|
||||
Drupal\Core\Utility\RequestGenerator: '@request_generator'
|
||||
base_route_title_resolver:
|
||||
class: Drupal\Core\Controller\BaseRouteTitleResolver
|
||||
arguments: ['@url_generator', '@title_resolver', '@plugin.manager.menu.local_task', '@router.route_provider', '@request_generator']
|
||||
Drupal\Core\Utility\BaseRouteTitleResolver: '@base_route_title_resolver'
|
||||
router:
|
||||
class: Drupal\Core\Routing\AccessAwareRouter
|
||||
arguments: ['@router.no_access_checks', '@access_manager', '@current_user']
|
||||
|
|
|
@ -1319,31 +1319,11 @@ function template_preprocess_html(&$variables) {
|
|||
$variables['page']['#title'] = (string) \Drupal::service('renderer')->render($variables['page']['#title']);
|
||||
}
|
||||
if (!empty($variables['page']['#title'])) {
|
||||
$head_title = [];
|
||||
|
||||
// Marking the title as safe since it has had the tags stripped.
|
||||
$head_title['title'] = Markup::create(trim(strip_tags($variables['page']['#title'])));
|
||||
|
||||
$maintenance_mode = defined('MAINTENANCE_MODE') || \Drupal::state()->get('system.maintenance_mode');
|
||||
$is_admin_route = (bool) \Drupal::routeMatch()->getRouteObject()?->getOption('_admin_route');
|
||||
if (!$maintenance_mode && $is_admin_route) {
|
||||
$base_route_title = \Drupal::service('base_route_title_resolver')->getTitle(\Drupal::requestStack()->getCurrentRequest(), \Drupal::routeMatch()->getRouteObject());
|
||||
if (is_array($base_route_title)) {
|
||||
$base_route_title = (string) \Drupal::service('renderer')->render($base_route_title);
|
||||
}
|
||||
if ($base_route_title) {
|
||||
// Marking the title as safe since it has had the tags stripped.
|
||||
$base_route_title = Markup::create(trim(strip_tags($base_route_title)));
|
||||
|
||||
// If the base route title is not the same as the current title, append it
|
||||
// to the head title.
|
||||
if ((string) $base_route_title !== (string) $head_title['title']) {
|
||||
$head_title['base_route_title'] = $base_route_title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$head_title['name'] = $site_config->get('name');
|
||||
$head_title = [
|
||||
// Marking the title as safe since it has had the tags stripped.
|
||||
'title' => Markup::create(trim(strip_tags($variables['page']['#title']))),
|
||||
'name' => $site_config->get('name'),
|
||||
];
|
||||
}
|
||||
// @todo Remove once views is not bypassing the view subscriber anymore.
|
||||
// @see https://www.drupal.org/node/2068471
|
||||
|
|
|
@ -2,15 +2,9 @@
|
|||
|
||||
namespace Drupal\Core\Block\Plugin\Block;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Block\Attribute\Block;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Block\TitleBlockPluginInterface;
|
||||
use Drupal\Core\Controller\TitleResolverInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
|
@ -23,7 +17,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
|
|||
'settings_tray' => FALSE,
|
||||
]
|
||||
)]
|
||||
class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface, ContainerFactoryPluginInterface {
|
||||
class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface {
|
||||
|
||||
/**
|
||||
* The page title: a string (plain title) or a render array (formatted title).
|
||||
|
@ -32,58 +26,6 @@ class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface, Con
|
|||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* Constructs a new PageTitleBlock.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Controller\TitleResolverInterface|null $titleResolver
|
||||
* The title resolver.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface|null $routeMatch
|
||||
* The route match.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack|null $requestStack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Controller\TitleResolverInterface|null $baseRouteTitleResolver
|
||||
* The base route title resolver.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
protected ?TitleResolverInterface $titleResolver,
|
||||
protected ?RouteMatchInterface $routeMatch,
|
||||
protected ?RequestStack $requestStack,
|
||||
protected ?TitleResolverInterface $baseRouteTitleResolver,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
if (!$this->titleResolver || !$this->routeMatch || !$this->requestStack || !$this->baseRouteTitleResolver) {
|
||||
@trigger_error('Calling PageTitleBlock::__construct() without the $titleResolver, $routeMatch, $requestStack, and $baseRouteTitleResolver arguments is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3397210', E_USER_DEPRECATED);
|
||||
$this->titleResolver = \Drupal::service('title_resolver');
|
||||
$this->routeMatch = \Drupal::service('current_route_match');
|
||||
$this->requestStack = \Drupal::service('request_stack');
|
||||
$this->baseRouteTitleResolver = \Drupal::service('base_route_title_resolver');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('title_resolver'),
|
||||
$container->get('current_route_match'),
|
||||
$container->get('request_stack'),
|
||||
$container->get('base_route_title_resolver'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -96,98 +38,17 @@ class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface, Con
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'label_display' => FALSE,
|
||||
'base_route_title' => FALSE,
|
||||
];
|
||||
return ['label_display' => FALSE];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$title = $this->title;
|
||||
if ($this->configuration['base_route_title']) {
|
||||
$base_route_title = $this->getTitleBasedOnBaseRoute();
|
||||
if (!is_null($base_route_title)) {
|
||||
$title = $base_route_title;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'#type' => 'page_title',
|
||||
'#title' => $title,
|
||||
'#title' => $this->title,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) : array {
|
||||
$form['base_route_title'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Title to be displayed'),
|
||||
'#options' => [
|
||||
0 => $this->t('Current page title'),
|
||||
1 => $this->t('Section page title'),
|
||||
],
|
||||
'#default_value' => (int) $this->configuration['base_route_title'],
|
||||
'#description' => $this->t('Choose whether to display the title of the current page or the current section. The section page title is preferred if the title is displayed before local tasks and if it is displayed after local tasks then the current page title is preferred.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) : void {
|
||||
$this->configuration['base_route_title'] = (bool) $form_state->getValue('base_route_title');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets title based on base route.
|
||||
*
|
||||
* @return array|string|null|\Stringable
|
||||
* The title based on base route.
|
||||
*/
|
||||
private function getTitleBasedOnBaseRoute(): array|string|null|\Stringable {
|
||||
$controller_title = $this->titleResolver->getTitle($this->requestStack->getCurrentRequest(), $this->routeMatch->getRouteObject());
|
||||
|
||||
// Controller render arrays using `#title` take precedent over the title
|
||||
// resolvers.
|
||||
if ((string) $this->titleToString($controller_title) !== (string) $this->titleToString($this->title)) {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
$base_route_title = $this->baseRouteTitleResolver->getTitle($this->requestStack->getCurrentRequest(), $this->routeMatch->getRouteObject());
|
||||
if (!is_null($base_route_title)) {
|
||||
// If the titles are equal, return the original title.
|
||||
if ((string) $this->titleToString($base_route_title) === (string) $this->titleToString($this->title)) {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
return $this->t('@section_title<span class="visually-hidden">: @current_title</span>', [
|
||||
'@section_title' => $this->titleToString($base_route_title),
|
||||
'@current_title' => $this->titleToString($this->title),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts title to string.
|
||||
*
|
||||
* @param array|string|null|\Stringable $title
|
||||
* A title that could be an array, string or stringable object.
|
||||
*
|
||||
* @return string|\Stringable
|
||||
*/
|
||||
private function titleToString(array|string|null|\Stringable $title): string|\Stringable {
|
||||
if (is_array($title)) {
|
||||
$title = \Drupal::service('renderer')->render($title);
|
||||
}
|
||||
|
||||
return $title ?? '';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Controller;
|
||||
|
||||
use Drupal\Core\Menu\LocalTaskManager;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Drupal\Core\Utility\RequestGenerator;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Exception\InvalidParameterException;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides a class which gets the title from the current local-task base route.
|
||||
*/
|
||||
class BaseRouteTitleResolver implements TitleResolverInterface {
|
||||
|
||||
/**
|
||||
* Constructs a RequestGenerator object.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $urlGenerator
|
||||
* The url generator.
|
||||
* @param \Drupal\Core\Controller\TitleResolverInterface $titleResolver
|
||||
* The title resolver.
|
||||
* @param \Drupal\Core\Menu\LocalTaskManager $localTaskManager
|
||||
* The local task manager.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider
|
||||
* The route provider.
|
||||
* @param \Drupal\Core\Utility\RequestGenerator $requestGenerator
|
||||
* The request generator.
|
||||
*/
|
||||
public function __construct(
|
||||
protected UrlGeneratorInterface $urlGenerator,
|
||||
protected TitleResolverInterface $titleResolver,
|
||||
protected LocalTaskManager $localTaskManager,
|
||||
protected RouteProviderInterface $routeProvider,
|
||||
protected RequestGenerator $requestGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle(Request $request, Route $route) : array|string|\Stringable|null {
|
||||
$route_match = RouteMatch::createFromRequest($request);
|
||||
$base_route_names = $this->localTaskManager->getBaseRouteNames($route_match->getRouteName());
|
||||
$title = NULL;
|
||||
if ($base_route_names) {
|
||||
$base_route_name = reset($base_route_names);
|
||||
if ($base_route_name !== $route_match->getRouteName()) {
|
||||
try {
|
||||
$path = $this->urlGenerator->getPathFromRoute($base_route_name, $route_match->getRawParameters()->all());
|
||||
}
|
||||
catch (RouteNotFoundException | InvalidParameterException) {
|
||||
return NULL;
|
||||
}
|
||||
$route_request = $this->requestGenerator->generateRequestForPath($path, []);
|
||||
if ($route_request) {
|
||||
$title = $this->titleResolver->getTitle($route_request, $this->routeProvider->getRouteByName($base_route_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
}
|
|
@ -197,11 +197,58 @@ class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerI
|
|||
public function getLocalTasksForRoute($route_name) {
|
||||
if (!isset($this->instances[$route_name])) {
|
||||
$this->instances[$route_name] = [];
|
||||
$data = $this->getLocalTasksDataForRoute($route_name);
|
||||
$base_routes = $data['base_routes'];
|
||||
$children = $data['children'];
|
||||
$parents = $data['parents'];
|
||||
|
||||
if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $route_name)) {
|
||||
$base_routes = $cache->data['base_routes'];
|
||||
$parents = $cache->data['parents'];
|
||||
$children = $cache->data['children'];
|
||||
}
|
||||
else {
|
||||
$definitions = $this->getDefinitions();
|
||||
// We build the hierarchy by finding all tabs that should
|
||||
// appear on the current route.
|
||||
$base_routes = [];
|
||||
$parents = [];
|
||||
$children = [];
|
||||
foreach ($definitions as $plugin_id => $task_info) {
|
||||
// Fill in the base_route from the parent to insure consistency.
|
||||
if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
|
||||
$task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
|
||||
// Populate the definitions we use in the next loop. Using a
|
||||
// reference like &$task_info causes bugs.
|
||||
$definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
|
||||
}
|
||||
if ($route_name == $task_info['route_name']) {
|
||||
if (!empty($task_info['base_route'])) {
|
||||
$base_routes[$task_info['base_route']] = $task_info['base_route'];
|
||||
}
|
||||
// Tabs that link to the current route are viable parents
|
||||
// and their parent and children should be visible also.
|
||||
// @todo - this only works for 2 levels of tabs.
|
||||
// instead need to iterate up.
|
||||
$parents[$plugin_id] = TRUE;
|
||||
if (!empty($task_info['parent_id'])) {
|
||||
$parents[$task_info['parent_id']] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($base_routes) {
|
||||
// Find all the plugins with the same root and that are at the top
|
||||
// level or that have a visible parent.
|
||||
foreach ($definitions as $plugin_id => $task_info) {
|
||||
if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
|
||||
// Concat '> ' with root ID for the parent of top-level tabs.
|
||||
$parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
|
||||
$children[$parent][$plugin_id] = $task_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = [
|
||||
'base_routes' => $base_routes,
|
||||
'parents' => $parents,
|
||||
'children' => $children,
|
||||
];
|
||||
$this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, Cache::PERMANENT, $this->cacheTags);
|
||||
}
|
||||
// Create a plugin instance for each element of the hierarchy.
|
||||
foreach ($base_routes as $base_route) {
|
||||
// Convert the tree keyed by plugin IDs into a simple one with
|
||||
|
@ -364,80 +411,4 @@ class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerI
|
|||
return $active;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBaseRouteNames(string $route_name): array {
|
||||
$data = $this->getLocalTasksDataForRoute($route_name);
|
||||
return $data['base_routes'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local task data for the given route.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The route name.
|
||||
*
|
||||
* @return array
|
||||
* The local task data with the following keys:
|
||||
* - base_routes: An array of base route names of the given route.
|
||||
* - children: An array of the route's child local task definitions keyed by
|
||||
* local task ID.
|
||||
* - parents: An array of the route's parent local task definitions.
|
||||
*/
|
||||
protected function getLocalTasksDataForRoute(string $route_name): array {
|
||||
if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $route_name)) {
|
||||
$data = $cache->data;
|
||||
}
|
||||
else {
|
||||
$definitions = $this->getDefinitions();
|
||||
// We build the hierarchy by finding all tabs that should
|
||||
// appear on the current route.
|
||||
$base_routes = [];
|
||||
$parents = [];
|
||||
$children = [];
|
||||
foreach ($definitions as $plugin_id => $task_info) {
|
||||
// Fill in the base_route from the parent to insure consistency.
|
||||
if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
|
||||
$task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
|
||||
// Populate the definitions we use in the next loop. Using a
|
||||
// reference like &$task_info causes bugs.
|
||||
$definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
|
||||
}
|
||||
if ($route_name == $task_info['route_name']) {
|
||||
if (!empty($task_info['base_route'])) {
|
||||
$base_routes[$task_info['base_route']] = $task_info['base_route'];
|
||||
}
|
||||
// Tabs that link to the current route are viable parents
|
||||
// and their parent and children should be visible also.
|
||||
// @todo - this only works for 2 levels of tabs.
|
||||
// instead need to iterate up.
|
||||
$parents[$plugin_id] = TRUE;
|
||||
if (!empty($task_info['parent_id'])) {
|
||||
$parents[$task_info['parent_id']] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($base_routes) {
|
||||
// Find all the plugins with the same root and that are at the top
|
||||
// level or that have a visible parent.
|
||||
foreach ($definitions as $plugin_id => $task_info) {
|
||||
if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
|
||||
// Concat '> ' with root ID for the parent of top-level tabs.
|
||||
$parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
|
||||
$children[$parent][$plugin_id] = $task_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = [
|
||||
'base_routes' => $base_routes,
|
||||
'parents' => $parents,
|
||||
'children' => $children,
|
||||
];
|
||||
$this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, Cache::PERMANENT, $this->cacheTags);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -70,15 +70,4 @@ interface LocalTaskManagerInterface extends PluginManagerInterface {
|
|||
*/
|
||||
public function getLocalTasks($route_name, $level = 0);
|
||||
|
||||
/**
|
||||
* Gets base route names for the given route.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The route name.
|
||||
*
|
||||
* @return string[]
|
||||
* The base route names.
|
||||
*/
|
||||
public function getBaseRouteNames(string $route_name): array;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||
|
||||
/**
|
||||
* Provides a class which generates a request.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class RequestGenerator {
|
||||
|
||||
/**
|
||||
* Constructs a RequestGenerator object.
|
||||
*
|
||||
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $pathProcessor
|
||||
* The inbound path processor.
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $currentPath
|
||||
* The current path.
|
||||
* @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
|
||||
* The dynamic router service.
|
||||
*/
|
||||
public function __construct(
|
||||
protected InboundPathProcessorInterface $pathProcessor,
|
||||
protected CurrentPathStack $currentPath,
|
||||
protected RequestMatcherInterface $router,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a request by matching 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|null
|
||||
* A populated request object or NULL if the path couldn't be matched.
|
||||
*/
|
||||
public function generateRequestForPath(string $path, array $exclude): ?Request {
|
||||
if (!empty($exclude[$path])) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$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 | ResourceNotFoundException | MethodNotAllowedException | AccessDeniedHttpException | NotFoundHttpException) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,9 +5,6 @@
|
|||
* Post update functions for Block.
|
||||
*/
|
||||
|
||||
use Drupal\block\BlockInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
|
||||
|
||||
/**
|
||||
* Implements hook_removed_post_updates().
|
||||
*/
|
||||
|
@ -19,19 +16,3 @@ function block_removed_post_updates() {
|
|||
'block_post_update_replace_node_type_condition' => '10.0.0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'base_route_title' setting for page title blocks.
|
||||
*/
|
||||
function block_post_update_add_base_route_title_page_title(&$sandbox = NULL): void {
|
||||
\Drupal::classResolver(ConfigEntityUpdater::class)
|
||||
->update($sandbox, 'block', function (BlockInterface $block) {
|
||||
if ($block->get('plugin') === 'page_title_block') {
|
||||
$settings = $block->get('settings');
|
||||
$settings['base_route_title'] = $block->get('theme') === 'claro';
|
||||
$block->set('settings', $settings);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests page title block.
|
||||
*
|
||||
* @group Block
|
||||
*/
|
||||
class PageTitleBlockTest extends BrowserTestBase {
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['block', 'update', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Add the page title block to the page.
|
||||
$this->drupalPlaceBlock('page_title_block', ['id' => 'stark_page_title']);
|
||||
|
||||
// Create node type.
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
|
||||
// Create administrative user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer blocks',
|
||||
'administer themes',
|
||||
'administer software updates',
|
||||
'administer nodes',
|
||||
'administer modules',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
'delete any article content',
|
||||
'delete any article content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the contextualized title is displayed.
|
||||
*/
|
||||
public function testContextualizeTitle(): void {
|
||||
$edit['admin_theme'] = $this->defaultTheme;
|
||||
$this->drupalGet('admin/appearance');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Make sure the title shown is non-contextualized.
|
||||
$this->drupalGet('admin/modules/update');
|
||||
$this->assertSession()->elementTextEquals('css', 'h1', 'Update');
|
||||
|
||||
// Checking if the title block is configured for showing
|
||||
// contextualized title and if it's not then configure it.
|
||||
$this->drupalGet('admin/structure/block/manage/' . $this->defaultTheme . '_page_title');
|
||||
// In stark theme it's not configured to show contextualized title
|
||||
// therefore the value of the configuration will be 1 otherwise 0.
|
||||
// @see \Drupal\Core\Block\Plugin\Block\PageTitleBlock::blockForm()
|
||||
$this->assertSession()->fieldValueEquals('settings[base_route_title]', 0);
|
||||
$this->submitForm(['settings[base_route_title]' => 1], 'Save block');
|
||||
|
||||
// Make sure the title shown is contextualized.
|
||||
$this->drupalGet('admin/modules/update');
|
||||
$this->assertSession()->elementTextEquals('css', 'h1', 'Extend: Update');
|
||||
$title = $this->assertSession()->elementExists('xpath', '//h1');
|
||||
$this->assertSession()->elementExists('xpath', '/span[@class="visually-hidden"]', $title);
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1/span', ': Update');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the contextualized title if base route is not accessible.
|
||||
*/
|
||||
public function testContextualizeTitleWhenBaseRouteIsNotAccessible(): void {
|
||||
// Create administrative user with access to 'admin/modules/update' but no
|
||||
// access to base route 'admin/modules'.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer blocks',
|
||||
'administer software updates',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$non_contextualized_title = 'Update';
|
||||
// Make sure the title shown is non-contextualized.
|
||||
$this->drupalGet('admin/modules/update');
|
||||
$this->assertSession()->elementTextEquals('css', 'h1', $non_contextualized_title);
|
||||
|
||||
// Configure title block to show contextualized title.
|
||||
$this->drupalGet('admin/structure/block/manage/' . $this->defaultTheme . '_page_title');
|
||||
$this->assertSession()->fieldValueEquals('settings[base_route_title]', 0);
|
||||
$this->submitForm(['settings[base_route_title]' => 1], 'Save block');
|
||||
|
||||
// Make sure the title shown is non-contextualized because the base route is
|
||||
// not accessible.
|
||||
// @see \Drupal\Core\Block\Plugin\Block\PageTitleBlock::getTitleBasedOnBaseRoute()
|
||||
$this->drupalGet('admin/modules/update');
|
||||
$this->assertSession()->elementTextEquals('css', 'h1', $non_contextualized_title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testContextualizeTitleOnNodeOperationPages().
|
||||
*
|
||||
* @return array[][]
|
||||
* The test cases.
|
||||
*/
|
||||
public function providerTestContextualizeTitleOnNodeOperationPages() : array {
|
||||
return [
|
||||
'node with random title' => [$this->randomMachineName(8)],
|
||||
'node with title set to 0' => ['0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if contextualized title displayed on all node operation pages.
|
||||
*
|
||||
* @dataProvider providerTestContextualizeTitleOnNodeOperationPages
|
||||
*/
|
||||
public function testContextualizeTitleOnNodeOperationPages($node_title): void {
|
||||
$settings = [
|
||||
'type' => 'article',
|
||||
'title' => $node_title,
|
||||
];
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Make sure the non-contextualized title is shown on all node operation
|
||||
// pages.
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', $node_title);
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', "Edit Article $node_title");
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/delete');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', "Are you sure you want to delete the content item $node_title?");
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/revisions');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', "Revisions for $node_title");
|
||||
|
||||
// Configure title block to show contextualized title.
|
||||
$this->drupalGet('admin/structure/block/manage/' . $this->defaultTheme . '_page_title');
|
||||
$this->submitForm(['settings[base_route_title]' => 1], 'Save block');
|
||||
|
||||
// Make sure the contextualized title is shown on all node operation pages. On all pages the title is same as before
|
||||
// because we don't change the title if it's already overridden.
|
||||
// @see \Drupal\Core\Block\Plugin\Block\PageTitleBlock::getTitleBasedOnBaseRoute()
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', $node_title);
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', "Edit Article $node_title");
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/delete');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', "Are you sure you want to delete the content item $node_title?");
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/revisions');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//h1', "Revisions for $node_title");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the update path for block title.
|
||||
*
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class BlockTitleUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.filled.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testPostUpdateAddContextualizePageTitle().
|
||||
*
|
||||
* @return array[][]
|
||||
* The test cases.
|
||||
*/
|
||||
public function providerTestPostUpdateAddContextualizePageTitle() : array {
|
||||
return [
|
||||
'Stark theme' => ['stark', FALSE],
|
||||
// For claro the 'base_route_title' configuration is enabled by default.
|
||||
'Claro theme' => ['claro', TRUE],
|
||||
'Olivero theme' => ['olivero', FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that title block is configured properly after update.
|
||||
*
|
||||
* @dataProvider providerTestPostUpdateAddContextualizePageTitle
|
||||
*/
|
||||
public function testPostUpdateAddContextualizePageTitle(string $theme, bool $contextual_title_enabled): void {
|
||||
$block_settings = $this->config('block.block.' . $theme . '_page_title')->get('settings');
|
||||
$this->assertArrayNotHasKey('base_route_title', $block_settings);
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
$block_settings = $this->config('block.block.' . $theme . '_page_title')->get('settings');
|
||||
$this->assertArrayHasKey('base_route_title', $block_settings);
|
||||
$this->assertSame($block_settings['base_route_title'], $contextual_title_enabled);
|
||||
}
|
||||
|
||||
}
|
|
@ -154,7 +154,7 @@ class BlockContentListTest extends BlockContentTestBase {
|
|||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id() . '/delete');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | $new_label | Drupal");
|
||||
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | Drupal");
|
||||
$this->submitForm([], 'Delete');
|
||||
|
||||
// Verify that the text of the label and machine name does not appear in
|
||||
|
|
|
@ -148,7 +148,7 @@ class BlockContentListViewsTest extends BlockContentTestBase {
|
|||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id() . '/delete');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | $new_label | Drupal");
|
||||
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | Drupal");
|
||||
$this->submitForm([], 'Delete');
|
||||
|
||||
// Verify that the text of the label and machine name does not appear in
|
||||
|
|
|
@ -45,7 +45,7 @@ class FieldUIRouteTest extends BrowserTestBase {
|
|||
$this->assertSession()->pageTextContains('No fields are present yet.');
|
||||
|
||||
$this->drupalGet('admin/config/people/accounts/fields');
|
||||
$this->assertSession()->titleEquals('Manage fields | Account settings | Drupal');
|
||||
$this->assertSession()->titleEquals('Manage fields | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
// Test manage display tabs and titles.
|
||||
|
@ -53,13 +53,13 @@ class FieldUIRouteTest extends BrowserTestBase {
|
|||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
$this->drupalGet('admin/config/people/accounts/display');
|
||||
$this->assertSession()->titleEquals('Manage display | Account settings | Drupal');
|
||||
$this->assertSession()->titleEquals('Manage display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
$edit = ['display_modes_custom[compact]' => TRUE];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet('admin/config/people/accounts/display/compact');
|
||||
$this->assertSession()->titleEquals('Manage display | Account settings | Drupal');
|
||||
$this->assertSession()->titleEquals('Manage display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
// Test manage form display tabs and titles.
|
||||
|
@ -67,14 +67,14 @@ class FieldUIRouteTest extends BrowserTestBase {
|
|||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
$this->drupalGet('admin/config/people/accounts/form-display');
|
||||
$this->assertSession()->titleEquals('Manage form display | Account settings | Drupal');
|
||||
$this->assertSession()->titleEquals('Manage form display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
$edit = ['display_modes_custom[register]' => TRUE];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->drupalGet('admin/config/people/accounts/form-display/register');
|
||||
$this->assertSession()->titleEquals('Manage form display | Account settings | Drupal');
|
||||
$this->assertSession()->titleEquals('Manage form display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
// Test that default secondary tab is in first position.
|
||||
$this->assertSession()->elementsCount('xpath', "//ul/li[1]/a[contains(text(), 'Default')]", 1);
|
||||
|
|
|
@ -531,10 +531,10 @@ class LayoutBuilderTest extends LayoutBuilderTestBase {
|
|||
// The original node title is available when viewing the node, but the
|
||||
// pending title is visible within the Layout Builder UI.
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->elementTextContains('css', 'body', 'The first node title');
|
||||
$assert_session->pageTextContains('The first node title');
|
||||
$page->clickLink('Layout');
|
||||
$assert_session->elementTextNotContains('css', 'body', 'The first node title');
|
||||
$assert_session->elementTextContains('css', 'body', 'The pending title of the first node');
|
||||
$assert_session->pageTextNotContains('The first node title');
|
||||
$assert_session->pageTextContains('The pending title of the first node');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -380,14 +380,6 @@ system.advisories:
|
|||
type: integer
|
||||
label: 'How often to check for security advisories, in hours'
|
||||
|
||||
block.settings.page_title_block:
|
||||
type: block_settings
|
||||
label: 'Page title block'
|
||||
mapping:
|
||||
base_route_title:
|
||||
type: boolean
|
||||
label: 'Display section title instead of current title'
|
||||
|
||||
block.settings.system_branding_block:
|
||||
type: block_settings
|
||||
label: 'Branding block'
|
||||
|
|
|
@ -8,8 +8,9 @@ use Drupal\Core\Breadcrumb\Breadcrumb;
|
|||
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Controller\TitleResolverInterface;
|
||||
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\Core\Path\PathMatcherInterface;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\Routing\RequestContext;
|
||||
|
@ -18,7 +19,11 @@ use Drupal\Core\Routing\RouteMatchInterface;
|
|||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Utility\RequestGenerator;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||
|
||||
/**
|
||||
|
@ -27,20 +32,7 @@ use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
|||
* @see \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface
|
||||
*/
|
||||
class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
use DeprecatedServicePropertyTrait;
|
||||
|
||||
/**
|
||||
* Defines deprecated injected properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $deprecatedProperties = [
|
||||
'router' => 'router',
|
||||
'pathProcessor' => 'path_processor_manager',
|
||||
'currentPath' => 'path.current',
|
||||
];
|
||||
|
||||
/**
|
||||
* The router request context.
|
||||
|
@ -56,6 +48,20 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The dynamic router service.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* The inbound path processor.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
|
||||
*/
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* Site config object.
|
||||
*
|
||||
|
@ -77,6 +83,13 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The current path service.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* The patch matcher service.
|
||||
*
|
||||
|
@ -84,13 +97,6 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
*/
|
||||
protected $pathMatcher;
|
||||
|
||||
/**
|
||||
* The request generator.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\RequestGenerator
|
||||
*/
|
||||
protected $requestGenerator;
|
||||
|
||||
/**
|
||||
* Constructs the PathBasedBreadcrumbBuilder.
|
||||
*
|
||||
|
@ -98,44 +104,31 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
* The router request context.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access check service.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface|\Symfony\Component\Routing\Matcher\RequestMatcherInterface $config_factory
|
||||
* @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|\Drupal\Core\PathProcessor\InboundPathProcessorInterface $title_resolver
|
||||
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
|
||||
* The title resolver service.
|
||||
* @param \Drupal\Core\Session\AccountInterface|\Drupal\Core\Config\ConfigFactoryInterface $current_user
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user object.
|
||||
* @param \Drupal\Core\Path\PathMatcherInterface|\Drupal\Core\Controller\TitleResolverInterface $path_matcher
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
||||
* The current path.
|
||||
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
|
||||
* The path matcher service.
|
||||
* @param \Drupal\Core\Utility\RequestGenerator|\Drupal\Core\Session\AccountInterface $request_generator
|
||||
* The request generator.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestContext $context,
|
||||
AccessManagerInterface $access_manager,
|
||||
ConfigFactoryInterface|RequestMatcherInterface $config_factory,
|
||||
TitleResolverInterface|InboundPathProcessorInterface $title_resolver,
|
||||
AccountInterface|ConfigFactoryInterface $current_user,
|
||||
PathMatcherInterface|TitleResolverInterface $path_matcher,
|
||||
RequestGenerator|AccountInterface $request_generator,
|
||||
) {
|
||||
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, PathMatcherInterface $path_matcher = NULL) {
|
||||
$this->context = $context;
|
||||
$this->accessManager = $access_manager;
|
||||
if ($config_factory instanceof RequestMatcherInterface) {
|
||||
@trigger_error('Calling PathBasedBreadcrumbBuilder::__construct() with the $router, $path_processor, $current_path arguments is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3397213', E_USER_DEPRECATED);
|
||||
@trigger_error('Calling PathBasedBreadcrumbBuilder::__construct() without the $request_generator argument is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3397213', E_USER_DEPRECATED);
|
||||
$config_factory = $current_user;
|
||||
$this->titleResolver = $path_matcher;
|
||||
$this->currentUser = $request_generator;
|
||||
$this->requestGenerator = \Drupal::service('request_generator');
|
||||
$this->pathMatcher = func_get_arg(8) ?: \Drupal::service('path.matcher');
|
||||
}
|
||||
else {
|
||||
$this->titleResolver = $title_resolver;
|
||||
$this->currentUser = $current_user;
|
||||
$this->requestGenerator = $request_generator;
|
||||
$this->pathMatcher = $path_matcher;
|
||||
}
|
||||
$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;
|
||||
$this->pathMatcher = $path_matcher ?: \Drupal::service('path.matcher');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,7 +169,7 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
while (count($path_elements) > 1) {
|
||||
array_pop($path_elements);
|
||||
// Copy the path elements for up-casting.
|
||||
$route_request = $this->requestGenerator->generateRequestForPath('/' . implode('/', $path_elements), $exclude);
|
||||
$route_request = $this->getRequestForPath('/' . implode('/', $path_elements), $exclude);
|
||||
if ($route_request) {
|
||||
$route_match = RouteMatch::createFromRequest($route_request);
|
||||
$access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
|
||||
|
@ -202,4 +195,40 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|||
return $breadcrumb->setLinks(array_reverse($links));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
$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 | ResourceNotFoundException | MethodNotAllowedException | AccessDeniedHttpException | NotFoundHttpException $e) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ services:
|
|||
Drupal\system\SystemManager: '@system.manager'
|
||||
system.breadcrumb.default:
|
||||
class: Drupal\system\PathBasedBreadcrumbBuilder
|
||||
arguments: ['@router.request_context', '@access_manager', '@config.factory', '@title_resolver', '@current_user', '@path.matcher', '@request_generator']
|
||||
arguments: ['@router.request_context', '@access_manager', '@router', '@path_processor_manager', '@config.factory', '@title_resolver', '@current_user', '@path.current', '@path.matcher']
|
||||
tags:
|
||||
- { name: breadcrumb_builder, priority: 0 }
|
||||
path_processor.files:
|
||||
|
|
|
@ -72,7 +72,7 @@ class UninstallTest extends BrowserTestBase {
|
|||
$this->rebuildAll();
|
||||
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->titleEquals('Uninstall | Extend | Drupal');
|
||||
$this->assertSession()->titleEquals('Uninstall | Drupal');
|
||||
|
||||
// Check that the experimental module link was rendered correctly.
|
||||
$this->assertSession()->elementExists('xpath', "//a[contains(@aria-label, 'View information on the Experimental status of the module Experimental Test')]");
|
||||
|
@ -177,7 +177,7 @@ class UninstallTest extends BrowserTestBase {
|
|||
// Make sure confirmation page is accessible only during uninstall process.
|
||||
$this->drupalGet('admin/modules/uninstall/confirm');
|
||||
$this->assertSession()->addressEquals('admin/modules/uninstall');
|
||||
$this->assertSession()->titleEquals('Uninstall | Extend | Drupal');
|
||||
$this->assertSession()->titleEquals('Uninstall | Drupal');
|
||||
|
||||
// Make sure the correct error is shown when no modules are selected.
|
||||
$edit = [];
|
||||
|
|
|
@ -4,28 +4,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace Drupal\Tests\system\Unit\Breadcrumbs;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Controller\TitleResolverInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Access\AccessResultAllowed;
|
||||
use Drupal\Core\Path\PathMatcherInterface;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\Routing\RequestContext;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Utility\LinkGeneratorInterface;
|
||||
use Drupal\Core\Utility\RequestGenerator;
|
||||
use Drupal\system\PathBasedBreadcrumbBuilder;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\Core\Routing\RouteObjectInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\HttpFoundation\InputBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
|
@ -39,63 +31,63 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
*
|
||||
* @var \Drupal\system\PathBasedBreadcrumbBuilder
|
||||
*/
|
||||
protected PathBasedBreadcrumbBuilder $builder;
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* The mocked title resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\TitleResolverInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected TitleResolverInterface|MockObject $titleResolver;
|
||||
protected $titleResolver;
|
||||
|
||||
/**
|
||||
* The mocked access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected AccessManagerInterface|MockObject $accessManager;
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The request matching mock object.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected RequestMatcherInterface|MockObject $requestMatcher;
|
||||
protected $requestMatcher;
|
||||
|
||||
/**
|
||||
* The mocked route request context.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RequestContext|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected RequestContext|MockObject $context;
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* The mocked current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected AccountInterface|MockObject $currentUser;
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The mocked path processor.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected InboundPathProcessorInterface|MockObject $pathProcessor;
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* The mocked current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* The mocked path matcher service.
|
||||
*
|
||||
* @var \Drupal\Core\Path\PathMatcherInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected PathMatcherInterface|MockObject $pathMatcher;
|
||||
|
||||
/**
|
||||
* The request generator service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\RequestGenerator|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected RequestGenerator|MockObject $requestGenerator;
|
||||
protected $pathMatcher;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -115,24 +107,22 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
$this->accessManager = $this->createMock('\Drupal\Core\Access\AccessManagerInterface');
|
||||
$this->titleResolver = $this->createMock('\Drupal\Core\Controller\TitleResolverInterface');
|
||||
$this->currentUser = $this->createMock('Drupal\Core\Session\AccountInterface');
|
||||
$this->currentPath = $this->getMockBuilder('Drupal\Core\Path\CurrentPathStack')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->pathMatcher = $this->createMock(PathMatcherInterface::class);
|
||||
$this->requestGenerator = new RequestGenerator(
|
||||
$this->pathProcessor,
|
||||
$this->getMockBuilder('Drupal\Core\Path\CurrentPathStack')
|
||||
->disableOriginalConstructor()
|
||||
->getMock(),
|
||||
$this->requestMatcher
|
||||
);
|
||||
|
||||
$this->builder = new TestPathBasedBreadcrumbBuilder(
|
||||
$this->context,
|
||||
$this->accessManager,
|
||||
$this->requestMatcher,
|
||||
$this->pathProcessor,
|
||||
$config_factory,
|
||||
$this->titleResolver,
|
||||
$this->currentUser,
|
||||
$this->pathMatcher,
|
||||
$this->requestGenerator,
|
||||
$this->currentPath,
|
||||
$this->pathMatcher
|
||||
);
|
||||
|
||||
$this->builder->setStringTranslation($this->getStringTranslationStub());
|
||||
|
@ -184,6 +174,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
* Tests the build method with two path elements.
|
||||
*
|
||||
* @covers ::build
|
||||
* @covers ::getRequestForPath
|
||||
*/
|
||||
public function testBuildWithTwoPathElements() {
|
||||
$this->context->expects($this->once())
|
||||
|
@ -222,6 +213,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
* Tests the build method with three path elements.
|
||||
*
|
||||
* @covers ::build
|
||||
* @covers ::getRequestForPath
|
||||
*/
|
||||
public function testBuildWithThreePathElements() {
|
||||
$this->context->expects($this->once())
|
||||
|
@ -277,6 +269,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
* Tests that exceptions during request matching are caught.
|
||||
*
|
||||
* @covers ::build
|
||||
* @covers ::getRequestForPath
|
||||
*
|
||||
* @dataProvider providerTestBuildWithException
|
||||
*/
|
||||
|
@ -319,6 +312,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
* Tests the build method with a non processed path.
|
||||
*
|
||||
* @covers ::build
|
||||
* @covers ::getRequestForPath
|
||||
*/
|
||||
public function testBuildWithNonProcessedPath() {
|
||||
$this->context->expects($this->once())
|
||||
|
@ -355,6 +349,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
|||
* Tests the breadcrumb for a user path.
|
||||
*
|
||||
* @covers ::build
|
||||
* @covers ::getRequestForPath
|
||||
*/
|
||||
public function testBuildWithUserPath() {
|
||||
$this->context->expects($this->once())
|
||||
|
|
|
@ -16,7 +16,6 @@ settings:
|
|||
label: 'Page title'
|
||||
label_display: '0'
|
||||
provider: core
|
||||
base_route_title: false
|
||||
visibility:
|
||||
request_path:
|
||||
id: request_path
|
||||
|
|
|
@ -14,5 +14,4 @@ settings:
|
|||
label: 'Page title'
|
||||
label_display: '0'
|
||||
provider: core
|
||||
base_route_title: false
|
||||
visibility: { }
|
||||
|
|
|
@ -14,5 +14,4 @@ settings:
|
|||
label: 'Page title'
|
||||
label_display: '0'
|
||||
provider: core
|
||||
base_route_title: false
|
||||
visibility: { }
|
||||
|
|
|
@ -183,9 +183,6 @@ class MappingTest extends KernelTestBase {
|
|||
'block.settings.extra_field_block:*:*:*' => [
|
||||
'formatter',
|
||||
],
|
||||
'block.settings.page_title_block' => [
|
||||
'base_route_title',
|
||||
],
|
||||
'block.settings.system_branding_block' => [
|
||||
'use_site_logo',
|
||||
'use_site_name',
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Core\Utility;
|
||||
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\Routing\RouteObjectInterface;
|
||||
use Drupal\Core\Utility\RequestGenerator;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Symfony\Component\HttpFoundation\InputBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\Utility\RequestGenerator
|
||||
* @group Utility
|
||||
*/
|
||||
class RequestGeneratorTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The request generator.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\RequestGenerator
|
||||
*/
|
||||
protected RequestGenerator $requestGenerator;
|
||||
|
||||
/**
|
||||
* The mocked path processor.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
|
||||
*/
|
||||
protected InboundPathProcessorInterface|ObjectProphecy $pathProcessor;
|
||||
|
||||
/**
|
||||
* The mocked current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected CurrentPathStack|ObjectProphecy $currentPath;
|
||||
|
||||
/**
|
||||
* The request matching mock object.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
|
||||
*/
|
||||
protected RequestMatcherInterface|ObjectProphecy $requestMatcher;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->pathProcessor = $this->prophesize(InboundPathProcessorInterface::class);
|
||||
$this->currentPath = $this->prophesize(CurrentPathStack::class);
|
||||
$this->requestMatcher = $this->prophesize(RequestMatcherInterface::class);
|
||||
$this->requestGenerator = new RequestGenerator(
|
||||
$this->pathProcessor->reveal(),
|
||||
$this->currentPath->reveal(),
|
||||
$this->requestMatcher->reveal(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGenerateRequestForPath().
|
||||
*
|
||||
* @return \Generator
|
||||
* The test cases.
|
||||
*/
|
||||
public function providerTestGenerateRequestForPath(): \Generator {
|
||||
$path = '/any/path';
|
||||
|
||||
yield 'request for a path with no paths to skip' => [
|
||||
$path,
|
||||
[],
|
||||
['processInbound' => 1, 'matchRequest' => 1],
|
||||
TRUE,
|
||||
];
|
||||
|
||||
yield 'request for a path which needs to be skipped' => [
|
||||
$path,
|
||||
[$path => TRUE],
|
||||
['processInbound' => 0, 'matchRequest' => 0],
|
||||
FALSE,
|
||||
];
|
||||
|
||||
yield 'request for a path with other path which needs to be skipped' => [
|
||||
$path,
|
||||
['/other/path' => TRUE],
|
||||
['processInbound' => 1, 'matchRequest' => 1],
|
||||
TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests generateRequestForPath().
|
||||
*
|
||||
* @dataProvider providerTestGenerateRequestForPath
|
||||
* @covers ::generateRequestForPath
|
||||
*/
|
||||
public function testGenerateRequestForPath($path, $exclude, $methods_called, $request_generated): void {
|
||||
$route = new Route($path);
|
||||
$this->pathProcessor->processInbound($path, Argument::type(Request::class))->willReturnArgument();
|
||||
$this->requestMatcher->matchRequest(Argument::type(Request::class))->will(function ($arguments) use ($route, $path) {
|
||||
[$request] = $arguments;
|
||||
if ($request->getPathInfo() == $path) {
|
||||
return [
|
||||
RouteObjectInterface::ROUTE_NAME => 'User Example',
|
||||
RouteObjectInterface::ROUTE_OBJECT => $route,
|
||||
'_raw_variables' => new InputBag([]),
|
||||
];
|
||||
}
|
||||
});
|
||||
$request = $this->requestGenerator->generateRequestForPath($path, $exclude);
|
||||
if ($request_generated) {
|
||||
$this->assertNotNull($request);
|
||||
$this->assertSame($request->getPathInfo(), $path);
|
||||
}
|
||||
else {
|
||||
$this->assertNull($request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGenerateRequestForPathWithException().
|
||||
*
|
||||
* @return \Generator
|
||||
* The test cases.
|
||||
*/
|
||||
public function providerTestGenerateRequestForPathWithException(): \Generator {
|
||||
yield 'ParamNotConvertedException' => [ParamNotConvertedException::class, ''];
|
||||
yield 'ResourceNotFoundException' => [ResourceNotFoundException::class, ''];
|
||||
yield 'MethodNotAllowedException' => [MethodNotAllowedException::class, []];
|
||||
yield 'AccessDeniedHttpException' => [AccessDeniedHttpException::class, ''];
|
||||
yield 'NotFoundHttpException' => [NotFoundHttpException::class, ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests generateRequestForPath() with exceptions during request matching.
|
||||
*
|
||||
* @dataProvider providerTestGenerateRequestForPathWithException
|
||||
* @covers ::generateRequestForPath
|
||||
*/
|
||||
public function testGenerateRequestForPathWithException($exception_class, $exception_argument): void {
|
||||
$path = '/example';
|
||||
$exclude = [];
|
||||
$this->pathProcessor->processInbound($path, Argument::type(Request::class))->willReturnArgument();
|
||||
$this->requestMatcher->matchRequest(Argument::type(Request::class))->willThrow(new $exception_class($exception_argument));
|
||||
$request = $this->requestGenerator->generateRequestForPath($path, $exclude);
|
||||
$this->assertNull($request);
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -14,5 +14,4 @@ settings:
|
|||
label: 'Page title'
|
||||
label_display: '0'
|
||||
provider: core
|
||||
base_route_title: true
|
||||
visibility: { }
|
||||
|
|
|
@ -14,5 +14,4 @@ settings:
|
|||
label: 'Page title'
|
||||
label_display: '0'
|
||||
provider: core
|
||||
base_route_title: false
|
||||
visibility: { }
|
||||
|
|
Loading…
Reference in New Issue