registry.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <?php
  2. /**
  3. * @file
  4. * List of functions used to alter the theme registry in Bootstrap based themes.
  5. */
  6. /**
  7. * @addtogroup registry
  8. * @{
  9. */
  10. // Define additional sub-groups for creating lists for all the theme files.
  11. /**
  12. * @defgroup theme_functions Theme Functions (.func.php)
  13. *
  14. * List of theme functions used in the Drupal Bootstrap base theme.
  15. *
  16. * View the parent topic for additional documentation.
  17. */
  18. /**
  19. * @defgroup theme_preprocess Theme Preprocess Functions (.vars.php)
  20. *
  21. * List of theme preprocess functions used in the Drupal Bootstrap base theme.
  22. *
  23. * View the parent topic for additional documentation.
  24. */
  25. /**
  26. * @defgroup theme_process Theme Process Functions (.vars.php)
  27. *
  28. * List of theme process functions used in the Drupal Bootstrap base theme.
  29. *
  30. * View the parent topic for additional documentation.
  31. */
  32. /**
  33. * @defgroup templates Theme Templates (.tpl.php)
  34. *
  35. * List of theme templates used in the Drupal Bootstrap base theme.
  36. *
  37. * View the parent topic for additional documentation.
  38. */
  39. /**
  40. * Stub implementation for bootstrap_theme().
  41. *
  42. * This base-theme's custom theme hook implementations. Never define "path"
  43. * or "template" as these are detected and automatically added.
  44. *
  45. * @see bootstrap_theme_registry_alter()
  46. * @see bootstrap_hook_theme_complete()
  47. * @see bootstrap_theme()
  48. * @see hook_theme()
  49. */
  50. function _bootstrap_theme(&$existing, $type, $theme, $path) {
  51. // Bootstrap Carousels.
  52. $hooks['bootstrap_carousel'] = array(
  53. 'variables' => array(
  54. 'attributes' => array(),
  55. 'items' => array(),
  56. 'start_index' => 0,
  57. 'controls' => TRUE,
  58. 'indicators' => TRUE,
  59. 'interval' => 5000,
  60. 'pause' => 'hover',
  61. 'wrap' => TRUE,
  62. ),
  63. );
  64. // Bootstrap Dropdowns.
  65. $hooks['bootstrap_dropdown'] = array(
  66. 'render element' => 'element',
  67. );
  68. // Bootstrap Modals.
  69. $hooks['bootstrap_modal'] = array(
  70. 'variables' => array(
  71. 'heading' => '',
  72. 'body' => '',
  73. 'footer' => '',
  74. 'dialog_attributes' => array(),
  75. 'attributes' => array(),
  76. 'size' => '',
  77. 'html_heading' => FALSE,
  78. ),
  79. );
  80. // Bootstrap Panels.
  81. $hooks['bootstrap_panel'] = array(
  82. 'render element' => 'element',
  83. );
  84. // Bootstrap search form wrapper.
  85. // @todo Remove this as it's not really needed and should use suggestions.
  86. $hooks['bootstrap_search_form_wrapper'] = array(
  87. 'render element' => 'element',
  88. );
  89. return $hooks;
  90. }
  91. /**
  92. * Implements hook_theme_registry_alter().
  93. */
  94. function bootstrap_theme_registry_alter(&$registry) {
  95. // Retrieve the active theme names.
  96. $themes = _bootstrap_get_base_themes(NULL, TRUE);
  97. // Return the theme registry unaltered if it is not Bootstrap based.
  98. if (!in_array('bootstrap', $themes)) {
  99. return;
  100. }
  101. // Inject the "footer" variable default in the existing "table" hook.
  102. // @see https://drupal.org/node/806982
  103. // @todo Make this discoverable in some way instead of a manual injection.
  104. $registry['table']['variables']['footer'] = NULL;
  105. // Process registered hooks in the theme registry.
  106. _bootstrap_process_theme_registry($registry, $themes);
  107. // Process registered hooks in the theme registry to add necessary theme hook
  108. // suggestion phased function invocations. This must be run after separately
  109. // and after all includes have been loaded.
  110. _bootstrap_process_theme_registry_suggestions($registry, $themes);
  111. // Post-process theme registry. This happens after all altering has occurred.
  112. foreach ($registry as $hook => $info) {
  113. // Ensure uniqueness.
  114. if (!empty($registry[$hook]['includes'])) {
  115. $registry[$hook]['includes'] = array_unique($info['includes']);
  116. }
  117. if (!empty($registry[$hook]['preprocess functions'])) {
  118. $registry[$hook]['preprocess functions'] = array_unique($info['preprocess functions']);
  119. }
  120. if (!empty($registry[$hook]['process functions'])) {
  121. $registry[$hook]['process functions'] = array_unique($info['process functions']);
  122. }
  123. // Ensure "theme path" is set.
  124. if (!isset($registry[$hook]['theme path'])) {
  125. $registry[$hook]['theme path'] = $GLOBALS['theme_path'];
  126. }
  127. }
  128. }
  129. /**
  130. * Processes registered hooks in the theme registry against list of themes.
  131. *
  132. * Discovers and fills missing elements in the theme registry. This is similar
  133. * to _theme_process_registry(), however severely modified for Bootstrap based
  134. * themes.
  135. *
  136. * All additions or modifications must live in `./templates`, relative to the
  137. * base theme or sub-theme's base folder. These files can be organized in any
  138. * order using sub-folders as it searches recursively.
  139. *
  140. * Adds or modifies the following theme hook keys:
  141. * - `includes`: When a variables file `*.vars.php` is found.
  142. * - `includes`: When a function file `*.func.php` is found.
  143. * - `function`: When a specific theme hook function override is found.
  144. * - `template`: When a template file `*.tpl.php` is found in. Note, if both
  145. * a function and a template are defined, a template implementation will
  146. * always be used and the `function` will be unset.
  147. * - `path`: When a template file `*.tpl.php` is found.
  148. * - `preprocess functions`: When a specific theme hook suggestion function
  149. * `hook_preprocess_HOOK__SUGGESTION` is found.
  150. * - `process functions` When a specific theme hook suggestion function
  151. * `hook_process_HOOK__SUGGESTION` is found.
  152. *
  153. * @param array $registry
  154. * The theme registry array, passed by reference.
  155. * @param string|array $themes
  156. * The name of the theme or list of theme names to process.
  157. *
  158. * @see bootstrap_theme_registry_alter()
  159. * @see _theme_process_registry()
  160. * @see _theme_build_registry()
  161. */
  162. function _bootstrap_process_theme_registry(&$registry, $themes) {
  163. // Convert to an array if needed.
  164. if (is_string($themes)) {
  165. $themes = array();
  166. }
  167. // Processor functions work in two distinct phases with the process
  168. // functions always being executed after the preprocess functions.
  169. $variable_process_phases = array(
  170. 'preprocess functions' => 'preprocess',
  171. 'process functions' => 'process',
  172. );
  173. // Iterate over each theme passed.
  174. // Iterate over the [pre]process phases.
  175. foreach ($variable_process_phases as $phase_key => $phase) {
  176. foreach ($themes as $theme) {
  177. // Get the theme's base path.
  178. $path = drupal_get_path('theme', $theme);
  179. // Find theme function overrides.
  180. foreach (drupal_system_listing('/\.(func|vars)\.php$/', $path, 'name', 0) as $name => $file) {
  181. // Strip off the extension.
  182. if (($pos = strpos($name, '.')) !== FALSE) {
  183. $name = substr($name, 0, $pos);
  184. }
  185. // Transform "-" in file names to "_" to match theme hook naming scheme.
  186. $hook = strtr($name, '-', '_');
  187. // File to be included by core's theme function when a theme hook is
  188. // invoked.
  189. if (isset($registry[$hook])) {
  190. if (!isset($registry[$hook][$phase_key])) {
  191. $registry[$hook][$phase_key] = array();
  192. }
  193. if (!isset($registry[$hook]['includes'])) {
  194. $registry[$hook]['includes'] = array();
  195. }
  196. // Include the file now so functions can be discovered below.
  197. include_once DRUPAL_ROOT . '/' . $file->uri;
  198. if (!in_array($file->uri, $registry[$hook]['includes'])) {
  199. $registry[$hook]['includes'][] = $file->uri;
  200. }
  201. }
  202. }
  203. // Process core's normal functionality.
  204. _theme_process_registry($registry, $theme, $GLOBALS['theme_key'] === $theme ? 'theme' : 'base_theme', $theme, $path);
  205. // Find necessary templates in the theme.
  206. $registry = drupal_array_merge_deep($registry, drupal_find_theme_templates($registry, '.tpl.php', $path));
  207. // Iterate over each registered hook.
  208. foreach ($registry as $hook => $info) {
  209. // Remove function callbacks if a template was found.
  210. if (isset($info['function']) && isset($info['template'])) {
  211. unset($registry[$hook]['function']);
  212. }
  213. // Correct template theme paths.
  214. if (!isset($info['theme path'])) {
  215. $registry[$hook]['theme path'] = $path;
  216. }
  217. // Correct the type that is implementing this override.
  218. $registry[$hook]['type'] = $GLOBALS['theme_path'] === $registry[$hook]['theme path'] ? 'theme' : 'base_theme';
  219. // Sort the phase functions.
  220. // @see https://www.drupal.org/node/2098551
  221. _bootstrap_registry_sort_phase_functions($registry[$hook][$phase_key], $hook, $phase, $themes);
  222. // Setup a default "context" variable. This allows #context to be passed
  223. // to every template and theme function.
  224. // @see https://drupal.org/node/2035055
  225. if (isset($info['variables']) && !isset($info['variables']['context'])) {
  226. $registry[$hook]['variables']['context'] = array();
  227. }
  228. // Setup a default "icon" variable. This allows #icon to be passed
  229. // to every template and theme function.
  230. // @see https://drupal.org/node/2219965
  231. if (isset($info['variables']) && !isset($info['variables']['icon'])) {
  232. $registry[$hook]['variables']['icon'] = NULL;
  233. }
  234. if (isset($info['variables']) && !isset($info['variables']['icon_position'])) {
  235. $registry[$hook]['variables']['icon_position'] = 'before';
  236. }
  237. }
  238. }
  239. }
  240. }
  241. /**
  242. * Ensures the phase functions are invoked in the correct order.
  243. *
  244. * @param array $functions
  245. * The phase functions to iterate over.
  246. * @param string $hook
  247. * The current hook being processed.
  248. * @param string $phase
  249. * The current phase being processed.
  250. * @param array $themes
  251. * An indexed array of current themes.
  252. *
  253. * @see https://www.drupal.org/node/2098551
  254. */
  255. function _bootstrap_registry_sort_phase_functions(&$functions, $hook, $phase, $themes) {
  256. // Immediately return if there is nothing to sort.
  257. if (count($functions) < 2) {
  258. return;
  259. }
  260. // Create an associative array of theme functions to ensure sort order.
  261. $theme_functions = array_fill_keys($themes, array());
  262. // Iterate over all the themes.
  263. foreach ($themes as $theme) {
  264. // Only add the function to the array of theme functions if it currently
  265. // exists in the $functions array.
  266. $function = $theme . '_' . $phase . '_' . $hook;
  267. $key = array_search($function, $functions);
  268. if ($key !== FALSE) {
  269. // Save the theme function to be added later, but sorted.
  270. $theme_functions[$theme][] = $function;
  271. // Remove it from the current $functions array.
  272. unset($functions[$key]);
  273. }
  274. }
  275. // Iterate over all the captured theme functions and place them back into
  276. // the phase functions array.
  277. foreach ($theme_functions as $array) {
  278. $functions = array_merge($functions, $array);
  279. }
  280. }
  281. /**
  282. * Processes registered hooks in the theme registry against list of themes.
  283. *
  284. * This is used to add the necessary phased functions to theme hook suggestions.
  285. * Because it uses get_defined_functions(), it must be invoked after all
  286. * includes have been detected and loaded. This is similar to
  287. * drupal_find_theme_functions(), however severely modified for Bootstrap based
  288. * themes.
  289. *
  290. * @param array $registry
  291. * The theme registry array, passed by reference.
  292. * @param string|array $themes
  293. * The name of the theme or list of theme names to process.
  294. *
  295. * @return array
  296. * The functions found, suitable for returning from hook_theme;
  297. *
  298. * @see https://drupal.org/node/939462
  299. * @see drupal_find_theme_functions()
  300. */
  301. function _bootstrap_process_theme_registry_suggestions(&$registry, $themes) {
  302. // Convert to an array if needed.
  303. if (is_string($themes)) {
  304. $themes = array();
  305. }
  306. // Merge in normal core detections first.
  307. $registry = drupal_array_merge_deep($registry, drupal_find_theme_functions($registry, $themes));
  308. // Processor functions work in two distinct phases with the process
  309. // functions always being executed after the preprocess functions.
  310. $variable_process_phases = array(
  311. 'preprocess functions' => 'preprocess',
  312. 'process functions' => 'process',
  313. );
  314. $grouped_functions = drupal_group_functions_by_prefix();
  315. // Iterate over each theme passed.
  316. foreach ($themes as $theme) {
  317. // Iterate over each registered hook.
  318. foreach ($registry as $hook => $info) {
  319. // The pattern to match.
  320. $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
  321. // Only process hooks that have not explicitly "turned off" patterns.
  322. if (empty($pattern)) {
  323. continue;
  324. }
  325. // Iterate over the [pre]process phases.
  326. foreach ($variable_process_phases as $phase_key => $phase) {
  327. // Find functions matching the specific theme and phase prefix.
  328. $prefix = $theme . '_' . $phase;
  329. // Grep only the functions which are within the prefix group.
  330. list($first_prefix,) = explode('_', $prefix, 2);
  331. if (isset($grouped_functions[$first_prefix]) && ($matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $grouped_functions[$first_prefix]))) {
  332. foreach ($matches as $match) {
  333. // Determine the current theme implementation.
  334. $hook = substr($match, strlen($prefix) + 1);
  335. $base_hook = $hook;
  336. // If there's no current theme implementation, keep checking for
  337. // more generic base hooks. If there's still no implementation,
  338. // one must be created using the last found implementation
  339. // information.
  340. if (!isset($registry[$base_hook]) || isset($registry[$base_hook]['base hook'])) {
  341. // Iteratively strip everything after the last '__' delimiter,
  342. // until an implementation is found.
  343. while ($pos = strrpos($base_hook, '__')) {
  344. $base_hook = substr($base_hook, 0, $pos);
  345. if (isset($registry[$base_hook])) {
  346. break;
  347. }
  348. }
  349. // No base hook was found, this allows the implementation to be
  350. // ignored in the next steps.
  351. if (!isset($registry[$base_hook])) {
  352. $base_hook = FALSE;
  353. }
  354. }
  355. // Process specific base hook implementations if necessary.
  356. if ($base_hook) {
  357. // The matched theme implementation does not exist in the
  358. // registry, one must be created if base hook information was
  359. // found, otherwise it will be ignored.
  360. if (!isset($registry[$hook])) {
  361. $registry[$base_hook] += array(
  362. 'type' => 'theme',
  363. 'preprocess functions' => array(),
  364. 'process functions' => array(),
  365. );
  366. $hook_type = isset($registry[$base_hook]['function']) ? 'function' : 'template';
  367. $arg_name = isset($registry[$base_hook]['variables']) ? 'variables' : 'render element';
  368. $registry[$hook] = array(
  369. $hook_type => $registry[$base_hook][$hook_type],
  370. $arg_name => $registry[$base_hook][$arg_name],
  371. 'base hook' => $base_hook,
  372. 'type' => $registry[$base_hook]['type'],
  373. 'preprocess functions' => array(),
  374. 'process functions' => array(),
  375. );
  376. if (isset($registry[$base_hook]['path'])) {
  377. $registry[$hook]['path'] = $registry[$base_hook]['path'];
  378. }
  379. if (isset($registry[$base_hook]['theme path'])) {
  380. $registry[$hook]['theme path'] = $registry[$base_hook]['theme path'];
  381. }
  382. }
  383. }
  384. // If the hook exists, merge in the functions. Otherwise ignore it
  385. // since there was no base hook found and a new implementation
  386. // could not be created.
  387. if (isset($registry[$hook])) {
  388. $registry[$hook] = drupal_array_merge_deep($registry[$hook], array(
  389. $phase_key => array($match),
  390. ));
  391. // Due to how theme() functions, if a base hook implements
  392. // preprocess or process functions, then the base hook info is
  393. // used to invoke the necessary phase functions instead of the
  394. // suggestion hook info. To get around this, a helper function
  395. // must be appended to the base hook info so it can call the
  396. // theme suggestion implementation's phase function.
  397. $function = '_bootstrap_' . $phase . '_theme_suggestion';
  398. if (!in_array($function, $registry[$base_hook][$phase_key])) {
  399. $registry[$base_hook][$phase_key][] = $function;
  400. }
  401. }
  402. }
  403. }
  404. }
  405. }
  406. }
  407. }
  408. /**
  409. * Performance gain.
  410. *
  411. * Do not remove from 7.x. This function is not available in every core version.
  412. *
  413. * @see https://www.drupal.org/node/2339447
  414. */
  415. if (!function_exists('drupal_group_functions_by_prefix')) {
  416. /**
  417. * Group all user functions by word before first underscore.
  418. *
  419. * @return array
  420. * Functions grouped by the first prefix.
  421. */
  422. function drupal_group_functions_by_prefix() {
  423. $functions = get_defined_functions();
  424. $grouped_functions = array();
  425. // Splitting user defined functions into groups by the first prefix.
  426. foreach ($functions['user'] as $function) {
  427. list($first_prefix,) = explode('_', $function, 2);
  428. $grouped_functions[$first_prefix][] = $function;
  429. }
  430. return $grouped_functions;
  431. }
  432. }
  433. /**
  434. * @} End of "addtogroup registry".
  435. */