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?