1765 lines
56 KiB
Plaintext
1765 lines
56 KiB
Plaintext
<?php
|
|
|
|
// This module holds functions useful for Drupal development.
|
|
// Please contribute!
|
|
|
|
// Suggested profiling and stacktrace library from http://www.xdebug.org/index.php
|
|
|
|
define('DEVEL_QUERY_SORT_BY_SOURCE', 0);
|
|
define('DEVEL_QUERY_SORT_BY_DURATION', 1);
|
|
|
|
define('DEVEL_ERROR_HANDLER_NONE', 0);
|
|
define('DEVEL_ERROR_HANDLER_STANDARD', 1);
|
|
define('DEVEL_ERROR_HANDLER_BACKTRACE', 2);
|
|
|
|
define('DEVEL_MIN_TEXTAREA', 50);
|
|
|
|
/**
|
|
* Implementation of hook_help().
|
|
*/
|
|
function devel_help($section) {
|
|
switch ($section) {
|
|
case 'devel/reference':
|
|
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>';
|
|
case 'devel/session':
|
|
return '<p>'. t('Here are the contents of your <code>$_SESSION</code> variable.') .'</p>';
|
|
case 'devel/variable':
|
|
$api = variable_get('devel_api_url', 'api.drupal.org');
|
|
return '<p>'. t('This is a list of the variables and their values currently stored in variables table and the <code>$conf</code> array of your settings.php file. These variables are usually accessed with <a href="@variable-get-doc">variable_get()</a> and <a href="@variable-set-doc">variable_set()</a>. Variables that are too long can slow down your pages.', array('@variable-get-doc' => "http://$api/api/HEAD/function/variable_get", '@variable-set-doc' => "http://$api/api/HEAD/function/variable_set")) .'</p>';
|
|
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 <a href="' . url('admin/structure/block') . '">block administration</a> 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 <a href="' . url('admin/structure/block') . '">block administration</a> 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 <a href="!url">turn off</a> 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('<p>The user is being redirected to <a href="@destination">@destination</a>.</p>', 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 <span class="marker">highlighted</span>.', 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 '<div class="dev-query">' . $output . '</div>';
|
|
}
|
|
}
|
|
|
|
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('<a href="@xhprof">XHProf output</a>. ', 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 = '<span class="dev-memory-usages"> Memory used at: devel_boot()=<strong>@memory_boot</strong> MB, devel_shutdown()=<strong>@memory_shutdown</strong> MB, PHP peak=<strong>@memory_peak</strong> MB.</span>';
|
|
// theme() may not be available. not t() either.
|
|
return t_safe($msg, $args);
|
|
}
|
|
}
|
|
|
|
function devel_shutdown_query($queries) {
|
|
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 = '</div>' . 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 '<script type="text/javascript">jQuery.extend(Drupal.settings, '. json_encode($settings) .");</script>\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 <code><?php ?></code> 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 = '<div class="devel-obj-output">';
|
|
if ($header) {
|
|
$output .= '<h3>'. t('Display of !type !obj', array('!type' => str_replace(array('$', '->'), '', $prefix), '!obj' => gettype($object))) .'</h3>';
|
|
}
|
|
$output .= _devel_print_object($object, $prefix);
|
|
$output .= '</div>';
|
|
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 = "<dl>\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 .', <em>'. $summary .'</em>)';
|
|
}
|
|
else {
|
|
$typesum = '('. $type .')';
|
|
}
|
|
|
|
$output .= '<span class="devel-attr">';
|
|
$output .= "<dt><span class=\"field\">{$prefix}{$field}</span> $typesum</dt>\n";
|
|
$output .= "<dd>\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 .= "</dd></span>\n";
|
|
}
|
|
$output .= "</dl>\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 = '<div class="dev-placeholders">' . check_plain($query['query']) . "</div>\n";
|
|
$args = '<div class="dev-arguments" style="display: none;"></div>' . "\n";
|
|
$explain = '<div class="dev-explain" style="display: none;"></div>' . "\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 .= "<div $attr>$data</div>";
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
function theme_devel_querylog($variables) {
|
|
$header = $variables['header'];
|
|
$rows = $variables['rows'];
|
|
$output = '';
|
|
if (!empty($header)) {
|
|
$output .= "<div class='devel-querylog devel-querylog-header clear-block'>";
|
|
$output .= theme('devel_querylog_row', array('row' => $header));
|
|
$output .= "</div>";
|
|
}
|
|
if (!empty($rows)) {
|
|
$i = 0;
|
|
foreach ($rows as $row) {
|
|
$i++;
|
|
$zebra = ($i % 2) == 0 ? 'even' : 'odd';
|
|
$output .= "<div class='devel-querylog devel-querylog-$zebra clear-block'>";
|
|
$output .= theme('devel_querylog_row', array('row' => $row));
|
|
$output .= "</div>";
|
|
}
|
|
}
|
|
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 = "<textarea rows=30 style=\"width: 100%;\">\n". $name . $output .'</textarea>';
|
|
}
|
|
else {
|
|
$printed_value = '<pre>'. $name . $output .'</pre>';
|
|
}
|
|
|
|
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();
|
|
}
|