file Bootstrap.php

  1. 8.x-3.x src/Bootstrap.php
  2. 8.x-3.x scripts/bootstrap.php
  3. 8.x-3.x src/Plugin/Markdown/AllowedHtml/Bootstrap.php

Namespace

Drupal\bootstrap
  1. <?php
  2. namespace Drupal\bootstrap;
  3. use Drupal\bootstrap\Plugin\AlterManager;
  4. use Drupal\bootstrap\Plugin\FormManager;
  5. use Drupal\bootstrap\Plugin\PreprocessManager;
  6. use Drupal\bootstrap\Utility\Crypt;
  7. use Drupal\bootstrap\Utility\Element;
  8. use Drupal\bootstrap\Utility\Unicode;
  9. use Drupal\Component\Render\FormattableMarkup;
  10. use Drupal\Component\Utility\NestedArray;
  11. use Drupal\Core\Extension\ThemeHandlerInterface;
  12. use Drupal\Core\Form\FormStateInterface;
  13. use Drupal\Core\Render\Markup;
  14. use Drupal\Core\Render\RenderContext;
  15. use Drupal\Core\Session\AccountInterface;
  16. use Drupal\Core\StringTranslation\TranslatableMarkup;
  17. use GuzzleHttp\Exception\GuzzleException;
  18. use GuzzleHttp\Psr7\Request;
  19. /**
  20. * The primary class for the Drupal Bootstrap base theme.
  21. *
  22. * Provides many helper methods.
  23. *
  24. * @ingroup utility
  25. */
  26. class Bootstrap {
  27. /**
  28. * Tag used to invalidate caches.
  29. *
  30. * @var string
  31. */
  32. const CACHE_TAG = 'theme_registry';
  33. /**
  34. * Append a callback.
  35. *
  36. * @var int
  37. */
  38. const CALLBACK_APPEND = 1;
  39. /**
  40. * Prepend a callback.
  41. *
  42. * @var int
  43. */
  44. const CALLBACK_PREPEND = 2;
  45. /**
  46. * Replace a callback or append it if not found.
  47. *
  48. * @var int
  49. */
  50. const CALLBACK_REPLACE_APPEND = 3;
  51. /**
  52. * Replace a callback or prepend it if not found.
  53. *
  54. * @var int
  55. */
  56. const CALLBACK_REPLACE_PREPEND = 4;
  57. /**
  58. * The current supported Bootstrap Framework version.
  59. *
  60. * @var string
  61. */
  62. const FRAMEWORK_VERSION = '3.4.1';
  63. /**
  64. * The Bootstrap Framework documentation site.
  65. *
  66. * @var string
  67. */
  68. const FRAMEWORK_HOMEPAGE = 'https://getbootstrap.com/docs/3.4/';
  69. /**
  70. * The Bootstrap Framework repository.
  71. *
  72. * @var string
  73. */
  74. const FRAMEWORK_REPOSITORY = 'https://github.com/twbs/bootstrap';
  75. /**
  76. * The project branch.
  77. *
  78. * @var string
  79. */
  80. const PROJECT_BRANCH = '8.x-3.x';
  81. /**
  82. * The Drupal Bootstrap documentation site.
  83. *
  84. * @var string
  85. */
  86. const PROJECT_DOCUMENTATION = 'https://drupal-bootstrap.org';
  87. /**
  88. * The project API search URL.
  89. *
  90. * @var string
  91. *
  92. * @todo Enable constant once PHP 5.5 is no longer supported.
  93. */
  94. // const PROJECT_API_SEARCH_URL = self::PROJECT_DOCUMENTATION . '/api/bootstrap/' . self::PROJECT_BRANCH . '/search/@query';
  95. /**
  96. * The Drupal Bootstrap project page.
  97. *
  98. * @var string
  99. */
  100. const PROJECT_PAGE = 'https://www.drupal.org/project/bootstrap';
  101. /**
  102. * The File System service, if it exists.
  103. *
  104. * @var \Drupal\Core\File\FileSystemInterface|false
  105. */
  106. protected static $fileSystem;
  107. /**
  108. * The Messenger service, if it exists.
  109. *
  110. * @var \Drupal\Core\Messenger\MessengerInterface|false
  111. */
  112. protected static $messenger;
  113. /**
  114. * The Renderer service.
  115. *
  116. * @var \Drupal\Core\Render\Renderer
  117. */
  118. protected static $renderer;
  119. /**
  120. * The Theme Registry service.
  121. *
  122. * @var \Drupal\Core\Theme\Registry
  123. */
  124. protected static $themeRegistry;
  125. /**
  126. * The Twig service.
  127. *
  128. * @var \Drupal\Core\Template\TwigEnvironment
  129. */
  130. protected static $twig;
  131. /**
  132. * Adds a callback to an array.
  133. *
  134. * @param array $callbacks
  135. * An array of callbacks to add the callback to, passed by reference.
  136. * @param array|string $callback
  137. * The callback to add.
  138. * @param array|string $replace
  139. * If specified, the callback will instead replace the specified value
  140. * instead of being appended to the $callbacks array.
  141. * @param int $action
  142. * Flag that determines how to add the callback to the array.
  143. *
  144. * @return bool
  145. * TRUE if the callback was added, FALSE if $replace was specified but its
  146. * callback could be found in the list of callbacks.
  147. */
  148. public static function addCallback(array &$callbacks, $callback, $replace = NULL, $action = Bootstrap::CALLBACK_APPEND) {
  149. // Replace a callback.
  150. if ($replace) {
  151. // Iterate through the callbacks.
  152. foreach ($callbacks as $key => $value) {
  153. // Convert each callback and match the string values.
  154. if (Unicode::convertCallback($value) === Unicode::convertCallback($replace)) {
  155. $callbacks[$key] = $callback;
  156. return TRUE;
  157. }
  158. }
  159. // No match found and action shouldn't append or prepend.
  160. if ($action !== self::CALLBACK_REPLACE_APPEND || $action !== self::CALLBACK_REPLACE_PREPEND) {
  161. return FALSE;
  162. }
  163. }
  164. // Append or prepend the callback.
  165. switch ($action) {
  166. case self::CALLBACK_APPEND:
  167. case self::CALLBACK_REPLACE_APPEND:
  168. $callbacks[] = $callback;
  169. return TRUE;
  170. case self::CALLBACK_PREPEND:
  171. case self::CALLBACK_REPLACE_PREPEND:
  172. array_unshift($callbacks, $callback);
  173. return TRUE;
  174. default:
  175. return FALSE;
  176. }
  177. }
  178. /**
  179. * Manages theme alter hooks as classes and allows sub-themes to sub-class.
  180. *
  181. * @param string $function
  182. * The procedural function name of the alter (e.g. __FUNCTION__).
  183. * @param mixed $data
  184. * The variable that was passed to the hook_TYPE_alter() implementation to
  185. * be altered. The type of this variable depends on the value of the $type
  186. * argument. For example, when altering a 'form', $data will be a structured
  187. * array. When altering a 'profile', $data will be an object.
  188. * @param mixed $context1
  189. * (optional) An additional variable that is passed by reference.
  190. * @param mixed $context2
  191. * (optional) An additional variable that is passed by reference. If more
  192. * context needs to be provided to implementations, then this should be an
  193. * associative array as described above.
  194. */
  195. public static function alter($function, &$data, &$context1 = NULL, &$context2 = NULL) {
  196. // Do not statically cache this as the active theme may change.
  197. $theme = static::getTheme();
  198. $theme_name = $theme->getName();
  199. // Immediately return if the active theme is not Bootstrap based.
  200. if (!$theme->isBootstrap()) {
  201. return;
  202. }
  203. // Handle alter and form managers.
  204. static $drupal_static_fast;
  205. if (!isset($drupal_static_fast)) {
  206. $drupal_static_fast['alter_managers'] = &drupal_static(__METHOD__ . '__alterManagers', []);
  207. $drupal_static_fast['form_managers'] = &drupal_static(__METHOD__ . '__formManagers', []);
  208. }
  209. /* @var \Drupal\bootstrap\Plugin\AlterManager[] $alter_managers */
  210. $alter_managers = &$drupal_static_fast['alter_managers'];
  211. if (!isset($alter_managers[$theme_name])) {
  212. $alter_managers[$theme_name] = new AlterManager($theme);
  213. }
  214. /* @var \Drupal\bootstrap\Plugin\FormManager[] $form_managers */
  215. $form_managers = &$drupal_static_fast['form_managers'];
  216. if (!isset($form_managers[$theme_name])) {
  217. $form_managers[$theme_name] = new FormManager($theme);
  218. }
  219. // Retrieve the alter and form managers for this theme.
  220. $alter_manager = $alter_managers[$theme_name];
  221. $form_manager = $form_managers[$theme_name];
  222. // Extract the alter hook name.
  223. $hook = Unicode::extractHook($function, 'alter');
  224. // Handle form alters as a separate plugin.
  225. if (strpos($hook, 'form') === 0 && $context1 instanceof FormStateInterface) {
  226. $form_state = $context1;
  227. $form_id = $context2;
  228. // Due to a core bug that affects admin themes, we should not double
  229. // process the "system_theme_settings" form twice in the global
  230. // hook_form_alter() invocation.
  231. // @see https://www.drupal.org/node/943212
  232. if ($form_id === 'system_theme_settings') {
  233. return;
  234. }
  235. // Keep track of the form identifiers.
  236. $ids = [];
  237. // Get the build data.
  238. $build_info = $form_state->getBuildInfo();
  239. // Extract the base_form_id.
  240. $base_form_id = !empty($build_info['base_form_id']) ? $build_info['base_form_id'] : FALSE;
  241. if ($base_form_id) {
  242. $ids[] = $base_form_id;
  243. }
  244. // If there was no provided form identifier, extract it.
  245. if (!$form_id) {
  246. $form_id = !empty($build_info['form_id']) ? $build_info['form_id'] : Unicode::extractHook($function, 'alter', 'form');
  247. }
  248. if ($form_id) {
  249. $ids[] = $form_id;
  250. }
  251. // Iterate over each form identifier and look for a possible plugin.
  252. foreach ($ids as $id) {
  253. /** @var \Drupal\bootstrap\Plugin\Form\FormInterface $form */
  254. if ($form_manager->hasDefinition($id) && ($form = $form_manager->createInstance($id, ['theme' => $theme]))) {
  255. $data['#submit'][] = [get_class($form), 'submitForm'];
  256. $data['#validate'][] = [get_class($form), 'validateForm'];
  257. $form->alterForm($data, $form_state, $form_id);
  258. }
  259. }
  260. }
  261. // Process hook alter normally.
  262. else {
  263. /** @var \Drupal\bootstrap\Plugin\Alter\AlterInterface $class */
  264. if ($alter_manager->hasDefinition($hook) && ($class = $alter_manager->createInstance($hook, ['theme' => $theme]))) {
  265. $class->alter($data, $context1, $context2);
  266. }
  267. }
  268. }
  269. /**
  270. * Returns a documentation search URL for a given query.
  271. *
  272. * @param string $query
  273. * The query to search for.
  274. *
  275. * @return \Drupal\Component\Render\FormattableMarkup
  276. * The complete URL to the documentation site.
  277. */
  278. public static function apiSearchUrl($query = '') {
  279. // @todo Move to a constant once PHP 5.5 is no longer supported.
  280. return new FormattableMarkup(self::PROJECT_DOCUMENTATION . '/api/bootstrap/' . self::PROJECT_BRANCH . '/search/@query', [
  281. '@query' => $query,
  282. ]);
  283. }
  284. /**
  285. * Returns the autoload fix include path.
  286. *
  287. * This method assists class based callbacks that normally do not work.
  288. *
  289. * If you notice that your class based callback is never invoked, you may try
  290. * using this helper method as an "include" or "file" for your callback, if
  291. * the callback metadata supports such an option.
  292. *
  293. * Depending on when or where a callback is invoked during a request, such as
  294. * an ajax or batch request, the theme handler may not yet be fully
  295. * initialized.
  296. *
  297. * Typically there is little that can be done about this "issue" from core.
  298. * It must balance the appropriate level that should be bootstrapped along
  299. * with common functionality. Cross-request class based callbacks are not
  300. * common in themes.
  301. *
  302. * When this file is included, it will attempt to jump start this process.
  303. *
  304. * Please keep in mind, that it is merely an attempt and does not guarantee
  305. * that it will actually work. If it does not appear to work, do not use it.
  306. *
  307. * @see \Drupal\Core\Extension\ThemeHandler::listInfo
  308. * @see \Drupal\Core\Extension\ThemeHandler::systemThemeList
  309. * @see system_list
  310. * @see system_register()
  311. * @see drupal_classloader_register()
  312. *
  313. * @return string
  314. * The autoload fix include path, relative to Drupal root.
  315. */
  316. public static function autoloadFixInclude() {
  317. return static::getTheme('bootstrap')->getPath() . '/autoload-fix.php';
  318. }
  319. /**
  320. * Checks whether a specific URL is reachable.
  321. *
  322. * @param string $url
  323. * The URL to check.
  324. * @param array $options
  325. * Additional options to pass to the HTTP client.
  326. * @param \Exception|null $exception
  327. * Any Exceptions throw, passed by reference.
  328. *
  329. * @return \Drupal\bootstrap\SerializedResponse
  330. * A SerializedResponse object.
  331. */
  332. public static function checkUrlIsReachable($url, array $options = [], &$exception = NULL) {
  333. $options['method'] = 'HEAD';
  334. $options['ttl'] = 0;
  335. return static::request($url, $options, $exception);
  336. }
  337. /**
  338. * Retrieves a response from a URL, using cached response if available.
  339. *
  340. * @param string $url
  341. * The URL to retrieve.
  342. * @param array $options
  343. * The options to pass to the HTTP client.
  344. * @param \Exception|null $exception
  345. * The exception thrown if there was an error, passed by reference.
  346. *
  347. * @return \Drupal\bootstrap\SerializedResponse
  348. * A Response object.
  349. */
  350. public static function request($url, array $options = [], &$exception = NULL) {
  351. $options += [
  352. 'method' => 'GET',
  353. 'headers' => [
  354. 'User-Agent' => 'Drupal Bootstrap ' . static::PROJECT_BRANCH . ' (' . static::PROJECT_PAGE . ')',
  355. ],
  356. ];
  357. // Determine if a custom TTL value was set.
  358. $ttl = isset($options['ttl']) ? $options['ttl'] : NULL;
  359. unset($options['ttl']);
  360. $cache = \Drupal::keyValueExpirable('theme:' . static::getTheme()->getName() . ':http');
  361. // The URL cannot be part of the prefix as the "name" field of
  362. // "key_value_expire" has a max length of 128.
  363. $hash = Crypt::generateBase64HashIdentifier(['url' => $url] + $options, 'request');
  364. $response = $cache->get($hash);
  365. if (!isset($response)) {
  366. /** @var \GuzzleHttp\Client $client */
  367. $client = \Drupal::service('http_client_factory')->fromOptions($options);
  368. $request = new Request($options['method'], $url, $options['headers']);
  369. try {
  370. $response = SerializedResponse::createFromGuzzleResponse($client->send($request, $options), $request);
  371. }
  372. catch (GuzzleException $e) {
  373. $exception = $e;
  374. $response = SerializedResponse::createFromException($e, $request);
  375. }
  376. catch (\Exception $e) {
  377. $exception = $e;
  378. $response = SerializedResponse::createFromException($e, $request);
  379. }
  380. // Only cache if a maximum age has been detected.
  381. $maxAge = (int) isset($ttl) ? $ttl : $response->getMaxAge();
  382. if ($response->getStatusCode() == 200 && $maxAge > 0) {
  383. // Due to key_value_expire setting the "expire" field to "INT(11)", it
  384. // is technically limited to a 32bit max value (Y2K38 bug).
  385. // @todo Remove this once this is no longer an issue.
  386. // @see https://www.drupal.org/project/drupal/issues/65474
  387. // @see https://www.drupal.org/project/drupal/issues/1003692
  388. $requestTime = \Drupal::time()->getRequestTime();
  389. if (($requestTime + $maxAge) > 2147483647) {
  390. $maxAge = 2147483647 - $requestTime;
  391. }
  392. try {
  393. $cache->setWithExpire($hash, $response, $maxAge);
  394. }
  395. catch (\Exception $e) {
  396. // Intentionally do nothing, tried to cache response... it failed.
  397. }
  398. }
  399. }
  400. return $response;
  401. }
  402. /**
  403. * Matches a Bootstrap class based on a string value.
  404. *
  405. * @param string|array $value
  406. * The string to match against to determine the class. Passed by reference
  407. * in case it is a render array that needs to be rendered and typecast.
  408. * @param string $default
  409. * The default class to return if no match is found.
  410. *
  411. * @return string
  412. * The Bootstrap class matched against the value of $haystack or $default
  413. * if no match could be made.
  414. */
  415. public static function cssClassFromString(&$value, $default = '') {
  416. static $lang;
  417. if (!isset($lang)) {
  418. $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  419. }
  420. $theme = static::getTheme();
  421. $texts = $theme->getCache('cssClassFromString', [$lang]);
  422. // Ensure it's a string value that was passed.
  423. $string = static::toString($value);
  424. if ($texts->isEmpty()) {
  425. $data = [
  426. // Text that match these specific strings are checked first.
  427. 'matches' => [
  428. // Primary class.
  429. t('Download feature')->render() => 'primary',
  430. // Success class.
  431. t('Add effect')->render() => 'success',
  432. t('Add and configure')->render() => 'success',
  433. t('Save configuration')->render() => 'success',
  434. t('Install and set as default')->render() => 'success',
  435. // Info class.
  436. t('Save and add')->render() => 'info',
  437. t('Add another item')->render() => 'info',
  438. t('Update style')->render() => 'info',
  439. ],
  440. // Text containing these words anywhere in the string are checked last.
  441. 'contains' => [
  442. // Primary class.
  443. t('Confirm')->render() => 'primary',
  444. t('Filter')->render() => 'primary',
  445. t('Log in')->render() => 'primary',
  446. t('Submit')->render() => 'primary',
  447. t('Search')->render() => 'primary',
  448. t('Settings')->render() => 'primary',
  449. t('Upload')->render() => 'primary',
  450. // Danger class.
  451. t('Delete')->render() => 'danger',
  452. t('Remove')->render() => 'danger',
  453. t('Reset')->render() => 'danger',
  454. t('Uninstall')->render() => 'danger',
  455. // Success class.
  456. t('Add')->render() => 'success',
  457. t('Create')->render() => 'success',
  458. t('Install')->render() => 'success',
  459. t('Save')->render() => 'success',
  460. t('Write')->render() => 'success',
  461. // Warning class.
  462. t('Export')->render() => 'warning',
  463. t('Import')->render() => 'warning',
  464. t('Restore')->render() => 'warning',
  465. t('Rebuild')->render() => 'warning',
  466. // Info class.
  467. t('Apply')->render() => 'info',
  468. t('Update')->render() => 'info',
  469. ],
  470. ];
  471. // Allow sub-themes to alter this array of patterns.
  472. /** @var \Drupal\Core\Theme\ThemeManager $theme_manager */
  473. $theme_manager = \Drupal::service('theme.manager');
  474. $theme_manager->alter('bootstrap_colorize_text', $data);
  475. $texts->setMultiple($data);
  476. }
  477. // Iterate over the array.
  478. foreach ($texts as $pattern => $strings) {
  479. foreach ($strings as $text => $class) {
  480. switch ($pattern) {
  481. case 'matches':
  482. if ($string === $text) {
  483. return $class;
  484. }
  485. break;
  486. case 'contains':
  487. if (strpos(Unicode::strtolower($string), Unicode::strtolower($text)) !== FALSE) {
  488. return $class;
  489. }
  490. break;
  491. }
  492. }
  493. }
  494. // Return the default if nothing was matched.
  495. return $default;
  496. }
  497. /**
  498. * Logs and displays a warning about a deprecated function/method being used.
  499. *
  500. * @param string $caller
  501. * Optional. The function or Class::method that should be shown as
  502. * deprecated. If not set, it will be extrapolated automatically from
  503. * the backtrace. This is primarily used when this method is being invoked
  504. * from inside another method that isn't technically deprecated but has to
  505. * support deprecated functionality.
  506. * @param bool $show_message
  507. * Flag indicating whether to show a message to the user. If TRUE, it will
  508. * force show the message. If FALSE, it will only log the message. If not
  509. * set, the message will be shown based on whether the current user is an
  510. * administrator and if the theme has suppressed deprecated warnings.
  511. * @param \Drupal\Core\StringTranslation\TranslatableMarkup $message
  512. * Optional. The message to show/log. If not set, it will be determined
  513. * automatically based on the caller.
  514. */
  515. public static function deprecated($caller = NULL, $show_message = NULL, TranslatableMarkup $message = NULL) {
  516. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  517. // Extrapolate the caller.
  518. if (!isset($caller) && !empty($backtrace[1]) && ($info = $backtrace[1])) {
  519. $caller = (!empty($info['class']) ? $info['class'] . '::' : '') . $info['function'];
  520. }
  521. // Remove class namespace.
  522. $method = FALSE;
  523. if (is_string($caller) && strpos($caller, '::') !== FALSE && ($parts = explode('\\', $caller))) {
  524. $method = TRUE;
  525. $caller = array_pop($parts);
  526. }
  527. if (!isset($message)) {
  528. $message = t('The following @type has been deprecated: <a href=":url" target="_blank">@title</a>. Please check the logs for a more detailed backtrace on where it is being invoked.', [
  529. '@type' => $method ? 'method' : 'function',
  530. ':url' => static::apiSearchUrl($caller),
  531. '@title' => $caller,
  532. ]);
  533. }
  534. if ($show_message || (!isset($show_message) && static::isAdmin() && !static::getTheme()->getSetting('suppress_deprecated_warnings', FALSE))) {
  535. static::message($message, 'warning');
  536. }
  537. // Log message and accompanying backtrace.
  538. \Drupal::logger('bootstrap')->warning('<div>@message</div><pre><code>@backtrace</code></pre>', [
  539. '@message' => $message,
  540. '@backtrace' => Markup::create(print_r($backtrace, TRUE)),
  541. ]);
  542. }
  543. /**
  544. * Provides additional variables to be used in elements and templates.
  545. *
  546. * @return array
  547. * An associative array containing key/default value pairs.
  548. */
  549. public static function extraVariables() {
  550. return [
  551. // @see https://www.drupal.org/node/2035055
  552. 'context' => [],
  553. // @see https://www.drupal.org/node/2219965
  554. 'icon' => NULL,
  555. 'icon_position' => 'before',
  556. 'icon_only' => FALSE,
  557. ];
  558. }
  559. /**
  560. * Retrieves the File System service, if it exists.
  561. *
  562. * @param string $method
  563. * Optional. A specific method on the file system service to check for
  564. * its existance.
  565. *
  566. * @return \Drupal\Core\File\FileSystemInterface
  567. * The File System service, if it exists and if $method exists if it was
  568. * passed.
  569. *
  570. * @deprecated in bootstrap:8.x-3.22 and is removed from bootstrap:5.0.0.
  571. * Use the "file_system" service instead.
  572. * @see https://www.drupal.org/project/bootstrap/issues/3096963
  573. */
  574. public static function fileSystem($method = NULL) {
  575. if (!isset(static::$fileSystem)) {
  576. static::$fileSystem = \Drupal::hasService('file_system') ? \Drupal::service('file_system') : FALSE;
  577. }
  578. if ($method) {
  579. return static::$fileSystem && method_exists(static::$fileSystem, $method) ? static::$fileSystem : FALSE;
  580. }
  581. return static::$fileSystem;
  582. }
  583. /**
  584. * Retrieves a theme instance of \Drupal\bootstrap.
  585. *
  586. * @param string $name
  587. * The machine name of a theme. If omitted, the active theme will be used.
  588. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  589. * The theme handler object.
  590. *
  591. * @return \Drupal\bootstrap\Theme
  592. * A theme object.
  593. */
  594. public static function getTheme($name = NULL, ThemeHandlerInterface $theme_handler = NULL) {
  595. // Immediately return if theme passed is already instantiated.
  596. if ($name instanceof Theme) {
  597. return $name;
  598. }
  599. static $themes = [];
  600. // Retrieve the active theme.
  601. // Do not statically cache this as the active theme may change.
  602. if (!isset($name)) {
  603. $name = \Drupal::theme()->getActiveTheme()->getName();
  604. }
  605. if (!isset($theme_handler)) {
  606. $theme_handler = self::getThemeHandler();
  607. }
  608. if (!isset($themes[$name])) {
  609. $themes[$name] = new Theme($theme_handler->getTheme($name), $theme_handler);
  610. }
  611. return $themes[$name];
  612. }
  613. /**
  614. * Retrieves the theme handler instance.
  615. *
  616. * @return \Drupal\Core\Extension\ThemeHandlerInterface
  617. * The theme handler instance.
  618. */
  619. public static function getThemeHandler() {
  620. static $theme_handler;
  621. if (!isset($theme_handler)) {
  622. $theme_handler = \Drupal::service('theme_handler');
  623. }
  624. return $theme_handler;
  625. }
  626. /**
  627. * Returns the theme hook definition information.
  628. *
  629. * This base-theme's custom theme hook implementations. Never define "path"
  630. * as this is automatically detected and added.
  631. *
  632. * @see \Drupal\bootstrap\Plugin\Alter\ThemeRegistry::alter()
  633. * @see bootstrap_theme_registry_alter()
  634. * @see bootstrap_theme()
  635. * @see hook_theme()
  636. */
  637. public static function getThemeHooks() {
  638. $hooks['bootstrap_carousel'] = [
  639. 'variables' => [
  640. 'attributes' => [],
  641. 'controls' => TRUE,
  642. 'id' => NULL,
  643. 'indicators' => TRUE,
  644. 'interval' => 5000,
  645. 'pause' => 'hover',
  646. 'slides' => [],
  647. 'start_index' => 0,
  648. 'wrap' => TRUE,
  649. ],
  650. ];
  651. $hooks['bootstrap_dropdown'] = [
  652. 'variables' => [
  653. 'alignment' => 'down',
  654. 'attributes' => [],
  655. 'items' => [],
  656. 'split' => FALSE,
  657. 'toggle' => NULL,
  658. ],
  659. ];
  660. $hooks['bootstrap_modal'] = [
  661. 'variables' => [
  662. 'attributes' => [],
  663. 'body' => '',
  664. 'body_attributes' => [],
  665. 'close_button' => TRUE,
  666. 'content_attributes' => [],
  667. 'description' => NULL,
  668. 'description_display' => 'before',
  669. 'dialog_attributes' => [],
  670. 'footer' => '',
  671. 'footer_attributes' => [],
  672. 'header_attributes' => [],
  673. 'id' => NULL,
  674. 'size' => NULL,
  675. 'title' => '',
  676. 'title_attributes' => [],
  677. ],
  678. ];
  679. $hooks['bootstrap_panel'] = [
  680. 'variables' => [
  681. 'attributes' => [],
  682. 'body' => [],
  683. 'body_attributes' => [],
  684. 'collapsible' => FALSE,
  685. 'collapsed' => FALSE,
  686. 'description' => NULL,
  687. 'description_display' => 'before',
  688. 'footer' => NULL,
  689. 'footer_attributes' => [],
  690. 'heading' => NULL,
  691. 'heading_attributes' => [],
  692. 'id' => NULL,
  693. 'panel_type' => 'default',
  694. ],
  695. ];
  696. return $hooks;
  697. }
  698. /**
  699. * Returns a specific Bootstrap Glyphicon.
  700. *
  701. * @param string $name
  702. * The icon name, minus the "glyphicon-" prefix.
  703. * @param array $default
  704. * (Optional) The default render array to use if $name is not available.
  705. *
  706. * @return array
  707. * The render containing the icon defined by $name, $default value if
  708. * icon does not exist or returns NULL if no icon could be rendered.
  709. */
  710. public static function glyphicon($name, array $default = []) {
  711. $icon = [];
  712. // Ensure the icon specified is a valid Bootstrap Glyphicon.
  713. // @todo Supply a specific version to _bootstrap_glyphicons() when Icon API
  714. // supports versioning.
  715. if (self::getTheme()->hasGlyphicons() && in_array($name, self::glyphicons())) {
  716. // Attempt to use the Icon API module, if enabled and it generates output.
  717. if (\Drupal::moduleHandler()->moduleExists('icon')) {
  718. $icon = [
  719. '#type' => 'icon',
  720. '#bundle' => 'bootstrap',
  721. '#icon' => 'glyphicon-' . $name,
  722. ];
  723. }
  724. else {
  725. $icon = [
  726. '#type' => 'html_tag',
  727. '#tag' => 'span',
  728. '#value' => '',
  729. '#attributes' => [
  730. 'class' => ['icon', 'glyphicon', 'glyphicon-' . $name],
  731. 'aria-hidden' => 'true',
  732. ],
  733. ];
  734. }
  735. }
  736. return $icon ?: $default;
  737. }
  738. /**
  739. * Matches a Bootstrap Glyphicon based on a string value.
  740. *
  741. * @param string $value
  742. * The string to match against to determine the icon. Passed by reference
  743. * in case it is a render array that needs to be rendered and typecast.
  744. * @param array $default
  745. * The default render array to return if no match is found.
  746. *
  747. * @return array
  748. * The Bootstrap icon matched against the value of $haystack or $default if
  749. * no match could be made.
  750. */
  751. public static function glyphiconFromString(&$value, array $default = []) {
  752. static $lang;
  753. if (!isset($lang)) {
  754. $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  755. }
  756. $theme = static::getTheme();
  757. $texts = $theme->getCache('glyphiconFromString', [$lang]);
  758. // Ensure it's a string value that was passed.
  759. $string = static::toString($value);
  760. if ($texts->isEmpty()) {
  761. $data = [
  762. // Text that match these specific strings are checked first.
  763. 'matches' => [],
  764. // Text containing these words anywhere in the string are checked last.
  765. 'contains' => [
  766. t('Manage')->render() => 'cog',
  767. t('Configure')->render() => 'cog',
  768. t('Settings')->render() => 'cog',
  769. t('Download')->render() => 'download',
  770. t('Export')->render() => 'export',
  771. t('Filter')->render() => 'filter',
  772. t('Import')->render() => 'import',
  773. t('Save')->render() => 'ok',
  774. t('Update')->render() => 'ok',
  775. t('Edit')->render() => 'pencil',
  776. t('Uninstall')->render() => 'trash',
  777. t('Install')->render() => 'plus',
  778. t('Write')->render() => 'plus',
  779. t('Cancel')->render() => 'remove',
  780. t('Delete')->render() => 'trash',
  781. t('Remove')->render() => 'trash',
  782. t('Reset')->render() => 'trash',
  783. t('Search')->render() => 'search',
  784. t('Upload')->render() => 'upload',
  785. t('Preview')->render() => 'eye-open',
  786. t('Log in')->render() => 'log-in',
  787. ],
  788. ];
  789. // Allow sub-themes to alter this array of patterns.
  790. /** @var \Drupal\Core\Theme\ThemeManager $theme_manager */
  791. $theme_manager = \Drupal::service('theme.manager');
  792. $theme_manager->alter('bootstrap_iconize_text', $data);
  793. $texts->setMultiple($data);
  794. }
  795. // Iterate over the array.
  796. foreach ($texts as $pattern => $strings) {
  797. foreach ($strings as $text => $icon) {
  798. switch ($pattern) {
  799. case 'matches':
  800. if ($string === $text) {
  801. return self::glyphicon($icon, $default);
  802. }
  803. break;
  804. case 'contains':
  805. if (strpos(Unicode::strtolower($string), Unicode::strtolower($text)) !== FALSE) {
  806. return self::glyphicon($icon, $default);
  807. }
  808. break;
  809. }
  810. }
  811. }
  812. // Return a default icon if nothing was matched.
  813. return $default;
  814. }
  815. /**
  816. * Returns a list of available Bootstrap Framework Glyphicons.
  817. *
  818. * @param string $version
  819. * The specific version of glyphicons to return. If not set, the latest
  820. * BOOTSTRAP_VERSION will be used.
  821. *
  822. * @return array
  823. * An associative array of icons keyed by their classes.
  824. */
  825. public static function glyphicons($version = NULL) {
  826. static $versions;
  827. if (!isset($versions)) {
  828. $versions = [];
  829. $versions['3.0.0'] = [
  830. // Class => Name.
  831. 'glyphicon-adjust' => 'adjust',
  832. 'glyphicon-align-center' => 'align-center',
  833. 'glyphicon-align-justify' => 'align-justify',
  834. 'glyphicon-align-left' => 'align-left',
  835. 'glyphicon-align-right' => 'align-right',
  836. 'glyphicon-arrow-down' => 'arrow-down',
  837. 'glyphicon-arrow-left' => 'arrow-left',
  838. 'glyphicon-arrow-right' => 'arrow-right',
  839. 'glyphicon-arrow-up' => 'arrow-up',
  840. 'glyphicon-asterisk' => 'asterisk',
  841. 'glyphicon-backward' => 'backward',
  842. 'glyphicon-ban-circle' => 'ban-circle',
  843. 'glyphicon-barcode' => 'barcode',
  844. 'glyphicon-bell' => 'bell',
  845. 'glyphicon-bold' => 'bold',
  846. 'glyphicon-book' => 'book',
  847. 'glyphicon-bookmark' => 'bookmark',
  848. 'glyphicon-briefcase' => 'briefcase',
  849. 'glyphicon-bullhorn' => 'bullhorn',
  850. 'glyphicon-calendar' => 'calendar',
  851. 'glyphicon-camera' => 'camera',
  852. 'glyphicon-certificate' => 'certificate',
  853. 'glyphicon-check' => 'check',
  854. 'glyphicon-chevron-down' => 'chevron-down',
  855. 'glyphicon-chevron-left' => 'chevron-left',
  856. 'glyphicon-chevron-right' => 'chevron-right',
  857. 'glyphicon-chevron-up' => 'chevron-up',
  858. 'glyphicon-circle-arrow-down' => 'circle-arrow-down',
  859. 'glyphicon-circle-arrow-left' => 'circle-arrow-left',
  860. 'glyphicon-circle-arrow-right' => 'circle-arrow-right',
  861. 'glyphicon-circle-arrow-up' => 'circle-arrow-up',
  862. 'glyphicon-cloud' => 'cloud',
  863. 'glyphicon-cloud-download' => 'cloud-download',
  864. 'glyphicon-cloud-upload' => 'cloud-upload',
  865. 'glyphicon-cog' => 'cog',
  866. 'glyphicon-collapse-down' => 'collapse-down',
  867. 'glyphicon-collapse-up' => 'collapse-up',
  868. 'glyphicon-comment' => 'comment',
  869. 'glyphicon-compressed' => 'compressed',
  870. 'glyphicon-copyright-mark' => 'copyright-mark',
  871. 'glyphicon-credit-card' => 'credit-card',
  872. 'glyphicon-cutlery' => 'cutlery',
  873. 'glyphicon-dashboard' => 'dashboard',
  874. 'glyphicon-download' => 'download',
  875. 'glyphicon-download-alt' => 'download-alt',
  876. 'glyphicon-earphone' => 'earphone',
  877. 'glyphicon-edit' => 'edit',
  878. 'glyphicon-eject' => 'eject',
  879. 'glyphicon-envelope' => 'envelope',
  880. 'glyphicon-euro' => 'euro',
  881. 'glyphicon-exclamation-sign' => 'exclamation-sign',
  882. 'glyphicon-expand' => 'expand',
  883. 'glyphicon-export' => 'export',
  884. 'glyphicon-eye-close' => 'eye-close',
  885. 'glyphicon-eye-open' => 'eye-open',
  886. 'glyphicon-facetime-video' => 'facetime-video',
  887. 'glyphicon-fast-backward' => 'fast-backward',
  888. 'glyphicon-fast-forward' => 'fast-forward',
  889. 'glyphicon-file' => 'file',
  890. 'glyphicon-film' => 'film',
  891. 'glyphicon-filter' => 'filter',
  892. 'glyphicon-fire' => 'fire',
  893. 'glyphicon-flag' => 'flag',
  894. 'glyphicon-flash' => 'flash',
  895. 'glyphicon-floppy-disk' => 'floppy-disk',
  896. 'glyphicon-floppy-open' => 'floppy-open',
  897. 'glyphicon-floppy-remove' => 'floppy-remove',
  898. 'glyphicon-floppy-save' => 'floppy-save',
  899. 'glyphicon-floppy-saved' => 'floppy-saved',
  900. 'glyphicon-folder-close' => 'folder-close',
  901. 'glyphicon-folder-open' => 'folder-open',
  902. 'glyphicon-font' => 'font',
  903. 'glyphicon-forward' => 'forward',
  904. 'glyphicon-fullscreen' => 'fullscreen',
  905. 'glyphicon-gbp' => 'gbp',
  906. 'glyphicon-gift' => 'gift',
  907. 'glyphicon-glass' => 'glass',
  908. 'glyphicon-globe' => 'globe',
  909. 'glyphicon-hand-down' => 'hand-down',
  910. 'glyphicon-hand-left' => 'hand-left',
  911. 'glyphicon-hand-right' => 'hand-right',
  912. 'glyphicon-hand-up' => 'hand-up',
  913. 'glyphicon-hd-video' => 'hd-video',
  914. 'glyphicon-hdd' => 'hdd',
  915. 'glyphicon-header' => 'header',
  916. 'glyphicon-headphones' => 'headphones',
  917. 'glyphicon-heart' => 'heart',
  918. 'glyphicon-heart-empty' => 'heart-empty',
  919. 'glyphicon-home' => 'home',
  920. 'glyphicon-import' => 'import',
  921. 'glyphicon-inbox' => 'inbox',
  922. 'glyphicon-indent-left' => 'indent-left',
  923. 'glyphicon-indent-right' => 'indent-right',
  924. 'glyphicon-info-sign' => 'info-sign',
  925. 'glyphicon-italic' => 'italic',
  926. 'glyphicon-leaf' => 'leaf',
  927. 'glyphicon-link' => 'link',
  928. 'glyphicon-list' => 'list',
  929. 'glyphicon-list-alt' => 'list-alt',
  930. 'glyphicon-lock' => 'lock',
  931. 'glyphicon-log-in' => 'log-in',
  932. 'glyphicon-log-out' => 'log-out',
  933. 'glyphicon-magnet' => 'magnet',
  934. 'glyphicon-map-marker' => 'map-marker',
  935. 'glyphicon-minus' => 'minus',
  936. 'glyphicon-minus-sign' => 'minus-sign',
  937. 'glyphicon-move' => 'move',
  938. 'glyphicon-music' => 'music',
  939. 'glyphicon-new-window' => 'new-window',
  940. 'glyphicon-off' => 'off',
  941. 'glyphicon-ok' => 'ok',
  942. 'glyphicon-ok-circle' => 'ok-circle',
  943. 'glyphicon-ok-sign' => 'ok-sign',
  944. 'glyphicon-open' => 'open',
  945. 'glyphicon-paperclip' => 'paperclip',
  946. 'glyphicon-pause' => 'pause',
  947. 'glyphicon-pencil' => 'pencil',
  948. 'glyphicon-phone' => 'phone',
  949. 'glyphicon-phone-alt' => 'phone-alt',
  950. 'glyphicon-picture' => 'picture',
  951. 'glyphicon-plane' => 'plane',
  952. 'glyphicon-play' => 'play',
  953. 'glyphicon-play-circle' => 'play-circle',
  954. 'glyphicon-plus' => 'plus',
  955. 'glyphicon-plus-sign' => 'plus-sign',
  956. 'glyphicon-print' => 'print',
  957. 'glyphicon-pushpin' => 'pushpin',
  958. 'glyphicon-qrcode' => 'qrcode',
  959. 'glyphicon-question-sign' => 'question-sign',
  960. 'glyphicon-random' => 'random',
  961. 'glyphicon-record' => 'record',
  962. 'glyphicon-refresh' => 'refresh',
  963. 'glyphicon-registration-mark' => 'registration-mark',
  964. 'glyphicon-remove' => 'remove',
  965. 'glyphicon-remove-circle' => 'remove-circle',
  966. 'glyphicon-remove-sign' => 'remove-sign',
  967. 'glyphicon-repeat' => 'repeat',
  968. 'glyphicon-resize-full' => 'resize-full',
  969. 'glyphicon-resize-horizontal' => 'resize-horizontal',
  970. 'glyphicon-resize-small' => 'resize-small',
  971. 'glyphicon-resize-vertical' => 'resize-vertical',
  972. 'glyphicon-retweet' => 'retweet',
  973. 'glyphicon-road' => 'road',
  974. 'glyphicon-save' => 'save',
  975. 'glyphicon-saved' => 'saved',
  976. 'glyphicon-screenshot' => 'screenshot',
  977. 'glyphicon-sd-video' => 'sd-video',
  978. 'glyphicon-search' => 'search',
  979. 'glyphicon-send' => 'send',
  980. 'glyphicon-share' => 'share',
  981. 'glyphicon-share-alt' => 'share-alt',
  982. 'glyphicon-shopping-cart' => 'shopping-cart',
  983. 'glyphicon-signal' => 'signal',
  984. 'glyphicon-sort' => 'sort',
  985. 'glyphicon-sort-by-alphabet' => 'sort-by-alphabet',
  986. 'glyphicon-sort-by-alphabet-alt' => 'sort-by-alphabet-alt',
  987. 'glyphicon-sort-by-attributes' => 'sort-by-attributes',
  988. 'glyphicon-sort-by-attributes-alt' => 'sort-by-attributes-alt',
  989. 'glyphicon-sort-by-order' => 'sort-by-order',
  990. 'glyphicon-sort-by-order-alt' => 'sort-by-order-alt',
  991. 'glyphicon-sound-5-1' => 'sound-5-1',
  992. 'glyphicon-sound-6-1' => 'sound-6-1',
  993. 'glyphicon-sound-7-1' => 'sound-7-1',
  994. 'glyphicon-sound-dolby' => 'sound-dolby',
  995. 'glyphicon-sound-stereo' => 'sound-stereo',
  996. 'glyphicon-star' => 'star',
  997. 'glyphicon-star-empty' => 'star-empty',
  998. 'glyphicon-stats' => 'stats',
  999. 'glyphicon-step-backward' => 'step-backward',
  1000. 'glyphicon-step-forward' => 'step-forward',
  1001. 'glyphicon-stop' => 'stop',
  1002. 'glyphicon-subtitles' => 'subtitles',
  1003. 'glyphicon-tag' => 'tag',
  1004. 'glyphicon-tags' => 'tags',
  1005. 'glyphicon-tasks' => 'tasks',
  1006. 'glyphicon-text-height' => 'text-height',
  1007. 'glyphicon-text-width' => 'text-width',
  1008. 'glyphicon-th' => 'th',
  1009. 'glyphicon-th-large' => 'th-large',
  1010. 'glyphicon-th-list' => 'th-list',
  1011. 'glyphicon-thumbs-down' => 'thumbs-down',
  1012. 'glyphicon-thumbs-up' => 'thumbs-up',
  1013. 'glyphicon-time' => 'time',
  1014. 'glyphicon-tint' => 'tint',
  1015. 'glyphicon-tower' => 'tower',
  1016. 'glyphicon-transfer' => 'transfer',
  1017. 'glyphicon-trash' => 'trash',
  1018. 'glyphicon-tree-conifer' => 'tree-conifer',
  1019. 'glyphicon-tree-deciduous' => 'tree-deciduous',
  1020. 'glyphicon-unchecked' => 'unchecked',
  1021. 'glyphicon-upload' => 'upload',
  1022. 'glyphicon-usd' => 'usd',
  1023. 'glyphicon-user' => 'user',
  1024. 'glyphicon-volume-down' => 'volume-down',
  1025. 'glyphicon-volume-off' => 'volume-off',
  1026. 'glyphicon-volume-up' => 'volume-up',
  1027. 'glyphicon-warning-sign' => 'warning-sign',
  1028. 'glyphicon-wrench' => 'wrench',
  1029. 'glyphicon-zoom-in' => 'zoom-in',
  1030. 'glyphicon-zoom-out' => 'zoom-out',
  1031. ];
  1032. $versions['3.0.1'] = $versions['3.0.0'];
  1033. $versions['3.0.2'] = $versions['3.0.1'];
  1034. $versions['3.0.3'] = $versions['3.0.2'];
  1035. $versions['3.1.0'] = $versions['3.0.3'];
  1036. $versions['3.1.1'] = $versions['3.1.0'];
  1037. $versions['3.2.0'] = $versions['3.1.1'];
  1038. $versions['3.3.0'] = array_merge($versions['3.2.0'], [
  1039. 'glyphicon-eur' => 'eur',
  1040. ]);
  1041. $versions['3.3.1'] = $versions['3.3.0'];
  1042. $versions['3.3.2'] = array_merge($versions['3.3.1'], [
  1043. 'glyphicon-alert' => 'alert',
  1044. 'glyphicon-apple' => 'apple',
  1045. 'glyphicon-baby-formula' => 'baby-formula',
  1046. 'glyphicon-bed' => 'bed',
  1047. 'glyphicon-bishop' => 'bishop',
  1048. 'glyphicon-bitcoin' => 'bitcoin',
  1049. 'glyphicon-blackboard' => 'blackboard',
  1050. 'glyphicon-cd' => 'cd',
  1051. 'glyphicon-console' => 'console',
  1052. 'glyphicon-copy' => 'copy',
  1053. 'glyphicon-duplicate' => 'duplicate',
  1054. 'glyphicon-education' => 'education',
  1055. 'glyphicon-equalizer' => 'equalizer',
  1056. 'glyphicon-erase' => 'erase',
  1057. 'glyphicon-grain' => 'grain',
  1058. 'glyphicon-hourglass' => 'hourglass',
  1059. 'glyphicon-ice-lolly' => 'ice-lolly',
  1060. 'glyphicon-ice-lolly-tasted' => 'ice-lolly-tasted',
  1061. 'glyphicon-king' => 'king',
  1062. 'glyphicon-knight' => 'knight',
  1063. 'glyphicon-lamp' => 'lamp',
  1064. 'glyphicon-level-up' => 'level-up',
  1065. 'glyphicon-menu-down' => 'menu-down',
  1066. 'glyphicon-menu-hamburger' => 'menu-hamburger',
  1067. 'glyphicon-menu-left' => 'menu-left',
  1068. 'glyphicon-menu-right' => 'menu-right',
  1069. 'glyphicon-menu-up' => 'menu-up',
  1070. 'glyphicon-modal-window' => 'modal-window',
  1071. 'glyphicon-object-align-bottom' => 'object-align-bottom',
  1072. 'glyphicon-object-align-horizontal' => 'object-align-horizontal',
  1073. 'glyphicon-object-align-left' => 'object-align-left',
  1074. 'glyphicon-object-align-right' => 'object-align-right',
  1075. 'glyphicon-object-align-top' => 'object-align-top',
  1076. 'glyphicon-object-align-vertical' => 'object-align-vertical',
  1077. 'glyphicon-oil' => 'oil',
  1078. 'glyphicon-open-file' => 'open-file',
  1079. 'glyphicon-option-horizontal' => 'option-horizontal',
  1080. 'glyphicon-option-vertical' => 'option-vertical',
  1081. 'glyphicon-paste' => 'paste',
  1082. 'glyphicon-pawn' => 'pawn',
  1083. 'glyphicon-piggy-bank' => 'piggy-bank',
  1084. 'glyphicon-queen' => 'queen',
  1085. 'glyphicon-ruble' => 'ruble',
  1086. 'glyphicon-save-file' => 'save-file',
  1087. 'glyphicon-scale' => 'scale',
  1088. 'glyphicon-scissors' => 'scissors',
  1089. 'glyphicon-subscript' => 'subscript',
  1090. 'glyphicon-sunglasses' => 'sunglasses',
  1091. 'glyphicon-superscript' => 'superscript',
  1092. 'glyphicon-tent' => 'tent',
  1093. 'glyphicon-text-background' => 'text-background',
  1094. 'glyphicon-text-color' => 'text-color',
  1095. 'glyphicon-text-size' => 'text-size',
  1096. 'glyphicon-triangle-bottom' => 'triangle-bottom',
  1097. 'glyphicon-triangle-left' => 'triangle-left',
  1098. 'glyphicon-triangle-right' => 'triangle-right',
  1099. 'glyphicon-triangle-top' => 'triangle-top',
  1100. 'glyphicon-yen' => 'yen',
  1101. ]);
  1102. $versions['3.3.4'] = array_merge($versions['3.3.2'], [
  1103. 'glyphicon-btc' => 'btc',
  1104. 'glyphicon-jpy' => 'jpy',
  1105. 'glyphicon-rub' => 'rub',
  1106. 'glyphicon-xbt' => 'xbt',
  1107. ]);
  1108. $versions['3.3.5'] = $versions['3.3.4'];
  1109. $versions['3.3.6'] = $versions['3.3.5'];
  1110. $versions['3.3.7'] = $versions['3.3.6'];
  1111. $versions['3.4.0'] = $versions['3.3.7'];
  1112. $versions['3.4.1'] = $versions['3.4.0'];
  1113. }
  1114. // Return a specific versions icon set.
  1115. if (isset($version) && isset($versions[$version])) {
  1116. return $versions[$version];
  1117. }
  1118. // Return the latest version.
  1119. return $versions[self::FRAMEWORK_VERSION];
  1120. }
  1121. /**
  1122. * Determines if the "cache_context.url.path.is_front" service exists.
  1123. *
  1124. * @return bool
  1125. * TRUE or FALSE
  1126. *
  1127. * @see \Drupal\bootstrap\Bootstrap::isFront
  1128. * @see \Drupal\bootstrap\Bootstrap::preprocess
  1129. * @see https://www.drupal.org/node/2829588
  1130. */
  1131. public static function hasIsFrontCacheContext() {
  1132. static $has_is_front_cache_context;
  1133. if (!isset($has_is_front_cache_context)) {
  1134. $has_is_front_cache_context = \Drupal::getContainer()->has('cache_context.url.path.is_front');
  1135. }
  1136. return $has_is_front_cache_context;
  1137. }
  1138. /**
  1139. * Initializes the active theme.
  1140. */
  1141. final public static function initialize() {
  1142. static $initialized = FALSE;
  1143. if (!$initialized) {
  1144. // Initialize the active theme.
  1145. $active_theme = self::getTheme();
  1146. // Include deprecated functions.
  1147. foreach ($active_theme->getAncestry() as $ancestor) {
  1148. if ($ancestor->getSetting('include_deprecated')) {
  1149. $files = $ancestor->fileScan('/^deprecated\.php$/');
  1150. if ($file = reset($files)) {
  1151. $ancestor->includeOnce($file->uri, FALSE);
  1152. }
  1153. }
  1154. }
  1155. $initialized = TRUE;
  1156. }
  1157. }
  1158. /**
  1159. * Checks whether a user is an administrator.
  1160. *
  1161. * @param \Drupal\Core\Session\AccountInterface $account
  1162. * Optional. A specific account to check. If not set, the currently logged
  1163. * in user account will be used.
  1164. *
  1165. * @return bool
  1166. * TRUE or FALSE
  1167. */
  1168. public static function isAdmin(AccountInterface $account = NULL) {
  1169. static $admins = [];
  1170. // Use the currently logged in user if no account was explicitly specified.
  1171. if (!$account) {
  1172. $account = \Drupal::currentUser();
  1173. }
  1174. $id = (int) $account->id();
  1175. if (!isset($admins[$id])) {
  1176. $admins[$id] = $account->hasPermission('access administration pages');
  1177. }
  1178. return $admins[$id];
  1179. }
  1180. /**
  1181. * Determines if the current path is the "front" page.
  1182. *
  1183. * *Note:* This method will not return `TRUE` if there is not a proper
  1184. * "cache_context.url.path.is_front" service defined.
  1185. *
  1186. * *Note:* If using this method in preprocess/render array logic, the proper
  1187. * #cache context must also be defined:
  1188. *
  1189. * ```php
  1190. * $variables['#cache']['contexts'][] = 'url.path.is_front';
  1191. * ```
  1192. *
  1193. * @return bool
  1194. * TRUE or FALSE
  1195. *
  1196. * @see \Drupal\bootstrap\Bootstrap::hasIsFrontCacheContext
  1197. * @see \Drupal\bootstrap\Bootstrap::preprocess
  1198. * @see https://www.drupal.org/node/2829588
  1199. */
  1200. public static function isFront() {
  1201. static $is_front;
  1202. if (!isset($is_front)) {
  1203. try {
  1204. $is_front = static::hasIsFrontCacheContext() ? \Drupal::service('path.matcher')->isFrontPage() : FALSE;
  1205. }
  1206. catch (\Exception $e) {
  1207. $is_front = FALSE;
  1208. }
  1209. }
  1210. return $is_front;
  1211. }
  1212. /**
  1213. * Wrapper to use new Messenger service or the legacy procedural function.
  1214. *
  1215. * This is to help support older installations without trigger deprecation
  1216. * notices for newer installations.
  1217. *
  1218. * @param string|\Drupal\Component\Render\MarkupInterface $message
  1219. * (optional) The translated message to be displayed to the user. For
  1220. * consistency with other messages, it should begin with a capital letter
  1221. * and end with a period.
  1222. * @param string $type
  1223. * (optional) The message's type. Defaults to 'status'. These values are
  1224. * supported:
  1225. * - 'status'
  1226. * - 'warning'
  1227. * - 'error'
  1228. * @param bool $repeat
  1229. * (optional) If this is FALSE and the message is already set, then the
  1230. * message won't be repeated. Defaults to FALSE.
  1231. *
  1232. * @see \Drupal\Core\Messenger\MessengerInterface
  1233. * @see drupal_set_message()
  1234. * @see https://www.drupal.org/node/2774931
  1235. *
  1236. * @deprecated in 8.x-3.18 and will be removed in a future release.
  1237. * Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead.
  1238. * @see https://www.drupal.org/project/bootstrap/issues/3096963
  1239. */
  1240. public static function message($message, $type = 'status', $repeat = FALSE) {
  1241. if (!isset(static::$messenger)) {
  1242. static::$messenger = \Drupal::hasService('messenger') ? \Drupal::service('messenger') : FALSE;
  1243. }
  1244. if (static::$messenger) {
  1245. static::$messenger->addMessage($message, $type, $repeat);
  1246. }
  1247. else {
  1248. drupal_set_message($message, $type, $repeat);
  1249. }
  1250. }
  1251. /**
  1252. * Preprocess theme hook variables.
  1253. *
  1254. * @param array $variables
  1255. * The variables array, passed by reference.
  1256. * @param string $hook
  1257. * The name of the theme hook.
  1258. * @param array $info
  1259. * The theme hook info.
  1260. */
  1261. public static function preprocess(array &$variables, $hook, array $info) {
  1262. // Do not statically cache this as the active theme may change.
  1263. $theme = static::getTheme();
  1264. $theme_name = $theme->getName();
  1265. // Handle preprocess managers.
  1266. static $drupal_static_fast;
  1267. if (!isset($drupal_static_fast)) {
  1268. $drupal_static_fast['preprocess_managers'] = &drupal_static(__METHOD__ . '__preprocessManagers', []);
  1269. $drupal_static_fast['theme_info'] = &drupal_static(__METHOD__ . '__themeInfo', []);
  1270. }
  1271. /* @var \Drupal\bootstrap\Plugin\PreprocessManager[] $preprocess_managers */
  1272. $preprocess_managers = &$drupal_static_fast['preprocess_managers'];
  1273. if (!isset($preprocess_managers[$theme_name])) {
  1274. $preprocess_managers[$theme_name] = new PreprocessManager($theme);
  1275. }
  1276. // Retrieve the theme info that will be used in the variables.
  1277. $theme_info = &$drupal_static_fast['theme_info'];
  1278. if (!isset($theme_info[$theme_name])) {
  1279. $theme_info[$theme_name] = $theme->getInfo();
  1280. $theme_info[$theme_name]['dev'] = $theme->isDev();
  1281. $theme_info[$theme_name]['livereload'] = $theme->livereloadUrl();
  1282. $theme_info[$theme_name]['name'] = $theme->getName();
  1283. $theme_info[$theme_name]['path'] = $theme->getPath();
  1284. $theme_info[$theme_name]['title'] = $theme->getTitle();
  1285. $theme_info[$theme_name]['settings'] = $theme->settings()->get();
  1286. $theme_info[$theme_name]['has_glyphicons'] = $theme->hasGlyphicons();
  1287. $theme_info[$theme_name]['query_string'] = \Drupal::getContainer()->get('state')->get('system.css_js_query_string') ?: '0';
  1288. }
  1289. // Retrieve the preprocess manager for this theme.
  1290. $preprocess_manager = $preprocess_managers[$theme_name];
  1291. // Add a global "is_admin" variable back to all templates.
  1292. if (!isset($variables['is_admin'])) {
  1293. $variables['is_admin'] = static::isAdmin();
  1294. }
  1295. // Adds a global "is_front" variable back to all templates.
  1296. // @see https://www.drupal.org/node/2829585
  1297. if (!isset($variables['is_front'])) {
  1298. $variables['is_front'] = static::isFront();
  1299. if (static::hasIsFrontCacheContext()) {
  1300. $variables['#cache']['contexts'][] = 'url.path.is_front';
  1301. }
  1302. }
  1303. // Ensure that any default theme hook variables exist. Due to how theme
  1304. // hook suggestion alters work, the variables provided are from the
  1305. // original theme hook, not the suggestion.
  1306. if (isset($info['variables'])) {
  1307. $variables = NestedArray::mergeDeepArray([$info['variables'], $variables], TRUE);
  1308. }
  1309. // Add active theme context.
  1310. // @see https://www.drupal.org/node/2630870
  1311. if (!isset($variables['theme'])) {
  1312. $variables['theme'] = $theme_info[$theme_name];
  1313. }
  1314. // Invoke necessary preprocess plugin.
  1315. if (isset($info['bootstrap preprocess'])) {
  1316. if ($preprocess_manager->hasDefinition($info['bootstrap preprocess'])) {
  1317. $class = $preprocess_manager->createInstance($info['bootstrap preprocess'], ['theme' => $theme]);
  1318. /** @var \Drupal\bootstrap\Plugin\Preprocess\PreprocessInterface $class */
  1319. $class->preprocess($variables, $hook, $info);
  1320. }
  1321. }
  1322. }
  1323. /**
  1324. * Renders a custom Twig template not registered in the theme system.
  1325. *
  1326. * Note: any template ending in .html.twig will be registered with the theme
  1327. * system automatically (that is simply how it works). For HTML based
  1328. * standalone Twig templates, just use .twig (without the .html prefix). For
  1329. * other file types, you may still use a prefix for the IDE to recognize the
  1330. * file type (e.g. .css.twig).
  1331. *
  1332. * @param string $path
  1333. * The path to the template.
  1334. * @param array $variables
  1335. * The variables to pass to the template.
  1336. * @param \Drupal\Core\Render\RenderContext $renderContext
  1337. * Optional. A RenderContext object to pass to the renderer.
  1338. *
  1339. * @return \Drupal\Component\Render\MarkupInterface
  1340. * The rendered template.
  1341. *
  1342. * @throws \RuntimeException
  1343. * If $path does not exist.
  1344. * @throws \InvalidArgumentException
  1345. * If $path references a Twig template already registered in the theme
  1346. * system.
  1347. */
  1348. public static function renderCustomTemplate($path, array $variables = [], RenderContext $renderContext = NULL) {
  1349. $realpath = realpath($path);
  1350. if (!file_exists($realpath)) {
  1351. throw new \RuntimeException(sprintf('Template does not exist: %s', $realpath));
  1352. }
  1353. // Ensure provided template isn't actually registered in the theme system.
  1354. $registry = static::themeRegistry()->get();
  1355. foreach ($registry as $hook => $info) {
  1356. // Only process template based theme hooks.
  1357. if (!isset($info['path']) || !isset($info['template'])) {
  1358. continue;
  1359. }
  1360. $registered = realpath($info['path'] . '/' . $info['template'] . '.html.twig');
  1361. if ($registered === $realpath) {
  1362. $basename = basename($path);
  1363. $example = "\n\n\$build = [\n '#theme' => '$hook',\n /* Other properties */\n];\n\Drupal::service('renderer')->renderPlain(\$build);\n\n";
  1364. throw new \InvalidArgumentException(sprintf('The template provided is not a standalone Twig template: "%s". This template is already registered in Drupal\'s Theme System as "%s". If this template is intended to be truly standalone, you can change the file extension from ".html.twig" to just ".twig". Otherwise, if this is a properly registered template in the Theme System, you should render it using Drupal\'s existing Render API and not this method: %s', $basename, $hook, $example));
  1365. }
  1366. }
  1367. $template = file_get_contents($realpath);
  1368. if (!isset($renderContext)) {
  1369. $renderContext = new RenderContext();
  1370. }
  1371. // Render the template.
  1372. $output = static::renderer()->executeInRenderContext($renderContext, function () use ($template, $variables) {
  1373. return static::twig()->createTemplate($template)->render($variables);
  1374. });
  1375. return Markup::create($output);
  1376. }
  1377. /**
  1378. * Helper function for writing data to the file system.
  1379. *
  1380. * Note: this is specifically designed with replacing chunks of existing
  1381. * data in mind.
  1382. *
  1383. * @param string $path
  1384. * The path to the file where the data will be written.
  1385. * @param string $data
  1386. * The data to write to $file.
  1387. * @param string $start
  1388. * Optional. A marker determining where to begin injecting $data.
  1389. * Note: this value is used within a regular expression.
  1390. * @param string $end
  1391. * Optional. A marker determining where to stop injecting $data. This is
  1392. * primarily useful for replacing a "chunk" of data within a file.
  1393. * Note: this value is used within a regular expression.
  1394. *
  1395. * @return bool
  1396. * TRUE if the file was successfully written, FALSE otherwise.
  1397. */
  1398. public static function putContents($path, $data, $start = NULL, $end = NULL) {
  1399. $realpath = realpath($path) ?: $path;
  1400. // Markers used, build regular expression to split any existing content.
  1401. if ($start || $end) {
  1402. $regExp = [];
  1403. if ($start) {
  1404. $regExp[] = preg_quote($start, '/');
  1405. }
  1406. if ($end) {
  1407. $regExp[] = preg_quote($end, '/');
  1408. }
  1409. $regExp = implode('|', $regExp);
  1410. $parts = @preg_split("/$regExp/", @file_get_contents($realpath) ?: '') ?: [];
  1411. $replaced = isset($parts[0]) ? trim($parts[0]) . "\n" : '';
  1412. $replaced .= "$data\n";
  1413. $replaced .= isset($parts[2]) ? trim($parts[2]) . "\n" : '';
  1414. $data = $replaced;
  1415. }
  1416. return !!file_put_contents($realpath, $data) !== FALSE;
  1417. }
  1418. /**
  1419. * Retrieves the Renderer service.
  1420. *
  1421. * @return \Drupal\Core\Render\Renderer
  1422. * The Renderer service.
  1423. */
  1424. public static function renderer() {
  1425. if (!isset(static::$renderer)) {
  1426. static::$renderer = \Drupal::service('renderer');
  1427. }
  1428. return static::$renderer;
  1429. }
  1430. /**
  1431. * Retrieves the Theme Registry service.
  1432. *
  1433. * @return \Drupal\Core\Theme\Registry
  1434. * The Theme Registry service.
  1435. */
  1436. public static function themeRegistry() {
  1437. if (!isset(static::$themeRegistry)) {
  1438. static::$themeRegistry = \Drupal::service('theme.registry');
  1439. }
  1440. return static::$themeRegistry;
  1441. }
  1442. /**
  1443. * Ensures a value is typecast to a string, rendering an array if necessary.
  1444. *
  1445. * @param string|array $value
  1446. * The value to typecast, passed by reference.
  1447. *
  1448. * @return string
  1449. * The typecast string value.
  1450. */
  1451. public static function toString(&$value) {
  1452. return (string) (Element::isRenderArray($value) ? Element::create($value)->renderPlain() : $value);
  1453. }
  1454. /**
  1455. * Retrieves the Twig service.
  1456. *
  1457. * @return \Drupal\Core\Template\TwigEnvironment
  1458. * The Twig service.
  1459. */
  1460. public static function twig() {
  1461. if (!isset(static::$twig)) {
  1462. static::$twig = \Drupal::service('twig');
  1463. }
  1464. return static::$twig;
  1465. }
  1466. }

Classes

Name Description
Bootstrap The primary class for the Drupal Bootstrap base theme.