Renaming extensions cache key.
[project/omega.git] / includes / omega.inc
1 <?php
2
3 /**
4 * @file
5 * Helper functions for the Omega base theme.
6 */
7
8 /**
9 * Retrieve a setting for the current theme or for a given theme.
10 *
11 * The final setting is obtained from the last value found in the following
12 * sources:
13 * - the default global settings specified in this function
14 * - the default theme-specific settings defined in any base theme's .info file
15 * - the default theme-specific settings defined in the theme's .info file
16 * - the saved values from the global theme settings form
17 * - the saved values from the theme's settings form
18 * To only retrieve the default global theme setting, an empty string should be
19 * given for $theme.
20 *
21 * @param $setting_name
22 * The name of the setting to be retrieved.
23 * @param $default
24 * (optional) A default value. Defaults to NULL.
25 * @param $theme
26 * (optional) The name of a given theme. Defaults to the NULL which
27 * evaluates to the current theme.
28 *
29 * @return
30 * The value of the requested setting, or the $default value if the setting
31 * does not exist.
32 *
33 * @see theme_get_setting().
34 */
35 function omega_theme_get_setting($setting_name, $default = NULL, $theme = NULL) {
36 $cache = &drupal_static('theme_get_setting', array());
37
38 // If no key is given, use the current theme if we can determine it.
39 if (!isset($theme)) {
40 $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : '';
41 }
42
43 if (empty($cache[$theme])) {
44 // If the cache has not been filled yet, invoke theme_get_setting to
45 // retrieve the value. This will populate the cache and make it available
46 // for subsequent requests.
47 if (($setting = theme_get_setting($setting_name, $theme)) !== NULL) {
48 // Use the default value if the setting does not exist.
49 return $setting;
50 }
51 }
52 else {
53 // Retrieve the value from the cache.
54 if (isset($cache[$theme][$setting_name])) {
55 return $cache[$theme][$setting_name];
56 }
57 }
58
59 // Use the default value if the settingdoes not exist.
60 return $default;
61 }
62
63 /**
64 * Builds the full theme trail (deepest base theme first, subtheme last) for a
65 * theme.
66 *
67 * @param $theme
68 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
69 * of the current theme.
70 *
71 * @return array
72 * An array of all themes in the trail, keyed by theme key.
73 */
74 function omega_theme_trail($theme = NULL) {
75 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
76 $cache = &drupal_static(__FUNCTION__);
77
78 if (!isset($cache[$theme])) {
79 $cache[$theme] = array();
80
81 if ($theme == $GLOBALS['theme'] && isset($GLOBALS['theme_info']->base_themes)) {
82 $cache[$theme] = $GLOBALS['theme_info']->base_themes;
83 }
84
85 $themes = list_themes();
86 if (empty($cache[$theme]) && isset($themes[$theme]->info['base theme'])) {
87 $cache[$theme] = system_find_base_themes($themes, $theme);
88 }
89
90 // Add our current subtheme ($key) to that array.
91 $cache[$theme][$theme] = $themes[$theme]->info['name'];
92 }
93
94 return $cache[$theme];
95 }
96
97 /**
98 * Pre-processes CSS files so that CSS files that have 'preprocess_media' set to
99 * TRUE are set to media="all" while having their former media query added to
100 * the file content.
101 *
102 * @param $elements
103 * An array of CSS files as in drupal_pre_render_styles().
104 *
105 * @return array
106 * An array of preprocessed CSS files.
107 *
108 * @see drupal_pre_render_styles()
109 */
110 function omega_css_preprocessor($elements) {
111 foreach ($elements['#items'] as &$item) {
112 if ($item['type'] == 'file' && $item['preprocess'] && $item['media'] != 'all') {
113 $item['data'] = omega_css_cache_media_queries($item);
114 $item['media'] = 'all';
115 }
116 }
117
118 return $elements;
119 }
120
121 /**
122 * Optimizes CSS aggregation by creating a cached version of each CSS file that,
123 * instead of using the 'media' attribute on the styles tag, writes the media
124 * query into the file itself using the '@media { ... }' syntax.
125 *
126 * This prevents unnecessary sprouting of new CSS aggregation.
127 *
128 * @see drupal_build_css_cache().
129 */
130 function omega_css_cache_media_queries($item) {
131 $map = variable_get('drupal_css_cache_files', array());
132 $key = hash('sha256', serialize($item));
133 $uri = isset($map[$key]) ? $map[$key] : NULL;
134
135 if (empty($uri) || !file_exists($uri)) {
136 // Build the base URL of this CSS file: start with the full URL.
137 $base = file_create_url($item['data']);
138 $base = substr($base, 0, strrpos($base, '/'));
139
140 if (substr($base, 0, strlen($GLOBALS['base_theme'])) == $GLOBALS['base_theme']) {
141 $base = substr($base, strlen($GLOBALS['base_theme']));
142 }
143
144 _drupal_build_css_path(NULL, $base . '/');
145
146 $data = drupal_load_stylesheet($item['data'], TRUE);
147
148 // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
149 $data = preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $data);
150 $data = '@media ' . $item['media'] . '{' . $data . '}';
151
152 // Create the css/ within the files folder.
153 $directory = 'public://css';
154 $uri = $directory . '/css_' . drupal_hash_base64($data) . '.css';
155
156 // Create the CSS file.
157 file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
158 if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
159 return FALSE;
160 }
161
162 // If CSS gzip compression is enabled, clean URLs are enabled (which means
163 // that rewrite rules are working) and the zlib extension is available then
164 // create a gzipped version of this file. This file is served conditionally
165 // to browsers that accept gzip using .htaccess rules.
166 if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
167 if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
168 return FALSE;
169 }
170 }
171
172 // Save the updated map.
173 $map[$key] = $uri;
174
175 // Write the updated map into the variable.
176 variable_set('drupal_css_cache_files', $map);
177 }
178
179 return $uri;
180 }
181
182 /**
183 * Helper function for eliminating elements from an array using a simplified
184 * regex pattern.
185 *
186 * @param $elements
187 * The array of elements that should have some elements nuked.
188 * @param $exclude
189 * An array of strings that should be matched against the keys of the array
190 * of elements.
191 *
192 * @return array
193 * The purged array.
194 */
195 function omega_exclude_assets(&$elements, $exclude) {
196 // For optimization reasons we load the theme trail to check whether a
197 // namespace matches the machine-readable name of one of the themes in the
198 // trail.
199 $trail = omega_theme_trail();
200
201 foreach ($exclude as $item) {
202 $path = '';
203 // The first segment (everything before the first slash) is the namespace.
204 list($namespace) = explode('/', $item);
205
206 // Check if the namespace refers to a file residing in the 'misc' folder.
207 if ($namespace == 'misc') {
208 $path = DRUPAL_ROOT . '/misc';
209 }
210 // Check if the namespace refers to a theme.
211 elseif (array_key_exists($namespace, $trail)) {
212 $path = drupal_get_path('theme', $namespace);
213 }
214 else {
215 // Otherwise, check if it refers to a module, profile or theme engine.
216 foreach (array('module', 'profile', 'theme_engine') as $type) {
217 if ($path = drupal_get_path($type, $namespace)) {
218 break;
219 }
220 }
221 }
222
223 // If a namespace could be identified, use its path as a prefix, otherwise
224 // use the plain file path as provided.
225 $item = $path ? $path . '/' . substr($item, strlen($namespace) + 1) : $item;
226 $item = preg_quote($item, '/');
227 // Turn the * wildcards into actual regex wildcards and make sure that, if
228 // a .css file is targeted directly we are also removing the RTL version of
229 // that file.
230 $item = str_replace(array('\*', '\.css'), array('(.*)', '(\.css|-rtl\.css)'), $item);
231
232 // Look up all elements that match this exclusion pattern.
233 $filtered = preg_grep("/^$item$/", array_keys($elements));
234 $elements = array_diff_key($elements, array_flip($filtered));
235 }
236 }
237
238 /**
239 * Retrieves the array of enabled extensions for a theme. Extensions can be
240 * registered through the .info file. Each extension can define a theme settings
241 * form altering function named
242 * 'THEMENAME_extension_EXTENSION_theme_settings_form_alter()' through a file
243 * named 'THEME_ROOT/includes/EXTENSION/EXTENSION.settings.inc' to have it
244 * automatically included whenever the theme settings form is displayed. Each
245 * extension can also define a
246 * 'THEMENAME_extension_EXTENSION_theme_registry_alter()' function through a
247 * file named 'THEME_ROOT/includes/EXTENSION/EXTENSION.inc' to register custom
248 * hooks with the theme registry.
249 *
250 * @param $theme
251 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
252 * of the current theme.
253 *
254 * @return array
255 * The theme info array of the passed or current theme.
256 *
257 * @see _system_default_theme_features()
258 * @see omega_extension_development_theme_settings_form_alter()
259 * @see omega_extension_development_theme_registry_alter()
260 */
261 function omega_extensions($theme = NULL) {
262 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
263 $extensions = drupal_static(__FUNCTION__);
264
265 if (!isset($extensions[$theme])) {
266 if (($cache = cache_get('omega_extensions:' . $theme)) !== FALSE) {
267 $extensions[$theme] = $cache->data;
268 }
269 else {
270 // Extensions can't be hidden.
271 $extensions[$theme] = omega_discovery('extension', $theme, TRUE);
272
273 foreach ($extensions[$theme] as $extension => &$info) {
274 // Make sure that the theme variable is never altered.
275 $context = $theme;
276 drupal_alter('omega_extension_info', $info, $context);
277
278 // Determine if the extension is enabled.
279 $info['enabled'] = omega_theme_get_setting('omega_toggle_extension_' . $extension, !empty($info['info']['enabled']));
280
281 // Check if all dependencies are met.
282 if ($info['enabled'] && !empty($info['dependencies'])) {
283 foreach ($info['dependencies'] as $dependency) {
284 $dependency = drupal_parse_dependency($dependency);
285
286 if ((!$module = system_get_info('module', $dependency['name'])) || drupal_check_incompatibility($dependency, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $module['version']))) {
287 $info['enabled'] = FALSE;
288 }
289 }
290 }
291 }
292
293 // Write to the cache.
294 cache_set('omega_extensions:' . $theme, $extensions[$theme]);
295 }
296 }
297
298 return $extensions[$theme];
299 }
300
301 /**
302 * Determines if an extension is enabled.
303 *
304 * @param $extension
305 * The machine-readable name of an extension.
306 * @param $theme
307 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
308 * of the current theme.
309 *
310 * @return bool
311 * TRUE if the extension is enabled, FALSE otherwise.
312 */
313 function omega_extension_enabled($extension, $theme = NULL) {
314 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
315 if (($extensions = omega_extensions($theme)) && isset($extensions[$extension])) {
316 return $extensions[$extension]['enabled'];
317 }
318 }
319
320 /**
321 * Looks up the info array of all themes in the theme trail and retrieves a
322 * particular info array element.
323 */
324 function omega_theme_trail_info($element, $merge = TRUE, $theme = NULL) {
325 $output = array();
326
327 // Loop over all themes in the theme trail and look up $element in the .info
328 // array.
329 foreach (array_reverse(omega_theme_trail($theme)) as $key => $name) {
330 $info = omega_theme_info($key);
331
332 // If $merge is TRUE we combine all the results of all themes in the theme
333 // trail. Otherwise we just return the first occurrence.
334 if (isset($info[$element]) && is_array($info[$element])) {
335 $output = array_merge($info[$element], $output);
336
337 if (!$merge) {
338 return array('theme' => $key, 'info' => $output);
339 }
340 }
341 }
342
343 return $output;
344 }
345
346 /**
347 * Retrieves the full info array of a theme.
348 *
349 * @param $theme
350 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
351 * of the current theme.
352 *
353 * @return array
354 * The theme info array of the passed or current theme.
355 */
356 function omega_theme_info($theme = NULL) {
357 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
358
359 // If this is the current theme, just load the theme info from the globals.
360 // Note: The global 'theme_key' property is not reliable in this case because
361 // it gets overridden on theme settings pages.
362 if ($theme == $GLOBALS['theme']) {
363 return $GLOBALS['theme_info']->info;
364 }
365
366 $themes = list_themes();
367 return $themes[$theme]->info;
368 }
369
370 /**
371 * Invoke a hook in all themes in the theme trail that implement it.
372 *
373 * @param $hook
374 * The name of the hook to invoke.
375 * @param $theme
376 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
377 * of the current theme.
378 * @param ...
379 * Arguments to pass to the hook.
380 *
381 * @return array
382 * An array of return values of the hook implementations. If themes return
383 * arrays from their implementations, those are merged into one array.
384 *
385 * @see module_invoke_all()
386 */
387 function omega_invoke_all($hook, $theme = NULL) {
388 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
389
390 $args = func_get_args();
391 // Remove $hook from the arguments.
392 unset($args[0], $args[1]);
393
394 $return = array();
395 foreach (omega_theme_trail($theme) as $key => $name) {
396 $function = $key . '_' . $hook;
397
398 if (function_exists($function)) {
399 $result = call_user_func_array($function, array_merge(array($theme), array_values($args)));
400 if (isset($result) && is_array($result)) {
401 // Append the 'theme' property to each array element.
402 foreach ($result as &$item) {
403 $item['theme'] = $key;
404 }
405 $return = array_merge_recursive($return, $result);
406 }
407 elseif (isset($result)) {
408 $return[] = $result;
409 }
410 }
411 }
412 return $return;
413 }
414
415 /**
416 * Custom implementation of drupal_array_get_nested_value() that also supports
417 * objects instead of just arrays.
418 *
419 * @param $object
420 * The array or object from which to get the value.
421 * @param $parents
422 * An array of parent keys of the value, starting with the outermost key.
423 * @param $key_exists
424 * (optional) If given, an already defined variable that is altered by
425 * reference.
426 *
427 * @return mixed
428 * The requested nested value. Possibly NULL if the value is NULL or not all
429 * nested parent keys exist. $key_exists is altered by reference and is a
430 * Boolean that indicates whether all nested parent keys exist (TRUE) or not
431 * (FALSE). This allows to distinguish between the two possibilities when NULL
432 * is returned.
433 *
434 * @see drupal_array_get_nested_value()
435 */
436 function omega_get_nested_value(&$object, array $parents, &$key_exists = NULL) {
437 $ref = &$object;
438 foreach ($parents as $parent) {
439 if (is_array($ref) && array_key_exists($parent, $ref)) {
440 $ref = &$ref[$parent];
441 }
442 elseif (is_object($ref) && property_exists($ref, $parent)) {
443 $ref = &$ref->$parent;
444 }
445 else {
446 $key_exists = FALSE;
447 return NULL;
448 }
449 }
450 $key_exists = TRUE;
451 return $ref;
452 }
453
454 /**
455 * Retrieves the info array for all available layouts.
456 *
457 * @param $theme
458 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
459 * of the current theme.
460 *
461 * @return array
462 * An array of available layouts for the given theme.
463 */
464 function omega_layouts_info($theme = NULL) {
465 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
466 $layouts = drupal_static(__FUNCTION__);
467
468 if (!isset($layouts[$theme])) {
469 $layouts[$theme] = omega_discovery('layout', $theme);
470
471 // A theme or base theme can explicitly restrict the available layouts to
472 // a subset defined through the .info file.
473 if ($filter = omega_theme_trail_info('layouts', TRUE, $theme)) {
474 $layouts[$theme] = array_intersect_key($layouts[$theme], array_flip($filter));
475 }
476
477 foreach ($layouts[$theme] as $layout => &$info) {
478 $info['attached'] = array();
479
480 // Look up possible CSS and JS file overrides.
481 if (isset($info['info']['stylesheets'])) {
482 foreach ($info['info']['stylesheets'] as $media => $files) {
483 foreach ($files as $key => $file) {
484 $info['attached']['css'][$key] = array(
485 'media' => $media,
486 'data' => $info['path'] . '/' . $file,
487 'group' => CSS_THEME,
488 'every_page' => TRUE,
489 'weight' => -10,
490 );
491 }
492 }
493 }
494
495 // Look up possible CSS and JS file overrides.
496 if (isset($info['info']['scripts'])) {
497 foreach ($info['info']['scripts'] as $key => $file) {
498 $info['attached']['js'][$key] = array(
499 'data' => $info['path'] . '/' . $file,
500 'group' => JS_THEME,
501 'every_page' => TRUE,
502 'weight' => -10,
503 );
504 }
505 }
506 }
507
508 $context = $theme;
509
510 // Give modules and themes a chance to alter the layout info array.
511 drupal_alter('omega_layouts_info', $layouts[$theme], $context);
512 }
513
514 return $layouts[$theme];
515 }
516
517 /**
518 * Retrieves the active layout for the current page.
519 *
520 * @return array|bool
521 * The info array for the active layout or FALSE if the current page does not
522 * use an alternative page layout.
523 */
524 function omega_layout() {
525 $cache = &drupal_static(__FUNCTION__);
526
527 if (!isset($cache)) {
528 $cache = FALSE;
529 // Load the default layout from the theme settings.
530 $layout = omega_theme_get_setting('omega_layout', 'epiqo');
531 drupal_alter('omega_layout', $layout);
532
533 $registry = theme_get_registry();
534 if (isset($registry['page__layout__' . $layout]['layout'])) {
535 $cache = $registry['page__layout__' . $layout]['layout'];
536 }
537 }
538
539 return $cache;
540 }
541
542 /**
543 * Allow themes to easily define libraries.
544 *
545 * @param $theme
546 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
547 * of the current theme.
548 *
549 * @return array
550 * An array of libraries defined by themes in the theme trail of the given
551 * theme.
552 */
553 function omega_theme_libraries_info($theme = NULL) {
554 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
555 $libraries = drupal_static(__FUNCTION__);
556
557 if (!isset($libraries[$theme])) {
558 $libraries[$theme] = array();
559
560 foreach (omega_invoke_all('omega_theme_libraries_info') as $library => $info) {
561 $libraries[$theme][$library] = $info;
562 }
563
564 $context = $theme;
565
566 // Give modules and themes a chance to alter the libraries info array.
567 drupal_alter('omega_theme_libraries_info', $libraries[$theme], $context);
568 }
569
570 return $libraries[$theme];
571 }
572
573 /**
574 * Helper function for discovering layouts, extensions or other pluggins of any
575 * sort in the theme trail.
576 *
577 * @param $type
578 * A theme extension type (e.g. layout or extension).
579 * @param $theme
580 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
581 * of the current theme.
582 * @param $all
583 * (Optional) Whether hidden elements should be listed. Defaults to FALSE.
584 *
585 * @return array
586 * An array containing the discovered definitions.
587 */
588 function omega_discovery($type, $theme = NULL, $all = FALSE) {
589 $discovery = array();
590 $strlen = strlen($type) + 1;
591
592 foreach (array_reverse(omega_theme_trail($theme)) as $key => $label) {
593 $path = drupal_get_path('theme', $key);
594
595 // Support files without '.inc' extension for backwards compatibilty.
596 foreach (file_scan_directory($path, '/\.' . $type . '(\.inc)?$/', array('key' => 'name')) as $name => $file) {
597 if (substr($name, -$strlen) === '.' . $type) {
598 $name = substr($name, 0, strlen($name) - $strlen);
599 }
600
601 // The 'hidden' flag is used by starterkits/blueprints.
602 if (($info = drupal_parse_info_file($file->uri)) && ($all || empty($info['hidden']))) {
603 $discovery[$name] = array(
604 'name' => $name,
605 'path' => dirname($file->uri),
606 'info' => $info,
607 'theme' => $key,
608 );
609 }
610 }
611 }
612
613 return $discovery;
614 }