Still on Drupal 7? Security support for Drupal 7 ended on 5 January 2025. Please visit our Drupal 7 End of Life resources page to review all of your options.Follow-up to #3277784, related to #3352384
Problem/Motivation
#3277784 introduced this with commit a894a04b:
// Route defaults that do not start with a leading "_" are also
// parameters, even if they are not included in path or host patterns.
foreach ($route->getDefaults() as $name => $value) {
if (!isset($raw_variables[$name]) && substr($name, 0, 1) !== '_') {
$raw_variables[$name] = $value;
}
}
Route parameters, however, might end up in TitleResolver, which is pulling all route parameters considering them safe as translation arguments:
if (($raw_parameters = $request->attributes->get('_raw_variables'))) {
foreach ($raw_parameters->all() as $key => $value) {
$args['@' . $key] = $value ?? '';
$args['%' . $key] = $value ?? '';
}
}
and
$route_title = $this->t($title, $args, $options);
So route defaults are now required to be scalars, as otherwise t() fails with a TypeError.
However (@larowlan):
I think default arguments can totally be arrays and as such that automagic '@arg' '%arg' should be checking if $value is a scalar - i.e. that sounds like a bug in TitleResolver in my book
This hit e.g. the CiviCRM module which defined a route default args being an array.
Steps to reproduce
You can reproduce the issue with Drupal 9.5.9 by declaring a new Route object with an array in its default argument, e.g.
in a module's modulename.routing.yml:
route_callbacks:
- '\Drupal\modulename\Routing\Routes::getRoutes'
in the module's src/Routing/Routes.php:
class Routes {
public function getRoutes() {
$route_collection = new \Symfony\Component\Routing\RouteCollection();
return $route_collection->add('/my-path', [
'_title' => 'Page Title',
'_controller' => 'Drupal\modulename\Controller\ModuleController:run',
'my_default_arg' => ['some' => 'array'],
]);
return $route_collection;
}
}
Proposed resolution
TitleResolver should check for route parameters being a scalar before copying them as translation arguments:
if (($raw_parameters = $request->attributes->get('_raw_variables'))) {
foreach ($raw_parameters->all() as $key => $value) {
if (is_scalar($value) {
$args['@' . $key] = $value ?? '';
$args['%' . $key] = $value ?? '';
}
}
}
or variables cast-able to string (while that might not make sense for all types).
Issue fork drupal-3358402
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
totten commentedYeah, that `Route` metadata seems to go through several copy/filter steps, and I don't know the history on all of them, but you only need one of them to be more selective.
+1 - Sounds sensible to me. That seems like the place where you go from a broader domain (all possible arguments to a page-controller) to a narrower domain (values that can be interpolated into a string).
Comment #5
lisotton commentedI created a MR applying what was suggested by @jensschuppe.
The problem I see is that using the function "is_scalar", it will not enter even when the value is null and before if the value was null, it was using an empty string.
What do you think is the best option?
- Not set the value if is null or array
- Leave the value as empty string if is null and not set the value if is array
- Set the value as empty string if both null or array
Comment #6
socialnicheguru commentedComment #7
jensschuppe commentedThanks @lisotton for your MR! I'd opt for keeping current behavior when the value is
null, i. e. set it to the empty string. I'm not sure about non-scalar values though. If the empty string was meant to be a fallback, I'd say it should be the default value for every non-scalar value. If it was meant to be the string representation ofnull, non-scalar values should have their own defaults or be ignored. But maybe this should be answered by someone more familiar. Historically, only scalar values ornullwould have been "allowed" anyway ...Comment #8
wellsI'm still trying to track down how it is related but I also have a regression related to #3277784: copyRawVariables should support default route parameters -- in my case I've got a view with a path and menu entry and the rendered menu item no longer sets
in_active_trailtoTRUEwhen accessing the view on a page with the menu.Reverting #3277784: copyRawVariables should support default route parameters resolves the issue.
Will try to see if I can create clean reproduction steps as well...
Comment #9
wellsI am able to reproduce the issue from #8 with the following steps on a fresh install of Drupal 9.5.x (using
drush siin my case):/admin/structure/views/add./site-content.primary-nav__menu-link--active-trailis not present onaelement.core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php#L71-75)./site-content.primary-nav__menu-link--active-trailis present onaelement.Still not sure if this belongs here or in a separate issue. Happy to create a new issue if it anyone thinks it should be separate.
Comment #10
znerol commentedObserved exactly the same issue today with a custom menu/route after rolling out a recent build. Glad that you found a repro relying on core only. I ended up reverting #3277784: copyRawVariables should support default route parameters to work around the issue for the moment.
Comment #11
znerol commentedI think that
$this->routeMatech->getRawParameters()->all()returns a different set of key-value pairs which in turn leads to an empty result from$this->menuLinkManager->loadLinksByroute()in MenuActiveTrail::getActiveLink():Comment #12
lisotton commentedI had similar issues in many places where I manipulate retrieve the menu tree.
I have some routes that share the same Controller and Action, but in the route declaration it declares default parameters. For example I have the route
mymodule.news_list(/news) which have the controllerListPageController::entryPointwith a default parameter called type with valuenews-list. Then I have another route calledmymodule.articles_list(/articles) which shares the same controller, but the default parameter type with valuearticles-list.Before when I was calling
\Drupal::service('plugin.manager.menu.link')->loadLinksByRoute('news_list'), without any second second parameter (route params), it was returning me the menu links, but with the change in 9.5.9, I'm enforced to call\Drupal::service('plugin.manager.menu.link')->loadLinksByRoute('news_list', ['type' => 'news-list').For me this broke many small things, like breadcrumbs, custom menu blocks, etc.
Comment #13
larowlanIt sounds like there are two separate issues here.
The first one (the warning from title controller) is covered by this patch.
Can we get a separate issue to address the menu active trail issue?
Comment #14
wellsMake sense. Thanks for chiming it, @larowlan. I have moved the active menu trail issue to #3359511: [regression] missing menu active trail since Drupal 9.5.9.
Comment #15
larowlanThanks, watching both actively to try and get these in asap
Comment #16
quietone commentedChanging title per Special titles.
Comment #17
smustgrave commentedBelieve the issue on this particular issue has been addressed and issue summary matches what is in the MR.
Comment #18
larowlanCan we get a version that applies cleanly to 11.x and 10.1.x too
thanks!
Comment #20
totten commentedAdded a forward-port (from !3953 on 9.5.x to !4129 on 10.0.x). The merge-conflict was fairly small. The new test passed locally, and I confirmed that one specific error scenario (re: civicrm-core@5.60) worked with this fix.
Aside: This is the first time I've posted a patch to drupal core, so I'm not too familiar with the workflows. But I can say that the merge-conflict should be resolved -- you can cherry-pick the
10.0.xpatch on10.1.xor11.x.Comment #23
totten commentedAdded forward ports for 10.1.x and 11.x. Tests still passing.
Comment #28
catchCommitted/pushed to 11.x, cherry-picked to 10.1.x and 10.0.x, committed the 9.5.x MR to 9.5.x, thanks!