Loading core/modules/contextual/contextual.libraries.yml +5 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,11 @@ drupal.contextual-links: theme: css/contextual.theme.css: {} css/contextual.icons.theme.css: {} drupalSettings: # These placeholder values will be set by # \Drupal\contextual\Element\ContextualLinksPlaceholder::preRenderPlaceholder(). contextual: theme: null dependencies: - core/jquery - core/drupal Loading core/modules/contextual/contextual.services.yml +6 −0 Original line number Diff line number Diff line parameters: contextual.skip_procedural_hook_scan: true services: theme.negotiator.contextual_links: class: Drupal\contextual\Theme\ContextualLinksNegotiator autowire: true tags: - { name: theme_negotiator } core/modules/contextual/js/contextual.js +9 −3 Original line number Diff line number Diff line Loading @@ -156,7 +156,9 @@ const uncachedIDs = []; const uncachedTokens = []; ids.forEach((contextualID) => { const html = storage.getItem(`Drupal.contextual.${contextualID.id}`); const html = storage.getItem( `Drupal.contextual.${contextualID.id}.${drupalSettings.contextual.theme}`, ); if (html?.length) { // Initialize after the current execution cycle, to make the AJAX // request for retrieving the uncached contextual links as soon as Loading Loading @@ -184,7 +186,8 @@ uncachedIDs.forEach((id) => data.append('ids[]', id)); uncachedTokens.forEach((id) => data.append('tokens[]', id)); const req = new Request(Drupal.url('contextual/render'), { const controllerUrl = `contextual/render?theme=${drupalSettings.contextual.theme}`; const req = new Request(Drupal.url(controllerUrl), { method: 'POST', body: data, }); Loading @@ -194,7 +197,10 @@ .then((json) => Object.entries(json).forEach(([contextualID, html]) => { // Store the metadata. storage.setItem(`Drupal.contextual.${contextualID}`, html); storage.setItem( `Drupal.contextual.${contextualID}.${drupalSettings.contextual.theme}`, html, ); // If the rendered contextual links are empty, then the current // user does not have permission to access the associated links: // don't render anything. Loading core/modules/contextual/src/Element/ContextualLinksPlaceholder.php +5 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ namespace Drupal\contextual\Element; use Drupal\Component\Utility\Crypt; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Site\Settings; use Drupal\Core\Template\Attribute; use Drupal\Core\Render\Attribute\RenderElement; Loading Loading @@ -51,6 +52,10 @@ public static function preRenderPlaceholder(array $element) { 'data-drupal-ajax-container' => '', ]); $element['#markup'] = new FormattableMarkup('<div@attributes></div>', ['@attributes' => $attribute]); $element['#attached']['drupalSettings']['contextual']['theme'] = \Drupal::service('theme.manager')->getActiveTheme()->getName(); $cache = CacheableMetadata::createFromRenderArray($element); $cache->addCacheContexts(['theme']); $cache->applyTo($element); return $element; } Loading core/modules/contextual/src/Theme/ContextualLinksNegotiator.php 0 → 100644 +38 −0 Original line number Diff line number Diff line <?php namespace Drupal\contextual\Theme; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Theme\ThemeNegotiatorInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Symfony\Component\HttpFoundation\RequestStack; /** * Set the theme according to the parameter passed to the controller. */ final class ContextualLinksNegotiator implements ThemeNegotiatorInterface { public function __construct( protected readonly RouteMatchInterface $route_match, protected readonly RequestStack $requestStack, protected readonly ThemeHandlerInterface $themeHandler, protected readonly ConfigFactoryInterface $configFactory, ) {} public function applies(RouteMatchInterface $route_match): bool { return $route_match->getRouteName() === 'contextual.render'; } public function determineActiveTheme(RouteMatchInterface $route_match): string { $request = $this->requestStack->getCurrentRequest(); $theme = $request?->query->get('theme', '') ?? ''; if ($this->themeHandler->themeExists($theme)) { return $theme; } return $this->configFactory->get('system.theme')->get('default'); } } Loading
core/modules/contextual/contextual.libraries.yml +5 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,11 @@ drupal.contextual-links: theme: css/contextual.theme.css: {} css/contextual.icons.theme.css: {} drupalSettings: # These placeholder values will be set by # \Drupal\contextual\Element\ContextualLinksPlaceholder::preRenderPlaceholder(). contextual: theme: null dependencies: - core/jquery - core/drupal Loading
core/modules/contextual/contextual.services.yml +6 −0 Original line number Diff line number Diff line parameters: contextual.skip_procedural_hook_scan: true services: theme.negotiator.contextual_links: class: Drupal\contextual\Theme\ContextualLinksNegotiator autowire: true tags: - { name: theme_negotiator }
core/modules/contextual/js/contextual.js +9 −3 Original line number Diff line number Diff line Loading @@ -156,7 +156,9 @@ const uncachedIDs = []; const uncachedTokens = []; ids.forEach((contextualID) => { const html = storage.getItem(`Drupal.contextual.${contextualID.id}`); const html = storage.getItem( `Drupal.contextual.${contextualID.id}.${drupalSettings.contextual.theme}`, ); if (html?.length) { // Initialize after the current execution cycle, to make the AJAX // request for retrieving the uncached contextual links as soon as Loading Loading @@ -184,7 +186,8 @@ uncachedIDs.forEach((id) => data.append('ids[]', id)); uncachedTokens.forEach((id) => data.append('tokens[]', id)); const req = new Request(Drupal.url('contextual/render'), { const controllerUrl = `contextual/render?theme=${drupalSettings.contextual.theme}`; const req = new Request(Drupal.url(controllerUrl), { method: 'POST', body: data, }); Loading @@ -194,7 +197,10 @@ .then((json) => Object.entries(json).forEach(([contextualID, html]) => { // Store the metadata. storage.setItem(`Drupal.contextual.${contextualID}`, html); storage.setItem( `Drupal.contextual.${contextualID}.${drupalSettings.contextual.theme}`, html, ); // If the rendered contextual links are empty, then the current // user does not have permission to access the associated links: // don't render anything. Loading
core/modules/contextual/src/Element/ContextualLinksPlaceholder.php +5 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ namespace Drupal\contextual\Element; use Drupal\Component\Utility\Crypt; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Site\Settings; use Drupal\Core\Template\Attribute; use Drupal\Core\Render\Attribute\RenderElement; Loading Loading @@ -51,6 +52,10 @@ public static function preRenderPlaceholder(array $element) { 'data-drupal-ajax-container' => '', ]); $element['#markup'] = new FormattableMarkup('<div@attributes></div>', ['@attributes' => $attribute]); $element['#attached']['drupalSettings']['contextual']['theme'] = \Drupal::service('theme.manager')->getActiveTheme()->getName(); $cache = CacheableMetadata::createFromRenderArray($element); $cache->addCacheContexts(['theme']); $cache->applyTo($element); return $element; } Loading
core/modules/contextual/src/Theme/ContextualLinksNegotiator.php 0 → 100644 +38 −0 Original line number Diff line number Diff line <?php namespace Drupal\contextual\Theme; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Theme\ThemeNegotiatorInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Symfony\Component\HttpFoundation\RequestStack; /** * Set the theme according to the parameter passed to the controller. */ final class ContextualLinksNegotiator implements ThemeNegotiatorInterface { public function __construct( protected readonly RouteMatchInterface $route_match, protected readonly RequestStack $requestStack, protected readonly ThemeHandlerInterface $themeHandler, protected readonly ConfigFactoryInterface $configFactory, ) {} public function applies(RouteMatchInterface $route_match): bool { return $route_match->getRouteName() === 'contextual.render'; } public function determineActiveTheme(RouteMatchInterface $route_match): string { $request = $this->requestStack->getCurrentRequest(); $theme = $request?->query->get('theme', '') ?? ''; if ($this->themeHandler->themeExists($theme)) { return $theme; } return $this->configFactory->get('system.theme')->get('default'); } }