Removing deprecated setting for inline media queries.
[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 elseif (isset($cache[$theme][$setting_name])) {
53 // Retrieve the value from the cache.
54 return $cache[$theme][$setting_name];
55 }
56
57 // Use the default value if the setting does not exist.
58 return $default;
59 }
60
61 /**
62 * Builds the full theme trail (deepest base theme first, subtheme last) for a
63 * theme.
64 *
65 * @param $theme
66 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
67 * of the current theme.
68 *
69 * @return array
70 * An array of all themes in the trail, keyed by theme key.
71 */
72 function omega_theme_trail($theme = NULL) {
73 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
74
75 if (($cache = &drupal_static(__FUNCTION__)) && isset($cache[$theme])) {
76 return $cache[$theme];
77 }
78
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 return $cache[$theme];
94 }
95
96 /**
97 * Helper function for generating a regex from a list of paths.
98 *
99 * Generates a single regex from a list of file paths that can be used to match
100 * JS or CSS files using preg_grep() for example in hook_css_alter() or
101 * hook_js_alter(). The '*' (asterisk) character can be used as a wild-card.
102 *
103 * @param $paths
104 * An array of file paths.
105 *
106 * @return string
107 * The generated regex.
108 *
109 * @see hook_js_alter()
110 * @see hook_css_alter()
111 */
112 function omega_generate_path_regex($paths) {
113 foreach ($paths as &$item) {
114 // The first segment (everything before the first slash) is the namespace.
115 // This rule only applies to local files... So if the namespace can not be
116 // mapped to a module, profile or theme engine we assume that the we are
117 // trying to target an external file.
118 list($namespace) = explode('/', $item);
119
120 // Check if the namespace refers to a file residing in the 'misc' folder or
121 // if it is a global wildcard.
122 if ($namespace !== '*' && $namespace !== 'misc') {
123 // Otherwise, check if it refers to a theme, module, profile or theme
124 // engine.
125 foreach (array('theme', 'module', 'profile', 'theme_engine') as $type) {
126 // We can't use drupal_get_path() directly because that uses dirname()
127 // internally which returns '.' if no filename was found.
128 if ($filename = drupal_get_filename($type, $namespace)) {
129 $prefix = dirname($filename);
130 $item = substr_replace($item, $prefix, 0, strlen($namespace));
131 break;
132 }
133 }
134 }
135
136 // Escape any regex characters and turn asterisk wildcards into actual regex
137 // wildcards.
138 $item = preg_quote($item, '/');
139 $item = str_replace('\*', '(.*)', $item);
140 }
141
142 return '/^' . implode('|', $paths) . '$/';
143 }
144
145 /**
146 * Helper function for eliminating elements from an array using a simplified
147 * regex pattern.
148 *
149 * @param $elements
150 * The array of elements that should have some of its items removed.
151 * @param $regex
152 * A regex as generated by omega_generate_path_regex().
153 */
154 function omega_exclude_assets(&$elements, $regex) {
155 $mapping = omega_generate_asset_mapping($elements);
156
157 // Finally, implode the array of items to exclude into a proper regex and
158 // invoke in on the array of files to be excluded.
159 $elements = array_diff_key($elements, preg_grep($regex, $mapping));
160 }
161
162 /**
163 * Helper function for generating a map of assets based on the data attribute.
164 *
165 * We can not rely on the array keys of the JS and CSS file arrays in Drupal
166 * because in case of inline JS or CSS (which uses numerical array keys) and due
167 * to potential overrides of the 'data' attribute which holds the actual,
168 * reliable path of the file. This function returns a single-level array of
169 * reliable JS/CSS file paths using the original array keys as keys. Elements of
170 * type 'inline' or 'setting' are ignored.
171 *
172 * @param $elements
173 * An array of JS or CSS files as given in hook_css_alter() or
174 * hook_js_alter().
175 *
176 * @return array
177 * A map of file paths generated from $elements.
178 *
179 * @see hook_js_alter()
180 * @see hook_css_alter()
181 */
182 function omega_generate_asset_mapping($elements) {
183 $mapping = array();
184 foreach ($elements as $key => $item) {
185 if ($item['type'] == 'inline' || $item['type'] == 'setting') {
186 // Naturally, in-line CSS is not supported.
187 continue;
188 }
189
190 // We need to build an array containing just the 'data' attribute because
191 // that's the actual path of the file. The array key of the elements can
192 // be something else if someone is sneaky enough to use drupal_add_js() or
193 // drupal_add_css() with a bogus first argument (normally, that is the
194 // path to the file) and then specify the actual path through the 'data'
195 // attribute in the $options array.
196 $mapping[$key] = $item['data'];
197 }
198
199 return $mapping;
200 }
201
202 /**
203 * Retrieves the array of enabled extensions for a theme. Extensions can be
204 * registered through the .info file. Each extension can define a theme settings
205 * form altering function named
206 * 'THEMENAME_extension_EXTENSION_theme_settings_form_alter()' through a file
207 * named 'THEME_ROOT/includes/EXTENSION/EXTENSION.settings.inc' to have it
208 * automatically included whenever the theme settings form is displayed. Each
209 * extension can also define a
210 * 'THEMENAME_extension_EXTENSION_theme_registry_alter()' function through a
211 * file named 'THEME_ROOT/includes/EXTENSION/EXTENSION.inc' to register custom
212 * hooks with the theme registry.
213 *
214 * @param $theme
215 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
216 * of the current theme.
217 *
218 * @return array
219 * The theme info array of the passed or current theme.
220 *
221 * @see _system_default_theme_features()
222 * @see omega_extension_development_theme_settings_form_alter()
223 * @see omega_extension_development_theme_registry_alter()
224 */
225 function omega_extensions($theme = NULL, $reset = FALSE) {
226 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
227
228 if (!$reset) {
229 if (($extensions = &drupal_static(__FUNCTION__)) && isset($extensions[$theme])) {
230 return $extensions[$theme];
231 }
232
233 if (($cache = cache_get('omega:' . $theme . ':extensions')) !== FALSE) {
234 return $extensions[$theme] = $cache->data;
235 }
236 }
237
238 // Extensions can't be hidden.
239 $extensions[$theme] = omega_discovery('extension', $theme);
240
241 foreach ($extensions[$theme] as $extension => &$info) {
242 // Make sure that the theme variable is never altered.
243 $context = $theme;
244 drupal_alter('omega_extension_info', $info, $context);
245
246 // Determine if the extension is enabled.
247 $info['enabled'] = omega_theme_get_setting('omega_toggle_extension_' . $extension, !empty($info['info']['enabled']));
248
249 // Check if all dependencies are met.
250 $info['errors'] = FALSE;
251 if (!empty($info['info']['dependencies'])) {
252 foreach ($info['info']['dependencies'] as $dependency) {
253 $dependency = drupal_parse_dependency($dependency);
254
255 if ((!$module = system_get_info('module', $dependency['name'])) || omega_check_incompatibility($dependency, $module['version'])) {
256 $info['errors'] = TRUE;
257 }
258 }
259 }
260 }
261
262 // Write to the cache.
263 cache_set('omega:' . $theme . ':extensions', $extensions[$theme]);
264
265 return $extensions[$theme];
266 }
267
268 /**
269 * Determines if an extension is enabled.
270 *
271 * @param $extension
272 * The machine-readable name of an extension.
273 * @param $theme
274 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
275 * of the current theme.
276 *
277 * @return bool
278 * TRUE if the extension is enabled, FALSE otherwise.
279 */
280 function omega_extension_enabled($extension, $theme = NULL) {
281 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
282 if (($extensions = omega_extensions($theme)) && isset($extensions[$extension])) {
283 return empty($extensions[$extension]['errors']) && !empty($extensions[$extension]['enabled']) && variable_get('omega_toggle_extension_' . $extension, TRUE);
284 }
285 }
286
287 /**
288 * Looks up the info array of all themes in the theme trail and retrieves a
289 * particular info array element.
290 */
291 function omega_theme_trail_info($element, $merge = TRUE, $theme = NULL) {
292 $output = array();
293
294 // Loop over all themes in the theme trail and look up $element in the .info
295 // array.
296 foreach (omega_theme_trail($theme) as $key => $name) {
297 $info = omega_theme_info($key);
298
299 // If $merge is TRUE we combine all the results of all themes in the theme
300 // trail. Otherwise we just return the first occurrence.
301 if (isset($info[$element]) && is_array($info[$element])) {
302 $output = array_merge($info[$element], $output);
303
304 if (!$merge) {
305 return array('theme' => $key, 'info' => $output);
306 }
307 }
308 }
309
310 return $output;
311 }
312
313 /**
314 * Retrieves the full info array of a theme.
315 *
316 * @param $theme
317 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
318 * of the current theme.
319 *
320 * @return array
321 * The theme info array of the passed or current theme.
322 */
323 function omega_theme_info($theme = NULL) {
324 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
325
326 // If this is the current theme, just load the theme info from the globals.
327 // Note: The global 'theme_key' property is not reliable in this case because
328 // it gets overridden on theme settings pages.
329 if ($theme == $GLOBALS['theme']) {
330 return $GLOBALS['theme_info']->info;
331 }
332
333 $themes = list_themes();
334 return $themes[$theme]->info;
335 }
336
337 /**
338 * Invoke a hook in all themes in the theme trail that implement it.
339 *
340 * @param $hook
341 * The name of the hook to invoke.
342 * @param $theme
343 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
344 * of the current theme.
345 * @param ...
346 * Arguments to pass to the hook.
347 *
348 * @return array
349 * An array of return values of the hook implementations. If themes return
350 * arrays from their implementations, those are merged into one array.
351 *
352 * @see module_invoke_all()
353 */
354 function omega_invoke_all($hook, $theme = NULL) {
355 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
356
357 $args = func_get_args();
358 // Remove $hook from the arguments.
359 unset($args[0], $args[1]);
360
361 $return = array();
362 foreach (omega_theme_trail($theme) as $key => $name) {
363 $function = $key . '_' . $hook;
364
365 if (function_exists($function)) {
366 $result = call_user_func_array($function, array_merge(array($theme), array_values($args)));
367 if (isset($result) && is_array($result)) {
368 // Append the 'theme' property to each array element.
369 foreach ($result as &$item) {
370 $item['theme'] = $key;
371 }
372 $return = array_merge_recursive($return, $result);
373 }
374 elseif (isset($result)) {
375 $return[] = $result;
376 }
377 }
378 }
379 return $return;
380 }
381
382 /**
383 * Custom implementation of drupal_array_get_nested_value() that also supports
384 * objects instead of just arrays.
385 *
386 * @param $object
387 * The array or object from which to get the value.
388 * @param $parents
389 * An array of parent keys of the value, starting with the outermost key.
390 * @param $key_exists
391 * (optional) If given, an already defined variable that is altered by
392 * reference.
393 *
394 * @return mixed
395 * The requested nested value. Possibly NULL if the value is NULL or not all
396 * nested parent keys exist. $key_exists is altered by reference and is a
397 * Boolean that indicates whether all nested parent keys exist (TRUE) or not
398 * (FALSE). This allows to distinguish between the two possibilities when NULL
399 * is returned.
400 *
401 * @see drupal_array_get_nested_value()
402 */
403 function omega_get_nested_value(&$object, array $parents, &$key_exists = NULL) {
404 $ref = &$object;
405 foreach ($parents as $parent) {
406 if (is_array($ref) && array_key_exists($parent, $ref)) {
407 $ref = &$ref[$parent];
408 }
409 elseif (is_object($ref) && property_exists($ref, $parent)) {
410 $ref = &$ref->$parent;
411 }
412 else {
413 $key_exists = FALSE;
414 return NULL;
415 }
416 }
417 $key_exists = TRUE;
418 return $ref;
419 }
420
421 /**
422 * Retrieves the info array for all available layouts.
423 *
424 * @return array
425 * An array of available layouts for the given theme.
426 */
427 function omega_layouts_info() {
428 if (($layouts = &drupal_static(__FUNCTION__)) !== NULL) {
429 return $layouts;
430 }
431
432 // Try to retrieve the layouts definitions from cache.
433 if (($cache = cache_get('omega:layouts')) !== FALSE) {
434 return $layouts = $cache->data;
435 }
436
437 // Layouts do not have a specific theme scope.
438 $layouts = omega_discovery('layout', FALSE);
439 foreach ($layouts as $layout => &$info) {
440 $info['attached'] = array();
441 $info['template'] = isset($info['info']['template']) ? $info['info']['template'] : $layout;
442 $root = drupal_get_path('theme', $info['theme']);
443
444 if (isset($info['info']['stylesheets'])) {
445 foreach ($info['info']['stylesheets'] as $media => $files) {
446 foreach ($files as $key => $file) {
447 if (is_file($info['path'] . '/' . $file)) {
448 // First, check if the file exists in the layout's path.
449 $path = $info['path'] . '/' . $file;
450 }
451 elseif (is_file($root . '/' . $file)) {
452 // Otherwise, check if the file exists in the theme's path.
453 $path = $root . '/' . $file;
454 }
455 else {
456 // The specified file does not exist.
457 continue;
458 }
459
460 $info['attached']['css']["$media:$key"] = array(
461 'data' => $path,
462 'media' => $media,
463 'group' => CSS_THEME,
464 'every_page' => TRUE,
465 'weight' => -10,
466 );
467 }
468 }
469 }
470
471 // Look up possible CSS and JS file overrides.
472 if (isset($info['info']['scripts'])) {
473 foreach ($info['info']['scripts'] as $key => $file) {
474 if (is_file($info['path'] . '/' . $file)) {
475 // First, check if the file exists in the layout's path.
476 $path = $info['path'] . '/' . $file;
477 }
478 elseif (is_file($root . '/' . $file)) {
479 // Otherwise, check if the file exists in the theme's path.
480 $path = $root . '/' . $file;
481 }
482 else {
483 // The specified file does not exist.
484 continue;
485 }
486
487 $info['attached']['js'][$key] = array(
488 'data' => $path,
489 'group' => JS_THEME,
490 'every_page' => TRUE,
491 'weight' => -10,
492 );
493 }
494 }
495 }
496
497 // Give modules and themes a chance to alter the layout info array.
498 drupal_alter('omega_layouts_info', $layouts);
499
500 // Cache the layout definitions in the database.
501 cache_set('omega:layouts', $layouts);
502
503 return $layouts;
504 }
505
506 /**
507 * Retrieves the active layout for the current page.
508 *
509 * @return array|bool
510 * The info array for the active layout or FALSE if the current page does not
511 * use an alternative page layout.
512 */
513 function omega_layout() {
514 if (($cache = &drupal_static(__FUNCTION__)) !== NULL) {
515 return $cache;
516 }
517
518 // Load the default layout from the theme settings.
519 $layout = omega_theme_get_setting('omega_layout', 'simple');
520 drupal_alter('omega_layout', $layout);
521
522 $layouts = omega_layouts_info();
523 $cache = isset($layouts[$layout]) ? $layouts[$layout] : FALSE;
524
525 return $cache;
526 }
527
528 /**
529 * Allow themes to easily define libraries.
530 *
531 * @param $theme
532 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
533 * of the current theme.
534 *
535 * @return array
536 * An array of libraries defined by themes in the theme trail of the given
537 * theme.
538 */
539 function omega_theme_libraries_info($theme = NULL) {
540 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
541
542 // Check if the libraries have already been statically cached.
543 if (($libraries = &drupal_static(__FUNCTION__)) && isset($libraries[$theme])) {
544 return $libraries[$theme];
545 };
546
547 $libraries[$theme] = omega_invoke_all('omega_theme_libraries_info');
548
549 $context = $theme;
550 // Give modules and themes a chance to alter the libraries info array.
551 drupal_alter('omega_theme_libraries_info', $libraries[$theme], $context);
552
553 return $libraries[$theme];
554 }
555
556 /**
557 * Helper function for discovering layouts, extensions or other plugins of any
558 * sort in the theme trail.
559 *
560 * @param $type
561 * A theme extension type (e.g. layout or extension).
562 * @param $theme
563 * (Optional) The key (machine-readable name) of a theme. Defaults to the key
564 * of the current theme.
565 *
566 * @return array
567 * An array containing the discovered definitions.
568 */
569 function omega_discovery($type, $theme = NULL) {
570 $theme = isset($theme) ? $theme : $GLOBALS['theme_key'];
571
572 if (($discovery = &drupal_static(__FUNCTION__, array())) && isset($discovery[$theme][$type])) {
573 return $discovery[$theme][$type];
574 }
575
576 $discovery[$theme][$type] = array();
577
578 // Retrieve all themes from the theme trail of the given theme.
579 $themes = $theme === FALSE ? list_themes() : omega_theme_trail($theme);
580
581 // Collect paths to all sub-themes grouped by base themes. These will be
582 // used for filtering. This allows base themes to have sub-themes in its
583 // folder hierarchy without affecting the base themes template discovery.
584 $paths = array();
585 foreach ($themes as $key => $info) {
586 $info = system_get_info('theme', $key);
587 if (!empty($info->base_theme)) {
588 $paths[$info->base_theme][$info->name] = dirname($info->filename);
589 }
590 }
591 foreach ($paths as $basetheme => $subthemes) {
592 foreach ($subthemes as $subtheme => $path) {
593 if (isset($paths[$subtheme])) {
594 $paths[$basetheme] = array_merge($paths[$basetheme], $paths[$subtheme]);
595 }
596 }
597 }
598
599 $strlen = strlen($type) + 1;
600 foreach ($themes as $key => $label) {
601 // Retrieve the array of paths that should be ignored for this theme.
602 $ignore = isset($paths[$key]) ? $paths[$key] : array();
603 $path = drupal_get_path('theme', $key);
604
605 // Support files without '.inc' extension for backwards compatibility.
606 foreach (file_scan_directory($path, '/\.' . $type . '(\.inc)?$/', array('key' => 'name')) as $name => $file) {
607 // Ignore sub-theme templates for the current theme.
608 if (strpos($file->uri, str_replace($ignore, '', $file->uri)) !== 0) {
609 continue;
610 }
611
612 if (substr($name, -$strlen) === '.' . $type) {
613 $name = substr($name, 0, strlen($name) - $strlen);
614 }
615
616 if ($info = drupal_parse_info_file($file->uri)) {
617 $discovery[$theme][$type][$name] = array(
618 'name' => $name,
619 'path' => dirname($file->uri),
620 'file' => $file->uri,
621 'info' => $info,
622 'theme' => $key,
623 );
624 }
625 }
626 }
627
628 return $discovery[$theme][$type];
629 }
630
631 /**
632 * Checks whether a version is compatible with a given dependency.
633 *
634 * This is a wrapper for drupal_check_incompatibility() which strips the core
635 * version and any potential development version suffix from the given string.
636 *
637 * @param $dependency
638 * The parsed dependency structure from drupal_parse_dependency().
639 * @param $current
640 * The version to check against (like 4.2).
641 *
642 * @return
643 * NULL if compatible, otherwise the original dependency version string that
644 * caused the incompatibility.
645 *
646 * @see drupal_check_incompatibility()
647 * @see drupal_parse_dependency()
648 */
649 function omega_check_incompatibility($dependency, $current) {
650 // Remove the core version from the version string.
651 $current = preg_replace('/^' . DRUPAL_CORE_COMPATIBILITY . '-/', '', $current);
652 // Remove any potential development version suffixes from the string.
653 $current = preg_replace('/-dev$/', '', $current);
654
655 return drupal_check_incompatibility($dependency, $current);
656 }