Problem/Motivation
#3232746 correctly made base/parent theme preprocess plugins run when a sub-theme is active. That matches core theme registry behavior for THEME_preprocess_HOOK() functions.
However, after that change, theme plugins are still ordered alphabetically by plugin ID (via findDefinitions()), not by theme ancestry. Core runs base theme preprocess first and the active (child) theme last so the child always wins.
With alphabetical ordering, a base theme whose machine name sorts after the child theme runs last and overwrites the child's changes.
Example with base theme "zzz_base" and child theme "aaa_child":
1. aaa_child.preprocess.field runs (child) and remaps button classes.
2. zzz_base.preprocess.field runs (base) and resets those classes.
"aaa_child" < "zzz_base" alphabetically, so the base theme wins. That is the opposite of core's expected order.
This started affecting sites as soon as they upgraded to preprocess 2.2.1 (where #3232746 shipped). Before 2.2.1, base-theme plugins were filtered out entirely when a child was active, so child overrides appeared to work by accident.
Steps to reproduce
- Install preprocess 2.2.1 or later.
- Create base theme "zzz_base" with a field preprocess plugin that sets a variable or attribute (e.g. button class "from-base").
- Create child theme "aaa_child" (base theme: zzz_base) with a field preprocess plugin for the same hook that sets a different value (e.g. "from-child"), ideally by extending the base plugin and calling parent::preprocess() then overriding.
- Enable the child theme and render a page that triggers that field hook.
- Observe that "from-base" wins even though the child theme is active, because plugin IDs are sorted alphabetically ("aaa_child.preprocess.field" before "zzz_base.preprocess.field") rather than by theme ancestry.
Proposed resolution
After filtering definitions to the active theme + its base themes in PreprocessPluginManager::getDefinitions(), sort theme providers by ancestry:
- Modules first (unchanged relative ordering among modules is fine).
- Themes in ancestry order: root base theme → … → direct parent → active theme (same approach core uses: array_reverse() of ActiveTheme::getBaseThemeExtensions(), then the active theme).
- Within the same provider, keep alphabetical order by plugin ID.
That makes child theme plugins run last, matching core theme registry behavior and the intent described in #3232746.
A patch implementing this is attached (also works against 2.2.3).
Note: #3165828 (plugin weights) would not fully solve this by itself. It requires manual per-plugin configuration, and child themes should win by default without maintainers remembering to set weights. Weights could still be useful later for ordering within the same theme/module.
Remaining tasks
- Review proposed ancestry sort in getDefinitions()
- Add a kernel/unit test with base + child theme plugins for the same hook asserting execution order (base then child)
- Confirm module plugins still run before theme plugins
- Manual testing with a multi-level base theme chain if possible
User interface changes
None.
API changes
None intended. Behavior change: theme preprocess plugins for the active theme and its base themes run in ancestry order instead of alphabetical plugin ID order. Sites that accidentally depended on alphabetical order may see different
(correct) results.
Data model changes
None.
Issue fork preprocess-3614103
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
daniel korteComment #4
daniel korte