Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Theme;
|
Chris@0
|
4
|
Chris@14
|
5 use Drupal\Component\Utility\NestedArray;
|
Chris@0
|
6 use Drupal\Core\Cache\Cache;
|
Chris@0
|
7 use Drupal\Core\Cache\CacheBackendInterface;
|
Chris@0
|
8 use Drupal\Core\DestructableInterface;
|
Chris@0
|
9 use Drupal\Core\Extension\ModuleHandlerInterface;
|
Chris@0
|
10 use Drupal\Core\Extension\ThemeHandlerInterface;
|
Chris@0
|
11 use Drupal\Core\Lock\LockBackendInterface;
|
Chris@0
|
12 use Drupal\Core\Utility\ThemeRegistry;
|
Chris@0
|
13
|
Chris@0
|
14 /**
|
Chris@0
|
15 * Defines the theme registry service.
|
Chris@0
|
16 *
|
Chris@0
|
17 * @internal
|
Chris@0
|
18 *
|
Chris@0
|
19 * Theme registry is expected to be used only internally since every
|
Chris@0
|
20 * hook_theme() implementation depends on the way this class is built. This
|
Chris@0
|
21 * class may get new features in minor releases so this class should be
|
Chris@0
|
22 * considered internal.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @todo Replace local $registry variables in methods with $this->registry.
|
Chris@0
|
25 */
|
Chris@0
|
26 class Registry implements DestructableInterface {
|
Chris@0
|
27
|
Chris@0
|
28 /**
|
Chris@0
|
29 * The theme object representing the active theme for this registry.
|
Chris@0
|
30 *
|
Chris@0
|
31 * @var \Drupal\Core\Theme\ActiveTheme
|
Chris@0
|
32 */
|
Chris@0
|
33 protected $theme;
|
Chris@0
|
34
|
Chris@0
|
35 /**
|
Chris@0
|
36 * The lock backend that should be used.
|
Chris@0
|
37 *
|
Chris@0
|
38 * @var \Drupal\Core\Lock\LockBackendInterface
|
Chris@0
|
39 */
|
Chris@0
|
40 protected $lock;
|
Chris@0
|
41
|
Chris@0
|
42 /**
|
Chris@0
|
43 * The complete theme registry.
|
Chris@0
|
44 *
|
Chris@0
|
45 * @var array
|
Chris@0
|
46 * An array of theme registries, keyed by the theme name. Each registry is
|
Chris@0
|
47 * an associative array keyed by theme hook names, whose values are
|
Chris@0
|
48 * associative arrays containing the aggregated hook definition:
|
Chris@0
|
49 * - type: The type of the extension the original theme hook originates
|
Chris@0
|
50 * from; e.g., 'module' for theme hook 'node' of Node module.
|
Chris@0
|
51 * - name: The name of the extension the original theme hook originates
|
Chris@0
|
52 * from; e.g., 'node' for theme hook 'node' of Node module.
|
Chris@0
|
53 * - theme path: The effective \Drupal\Core\Theme\ActiveTheme::getPath()
|
Chris@0
|
54 * during \Drupal\Core\Theme\ThemeManagerInterface::render(), available
|
Chris@0
|
55 * as 'directory' variable in templates. For functions, it should point
|
Chris@0
|
56 * to the respective theme. For templates, it should point to the
|
Chris@0
|
57 * directory that contains the template.
|
Chris@0
|
58 * - includes: (optional) An array of include files to load when the theme
|
Chris@0
|
59 * hook is executed by \Drupal\Core\Theme\ThemeManagerInterface::render().
|
Chris@0
|
60 * - file: (optional) A filename to add to 'includes', either prefixed with
|
Chris@0
|
61 * the value of 'path', or the path of the extension implementing
|
Chris@0
|
62 * hook_theme().
|
Chris@0
|
63 * In case of a theme base hook, one of the following:
|
Chris@0
|
64 * - variables: An associative array whose keys are variable names and whose
|
Chris@0
|
65 * values are default values of the variables to use for this theme hook.
|
Chris@0
|
66 * - render element: A string denoting the name of the variable name, in
|
Chris@0
|
67 * which the render element for this theme hook is provided.
|
Chris@0
|
68 * In case of a theme template file:
|
Chris@0
|
69 * - path: The path to the template file to use. Defaults to the
|
Chris@0
|
70 * subdirectory 'templates' of the path of the extension implementing
|
Chris@0
|
71 * hook_theme(); e.g., 'core/modules/node/templates' for Node module.
|
Chris@0
|
72 * - template: The basename of the template file to use, without extension
|
Chris@0
|
73 * (as the extension is specific to the theme engine). The template file
|
Chris@0
|
74 * is in the directory defined by 'path'.
|
Chris@0
|
75 * - template_file: A full path and file name to a template file to use.
|
Chris@0
|
76 * Allows any extension to override the effective template file.
|
Chris@0
|
77 * - engine: The theme engine to use for the template file.
|
Chris@0
|
78 * In case of a theme function:
|
Chris@0
|
79 * - function: The function name to call to generate the output.
|
Chris@0
|
80 * For any registered theme hook, including theme hook suggestions:
|
Chris@0
|
81 * - preprocess: An array of theme variable preprocess callbacks to invoke
|
Chris@0
|
82 * before invoking final theme variable processors.
|
Chris@0
|
83 * - process: An array of theme variable process callbacks to invoke
|
Chris@0
|
84 * before invoking the actual theme function or template.
|
Chris@0
|
85 */
|
Chris@0
|
86 protected $registry = [];
|
Chris@0
|
87
|
Chris@0
|
88 /**
|
Chris@0
|
89 * The cache backend to use for the complete theme registry data.
|
Chris@0
|
90 *
|
Chris@0
|
91 * @var \Drupal\Core\Cache\CacheBackendInterface
|
Chris@0
|
92 */
|
Chris@0
|
93 protected $cache;
|
Chris@0
|
94
|
Chris@0
|
95 /**
|
Chris@0
|
96 * The module handler to use to load modules.
|
Chris@0
|
97 *
|
Chris@0
|
98 * @var \Drupal\Core\Extension\ModuleHandlerInterface
|
Chris@0
|
99 */
|
Chris@0
|
100 protected $moduleHandler;
|
Chris@0
|
101
|
Chris@0
|
102 /**
|
Chris@0
|
103 * An array of incomplete, runtime theme registries, keyed by theme name.
|
Chris@0
|
104 *
|
Chris@0
|
105 * @var \Drupal\Core\Utility\ThemeRegistry[]
|
Chris@0
|
106 */
|
Chris@0
|
107 protected $runtimeRegistry = [];
|
Chris@0
|
108
|
Chris@0
|
109 /**
|
Chris@0
|
110 * Stores whether the registry was already initialized.
|
Chris@0
|
111 *
|
Chris@0
|
112 * @var bool
|
Chris@0
|
113 */
|
Chris@0
|
114 protected $initialized = FALSE;
|
Chris@0
|
115
|
Chris@0
|
116 /**
|
Chris@0
|
117 * The name of the theme for which to construct the registry, if given.
|
Chris@0
|
118 *
|
Chris@0
|
119 * @var string|null
|
Chris@0
|
120 */
|
Chris@0
|
121 protected $themeName;
|
Chris@0
|
122
|
Chris@0
|
123 /**
|
Chris@0
|
124 * The app root.
|
Chris@0
|
125 *
|
Chris@0
|
126 * @var string
|
Chris@0
|
127 */
|
Chris@0
|
128 protected $root;
|
Chris@0
|
129
|
Chris@0
|
130 /**
|
Chris@0
|
131 * The theme handler.
|
Chris@0
|
132 *
|
Chris@0
|
133 * @var \Drupal\Core\Extension\ThemeHandlerInterface
|
Chris@0
|
134 */
|
Chris@0
|
135 protected $themeHandler;
|
Chris@0
|
136
|
Chris@0
|
137 /**
|
Chris@0
|
138 * The theme manager.
|
Chris@0
|
139 *
|
Chris@0
|
140 * @var \Drupal\Core\Theme\ThemeManagerInterface
|
Chris@0
|
141 */
|
Chris@0
|
142 protected $themeManager;
|
Chris@0
|
143
|
Chris@0
|
144 /**
|
Chris@0
|
145 * The runtime cache.
|
Chris@0
|
146 *
|
Chris@0
|
147 * @var \Drupal\Core\Cache\CacheBackendInterface
|
Chris@0
|
148 */
|
Chris@0
|
149 protected $runtimeCache;
|
Chris@0
|
150
|
Chris@0
|
151 /**
|
Chris@0
|
152 * Constructs a \Drupal\Core\Theme\Registry object.
|
Chris@0
|
153 *
|
Chris@0
|
154 * @param string $root
|
Chris@0
|
155 * The app root.
|
Chris@0
|
156 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
Chris@0
|
157 * The cache backend interface to use for the complete theme registry data.
|
Chris@0
|
158 * @param \Drupal\Core\Lock\LockBackendInterface $lock
|
Chris@0
|
159 * The lock backend.
|
Chris@0
|
160 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
Chris@0
|
161 * The module handler to use to load modules.
|
Chris@0
|
162 * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
Chris@0
|
163 * The theme handler.
|
Chris@0
|
164 * @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
|
Chris@0
|
165 * The theme initialization.
|
Chris@0
|
166 * @param string $theme_name
|
Chris@0
|
167 * (optional) The name of the theme for which to construct the registry.
|
Chris@0
|
168 * @param \Drupal\Core\Cache\CacheBackendInterface $runtime_cache
|
Chris@0
|
169 * The cache backend interface to use for the runtime theme registry data.
|
Chris@0
|
170 */
|
Chris@0
|
171 public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, $theme_name = NULL, CacheBackendInterface $runtime_cache = NULL) {
|
Chris@0
|
172 $this->root = $root;
|
Chris@0
|
173 $this->cache = $cache;
|
Chris@0
|
174 $this->lock = $lock;
|
Chris@0
|
175 $this->moduleHandler = $module_handler;
|
Chris@0
|
176 $this->themeName = $theme_name;
|
Chris@0
|
177 $this->themeHandler = $theme_handler;
|
Chris@0
|
178 $this->themeInitialization = $theme_initialization;
|
Chris@0
|
179 $this->runtimeCache = $runtime_cache;
|
Chris@0
|
180 }
|
Chris@0
|
181
|
Chris@0
|
182 /**
|
Chris@0
|
183 * Sets the theme manager.
|
Chris@0
|
184 *
|
Chris@0
|
185 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
|
Chris@0
|
186 * The theme manager.
|
Chris@0
|
187 */
|
Chris@0
|
188 public function setThemeManager(ThemeManagerInterface $theme_manager) {
|
Chris@0
|
189 $this->themeManager = $theme_manager;
|
Chris@0
|
190 }
|
Chris@0
|
191
|
Chris@0
|
192 /**
|
Chris@0
|
193 * Initializes a theme with a certain name.
|
Chris@0
|
194 *
|
Chris@0
|
195 * This function does to much magic, so it should be replaced by another
|
Chris@0
|
196 * services which holds the current active theme information.
|
Chris@0
|
197 *
|
Chris@0
|
198 * @param string $theme_name
|
Chris@0
|
199 * (optional) The name of the theme for which to construct the registry.
|
Chris@0
|
200 */
|
Chris@0
|
201 protected function init($theme_name = NULL) {
|
Chris@0
|
202 if ($this->initialized) {
|
Chris@0
|
203 return;
|
Chris@0
|
204 }
|
Chris@0
|
205 // Unless instantiated for a specific theme, use globals.
|
Chris@0
|
206 if (!isset($theme_name)) {
|
Chris@0
|
207 $this->theme = $this->themeManager->getActiveTheme();
|
Chris@0
|
208 }
|
Chris@0
|
209 // Instead of the active theme, a specific theme was requested.
|
Chris@0
|
210 else {
|
Chris@0
|
211 $this->theme = $this->themeInitialization->getActiveThemeByName($theme_name);
|
Chris@0
|
212 $this->themeInitialization->loadActiveTheme($this->theme);
|
Chris@0
|
213 }
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 /**
|
Chris@0
|
217 * Returns the complete theme registry from cache or rebuilds it.
|
Chris@0
|
218 *
|
Chris@0
|
219 * @return array
|
Chris@0
|
220 * The complete theme registry data array.
|
Chris@0
|
221 *
|
Chris@0
|
222 * @see Registry::$registry
|
Chris@0
|
223 */
|
Chris@0
|
224 public function get() {
|
Chris@0
|
225 $this->init($this->themeName);
|
Chris@0
|
226 if (isset($this->registry[$this->theme->getName()])) {
|
Chris@0
|
227 return $this->registry[$this->theme->getName()];
|
Chris@0
|
228 }
|
Chris@0
|
229 if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) {
|
Chris@0
|
230 $this->registry[$this->theme->getName()] = $cache->data;
|
Chris@0
|
231 }
|
Chris@0
|
232 else {
|
Chris@0
|
233 $this->build();
|
Chris@0
|
234 // Only persist it if all modules are loaded to ensure it is complete.
|
Chris@0
|
235 if ($this->moduleHandler->isLoaded()) {
|
Chris@0
|
236 $this->setCache();
|
Chris@0
|
237 }
|
Chris@0
|
238 }
|
Chris@0
|
239 return $this->registry[$this->theme->getName()];
|
Chris@0
|
240 }
|
Chris@0
|
241
|
Chris@0
|
242 /**
|
Chris@0
|
243 * Returns the incomplete, runtime theme registry.
|
Chris@0
|
244 *
|
Chris@0
|
245 * @return \Drupal\Core\Utility\ThemeRegistry
|
Chris@0
|
246 * A shared instance of the ThemeRegistry class, provides an ArrayObject
|
Chris@0
|
247 * that allows it to be accessed with array syntax and isset(), and is more
|
Chris@0
|
248 * lightweight than the full registry.
|
Chris@0
|
249 */
|
Chris@0
|
250 public function getRuntime() {
|
Chris@0
|
251 $this->init($this->themeName);
|
Chris@0
|
252 if (!isset($this->runtimeRegistry[$this->theme->getName()])) {
|
Chris@0
|
253 $this->runtimeRegistry[$this->theme->getName()] = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->runtimeCache ?: $this->cache, $this->lock, ['theme_registry'], $this->moduleHandler->isLoaded());
|
Chris@0
|
254 }
|
Chris@0
|
255 return $this->runtimeRegistry[$this->theme->getName()];
|
Chris@0
|
256 }
|
Chris@0
|
257
|
Chris@0
|
258 /**
|
Chris@0
|
259 * Persists the theme registry in the cache backend.
|
Chris@0
|
260 */
|
Chris@0
|
261 protected function setCache() {
|
Chris@0
|
262 $this->cache->set('theme_registry:' . $this->theme->getName(), $this->registry[$this->theme->getName()], Cache::PERMANENT, ['theme_registry']);
|
Chris@0
|
263 }
|
Chris@0
|
264
|
Chris@0
|
265 /**
|
Chris@0
|
266 * Returns the base hook for a given hook suggestion.
|
Chris@0
|
267 *
|
Chris@0
|
268 * @param string $hook
|
Chris@0
|
269 * The name of a theme hook whose base hook to find.
|
Chris@0
|
270 *
|
Chris@0
|
271 * @return string|false
|
Chris@0
|
272 * The name of the base hook or FALSE.
|
Chris@0
|
273 */
|
Chris@0
|
274 public function getBaseHook($hook) {
|
Chris@0
|
275 $this->init($this->themeName);
|
Chris@0
|
276 $base_hook = $hook;
|
Chris@0
|
277 // Iteratively strip everything after the last '__' delimiter, until a
|
Chris@0
|
278 // base hook definition is found. Recursive base hooks of base hooks are
|
Chris@0
|
279 // not supported, so the base hook must be an original implementation that
|
Chris@0
|
280 // points to a theme function or template.
|
Chris@0
|
281 while ($pos = strrpos($base_hook, '__')) {
|
Chris@0
|
282 $base_hook = substr($base_hook, 0, $pos);
|
Chris@0
|
283 if (isset($this->registry[$base_hook]['exists'])) {
|
Chris@0
|
284 break;
|
Chris@0
|
285 }
|
Chris@0
|
286 }
|
Chris@0
|
287 if ($pos !== FALSE && $base_hook !== $hook) {
|
Chris@0
|
288 return $base_hook;
|
Chris@0
|
289 }
|
Chris@0
|
290 return FALSE;
|
Chris@0
|
291 }
|
Chris@0
|
292
|
Chris@0
|
293 /**
|
Chris@0
|
294 * Builds the theme registry cache.
|
Chris@0
|
295 *
|
Chris@0
|
296 * Theme hook definitions are collected in the following order:
|
Chris@0
|
297 * - Modules
|
Chris@0
|
298 * - Base theme engines
|
Chris@0
|
299 * - Base themes
|
Chris@0
|
300 * - Theme engine
|
Chris@0
|
301 * - Theme
|
Chris@0
|
302 *
|
Chris@0
|
303 * All theme hook definitions are essentially just collated and merged in the
|
Chris@0
|
304 * above order. However, various extension-specific default values and
|
Chris@0
|
305 * customizations are required; e.g., to record the effective file path for
|
Chris@0
|
306 * theme template. Therefore, this method first collects all extensions per
|
Chris@0
|
307 * type, and then dispatches the processing for each extension to
|
Chris@0
|
308 * processExtension().
|
Chris@0
|
309 *
|
Chris@0
|
310 * After completing the collection, modules are allowed to alter it. Lastly,
|
Chris@0
|
311 * any derived and incomplete theme hook definitions that are hook suggestions
|
Chris@0
|
312 * for base hooks (e.g., 'block__node' for the base hook 'block') need to be
|
Chris@0
|
313 * determined based on the full registry and classified as 'base hook'.
|
Chris@0
|
314 *
|
Chris@0
|
315 * See the @link themeable Default theme implementations topic @endlink for
|
Chris@0
|
316 * details.
|
Chris@0
|
317 *
|
Chris@0
|
318 * @return \Drupal\Core\Utility\ThemeRegistry
|
Chris@0
|
319 * The build theme registry.
|
Chris@0
|
320 *
|
Chris@0
|
321 * @see hook_theme_registry_alter()
|
Chris@0
|
322 */
|
Chris@0
|
323 protected function build() {
|
Chris@0
|
324 $cache = [];
|
Chris@0
|
325 // First, preprocess the theme hooks advertised by modules. This will
|
Chris@0
|
326 // serve as the basic registry. Since the list of enabled modules is the
|
Chris@0
|
327 // same regardless of the theme used, this is cached in its own entry to
|
Chris@0
|
328 // save building it for every theme.
|
Chris@0
|
329 if ($cached = $this->cache->get('theme_registry:build:modules')) {
|
Chris@0
|
330 $cache = $cached->data;
|
Chris@0
|
331 }
|
Chris@0
|
332 else {
|
Chris@0
|
333 foreach ($this->moduleHandler->getImplementations('theme') as $module) {
|
Chris@0
|
334 $this->processExtension($cache, $module, 'module', $module, $this->getPath($module));
|
Chris@0
|
335 }
|
Chris@0
|
336 // Only cache this registry if all modules are loaded.
|
Chris@0
|
337 if ($this->moduleHandler->isLoaded()) {
|
Chris@0
|
338 $this->cache->set("theme_registry:build:modules", $cache, Cache::PERMANENT, ['theme_registry']);
|
Chris@0
|
339 }
|
Chris@0
|
340 }
|
Chris@0
|
341
|
Chris@0
|
342 // Process each base theme.
|
Chris@0
|
343 // Ensure that we start with the root of the parents, so that both CSS files
|
Chris@0
|
344 // and preprocess functions comes first.
|
Chris@18
|
345 foreach (array_reverse($this->theme->getBaseThemeExtensions()) as $base) {
|
Chris@0
|
346 // If the base theme uses a theme engine, process its hooks.
|
Chris@0
|
347 $base_path = $base->getPath();
|
Chris@0
|
348 if ($this->theme->getEngine()) {
|
Chris@0
|
349 $this->processExtension($cache, $this->theme->getEngine(), 'base_theme_engine', $base->getName(), $base_path);
|
Chris@0
|
350 }
|
Chris@0
|
351 $this->processExtension($cache, $base->getName(), 'base_theme', $base->getName(), $base_path);
|
Chris@0
|
352 }
|
Chris@0
|
353
|
Chris@0
|
354 // And then the same thing, but for the theme.
|
Chris@0
|
355 if ($this->theme->getEngine()) {
|
Chris@0
|
356 $this->processExtension($cache, $this->theme->getEngine(), 'theme_engine', $this->theme->getName(), $this->theme->getPath());
|
Chris@0
|
357 }
|
Chris@0
|
358
|
Chris@0
|
359 // Hooks provided by the theme itself.
|
Chris@0
|
360 $this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath());
|
Chris@0
|
361
|
Chris@0
|
362 // Discover and add all preprocess functions for theme hook suggestions.
|
Chris@0
|
363 $this->postProcessExtension($cache, $this->theme);
|
Chris@0
|
364
|
Chris@0
|
365 // Let modules and themes alter the registry.
|
Chris@0
|
366 $this->moduleHandler->alter('theme_registry', $cache);
|
Chris@0
|
367 $this->themeManager->alterForTheme($this->theme, 'theme_registry', $cache);
|
Chris@0
|
368
|
Chris@0
|
369 // @todo Implement more reduction of the theme registry entry.
|
Chris@0
|
370 // Optimize the registry to not have empty arrays for functions.
|
Chris@0
|
371 foreach ($cache as $hook => $info) {
|
Chris@0
|
372 if (empty($info['preprocess functions'])) {
|
Chris@0
|
373 unset($cache[$hook]['preprocess functions']);
|
Chris@0
|
374 }
|
Chris@0
|
375 }
|
Chris@0
|
376 $this->registry[$this->theme->getName()] = $cache;
|
Chris@0
|
377
|
Chris@0
|
378 return $this->registry[$this->theme->getName()];
|
Chris@0
|
379 }
|
Chris@0
|
380
|
Chris@0
|
381 /**
|
Chris@0
|
382 * Process a single implementation of hook_theme().
|
Chris@0
|
383 *
|
Chris@0
|
384 * @param array $cache
|
Chris@0
|
385 * The theme registry that will eventually be cached; It is an associative
|
Chris@0
|
386 * array keyed by theme hooks, whose values are associative arrays
|
Chris@0
|
387 * describing the hook:
|
Chris@0
|
388 * - 'type': The passed-in $type.
|
Chris@0
|
389 * - 'theme path': The passed-in $path.
|
Chris@0
|
390 * - 'function': The name of the function generating output for this theme
|
Chris@0
|
391 * hook. Either defined explicitly in hook_theme() or, if neither
|
Chris@0
|
392 * 'function' nor 'template' is defined, then the default theme function
|
Chris@0
|
393 * name is used. The default theme function name is the theme hook
|
Chris@0
|
394 * prefixed by either 'theme_' for modules or '$name_' for everything
|
Chris@0
|
395 * else. If 'function' is defined, 'template' is not used.
|
Chris@0
|
396 * - 'template': The filename of the template generating output for this
|
Chris@0
|
397 * theme hook. The template is in the directory defined by the 'path' key
|
Chris@0
|
398 * of hook_theme() or defaults to "$path/templates".
|
Chris@0
|
399 * - 'variables': The variables for this theme hook as defined in
|
Chris@0
|
400 * hook_theme(). If there is more than one implementation and 'variables'
|
Chris@0
|
401 * is not specified in a later one, then the previous definition is kept.
|
Chris@0
|
402 * - 'render element': The renderable element for this theme hook as defined
|
Chris@0
|
403 * in hook_theme(). If there is more than one implementation and
|
Chris@0
|
404 * 'render element' is not specified in a later one, then the previous
|
Chris@0
|
405 * definition is kept.
|
Chris@0
|
406 * - See the @link themeable Theme system overview topic @endlink for
|
Chris@0
|
407 * detailed documentation.
|
Chris@0
|
408 * @param string $name
|
Chris@0
|
409 * The name of the module, theme engine, base theme engine, theme or base
|
Chris@0
|
410 * theme implementing hook_theme().
|
Chris@0
|
411 * @param string $type
|
Chris@0
|
412 * One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or
|
Chris@0
|
413 * 'base_theme'. Unlike regular hooks that can only be implemented by
|
Chris@0
|
414 * modules, each of these can implement hook_theme(). This function is
|
Chris@0
|
415 * called in aforementioned order and new entries override older ones. For
|
Chris@0
|
416 * example, if a theme hook is both defined by a module and a theme, then
|
Chris@0
|
417 * the definition in the theme will be used.
|
Chris@0
|
418 * @param string $theme
|
Chris@0
|
419 * The actual name of theme, module, etc. that is being processed.
|
Chris@0
|
420 * @param string $path
|
Chris@0
|
421 * The directory where $name is. For example, modules/system or
|
Chris@0
|
422 * themes/bartik.
|
Chris@0
|
423 *
|
Chris@0
|
424 * @see \Drupal\Core\Theme\ThemeManagerInterface::render()
|
Chris@0
|
425 * @see hook_theme()
|
Chris@0
|
426 * @see \Drupal\Core\Extension\ThemeHandler::listInfo()
|
Chris@0
|
427 * @see twig_render_template()
|
Chris@0
|
428 *
|
Chris@0
|
429 * @throws \BadFunctionCallException
|
Chris@0
|
430 */
|
Chris@0
|
431 protected function processExtension(array &$cache, $name, $type, $theme, $path) {
|
Chris@0
|
432 $result = [];
|
Chris@0
|
433
|
Chris@0
|
434 $hook_defaults = [
|
Chris@0
|
435 'variables' => TRUE,
|
Chris@0
|
436 'render element' => TRUE,
|
Chris@0
|
437 'pattern' => TRUE,
|
Chris@0
|
438 'base hook' => TRUE,
|
Chris@0
|
439 ];
|
Chris@0
|
440
|
Chris@0
|
441 $module_list = array_keys($this->moduleHandler->getModuleList());
|
Chris@0
|
442
|
Chris@0
|
443 // Invoke the hook_theme() implementation, preprocess what is returned, and
|
Chris@0
|
444 // merge it into $cache.
|
Chris@0
|
445 $function = $name . '_theme';
|
Chris@0
|
446 if (function_exists($function)) {
|
Chris@0
|
447 $result = $function($cache, $type, $theme, $path);
|
Chris@0
|
448 foreach ($result as $hook => $info) {
|
Chris@0
|
449 // When a theme or engine overrides a module's theme function
|
Chris@0
|
450 // $result[$hook] will only contain key/value pairs for information being
|
Chris@0
|
451 // overridden. Pull the rest of the information from what was defined by
|
Chris@0
|
452 // an earlier hook.
|
Chris@0
|
453
|
Chris@0
|
454 // Fill in the type and path of the module, theme, or engine that
|
Chris@0
|
455 // implements this theme function.
|
Chris@0
|
456 $result[$hook]['type'] = $type;
|
Chris@0
|
457 $result[$hook]['theme path'] = $path;
|
Chris@0
|
458
|
Chris@0
|
459 // If a theme hook has a base hook, mark its preprocess functions always
|
Chris@0
|
460 // incomplete in order to inherit the base hook's preprocess functions.
|
Chris@0
|
461 if (!empty($result[$hook]['base hook'])) {
|
Chris@0
|
462 $result[$hook]['incomplete preprocess functions'] = TRUE;
|
Chris@0
|
463 }
|
Chris@0
|
464
|
Chris@0
|
465 if (isset($cache[$hook]['includes'])) {
|
Chris@0
|
466 $result[$hook]['includes'] = $cache[$hook]['includes'];
|
Chris@0
|
467 }
|
Chris@0
|
468
|
Chris@0
|
469 // Load the includes, as they may contain preprocess functions.
|
Chris@0
|
470 if (isset($info['includes'])) {
|
Chris@0
|
471 foreach ($info['includes'] as $include_file) {
|
Chris@0
|
472 include_once $this->root . '/' . $include_file;
|
Chris@0
|
473 }
|
Chris@0
|
474 }
|
Chris@0
|
475
|
Chris@0
|
476 // If the theme implementation defines a file, then also use the path
|
Chris@0
|
477 // that it defined. Otherwise use the default path. This allows
|
Chris@0
|
478 // system.module to declare theme functions on behalf of core .include
|
Chris@0
|
479 // files.
|
Chris@0
|
480 if (isset($info['file'])) {
|
Chris@0
|
481 $include_file = isset($info['path']) ? $info['path'] : $path;
|
Chris@0
|
482 $include_file .= '/' . $info['file'];
|
Chris@0
|
483 include_once $this->root . '/' . $include_file;
|
Chris@0
|
484 $result[$hook]['includes'][] = $include_file;
|
Chris@0
|
485 }
|
Chris@0
|
486
|
Chris@0
|
487 // A template file is the default implementation for a theme hook, but
|
Chris@0
|
488 // if the theme hook specifies a function callback instead, check to
|
Chris@0
|
489 // ensure the function actually exists.
|
Chris@0
|
490 if (isset($info['function'])) {
|
Chris@0
|
491 if (!function_exists($info['function'])) {
|
Chris@0
|
492 throw new \BadFunctionCallException(sprintf(
|
Chris@0
|
493 'Theme hook "%s" refers to a theme function callback that does not exist: "%s"',
|
Chris@0
|
494 $hook,
|
Chris@0
|
495 $info['function']
|
Chris@0
|
496 ));
|
Chris@0
|
497 }
|
Chris@0
|
498 }
|
Chris@0
|
499 // Provide a default naming convention for 'template' based on the
|
Chris@0
|
500 // hook used. If the template does not exist, the theme engine used
|
Chris@0
|
501 // should throw an exception at runtime when attempting to include
|
Chris@0
|
502 // the template file.
|
Chris@0
|
503 elseif (!isset($info['template'])) {
|
Chris@0
|
504 $info['template'] = strtr($hook, '_', '-');
|
Chris@0
|
505 $result[$hook]['template'] = $info['template'];
|
Chris@0
|
506 }
|
Chris@0
|
507
|
Chris@0
|
508 // Prepend the current theming path when none is set. This is required
|
Chris@0
|
509 // for the default theme engine to know where the template lives.
|
Chris@0
|
510 if (isset($result[$hook]['template']) && !isset($info['path'])) {
|
Chris@0
|
511 $result[$hook]['path'] = $path . '/templates';
|
Chris@0
|
512 }
|
Chris@0
|
513
|
Chris@0
|
514 // If the default keys are not set, use the default values registered
|
Chris@0
|
515 // by the module.
|
Chris@0
|
516 if (isset($cache[$hook])) {
|
Chris@0
|
517 $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
|
Chris@0
|
518 }
|
Chris@0
|
519
|
Chris@0
|
520 // Preprocess variables for all theming hooks, whether the hook is
|
Chris@0
|
521 // implemented as a template or as a function. Ensure they are arrays.
|
Chris@0
|
522 if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
|
Chris@0
|
523 $info['preprocess functions'] = [];
|
Chris@0
|
524 $prefixes = [];
|
Chris@0
|
525 if ($type == 'module') {
|
Chris@0
|
526 // Default variable preprocessor prefix.
|
Chris@0
|
527 $prefixes[] = 'template';
|
Chris@0
|
528 // Add all modules so they can intervene with their own variable
|
Chris@0
|
529 // preprocessors. This allows them to provide variable preprocessors
|
Chris@0
|
530 // even if they are not the owner of the current hook.
|
Chris@0
|
531 $prefixes = array_merge($prefixes, $module_list);
|
Chris@0
|
532 }
|
Chris@0
|
533 elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
|
Chris@0
|
534 // Theme engines get an extra set that come before the normally
|
Chris@0
|
535 // named variable preprocessors.
|
Chris@0
|
536 $prefixes[] = $name . '_engine';
|
Chris@0
|
537 // The theme engine registers on behalf of the theme using the
|
Chris@0
|
538 // theme's name.
|
Chris@0
|
539 $prefixes[] = $theme;
|
Chris@0
|
540 }
|
Chris@0
|
541 else {
|
Chris@0
|
542 // This applies when the theme manually registers their own variable
|
Chris@0
|
543 // preprocessors.
|
Chris@0
|
544 $prefixes[] = $name;
|
Chris@0
|
545 }
|
Chris@0
|
546 foreach ($prefixes as $prefix) {
|
Chris@0
|
547 // Only use non-hook-specific variable preprocessors for theming
|
Chris@0
|
548 // hooks implemented as templates. See the @defgroup themeable
|
Chris@0
|
549 // topic.
|
Chris@0
|
550 if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
|
Chris@0
|
551 $info['preprocess functions'][] = $prefix . '_preprocess';
|
Chris@0
|
552 }
|
Chris@0
|
553 if (function_exists($prefix . '_preprocess_' . $hook)) {
|
Chris@0
|
554 $info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
|
Chris@0
|
555 }
|
Chris@0
|
556 }
|
Chris@0
|
557 }
|
Chris@0
|
558 // Check for the override flag and prevent the cached variable
|
Chris@0
|
559 // preprocessors from being used. This allows themes or theme engines
|
Chris@0
|
560 // to remove variable preprocessors set earlier in the registry build.
|
Chris@0
|
561 if (!empty($info['override preprocess functions'])) {
|
Chris@0
|
562 // Flag not needed inside the registry.
|
Chris@0
|
563 unset($result[$hook]['override preprocess functions']);
|
Chris@0
|
564 }
|
Chris@0
|
565 elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
|
Chris@0
|
566 $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
|
Chris@0
|
567 }
|
Chris@0
|
568 $result[$hook]['preprocess functions'] = $info['preprocess functions'];
|
Chris@14
|
569
|
Chris@14
|
570 // If a theme implementation definition provides both 'template' and
|
Chris@14
|
571 // 'function', the 'function' will be used. In this case, if the new
|
Chris@14
|
572 // result provides a 'template' value, any existing 'function' value
|
Chris@14
|
573 // must be removed for the override to be called.
|
Chris@14
|
574 if (isset($result[$hook]['template'])) {
|
Chris@14
|
575 unset($cache[$hook]['function']);
|
Chris@14
|
576 }
|
Chris@0
|
577 }
|
Chris@0
|
578
|
Chris@0
|
579 // Merge the newly created theme hooks into the existing cache.
|
Chris@14
|
580 $cache = NestedArray::mergeDeep($cache, $result);
|
Chris@0
|
581 }
|
Chris@0
|
582
|
Chris@0
|
583 // Let themes have variable preprocessors even if they didn't register a
|
Chris@0
|
584 // template.
|
Chris@0
|
585 if ($type == 'theme' || $type == 'base_theme') {
|
Chris@0
|
586 foreach ($cache as $hook => $info) {
|
Chris@0
|
587 // Check only if not registered by the theme or engine.
|
Chris@0
|
588 if (empty($result[$hook])) {
|
Chris@0
|
589 if (!isset($info['preprocess functions'])) {
|
Chris@0
|
590 $cache[$hook]['preprocess functions'] = [];
|
Chris@0
|
591 }
|
Chris@0
|
592 // Only use non-hook-specific variable preprocessors for theme hooks
|
Chris@0
|
593 // implemented as templates. See the @defgroup themeable topic.
|
Chris@0
|
594 if (isset($info['template']) && function_exists($name . '_preprocess')) {
|
Chris@0
|
595 $cache[$hook]['preprocess functions'][] = $name . '_preprocess';
|
Chris@0
|
596 }
|
Chris@0
|
597 if (function_exists($name . '_preprocess_' . $hook)) {
|
Chris@0
|
598 $cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
|
Chris@0
|
599 $cache[$hook]['theme path'] = $path;
|
Chris@0
|
600 }
|
Chris@0
|
601 }
|
Chris@0
|
602 }
|
Chris@0
|
603 }
|
Chris@0
|
604 }
|
Chris@0
|
605
|
Chris@0
|
606 /**
|
Chris@0
|
607 * Completes the definition of the requested suggestion hook.
|
Chris@0
|
608 *
|
Chris@0
|
609 * @param string $hook
|
Chris@0
|
610 * The name of the suggestion hook to complete.
|
Chris@0
|
611 * @param array $cache
|
Chris@0
|
612 * The theme registry, as documented in
|
Chris@0
|
613 * \Drupal\Core\Theme\Registry::processExtension().
|
Chris@0
|
614 */
|
Chris@0
|
615 protected function completeSuggestion($hook, array &$cache) {
|
Chris@0
|
616 $previous_hook = $hook;
|
Chris@0
|
617 $incomplete_previous_hook = [];
|
Chris@0
|
618 // Continue looping if the candidate hook doesn't exist or if the candidate
|
Chris@0
|
619 // hook has incomplete preprocess functions, and if the candidate hook is a
|
Chris@0
|
620 // suggestion (has a double underscore).
|
Chris@0
|
621 while ((!isset($cache[$previous_hook]) || isset($cache[$previous_hook]['incomplete preprocess functions']))
|
Chris@0
|
622 && $pos = strrpos($previous_hook, '__')) {
|
Chris@0
|
623 // Find the first existing candidate hook that has incomplete preprocess
|
Chris@0
|
624 // functions.
|
Chris@0
|
625 if (isset($cache[$previous_hook]) && !$incomplete_previous_hook && isset($cache[$previous_hook]['incomplete preprocess functions'])) {
|
Chris@0
|
626 $incomplete_previous_hook = $cache[$previous_hook];
|
Chris@0
|
627 unset($incomplete_previous_hook['incomplete preprocess functions']);
|
Chris@0
|
628 }
|
Chris@0
|
629 $previous_hook = substr($previous_hook, 0, $pos);
|
Chris@0
|
630 $this->mergePreprocessFunctions($hook, $previous_hook, $incomplete_previous_hook, $cache);
|
Chris@0
|
631 }
|
Chris@0
|
632
|
Chris@0
|
633 // In addition to processing suggestions, include base hooks.
|
Chris@0
|
634 if (isset($cache[$hook]['base hook'])) {
|
Chris@0
|
635 // In order to retain the additions from above, pass in the current hook
|
Chris@0
|
636 // as the parent hook, otherwise it will be overwritten.
|
Chris@0
|
637 $this->mergePreprocessFunctions($hook, $cache[$hook]['base hook'], $cache[$hook], $cache);
|
Chris@0
|
638 }
|
Chris@0
|
639 }
|
Chris@0
|
640
|
Chris@0
|
641 /**
|
Chris@0
|
642 * Merges the source hook's preprocess functions into the destination hook's.
|
Chris@0
|
643 *
|
Chris@0
|
644 * @param string $destination_hook_name
|
Chris@0
|
645 * The name of the hook to merge preprocess functions to.
|
Chris@0
|
646 * @param string $source_hook_name
|
Chris@0
|
647 * The name of the hook to merge preprocess functions from.
|
Chris@0
|
648 * @param array $parent_hook
|
Chris@0
|
649 * The parent hook if it exists. Either an incomplete hook from suggestions
|
Chris@0
|
650 * or a base hook.
|
Chris@0
|
651 * @param array $cache
|
Chris@0
|
652 * The theme registry, as documented in
|
Chris@0
|
653 * \Drupal\Core\Theme\Registry::processExtension().
|
Chris@0
|
654 */
|
Chris@0
|
655 protected function mergePreprocessFunctions($destination_hook_name, $source_hook_name, $parent_hook, array &$cache) {
|
Chris@0
|
656 // If base hook exists clone of it for the preprocess function
|
Chris@0
|
657 // without a template.
|
Chris@0
|
658 // @see https://www.drupal.org/node/2457295
|
Chris@0
|
659 if (isset($cache[$source_hook_name]) && (!isset($cache[$source_hook_name]['incomplete preprocess functions']) || !isset($cache[$destination_hook_name]['incomplete preprocess functions']))) {
|
Chris@0
|
660 $cache[$destination_hook_name] = $parent_hook + $cache[$source_hook_name];
|
Chris@0
|
661 if (isset($parent_hook['preprocess functions'])) {
|
Chris@0
|
662 $diff = array_diff($parent_hook['preprocess functions'], $cache[$source_hook_name]['preprocess functions']);
|
Chris@0
|
663 $cache[$destination_hook_name]['preprocess functions'] = array_merge($cache[$source_hook_name]['preprocess functions'], $diff);
|
Chris@0
|
664 }
|
Chris@0
|
665 // If a base hook isn't set, this is the actual base hook.
|
Chris@0
|
666 if (!isset($cache[$source_hook_name]['base hook'])) {
|
Chris@0
|
667 $cache[$destination_hook_name]['base hook'] = $source_hook_name;
|
Chris@0
|
668 }
|
Chris@0
|
669 }
|
Chris@0
|
670 }
|
Chris@0
|
671
|
Chris@0
|
672 /**
|
Chris@0
|
673 * Completes the theme registry adding discovered functions and hooks.
|
Chris@0
|
674 *
|
Chris@0
|
675 * @param array $cache
|
Chris@0
|
676 * The theme registry as documented in
|
Chris@0
|
677 * \Drupal\Core\Theme\Registry::processExtension().
|
Chris@0
|
678 * @param \Drupal\Core\Theme\ActiveTheme $theme
|
Chris@0
|
679 * Current active theme.
|
Chris@0
|
680 *
|
Chris@0
|
681 * @see ::processExtension()
|
Chris@0
|
682 */
|
Chris@0
|
683 protected function postProcessExtension(array &$cache, ActiveTheme $theme) {
|
Chris@0
|
684 // Gather prefixes. This will be used to limit the found functions to the
|
Chris@0
|
685 // expected naming conventions.
|
Chris@0
|
686 $prefixes = array_keys((array) $this->moduleHandler->getModuleList());
|
Chris@18
|
687 foreach (array_reverse($theme->getBaseThemeExtensions()) as $base) {
|
Chris@0
|
688 $prefixes[] = $base->getName();
|
Chris@0
|
689 }
|
Chris@0
|
690 if ($theme->getEngine()) {
|
Chris@0
|
691 $prefixes[] = $theme->getEngine() . '_engine';
|
Chris@0
|
692 }
|
Chris@0
|
693 $prefixes[] = $theme->getName();
|
Chris@0
|
694
|
Chris@0
|
695 $grouped_functions = $this->getPrefixGroupedUserFunctions($prefixes);
|
Chris@0
|
696
|
Chris@0
|
697 // Collect all variable preprocess functions in the correct order.
|
Chris@0
|
698 $suggestion_level = [];
|
Chris@0
|
699 $matches = [];
|
Chris@0
|
700 // Look for functions named according to the pattern and add them if they
|
Chris@0
|
701 // have matching hooks in the registry.
|
Chris@0
|
702 foreach ($prefixes as $prefix) {
|
Chris@0
|
703 // Grep only the functions which are within the prefix group.
|
Chris@0
|
704 list($first_prefix,) = explode('_', $prefix, 2);
|
Chris@0
|
705 if (!isset($grouped_functions[$first_prefix])) {
|
Chris@0
|
706 continue;
|
Chris@0
|
707 }
|
Chris@0
|
708 // Add the function and the name of the associated theme hook to the list
|
Chris@0
|
709 // of preprocess functions grouped by suggestion specificity if a matching
|
Chris@0
|
710 // base hook is found.
|
Chris@0
|
711 foreach ($grouped_functions[$first_prefix] as $candidate) {
|
Chris@0
|
712 if (preg_match("/^{$prefix}_preprocess_(((?:[^_]++|_(?!_))+)__.*)/", $candidate, $matches)) {
|
Chris@0
|
713 if (isset($cache[$matches[2]])) {
|
Chris@0
|
714 $level = substr_count($matches[1], '__');
|
Chris@0
|
715 $suggestion_level[$level][$candidate] = $matches[1];
|
Chris@0
|
716 }
|
Chris@0
|
717 }
|
Chris@0
|
718 }
|
Chris@0
|
719 }
|
Chris@0
|
720
|
Chris@0
|
721 // Add missing variable preprocessors. This is needed for modules that do
|
Chris@0
|
722 // not explicitly register the hook. For example, when a theme contains a
|
Chris@0
|
723 // variable preprocess function but it does not implement a template, it
|
Chris@0
|
724 // will go missing. This will add the expected function. It also allows
|
Chris@0
|
725 // modules or themes to have a variable process function based on a pattern
|
Chris@0
|
726 // even if the hook does not exist.
|
Chris@0
|
727 ksort($suggestion_level);
|
Chris@0
|
728 foreach ($suggestion_level as $level => $item) {
|
Chris@0
|
729 foreach ($item as $preprocessor => $hook) {
|
Chris@0
|
730 if (isset($cache[$hook]['preprocess functions']) && !in_array($hook, $cache[$hook]['preprocess functions'])) {
|
Chris@0
|
731 // Add missing preprocessor to existing hook.
|
Chris@0
|
732 $cache[$hook]['preprocess functions'][] = $preprocessor;
|
Chris@0
|
733 }
|
Chris@0
|
734 elseif (!isset($cache[$hook]) && strpos($hook, '__')) {
|
Chris@0
|
735 // Process non-existing hook and register it.
|
Chris@0
|
736 // Look for a previously defined hook that is either a less specific
|
Chris@0
|
737 // suggestion hook or the base hook.
|
Chris@0
|
738 $this->completeSuggestion($hook, $cache);
|
Chris@0
|
739 $cache[$hook]['preprocess functions'][] = $preprocessor;
|
Chris@0
|
740 }
|
Chris@0
|
741 }
|
Chris@0
|
742 }
|
Chris@0
|
743 // Inherit all base hook variable preprocess functions into suggestion
|
Chris@0
|
744 // hooks. This ensures that derivative hooks have a complete set of variable
|
Chris@0
|
745 // preprocess functions.
|
Chris@0
|
746 foreach ($cache as $hook => $info) {
|
Chris@0
|
747 // The 'base hook' is only applied to derivative hooks already registered
|
Chris@0
|
748 // from a pattern. This is typically set from
|
Chris@0
|
749 // drupal_find_theme_functions() and drupal_find_theme_templates().
|
Chris@0
|
750 if (isset($info['incomplete preprocess functions'])) {
|
Chris@0
|
751 $this->completeSuggestion($hook, $cache);
|
Chris@0
|
752 unset($cache[$hook]['incomplete preprocess functions']);
|
Chris@0
|
753 }
|
Chris@0
|
754
|
Chris@0
|
755 // Optimize the registry.
|
Chris@0
|
756 if (isset($cache[$hook]['preprocess functions']) && empty($cache[$hook]['preprocess functions'])) {
|
Chris@0
|
757 unset($cache[$hook]['preprocess functions']);
|
Chris@0
|
758 }
|
Chris@0
|
759 // Ensure uniqueness.
|
Chris@0
|
760 if (isset($cache[$hook]['preprocess functions'])) {
|
Chris@0
|
761 $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
|
Chris@0
|
762 }
|
Chris@0
|
763 }
|
Chris@0
|
764 }
|
Chris@0
|
765
|
Chris@0
|
766 /**
|
Chris@0
|
767 * Invalidates theme registry caches.
|
Chris@0
|
768 *
|
Chris@0
|
769 * To be called when the list of enabled extensions is changed.
|
Chris@0
|
770 */
|
Chris@0
|
771 public function reset() {
|
Chris@0
|
772 // Reset the runtime registry.
|
Chris@0
|
773 foreach ($this->runtimeRegistry as $runtime_registry) {
|
Chris@0
|
774 $runtime_registry->clear();
|
Chris@0
|
775 }
|
Chris@0
|
776 $this->runtimeRegistry = [];
|
Chris@0
|
777
|
Chris@0
|
778 $this->registry = [];
|
Chris@0
|
779 Cache::invalidateTags(['theme_registry']);
|
Chris@0
|
780 return $this;
|
Chris@0
|
781 }
|
Chris@0
|
782
|
Chris@0
|
783 /**
|
Chris@0
|
784 * {@inheritdoc}
|
Chris@0
|
785 */
|
Chris@0
|
786 public function destruct() {
|
Chris@0
|
787 foreach ($this->runtimeRegistry as $runtime_registry) {
|
Chris@0
|
788 $runtime_registry->destruct();
|
Chris@0
|
789 }
|
Chris@0
|
790 }
|
Chris@0
|
791
|
Chris@0
|
792 /**
|
Chris@0
|
793 * Gets all user functions grouped by the word before the first underscore.
|
Chris@0
|
794 *
|
Chris@0
|
795 * @param $prefixes
|
Chris@0
|
796 * An array of function prefixes by which the list can be limited.
|
Chris@0
|
797 * @return array
|
Chris@0
|
798 * Functions grouped by the first prefix.
|
Chris@0
|
799 */
|
Chris@0
|
800 public function getPrefixGroupedUserFunctions($prefixes = []) {
|
Chris@0
|
801 $functions = get_defined_functions();
|
Chris@0
|
802
|
Chris@0
|
803 // If a list of prefixes is supplied, trim down the list to those items
|
Chris@0
|
804 // only as efficiently as possible.
|
Chris@0
|
805 if ($prefixes) {
|
Chris@0
|
806 $theme_functions = preg_grep('/^(' . implode(')|(', $prefixes) . ')_/', $functions['user']);
|
Chris@0
|
807 }
|
Chris@0
|
808 else {
|
Chris@0
|
809 $theme_functions = $functions['user'];
|
Chris@0
|
810 }
|
Chris@0
|
811
|
Chris@0
|
812 $grouped_functions = [];
|
Chris@0
|
813 // Splitting user defined functions into groups by the first prefix.
|
Chris@0
|
814 foreach ($theme_functions as $function) {
|
Chris@0
|
815 list($first_prefix,) = explode('_', $function, 2);
|
Chris@0
|
816 $grouped_functions[$first_prefix][] = $function;
|
Chris@0
|
817 }
|
Chris@0
|
818
|
Chris@0
|
819 return $grouped_functions;
|
Chris@0
|
820 }
|
Chris@0
|
821
|
Chris@0
|
822 /**
|
Chris@0
|
823 * Wraps drupal_get_path().
|
Chris@0
|
824 *
|
Chris@0
|
825 * @param string $module
|
Chris@0
|
826 * The name of the item for which the path is requested.
|
Chris@0
|
827 *
|
Chris@0
|
828 * @return string
|
Chris@0
|
829 */
|
Chris@0
|
830 protected function getPath($module) {
|
Chris@0
|
831 return drupal_get_path('module', $module);
|
Chris@0
|
832 }
|
Chris@0
|
833
|
Chris@0
|
834 }
|