devel.module 54.4 KB
Newer Older
1
<?php
2
// $Id$
3

4
// This module holds functions useful for Drupal development.
5 6
// Please contribute!

7
// Suggested profiling and stacktrace library from http://www.xdebug.org/index.php
8

9 10
define('DEVEL_QUERY_SORT_BY_SOURCE', 0);
define('DEVEL_QUERY_SORT_BY_DURATION', 1);
11

12 13 14 15
define('DEVEL_ERROR_HANDLER_NONE', 0);
define('DEVEL_ERROR_HANDLER_STANDARD', 1);
define('DEVEL_ERROR_HANDLER_BACKTRACE', 2);

16 17
define('DEVEL_MIN_TEXTAREA', 50);

18
/**
19
 * Implementation of hook_help().
20
 */
21 22
function devel_help($section) {
  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
    case 'devel/reinstall':
      return t('Warning - will delete your module tables and variables.');
32
  }
33 34
}

35
/**
36
 * Implementationation of hook_menu().
37
 */
38
function devel_menu() {
39
  $items = array();
40
  // Note: we can't dynamically append destination to querystring. Do so at theme layer. Fix in D7?
41
  $items['devel/cache/clear'] = array(
42
    'title' => 'Empty cache',
43
    'page callback' => 'devel_cache_clear',
44
    'description' => 'Clear the CSS cache and all database cache tables which store page, node, theme and variable caches.',
45
    'access arguments' => array('access devel information'),
46
    'file' => 'devel.pages.inc',
47
    'menu_name' => 'devel',
48
  );
49

50
  $items['devel/reference'] = array(
51 52
    'title' => 'Function reference',
    'description' => 'View a list of currently defined user functions with documentation links.',
53 54
    'page callback' => 'devel_function_reference',
    'access arguments' => array('access devel information'),
55
    'file' => 'devel.pages.inc',
56
    'menu_name' => 'devel',
57 58
  );
  $items['devel/reinstall'] = array(
59
    'title' => 'Reinstall modules',
60 61
    'page callback' => 'drupal_get_form',
    'page arguments' => array('devel_reinstall'),
62
    'description' => 'Run hook_uninstall() and then hook_install() for a given module.',
63
    'access arguments' => array('access devel information'),
64
    'file' => 'devel.pages.inc',
65
    'menu_name' => 'devel',
66
  );
Jeff Robbins's avatar
Jeff Robbins committed
67 68 69 70 71
  $items['devel/source'] = array(
    'title' => 'Display the PHP code of any file in your Drupal installation',
    'page callback' => 'devel_display_source',
    'access arguments' => array('display source code'),
    'type' => MENU_CALLBACK,
72
    'file' => 'devel.pages.inc',
73
    'menu_name' => 'devel',
Jeff Robbins's avatar
Jeff Robbins committed
74
  );
75
  $items['devel/menu/reset'] = array(
76
    'title' => 'Rebuild menus',
77
    'description' => 'Rebuild menu based on hook_menu() and revert any custom changes. All menu items return to their default settings.',
78
    'page callback' => 'drupal_get_form',
79
    'page arguments' => array('devel_menu_rebuild'),
80
    'access arguments' => array('access devel information'),
81
    'file' => 'devel.pages.inc',
82
    'menu_name' => 'devel',
83
  );
84 85 86 87 88
  $items['devel/menu/item'] = array(
    'title' => 'Menu item',
    'description' => 'Details about a given menu item.',
    'page callback' => 'devel_menu_item',
    'access arguments' => array('access devel information'),
89
    'file' => 'devel.pages.inc',
90 91
    'menu_name' => 'devel',
  );
92
  $items['devel/variable'] = array(
93
    'title' => 'Variable editor',
94
    'description' => 'Edit and delete site variables.',
95 96
    'page callback' => 'devel_variable_page',
    'access arguments' => array('access devel information'),
97
    'file' => 'devel.pages.inc',
98
    'menu_name' => 'devel',
99
  );
100 101
  // we don't want the abbreviated version provided by status report
  $items['devel/phpinfo'] = array(
102
    'title' => 'PHPinfo()',
103
    'description' => 'View your server\'s PHP configuration',
104 105
    'page callback' => 'devel_phpinfo',
    'access arguments' => array('access devel information'),
106
    'file' => 'devel.pages.inc',
107
    'menu_name' => 'devel',
108 109 110 111 112 113 114
  );
  $items['devel/php'] = array(
    'title' => 'Execute PHP Code',
    'description' => 'Execute some PHP code',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('devel_execute_form'),
    'access arguments' => array('execute php code'),
115
    'file' => 'devel.pages.inc',
116
    'menu_name' => 'devel',
117 118
  );
  $items['devel/theme/registry'] = array(
119
    'title' => 'Theme registry',
120
    'description' => 'View a list of available theme functions across the whole site.',
121 122
    'page callback' => 'devel_theme_registry',
    'access arguments' => array('access devel information'),
123
    'file' => 'devel.pages.inc',
124
    'menu_name' => 'devel',
125
  );
moshe weitzman's avatar
moshe weitzman committed
126 127 128 129 130 131 132 133
  $items['devel/entity/info'] = array(
    'title' => 'Entity info',
    'description' => 'View entity information across the whole site.',
    'page callback' => 'devel_entity_info_page',
    'access arguments' => array('access devel information'),
    'file' => 'devel.pages.inc',
    'menu_name' => 'devel',
  );
134 135 136
  $items['devel/field/info'] = array(
    'title' => 'Field info',
    'description' => 'View fields information across the whole site.',
137
    'page callback' => 'devel_field_info_page',
138
    'access arguments' => array('access devel information'),
139
    'file' => 'devel.pages.inc',
140 141
    'menu_name' => 'devel',
  );
142
  $items['devel/elements'] = array(
143 144
    'title' => 'Hook_elements()',
    'description' => 'View the active form/render elements for this site.',
145 146
    'page callback' => 'devel_elements_page',
    'access arguments' => array('access devel information'),
147
    'file' => 'devel.pages.inc',
148
    'menu_name' => 'devel',
149
  );
150 151
  $items['devel/variable/edit/%'] = array(
    'title' => 'Variable editor',
152
    'page callback' => 'drupal_get_form',
153
    'page arguments' => array('devel_variable_edit', 3),
154 155
    'access arguments' => array('access devel information'),
    'type' => MENU_CALLBACK,
156
    'file' => 'devel.pages.inc',
157
    'menu_name' => 'devel',
158 159
  );
  $items['devel/session'] = array(
160
    'title' => 'Session viewer',
161
    'description' => 'List the contents of $_SESSION.',
162 163
    'page callback' => 'devel_session',
    'access arguments' => array('access devel information'),
164
    'file' => 'devel.pages.inc',
165
    'menu_name' => 'devel',
166 167
  );
  $items['devel/switch'] = array(
168
    'title' => 'Switch user',
169 170 171
    'page callback' => 'devel_switch_user',
    'access arguments' => array('switch users'),
    'type' => MENU_CALLBACK,
172
    'file' => 'devel.pages.inc',
173
    'menu_name' => 'devel',
174
  );
175 176 177 178 179
  $items['devel/explain'] = array(
    'title' => 'Explain query',
    'page callback' => 'devel_querylog_explain',
    'description' => 'Run an EXPLAIN on a given query. Used by query log',
    'access arguments' => array('access devel information'),
180
    'file' => 'devel.pages.inc',
181 182 183 184 185
    'type' => MENU_CALLBACK
  );
  $items['devel/arguments'] = array(
    'title' => 'Arguments query',
    'page callback' => 'devel_querylog_arguments',
186
    'description' => 'Return a given query, with arguments instead of placeholders. Used by query log',
187
    'access arguments' => array('access devel information'),
188
    'file' => 'devel.pages.inc',
189 190
    'type' => MENU_CALLBACK
  );
191 192 193 194 195 196 197 198
  $items['devel/run-cron'] = array(
    'title' => 'Run cron',
    'page callback' => 'system_run_cron',
    'access arguments' => array('administer site configuration'),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
    'menu_name' => 'devel',
  );
199 200 201

  // Duplicate path in 2 different menus. See http://drupal.org/node/601788.
  $items['devel/settings'] = array(
202
    'title' => 'Devel settings',
203
    '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.',
204 205 206
    'page callback' => 'drupal_get_form',
    'page arguments' => array('devel_admin_settings'),
    'access arguments' => array('administer site configuration'),
207
    'file' => 'devel.admin.inc',
208
    'menu_name' => 'devel',
209
  );
210 211 212 213 214
  $items['admin/config/development/devel'] = array(
    'title' => 'Devel settings',
    '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.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('devel_admin_settings'),
215
    'file' => 'devel.admin.inc',
216 217 218
    'access arguments' => array('administer site configuration'),
  );

219 220
  $items['node/%node/devel'] = array(
    'title' => 'Devel',
221
    'page callback' => 'devel_load_object',
222
    'page arguments' => array(1, 'node'),
223 224
    'access arguments' => array('access devel information'),
    'type' => MENU_LOCAL_TASK,
225
    'file' => 'devel.pages.inc',
226
    'weight' => 100,
227
  );
228 229 230 231 232
  $items['node/%node/devel/load'] = array(
    'title' => 'Load',
    'page callback' => 'devel_load_object',
    'page arguments' => array(1, 'node'),
    'access arguments' => array('access devel information'),
233
    'file' => 'devel.pages.inc',
234 235
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
moshe weitzman's avatar
moshe weitzman committed
236
  $items['node/%node/devel/render'] = array(
237
    'title' => 'Render',
238 239
    'page callback' => 'devel_render_object',
    'page arguments' => array('node', 1),
240
    'access arguments' => array('access devel information'),
241
    'file' => 'devel.pages.inc',
242
    'type' => MENU_LOCAL_TASK,
243
    'weight' => 100,
244
  );
245 246
  $items['comment/%comment/devel'] = array(
    'title' => 'Devel',
247 248 249 250
    'page callback' => 'devel_load_object',
    'page arguments' => array(1, 'comment'),
    'access arguments' => array('access devel information'),
    'type' => MENU_LOCAL_TASK,
251
    'file' => 'devel.pages.inc',
252 253
    'weight' => 100,
  );
254 255 256 257 258 259
  $items['comment/%comment/devel/load'] = array(
    'title' => 'Load',
    'page callback' => 'devel_load_object',
    'page arguments' => array(1, 'comment'),
    'access arguments' => array('access devel information'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
260
    'file' => 'devel.pages.inc',
261
  );
262
  $items['comment/%comment/devel/render'] = array(
263
    'title' => 'Render',
264 265 266 267
    'page callback' => 'devel_render_object',
    'page arguments' => array('comment', 1),
    'access arguments' => array('access devel information'),
    'type' => MENU_LOCAL_TASK,
268
    'file' => 'devel.pages.inc',
269 270
    'weight' => 100,
  );
271 272
  $items['user/%user/devel'] = array(
    'title' => 'Devel',
273
    'page callback' => 'devel_load_object',
274
    'page arguments' => array(1, 'user'),
275 276
    'access arguments' => array('access devel information'),
    'type' => MENU_LOCAL_TASK,
277
    'file' => 'devel.pages.inc',
278
    'weight' => 100,
279
  );
280 281 282 283 284
  $items['user/%user/devel/load'] = array(
    'title' => 'Load',
    'page callback' => 'devel_load_object',
    'page arguments' => array(1, 'user'),
    'access arguments' => array('access devel information'),
285
    'file' => 'devel.pages.inc',
286 287
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
moshe weitzman's avatar
moshe weitzman committed
288
  $items['user/%user/devel/render'] = array(
289
    'title' => 'Render',
290 291 292
    'page callback' => 'devel_render_object',
    'page arguments' => array('user', 1),
    'access arguments' => array('access devel information'),
293
    'file' => 'devel.pages.inc',
294
    'type' => MENU_LOCAL_TASK,
295
    'weight' => 100,
296
  );
297 298
  $items['taxonomy/term/%taxonomy_term/devel'] = array(
    'title' => 'Devel',
299 300 301
    'page callback' => 'devel_load_object',
    'page arguments' => array(2, 'term'),
    'access arguments' => array('access devel information'),
302
    'file' => 'devel.pages.inc',
303 304 305
    'type' => MENU_LOCAL_TASK,
    'weight' => 100,
  );
306 307 308 309 310
  $items['taxonomy/term/%taxonomy_term/devel/load'] = array(
    'title' => 'Load',
    'page callback' => 'devel_load_object',
    'page arguments' => array(2, 'term'),
    'access arguments' => array('access devel information'),
311
    'file' => 'devel.pages.inc',
312 313
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
314
  $items['taxonomy/term/%taxonomy_term/devel/render'] = array(
315
    'title' => 'Render',
316 317 318 319
    'page callback' => 'devel_render_term',
    'page arguments' => array(2),
    'access arguments' => array('access devel information'),
    'type' => MENU_LOCAL_TASK,
320
    'file' => 'devel.pages.inc',
321 322
    'weight' => 100,
  );
323

324 325 326
  return $items;
}

327
function devel_menu_need_destination() {
328
  return array('devel/cache/clear', 'devel/reinstall', 'devel/menu/reset', 'devel/variable', 'admin/reports/status/run-cron');
329 330 331 332
}

/**
 * An implementation of hook_menu_link_alter(). Flag this link as needing alter at display time.
moshe weitzman's avatar
moshe weitzman committed
333
 * This is more robust than setting alter in hook_menu().
334
 * @see devel_translated_menu_link_alter().
335 336
 *
 **/
337
function devel_menu_link_alter(&$item) {
338
  if (in_array($item['link_path'], devel_menu_need_destination()) || $item['link_path'] == 'devel/menu/item') {
339 340 341 342
    $item['options']['alter'] = TRUE;
  }
}

343
/**
344
 * An implementation of hook_translated_menu_item_alter(). Append dynamic
345
 * querystring 'destination' to several of our own menu items.
346
 *
347 348
 **/
function devel_translated_menu_link_alter(&$item) {
349
  if (in_array($item['href'], devel_menu_need_destination())) {
350 351
    $item['localized_options']['query'] = drupal_get_destination();
  }
352 353 354
  elseif ($item['href'] == 'devel/menu/item') {
    $item['localized_options']['query'] = array('path' => $_GET['q']);
  }
355 356
}

357 358 359
/**
 * Implementation of hook_theme()
 */
360
function devel_theme() {
361
  return array(
362
    'devel_querylog' => array(
363
      'variables' => array('header' => array(), 'rows' => array()),
364 365
    ),
    'devel_querylog_row' => array(
366
      'variables' => array('row' => array()),
367
    ),
368
  );
369 370
}

371
/**
372
 * Implementation of hook_init().
373
 */
374
function devel_init() {
375
  if (!devel_silent()) {
376
    if (user_access('access devel information')) {
377
      devel_set_handler(variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD));
378
      // We want to include the class early so that anyone may call krumo() as needed. See http://krumo.sourceforge.net/
379
      has_krumo();
380

381 382 383 384 385 386 387 388
      // See http://www.firephp.org/HQ/Install.htm
      $path = NULL;
      if (@include_once('fb.php')) {
        // FirePHPCore is in include_path. Probably a PEAR installation.
        $path = '';
      }
      elseif (module_exists('libraries')) {
        // Support Libraries API - http://drupal.org/project/libraries
389 390
        $firephp_path = libraries_get_path('FirePHPCore') . '/lib/FirePHPCore/';
        $chromephp_path = libraries_get_path('chromephp');
391 392
      }
      else {
393 394
        $firephp_path = './'. drupal_get_path('module', 'devel') .'/FirePHPCore/lib/FirePHPCore/';
        $chromephp_path = './' . drupal_get_path('module', 'devel') .'/chromephp';
395
      }
396 397 398 399 400 401 402 403 404 405

      // include FirePHP if exists...
      if (file_exists($firephp_path .'fb.php')) {
        include_once $firephp_path .'fb.php';
        include_once $firephp_path .'FirePHP.class.php';
      }

      // include ChromePHP if exists...
      if (file_exists($chromephp_path . '/ChromePhp.php')) {
        include_once $chromephp_path . '/ChromePhp.php';
406
      }
407

408

409 410
      // Add CSS for query log if should be displayed.
      if (variable_get('devel_query_display', 0)) {
411
        drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css');
412
        drupal_add_js(drupal_get_path('module', 'devel'). '/devel.js');
413
      }
414
    }
415
  }
416 417 418 419 420
  if (variable_get('devel_rebuild_theme_registry', FALSE)) {
    drupal_theme_rebuild();
    if (flood_is_allowed('devel_rebuild_registry_warning', 1)) {
      flood_register_event('devel_rebuild_registry_warning');
      if (!devel_silent() && user_access('access devel information')) {
421
        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'))));
422 423 424
      }
    }
  }
425 426
}

427
function devel_set_message($msg, $type = NULL) {
428 429
  $function  = function_exists('drush_log') ? 'drush_log' : 'drupal_set_message';
  $function($msg, $type);
430 431
}

moshe weitzman's avatar
moshe weitzman committed
432
// Return boolean. No need for cache here.
433 434
function has_krumo() {
  // see README.txt or just download from http://krumo.sourceforge.net/
moshe weitzman's avatar
moshe weitzman committed
435 436
  @include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'devel') .'/krumo/class.krumo.php';
  return function_exists('krumo') && !drupal_is_cli();
437 438
}

439 440 441
/**
 * Decide whether or not to print a debug variable using krumo().
 *
442
 * @param $input
443 444 445
 * @return boolean
 */
function merits_krumo($input) {
446
  return (is_object($input) || is_array($input)) && has_krumo() && variable_get('devel_krumo_skin', '') != 'disabled';
447 448
}

449 450 451 452 453 454
/**
 * Calls the http://www.firephp.org/ fb() function if it is found.
 *
 * @return void
 */
function dfb() {
455
  if (function_exists('fb') && user_access('access devel information') && !headers_sent()) {
456 457 458 459 460
    $args = func_get_args();
    call_user_func_array('fb', $args);
  }
}

461 462 463 464 465 466 467
/**
 * Calls dfb() to output a backtrace.
 */
function dfbt($label) {
  dfb($label, FirePHP::TRACE);
}

468 469 470 471 472 473 474 475 476 477
/**
 * Wrapper for ChromePHP Class log method
 */
function dcp() {
  if (class_exists('ChromePhp') && user_access('access devel information')) {
    $args = func_get_args();
    call_user_func_array(array('ChromePhp', 'log'), $args);
  }
}

478 479 480 481
/**
 * Implements hook_watchdog().
 */
function devel_watchdog(array $log_entry) {
482
  if (class_exists('FirePHP') && !drupal_is_cli()) {
483
    switch ($log_entry['severity']) {
484
      case WATCHDOG_EMERGENCY:
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
      case WATCHDOG_ALERT:
      case WATCHDOG_CRITICAL:
      case WATCHDOG_ERROR:
        $type = FirePHP::ERROR;
        break;
      case WATCHDOG_WARNING:
        $type = FirePHP::WARN;
        break;
      case WATCHDOG_NOTICE:
      case WATCHDOG_INFO:
        $type = FirePHP::INFO;
        break;
      case WATCHDOG_DEBUG:
      DEFAULT:
        $type = FirePHP::LOG;
    }
  }
  else {
    $type = 'watchdog';
  }
505
  $function = function_exists('decode_entities') ? 'decode_entities' : 'html_entity_decode';
506 507
  $watchdog = array(
    'type' => $log_entry['type'],
508
    'message' => $function(strtr($log_entry['message'], (array)$log_entry['variables'])),
509 510 511 512 513 514
  );
  if (isset($log_entry['link'])) {
    $watchdog['link'] = $log_entry['link'];
  }
  dfb($watchdog, $type);
}
515

516 517 518 519 520 521
function devel_set_handler($handler) {
  switch ($handler) {
    case DEVEL_ERROR_HANDLER_STANDARD:
      // do nothing
      break;
    case DEVEL_ERROR_HANDLER_BACKTRACE:
522
      if (has_krumo()) {
523 524
        set_error_handler('backtrace_error_handler');
      }
525 526 527 528 529 530 531 532
      break;
    case DEVEL_ERROR_HANDLER_NONE:
      restore_error_handler();
      break;
  }
}

function devel_silent() {
533
  // isset($_GET['q']) is needed when calling the front page. q is not set.
534
  // Don't interfere with private files/images.
535
  return
536
    function_exists('drupal_is_cli') && drupal_is_cli() ||
moshe weitzman's avatar
moshe weitzman committed
537
    (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'ApacheBench') !== FALSE) ||
538
    !empty($_REQUEST['XDEBUG_PROFILE']) ||
539 540
    isset($GLOBALS['devel_shutdown']) ||
    strstr($_SERVER['PHP_SELF'], 'update.php') ||
541
    (isset($_GET['q']) && (
542
      in_array($_GET['q'], array( 'admin/content/node-settings/rebuild')) ||
543
      substr($_GET['q'], 0, strlen('system/files')) == 'system/files' ||
544 545
      substr($_GET['q'], 0, strlen('batch')) == 'batch' ||
      substr($_GET['q'], 0, strlen('file/ajax')) == 'file/ajax')
546
    );
547 548
}

549 550 551 552 553 554 555 556 557 558 559
function devel_xhprof_enable() {
  if (extension_loaded('xhprof') && variable_get('devel_xhprof_enabled', FALSE)) {
    if ($path = variable_get('devel_xhprof_directory', '')) {
      include_once $path . '/xhprof_lib/utils/xhprof_lib.php';
      include_once $path . '/xhprof_lib/utils/xhprof_runs.php';
      // @todo: consider a variable per-flag instead.
      xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
    }
  }
}

560 561 562 563
/**
 * Implementation of hook_boot(). Runs even for cached pages.
 */
function devel_boot() {
564 565
  // Initialize XHProf.
  devel_xhprof_enable();
566

567 568 569 570 571
  if (!devel_silent()) {
    if (variable_get('dev_mem', 0)) {
      global $memory_init;
      $memory_init = memory_get_usage();
    }
572

573 574 575 576
    if (devel_query_enabled()) {
      @include_once DRUPAL_ROOT . '/includes/database/log.inc';
      Database::startLog('devel');;
    }
577
  }
578

579 580 581 582
  // We need user_access() in the shutdown function. make sure it gets loaded.
  // Also prime the drupal_get_filename() static with user.module's location to
  // avoid a stray query.
  drupal_get_filename('module', 'user', 'modules/user/user.module');
583
  drupal_load('module', 'user');
584
  drupal_register_shutdown_function('devel_shutdown');
585 586
}

587
function backtrace_error_handler($errno, $message, $filename, $line) {
588 589
  // Don't respond to the error if it was suppressed with a '@'
  if (error_reporting() == 0) return;
590

591
  if ($errno & (E_ALL ^ E_NOTICE)) {
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
    // We can't use the PHP E_* constants here as not all versions of PHP have all
    // the constants defined, so for consistency, we just use the numeric equivelant.
    $types = array(
      1 => 'error',
      2 => 'warning',
      4 => 'parse error',
      8 => 'notice',
      16 => 'core error',
      32 => 'core warning',
      64 => 'compile error',
      128 => 'compile warning',
      256 => 'user error',
      512 => 'user warning',
      1024 => 'user notice',
      2048 => 'strict warning',
      4096 => 'recoverable error',
      8192 => 'deprecated',
      16384 => 'user deprecated',
    );
611 612 613 614
    $entry = $types[$errno] .': '. $message .' in '. $filename .' on line '. $line .'.';

    if (variable_get('error_level', 1) == 1) {
      $backtrace = debug_backtrace();
615 616
      foreach ($backtrace as $call) {
        $nicetrace[$call['function']] = $call;
617
      }
618
      krumo($nicetrace);
619 620
    }

621
    watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
622
  }
623 624
}

625
/**
moshe weitzman's avatar
moshe weitzman committed
626
 * Implement hook_permission().
627
 */
moshe weitzman's avatar
moshe weitzman committed
628
function devel_permission() {
629
  return array(
moshe weitzman's avatar
moshe weitzman committed
630
    'access devel information' => array(
631
      'description' => t('View developer output like variable printouts, query log, etc.'),
moshe weitzman's avatar
moshe weitzman committed
632 633 634 635 636 637 638 639 640 641 642 643 644 645
      'title' => t('Access developer information'),
    ),
    'execute php code' => array(
      'title' => t('Execute PHP code'),
      'description' => t('Run arbitrary PHP from a block. Danger!'),
    ),
    'switch users' => array(
      'title' => t('Switch users'),
      'description' => t('Become any user on the site with just a click. Danger!'),
    ),
    'display source code' => array(
      'title' => t('Display source code'),
      'description' => t('View the site\'s php source code. Danger!'),
    ),
646
  );
647 648
}

649 650 651 652 653 654 655 656 657
function devel_block_info() {
  $blocks['execute_php'] = array(
    'info' => t('Execute PHP'),
    'cache' => DRUPAL_NO_CACHE,
  );
  $blocks['switch_user'] = array(
    'info' => t('Switch user'),
    'cache' => DRUPAL_NO_CACHE,
  );
658 659 660 661
  return $blocks;
}

/**
662
 * Implementation of hook_block_configure().
663 664
 */
function devel_block_configure($delta) {
665
  if ($delta == 'switch_user') {
666
    $form['list_size'] = array(
667 668 669 670 671 672
      '#type' => 'textfield',
      '#title' => t('Number of users to display in the list'),
      '#default_value' => variable_get('devel_switch_user_list_size', 10),
      '#size' => '3',
      '#maxlength' => '4',
    );
673
    $form['include_anon'] = array(
674
      '#type' => 'checkbox',
675
      '#title' => t('Include %anonymous', array('%anonymous' => format_username(drupal_anonymous_user()))),
676 677
      '#default_value' => variable_get('devel_switch_user_include_anon', FALSE),
    );
678 679 680 681 682
    $form['show_form'] = array(
      '#type' => 'checkbox',
      '#title' => t('Allow entering any user name'),
      '#default_value' => variable_get('devel_switch_user_show_form', TRUE),
    );
683 684
    return $form;
  }
685 686 687
}

function devel_block_save($delta, $edit = array()) {
688
  if ($delta == 'switch_user') {
689 690 691
    variable_set('devel_switch_user_list_size', $edit['list_size']);
    variable_set('devel_switch_user_include_anon', $edit['include_anon']);
    variable_set('devel_switch_user_show_form', $edit['show_form']);
692
  }
693 694 695 696 697
}

function devel_block_view($delta) {
  $block = array();
  switch ($delta) {
698
    case 'switch_user':
699 700
      $block = devel_block_switch_user();
      break;
701 702

    case 'execute_php':
703
      if (user_access('execute php code')) {
704
        $block['content'] = drupal_get_form('devel_execute_block_form');
705 706
      }
      break;
707
  }
708
  return $block;
709
}
710

711
function devel_block_switch_user() {
712
  $links = devel_switch_user_list();
713
  if (!empty($links) || user_access('switch users')) {
714
    $block['subject'] = t('Switch user');
715
    $build['devel_links'] = array('#theme' => 'links', '#links' => $links);
716 717 718
    if (variable_get('devel_switch_user_show_form', TRUE)) {
      $build['devel_form'] = drupal_get_form('devel_switch_user_form');
    }
719
    $block['content'] = $build;
720 721 722 723 724
    return $block;
  }
}

function devel_switch_user_list() {
725 726
  global $user;

727 728
  $links = array();
  if (user_access('switch users')) {
729
    $list_size = variable_get('devel_switch_user_list_size', 10);
730 731 732
    if ($include_anon = ($user->uid && variable_get('devel_switch_user_include_anon', FALSE))) {
      --$list_size;
    }
733
    $dest = drupal_get_destination();
734
    // Try to find at least $list_size users that can switch.
735
    // Inactive users are omitted from all of the following db selects.
736 737
    $roles = user_roles(TRUE, 'switch users');
    if (isset($roles[DRUPAL_AUTHENTICATED_RID])) {
738
      // If authenticated users have this permission, just grab
739
      // the last $list_size users, since there won't be records in
740
      // {user_roles} and every user on the system can switch.
741
      $accounts = db_query_range("SELECT DISTINCT u.uid, u.name, u.access FROM {users} u WHERE u.uid > 0 AND u.status > 0 ORDER BY u.access DESC", 0, $list_size);
742 743 744 745 746 747 748
    }
    else {
      $where = array('u.uid = 1');
      if (count($roles)) {
        $where[] = 'r.rid IN ('. implode(',', array_keys($roles)) .')';
      }
      $where_sql = implode(' OR ', $where);
749
      $accounts = 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) AND u.status > 0 ORDER BY u.access DESC", 0, $list_size);
750
    }
751 752
    foreach ($accounts as $account) {
      $links[$account->uid] = array(
753
        'title' => drupal_placeholder($account->name),
754
        'href' => 'devel/switch/'. $account->name,
755 756 757
        'query' => $dest,
        'attributes' => array('title' => t('This user can switch back.')),
        'html' => TRUE,
758
        'last_access' => $account->access,
759
      );
760 761
    }
    $num_links = count($links);
762 763
    if ($num_links < $list_size) {
      // If we don't have enough, add distinct uids until we hit $list_size.
764
      $accounts = db_query_range('SELECT uid, name, access FROM {users} WHERE uid > 0 AND uid NOT IN ('. implode(',', array_keys($links)) .') AND status > 0 ORDER BY access DESC', 0, $list_size - $num_links);
765 766 767 768
      foreach ($accounts as $account) {
        $links[$account->uid] = array(
          'title' => $account->name ? $account->name : 'anon',
          'href' => 'devel/switch/'. $account->name,
769
          'query' => $dest,
770
          'attributes' => array('title' => t('Caution: this user will be unable to switch back.')),
771
          'last_access' => $account->access,
772
        );
773
      }
774
      uasort($links, '_devel_switch_user_list_cmp');
775
    }
776 777
    if ($include_anon) {
      $link = array(
778
        'title' => check_plain(format_username(drupal_anonymous_user())),
779 780 781 782
        'href' => 'devel/switch',
        'query' => $dest,
        'attributes' => array('title' => t('Caution: the anonymous user will be unable to switch back.')),
      );
783 784
      if (user_access('switch users', drupal_anonymous_user())) {
        $link['title'] = drupal_placeholder($link['title']);
785 786 787 788 789
        $link['attributes'] = array('title' => t('This user can switch back.'));
        $link['html'] = TRUE;
      }
      $links[] = $link;
    }
790
  }
791
  return $links;
792 793
}

794 795 796 797 798 799 800 801 802
/**
 * Comparison helper function for uasort() in devel_switch_user_list().
 *
 * Sorts the Switch User links by the user's last access timestamp.
 */
function _devel_switch_user_list_cmp($a, $b) {
  return $b['last_access'] - $a['last_access'];
}

803 804 805 806
function devel_switch_user_form() {
  $form['username'] = array(
    '#type' => 'textfield',
    '#description' => t('Enter username'),
807 808
    '#autocomplete_path' => 'user/autocomplete',
    '#maxlength' => USERNAME_MAX_LENGTH,
809 810 811
    '#size' => 16,
  );
  $form['submit'] = array(
812
    '#type' => 'submit',
813 814 815
    '#value' => t('Switch'),
  );
  return $form;
816

817 818
}

819
function devel_doc_function_form() {
820
  $version = devel_get_core_version(VERSION);
821 822 823 824 825 826 827 828
  $form['function'] = array(
    '#type' => 'textfield',
    '#description' => t('Enter function name for api lookup'),
    '#size' => 16,
    '#maxlength' => 255,
  );
  $form['version'] = array('#type' => 'value', '#value' => $version);
  $form['submit_button'] = array(
829
    '#type' => 'submit',
830 831 832 833 834
    '#value' => t('Submit'),
  );
  return $form;
}

835 836 837
function devel_doc_function_form_submit($form, &$form_state) {
  $version = $form_state['values']['version'];
  $function = $form_state['values']['function'];
838
  $api = variable_get('devel_api_url', 'api.drupal.org');
839
  $form_state['redirect'] =   "http://$api/api/function/$function/$version";
840 841
}

842
function devel_switch_user_form_validate($form, &$form_state) {
843
  if (!$account = user_load_by_name($form_state['values']['username'])) {
844
    form_set_error('username', t('Username not found'));
845
  }
846 847
}

848 849
function devel_switch_user_form_submit($form, &$form_state) {
  $form_state['redirect'] = 'devel/switch/'. $form_state['values']['username'];
850 851
}

852 853
// An implementation of hook_drupal_goto_alter().
function devel_drupal_goto_alter($path, $options, $http_response_code) {
854
  global $user;
855

856
  if (isset($path) && !devel_silent()) {
857 858
    // The page we are leaving is a drupal_goto(). Present a redirection page
    // so that the developer can see the intermediate query log.
859
    // We don't want to load user module here, so keep function_exists() call.
860
    if (isset($user) && function_exists('user_access') && user_access('access devel information') && variable_get('devel_redirect_page', 0)) {
861
      $destination = function_exists('url') ? url($path, $options) : $path;
862
      $output = t_safe('<p>The user is being redirected to <a href="@destination">@destination</a>.</p>', array('@destination' => $destination));
863
      drupal_deliver_page($output);
864

865 866 867
      // Don't allow the automatic redirect to happen.
      exit();
    }
868
    else {
869
      $GLOBALS['devel_redirecting'] = TRUE;
870
    }
871
  }
872
}
873

874
/**
875
 * See devel_start() which registers this function as a shutdown function.
876 877
 */
function devel_shutdown() {
878
  // Register the real shutdown function so it runs later than other shutdown functions.
879
  drupal_register_shutdown_function('devel_shutdown_real');
880 881 882

  global $devel_run_id;
  $devel_run_id = variable_get('devel_xhprof_enabled', FALSE) ? devel_shutdown_xhprof(): NULL;
moshe weitzman's avatar
moshe weitzman committed
883 884
  if ($devel_run_id && function_exists('drush_log')) {
    drush_log('xhprof link: ' . devel_xhprof_link($devel_run_id, 'url'), 'notice');
885
  }
886 887
}

888 889 890 891 892
function devel_page_alter($page) {
  if (variable_get('devel_page_alter', FALSE) && user_access('access devel information')) {
    dpm($page, 'page');
  }
}
893

894 895 896 897 898 899
// AJAX render reponses sometimers are sent as text/html so we have to catch them here
// and disable our footer stuff.
function devel_ajax_render_alter() {
  $GLOBALS['devel_shutdown'] = FALSE;
}

900 901 902 903
/**
 * See devel_shutdown() which registers this function as a shutdown function. Displays developer information in the footer.
 */
function devel_shutdown_real() {
904
  global $user;
905
  $output = $txt = '';
906

907 908 909 910
  // Set $GLOBALS['devel_shutdown'] = FALSE in order to supress the
  // devel footer for a page.  Not necessary if your page outputs any
  // of the Content-type http headers tested below (e.g. text/xml,
  // text/javascript, etc).  This is is advised where applicable.
911
  if (!devel_silent() && !isset($GLOBALS['devel_shutdown']) && !isset($GLOBALS['devel_redirecting'])) {
912
    // Try not to break non html pages.
913 914 915
    if (function_exists('drupal_get_http_header')) {
      $header = drupal_get_http_header('content-type');
      if ($header) {
916 917
        $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values');
        foreach ($formats as $format) {
918
          if (strstr($header, $format)) {
919 920
            return;
          }
921
        }
922
      }
923
    }
924

925
    if (isset($user) && user_access('access devel information')) {
926
      $queries = Database::getLog('devel', 'default');
927
      $output .= devel_shutdown_summary($queries);
928
      $output .= devel_shutdown_query($queries);
929
    }
930

931
    if ($output) {
932
      // TODO: gzip this text if we are sending a gzip page. see drupal_page_header().
933
      // For some reason, this is not actually printing for cached pages even though it gets executed
934
      // and $output looks good.
935
      print $output;
936
    }
937
  }
938 939
}

940
function devel_shutdown_summary($queries) {
941
  $sum = 0;
942
  $output = '';
943
  list($counts, $query_summary) = devel_query_summary($queries);
944

945
  if (variable_get('devel_query_display', FALSE)) {
946
    // Query log on.
947
    $output .= $query_summary;
948
    $output .= t_safe(' Queries exceeding @threshold ms are <span class="marker">highlighted</span>.', array('@threshold' => variable_get('devel_execution', 5)));
949
  }
950

951 952 953 954
  if (variable_get('dev_timer', 0)) {
    $output .= devel_timer();
  }

955
  if (variable_get('devel_xhprof_enabled', FALSE)) {
956
    $output .= ' ' . devel_xhprof_link($GLOBALS['devel_run_id']);
957 958
  }

959 960
  $output .= devel_shutdown_memory();

961 962 963
  if ($output) {
    return '<div class="dev-query">' . $output . '</div>';
  }
964 965
}

966
function devel_shutdown_xhprof() {
967 968 969
  $namespace = variable_get('site_name', '');  // namespace for your application
  $xhprof_data = xhprof_disable();
  $xhprof_runs = new XHProfRuns_Default();
970 971 972 973
  return $xhprof_runs->save_run($xhprof_data, $namespace);
}

function devel_xhprof_link($run_id, $type = 'link') {
974 975
  // @todo: render results from within Drupal.
  $xhprof_url = variable_get('devel_xhprof_url', '');
976
  $namespace = variable_get('site_name', '');  // namespace for your application
977
  if ($xhprof_url) {
978 979
    $url  = $xhprof_url . "/index.php?run=$run_id&source=$namespace";
    return $type == 'url' ? $url : t('<a href="@xhprof">XHProf output</a>. ', array('@xhprof' => $url));
980 981 982
  }
}

983 984 985 986 987 988 989 990 991 992 993 994 995
function devel_shutdown_memory() {
  global $memory_init;

  if (variable_get('dev_mem', FALSE)) {
    $memory_shutdown = memory_get_usage();
    $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));
    $msg = '<span class="dev-memory-usages"> Memory used at: devel_boot()=<strong>@memory_boot</strong> MB, devel_shutdown()=<strong>@memory_shutdown</strong> MB, PHP peak=<strong>@memory_peak</strong> MB.</span>';
    // theme() may not be available. not t() either.
    return t_safe($msg, $args);
  }
}

function devel_shutdown_query($queries) {
996 997 998 999
  if (!empty($queries)) {
    if (function_exists('theme_get_registry') && theme_get_registry()) {
      // Safe to call theme('table).
      list($counts, $query_summary) = devel_query_summary($queries);
moshe weitzman's avatar
moshe weitzman committed
1000
      $output = devel_query_table($queries, $counts);
1001

1002 1003 1004 1005
      // Save all queries to a file in temp dir. Retrieved via AJAX.
      devel_query_put_contents($queries);
    }
    else {
moshe weitzman's avatar
moshe weitzman committed
1006
      $output = '</div>' . dprint_r($queries, TRUE);
1007
    }
moshe weitzman's avatar
moshe weitzman committed
1008
    return $output;
1009 1010 1011
  }
}

1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
// Write the variables information to the a file. It will be retrieved on demand via AJAX.
function devel_query_put_contents($queries) {
  $request_id = mt_rand(1, 1000000);
  $path = "temporary://devel_querylog";

  // Create the devel_querylog within the temp folder, if needed.
  file_prepare_directory($path, FILE_CREATE_DIRECTORY);

  // Occassionally wipe the querylog dir so that files don't accumulate.
  if (mt_rand(1, 1000) == 401) {
    devel_empty_dir($path);
  }

1025 1026
  $path .= "/$request_id.txt";
  $path = file_stream_wrapper_uri_normalize($path);
moshe weitzman's avatar