devel_themer.module

  1. cis7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  2. cle7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  3. ecd7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  4. elmsmedia7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  5. harmony7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  6. icor7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  7. meedjum_blog7 sites/all/modules/ulmus/devel_themer/devel_themer.module
  8. mooc7 sites/all/modules/ulmus/devel_themer/devel_themer.module

Functions

Namesort descending Description
devel_counter
devel_themer_ajax_variables A menu callback used by popup to retrieve variables from cache for a recent page.
devel_themer_catch_function Intercepts all theme calls (including templates), adds to template log, and dispatches to original theme function.
devel_themer_compression_enabled Returns true if compression of temporary files is enabled.
devel_themer_cron Implements hook_cron().
devel_themer_init Implements hook_init().
devel_themer_inject_markers Injects markers into the html returned by theme functions/templates.
devel_themer_load_krumo Load stored variables.
devel_themer_log Show all theme templates and functions that could have been used on this page.
devel_themer_menu Implements hook_menu().
devel_themer_module_implements_alter Implements hook_module_implements_alter().
devel_themer_page_alter Implements hook_page_alter().
devel_themer_popup Return the popup template placed here for easy editing
devel_themer_post_process_page
devel_themer_shutdown
devel_themer_store_krumo Temporarily store theme hook variables so that they can be request via ajax when needed.
devel_themer_theme_registry_alter Implements hook_theme_registry_alter().
devel_themer_theme_twin Nearly clones the Drupal API theme() function.
_devel_themer_get_theme_arguments Returns the arguments passed to the original theme function.

Constants

Namesort descending Description
DEVEL_THEMER_ATTRIBUTE The name of the attribute used to store the thmr id.

File

sites/all/modules/ulmus/devel_themer/devel_themer.module
View source
  1. <?php
  2. /**
  3. * The name of the attribute used to store the thmr id.
  4. */
  5. define ('DEVEL_THEMER_ATTRIBUTE', 'data-thmr');
  6. /**
  7. * Implements hook_menu().
  8. */
  9. function devel_themer_menu() {
  10. $items = array();
  11. $items['admin/config/development/devel_themer'] = array(
  12. 'title' => 'Devel Themer',
  13. 'description' => 'Display or hide the textual template log',
  14. 'page callback' => 'drupal_get_form',
  15. 'page arguments' => array('devel_themer_admin_settings'),
  16. 'access arguments' => array('administer site configuration'),
  17. 'file' => 'devel_themer.admin.inc',
  18. 'type' => MENU_NORMAL_ITEM,
  19. );
  20. $items['devel_themer/variables/%'] = array(
  21. 'title' => 'Theme Development AJAX variables',
  22. 'page callback' => 'devel_themer_ajax_variables',
  23. 'page arguments' => array(2),
  24. 'delivery callback' => 'ajax_deliver',
  25. 'access arguments' => array('access devel information'),
  26. 'type' => MENU_CALLBACK,
  27. );
  28. return $items;
  29. }
  30. /**
  31. * A menu callback used by popup to retrieve variables from cache for a recent page.
  32. *
  33. * @param $key
  34. * The unique key that is sent to the browser to identify the variables
  35. * for a theme hook.
  36. * @return string
  37. * A chunk of HTML with the devel_print_object() rendering of the variables.
  38. */
  39. function devel_themer_ajax_variables($key) {
  40. $content = devel_themer_load_krumo($key);
  41. if (empty($content)) {
  42. $content = t('Unable to load variables from temporary storage.');
  43. }
  44. $commands[] = ajax_command_replace('div.themer-variables', '<div class="themer-variables">' . $content . '</div>');
  45. return array('#type' => 'ajax', '#commands' => $commands);
  46. }
  47. /**
  48. * Implements hook_init().
  49. */
  50. function devel_themer_init() {
  51. // Make sure the temporary directory exists.
  52. $directory = "temporary://devel_themer";
  53. file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  54. if (user_access('access devel information')) {
  55. // Add requisite libraries.
  56. drupal_add_library('system', 'jquery.form');
  57. drupal_add_library('system', 'drupal.ajax');
  58. drupal_add_library('system', 'ui.draggable');
  59. drupal_add_library('system', 'ui.resizable');
  60. // Add this module's JS and CSS.
  61. $path = drupal_get_path('module', 'devel_themer');
  62. drupal_add_js($path . '/devel_themer.js');
  63. drupal_add_css($path . '/devel_themer.css');
  64. drupal_add_css($path . '/devel_themer_ie_fix.css', array(
  65. 'browsers' => array('IE' => 'lt IE 7', '!IE' => FALSE),
  66. 'media' => 'screen',
  67. 'weight' => 20,
  68. 'preprocess' => FALSE,
  69. ));
  70. // Add krumo JS and CSS. We can't rely on krumo automatically doing this,
  71. // because we add our HTML dynamically after initial page load.
  72. if (has_krumo()) {
  73. $path_to_devel = drupal_get_path('module', 'devel');
  74. // Krumo files don't work correctly when aggregated.
  75. drupal_add_js($path_to_devel . '/krumo/krumo.js', array('preprocess' => FALSE));
  76. drupal_add_css($path_to_devel . '/krumo/skins/default/skin.css', array('preprocess' => FALSE));
  77. }
  78. devel_themer_popup();
  79. if (!devel_silent() && variable_get('devel_themer_log', FALSE)) {
  80. register_shutdown_function('devel_themer_shutdown');
  81. }
  82. }
  83. }
  84. function devel_themer_shutdown() {
  85. print devel_themer_log();
  86. }
  87. /**
  88. * Show all theme templates and functions that could have been used on this page.
  89. */
  90. function devel_themer_log() {
  91. if (isset($GLOBALS['devel_theme_calls'])) {
  92. foreach ($GLOBALS['devel_theme_calls'] as $counter => $call) {
  93. // Sometimes $call is a string. Not sure why.
  94. if (is_array($call)) {
  95. $id = "devel_theme_log_link_$counter";
  96. $marker = "<div id=\"$id\" class=\"devel_theme_log_link\"></div>\n";
  97. $used = $call['used'];
  98. if ($call['type'] == 'func') {
  99. $name = $call['name'] . '()';
  100. foreach ($call['candidates'] as $item) {
  101. if ($item == $used) {
  102. $items[] = "<strong>$used</strong>";
  103. }
  104. else {
  105. $items[] = $item;
  106. }
  107. }
  108. }
  109. else {
  110. $name = $call['name'];
  111. foreach ($call['candidates'] as $item) {
  112. if ($item == basename($used)) {
  113. $items[] = "<strong>$used</strong>";
  114. }
  115. else {
  116. $items[] = $item;
  117. }
  118. }
  119. }
  120. $rows[] = array($call['duration'], $marker . $name, implode(', ', $items));
  121. unset($items);
  122. }
  123. }
  124. $header = array('Duration (ms)', 'Template/Function', "Candidate template files or function names");
  125. $output = theme('table', $header, $rows);
  126. return $output;
  127. }
  128. }
  129. /**
  130. * Implements hook_theme_registry_alter().
  131. *
  132. * Route all theme hooks to devel_themer_catch_function().
  133. */
  134. function devel_themer_theme_registry_alter(&$theme_registry) {
  135. foreach ($theme_registry as $hook => $data) {
  136. // We wrap all hooks with our custom handler.
  137. $theme_registry[$hook] = array(
  138. 'function' => 'devel_themer_catch_function',
  139. 'theme path' => $data['theme path'],
  140. 'variables' => array(),
  141. 'original' => $data,
  142. );
  143. }
  144. }
  145. /**
  146. * Implements hook_module_implements_alter().
  147. *
  148. * Ensure devel_themer_theme_registry_alter() runs as late as possible.
  149. */
  150. function devel_themer_module_implements_alter(&$implementations, $hook) {
  151. if (in_array($hook, array('page_alter', 'theme_registry_alter'))) {
  152. // Unsetting and resetting moves the item to the end of the array.
  153. $group = $implementations['devel_themer'];
  154. unset($implementations['devel_themer']);
  155. $implementations['devel_themer'] = $group;
  156. }
  157. }
  158. /**
  159. * Injects markers into the html returned by theme functions/templates.
  160. *
  161. * Uses simplehtmldom to add a thmr attribute to toplevel html elements.
  162. * A toplevel text element will be wrapped in a span.
  163. *
  164. * @param string $html
  165. * @param string $marker
  166. */
  167. function devel_themer_inject_markers($html, $marker) {
  168. if (!module_exists('simplehtmldom')) {
  169. drupal_set_message(t('<a href="http://drupal.org/project/simplehtmldom">Simplehtmldom</a> module is missing and required by Theme developer.'), 'error', FALSE);
  170. return $html;
  171. }
  172. $html_dom = new simple_html_dom();
  173. $html_dom->load($html);
  174. foreach ($html_dom->root->nodes as $element) {
  175. if ($element->nodetype == HDOM_TYPE_TEXT) {
  176. if (trim($element->innertext) !== '') {
  177. $element->innertext = "<span " . DEVEL_THEMER_ATTRIBUTE . "='$marker' class='devel-themer-wrapper'>{$element->innertext}</span>";
  178. }
  179. }
  180. elseif ($element->hasAttribute(DEVEL_THEMER_ATTRIBUTE)) {
  181. $element->setAttribute(DEVEL_THEMER_ATTRIBUTE, "$marker " . $element->getAttribute(DEVEL_THEMER_ATTRIBUTE));
  182. }
  183. else {
  184. $element->setAttribute(DEVEL_THEMER_ATTRIBUTE, $marker);
  185. }
  186. }
  187. $html = (string)$html_dom;
  188. // Release memory.
  189. $html_dom->clear();
  190. unset($html_dom);
  191. return $html;
  192. }
  193. /**
  194. * Returns the arguments passed to the original theme function.
  195. *
  196. * This function uses debug_backtrace to find these arguments and assumes that
  197. * theme() is two levels up. This function is called by
  198. * devel_themer_catch_function() which is called by theme().
  199. */
  200. function _devel_themer_get_theme_arguments() {
  201. $trace = debug_backtrace(FALSE);
  202. $hook = $trace[2]['args'][0];
  203. if (sizeof($trace[2]['args']) > 1) {
  204. $variables = $trace[2]['args'][1];
  205. if (!is_array($variables)) {
  206. watchdog('devel_themer', 'Variables should be passed as an associative array to the theme hook !hook.', array('!hook' => is_array($hook) ? implode(', ', $hook) : $hook), WATCHDOG_ERROR);
  207. }
  208. }
  209. else {
  210. $variables = array();
  211. }
  212. return array($hook, $variables);
  213. }
  214. /**
  215. * Intercepts all theme calls (including templates), adds to template log, and dispatches to original theme function.
  216. */
  217. function devel_themer_catch_function() {
  218. list($hook, $variables) = _devel_themer_get_theme_arguments();
  219. $counter = devel_counter();
  220. $key = "thmr_" . $counter;
  221. timer_start($key);
  222. // The twin of theme(). All rendering done through here.
  223. $meta = array(
  224. 'name' => $hook,
  225. 'process functions' => array(),
  226. 'preprocess functions' => array(),
  227. 'suggestions' => array(),
  228. 'variables' => $variables,
  229. );
  230. $return = devel_themer_theme_twin($hook, $variables, $meta);
  231. $time = timer_stop($key);
  232. if (!empty($return) && !is_array($return) && !is_object($return) && user_access('access devel information')) {
  233. // Check for themer attribute in content returned. Apply word boundaries so
  234. // that 'thmr_10' doesn't match 'thmr_1'.
  235. if (!preg_match("/\\b$key\\b/", $return)) {
  236. // Exclude wrapping a SPAN around content returned by theme functions
  237. // whose result is not intended for HTML usage.
  238. $exclude = array('options_none');
  239. // theme_html_tag() is a low-level theme function intended primarily for
  240. // markup added to the document HEAD.
  241. $exclude[] = 'html_tag';
  242. // DATE MODULE: Inline labels for date select lists shouldn't be wrapped.
  243. if (strpos($meta['hook'], 'date_part_label_') === 0 && $variables['element']['#type'] == 'date_select' && $variables['element']['#date_label_position'] == 'within') {
  244. $exclude[] = $hook;
  245. }
  246. if (!in_array($hook, $exclude)) {
  247. $return = devel_themer_inject_markers($return, $key);
  248. }
  249. }
  250. if ($meta['type'] == 'function') {
  251. global $theme;
  252. // If the function hasn't been overwritten by the current theme, add it
  253. // as a suggestion.
  254. if ("{$theme}_{$meta['suggested_hook']}()" != $meta['used']) {
  255. $meta['suggestions'][] = $meta['suggested_hook'];
  256. }
  257. foreach ($meta['suggestions'] as $delta => $suggestion) {
  258. $meta['suggestions'][$delta] = "{$theme}_{$suggestion}()";
  259. }
  260. }
  261. else {
  262. // If the template hasn't been overwritten by the theme, add it as a
  263. // suggestion.
  264. if (FALSE === strpos($meta['template_file'], path_to_theme() . '/')) {
  265. $meta['suggestions'][] = $meta['suggested_hook'];
  266. }
  267. foreach ($meta['suggestions'] as $delta => $suggestion) {
  268. $meta['suggestions'][$delta] = strtr($suggestion, '_', '-') . $meta['extension'];
  269. }
  270. }
  271. $GLOBALS['devel_theme_calls'][$key] = array(
  272. 'id' => $key,
  273. 'name' => $meta['suggested_hook'],
  274. 'used' => ($meta['type'] == 'function') ? $meta['used'] : $meta['template_file'],
  275. 'type' => $meta['type'],
  276. 'duration' => $time['time'],
  277. 'candidates' => $meta['suggestions'],
  278. 'preprocessors' => $meta['preprocess functions'],
  279. 'processors' => $meta['process functions'],
  280. // Variables are stored on the server and sent to browser via Ajax.
  281. 'variables' => devel_themer_store_krumo($meta['variables']),
  282. );
  283. }
  284. return $return;
  285. }
  286. /**
  287. * Returns true if compression of temporary files is enabled.
  288. */
  289. function devel_themer_compression_enabled() {
  290. return variable_get('devel_themer_compress_temporary_files', TRUE)
  291. && function_exists('gzcompress');
  292. }
  293. /**
  294. * Temporarily store theme hook variables so that they can be request via ajax
  295. * when needed.
  296. */
  297. function devel_themer_store_krumo($variables) {
  298. // Sometimes serialize will fail, so we will use krumo_ob as a backup.
  299. // We don't use krumo_ob by default because it's resource heavy.
  300. $data = serialize($variables);
  301. if (empty($data)) {
  302. $data = krumo_ob($variables);
  303. $filename = 'k' . sha1($data);
  304. }
  305. else {
  306. $filename = 's' . sha1($data);
  307. }
  308. if (devel_themer_compression_enabled()) {
  309. $filename = 'c' . $filename;
  310. $data = gzcompress($data);
  311. }
  312. // Write the variables information to the a file. It will be retrieved on demand via AJAX.
  313. // We used to write this to DB but was getting 'Warning: Got a packet bigger than 'max_allowed_packet' bytes'
  314. // Writing to temp dir means we don't worry about folder existence/perms and cleanup is free.
  315. file_put_contents("temporary://devel_themer/$filename", $data);
  316. return $filename;
  317. }
  318. /**
  319. * Implements hook_cron().
  320. */
  321. function devel_themer_cron() {
  322. // We don't use managed temporary files any more because of performance issues
  323. // so we need to clean up old files ourselves.
  324. foreach (file_scan_directory('temporary://devel_themer/', '/.*/') as $file) {
  325. if (filemtime($file->uri) < REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE) {
  326. unlink($file->uri);
  327. }
  328. }
  329. }
  330. /**
  331. * Load stored variables.
  332. */
  333. function devel_themer_load_krumo($key) {
  334. $data = file_get_contents("temporary://devel_themer/$key");
  335. if (empty($data)) {
  336. return FALSE;
  337. }
  338. if ($key{0} == 'c') {
  339. if (function_exists('gzuncompress')) {
  340. $data = gzuncompress($data);
  341. $key = substr($key, 1);
  342. }
  343. else {
  344. return FALSE;
  345. }
  346. }
  347. if ($key{0} == 's') {
  348. $data = krumo_ob(unserialize($data));
  349. }
  350. return $data;
  351. }
  352. /**
  353. * Nearly clones the Drupal API theme() function.
  354. *
  355. * It should behave exactly as the core theme() function. The only difference
  356. * is a third parameter $meta which is used to collect meta data about the
  357. * theming process.
  358. *
  359. * The code differences between theme() and devel_themer_theme_twin() should
  360. * be kept to a minimum. We should only add lines to collect meta data and
  361. * any occurrence of $hooks[$hook] should be replaced by
  362. * $hooks[$hook]['original'].
  363. *
  364. * @param $meta
  365. * An associative array with the following keys:
  366. * - 'suggestions': Candidate hooks which have been looked at but don't have
  367. * an implementation.
  368. * - 'hook': The first found hook with an implementation.
  369. * - 'suggested_hook': Processor functions can suggest other theme hooks.
  370. * - 'used': The specific theme function/template that is actually used.
  371. * - 'preprocess functions': The preprocess functions.
  372. * - 'process functions: The process functions.
  373. * - 'type': template or function.
  374. * - 'extension': Holds the template extension. This is only set if type
  375. * is template.
  376. * - 'template_file': Holds the full path to the template. This is only set
  377. * if type is template.
  378. * @see theme().
  379. */
  380. function devel_themer_theme_twin($hook, $variables, &$meta) {
  381. // If called before all modules are loaded, we do not necessarily have a full
  382. // theme registry to work with, and therefore cannot process the theme
  383. // request properly. See also _theme_load_registry().
  384. if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) {
  385. throw new Exception(t('theme() may not be called until all modules are loaded.'));
  386. }
  387. $hooks = theme_get_registry(FALSE);
  388. // If an array of hook candidates were passed, use the first one that has an
  389. // implementation.
  390. if (is_array($hook)) {
  391. foreach ($hook as $candidate) {
  392. if (isset($hooks[$candidate])) {
  393. break;
  394. }
  395. $meta['suggestions'][] = $candidate;
  396. }
  397. $hook = $candidate;
  398. }
  399. // If there's no implementation, check for more generic fallbacks. If there's
  400. // still no implementation, log an error and return an empty string.
  401. if (!isset($hooks[$hook])) {
  402. // Iteratively strip everything after the last '__' delimiter, until an
  403. // implementation is found.
  404. while ($pos = strrpos($hook, '__')) {
  405. $hook = substr($hook, 0, $pos);
  406. if (isset($hooks[$hook])) {
  407. break;
  408. }
  409. $meta['suggestions'][] = $hook;
  410. }
  411. if (!isset($hooks[$hook])) {
  412. // Only log a message when not trying theme suggestions ($hook being an
  413. // array).
  414. if (!isset($candidate)) {
  415. watchdog('devel_themer', 'Theme hook %hook not found.', array('%hook' => $hook), WATCHDOG_WARNING);
  416. }
  417. return '';
  418. }
  419. }
  420. $info = $hooks[$hook]['original'];
  421. $meta['suggested_hook'] = $meta['hook'] = $hook;
  422. global $theme_path;
  423. $temp = $theme_path;
  424. // point path_to_theme() to the currently used theme path:
  425. $theme_path = $info['theme path'];
  426. // Include a file if the theme function or variable processor is held
  427. // elsewhere.
  428. if (!empty($info['includes'])) {
  429. foreach ($info['includes'] as $include_file) {
  430. include_once DRUPAL_ROOT . '/' . $include_file;
  431. }
  432. }
  433. // If a renderable array is passed as $variables, then set $variables to
  434. // the arguments expected by the theme function.
  435. if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) {
  436. $element = $variables;
  437. $variables = array();
  438. if (isset($info['variables'])) {
  439. foreach (array_keys($info['variables']) as $name) {
  440. if (isset($element["#$name"])) {
  441. $variables[$name] = $element["#$name"];
  442. }
  443. }
  444. }
  445. else {
  446. $variables[$info['render element']] = $element;
  447. }
  448. }
  449. // Merge in argument defaults.
  450. if (!empty($info['variables'])) {
  451. $variables += $info['variables'];
  452. }
  453. elseif (!empty($info['render element'])) {
  454. $variables += array($info['render element'] => array());
  455. }
  456. // Invoke the variable processors, if any. The processors may specify
  457. // alternate suggestions for which hook's template/function to use. If the
  458. // hook is a suggestion of a base hook, invoke the variable processors of
  459. // the base hook, but retain the suggestion as a high priority suggestion to
  460. // be used unless overridden by a variable processor function.
  461. if (isset($info['base hook'])) {
  462. $base_hook = $info['base hook'];
  463. $base_hook_info = $hooks[$base_hook]['original'];
  464. // Include files required by the base hook, since its variable processors
  465. // might reside there.
  466. if (!empty($base_hook_info['includes'])) {
  467. foreach ($base_hook_info['includes'] as $include_file) {
  468. include_once DRUPAL_ROOT . '/' . $include_file;
  469. }
  470. }
  471. if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) {
  472. $variables['theme_hook_suggestion'] = $hook;
  473. $hook = $base_hook;
  474. $info = $base_hook_info;
  475. }
  476. }
  477. if (isset($info['preprocess functions']) || isset($info['process functions'])) {
  478. $variables['theme_hook_suggestions'] = array();
  479. foreach (array('preprocess functions', 'process functions') as $phase) {
  480. if (!empty($info[$phase])) {
  481. foreach ($info[$phase] as $processor_function) {
  482. if (function_exists($processor_function)) {
  483. $meta[$phase][] = $processor_function;
  484. // We don't want a poorly behaved process function changing $hook.
  485. $hook_clone = $hook;
  486. $processor_function($variables, $hook_clone);
  487. }
  488. }
  489. }
  490. }
  491. // If the preprocess/process functions specified hook suggestions, and the
  492. // suggestion exists in the theme registry, use it instead of the hook that
  493. // theme() was called with. This allows the preprocess/process step to
  494. // route to a more specific theme hook. For example, a function may call
  495. // theme('node', ...), but a preprocess function can add 'node__article' as
  496. // a suggestion, enabling a theme to have an alternate template file for
  497. // article nodes. Suggestions are checked in the following order:
  498. // - The 'theme_hook_suggestion' variable is checked first. It overrides
  499. // all others.
  500. // - The 'theme_hook_suggestions' variable is checked in FILO order, so the
  501. // last suggestion added to the array takes precedence over suggestions
  502. // added earlier.
  503. $suggestions = array();
  504. if (!empty($variables['theme_hook_suggestions'])) {
  505. $suggestions = $variables['theme_hook_suggestions'];
  506. }
  507. if (!empty($variables['theme_hook_suggestion'])) {
  508. $suggestions[] = $variables['theme_hook_suggestion'];
  509. }
  510. foreach (array_reverse($suggestions) as $suggestion) {
  511. if (isset($hooks[$suggestion])) {
  512. $info = $hooks[$suggestion]['original'];
  513. $meta['suggested_hook'] = $suggestion;
  514. break;
  515. }
  516. $meta['suggestions'][] = $suggestion;
  517. }
  518. }
  519. // Generate the output using either a function or a template.
  520. $output = '';
  521. if (isset($info['function'])) {
  522. $meta['type'] = 'function';
  523. $meta['used'] = $info['function'] . '()';
  524. if (function_exists($info['function'])) {
  525. $output = $info['function']($variables);
  526. }
  527. }
  528. else {
  529. $meta['type'] = 'template';
  530. // Default render function and extension.
  531. $render_function = 'theme_render_template';
  532. $extension = '.tpl.php';
  533. // The theme engine may use a different extension and a different renderer.
  534. global $theme_engine;
  535. if (isset($theme_engine)) {
  536. if ($info['type'] != 'module') {
  537. if (function_exists($theme_engine . '_render_template')) {
  538. $render_function = $theme_engine . '_render_template';
  539. }
  540. $extension_function = $theme_engine . '_extension';
  541. if (function_exists($extension_function)) {
  542. $extension = $extension_function();
  543. }
  544. }
  545. }
  546. $meta['extension'] = $extension;
  547. // In some cases, a template implementation may not have had
  548. // template_preprocess() run (for example, if the default implementation is
  549. // a function, but a template overrides that default implementation). In
  550. // these cases, a template should still be able to expect to have access to
  551. // the variables provided by template_preprocess(), so we add them here if
  552. // they don't already exist. We don't want to run template_preprocess()
  553. // twice (it would be inefficient and mess up zebra striping), so we use the
  554. // 'directory' variable to determine if it has already run, which while not
  555. // completely intuitive, is reasonably safe, and allows us to save on the
  556. // overhead of adding some new variable to track that.
  557. if (!isset($variables['directory'])) {
  558. $default_template_variables = array();
  559. template_preprocess($default_template_variables, $hook);
  560. $variables += $default_template_variables;
  561. }
  562. // Render the output using the template file.
  563. $template_file = $info['template'] . $extension;
  564. $meta['used'] = $template_file;
  565. if (isset($info['path'])) {
  566. $template_file = $info['path'] . '/' . $template_file;
  567. }
  568. $meta['template_file'] = $template_file;
  569. $output = $render_function($template_file, $variables);
  570. }
  571. $meta['variables'] = $variables;
  572. // restore path_to_theme()
  573. $theme_path = $temp;
  574. return $output;
  575. }
  576. /**
  577. * Implements hook_page_alter().
  578. */
  579. function devel_themer_page_alter(&$page) {
  580. $page['#post_render'][] = 'devel_themer_post_process_page';
  581. }
  582. function devel_themer_post_process_page($page, $elements) {
  583. if (!empty($GLOBALS['devel_theme_calls']) && $_SERVER['REQUEST_METHOD'] != 'POST') {
  584. $GLOBALS['devel_theme_calls']['devel_themer_uri'] = url("devel_themer/variables");
  585. $javascript = '<script type="text/javascript">jQuery.extend(Drupal.settings, ' . drupal_json_encode($GLOBALS['devel_theme_calls']) . ");</script>\n";
  586. $page = preg_replace('#</body>#', "\n$javascript\n</body>", $page, 1);
  587. }
  588. return $page;
  589. }
  590. // just hand out next counter, or return current value
  591. function devel_counter($increment = TRUE) {
  592. static $counter = 0;
  593. if ($increment) {
  594. $counter++;
  595. }
  596. return $counter;
  597. }
  598. /**
  599. * Return the popup template
  600. * placed here for easy editing
  601. */
  602. function devel_themer_popup() {
  603. $majorver = substr(VERSION, 0, strpos(VERSION, '.'));
  604. // add translatable strings
  605. drupal_add_js(array('thmrStrings' =>
  606. array(
  607. 'themer_info' => t('Themer info'),
  608. 'toggle_throbber' => ' <img src="' . base_path() . drupal_get_path('module', 'devel_themer') . '/loader-little.gif' . '" alt="' . t('loading') . '" class="throbber" width="16" height="16" style="display:none" />',
  609. 'parents' => t('Parents:') . ' ',
  610. 'function_called' => t('Function called:') . ' ',
  611. 'template_called' => t('Template called:') . ' ',
  612. 'candidate_files' => t('Candidate template files:') . ' ',
  613. 'preprocessors' => t('Preprocess functions:') . ' ',
  614. 'processors' => t('Process functions:') . ' ',
  615. 'candidate_functions' => t('Candidate function names:') . ' ',
  616. 'drupal_api_docs' => t('link to Drupal API documentation'),
  617. 'source_link_title' => t('link to source code'),
  618. 'function_arguments' => t('Function Arguments'),
  619. 'template_variables' => t('Template Variables'),
  620. 'file_used' => t('File used:') . ' ',
  621. 'duration' => t('Duration:') . ' ',
  622. 'api_site' => variable_get('devel_api_site', 'http://api.drupal.org/'),
  623. 'drupal_version' => $majorver,
  624. 'source_link' => url('devel/source', array('query' => array('file' => ''))),
  625. ))
  626. , 'setting');
  627. $title = t('Drupal Themer Information');
  628. $intro = t('Click on any element to see information about the Drupal theme function or template that created it.');
  629. $popup = <<<EOT
  630. <div id="themer-fixeder">
  631. <div id="themer-relativer">
  632. <div id="themer-popup">
  633. <div class="topper">
  634. <span class="close">X</span> $title
  635. </div>
  636. <div id="parents" class="row">
  637. </div>
  638. <div class="info row">
  639. <div class="starter">$intro</div>
  640. <dl>
  641. <dt class="key-type">
  642. </dt>
  643. <dd class="key">
  644. </dd>
  645. <div class="used">
  646. </div>
  647. <dt class="candidates-type">
  648. </dt>
  649. <dd class="candidates">
  650. </dd>
  651. <dt class="preprocessors-type">
  652. </dt>
  653. <dd class="preprocessors">
  654. </dd>
  655. <dt class="processors-type">
  656. </dt>
  657. <dd class="processors">
  658. </dd>
  659. <div class="duration"></div>
  660. </dl>
  661. </div><!-- /info -->
  662. <div class="attributes row">
  663. <div class="themer-variables"></div>
  664. </div><!-- /attributes -->
  665. </div><!-- /themer-popup -->
  666. </div>
  667. </div>
  668. EOT;
  669. drupal_add_js(array('thmr_popup' => $popup), 'setting');
  670. }