Added Entity and Entity Reference Revisions which got dropped somewhere along the...
[yaffs-website] / web / themes / contrib / bootstrap / src / Bootstrap.php
1 <?php
2
3 namespace Drupal\bootstrap;
4
5 use Drupal\bootstrap\Plugin\AlterManager;
6 use Drupal\bootstrap\Plugin\FormManager;
7 use Drupal\bootstrap\Plugin\PreprocessManager;
8 use Drupal\bootstrap\Utility\Element;
9 use Drupal\bootstrap\Utility\Unicode;
10 use Drupal\Component\Utility\Html;
11 use Drupal\Component\Utility\NestedArray;
12 use Drupal\Core\Extension\ThemeHandlerInterface;
13 use Drupal\Core\Form\FormStateInterface;
14
15 /**
16  * The primary class for the Drupal Bootstrap base theme.
17  *
18  * Provides many helper methods.
19  *
20  * @ingroup utility
21  */
22 class Bootstrap {
23
24   /**
25    * Tag used to invalidate caches.
26    *
27    * @var string
28    */
29   const CACHE_TAG = 'theme_registry';
30
31   /**
32    * Append a callback.
33    *
34    * @var int
35    */
36   const CALLBACK_APPEND = 1;
37
38   /**
39    * Prepend a callback.
40    *
41    * @var int
42    */
43   const CALLBACK_PREPEND = 2;
44
45   /**
46    * Replace a callback or append it if not found.
47    *
48    * @var int
49    */
50   const CALLBACK_REPLACE_APPEND = 3;
51
52   /**
53    * Replace a callback or prepend it if not found.
54    *
55    * @var int
56    */
57   const CALLBACK_REPLACE_PREPEND = 4;
58
59   /**
60    * The current supported Bootstrap Framework version.
61    *
62    * @var string
63    */
64   const FRAMEWORK_VERSION = '3.3.7';
65
66   /**
67    * The Bootstrap Framework documentation site.
68    *
69    * @var string
70    */
71   const FRAMEWORK_HOMEPAGE = 'https://getbootstrap.com/docs/3.3/';
72
73   /**
74    * The Bootstrap Framework repository.
75    *
76    * @var string
77    */
78   const FRAMEWORK_REPOSITORY = 'https://github.com/twbs/bootstrap';
79
80   /**
81    * The project branch.
82    *
83    * @var string
84    */
85   const PROJECT_BRANCH = '8.x-3.x';
86
87   /**
88    * The Drupal Bootstrap documentation site.
89    *
90    * @var string
91    */
92   const PROJECT_DOCUMENTATION = 'https://drupal-bootstrap.org';
93
94   /**
95    * The Drupal Bootstrap project page.
96    *
97    * @var string
98    */
99   const PROJECT_PAGE = 'https://www.drupal.org/project/bootstrap';
100
101   /**
102    * Adds a callback to an array.
103    *
104    * @param array $callbacks
105    *   An array of callbacks to add the callback to, passed by reference.
106    * @param array|string $callback
107    *   The callback to add.
108    * @param array|string $replace
109    *   If specified, the callback will instead replace the specified value
110    *   instead of being appended to the $callbacks array.
111    * @param int $action
112    *   Flag that determines how to add the callback to the array.
113    *
114    * @return bool
115    *   TRUE if the callback was added, FALSE if $replace was specified but its
116    *   callback could be found in the list of callbacks.
117    */
118   public static function addCallback(array &$callbacks, $callback, $replace = NULL, $action = Bootstrap::CALLBACK_APPEND) {
119     // Replace a callback.
120     if ($replace) {
121       // Iterate through the callbacks.
122       foreach ($callbacks as $key => $value) {
123         // Convert each callback and match the string values.
124         if (Unicode::convertCallback($value) === Unicode::convertCallback($replace)) {
125           $callbacks[$key] = $callback;
126           return TRUE;
127         }
128       }
129       // No match found and action shouldn't append or prepend.
130       if ($action !== self::CALLBACK_REPLACE_APPEND || $action !== self::CALLBACK_REPLACE_PREPEND) {
131         return FALSE;
132       }
133     }
134
135     // Append or prepend the callback.
136     switch ($action) {
137       case self::CALLBACK_APPEND:
138       case self::CALLBACK_REPLACE_APPEND:
139         $callbacks[] = $callback;
140         return TRUE;
141
142       case self::CALLBACK_PREPEND:
143       case self::CALLBACK_REPLACE_PREPEND:
144         array_unshift($callbacks, $callback);
145         return TRUE;
146
147       default:
148         return FALSE;
149     }
150   }
151
152   /**
153    * Manages theme alter hooks as classes and allows sub-themes to sub-class.
154    *
155    * @param string $function
156    *   The procedural function name of the alter (e.g. __FUNCTION__).
157    * @param mixed $data
158    *   The variable that was passed to the hook_TYPE_alter() implementation to
159    *   be altered. The type of this variable depends on the value of the $type
160    *   argument. For example, when altering a 'form', $data will be a structured
161    *   array. When altering a 'profile', $data will be an object.
162    * @param mixed $context1
163    *   (optional) An additional variable that is passed by reference.
164    * @param mixed $context2
165    *   (optional) An additional variable that is passed by reference. If more
166    *   context needs to be provided to implementations, then this should be an
167    *   associative array as described above.
168    */
169   public static function alter($function, &$data, &$context1 = NULL, &$context2 = NULL) {
170     // Do not statically cache this as the active theme may change.
171     $theme = static::getTheme();
172     $theme_name = $theme->getName();
173
174     // Immediately return if the active theme is not Bootstrap based.
175     if (!$theme->isBootstrap()) {
176       return;
177     }
178
179     // Handle alter and form managers.
180     static $drupal_static_fast;
181     if (!isset($drupal_static_fast)) {
182       $drupal_static_fast['alter_managers'] = &drupal_static(__METHOD__ . '__alterManagers', []);
183       $drupal_static_fast['form_managers'] = &drupal_static(__METHOD__ . '__formManagers', []);
184     }
185
186     /* @var \Drupal\bootstrap\Plugin\AlterManager[] $alter_managers */
187     $alter_managers = &$drupal_static_fast['alter_managers'];
188     if (!isset($alter_managers[$theme_name])) {
189       $alter_managers[$theme_name] = new AlterManager($theme);
190     }
191
192     /* @var \Drupal\bootstrap\Plugin\FormManager[] $form_managers */
193     $form_managers = &$drupal_static_fast['form_managers'];
194     if (!isset($form_managers[$theme_name])) {
195       $form_managers[$theme_name] = new FormManager($theme);
196     }
197
198     // Retrieve the alter and form managers for this theme.
199     $alter_manager = $alter_managers[$theme_name];
200     $form_manager = $form_managers[$theme_name];
201
202     // Extract the alter hook name.
203     $hook = Unicode::extractHook($function, 'alter');
204
205     // Handle form alters as a separate plugin.
206     if (strpos($hook, 'form') === 0 && $context1 instanceof FormStateInterface) {
207       $form_state = $context1;
208       $form_id = $context2;
209
210       // Due to a core bug that affects admin themes, we should not double
211       // process the "system_theme_settings" form twice in the global
212       // hook_form_alter() invocation.
213       // @see https://www.drupal.org/node/943212
214       if ($form_id === 'system_theme_settings') {
215         return;
216       }
217
218       // Keep track of the form identifiers.
219       $ids = [];
220
221       // Get the build data.
222       $build_info = $form_state->getBuildInfo();
223
224       // Extract the base_form_id.
225       $base_form_id = !empty($build_info['base_form_id']) ? $build_info['base_form_id'] : FALSE;
226       if ($base_form_id) {
227         $ids[] = $base_form_id;
228       }
229
230       // If there was no provided form identifier, extract it.
231       if (!$form_id) {
232         $form_id = !empty($build_info['form_id']) ? $build_info['form_id'] : Unicode::extractHook($function, 'alter', 'form');
233       }
234       if ($form_id) {
235         $ids[] = $form_id;
236       }
237
238       // Iterate over each form identifier and look for a possible plugin.
239       foreach ($ids as $id) {
240         /** @var \Drupal\bootstrap\Plugin\Form\FormInterface $form */
241         if ($form_manager->hasDefinition($id) && ($form = $form_manager->createInstance($id, ['theme' => $theme]))) {
242           $data['#submit'][] = [get_class($form), 'submitForm'];
243           $data['#validate'][] = [get_class($form), 'validateForm'];
244           $form->alterForm($data, $form_state, $form_id);
245         }
246       }
247     }
248     // Process hook alter normally.
249     else {
250       /** @var \Drupal\bootstrap\Plugin\Alter\AlterInterface $class */
251       if ($alter_manager->hasDefinition($hook) && ($class = $alter_manager->createInstance($hook, ['theme' => $theme]))) {
252         $class->alter($data, $context1, $context2);
253       }
254     }
255   }
256
257   /**
258    * Returns a documentation search URL for a given query.
259    *
260    * @param string $query
261    *   The query to search for.
262    *
263    * @return string
264    *   The complete URL to the documentation site.
265    */
266   public static function apiSearchUrl($query = '') {
267     return self::PROJECT_DOCUMENTATION . '/api/bootstrap/' . self::PROJECT_BRANCH . '/search/' . Html::escape($query);
268   }
269
270   /**
271    * Returns the autoload fix include path.
272    *
273    * This method assists class based callbacks that normally do not work.
274    *
275    * If you notice that your class based callback is never invoked, you may try
276    * using this helper method as an "include" or "file" for your callback, if
277    * the callback metadata supports such an option.
278    *
279    * Depending on when or where a callback is invoked during a request, such as
280    * an ajax or batch request, the theme handler may not yet be fully
281    * initialized.
282    *
283    * Typically there is little that can be done about this "issue" from core.
284    * It must balance the appropriate level that should be bootstrapped along
285    * with common functionality. Cross-request class based callbacks are not
286    * common in themes.
287    *
288    * When this file is included, it will attempt to jump start this process.
289    *
290    * Please keep in mind, that it is merely an attempt and does not guarantee
291    * that it will actually work. If it does not appear to work, do not use it.
292    *
293    * @see \Drupal\Core\Extension\ThemeHandler::listInfo
294    * @see \Drupal\Core\Extension\ThemeHandler::systemThemeList
295    * @see system_list
296    * @see system_register()
297    * @see drupal_classloader_register()
298    *
299    * @return string
300    *   The autoload fix include path, relative to Drupal root.
301    */
302   public static function autoloadFixInclude() {
303     return static::getTheme('bootstrap')->getPath() . '/autoload-fix.php';
304   }
305
306   /**
307    * Matches a Bootstrap class based on a string value.
308    *
309    * @param string|array $value
310    *   The string to match against to determine the class. Passed by reference
311    *   in case it is a render array that needs to be rendered and typecast.
312    * @param string $default
313    *   The default class to return if no match is found.
314    *
315    * @return string
316    *   The Bootstrap class matched against the value of $haystack or $default
317    *   if no match could be made.
318    */
319   public static function cssClassFromString(&$value, $default = '') {
320     static $lang;
321     if (!isset($lang)) {
322       $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
323     }
324
325     $theme = static::getTheme();
326     $texts = $theme->getCache('cssClassFromString', [$lang]);
327
328     // Ensure it's a string value that was passed.
329     $string = static::toString($value);
330
331     if ($texts->isEmpty()) {
332       $data = [
333         // Text that match these specific strings are checked first.
334         'matches' => [
335           // Primary class.
336           t('Download feature')->render()   => 'primary',
337
338           // Success class.
339           t('Add effect')->render()         => 'success',
340           t('Add and configure')->render()  => 'success',
341           t('Save configuration')->render() => 'success',
342           t('Install and set as default')->render() => 'success',
343
344           // Info class.
345           t('Save and add')->render()       => 'info',
346           t('Add another item')->render()   => 'info',
347           t('Update style')->render()       => 'info',
348         ],
349
350         // Text containing these words anywhere in the string are checked last.
351         'contains' => [
352           // Primary class.
353           t('Confirm')->render()            => 'primary',
354           t('Filter')->render()             => 'primary',
355           t('Log in')->render()             => 'primary',
356           t('Submit')->render()             => 'primary',
357           t('Search')->render()             => 'primary',
358           t('Settings')->render()           => 'primary',
359           t('Upload')->render()             => 'primary',
360
361           // Danger class.
362           t('Delete')->render()             => 'danger',
363           t('Remove')->render()             => 'danger',
364           t('Uninstall')->render()          => 'danger',
365
366           // Success class.
367           t('Add')->render()                => 'success',
368           t('Create')->render()             => 'success',
369           t('Install')->render()            => 'success',
370           t('Save')->render()               => 'success',
371           t('Write')->render()              => 'success',
372
373           // Warning class.
374           t('Export')->render()             => 'warning',
375           t('Import')->render()             => 'warning',
376           t('Restore')->render()            => 'warning',
377           t('Rebuild')->render()            => 'warning',
378
379           // Info class.
380           t('Apply')->render()              => 'info',
381           t('Update')->render()             => 'info',
382         ],
383       ];
384
385       // Allow sub-themes to alter this array of patterns.
386       /** @var \Drupal\Core\Theme\ThemeManager $theme_manager */
387       $theme_manager = \Drupal::service('theme.manager');
388       $theme_manager->alter('bootstrap_colorize_text', $data);
389
390       $texts->setMultiple($data);
391     }
392
393     // Iterate over the array.
394     foreach ($texts as $pattern => $strings) {
395       foreach ($strings as $text => $class) {
396         switch ($pattern) {
397           case 'matches':
398             if ($string === $text) {
399               return $class;
400             }
401             break;
402
403           case 'contains':
404             if (strpos(Unicode::strtolower($string), Unicode::strtolower($text)) !== FALSE) {
405               return $class;
406             }
407             break;
408         }
409       }
410     }
411
412     // Return the default if nothing was matched.
413     return $default;
414   }
415
416   /**
417    * Logs and displays a warning about a deprecated function/method being used.
418    */
419   public static function deprecated() {
420     // Log backtrace.
421     $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
422     \Drupal::logger('bootstrap')->warning('<pre><code>' . print_r($backtrace, TRUE) . '</code></pre>');
423
424     if (!self::getTheme()->getSetting('suppress_deprecated_warnings')) {
425       return;
426     }
427
428     // Extrapolate the caller.
429     $caller = $backtrace[1];
430     $class = '';
431     if (isset($caller['class'])) {
432       $parts = explode('\\', $caller['class']);
433       $class = array_pop($parts) . '::';
434     }
435     drupal_set_message(t('The following function(s) or method(s) have been deprecated, please check the logs for a more detailed backtrace on where these are being invoked. Click on the function or method link to search the documentation site for a possible replacement or solution.'), 'warning');
436     drupal_set_message(t('<a href=":url" target="_blank">@title</a>.', [
437       ':url' => self::apiSearchUrl($class . $caller['function']),
438       '@title' => ($class ? $caller['class'] . $caller['type'] : '') . $caller['function'] . '()',
439     ]), 'warning');
440   }
441
442   /**
443    * Provides additional variables to be used in elements and templates.
444    *
445    * @return array
446    *   An associative array containing key/default value pairs.
447    */
448   public static function extraVariables() {
449     return [
450       // @see https://www.drupal.org/node/2035055
451       'context' => [],
452
453       // @see https://www.drupal.org/node/2219965
454       'icon' => NULL,
455       'icon_position' => 'before',
456       'icon_only' => FALSE,
457     ];
458   }
459
460   /**
461    * Retrieves a theme instance of \Drupal\bootstrap.
462    *
463    * @param string $name
464    *   The machine name of a theme. If omitted, the active theme will be used.
465    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
466    *   The theme handler object.
467    *
468    * @return \Drupal\bootstrap\Theme
469    *   A theme object.
470    */
471   public static function getTheme($name = NULL, ThemeHandlerInterface $theme_handler = NULL) {
472     // Immediately return if theme passed is already instantiated.
473     if ($name instanceof Theme) {
474       return $name;
475     }
476
477     static $themes = [];
478
479     // Retrieve the active theme.
480     // Do not statically cache this as the active theme may change.
481     if (!isset($name)) {
482       $name = \Drupal::theme()->getActiveTheme()->getName();
483     }
484
485     if (!isset($theme_handler)) {
486       $theme_handler = self::getThemeHandler();
487     }
488
489     if (!isset($themes[$name])) {
490       $themes[$name] = new Theme($theme_handler->getTheme($name), $theme_handler);
491     }
492
493     return $themes[$name];
494   }
495
496   /**
497    * Retrieves the theme handler instance.
498    *
499    * @return \Drupal\Core\Extension\ThemeHandlerInterface
500    *   The theme handler instance.
501    */
502   public static function getThemeHandler() {
503     static $theme_handler;
504     if (!isset($theme_handler)) {
505       $theme_handler = \Drupal::service('theme_handler');
506     }
507     return $theme_handler;
508   }
509
510   /**
511    * Returns the theme hook definition information.
512    *
513    * This base-theme's custom theme hook implementations. Never define "path"
514    * as this is automatically detected and added.
515    *
516    * @see \Drupal\bootstrap\Plugin\Alter\ThemeRegistry::alter()
517    * @see bootstrap_theme_registry_alter()
518    * @see bootstrap_theme()
519    * @see hook_theme()
520    */
521   public static function getThemeHooks() {
522     $hooks['bootstrap_carousel'] = [
523       'variables' => [
524         'attributes' => [],
525         'controls' => TRUE,
526         'id' => NULL,
527         'indicators' => TRUE,
528         'interval' => 5000,
529         'pause' => 'hover',
530         'slides' => [],
531         'start_index' => 0,
532         'wrap' => TRUE,
533       ],
534     ];
535
536     $hooks['bootstrap_dropdown'] = [
537       'variables' => [
538         'alignment' => 'down',
539         'attributes' => [],
540         'items' => [],
541         'split' => FALSE,
542         'toggle' => NULL,
543       ],
544     ];
545
546     $hooks['bootstrap_modal'] = [
547       'variables' => [
548         'attributes' => [],
549         'body' => '',
550         'body_attributes' => [],
551         'close_button' => TRUE,
552         'content_attributes' => [],
553         'description' => NULL,
554         'description_display' => 'before',
555         'dialog_attributes' => [],
556         'footer' => '',
557         'footer_attributes' => [],
558         'header_attributes' => [],
559         'id' => NULL,
560         'size' => NULL,
561         'title' => '',
562         'title_attributes' => [],
563       ],
564     ];
565
566     $hooks['bootstrap_panel'] = [
567       'variables' => [
568         'attributes' => [],
569         'body' => [],
570         'body_attributes' => [],
571         'collapsible' => FALSE,
572         'collapsed' => FALSE,
573         'description' => NULL,
574         'description_display' => 'before',
575         'footer' => NULL,
576         'footer_attributes' => [],
577         'heading' => NULL,
578         'heading_attributes' => [],
579         'id' => NULL,
580         'panel_type' => 'default',
581       ],
582     ];
583
584     return $hooks;
585   }
586
587   /**
588    * Returns a specific Bootstrap Glyphicon.
589    *
590    * @param string $name
591    *   The icon name, minus the "glyphicon-" prefix.
592    * @param array $default
593    *   (Optional) The default render array to use if $name is not available.
594    *
595    * @return array
596    *   The render containing the icon defined by $name, $default value if
597    *   icon does not exist or returns NULL if no icon could be rendered.
598    */
599   public static function glyphicon($name, array $default = []) {
600     $icon = [];
601
602     // Ensure the icon specified is a valid Bootstrap Glyphicon.
603     // @todo Supply a specific version to _bootstrap_glyphicons() when Icon API
604     // supports versioning.
605     if (self::getTheme()->hasGlyphicons() && in_array($name, self::glyphicons())) {
606       // Attempt to use the Icon API module, if enabled and it generates output.
607       if (\Drupal::moduleHandler()->moduleExists('icon')) {
608         $icon = [
609           '#type' => 'icon',
610           '#bundle' => 'bootstrap',
611           '#icon' => 'glyphicon-' . $name,
612         ];
613       }
614       else {
615         $icon = [
616           '#type' => 'html_tag',
617           '#tag' => 'span',
618           '#value' => '',
619           '#attributes' => [
620             'class' => ['icon', 'glyphicon', 'glyphicon-' . $name],
621             'aria-hidden' => 'true',
622           ],
623         ];
624       }
625     }
626
627     return $icon ?: $default;
628   }
629
630   /**
631    * Matches a Bootstrap Glyphicon based on a string value.
632    *
633    * @param string $value
634    *   The string to match against to determine the icon. Passed by reference
635    *   in case it is a render array that needs to be rendered and typecast.
636    * @param array $default
637    *   The default render array to return if no match is found.
638    *
639    * @return array
640    *   The Bootstrap icon matched against the value of $haystack or $default if
641    *   no match could be made.
642    */
643   public static function glyphiconFromString(&$value, array $default = []) {
644     static $lang;
645     if (!isset($lang)) {
646       $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
647     }
648
649     $theme = static::getTheme();
650     $texts = $theme->getCache('glyphiconFromString', [$lang]);
651
652     // Ensure it's a string value that was passed.
653     $string = static::toString($value);
654
655     if ($texts->isEmpty()) {
656       $data = [
657         // Text that match these specific strings are checked first.
658         'matches' => [],
659
660         // Text containing these words anywhere in the string are checked last.
661         'contains' => [
662           t('Manage')->render()     => 'cog',
663           t('Configure')->render()  => 'cog',
664           t('Settings')->render()   => 'cog',
665           t('Download')->render()   => 'download',
666           t('Export')->render()     => 'export',
667           t('Filter')->render()     => 'filter',
668           t('Import')->render()     => 'import',
669           t('Save')->render()       => 'ok',
670           t('Update')->render()     => 'ok',
671           t('Edit')->render()       => 'pencil',
672           t('Uninstall')->render()  => 'trash',
673           t('Install')->render()    => 'plus',
674           t('Write')->render()      => 'plus',
675           t('Cancel')->render()     => 'remove',
676           t('Delete')->render()     => 'trash',
677           t('Remove')->render()     => 'trash',
678           t('Search')->render()     => 'search',
679           t('Upload')->render()     => 'upload',
680           t('Preview')->render()    => 'eye-open',
681           t('Log in')->render()     => 'log-in',
682         ],
683       ];
684
685       // Allow sub-themes to alter this array of patterns.
686       /** @var \Drupal\Core\Theme\ThemeManager $theme_manager */
687       $theme_manager = \Drupal::service('theme.manager');
688       $theme_manager->alter('bootstrap_iconize_text', $data);
689
690       $texts->setMultiple($data);
691     }
692
693     // Iterate over the array.
694     foreach ($texts as $pattern => $strings) {
695       foreach ($strings as $text => $icon) {
696         switch ($pattern) {
697           case 'matches':
698             if ($string === $text) {
699               return self::glyphicon($icon, $default);
700             }
701             break;
702
703           case 'contains':
704             if (strpos(Unicode::strtolower($string), Unicode::strtolower($text)) !== FALSE) {
705               return self::glyphicon($icon, $default);
706             }
707             break;
708         }
709       }
710     }
711
712     // Return a default icon if nothing was matched.
713     return $default;
714   }
715
716   /**
717    * Returns a list of available Bootstrap Framework Glyphicons.
718    *
719    * @param string $version
720    *   The specific version of glyphicons to return. If not set, the latest
721    *   BOOTSTRAP_VERSION will be used.
722    *
723    * @return array
724    *   An associative array of icons keyed by their classes.
725    */
726   public static function glyphicons($version = NULL) {
727     static $versions;
728     if (!isset($versions)) {
729       $versions = [];
730       $versions['3.0.0'] = [
731         // Class => Name.
732         'glyphicon-adjust' => 'adjust',
733         'glyphicon-align-center' => 'align-center',
734         'glyphicon-align-justify' => 'align-justify',
735         'glyphicon-align-left' => 'align-left',
736         'glyphicon-align-right' => 'align-right',
737         'glyphicon-arrow-down' => 'arrow-down',
738         'glyphicon-arrow-left' => 'arrow-left',
739         'glyphicon-arrow-right' => 'arrow-right',
740         'glyphicon-arrow-up' => 'arrow-up',
741         'glyphicon-asterisk' => 'asterisk',
742         'glyphicon-backward' => 'backward',
743         'glyphicon-ban-circle' => 'ban-circle',
744         'glyphicon-barcode' => 'barcode',
745         'glyphicon-bell' => 'bell',
746         'glyphicon-bold' => 'bold',
747         'glyphicon-book' => 'book',
748         'glyphicon-bookmark' => 'bookmark',
749         'glyphicon-briefcase' => 'briefcase',
750         'glyphicon-bullhorn' => 'bullhorn',
751         'glyphicon-calendar' => 'calendar',
752         'glyphicon-camera' => 'camera',
753         'glyphicon-certificate' => 'certificate',
754         'glyphicon-check' => 'check',
755         'glyphicon-chevron-down' => 'chevron-down',
756         'glyphicon-chevron-left' => 'chevron-left',
757         'glyphicon-chevron-right' => 'chevron-right',
758         'glyphicon-chevron-up' => 'chevron-up',
759         'glyphicon-circle-arrow-down' => 'circle-arrow-down',
760         'glyphicon-circle-arrow-left' => 'circle-arrow-left',
761         'glyphicon-circle-arrow-right' => 'circle-arrow-right',
762         'glyphicon-circle-arrow-up' => 'circle-arrow-up',
763         'glyphicon-cloud' => 'cloud',
764         'glyphicon-cloud-download' => 'cloud-download',
765         'glyphicon-cloud-upload' => 'cloud-upload',
766         'glyphicon-cog' => 'cog',
767         'glyphicon-collapse-down' => 'collapse-down',
768         'glyphicon-collapse-up' => 'collapse-up',
769         'glyphicon-comment' => 'comment',
770         'glyphicon-compressed' => 'compressed',
771         'glyphicon-copyright-mark' => 'copyright-mark',
772         'glyphicon-credit-card' => 'credit-card',
773         'glyphicon-cutlery' => 'cutlery',
774         'glyphicon-dashboard' => 'dashboard',
775         'glyphicon-download' => 'download',
776         'glyphicon-download-alt' => 'download-alt',
777         'glyphicon-earphone' => 'earphone',
778         'glyphicon-edit' => 'edit',
779         'glyphicon-eject' => 'eject',
780         'glyphicon-envelope' => 'envelope',
781         'glyphicon-euro' => 'euro',
782         'glyphicon-exclamation-sign' => 'exclamation-sign',
783         'glyphicon-expand' => 'expand',
784         'glyphicon-export' => 'export',
785         'glyphicon-eye-close' => 'eye-close',
786         'glyphicon-eye-open' => 'eye-open',
787         'glyphicon-facetime-video' => 'facetime-video',
788         'glyphicon-fast-backward' => 'fast-backward',
789         'glyphicon-fast-forward' => 'fast-forward',
790         'glyphicon-file' => 'file',
791         'glyphicon-film' => 'film',
792         'glyphicon-filter' => 'filter',
793         'glyphicon-fire' => 'fire',
794         'glyphicon-flag' => 'flag',
795         'glyphicon-flash' => 'flash',
796         'glyphicon-floppy-disk' => 'floppy-disk',
797         'glyphicon-floppy-open' => 'floppy-open',
798         'glyphicon-floppy-remove' => 'floppy-remove',
799         'glyphicon-floppy-save' => 'floppy-save',
800         'glyphicon-floppy-saved' => 'floppy-saved',
801         'glyphicon-folder-close' => 'folder-close',
802         'glyphicon-folder-open' => 'folder-open',
803         'glyphicon-font' => 'font',
804         'glyphicon-forward' => 'forward',
805         'glyphicon-fullscreen' => 'fullscreen',
806         'glyphicon-gbp' => 'gbp',
807         'glyphicon-gift' => 'gift',
808         'glyphicon-glass' => 'glass',
809         'glyphicon-globe' => 'globe',
810         'glyphicon-hand-down' => 'hand-down',
811         'glyphicon-hand-left' => 'hand-left',
812         'glyphicon-hand-right' => 'hand-right',
813         'glyphicon-hand-up' => 'hand-up',
814         'glyphicon-hd-video' => 'hd-video',
815         'glyphicon-hdd' => 'hdd',
816         'glyphicon-header' => 'header',
817         'glyphicon-headphones' => 'headphones',
818         'glyphicon-heart' => 'heart',
819         'glyphicon-heart-empty' => 'heart-empty',
820         'glyphicon-home' => 'home',
821         'glyphicon-import' => 'import',
822         'glyphicon-inbox' => 'inbox',
823         'glyphicon-indent-left' => 'indent-left',
824         'glyphicon-indent-right' => 'indent-right',
825         'glyphicon-info-sign' => 'info-sign',
826         'glyphicon-italic' => 'italic',
827         'glyphicon-leaf' => 'leaf',
828         'glyphicon-link' => 'link',
829         'glyphicon-list' => 'list',
830         'glyphicon-list-alt' => 'list-alt',
831         'glyphicon-lock' => 'lock',
832         'glyphicon-log-in' => 'log-in',
833         'glyphicon-log-out' => 'log-out',
834         'glyphicon-magnet' => 'magnet',
835         'glyphicon-map-marker' => 'map-marker',
836         'glyphicon-minus' => 'minus',
837         'glyphicon-minus-sign' => 'minus-sign',
838         'glyphicon-move' => 'move',
839         'glyphicon-music' => 'music',
840         'glyphicon-new-window' => 'new-window',
841         'glyphicon-off' => 'off',
842         'glyphicon-ok' => 'ok',
843         'glyphicon-ok-circle' => 'ok-circle',
844         'glyphicon-ok-sign' => 'ok-sign',
845         'glyphicon-open' => 'open',
846         'glyphicon-paperclip' => 'paperclip',
847         'glyphicon-pause' => 'pause',
848         'glyphicon-pencil' => 'pencil',
849         'glyphicon-phone' => 'phone',
850         'glyphicon-phone-alt' => 'phone-alt',
851         'glyphicon-picture' => 'picture',
852         'glyphicon-plane' => 'plane',
853         'glyphicon-play' => 'play',
854         'glyphicon-play-circle' => 'play-circle',
855         'glyphicon-plus' => 'plus',
856         'glyphicon-plus-sign' => 'plus-sign',
857         'glyphicon-print' => 'print',
858         'glyphicon-pushpin' => 'pushpin',
859         'glyphicon-qrcode' => 'qrcode',
860         'glyphicon-question-sign' => 'question-sign',
861         'glyphicon-random' => 'random',
862         'glyphicon-record' => 'record',
863         'glyphicon-refresh' => 'refresh',
864         'glyphicon-registration-mark' => 'registration-mark',
865         'glyphicon-remove' => 'remove',
866         'glyphicon-remove-circle' => 'remove-circle',
867         'glyphicon-remove-sign' => 'remove-sign',
868         'glyphicon-repeat' => 'repeat',
869         'glyphicon-resize-full' => 'resize-full',
870         'glyphicon-resize-horizontal' => 'resize-horizontal',
871         'glyphicon-resize-small' => 'resize-small',
872         'glyphicon-resize-vertical' => 'resize-vertical',
873         'glyphicon-retweet' => 'retweet',
874         'glyphicon-road' => 'road',
875         'glyphicon-save' => 'save',
876         'glyphicon-saved' => 'saved',
877         'glyphicon-screenshot' => 'screenshot',
878         'glyphicon-sd-video' => 'sd-video',
879         'glyphicon-search' => 'search',
880         'glyphicon-send' => 'send',
881         'glyphicon-share' => 'share',
882         'glyphicon-share-alt' => 'share-alt',
883         'glyphicon-shopping-cart' => 'shopping-cart',
884         'glyphicon-signal' => 'signal',
885         'glyphicon-sort' => 'sort',
886         'glyphicon-sort-by-alphabet' => 'sort-by-alphabet',
887         'glyphicon-sort-by-alphabet-alt' => 'sort-by-alphabet-alt',
888         'glyphicon-sort-by-attributes' => 'sort-by-attributes',
889         'glyphicon-sort-by-attributes-alt' => 'sort-by-attributes-alt',
890         'glyphicon-sort-by-order' => 'sort-by-order',
891         'glyphicon-sort-by-order-alt' => 'sort-by-order-alt',
892         'glyphicon-sound-5-1' => 'sound-5-1',
893         'glyphicon-sound-6-1' => 'sound-6-1',
894         'glyphicon-sound-7-1' => 'sound-7-1',
895         'glyphicon-sound-dolby' => 'sound-dolby',
896         'glyphicon-sound-stereo' => 'sound-stereo',
897         'glyphicon-star' => 'star',
898         'glyphicon-star-empty' => 'star-empty',
899         'glyphicon-stats' => 'stats',
900         'glyphicon-step-backward' => 'step-backward',
901         'glyphicon-step-forward' => 'step-forward',
902         'glyphicon-stop' => 'stop',
903         'glyphicon-subtitles' => 'subtitles',
904         'glyphicon-tag' => 'tag',
905         'glyphicon-tags' => 'tags',
906         'glyphicon-tasks' => 'tasks',
907         'glyphicon-text-height' => 'text-height',
908         'glyphicon-text-width' => 'text-width',
909         'glyphicon-th' => 'th',
910         'glyphicon-th-large' => 'th-large',
911         'glyphicon-th-list' => 'th-list',
912         'glyphicon-thumbs-down' => 'thumbs-down',
913         'glyphicon-thumbs-up' => 'thumbs-up',
914         'glyphicon-time' => 'time',
915         'glyphicon-tint' => 'tint',
916         'glyphicon-tower' => 'tower',
917         'glyphicon-transfer' => 'transfer',
918         'glyphicon-trash' => 'trash',
919         'glyphicon-tree-conifer' => 'tree-conifer',
920         'glyphicon-tree-deciduous' => 'tree-deciduous',
921         'glyphicon-unchecked' => 'unchecked',
922         'glyphicon-upload' => 'upload',
923         'glyphicon-usd' => 'usd',
924         'glyphicon-user' => 'user',
925         'glyphicon-volume-down' => 'volume-down',
926         'glyphicon-volume-off' => 'volume-off',
927         'glyphicon-volume-up' => 'volume-up',
928         'glyphicon-warning-sign' => 'warning-sign',
929         'glyphicon-wrench' => 'wrench',
930         'glyphicon-zoom-in' => 'zoom-in',
931         'glyphicon-zoom-out' => 'zoom-out',
932       ];
933       $versions['3.0.1'] = $versions['3.0.0'];
934       $versions['3.0.2'] = $versions['3.0.1'];
935       $versions['3.0.3'] = $versions['3.0.2'];
936       $versions['3.1.0'] = $versions['3.0.3'];
937       $versions['3.1.1'] = $versions['3.1.0'];
938       $versions['3.2.0'] = $versions['3.1.1'];
939       $versions['3.3.0'] = array_merge($versions['3.2.0'], [
940         'glyphicon-eur' => 'eur',
941       ]);
942       $versions['3.3.1'] = $versions['3.3.0'];
943       $versions['3.3.2'] = array_merge($versions['3.3.1'], [
944         'glyphicon-alert' => 'alert',
945         'glyphicon-apple' => 'apple',
946         'glyphicon-baby-formula' => 'baby-formula',
947         'glyphicon-bed' => 'bed',
948         'glyphicon-bishop' => 'bishop',
949         'glyphicon-bitcoin' => 'bitcoin',
950         'glyphicon-blackboard' => 'blackboard',
951         'glyphicon-cd' => 'cd',
952         'glyphicon-console' => 'console',
953         'glyphicon-copy' => 'copy',
954         'glyphicon-duplicate' => 'duplicate',
955         'glyphicon-education' => 'education',
956         'glyphicon-equalizer' => 'equalizer',
957         'glyphicon-erase' => 'erase',
958         'glyphicon-grain' => 'grain',
959         'glyphicon-hourglass' => 'hourglass',
960         'glyphicon-ice-lolly' => 'ice-lolly',
961         'glyphicon-ice-lolly-tasted' => 'ice-lolly-tasted',
962         'glyphicon-king' => 'king',
963         'glyphicon-knight' => 'knight',
964         'glyphicon-lamp' => 'lamp',
965         'glyphicon-level-up' => 'level-up',
966         'glyphicon-menu-down' => 'menu-down',
967         'glyphicon-menu-hamburger' => 'menu-hamburger',
968         'glyphicon-menu-left' => 'menu-left',
969         'glyphicon-menu-right' => 'menu-right',
970         'glyphicon-menu-up' => 'menu-up',
971         'glyphicon-modal-window' => 'modal-window',
972         'glyphicon-object-align-bottom' => 'object-align-bottom',
973         'glyphicon-object-align-horizontal' => 'object-align-horizontal',
974         'glyphicon-object-align-left' => 'object-align-left',
975         'glyphicon-object-align-right' => 'object-align-right',
976         'glyphicon-object-align-top' => 'object-align-top',
977         'glyphicon-object-align-vertical' => 'object-align-vertical',
978         'glyphicon-oil' => 'oil',
979         'glyphicon-open-file' => 'open-file',
980         'glyphicon-option-horizontal' => 'option-horizontal',
981         'glyphicon-option-vertical' => 'option-vertical',
982         'glyphicon-paste' => 'paste',
983         'glyphicon-pawn' => 'pawn',
984         'glyphicon-piggy-bank' => 'piggy-bank',
985         'glyphicon-queen' => 'queen',
986         'glyphicon-ruble' => 'ruble',
987         'glyphicon-save-file' => 'save-file',
988         'glyphicon-scale' => 'scale',
989         'glyphicon-scissors' => 'scissors',
990         'glyphicon-subscript' => 'subscript',
991         'glyphicon-sunglasses' => 'sunglasses',
992         'glyphicon-superscript' => 'superscript',
993         'glyphicon-tent' => 'tent',
994         'glyphicon-text-background' => 'text-background',
995         'glyphicon-text-color' => 'text-color',
996         'glyphicon-text-size' => 'text-size',
997         'glyphicon-triangle-bottom' => 'triangle-bottom',
998         'glyphicon-triangle-left' => 'triangle-left',
999         'glyphicon-triangle-right' => 'triangle-right',
1000         'glyphicon-triangle-top' => 'triangle-top',
1001         'glyphicon-yen' => 'yen',
1002       ]);
1003       $versions['3.3.4'] = array_merge($versions['3.3.2'], [
1004         'glyphicon-btc' => 'btc',
1005         'glyphicon-jpy' => 'jpy',
1006         'glyphicon-rub' => 'rub',
1007         'glyphicon-xbt' => 'xbt',
1008       ]);
1009       $versions['3.3.5'] = $versions['3.3.4'];
1010       $versions['3.3.6'] = $versions['3.3.5'];
1011       $versions['3.3.7'] = $versions['3.3.6'];
1012     }
1013
1014     // Return a specific versions icon set.
1015     if (isset($version) && isset($versions[$version])) {
1016       return $versions[$version];
1017     }
1018
1019     // Return the latest version.
1020     return $versions[self::FRAMEWORK_VERSION];
1021   }
1022
1023   /**
1024    * Determines if the "cache_context.url.path.is_front" service exists.
1025    *
1026    * @return bool
1027    *   TRUE or FALSE
1028    *
1029    * @see \Drupal\bootstrap\Bootstrap::isFront
1030    * @see \Drupal\bootstrap\Bootstrap::preprocess
1031    * @see https://www.drupal.org/node/2829588
1032    */
1033   public static function hasIsFrontCacheContext() {
1034     static $has_is_front_cache_context;
1035     if (!isset($has_is_front_cache_context)) {
1036       $has_is_front_cache_context = \Drupal::getContainer()->has('cache_context.url.path.is_front');
1037     }
1038     return $has_is_front_cache_context;
1039   }
1040
1041   /**
1042    * Initializes the active theme.
1043    */
1044   final public static function initialize() {
1045     static $initialized = FALSE;
1046     if (!$initialized) {
1047       // Initialize the active theme.
1048       $active_theme = self::getTheme();
1049
1050       // Include deprecated functions.
1051       foreach ($active_theme->getAncestry() as $ancestor) {
1052         if ($ancestor->getSetting('include_deprecated')) {
1053           $files = $ancestor->fileScan('/^deprecated\.php$/');
1054           if ($file = reset($files)) {
1055             $ancestor->includeOnce($file->uri, FALSE);
1056           }
1057         }
1058       }
1059
1060       $initialized = TRUE;
1061     }
1062   }
1063
1064   /**
1065    * Determines if the current path is the "front" page.
1066    *
1067    * *Note:* This method will not return `TRUE` if there is not a proper
1068    * "cache_context.url.path.is_front" service defined.
1069    *
1070    * *Note:* If using this method in preprocess/render array logic, the proper
1071    * #cache context must also be defined:
1072    *
1073    * ```php
1074    * $variables['#cache']['contexts'][] = 'url.path.is_front';
1075    * ```
1076    *
1077    * @return bool
1078    *   TRUE or FALSE
1079    *
1080    * @see \Drupal\bootstrap\Bootstrap::hasIsFrontCacheContext
1081    * @see \Drupal\bootstrap\Bootstrap::preprocess
1082    * @see https://www.drupal.org/node/2829588
1083    */
1084   public static function isFront() {
1085     static $is_front;
1086     if (!isset($is_front)) {
1087       try {
1088         $is_front = static::hasIsFrontCacheContext() ? \Drupal::service('path.matcher')->isFrontPage() : FALSE;
1089       }
1090       catch (\Exception $e) {
1091         $is_front = FALSE;
1092       }
1093     }
1094     return $is_front;
1095   }
1096
1097   /**
1098    * Preprocess theme hook variables.
1099    *
1100    * @param array $variables
1101    *   The variables array, passed by reference.
1102    * @param string $hook
1103    *   The name of the theme hook.
1104    * @param array $info
1105    *   The theme hook info.
1106    */
1107   public static function preprocess(array &$variables, $hook, array $info) {
1108     // Do not statically cache this as the active theme may change.
1109     $theme = static::getTheme();
1110     $theme_name = $theme->getName();
1111
1112     // Handle preprocess managers.
1113     static $drupal_static_fast;
1114     if (!isset($drupal_static_fast)) {
1115       $drupal_static_fast['preprocess_managers'] = &drupal_static(__METHOD__ . '__preprocessManagers', []);
1116       $drupal_static_fast['theme_info'] = &drupal_static(__METHOD__ . '__themeInfo', []);
1117     }
1118
1119     /* @var \Drupal\bootstrap\Plugin\PreprocessManager[] $preprocess_managers */
1120     $preprocess_managers = &$drupal_static_fast['preprocess_managers'];
1121     if (!isset($preprocess_managers[$theme_name])) {
1122       $preprocess_managers[$theme_name] = new PreprocessManager($theme);
1123     }
1124
1125     // Retrieve the theme info that will be used in the variables.
1126     $theme_info = &$drupal_static_fast['theme_info'];
1127     if (!isset($theme_info[$theme_name])) {
1128       $theme_info[$theme_name] = $theme->getInfo();
1129       $theme_info[$theme_name]['dev'] = $theme->isDev();
1130       $theme_info[$theme_name]['livereload'] = $theme->livereloadUrl();
1131       $theme_info[$theme_name]['name'] = $theme->getName();
1132       $theme_info[$theme_name]['path'] = $theme->getPath();
1133       $theme_info[$theme_name]['title'] = $theme->getTitle();
1134       $theme_info[$theme_name]['settings'] = $theme->settings()->get();
1135       $theme_info[$theme_name]['has_glyphicons'] = $theme->hasGlyphicons();
1136       $theme_info[$theme_name]['query_string'] = \Drupal::getContainer()->get('state')->get('system.css_js_query_string') ?: '0';
1137     }
1138
1139     // Retrieve the preprocess manager for this theme.
1140     $preprocess_manager = $preprocess_managers[$theme_name];
1141
1142     // Adds a global "is_front" variable back to all templates.
1143     // @see https://www.drupal.org/node/2829585
1144     if (!isset($variables['is_front'])) {
1145       $variables['is_front'] = static::isFront();
1146       if (static::hasIsFrontCacheContext()) {
1147         $variables['#cache']['contexts'][] = 'url.path.is_front';
1148       }
1149     }
1150
1151     // Ensure that any default theme hook variables exist. Due to how theme
1152     // hook suggestion alters work, the variables provided are from the
1153     // original theme hook, not the suggestion.
1154     if (isset($info['variables'])) {
1155       $variables = NestedArray::mergeDeepArray([$info['variables'], $variables], TRUE);
1156     }
1157
1158     // Add active theme context.
1159     // @see https://www.drupal.org/node/2630870
1160     if (!isset($variables['theme'])) {
1161       $variables['theme'] = $theme_info[$theme_name];
1162     }
1163
1164     // Invoke necessary preprocess plugin.
1165     if (isset($info['bootstrap preprocess'])) {
1166       if ($preprocess_manager->hasDefinition($info['bootstrap preprocess'])) {
1167         $class = $preprocess_manager->createInstance($info['bootstrap preprocess'], ['theme' => $theme]);
1168         /** @var \Drupal\bootstrap\Plugin\Preprocess\PreprocessInterface $class */
1169         $class->preprocess($variables, $hook, $info);
1170       }
1171     }
1172   }
1173
1174   /**
1175    * Ensures a value is typecast to a string, rendering an array if necessary.
1176    *
1177    * @param string|array $value
1178    *   The value to typecast, passed by reference.
1179    *
1180    * @return string
1181    *   The typecast string value.
1182    */
1183   public static function toString(&$value) {
1184     return (string) (Element::isRenderArray($value) ? Element::create($value)->renderPlain() : $value);
1185   }
1186
1187 }