common.inc 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557
  1. <?php
  2. /**
  3. * @file
  4. * List of common helper functions for use in Drupal Bootstrap based themes.
  5. */
  6. /**
  7. * @defgroup utility Utilities
  8. *
  9. * List of common helper functions for use in Drupal Bootstrap based themes.
  10. *
  11. * @{
  12. */
  13. define('BOOTSTRAP_VERSION_MAJOR', 3);
  14. define('BOOTSTRAP_VERSION_MINOR', 4);
  15. define('BOOTSTRAP_VERSION_PATCH', 1);
  16. define('BOOTSTRAP_VERSION', BOOTSTRAP_VERSION_MAJOR . '.' . BOOTSTRAP_VERSION_MINOR . '.' . BOOTSTRAP_VERSION_PATCH);
  17. /**
  18. * Converts an element description into a tooltip based on certain criteria.
  19. *
  20. * @param array $element
  21. * An element render array, passed by reference.
  22. * @param array $target
  23. * The target element render array the tooltip is to be attached to, passed
  24. * by reference. If not set, it will default to the $element passed.
  25. * @param bool $input_only
  26. * Toggle determining whether or not to only convert input elements.
  27. * @param int $length
  28. * The length of characters to determine if description is "simple".
  29. */
  30. function bootstrap_element_smart_description(array &$element, array &$target = NULL, $input_only = TRUE, $length = NULL) {
  31. // Determine if tooltips are enabled.
  32. static $enabled;
  33. if (!isset($enabled)) {
  34. $enabled = bootstrap_setting('tooltip_enabled') && bootstrap_setting('forms_smart_descriptions');
  35. }
  36. // Immediately return if "simple" tooltip descriptions are not enabled.
  37. if (!$enabled) {
  38. return;
  39. }
  40. // Allow a different element to attach the tooltip.
  41. if (!isset($target)) {
  42. $target = &$element;
  43. }
  44. // Retrieve the length limit for smart descriptions.
  45. if (!isset($length)) {
  46. $length = (int) bootstrap_setting('forms_smart_descriptions_limit');
  47. // Disable length checking by setting it to FALSE if empty.
  48. if (empty($length)) {
  49. $length = FALSE;
  50. }
  51. }
  52. // Retrieve the allowed tags for smart descriptions. This is primarily used
  53. // for display purposes only (i.e. non-UI/UX related elements that wouldn't
  54. // require a user to "click", like a link).
  55. $allowed_tags = array_filter(array_unique(array_map('trim', explode(',', bootstrap_setting('forms_smart_descriptions_allowed_tags') . ''))));
  56. // Disable length checking by setting it to FALSE if empty.
  57. if (empty($allowed_tags)) {
  58. $allowed_tags = FALSE;
  59. }
  60. $html = FALSE;
  61. $type = !empty($element['#type']) ? $element['#type'] : FALSE;
  62. // Return if element or target shouldn't have "simple" tooltip descriptions.
  63. if (($input_only && !isset($target['#input']))
  64. // Ignore text_format elements.
  65. // @see https://www.drupal.org/node/2478339
  66. || $type === 'text_format'
  67. // Ignore if the actual element has no #description set.
  68. || empty($element['#description'])
  69. // Ignore if the target element already has a "data-toggle" attribute set.
  70. || !empty($target['#attributes']['data-toggle'])
  71. // Ignore if the target element is #disabled.
  72. || isset($target['#disabled'])
  73. // Ignore if either the actual element or target element has an explicit
  74. // #smart_description property set to FALSE.
  75. || (isset($element['#smart_description']) && !$element['#smart_description'])
  76. || (isset($target['#smart_description']) && !$target['#smart_description'])
  77. // Ignore if the description is not "simple".
  78. || !_bootstrap_is_simple_string($element['#description'], $length, $allowed_tags, $html)
  79. ) {
  80. // Set the both the actual element and the target element
  81. // #smart_description property to FALSE.
  82. $element['#smart_description'] = FALSE;
  83. $target['#smart_description'] = FALSE;
  84. return;
  85. }
  86. // Default property (on the element itself).
  87. $property = 'attributes';
  88. // Add the tooltip to the #label_attributes property for 'checkbox'
  89. // and 'radio' elements.
  90. if ($type === 'checkbox' || $type === 'radio') {
  91. $property = 'label_attributes';
  92. }
  93. // Add the tooltip to the #wrapper_attributes property for 'checkboxes'
  94. // and 'radios' elements.
  95. elseif ($type === 'checkboxes' || $type === 'radios') {
  96. $property = 'wrapper_attributes';
  97. }
  98. // Add the tooltip to the #input_group_attributes property for elements
  99. // that have valid input groups set.
  100. elseif ((!empty($element['#field_prefix']) || !empty($element['#field_suffix'])) && (!empty($element['#input_group']) || !empty($element['#input_group_button']))) {
  101. $property = 'input_group_attributes';
  102. }
  103. // Retrieve the proper attributes array.
  104. $attributes = &_bootstrap_get_attributes($target, $property);
  105. // Set the tooltip attributes.
  106. $attributes['title'] = $allowed_tags !== FALSE ? filter_xss($element['#description'], $allowed_tags) : $element['#description'];
  107. $attributes['data-toggle'] = 'tooltip';
  108. if ($html || $allowed_tags === FALSE) {
  109. $attributes['data-html'] = 'true';
  110. }
  111. // Remove the element description so it isn't (re-)rendered later.
  112. unset($element['#description']);
  113. }
  114. /**
  115. * Retrieves a plugin that uses backported CDN Provider logic from 8.x-3.x.
  116. *
  117. * @param string $theme
  118. * The name of a given theme; defaults to the currently active theme.
  119. *
  120. * @return \Drupal\bootstrap\Backport\Plugin\Provider\ProviderBase|false
  121. * A CDN Provider instance or FALSE if provider cannot use backported code.
  122. */
  123. function _bootstrap_backport_cdn_provider($theme = NULL, $provider = NULL) {
  124. /** @var \Drupal\bootstrap\Backport\Plugin\Provider\ProviderBase[] $providers */
  125. static $providers = array();
  126. // If no key is given, use the current theme if we can determine it.
  127. if (!isset($theme)) {
  128. $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : '';
  129. }
  130. if (!isset($provider)) {
  131. $provider = bootstrap_setting('cdn_provider', $theme);
  132. }
  133. if (!isset($providers[$theme][$provider])) {
  134. $providers[$theme][$provider] = FALSE;
  135. if (bootstrap_setting('cdn_use_backported_code', $theme, 'bootstrap', TRUE)) {
  136. if ($provider === 'jsdelivr') {
  137. require_once __DIR__ . '/cdn/ProviderBase.php';
  138. require_once __DIR__ . '/cdn/JsDelivr.php';
  139. $providers[$theme][$provider] = new \Drupal\bootstrap\Backport\Plugin\Provider\JsDelivr();
  140. }
  141. elseif ($provider === 'custom') {
  142. require_once __DIR__ . '/cdn/ProviderBase.php';
  143. require_once __DIR__ . '/cdn/Custom.php';
  144. $providers[$theme][$provider] = new \Drupal\bootstrap\Backport\Plugin\Provider\Custom();
  145. }
  146. }
  147. }
  148. return $providers[$theme][$provider];
  149. }
  150. /**
  151. * Retrieves CDN assets for the active provider, if any.
  152. *
  153. * @param string|array $type
  154. * The type of asset to retrieve: "css" or "js", defaults to an array
  155. * array containing both if not set.
  156. * @param string $provider
  157. * The name of a specific CDN provider to use, defaults to the active provider
  158. * set in the theme settings.
  159. * @param string $theme
  160. * The name of a specific theme the settings should be retrieved from,
  161. * defaults to the active theme.
  162. *
  163. * @return array
  164. * If $type is a string or an array with only one (1) item in it, the assets
  165. * are returned as an indexed array of files. Otherwise, an associative array
  166. * is returned keyed by the type.
  167. */
  168. function bootstrap_get_cdn_assets($type = NULL, $provider = NULL, $theme = NULL) {
  169. bootstrap_include('bootstrap', 'includes/cdn.inc');
  170. $original_type = $type;
  171. $assets = array();
  172. $types = array();
  173. // If no type is set, return all CSS and JS.
  174. if (!isset($type)) {
  175. $types = array('css', 'js');
  176. }
  177. elseif (isset($type)) {
  178. if (!is_array($type)) {
  179. $type = array($type);
  180. }
  181. $types = $type;
  182. }
  183. // Ensure default arrays exist for the requested types.
  184. foreach ($types as $type) {
  185. $assets[$type] = array();
  186. }
  187. // Retrieve the CDN provider from the theme.
  188. if (!isset($provider)) {
  189. $provider = bootstrap_setting('cdn_provider', $theme);
  190. }
  191. // Immediately return if there's no provider set.
  192. if (empty($provider)) {
  193. return array();
  194. }
  195. // Check if the backported 8.x-3.x jsDelivr code should be used.
  196. if ($provider = _bootstrap_backport_cdn_provider($theme, $provider)) {
  197. $framework = array();
  198. $provider->alterFrameworkLibrary($framework);
  199. $assets = array_map(function ($data) {
  200. $assets = array();
  201. foreach ($data as $info) {
  202. if (isset($info['data'])) {
  203. $assets[] = $info['data'];
  204. }
  205. }
  206. return $assets;
  207. }, array_intersect_key($framework, array_flip($types)));
  208. }
  209. // Otherwise, use the legacy CDN provider code.
  210. else {
  211. if (!empty($provider) && ($data = bootstrap_cdn_provider($provider))) {
  212. // Alter the assets based on provider.
  213. $function = 'bootstrap_bootstrap_cdn_provider_' . $provider . '_assets_alter';
  214. if (function_exists($function)) {
  215. $function($data, $theme);
  216. }
  217. // Iterate over each type.
  218. foreach ($types as $type) {
  219. if (variable_get("preprocess_$type", FALSE) && !empty($data['min'][$type])) {
  220. $assets[$type] = $data['min'][$type];
  221. }
  222. elseif (!empty($data[$type])) {
  223. $assets[$type] = $data[$type];
  224. }
  225. }
  226. } }
  227. return is_string($original_type) ? $assets[$original_type] : $assets;
  228. }
  229. /**
  230. * Return information from the .info file of a theme (and possible base themes).
  231. *
  232. * @param string $theme_key
  233. * The machine name of the theme.
  234. * @param string $key
  235. * The key name of the item to return from the .info file. This value can
  236. * include "][" to automatically attempt to traverse any arrays.
  237. * @param bool $base_themes
  238. * Recursively search base themes, defaults to TRUE.
  239. *
  240. * @return string|array|false
  241. * A string or array depending on the type of value and if a base theme also
  242. * contains the same $key, FALSE if no $key is found.
  243. */
  244. function bootstrap_get_theme_info($theme_key = NULL, $key = NULL, $base_themes = TRUE) {
  245. // If no $theme_key is given, use the current theme if we can determine it.
  246. if (!isset($theme_key)) {
  247. $theme_key = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : FALSE;
  248. }
  249. if ($theme_key) {
  250. $themes = list_themes();
  251. if (!empty($themes[$theme_key])) {
  252. $theme = $themes[$theme_key];
  253. // If a key name was specified, return just that array.
  254. if ($key) {
  255. $value = FALSE;
  256. // Recursively add base theme values.
  257. if ($base_themes && isset($theme->base_themes)) {
  258. foreach (array_keys($theme->base_themes) as $base_theme) {
  259. $value = bootstrap_get_theme_info($base_theme, $key);
  260. }
  261. }
  262. if (!empty($themes[$theme_key])) {
  263. $info = $themes[$theme_key]->info;
  264. // Allow array traversal.
  265. $keys = explode('][', $key);
  266. foreach ($keys as $parent) {
  267. if (isset($info[$parent])) {
  268. $info = $info[$parent];
  269. }
  270. else {
  271. $info = FALSE;
  272. }
  273. }
  274. if (is_array($value)) {
  275. if (!empty($info)) {
  276. if (!is_array($info)) {
  277. $info = array($info);
  278. }
  279. $value = drupal_array_merge_deep($value, $info);
  280. }
  281. }
  282. else {
  283. if (!empty($info)) {
  284. if (empty($value)) {
  285. $value = $info;
  286. }
  287. else {
  288. if (!is_array($value)) {
  289. $value = array($value);
  290. }
  291. if (!is_array($info)) {
  292. $info = array($info);
  293. }
  294. $value = drupal_array_merge_deep($value, $info);
  295. }
  296. }
  297. }
  298. }
  299. return $value;
  300. }
  301. // If no info $key was specified, just return the entire info array.
  302. return $theme->info;
  303. }
  304. }
  305. return FALSE;
  306. }
  307. /**
  308. * Includes a theme file.
  309. *
  310. * @param string $theme
  311. * Name of the theme to use for base path.
  312. * @param string $path
  313. * Path relative to $theme.
  314. *
  315. * @return string|false
  316. * The absolute path to the include file or FALSE if it doesn't exist.
  317. */
  318. function bootstrap_include($theme, $path) {
  319. static $included = array();
  320. static $themes = array();
  321. if (!isset($themes[$theme])) {
  322. $themes[$theme] = drupal_get_path('theme', $theme);
  323. }
  324. if (!isset($included["$theme:$path"])) {
  325. $file = DRUPAL_ROOT . '/' . $themes[$theme] . '/' . $path;
  326. if (file_exists($file)) {
  327. include_once $file;
  328. }
  329. else {
  330. $file = FALSE;
  331. }
  332. $included["$theme:$path"] = $file;
  333. }
  334. return $included["$theme:$path"];
  335. }
  336. /**
  337. * Retrieves a setting for the current theme or for a given theme.
  338. *
  339. * This is a wrapper for theme_get_setting(), ensuring to use deprecated
  340. * setting values instead.
  341. *
  342. * @param string $name
  343. * The name of the setting to be retrieved.
  344. * @param string $theme
  345. * The name of a given theme; defaults to the currently active theme.
  346. * @param string $prefix
  347. * The prefix used on the $name of the setting, this will be appended with
  348. * "_" automatically if set.
  349. * @param mixed $default
  350. * The default value to return if setting doesn't exist or is not set.
  351. *
  352. * @return mixed
  353. * The value of the requested setting, NULL if the setting does not exist.
  354. *
  355. * @see theme_get_setting()
  356. *
  357. * @todo Refactor in 7.x-4.x and get rid of the deprecated settings.
  358. */
  359. function bootstrap_setting($name, $theme = NULL, $prefix = 'bootstrap', $default = NULL) {
  360. $prefix = !empty($prefix) ? $prefix . '_' : '';
  361. $setting = theme_get_setting($prefix . $name, $theme);
  362. switch ($prefix . $name) {
  363. case 'bootstrap_cdn_provider':
  364. $deprecated = theme_get_setting('bootstrap_cdn', $theme);
  365. if (isset($deprecated)) {
  366. $setting = empty($deprecated) ? '' : 'jsdelivr';
  367. }
  368. break;
  369. case 'bootstrap_cdn_jsdelivr_version':
  370. $deprecated = theme_get_setting('bootstrap_cdn', $theme);
  371. if (isset($deprecated)) {
  372. $setting = empty($deprecated) ? BOOTSTRAP_VERSION : $deprecated;
  373. }
  374. break;
  375. case 'bootstrap_cdn_jsdelivr_theme':
  376. $deprecated = theme_get_setting('bootstrap_bootswatch', $theme);
  377. if (isset($deprecated)) {
  378. $setting = empty($deprecated) ? 'bootstrap' : $deprecated;
  379. }
  380. break;
  381. case 'bootstrap_forms_smart_descriptions':
  382. $deprecated = theme_get_setting('bootstrap_tooltip_descriptions', $theme);
  383. if (isset($deprecated)) {
  384. $setting = (int) !empty($deprecated);
  385. }
  386. break;
  387. case 'bootstrap_forms_smart_descriptions_limit':
  388. $deprecated = theme_get_setting('bootstrap_tooltip_descriptions_length', $theme);
  389. if (isset($deprecated)) {
  390. $setting = (int) !empty($deprecated);
  391. }
  392. break;
  393. }
  394. return isset($setting) ? $setting : $default;
  395. }
  396. /**
  397. * Sets a theme setting.
  398. *
  399. * @param string $name
  400. * The name of the setting to set.
  401. * @param mixed $value
  402. * The value of the setting to set.
  403. * @param string $prefix
  404. * The prefix used on the $name of the setting, this will be appended with
  405. * "_" automatically if set.
  406. * @param string $theme
  407. * The name of a given theme; defaults to the currently active theme.
  408. */
  409. function _bootstrap_set_setting($name, $value, $prefix = 'bootstrap', $theme = NULL) {
  410. // If no key is given, use the current theme if we can determine it.
  411. if (!isset($theme)) {
  412. $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : '';
  413. }
  414. // Trigger at least once so it can populate defaults.
  415. theme_get_setting('', $theme);
  416. // Retrieve the cached settings.
  417. $cache = &drupal_static('theme_get_setting', array());
  418. $settings = $cache[$theme];
  419. // Set the value.
  420. $prefix = !empty($prefix) ? $prefix . '_' : '';
  421. $settings["$prefix$name"] = $value;
  422. // Set the settings.
  423. variable_set('theme_' . $theme . '_settings', $settings);
  424. }
  425. /**
  426. * Retrieves an element's "attributes" array.
  427. *
  428. * @param array $element
  429. * The individual renderable array element. It is possible to also pass the
  430. * $variables parameter in [pre]process functions and it will logically
  431. * determine the correct path to that particular theme hook's attribute array.
  432. * Passed by reference.
  433. * @param string $property
  434. * Determines which attributes array to retrieve. By default, this is the
  435. * normal attributes, but can be "wrapper_attributes" or
  436. * "input_group_attributes".
  437. *
  438. * @return array
  439. * The attributes array. Passed by reference.
  440. */
  441. function &_bootstrap_get_attributes(array &$element, $property = 'attributes') {
  442. // Attempt to retrieve a renderable element attributes first.
  443. if (
  444. isset($element['#type']) ||
  445. isset($element['#theme']) ||
  446. isset($element['#pre_render']) ||
  447. isset($element['#markup']) ||
  448. isset($element['#theme_wrappers']) ||
  449. isset($element["#$property"])
  450. ) {
  451. if (!isset($element["#$property"])) {
  452. $element["#$property"] = array();
  453. }
  454. return $element["#$property"];
  455. }
  456. // Treat $element as if it were a [pre]process function $variables parameter
  457. // and look for a renderable "element".
  458. elseif (isset($element['element'])) {
  459. if (!isset($element['element']["#$property"])) {
  460. $element['element']["#$property"] = array();
  461. }
  462. return $element['element']["#$property"];
  463. }
  464. // If all else fails, create (if needed) a default "attributes" array. This
  465. // will, at the very least, either work or cause an error that can be tracked.
  466. if (!isset($element[$property])) {
  467. $element[$property] = array();
  468. }
  469. return $element[$property];
  470. }
  471. /**
  472. * Retrieves an element's "class" array.
  473. *
  474. * @param array $element
  475. * The individual renderable array element. It is possible to also pass the
  476. * $variables parameter in [pre]process functions and it will logically
  477. * determine the correct path to that particular theme hook's classes array.
  478. * Passed by reference.
  479. * @param string $property
  480. * Determines which attributes array to retrieve. By default, this is the
  481. * normal attributes, but can be "wrapper_attributes" or
  482. * "input_group_attributes".
  483. *
  484. * @return array
  485. * The classes array. Passed by reference.
  486. */
  487. function &_bootstrap_get_classes(array &$element, $property = 'attributes') {
  488. $attributes = &_bootstrap_get_attributes($element, $property);
  489. if (!isset($attributes['class'])) {
  490. $attributes['class'] = array();
  491. }
  492. // Contrib modules have a very bad habit of frequently adding classes as
  493. // strings, convert them to a proper array.
  494. // @see https://www.drupal.org/node/2269653
  495. elseif (!is_array($attributes['class'])) {
  496. $attributes['class'] = explode(' ', $attributes['class']);
  497. }
  498. // Ensure classes are not duplicated.
  499. $attributes['class'] = array_unique($attributes['class']);
  500. return $attributes['class'];
  501. }
  502. /**
  503. * Adds a class to an element's render array.
  504. *
  505. * @param string|array $class
  506. * An individual class or an array of classes to add.
  507. * @param array $element
  508. * The individual renderable array element. It is possible to also pass the
  509. * $variables parameter in [pre]process functions and it will logically
  510. * determine the correct path to that particular theme hook's classes array.
  511. * Passed by reference.
  512. * @param string $property
  513. * Determines which attributes array to retrieve. By default, this is the
  514. * normal attributes, but can be "wrapper_attributes" or
  515. * "input_group_attributes".
  516. */
  517. function _bootstrap_add_class($class, array &$element, $property = 'attributes') {
  518. // Retrieve the element's classes.
  519. $classes = &_bootstrap_get_classes($element, $property);
  520. // Convert the class to an array.
  521. if (!is_array($class)) {
  522. $class = array($class);
  523. }
  524. // Iterate over all classes to add.
  525. foreach ($class as $_class) {
  526. // Ensure the class to add does not yet already exist.
  527. if (!in_array($_class, $classes)) {
  528. $classes[] = $_class;
  529. }
  530. }
  531. }
  532. /**
  533. * Removes a class from an element's render array.
  534. *
  535. * @param string|array $class
  536. * An individual class or an array of classes to remove.
  537. * @param array $element
  538. * The individual renderable array element. It is possible to also pass the
  539. * $variables parameter in [pre]process functions and it will logically
  540. * determine the correct path to that particular theme hook's classes array.
  541. * Passed by reference.
  542. * @param string $property
  543. * Determines which attributes array to retrieve. By default, this is the
  544. * normal attributes, but can be "wrapper_attributes" or
  545. * "input_group_attributes".
  546. */
  547. function _bootstrap_remove_class($class, array &$element, $property = 'attributes') {
  548. // Retrieve the element's classes.
  549. $classes = &_bootstrap_get_classes($element, $property);
  550. // Convert the class to an array.
  551. if (!is_array($class)) {
  552. $class = array($class);
  553. }
  554. // Iterate over all classes to add.
  555. foreach ($class as $_class) {
  556. $key = array_search($_class, $classes);
  557. if ($key !== FALSE) {
  558. unset($classes[$key]);
  559. }
  560. }
  561. }
  562. /**
  563. * Returns a list of base themes for active or provided theme.
  564. *
  565. * @param string $theme_key
  566. * The machine name of the theme to check, if not set the active theme name
  567. * will be used.
  568. * @param bool $include_theme_key
  569. * Whether to append the returned list with $theme_key.
  570. *
  571. * @return array
  572. * An indexed array of base themes.
  573. */
  574. function _bootstrap_get_base_themes($theme_key = NULL, $include_theme_key = FALSE) {
  575. static $themes;
  576. if (!isset($theme_key)) {
  577. $theme_key = $GLOBALS['theme_key'];
  578. }
  579. if (!isset($themes[$theme_key])) {
  580. $themes[$theme_key] = array_unique(array_filter((array) bootstrap_get_theme_info($theme_key, 'base theme')));
  581. }
  582. if ($include_theme_key) {
  583. $themes[$theme_key][] = $theme_key;
  584. }
  585. return $themes[$theme_key];
  586. }
  587. /**
  588. * Retrieves the full base/sub-theme ancestry of a theme.
  589. *
  590. * @param string $theme_key
  591. * The machine name of the theme to check, if not set the active theme name
  592. * will be used.
  593. * @param bool $reverse
  594. * Whether or not to return the array of themes in reverse order, where the
  595. * active theme is the first entry.
  596. *
  597. * @return string[]
  598. * An array of theme names.
  599. */
  600. function _bootstrap_get_ancestry($theme_key = NULL, $reverse = FALSE) {
  601. $ancestry = _bootstrap_get_base_themes($theme_key, TRUE);
  602. return $reverse ? array_reverse($ancestry) : $ancestry;
  603. }
  604. /**
  605. * Wrapper for the core file_scan_directory() function.
  606. *
  607. * Finds all files that match a given mask in a given directory and then caches
  608. * the results. A general site cache clear will force new scans to be initiated
  609. * for already cached directories.
  610. *
  611. * @param string $dir
  612. * The base directory or URI to scan, without trailing slash.
  613. * @param string $mask
  614. * The preg_match() regular expression of the files to find.
  615. * @param array $options
  616. * Additional options to pass to file_scan_directory().
  617. *
  618. * @return array
  619. * An associative array (keyed on the chosen key) of objects with 'uri',
  620. * 'filename', and 'name' members corresponding to the matching files.
  621. *
  622. * @see file_scan_directory()
  623. */
  624. function _bootstrap_file_scan_directory($dir, $mask, array $options = array()) {
  625. $files = &drupal_static(__FUNCTION__, array());
  626. // Generate a unique cache identifier for all parameters passed as a change
  627. // in any of them would return different results.
  628. $cid = 'theme_registry:bootstrap:files:' . drupal_hash_base64(serialize(func_get_args()));
  629. // Load from DB cache or scan filesystem if files are not statically cached.
  630. if (!isset($files[$cid])) {
  631. if (($cache = cache_get($cid)) && isset($cache->data)) {
  632. $files[$cid] = $cache->data;
  633. }
  634. else {
  635. $files[$cid] = file_scan_directory($dir, $mask, $options);
  636. cache_set($cid, $files[$cid]);
  637. }
  638. }
  639. return $files[$cid];
  640. }
  641. /**
  642. * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
  643. *
  644. * Very similar to core's filter_xss(). It does, however, include the addition
  645. * of the "span", "div" and "i" elements which are commonly used in Bootstrap.
  646. *
  647. * @param string $string
  648. * The string with raw HTML in it. It will be stripped of everything that can
  649. * cause an XSS attack.
  650. * @param array $allowed_tags
  651. * An array of allowed tags.
  652. *
  653. * @return string
  654. * An XSS safe version of $string, or an empty string if $string is not
  655. * valid UTF-8.
  656. *
  657. * @see filter_xss()
  658. * @see filter_xss_admin()
  659. *
  660. * @deprecated Use filter_xss() or filter_xss_admin() instead.
  661. * Will be removed in a future release.
  662. */
  663. function _bootstrap_filter_xss($string, array $allowed_tags = NULL) {
  664. if (is_null($allowed_tags)) {
  665. $allowed_tags = array(
  666. // Inline elements.
  667. 'a',
  668. 'cite',
  669. 'em',
  670. 'i',
  671. 'span',
  672. 'strong',
  673. // Block elements.
  674. 'blockquote',
  675. 'code',
  676. 'div',
  677. 'ul',
  678. 'ol',
  679. 'li',
  680. 'dl',
  681. 'dt',
  682. 'dd',
  683. );
  684. }
  685. return filter_xss($string, $allowed_tags);
  686. }
  687. /**
  688. * Returns a list of available Bootstrap Glyphicons.
  689. *
  690. * @param string $version
  691. * The specific version of glyphicons to return. If not set, the latest
  692. * BOOTSTRAP_VERSION will be used.
  693. *
  694. * @return array
  695. * An associative array of icons keyed by their classes.
  696. */
  697. function _bootstrap_glyphicons($version = NULL) {
  698. static $versions;
  699. if (!isset($versions)) {
  700. $versions = array();
  701. $versions['3.0.0'] = array(
  702. // Class => Name.
  703. 'glyphicon-adjust' => 'adjust',
  704. 'glyphicon-align-center' => 'align-center',
  705. 'glyphicon-align-justify' => 'align-justify',
  706. 'glyphicon-align-left' => 'align-left',
  707. 'glyphicon-align-right' => 'align-right',
  708. 'glyphicon-arrow-down' => 'arrow-down',
  709. 'glyphicon-arrow-left' => 'arrow-left',
  710. 'glyphicon-arrow-right' => 'arrow-right',
  711. 'glyphicon-arrow-up' => 'arrow-up',
  712. 'glyphicon-asterisk' => 'asterisk',
  713. 'glyphicon-backward' => 'backward',
  714. 'glyphicon-ban-circle' => 'ban-circle',
  715. 'glyphicon-barcode' => 'barcode',
  716. 'glyphicon-bell' => 'bell',
  717. 'glyphicon-bold' => 'bold',
  718. 'glyphicon-book' => 'book',
  719. 'glyphicon-bookmark' => 'bookmark',
  720. 'glyphicon-briefcase' => 'briefcase',
  721. 'glyphicon-bullhorn' => 'bullhorn',
  722. 'glyphicon-calendar' => 'calendar',
  723. 'glyphicon-camera' => 'camera',
  724. 'glyphicon-certificate' => 'certificate',
  725. 'glyphicon-check' => 'check',
  726. 'glyphicon-chevron-down' => 'chevron-down',
  727. 'glyphicon-chevron-left' => 'chevron-left',
  728. 'glyphicon-chevron-right' => 'chevron-right',
  729. 'glyphicon-chevron-up' => 'chevron-up',
  730. 'glyphicon-circle-arrow-down' => 'circle-arrow-down',
  731. 'glyphicon-circle-arrow-left' => 'circle-arrow-left',
  732. 'glyphicon-circle-arrow-right' => 'circle-arrow-right',
  733. 'glyphicon-circle-arrow-up' => 'circle-arrow-up',
  734. 'glyphicon-cloud' => 'cloud',
  735. 'glyphicon-cloud-download' => 'cloud-download',
  736. 'glyphicon-cloud-upload' => 'cloud-upload',
  737. 'glyphicon-cog' => 'cog',
  738. 'glyphicon-collapse-down' => 'collapse-down',
  739. 'glyphicon-collapse-up' => 'collapse-up',
  740. 'glyphicon-comment' => 'comment',
  741. 'glyphicon-compressed' => 'compressed',
  742. 'glyphicon-copyright-mark' => 'copyright-mark',
  743. 'glyphicon-credit-card' => 'credit-card',
  744. 'glyphicon-cutlery' => 'cutlery',
  745. 'glyphicon-dashboard' => 'dashboard',
  746. 'glyphicon-download' => 'download',
  747. 'glyphicon-download-alt' => 'download-alt',
  748. 'glyphicon-earphone' => 'earphone',
  749. 'glyphicon-edit' => 'edit',
  750. 'glyphicon-eject' => 'eject',
  751. 'glyphicon-envelope' => 'envelope',
  752. 'glyphicon-euro' => 'euro',
  753. 'glyphicon-exclamation-sign' => 'exclamation-sign',
  754. 'glyphicon-expand' => 'expand',
  755. 'glyphicon-export' => 'export',
  756. 'glyphicon-eye-close' => 'eye-close',
  757. 'glyphicon-eye-open' => 'eye-open',
  758. 'glyphicon-facetime-video' => 'facetime-video',
  759. 'glyphicon-fast-backward' => 'fast-backward',
  760. 'glyphicon-fast-forward' => 'fast-forward',
  761. 'glyphicon-file' => 'file',
  762. 'glyphicon-film' => 'film',
  763. 'glyphicon-filter' => 'filter',
  764. 'glyphicon-fire' => 'fire',
  765. 'glyphicon-flag' => 'flag',
  766. 'glyphicon-flash' => 'flash',
  767. 'glyphicon-floppy-disk' => 'floppy-disk',
  768. 'glyphicon-floppy-open' => 'floppy-open',
  769. 'glyphicon-floppy-remove' => 'floppy-remove',
  770. 'glyphicon-floppy-save' => 'floppy-save',
  771. 'glyphicon-floppy-saved' => 'floppy-saved',
  772. 'glyphicon-folder-close' => 'folder-close',
  773. 'glyphicon-folder-open' => 'folder-open',
  774. 'glyphicon-font' => 'font',
  775. 'glyphicon-forward' => 'forward',
  776. 'glyphicon-fullscreen' => 'fullscreen',
  777. 'glyphicon-gbp' => 'gbp',
  778. 'glyphicon-gift' => 'gift',
  779. 'glyphicon-glass' => 'glass',
  780. 'glyphicon-globe' => 'globe',
  781. 'glyphicon-hand-down' => 'hand-down',
  782. 'glyphicon-hand-left' => 'hand-left',
  783. 'glyphicon-hand-right' => 'hand-right',
  784. 'glyphicon-hand-up' => 'hand-up',
  785. 'glyphicon-hd-video' => 'hd-video',
  786. 'glyphicon-hdd' => 'hdd',
  787. 'glyphicon-header' => 'header',
  788. 'glyphicon-headphones' => 'headphones',
  789. 'glyphicon-heart' => 'heart',
  790. 'glyphicon-heart-empty' => 'heart-empty',
  791. 'glyphicon-home' => 'home',
  792. 'glyphicon-import' => 'import',
  793. 'glyphicon-inbox' => 'inbox',
  794. 'glyphicon-indent-left' => 'indent-left',
  795. 'glyphicon-indent-right' => 'indent-right',
  796. 'glyphicon-info-sign' => 'info-sign',
  797. 'glyphicon-italic' => 'italic',
  798. 'glyphicon-leaf' => 'leaf',
  799. 'glyphicon-link' => 'link',
  800. 'glyphicon-list' => 'list',
  801. 'glyphicon-list-alt' => 'list-alt',
  802. 'glyphicon-lock' => 'lock',
  803. 'glyphicon-log-in' => 'log-in',
  804. 'glyphicon-log-out' => 'log-out',
  805. 'glyphicon-magnet' => 'magnet',
  806. 'glyphicon-map-marker' => 'map-marker',
  807. 'glyphicon-minus' => 'minus',
  808. 'glyphicon-minus-sign' => 'minus-sign',
  809. 'glyphicon-move' => 'move',
  810. 'glyphicon-music' => 'music',
  811. 'glyphicon-new-window' => 'new-window',
  812. 'glyphicon-off' => 'off',
  813. 'glyphicon-ok' => 'ok',
  814. 'glyphicon-ok-circle' => 'ok-circle',
  815. 'glyphicon-ok-sign' => 'ok-sign',
  816. 'glyphicon-open' => 'open',
  817. 'glyphicon-paperclip' => 'paperclip',
  818. 'glyphicon-pause' => 'pause',
  819. 'glyphicon-pencil' => 'pencil',
  820. 'glyphicon-phone' => 'phone',
  821. 'glyphicon-phone-alt' => 'phone-alt',
  822. 'glyphicon-picture' => 'picture',
  823. 'glyphicon-plane' => 'plane',
  824. 'glyphicon-play' => 'play',
  825. 'glyphicon-play-circle' => 'play-circle',
  826. 'glyphicon-plus' => 'plus',
  827. 'glyphicon-plus-sign' => 'plus-sign',
  828. 'glyphicon-print' => 'print',
  829. 'glyphicon-pushpin' => 'pushpin',
  830. 'glyphicon-qrcode' => 'qrcode',
  831. 'glyphicon-question-sign' => 'question-sign',
  832. 'glyphicon-random' => 'random',
  833. 'glyphicon-record' => 'record',
  834. 'glyphicon-refresh' => 'refresh',
  835. 'glyphicon-registration-mark' => 'registration-mark',
  836. 'glyphicon-remove' => 'remove',
  837. 'glyphicon-remove-circle' => 'remove-circle',
  838. 'glyphicon-remove-sign' => 'remove-sign',
  839. 'glyphicon-repeat' => 'repeat',
  840. 'glyphicon-resize-full' => 'resize-full',
  841. 'glyphicon-resize-horizontal' => 'resize-horizontal',
  842. 'glyphicon-resize-small' => 'resize-small',
  843. 'glyphicon-resize-vertical' => 'resize-vertical',
  844. 'glyphicon-retweet' => 'retweet',
  845. 'glyphicon-road' => 'road',
  846. 'glyphicon-save' => 'save',
  847. 'glyphicon-saved' => 'saved',
  848. 'glyphicon-screenshot' => 'screenshot',
  849. 'glyphicon-sd-video' => 'sd-video',
  850. 'glyphicon-search' => 'search',
  851. 'glyphicon-send' => 'send',
  852. 'glyphicon-share' => 'share',
  853. 'glyphicon-share-alt' => 'share-alt',
  854. 'glyphicon-shopping-cart' => 'shopping-cart',
  855. 'glyphicon-signal' => 'signal',
  856. 'glyphicon-sort' => 'sort',
  857. 'glyphicon-sort-by-alphabet' => 'sort-by-alphabet',
  858. 'glyphicon-sort-by-alphabet-alt' => 'sort-by-alphabet-alt',
  859. 'glyphicon-sort-by-attributes' => 'sort-by-attributes',
  860. 'glyphicon-sort-by-attributes-alt' => 'sort-by-attributes-alt',
  861. 'glyphicon-sort-by-order' => 'sort-by-order',
  862. 'glyphicon-sort-by-order-alt' => 'sort-by-order-alt',
  863. 'glyphicon-sound-5-1' => 'sound-5-1',
  864. 'glyphicon-sound-6-1' => 'sound-6-1',
  865. 'glyphicon-sound-7-1' => 'sound-7-1',
  866. 'glyphicon-sound-dolby' => 'sound-dolby',
  867. 'glyphicon-sound-stereo' => 'sound-stereo',
  868. 'glyphicon-star' => 'star',
  869. 'glyphicon-star-empty' => 'star-empty',
  870. 'glyphicon-stats' => 'stats',
  871. 'glyphicon-step-backward' => 'step-backward',
  872. 'glyphicon-step-forward' => 'step-forward',
  873. 'glyphicon-stop' => 'stop',
  874. 'glyphicon-subtitles' => 'subtitles',
  875. 'glyphicon-tag' => 'tag',
  876. 'glyphicon-tags' => 'tags',
  877. 'glyphicon-tasks' => 'tasks',
  878. 'glyphicon-text-height' => 'text-height',
  879. 'glyphicon-text-width' => 'text-width',
  880. 'glyphicon-th' => 'th',
  881. 'glyphicon-th-large' => 'th-large',
  882. 'glyphicon-th-list' => 'th-list',
  883. 'glyphicon-thumbs-down' => 'thumbs-down',
  884. 'glyphicon-thumbs-up' => 'thumbs-up',
  885. 'glyphicon-time' => 'time',
  886. 'glyphicon-tint' => 'tint',
  887. 'glyphicon-tower' => 'tower',
  888. 'glyphicon-transfer' => 'transfer',
  889. 'glyphicon-trash' => 'trash',
  890. 'glyphicon-tree-conifer' => 'tree-conifer',
  891. 'glyphicon-tree-deciduous' => 'tree-deciduous',
  892. 'glyphicon-unchecked' => 'unchecked',
  893. 'glyphicon-upload' => 'upload',
  894. 'glyphicon-usd' => 'usd',
  895. 'glyphicon-user' => 'user',
  896. 'glyphicon-volume-down' => 'volume-down',
  897. 'glyphicon-volume-off' => 'volume-off',
  898. 'glyphicon-volume-up' => 'volume-up',
  899. 'glyphicon-warning-sign' => 'warning-sign',
  900. 'glyphicon-wrench' => 'wrench',
  901. 'glyphicon-zoom-in' => 'zoom-in',
  902. 'glyphicon-zoom-out' => 'zoom-out',
  903. );
  904. $versions['3.0.1'] = $versions['3.0.0'];
  905. $versions['3.0.2'] = $versions['3.0.1'];
  906. $versions['3.0.3'] = $versions['3.0.2'];
  907. $versions['3.1.0'] = $versions['3.0.3'];
  908. $versions['3.1.1'] = $versions['3.1.0'];
  909. $versions['3.2.0'] = $versions['3.1.1'];
  910. $versions['3.3.0'] = array_merge($versions['3.2.0'], array(
  911. 'glyphicon-eur' => 'eur',
  912. ));
  913. $versions['3.3.1'] = $versions['3.3.0'];
  914. $versions['3.3.2'] = array_merge($versions['3.3.1'], array(
  915. 'glyphicon-alert' => 'alert',
  916. 'glyphicon-apple' => 'apple',
  917. 'glyphicon-baby-formula' => 'baby-formula',
  918. 'glyphicon-bed' => 'bed',
  919. 'glyphicon-bishop' => 'bishop',
  920. 'glyphicon-bitcoin' => 'bitcoin',
  921. 'glyphicon-blackboard' => 'blackboard',
  922. 'glyphicon-cd' => 'cd',
  923. 'glyphicon-console' => 'console',
  924. 'glyphicon-copy' => 'copy',
  925. 'glyphicon-duplicate' => 'duplicate',
  926. 'glyphicon-education' => 'education',
  927. 'glyphicon-equalizer' => 'equalizer',
  928. 'glyphicon-erase' => 'erase',
  929. 'glyphicon-grain' => 'grain',
  930. 'glyphicon-hourglass' => 'hourglass',
  931. 'glyphicon-ice-lolly' => 'ice-lolly',
  932. 'glyphicon-ice-lolly-tasted' => 'ice-lolly-tasted',
  933. 'glyphicon-king' => 'king',
  934. 'glyphicon-knight' => 'knight',
  935. 'glyphicon-lamp' => 'lamp',
  936. 'glyphicon-level-up' => 'level-up',
  937. 'glyphicon-menu-down' => 'menu-down',
  938. 'glyphicon-menu-hamburger' => 'menu-hamburger',
  939. 'glyphicon-menu-left' => 'menu-left',
  940. 'glyphicon-menu-right' => 'menu-right',
  941. 'glyphicon-menu-up' => 'menu-up',
  942. 'glyphicon-modal-window' => 'modal-window',
  943. 'glyphicon-object-align-bottom' => 'object-align-bottom',
  944. 'glyphicon-object-align-horizontal' => 'object-align-horizontal',
  945. 'glyphicon-object-align-left' => 'object-align-left',
  946. 'glyphicon-object-align-right' => 'object-align-right',
  947. 'glyphicon-object-align-top' => 'object-align-top',
  948. 'glyphicon-object-align-vertical' => 'object-align-vertical',
  949. 'glyphicon-oil' => 'oil',
  950. 'glyphicon-open-file' => 'open-file',
  951. 'glyphicon-option-horizontal' => 'option-horizontal',
  952. 'glyphicon-option-vertical' => 'option-vertical',
  953. 'glyphicon-paste' => 'paste',
  954. 'glyphicon-pawn' => 'pawn',
  955. 'glyphicon-piggy-bank' => 'piggy-bank',
  956. 'glyphicon-queen' => 'queen',
  957. 'glyphicon-ruble' => 'ruble',
  958. 'glyphicon-save-file' => 'save-file',
  959. 'glyphicon-scale' => 'scale',
  960. 'glyphicon-scissors' => 'scissors',
  961. 'glyphicon-subscript' => 'subscript',
  962. 'glyphicon-sunglasses' => 'sunglasses',
  963. 'glyphicon-superscript' => 'superscript',
  964. 'glyphicon-tent' => 'tent',
  965. 'glyphicon-text-background' => 'text-background',
  966. 'glyphicon-text-color' => 'text-color',
  967. 'glyphicon-text-size' => 'text-size',
  968. 'glyphicon-triangle-bottom' => 'triangle-bottom',
  969. 'glyphicon-triangle-left' => 'triangle-left',
  970. 'glyphicon-triangle-right' => 'triangle-right',
  971. 'glyphicon-triangle-top' => 'triangle-top',
  972. 'glyphicon-yen' => 'yen',
  973. ));
  974. $versions['3.3.4'] = array_merge($versions['3.3.2'], array(
  975. 'glyphicon-btc' => 'btc',
  976. 'glyphicon-jpy' => 'jpy',
  977. 'glyphicon-rub' => 'rub',
  978. 'glyphicon-xbt' => 'xbt',
  979. ));
  980. $versions['3.3.5'] = $versions['3.3.4'];
  981. $versions['3.3.6'] = $versions['3.3.5'];
  982. $versions['3.3.7'] = $versions['3.3.6'];
  983. $versions['3.4.0'] = $versions['3.3.7'];
  984. $versions['3.4.1'] = $versions['3.4.0'];
  985. }
  986. // Return a specific versions icon set.
  987. if (isset($version) && isset($versions[$version])) {
  988. return $versions[$version];
  989. }
  990. // Return the latest version.
  991. return $versions[BOOTSTRAP_VERSION];
  992. }
  993. /**
  994. * Returns a specific Bootstrap Glyphicon as rendered HTML markup.
  995. *
  996. * @param string $name
  997. * The icon name, minus the "glyphicon-" prefix.
  998. * @param string $default
  999. * (Optional) The default value to return.
  1000. * @param array $attributes
  1001. * (Optional) Additional attributes to merge onto the icon.
  1002. *
  1003. * @return string
  1004. * The HTML markup containing the icon defined by $name, $default value if
  1005. * icon does not exist or returns empty output for whatever reason.
  1006. */
  1007. function _bootstrap_icon($name, $default = NULL, array $attributes = array()) {
  1008. $icon = _bootstrap_glyphicon($name, $default, $attributes);
  1009. return render($icon);
  1010. }
  1011. /**
  1012. * Returns a specific Bootstrap Glyphicon as a render array.
  1013. *
  1014. * Note: This function was added to keep BC with the former _bootstrap_icon()
  1015. * implementation since it didn't return a render array. It is basically a
  1016. * backport of 8.x-3.x code so the added $attributes parameter can be more
  1017. * easily dealt with.
  1018. *
  1019. * @param string $name
  1020. * The icon name, minus the "glyphicon-" prefix.
  1021. * @param array|string $default
  1022. * (Optional) The default render array to use if $name is not available.
  1023. * @param array $attributes
  1024. * (Optional) Additional attributes to merge onto the icon.
  1025. *
  1026. * @see https://www.drupal.org/project/bootstrap/issues/2844885
  1027. *
  1028. * @return array
  1029. * The render containing the icon defined by $name, $default value if
  1030. * icon does not exist or returns NULL if no icon could be rendered.
  1031. */
  1032. function _bootstrap_glyphicon($name, $default = array(), array $attributes = array()) {
  1033. $icon = array();
  1034. // Ensure the icon specified is a valid Bootstrap Glyphicon.
  1035. if (_bootstrap_glyphicons_supported() && in_array($name, _bootstrap_glyphicons())) {
  1036. // Attempt to use the Icon API module, if enabled and it generates output.
  1037. if (module_exists('icon')) {
  1038. $icon = array(
  1039. '#theme' => 'icon',
  1040. '#bundle' => 'bootstrap',
  1041. '#icon' => 'glyphicon-' . $name,
  1042. '#attributes' => $attributes,
  1043. );
  1044. }
  1045. else {
  1046. $icon = array(
  1047. '#theme' => 'html_tag',
  1048. '#tag' => 'span',
  1049. '#value' => '',
  1050. '#attributes' => $attributes,
  1051. );
  1052. // Unlike 8.x-3.x, it will be easier to add the classes and aria-hidden
  1053. // attribute afterwards since there is a passed default $attributes
  1054. // parameter and drupal_array_merge_deep() is notorious for not properly
  1055. // merging attributes, especially when dealing with classes.
  1056. _bootstrap_add_class(array('icon', 'glyphicon', 'glyphicon-' . $name), $icon);
  1057. $icon['#attributes']['aria-hidden'] = 'true';
  1058. }
  1059. }
  1060. // Return the icon.
  1061. if (!empty($icon)) {
  1062. return $icon;
  1063. }
  1064. // _bootstrap_icon() may pass NULL as $default. If so, return an empty array.
  1065. return isset($default) ? $default : array();
  1066. }
  1067. /**
  1068. * Determine whether or not Bootstrap Glyphicons can be used.
  1069. */
  1070. function _bootstrap_glyphicons_supported() {
  1071. global $theme;
  1072. // Use the advanced drupal_static() pattern, since this has the potential to
  1073. // be called very often by _bootstrap_icon().
  1074. static $drupal_static_fast;
  1075. if (!isset($drupal_static_fast)) {
  1076. $drupal_static_fast['supported'] = &drupal_static(__FUNCTION__);
  1077. }
  1078. // Get static data.
  1079. $supported = &$drupal_static_fast['supported'];
  1080. // Retrieve supported themes.
  1081. if (!isset($supported)) {
  1082. $supported = array();
  1083. // Retrieve cached data.
  1084. $cid = 'theme_registry:bootstrap:icon_support';
  1085. if (($cache = cache_get($cid)) && !empty($cache->data)) {
  1086. $supported = $cache->data;
  1087. }
  1088. }
  1089. // Determine active theme support if not yet set.
  1090. if (!isset($supported[$theme])) {
  1091. // Bootstrap based themes are enabled by default to use CDN. Check if
  1092. // that is the case here so no file discovery is necessary. If the active
  1093. // theme does not have this setting, it falls back to the base theme that
  1094. // does.
  1095. $supported[$theme] = !!bootstrap_get_cdn_assets('css', NULL, $theme);
  1096. // CDN not used, iterate over all of the active (base) themes to determine
  1097. // if they contain glyphicon font files.
  1098. if (!$supported[$theme]) {
  1099. foreach (_bootstrap_get_base_themes($theme, TRUE) as $_theme) {
  1100. // Scan the theme for files.
  1101. $fonts = _bootstrap_file_scan_directory(drupal_get_path('theme', $_theme), '/glyphicons-halflings-regular\.(eot|svg|ttf|woff)$/');
  1102. // Fonts found, stop the search.
  1103. if (!empty($fonts)) {
  1104. $supported[$theme] = TRUE;
  1105. break;
  1106. }
  1107. }
  1108. }
  1109. // Cache all supported themes now that this theme is added to the array.
  1110. cache_set($cid, $supported);
  1111. }
  1112. return $supported[$theme];
  1113. }
  1114. /**
  1115. * Determine whether a specific element is a button.
  1116. *
  1117. * @param array $element
  1118. * A renderable element.
  1119. *
  1120. * @return bool
  1121. * TRUE or FALSE.
  1122. */
  1123. function _bootstrap_is_button(array $element) {
  1124. return
  1125. !empty($element['#type']) &&
  1126. !empty($element['#value']) && (
  1127. $element['#type'] === 'button' ||
  1128. $element['#type'] === 'submit' ||
  1129. $element['#type'] === 'image_button'
  1130. );
  1131. }
  1132. /**
  1133. * Adds a specific Bootstrap class to color a button based on its text value.
  1134. *
  1135. * @param array $element
  1136. * The form element, passed by reference.
  1137. */
  1138. function _bootstrap_colorize_button(array &$element) {
  1139. if (_bootstrap_is_button($element)) {
  1140. // Do not add the class if one is already present in the array.
  1141. $button_classes = array(
  1142. 'btn-default',
  1143. 'btn-primary',
  1144. 'btn-success',
  1145. 'btn-info',
  1146. 'btn-warning',
  1147. 'btn-danger',
  1148. 'btn-link',
  1149. );
  1150. $class_intersection = array_intersect($button_classes, $element['#attributes']['class']);
  1151. if (empty($class_intersection)) {
  1152. // Get the matched class.
  1153. $class = bootstrap_setting('button_colorize') ? _bootstrap_colorize_text($element['#value']) : FALSE;
  1154. // If no particular class matched, use the default style.
  1155. if (!$class) {
  1156. $class = 'default';
  1157. }
  1158. $element['#attributes']['class'][] = 'btn-' . $class;
  1159. }
  1160. }
  1161. }
  1162. /**
  1163. * Matches a Bootstrap class based on a string value.
  1164. *
  1165. * @param string $string
  1166. * The string to match classes against.
  1167. * @param string $default
  1168. * The default class to return if no match is found.
  1169. *
  1170. * @return string
  1171. * The Bootstrap class matched against the value of $haystack or $default if
  1172. * no match could be made.
  1173. */
  1174. function _bootstrap_colorize_text($string, $default = '') {
  1175. static $texts;
  1176. if (!isset($texts)) {
  1177. $texts = array(
  1178. // Text that match these specific strings are checked first.
  1179. 'matches' => array(
  1180. // Primary class.
  1181. t('Download feature') => 'primary',
  1182. // Success class.
  1183. t('Add effect') => 'success',
  1184. t('Add and configure') => 'success',
  1185. // Info class.
  1186. t('Save and add') => 'info',
  1187. t('Add another item') => 'info',
  1188. t('Update style') => 'info',
  1189. ),
  1190. // Text that contain these words anywhere in the string are checked last.
  1191. 'contains' => array(
  1192. // Primary class.
  1193. t('Confirm') => 'primary',
  1194. t('Filter') => 'primary',
  1195. t('Log in') => 'primary',
  1196. t('Submit') => 'primary',
  1197. t('Search') => 'primary',
  1198. t('Upload') => 'primary',
  1199. // Success class.
  1200. t('Add') => 'success',
  1201. t('Create') => 'success',
  1202. t('Save') => 'success',
  1203. t('Write') => 'success',
  1204. // Warning class.
  1205. t('Export') => 'warning',
  1206. t('Import') => 'warning',
  1207. t('Restore') => 'warning',
  1208. t('Rebuild') => 'warning',
  1209. // Info class.
  1210. t('Apply') => 'info',
  1211. t('Update') => 'info',
  1212. // Danger class.
  1213. t('Delete') => 'danger',
  1214. t('Remove') => 'danger',
  1215. ),
  1216. );
  1217. // Allow sub-themes to alter this array of patterns.
  1218. drupal_alter('bootstrap_colorize_text', $texts);
  1219. }
  1220. // Iterate over the array.
  1221. foreach ($texts as $pattern => $strings) {
  1222. foreach ($strings as $value => $class) {
  1223. switch ($pattern) {
  1224. case 'matches':
  1225. if ($string === $value) {
  1226. return $class;
  1227. }
  1228. break;
  1229. case 'contains':
  1230. if (strpos(drupal_strtolower($string), drupal_strtolower($value)) !== FALSE) {
  1231. return $class;
  1232. }
  1233. break;
  1234. }
  1235. }
  1236. }
  1237. // Return the default if nothing was matched.
  1238. return $default;
  1239. }
  1240. /**
  1241. * Adds an icon to button element based on its text value.
  1242. *
  1243. * @param array $element
  1244. * The form element, passed by reference.
  1245. */
  1246. function _bootstrap_iconize_button(array &$element) {
  1247. if (bootstrap_setting('button_iconize') && _bootstrap_is_button($element) && ($icon = _bootstrap_iconize_text($element['#value']))) {
  1248. $element['#icon'] = $icon;
  1249. }
  1250. }
  1251. /**
  1252. * Matches a Bootstrap Glyphicon based on a string value.
  1253. *
  1254. * @param string $string
  1255. * The string to match classes against.
  1256. * @param string $default
  1257. * The default icon to return if no match is found.
  1258. *
  1259. * @return string
  1260. * The Bootstrap icon matched against the value of $haystack or $default if
  1261. * no match could be made.
  1262. */
  1263. function _bootstrap_iconize_text($string, $default = '') {
  1264. static $texts;
  1265. if (!isset($texts)) {
  1266. $texts = array(
  1267. // Text that match these specific strings are checked first.
  1268. 'matches' => array(),
  1269. // Text that contain these words anywhere in the string are checked last.
  1270. 'contains' => array(
  1271. t('Manage') => 'cog',
  1272. t('Configure') => 'cog',
  1273. t('Download') => 'download',
  1274. t('Export') => 'export',
  1275. t('Filter') => 'filter',
  1276. t('Import') => 'import',
  1277. t('Save') => 'ok',
  1278. t('Update') => 'ok',
  1279. t('Edit') => 'pencil',
  1280. t('Add') => 'plus',
  1281. t('Write') => 'plus',
  1282. t('Cancel') => 'remove',
  1283. t('Delete') => 'trash',
  1284. t('Remove') => 'trash',
  1285. t('Upload') => 'upload',
  1286. t('Log In') => 'log-in',
  1287. ),
  1288. );
  1289. // Allow sub-themes to alter this array of patterns.
  1290. drupal_alter('bootstrap_iconize_text', $texts);
  1291. }
  1292. // Iterate over the array.
  1293. foreach ($texts as $pattern => $strings) {
  1294. foreach ($strings as $value => $icon) {
  1295. switch ($pattern) {
  1296. case 'matches':
  1297. if ($string === $value) {
  1298. return _bootstrap_icon($icon, $default);
  1299. }
  1300. break;
  1301. case 'contains':
  1302. if (strpos(drupal_strtolower($string), drupal_strtolower($value)) !== FALSE) {
  1303. return _bootstrap_icon($icon, $default);
  1304. }
  1305. break;
  1306. }
  1307. }
  1308. }
  1309. // Return a default icon if nothing was matched.
  1310. return _bootstrap_icon($default);
  1311. }
  1312. /**
  1313. * Invokes a specific suggestion's preprocess functions.
  1314. *
  1315. * @param array $variables
  1316. * The theme implementation variables array.
  1317. */
  1318. function _bootstrap_preprocess_theme_suggestion(array &$variables) {
  1319. $registry = theme_get_registry();
  1320. if (!empty($variables['theme_hook_suggestion']) && !empty($registry[$variables['theme_hook_suggestion']]['preprocess functions'])) {
  1321. // Save the suggestion as the hook to pass to the function.
  1322. $hook = $variables['theme_hook_suggestion'];
  1323. // Iterate over the preprocess functions.
  1324. foreach ($registry[$hook]['preprocess functions'] as $function) {
  1325. // Ensure that the function is not this one (recursive) and exists.
  1326. if ($function !== __FUNCTION__ && function_exists($function)) {
  1327. // Invoke theme hook suggestion preprocess function.
  1328. $function($variables, $hook);
  1329. // Unset the theme_hook_suggestion so the suggestion's preprocess
  1330. // functions can provide theme_hook_suggestions if needed.
  1331. if (!empty($variables['theme_hook_suggestions'])) {
  1332. unset($variables['theme_hook_suggestion']);
  1333. }
  1334. }
  1335. }
  1336. }
  1337. }
  1338. /**
  1339. * Invokes a specific suggestion's process functions.
  1340. *
  1341. * @param array $variables
  1342. * The theme implementation variables array.
  1343. */
  1344. function _bootstrap_process_theme_suggestion(array &$variables) {
  1345. $registry = theme_get_registry();
  1346. if (!empty($variables['theme_hook_suggestion']) && !empty($registry[$variables['theme_hook_suggestion']]['process functions'])) {
  1347. // Save the suggestion as the hook to pass to the function.
  1348. $hook = $variables['theme_hook_suggestion'];
  1349. // Iterate over the process functions.
  1350. foreach ($registry[$hook]['process functions'] as $function) {
  1351. if (function_exists($function)) {
  1352. // Invoke theme hook suggestion process function.
  1353. $function($variables, $hook);
  1354. // Unset the theme_hook_suggestion so the suggestion's preprocess
  1355. // functions can provide theme_hook_suggestions if needed.
  1356. if (!empty($variables['theme_hook_suggestions'])) {
  1357. unset($variables['theme_hook_suggestion']);
  1358. }
  1359. }
  1360. }
  1361. }
  1362. }
  1363. /**
  1364. * Determines if a string of text is considered "simple".
  1365. *
  1366. * @param string $string
  1367. * The string of text to check "simple" criteria on.
  1368. * @param int|false $length
  1369. * The length of characters used to determine whether or not $string is
  1370. * considered "simple". Set explicitly to FALSE to disable this criteria.
  1371. * @param array|false $allowed_tags
  1372. * An array of allowed tag elements. Set explicitly to FALSE to disable this
  1373. * criteria.
  1374. * @param bool $html
  1375. * A variable, passed by reference, that indicates whether or not the
  1376. * string contains HTML.
  1377. *
  1378. * @return bool
  1379. * Returns TRUE if the $string is considered "simple", FALSE otherwise.
  1380. */
  1381. function _bootstrap_is_simple_string($string, $length = 250, $allowed_tags = NULL, &$html = FALSE) {
  1382. // Use the advanced drupal_static() pattern, since this is called very often.
  1383. static $drupal_static_fast;
  1384. if (!isset($drupal_static_fast)) {
  1385. $drupal_static_fast['strings'] = &drupal_static(__FUNCTION__);
  1386. }
  1387. $strings = &$drupal_static_fast['strings'];
  1388. if (!isset($strings[$string])) {
  1389. $plain_string = strip_tags($string);
  1390. $simple = TRUE;
  1391. if ($allowed_tags !== FALSE) {
  1392. $filtered_string = filter_xss($string, $allowed_tags);
  1393. $html = $filtered_string !== $plain_string;
  1394. $simple = $simple && $string === $filtered_string;
  1395. }
  1396. if ($length !== FALSE) {
  1397. $simple = $simple && strlen($plain_string) <= intval($length);
  1398. }
  1399. $strings[$string] = $simple;
  1400. }
  1401. return $strings[$string];
  1402. }
  1403. /**
  1404. * Determines if the Path Breadcrumbs module theme function should be used.
  1405. *
  1406. * @param string $theme
  1407. * The machine name of a specific theme to determine status if the Path
  1408. * Breadcrumbs module has been configured to only use its internal function
  1409. * on a specific list of themes.
  1410. *
  1411. * @return bool
  1412. * TRUE or FALSE
  1413. */
  1414. function _bootstrap_use_path_breadcrumbs($theme = NULL) {
  1415. static $path_breadcrumbs;
  1416. if (!isset($path_breadcrumbs)) {
  1417. $path_breadcrumbs = FALSE;
  1418. // Use active theme as the theme key if not explicitly set.
  1419. if (!isset($theme)) {
  1420. $theme = $GLOBALS['theme_key'];
  1421. }
  1422. // Determine whether or not the internal Path Breadcrumbs theme function
  1423. // should be used or not.
  1424. if (function_exists('path_breadcrumbs_breadcrumb') && module_exists('path_breadcrumbs')) {
  1425. $internal_render = variable_get('path_breadcrumbs_internal_render', 1);
  1426. $themes = variable_get('path_breadcrumbs_internal_render_themes', array());
  1427. $path_breadcrumbs = ($internal_render && (empty($themes) || in_array($theme, $themes)));
  1428. }
  1429. }
  1430. return $path_breadcrumbs;
  1431. }
  1432. /**
  1433. * @} End of "ingroup utility".
  1434. */