Remove old comment.
[project/devel.git] / devel.module
1 <?php
2 // $Id$
3
4 // This module holds functions useful for Drupal development.
5 // Please contribute!
6
7 // Suggested profiling and stacktrace library from http://www.xdebug.org/index.php
8
9 define('DEVEL_QUERY_SORT_BY_SOURCE', 0);
10 define('DEVEL_QUERY_SORT_BY_DURATION', 1);
11
12 define('DEVEL_ERROR_HANDLER_NONE', 0);
13 define('DEVEL_ERROR_HANDLER_STANDARD', 1);
14 define('DEVEL_ERROR_HANDLER_BACKTRACE', 2);
15
16 define('DEVEL_MIN_TEXTAREA', 50);
17
18 /**
19 * Implementation of hook_help().
20 */
21 function devel_help($section) {
22 switch ($section) {
23 case 'devel/reference':
24 return '<p>'. t('This is a list of defined user functions that generated this current request lifecycle. Click on a function name to view its documention.') .'</p>';
25 case 'devel/session':
26 return '<p>'. t('Here are the contents of your <code>$_SESSION</code> variable.') .'</p>';
27 case 'devel/variable':
28 $api = variable_get('devel_api_url', 'api.drupal.org');
29 return '<p>'. t('This is a list of the variables and their values currently stored in variables table and the <code>$conf</code> array of your settings.php file. These variables are usually accessed with <a href="@variable-get-doc">variable_get()</a> and <a href="@variable-set-doc">variable_set()</a>. Variables that are too long can slow down your pages.', array('@variable-get-doc' => "http://$api/api/HEAD/function/variable_get", '@variable-set-doc' => "http://$api/api/HEAD/function/variable_set")) .'</p>';
30 }
31 }
32
33 /**
34 * Implementationation of hook_menu().
35 */
36 function devel_menu() {
37 $items = array();
38 // Note: we can't dynamically append destination to querystring. Do so at theme layer. Fix in D7?
39 $items['devel/cache/clear'] = array(
40 'title' => 'Empty cache',
41 'page callback' => 'devel_cache_clear',
42 'description' => 'Clear the CSS cache and all database cache tables which store page, node, theme and variable caches.',
43 'access arguments' => array('access devel information'),
44 'menu_name' => 'devel',
45 );
46
47 $items['devel/reference'] = array(
48 'title' => 'Function reference',
49 'description' => 'View a list of currently defined user functions with documentation links.',
50 'page callback' => 'devel_function_reference',
51 'access arguments' => array('access devel information'),
52 'menu_name' => 'devel',
53 );
54 $items['devel/reinstall'] = array(
55 'title' => 'Reinstall modules',
56 'page callback' => 'drupal_get_form',
57 'page arguments' => array('devel_reinstall'),
58 'description' => 'Run hook_uninstall() and then hook_install() for a given module.',
59 'access arguments' => array('access devel information'),
60 'menu_name' => 'devel',
61 );
62 $items['devel/devel_themer'] = array(
63 'title callback' => 'devel_menu_title_theme_developer',
64 'title arguments' => array(NULL),
65 'title' => 'foo',
66 'description' => 'Quickly enable or disable theme developer module. Useful for removing HTML cruft added by that module.',
67 'page callback' => 'devel_devel_themer_toggle',
68 'access arguments' => array('access devel information'),
69 'menu_name' => 'devel',
70 );
71 $items['devel/source'] = array(
72 'title' => 'Display the PHP code of any file in your Drupal installation',
73 'page callback' => 'devel_display_source',
74 'access arguments' => array('display source code'),
75 'type' => MENU_CALLBACK,
76 'menu_name' => 'devel',
77 );
78 $items['devel/menu/reset'] = array(
79 'title' => 'Rebuild menus',
80 'description' => 'Rebuild menu based on hook_menu() and revert any custom changes. All menu items return to their default settings.',
81 'page callback' => 'drupal_get_form',
82 'page arguments' => array('devel_menu_rebuild'),
83 'access arguments' => array('access devel information'),
84 'menu_name' => 'devel',
85 );
86 $items['devel/variable'] = array(
87 'title' => 'Variable editor',
88 'description' => 'Edit and delete site variables.',
89 'page callback' => 'devel_variable_page',
90 'access arguments' => array('access devel information'),
91 'menu_name' => 'devel',
92 );
93 // we don't want the abbreviated version provided by status report
94 $items['devel/phpinfo'] = array(
95 'title' => 'PHPinfo()',
96 'description' => 'View your server\'s PHP configuration',
97 'page callback' => 'devel_phpinfo',
98 'access arguments' => array('access devel information'),
99 'menu_name' => 'devel',
100 );
101 $items['devel/php'] = array(
102 'title' => 'Execute PHP Code',
103 'description' => 'Execute some PHP code',
104 'page callback' => 'drupal_get_form',
105 'page arguments' => array('devel_execute_form'),
106 'access arguments' => array('execute php code'),
107 'menu_name' => 'devel',
108 );
109 $items['devel/theme/registry'] = array(
110 'title' => 'Theme registry',
111 'description' => 'View a list of available theme functions across the whole site.',
112 'page callback' => 'devel_theme_registry',
113 'access arguments' => array('access devel information'),
114 'menu_name' => 'devel',
115 );
116 $items['devel/field/info'] = array(
117 'title' => 'Field info',
118 'description' => 'View fields information across the whole site.',
119 'page callback' => 'devel_field_info_page',
120 'access arguments' => array('access devel information'),
121 'menu_name' => 'devel',
122 );
123 $items['devel/elements'] = array(
124 'title' => 'Hook_elements()',
125 'description' => 'View the active form/render elements for this site.',
126 'page callback' => 'devel_elements_page',
127 'access arguments' => array('access devel information'),
128 'menu_name' => 'devel',
129 );
130 $items['devel/variable/edit/%'] = array(
131 'title' => 'Variable editor',
132 'page callback' => 'drupal_get_form',
133 'page arguments' => array('devel_variable_edit', 3),
134 'access arguments' => array('access devel information'),
135 'type' => MENU_CALLBACK,
136 'menu_name' => 'devel',
137 );
138 $items['devel/session'] = array(
139 'title' => 'Session viewer',
140 'description' => 'List the contents of $_SESSION.',
141 'page callback' => 'devel_session',
142 'access arguments' => array('access devel information'),
143 'menu_name' => 'devel',
144 );
145 $items['devel/switch'] = array(
146 'title' => 'Switch user',
147 'page callback' => 'devel_switch_user',
148 'access arguments' => array('switch users'),
149 'type' => MENU_CALLBACK,
150 'menu_name' => 'devel',
151 );
152 $items['devel/run-cron'] = array(
153 'title' => 'Run cron',
154 'page callback' => 'system_run_cron',
155 'access arguments' => array('administer site configuration'),
156 'file' => 'system.admin.inc',
157 'file path' => drupal_get_path('module', 'system'),
158 'menu_name' => 'devel',
159 );
160 $items['admin/config/development/devel'] = array(
161 'title' => 'Devel settings',
162 'description' => 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the <a href="' . url('admin/structure/block') . '">block administration</a> page.',
163 'page callback' => 'drupal_get_form',
164 'page arguments' => array('devel_admin_settings'),
165 'access arguments' => array('administer site configuration'),
166 'menu_name' => 'devel',
167 );
168 $items['node/%node/devel/load'] = array(
169 'title' => 'Dev load',
170 'page callback' => 'devel_load_object',
171 'page arguments' => array(1, 'node'),
172 'access callback' => 'user_access',
173 'access arguments' => array('access devel information'),
174 'type' => MENU_LOCAL_TASK,
175 'weight' => 100,
176 );
177 $items['node/%node/devel/render'] = array(
178 'title' => 'Dev render',
179 'page callback' => 'devel_render_object',
180 'page arguments' => array('node', 1),
181 'access callback' => 'user_access',
182 'access arguments' => array('access devel information'),
183 'type' => MENU_LOCAL_TASK,
184 'weight' => 100,
185 );
186 $items['comment/%comment/devel/load'] = array(
187 'title' => 'Dev load',
188 'page callback' => 'devel_load_object',
189 'page arguments' => array(1, 'comment'),
190 'access callback' => 'user_access',
191 'access arguments' => array('access devel information'),
192 'type' => MENU_LOCAL_TASK,
193 'weight' => 100,
194 );
195 $items['comment/%comment/devel/render'] = array(
196 'title' => 'Dev render',
197 'page callback' => 'devel_render_object',
198 'page arguments' => array('comment', 1),
199 'access callback' => 'user_access',
200 'access arguments' => array('access devel information'),
201 'type' => MENU_LOCAL_TASK,
202 'weight' => 100,
203 );
204 $items['user/%user/devel/load'] = array(
205 'title' => 'Dev load',
206 'page callback' => 'devel_load_object',
207 'page arguments' => array(1, 'user'),
208 'access callback' => 'user_access',
209 'access arguments' => array('access devel information'),
210 'type' => MENU_LOCAL_TASK,
211 'weight' => 100,
212 );
213 $items['user/%user/devel/render'] = array(
214 'title' => 'Dev render',
215 'page callback' => 'devel_render_object',
216 'page arguments' => array('user', 1),
217 'access callback' => 'user_access',
218 'access arguments' => array('access devel information'),
219 'type' => MENU_LOCAL_TASK,
220 'weight' => 100,
221 );
222 $items['taxonomy/term/%taxonomy_term/devel/load'] = array(
223 'title' => 'Dev load',
224 'page callback' => 'devel_load_object',
225 'page arguments' => array(2, 'term'),
226 'access callback' => 'user_access',
227 'access arguments' => array('access devel information'),
228 'type' => MENU_LOCAL_TASK,
229 'weight' => 100,
230 );
231 $items['taxonomy/term/%taxonomy_term/devel/render'] = array(
232 'title' => 'Dev render',
233 'page callback' => 'devel_render_term',
234 'page arguments' => array(2),
235 'access callback' => 'user_access',
236 'access arguments' => array('access devel information'),
237 'type' => MENU_LOCAL_TASK,
238 'weight' => 100,
239 );
240
241 return $items;
242 }
243
244 function devel_menu_need_destination() {
245 return array('devel/cache/clear', 'devel/devel_themer', 'devel/reinstall', 'devel/menu/reset', 'admin/og/og', 'devel/variable', 'admin/reports/status/run-cron');
246 }
247
248 /**
249 * An implementation of hook_menu_link_alter(). Flag this link as needing alter at display time.
250 * This is more robust that setting alter in hook_menu(). See devel_translated_menu_link_alter().
251 *
252 **/
253 function devel_menu_link_alter(&$item) {
254 if (in_array($item['link_path'], devel_menu_need_destination())) {
255 $item['options']['alter'] = TRUE;
256 }
257 }
258
259 /**
260 * An implementation of hook_translated_menu_item_alter(). Append dynamic
261 * querystring 'destination' to several of our own menu items.
262 *
263 **/
264 function devel_translated_menu_link_alter(&$item) {
265 if (in_array($item['href'], devel_menu_need_destination())) {
266 $item['localized_options']['query'] = drupal_get_destination();
267 }
268 }
269
270 function devel_menu_title_theme_developer() {
271 if (module_exists('devel_themer')) {
272 return t('Disable Theme developer');
273 }
274 else {
275 return t('Enable Theme developer');
276 }
277 }
278
279 function devel_devel_themer_toggle() {
280 if (module_exists('devel_themer')) {
281 module_disable(array('devel_themer'));
282 }
283 else {
284 // Sanity check in case the devel_themer schema is not installed.
285 include_once('./includes/install.inc');
286 if (drupal_get_schema_versions('devel_themer') == FALSE) {
287 drupal_install_modules(array('devel_themer'));
288 }
289 else {
290 module_enable(array('devel_themer'));
291 }
292 }
293 drupal_theme_rebuild();
294 menu_rebuild();
295 drupal_goto();
296 }
297
298 /**
299 * Implementation of hook_theme()
300 */
301 function devel_theme() { // &$cache, $type, $theme, $path
302 return array(
303 'devel_querylog' => array(
304 'variables' => array('header' => array(), 'rows' => array()),
305 ),
306 'devel_querylog_row' => array(
307 'variables' => array('row' => array()),
308 ),
309 );
310 }
311
312 /**
313 * Page callback to display syntax hilighted source code
314 *
315 * note: the path for this function is received via $_GET['path']
316 * example http://www.example.com/devel/source?file=modules/node/node.module
317 *
318 * @param $standalone
319 * Set to FALSE to place the code inside a Drupal page. Otherwise code displays on its own.
320 */
321 function devel_display_source($standalone = TRUE) {
322 $path = $_GET['file'];
323 // take out the nasties
324 $path = str_replace('../', '', $path);
325 $output = devel_highlight_file($path, $standalone);
326 if ($output) {
327 if ($standalone) {
328 print $output;
329 exit();
330 }
331 return $output;
332 }
333 else {
334 drupal_set_message(t('Invalid file path'), 'error');
335 drupal_not_found();
336 }
337 }
338
339 /**
340 * Return PHP highlighted file
341 *
342 * @param $path
343 * path to the file
344 * *warning* there is NO VALIDATION in this function
345 * Beware of paths such as '../../../../../etc/apache/httpd.conf'
346 *
347 * @param $standalone
348 * should the returned HTML be wrapped in a full <html> page or will it be output by Drupal?
349 */
350 function devel_highlight_file($path = NULL, $standalone = FALSE) {
351 if (file_exists($path)) {
352 $source = highlight_file($path, TRUE);
353 // add anchor links before all functions
354 // with doxygen
355 // $source = preg_replace('!(\/\*\*.*?\*\/.*?)<br.*?function.*?#0000BB">(.*?)<\/span>!', '<a id="$2"></a> $0', $source);
356 //$source = preg_replace('!(\/\*\*.*?\*\/).*?function.*?#0000BB">(.*?)<\/span>!', '<a id="$2"></a> $0', $source);
357 if ($standalone) {
358 $source = <<<EOT
359 <head><title>$path</title></head>
360 <body>$source</body>
361 EOT;
362 }
363 return $source;
364 }
365 else {
366 return FALSE;
367 }
368 }
369
370 /**
371 * Implementation of hook_init().
372 */
373 function devel_init() {
374 if (!devel_silent()) {
375 if (user_access('access devel information')) {
376 devel_set_handler(variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD));
377 // We want to include the class early so that anyone may call krumo() as needed. See http://krumo.sourceforge.net/
378 has_krumo();
379
380 // See http://www.firephp.org/.
381 // Support Libraries API - http://drupal.org/project/libraries
382 if (module_exists('libraries')) {
383 $path = libraries_get_path('FirePHPCore') . '/lib/FirePHPCore/fb.php';
384 }
385 else {
386 $path = './'. drupal_get_path('module', 'devel') .'/FirePHPCore/lib/FirePHPCore/fb.php';
387 }
388 if (file_exists($path)) {
389 include_once $path;
390 }
391 // Add CSS for query log if should be displayed.
392 if (variable_get('devel_query_display', 0)) {
393 drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css');
394 }
395 }
396 }
397 if (variable_get('devel_rebuild_theme_registry', FALSE)) {
398 drupal_theme_rebuild();
399 if (flood_is_allowed('devel_rebuild_registry_warning', 1)) {
400 flood_register_event('devel_rebuild_registry_warning');
401 if (!devel_silent() && user_access('access devel information')) {
402 drupal_set_message(t('The theme registry is being rebuilt on every request. Remember to <a href="!url">turn off</a> this feature on production websites.', array("!url" => url('admin/config/development/devel'))));
403 }
404 }
405 }
406 }
407
408 // return boolean. no need for cache here.
409 function has_krumo() {
410 // see README.txt or just download from http://krumo.sourceforge.net/
411 @include_once './'. drupal_get_path('module', 'devel') .'/krumo/class.krumo.php';
412 if (function_exists('krumo') && php_sapi_name() != 'cli') {
413 return TRUE;
414 }
415 else {
416 return FALSE;
417 }
418 }
419
420 /**
421 * Decide whether or not to print a debug variable using krumo().
422 *
423 * @param $input
424 * @return boolean
425 */
426 function merits_krumo($input) {
427 return (is_object($input) || is_array($input)) && has_krumo() && variable_get('devel_krumo_skin', '') != 'disabled';
428 }
429
430 /**
431 * Calls the http://www.firephp.org/ fb() function if it is found.
432 *
433 * @return void
434 */
435 function dfb() {
436 if (function_exists('fb') && user_access('access devel information')) {
437 $args = func_get_args();
438 call_user_func_array('fb', $args);
439 }
440 }
441
442
443 function devel_set_handler($handler) {
444 switch ($handler) {
445 case DEVEL_ERROR_HANDLER_STANDARD:
446 // do nothing
447 break;
448 case DEVEL_ERROR_HANDLER_BACKTRACE:
449 if (has_krumo()) {
450 set_error_handler('backtrace_error_handler');
451 }
452 break;
453 case DEVEL_ERROR_HANDLER_NONE:
454 restore_error_handler();
455 break;
456 }
457 }
458
459 function devel_silent() {
460 // isset($_GET['q']) is needed on IIS when calling the front page. q is not set.
461 // Don't interfere with private files/images.
462 return
463 devel_verify_cli() ||
464 strpos($_SERVER['HTTP_USER_AGENT'], 'ApacheBench') !== FALSE ||
465 !empty($_REQUEST['XDEBUG_PROFILE']) ||
466 isset($GLOBALS['devel_shutdown']) ||
467 strstr($_SERVER['PHP_SELF'], 'update.php') ||
468 (isset($_GET['q']) && (
469 in_array($_GET['q'], array('upload/js', 'admin/content/node-settings/rebuild')) ||
470 substr($_GET['q'], 0, strlen('system/files')) == 'system/files' ||
471 substr($_GET['q'], 0, strlen('batch')) == 'batch')
472 );
473 }
474
475 /**
476 * Implementation of hook_boot(). Runs even for cached pages.
477 */
478 function devel_boot() {
479 if (!devel_silent()) {
480 devel_start();
481 }
482 }
483
484 // Kickoff our tricks. Put here all code which must run for cached pages too. Called from both devel_boot() and devel_init().
485 function devel_start() {
486 if (variable_get('dev_mem', 0)) {
487 global $memory_init;
488 $memory_init = memory_get_usage();
489 }
490
491 if (devel_query_enabled()) {
492 //TODO: How best to include this?
493 @include_once DRUPAL_ROOT . '/includes/database/log.inc';
494 Database::startLog('devel');;
495 }
496
497 if (devel_code_coverage_enabled()) {
498 xdebug_start_code_coverage();
499 }
500
501 // We need user_access() in the shutdown function. make sure it gets loaded.
502 // Also prime the drupal_get_filename() static with user.module's location to
503 // avoid a stray query.
504 drupal_get_filename('module', 'user', 'modules/user/user.module');
505 drupal_load('module', 'user');
506 register_shutdown_function('devel_shutdown');
507 }
508
509 function backtrace_error_handler($errno, $message, $filename, $line) {
510 // Don't respond to the error if it was suppressed with a '@'
511 if (error_reporting() == 0) return;
512
513 if ($errno & (E_ALL ^ E_NOTICE)) {
514 // We can't use the PHP E_* constants here as not all versions of PHP have all
515 // the constants defined, so for consistency, we just use the numeric equivelant.
516 $types = array(
517 1 => 'error',
518 2 => 'warning',
519 4 => 'parse error',
520 8 => 'notice',
521 16 => 'core error',
522 32 => 'core warning',
523 64 => 'compile error',
524 128 => 'compile warning',
525 256 => 'user error',
526 512 => 'user warning',
527 1024 => 'user notice',
528 2048 => 'strict warning',
529 4096 => 'recoverable error',
530 8192 => 'deprecated',
531 16384 => 'user deprecated',
532 );
533 $entry = $types[$errno] .': '. $message .' in '. $filename .' on line '. $line .'.';
534
535 if (variable_get('error_level', 1) == 1) {
536 $backtrace = debug_backtrace();
537 foreach ($backtrace as $call) {
538 $nicetrace[$call['function']] = $call;
539 }
540 krumo($nicetrace);
541 }
542
543 watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
544 }
545 }
546
547 /**
548 * Implement hook_permission().
549 */
550 function devel_permission() {
551 return array(
552 'access devel information' => array(
553 'description' => t('View developer output like variable printouts, query log, etc.'),
554 'title' => t('Access developer information'),
555 ),
556 'execute php code' => array(
557 'title' => t('Execute PHP code'),
558 'description' => t('Run arbitrary PHP from a block. Danger!'),
559 ),
560 'switch users' => array(
561 'title' => t('Switch users'),
562 'description' => t('Become any user on the site with just a click. Danger!'),
563 ),
564 'display source code' => array(
565 'title' => t('Display source code'),
566 'description' => t('View the site\'s php source code. Danger!'),
567 ),
568 );
569 }
570
571 function devel_block_info() {
572 $blocks['execute_php'] = array(
573 'info' => t('Execute PHP'),
574 'cache' => DRUPAL_NO_CACHE,
575 );
576 $blocks['switch_user'] = array(
577 'info' => t('Switch user'),
578 'cache' => DRUPAL_NO_CACHE,
579 );
580 return $blocks;
581 }
582
583 /**
584 * Implementation of hook_block_configure().
585 */
586 function devel_block_configure($delta) {
587 if ($delta == 'switch_user') {
588 $form['devel_switch_user_list_size'] = array(
589 '#type' => 'textfield',
590 '#title' => t('Number of users to display in the list'),
591 '#default_value' => variable_get('devel_switch_user_list_size', 10),
592 '#size' => '3',
593 '#maxlength' => '4',
594 );
595 return $form;
596 }
597 }
598
599 function devel_block_save($delta, $edit = array()) {
600 if ($delta == 'switch_user') {
601 variable_set('devel_switch_user_list_size', $edit['devel_switch_user_list_size']);
602 }
603 }
604
605 function devel_block_view($delta) {
606 $block = array();
607 switch ($delta) {
608 case 'switch_user':
609 $block = devel_block_switch_user();
610 break;
611
612 case 'execute_php':
613 if (user_access('execute php code')) {
614 $block['subject'] = t('Execute PHP');
615 $block['content'] = drupal_get_form('devel_execute_form');
616 }
617 break;
618 }
619 return $block;
620 }
621
622 function devel_block_switch_user() {
623 $links = devel_switch_user_list();
624 if (!empty($links)) {
625 $block['subject'] = t('Switch user');
626 $build['devel_links'] = array('#theme' => 'links', '#links' => $links);
627 $build['devel_form'] = drupal_get_form('devel_switch_user_form');
628 $block['content'] = $build;
629 return $block;
630 }
631 }
632
633 function devel_switch_user_list() {
634 $links = array();
635 if (user_access('switch users')) {
636 $list_size = variable_get('devel_switch_user_list_size', 10);
637 $dest = drupal_get_destination();
638 // Try to find at least $list_size users that can switch.
639 $roles = user_roles(1, 'switch users');
640 if (isset($roles[2])) {
641 // If authenticated users have this permission, just grab
642 // the last $list_size users, since there won't be records in
643 // {user_roles} and every user on the system can switch.
644 $users = db_query_range("SELECT DISTINCT u.uid, u.name, u.access FROM {users} u WHERE u.uid > 0 ORDER BY u.access DESC", 0, $list_size);
645 }
646 else {
647 $where = array('u.uid = 1');
648 if (count($roles)) {
649 $where[] = 'r.rid IN ('. implode(',', array_keys($roles)) .')';
650 }
651 $where_sql = implode(' OR ', $where);
652 $users = db_query_range("SELECT DISTINCT u.uid, u.name, u.access FROM {users} u LEFT JOIN {users_roles} r ON u.uid = r.uid WHERE $where_sql ORDER BY u.access DESC", 0, $list_size);
653 }
654 foreach ($users as $user) {
655 $links[$user->uid] = array(
656 'title' => drupal_placeholder(array('text' => $user->name)),
657 'href' => 'devel/switch/'. $user->name,
658 'query' => $dest,
659 'attributes' => array('title' => t('This user can switch back.')),
660 'html' => TRUE,
661 );
662 }
663 $num_links = count($links);
664 if ($num_links < $list_size) {
665 // If we don't have enough, add distinct uids until we hit $list_size.
666 $users = db_query_range('SELECT uid, name, access FROM {users} WHERE uid > 0 AND uid NOT IN ('. implode(',', array_keys($links)) .') ORDER BY access DESC', 0, $list_size - $num_links);
667 foreach ($users as $user) {
668 if (count($links) >= $list_size) {
669 break;
670 }
671 $links[$user->uid] = array(
672 'title' => $user->name ? $user->name : 'anon',
673 'href' => 'devel/switch/'. $user->name,
674 'query' => $dest,
675 'attributes' => array('title' => t('Caution: this user will be unable switch back.')),
676 );
677 }
678 }
679 }
680 return $links;
681 }
682
683 function devel_phpinfo() {
684 print phpinfo();
685 exit;
686 }
687
688 function devel_switch_user_form() {
689 $form['username'] = array(
690 '#type' => 'textfield',
691 '#description' => t('Enter username'),
692 '#autocomplete_path' => 'user/autocomplete',
693 '#maxlength' => USERNAME_MAX_LENGTH,
694 '#size' => 16,
695 );
696 $form['submit'] = array(
697 '#type' => 'submit',
698 '#value' => t('Switch'),
699 );
700 return $form;
701
702 }
703
704 function devel_doc_function_form() {
705 $version = devel_get_core_version(VERSION);
706 $form['function'] = array(
707 '#type' => 'textfield',
708 '#description' => t('Enter function name for api lookup'),
709 '#size' => 16,
710 '#maxlength' => 255,
711 );
712 $form['version'] = array('#type' => 'value', '#value' => $version);
713 $form['submit_button'] = array(
714 '#type' => 'submit',
715 '#value' => t('Submit'),
716 );
717 return $form;
718 }
719
720 function devel_doc_function_form_submit($form, &$form_state) {
721 $version = $form_state['values']['version'];
722 $function = $form_state['values']['function'];
723 $api = variable_get('devel_api_url', 'api.drupal.org');
724 $form_state['redirect'] = "http://$api/api/$version/function/$function";
725 }
726
727 function devel_switch_user_form_validate($form, &$form_state) {
728 if (!$account = user_load_by_name($form_state['values']['username'])) {
729 form_set_error('username', t('Username not found'));
730 }
731 }
732
733 function devel_switch_user_form_submit($form, &$form_state) {
734 $form_state['redirect'] = 'devel/switch/'. $form_state['values']['username'];
735 }
736
737
738 /**
739 * TODO: I switched params as per http://drupal.org/node/144132#form-alter but needs work still
740 * Implementation of hook_form_alter().
741 */
742 function devel_form_alter(&$form, $form_state, $form_id, $key_in = NULL) {
743 if (user_access('access devel information') && variable_get('devel_form_weights', 0)) {
744 $children = element_children($form);
745 if (empty($children)) {
746 if (isset($form['#type']) && !in_array($form['#type'], array('value', 'hidden'))) {
747 if (!isset($form['#title'])) {
748 $form['#title'] = '';
749 }
750 $form['#title'] .= " (key=$key_in, weight=". (isset($form['#weight']) ? $form['#weight'] : 0) .')';
751 }
752 }
753 else {
754 foreach (element_children($form) as $key) {
755 // We need to add the weight to fieldsets.
756 if (element_children($form[$key])) { // Which are a container of others.
757 if (!isset($form[$key]['#title'])) {
758 $form[$key]['#title'] = '';
759 }
760 $form[$key]['#title'] .= " (key=$key, weight=". (isset($form[$key]['#weight']) ? $form[$key]['#weight'] : 0) .')';
761 }
762 devel_form_alter($form[$key], $form_state, $form_id, $key);
763 }
764 }
765 }
766 }
767
768 // An implementation of hook_exit().
769 function devel_exit($destination = NULL) {
770 global $user;
771
772 if (isset($destination) && !devel_silent()) {
773 // The page we are leaving is a drupal_goto(). Present a redirection page
774 // so that the developer can see the intermediate query log.
775 // We don't want to load user module here, so keep function_exists() call.
776 if (isset($user) && function_exists('user_access') && user_access('access devel information') && variable_get('devel_redirect_page', 0)) {
777 $output = t_safe('<p>The user is being redirected to <a href="@destination">@destination</a>.</p>', array('@destination' => $destination));
778 drupal_set_page_content($output);
779 $page = element_info('page');
780 print drupal_render_page($page);
781
782 // Don't allow the automatic redirect to happen.
783 drupal_page_footer();
784 exit();
785 }
786 else {
787 $GLOBALS['devel_redirecting'] = TRUE;
788 }
789 }
790 }
791
792 /**
793 * See devel_start() which registers this function as a shutdown function.
794 */
795 function devel_shutdown() {
796 // Register the real shutdown function so it runs later than other shutdown functions.
797 register_shutdown_function('devel_shutdown_real');
798 }
799
800 // Borrowed from drush.
801 function devel_verify_cli() {
802 if (php_sapi_name() == 'cgi') {
803 return (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0);
804 }
805
806 return (php_sapi_name() == 'cli');
807 }
808
809 function devel_page_alter($page) {
810 if (variable_get('devel_page_alter', FALSE) && user_access('access devel information')) {
811 dpm($page, 'page');
812 }
813 }
814
815 /**
816 * See devel_shutdown() which registers this function as a shutdown function. Displays developer information in the footer.
817 */
818 function devel_shutdown_real() {
819 global $memory_init, $user;
820 $output = $txt = '';
821
822 // Set $GLOBALS['devel_shutdown'] = FALSE in order to supress the
823 // devel footer for a page. Not necessary if your page outputs any
824 // of the Content-type http headers tested below (e.g. text/xml,
825 // text/javascript, etc). This is is advised where applicable.
826 if (!isset($GLOBALS['devel_shutdown']) && !isset($GLOBALS['devel_redirecting'])) {
827 // Try not to break non html pages.
828 if (function_exists('drupal_get_header')) {
829 if ($headers = drupal_get_header()) {
830 $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values');
831 foreach ($formats as $format) {
832 if (strstr($headers['content-type'], $format)) {
833 return;
834 }
835 }
836 }
837 }
838
839 if (isset($user) && user_access('access devel information')) {
840 if (devel_query_enabled()) {
841 $output .= devel_shutdown_query();
842 }
843 if (variable_get('dev_mem', FALSE)) {
844 $memory_shutdown = memory_get_usage();
845 $args = array('@memory_boot' => round($memory_init / 1024 / 1024, 2), '@memory_shutdown' => round($memory_shutdown / 1024 / 1024, 2), '@memory_peak' => round(memory_get_peak_usage(TRUE) / 1024 / 1024, 2));
846 $msg = '<div class="dev-memory-usage"><h3>Memory usage:</h3> Memory used at: devel_boot()=<strong>@memory_boot</strong> MB, devel_shutdown()=<strong>@memory_shutdown</strong> MB, PHP peak usage=<strong>@memory_peak</strong> MB.</div>';
847 // theme() may not be available. not t() either.
848 $output .= t_safe($msg, $args);
849 }
850
851 // Code coverage reporting.
852 if (devel_code_coverage_enabled()) {
853 $mode = variable_get('devel_code_coverage', FALSE);
854 $coverage = xdebug_get_code_coverage();
855 if ($mode == 1) {
856 $output .= dpr(array_keys($coverage), TRUE);
857 }
858 else {
859 $output .= dpr($coverage, TRUE);
860 }
861 }
862 }
863
864 if ($output) {
865 // TODO: gzip this text if we are sending a gzip page. see drupal_page_header().
866 // For some reason, this is not actually printing for cached pages even though it gets executed
867 // and $output looks good.
868 print $output;
869 }
870 }
871 }
872
873 function devel_shutdown_query() {
874 $output = '';
875 $queries = Database::getLog('devel', 'default');
876 list($counts, $query_summary) = devel_query_summary($queries);
877 // Query log off, timer on.
878 if (!variable_get('devel_query_display', 0) && variable_get('dev_timer', 0)) {
879 $output .= '<div class="dev-timer">'. devel_timer() .' '. $query_summary. '</div>';
880 }
881
882 // Query log on.
883 $sum = 0;
884 if (variable_get('devel_query_display', FALSE)) {
885 $output .= '<div class="dev-query">';
886 $output .= $query_summary;
887 // calling theme() during shutdown is very bad if registry gets rebuilt like when making a change on admin/build/modules
888 // so we check for presence of theme registry before calling theme()
889 if (function_exists('theme_get_registry') && theme_get_registry()) {
890 $txt = t_safe(' Queries taking longer than @threshold ms and queries executed more than once, are <span class="marker">highlighted</span>.', array('@threshold' => variable_get('devel_execution', 5)));
891 if (variable_get('dev_timer', 0)) {
892 $txt .= devel_timer();
893 }
894 $output .= $txt;
895 $output .= '</div>';
896 $output .= devel_query_table($queries, $counts);
897 }
898 else {
899 $output .= $txt . '</div>' . dprint_r($queries, TRUE);
900 }
901 }
902
903 return $output;
904 }
905
906 function devel_query_enabled() {
907 return method_exists('Database', 'getLog') && variable_get('devel_query_display', FALSE);
908 }
909
910 function devel_code_coverage_enabled() {
911 return function_exists('xdebug_get_code_coverage') && variable_get('devel_code_coverage', FALSE);
912 }
913
914 function devel_query_summary($queries) {
915 if (variable_get('devel_query_display', FALSE) && is_array($queries)) {
916 $sum = 0;
917 foreach ($queries as $query) {
918 $text[] = $query['query'];
919 $sum += $query['time'];
920 }
921 $counts = array_count_values($text);
922 return array($counts, t_safe('Executed @queries queries in @time milliseconds.', array('@queries' => count($queries), '@time' => round($sum * 1000, 2))));
923 }
924 }
925
926 function t_safe($string, $args) {
927 // get_t caused problems here with theme registry after changing on admin/build/modules. the theme_get_registry call is needed.
928 if (function_exists('t') && function_exists('theme_get_registry')) {
929 theme_get_registry();
930 return t($string, $args);
931 }
932 else {
933 strtr($string, $args);
934 }
935 }
936
937 /**
938 * Returns a list of all currently defined user functions in the current
939 * request lifecycle, with links their documentation.
940 */
941 function devel_function_reference() {
942 $functions = get_defined_functions();
943 $version = devel_get_core_version(VERSION);
944 $ufunctions = $functions['user'];
945 sort($ufunctions);
946 $api = variable_get('devel_api_url', 'api.drupal.org');
947 foreach ($ufunctions as $function) {
948 $links[] = l($function, "http://$api/api/$version/function/$function");
949 }
950 return theme('item_list', array('items' => $links));
951 }
952
953 function devel_get_core_version($version) {
954 $version_parts = explode('.', $version);
955 // Map from 4.7.10 -> 4.7
956 if ($version_parts[0] < 5) {
957 return $version_parts[0] .'.'. $version_parts[1];
958 }
959 // Map from 5.5 -> 5 or 6.0-beta2 -> 6
960 else {
961 return $version_parts[0];
962 }
963 }
964
965 // See http://drupal.org/node/126098
966 function devel_is_compatible_optimizer() {
967 ob_start();
968 phpinfo();
969 $info = ob_get_contents();
970 ob_end_clean();
971
972 // Match the Zend Optimizer version in the phpinfo information
973 $found = preg_match('/Zend&nbsp;Optimizer&nbsp;v([0-9])\.([0-9])\.([0-9])/', $info, $matches);
974
975 if ($matches) {
976 $major = $matches[1];
977 $minor = $matches[2];
978 $build = $matches[3];
979
980 if ($major >= 3) {
981 if ($minor >= 3) {
982 return TRUE;
983 }
984 elseif ($minor == 2 && $build >= 8) {
985 return TRUE;
986 }
987 else {
988 return FALSE;
989 }
990 }
991 else {
992 return FALSE;
993 }
994 }
995 else {
996 return TRUE;
997 }
998 }
999
1000 function devel_admin_settings() {
1001 $form['queries'] = array('#type' => 'fieldset', '#title' => t('Query log'));
1002
1003 $description = t('Display a log of the database queries needed to generate the current page, and the execution time for each. Also, queries which are repeated during a single page view are summed in the # column, and printed in red since they are candidates for caching.');
1004 if (!devel_is_compatible_optimizer()) {
1005 $description = t('You must disable or upgrade the php Zend Optimizer extension in order to enable this feature. The minimum required version is 3.2.8. Earlier versions of Zend Optimizer are <a href="!url">horribly buggy and segfault your Apache</a> ... ', array('!url' => url('http://drupal.org/node/126098'))) . $description;
1006 }
1007 $form['queries']['devel_query_display'] = array('#type' => 'checkbox',
1008 '#title' => t('Display query log'),
1009 '#default_value' => variable_get('devel_query_display', 0),
1010 '#description' => $description,
1011 '#disabled' => !devel_is_compatible_optimizer(),
1012 );
1013 $form['queries']['devel_query_sort'] = array('#type' => 'radios',
1014 '#title' => t('Sort query log'),
1015 '#default_value' => variable_get('devel_query_sort', DEVEL_QUERY_SORT_BY_SOURCE),
1016 '#options' => array(t('by source'), t('by duration')),
1017 '#description' => t('The query table can be sorted in the order that the queries were executed or by descending duration.'),
1018 );
1019 $form['queries']['devel_execution'] = array('#type' => 'textfield',
1020 '#title' => t('Slow query highlighting'),
1021 '#default_value' => variable_get('devel_execution', 5),
1022 '#size' => 4,
1023 '#maxlength' => 4,
1024 '#description' => t('Enter an integer in milliseconds. Any query which takes longer than this many milliseconds will be highlighted in the query log. This indicates a possibly inefficient query, or a candidate for caching.'),
1025 );
1026 $form['devel_api_url'] = array('#type' => 'textfield',
1027 '#title' => t('API Site'),
1028 '#default_value' => variable_get('devel_api_url', 'api.drupal.org'),
1029 '#description' => t('The base URL for your developer documentation links. You might change this if you run <a href="!url">api.module</a> locally.', array('!url' => url('http://drupal.org/project/api'))));
1030 $form['dev_timer'] = array('#type' => 'checkbox',
1031 '#title' => t('Display page timer'),
1032 '#default_value' => variable_get('dev_timer', 0),
1033 '#description' => t('Display page execution time in the query log box.'),
1034 );
1035 $form['dev_mem'] = array('#type' => 'checkbox',
1036 '#title' => t('Display memory usage'),
1037 '#default_value' => variable_get('dev_mem', 0),
1038 '#description' => t('Display how much memory is used to generate the current page. This will show memory usage when devel_init() is called and when devel_exit() is called.'),
1039 );
1040 $form['devel_code_coverage'] = array(
1041 '#type' => 'radios',
1042 '#title' => t('Code Coverage'),
1043 '#disabled' => !function_exists('xdebug_start_code_coverage'),
1044 '#default_value' => variable_get('devel_code_coverage', 0),
1045 '#options' => array(t('Disabled'), t('Files only'), 'Files and lines'),
1046 '#description' => t('Show the files (and optionally the lines in those files) which were loaded by PHP to fulfill this request. In general, fewer files is better since parsing files costs significant performance. Requires <a href="http://xdebug.org">xdebug</a>.'),
1047 );
1048 $form['devel_redirect_page'] = array('#type' => 'checkbox',
1049 '#title' => t('Display redirection page'),
1050 '#default_value' => variable_get('devel_redirect_page', 0),
1051 '#description' => t('When a module executes drupal_goto(), the query log and other developer information is lost. Enabling this setting presents an intermediate page to developers so that the log can be examined before continuing to the destination page.'),
1052 );
1053 $form['devel_form_weights'] = array('#type' => 'checkbox',
1054 '#title' => t('Display form element keys and weights'),
1055 '#default_value' => variable_get('devel_form_weights', 0),
1056 '#description' => t('Form element names are needed for performing themeing or altering a form. Their weights determine the position of the element. Enabling this setting will show these keys and weights beside each form item.'),
1057 );
1058 $form['devel_page_alter'] = array('#type' => 'checkbox',
1059 '#title' => t('Display $page array'),
1060 '#default_value' => variable_get('devel_page_alter', FALSE),
1061 '#description' => t('Display $page array from <a href="http://api.drupal.org/api/function/hook_page_alter/7">hook_page_alter()</a> in the messages area of each page.'),
1062 );
1063 $form['devel_error_handler'] = array('#type' => 'radios',
1064 '#title' => t('Error handler'),
1065 '#default_value' => variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD),
1066 '#options' => array(DEVEL_ERROR_HANDLER_NONE => t('None'), DEVEL_ERROR_HANDLER_STANDARD => t('Standard drupal')),
1067 '#description' => t('Choose an error handler for your site. <em>Backtrace</em> prints nice debug information when an error is noticed, and you <a href="@choose">choose to show errors on screen</a>. <strong>Backtrace requires the <a href="@krumo">krumo library</a></strong>. <em>None</em> is a good option when stepping through the site in your debugger.', array('@krumo' => url('http://krumo.sourceforge.net'), '@choose' => url('admin/settings/error-reporting'))),
1068 );
1069 if (has_krumo()) {
1070 $form['devel_error_handler']['#options'][DEVEL_ERROR_HANDLER_BACKTRACE] = t('Backtrace');
1071 }
1072
1073 $options = drupal_map_assoc(array('default', 'blue', 'green', 'orange', 'schablon.com', 'disabled'));
1074 $form['devel_krumo_skin'] = array(
1075 '#type' => 'radios',
1076 '#title' => t('Krumo display'),
1077 '#description' => t('Select a skin for your debug messages or select <em>disabled</em> to display object and array output in standard PHP format.'),
1078 '#options' => $options,
1079 '#default_value' => variable_get('devel_krumo_skin', 'default'),
1080 );
1081
1082 $form['devel_rebuild_theme_registry'] = array(
1083 '#type' => 'checkbox',
1084 '#title' => t('Rebuild the theme registry on every page load'),
1085 '#description' => t('While creating new templates and theme_ overrides the theme registry needs to be rebuilt.'),
1086 '#default_value' => variable_get('devel_rebuild_theme_registry', FALSE),
1087 );
1088
1089 return system_settings_form($form);
1090 }
1091
1092 /**
1093 * Menu callback; clears all caches, then redirects to the previous page.
1094 */
1095 function devel_cache_clear() {
1096 drupal_flush_all_caches();
1097
1098 drupal_set_message('Cache cleared.');
1099
1100 drupal_goto();
1101 }
1102
1103 /**
1104 * Generates the execute block form.
1105 */
1106 function devel_execute_form() {
1107 $form['code'] = array(
1108 '#type' => 'textarea',
1109 '#title' => t('PHP code to execute'),
1110 '#description' => t('Enter some code. Do not use <code>&lt;?php ?&gt;</code> tags.')
1111 );
1112 $form['op'] = array('#type' => 'submit', '#value' => t('Execute'));
1113 $form['#redirect'] = FALSE;
1114 $form['#skip_duplicate_check'] = TRUE;
1115 return $form;
1116 }
1117
1118 /**
1119 * Process PHP execute form submissions.
1120 */
1121 function devel_execute_form_submit($form, &$form_state) {
1122 ob_start();
1123 print eval($form_state['values']['code']);
1124 dsm(ob_get_clean());
1125 }
1126
1127 /**
1128 * Menu callback; clear the database, resetting the menu to factory defaults.
1129 */
1130 function devel_menu_rebuild() {
1131 menu_rebuild();
1132 drupal_set_message(t('The menu router has been rebuilt.'));
1133 drupal_goto();
1134 }
1135
1136 /**
1137 * Display a dropdown of installed modules with the option to reinstall them.
1138 */
1139 function devel_reinstall($form, &$form_state) {
1140 $output = '';
1141 $modules = module_list();
1142 sort($modules);
1143 $options = drupal_map_assoc($modules);
1144 $form['list'] = array(
1145 '#type' => 'checkboxes',
1146 '#options' => $options,
1147 '#description' => t('Uninstall and then install the selected modules. <code>hook_uninstall()</code> and <code>hook_install()</code> will be executed and the schema version number will be set to the most recent update number. You may have to manually clear out any existing tables first if the module doesn\'t implement <code>hook_uninstall()</code>.'),
1148 );
1149 $form['submit'] = array(
1150 '#value' => t('Reinstall'),
1151 '#type' => 'submit',
1152 );
1153 if (empty($form_state['post'])) {
1154 drupal_set_message(t('Warning - will delete your module tables and variables.'), 'error');
1155 }
1156 return $form;
1157 }
1158
1159 /**
1160 * Process reinstall menu form submissions.
1161 */
1162 function devel_reinstall_submit($form, &$form_state) {
1163 require_once './includes/install.inc';
1164 $modules = array_filter($form_state['values']['list']);
1165 foreach ($modules as $module) {
1166 module_load_install($module);
1167 $versions = drupal_get_schema_versions($module);
1168 drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
1169 module_invoke($module, 'uninstall');
1170 _drupal_install_module($module);
1171 module_invoke($module, 'enable');
1172 drupal_get_schema(NULL, TRUE);
1173 drupal_set_message(t('Uninstalled and installed the %name module.', array('%name' => $module)));
1174 }
1175 }
1176
1177 // Menu callback.
1178 function devel_theme_registry() {
1179 drupal_theme_initialize();
1180 $hooks = theme_get_registry();
1181 ksort($hooks);
1182 return kprint_r($hooks, TRUE);
1183 }
1184
1185 // Menu callback.
1186 function devel_field_info_page() {
1187 $info = field_info_fields();
1188 return kprint_r($info, TRUE);
1189 }
1190
1191 /**
1192 * Menu callback; display all variables.
1193 */
1194 function devel_variable_page() {
1195 // We return our own $page so as to avoid blocks.
1196 $output = drupal_get_form('devel_variable_form');
1197 drupal_set_page_content($output);
1198 $page = element_info('page');
1199 return $page;
1200 }
1201
1202 function devel_variable_form() {
1203 $header = array(
1204 'name' => array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'),
1205 'value' => array('data' => t('Value'), 'field' => 'value'),
1206 'length' => array('data' => t('Length'), 'field' => 'length'),
1207 'edit' => array('data' => t('Operations')),
1208 );
1209 // TODO: we could get variables out of $conf but that would include hard coded ones too. ideally i would highlight overrridden/hard coded variables
1210 $query = db_select('variable', 'v')->extend('TableSort');
1211 $query->fields('v', array('name', 'value'));
1212 switch (db_driver()) {
1213 case 'mssql':
1214 $query->addExpression("COL_LENGTH('{variable}', 'value')", 'length');
1215 break;
1216 default:
1217 $query->addExpression("CONVERT(LENGTH(v.value), UNSIGNED INTEGER)", 'length');
1218 break;
1219 }
1220 $result = $query
1221 ->orderByHeader($header)
1222 ->execute();
1223
1224 foreach ($result as $row) {
1225 // $variables[$row->name] = '';
1226 $options[$row->name]['name'] = check_plain($row->name);
1227 if (merits_krumo($row->value)) {
1228 $value = krumo_ob(variable_get($row->name, NULL));
1229 }
1230 else {
1231 if (drupal_strlen($row->value) > 70) {
1232 $value = check_plain(drupal_substr($row->value, 0, 65)) .'...';
1233 }
1234 else {
1235 $value = check_plain($row->value);
1236 }
1237 }
1238 $options[$row->name]['value'] = $value;
1239 $options[$row->name]['length'] = $row->length;
1240 $options[$row->name]['edit'] = l(t('Edit'), "devel/variable/edit/$row->name");
1241 }
1242 $form['variables'] = array(
1243 '#type' => 'tableselect',
1244 '#header' => $header,
1245 '#options' => $options,
1246 '#empty' => t('No variables.'),
1247 );
1248 $form['submit'] = array(
1249 '#type' => 'submit',
1250 '#value' => t('Delete'),
1251 );
1252
1253 // krumo($form);
1254 return $form;
1255 }
1256
1257 function devel_variable_form_submit($form, &$form_state) {
1258 $deletes = array_filter($form_state['values']['variables']);
1259 array_walk($deletes, 'variable_del');
1260 drupal_set_message(format_plural(count($deletes), 'One variable deleted.', '@count variables deleted.'));
1261 }
1262
1263 function devel_variable_edit($form, &$form_state, $name) {
1264 $value = variable_get($name, 'not found');
1265 $form['name'] = array(
1266 '#type' => 'value',
1267 '#value' => $name
1268 );
1269 $form['value'] = array(
1270 '#type' => 'item',
1271 '#title' => t('Old value'),
1272 '#markup' => dpr($value, TRUE),
1273 );
1274 if (is_string($value) || is_numeric($value)) {
1275 $form['new'] = array(
1276 '#type' => 'textarea',
1277 '#title' => t('New value'),
1278 '#default_value' => $value
1279 );
1280 $form['submit'] = array(
1281 '#type' => 'submit',
1282 '#value' => t('Submit'),
1283 );
1284 }
1285 else {
1286 $api = variable_get('devel_api_url', 'api.drupal.org');
1287 $form['new'] = array(
1288 '#type' => 'item',
1289 '#title' => t('New value'),
1290 '#value' => t('Sorry, complex variable types may not be edited yet. Use the <em>Execute PHP</em> block and the <a href="@variable-set-doc">variable_set()</a> function.', array('@variable-set-doc' => "http://$api/api/HEAD/function/variable_set"))
1291 );
1292 }
1293 drupal_set_title($name);
1294 return $form;
1295 }
1296
1297 function devel_variable_edit_submit($form, &$form_state) {
1298 variable_set($form_state['values']['name'], $form_state['values']['new']);
1299 drupal_set_message(t('Saved new value for %name.', array('%name' => $form_state['values']['name'])));
1300 'devel/variable';
1301 }
1302
1303 /**
1304 * Menu callback: display the session.
1305 */
1306 function devel_session() {
1307 global $user;
1308 $output = kprint_r($_SESSION, TRUE);
1309 $headers = array(t('Session name'), t('Session ID'));
1310 $output .= theme('table', array('headers' => $headers, 'rows' => array(array(session_name(), session_id()))));
1311 return $output;
1312 }
1313
1314 /**
1315 * Switch from original user to another user and back.
1316 * We don't call session_save_session() because we really want to change users. Usually unsafe!
1317 *
1318 * @param $name The username to switch to.
1319 */
1320 function devel_switch_user($name = NULL) {
1321 global $user;
1322 static $orig_user = array();
1323
1324 if (isset($name)) {
1325 $user = user_load_by_name($name);
1326 }
1327 // Retrieve the initial user. Can be called multiple times.
1328 elseif (count($orig_user)) {
1329 $user = array_shift($orig_user);
1330 array_unshift($orig_user, $user);
1331 }
1332 // Store the initial user.
1333 else {
1334 $orig_user[] = $user;
1335 }
1336 drupal_goto();
1337 }
1338
1339 /**
1340 * Menu callback; prints the loaded structure of the current node/user.
1341 */
1342 function devel_load_object($object, $name = NULL) {
1343 $title = isset($object->title) ? $object->title : $object->name;
1344 drupal_set_title($title);
1345 return kdevel_print_object($object, '$'. $name .'->');
1346 }
1347
1348
1349 /**
1350 * Menu callback; prints the render structure of the a term.
1351 */
1352 function devel_render_term($term) {
1353 $build = array();
1354 $build += field_attach_view('taxonomy_term', $term);
1355 if (!empty($term->description)) {
1356 $build['term_description'] = array(
1357 '#markup' => filter_xss_admin($term->description),
1358 '#weight' => -1,
1359 '#prefix' => '<div class="taxonomy-term-description">',
1360 '#suffix' => '</div>',
1361 );
1362 }
1363 return kdevel_print_object($build, '$term->');
1364 }
1365
1366 /**
1367 * Menu callback; prints the render structure of the current object (currently node or user).
1368 */
1369 function devel_render_object($type, $object) {
1370 $output = '';
1371 $title = isset($object->title) ? $object->title : $object->name;
1372 // not sure why menu system doesn't give us a reasonable title here.
1373 drupal_set_title($title);
1374 $function = $type .'_build_content';
1375 $content = $function($object);
1376 return kdevel_print_object($content, '$'. $type .'->');
1377 }
1378
1379 function devel_elements_page() {
1380 return kdevel_print_object(module_invoke_all('elements'));
1381 }
1382
1383 /**
1384 * Print an object or array using either Krumo (if installed) or devel_print_object()
1385 *
1386 * @param $object
1387 * array or object to print
1388 * @param $prefix
1389 * prefixing for output items
1390 */
1391 function kdevel_print_object($object, $prefix = NULL) {
1392 return has_krumo() ? krumo_ob($object) : devel_print_object($object, $prefix);
1393 }
1394
1395 // Save krumo htlm using output buffering.
1396 function krumo_ob($object) {
1397 ob_start();
1398 krumo($object);
1399 $output = ob_get_contents();
1400 ob_end_clean();
1401 return $output;
1402 }
1403
1404 /**
1405 * Display an object or array
1406 *
1407 * @param $object
1408 * the object or array
1409 * @param $prefix
1410 * prefix for the output items (example "$node->", "$user->", "$")
1411 * @param $header
1412 * set to FALSE to suppress the output of the h3
1413 */
1414 function devel_print_object($object, $prefix = NULL, $header = TRUE) {
1415 drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css');
1416 $output = '<div class="devel-obj-output">';
1417 if ($header) {
1418 $output .= '<h3>'. t('Display of !type !obj', array('!type' => str_replace(array('$', '->'), '', $prefix), '!obj' => gettype($object))) .'</h3>';
1419 }
1420 $output .= _devel_print_object($object, $prefix);
1421 $output .= '</div>';
1422 return $output;
1423 }
1424
1425 /**
1426 * Recursive (and therefore magical) function goes through an array or object and
1427 * returns a nicely formatted listing of its contents.
1428 *
1429 * @param $obj
1430 * array or object to recurse through
1431 * @param $prefix
1432 * prefix for the output items (example "$node->", "$user->", "$")
1433 * @param $parents
1434 * used by recursion
1435 * @param $object
1436 * used by recursion
1437 * @return
1438 * fomatted html
1439 *
1440 * @todo
1441 * currently there are problems sending an array with a varname
1442 */
1443 function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FALSE) {
1444 static $root_type, $out_format;
1445
1446 // TODO: support objects with references. See http://drupal.org/node/234581.
1447 if (isset($obj->view)) {
1448 return;
1449 }
1450
1451 if (!isset($root_type)) {
1452 $root_type = gettype($obj);
1453 if ($root_type == 'object') {
1454 $object = TRUE;
1455 }
1456 }
1457
1458 if (is_object($obj)) {
1459 $obj = (array)$obj;
1460 }
1461 if (is_array($obj)) {
1462 $output = "<dl>\n";
1463 foreach ($obj as $field => $value) {
1464 if ($field == 'devel_flag_reference') {
1465 continue;
1466 }
1467 if (!is_null($parents)) {
1468 if ($object) {
1469 $field = $parents .'->'. $field;
1470 }
1471 else {
1472 if (is_int($field)) {
1473 $field = $parents .'['. $field .']';
1474 }
1475 else {
1476 $field = $parents .'[\''. $field .'\']';
1477 }
1478 }
1479 }
1480
1481 $type = gettype($value);
1482
1483 $show_summary = TRUE;
1484 $summary = NULL;
1485 if ($show_summary) {
1486 switch ($type) {
1487 case 'string' :
1488 case 'float' :
1489 case 'integer' :
1490 if (strlen($value) == 0) {
1491 $summary = t("{empty}");
1492 }
1493 elseif (strlen($value) < 40) {
1494 $summary = htmlspecialchars($value);
1495 }
1496 else {
1497 $summary = format_plural(drupal_strlen($value), '1 character', '@count characters');
1498 }
1499 break;
1500 case 'array' :
1501 case 'object' :
1502 $summary = format_plural(count((array)$value), '1 element', '@count elements');
1503 break;
1504 case 'boolean' :
1505 $summary = $value ? t('TRUE') : t('FALSE');
1506 break;
1507 }
1508 }
1509 if (!is_null($summary)) {
1510 $typesum = '('. $type .', <em>'. $summary .'</em>)';
1511 }
1512 else {
1513 $typesum = '('. $type .')';
1514 }
1515
1516 $output .= '<span class="devel-attr">';
1517 $output .= "<dt><span class=\"field\">{$prefix}{$field}</span> $typesum</dt>\n";
1518 $output .= "<dd>\n";
1519 // Check for references.
1520 if (is_array($value) && isset($value['devel_flag_reference'])) {
1521 $value['devel_flag_reference'] = TRUE;
1522 }
1523 // Check for references to prevent errors from recursions.
1524 if (is_array($value) && isset($value['devel_flag_reference']) && !$value['devel_flag_reference']) {
1525 $value['devel_flag_reference'] = FALSE;
1526 $output .= _devel_print_object($value, $prefix, $field);
1527 }
1528 elseif (is_object($value)) {
1529 $value->devel_flag_reference = FALSE;
1530 $output .= _devel_print_object((array)$value, $prefix, $field, TRUE);
1531 }
1532 else {
1533 $value = is_bool($value) ? ($value ? 'TRUE' : 'FALSE') : $value;
1534 $output .= htmlspecialchars(print_r($value, TRUE)) ."\n";
1535 }
1536 $output .= "</dd></span>\n";
1537 }
1538 $output .= "</dl>\n";
1539 }
1540 return $output;
1541 }
1542
1543 /**
1544 * Adds a table at the bottom of the page cataloguing data on all the database queries that were made to
1545 * generate the page.
1546 */
1547 function devel_query_table($queries, $counts) {
1548 $version = devel_get_core_version(VERSION);
1549 $header = array ('ms', '#', 'where', 'query', 'target');
1550 $i = 0;
1551 $api = variable_get('devel_api_url', 'api.drupal.org');
1552 foreach ($queries as $query) {
1553 $function = $query['caller']['function'];
1554 $count = isset($counts[$query['query']]) ? $counts[$query['query']] : 0;
1555
1556 $diff = round($query['time'] * 1000, 2);
1557 if ($diff > variable_get('devel_execution', 5)) {
1558 $cell[$i][] = array ('data' => $diff, 'class' => 'marker');
1559 }
1560 else {
1561 $cell[$i][] = $diff;
1562 }
1563 if ($count > 1) {
1564 $cell[$i][] = array ('data' => $count, 'class' => 'marker');
1565 }
1566 else {
1567 $cell[$i][] = $count;
1568 }
1569 $cell[$i][] = l($function, "http://$api/api/$version/function/$function");
1570 $cell[$i][] = check_plain($query['query']);
1571 $cell[$i][] = $query['target'];
1572 $i++;
1573 unset($diff, $count);
1574 }
1575 if (variable_get('devel_query_sort', DEVEL_QUERY_SORT_BY_SOURCE)) {
1576 usort($cell, '_devel_table_sort');
1577 }
1578 return theme('devel_querylog', array('header' => $header, 'rows' => $cell));
1579 }
1580
1581 function theme_devel_querylog_row($variables) {
1582 $row = $variables['row'];
1583 $i = 0;
1584 $output = '';
1585 foreach ($row as $cell) {
1586 $i++;
1587
1588 if (is_array($cell)) {
1589 $data = !empty($cell['data']) ? $cell['data'] : '';
1590 unset($cell['data']);
1591 $attr = $cell;
1592 }
1593 else {
1594 $data = $cell;
1595 $attr = array();
1596 }
1597
1598 if (!empty($attr['class'])) {
1599 $attr['class'] .= " cell cell-$i";
1600 }
1601 else {
1602 $attr['class'] = "cell cell-$i";
1603 }
1604 $attr = drupal_attributes($attr);
1605
1606 $output .= "<div $attr>$data</div>";
1607 }
1608 return $output;
1609 }
1610
1611 function theme_devel_querylog($variables) {
1612 $header = $variables['header'];
1613 $rows = $variables['rows'];
1614 $output = '';
1615 if (!empty($header)) {
1616 $output .= "<div class='devel-querylog devel-querylog-header clear-block'>";
1617 $output .= theme('devel_querylog_row', array('row' => $header));
1618 $output .= "</div>";
1619 }
1620 if (!empty($rows)) {
1621 $i = 0;
1622 foreach ($rows as $row) {
1623 $i++;
1624 $zebra = ($i % 2) == 0 ? 'even' : 'odd';
1625 $output .= "<div class='devel-querylog devel-querylog-$zebra clear-block'>";
1626 $output .= theme('devel_querylog_row', array('row' => $row));
1627 $output .= "</div>";
1628 }
1629 }
1630 return $output;
1631 }
1632
1633 function _devel_table_sort($a, $b) {
1634 $a = is_array($a[0]) ? $a[0]['data'] : $a[0];
1635 $b = is_array($b[0]) ? $b[0]['data'] : $b[0];
1636 if ($a < $b) {
1637 return 1;
1638 }
1639 if ($a > $b) {
1640 return -1;
1641 }
1642 return 0;
1643 }
1644
1645 /**
1646 * Displays page execution time at the bottom of the page.
1647 */
1648 function devel_timer() {
1649 $time = timer_read('page');
1650 return t_safe(' Page execution time was @time ms.', array('@time' => $time));
1651 }
1652
1653 // An alias for drupal_debug().
1654 function dd($data, $label = NULL) {
1655 return drupal_debug($data, $label);
1656 }
1657
1658 // Log any variable to a drupal_debug.log in the site's temp directory.
1659 // See http://drupal.org/node/314112
1660 function drupal_debug($data, $label = NULL) {
1661 ob_start();
1662 print_r($data);
1663 $string = ob_get_clean();
1664 if ($label) {
1665 $out = $label .': '. $string;
1666 }
1667 else {
1668 $out = $string;
1669 }
1670 $out .= "\n";
1671
1672 // The temp directory does vary across multiple simpletest instances.
1673 $file = file_directory_path('temporary') . '/drupal_debug.txt';
1674 if (file_put_contents($file, $out, FILE_APPEND) === FALSE) {
1675 drupal_set_message(t('The file could not be written.'), 'error');
1676 return FALSE;
1677 }
1678 }
1679
1680 /**
1681 * Prints the arguments passed into the current function
1682 */
1683 function dargs($always = TRUE) {
1684 static $printed;
1685 if ($always || !$printed) {
1686 $bt = debug_backtrace();
1687 print kdevel_print_object($bt[1]['args']);
1688 $printed = TRUE;
1689 }
1690 }
1691
1692 /**
1693 * Print a variable to the 'message' area of the page. Uses drupal_set_message()
1694 */
1695 function dpm($input, $name = NULL) {
1696 if (user_access('access devel information')) {
1697 $export = kprint_r($input, TRUE, $name);
1698 drupal_set_message($export);
1699 }
1700 }
1701
1702 /**
1703 * Var_dump() a variable to the 'message' area of the page. Uses drupal_set_message()
1704 */
1705 function dvm($input, $name = NULL) {
1706 if (user_access('access devel information')) {
1707 $export = dprint_r($input, TRUE, $name, 'var_dump', FALSE);
1708 drupal_set_message($export);
1709 }
1710 }
1711
1712 // legacy function that was poorly named. use dpm() instead, since the 'p' maps to 'print_r'
1713 function dsm($input, $name = NULL) {
1714 dpm($input, $name);
1715 }
1716
1717 /**
1718 * An alias for dprint_r(). Saves carpal tunnel syndrome.
1719 */
1720 function dpr($input, $return = FALSE, $name = NULL) {
1721 return dprint_r($input, $return, $name);
1722 }
1723
1724 /**
1725 * An alias for kprint_r(). Saves carpal tunnel syndrome.
1726 */
1727 function kpr($input, $return = FALSE, $name = NULL) {
1728 return kprint_r($input, $return, $name);
1729 }
1730
1731 /**
1732 * Like dpr, but uses var_dump() instead
1733 */
1734 function dvr($input, $return = FALSE, $name = NULL) {
1735 return dprint_r($input, $return, $name, 'var_dump', FALSE);
1736 }
1737
1738 function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') {
1739 // We do not want to krumo() strings and integers and such
1740 if (merits_krumo($input)) {
1741 if (user_access('access devel information')) {
1742 return $return ? (isset($name) ? $name .' => ' : '') . krumo_ob($input) : krumo($input);
1743 }
1744 }
1745 else {
1746 return dprint_r($input, $return, $name, $function);
1747 }
1748 }
1749
1750 /**
1751 * Pretty-print a variable to the browser (no krumo).
1752 * Displays only for users with proper permissions. If
1753 * you want a string returned instead of a print, use the 2nd param.
1754 */
1755 function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check= TRUE) {
1756 if (user_access('access devel information')) {
1757 if ($name) {
1758 $name .= ' => ';
1759 }
1760 ob_start();
1761 $function($input);
1762 $output = ob_get_clean();
1763 if ($check) {
1764 $output = check_plain($output);
1765 }
1766 if (count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) {
1767 // don't use fapi here because sometimes fapi will not be loaded
1768 $printed_value = "<textarea rows=30 style=\"width: 100%;\">\n". $name . $output .'</textarea>';
1769 }
1770 else {
1771 $printed_value = '<pre>'. $name . $output .'</pre>';
1772 }
1773
1774 if ($return) {
1775 return $printed_value;
1776 }
1777 else {
1778 print $printed_value;
1779 }
1780 }
1781 }
1782
1783 /**
1784 * Print the function call stack.
1785 */
1786 function ddebug_backtrace() {
1787 if (user_access('access devel information')) {
1788 $trace = debug_backtrace();
1789 array_shift($trace);
1790 foreach ($trace as $key => $value) {
1791 $rich_trace[$value['function']] = $value;
1792 }
1793 if (has_krumo()) {
1794 print krumo($rich_trace);
1795 }
1796 else {
1797 dprint_r($rich_trace);
1798 }
1799 }
1800 }
1801
1802 /*
1803 * migration related functions
1804 */
1805
1806
1807 /**
1808 * Update node_comment_statistics table for nodes with comments.
1809 * TODO: if 2 comments have exact same timestamp, the function can get wrong uid and name fields.
1810 * Handles when comment timestamps have been manually set in admin
1811 *
1812 * @return void
1813 **/
1814 function devel_rebuild_node_comment_statistics() {
1815 // Empty table
1816 db_delete('node_comment_statistics');
1817
1818 $sql = "INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) (SELECT nid, c.timestamp, name, uid, comment_count FROM {comments} c INNER JOIN (SELECT MAX(timestamp) AS timestamp, COUNT(*) AS comment_count FROM {comments} WHERE status=%d GROUP BY nid) AS c2 ON c.timestamp=c2.timestamp)";
1819 db_query($sql, COMMENT_PUBLISHED);
1820
1821 // Insert 0 count records into the node_comment_statistics for nodes that are missing. See comment_enable()
1822 db_query_temporary("SELECT n.nid, n.changed, n.uid FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE c.comment_count IS NULL", 'missing_nids');
1823 db_query("INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) SELECT n.nid, n.changed, NULL, n.uid, 0 FROM missing_nids n");
1824 }