Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 294415

Some theme hooks are not invoking (depends on templates order provided by filesystem)

$
0
0

Problem/Motivation

Is some cases theme hook for default view mode is not invoked when rendering entity in some non-default view mode.
Basically problem can be with any theme hook with some suggestion (double underscores after base hook).

example:
my_theme_preprocess_paragraph__my_paragraph()
is not invoked when rendering this paragraph in teaser view mode.
Only related theme hook my_theme_preprocess_paragraph__my_paragraph__teaser() is invoked.

After debugging I realised that problem is cased by theme hooks order, provided by filesystem.

If your filesystem returns template
paragraph--my-paragraph--teaser.html.twig
before
paragraph--my-paragraph.html.twig
preprocess function for default view mode is not listed in related hook preprocess functions in
web/core/lib/Drupal/Core/Theme/Registry.php

It happens here (in that case 'incomplete preprocess functions' is removed which cause missing preprocess function for default view mode in teaser hook data):

/**
   * Completes the definition of the requested suggestion hook.
   *
   * @param string $hook
   *   The name of the suggestion hook to complete.
   * @param array $cache
   *   The theme registry, as documented in
   *   \Drupal\Core\Theme\Registry::processExtension().
   */
  protected function completeSuggestion($hook, array &$cache) {
    $previous_hook = $hook;
    $incomplete_previous_hook = [];
    // Continue looping if the candidate hook doesn't exist or if the candidate
    // hook has incomplete preprocess functions, and if the candidate hook is a
    // suggestion (has a double underscore).
    while ((!isset($cache[$previous_hook]) || isset($cache[$previous_hook]['incomplete preprocess functions']))
      && $pos = strrpos($previous_hook, '__')) {
      // Find the first existing candidate hook that has incomplete preprocess
      // functions.
      if (isset($cache[$previous_hook]) && !$incomplete_previous_hook && isset($cache[$previous_hook]['incomplete preprocess functions'])) {
        $incomplete_previous_hook = $cache[$previous_hook];
        unset($incomplete_previous_hook['incomplete preprocess functions']);
      }
      $previous_hook = substr($previous_hook, 0, $pos);
      $this->mergePreprocessFunctions($hook, $previous_hook, $incomplete_previous_hook, $cache);
    }

    // In addition to processing suggestions, include base hooks.
    if (isset($cache[$hook]['base hook'])) {
      // In order to retain the additions from above, pass in the current hook
      // as the parent hook, otherwise it will be overwritten.
      $this->mergePreprocessFunctions($hook, $cache[$hook]['base hook'], $cache[$hook], $cache);
    }
  }

Theme hooks order comes from twig_theme() function in web/core/themes/engines/twig/twig.engine

/**
 * Implements hook_theme().
 */
function twig_theme($existing, $type, $theme, $path) {
  $templates = drupal_find_theme_functions($existing, [$theme]);
  $templates += drupal_find_theme_templates($existing, '.html.twig', $path);
  return $templates;
}

List of templates is generated here in drupal_find_theme_templates()

$files = \Drupal::service('file_system')->scanDirectory($path, $regex, ['key' => 'filename']);

In code of this service is called readdir() function and the entries are returned in the order in which they are stored by the filesystem (according php documentation).

Steps to reproduce

* Create new paragraph type My paragraph (machine name my_paragraph)
* Allow this paragraph in some entity reference field and set for this field in display settings 'Rendered entity' and teaser view mode
* Create two preprocess function in your theme: my_theme_preprocess_paragraph__my_paragraph() and my_theme_preprocess_paragraph__my_paragraph__teaser()
* Create related templates in your theme: paragraph--my-paragraph.html.twig and paragraph--my-paragraph--teaser.html.twig

* Because order of templates (related theme hooks) depends on filesystem, it may vary on every environment. If you make temporary hardcode change here, you can reproduce that behavior:
in web/core/themes/engines/twig/twig.engine

function twig_theme($existing, $type, $theme, $path) {
  $templates = drupal_find_theme_functions($existing, [$theme]);
  $templates += drupal_find_theme_templates($existing, '.html.twig', $path);
  if ($theme === 'my_theme') {
    $myParagraphDefaultViewModeHook = $templates['paragraph__my_paragraph'];
    unset($templates['paragraph__my_paragraph']);
    // move that theme hook to the end of array
    // simulate situation when filesystem sorts templates wrong way
    // (first paragraph__my_paragraph__teaser then paragraph__my_paragraph)
    $templates['paragraph__my_paragraph'] = $myParagraphDefaultViewModeHook;
  }
  return $templates;
}

* Clear cache
* Create some content with this paragraph and open that page.

Result: my_theme_preprocess_paragraph__my_paragraph__teaser() is invoked, my_theme_preprocess_paragraph__my_paragraph() isn't.

Proposed resolution

Sort returned theme hooks from twig_theme().
I have no experiences with contributing so please, can anybody check this a see my patch?


Viewing all articles
Browse latest Browse all 294415

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>