Removing translation directories
[project/devel.git] / devel.module
CommitLineData
22c2656f 1<?php
2
9d450e49 3// This module holds functions useful for Drupal development.
22c2656f 4// Please contribute!
5
defc4865 6// suggested profiling and stacktrace library from http://www.xdebug.org/index.php
7// if you activate this extension, this module will use it.
8// you probably want these php.ini or .htaccess directives:
9// xdebug.auto_profile=1
10// xdebug.auto_profile_mode=3
24e11af9 11// xdebug.output_dir='/php'
defc4865 12// xdebug.default_enable
13
f61cbcf2 14define('DEVEL_QUERY_SORT_BY_SOURCE', 0);
15define('DEVEL_QUERY_SORT_BY_DURATION', 1);
dcd35dd9 16
585b64f0 17define('DEVEL_ERROR_HANDLER_NONE', 0);
18define('DEVEL_ERROR_HANDLER_STANDARD', 1);
19define('DEVEL_ERROR_HANDLER_BACKTRACE', 2);
20
24622db0 21define('DEVEL_MIN_TEXTAREA', 50);
22
d5e1689b 23define('DEVEL_CURRENT_DRUPAL_VERSION',5);
24
1e7a3874 25/**
9d450e49 26 * Implementation of hook_help().
1e7a3874 27 */
9d450e49 28function devel_help($section) {
29 switch ($section) {
9d450e49 30 case 'admin/settings/devel':
fefa2039 31 return '<p>'. t('Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the <a href="@block">block administration</a> page.', array('@block' => url('admin/build/block'))). '</p>';
307c8920 32 case 'devel/reference':
33 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>';
9d450e49 34 case 'devel/reinstall':
b3cff101 35 return '<p>'. t('Clicking a module\'s reinstall button will simulate uninstalling/installing a module. <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>.'). '</p>';
307c8920 36 case 'devel/session':
37 return '<p>'. t('Here are the contents of your <code>$_SESSION</code> variable.'). '</p>';
38 case 'devel/variable':
e31293a4 39 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.drupal.org/api/HEAD/function/variable_get', '@variable-set-doc' => 'http://api.drupal.org/api/HEAD/function/variable_set')).'</p>';
fc909860 40 }
defc4865 41}
42
1e7a3874
JC
43/**
44 * Implementation of hook_menu().
45 */
46function devel_menu($may_cache) {
47 $items = array();
48
49 if ($may_cache) {
9d450e49 50 $items[] = array('path' => 'devel/cache/clear',
27f6d183 51 'title' => t('Empty cache'),
1e7a3874 52 'callback' => 'devel_cache_clear',
9d450e49 53 'access' => user_access('access devel information'),
54 'type' => MENU_CALLBACK,
55 );
9d14df83 56 $items[] = array('path' => 'devel/rebuild_node_comment_statistics',
57 'title' => t('Rebuild node_comment_statistics table'),
58 'callback' => 'devel_rebuild_node_comment_statistics_page',
59 'access' => user_access('access devel information'),
60 'type' => MENU_CALLBACK,
61 );
7ac60dac 62 $items[] = array('path' => 'devel/queries',
27f6d183 63 'title' => t('Database queries'),
7ac60dac
GK
64 'callback' => 'devel_queries',
65 'access' => user_access('access devel information'));
66 $items[] = array('path' => 'devel/queries/empty',
27f6d183 67 'title' => t('Empty database queries'),
7ac60dac
GK
68 'callback' => 'devel_queries_empty',
69 'access' => user_access('access devel information'),
70 'type' => MENU_CALLBACK);
383138d6 71 $items[] = array('path' => 'devel/reference',
72 'title' => t('function reference'),
73 'callback' => 'devel_function_reference',
74 'access' => user_access('access devel information'),
75 'type' => MENU_CALLBACK,
76 );
9d450e49 77 $items[] = array('path' => 'devel/reinstall',
27f6d183 78 'title' => t('Reinstall modules'),
9b46a17d
ND
79 'callback' => 'devel_reinstall',
80 'access' => user_access('access devel information'),
9d450e49 81 'type' => MENU_CALLBACK,
9b46a17d 82 );
d31522e4 83 if (module_exists('menu')) {
9f6e6334 84 $items[] = array('path' => 'devel/menu/reset',
27f6d183 85 'title' => t('Reset menus'),
d31522e4 86 'callback' => 'drupal_get_form',
87 'callback arguments' => 'devel_menu_reset_form',
9d450e49 88 'access' => user_access('access devel information'),
89 'type' => MENU_CALLBACK,
9f6e6334
GK
90 );
91 }
80a68d0c 92 $items[] = array('path' => 'devel/php',
93 'title' => t('Execute PHP Code'),
94 'callback' => 'drupal_get_form',
95 'callback arguments' => array('devel_execute_form'),
96 'access' => user_access('execute php code'),
97 'type' => MENU_CALLBACK,
98 );
9d450e49 99 $items[] = array('path' => 'devel/variable',
7e7b9d52 100 'title' => t('Variable editor'),
4eab3ed9 101 'callback' => 'devel_variable_page',
7e7b9d52 102 'access' => user_access('access devel information'),
103 'type' => MENU_CALLBACK,
104 );
105 $items[] = array('path' => 'devel/variable/edit',
106 'title' => t('Variable editor'),
107 'callback' => 'drupal_get_form',
108 'callback arguments' => array('devel_variable_edit'),
109 'access' => user_access('access devel information'),
110 'type' => MENU_CALLBACK,
111 );
112 $items[] = array('path' => 'devel/variable/delete',
113 'title' => t('Variable editor'),
114 'callback' => 'drupal_get_form',
115 'callback arguments' => array('devel_variable_delete'),
9d450e49 116 'access' => user_access('access devel information'),
117 'type' => MENU_CALLBACK,
118 );
f3ef93f8 119 $items[] = array('path' => 'devel/session',
27f6d183 120 'title' => t('Session viewer'),
f3ef93f8 121 'callback' => 'devel_session',
122 'access' => user_access('access devel information'),
123 'type' => MENU_CALLBACK,
124 );
9d450e49 125 $items[] = array('path' => 'devel/switch',
27f6d183 126 'title' => t('Switch user'),
9d450e49 127 'callback' => 'devel_switch_user',
128 'access' => user_access('switch users'),
129 'type' => MENU_CALLBACK,
130 );
8770c9c1 131 $items[] = array(
132 'path' => 'admin/settings/devel',
27f6d183 133 'title' => t('Devel'),
d31522e4 134 'callback' => 'drupal_get_form',
135 'callback arguments' => array('devel_admin_settings'),
8770c9c1 136 'access' => user_access('administer site configuration'),
137 'type' => MENU_NORMAL_ITEM
138 );
1e7a3874 139 }
0356e334 140 else {
fed0ff20 141 // Include the class so calls to krumo() succeed.
142 has_krumo();
9d450e49 143 if (is_numeric(arg(1))) {
144 if (arg(0) == 'node') {
0ce0016a 145 $items[] = array('path' => 'node/'. arg(1) .'/devel/load',
b3d50dbb 146 'title' => t('Dev load'),
0274ec99 147 'callback' => 'devel_load_object',
148 'callback arguments' => array('node', arg(1)),
149 'access' => user_access('access devel information'),
150 'type' => MENU_LOCAL_TASK,
151 );
0ce0016a 152 $items[] = array('path' => 'node/'. arg(1) .'/devel/render',
b3d50dbb 153 'title' => t('Dev render'),
0274ec99 154 'callback' => 'devel_render_object',
9d450e49 155 'callback arguments' => array('node', arg(1)),
156 'access' => user_access('access devel information'),
157 'type' => MENU_LOCAL_TASK,
158 );
159 }
160 elseif (arg(0) == 'user') {
0274ec99 161 $items[] = array('path' => 'user/'. arg(1) .'/load',
726eb082 162 'title' => t('Dev load'),
0274ec99 163 'callback' => 'devel_load_object',
9d450e49 164 'callback arguments' => array('user', arg(1)),
165 'access' => user_access('access devel information'),
166 'type' => MENU_LOCAL_TASK,
167 );
168 }
0356e334 169 }
c6a11c27 170
f5e5cac3 171 /*
c6a11c27 172 * TODO: this is very naive. We don't preserve views arguments like other tabs.
f5e5cac3 173 * I tried, but it I started borrowing too much Views menu code. I think
174 * we need some refactoring in Views!
175 * I concluded that the result and 'render' operations yield no useful info
176 * so no tabs exist for those.
177 */
249b9993 178 if (module_exists('views_ui')) {
179 $urls = views_get_all_urls();
180 foreach ($urls as $view_name => $url) {
0ce0016a 181 $items[] = array('path' => "$url/devel/load",
f5e5cac3 182 'title' => t('Dev load'),
183 'callback' => 'devel_load_object',
184 'callback arguments' => array('view', $view_name),
185 'access' => user_access('administer views'),
186 'type' => MENU_LOCAL_TASK,
187 'weight' => 8,
188 );
0ce0016a 189 $items[] = array('path' => "$url/devel/queries",
f5e5cac3 190 'title' => t('Dev queries'),
191 'callback' => 'devel_views_object',
192 'callback arguments' => array('queries', $view_name),
193 'access' => user_access('administer views'),
194 'type' => MENU_LOCAL_TASK,
195 'weight' => 9,
196 );
0ce0016a 197 $items[] = array('path' => "$url/devel/items",
f5e5cac3 198 'title' => t('Dev items'),
199 'callback' => 'devel_views_object',
200 'callback arguments' => array('items', $view_name),
201 'access' => user_access('administer views'),
202 'type' => MENU_LOCAL_TASK,
203 'weight' => 10,
204 );
205 }
206 }
c6a11c27 207
653e855b 208 if (user_access('access devel information')) {
209 drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css');
210 drupal_add_js(drupal_get_path('module', 'devel') .'/devel.js');
2a8beb4f 211 $path = './'. drupal_get_path('module', 'devel') .'/FirePHPCore/lib/FirePHPCore/fb.php';
212 if (file_exists($path)) {
213 include_once $path;
214 }
653e855b 215 }
0356e334 216 }
1e7a3874
JC
217
218 return $items;
219}
220
56a4fa6f 221/**
222 * Calls the http://www.firephp.org/ fb() function if it is found.
223 *
224 * @return void
225 */
226function dfb() {
576c58d2 227 if (function_exists('fb') && user_access('access devel information')) {
56a4fa6f 228 $args = func_get_args();
229 call_user_func_array('fb', $args);
230 }
231}
232
f5e5cac3 233function devel_views_object($type, $view_name) {
234 $view = views_get_view($view_name);
235 $return = views_build_view($type, $view);
236 // dvr($return);
237 return devel_print_object($return);
238}
239
9d450e49 240/**
241 * Implementation of hook_init(). Avoids custom error handling for better
242 * behavior when stepping though in a debugger.
243 */
c6a11c27 244function devel_init() {
d5a886b3 245 if (strstr($_SERVER['PHP_SELF'], 'update.php') || strstr($_GET['q'], 'autocomplete') || $_GET['q'] == 'admin/content/node-settings/rebuild' || $_GET['q'] == 'upload/js' || substr($_GET['q'], 0, strlen('system/files')) == 'system/files') {
5536a6aa 246 // update.php relies on standard error handler. avoid breaking a few other pages.
585b64f0 247 }
ee1dbb25 248 else {
e0280c43 249 // no css or handler if we are serving a cached page
250 if (function_exists('drupal_set_content')) {
d2b1825c 251 if (user_access('access devel information')) {
252 drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css');
253 $handler = variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD);
254 switch ($handler) {
255 case DEVEL_ERROR_HANDLER_STANDARD:
256 // do nothing
257 break;
258 case DEVEL_ERROR_HANDLER_BACKTRACE:
259 set_error_handler('backtrace_error_handler');
fed0ff20 260 // we want to include the class early so that anyone may call krumo() as needed.
261 has_krumo();
d2b1825c 262 break;
263 case DEVEL_ERROR_HANDLER_NONE:
264 restore_error_handler();
265 break;
266 }
e0280c43 267 }
268 }
269 else {
270 // we need user_access() in the shutdown function
271 drupal_load('module', 'user');
272 }
c6a11c27 273
ee1dbb25 274 register_shutdown_function('devel_shutdown');
c6a11c27
RD
275
276
ee1dbb25 277 if (variable_get('dev_mem', 0) && function_exists('memory_get_usage')) {
278 global $memory_init;
279
280 $memory_init = memory_get_usage();
281 }
585b64f0 282 }
283}
284
fed0ff20 285// return boolean. no need for cache here.
286function has_krumo() {
287 // see README.txt or just download from http://krumo.sourceforge.net/
288 @include_once './'. drupal_get_path('module', 'devel'). '/krumo/class.krumo.php';
e992eae6 289 if (function_exists('krumo') && php_sapi_name() != 'cli') {
fed0ff20 290 return TRUE;
291 }
292 else {
293 return FALSE;
294 }
295}
296
585b64f0 297function backtrace_error_handler($errno, $message, $filename, $line) {
7c79a90e 298 // Don't respond to the error if it was suppressed with a '@'
299 if (error_reporting() == 0) return;
300
585b64f0 301 if ($errno & (E_ALL ^ E_NOTICE)) {
eb9967ed 302 // We can't use the PHP E_* constants here as not all versions of PHP have all
303 // the constants defined, so for consistency, we just use the numeric equivelant.
304 $types = array(
305 1 => 'error',
306 2 => 'warning',
307 4 => 'parse error',
308 8 => 'notice',
309 16 => 'core error',
310 32 => 'core warning',
311 64 => 'compile error',
312 128 => 'compile warning',
313 256 => 'user error',
314 512 => 'user warning',
315 1024 => 'user notice',
316 2048 => 'strict warning',
317 4096 => 'recoverable error',
318 8192 => 'deprecated',
319 16384 => 'user deprecated',
320 );
585b64f0 321 $entry = $types[$errno] .': '. $message .' in '. $filename .' on line '. $line .'.';
322
323 if (variable_get('error_level', 1) == 1) {
324 $backtrace = debug_backtrace();
fed0ff20 325 foreach ($backtrace as $call) {
326 $nicetrace[$call['function']] = $call;
585b64f0 327 }
fed0ff20 328 krumo($nicetrace);
585b64f0 329 }
330
bde62946 331 watchdog('php', t('%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line)), WATCHDOG_ERROR);
c380bd17 332 }
74b35ebe 333}
334
9d450e49 335/**
336 * Implementation of hook_perm().
337 */
338function devel_perm() {
339 return array('access devel information', 'execute php code', 'switch users');
340}
341
342/**
343 * Implementation of hook_block().
344 */
452690b1 345function devel_block($op = 'list', $delta = 0, $edit = array()) {
9d450e49 346 if ($op == 'list') {
347 $blocks[0]['info'] = t('Switch user');
348 $blocks[1]['info'] = t('Devel');
c6a11c27 349 $blocks[2]['info'] = t('Execute PHP');
9d450e49 350 return $blocks;
351 }
452690b1
DW
352 else if ($op == 'configure' && $delta == 0) {
353 $form['devel_switch_user_list_size'] = array(
354 '#type' => 'textfield',
355 '#title' => t('Number of users to display in the list'),
356 '#default_value' => variable_get('devel_switch_user_list_size', 10),
357 '#size' => '3',
358 '#maxlength' => '4',
359 );
360 return $form;
361 }
362 else if ($op == 'save' && $delta == 0) {
363 variable_set('devel_switch_user_list_size', $edit['devel_switch_user_list_size']);
364 }
9d450e49 365 else if ($op == 'view') {
9d450e49 366 switch ($delta) {
367 case 0:
523a946f 368 $block['subject'] = t('Switch user');
92b49820 369 $links = devel_switch_user_list();
523a946f 370 if (!empty($links)) {
0b84a192 371 $block['content'] = theme('item_list', $links);
d5d7d37e 372 $block['content'] .= drupal_get_form('devel_switch_user_form');
1e782d97 373 }
9d450e49 374 break;
375 case 1:
5c93376c 376 $links = array();
9d450e49 377 $block['subject'] = t('devel');
378 if (user_access('access devel information')) {
307c8920 379 $links[] = l('Devel settings', 'admin/settings/devel', array('title' => t('Adjust module settings for devel module')));
380 $links[] = l('Empty cache', 'devel/cache/clear', array('title' => t('Clear the database cache tables which store page, menu, node, and variable caches.')), drupal_get_destination());
80a68d0c 381 $links[] = l('Execute PHP Code', 'devel/php', array('title' => t('Execute some PHP code')));
4660fbc7 382 $links[] = l('Run cron', 'admin/logs/status/run-cron', array('title' => t('Execute functions scheduled for cron runs.')), drupal_get_destination());
d8d2265a 383 $links[] = l('Phpinfo()', 'admin/logs/status/php');
307c8920 384 $links[] = l('Function reference', 'devel/reference', array('title' => t('View a list of currently defined user functions with documentation links')));
385 $links[] = l('Reinstall modules', 'devel/reinstall', array('title' => t('Re-run hook_install() for a given module')));
386 $links[] = l('Reset menus', 'devel/menu/reset', array('title' => t('Resets all menu items to their default settings')));
7e7b9d52 387 $links[] = l('Variable editor', 'devel/variable', array('title' => t('Edit and delete site variables')));
307c8920 388 $links[] = l('Session viewer', 'devel/session', array('title' => t('List the contents of $_SESSION')));
9d450e49 389 }
79baf6c9
Z
390 if (function_exists('devel_node_access_perm') && user_access(DNA_ACCESS_VIEW)) {
391 // True only if devel_node_access enabled.
6d2d0af2 392 $links[] = l('Node access summary', 'devel/node_access/summary');
79baf6c9 393 }
5c93376c 394 if ($links) {
395 $block['content'] = theme('item_list', $links);
396 }
397 break;
398 case 2:
399 if (user_access('execute php code')) {
9a860b8c 400 $block['subject'] = t('Execute php');
d31522e4 401 $block['content'] = drupal_get_form('devel_execute_form');
5c93376c 402 }
403 break;
9d450e49 404 }
405
406 return $block;
407 }
408}
409
92b49820 410function devel_switch_user_list() {
411 $links = array();
412 if (user_access('switch users')) {
7fdc9d8a 413 $list_size = variable_get('devel_switch_user_list_size', 10);
92b49820 414 $dest = drupal_get_destination();
7fdc9d8a 415 // Try to find at least $list_size users that can switch.
92b49820 416 $roles = user_roles(1, 'switch users');
417 if (isset($roles[2])) {
418 // If authenticated users have this permission, just grab
7fdc9d8a 419 // the last $list_size users, since there won't be records in
92b49820 420 // {user_roles} and every user on the system can switch.
7fdc9d8a 421 $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);
92b49820 422 }
423 else {
424 $where = array('u.uid = 1');
425 if (count($roles)) {
426 $where[] = 'r.rid IN ('. implode(',', array_keys($roles)) .')';
427 }
428 $where_sql = implode(' OR ', $where);
7fdc9d8a 429 $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);
92b49820 430 }
431 while ($user = db_fetch_object($users)) {
432 $links[$user->uid] = l(theme('placeholder', $user->name), 'devel/switch/'. $user->name, array('title' => t('This user can switch back.')), $dest, NULL, FALSE, TRUE);
433 }
434 $num_links = count($links);
7fdc9d8a
DW
435 if ($num_links < $list_size) {
436 // If we don't have enough, add distinct uids until we hit $list_size.
437 $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);
438 while (($user = db_fetch_object($users)) && count($links) < $list_size) {
92b49820 439 $links[$user->uid] = l($user->name, 'devel/switch/'. $user->name, array('title' => t('Caution: this user will be unable to switch back.')), $dest);
440 }
441 }
442 }
443 return $links;
444}
445
d5d7d37e 446function devel_switch_user_form() {
447 $form['username'] = array(
448 '#type' => 'textfield',
449 '#description' => t('Enter username'),
0e5f4fd9
DW
450 '#autocomplete_path' => 'user/autocomplete',
451 '#maxlength' => USERNAME_MAX_LENGTH,
d5d7d37e 452 '#size' => 16,
d5d7d37e 453 );
454 $form['submit'] = array(
c6a11c27 455 '#type' => 'submit',
d5d7d37e 456 '#value' => t('Switch'),
457 );
458 return $form;
c6a11c27 459
d5d7d37e 460}
461
462function devel_switch_user_form_validate($form_id, $form_values) {
463 if (!$account = user_load(array('name' => $form_values['username']))) {
464 form_set_error('username', t('Username not found'));
c6a11c27 465 }
d5d7d37e 466}
467
468function devel_switch_user_form_submit($form_id, $form_values) {
469 return 'devel/switch/'. $form_values['username'];
470}
471
9d450e49 472/**
473 * Implementation of hook_form_alter().
474 */
cf6b114a 475function devel_form_alter($form_id, &$form, $key_in = NULL) {
df0e59a4 476 if (user_access('access devel information') && variable_get('devel_form_weights', 0)) {
9d450e49 477 $children = element_children($form);
478 if (empty($children)) {
79baf6c9
Z
479 if (isset($form['#type']) && !in_array($form['#type'], array('value', 'hidden'))) {
480 if (!isset($form['#title'])) {
481 $form['#title'] = '';
482 }
cf6b114a 483 $form['#title'] .= " (key=$key_in, weight=". (isset($form['#weight']) ? $form['#weight'] : 0) .')';
9d450e49 484 }
485 }
486 else {
487 foreach (element_children($form) as $key) {
488 // We need to add the weight to fieldsets.
489 if (element_children($form[$key])) { // Which are a container of others.
79baf6c9
Z
490 if (!isset($form[$key]['#title'])) {
491 $form[$key]['#title'] = '';
492 }
cf6b114a 493 $form[$key]['#title'] .= " (key=$key, weight=". (isset($form[$key]['#weight']) ? $form[$key]['#weight'] : 0) .')';
9d450e49 494 }
cf6b114a 495 devel_form_alter($form_id, $form[$key], $key);
9d450e49 496 }
497 }
498 }
defc4865 499}
500
54fecd4a 501function devel_exit($destination = NULL) {
3fd66239 502 global $user;
54fecd4a
JC
503 if (isset($destination)) {
504 // The page we are leaving is a drupal_goto(). Present a redirection page
505 // so that the developer can see the intermediate query log.
918674b6 506 if (isset($user) && function_exists('user_access') && user_access('access devel information') && variable_get('devel_redirect_page', 0)) {
f2021678 507 $output = t('<p>The user is being redirected to <a href="@destination">@destination</a>.</p>', array('@destination' => $destination));
96ea4c53 508 print theme('page', $output);
8416c64c 509
54fecd4a
JC
510 // Don't allow the automatic redirect to happen.
511 drupal_page_footer();
512 exit();
513 }
8416c64c
JC
514 else {
515 // Make sure not to print anything before the automatic redirect.
516 return;
517 }
54fecd4a 518 }
bd82ac93 519}
8416c64c 520
bd82ac93 521/**
522 * See devel_init() which registers this function as a shutdown function. Displays developer information in the footer.
523 */
524function devel_shutdown() {
3fd66239 525 global $queries, $memory_init, $user;
bd82ac93 526
527 $output = '';
528
4c613983 529 // Set $GLOBALS['devel_shutdown'] = FALSE in order to supress the
530 // devel footer for a page. Not necessary if your page outputs any
531 // of the Content-type http headers tested below (e.g. text/xml,
532 // text/javascript, etc). This is is advised where applicable.
e0280c43 533 if ($GLOBALS['devel_shutdown'] !== FALSE) {
534 // Try not to break non html pages.
535 if (function_exists('drupal_get_headers')) {
4c613983 536 $headers = drupal_get_headers();
537 $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'x-comma-separated-values');
538 foreach ($formats as $format) {
539 if (strstr($headers, $format)) {
540 return;
541 }
542 }
9edc54df 543 }
c6a11c27 544
7cd9cd53 545 // don't append to CLI scripts
546 if (empty($_SERVER['REQUEST_METHOD'])) {
547 return;
548 }
c6a11c27 549
3fd66239 550 if (isset($user) && user_access('access devel information')) {
c6a11c27 551
3c8cf672 552 list($counts, $query_summary) = devel_query_summary();
553 // Query log off, timer on.
554 if (!variable_get('devel_query_display', 0) && variable_get('dev_timer', 0)) {
555 $output = '<div class="dev-timer">'. devel_timer() .' '. $query_summary. '</div>';
556 }
8416c64c 557
3c8cf672 558 // Query log on.
559 $sum = 0;
560 if (variable_get('devel_query_display', FALSE)) {
561 $output .= '<div class="dev-query">';
562 $output .= $query_summary;
563 if (function_exists('theme_table')) {
564 $txt .= t(' 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)));
565 if (variable_get('dev_timer', 0)) {
566 $txt .= devel_timer();
567 }
568 $output .= $txt. devel_query_table($queries, $counts);
9d450e49 569 }
3c8cf672 570 else {
571 $output .= $txt;
572 ob_start();
573 dprint_r($queries);
574 $output .= ob_get_clean();
575 }
576 $output .= '</div>';
302fd7ef 577 }
c6a11c27 578
3c8cf672 579 if (variable_get('dev_mem', FALSE) && function_exists('memory_get_usage')) {
580 $memory_shutdown = memory_get_usage();
581 $list = array();
582 foreach (array('devel_init()' => $memory_init, 'devel_shutdown()' => $memory_shutdown) as $type => $value) {
583 $list[] = t('Memory used at %type: %value MB', array('%type' => $type, '%value' => round($value / 1024 / 1024, 2)));
584 }
585 $output .= '<div class="dev-memory-usage"><h3>'. 'Memory usage:' .'</h3>'. theme('item_list', $list) .'</div>';
fc909860 586 }
c6a11c27
RD
587 // TODO: gzip this text if we are sending a gzip page. see drupal_page_header().
588
589
3c8cf672 590 if ($output) {
591 print $output;
592 }
e6e309b8 593 }
c6a11c27 594 }
7ac60dac 595
bd82ac93 596 devel_store_queries();
597}
598
599function devel_store_queries() {
7ac60dac 600 if (variable_get('devel_store_queries', 0) && rand(1, variable_get('devel_store_random', 1)) == 1) {
bd82ac93 601 global $active_db, $queries;
7ac60dac
GK
602 $qids = array();
603 $values = array();
604 $fields = array();
20079c18
GK
605 // We need this for the devel_queries insert below.
606 setlocale(LC_NUMERIC, 'C');
607 foreach ($queries as $value) {
608 list($function, $query) = explode("\n", $value[0]);
609 $query = preg_replace(array("/'.*'/s", "/\d.*\.\d.*/", "/\d.*/"), array("S", "F", "D"), $query);
610 $hash = md5($function . $query);
7ac60dac
GK
611 if (!isset($qids[$hash])) {
612 $qids[$hash] = db_result(devel_db_query("SELECT qid FROM {devel_queries} WHERE hash = '%s'", $hash));
613 if (!$qids[$hash]) {
20079c18 614 devel_db_query("INSERT INTO {devel_queries} (query, function, hash) VALUES ('%s', '%s', '%s')", $query, $function, $hash);
7ac60dac
GK
615 $qids[$hash] = mysql_insert_id();
616 }
617 }
20079c18 618 $fields[] = "(%d, '%f')";
7ac60dac 619 $values[] = $qids[$hash];
20079c18 620 $values[] = $value[1];
7ac60dac
GK
621 }
622 if (count($fields)) {
623 devel_db_query('INSERT INTO {devel_times} (qid, time) VALUES '. implode(',', $fields), $values);
624 }
625 }
626}
627
8143d2da 628function devel_query_summary() {
dafb42f2 629 global $queries;
dc1370a2 630 if (variable_get('dev_query', FALSE) && is_array($queries)) {
87a3a7d7 631 foreach ($queries as $query) {
632 $text[] = $query[0];
633 $sum += $query[1];
634 }
635 $counts = array_count_values($text);
e0280c43 636 return array($counts, t_safe('Executed %queries queries in %time milliseconds.', array('%queries' => count($queries), '%time' => round($sum * 1000, 2))));
8143d2da 637 }
8143d2da 638}
639
e0280c43 640function t_safe($string) {
641 $args = func_get_args();
642 return function_exists('t') ? call_user_func_array('t', $args) : call_user_func_array('strtr', $args);
643}
644
307c8920 645/**
c6a11c27 646 * Returns a list of all currently defined user functions in the current
307c8920 647 * request lifecycle, with links their documentation.
648 */
383138d6 649function devel_function_reference() {
307c8920 650 $functions = get_defined_functions();
651 $ufunctions = $functions['user'];
652 sort($ufunctions);
653 foreach($ufunctions as $function) {
d5e1689b 654 if (class_exists('ReflectionFunction')) {
655 $func = new ReflectionFunction($function);
656 $isNotCore = stristr($func->getFileName(), realpath($_SERVER['DOCUMENT_ROOT'] . '/sites')) ? true: false;
657 }
658 if (!$isNotCore) {
659 $links[] = l($function, "http://api.drupal.org/api/function/$function/" . DEVEL_CURRENT_DRUPAL_VERSION);
660 }
307c8920 661 }
662 return theme('item_list', $links);
383138d6 663}
c6a11c27 664
7ac60dac
GK
665function devel_db_query($query) {
666 global $active_db;
667 $args = func_get_args();
668 array_shift($args);
669 $query = db_prefix_tables($query);
670 if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
671 $args = $args[0];
672 }
673 _db_query_callback($args, TRUE);
674 $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
675 return mysql_query($query, $active_db);
22c2656f 676}
677
f327a6eb 678// See http://drupal.org/node/126098
679function devel_is_compatible_optimizer() {
680 ob_start();
681 phpinfo();
682 $info = ob_get_contents();
683 ob_end_clean();
684
685 // Match the Zend Optimezer version in the phpinfo information
686 $found = preg_match('/Zend&nbsp;Optimizer&nbsp;v([0-9])\.([0-9])\.([0-9])/', $info, $matches);
687
688 if ($matches) {
689 $major = $matches[1];
690 $minor = $matches[2];
691 $build = $matches[3];
692
693 if ($major >= 3) {
694 if ($minor >= 3) {
695 return TRUE;
696 }
697 elseif ($minor == 2 && $build >= 8) {
698 return TRUE;
699 }
700 else {
701 return FALSE;
702 }
703 }
704 else {
705 return FALSE;
706 }
707 }
708 else {
709 return TRUE;
710 }
711}
712
8770c9c1 713function devel_admin_settings() {
cd1b6ba3 714 $form['queries'] = array('#type' => 'fieldset', '#title' => t('Query log'));
b90ec59b 715
716 $description = t("Collect query info. If disabled, no query log functionality will work.");
f327a6eb 717 if (!devel_is_compatible_optimizer()) {
718 $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;
b90ec59b 719 }
cd1b6ba3 720 $form['queries']['dev_query'] = array('#type' => 'checkbox',
7ac60dac
GK
721 '#title' => t('Collect query info'),
722 '#default_value' => variable_get('dev_query', 0),
b90ec59b 723 '#description' =>$description,
f327a6eb 724 '#disabled' => !devel_is_compatible_optimizer() ? TRUE : FALSE,
b90ec59b 725 );
726
cd1b6ba3 727 $form['queries']['devel_query_display'] = array('#type' => 'checkbox',
9d450e49 728 '#title' => t('Display query log'),
7ac60dac 729 '#default_value' => variable_get('devel_query_display', 0),
cd1b6ba3 730 '#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.'));
731 $form['queries']['devel_query_sort'] = array('#type' => 'radios',
dcd35dd9 732 '#title' => t('Sort query log'),
733 '#default_value' => variable_get('devel_query_sort', DEVEL_QUERY_SORT_BY_SOURCE),
734 '#options' => array(t('by source'), t('by duration')),
735 '#description' => t('The query table can be sorted in the order that the queries were executed or by descending duration.'),
736 );
cd1b6ba3 737 $form['queries']['devel_execution'] = array('#type' => 'textfield',
738 '#title' => t('Slow query highlighting'),
9d450e49 739 '#default_value' => variable_get('devel_execution', 5),
740 '#size' => 4,
741 '#maxlength' => 4,
7d7d6c07 742 '#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.'),
743 );
c6a11c27
RD
744 $form['queries']['devel_store_queries'] = array('#type' => 'checkbox',
745 '#title' => t('Store executed queries'),
746 '#default_value' => variable_get('devel_store_queries', 0),
cd1b6ba3 747 '#description' => t('Store statistics about executed queries. See the devel_x tables. This feature is currently only available for the MySQL database backend.'));
c6a11c27
RD
748 $form['queries']['devel_store_random'] = array('#type' => 'textfield',
749 '#title' => t('Sampling interval'),
750 '#default_value' => variable_get('devel_store_random', 1),
751 '#size' => 4,
cd1b6ba3 752 '#description' => t('If storing query statistics, only store every nth page view. 1 means every page view, 2 every second, and so on.'));
c6a11c27 753
cd1b6ba3 754 $form['dev_timer'] = array('#type' => 'checkbox',
755 '#title' => t('Display page timer'),
756 '#default_value' => variable_get('dev_timer', 0),
757 '#description' => t('Display page execution time in the query log box.'),
758 );
7d7d6c07 759 $form['dev_mem'] = array('#type' => 'checkbox',
760 '#title' => t('Display memory usage'),
761 '#default_value' => variable_get('dev_mem', 0),
7d7d6c07 762 '#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. PHP must have been compiled with the <em>--enable-memory-limit</em> configuration option for this feature to work.'),
9d450e49 763 );
7d7d6c07 764 $form['devel_redirect_page'] = array('#type' => 'checkbox',
9d450e49 765 '#title' => t('Display redirection page'),
766 '#default_value' => variable_get('devel_redirect_page', 0),
9d450e49 767 '#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.'),
768 );
7d7d6c07 769 $form['devel_form_weights'] = array('#type' => 'checkbox',
cf6b114a 770 '#title' => t('Display form element keys and weights'),
9d450e49 771 '#default_value' => variable_get('devel_form_weights', 0),
cf6b114a 772 '#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.'),
9d450e49 773 );
585b64f0 774 $form['devel_error_handler'] = array('#type' => 'radios',
775 '#title' => t('Error handler'),
776 '#default_value' => variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD),
777 '#options' => array(DEVEL_ERROR_HANDLER_NONE => t('None'), DEVEL_ERROR_HANDLER_STANDARD => t('Standard drupal'), DEVEL_ERROR_HANDLER_BACKTRACE => t('Backtrace')),
fefa2039 778 '#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'))),
585b64f0 779 );
9d450e49 780
61d85437
ND
781 // Save any old SMTP library
782 if (variable_get('smtp_library', '') != '' && variable_get('smtp_library', '') != drupal_get_filename('module', 'devel')) {
783 variable_set('devel_old_smtp_library', variable_get('smtp_library', ''));
784 }
785 $smtp_options = array(
786 '' => t('Default'),
787 drupal_get_filename('module', 'devel') => t('Log only'),
788 );
789 if (variable_get('devel_old_smtp_library', '') != '') {
151dbf3b 790 $smtp_options[variable_get('devel_old_smtp_library', '')] = t('Other (!library)', array('!library' => variable_get('devel_old_smtp_library', '')));
61d85437
ND
791 }
792 $form['smtp_library'] = array(
793 '#type' => 'radios',
794 '#title' => t('SMTP library'),
795 '#options' => $smtp_options,
796 '#default_value' => variable_get('smtp_library', ''),
797 );
798
d31522e4 799 return system_settings_form($form);
9d450e49 800}
801
802/**
1e7a3874
JC
803 * Menu callback; clears all caches, then redirects to the previous page.
804 */
805function devel_cache_clear() {
b56530e9 806 // clear preprocessor cache
807 drupal_clear_css_cache();
c6a11c27 808
b56530e9 809 // clear core tables
b97f20ae 810 $core = array('cache', 'cache_filter', 'cache_menu', 'cache_page');
811 $alltables = array_merge($core, module_invoke_all('devel_caches'));
27f6d183 812 foreach ($alltables as $table) {
6e56c8bb 813 cache_clear_all('*', $table, TRUE);
27f6d183 814 }
9a860b8c 815 drupal_set_message('Cache cleared.');
27f6d183 816 drupal_goto();
1e7a3874
JC
817}
818
1e7a3874 819/**
d31522e4 820 * Generates the execute block form.
1e7a3874 821 */
d31522e4 822function devel_execute_form() {
595dd5c7
Z
823 $form['code'] = array(
824 '#type' => 'textarea',
9d450e49 825 '#title' => t('PHP code to execute'),
595dd5c7 826 '#description' => t('Enter some code. Do not use <code>&lt;?php ?&gt;</code> tags.')
9d450e49 827 );
828 $form['op'] = array('#type' => 'submit', '#value' => t('Execute'));
d31522e4 829 $form['#redirect'] = FALSE;
f1bdd3dc 830 $form['#skip_duplicate_check'] = TRUE;
d31522e4 831 return $form;
e0379979 832}
833
1e7a3874 834/**
d31522e4 835 * Process PHP execute form submissions.
1e7a3874 836 */
d31522e4 837function devel_execute_form_submit($form_id, $form) {
838 ob_start();
839 print eval($form['code']);
840 dsm(ob_get_clean());
22c2656f 841}
842
1e7a3874 843/**
9d450e49 844 * Menu callback; clear the database, resetting the menu to factory defaults.
1e7a3874 845 */
9d450e49 846function devel_menu_reset_form() {
c6a11c27
RD
847 return confirm_form(array(),
848 t('Are you sure you want to reset all menu items to their default settings?'),
849 'admin/build/menu',
850 t('Any custom additions or changes to the menu will be lost.'),
d31522e4 851 t('Reset all'),
852 t('Cancel')
853 );
08aa077e 854}
855
3f2b1295 856/**
9d450e49 857 * Process menu reset form submission.
3f2b1295 858 */
9d450e49 859function devel_menu_reset_form_submit() {
860 db_query('DELETE FROM {menu}');
baa50abf 861 $mid = module_invoke('menu', 'edit_item_save', array('title' => t('Primary links'), 'pid' => 0, 'type' => MENU_CUSTOM_MENU));
9d450e49 862 variable_set('menu_primary_menu', $mid);
863 variable_set('menu_secondary_menu', $mid);
4ea887e0 864
9d450e49 865 drupal_set_message(t('The menu items have been reset to their default settings.'));
4ea887e0 866
0184d410 867 return 'admin/build/menu';
9b46a17d
ND
868}
869
9d450e49 870/**
d31522e4 871 * Implementation of hook_forms().
872 */
873function devel_forms() {
874 $forms = array();
875 if (user_access('access devel information')) {
876 // registers each devel_reinstall_$module form_id
877 $modules = module_list();
878 foreach ($modules as $module) {
879 $forms['devel_reinstall_'. $module]['callback'] = 'devel_reinstall_form';
880 }
881 }
882 return $forms;
883}
884
885function devel_reinstall_form($module) {
886 $form = array(
887 '#base' => 'devel_reinstall',
888 'submit' => array(
889 '#type' => 'submit',
890 '#value' => t('Reinstall @name module', array('@name' => $module))
891 ),
892 );
893 return $form;
894}
895
896/**
9d450e49 897 * Menu callback; Display a list of installed modules with the option to reinstall them via hook_install.
898 */
9b46a17d
ND
899function devel_reinstall() {
900 $output = '';
580dc2a9
ND
901 $modules = module_list();
902 sort($modules);
903 foreach ($modules as $module) {
d31522e4 904 $output .= drupal_get_form('devel_reinstall_'. $module, $module);
9b46a17d 905 }
b3cff101 906 drupal_set_message(t('Warning - will delete your module tables and variables.'), 'error');
9b46a17d
ND
907 return $output;
908}
909
9d450e49 910/**
911 * Process reinstall menu form submissions.
912 */
9b46a17d 913function devel_reinstall_submit($form_id, $form_values) {
d31522e4 914 include_once './includes/install.inc';
228b5925 915 $module = str_replace('devel_reinstall_', '', $form_id);
b3cff101 916 module_load_install($module);
9b46a17d
ND
917 $versions = drupal_get_schema_versions($module);
918 drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED);
b3cff101 919 module_invoke($module, 'uninstall');
9b46a17d
ND
920 module_invoke($module, 'install');
921 drupal_set_message(t('Reinstalled the %name module.', array('%name' => $module)));
922}
923
3f2b1295 924/**
9d450e49 925 * Menu callback; display all variables.
3f2b1295 926 */
4eab3ed9 927function devel_variable_page() {
928 // we print our own page so as to avoid blocks
929 $output = drupal_get_form('devel_variable');
930 print theme('page', $output, FALSE);
931}
932
9d450e49 933function devel_variable() {
7e7b9d52 934 $header = array(
935 array(''),
936 array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'),
937 array('data' => t('Value'), 'field' => 'value'),
938 array('data' => t('Length'), 'field' => 'length'),
939 array('data' => t('Operations')),
940 );
941 // TODO: we could get variables out of $conf but that would include hard coded ones too. ideally i would highlight overrridden/hard coded variables
2d892986 942 switch ($GLOBALS['db_type']) {
943 case 'mssql':
944 $sql = "SELECT *, COL_LENGTH('{variable}', 'value') AS length FROM {variable}";
945 break;
946 default:
947 $sql = "SELECT *, LENGTH(value) AS length FROM {variable}";
948 break;
949 }
7e7b9d52 950 $result = db_query($sql. tablesort_sql($header));
951 while ($row = db_fetch_object($result)) {
952 $variables[$row->name] = '';
f58d55de 953 $form['name'][$row->name] = array('#value' => check_plain($row->name));
fed0ff20 954 if (has_krumo()) {
955 $value = krumo_ob(variable_get($row->name, NULL));
7e7b9d52 956 }
957 else {
fed0ff20 958 if (drupal_strlen($row->value) > 70) {
959 $value = check_plain(drupal_substr($row->value, 0, 65)) .'...';
960 }
961 else {
962 $value = check_plain($row->value);
963 }
7e7b9d52 964 }
fed0ff20 965 $form[$row->name]['value'] = array('#value' => $value);
7e7b9d52 966 $form[$row->name]['length'] = array('#value' => $row->length);
967 $form[$row->name]['edit'] = array('#value' => l(t('edit'), "devel/variable/edit/$row->name"));
968 }
969 $form['variables'] = array('#type' => 'checkboxes', '#options' => $variables);
970 $form['submit'] = array(
c6a11c27 971 '#type' => 'submit',
7e7b9d52 972 '#value' => t('Delete'),
973 );
974 return $form;
975}
976
977function theme_devel_variable($form) {
978 $children = element_children($form['name']);
c6a11c27 979 foreach ($children as $key) {
7e7b9d52 980 $rows[] = array(
981 drupal_render($form['variables'][$key]),
982 drupal_render($form['name'][$key]),
983 drupal_render($form[$key]['value']),
984 drupal_render($form[$key]['length']),
985 drupal_render($form[$key]['edit']),
986 );
987 }
988 $header = array(
a0c40e8c 989 theme('table_select_header_cell'),
7e7b9d52 990 array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'),
991 array('data' => t('Value'), 'field' => 'value'),
992 array('data' => t('Length'), 'field' => 'length'),
993 array('data' => t('Operations'), 'colspan' => 2),
994 );
995 $output = theme('table', $header, $rows);
996 $output .= drupal_render($form);
997
998 return $output;
999}
1000
1001function devel_variable_submit($form_id, $form_values) {
1002 $deletes = array_filter($form_values['variables']);
1003 array_walk($deletes, 'variable_del');
ed57572f 1004 drupal_set_message(format_plural(count($deletes), 'one variable deleted', '@count variables deleted'));
7e7b9d52 1005}
1006
1007function devel_variable_edit($name) {
1008 $value = variable_get($name, 'not found');
1009 $form['name'] = array(
c6a11c27 1010 '#type' => 'value',
7e7b9d52 1011 '#value' => $name
1012 );
1013 $form['value'] = array(
1014 '#type' => 'item',
1015 '#title' => t('Old value'), // maybe check_plain() done by fapi
478b3ad7 1016 '#value' => dpr($value, TRUE),
7e7b9d52 1017 );
6380c48a 1018 if (is_string($value) || is_numeric($value)) {
478b3ad7 1019 $form['new'] = array(
c6a11c27 1020 '#type' => 'textarea',
478b3ad7 1021 '#title' => t('New value'),
1022 '#default_value' => $value
1023 );
1024 $form['submit'] = array(
c6a11c27 1025 '#type' => 'submit',
478b3ad7 1026 '#value' => t('Submit'),
1027 );
1028 }
1029 else {
1030 $form['new'] = array(
c6a11c27 1031 '#type' => 'item',
478b3ad7 1032 '#title' => t('New value'),
1033 '#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.drupal.org/api/HEAD/function/variable_set'))
1034 );
1035 }
979da168 1036 drupal_set_title(check_plain($name));
7e7b9d52 1037 return $form;
1038}
1039
1040function devel_variable_edit_submit($form_id, $form_values) {
1041 variable_set($form_values['name'], $form_values['new']);
1042 drupal_set_message(t('Saved new value for %name', array('%name' => $form_values['name'])));
1043 return 'devel/variable';
3f2b1295 1044}
9f4ee613
MI
1045
1046/**
f3ef93f8 1047 * Menu callback: display the session.
1048 */
1049function devel_session() {
c6a11c27 1050 global $user;
fed0ff20 1051 $output = kprint_r($_SESSION, TRUE);
1052 $headers = array(t('Session name'), t('Session ID'));
15d807dc 1053 $output .= theme('table', $headers, array(array(session_name(), session_id())));
c6a11c27 1054 return $output;
f3ef93f8 1055}
1056
1057/**
9d450e49 1058 * Switch from original user to another user and back.
1059 *
1060 * Note: taken from mailhandler.module.
1061 *
1062 * Note: You first need to run devel_switch_user without
1063 * argument to store the current user. Call devel_switch_user
1064 * without argument to set the user back to the original user.
1065 *
d5d7d37e 1066 * @param $name The username to switch to.
9d450e49 1067 *
9f4ee613 1068 */
d5d7d37e 1069function devel_switch_user($name = NULL) {
9d450e49 1070 global $user;
1071 static $orig_user = array();
1072
d5d7d37e 1073 if (isset($name)) {
1074 $user = user_load(array('name' => $name));
9d450e49 1075 }
1076 // Retrieve the initial user. Can be called multiple times.
1077 else if (count($orig_user)) {
1078 $user = array_shift($orig_user);
1079 array_unshift($orig_user, $user);
9f4ee613 1080 }
9d450e49 1081 // Store the initial user.
1082 else {
1083 $orig_user[] = $user;
1084 }
1085 drupal_goto();
9f4ee613
MI
1086}
1087
0356e334 1088/**
0274ec99 1089 * Menu callback; prints the loaded structure of the current node/user.
0356e334 1090 */
0274ec99 1091function devel_load_object($type, $id) {
0356e334
JC
1092 $output = '';
1093
1094 switch ($type) {
1095 case 'node':
1096 $object = node_load($id);
1097 drupal_set_title(check_plain($object->title));
1098 break;
1099
1100 case 'user':
1101 $object = user_load(array('uid' => $id));
1102 drupal_set_title(check_plain($object->name));
1103 break;
f5e5cac3 1104 case 'view':
1105 $object = views_get_view($id);
1106 drupal_set_title(check_plain($object->name));
0356e334
JC
1107 }
1108
0274ec99 1109 return devel_print_object($object);
1110}
1111
1112/**
1113 * Menu callback; prints the renderstructure of the current node.
1114 */
1115function devel_render_object($type, $id) {
1116 $output = '';
1117
1118 switch ($type) {
1119 case 'node':
1120 $object = node_build_content(node_load($id), FALSE, FALSE);
1121 drupal_set_title(check_plain($object->title));
1122 break;
1123
1124 case 'user':
1125 // not yet using fapi render model
1126 break;
1127 }
1128
1129 return devel_print_object($object);
1130}
1131
fed0ff20 1132/**
1133 * Print an object or array using either Krumo (if installed) or devel_print_object()
1134 *
1135 * @param $object
1136 * array or object to print
1137 * @param $prefix
1138 * prefixing for output items
1139 */
1140function kdevel_print_object($object, $prefix = NULL) {
1141 return has_krumo() ? krumo_ob($object) : devel_print_object($object, $prefix);
1142}
1143
1144// Save krumo htlm using output buffering.
1145function krumo_ob($object) {
1146 ob_start();
1147 krumo($object);
1148 $output = ob_get_contents();
1149 ob_end_clean();
1150 return $output;
1151}
1152
0274ec99 1153function devel_print_object($object) {
455b7201 1154 if (is_array($object) || is_object($object)) {
f5e5cac3 1155 foreach ($object as $field => $value) {
1156 if (is_null($value)) {
1157 $printed_value = 'NULL';
1158 }
1159 else if (is_array($value) || is_object($value)) {
1160 ob_start();
1161 print_r($value);
1162 $printed_value = ob_get_clean();
1163 $printed_value = '<pre>'. check_plain($printed_value) .'</pre>';
1164 }
1165 else {
1166 $printed_value = check_plain($value);
1167 }
0356e334 1168
f5e5cac3 1169 $output .= theme('box', $field, $printed_value);
1170 }
0356e334 1171 }
f5e5cac3 1172 else {
1173 $output .= theme('box', 'Value', check_plain($object));
1174 }
c6a11c27 1175
0356e334
JC
1176 return $output;
1177}
9f6e6334
GK
1178
1179/**
9d450e49 1180 * Adds a table at the bottom of the page cataloguing data on all the database queries that were made to
1181 * generate the page.
9f6e6334 1182 */
9d450e49 1183function devel_query_table($queries, $counts) {
1184 $header = array ('ms', '#', 'where', 'query');
1185 $i = 0;
1186 foreach ($queries as $query) {
9d450e49 1187 $ar = explode("\n", $query[0]);
9d1caf04 1188 $function=array_shift($ar);
4215d16e 1189 $count = isset($counts[$query[0]]) ? $counts[$query[0]] : 0;
9d1caf04 1190 $query[0]=join(' ',$ar);
9d450e49 1191
1192 $diff = round($query[1] * 1000, 2);
9d450e49 1193 if ($diff > variable_get('devel_execution', 5)) {
1194 $cell[$i][] = array ('data' => $diff, 'class' => 'marker');
1195 }
1196 else {
1197 $cell[$i][] = $diff;
1198 }
1199 if ($count > 1) {
1200 $cell[$i][] = array ('data' => $count, 'class' => 'marker');
1201 }
1202 else {
1203 $cell[$i][] = $count;
1204 }
1205 $cell[$i][] = l($function, "http://api.drupal.org/api/HEAD/function/$function");
1206 $cell[$i][] = check_plain($query[0]);
1207 $i++;
1208 unset($diff, $count);
1209 }
dcd35dd9 1210 if (variable_get('devel_query_sort', DEVEL_QUERY_SORT_BY_SOURCE)) {
1211 usort($cell, '_devel_table_sort');
1212 }
9d450e49 1213 return theme('table', $header, $cell);
9f6e6334
GK
1214}
1215
dcd35dd9 1216function _devel_table_sort($a, $b) {
1217 $a = is_array($a[0]) ? $a[0]['data'] : $a[0];
1218 $b = is_array($b[0]) ? $b[0]['data'] : $b[0];
1219 if ($a < $b) { return 1; }
1220 if ($a > $b) { return -1; }
c6a11c27 1221 return 0;
dcd35dd9 1222}
1223
9f6e6334 1224/**
9d450e49 1225 * Displays page execution time at the bottom of the page.
9f6e6334 1226 */
9d450e49 1227function devel_timer() {
1228 $time = timer_read('page');
8143d2da 1229 return t(' Page execution time was %time ms.', array('%time' => $time));
9d450e49 1230}
1231
1232/**
8e015c96 1233 * Prints the arguments for passed into the current function
9d450e49 1234 */
8e015c96 1235function dargs($always = TRUE) {
1236 static $printed;
1237 if ($always || !$printed) {
1238 $bt = debug_backtrace();
1239 dsm($bt[1]['args']);
1240 $printed = TRUE;
1241 }
c6a11c27 1242}
8e015c96 1243
99011ac8 1244// An alias for drupal_debug().
1245function dd($data, $label = NULL) {
1246 return drupal_debug($data, $label);
1247}
1248
1249// Log any variable to a drupal_debug.log in the site's temp directory.
1250// See http://drupal.org/node/314112
1251function drupal_debug($data, $label = NULL) {
1252 ob_start();
1253 print_r($data);
1254 $string = ob_get_clean();
1255 if ($label) {
1256 $out = $label. ': '. $string;
1257 }
1258 else {
1259 $out = $string;
1260 }
1261 $out .= "\n";
1262
1263 // The temp directory does vary across multiple simpletest instances.
1264 $file = file_directory_temp(). '/drupal_debug.txt';
1265 if (file_put_contents($file, $out, FILE_APPEND) === FALSE) {
1266 drupal_set_message(t('The file could not be written.'), 'error');
1267 return FALSE;
1268 }
1269}
1270
8e015c96 1271/**
1272 * Print a variable to the 'message' area of the page. Uses drupal_set_message()
1273 */
24622db0 1274function dpm($input, $name = NULL) {
9d450e49 1275 if (user_access('access devel information')) {
1622b9bc 1276 $export = kprint_r($input, TRUE, $name);
8e015c96 1277 drupal_set_message($export);
9d450e49 1278 }
1279}
1280
1281/**
ea8949b6 1282 * Var_dump() a variable to the 'message' area of the page. Uses drupal_set_message()
1283 */
24622db0 1284function dvm($input, $name = NULL) {
ea8949b6 1285 if (user_access('access devel information')) {
f68f03bd 1286 $export = dprint_r($input, TRUE, $name, 'var_dump', FALSE);
ea8949b6 1287 drupal_set_message($export);
1288 }
1289}
24622db0 1290
1291// legacy function that was poorly named. use dpm() instead, since the 'p' maps to 'print_r'
1292function dsm($input, $name = NULL) {
7eb168ac 1293 dpm($input, $name);
24622db0 1294}
1295
ea8949b6 1296/**
8e015c96 1297 * An alias for dprint_r(). Saves carpal tunnel syndrome.
1298 */
ea8949b6 1299function dpr($input, $return = FALSE, $name = NULL) {
1300 return dprint_r($input, $return, $name);
8e015c96 1301}
1302
1303/**
fed0ff20 1304 * An alias for kprint_r(). Saves carpal tunnel syndrome.
1305 */
1306function kpr($input, $return = FALSE, $name = NULL) {
1307 return kprint_r($input, $return, $name);
1308}
1309
1310/**
ea8949b6 1311 * Like dpr, but uses var_dump() instead
1312 */
1313function dvr($input, $return = FALSE, $name = NULL) {
f68f03bd 1314 return dprint_r($input, $return, $name, 'var_dump', FALSE);
ea8949b6 1315}
1316
fed0ff20 1317function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') {
1318 if (has_krumo()) {
1319 if (user_access('access devel information')) {
5ce5d923 1320 return $return ? (isset($name) ? $name .' => ' : '') . krumo_ob($input) : krumo($input);
fed0ff20 1321 }
1322 }
1323 else {
26d22c49 1324 return dprint_r($input, $return, $name, $function);
fed0ff20 1325 }
1326}
1327
ea8949b6 1328/**
8e015c96 1329 * Pretty-print a variable to the browser.
9d450e49 1330 * Displays only for users with proper permissions. If
8e015c96 1331 * you want a string returned instead of a print, use the 2nd param.
9d450e49 1332 */
f68f03bd 1333function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check = TRUE) {
9d450e49 1334 if (user_access('access devel information')) {
24622db0 1335 if ($name) {
1336 $name .= ' => ';
1337 }
3d5b4cb9 1338 ob_start();
ea8949b6 1339 $function($input);
f68f03bd 1340 $output = ob_get_clean();
1341 if ($check) {
1342 $output = check_plain($output);
1343 }
24622db0 1344 if (count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) {
fed0ff20 1345 if (has_krumo()) {
1346 $printed_value = krumo_ob($input);
1347 }
1348 else {
1349 // don't use fapi here because sometimes fapi will not be loaded
1350 $printed_value = "<textarea rows=30 style=\"width: 100%;\">\n". $name . $output . '</textarea>';
1351 }
24622db0 1352 }
1353 else {
6a02b28c 1354 $printed_value = '<pre>' . $name . $output . '</pre>';
24622db0 1355 }
9d450e49 1356 if ($return) {
3d5b4cb9 1357 return $printed_value;
c6a11c27 1358 }
ea8949b6 1359 else {
3d5b4cb9 1360 print $printed_value;
9d450e49 1361 }
1362 }
1363}
1364
1365/**
fed0ff20 1366 * Print the function call stack.
9d450e49 1367 */
1368function ddebug_backtrace() {
1369 if (user_access('access devel information')) {
fed0ff20 1370 $trace = debug_backtrace();
1371 array_shift($trace);
1372 foreach ($trace as $key => $value) {
1373 $rich_trace[$value['function']] = $value;
1374 }
1375 if (has_krumo()) {
1376 print krumo($rich_trace);
9d450e49 1377 }
1378 else {
fed0ff20 1379 dprint_r($rich_trace);
9d450e49 1380 }
1381 }
9f6e6334 1382}
61d85437 1383
66055388
RD
1384/**
1385 * Debugging version of db_query().
1386 *
1387 * Echoes the query to the browser.
1388 */
1389function db_queryd($query) {
1390 $args = func_get_args();
1391 array_shift($args);
1392 $query = db_prefix_tables($query);
1393 if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
1394 $args = $args[0];
1395 }
1396 _db_query_callback($args, TRUE);
1397 $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
1398 return _db_query($query, 1);
1399}
1400
61d85437
ND
1401// Only define our mail wrapper if the devel module is the current mail
1402// wrapper.
1403if (variable_get('smtp_library', '') == drupal_get_filename('module', 'devel')) {
1404 /**
1405 * Log the mails sent out instead of mailing.
1406 */
6032b7f5 1407 function drupal_mail_wrapper($mailkey, $to, $subject, $body, $from, $headers) {
1408 $mimeheaders = array();
1409 foreach ($headers as $name => $value) {
1410 // the check_plain nicely encodes <> chars for web presentation
1411 $mimeheaders[] = check_plain($name .': '. mime_header_encode($value));
1412 }
1413 watchdog('devel', t('Mail sent:<br />Key: %mailkey<br />To: %to<br />From: %from<br />Subject: %subject<br />Body: %body<br /><br />Additional headers: !header', array(
1414 '%mailkey' => $mailkey,
1415 '%to' => $to,
1416 '%from' => $from,
61d85437 1417 '%subject' => $subject,
6032b7f5 1418 '%body' => $body,
1419 '!header' => implode("<br />", $mimeheaders),
61d85437
ND
1420 )));
1421 return TRUE;
1422 }
1423}
7ac60dac
GK
1424
1425function devel_queries() {
1426 $header = array(
8062d13e 1427 array('data' => t('Total (ms)'), 'field' => 'total_time', 'sort' => 'desc'),
7ac60dac 1428 array('data' => t('Average (ms)'), 'field' => 'average', 'sort' => 'desc'),
8062d13e 1429 array('data' => t('Std deviation (ms)')),
7ac60dac 1430 array('data' => t('Count'), 'field' => 'count'),
20079c18 1431 array('data' => t('Function'), 'field' => 'q.function'),
7ac60dac
GK
1432 array('data' => t('Query'), 'field' => 'q.query'),
1433 );
1434
20079c18 1435 $result = pager_query('SELECT q.qid, q.query, q.function, t.*, COUNT(t.qid) AS count, SUM(t.time) AS total_time, AVG(t.time) AS average, STDDEV(t.time) AS stddev FROM {devel_queries} q INNER JOIN {devel_times} t ON q.qid = t.qid GROUP BY t.qid '. tablesort_sql($header), 30, 0, 'SELECT COUNT(qid) FROM {devel_queries}');
7ac60dac
GK
1436 while ($log = db_fetch_object($result)) {
1437 $rows[] = array(
1f63917c
GK
1438 round($log->total_time * 1000, 3),
1439 round($log->average * 1000, 3),
1440 round($log->stddev * 1000, 3),
7ac60dac 1441 $log->count,
20079c18 1442 $log->function,
7ac60dac
GK
1443 check_plain($log->query)
1444 );
1445 }
1446
1447 drupal_set_title(check_plain($node->title));
1448 $output = theme('table', $header, $rows);
20079c18 1449 $output .= theme('pager', NULL, 30, 0);
7ac60dac
GK
1450 $output .= l(t('Delete collected query statistics'), 'devel/queries/empty');
1451
8062d13e 1452 print theme('page', $output, FALSE);
7ac60dac
GK
1453}
1454
1455function devel_queries_empty() {
1456 db_query('DELETE FROM {devel_queries}');
1457 db_query('DELETE FROM {devel_times}');
1458 drupal_set_message(t('Stored query statistics deleted.'));
1459 drupal_goto('devel/queries');
1460}
1461
9d14df83 1462/*
1463 * migration related functions
1464 */
c6a11c27 1465
9d14df83 1466/**
1467 * Menu callback. Rebuild node _comment_stats table.
c6a11c27 1468 *
9d14df83 1469 * @return void
1470 **/
1471function devel_rebuild_node_comment_statistics_page() {
1472 devel_rebuild_node_comment_statistics();
1473 drupal_set_message('node_comment_statistics table has been rebuilt.');
1474 drupal_goto('admin');
1475}
1476
1477/**
d28d5445 1478 * Update node_comment_statistics table for nodes with comments.
9d14df83 1479 * TODO: if 2 comments have exact same timestamp, the function can get wrong uid and name fields.
1480 * Handles when comment timestamps have been manually set in admin
c6a11c27 1481 *
9d14df83 1482 * @return void
c6a11c27 1483 **/
9d14df83 1484function devel_rebuild_node_comment_statistics() {
d28d5445 1485 // Empty table
1486 $sql = "DELETE FROM {node_comment_statistics}";
1487 db_query($sql);
1488
1489 $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)";
9d14df83 1490 db_query($sql, COMMENT_PUBLISHED);
d28d5445 1491
1492 // Insert 0 count records into the node_comment_statistics for nodes that are missing. See comment_enable()
1493 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');
1494 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");
9d14df83 1495}
1496
1497/**
1498 * Set sequences table to a sane state. Useful after a bulk import.
c6a11c27 1499 *
9d14df83 1500 * @return void
1501 **/
1502function sequence_reset($table, $column) {
1503 $max = db_result(db_query("SELECT MAX($column) FROM {$table}"));
1504 $max = ($max ? $max+1 : 2);
1505 db_query("UPDATE {sequences} SET id = $max WHERE name='${table}_$column'");
1506}
1507
1508/**
1509 * Enable or disable indexes for a given table. Useful during a bulk import.
c6a11c27 1510 *
9d14df83 1511 * @return void
1512 **/
1513function devel_table_keys($tables, $verb = 'ENABLE') {
1514 foreach ($tables as $table) {
1515 db_query("ALTER TABLE $table $verb KEYS");
1516 }
1517}
1518
1519/**
1520 * Save contents of an url to filesystem. Works for images.
c6a11c27 1521 *
9d14df83 1522 * @return void
1523 **/
1524function wget($url, $file) {
1525 if (file_exists($file)) {
1526 return;
1527 }
1528 print "get: $url\n";
1529 $ch = curl_init($url);
1530 $fp = fopen($file, "w");
1531 curl_setopt($ch, CURLOPT_FILE, $fp);
1532 curl_setopt($ch, CURLOPT_HEADER, 0);
1533 curl_exec($ch);
1534 curl_close($ch);
1535 fclose($fp);
1536}