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