4 * Contains \Drupal\bootstrap\Plugin\Alter\ThemeRegistry.
7 // Name of the base theme must be lowercase for it to be autoload discoverable.
8 namespace Drupal\bootstrap\Plugin\Alter;
10 use Drupal\bootstrap\Annotation\BootstrapAlter;
11 use Drupal\bootstrap\Bootstrap;
12 use Drupal\bootstrap\Plugin\PreprocessManager;
13 use Drupal\Core\Theme\Registry;
16 * Extends the theme registry to override and use protected functions.
18 * @todo Refactor into a proper theme.registry service replacement in a
19 * bootstrap_core sub-module once this theme can add it as a dependency.
21 * @see https://www.drupal.org/node/474684
23 * @ingroup plugins_alter
25 * @BootstrapAlter("theme_registry")
27 class ThemeRegistry extends Registry implements AlterInterface {
30 * The currently set Bootstrap theme object.
32 * Cannot use "$theme" because this is the Registry's ActiveTheme object.
34 * @var \Drupal\bootstrap\Theme
36 protected $currentTheme;
41 public function __construct(array $configuration, $plugin_id, $plugin_definition) {
42 // This is technically a plugin constructor, but because we wish to use the
43 // protected methods of the Registry class, we must extend from it. Thus,
44 // to properly construct the extended Registry object, we must pass the
45 // arguments it would normally get from the service container to "fake" it.
46 if (!isset($configuration['theme'])) {
47 $configuration['theme'] = Bootstrap::getTheme();
49 $this->currentTheme = $configuration['theme'];
51 \Drupal::service('app.root'),
52 \Drupal::service('cache.default'),
53 \Drupal::service('lock'),
54 \Drupal::service('module_handler'),
55 \Drupal::service('theme_handler'),
56 \Drupal::service('theme.initialization'),
57 $this->currentTheme->getName()
59 $this->setThemeManager(\Drupal::service('theme.manager'));
66 public function alter(&$cache, &$context1 = NULL, &$context2 = NULL) {
67 // Sort the registry alphabetically (for easier debugging).
70 // Add extra variables to all theme hooks.
71 $extra_variables = Bootstrap::extraVariables();
72 foreach (array_keys($cache) as $hook) {
73 // Skip theme hooks that don't set variables.
74 if (!isset($cache[$hook]['variables'])) {
77 $cache[$hook]['variables'] += $extra_variables;
80 // Ensure paths to templates are set properly. This allows templates to
81 // be moved around in a theme without having to constantly ensuring that
82 // the theme's hook_theme() definitions have the correct static "path" set.
83 foreach ($this->currentTheme->getAncestry() as $ancestor) {
84 $current_theme = $ancestor->getName() === $this->currentTheme->getName();
85 $theme_path = $ancestor->getPath();
86 foreach ($ancestor->fileScan('/\.html\.twig$/', 'templates') as $file) {
87 $hook = str_replace('-', '_', str_replace('.html.twig', '', $file->filename));
88 $path = dirname($file->uri);
89 $incomplete = !isset($cache[$hook]) || strrpos($hook, '__');
91 // Create a new theme hook. This primarily happens when theme hook
92 // suggestion templates are created. To prevent the new hook from
93 // inheriting parent hook's "template", it must be manually set here.
94 // @see https://www.drupal.org/node/2871551
95 if (!isset($cache[$hook])) {
97 'template' => str_replace('.html.twig', '', $file->filename),
101 // Always ensure that "path", "type" and "theme path" are properly set.
102 $cache[$hook]['path'] = $path;
103 $cache[$hook]['type'] = $current_theme ? 'theme' : 'base_theme';
104 $cache[$hook]['theme path'] = $theme_path;
108 $cache[$hook]['incomplete preprocess functions'] = TRUE;
113 // Discover all the theme's preprocess plugins.
114 $preprocess_manager = new PreprocessManager($this->currentTheme);
115 $plugins = $preprocess_manager->getDefinitions();
116 ksort($plugins, SORT_NATURAL);
118 // Iterate over the preprocess plugins.
119 foreach ($plugins as $plugin_id => $definition) {
120 $incomplete = !isset($cache[$plugin_id]) || strrpos($plugin_id, '__');
121 if (!isset($cache[$plugin_id])) {
122 $cache[$plugin_id] = [];
124 array_walk($cache, function (&$info, $hook) use ($plugin_id, $definition) {
125 if ($hook === $plugin_id || strpos($hook, $plugin_id . '__') === 0) {
126 if (!isset($info['preprocess functions'])) {
127 $info['preprocess functions'] = [];
129 // Due to a limitation in \Drupal\Core\Theme\ThemeManager::render,
130 // callbacks must be functions and not classes. We always specify
131 // "bootstrap_preprocess" here and then assign the plugin ID to a
132 // separate property that we can later intercept and properly invoke.
133 // @todo Revisit if/when preprocess callbacks can be any callable.
134 Bootstrap::addCallback($info['preprocess functions'], 'bootstrap_preprocess', $definition['replace'], $definition['action']);
135 $info['preprocess functions'] = array_unique($info['preprocess functions']);
136 $info['bootstrap preprocess'] = $plugin_id;
141 $cache[$plugin_id]['incomplete preprocess functions'] = TRUE;
145 // Allow core to post process.
146 $this->postProcessExtension($cache, $this->theme);