Projet

Général

Profil

Paste
Télécharger (43,3 ko) Statistiques
| Branche: | Révision:

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

1
<?php
2
/**
3
 * @file
4
 * List of common helper functions for use in Bootstrap based themes.
5
 */
6

    
7
define('BOOTSTRAP_VERSION_MAJOR', 3);
8
define('BOOTSTRAP_VERSION_MINOR', 3);
9
define('BOOTSTRAP_VERSION_PATCH', 5);
10
define('BOOTSTRAP_VERSION', BOOTSTRAP_VERSION_MAJOR . '.' . BOOTSTRAP_VERSION_MINOR . '.' . BOOTSTRAP_VERSION_PATCH);
11

    
12
/**
13
 * @defgroup subtheme_helper_functions Helper Functions
14
 * @ingroup subtheme
15
 *
16
 * List of common helper functions for use in Drupal Bootstrap based themes.
17
 *
18
 * @{
19
 */
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(&$element, &$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
  if (!$input_only || !empty($target['#input']) || !empty($element['#smart_description']) || !empty($target['#smart_description'])) {
73
    if (!empty($element['#description']) && empty($target['#attributes']['title']) && empty($target['#attributes']['data-toggle'])) {
74
      if (_bootstrap_is_simple_string($element['#description'], $length, $allowed_tags, $html)) {
75
        // Default property (on the element itself).
76
        $property = 'attributes';
77

    
78
        // Add the tooltip to the #label_attributes property for 'checkbox'
79
        // and 'radio' elements.
80
        if ($type === 'checkbox' || $type === 'radio') {
81
          $property = 'label_attributes';
82
        }
83
        // Add the tooltip to the #wrapper_attributes property for 'checkboxes'
84
        // and 'radios' elements.
85
        elseif ($type === 'checkboxes' || $type === 'radios') {
86
          $property = 'wrapper_attributes';
87
        }
88
        // Add the tooltip to the #input_group_attributes property for elements
89
        // that have valid input groups set.
90
        elseif ((!empty($element['#field_prefix']) || !empty($element['#field_suffix'])) && (!empty($element['#input_group']) || !empty($element['#input_group_button']))) {
91
          $property = 'input_group_attributes';
92
        }
93

    
94
        // Retrieve the proper attributes array.
95
        $attributes = &_bootstrap_get_attributes($target, $property);
96

    
97
        // Set the tooltip attributes.
98
        $attributes['title'] = $allowed_tags !== FALSE ? filter_xss($element['#description'], $allowed_tags) : $element['#description'];
99
        $attributes['data-toggle'] = 'tooltip';
100
        if ($html || $allowed_tags === FALSE) {
101
          $attributes['data-html'] = 'true';
102
        }
103

    
104
        // Remove the element description so it isn't (re-)rendered later.
105
        unset($element['#description']);
106
      }
107
    }
108
  }
109
}
110

    
111
/**
112
 * Retrieves CDN assets for the active provider, if any.
113
 *
114
 * @param string|array $type
115
 *   The type of asset to retrieve: "css" or "js", defaults to an array
116
 *   array containing both if not set.
117
 * @param string $provider
118
 *   The name of a specific CDN provider to use, defaults to the active provider
119
 *   set in the theme settings.
120
 * @param string $theme
121
 *   The name of a specific theme the settings should be retrieved from,
122
 *   defaults to the active theme.
123
 *
124
 * @return array
125
 *   If $type is a string or an array with only one (1) item in it, the assets
126
 *   are returned as an indexed array of files. Otherwise, an associative array
127
 *   is returned keyed by the type.
128
 */
129
function bootstrap_get_cdn_assets($type = NULL, $provider = NULL, $theme = NULL) {
130
  $original_type = $type;
131
  $assets = array();
132
  $types = array();
133

    
134
  // If no type is set, return all CSS and JS.
135
  if (!isset($type)) {
136
    $types = array('css', 'js');
137
  }
138
  elseif (isset($type)) {
139
    if (!is_array($type)) {
140
      $type = array($type);
141
    }
142
    $types = $type;
143
  }
144

    
145
  // Ensure default arrays exist for the requested types.
146
  foreach ($types as $type) {
147
    $assets[$type] = array();
148
  }
149

    
150
  // Retrieve the CDN provider from the theme.
151
  if (!isset($provider)) {
152
    $provider = bootstrap_setting('cdn_provider', $theme);
153
  }
154

    
155
  // Load the CDN provider data.
156
  bootstrap_include('bootstrap', 'includes/cdn.inc');
157
  if (!empty($provider) && ($data = bootstrap_cdn_provider($provider))) {
158
    // Alter the assets based on provider.
159
    $function = 'bootstrap_bootstrap_cdn_provider_' . $provider . '_assets_alter';
160
    if (function_exists($function)) {
161
      $function($data, $theme);
162
    }
163
    // @todo Use drupal_alter() once CDN is in bootstrap_core companion module.
164
    // drupal_alter('bootstrap_cdn_provider_' . $provider . '_assets', $data, $theme);
165

    
166
    // Iterate over each type.
167
    foreach ($types as $type) {
168
      if (variable_get("preprocess_$type", FALSE) && !empty($data['min'][$type])) {
169
        $assets[$type] = $data['min'][$type];
170
      }
171
      elseif (!empty($data[$type])) {
172
        $assets[$type] = $data[$type];
173
      }
174
    }
175
  }
176

    
177
  return is_string($original_type) ? $assets[$original_type] : $assets;
178
}
179

    
180
/**
181
 * Return information from the .info file of a theme (and possible base themes).
182
 *
183
 * @param string $theme_key
184
 *   The machine name of the theme.
185
 * @param string $key
186
 *   The key name of the item to return from the .info file. This value can
187
 *   include "][" to automatically attempt to traverse any arrays.
188
 * @param bool $base_themes
189
 *   Recursively search base themes, defaults to TRUE.
190
 *
191
 * @return string|array|false
192
 *   A string or array depending on the type of value and if a base theme also
193
 *   contains the same $key, FALSE if no $key is found.
194
 */
195
function bootstrap_get_theme_info($theme_key = NULL, $key = NULL, $base_themes = TRUE) {
196
  // If no $theme_key is given, use the current theme if we can determine it.
197
  if (!isset($theme_key)) {
198
    $theme_key = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : FALSE;
199
  }
200
  if ($theme_key) {
201
    $themes = list_themes();
202
    if (!empty($themes[$theme_key])) {
203
      $theme = $themes[$theme_key];
204
      // If a key name was specified, return just that array.
205
      if ($key) {
206
        $value = FALSE;
207
        // Recursively add base theme values.
208
        if ($base_themes && isset($theme->base_themes)) {
209
          foreach (array_keys($theme->base_themes) as $base_theme) {
210
            $value = bootstrap_get_theme_info($base_theme, $key);
211
          }
212
        }
213
        if (!empty($themes[$theme_key])) {
214
          $info = $themes[$theme_key]->info;
215
          // Allow array traversal.
216
          $keys = explode('][', $key);
217
          foreach ($keys as $parent) {
218
            if (isset($info[$parent])) {
219
              $info = $info[$parent];
220
            }
221
            else {
222
              $info = FALSE;
223
            }
224
          }
225
          if (is_array($value)) {
226
            if (!empty($info)) {
227
              if (!is_array($info)) {
228
                $info = array($info);
229
              }
230
              $value = drupal_array_merge_deep($value, $info);
231
            }
232
          }
233
          else {
234
            if (!empty($info)) {
235
              if (empty($value)) {
236
                $value = $info;
237
              }
238
              else {
239
                if (!is_array($value)) {
240
                  $value = array($value);
241
                }
242
                if (!is_array($info)) {
243
                  $info = array($info);
244
                }
245
                $value = drupal_array_merge_deep($value, $info);
246
              }
247
            }
248
          }
249
        }
250
        return $value;
251
      }
252
      // If no info $key was specified, just return the entire info array.
253
      return $theme->info;
254
    }
255
  }
256
  return FALSE;
257
}
258

    
259
/**
260
 * Includes a theme file.
261
 *
262
 * @param string $theme
263
 *   Name of the theme to use for base path.
264
 * @param string $path
265
 *   Path relative to $theme.
266
 */
267
function bootstrap_include($theme, $path) {
268
  static $themes = array();
269
  if (!isset($themes[$theme])) {
270
    $themes[$theme] = drupal_get_path('theme', $theme);
271
  }
272
  if ($themes[$theme] && ($file = DRUPAL_ROOT . '/' . $themes[$theme] . '/' . $path) && file_exists($file)) {
273
    include_once $file;
274
  }
275
}
276

    
277
/**
278
 * Retrieves a setting for the current theme or for a given theme.
279
 *
280
 * This is a wrapper for theme_get_setting(), ensuring to use deprecated
281
 * setting values instead.
282
 *
283
 * @param string $name
284
 *   The name of the setting to be retrieved.
285
 * @param string $theme
286
 *   The name of a given theme; defaults to the currently active theme.
287
 * @param string $prefix
288
 *   The prefix used on the $name of the setting, this will be appended with
289
 *   "_" automatically if set.
290
 *
291
 * @return mixed
292
 *   The value of the requested setting, NULL if the setting does not exist.
293
 *
294
 * @see theme_get_setting()
295
 *
296
 * @todo Refactor in 7.x-4.x and get rid of the deprecated settings.
297
 */
298
function bootstrap_setting($name, $theme = NULL, $prefix = 'bootstrap') {
299
  $prefix = !empty($prefix) ? $prefix . '_' : '';
300
  $setting = theme_get_setting($prefix . $name, $theme);
301
  switch ($prefix . $name) {
302
    case 'bootstrap_cdn_provider':
303
      $deprecated = theme_get_setting('bootstrap_cdn', $theme);
304
      if (isset($deprecated)) {
305
        $setting = empty($deprecated) ? '' : 'jsdelivr';
306
      }
307
      break;
308

    
309
    case 'bootstrap_cdn_jsdelivr_version':
310
      $deprecated = theme_get_setting('bootstrap_cdn', $theme);
311
      if (isset($deprecated)) {
312
        $setting = empty($deprecated) ? BOOTSTRAP_VERSION : $deprecated;
313
      }
314
      break;
315

    
316
    case 'bootstrap_cdn_jsdelivr_theme':
317
      $deprecated = theme_get_setting('bootstrap_bootswatch', $theme);
318
      if (isset($deprecated)) {
319
        $setting = empty($deprecated) ? 'bootstrap' : $deprecated;
320
      }
321
      break;
322

    
323
    case 'bootstrap_forms_smart_descriptions':
324
      $deprecated = theme_get_setting('bootstrap_tooltip_descriptions', $theme);
325
      if (isset($deprecated)) {
326
        $setting = (int) !empty($deprecated);
327
      }
328
      break;
329

    
330
    case 'bootstrap_forms_smart_descriptions_limit':
331
      $deprecated = theme_get_setting('bootstrap_tooltip_descriptions_length', $theme);
332
      if (isset($deprecated)) {
333
        $setting = (int) !empty($deprecated);
334
      }
335
      break;
336

    
337
  }
338
  return $setting;
339
}
340

    
341
/**
342
 * Retrieves an element's "attributes" array.
343
 *
344
 * @param array $element
345
 *   The individual renderable array element. It is possible to also pass the
346
 *   $variables parameter in [pre]process functions and it will logically
347
 *   determine the correct path to that particular theme hook's attribute array.
348
 *   Passed by reference.
349
 * @param string $property
350
 *   Determines which attributes array to retrieve. By default, this is the
351
 *   normal attributes, but can be "wrapper_attributes" or
352
 *   "input_group_attributes".
353
 *
354
 * @return array
355
 *   The attributes array. Passed by reference.
356
 */
357
function &_bootstrap_get_attributes(&$element, $property = 'attributes') {
358
  // Attempt to retrieve a renderable element attributes first.
359
  if (
360
    isset($element['#type']) ||
361
    isset($element['#theme']) ||
362
    isset($element['#pre_render']) ||
363
    isset($element['#markup']) ||
364
    isset($element['#theme_wrappers']) ||
365
    isset($element["#$property"])
366
  ) {
367
    if (!isset($element["#$property"])) {
368
      $element["#$property"] = array();
369
    }
370
    return $element["#$property"];
371
  }
372
  // Treat $element as if it were a [pre]process function $variables parameter
373
  // and look for a renderable "element".
374
  elseif (isset($element['element'])) {
375
    if (!isset($element['element']["#$property"])) {
376
      $element['element']["#$property"] = array();
377
    }
378
    return $element['element']["#$property"];
379
  }
380

    
381
  // If all else fails, create (if needed) a default "attributes" array. This
382
  // will, at the very least, either work or cause an error that can be tracked.
383
  if (!isset($element[$property])) {
384
    $element[$property] = array();
385
  }
386

    
387
  return $element[$property];
388
}
389

    
390
/**
391
 * Retrieves an element's "class" array.
392
 *
393
 * @param array $element
394
 *   The individual renderable array element. It is possible to also pass the
395
 *   $variables parameter in [pre]process functions and it will logically
396
 *   determine the correct path to that particular theme hook's classes array.
397
 *   Passed by reference.
398
 * @param string $property
399
 *   Determines which attributes array to retrieve. By default, this is the
400
 *   normal attributes, but can be "wrapper_attributes" or
401
 *   "input_group_attributes".
402
 *
403
 * @return array
404
 *   The classes array. Passed by reference.
405
 */
406
function &_bootstrap_get_classes(&$element, $property = 'attributes') {
407
  $attributes = &_bootstrap_get_attributes($element, $property);
408

    
409
  if (!isset($attributes['class'])) {
410
    $attributes['class'] = array();
411
  }
412
  // Contrib modules have a very bad habit of frequently adding classes as
413
  // strings, convert them to a proper array.
414
  // @see https://www.drupal.org/node/2269653
415
  elseif (!is_array($attributes['class'])) {
416
    $attributes['class'] = explode(' ', $attributes['class']);
417
  }
418

    
419
  // Ensure classes are not duplicated.
420
  $attributes['class'] = array_unique($attributes['class']);
421
  return $attributes['class'];
422
}
423

    
424
/**
425
 * Adds a class to an element's render array.
426
 *
427
 * @param string|array $class
428
 *   An individual class or an array of classes to add.
429
 * @param array $element
430
 *   The individual renderable array element. It is possible to also pass the
431
 *   $variables parameter in [pre]process functions and it will logically
432
 *   determine the correct path to that particular theme hook's classes array.
433
 *   Passed by reference.
434
 * @param string $property
435
 *   Determines which attributes array to retrieve. By default, this is the
436
 *   normal attributes, but can be "wrapper_attributes" or
437
 *   "input_group_attributes".
438
 */
439
function _bootstrap_add_class($class, &$element, $property = 'attributes') {
440
  // Retrieve the element's classes.
441
  $classes = &_bootstrap_get_classes($element, $property);
442

    
443
  // Convert the class to an array.
444
  if (!is_array($class)) {
445
    $class = array($class);
446
  }
447

    
448
  // Iterate over all classes to add.
449
  foreach ($class as $_class) {
450
    // Ensure the class to add does not yet already exist.
451
    if (!in_array($_class, $classes)) {
452
      $classes[] = $_class;
453
    }
454
  }
455
}
456

    
457
/**
458
 * Removes a class from an element's render array.
459
 *
460
 * @param string|array $class
461
 *   An individual class or an array of classes to remove.
462
 * @param array $element
463
 *   The individual renderable array element. It is possible to also pass the
464
 *   $variables parameter in [pre]process functions and it will logically
465
 *   determine the correct path to that particular theme hook's classes array.
466
 *   Passed by reference.
467
 * @param string $property
468
 *   Determines which attributes array to retrieve. By default, this is the
469
 *   normal attributes, but can be "wrapper_attributes" or
470
 *   "input_group_attributes".
471
 */
472
function _bootstrap_remove_class($class, &$element, $property = 'attributes') {
473
  // Retrieve the element's classes.
474
  $classes = &_bootstrap_get_classes($element, $property);
475

    
476
  // Convert the class to an array.
477
  if (!is_array($class)) {
478
    $class = array($class);
479
  }
480

    
481
  // Iterate over all classes to add.
482
  foreach ($class as $_class) {
483
    $key = array_search($_class, $classes);
484
    if ($key !== FALSE) {
485
      unset($classes[$key]);
486
    }
487
  }
488
}
489

    
490
/**
491
 * Returns a list of base themes for active or provided theme.
492
 *
493
 * @param string $theme_key
494
 *   The machine name of the theme to check, if not set the active theme name
495
 *   will be used.
496
 * @param bool $include_theme_key
497
 *   Whether to append the returned list with $theme_key.
498
 *
499
 * @return array
500
 *   An indexed array of base themes.
501
 */
502
function _bootstrap_get_base_themes($theme_key = NULL, $include_theme_key = FALSE) {
503
  static $themes;
504
  if (!isset($theme_key)) {
505
    $theme_key = $GLOBALS['theme_key'];
506
  }
507
  if (!isset($themes[$theme_key])) {
508
    $themes[$theme_key] = array_unique(array_filter((array) bootstrap_get_theme_info($theme_key, 'base theme')));
509
  }
510
  if ($include_theme_key) {
511
    $themes[$theme_key][] = $theme_key;
512
  }
513
  return $themes[$theme_key];
514
}
515

    
516
/**
517
 * Wrapper for the core file_scan_directory() function.
518
 *
519
 * Finds all files that match a given mask in a given directory and then caches
520
 * the results. A general site cache clear will force new scans to be initiated
521
 * for already cached directories.
522
 *
523
 * @param string $dir
524
 *   The base directory or URI to scan, without trailing slash.
525
 * @param string $mask
526
 *   The preg_match() regular expression of the files to find.
527
 * @param array $options
528
 *   Additional options to pass to file_scan_directory().
529
 *
530
 * @return array
531
 *   An associative array (keyed on the chosen key) of objects with 'uri',
532
 *   'filename', and 'name' members corresponding to the matching files.
533
 *
534
 * @see file_scan_directory()
535
 */
536
function _bootstrap_file_scan_directory($dir, $mask, array $options = array()) {
537
  // Retrieve cached data.
538
  $cid = 'theme_registry:bootstrap:files';
539
  $files = array();
540
  if ($cache = cache_get($cid)) {
541
    $files = $cache->data;
542
  }
543
  // Generate a unique hash for all parameters passed as a change in any of
544
  // them would return different results.
545
  $hash = drupal_hash_base64(serialize(func_get_args()));
546
  if (!isset($files[$hash])) {
547
    $files[$hash] = file_scan_directory($dir, $mask, $options);
548
    cache_set($cid, $files);
549
  }
550
  return $files[$hash];
551
}
552

    
553
/**
554
 * Returns a list of available Bootstrap Glyphicons.
555
 *
556
 * @param string $version
557
 *   The specific version of glyphicons to return. If not set, the latest
558
 *   BOOTSTRAP_VERSION will be used.
559
 *
560
 * @return array
561
 *   An associative array of icons keyed by their classes.
562
 */
563
function _bootstrap_glyphicons($version = NULL) {
564
  static $versions;
565
  if (!isset($versions)) {
566
    $versions = array();
567
    $versions['3.0.0'] = array(
568
      // Class => Name.
569
      'glyphicon-adjust' => 'adjust',
570
      'glyphicon-align-center' => 'align-center',
571
      'glyphicon-align-justify' => 'align-justify',
572
      'glyphicon-align-left' => 'align-left',
573
      'glyphicon-align-right' => 'align-right',
574
      'glyphicon-arrow-down' => 'arrow-down',
575
      'glyphicon-arrow-left' => 'arrow-left',
576
      'glyphicon-arrow-right' => 'arrow-right',
577
      'glyphicon-arrow-up' => 'arrow-up',
578
      'glyphicon-asterisk' => 'asterisk',
579
      'glyphicon-backward' => 'backward',
580
      'glyphicon-ban-circle' => 'ban-circle',
581
      'glyphicon-barcode' => 'barcode',
582
      'glyphicon-bell' => 'bell',
583
      'glyphicon-bold' => 'bold',
584
      'glyphicon-book' => 'book',
585
      'glyphicon-bookmark' => 'bookmark',
586
      'glyphicon-briefcase' => 'briefcase',
587
      'glyphicon-bullhorn' => 'bullhorn',
588
      'glyphicon-calendar' => 'calendar',
589
      'glyphicon-camera' => 'camera',
590
      'glyphicon-certificate' => 'certificate',
591
      'glyphicon-check' => 'check',
592
      'glyphicon-chevron-down' => 'chevron-down',
593
      'glyphicon-chevron-left' => 'chevron-left',
594
      'glyphicon-chevron-right' => 'chevron-right',
595
      'glyphicon-chevron-up' => 'chevron-up',
596
      'glyphicon-circle-arrow-down' => 'circle-arrow-down',
597
      'glyphicon-circle-arrow-left' => 'circle-arrow-left',
598
      'glyphicon-circle-arrow-right' => 'circle-arrow-right',
599
      'glyphicon-circle-arrow-up' => 'circle-arrow-up',
600
      'glyphicon-cloud' => 'cloud',
601
      'glyphicon-cloud-download' => 'cloud-download',
602
      'glyphicon-cloud-upload' => 'cloud-upload',
603
      'glyphicon-cog' => 'cog',
604
      'glyphicon-collapse-down' => 'collapse-down',
605
      'glyphicon-collapse-up' => 'collapse-up',
606
      'glyphicon-comment' => 'comment',
607
      'glyphicon-compressed' => 'compressed',
608
      'glyphicon-copyright-mark' => 'copyright-mark',
609
      'glyphicon-credit-card' => 'credit-card',
610
      'glyphicon-cutlery' => 'cutlery',
611
      'glyphicon-dashboard' => 'dashboard',
612
      'glyphicon-download' => 'download',
613
      'glyphicon-download-alt' => 'download-alt',
614
      'glyphicon-earphone' => 'earphone',
615
      'glyphicon-edit' => 'edit',
616
      'glyphicon-eject' => 'eject',
617
      'glyphicon-envelope' => 'envelope',
618
      'glyphicon-euro' => 'euro',
619
      'glyphicon-exclamation-sign' => 'exclamation-sign',
620
      'glyphicon-expand' => 'expand',
621
      'glyphicon-export' => 'export',
622
      'glyphicon-eye-close' => 'eye-close',
623
      'glyphicon-eye-open' => 'eye-open',
624
      'glyphicon-facetime-video' => 'facetime-video',
625
      'glyphicon-fast-backward' => 'fast-backward',
626
      'glyphicon-fast-forward' => 'fast-forward',
627
      'glyphicon-file' => 'file',
628
      'glyphicon-film' => 'film',
629
      'glyphicon-filter' => 'filter',
630
      'glyphicon-fire' => 'fire',
631
      'glyphicon-flag' => 'flag',
632
      'glyphicon-flash' => 'flash',
633
      'glyphicon-floppy-disk' => 'floppy-disk',
634
      'glyphicon-floppy-open' => 'floppy-open',
635
      'glyphicon-floppy-remove' => 'floppy-remove',
636
      'glyphicon-floppy-save' => 'floppy-save',
637
      'glyphicon-floppy-saved' => 'floppy-saved',
638
      'glyphicon-folder-close' => 'folder-close',
639
      'glyphicon-folder-open' => 'folder-open',
640
      'glyphicon-font' => 'font',
641
      'glyphicon-forward' => 'forward',
642
      'glyphicon-fullscreen' => 'fullscreen',
643
      'glyphicon-gbp' => 'gbp',
644
      'glyphicon-gift' => 'gift',
645
      'glyphicon-glass' => 'glass',
646
      'glyphicon-globe' => 'globe',
647
      'glyphicon-hand-down' => 'hand-down',
648
      'glyphicon-hand-left' => 'hand-left',
649
      'glyphicon-hand-right' => 'hand-right',
650
      'glyphicon-hand-up' => 'hand-up',
651
      'glyphicon-hd-video' => 'hd-video',
652
      'glyphicon-hdd' => 'hdd',
653
      'glyphicon-header' => 'header',
654
      'glyphicon-headphones' => 'headphones',
655
      'glyphicon-heart' => 'heart',
656
      'glyphicon-heart-empty' => 'heart-empty',
657
      'glyphicon-home' => 'home',
658
      'glyphicon-import' => 'import',
659
      'glyphicon-inbox' => 'inbox',
660
      'glyphicon-indent-left' => 'indent-left',
661
      'glyphicon-indent-right' => 'indent-right',
662
      'glyphicon-info-sign' => 'info-sign',
663
      'glyphicon-italic' => 'italic',
664
      'glyphicon-leaf' => 'leaf',
665
      'glyphicon-link' => 'link',
666
      'glyphicon-list' => 'list',
667
      'glyphicon-list-alt' => 'list-alt',
668
      'glyphicon-lock' => 'lock',
669
      'glyphicon-log-in' => 'log-in',
670
      'glyphicon-log-out' => 'log-out',
671
      'glyphicon-magnet' => 'magnet',
672
      'glyphicon-map-marker' => 'map-marker',
673
      'glyphicon-minus' => 'minus',
674
      'glyphicon-minus-sign' => 'minus-sign',
675
      'glyphicon-move' => 'move',
676
      'glyphicon-music' => 'music',
677
      'glyphicon-new-window' => 'new-window',
678
      'glyphicon-off' => 'off',
679
      'glyphicon-ok' => 'ok',
680
      'glyphicon-ok-circle' => 'ok-circle',
681
      'glyphicon-ok-sign' => 'ok-sign',
682
      'glyphicon-open' => 'open',
683
      'glyphicon-paperclip' => 'paperclip',
684
      'glyphicon-pause' => 'pause',
685
      'glyphicon-pencil' => 'pencil',
686
      'glyphicon-phone' => 'phone',
687
      'glyphicon-phone-alt' => 'phone-alt',
688
      'glyphicon-picture' => 'picture',
689
      'glyphicon-plane' => 'plane',
690
      'glyphicon-play' => 'play',
691
      'glyphicon-play-circle' => 'play-circle',
692
      'glyphicon-plus' => 'plus',
693
      'glyphicon-plus-sign' => 'plus-sign',
694
      'glyphicon-print' => 'print',
695
      'glyphicon-pushpin' => 'pushpin',
696
      'glyphicon-qrcode' => 'qrcode',
697
      'glyphicon-question-sign' => 'question-sign',
698
      'glyphicon-random' => 'random',
699
      'glyphicon-record' => 'record',
700
      'glyphicon-refresh' => 'refresh',
701
      'glyphicon-registration-mark' => 'registration-mark',
702
      'glyphicon-remove' => 'remove',
703
      'glyphicon-remove-circle' => 'remove-circle',
704
      'glyphicon-remove-sign' => 'remove-sign',
705
      'glyphicon-repeat' => 'repeat',
706
      'glyphicon-resize-full' => 'resize-full',
707
      'glyphicon-resize-horizontal' => 'resize-horizontal',
708
      'glyphicon-resize-small' => 'resize-small',
709
      'glyphicon-resize-vertical' => 'resize-vertical',
710
      'glyphicon-retweet' => 'retweet',
711
      'glyphicon-road' => 'road',
712
      'glyphicon-save' => 'save',
713
      'glyphicon-saved' => 'saved',
714
      'glyphicon-screenshot' => 'screenshot',
715
      'glyphicon-sd-video' => 'sd-video',
716
      'glyphicon-search' => 'search',
717
      'glyphicon-send' => 'send',
718
      'glyphicon-share' => 'share',
719
      'glyphicon-share-alt' => 'share-alt',
720
      'glyphicon-shopping-cart' => 'shopping-cart',
721
      'glyphicon-signal' => 'signal',
722
      'glyphicon-sort' => 'sort',
723
      'glyphicon-sort-by-alphabet' => 'sort-by-alphabet',
724
      'glyphicon-sort-by-alphabet-alt' => 'sort-by-alphabet-alt',
725
      'glyphicon-sort-by-attributes' => 'sort-by-attributes',
726
      'glyphicon-sort-by-attributes-alt' => 'sort-by-attributes-alt',
727
      'glyphicon-sort-by-order' => 'sort-by-order',
728
      'glyphicon-sort-by-order-alt' => 'sort-by-order-alt',
729
      'glyphicon-sound-5-1' => 'sound-5-1',
730
      'glyphicon-sound-6-1' => 'sound-6-1',
731
      'glyphicon-sound-7-1' => 'sound-7-1',
732
      'glyphicon-sound-dolby' => 'sound-dolby',
733
      'glyphicon-sound-stereo' => 'sound-stereo',
734
      'glyphicon-star' => 'star',
735
      'glyphicon-star-empty' => 'star-empty',
736
      'glyphicon-stats' => 'stats',
737
      'glyphicon-step-backward' => 'step-backward',
738
      'glyphicon-step-forward' => 'step-forward',
739
      'glyphicon-stop' => 'stop',
740
      'glyphicon-subtitles' => 'subtitles',
741
      'glyphicon-tag' => 'tag',
742
      'glyphicon-tags' => 'tags',
743
      'glyphicon-tasks' => 'tasks',
744
      'glyphicon-text-height' => 'text-height',
745
      'glyphicon-text-width' => 'text-width',
746
      'glyphicon-th' => 'th',
747
      'glyphicon-th-large' => 'th-large',
748
      'glyphicon-th-list' => 'th-list',
749
      'glyphicon-thumbs-down' => 'thumbs-down',
750
      'glyphicon-thumbs-up' => 'thumbs-up',
751
      'glyphicon-time' => 'time',
752
      'glyphicon-tint' => 'tint',
753
      'glyphicon-tower' => 'tower',
754
      'glyphicon-transfer' => 'transfer',
755
      'glyphicon-trash' => 'trash',
756
      'glyphicon-tree-conifer' => 'tree-conifer',
757
      'glyphicon-tree-deciduous' => 'tree-deciduous',
758
      'glyphicon-unchecked' => 'unchecked',
759
      'glyphicon-upload' => 'upload',
760
      'glyphicon-usd' => 'usd',
761
      'glyphicon-user' => 'user',
762
      'glyphicon-volume-down' => 'volume-down',
763
      'glyphicon-volume-off' => 'volume-off',
764
      'glyphicon-volume-up' => 'volume-up',
765
      'glyphicon-warning-sign' => 'warning-sign',
766
      'glyphicon-wrench' => 'wrench',
767
      'glyphicon-zoom-in' => 'zoom-in',
768
      'glyphicon-zoom-out' => 'zoom-out',
769
    );
770
    $versions['3.0.1'] = $versions['3.0.0'];
771
    $versions['3.0.2'] = $versions['3.0.1'];
772
    $versions['3.0.3'] = $versions['3.0.2'];
773
    $versions['3.1.0'] = $versions['3.0.3'];
774
    $versions['3.1.1'] = $versions['3.1.0'];
775
    $versions['3.2.0'] = $versions['3.1.1'];
776
    $versions['3.3.0'] = array_merge($versions['3.2.0'], array(
777
      'glyphicon-eur' => 'eur',
778
    ));
779
    $versions['3.3.1'] = $versions['3.3.0'];
780
    $versions['3.3.2'] = array_merge($versions['3.3.1'], array(
781
      'glyphicon-alert' => 'alert',
782
      'glyphicon-apple' => 'apple',
783
      'glyphicon-baby-formula' => 'baby-formula',
784
      'glyphicon-bed' => 'bed',
785
      'glyphicon-bishop' => 'bishop',
786
      'glyphicon-bitcoin' => 'bitcoin',
787
      'glyphicon-blackboard' => 'blackboard',
788
      'glyphicon-cd' => 'cd',
789
      'glyphicon-console' => 'console',
790
      'glyphicon-copy' => 'copy',
791
      'glyphicon-duplicate' => 'duplicate',
792
      'glyphicon-education' => 'education',
793
      'glyphicon-equalizer' => 'equalizer',
794
      'glyphicon-erase' => 'erase',
795
      'glyphicon-grain' => 'grain',
796
      'glyphicon-hourglass' => 'hourglass',
797
      'glyphicon-ice-lolly' => 'ice-lolly',
798
      'glyphicon-ice-lolly-tasted' => 'ice-lolly-tasted',
799
      'glyphicon-king' => 'king',
800
      'glyphicon-knight' => 'knight',
801
      'glyphicon-lamp' => 'lamp',
802
      'glyphicon-level-up' => 'level-up',
803
      'glyphicon-menu-down' => 'menu-down',
804
      'glyphicon-menu-hamburger' => 'menu-hamburger',
805
      'glyphicon-menu-left' => 'menu-left',
806
      'glyphicon-menu-right' => 'menu-right',
807
      'glyphicon-menu-up' => 'menu-up',
808
      'glyphicon-modal-window' => 'modal-window',
809
      'glyphicon-object-align-bottom' => 'object-align-bottom',
810
      'glyphicon-object-align-horizontal' => 'object-align-horizontal',
811
      'glyphicon-object-align-left' => 'object-align-left',
812
      'glyphicon-object-align-right' => 'object-align-right',
813
      'glyphicon-object-align-top' => 'object-align-top',
814
      'glyphicon-object-align-vertical' => 'object-align-vertical',
815
      'glyphicon-oil' => 'oil',
816
      'glyphicon-open-file' => 'open-file',
817
      'glyphicon-option-horizontal' => 'option-horizontal',
818
      'glyphicon-option-vertical' => 'option-vertical',
819
      'glyphicon-paste' => 'paste',
820
      'glyphicon-pawn' => 'pawn',
821
      'glyphicon-piggy-bank' => 'piggy-bank',
822
      'glyphicon-queen' => 'queen',
823
      'glyphicon-ruble' => 'ruble',
824
      'glyphicon-save-file' => 'save-file',
825
      'glyphicon-scale' => 'scale',
826
      'glyphicon-scissors' => 'scissors',
827
      'glyphicon-subscript' => 'subscript',
828
      'glyphicon-sunglasses' => 'sunglasses',
829
      'glyphicon-superscript' => 'superscript',
830
      'glyphicon-tent' => 'tent',
831
      'glyphicon-text-background' => 'text-background',
832
      'glyphicon-text-color' => 'text-color',
833
      'glyphicon-text-size' => 'text-size',
834
      'glyphicon-triangle-bottom' => 'triangle-bottom',
835
      'glyphicon-triangle-left' => 'triangle-left',
836
      'glyphicon-triangle-right' => 'triangle-right',
837
      'glyphicon-triangle-top' => 'triangle-top',
838
      'glyphicon-yen' => 'yen',
839
    ));
840
    $versions['3.3.4'] = array_merge($versions['3.3.2'], array(
841
      'glyphicon-btc' => 'btc',
842
      'glyphicon-jpy' => 'jpy',
843
      'glyphicon-rub' => 'rub',
844
      'glyphicon-xbt' => 'xbt',
845
    ));
846
    $versions['3.3.5'] = $versions['3.3.4'];
847
  }
848

    
849
  // Return a specific versions icon set.
850
  if (isset($version) && isset($versions[$version])) {
851
    return $versions[$version];
852
  }
853

    
854
  // Return the latest version.
855
  return $versions[BOOTSTRAP_VERSION];
856
}
857

    
858
/**
859
 * Returns a specific Bootstrap Glyphicon.
860
 *
861
 * @param string $name
862
 *   The icon name, minus the "glyphicon-" prefix.
863
 * @param string $default
864
 *   (Optional) The default value to return.
865
 *
866
 * @return string
867
 *   The HTML markup containing the icon defined by $name, $default value if
868
 *   icon does not exist or returns empty output for whatever reason.
869
 */
870
function _bootstrap_icon($name, $default = NULL) {
871
  $output = NULL;
872
  // Ensure the icon specified is a valid Bootstrap Glyphicon.
873
  // @todo Supply a specific version to _bootstrap_glyphicons() when Icon API
874
  // supports versioning.
875
  if (_bootstrap_glyphicons_supported() && in_array($name, _bootstrap_glyphicons())) {
876
    // Attempt to use the Icon API module, if enabled and it generates output.
877
    if (module_exists('icon')) {
878
      $output = theme('icon', array('bundle' => 'bootstrap', 'icon' => 'glyphicon-' . $name));
879
    }
880
    if (empty($output)) {
881
      // Mimic the Icon API markup.
882
      $attributes = array(
883
        'class' => array('icon', 'glyphicon', 'glyphicon-' . $name),
884
        'aria-hidden' => 'true',
885
      );
886
      $output = '<span' . drupal_attributes($attributes) . '></span>';
887
    }
888
  }
889
  return empty($output) && isset($default) ? $default : $output;
890
}
891

    
892
/**
893
 * Determine whether or not Bootstrap Glyphicons can be used.
894
 */
895
function _bootstrap_glyphicons_supported() {
896
  // Use the advanced drupal_static() pattern, since this has the potential to
897
  // be called very often by _bootstrap_icon().
898
  static $drupal_static_fast;
899
  if (!isset($drupal_static_fast)) {
900
    $drupal_static_fast['supported'] = &drupal_static(__FUNCTION__);
901
    // Get the active theme.
902
    $drupal_static_fast['theme'] = variable_get('theme_default', $GLOBALS['theme']);
903
  }
904

    
905
  // Get static data.
906
  $supported = &$drupal_static_fast['supported'];
907
  $theme = &$drupal_static_fast['theme'];
908

    
909
  // Retrieve supported themes.
910
  if (!isset($supported)) {
911
    $supported = array();
912

    
913
    // Retrieve cached data.
914
    $cid = 'theme_registry:bootstrap:icon_support';
915
    if (($cache = cache_get($cid)) && !empty($cache->data)) {
916
      $supported = $cache->data;
917
    }
918

    
919
    if (!isset($supported[$theme])) {
920
      // Bootstrap based themes are enabled by default to use CDN. Check if
921
      // that is the case here so no file discovery is necessary. If the active
922
      // theme does not have this setting, it falls back to the base theme that
923
      // does.
924
      $supported[$theme] = !!bootstrap_get_cdn_assets('css', NULL, $theme);
925

    
926
      // CDN not used, iterate over all of the active (base) themes to determine
927
      // if they contain glyphicon font files.
928
      if (!$supported[$theme]) {
929
        foreach (_bootstrap_get_base_themes($theme, TRUE) as $_theme) {
930
          // Scan the theme for files.
931
          $fonts = _bootstrap_file_scan_directory(drupal_get_path('theme', $_theme), '/glyphicons-halflings-regular\.(eot|svg|ttf|woff)$/');
932

    
933
          // Fonts found, stop the search.
934
          if (!empty($fonts)) {
935
            $supported[$theme] = TRUE;
936
            break;
937
          }
938
        }
939
      }
940

    
941
      // Cache all supported themes now that this theme is added to the array.
942
      cache_set($cid, $supported);
943
    }
944
  }
945
  return $supported[$theme];
946
}
947

    
948
/**
949
 * Determine whether a specific element is a button.
950
 *
951
 * @param array $element
952
 *   A renderable element.
953
 *
954
 * @return bool
955
 *   TRUE or FALSE.
956
 */
957
function _bootstrap_is_button($element) {
958
  return
959
    !empty($element['#type']) &&
960
    !empty($element['#value']) && (
961
      $element['#type'] === 'button' ||
962
      $element['#type'] === 'submit' ||
963
      $element['#type'] === 'image_button'
964
    );
965
}
966

    
967
/**
968
 * Adds a specific Bootstrap class to color a button based on its text value.
969
 *
970
 * @param array $element
971
 *   The form element, passed by reference.
972
 */
973
function _bootstrap_colorize_button(&$element) {
974
  if (_bootstrap_is_button($element)) {
975
    // Do not add the class if one is already present in the array.
976
    $button_classes = array(
977
      'btn-default',
978
      'btn-primary',
979
      'btn-success',
980
      'btn-info',
981
      'btn-warning',
982
      'btn-danger',
983
      'btn-link',
984
    );
985
    $class_intersection = array_intersect($button_classes, $element['#attributes']['class']);
986
    if (empty($class_intersection)) {
987
      // Get the matched class.
988
      $class = bootstrap_setting('button_colorize') ? _bootstrap_colorize_text($element['#value']) : FALSE;
989
      // If no particular class matched, use the default style.
990
      if (!$class) {
991
        $class = 'default';
992
      }
993
      $element['#attributes']['class'][] = 'btn-' . $class;
994
    }
995
  }
996
}
997

    
998
/**
999
 * Matches a Bootstrap class based on a string value.
1000
 *
1001
 * @param string $string
1002
 *   The string to match classes against.
1003
 * @param string $default
1004
 *   The default class to return if no match is found.
1005
 *
1006
 * @return string
1007
 *   The Bootstrap class matched against the value of $haystack or $default if
1008
 *   no match could be made.
1009
 */
1010
function _bootstrap_colorize_text($string, $default = '') {
1011
  static $texts;
1012
  if (!isset($texts)) {
1013
    $texts = array(
1014
      // Text that match these specific strings are checked first.
1015
      'matches' => array(
1016
        // Primary class.
1017
        t('Download feature')   => 'primary',
1018

    
1019
        // Success class.
1020
        t('Add effect')         => 'success',
1021
        t('Add and configure')  => 'success',
1022

    
1023
        // Info class.
1024
        t('Save and add')       => 'info',
1025
        t('Add another item')   => 'info',
1026
        t('Update style')       => 'info',
1027
      ),
1028

    
1029
      // Text that contain these words anywhere in the string are checked last.
1030
      'contains' => array(
1031
        // Primary class.
1032
        t('Confirm')            => 'primary',
1033
        t('Filter')             => 'primary',
1034
        t('Submit')             => 'primary',
1035
        t('Search')             => 'primary',
1036

    
1037
        // Success class.
1038
        t('Add')                => 'success',
1039
        t('Create')             => 'success',
1040
        t('Save')               => 'success',
1041
        t('Write')              => 'success',
1042

    
1043
        // Warning class.
1044
        t('Export')             => 'warning',
1045
        t('Import')             => 'warning',
1046
        t('Restore')            => 'warning',
1047
        t('Rebuild')            => 'warning',
1048

    
1049
        // Info class.
1050
        t('Apply')              => 'info',
1051
        t('Update')             => 'info',
1052

    
1053
        // Danger class.
1054
        t('Delete')             => 'danger',
1055
        t('Remove')             => 'danger',
1056
      ),
1057
    );
1058

    
1059
    // Allow sub-themes to alter this array of patterns.
1060
    drupal_alter('bootstrap_colorize_text', $texts);
1061
  }
1062

    
1063
  // Iterate over the array.
1064
  foreach ($texts as $pattern => $strings) {
1065
    foreach ($strings as $value => $class) {
1066
      switch ($pattern) {
1067
        case 'matches':
1068
          if ($string === $value) {
1069
            return $class;
1070
          }
1071
          break;
1072

    
1073
        case 'contains':
1074
          if (strpos(drupal_strtolower($string), drupal_strtolower($value)) !== FALSE) {
1075
            return $class;
1076
          }
1077
          break;
1078
      }
1079
    }
1080
  }
1081

    
1082
  // Return the default if nothing was matched.
1083
  return $default;
1084
}
1085

    
1086
/**
1087
 * Adds an icon to button element based on its text value.
1088
 *
1089
 * @param array $element
1090
 *   The form element, passed by reference.
1091
 */
1092
function _bootstrap_iconize_button(&$element) {
1093
  if (bootstrap_setting('button_iconize') && _bootstrap_is_button($element) && ($icon = _bootstrap_iconize_text($element['#value']))) {
1094
    $element['#icon'] = $icon;
1095
  }
1096
}
1097

    
1098
/**
1099
 * Matches a Bootstrap Glyphicon based on a string value.
1100
 *
1101
 * @param string $string
1102
 *   The string to match classes against.
1103
 * @param string $default
1104
 *   The default icon to return if no match is found.
1105
 *
1106
 * @return string
1107
 *   The Bootstrap icon matched against the value of $haystack or $default if
1108
 *   no match could be made.
1109
 */
1110
function _bootstrap_iconize_text($string, $default = '') {
1111
  static $texts;
1112
  if (!isset($texts)) {
1113
    $texts = array(
1114
      // Text that match these specific strings are checked first.
1115
      'matches' => array(),
1116

    
1117
      // Text that contain these words anywhere in the string are checked last.
1118
      'contains' => array(
1119
        t('Manage')     => 'cog',
1120
        t('Configure')  => 'cog',
1121
        t('Download')   => 'download',
1122
        t('Export')     => 'export',
1123
        t('Filter')     => 'filter',
1124
        t('Import')     => 'import',
1125
        t('Save')       => 'ok',
1126
        t('Update')     => 'ok',
1127
        t('Edit')       => 'pencil',
1128
        t('Add')        => 'plus',
1129
        t('Write')      => 'plus',
1130
        t('Cancel')     => 'remove',
1131
        t('Delete')     => 'trash',
1132
        t('Remove')     => 'trash',
1133
        t('Upload')     => 'upload',
1134
      ),
1135
    );
1136

    
1137
    // Allow sub-themes to alter this array of patterns.
1138
    drupal_alter('bootstrap_iconize_text', $texts);
1139
  }
1140

    
1141
  // Iterate over the array.
1142
  foreach ($texts as $pattern => $strings) {
1143
    foreach ($strings as $value => $icon) {
1144
      switch ($pattern) {
1145
        case 'matches':
1146
          if ($string === $value) {
1147
            return _bootstrap_icon($icon, $default);
1148
          }
1149
          break;
1150

    
1151
        case 'contains':
1152
          if (strpos(drupal_strtolower($string), drupal_strtolower($value)) !== FALSE) {
1153
            return _bootstrap_icon($icon, $default);
1154
          }
1155
          break;
1156
      }
1157
    }
1158
  }
1159

    
1160
  // Return a default icon if nothing was matched.
1161
  return _bootstrap_icon($default);
1162
}
1163

    
1164
/**
1165
 * Invokes a specific suggestion's preprocess functions.
1166
 *
1167
 * @param array $variables
1168
 *   The theme implementation variables array.
1169
 */
1170
function _bootstrap_preprocess_theme_suggestion(&$variables) {
1171
  $registry = theme_get_registry();
1172
  if (!empty($variables['theme_hook_suggestion']) && !empty($registry[$variables['theme_hook_suggestion']]['preprocess functions'])) {
1173
    // Save the suggestion as the hook to pass to the function.
1174
    $hook = $variables['theme_hook_suggestion'];
1175

    
1176
    // Iterate over the preprocess functions.
1177
    foreach ($registry[$hook]['preprocess functions'] as $function) {
1178
      if (function_exists($function)) {
1179
        // Invoke theme hook suggestion preprocess function.
1180
        $function($variables, $hook);
1181

    
1182
        // Unset the theme_hook_suggestion so the suggestion's preprocess
1183
        // functions can provide theme_hook_suggestions if needed.
1184
        if (!empty($variables['theme_hook_suggestions'])) {
1185
          unset($variables['theme_hook_suggestion']);
1186
        }
1187
      }
1188
    }
1189
  }
1190
}
1191

    
1192
/**
1193
 * Invokes a specific suggestion's process functions.
1194
 *
1195
 * @param array $variables
1196
 *   The theme implementation variables array.
1197
 */
1198
function _bootstrap_process_theme_suggestion(&$variables) {
1199
  $registry = theme_get_registry();
1200
  if (!empty($variables['theme_hook_suggestion']) && !empty($registry[$variables['theme_hook_suggestion']]['process functions'])) {
1201
    // Save the suggestion as the hook to pass to the function.
1202
    $hook = $variables['theme_hook_suggestion'];
1203

    
1204
    // Iterate over the process functions.
1205
    foreach ($registry[$hook]['process functions'] as $function) {
1206
      if (function_exists($function)) {
1207
        // Invoke theme hook suggestion process function.
1208
        $function($variables, $hook);
1209

    
1210
        // Unset the theme_hook_suggestion so the suggestion's preprocess
1211
        // functions can provide theme_hook_suggestions if needed.
1212
        if (!empty($variables['theme_hook_suggestions'])) {
1213
          unset($variables['theme_hook_suggestion']);
1214
        }
1215
      }
1216
    }
1217
  }
1218
}
1219

    
1220
/**
1221
 * Determines if a string of text is considered "simple".
1222
 *
1223
 * @param string $string
1224
 *   The string of text to check "simple" criteria on.
1225
 * @param int|FALSE $length
1226
 *   The length of characters used to determine whether or not $string is
1227
 *   considered "simple". Set explicitly to FALSE to disable this criteria.
1228
 * @param array|FALSE $allowed_tags
1229
 *   An array of allowed tag elements. Set explicitly to FALSE to disable this
1230
 *   criteria.
1231
 * @param bool $html
1232
 *   A variable, passed by reference, that indicates whether or not the
1233
 *   string contains HTML.
1234
 *
1235
 * @return bool
1236
 *   Returns TRUE if the $string is considered "simple", FALSE otherwise.
1237
 */
1238
function _bootstrap_is_simple_string($string, $length = 250, $allowed_tags = NULL, &$html = FALSE) {
1239
  // Use the advanced drupal_static() pattern, since this is called very often.
1240
  static $drupal_static_fast;
1241
  if (!isset($drupal_static_fast)) {
1242
    $drupal_static_fast['strings'] = &drupal_static(__FUNCTION__);
1243
  }
1244
  $strings = &$drupal_static_fast['strings'];
1245
  if (!isset($strings[$string])) {
1246
    $plain_string = strip_tags($string);
1247
    $simple = TRUE;
1248
    if ($allowed_tags !== FALSE) {
1249
      $filtered_string = filter_xss($string, $allowed_tags);
1250
      $html = $filtered_string !== $plain_string;
1251
      $simple = $simple && $string === $filtered_string;
1252
    }
1253
    if ($length !== FALSE) {
1254
      $simple = $simple && strlen($plain_string) <= intval($length);
1255
    }
1256
    $strings[$string] = $simple;
1257
  }
1258
  return $strings[$string];
1259
}
1260

    
1261
/**
1262
 * @} End of "defgroup subtheme_helper_functions".
1263
 */