alter.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <?php
  2. /**
  3. * @file
  4. * alter.inc
  5. *
  6. * Contains various implementations of hook_*_alter().
  7. */
  8. /**
  9. * Include #pre_render callbacks for elements.
  10. */
  11. bootstrap_include('bootstrap', 'includes/pre-render.inc');
  12. /**
  13. * Include #process callbacks for elements.
  14. */
  15. bootstrap_include('bootstrap', 'includes/process.inc');
  16. /**
  17. * Implements hook_css_alter().
  18. */
  19. function bootstrap_css_alter(&$css) {
  20. $theme_path = drupal_get_path('theme', 'bootstrap');
  21. // Exclude specified CSS files from theme.
  22. $excludes = bootstrap_get_theme_info(NULL, 'exclude][css');
  23. // Add CDN assets, if any.
  24. $provider = bootstrap_setting('cdn_provider');
  25. if ($cdn_assets = bootstrap_get_cdn_assets('css', $provider)) {
  26. $cdn_weight = -2.99;
  27. foreach ($cdn_assets as $cdn_asset) {
  28. $cdn_weight += .01;
  29. $css[$cdn_asset] = array(
  30. 'data' => $cdn_asset,
  31. 'type' => 'external',
  32. 'every_page' => TRUE,
  33. 'media' => 'all',
  34. 'preprocess' => FALSE,
  35. 'group' => CSS_THEME,
  36. 'browsers' => array('IE' => TRUE, '!IE' => TRUE),
  37. 'weight' => $cdn_weight,
  38. );
  39. }
  40. // Add a specific version and theme CSS overrides file.
  41. $version = bootstrap_setting('cdn_' . $provider . '_version');
  42. if (!$version) {
  43. $version = BOOTSTRAP_VERSION;
  44. }
  45. $theme = bootstrap_setting('cdn_' . $provider . '_theme');
  46. if (!$theme) {
  47. $theme = 'bootstrap';
  48. }
  49. $theme = $theme === 'bootstrap' || $theme === 'bootstrap_theme' ? '' : "-$theme";
  50. $overrides = "$theme_path/css/$version/overrides$theme.min.css";
  51. if (file_exists($overrides)) {
  52. $css[$overrides] = array(
  53. 'data' => $overrides,
  54. 'type' => 'file',
  55. 'every_page' => TRUE,
  56. 'media' => 'all',
  57. 'preprocess' => TRUE,
  58. 'group' => CSS_THEME,
  59. 'browsers' => array('IE' => TRUE, '!IE' => TRUE),
  60. 'weight' => -1,
  61. );
  62. }
  63. }
  64. if (!empty($excludes)) {
  65. $css = array_diff_key($css, drupal_map_assoc($excludes));
  66. }
  67. }
  68. /**
  69. * Implements hook_element_info_alter().
  70. */
  71. function bootstrap_element_info_alter(&$info) {
  72. global $theme_key;
  73. $cid = "theme_registry:bootstrap:element_info";
  74. $cached = array();
  75. if (($cache = cache_get($cid)) && !empty($cache->data)) {
  76. $cached = $cache->data;
  77. }
  78. $themes = _bootstrap_get_base_themes($theme_key, TRUE);
  79. foreach ($themes as $theme) {
  80. if (!isset($cached[$theme])) {
  81. $cached[$theme] = array();
  82. foreach (array_keys($info) as $type) {
  83. $element = array();
  84. // Ensure elements that have a base type with the #input set match.
  85. if (isset($info[$type]['#base_type']) && isset($info[$type][$info[$type]['#base_type']]['#input'])) {
  86. $element['#input'] = $info[$info[$type]['#base_type']]['#input'];
  87. }
  88. // Replace fieldset theme implementations with bootstrap_panel.
  89. if (!empty($info[$type]['#theme']) && $info[$type]['#theme'] === 'fieldset') {
  90. $element['#bootstrap_replace']['#theme'] = 'bootstrap_panel';
  91. }
  92. if (!empty($info[$type]['#theme_wrappers']) && array_search('fieldset', $info[$type]['#theme_wrappers']) !== FALSE) {
  93. $element['#bootstrap_replace']['#theme_wrappers']['fieldset'] = 'bootstrap_panel';
  94. }
  95. // Setup a default "icon" variable. This allows #icon to be passed
  96. // to every template and theme function.
  97. // @see https://drupal.org/node/2219965
  98. $element['#icon'] = NULL;
  99. $element['#icon_position'] = 'before';
  100. $properties = array(
  101. '#process' => array(
  102. 'form_process',
  103. 'form_process_' . $type,
  104. ),
  105. '#pre_render' => array(
  106. 'pre_render',
  107. 'pre_render_' . $type,
  108. ),
  109. );
  110. foreach ($properties as $property => $callbacks) {
  111. foreach ($callbacks as $callback) {
  112. $function = $theme . '_' . $callback;
  113. if (function_exists($function)) {
  114. // Replace direct core function correlation.
  115. if (!empty($info[$type][$property]) && array_search($callback, $info[$type][$property]) !== FALSE) {
  116. $element['#bootstrap_replace'][$property][$callback] = $function;
  117. }
  118. // Check for a "form_" prefix instead (for #pre_render).
  119. elseif (!empty($info[$type][$property]) && array_search('form_' . $callback, $info[$type][$property]) !== FALSE) {
  120. $element['#bootstrap_replace'][$property]['form_' . $callback] = $function;
  121. }
  122. // Otherwise, append the function.
  123. else {
  124. $element[$property][] = $function;
  125. }
  126. }
  127. }
  128. }
  129. $cached[$theme][$type] = $element;
  130. }
  131. // Cache the element information.
  132. cache_set($cid, $cached);
  133. }
  134. // Merge in each theme's cached element info.
  135. $info = _bootstrap_element_info_array_merge($info, $cached[$theme]);
  136. }
  137. }
  138. /**
  139. * Merges the cached element information into the runtime array.
  140. *
  141. * @param array $info
  142. * The element info array to merge data into.
  143. * @param array $cached
  144. * The cached element info data array to merge from.
  145. *
  146. * @return array
  147. * The altered element info array.
  148. */
  149. function _bootstrap_element_info_array_merge($info, $cached) {
  150. foreach ($cached as $type => $element) {
  151. $replacement_data = isset($element['#bootstrap_replace']) ? $element['#bootstrap_replace'] : array();
  152. unset($element['#bootstrap_replace']);
  153. foreach ($element as $property => $data) {
  154. if (is_array($data)) {
  155. if (!isset($info[$type][$property])) {
  156. $info[$type][$property] = array();
  157. }
  158. // Append the values if not already in the array.
  159. foreach ($data as $key => $value) {
  160. if (!in_array($value, $info[$type][$property])) {
  161. $info[$type][$property][] = $value;
  162. }
  163. }
  164. }
  165. // Create the property, if not already set.
  166. elseif (!isset($info[$type][$property])) {
  167. $info[$type][$property] = $data;
  168. }
  169. }
  170. // Replace data, if necessary.
  171. foreach ($replacement_data as $property => $data) {
  172. if (is_array($data)) {
  173. foreach ($data as $needle => $replacement) {
  174. if (!empty($info[$type][$property]) && ($key = array_search($needle, $info[$type][$property])) !== FALSE) {
  175. $info[$type][$property][$key] = $replacement;
  176. }
  177. }
  178. }
  179. // Replace the property with the new data.
  180. else {
  181. $info[$type][$property] = $data;
  182. }
  183. }
  184. }
  185. // Return the altered element info array.
  186. return $info;
  187. }
  188. /**
  189. * Implements hook_form_alter().
  190. */
  191. function bootstrap_form_alter(array &$form, array &$form_state = array(), $form_id = NULL) {
  192. if ($form_id) {
  193. switch ($form_id) {
  194. case 'system_theme_settings':
  195. // Create vertical tabs for global settings (provided by core or other
  196. // contrib modules).
  197. if (!isset($form['global'])) {
  198. $form['global'] = array(
  199. '#type' => 'vertical_tabs',
  200. '#weight' => -9,
  201. );
  202. if (!empty($form_state['build_info']['args'][0])) {
  203. $form['global']['#prefix'] = '<h2><small>' . t('Override Global Settings') . '</small></h2>';
  204. }
  205. }
  206. // Iterate over all child elements and check to see if they should be
  207. // moved in the global vertical tabs.
  208. $global_children = element_children($form);
  209. foreach ($global_children as $child) {
  210. if (isset($form[$child]['#type']) && $form[$child]['#type'] === 'fieldset' && !isset($form[$child]['#group'])) {
  211. $form[$child]['#group'] = 'global';
  212. }
  213. }
  214. break;
  215. case 'search_form':
  216. // Add a clearfix class so the results don't overflow onto the form.
  217. $form['#attributes']['class'][] = 'clearfix';
  218. // Remove container-inline from the container classes.
  219. $form['basic']['#attributes']['class'] = array();
  220. // Hide the default button from display.
  221. $form['basic']['submit']['#attributes']['class'][] = 'element-invisible';
  222. // Implement a theme wrapper to add a submit button containing a search
  223. // icon directly after the input element.
  224. $form['basic']['keys']['#theme_wrappers'] = array('bootstrap_search_form_wrapper');
  225. $form['basic']['keys']['#title'] = '';
  226. $form['basic']['keys']['#attributes']['placeholder'] = t('Search');
  227. break;
  228. case 'search_block_form':
  229. $form['#attributes']['class'][] = 'form-search';
  230. $form['search_block_form']['#title'] = '';
  231. $form['search_block_form']['#attributes']['placeholder'] = t('Search');
  232. // Hide the default button from display and implement a theme wrapper
  233. // to add a submit button containing a search icon directly after the
  234. // input element.
  235. $form['actions']['submit']['#attributes']['class'][] = 'element-invisible';
  236. $form['search_block_form']['#theme_wrappers'] = array('bootstrap_search_form_wrapper');
  237. // Apply a clearfix so the results don't overflow onto the form.
  238. $form['#attributes']['class'][] = 'content-search';
  239. break;
  240. case 'image_style_form':
  241. $form['effects']['new']['new']['#input_group_button'] = TRUE;
  242. break;
  243. case 'path_admin_filter_form':
  244. $form['basic']['filter']['#input_group_button'] = TRUE;
  245. break;
  246. }
  247. }
  248. }
  249. /**
  250. * Implements hook_js_alter().
  251. */
  252. function bootstrap_js_alter(&$js) {
  253. // Exclude specified JavaScript files from theme.
  254. $excludes = bootstrap_get_theme_info(NULL, 'exclude][js');
  255. $theme_path = drupal_get_path('theme', 'bootstrap');
  256. // Add or replace JavaScript files when matching paths are detected.
  257. // Replacement files must begin with '_', like '_node.js'.
  258. $files = _bootstrap_file_scan_directory($theme_path . '/js', '/\.js$/');
  259. foreach ($files as $file) {
  260. $path = str_replace($theme_path . '/js/', '', $file->uri);
  261. // Detect if this is a replacement file.
  262. $replace = FALSE;
  263. if (preg_match('/^[_]/', $file->filename)) {
  264. $replace = TRUE;
  265. $path = dirname($path) . '/' . preg_replace('/^[_]/', '', $file->filename);
  266. }
  267. $matches = array();
  268. if (preg_match('/^modules\/([^\/]*)/', $path, $matches)) {
  269. if (!module_exists($matches[1])) {
  270. continue;
  271. }
  272. else {
  273. $path = str_replace('modules/' . $matches[1], drupal_get_path('module', $matches[1]), $path);
  274. }
  275. }
  276. // Path should always exist to either add or replace JavaScript file.
  277. if (!empty($js[$path])) {
  278. // Replace file.
  279. if ($replace) {
  280. $js[$file->uri] = $js[$path];
  281. $js[$file->uri]['data'] = $file->uri;
  282. unset($js[$path]);
  283. }
  284. // Add file.
  285. else {
  286. $js[$file->uri] = drupal_js_defaults($file->uri);
  287. $js[$file->uri]['group'] = JS_THEME;
  288. }
  289. }
  290. }
  291. // Ensure jQuery Once is always loaded.
  292. // @see https://drupal.org/node/2149561
  293. if (empty($js['misc/jquery.once.js'])) {
  294. $jquery_once = drupal_get_library('system', 'jquery.once');
  295. $js['misc/jquery.once.js'] = $jquery_once['js']['misc/jquery.once.js'];
  296. $js['misc/jquery.once.js'] += drupal_js_defaults('misc/jquery.once.js');
  297. }
  298. // Always add bootstrap.js last.
  299. $bootstrap = $theme_path . '/js/bootstrap.js';
  300. $js[$bootstrap] = drupal_js_defaults($bootstrap);
  301. $js[$bootstrap]['group'] = JS_THEME;
  302. $js[$bootstrap]['scope'] = 'footer';
  303. if (!empty($excludes)) {
  304. $js = array_diff_key($js, drupal_map_assoc($excludes));
  305. }
  306. // Add Bootstrap settings.
  307. $js['settings']['data'][]['bootstrap'] = array(
  308. 'anchorsFix' => bootstrap_setting('anchors_fix'),
  309. 'anchorsSmoothScrolling' => bootstrap_setting('anchors_smooth_scrolling'),
  310. 'formHasError' => (int) bootstrap_setting('forms_has_error_value_toggle'),
  311. 'popoverEnabled' => bootstrap_setting('popover_enabled'),
  312. 'popoverOptions' => array(
  313. 'animation' => (int) bootstrap_setting('popover_animation'),
  314. 'html' => (int) bootstrap_setting('popover_html'),
  315. 'placement' => bootstrap_setting('popover_placement'),
  316. 'selector' => bootstrap_setting('popover_selector'),
  317. 'trigger' => implode(' ', array_filter(array_values((array) bootstrap_setting('popover_trigger')))),
  318. 'triggerAutoclose' => (int) bootstrap_setting('popover_trigger_autoclose'),
  319. 'title' => bootstrap_setting('popover_title'),
  320. 'content' => bootstrap_setting('popover_content'),
  321. 'delay' => (int) bootstrap_setting('popover_delay'),
  322. 'container' => bootstrap_setting('popover_container'),
  323. ),
  324. 'tooltipEnabled' => bootstrap_setting('tooltip_enabled'),
  325. 'tooltipOptions' => array(
  326. 'animation' => (int) bootstrap_setting('tooltip_animation'),
  327. 'html' => (int) bootstrap_setting('tooltip_html'),
  328. 'placement' => bootstrap_setting('tooltip_placement'),
  329. 'selector' => bootstrap_setting('tooltip_selector'),
  330. 'trigger' => implode(' ', array_filter(array_values((array) bootstrap_setting('tooltip_trigger')))),
  331. 'delay' => (int) bootstrap_setting('tooltip_delay'),
  332. 'container' => bootstrap_setting('tooltip_container'),
  333. ),
  334. );
  335. // Add CDN assets, if any.
  336. if ($cdn_assets = bootstrap_get_cdn_assets('js')) {
  337. $cdn_weight = -99.99;
  338. foreach ($cdn_assets as $cdn_asset) {
  339. $cdn_weight += .01;
  340. $js[$cdn_asset] = drupal_js_defaults($cdn_asset);
  341. $js[$cdn_asset]['type'] = 'external';
  342. $js[$cdn_asset]['every_page'] = TRUE;
  343. $js[$cdn_asset]['weight'] = $cdn_weight;
  344. }
  345. }
  346. }
  347. /**
  348. * Implements hook_icon_bundle_list_alter().
  349. */
  350. function bootstrap_icon_bundle_list_alter(&$build, $bundle) {
  351. if (bootstrap_setting('tooltip_enabled')) {
  352. foreach ($build as &$icon) {
  353. $icon['#attributes']['data-toggle'] = 'tooltip';
  354. $icon['#attributes']['data-placement'] = 'bottom';
  355. }
  356. }
  357. }
  358. /**
  359. * Implements hook_menu_local_tasks_alter().
  360. */
  361. function bootstrap_menu_local_tasks_alter(&$data, &$router_item, $root_path) {
  362. if (!empty($data['actions']['output'])) {
  363. $items = array();
  364. foreach ($data['actions']['output'] as $item) {
  365. $items[] = array(
  366. 'data' => $item,
  367. );
  368. }
  369. $data['actions']['output'] = array(
  370. '#theme' => 'item_list__action_links',
  371. '#items' => $items,
  372. '#attributes' => array(
  373. 'class' => array('action-links'),
  374. ),
  375. );
  376. }
  377. }
  378. /**
  379. * Implements hook_js_callback_filter_xss_alter().
  380. */
  381. function bootstrap_js_callback_filter_xss_alter(array &$allowed_tags = array()) {
  382. $allowed_tags[] = 'button';
  383. }