Projet

Général

Profil

Paste
Télécharger (52,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / themes / bootstrap / includes / common.inc @ b6b9edaa

1
<?php
2

    
3
/**
4
 * @file
5
 * List of common helper functions for use in Drupal Bootstrap based themes.
6
 */
7

    
8
/**
9
 * @defgroup utility Utilities
10
 *
11
 * List of common helper functions for use in Drupal Bootstrap based themes.
12
 *
13
 * @{
14
 */
15

    
16
define('BOOTSTRAP_VERSION_MAJOR', 3);
17
define('BOOTSTRAP_VERSION_MINOR', 4);
18
define('BOOTSTRAP_VERSION_PATCH', 1);
19
define('BOOTSTRAP_VERSION', BOOTSTRAP_VERSION_MAJOR . '.' . BOOTSTRAP_VERSION_MINOR . '.' . BOOTSTRAP_VERSION_PATCH);
20

    
21
/**
22
 * Converts an element description into a tooltip based on certain criteria.
23
 *
24
 * @param array $element
25
 *   An element render array, passed by reference.
26
 * @param array $target
27
 *   The target element render array the tooltip is to be attached to, passed
28
 *   by reference. If not set, it will default to the $element passed.
29
 * @param bool $input_only
30
 *   Toggle determining whether or not to only convert input elements.
31
 * @param int $length
32
 *   The length of characters to determine if description is "simple".
33
 */
34
function bootstrap_element_smart_description(array &$element, array &$target = NULL, $input_only = TRUE, $length = NULL) {
35
  // Determine if tooltips are enabled.
36
  static $enabled;
37
  if (!isset($enabled)) {
38
    $enabled = bootstrap_setting('tooltip_enabled') && bootstrap_setting('forms_smart_descriptions');
39
  }
40

    
41
  // Immediately return if "simple" tooltip descriptions are not enabled.
42
  if (!$enabled) {
43
    return;
44
  }
45

    
46
  // Allow a different element to attach the tooltip.
47
  if (!isset($target)) {
48
    $target = &$element;
49
  }
50

    
51
  // Retrieve the length limit for smart descriptions.
52
  if (!isset($length)) {
53
    $length = (int) bootstrap_setting('forms_smart_descriptions_limit');
54
    // Disable length checking by setting it to FALSE if empty.
55
    if (empty($length)) {
56
      $length = FALSE;
57
    }
58
  }
59

    
60
  // Retrieve the allowed tags for smart descriptions. This is primarily used
61
  // for display purposes only (i.e. non-UI/UX related elements that wouldn't
62
  // require a user to "click", like a link).
63
  $allowed_tags = array_filter(array_unique(array_map('trim', explode(',', bootstrap_setting('forms_smart_descriptions_allowed_tags') . ''))));
64

    
65
  // Disable length checking by setting it to FALSE if empty.
66
  if (empty($allowed_tags)) {
67
    $allowed_tags = FALSE;
68
  }
69

    
70
  $html = FALSE;
71
  $type = !empty($element['#type']) ? $element['#type'] : FALSE;
72

    
73
  // Return if element or target shouldn't have "simple" tooltip descriptions.
74
  if (($input_only && !isset($target['#input']))
75
    // Ignore text_format elements.
76
    // @see https://www.drupal.org/node/2478339
77
    || $type === 'text_format'
78

    
79
    // Ignore if the actual element has no #description set.
80
    || empty($element['#description'])
81

    
82
    // Ignore if the target element already has a "data-toggle" attribute set.
83
    || !empty($target['#attributes']['data-toggle'])
84

    
85
    // Ignore if the target element is #disabled.
86
    || isset($target['#disabled'])
87

    
88
    // Ignore if either the actual element or target element has an explicit
89
    // #smart_description property set to FALSE.
90
    || (isset($element['#smart_description']) && !$element['#smart_description'])
91
    || (isset($target['#smart_description']) && !$target['#smart_description'])
92

    
93
    // Ignore if the description is not "simple".
94
    || !_bootstrap_is_simple_string($element['#description'], $length, $allowed_tags, $html)
95
  ) {
96
    // Set the both the actual element and the target element
97
    // #smart_description property to FALSE.
98
    $element['#smart_description'] = FALSE;
99
    $target['#smart_description'] = FALSE;
100
    return;
101
  }
102

    
103
  // Default property (on the element itself).
104
  $property = 'attributes';
105

    
106
  // Add the tooltip to the #label_attributes property for 'checkbox'
107
  // and 'radio' elements.
108
  if ($type === 'checkbox' || $type === 'radio') {
109
    $property = 'label_attributes';
110
  }
111
  // Add the tooltip to the #wrapper_attributes property for 'checkboxes'
112
  // and 'radios' elements.
113
  elseif ($type === 'checkboxes' || $type === 'radios') {
114
    $property = 'wrapper_attributes';
115
  }
116
  // Add the tooltip to the #input_group_attributes property for elements
117
  // that have valid input groups set.
118
  elseif ((!empty($element['#field_prefix']) || !empty($element['#field_suffix'])) && (!empty($element['#input_group']) || !empty($element['#input_group_button']))) {
119
    $property = 'input_group_attributes';
120
  }
121

    
122
  // Retrieve the proper attributes array.
123
  $attributes = &_bootstrap_get_attributes($target, $property);
124

    
125
  // Set the tooltip attributes.
126
  $attributes['title'] = $allowed_tags !== FALSE ? filter_xss($element['#description'], $allowed_tags) : $element['#description'];
127
  $attributes['data-toggle'] = 'tooltip';
128
  if ($html || $allowed_tags === FALSE) {
129
    $attributes['data-html'] = 'true';
130
  }
131

    
132
  // Remove the element description so it isn't (re-)rendered later.
133
  unset($element['#description']);
134
}
135

    
136
/**
137
 * Retrieves a plugin that uses backported CDN Provider logic from 8.x-3.x.
138
 *
139
 * @param string $theme
140
 *   The name of a given theme; defaults to the currently active theme.
141
 *
142
 * @return \Drupal\bootstrap\Backport\Plugin\Provider\ProviderBase|false
143
 *   A CDN Provider instance or FALSE if provider cannot use backported code.
144
 */
145
function _bootstrap_backport_cdn_provider($theme = NULL, $provider = NULL) {
146
  /** @var \Drupal\bootstrap\Backport\Plugin\Provider\ProviderBase[] $providers */
147
  static $providers = array();
148

    
149
  // If no key is given, use the current theme if we can determine it.
150
  if (!isset($theme)) {
151
    $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : '';
152
  }
153

    
154
  if (!isset($provider)) {
155
    $provider = bootstrap_setting('cdn_provider', $theme);
156
  }
157

    
158
  if (!isset($providers[$theme][$provider])) {
159
    $providers[$theme][$provider]  = FALSE;
160
    if (bootstrap_setting('cdn_use_backported_code', $theme, 'bootstrap', TRUE)) {
161
      if ($provider === 'jsdelivr') {
162
        require_once __DIR__ . '/cdn/ProviderBase.php';
163
        require_once __DIR__ . '/cdn/JsDelivr.php';
164
        $providers[$theme][$provider] = new \Drupal\bootstrap\Backport\Plugin\Provider\JsDelivr();
165
      }
166
      elseif ($provider === 'custom') {
167
        require_once __DIR__ . '/cdn/ProviderBase.php';
168
        require_once __DIR__ . '/cdn/Custom.php';
169
        $providers[$theme][$provider] = new \Drupal\bootstrap\Backport\Plugin\Provider\Custom();
170
      }
171
    }
172
  }
173

    
174
  return $providers[$theme][$provider];
175
}
176

    
177
/**
178
 * Retrieves CDN assets for the active provider, if any.
179
 *
180
 * @param string|array $type
181
 *   The type of asset to retrieve: "css" or "js", defaults to an array
182
 *   array containing both if not set.
183
 * @param string $provider
184
 *   The name of a specific CDN provider to use, defaults to the active provider
185
 *   set in the theme settings.
186
 * @param string $theme
187
 *   The name of a specific theme the settings should be retrieved from,
188
 *   defaults to the active theme.
189
 *
190
 * @return array
191
 *   If $type is a string or an array with only one (1) item in it, the assets
192
 *   are returned as an indexed array of files. Otherwise, an associative array
193
 *   is returned keyed by the type.
194
 */
195
function bootstrap_get_cdn_assets($type = NULL, $provider = NULL, $theme = NULL) {
196
  bootstrap_include('bootstrap', 'includes/cdn.inc');
197

    
198
  $original_type = $type;
199
  $assets = array();
200
  $types = array();
201

    
202
  // If no type is set, return all CSS and JS.
203
  if (!isset($type)) {
204
    $types = array('css', 'js');
205
  }
206
  elseif (isset($type)) {
207
    if (!is_array($type)) {
208
      $type = array($type);
209
    }
210
    $types = $type;
211
  }
212

    
213
  // Ensure default arrays exist for the requested types.
214
  foreach ($types as $type) {
215
    $assets[$type] = array();
216
  }
217

    
218
  // Retrieve the CDN provider from the theme.
219
  if (!isset($provider)) {
220
    $provider = bootstrap_setting('cdn_provider', $theme);
221
  }
222

    
223
  // Immediately return if there's no provider set.
224
  if (empty($provider)) {
225
    return array();
226
  }
227

    
228
  // Check if the backported 8.x-3.x jsDelivr code should be used.
229
  if ($provider = _bootstrap_backport_cdn_provider($theme, $provider)) {
230
    $framework = array();
231
    $provider->alterFrameworkLibrary($framework);
232
    $assets = array_map(function ($data) {
233
      $assets = array();
234
      foreach ($data as $info) {
235
        if (isset($info['data'])) {
236
          $assets[] = $info['data'];
237
        }
238
      }
239
      return $assets;
240
    }, array_intersect_key($framework, array_flip($types)));
241
  }
242
  // Otherwise, use the legacy CDN provider code.
243
  else {
244
    if (!empty($provider) && ($data = bootstrap_cdn_provider($provider))) {
245
      // Alter the assets based on provider.
246
      $function = 'bootstrap_bootstrap_cdn_provider_' . $provider . '_assets_alter';
247
      if (function_exists($function)) {
248
        $function($data, $theme);
249
      }
250
      // Iterate over each type.
251
      foreach ($types as $type) {
252
        if (variable_get("preprocess_$type", FALSE) && !empty($data['min'][$type])) {
253
          $assets[$type] = $data['min'][$type];
254
        }
255
        elseif (!empty($data[$type])) {
256
          $assets[$type] = $data[$type];
257
        }
258
      }
259
    }  }
260

    
261
  return is_string($original_type) ? $assets[$original_type] : $assets;
262
}
263

    
264
/**
265
 * Return information from the .info file of a theme (and possible base themes).
266
 *
267
 * @param string $theme_key
268
 *   The machine name of the theme.
269
 * @param string $key
270
 *   The key name of the item to return from the .info file. This value can
271
 *   include "][" to automatically attempt to traverse any arrays.
272
 * @param bool $base_themes
273
 *   Recursively search base themes, defaults to TRUE.
274
 *
275
 * @return string|array|false
276
 *   A string or array depending on the type of value and if a base theme also
277
 *   contains the same $key, FALSE if no $key is found.
278
 */
279
function bootstrap_get_theme_info($theme_key = NULL, $key = NULL, $base_themes = TRUE) {
280
  // If no $theme_key is given, use the current theme if we can determine it.
281
  if (!isset($theme_key)) {
282
    $theme_key = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : FALSE;
283
  }
284
  if ($theme_key) {
285
    $themes = list_themes();
286
    if (!empty($themes[$theme_key])) {
287
      $theme = $themes[$theme_key];
288
      // If a key name was specified, return just that array.
289
      if ($key) {
290
        $value = FALSE;
291
        // Recursively add base theme values.
292
        if ($base_themes && isset($theme->base_themes)) {
293
          foreach (array_keys($theme->base_themes) as $base_theme) {
294
            $value = bootstrap_get_theme_info($base_theme, $key);
295
          }
296
        }
297
        if (!empty($themes[$theme_key])) {
298
          $info = $themes[$theme_key]->info;
299
          // Allow array traversal.
300
          $keys = explode('][', $key);
301
          foreach ($keys as $parent) {
302
            if (isset($info[$parent])) {
303
              $info = $info[$parent];
304
            }
305
            else {
306
              $info = FALSE;
307
            }
308
          }
309
          if (is_array($value)) {
310
            if (!empty($info)) {
311
              if (!is_array($info)) {
312
                $info = array($info);
313
              }
314
              $value = drupal_array_merge_deep($value, $info);
315
            }
316
          }
317
          else {
318
            if (!empty($info)) {
319
              if (empty($value)) {
320
                $value = $info;
321
              }
322
              else {
323
                if (!is_array($value)) {
324
                  $value = array($value);
325
                }
326
                if (!is_array($info)) {
327
                  $info = array($info);
328
                }
329
                $value = drupal_array_merge_deep($value, $info);
330
              }
331
            }
332
          }
333
        }
334
        return $value;
335
      }
336
      // If no info $key was specified, just return the entire info array.
337
      return $theme->info;
338
    }
339
  }
340
  return FALSE;
341
}
342

    
343
/**
344
 * Includes a theme file.
345
 *
346
 * @param string $theme
347
 *   Name of the theme to use for base path.
348
 * @param string $path
349
 *   Path relative to $theme.
350
 *
351
 * @return string|false
352
 *   The absolute path to the include file or FALSE if it doesn't exist.
353
 */
354
function bootstrap_include($theme, $path) {
355
  static $included = array();
356
  static $themes = array();
357

    
358
  if (!isset($themes[$theme])) {
359
    $themes[$theme] = drupal_get_path('theme', $theme);
360
  }
361

    
362
  if (!isset($included["$theme:$path"])) {
363
    $file = DRUPAL_ROOT . '/' . $themes[$theme] . '/' . $path;
364
    if (file_exists($file)) {
365
      include_once $file;
366
    }
367
    else {
368
      $file = FALSE;
369
    }
370
    $included["$theme:$path"] = $file;
371
  }
372

    
373
  return $included["$theme:$path"];
374
}
375

    
376
/**
377
 * Retrieves a setting for the current theme or for a given theme.
378
 *
379
 * This is a wrapper for theme_get_setting(), ensuring to use deprecated
380
 * setting values instead.
381
 *
382
 * @param string $name
383
 *   The name of the setting to be retrieved.
384
 * @param string $theme
385
 *   The name of a given theme; defaults to the currently active theme.
386
 * @param string $prefix
387
 *   The prefix used on the $name of the setting, this will be appended with
388
 *   "_" automatically if set.
389
 * @param mixed $default
390
 *   The default value to return if setting doesn't exist or is not set.
391
 *
392
 * @return mixed
393
 *   The value of the requested setting, NULL if the setting does not exist.
394
 *
395
 * @see theme_get_setting()
396
 *
397
 * @todo Refactor in 7.x-4.x and get rid of the deprecated settings.
398
 */
399
function bootstrap_setting($name, $theme = NULL, $prefix = 'bootstrap', $default = NULL) {
400
  $prefix = !empty($prefix) ? $prefix . '_' : '';
401
  $setting = theme_get_setting($prefix . $name, $theme);
402
  switch ($prefix . $name) {
403
    case 'bootstrap_cdn_provider':
404
      $deprecated = theme_get_setting('bootstrap_cdn', $theme);
405
      if (isset($deprecated)) {
406
        $setting = empty($deprecated) ? '' : 'jsdelivr';
407
      }
408
      break;
409

    
410
    case 'bootstrap_cdn_jsdelivr_version':
411
      $deprecated = theme_get_setting('bootstrap_cdn', $theme);
412
      if (isset($deprecated)) {
413
        $setting = empty($deprecated) ? BOOTSTRAP_VERSION : $deprecated;
414
      }
415
      break;
416

    
417
    case 'bootstrap_cdn_jsdelivr_theme':
418
      $deprecated = theme_get_setting('bootstrap_bootswatch', $theme);
419
      if (isset($deprecated)) {
420
        $setting = empty($deprecated) ? 'bootstrap' : $deprecated;
421
      }
422
      break;
423

    
424
    case 'bootstrap_forms_smart_descriptions':
425
      $deprecated = theme_get_setting('bootstrap_tooltip_descriptions', $theme);
426
      if (isset($deprecated)) {
427
        $setting = (int) !empty($deprecated);
428
      }
429
      break;
430

    
431
    case 'bootstrap_forms_smart_descriptions_limit':
432
      $deprecated = theme_get_setting('bootstrap_tooltip_descriptions_length', $theme);
433
      if (isset($deprecated)) {
434
        $setting = (int) !empty($deprecated);
435
      }
436
      break;
437

    
438
  }
439
  return isset($setting) ? $setting : $default;
440
}
441

    
442
/**
443
 * Sets a theme setting.
444
 *
445
 * @param string $name
446
 *   The name of the setting to set.
447
 * @param mixed $value
448
 *   The value of the setting to set.
449
 * @param string $prefix
450
 *   The prefix used on the $name of the setting, this will be appended with
451
 *   "_" automatically if set.
452
 * @param string $theme
453
 *   The name of a given theme; defaults to the currently active theme.
454
 */
455
function _bootstrap_set_setting($name, $value, $prefix = 'bootstrap', $theme = NULL) {
456
  // If no key is given, use the current theme if we can determine it.
457
  if (!isset($theme)) {
458
    $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : '';
459
  }
460

    
461
  // Trigger at least once so it can populate defaults.
462
  theme_get_setting('', $theme);
463

    
464
  // Retrieve the cached settings.
465
  $cache = &drupal_static('theme_get_setting', array());
466
  $settings = $cache[$theme];
467

    
468
  // Set the value.
469
  $prefix = !empty($prefix) ? $prefix . '_' : '';
470
  $settings["$prefix$name"] = $value;
471

    
472
  // Set the settings.
473
  variable_set('theme_' . $theme . '_settings', $settings);
474
}
475

    
476
/**
477
 * Retrieves an element's "attributes" array.
478
 *
479
 * @param array $element
480
 *   The individual renderable array element. It is possible to also pass the
481
 *   $variables parameter in [pre]process functions and it will logically
482
 *   determine the correct path to that particular theme hook's attribute array.
483
 *   Passed by reference.
484
 * @param string $property
485
 *   Determines which attributes array to retrieve. By default, this is the
486
 *   normal attributes, but can be "wrapper_attributes" or
487
 *   "input_group_attributes".
488
 *
489
 * @return array
490
 *   The attributes array. Passed by reference.
491
 */
492
function &_bootstrap_get_attributes(array &$element, $property = 'attributes') {
493
  // Attempt to retrieve a renderable element attributes first.
494
  if (
495
    isset($element['#type']) ||
496
    isset($element['#theme']) ||
497
    isset($element['#pre_render']) ||
498
    isset($element['#markup']) ||
499
    isset($element['#theme_wrappers']) ||
500
    isset($element["#$property"])
501
  ) {
502
    if (!isset($element["#$property"])) {
503
      $element["#$property"] = array();
504
    }
505
    return $element["#$property"];
506
  }
507
  // Treat $element as if it were a [pre]process function $variables parameter
508
  // and look for a renderable "element".
509
  elseif (isset($element['element'])) {
510
    if (!isset($element['element']["#$property"])) {
511
      $element['element']["#$property"] = array();
512
    }
513
    return $element['element']["#$property"];
514
  }
515

    
516
  // If all else fails, create (if needed) a default "attributes" array. This
517
  // will, at the very least, either work or cause an error that can be tracked.
518
  if (!isset($element[$property])) {
519
    $element[$property] = array();
520
  }
521

    
522
  return $element[$property];
523
}
524

    
525
/**
526
 * Retrieves an element's "class" array.
527
 *
528
 * @param array $element
529
 *   The individual renderable array element. It is possible to also pass the
530
 *   $variables parameter in [pre]process functions and it will logically
531
 *   determine the correct path to that particular theme hook's classes array.
532
 *   Passed by reference.
533
 * @param string $property
534
 *   Determines which attributes array to retrieve. By default, this is the
535
 *   normal attributes, but can be "wrapper_attributes" or
536
 *   "input_group_attributes".
537
 *
538
 * @return array
539
 *   The classes array. Passed by reference.
540
 */
541
function &_bootstrap_get_classes(array &$element, $property = 'attributes') {
542
  $attributes = &_bootstrap_get_attributes($element, $property);
543

    
544
  if (!isset($attributes['class'])) {
545
    $attributes['class'] = array();
546
  }
547
  // Contrib modules have a very bad habit of frequently adding classes as
548
  // strings, convert them to a proper array.
549
  // @see https://www.drupal.org/node/2269653
550
  elseif (!is_array($attributes['class'])) {
551
    $attributes['class'] = explode(' ', $attributes['class']);
552
  }
553

    
554
  // Ensure classes are not duplicated.
555
  $attributes['class'] = array_unique($attributes['class']);
556
  return $attributes['class'];
557
}
558

    
559
/**
560
 * Adds a class to an element's render array.
561
 *
562
 * @param string|array $class
563
 *   An individual class or an array of classes to add.
564
 * @param array $element
565
 *   The individual renderable array element. It is possible to also pass the
566
 *   $variables parameter in [pre]process functions and it will logically
567
 *   determine the correct path to that particular theme hook's classes array.
568
 *   Passed by reference.
569
 * @param string $property
570
 *   Determines which attributes array to retrieve. By default, this is the
571
 *   normal attributes, but can be "wrapper_attributes" or
572
 *   "input_group_attributes".
573
 */
574
function _bootstrap_add_class($class, array &$element, $property = 'attributes') {
575
  // Retrieve the element's classes.
576
  $classes = &_bootstrap_get_classes($element, $property);
577

    
578
  // Convert the class to an array.
579
  if (!is_array($class)) {
580
    $class = array($class);
581
  }
582

    
583
  // Iterate over all classes to add.
584
  foreach ($class as $_class) {
585
    // Ensure the class to add does not yet already exist.
586
    if (!in_array($_class, $classes)) {
587
      $classes[] = $_class;
588
    }
589
  }
590
}
591

    
592
/**
593
 * Removes a class from an element's render array.
594
 *
595
 * @param string|array $class
596
 *   An individual class or an array of classes to remove.
597
 * @param array $element
598
 *   The individual renderable array element. It is possible to also pass the
599
 *   $variables parameter in [pre]process functions and it will logically
600
 *   determine the correct path to that particular theme hook's classes array.
601
 *   Passed by reference.
602
 * @param string $property
603
 *   Determines which attributes array to retrieve. By default, this is the
604
 *   normal attributes, but can be "wrapper_attributes" or
605
 *   "input_group_attributes".
606
 */
607
function _bootstrap_remove_class($class, array &$element, $property = 'attributes') {
608
  // Retrieve the element's classes.
609
  $classes = &_bootstrap_get_classes($element, $property);
610

    
611
  // Convert the class to an array.
612
  if (!is_array($class)) {
613
    $class = array($class);
614
  }
615

    
616
  // Iterate over all classes to add.
617
  foreach ($class as $_class) {
618
    $key = array_search($_class, $classes);
619
    if ($key !== FALSE) {
620
      unset($classes[$key]);
621
    }
622
  }
623
}
624

    
625
/**
626
 * Returns a list of base themes for active or provided theme.
627
 *
628
 * @param string $theme_key
629
 *   The machine name of the theme to check, if not set the active theme name
630
 *   will be used.
631
 * @param bool $include_theme_key
632
 *   Whether to append the returned list with $theme_key.
633
 *
634
 * @return array
635
 *   An indexed array of base themes.
636
 */
637
function _bootstrap_get_base_themes($theme_key = NULL, $include_theme_key = FALSE) {
638
  static $themes;
639
  if (!isset($theme_key)) {
640
    $theme_key = $GLOBALS['theme_key'];
641
  }
642
  if (!isset($themes[$theme_key])) {
643
    $themes[$theme_key] = array_unique(array_filter((array) bootstrap_get_theme_info($theme_key, 'base theme')));
644
  }
645
  if ($include_theme_key) {
646
    $themes[$theme_key][] = $theme_key;
647
  }
648
  return $themes[$theme_key];
649
}
650

    
651
/**
652
 * Retrieves the full base/sub-theme ancestry of a theme.
653
 *
654
 * @param string $theme_key
655
 *   The machine name of the theme to check, if not set the active theme name
656
 *   will be used.
657
 * @param bool $reverse
658
 *   Whether or not to return the array of themes in reverse order, where the
659
 *   active theme is the first entry.
660
 *
661
 * @return string[]
662
 *   An array of theme names.
663
 */
664
function _bootstrap_get_ancestry($theme_key = NULL, $reverse = FALSE) {
665
  $ancestry = _bootstrap_get_base_themes($theme_key, TRUE);
666
  return $reverse ? array_reverse($ancestry) : $ancestry;
667
}
668

    
669
/**
670
 * Wrapper for the core file_scan_directory() function.
671
 *
672
 * Finds all files that match a given mask in a given directory and then caches
673
 * the results. A general site cache clear will force new scans to be initiated
674
 * for already cached directories.
675
 *
676
 * @param string $dir
677
 *   The base directory or URI to scan, without trailing slash.
678
 * @param string $mask
679
 *   The preg_match() regular expression of the files to find.
680
 * @param array $options
681
 *   Additional options to pass to file_scan_directory().
682
 *
683
 * @return array
684
 *   An associative array (keyed on the chosen key) of objects with 'uri',
685
 *   'filename', and 'name' members corresponding to the matching files.
686
 *
687
 * @see file_scan_directory()
688
 */
689
function _bootstrap_file_scan_directory($dir, $mask, array $options = array()) {
690
  $files = &drupal_static(__FUNCTION__, array());
691

    
692
  // Generate a unique cache identifier for all parameters passed as a change
693
  // in any of them would return different results.
694
  $cid = 'theme_registry:bootstrap:files:' . drupal_hash_base64(serialize(func_get_args()));
695

    
696
  // Load from DB cache or scan filesystem if files are not statically cached.
697
  if (!isset($files[$cid])) {
698
    if (($cache = cache_get($cid)) && isset($cache->data)) {
699
      $files[$cid] = $cache->data;
700
    }
701
    else {
702
      $files[$cid] = file_scan_directory($dir, $mask, $options);
703
      cache_set($cid, $files[$cid]);
704
    }
705
  }
706

    
707
  return $files[$cid];
708
}
709

    
710
/**
711
 * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
712
 *
713
 * Very similar to core's filter_xss(). It does, however, include the addition
714
 * of the "span", "div" and "i" elements which are commonly used in Bootstrap.
715
 *
716
 * @param string $string
717
 *   The string with raw HTML in it. It will be stripped of everything that can
718
 *   cause an XSS attack.
719
 * @param array $allowed_tags
720
 *   An array of allowed tags.
721
 *
722
 * @return string
723
 *   An XSS safe version of $string, or an empty string if $string is not
724
 *   valid UTF-8.
725
 *
726
 * @see filter_xss()
727
 * @see filter_xss_admin()
728
 *
729
 * @deprecated Use filter_xss() or filter_xss_admin() instead.
730
 * Will be removed in a future release.
731
 */
732
function _bootstrap_filter_xss($string, array $allowed_tags = NULL) {
733
  if (is_null($allowed_tags)) {
734
    $allowed_tags = array(
735
      // Inline elements.
736
      'a',
737
      'cite',
738
      'em',
739
      'i',
740
      'span',
741
      'strong',
742

    
743
      // Block elements.
744
      'blockquote',
745
      'code',
746
      'div',
747
      'ul',
748
      'ol',
749
      'li',
750
      'dl',
751
      'dt',
752
      'dd',
753
    );
754
  }
755
  return filter_xss($string, $allowed_tags);
756
}
757

    
758
/**
759
 * Returns a list of available Bootstrap Glyphicons.
760
 *
761
 * @param string $version
762
 *   The specific version of glyphicons to return. If not set, the latest
763
 *   BOOTSTRAP_VERSION will be used.
764
 *
765
 * @return array
766
 *   An associative array of icons keyed by their classes.
767
 */
768
function _bootstrap_glyphicons($version = NULL) {
769
  static $versions;
770
  if (!isset($versions)) {
771
    $versions = array();
772
    $versions['3.0.0'] = array(
773
      // Class => Name.
774
      'glyphicon-adjust' => 'adjust',
775
      'glyphicon-align-center' => 'align-center',
776
      'glyphicon-align-justify' => 'align-justify',
777
      'glyphicon-align-left' => 'align-left',
778
      'glyphicon-align-right' => 'align-right',
779
      'glyphicon-arrow-down' => 'arrow-down',
780
      'glyphicon-arrow-left' => 'arrow-left',
781
      'glyphicon-arrow-right' => 'arrow-right',
782
      'glyphicon-arrow-up' => 'arrow-up',
783
      'glyphicon-asterisk' => 'asterisk',
784
      'glyphicon-backward' => 'backward',
785
      'glyphicon-ban-circle' => 'ban-circle',
786
      'glyphicon-barcode' => 'barcode',
787
      'glyphicon-bell' => 'bell',
788
      'glyphicon-bold' => 'bold',
789
      'glyphicon-book' => 'book',
790
      'glyphicon-bookmark' => 'bookmark',
791
      'glyphicon-briefcase' => 'briefcase',
792
      'glyphicon-bullhorn' => 'bullhorn',
793
      'glyphicon-calendar' => 'calendar',
794
      'glyphicon-camera' => 'camera',
795
      'glyphicon-certificate' => 'certificate',
796
      'glyphicon-check' => 'check',
797
      'glyphicon-chevron-down' => 'chevron-down',
798
      'glyphicon-chevron-left' => 'chevron-left',
799
      'glyphicon-chevron-right' => 'chevron-right',
800
      'glyphicon-chevron-up' => 'chevron-up',
801
      'glyphicon-circle-arrow-down' => 'circle-arrow-down',
802
      'glyphicon-circle-arrow-left' => 'circle-arrow-left',
803
      'glyphicon-circle-arrow-right' => 'circle-arrow-right',
804
      'glyphicon-circle-arrow-up' => 'circle-arrow-up',
805
      'glyphicon-cloud' => 'cloud',
806
      'glyphicon-cloud-download' => 'cloud-download',
807
      'glyphicon-cloud-upload' => 'cloud-upload',
808
      'glyphicon-cog' => 'cog',
809
      'glyphicon-collapse-down' => 'collapse-down',
810
      'glyphicon-collapse-up' => 'collapse-up',
811
      'glyphicon-comment' => 'comment',
812
      'glyphicon-compressed' => 'compressed',
813
      'glyphicon-copyright-mark' => 'copyright-mark',
814
      'glyphicon-credit-card' => 'credit-card',
815
      'glyphicon-cutlery' => 'cutlery',
816
      'glyphicon-dashboard' => 'dashboard',
817
      'glyphicon-download' => 'download',
818
      'glyphicon-download-alt' => 'download-alt',
819
      'glyphicon-earphone' => 'earphone',
820
      'glyphicon-edit' => 'edit',
821
      'glyphicon-eject' => 'eject',
822
      'glyphicon-envelope' => 'envelope',
823
      'glyphicon-euro' => 'euro',
824
      'glyphicon-exclamation-sign' => 'exclamation-sign',
825
      'glyphicon-expand' => 'expand',
826
      'glyphicon-export' => 'export',
827
      'glyphicon-eye-close' => 'eye-close',
828
      'glyphicon-eye-open' => 'eye-open',
829
      'glyphicon-facetime-video' => 'facetime-video',
830
      'glyphicon-fast-backward' => 'fast-backward',
831
      'glyphicon-fast-forward' => 'fast-forward',
832
      'glyphicon-file' => 'file',
833
      'glyphicon-film' => 'film',
834
      'glyphicon-filter' => 'filter',
835
      'glyphicon-fire' => 'fire',
836
      'glyphicon-flag' => 'flag',
837
      'glyphicon-flash' => 'flash',
838
      'glyphicon-floppy-disk' => 'floppy-disk',
839
      'glyphicon-floppy-open' => 'floppy-open',
840
      'glyphicon-floppy-remove' => 'floppy-remove',
841
      'glyphicon-floppy-save' => 'floppy-save',
842
      'glyphicon-floppy-saved' => 'floppy-saved',
843
      'glyphicon-folder-close' => 'folder-close',
844
      'glyphicon-folder-open' => 'folder-open',
845
      'glyphicon-font' => 'font',
846
      'glyphicon-forward' => 'forward',
847
      'glyphicon-fullscreen' => 'fullscreen',
848
      'glyphicon-gbp' => 'gbp',
849
      'glyphicon-gift' => 'gift',
850
      'glyphicon-glass' => 'glass',
851
      'glyphicon-globe' => 'globe',
852
      'glyphicon-hand-down' => 'hand-down',
853
      'glyphicon-hand-left' => 'hand-left',
854
      'glyphicon-hand-right' => 'hand-right',
855
      'glyphicon-hand-up' => 'hand-up',
856
      'glyphicon-hd-video' => 'hd-video',
857
      'glyphicon-hdd' => 'hdd',
858
      'glyphicon-header' => 'header',
859
      'glyphicon-headphones' => 'headphones',
860
      'glyphicon-heart' => 'heart',
861
      'glyphicon-heart-empty' => 'heart-empty',
862
      'glyphicon-home' => 'home',
863
      'glyphicon-import' => 'import',
864
      'glyphicon-inbox' => 'inbox',
865
      'glyphicon-indent-left' => 'indent-left',
866
      'glyphicon-indent-right' => 'indent-right',
867
      'glyphicon-info-sign' => 'info-sign',
868
      'glyphicon-italic' => 'italic',
869
      'glyphicon-leaf' => 'leaf',
870
      'glyphicon-link' => 'link',
871
      'glyphicon-list' => 'list',
872
      'glyphicon-list-alt' => 'list-alt',
873
      'glyphicon-lock' => 'lock',
874
      'glyphicon-log-in' => 'log-in',
875
      'glyphicon-log-out' => 'log-out',
876
      'glyphicon-magnet' => 'magnet',
877
      'glyphicon-map-marker' => 'map-marker',
878
      'glyphicon-minus' => 'minus',
879
      'glyphicon-minus-sign' => 'minus-sign',
880
      'glyphicon-move' => 'move',
881
      'glyphicon-music' => 'music',
882
      'glyphicon-new-window' => 'new-window',
883
      'glyphicon-off' => 'off',
884
      'glyphicon-ok' => 'ok',
885
      'glyphicon-ok-circle' => 'ok-circle',
886
      'glyphicon-ok-sign' => 'ok-sign',
887
      'glyphicon-open' => 'open',
888
      'glyphicon-paperclip' => 'paperclip',
889
      'glyphicon-pause' => 'pause',
890
      'glyphicon-pencil' => 'pencil',
891
      'glyphicon-phone' => 'phone',
892
      'glyphicon-phone-alt' => 'phone-alt',
893
      'glyphicon-picture' => 'picture',
894
      'glyphicon-plane' => 'plane',
895
      'glyphicon-play' => 'play',
896
      'glyphicon-play-circle' => 'play-circle',
897
      'glyphicon-plus' => 'plus',
898
      'glyphicon-plus-sign' => 'plus-sign',
899
      'glyphicon-print' => 'print',
900
      'glyphicon-pushpin' => 'pushpin',
901
      'glyphicon-qrcode' => 'qrcode',
902
      'glyphicon-question-sign' => 'question-sign',
903
      'glyphicon-random' => 'random',
904
      'glyphicon-record' => 'record',
905
      'glyphicon-refresh' => 'refresh',
906
      'glyphicon-registration-mark' => 'registration-mark',
907
      'glyphicon-remove' => 'remove',
908
      'glyphicon-remove-circle' => 'remove-circle',
909
      'glyphicon-remove-sign' => 'remove-sign',
910
      'glyphicon-repeat' => 'repeat',
911
      'glyphicon-resize-full' => 'resize-full',
912
      'glyphicon-resize-horizontal' => 'resize-horizontal',
913
      'glyphicon-resize-small' => 'resize-small',
914
      'glyphicon-resize-vertical' => 'resize-vertical',
915
      'glyphicon-retweet' => 'retweet',
916
      'glyphicon-road' => 'road',
917
      'glyphicon-save' => 'save',
918
      'glyphicon-saved' => 'saved',
919
      'glyphicon-screenshot' => 'screenshot',
920
      'glyphicon-sd-video' => 'sd-video',
921
      'glyphicon-search' => 'search',
922
      'glyphicon-send' => 'send',
923
      'glyphicon-share' => 'share',
924
      'glyphicon-share-alt' => 'share-alt',
925
      'glyphicon-shopping-cart' => 'shopping-cart',
926
      'glyphicon-signal' => 'signal',
927
      'glyphicon-sort' => 'sort',
928
      'glyphicon-sort-by-alphabet' => 'sort-by-alphabet',
929
      'glyphicon-sort-by-alphabet-alt' => 'sort-by-alphabet-alt',
930
      'glyphicon-sort-by-attributes' => 'sort-by-attributes',
931
      'glyphicon-sort-by-attributes-alt' => 'sort-by-attributes-alt',
932
      'glyphicon-sort-by-order' => 'sort-by-order',
933
      'glyphicon-sort-by-order-alt' => 'sort-by-order-alt',
934
      'glyphicon-sound-5-1' => 'sound-5-1',
935
      'glyphicon-sound-6-1' => 'sound-6-1',
936
      'glyphicon-sound-7-1' => 'sound-7-1',
937
      'glyphicon-sound-dolby' => 'sound-dolby',
938
      'glyphicon-sound-stereo' => 'sound-stereo',
939
      'glyphicon-star' => 'star',
940
      'glyphicon-star-empty' => 'star-empty',
941
      'glyphicon-stats' => 'stats',
942
      'glyphicon-step-backward' => 'step-backward',
943
      'glyphicon-step-forward' => 'step-forward',
944
      'glyphicon-stop' => 'stop',
945
      'glyphicon-subtitles' => 'subtitles',
946
      'glyphicon-tag' => 'tag',
947
      'glyphicon-tags' => 'tags',
948
      'glyphicon-tasks' => 'tasks',
949
      'glyphicon-text-height' => 'text-height',
950
      'glyphicon-text-width' => 'text-width',
951
      'glyphicon-th' => 'th',
952
      'glyphicon-th-large' => 'th-large',
953
      'glyphicon-th-list' => 'th-list',
954
      'glyphicon-thumbs-down' => 'thumbs-down',
955
      'glyphicon-thumbs-up' => 'thumbs-up',
956
      'glyphicon-time' => 'time',
957
      'glyphicon-tint' => 'tint',
958
      'glyphicon-tower' => 'tower',
959
      'glyphicon-transfer' => 'transfer',
960
      'glyphicon-trash' => 'trash',
961
      'glyphicon-tree-conifer' => 'tree-conifer',
962
      'glyphicon-tree-deciduous' => 'tree-deciduous',
963
      'glyphicon-unchecked' => 'unchecked',
964
      'glyphicon-upload' => 'upload',
965
      'glyphicon-usd' => 'usd',
966
      'glyphicon-user' => 'user',
967
      'glyphicon-volume-down' => 'volume-down',
968
      'glyphicon-volume-off' => 'volume-off',
969
      'glyphicon-volume-up' => 'volume-up',
970
      'glyphicon-warning-sign' => 'warning-sign',
971
      'glyphicon-wrench' => 'wrench',
972
      'glyphicon-zoom-in' => 'zoom-in',
973
      'glyphicon-zoom-out' => 'zoom-out',
974
    );
975
    $versions['3.0.1'] = $versions['3.0.0'];
976
    $versions['3.0.2'] = $versions['3.0.1'];
977
    $versions['3.0.3'] = $versions['3.0.2'];
978
    $versions['3.1.0'] = $versions['3.0.3'];
979
    $versions['3.1.1'] = $versions['3.1.0'];
980
    $versions['3.2.0'] = $versions['3.1.1'];
981
    $versions['3.3.0'] = array_merge($versions['3.2.0'], array(
982
      'glyphicon-eur' => 'eur',
983
    ));
984
    $versions['3.3.1'] = $versions['3.3.0'];
985
    $versions['3.3.2'] = array_merge($versions['3.3.1'], array(
986
      'glyphicon-alert' => 'alert',
987
      'glyphicon-apple' => 'apple',
988
      'glyphicon-baby-formula' => 'baby-formula',
989
      'glyphicon-bed' => 'bed',
990
      'glyphicon-bishop' => 'bishop',
991
      'glyphicon-bitcoin' => 'bitcoin',
992
      'glyphicon-blackboard' => 'blackboard',
993
      'glyphicon-cd' => 'cd',
994
      'glyphicon-console' => 'console',
995
      'glyphicon-copy' => 'copy',
996
      'glyphicon-duplicate' => 'duplicate',
997
      'glyphicon-education' => 'education',
998
      'glyphicon-equalizer' => 'equalizer',
999
      'glyphicon-erase' => 'erase',
1000
      'glyphicon-grain' => 'grain',
1001
      'glyphicon-hourglass' => 'hourglass',
1002
      'glyphicon-ice-lolly' => 'ice-lolly',
1003
      'glyphicon-ice-lolly-tasted' => 'ice-lolly-tasted',
1004
      'glyphicon-king' => 'king',
1005
      'glyphicon-knight' => 'knight',
1006
      'glyphicon-lamp' => 'lamp',
1007
      'glyphicon-level-up' => 'level-up',
1008
      'glyphicon-menu-down' => 'menu-down',
1009
      'glyphicon-menu-hamburger' => 'menu-hamburger',
1010
      'glyphicon-menu-left' => 'menu-left',
1011
      'glyphicon-menu-right' => 'menu-right',
1012
      'glyphicon-menu-up' => 'menu-up',
1013
      'glyphicon-modal-window' => 'modal-window',
1014
      'glyphicon-object-align-bottom' => 'object-align-bottom',
1015
      'glyphicon-object-align-horizontal' => 'object-align-horizontal',
1016
      'glyphicon-object-align-left' => 'object-align-left',
1017
      'glyphicon-object-align-right' => 'object-align-right',
1018
      'glyphicon-object-align-top' => 'object-align-top',
1019
      'glyphicon-object-align-vertical' => 'object-align-vertical',
1020
      'glyphicon-oil' => 'oil',
1021
      'glyphicon-open-file' => 'open-file',
1022
      'glyphicon-option-horizontal' => 'option-horizontal',
1023
      'glyphicon-option-vertical' => 'option-vertical',
1024
      'glyphicon-paste' => 'paste',
1025
      'glyphicon-pawn' => 'pawn',
1026
      'glyphicon-piggy-bank' => 'piggy-bank',
1027
      'glyphicon-queen' => 'queen',
1028
      'glyphicon-ruble' => 'ruble',
1029
      'glyphicon-save-file' => 'save-file',
1030
      'glyphicon-scale' => 'scale',
1031
      'glyphicon-scissors' => 'scissors',
1032
      'glyphicon-subscript' => 'subscript',
1033
      'glyphicon-sunglasses' => 'sunglasses',
1034
      'glyphicon-superscript' => 'superscript',
1035
      'glyphicon-tent' => 'tent',
1036
      'glyphicon-text-background' => 'text-background',
1037
      'glyphicon-text-color' => 'text-color',
1038
      'glyphicon-text-size' => 'text-size',
1039
      'glyphicon-triangle-bottom' => 'triangle-bottom',
1040
      'glyphicon-triangle-left' => 'triangle-left',
1041
      'glyphicon-triangle-right' => 'triangle-right',
1042
      'glyphicon-triangle-top' => 'triangle-top',
1043
      'glyphicon-yen' => 'yen',
1044
    ));
1045
    $versions['3.3.4'] = array_merge($versions['3.3.2'], array(
1046
      'glyphicon-btc' => 'btc',
1047
      'glyphicon-jpy' => 'jpy',
1048
      'glyphicon-rub' => 'rub',
1049
      'glyphicon-xbt' => 'xbt',
1050
    ));
1051
    $versions['3.3.5'] = $versions['3.3.4'];
1052
    $versions['3.3.6'] = $versions['3.3.5'];
1053
    $versions['3.3.7'] = $versions['3.3.6'];
1054
    $versions['3.4.0'] = $versions['3.3.7'];
1055
    $versions['3.4.1'] = $versions['3.4.0'];
1056
  }
1057

    
1058
  // Return a specific versions icon set.
1059
  if (isset($version) && isset($versions[$version])) {
1060
    return $versions[$version];
1061
  }
1062

    
1063
  // Return the latest version.
1064
  return $versions[BOOTSTRAP_VERSION];
1065
}
1066

    
1067
/**
1068
 * Returns a specific Bootstrap Glyphicon as rendered HTML markup.
1069
 *
1070
 * @param string $name
1071
 *   The icon name, minus the "glyphicon-" prefix.
1072
 * @param string $default
1073
 *   (Optional) The default value to return.
1074
 * @param array $attributes
1075
 *   (Optional) Additional attributes to merge onto the icon.
1076
 *
1077
 * @return string
1078
 *   The HTML markup containing the icon defined by $name, $default value if
1079
 *   icon does not exist or returns empty output for whatever reason.
1080
 */
1081
function _bootstrap_icon($name, $default = NULL, array $attributes = array()) {
1082
  $icon = _bootstrap_glyphicon($name, $default, $attributes);
1083
  return render($icon);
1084
}
1085

    
1086
/**
1087
 * Returns a specific Bootstrap Glyphicon as a render array.
1088
 *
1089
 * Note: This function was added to keep BC with the former _bootstrap_icon()
1090
 * implementation since it didn't return a render array. It is basically a
1091
 * backport of 8.x-3.x code so the added $attributes parameter can be more
1092
 * easily dealt with.
1093
 *
1094
 * @param string $name
1095
 *   The icon name, minus the "glyphicon-" prefix.
1096
 * @param array|string $default
1097
 *   (Optional) The default render array to use if $name is not available.
1098
 * @param array $attributes
1099
 *   (Optional) Additional attributes to merge onto the icon.
1100
 *
1101
 * @see https://www.drupal.org/project/bootstrap/issues/2844885
1102
 *
1103
 * @return array
1104
 *   The render containing the icon defined by $name, $default value if
1105
 *   icon does not exist or returns NULL if no icon could be rendered.
1106
 */
1107
function _bootstrap_glyphicon($name, $default = array(), array $attributes = array()) {
1108
  $icon = array();
1109

    
1110
  // Ensure the icon specified is a valid Bootstrap Glyphicon.
1111
  if (_bootstrap_glyphicons_supported() && in_array($name, _bootstrap_glyphicons())) {
1112
    // Attempt to use the Icon API module, if enabled and it generates output.
1113
    if (module_exists('icon')) {
1114
      $icon = array(
1115
        '#theme' => 'icon',
1116
        '#bundle' => 'bootstrap',
1117
        '#icon' => 'glyphicon-' . $name,
1118
        '#attributes' => $attributes,
1119
      );
1120
    }
1121
    else {
1122
      $icon = array(
1123
        '#theme' => 'html_tag',
1124
        '#tag' => 'span',
1125
        '#value' => '',
1126
        '#attributes' => $attributes,
1127
      );
1128

    
1129
      // Unlike 8.x-3.x, it will be easier to add the classes and aria-hidden
1130
      // attribute afterwards since there is a passed default $attributes
1131
      // parameter and drupal_array_merge_deep() is notorious for not properly
1132
      // merging attributes, especially when dealing with classes.
1133
      _bootstrap_add_class(array('icon', 'glyphicon', 'glyphicon-' . $name), $icon);
1134
      $icon['#attributes']['aria-hidden'] = 'true';
1135
    }
1136
  }
1137

    
1138
  // Return the icon.
1139
  if (!empty($icon)) {
1140
    return $icon;
1141
  }
1142

    
1143
  // _bootstrap_icon() may pass NULL as $default. If so, return an empty array.
1144
  return isset($default) ? $default : array();
1145
}
1146

    
1147
/**
1148
 * Determine whether or not Bootstrap Glyphicons can be used.
1149
 */
1150
function _bootstrap_glyphicons_supported() {
1151
  global $theme;
1152

    
1153
  // Use the advanced drupal_static() pattern, since this has the potential to
1154
  // be called very often by _bootstrap_icon().
1155
  static $drupal_static_fast;
1156
  if (!isset($drupal_static_fast)) {
1157
    $drupal_static_fast['supported'] = &drupal_static(__FUNCTION__);
1158
  }
1159

    
1160
  // Get static data.
1161
  $supported = &$drupal_static_fast['supported'];
1162

    
1163
  // Retrieve supported themes.
1164
  if (!isset($supported)) {
1165
    $supported = array();
1166

    
1167
    // Retrieve cached data.
1168
    $cid = 'theme_registry:bootstrap:icon_support';
1169
    if (($cache = cache_get($cid)) && !empty($cache->data)) {
1170
      $supported = $cache->data;
1171
    }
1172
  }
1173

    
1174
  // Determine active theme support if not yet set.
1175
  if (!isset($supported[$theme])) {
1176
    // Bootstrap based themes are enabled by default to use CDN. Check if
1177
    // that is the case here so no file discovery is necessary. If the active
1178
    // theme does not have this setting, it falls back to the base theme that
1179
    // does.
1180
    $supported[$theme] = !!bootstrap_get_cdn_assets('css', NULL, $theme);
1181

    
1182
    // CDN not used, iterate over all of the active (base) themes to determine
1183
    // if they contain glyphicon font files.
1184
    if (!$supported[$theme]) {
1185
      foreach (_bootstrap_get_base_themes($theme, TRUE) as $_theme) {
1186
        // Scan the theme for files.
1187
        $fonts = _bootstrap_file_scan_directory(drupal_get_path('theme', $_theme), '/glyphicons-halflings-regular\.(eot|svg|ttf|woff)$/');
1188

    
1189
        // Fonts found, stop the search.
1190
        if (!empty($fonts)) {
1191
          $supported[$theme] = TRUE;
1192
          break;
1193
        }
1194
      }
1195
    }
1196

    
1197
    // Cache all supported themes now that this theme is added to the array.
1198
    cache_set($cid, $supported);
1199
  }
1200

    
1201
  return $supported[$theme];
1202
}
1203

    
1204
/**
1205
 * Determine whether a specific element is a button.
1206
 *
1207
 * @param array $element
1208
 *   A renderable element.
1209
 *
1210
 * @return bool
1211
 *   TRUE or FALSE.
1212
 */
1213
function _bootstrap_is_button(array $element) {
1214
  return
1215
    !empty($element['#type']) &&
1216
    !empty($element['#value']) && (
1217
      $element['#type'] === 'button' ||
1218
      $element['#type'] === 'submit' ||
1219
      $element['#type'] === 'image_button'
1220
    );
1221
}
1222

    
1223
/**
1224
 * Adds a specific Bootstrap class to color a button based on its text value.
1225
 *
1226
 * @param array $element
1227
 *   The form element, passed by reference.
1228
 */
1229
function _bootstrap_colorize_button(array &$element) {
1230
  if (_bootstrap_is_button($element)) {
1231
    // Do not add the class if one is already present in the array.
1232
    $button_classes = array(
1233
      'btn-default',
1234
      'btn-primary',
1235
      'btn-success',
1236
      'btn-info',
1237
      'btn-warning',
1238
      'btn-danger',
1239
      'btn-link',
1240
    );
1241
    $class_intersection = array_intersect($button_classes, $element['#attributes']['class']);
1242
    if (empty($class_intersection)) {
1243
      // Get the matched class.
1244
      $class = bootstrap_setting('button_colorize') ? _bootstrap_colorize_text($element['#value']) : FALSE;
1245
      // If no particular class matched, use the default style.
1246
      if (!$class) {
1247
        $class = 'default';
1248
      }
1249
      $element['#attributes']['class'][] = 'btn-' . $class;
1250
    }
1251
  }
1252
}
1253

    
1254
/**
1255
 * Matches a Bootstrap class based on a string value.
1256
 *
1257
 * @param string $string
1258
 *   The string to match classes against.
1259
 * @param string $default
1260
 *   The default class to return if no match is found.
1261
 *
1262
 * @return string
1263
 *   The Bootstrap class matched against the value of $haystack or $default if
1264
 *   no match could be made.
1265
 */
1266
function _bootstrap_colorize_text($string, $default = '') {
1267
  static $texts;
1268
  if (!isset($texts)) {
1269
    $texts = array(
1270
      // Text that match these specific strings are checked first.
1271
      'matches' => array(
1272
        // Primary class.
1273
        t('Download feature')   => 'primary',
1274

    
1275
        // Success class.
1276
        t('Add effect')         => 'success',
1277
        t('Add and configure')  => 'success',
1278

    
1279
        // Info class.
1280
        t('Save and add')       => 'info',
1281
        t('Add another item')   => 'info',
1282
        t('Update style')       => 'info',
1283
      ),
1284

    
1285
      // Text that contain these words anywhere in the string are checked last.
1286
      'contains' => array(
1287
        // Primary class.
1288
        t('Confirm')            => 'primary',
1289
        t('Filter')             => 'primary',
1290
        t('Log in')             => 'primary',
1291
        t('Submit')             => 'primary',
1292
        t('Search')             => 'primary',
1293
        t('Upload')             => 'primary',
1294

    
1295
        // Success class.
1296
        t('Add')                => 'success',
1297
        t('Create')             => 'success',
1298
        t('Save')               => 'success',
1299
        t('Write')              => 'success',
1300

    
1301
        // Warning class.
1302
        t('Export')             => 'warning',
1303
        t('Import')             => 'warning',
1304
        t('Restore')            => 'warning',
1305
        t('Rebuild')            => 'warning',
1306

    
1307
        // Info class.
1308
        t('Apply')              => 'info',
1309
        t('Update')             => 'info',
1310

    
1311
        // Danger class.
1312
        t('Delete')             => 'danger',
1313
        t('Remove')             => 'danger',
1314
      ),
1315
    );
1316

    
1317
    // Allow sub-themes to alter this array of patterns.
1318
    drupal_alter('bootstrap_colorize_text', $texts);
1319
  }
1320

    
1321
  // Iterate over the array.
1322
  foreach ($texts as $pattern => $strings) {
1323
    foreach ($strings as $value => $class) {
1324
      switch ($pattern) {
1325
        case 'matches':
1326
          if ($string === $value) {
1327
            return $class;
1328
          }
1329
          break;
1330

    
1331
        case 'contains':
1332
          if (strpos(drupal_strtolower($string), drupal_strtolower($value)) !== FALSE) {
1333
            return $class;
1334
          }
1335
          break;
1336
      }
1337
    }
1338
  }
1339

    
1340
  // Return the default if nothing was matched.
1341
  return $default;
1342
}
1343

    
1344
/**
1345
 * Adds an icon to button element based on its text value.
1346
 *
1347
 * @param array $element
1348
 *   The form element, passed by reference.
1349
 */
1350
function _bootstrap_iconize_button(array &$element) {
1351
  if (bootstrap_setting('button_iconize') && _bootstrap_is_button($element) && ($icon = _bootstrap_iconize_text($element['#value']))) {
1352
    $element['#icon'] = $icon;
1353
  }
1354
}
1355

    
1356
/**
1357
 * Matches a Bootstrap Glyphicon based on a string value.
1358
 *
1359
 * @param string $string
1360
 *   The string to match classes against.
1361
 * @param string $default
1362
 *   The default icon to return if no match is found.
1363
 *
1364
 * @return string
1365
 *   The Bootstrap icon matched against the value of $haystack or $default if
1366
 *   no match could be made.
1367
 */
1368
function _bootstrap_iconize_text($string, $default = '') {
1369
  static $texts;
1370
  if (!isset($texts)) {
1371
    $texts = array(
1372
      // Text that match these specific strings are checked first.
1373
      'matches' => array(),
1374

    
1375
      // Text that contain these words anywhere in the string are checked last.
1376
      'contains' => array(
1377
        t('Manage')     => 'cog',
1378
        t('Configure')  => 'cog',
1379
        t('Download')   => 'download',
1380
        t('Export')     => 'export',
1381
        t('Filter')     => 'filter',
1382
        t('Import')     => 'import',
1383
        t('Save')       => 'ok',
1384
        t('Update')     => 'ok',
1385
        t('Edit')       => 'pencil',
1386
        t('Add')        => 'plus',
1387
        t('Write')      => 'plus',
1388
        t('Cancel')     => 'remove',
1389
        t('Delete')     => 'trash',
1390
        t('Remove')     => 'trash',
1391
        t('Upload')     => 'upload',
1392
        t('Log In')     => 'log-in',
1393
      ),
1394
    );
1395

    
1396
    // Allow sub-themes to alter this array of patterns.
1397
    drupal_alter('bootstrap_iconize_text', $texts);
1398
  }
1399

    
1400
  // Iterate over the array.
1401
  foreach ($texts as $pattern => $strings) {
1402
    foreach ($strings as $value => $icon) {
1403
      switch ($pattern) {
1404
        case 'matches':
1405
          if ($string === $value) {
1406
            return _bootstrap_icon($icon, $default);
1407
          }
1408
          break;
1409

    
1410
        case 'contains':
1411
          if (strpos(drupal_strtolower($string), drupal_strtolower($value)) !== FALSE) {
1412
            return _bootstrap_icon($icon, $default);
1413
          }
1414
          break;
1415
      }
1416
    }
1417
  }
1418

    
1419
  // Return a default icon if nothing was matched.
1420
  return _bootstrap_icon($default);
1421
}
1422

    
1423
/**
1424
 * Invokes a specific suggestion's preprocess functions.
1425
 *
1426
 * @param array $variables
1427
 *   The theme implementation variables array.
1428
 */
1429
function _bootstrap_preprocess_theme_suggestion(array &$variables) {
1430
  $registry = theme_get_registry();
1431
  if (!empty($variables['theme_hook_suggestion']) && !empty($registry[$variables['theme_hook_suggestion']]['preprocess functions'])) {
1432
    // Save the suggestion as the hook to pass to the function.
1433
    $hook = $variables['theme_hook_suggestion'];
1434

    
1435
    // Iterate over the preprocess functions.
1436
    foreach ($registry[$hook]['preprocess functions'] as $function) {
1437
      // Ensure that the function is not this one (recursive) and exists.
1438
      if ($function !== __FUNCTION__ && function_exists($function)) {
1439
        // Invoke theme hook suggestion preprocess function.
1440
        $function($variables, $hook);
1441

    
1442
        // Unset the theme_hook_suggestion so the suggestion's preprocess
1443
        // functions can provide theme_hook_suggestions if needed.
1444
        if (!empty($variables['theme_hook_suggestions'])) {
1445
          unset($variables['theme_hook_suggestion']);
1446
        }
1447
      }
1448
    }
1449
  }
1450
}
1451

    
1452
/**
1453
 * Invokes a specific suggestion's process functions.
1454
 *
1455
 * @param array $variables
1456
 *   The theme implementation variables array.
1457
 */
1458
function _bootstrap_process_theme_suggestion(array &$variables) {
1459
  $registry = theme_get_registry();
1460
  if (!empty($variables['theme_hook_suggestion']) && !empty($registry[$variables['theme_hook_suggestion']]['process functions'])) {
1461
    // Save the suggestion as the hook to pass to the function.
1462
    $hook = $variables['theme_hook_suggestion'];
1463

    
1464
    // Iterate over the process functions.
1465
    foreach ($registry[$hook]['process functions'] as $function) {
1466
      if (function_exists($function)) {
1467
        // Invoke theme hook suggestion process function.
1468
        $function($variables, $hook);
1469

    
1470
        // Unset the theme_hook_suggestion so the suggestion's preprocess
1471
        // functions can provide theme_hook_suggestions if needed.
1472
        if (!empty($variables['theme_hook_suggestions'])) {
1473
          unset($variables['theme_hook_suggestion']);
1474
        }
1475
      }
1476
    }
1477
  }
1478
}
1479

    
1480
/**
1481
 * Determines if a string of text is considered "simple".
1482
 *
1483
 * @param string $string
1484
 *   The string of text to check "simple" criteria on.
1485
 * @param int|false $length
1486
 *   The length of characters used to determine whether or not $string is
1487
 *   considered "simple". Set explicitly to FALSE to disable this criteria.
1488
 * @param array|false $allowed_tags
1489
 *   An array of allowed tag elements. Set explicitly to FALSE to disable this
1490
 *   criteria.
1491
 * @param bool $html
1492
 *   A variable, passed by reference, that indicates whether or not the
1493
 *   string contains HTML.
1494
 *
1495
 * @return bool
1496
 *   Returns TRUE if the $string is considered "simple", FALSE otherwise.
1497
 */
1498
function _bootstrap_is_simple_string($string, $length = 250, $allowed_tags = NULL, &$html = FALSE) {
1499
  // Use the advanced drupal_static() pattern, since this is called very often.
1500
  static $drupal_static_fast;
1501
  if (!isset($drupal_static_fast)) {
1502
    $drupal_static_fast['strings'] = &drupal_static(__FUNCTION__);
1503
  }
1504
  $strings = &$drupal_static_fast['strings'];
1505
  if (!isset($strings[$string])) {
1506
    $plain_string = strip_tags($string);
1507
    $simple = TRUE;
1508
    if ($allowed_tags !== FALSE) {
1509
      $filtered_string = filter_xss($string, $allowed_tags);
1510
      $html = $filtered_string !== $plain_string;
1511
      $simple = $simple && $string === $filtered_string;
1512
    }
1513
    if ($length !== FALSE) {
1514
      $simple = $simple && strlen($plain_string) <= intval($length);
1515
    }
1516
    $strings[$string] = $simple;
1517
  }
1518
  return $strings[$string];
1519
}
1520

    
1521
/**
1522
 * Determines if the Path Breadcrumbs module theme function should be used.
1523
 *
1524
 * @param string $theme
1525
 *   The machine name of a specific theme to determine status if the Path
1526
 *   Breadcrumbs module has been configured to only use its internal function
1527
 *   on a specific list of themes.
1528
 *
1529
 * @return bool
1530
 *   TRUE or FALSE
1531
 */
1532
function _bootstrap_use_path_breadcrumbs($theme = NULL) {
1533
  static $path_breadcrumbs;
1534

    
1535
  if (!isset($path_breadcrumbs)) {
1536
    $path_breadcrumbs = FALSE;
1537

    
1538
    // Use active theme as the theme key if not explicitly set.
1539
    if (!isset($theme)) {
1540
      $theme = $GLOBALS['theme_key'];
1541
    }
1542

    
1543
    // Determine whether or not the internal Path Breadcrumbs theme function
1544
    // should be used or not.
1545
    if (function_exists('path_breadcrumbs_breadcrumb') && module_exists('path_breadcrumbs')) {
1546
      $internal_render = variable_get('path_breadcrumbs_internal_render', 1);
1547
      $themes = variable_get('path_breadcrumbs_internal_render_themes', array());
1548
      $path_breadcrumbs = ($internal_render && (empty($themes) || in_array($theme, $themes)));
1549
    }
1550
  }
1551

    
1552
  return $path_breadcrumbs;
1553
}
1554

    
1555
/**
1556
 * @} End of "ingroup utility".
1557
 */