Verified Commit 35a0de45 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

feat: #3556794 preprocess_HOOK__candidates in themes can be assigned to module invoke maps

By: jurgenhaas
By: nicxvan
By: berdir
By: larowlan
(cherry picked from commit d597c26b)
parent 53f35651
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -287,7 +287,7 @@ protected function calculateImplementations(): array {
      }
      foreach ($moduleImplements as $module => $v) {
        if (is_string($hook) && str_starts_with($hook, 'preprocess_') && str_contains($hook, '__')) {
          $this->preprocessForSuggestions[$module . '_' . $hook] = TRUE;
          $this->preprocessForSuggestions[$module . '_' . $hook] = 'module';
        }
        foreach (array_keys($implementationsByHookOrig[$hook], $module, TRUE) as $identifier) {
          $implementationsByHook[$hook][$identifier] = $module;
+1 −1
Original line number Diff line number Diff line
@@ -133,7 +133,7 @@ protected function calculateImplementations(): array {
    foreach ($implementationsByHookOrig as $hook => $hookImplementations) {
      if (is_string($hook) && str_starts_with($hook, 'preprocess_') && str_contains($hook, '__')) {
        foreach ($hookImplementations as $theme) {
          $this->preprocessForSuggestions[$theme . '_' . $hook] = TRUE;
          $this->preprocessForSuggestions[$theme . '_' . $hook] = 'theme';
        }
      }
    }
+11 −6
Original line number Diff line number Diff line
@@ -184,7 +184,7 @@ class Registry implements DestructableInterface {
   * These are stored to prevent adding preprocess suggestions to the invoke map
   * that are not discovered in modules.
   *
   * @var array<string, true>
   * @var array<string, string>
   */
  protected ?array $preprocessForSuggestions = NULL;

@@ -833,6 +833,8 @@ protected function postProcessExtension(array &$cache, ActiveTheme $theme) {
    // Collect all variable preprocess functions in the correct order.
    $suggestion_level = [];
    $invokes = [];
    $preprocess_extension_types = $this->getPreprocessForSuggestions();

    // Look for functions named according to the pattern and add them if they
    // have matching hooks in the registry.
    foreach ($prefixes as $prefix) {
@@ -849,9 +851,12 @@ protected function postProcessExtension(array &$cache, ActiveTheme $theme) {
          if (isset($cache[$matches[2]])) {
            $level = substr_count($matches[1], '__');
            $suggestion_level[$level][$candidate] = $matches[1];
            $module_preprocess_function = $prefix . '_preprocess_' . $matches[1];
            if (isset($this->getPreprocessForSuggestions()[$module_preprocess_function])) {
              $invokes[$candidate] = ['module' => $prefix, 'hook' => 'preprocess_' . $matches[1]];
            $preprocess_function = $prefix . '_preprocess_' . $matches[1];
            if (\array_key_exists($preprocess_function, $preprocess_extension_types)) {
              $invokes[$candidate] = [
                $preprocess_extension_types[$preprocess_function] => $prefix,
                'hook' => 'preprocess_' . $matches[1],
              ];
            }
          }
        }
@@ -1033,8 +1038,8 @@ protected function hasThemeHookImplementation(string $theme, string $hook): bool
  /**
   * Returns discovered preprocess suggestions.
   *
   * @return array<string, true>
   *   Preprocess suggestions discovered in modules.
   * @return array<string, string>
   *   Preprocess suggestions discovered in themes and modules.
   */
  protected function getPreprocessForSuggestions(): array {
    if ($this->preprocessForSuggestions === NULL) {
+15 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Theme\Registry;
use Drupal\Core\Theme\ThemeInitializationInterface;
use Drupal\Core\Utility\ThemeRegistry;
use Drupal\KernelTests\KernelTestBase;
use PHPUnit\Framework\Attributes\DataProvider;
@@ -115,6 +116,8 @@ public function testSuggestionPreprocessFunctions(): void {
    $theme_handler = \Drupal::service('theme_handler');
    \Drupal::service('theme_installer')->install(['test_theme']);

    \Drupal::theme()->setActiveTheme(\Drupal::service(ThemeInitializationInterface::class)->initTheme('test_theme'));

    $extension_list = $this->container->get('extension.list.module');
    assert($extension_list instanceof ModuleExtensionList);
    $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, \Drupal::service('kernel'), 'test_theme', \Drupal::service('keyvalue'));
@@ -131,8 +134,20 @@ public function testSuggestionPreprocessFunctions(): void {
      $expected_preprocess_functions[] = "test_theme_preprocess_$hook";
      $preprocess_functions = $registry_theme->get()[$hook]['preprocess functions'];
      $this->assertSame($expected_preprocess_functions, $preprocess_functions, "$hook has correct preprocess functions.");

      // Ensure the invoke map has the expected structure.
      $expected_invoke_map = [
        'theme' => 'test_theme',
        'hook' => "preprocess_$hook",
      ];
      $this->assertEquals($expected_invoke_map, $registry_theme->get()['preprocess invokes']["test_theme_preprocess_$hook"], "$hook has correct invokes.");
    } while ($suggestion = array_shift($suggestions));

    // Ensure the theme preprocess for the suggestion runs and sets the bar
    // variable.
    $output = \Drupal::theme()->render('theme_test_preprocess_suggestions__kitten__flamingo', []);
    $this->assertStringContainsString('Flamingo', (string) $output);

    $expected_preprocess_functions = [
      'theme_test_preprocess_theme_test_preprocess_suggestions',
      'test_theme_preprocess_theme_test_preprocess_suggestions',