devel.module

Tracking 5.x-1.x branch
  1. drupal
    1. 5 contributions/devel/devel.module
    2. 6 contributions/devel/devel.module
    3. 7 contributions/devel/devel.module
    4. 8 contributions/devel/devel.module

Functions & methods

NameDescription
backtrace_error_handler
dargsPrints the arguments for passed into the current function
db_querydDebugging version of db_query().
dd
ddebug_backtracePrint the function call stack.
devel_admin_settings
devel_blockImplementation of hook_block().
devel_cache_clearMenu callback; clears all caches, then redirects to the previous page.
devel_db_query
devel_execute_formGenerates the execute block form.
devel_execute_form_submitProcess PHP execute form submissions.
devel_exit
devel_formsImplementation of hook_forms().
devel_form_alterImplementation of hook_form_alter().
devel_function_referenceReturns a list of all currently defined user functions in the current request lifecycle, with links their documentation.
devel_helpImplementation of hook_help().
devel_initImplementation of hook_init(). Avoids custom error handling for better behavior when stepping though in a debugger.
devel_is_compatible_optimizer
devel_load_objectMenu callback; prints the loaded structure of the current node/user.
devel_menuImplementation of hook_menu().
devel_menu_reset_formMenu callback; clear the database, resetting the menu to factory defaults.
devel_menu_reset_form_submitProcess menu reset form submission.
devel_permImplementation of hook_perm().
devel_print_object
devel_queries
devel_queries_empty
devel_query_summary
devel_query_tableAdds a table at the bottom of the page cataloguing data on all the database queries that were made to generate the page.
devel_rebuild_node_comment_statisticsUpdate node_comment_statistics table for nodes with comments. TODO: if 2 comments have exact same timestamp, the function can get wrong uid and name fields. Handles when comment timestamps have been manually set in admin
devel_rebuild_node_comment_statistics_pageMenu callback. Rebuild node _comment_stats table.
devel_reinstallMenu callback; Display a list of installed modules with the option to reinstall them via hook_install.
devel_reinstall_form
devel_reinstall_submitProcess reinstall menu form submissions.
devel_render_objectMenu callback; prints the renderstructure of the current node.
devel_sessionMenu callback: display the session.
devel_shutdownSee devel_init() which registers this function as a shutdown function. Displays developer information in the footer.
devel_store_queries
devel_switch_userSwitch from original user to another user and back.
devel_switch_user_form
devel_switch_user_form_submit
devel_switch_user_form_validate
devel_switch_user_list
devel_table_keysEnable or disable indexes for a given table. Useful during a bulk import.
devel_timerDisplays page execution time at the bottom of the page.
devel_variable
devel_variable_edit
devel_variable_edit_submit
devel_variable_pageMenu callback; display all variables.
devel_variable_submit
devel_views_object
dfbCalls the http://www.firephp.org/ fb() function if it is found.
dpmPrint a variable to the 'message' area of the page. Uses drupal_set_message()
dprAn alias for dprint_r(). Saves carpal tunnel syndrome.
dprint_rPretty-print a variable to the browser. Displays only for users with proper permissions. If you want a string returned instead of a print, use the 2nd param.
drupal_debug
dsm
dvmVar_dump() a variable to the 'message' area of the page. Uses drupal_set_message()
dvrLike dpr, but uses var_dump() instead
has_krumo
kdevel_print_objectPrint an object or array using either Krumo (if installed) or devel_print_object()
kprAn alias for kprint_r(). Saves carpal tunnel syndrome.
kprint_r
krumo_ob
sequence_resetSet sequences table to a sane state. Useful after a bulk import.
theme_devel_variable
t_safe
wgetSave contents of an url to filesystem. Works for images.
_devel_table_sort

Constants

NameDescription
DEVEL_CURRENT_DRUPAL_VERSION
DEVEL_ERROR_HANDLER_BACKTRACE
DEVEL_ERROR_HANDLER_NONE
DEVEL_ERROR_HANDLER_STANDARD
DEVEL_MIN_TEXTAREA
DEVEL_QUERY_SORT_BY_DURATION
DEVEL_QUERY_SORT_BY_SOURCE

File

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