---
In RenderElement::preRenderAjaxForm, the following is how the url setting is processed:
// Convert \Drupal\Core\Url object to string.
if (isset($settings['url']) && $settings['url'] instanceof Url) {
$url = $settings['url']->setOptions($settings['options'])->toString(TRUE);
BubbleableMetadata::createFromRenderArray($element)
->merge($url)
->applyTo($element);
$settings['url'] = $url->getGeneratedUrl();
}
else {
$settings['url'] = NULL;
}
Because of ->toString(TRUE), the bubbled metadata is collected and the returned value is \Drupal\Core\GeneratedUrl.
It then goes into applying said metadata to the element passed. This would be fine if the Url was ultimately rendered on the page (as markup). However, this is the array that is passed into drupalSettings. The placeholder here is never rendered and, thus, the CSRF token never replaced.
I tried simply removing TRUE thinking it would simply return the proper token, but that doesn't work.
Decided to step through this:
Url::toString() invokes MetadataBubblingUrlGenerator::generateFromRoute() which $collect_bubbleable_metadata defaults to FALSE.
However, in that method, it invokes the non-bubbling UrlGenerator::generateFromRoute where $collect_bubbleable_metadata is explicitly set to TRUE:
$generated_url = $this->urlGenerator->generateFromRoute($name, $parameters, $options, TRUE);
This, in turn, causes RouteProcessorCsrf::processOutbound to ALWAYS return a #lazy_builder placeholder and not the actual token.
---
Until I can get some feedback on why/what is going on here, I created a temporary workaround so it would use the correct CSRF token in the Url:
// Ensure the Url passed is properly generated.
if (isset($ajax_settings['url'])) {
/** @var \Drupal\Core\Url $url */
$url = $ajax_settings['url'];
$generated_url = $url->toString(TRUE);
if (($attachments = $generated_url->getAttachments()) && !empty($attachments['placeholders'])) {
$build['#markup'] = $generated_url->getGeneratedUrl();
$build['#attached'] = $attachments;
/** @var \Drupal\Core\Render\Renderer $renderer */
$renderer = \Drupal::service('renderer');
foreach (array_keys($build['#attached']['placeholders']) as $placeholder) {
$build = $renderer->renderPlaceholder($placeholder, $build);
}
$ajax_settings['url'] = Url::fromUserInput((string) $build['#markup']);
}
// RenderElement::preRenderAjaxForm overrides any token that is already
// set when it renders the Url object. However, it can be "reset" back to
// the original query by setting the #ajax options property explicitly.
$ajax_settings['options']['query'] = $ajax_settings['url']->getOption('query');
}
Comments
Comment #2
markhalliwellTurns out this is part of a larger problem. Postponing any "fixes" to
#ajaxuntil that issue can be resolved.Comment #3
markhalliwellComment #15
catch