'. t('This is a list of defined user functions that generated this current request lifecycle. Click on a function name to view its documention.') .'

'; case 'devel/session': return '

'. t('Here are the contents of your $_SESSION variable.') .'

'; case 'devel/variable': $api = variable_get('devel_api_url', 'api.drupal.org'); return '

'. t('This is a list of the variables and their values currently stored in variables table and the $conf array of your settings.php file. These variables are usually accessed with variable_get() and variable_set(). 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")) .'

'; case 'devel/reinstall': return t('Warning - will delete your module tables and variables.'); } } /** * Implements hook_modules_installed(). * * @see devel_install() */ function devel_modules_installed($modules) { if (in_array('menu', $modules)) { $menu = array( 'menu_name' => 'devel', 'title' => t('Development'), 'description' => t('Development link'), ); menu_save($menu); } } /** * Implements hook_menu(). */ function devel_menu() { // Note: we can't dynamically append destination to querystring. Do so at theme layer. Fix in D7? $items['devel/cache/clear'] = array( 'title' => 'Empty cache', 'page callback' => 'devel_cache_clear', 'description' => 'Clear the CSS cache and all database cache tables which store page, node, theme and variable caches.', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/reference'] = array( 'title' => 'Function reference', 'description' => 'View a list of currently defined user functions with documentation links.', 'page callback' => 'devel_function_reference', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/reinstall'] = array( 'title' => 'Reinstall modules', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_reinstall'), 'description' => 'Run hook_uninstall() and then hook_install() for a given module.', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/menu/reset'] = array( 'title' => 'Rebuild menus', 'description' => 'Rebuild menu based on hook_menu() and revert any custom changes. All menu items return to their default settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_menu_rebuild'), 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $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'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/variable'] = array( 'title' => 'Variable editor', 'description' => 'Edit and delete site variables.', 'page callback' => 'devel_variable_page', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); // we don't want the abbreviated version provided by status report $items['devel/phpinfo'] = array( 'title' => 'PHPinfo()', 'description' => 'View your server\'s PHP configuration', 'page callback' => 'devel_phpinfo', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $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'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/theme/registry'] = array( 'title' => 'Theme registry', 'description' => 'View a list of available theme functions across the whole site.', 'page callback' => 'devel_theme_registry', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $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', ); $items['devel/field/info'] = array( 'title' => 'Field info', 'description' => 'View fields information across the whole site.', 'page callback' => 'devel_field_info_page', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/elements'] = array( 'title' => 'Hook_elements()', 'description' => 'View the active form/render elements for this site.', 'page callback' => 'devel_elements_page', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/variable/edit/%'] = array( 'title' => 'Variable editor', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_variable_edit', 3), 'access arguments' => array('access devel information'), 'type' => MENU_CALLBACK, 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/session'] = array( 'title' => 'Session viewer', 'description' => 'List the contents of $_SESSION.', 'page callback' => 'devel_session', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $items['devel/switch'] = array( 'title' => 'Switch user', 'page callback' => 'devel_switch_user', 'access arguments' => array('switch users'), 'type' => MENU_CALLBACK, 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); $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'), 'file' => 'devel.pages.inc', 'type' => MENU_CALLBACK ); $items['devel/arguments'] = array( 'title' => 'Arguments query', 'page callback' => 'devel_querylog_arguments', 'description' => 'Return a given query, with arguments instead of placeholders. Used by query log', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'type' => MENU_CALLBACK ); $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', ); // Duplicate path in 2 different menus. See http://drupal.org/node/601788. $items['devel/settings'] = array( 'title' => 'Devel settings', 'description' => 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_admin_settings'), 'access arguments' => array('administer site configuration'), 'file' => 'devel.admin.inc', 'menu_name' => 'devel', ); $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 block administration page.', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_admin_settings'), 'file' => 'devel.admin.inc', 'access arguments' => array('administer site configuration'), ); $items['node/%node/devel'] = array( 'title' => 'Devel', 'page callback' => 'devel_load_object', 'page arguments' => array('node', 1), 'access arguments' => array('access devel information'), 'type' => MENU_LOCAL_TASK, 'file' => 'devel.pages.inc', 'weight' => 100, ); $items['node/%node/devel/load'] = array( 'title' => 'Load', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['node/%node/devel/render'] = array( 'title' => 'Render', 'page callback' => 'devel_render_object', 'page arguments' => array('node', 1), 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 100, ); $items['comment/%comment/devel'] = array( 'title' => 'Devel', 'page callback' => 'devel_load_object', 'page arguments' => array('comment', 1), 'access arguments' => array('access devel information'), 'type' => MENU_LOCAL_TASK, 'file' => 'devel.pages.inc', 'weight' => 100, ); $items['comment/%comment/devel/load'] = array( 'title' => 'Load', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['comment/%comment/devel/render'] = array( 'title' => 'Render', 'page callback' => 'devel_render_object', 'page arguments' => array('comment', 1), 'access arguments' => array('access devel information'), 'type' => MENU_LOCAL_TASK, 'file' => 'devel.pages.inc', 'weight' => 100, ); $items['user/%user/devel'] = array( 'title' => 'Devel', 'page callback' => 'devel_load_object', 'page arguments' => array('user', 1), 'access arguments' => array('access devel information'), 'type' => MENU_LOCAL_TASK, 'file' => 'devel.pages.inc', 'weight' => 100, ); $items['user/%user/devel/load'] = array( 'title' => 'Load', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['user/%user/devel/render'] = array( 'title' => 'Render', 'page callback' => 'devel_render_object', 'page arguments' => array('user', 1), 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 100, ); $items['taxonomy/term/%taxonomy_term/devel'] = array( 'title' => 'Devel', 'page callback' => 'devel_load_object', 'page arguments' => array('taxonomy_term', 2, 'term'), 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 100, ); $items['taxonomy/term/%taxonomy_term/devel/load'] = array( 'title' => 'Load', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['taxonomy/term/%taxonomy_term/devel/render'] = array( 'title' => 'Render', 'page callback' => 'devel_render_object', 'page arguments' => array('taxonomy_term', 2, 'term'), 'access arguments' => array('access devel information'), 'type' => MENU_LOCAL_TASK, 'file' => 'devel.pages.inc', 'weight' => 100, ); return $items; } /** * Implements hook_admin_paths(). */ function devel_admin_paths() { $paths = array( 'devel/*' => TRUE, 'node/*/devel' => TRUE, 'node/*/devel/*' => TRUE, 'comment/*/devel' => TRUE, 'comment/*/devel/*' => TRUE, 'user/*/devel' => TRUE, 'user/*/devel/*' => TRUE, 'taxonomy/term/*/devel' => TRUE, 'taxonomy/term/*/devel/*' => TRUE, ); return $paths; } function devel_menu_need_destination() { return array('devel/cache/clear', 'devel/reinstall', 'devel/menu/reset', 'devel/variable', 'admin/reports/status/run-cron'); } /** * An implementation of hook_menu_link_alter(). Flag this link as needing alter at display time. * This is more robust than setting alter in hook_menu(). * @see devel_translated_menu_link_alter(). * **/ function devel_menu_link_alter(&$item) { if (in_array($item['link_path'], devel_menu_need_destination()) || $item['link_path'] == 'devel/menu/item') { $item['options']['alter'] = TRUE; } } /** * An implementation of hook_translated_menu_item_alter(). Append dynamic * querystring 'destination' to several of our own menu items. * **/ function devel_translated_menu_link_alter(&$item) { if (in_array($item['href'], devel_menu_need_destination())) { $item['localized_options']['query'] = drupal_get_destination(); } elseif ($item['href'] == 'devel/menu/item') { $item['localized_options']['query'] = array('path' => $_GET['q']); } } /** * Implementation of hook_theme() */ function devel_theme() { return array( 'devel_querylog' => array( 'variables' => array('header' => array(), 'rows' => array()), ), 'devel_querylog_row' => array( 'variables' => array('row' => array()), ), ); } /** * Implementation of hook_init(). */ function devel_init() { if (!devel_silent()) { if (user_access('access devel information')) { devel_set_handler(variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD)); // We want to include the class early so that anyone may call krumo() as needed. See http://krumo.sourceforge.net/ has_krumo(); // 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 $firephp_path = libraries_get_path('FirePHPCore'); $firephp_path = ($firephp_path ? $firephp_path . '/lib/FirePHPCore/' : ''); $chromephp_path = libraries_get_path('chromephp'); } else { $firephp_path = './' . drupal_get_path('module', 'devel') . '/FirePHPCore/lib/FirePHPCore/'; $chromephp_path = './' . drupal_get_path('module', 'devel') . '/chromephp'; } // Include FirePHP if it exists. if (!empty($firephp_path) && file_exists($firephp_path . 'fb.php')) { include_once $firephp_path . 'fb.php'; include_once $firephp_path . 'FirePHP.class.php'; } // Include ChromePHP if it exists. if (!empty($chromephp_path) && file_exists($chromephp_path .= '/ChromePhp.php')) { include_once $chromephp_path; } // Add CSS for query log if should be displayed. if (variable_get('devel_query_display', 0)) { drupal_add_css(drupal_get_path('module', 'devel') . '/devel.css'); drupal_add_js(drupal_get_path('module', 'devel') . '/devel.js'); } } } 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')) { drupal_set_message(t('The theme registry is being rebuilt on every request. Remember to turn off this feature on production websites.', array("!url" => url('admin/config/development/devel')))); } } } } function devel_set_message($msg, $type = NULL) { $function = function_exists('drush_log') ? 'drush_log' : 'drupal_set_message'; $function($msg, $type); } // Return boolean. No need for cache here. function has_krumo() { // see README.txt or just download from http://krumo.sourceforge.net/ @include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'devel') .'/krumo/class.krumo.php'; return function_exists('krumo') && !drupal_is_cli(); } /** * Decide whether or not to print a debug variable using krumo(). * * @param $input * @return boolean */ function merits_krumo($input) { return (is_object($input) || is_array($input)) && has_krumo() && variable_get('devel_krumo_skin', '') != 'disabled'; } /** * Calls the http://www.firephp.org/ fb() function if it is found. * * @return void */ function dfb() { if (function_exists('fb') && user_access('access devel information') && !headers_sent()) { $args = func_get_args(); call_user_func_array('fb', $args); } } /** * Calls dfb() to output a backtrace. */ function dfbt($label) { dfb($label, FirePHP::TRACE); } /** * 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); } } /** * Implements hook_watchdog(). */ function devel_watchdog(array $log_entry) { if (class_exists('FirePHP') && !drupal_is_cli()) { switch ($log_entry['severity']) { case WATCHDOG_EMERGENCY: 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'; } $function = function_exists('decode_entities') ? 'decode_entities' : 'html_entity_decode'; $watchdog = array( 'type' => $log_entry['type'], 'message' => $function(strtr($log_entry['message'], (array)$log_entry['variables'])), ); if (isset($log_entry['link'])) { $watchdog['link'] = $log_entry['link']; } dfb($watchdog, $type); } function devel_set_handler($handler) { switch ($handler) { case DEVEL_ERROR_HANDLER_STANDARD: // do nothing break; case DEVEL_ERROR_HANDLER_BACKTRACE: if (has_krumo()) { set_error_handler('backtrace_error_handler'); } break; case DEVEL_ERROR_HANDLER_NONE: restore_error_handler(); break; } } function devel_silent() { // isset($_GET['q']) is needed when calling the front page. q is not set. // Don't interfere with private files/images. return function_exists('drupal_is_cli') && drupal_is_cli() || (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'ApacheBench') !== FALSE) || !empty($_REQUEST['XDEBUG_PROFILE']) || isset($GLOBALS['devel_shutdown']) || strstr($_SERVER['PHP_SELF'], 'update.php') || (isset($_GET['q']) && ( in_array($_GET['q'], array( 'admin/content/node-settings/rebuild')) || substr($_GET['q'], 0, strlen('system/files')) == 'system/files' || substr($_GET['q'], 0, strlen('batch')) == 'batch' || substr($_GET['q'], 0, strlen('file/ajax')) == 'file/ajax') ); } function devel_xhprof_enable() { if (devel_xhprof_is_enabled()) { 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); } } } function devel_xhprof_is_enabled() { return extension_loaded('xhprof') && variable_get('devel_xhprof_enabled', FALSE); } /** * Implementation of hook_boot(). Runs even for cached pages. */ function devel_boot() { // Initialize XHProf. devel_xhprof_enable(); if (!devel_silent()) { if (variable_get('dev_mem', 0)) { global $memory_init; $memory_init = memory_get_usage(); } if (devel_query_enabled()) { @include_once DRUPAL_ROOT . '/includes/database/log.inc'; Database::startLog('devel');; } } // 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'); drupal_load('module', 'user'); drupal_register_shutdown_function('devel_shutdown'); } function backtrace_error_handler($error_level, $message, $filename, $line, $context) { // Hide stack trace and parameters from unqualified users. if (!user_access('access devel information')) { return _drupal_error_handler($error_level, $message, $filename, $line, $context); } // Don't respond to the error if it was suppressed with a '@' if (error_reporting() == 0) { return; } // Don't respond to warning caused by ourselves. if (preg_match('#Cannot modify header information - headers already sent by \\([^\\)]*[/\\\\]devel[/\\\\]#', $message)) { return; } if ($error_level & error_reporting()) { // Only write each distinct NOTICE message once, as repeats do not give any // further information and can choke the page output. if ($error_level == E_NOTICE) { static $written = array(); if (!empty($written[$line][$filename][$message])) { return; } $written[$line][$filename][$message] = TRUE; } require_once DRUPAL_ROOT . '/includes/errors.inc'; $types = drupal_error_levels(); $type = $types[$error_level]; $backtrace = debug_backtrace(); array_shift($backtrace); $variables = array('%error' => $type[0], '%message' => $message, '%function' => $backtrace[0]['function'] .'()', '%file' => $filename, '%line' => $line); $counter = count($backtrace); // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher. // (This is Drupal's error_level, which is different from $error_level, // and we purposely ignore the difference between _SOME and _ALL, // see #970688!) if (variable_get('error_level', 1) >= 1) { foreach ($backtrace as $call) { $nicetrace[--$counter . ': ' . $call['function']] = $call; } print t('%error: %message in %function (line %line of %file).', $variables) ." =>\n"; krumo($nicetrace); } watchdog('php', '%error: %message in %function (line %line of %file).', $variables, $type[1]); } } /** * Implement hook_permission(). */ function devel_permission() { return array( 'access devel information' => array( 'description' => t('View developer output like variable printouts, query log, etc.'), 'title' => t('Access developer information'), 'restrict access' => TRUE, ), 'execute php code' => array( 'title' => t('Execute PHP code'), 'description' => t('Run arbitrary PHP from a block.'), 'restrict access' => TRUE, ), 'switch users' => array( 'title' => t('Switch users'), 'description' => t('Become any user on the site with just a click.'), 'restrict access' => TRUE, ), ); } 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, ); return $blocks; } /** * Implementation of hook_block_configure(). */ function devel_block_configure($delta) { if ($delta == 'switch_user') { $form['list_size'] = array( '#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', ); $form['include_anon'] = array( '#type' => 'checkbox', '#title' => t('Include %anonymous', array('%anonymous' => format_username(drupal_anonymous_user()))), '#default_value' => variable_get('devel_switch_user_include_anon', FALSE), ); $form['show_form'] = array( '#type' => 'checkbox', '#title' => t('Allow entering any user name'), '#default_value' => variable_get('devel_switch_user_show_form', TRUE), ); return $form; } } function devel_block_save($delta, $edit = array()) { if ($delta == 'switch_user') { 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']); } } function devel_block_view($delta) { $block = array(); switch ($delta) { case 'switch_user': $block = devel_block_switch_user(); break; case 'execute_php': if (user_access('execute php code')) { $block['content'] = drupal_get_form('devel_execute_block_form'); } break; } return $block; } function devel_block_switch_user() { $links = devel_switch_user_list(); if (!empty($links) || user_access('switch users')) { $block['subject'] = t('Switch user'); $build['devel_links'] = array('#theme' => 'links', '#links' => $links); if (variable_get('devel_switch_user_show_form', TRUE)) { $build['devel_form'] = drupal_get_form('devel_switch_user_form'); } $block['content'] = $build; return $block; } } function devel_switch_user_list() { global $user; $links = array(); if (user_access('switch users')) { $list_size = variable_get('devel_switch_user_list_size', 10); if ($include_anon = ($user->uid && variable_get('devel_switch_user_include_anon', FALSE))) { --$list_size; } $dest = drupal_get_destination(); // Try to find at least $list_size users that can switch. // Inactive users are omitted from all of the following db selects. $roles = user_roles(TRUE, 'switch users'); $query = db_select('users', 'u'); $query->addField('u', 'uid'); $query->addField('u', 'access'); $query->distinct(); $query->condition('u.uid', 0, '>'); $query->condition('u.status', 0, '>'); $query->orderBy('u.access', 'DESC'); $query->range(0, $list_size); if (!isset($roles[DRUPAL_AUTHENTICATED_RID])) { $query->leftJoin('users_roles', 'r', 'u.uid = r.uid'); $or_condition = db_or(); $or_condition->condition('u.uid', 1); if (!empty($roles)) { $or_condition->condition('r.rid', array_keys($roles), 'IN'); } $query->condition($or_condition); } $uids = $query->execute()->fetchCol(); $accounts = user_load_multiple($uids); foreach ($accounts as $account) { $links[$account->uid] = array( 'title' => drupal_placeholder(format_username($account)), 'href' => 'devel/switch/'. $account->name, 'query' => $dest, 'attributes' => array('title' => t('This user can switch back.')), 'html' => TRUE, 'last_access' => $account->access, ); } $num_links = count($links); if ($num_links < $list_size) { // If we don't have enough, add distinct uids until we hit $list_size. $uids = db_query_range('SELECT uid FROM {users} WHERE uid > 0 AND uid NOT IN (:uids) AND status > 0 ORDER BY access DESC', 0, $list_size - $num_links, array(':uids' => array_keys($links)))->fetchCol(); $accounts = user_load_multiple($uids); foreach ($accounts as $account) { $links[$account->uid] = array( 'title' => format_username($account), 'href' => 'devel/switch/'. $account->name, 'query' => $dest, 'attributes' => array('title' => t('Caution: this user will be unable to switch back.')), 'last_access' => $account->access, ); } uasort($links, '_devel_switch_user_list_cmp'); } if ($include_anon) { $link = array( 'title' => format_username(drupal_anonymous_user()), 'href' => 'devel/switch', 'query' => $dest, 'attributes' => array('title' => t('Caution: the anonymous user will be unable to switch back.')), ); if (user_access('switch users', drupal_anonymous_user())) { $link['title'] = drupal_placeholder($link['title']); $link['attributes'] = array('title' => t('This user can switch back.')); $link['html'] = TRUE; } $links[] = $link; } } return $links; } /** * 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']; } function devel_switch_user_form() { $form['username'] = array( '#type' => 'textfield', '#description' => t('Enter username'), '#autocomplete_path' => 'user/autocomplete', '#maxlength' => USERNAME_MAX_LENGTH, '#size' => 16, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Switch'), ); return $form; } function devel_doc_function_form() { $version = devel_get_core_version(VERSION); $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( '#type' => 'submit', '#value' => t('Submit'), ); return $form; } function devel_doc_function_form_submit($form, &$form_state) { $version = $form_state['values']['version']; $function = $form_state['values']['function']; $api = variable_get('devel_api_url', 'api.drupal.org'); $form_state['redirect'] = "http://$api/api/function/$function/$version"; } function devel_switch_user_form_validate($form, &$form_state) { if (!$account = user_load_by_name($form_state['values']['username'])) { form_set_error('username', t('Username not found')); } } function devel_switch_user_form_submit($form, &$form_state) { $form_state['redirect'] = 'devel/switch/'. $form_state['values']['username']; } /** * Implements hook_drupal_goto_alter(). */ function devel_drupal_goto_alter($path, $options, $http_response_code) { global $user; if (isset($path) && !devel_silent()) { // The page we are leaving is a drupal_goto(). Present a redirection page // so that the developer can see the intermediate query log. // We don't want to load user module here, so keep function_exists() call. if (isset($user) && function_exists('user_access') && user_access('access devel information') && variable_get('devel_redirect_page', 0)) { $destination = function_exists('url') ? url($path, $options) : $path; $output = t_safe('

The user is being redirected to @destination.

', array('@destination' => $destination)); drupal_deliver_page($output); // Don't allow the automatic redirect to happen. exit(); } else { $GLOBALS['devel_redirecting'] = TRUE; } } } /** * Implements hook_library_alter(). */ function devel_library_alter(&$libraries, $module) { // Use an uncompressed version of jQuery for debugging. if ($module === 'system' && variable_get('devel_use_uncompressed_jquery', FALSE) && isset($libraries['jquery'])) { // Make sure we're not changing the jQuery version used on the site. if (version_compare($libraries['jquery']['version'], '1.4.4', '=')) { $libraries['jquery']['js'] = array( drupal_get_path('module', 'devel') . '/jquery-1.4.4-uncompressed.js' => array('weight' => JS_LIBRARY - 20), ); } else { if (!devel_silent() && user_access('access devel information')) { drupal_set_message(t('jQuery could not be replaced with an uncompressed version of 1.4.4, because jQuery @version is running on the site.', array('@version' => $libraries['jquery']['version']))); } } } } /** * See devel_start() which registers this function as a shutdown function. */ function devel_shutdown() { // Register the real shutdown function so it runs later than other shutdown functions. drupal_register_shutdown_function('devel_shutdown_real'); global $devel_run_id; $devel_run_id = devel_xhprof_is_enabled() ? devel_shutdown_xhprof(): NULL; if ($devel_run_id && function_exists('drush_log')) { drush_log('xhprof link: ' . devel_xhprof_link($devel_run_id, 'url'), 'notice'); } } function devel_page_alter($page) { if (variable_get('devel_page_alter', FALSE) && user_access('access devel information')) { dpm($page, 'page'); } } // 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; } /** * See devel_shutdown() which registers this function as a shutdown function. Displays developer information in the footer. */ function devel_shutdown_real() { global $user; $output = $txt = ''; // 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. if (!devel_silent() && !isset($GLOBALS['devel_shutdown']) && !isset($GLOBALS['devel_redirecting'])) { // Try not to break non html pages. if (function_exists('drupal_get_http_header')) { $header = drupal_get_http_header('content-type'); if ($header) { $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values'); foreach ($formats as $format) { if (strstr($header, $format)) { return; } } } } if (isset($user) && user_access('access devel information')) { $queries = (devel_query_enabled() ? Database::getLog('devel', 'default') : NULL); $output .= devel_shutdown_summary($queries); $output .= devel_shutdown_query($queries); } if ($output) { // TODO: gzip this text if we are sending a gzip page. see drupal_page_header(). // For some reason, this is not actually printing for cached pages even though it gets executed // and $output looks good. print $output; } } } function devel_shutdown_summary($queries) { $sum = 0; $output = ''; list($counts, $query_summary) = devel_query_summary($queries); if (variable_get('devel_query_display', FALSE)) { // Query log on. $output .= $query_summary; $output .= t_safe(' Queries exceeding @threshold ms are highlighted.', array('@threshold' => variable_get('devel_execution', 5))); } if (variable_get('dev_timer', 0)) { $output .= devel_timer(); } if (devel_xhprof_is_enabled()) { $output .= ' ' . devel_xhprof_link($GLOBALS['devel_run_id']); } $output .= devel_shutdown_memory(); if ($output) { return '
' . $output . '
'; } } function devel_shutdown_xhprof() { $namespace = variable_get('site_name', ''); // namespace for your application $xhprof_data = xhprof_disable(); $xhprof_runs = new XHProfRuns_Default(); return $xhprof_runs->save_run($xhprof_data, $namespace); } function devel_xhprof_link($run_id, $type = 'link') { // @todo: render results from within Drupal. $xhprof_url = variable_get('devel_xhprof_url', ''); $namespace = variable_get('site_name', ''); // namespace for your application if ($xhprof_url) { $url = $xhprof_url . "/index.php?run=$run_id&source=$namespace"; return $type == 'url' ? $url : t('XHProf output. ', array('@xhprof' => $url)); } } 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 = ' Memory used at: devel_boot()=@memory_boot MB, devel_shutdown()=@memory_shutdown MB, PHP peak=@memory_peak MB.'; // theme() may not be available. not t() either. return t_safe($msg, $args); } } function devel_shutdown_query($queries) { 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); $output = devel_query_table($queries, $counts); // Save all queries to a file in temp dir. Retrieved via AJAX. devel_query_put_contents($queries); } else { $output = '' . dprint_r($queries, TRUE); } return $output; } } // 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); } $path .= "/$request_id.txt"; $path = file_stream_wrapper_uri_normalize($path); // Save queries as a json array. Suppress errors due to recursion () file_put_contents($path, @json_encode($queries)); $settings['devel'] = array( // A random string that is sent to the browser. It enables the AJAX to retrieve queries from this request. 'request_id' => $request_id, ); print '\n"; } function devel_query_enabled() { return method_exists('Database', 'getLog') && variable_get('devel_query_display', FALSE); } function devel_query_summary($queries) { if (variable_get('devel_query_display', FALSE) && is_array($queries)) { $sum = 0; foreach ($queries as $query) { $text[] = $query['query']; $sum += $query['time']; } $counts = array_count_values($text); return array($counts, t_safe('Executed @queries queries in @time ms.', array('@queries' => count($queries), '@time' => round($sum * 1000, 2)))); } } function t_safe($string, $args) { // get_t caused problems here with theme registry after changing on admin/build/modules. the theme_get_registry call is needed. if (function_exists('t') && function_exists('theme_get_registry')) { theme_get_registry(); return t($string, $args); } else { strtr($string, $args); } } function devel_get_core_version($version) { $version_parts = explode('.', $version); // Map from 4.7.10 -> 4.7 if ($version_parts[0] < 5) { return $version_parts[0] .'.'. $version_parts[1]; } // Map from 5.5 -> 5 or 6.0-beta2 -> 6 else { return $version_parts[0]; } } // See http://drupal.org/node/126098 function devel_is_compatible_optimizer() { ob_start(); phpinfo(); $info = ob_get_contents(); ob_end_clean(); // Match the Zend Optimizer version in the phpinfo information $found = preg_match('/Zend Optimizer v([0-9])\.([0-9])\.([0-9])/', $info, $matches); if ($matches) { $major = $matches[1]; $minor = $matches[2]; $build = $matches[3]; if ($major >= 3) { if ($minor >= 3) { return TRUE; } elseif ($minor == 2 && $build >= 8) { return TRUE; } else { return FALSE; } } else { return FALSE; } } else { return TRUE; } } /** * Generates the execute block form. */ function devel_execute_block_form() { $form['execute'] = array( '#type' => 'fieldset', '#title' => t('Execute PHP Code'), '#collapsible' => TRUE, '#collapsed' => (!isset($_SESSION['devel_execute_code'])), ); $form['#submit'] = array('devel_execute_form_submit'); return array_merge_recursive($form, devel_execute_form()); } /** * Generates the execute form. */ function devel_execute_form() { $form['execute']['code'] = array( '#type' => 'textarea', '#title' => t('PHP code to execute'), '#description' => t('Enter some code. Do not use <?php ?> tags.'), '#default_value' => (isset($_SESSION['devel_execute_code']) ? $_SESSION['devel_execute_code'] : ''), '#rows' => 20, ); $form['execute']['op'] = array('#type' => 'submit', '#value' => t('Execute')); $form['#redirect'] = FALSE; if (isset($_SESSION['devel_execute_code'])) { unset($_SESSION['devel_execute_code']); } return $form; } /** * Process PHP execute form submissions. */ function devel_execute_form_submit($form, &$form_state) { ob_start(); print eval($form_state['values']['code']); $_SESSION['devel_execute_code'] = $form_state['values']['code']; dsm(ob_get_clean()); } /** * Switch from original user to another user and back. * We don't call session_save_session() because we really want to change users. Usually unsafe! * * @param $name The username to switch to, or NULL to log out. */ function devel_switch_user($name = NULL) { global $user; if ($user->uid) { module_invoke_all('user_logout', $user); } if (isset($name) && $account = user_load_by_name($name)) { $old_uid = $user->uid; $user = $account; $user->timestamp = time() - 9999; if (!$old_uid) { // Switch from anonymous to authorized. drupal_session_regenerate(); } $edit = array(); user_module_invoke('login', $edit, $user); } elseif ($user->uid) { session_destroy(); } drupal_goto(); } /** * Print an object or array using either Krumo (if installed) or devel_print_object() * * @param $object * array or object to print * @param $prefix * prefixing for output items */ function kdevel_print_object($object, $prefix = NULL) { return has_krumo() ? krumo_ob($object) : devel_print_object($object, $prefix); } // Save krumo htlm using output buffering. function krumo_ob($object) { ob_start(); krumo($object); $output = ob_get_contents(); ob_end_clean(); return $output; } /** * Display an object or array * * @param $object * the object or array * @param $prefix * prefix for the output items (example "$node->", "$user->", "$") * @param $header * set to FALSE to suppress the output of the h3 */ function devel_print_object($object, $prefix = NULL, $header = TRUE) { drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css'); $output = '
'; if ($header) { $output .= '

'. t('Display of !type !obj', array('!type' => str_replace(array('$', '->'), '', $prefix), '!obj' => gettype($object))) .'

'; } $output .= _devel_print_object($object, $prefix); $output .= '
'; return $output; } /** * Recursive (and therefore magical) function goes through an array or object and * returns a nicely formatted listing of its contents. * * @param $obj * array or object to recurse through * @param $prefix * prefix for the output items (example "$node->", "$user->", "$") * @param $parents * used by recursion * @param $object * used by recursion * @return * fomatted html * * @todo * currently there are problems sending an array with a varname */ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FALSE) { static $root_type, $out_format; // TODO: support objects with references. See http://drupal.org/node/234581. if (isset($obj->view)) { return; } if (!isset($root_type)) { $root_type = gettype($obj); if ($root_type == 'object') { $object = TRUE; } } if (is_object($obj)) { $obj = (array)$obj; } if (is_array($obj)) { $output = "
\n"; foreach ($obj as $field => $value) { if ($field == 'devel_flag_reference') { continue; } if (!is_null($parents)) { if ($object) { $field = $parents .'->'. $field; } else { if (is_int($field)) { $field = $parents .'['. $field .']'; } else { $field = $parents .'[\''. $field .'\']'; } } } $type = gettype($value); $show_summary = TRUE; $summary = NULL; if ($show_summary) { switch ($type) { case 'string' : case 'float' : case 'integer' : if (strlen($value) == 0) { $summary = t("{empty}"); } elseif (strlen($value) < 40) { $summary = htmlspecialchars($value); } else { $summary = format_plural(drupal_strlen($value), '1 character', '@count characters'); } break; case 'array' : case 'object' : $summary = format_plural(count((array)$value), '1 element', '@count elements'); break; case 'boolean' : $summary = $value ? t('TRUE') : t('FALSE'); break; } } if (!is_null($summary)) { $typesum = '('. $type .', '. $summary .')'; } else { $typesum = '('. $type .')'; } $output .= ''; $output .= "
{$prefix}{$field} $typesum
\n"; $output .= "
\n"; // Check for references. if (is_array($value) && isset($value['devel_flag_reference'])) { $value['devel_flag_reference'] = TRUE; } // Check for references to prevent errors from recursions. if (is_array($value) && isset($value['devel_flag_reference']) && !$value['devel_flag_reference']) { $value['devel_flag_reference'] = FALSE; $output .= _devel_print_object($value, $prefix, $field); } elseif (is_object($value)) { $value->devel_flag_reference = FALSE; $output .= _devel_print_object((array)$value, $prefix, $field, TRUE); } else { $value = is_bool($value) ? ($value ? 'TRUE' : 'FALSE') : $value; $output .= htmlspecialchars(print_r($value, TRUE)) ."\n"; } $output .= "
\n"; } $output .= "
\n"; } return $output; } /** * Adds a table at the bottom of the page cataloguing data on all the database queries that were made to * generate the page. */ function devel_query_table($queries, $counts) { $version = devel_get_core_version(VERSION); $header = array ('ms', '#', 'where', 'ops', 'query', 'target'); $i = 0; $api = variable_get('devel_api_url', 'api.drupal.org'); foreach ($queries as $query) { $function = !empty($query['caller']['class']) ? $query['caller']['class'] . '::' : ''; $function .= $query['caller']['function']; $count = isset($counts[$query['query']]) ? $counts[$query['query']] : 0; $diff = round($query['time'] * 1000, 2); if ($diff > variable_get('devel_execution', 5)) { $cell[$i][] = array ('data' => $diff, 'class' => 'marker'); } else { $cell[$i][] = $diff; } $cell[$i][] = $count; $cell[$i][] = l($function, "http://$api/api/function/$function/$version"); $ops[] = l('P', '', array('attributes' => array('title' => 'Show placeholders', 'class' => 'dev-placeholders', 'qid' => $i))); $ops[] = l('A', '', array('attributes' => array('title' => 'Show arguments', 'class' => 'dev-arguments', 'qid' => $i))); // EXPLAIN only valid for select queries. if (strpos($query['query'], 'UPDATE') === FALSE && strpos($query['query'], 'INSERT') === FALSE && strpos($query['query'], 'DELETE') === FALSE) { $ops[] = l('E', '', array('attributes' => array('title' => 'Show EXPLAIN', 'class' => 'dev-explain', 'qid' => $i))); } $cell[$i][] = implode(' ', $ops); // 3 divs for each variation of the query. Last 2 are hidden by default. $placeholders = '
' . check_plain($query['query']) . "
\n"; $args = '' . "\n"; $explain = '' . "\n"; $cell[$i][] = array( 'id' => "devel-query-$i", 'data' => $placeholders . $args . $explain, ); $cell[$i][] = $query['target']; $i++; unset($diff, $count, $ops); } if (variable_get('devel_query_sort', DEVEL_QUERY_SORT_BY_SOURCE)) { usort($cell, '_devel_table_sort'); } return theme('devel_querylog', array('header' => $header, 'rows' => $cell)); } function theme_devel_querylog_row($variables) { $row = $variables['row']; $i = 0; $output = ''; foreach ($row as $cell) { $i++; if (is_array($cell)) { $data = !empty($cell['data']) ? $cell['data'] : ''; unset($cell['data']); $attr = $cell; } else { $data = $cell; $attr = array(); } if (!empty($attr['class'])) { $attr['class'] .= " cell cell-$i"; } else { $attr['class'] = "cell cell-$i"; } $attr = drupal_attributes($attr); $output .= "
$data
"; } return $output; } function theme_devel_querylog($variables) { $header = $variables['header']; $rows = $variables['rows']; $output = ''; if (!empty($header)) { $output .= "
"; $output .= theme('devel_querylog_row', array('row' => $header)); $output .= "
"; } if (!empty($rows)) { $i = 0; foreach ($rows as $row) { $i++; $zebra = ($i % 2) == 0 ? 'even' : 'odd'; $output .= "
"; $output .= theme('devel_querylog_row', array('row' => $row)); $output .= "
"; } } return $output; } function _devel_table_sort($a, $b) { $a = is_array($a[0]) ? $a[0]['data'] : $a[0]; $b = is_array($b[0]) ? $b[0]['data'] : $b[0]; if ($a < $b) { return 1; } if ($a > $b) { return -1; } return 0; } /** * Displays page execution time at the bottom of the page. */ function devel_timer() { $time = timer_read('page'); return t_safe(' Page execution time was @time ms.', array('@time' => $time)); } // An alias for drupal_debug(). function dd($data, $label = NULL) { return drupal_debug($data, $label); } // Log any variable to a drupal_debug.log in the site's temp directory. // See http://drupal.org/node/314112 function drupal_debug($data, $label = NULL) { ob_start(); print_r($data); $string = ob_get_clean(); if ($label) { $out = $label .': '. $string; } else { $out = $string; } $out .= "\n"; // The temp directory does vary across multiple simpletest instances. $file = 'temporary://drupal_debug.txt'; if (file_put_contents($file, $out, FILE_APPEND) === FALSE) { drupal_set_message(t('The file could not be written.'), 'error'); return FALSE; } } /** * Prints the arguments passed into the current function */ function dargs($always = TRUE) { static $printed; if ($always || !$printed) { $bt = debug_backtrace(); print kdevel_print_object($bt[1]['args']); $printed = TRUE; } } /** * Print a SQL string from a DBTNG Query object. Includes quoted arguments. * * @param $query * A Query object. * @param $return * Whether to return or print the string. Default to FALSE. * @param $name * Optional name for identifying the output. */ function dpq($query, $return = FALSE, $name = NULL) { if (user_access('access devel information')) { $query->preExecute(); $sql = (string) $query; $quoted = array(); $connection = Database::getConnection(); foreach ((array)$query->arguments() as $key => $val) { $quoted[$key] = $connection->quote($val); } $sql = strtr($sql, $quoted); if ($return) { return $sql; } else { dpm($sql, $name); } } } /** * Print a variable to the 'message' area of the page. Uses drupal_set_message() */ function dpm($input, $name = NULL) { if (user_access('access devel information')) { $export = kprint_r($input, TRUE, $name); drupal_set_message($export); } } /** * drupal_var_export() a variable to the 'message' area of the page. Uses drupal_set_message() */ function dvm($input, $name = NULL) { if (user_access('access devel information')) { $export = dprint_r($input, TRUE, $name, 'drupal_var_export', FALSE); drupal_set_message($export); } } // legacy function that was poorly named. use dpm() instead, since the 'p' maps to 'print_r' function dsm($input, $name = NULL) { dpm($input, $name); } /** * An alias for dprint_r(). Saves carpal tunnel syndrome. */ function dpr($input, $return = FALSE, $name = NULL) { return dprint_r($input, $return, $name); } /** * An alias for kprint_r(). Saves carpal tunnel syndrome. */ function kpr($input, $return = FALSE, $name = NULL) { return kprint_r($input, $return, $name); } /** * Like dpr, but uses drupal_var_export() instead */ function dvr($input, $return = FALSE, $name = NULL) { return dprint_r($input, $return, $name, 'drupal_var_export', FALSE); } function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') { // We do not want to krumo() strings and integers and such if (merits_krumo($input)) { if (user_access('access devel information')) { return $return ? (isset($name) ? $name .' => ' : '') . krumo_ob($input) : krumo($input); } } else { return dprint_r($input, $return, $name, $function); } } /** * Pretty-print a variable to the browser (no krumo). * Displays only for users with proper permissions. If * you want a string returned instead of a print, use the 2nd param. */ function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check= TRUE) { if (user_access('access devel information')) { if ($name) { $name .= ' => '; } if ($function == 'drupal_var_export') { include_once DRUPAL_ROOT . '/includes/utility.inc'; $output = drupal_var_export($input); } else { ob_start(); $function($input); $output = ob_get_clean(); } if ($check) { $output = check_plain($output); } if (count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) { // don't use fapi here because sometimes fapi will not be loaded $printed_value = "'; } else { $printed_value = '
'. $name . $output .'
'; } if ($return) { return $printed_value; } else { print $printed_value; } } } /** * Prints a renderable array element to the screen using kprint_r(). * * #pre_render and/or #post_render pass-through callback for kprint_r(). * * @todo Investigate appending to #suffix. * @todo Investigate label derived from #id, #title, #name, and #theme. */ function devel_render() { $args = func_get_args(); // #pre_render and #post_render pass the rendered $element as last argument. kprint_r(end($args)); // #pre_render and #post_render expect the first argument to be returned. return reset($args); } /** * Print the function call stack. */ function ddebug_backtrace() { if (user_access('access devel information')) { $trace = debug_backtrace(); array_shift($trace); $count = count($trace); foreach ($trace as $i => $call) { $key = ($count - $i) . ': ' . $call['function']; $rich_trace[$key] = $call; } if (has_krumo()) { print krumo($rich_trace); } else { dprint_r($rich_trace); } } } // Delete all files in a dir. http://www.plus2net.com/php_tutorial/php-files-delete.php function devel_empty_dir($dir) { foreach (new DirectoryIterator($dir) as $fileInfo) { unlink($fileInfo->getPathname()); } } /* * migration related functions */ /** * Regenerate the data in node_comment_statistics table. Technique comes from * http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101 * * @return void **/ function devel_rebuild_node_comment_statistics() { // Empty table db_truncate('node_comment_statistics')->execute(); // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case when // two comments on the same node share same timestamp. $sql = " INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) ( SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c JOIN ( SELECT c.nid, MAX(c.created) AS created, COUNT(*) AS comment_count FROM {comment} c WHERE status = 1 GROUP BY c.nid ) AS c2 ON c.nid = c2.nid AND c.created = c2.created )"; db_query($sql, array(':published' => COMMENT_PUBLISHED)); // Insert records into the node_comment_statistics for nodes that are missing. $query = db_select('node', 'n'); $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid'); $query->addField('n', 'changed', 'last_comment_timestamp'); $query->addField('n', 'uid', 'last_comment_uid'); $query->addField('n', 'nid'); $query->addExpression('0', 'comment_count'); $query->addExpression('NULL', 'last_comment_name'); $query->isNull('ncs.comment_count'); db_insert('node_comment_statistics') ->from($query) ->execute(); }