/** * {@inheritdoc} */ public static function preRenderElement(Element $element) { // Injects the icon into the title (the only way this is possible). if ($icon =& $element->getProperty('icon')) { $title = $element->getProperty('title'); // Handle #icon_only. if ($element->getProperty('icon_only')) { if ($attribute_title = $element->getAttribute('title', '')) { $title .= ' - ' . $attribute_title; } $element->setAttribute('title', $title)->addClass('icon-only')->setProperty('title', $icon); if (Bootstrap::getTheme()->getSetting('tooltip_enabled')) { $element->setAttribute('data-toggle', 'tooltip'); } return; } // Handle #icon_position. $position = $element->getProperty('icon_position', 'before'); // Render #icon and trim it (so it doesn't add underlines in whitespace). $rendered_icon = trim(Element::create($icon)->render()); // Default position is before. $markup = "{$rendered_icon}@title"; if ($position === 'after') { $markup = "@title{$rendered_icon}"; } // Replace the title and set an icon position class. $element->setProperty('title', new FormattableMarkup($markup, ['@title' => $title]))->addClass("icon-{$position}"); } }
/** * Global #process callback for form elements. * * @param array $element * The element render array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The altered element array. * * @see \Drupal\bootstrap\Plugin\Alter\ElementInfo::alter */ public static function process(array $element, FormStateInterface $form_state, array &$complete_form) { if (!empty($element['#bootstrap_ignore_process'])) { return $element; } static $theme; if (!isset($theme)) { $theme = Bootstrap::getTheme(); } $e = Element::create($element, $form_state); // Process AJAX. if ($e->getProperty('ajax') && !$e->isButton() || $e->getProperty('autocomplete_route_name')) { static::processAjax($e, $form_state, $complete_form); } // Add "form-inline" class. if ($e->hasClass('container-inline')) { $e->replaceClass('container-inline', 'form-inline'); } if ($e->isType(['color', 'date', 'number', 'range', 'tel', 'weight'])) { $e->addClass('form-inline', 'wrapper_attributes'); } // Check for errors and set the "has_error" property flag. $errors = $e->getError(); $e->setProperty('errors', $errors); if (isset($errors) || $e->getProperty('required') && $theme->getSetting('forms_required_has_error')) { $e->setProperty('has_error', TRUE); } // Process input groups. if ($e->getProperty('input') && ($e->getProperty('input_group') || $e->getProperty('input_group_button'))) { static::processInputGroups($e, $form_state, $complete_form); } return $element; }
/** * Extracts the hook name from a function name. * * @param string $string * The function name to extract the hook name from. * @param string $suffix * A suffix hook ending (like "alter") to also remove. * @param string $prefix * A prefix hook beginning (like "form") to also remove. * * @return string * The extracted hook name. */ public static function extractHook($string, $suffix = NULL, $prefix = NULL) { $regex = '^(' . implode('|', array_keys(Bootstrap::getTheme()->getAncestry())) . ')'; $regex .= $prefix ? '_' . $prefix : ''; $regex .= $suffix ? '_|_' . $suffix . '$' : ''; return preg_replace("/{$regex}/", '', $string); }
/** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { if (!isset($configuration['theme'])) { $configuration['theme'] = Bootstrap::getTheme(); } $this->theme = $configuration['theme']; parent::__construct($configuration, $plugin_id, $plugin_definition); }
/** * Retrieves the currently selected theme on the settings form. * * @param array $form * Nested array of form elements that comprise the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * * @return \Drupal\bootstrap\Theme|FALSE * The currently selected theme object or FALSE if not a Bootstrap theme. */ public static function getTheme(array &$form, FormStateInterface $form_state) { $build_info = $form_state->getBuildInfo(); $theme = isset($build_info['args'][0]) ? Bootstrap::getTheme($build_info['args'][0]) : FALSE; // Do not continue if the theme is not Bootstrap specific. if (!$theme || !$theme->subthemeOf('bootstrap')) { unset($form['#submit'][0]); unset($form['#validate'][0]); } return $theme; }
/** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { // This is technically a plugin constructor, but because we wish to use the // protected methods of the Registry class, we must extend from it. Thus, // to properly construct the extended Registry object, we must pass the // arguments it would normally get from the service container to "fake" it. if (!isset($configuration['theme'])) { $configuration['theme'] = Bootstrap::getTheme(); } $this->currentTheme = $configuration['theme']; parent::__construct(\Drupal::service('app.root'), \Drupal::service('cache.default'), \Drupal::service('lock'), \Drupal::service('module_handler'), \Drupal::service('theme_handler'), \Drupal::service('theme.initialization'), $this->currentTheme->getName()); $this->setThemeManager(\Drupal::service('theme.manager')); $this->init(); }
/** * {@inheritdoc} */ public static function processElement(Element $element, FormStateInterface $form_state, array &$complete_form) { if (isset($element->format)) { $element->format->addClass('form-inline'); // Guidelines (removed). $element->format->guidelines->setProperty('access', FALSE); // Format (select). $element->format->format->addClass('input-sm')->setProperty('title_display', 'invisible')->setProperty('weight', -10); // Help (link). $element->format->help->about->setAttribute('title', t('Opens in new window'))->setProperty('icon', Bootstrap::glyphicon('question-sign')); if (Bootstrap::getTheme()->getSetting('tooltip_enabled')) { $element->format->help->about->setAttribute('data-toggle', 'tooltip'); } } }
/** * Global #process callback for form elements. * * @param array $element * The element render array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The altered element array. * * @see \Drupal\bootstrap\Plugin\Alter\ElementInfo::alter */ public static function process(array $element, FormStateInterface $form_state, array &$complete_form) { if (!empty($element['#bootstrap_ignore_process'])) { return $element; } static $theme; if (!isset($theme)) { $theme = Bootstrap::getTheme(); } $e = Element::create($element, $form_state); // Add "form-inline" class. if ($e->hasClass('container-inline')) { $e->replaceClass('container-inline', 'form-inline'); } if ($e->isType(['color', 'date', 'number', 'password', 'password_confirm', 'range', 'tel', 'weight'])) { $e->addClass('form-inline', 'wrapper_attributes'); } // Add "form-group" class, don't replace "form-wrapper" as that is needed // by some JavaScript for certain functionality to work. if ($e->hasClass('form-wrapper')) { $e->addClass('form-group'); } // Check for errors and set the "has_error" property flag. $errors = $e->getError(); $e->setProperty('errors', $errors); if (isset($errors) || $e->getProperty('required') && $theme->getSetting('forms_required_has_error')) { $e->setProperty('has_error', TRUE); } // Automatically inject the nearest button found after this element if // #input_group_button exists. if ($e->getProperty('input_group_button')) { // Obtain the parent array to limit search. $array_parents = $e->getProperty('array_parents', []); // Remove the current element from the array. array_pop($array_parents); // If element is nested, return the referenced parent from the form. // Otherwise return the complete form. $parent = Element::create($array_parents ? NestedArray::getValue($complete_form, $array_parents) : $complete_form, $form_state); // Find the closest button. if ($button = self::findButton($parent)) { $e->setProperty('field_suffix', $button->setIcon()->getArray()); $button->setProperty('access', FALSE); } } return $element; }
/** * AJAX callback for reloading CDN provider elements. * * @param array $form * Nested array of form elements that comprise the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public static function ajaxCallback(array $form, FormStateInterface $form_state) { return $form['advanced']['cdn'][$form_state->getValue('cdn_provider', Bootstrap::getTheme()->getSetting('cdn_provider'))]; }
/** * Retrieves a setting for the current theme or for a given theme. * * @param string $name * The name of the setting to be retrieved. * @param string $theme * The name of a given theme; defaults to the currently active theme. * @param string $prefix * The prefix used on the $name of the setting, this will be appended with * "_" automatically if set. * * @return mixed * The value of the requested setting, NULL if the setting does not exist. * * @deprecated Will be removed in a future release. * * @code * // Before ("button_colorize" and "my_subtheme_custom_option"). * $colorize = bootstrap_setting('button_colorize', 'my_subtheme'); * $custom_option = bootstrap_setting('custom_option', 'my_subtheme', 'my_subtheme'); * * // After ("button_colorize" and "my_subtheme_custom_option"). * use Drupal\bootstrap\Bootstrap; * $my_subtheme = Bootstrap::getTheme('my_subtheme'); * $my_subtheme->getSetting('button_colorize'); * $my_subtheme->getSetting('my_subtheme_custom_option'); * @endcode * * @see \Drupal\bootstrap\Theme::getSetting() * @see \Drupal\bootstrap\Bootstrap::getTheme() */ function bootstrap_setting($name, $theme = NULL, $prefix = 'bootstrap') { Bootstrap::deprecated(); $theme = Bootstrap::getTheme($theme); $prefix = $prefix !== 'bootstrap' && !empty($prefix) ? $prefix . '_' : ''; return $theme->getSetting($prefix . $name); }
/** * Retrieves the full base/sub-theme ancestry of a theme. * * @param bool $reverse * Whether or not to return the array of themes in reverse order, where the * active theme is the first entry. * * @return \Drupal\bootstrap\Theme[] * An associative array of \Drupal\bootstrap objects (theme), keyed * by machine name. */ public function getAncestry($reverse = FALSE) { $ancestry = $this->themeHandler->getBaseThemes($this->themes, $this->getName()); foreach (array_keys($ancestry) as $name) { $ancestry[$name] = Bootstrap::getTheme($name, $this->themeHandler); } $ancestry[$this->getName()] = $this; return $reverse ? array_reverse($ancestry) : $ancestry; }
/** * Converts an element description into a tooltip based on certain criteria. * * @param array|\Drupal\bootstrap\Utility\Element|NULL $target_element * The target element render array the tooltip is to be attached to, passed * by reference or an existing Element object. If not set, it will default * this Element instance. * @param bool $input_only * Toggle determining whether or not to only convert input elements. * @param int $length * The length of characters to determine if description is "simple". * * @return $this */ public function smartDescription(&$target_element = NULL, $input_only = TRUE, $length = NULL) { static $theme; if (!isset($theme)) { $theme = Bootstrap::getTheme(); } // Determine if tooltips are enabled. static $enabled; if (!isset($enabled)) { $enabled = $theme->getSetting('tooltip_enabled') && $theme->getSetting('forms_smart_descriptions'); } // Immediately return if tooltip descriptions are not enabled. if (!$enabled) { return $this; } // Allow a different element to attach the tooltip. /** @var Element $target */ if (is_object($target_element) && $target_element instanceof self) { $target = $target_element; } elseif (isset($target_element) && is_array($target_element)) { $target = new self($target_element, $this->formState); } else { $target = $this; } // Retrieve the length limit for smart descriptions. if (!isset($length)) { // Disable length checking by setting it to FALSE if empty. $length = (int) $theme->getSetting('forms_smart_descriptions_limit') ?: FALSE; } // Retrieve the allowed tags for smart descriptions. This is primarily used // for display purposes only (i.e. non-UI/UX related elements that wouldn't // require a user to "click", like a link). Disable length checking by // setting it to FALSE if empty. static $allowed_tags; if (!isset($allowed_tags)) { $allowed_tags = array_filter(array_unique(array_map('trim', explode(',', $theme->getSetting('forms_smart_descriptions_allowed_tags') . '')))) ?: FALSE; } // Return if element or target shouldn't have "simple" tooltip descriptions. $html = FALSE; if ($input_only && !$target->hasProperty('input') || !$this->getProperty('smart_description', TRUE) || !$target->getProperty('smart_description', TRUE) || !$this->hasProperty('description') || $target->hasAttribute('data-toggle') || !Unicode::isSimple($this->getProperty('description'), $length, $allowed_tags, $html)) { return $this; } // Default attributes type. $type = DrupalAttributes::ATTRIBUTES; // Use #label_attributes for 'checkbox' and 'radio' elements. if ($this->isType(['checkbox', 'radio'])) { $type = DrupalAttributes::LABEL; } elseif ($this->isType(['checkboxes', 'radios'])) { $type = DrupalAttributes::WRAPPER; } // Retrieve the proper attributes array. $attributes = $target->getAttributes($type); // Set the tooltip attributes. $attributes['title'] = $allowed_tags !== FALSE ? Xss::filter((string) $this->getProperty('description'), $allowed_tags) : $this->getProperty('description'); $attributes['data-toggle'] = 'tooltip'; if ($html || $allowed_tags === FALSE) { $attributes['data-html'] = 'true'; } // Remove the element description so it isn't (re-)rendered later. $this->unsetProperty('description'); return $this; }
/** * Matches a Bootstrap Glyphicon based on a string value. * * @param string $string * The string to match classes against. * @param array $default * The default render array to return if no match is found. * * @return string * The Bootstrap icon matched against the value of $haystack or $default if * no match could be made. */ public static function glyphiconFromString($string, $default = []) { static $lang; if (!isset($lang)) { $lang = \Drupal::languageManager()->getCurrentLanguage()->getId(); } $theme = Bootstrap::getTheme(); $texts = $theme->getCache('glyphiconFromString', [$lang]); $string = (string) $string; if ($texts->isEmpty()) { $data = ['matches' => [], 'contains' => [t('Manage')->render() => 'cog', t('Configure')->render() => 'cog', t('Settings')->render() => 'cog', t('Download')->render() => 'download', t('Export')->render() => 'export', t('Filter')->render() => 'filter', t('Import')->render() => 'import', t('Save')->render() => 'ok', t('Update')->render() => 'ok', t('Edit')->render() => 'pencil', t('Uninstall')->render() => 'trash', t('Install')->render() => 'plus', t('Write')->render() => 'plus', t('Cancel')->render() => 'remove', t('Delete')->render() => 'trash', t('Remove')->render() => 'trash', t('Search')->render() => 'search', t('Upload')->render() => 'upload', t('Preview')->render() => 'eye-open']]; // Allow sub-themes to alter this array of patterns. /** @var \Drupal\Core\Theme\ThemeManager $theme_manager */ $theme_manager = \Drupal::service('theme.manager'); $theme_manager->alter('bootstrap_iconize_text', $data); $texts->setMultiple($data); } // Iterate over the array. foreach ($texts as $pattern => $strings) { foreach ($strings as $value => $icon) { switch ($pattern) { case 'matches': if ($string === $value) { return self::glyphicon($icon, $default); } break; case 'contains': if (strpos(Unicode::strtolower($string), Unicode::strtolower($value)) !== FALSE) { return self::glyphicon($icon, $default); } break; } } } // Return a default icon if nothing was matched. return $default; }
/** * {@inheritdoc} */ public function getTheme() { return Bootstrap::getTheme($this->pluginDefinition['provider']); }
/** * Batch 'finished' callback * * @param bool $success * A boolean indicating whether the batch has completed successfully. * @param array $results * The value(s) set in $context['results'] in * \Drupal\bootstrap\Plugin\Setting\Update::batchProcess(). * @param $operations * If $success is FALSE, contains the operations that remained unprocessed. */ public static function batchFinished($success, $results, $operations) { /** @type \Drupal\bootstrap\Theme $theme */ // Reconstruct the theme object this update is being applied to. $theme = Bootstrap::getTheme($results['theme_name']); // Save the current state of the installed schemas. $theme->setSetting('schemas', $results['schemas']); // Show successful updates. if (!empty($results['success'])) { $build = ['#theme' => 'item_list__theme_update', '#items' => $results['success'], '#context' => ['type' => 'success']]; drupal_set_message(new FormattableMarkup('@message' . \Drupal::service('renderer')->render($build), ['@message' => t('Successfully completed the following theme updates:')])); } // Show failed errors. if (!empty($results['errors'])) { $build = ['#theme' => 'item_list__theme_update', '#items' => $results['errors'], '#context' => ['type' => 'errors']]; drupal_set_message(new FormattableMarkup('@message' . \Drupal::service('renderer')->render($build), ['@message' => t('The following theme updates could not be completed:')]), 'error'); } }