3 namespace Drupal\bootstrap;
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;
16 * The primary class for the Drupal Bootstrap base theme.
18 * Provides many helper methods.
25 * Tag used to invalidate caches.
29 const CACHE_TAG = 'theme_registry';
36 const CALLBACK_APPEND = 1;
43 const CALLBACK_PREPEND = 2;
46 * Replace a callback or append it if not found.
50 const CALLBACK_REPLACE_APPEND = 3;
53 * Replace a callback or prepend it if not found.
57 const CALLBACK_REPLACE_PREPEND = 4;
60 * The current supported Bootstrap Framework version.
64 const FRAMEWORK_VERSION = '3.3.7';
67 * The Bootstrap Framework documentation site.
71 const FRAMEWORK_HOMEPAGE = 'https://getbootstrap.com/docs/3.3/';
74 * The Bootstrap Framework repository.
78 const FRAMEWORK_REPOSITORY = 'https://github.com/twbs/bootstrap';
85 const PROJECT_BRANCH = '8.x-3.x';
88 * The Drupal Bootstrap documentation site.
92 const PROJECT_DOCUMENTATION = 'https://drupal-bootstrap.org';
95 * The Drupal Bootstrap project page.
99 const PROJECT_PAGE = 'https://www.drupal.org/project/bootstrap';
102 * Adds a callback to an array.
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.
112 * Flag that determines how to add the callback to the array.
115 * TRUE if the callback was added, FALSE if $replace was specified but its
116 * callback could be found in the list of callbacks.
118 public static function addCallback(array &$callbacks, $callback, $replace = NULL, $action = Bootstrap::CALLBACK_APPEND) {
119 // Replace a callback.
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;
129 // No match found and action shouldn't append or prepend.
130 if ($action !== self::CALLBACK_REPLACE_APPEND || $action !== self::CALLBACK_REPLACE_PREPEND) {
135 // Append or prepend the callback.
137 case self::CALLBACK_APPEND:
138 case self::CALLBACK_REPLACE_APPEND:
139 $callbacks[] = $callback;
142 case self::CALLBACK_PREPEND:
143 case self::CALLBACK_REPLACE_PREPEND:
144 array_unshift($callbacks, $callback);
153 * Manages theme alter hooks as classes and allows sub-themes to sub-class.
155 * @param string $function
156 * The procedural function name of the alter (e.g. __FUNCTION__).
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.
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();
174 // Immediately return if the active theme is not Bootstrap based.
175 if (!$theme->isBootstrap()) {
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', []);
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);
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);
198 // Retrieve the alter and form managers for this theme.
199 $alter_manager = $alter_managers[$theme_name];
200 $form_manager = $form_managers[$theme_name];
202 // Extract the alter hook name.
203 $hook = Unicode::extractHook($function, 'alter');
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;
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') {
218 // Keep track of the form identifiers.
221 // Get the build data.
222 $build_info = $form_state->getBuildInfo();
224 // Extract the base_form_id.
225 $base_form_id = !empty($build_info['base_form_id']) ? $build_info['base_form_id'] : FALSE;
227 $ids[] = $base_form_id;
230 // If there was no provided form identifier, extract it.
232 $form_id = !empty($build_info['form_id']) ? $build_info['form_id'] : Unicode::extractHook($function, 'alter', 'form');
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);
248 // Process hook alter normally.
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);
258 * Returns a documentation search URL for a given query.
260 * @param string $query
261 * The query to search for.
264 * The complete URL to the documentation site.
266 public static function apiSearchUrl($query = '') {
267 return self::PROJECT_DOCUMENTATION . '/api/bootstrap/' . self::PROJECT_BRANCH . '/search/' . Html::escape($query);
271 * Returns the autoload fix include path.
273 * This method assists class based callbacks that normally do not work.
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.
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
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
288 * When this file is included, it will attempt to jump start this process.
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.
293 * @see \Drupal\Core\Extension\ThemeHandler::listInfo
294 * @see \Drupal\Core\Extension\ThemeHandler::systemThemeList
296 * @see system_register()
297 * @see drupal_classloader_register()
300 * The autoload fix include path, relative to Drupal root.
302 public static function autoloadFixInclude() {
303 return static::getTheme('bootstrap')->getPath() . '/autoload-fix.php';
307 * Matches a Bootstrap class based on a string value.
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.
316 * The Bootstrap class matched against the value of $haystack or $default
317 * if no match could be made.
319 public static function cssClassFromString(&$value, $default = '') {
322 $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
325 $theme = static::getTheme();
326 $texts = $theme->getCache('cssClassFromString', [$lang]);
328 // Ensure it's a string value that was passed.
329 $string = static::toString($value);
331 if ($texts->isEmpty()) {
333 // Text that match these specific strings are checked first.
336 t('Download feature')->render() => 'primary',
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',
345 t('Save and add')->render() => 'info',
346 t('Add another item')->render() => 'info',
347 t('Update style')->render() => 'info',
350 // Text containing these words anywhere in the string are checked last.
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',
362 t('Delete')->render() => 'danger',
363 t('Remove')->render() => 'danger',
364 t('Uninstall')->render() => 'danger',
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',
374 t('Export')->render() => 'warning',
375 t('Import')->render() => 'warning',
376 t('Restore')->render() => 'warning',
377 t('Rebuild')->render() => 'warning',
380 t('Apply')->render() => 'info',
381 t('Update')->render() => 'info',
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);
390 $texts->setMultiple($data);
393 // Iterate over the array.
394 foreach ($texts as $pattern => $strings) {
395 foreach ($strings as $text => $class) {
398 if ($string === $text) {
404 if (strpos(Unicode::strtolower($string), Unicode::strtolower($text)) !== FALSE) {
412 // Return the default if nothing was matched.
417 * Logs and displays a warning about a deprecated function/method being used.
419 public static function deprecated() {
421 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
422 \Drupal::logger('bootstrap')->warning('<pre><code>' . print_r($backtrace, TRUE) . '</code></pre>');
424 if (!self::getTheme()->getSetting('suppress_deprecated_warnings')) {
428 // Extrapolate the caller.
429 $caller = $backtrace[1];
431 if (isset($caller['class'])) {
432 $parts = explode('\\', $caller['class']);
433 $class = array_pop($parts) . '::';
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'] . '()',
443 * Provides additional variables to be used in elements and templates.
446 * An associative array containing key/default value pairs.
448 public static function extraVariables() {
450 // @see https://www.drupal.org/node/2035055
453 // @see https://www.drupal.org/node/2219965
455 'icon_position' => 'before',
456 'icon_only' => FALSE,
461 * Retrieves a theme instance of \Drupal\bootstrap.
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.
468 * @return \Drupal\bootstrap\Theme
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) {
479 // Retrieve the active theme.
480 // Do not statically cache this as the active theme may change.
482 $name = \Drupal::theme()->getActiveTheme()->getName();
485 if (!isset($theme_handler)) {
486 $theme_handler = self::getThemeHandler();
489 if (!isset($themes[$name])) {
490 $themes[$name] = new Theme($theme_handler->getTheme($name), $theme_handler);
493 return $themes[$name];
497 * Retrieves the theme handler instance.
499 * @return \Drupal\Core\Extension\ThemeHandlerInterface
500 * The theme handler instance.
502 public static function getThemeHandler() {
503 static $theme_handler;
504 if (!isset($theme_handler)) {
505 $theme_handler = \Drupal::service('theme_handler');
507 return $theme_handler;
511 * Returns the theme hook definition information.
513 * This base-theme's custom theme hook implementations. Never define "path"
514 * as this is automatically detected and added.
516 * @see \Drupal\bootstrap\Plugin\Alter\ThemeRegistry::alter()
517 * @see bootstrap_theme_registry_alter()
518 * @see bootstrap_theme()
521 public static function getThemeHooks() {
522 $hooks['bootstrap_carousel'] = [
527 'indicators' => TRUE,
536 $hooks['bootstrap_dropdown'] = [
538 'alignment' => 'down',
546 $hooks['bootstrap_modal'] = [
550 'body_attributes' => [],
551 'close_button' => TRUE,
552 'content_attributes' => [],
553 'description' => NULL,
554 'description_display' => 'before',
555 'dialog_attributes' => [],
557 'footer_attributes' => [],
558 'header_attributes' => [],
562 'title_attributes' => [],
566 $hooks['bootstrap_panel'] = [
570 'body_attributes' => [],
571 'collapsible' => FALSE,
572 'collapsed' => FALSE,
573 'description' => NULL,
574 'description_display' => 'before',
576 'footer_attributes' => [],
578 'heading_attributes' => [],
580 'panel_type' => 'default',
588 * Returns a specific Bootstrap Glyphicon.
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.
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.
599 public static function glyphicon($name, array $default = []) {
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')) {
610 '#bundle' => 'bootstrap',
611 '#icon' => 'glyphicon-' . $name,
616 '#type' => 'html_tag',
620 'class' => ['icon', 'glyphicon', 'glyphicon-' . $name],
621 'aria-hidden' => 'true',
627 return $icon ?: $default;
631 * Matches a Bootstrap Glyphicon based on a string value.
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.
640 * The Bootstrap icon matched against the value of $haystack or $default if
641 * no match could be made.
643 public static function glyphiconFromString(&$value, array $default = []) {
646 $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
649 $theme = static::getTheme();
650 $texts = $theme->getCache('glyphiconFromString', [$lang]);
652 // Ensure it's a string value that was passed.
653 $string = static::toString($value);
655 if ($texts->isEmpty()) {
657 // Text that match these specific strings are checked first.
660 // Text containing these words anywhere in the string are checked last.
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',
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);
690 $texts->setMultiple($data);
693 // Iterate over the array.
694 foreach ($texts as $pattern => $strings) {
695 foreach ($strings as $text => $icon) {
698 if ($string === $text) {
699 return self::glyphicon($icon, $default);
704 if (strpos(Unicode::strtolower($string), Unicode::strtolower($text)) !== FALSE) {
705 return self::glyphicon($icon, $default);
712 // Return a default icon if nothing was matched.
717 * Returns a list of available Bootstrap Framework Glyphicons.
719 * @param string $version
720 * The specific version of glyphicons to return. If not set, the latest
721 * BOOTSTRAP_VERSION will be used.
724 * An associative array of icons keyed by their classes.
726 public static function glyphicons($version = NULL) {
728 if (!isset($versions)) {
730 $versions['3.0.0'] = [
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',
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',
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',
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',
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'];
1014 // Return a specific versions icon set.
1015 if (isset($version) && isset($versions[$version])) {
1016 return $versions[$version];
1019 // Return the latest version.
1020 return $versions[self::FRAMEWORK_VERSION];
1024 * Determines if the "cache_context.url.path.is_front" service exists.
1029 * @see \Drupal\bootstrap\Bootstrap::isFront
1030 * @see \Drupal\bootstrap\Bootstrap::preprocess
1031 * @see https://www.drupal.org/node/2829588
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');
1038 return $has_is_front_cache_context;
1042 * Initializes the active theme.
1044 final public static function initialize() {
1045 static $initialized = FALSE;
1046 if (!$initialized) {
1047 // Initialize the active theme.
1048 $active_theme = self::getTheme();
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);
1060 $initialized = TRUE;
1065 * Determines if the current path is the "front" page.
1067 * *Note:* This method will not return `TRUE` if there is not a proper
1068 * "cache_context.url.path.is_front" service defined.
1070 * *Note:* If using this method in preprocess/render array logic, the proper
1071 * #cache context must also be defined:
1074 * $variables['#cache']['contexts'][] = 'url.path.is_front';
1080 * @see \Drupal\bootstrap\Bootstrap::hasIsFrontCacheContext
1081 * @see \Drupal\bootstrap\Bootstrap::preprocess
1082 * @see https://www.drupal.org/node/2829588
1084 public static function isFront() {
1086 if (!isset($is_front)) {
1088 $is_front = static::hasIsFrontCacheContext() ? \Drupal::service('path.matcher')->isFrontPage() : FALSE;
1090 catch (\Exception $e) {
1098 * Preprocess theme hook variables.
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.
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();
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', []);
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);
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';
1139 // Retrieve the preprocess manager for this theme.
1140 $preprocess_manager = $preprocess_managers[$theme_name];
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';
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);
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];
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);
1175 * Ensures a value is typecast to a string, rendering an array if necessary.
1177 * @param string|array $value
1178 * The value to typecast, passed by reference.
1181 * The typecast string value.
1183 public static function toString(&$value) {
1184 return (string) (Element::isRenderArray($value) ? Element::create($value)->renderPlain() : $value);