devel.module 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764
  1. <?php
  2. // This module holds functions useful for Drupal development.
  3. // Please contribute!
  4. // Suggested profiling and stacktrace library from http://www.xdebug.org/index.php
  5. define('DEVEL_QUERY_SORT_BY_SOURCE', 0);
  6. define('DEVEL_QUERY_SORT_BY_DURATION', 1);
  7. define('DEVEL_ERROR_HANDLER_NONE', 0);
  8. define('DEVEL_ERROR_HANDLER_STANDARD', 1);
  9. define('DEVEL_ERROR_HANDLER_BACKTRACE', 2);
  10. define('DEVEL_MIN_TEXTAREA', 50);
  11. /**
  12. * Implementation of hook_help().
  13. */
  14. function devel_help($section) {
  15. switch ($section) {
  16. case 'devel/reference':
  17. 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>';
  18. case 'devel/session':
  19. return '<p>'. t('Here are the contents of your <code>$_SESSION</code> variable.') .'</p>';
  20. case 'devel/variable':
  21. $api = variable_get('devel_api_url', 'api.drupal.org');
  22. 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>';
  23. case 'devel/reinstall':
  24. return t('Warning - will delete your module tables and variables.');
  25. }
  26. }
  27. /**
  28. * Implements hook_modules_installed().
  29. *
  30. * @see devel_install()
  31. */
  32. function devel_modules_installed($modules) {
  33. if (in_array('menu', $modules)) {
  34. $menu = array(
  35. 'menu_name' => 'devel',
  36. 'title' => t('Development'),
  37. 'description' => t('Development link'),
  38. );
  39. menu_save($menu);
  40. }
  41. }
  42. /**
  43. * Implements hook_menu().
  44. */
  45. function devel_menu() {
  46. // Note: we can't dynamically append destination to querystring. Do so at theme layer. Fix in D7?
  47. $items['devel/cache/clear'] = array(
  48. 'title' => 'Empty cache',
  49. 'page callback' => 'devel_cache_clear',
  50. 'description' => 'Clear the CSS cache and all database cache tables which store page, node, theme and variable caches.',
  51. 'access arguments' => array('access devel information'),
  52. 'file' => 'devel.pages.inc',
  53. 'menu_name' => 'devel',
  54. );
  55. $items['devel/reference'] = array(
  56. 'title' => 'Function reference',
  57. 'description' => 'View a list of currently defined user functions with documentation links.',
  58. 'page callback' => 'devel_function_reference',
  59. 'access arguments' => array('access devel information'),
  60. 'file' => 'devel.pages.inc',
  61. 'menu_name' => 'devel',
  62. );
  63. $items['devel/reinstall'] = array(
  64. 'title' => 'Reinstall modules',
  65. 'page callback' => 'drupal_get_form',
  66. 'page arguments' => array('devel_reinstall'),
  67. 'description' => 'Run hook_uninstall() and then hook_install() for a given module.',
  68. 'access arguments' => array('access devel information'),
  69. 'file' => 'devel.pages.inc',
  70. 'menu_name' => 'devel',
  71. );
  72. $items['devel/menu/reset'] = array(
  73. 'title' => 'Rebuild menus',
  74. 'description' => 'Rebuild menu based on hook_menu() and revert any custom changes. All menu items return to their default settings.',
  75. 'page callback' => 'drupal_get_form',
  76. 'page arguments' => array('devel_menu_rebuild'),
  77. 'access arguments' => array('access devel information'),
  78. 'file' => 'devel.pages.inc',
  79. 'menu_name' => 'devel',
  80. );
  81. $items['devel/menu/item'] = array(
  82. 'title' => 'Menu item',
  83. 'description' => 'Details about a given menu item.',
  84. 'page callback' => 'devel_menu_item',
  85. 'access arguments' => array('access devel information'),
  86. 'file' => 'devel.pages.inc',
  87. 'menu_name' => 'devel',
  88. );
  89. $items['devel/variable'] = array(
  90. 'title' => 'Variable editor',
  91. 'description' => 'Edit and delete site variables.',
  92. 'page callback' => 'devel_variable_page',
  93. 'access arguments' => array('access devel information'),
  94. 'file' => 'devel.pages.inc',
  95. 'menu_name' => 'devel',
  96. );
  97. // we don't want the abbreviated version provided by status report
  98. $items['devel/phpinfo'] = array(
  99. 'title' => 'PHPinfo()',
  100. 'description' => 'View your server\'s PHP configuration',
  101. 'page callback' => 'devel_phpinfo',
  102. 'access arguments' => array('access devel information'),
  103. 'file' => 'devel.pages.inc',
  104. 'menu_name' => 'devel',
  105. );
  106. $items['devel/php'] = array(
  107. 'title' => 'Execute PHP Code',
  108. 'description' => 'Execute some PHP code',
  109. 'page callback' => 'drupal_get_form',
  110. 'page arguments' => array('devel_execute_form'),
  111. 'access arguments' => array('execute php code'),
  112. 'file' => 'devel.pages.inc',
  113. 'menu_name' => 'devel',
  114. );
  115. $items['devel/theme/registry'] = array(
  116. 'title' => 'Theme registry',
  117. 'description' => 'View a list of available theme functions across the whole site.',
  118. 'page callback' => 'devel_theme_registry',
  119. 'access arguments' => array('access devel information'),
  120. 'file' => 'devel.pages.inc',
  121. 'menu_name' => 'devel',
  122. );
  123. $items['devel/entity/info'] = array(
  124. 'title' => 'Entity info',
  125. 'description' => 'View entity information across the whole site.',
  126. 'page callback' => 'devel_entity_info_page',
  127. 'access arguments' => array('access devel information'),
  128. 'file' => 'devel.pages.inc',
  129. 'menu_name' => 'devel',
  130. );
  131. $items['devel/field/info'] = array(
  132. 'title' => 'Field info',
  133. 'description' => 'View fields information across the whole site.',
  134. 'page callback' => 'devel_field_info_page',
  135. 'access arguments' => array('access devel information'),
  136. 'file' => 'devel.pages.inc',
  137. 'menu_name' => 'devel',
  138. );
  139. $items['devel/elements'] = array(
  140. 'title' => 'Hook_elements()',
  141. 'description' => 'View the active form/render elements for this site.',
  142. 'page callback' => 'devel_elements_page',
  143. 'access arguments' => array('access devel information'),
  144. 'file' => 'devel.pages.inc',
  145. 'menu_name' => 'devel',
  146. );
  147. $items['devel/variable/edit/%'] = array(
  148. 'title' => 'Variable editor',
  149. 'page callback' => 'drupal_get_form',
  150. 'page arguments' => array('devel_variable_edit', 3),
  151. 'access arguments' => array('access devel information'),
  152. 'type' => MENU_CALLBACK,
  153. 'file' => 'devel.pages.inc',
  154. 'menu_name' => 'devel',
  155. );
  156. $items['devel/session'] = array(
  157. 'title' => 'Session viewer',
  158. 'description' => 'List the contents of $_SESSION.',
  159. 'page callback' => 'devel_session',
  160. 'access arguments' => array('access devel information'),
  161. 'file' => 'devel.pages.inc',
  162. 'menu_name' => 'devel',
  163. );
  164. $items['devel/switch'] = array(
  165. 'title' => 'Switch user',
  166. 'page callback' => 'devel_switch_user',
  167. 'access arguments' => array('switch users'),
  168. 'type' => MENU_CALLBACK,
  169. 'file' => 'devel.pages.inc',
  170. 'menu_name' => 'devel',
  171. );
  172. $items['devel/explain'] = array(
  173. 'title' => 'Explain query',
  174. 'page callback' => 'devel_querylog_explain',
  175. 'description' => 'Run an EXPLAIN on a given query. Used by query log',
  176. 'access arguments' => array('access devel information'),
  177. 'file' => 'devel.pages.inc',
  178. 'type' => MENU_CALLBACK
  179. );
  180. $items['devel/arguments'] = array(
  181. 'title' => 'Arguments query',
  182. 'page callback' => 'devel_querylog_arguments',
  183. 'description' => 'Return a given query, with arguments instead of placeholders. Used by query log',
  184. 'access arguments' => array('access devel information'),
  185. 'file' => 'devel.pages.inc',
  186. 'type' => MENU_CALLBACK
  187. );
  188. $items['devel/run-cron'] = array(
  189. 'title' => 'Run cron',
  190. 'page callback' => 'system_run_cron',
  191. 'access arguments' => array('administer site configuration'),
  192. 'file' => 'system.admin.inc',
  193. 'file path' => drupal_get_path('module', 'system'),
  194. 'menu_name' => 'devel',
  195. );
  196. // Duplicate path in 2 different menus. See http://drupal.org/node/601788.
  197. $items['devel/settings'] = array(
  198. 'title' => 'Devel settings',
  199. '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.',
  200. 'page callback' => 'drupal_get_form',
  201. 'page arguments' => array('devel_admin_settings'),
  202. 'access arguments' => array('administer site configuration'),
  203. 'file' => 'devel.admin.inc',
  204. 'menu_name' => 'devel',
  205. );
  206. $items['admin/config/development/devel'] = array(
  207. 'title' => 'Devel settings',
  208. '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.',
  209. 'page callback' => 'drupal_get_form',
  210. 'page arguments' => array('devel_admin_settings'),
  211. 'file' => 'devel.admin.inc',
  212. 'access arguments' => array('administer site configuration'),
  213. );
  214. $items['node/%node/devel'] = array(
  215. 'title' => 'Devel',
  216. 'page callback' => 'devel_load_object',
  217. 'page arguments' => array('node', 1),
  218. 'access arguments' => array('access devel information'),
  219. 'type' => MENU_LOCAL_TASK,
  220. 'file' => 'devel.pages.inc',
  221. 'weight' => 100,
  222. );
  223. $items['node/%node/devel/load'] = array(
  224. 'title' => 'Load',
  225. 'type' => MENU_DEFAULT_LOCAL_TASK,
  226. );
  227. $items['node/%node/devel/render'] = array(
  228. 'title' => 'Render',
  229. 'page callback' => 'devel_render_object',
  230. 'page arguments' => array('node', 1),
  231. 'access arguments' => array('access devel information'),
  232. 'file' => 'devel.pages.inc',
  233. 'type' => MENU_LOCAL_TASK,
  234. 'weight' => 100,
  235. );
  236. $items['comment/%comment/devel'] = array(
  237. 'title' => 'Devel',
  238. 'page callback' => 'devel_load_object',
  239. 'page arguments' => array('comment', 1),
  240. 'access arguments' => array('access devel information'),
  241. 'type' => MENU_LOCAL_TASK,
  242. 'file' => 'devel.pages.inc',
  243. 'weight' => 100,
  244. );
  245. $items['comment/%comment/devel/load'] = array(
  246. 'title' => 'Load',
  247. 'type' => MENU_DEFAULT_LOCAL_TASK,
  248. );
  249. $items['comment/%comment/devel/render'] = array(
  250. 'title' => 'Render',
  251. 'page callback' => 'devel_render_object',
  252. 'page arguments' => array('comment', 1),
  253. 'access arguments' => array('access devel information'),
  254. 'type' => MENU_LOCAL_TASK,
  255. 'file' => 'devel.pages.inc',
  256. 'weight' => 100,
  257. );
  258. $items['user/%user/devel'] = array(
  259. 'title' => 'Devel',
  260. 'page callback' => 'devel_load_object',
  261. 'page arguments' => array('user', 1),
  262. 'access arguments' => array('access devel information'),
  263. 'type' => MENU_LOCAL_TASK,
  264. 'file' => 'devel.pages.inc',
  265. 'weight' => 100,
  266. );
  267. $items['user/%user/devel/load'] = array(
  268. 'title' => 'Load',
  269. 'type' => MENU_DEFAULT_LOCAL_TASK,
  270. );
  271. $items['user/%user/devel/render'] = array(
  272. 'title' => 'Render',
  273. 'page callback' => 'devel_render_object',
  274. 'page arguments' => array('user', 1),
  275. 'access arguments' => array('access devel information'),
  276. 'file' => 'devel.pages.inc',
  277. 'type' => MENU_LOCAL_TASK,
  278. 'weight' => 100,
  279. );
  280. $items['taxonomy/term/%taxonomy_term/devel'] = array(
  281. 'title' => 'Devel',
  282. 'page callback' => 'devel_load_object',
  283. 'page arguments' => array('taxonomy_term', 2, 'term'),
  284. 'access arguments' => array('access devel information'),
  285. 'file' => 'devel.pages.inc',
  286. 'type' => MENU_LOCAL_TASK,
  287. 'weight' => 100,
  288. );
  289. $items['taxonomy/term/%taxonomy_term/devel/load'] = array(
  290. 'title' => 'Load',
  291. 'type' => MENU_DEFAULT_LOCAL_TASK,
  292. );
  293. $items['taxonomy/term/%taxonomy_term/devel/render'] = array(
  294. 'title' => 'Render',
  295. 'page callback' => 'devel_render_object',
  296. 'page arguments' => array('taxonomy_term', 2, 'term'),
  297. 'access arguments' => array('access devel information'),
  298. 'type' => MENU_LOCAL_TASK,
  299. 'file' => 'devel.pages.inc',
  300. 'weight' => 100,
  301. );
  302. return $items;
  303. }
  304. /**
  305. * Implements hook_admin_paths().
  306. */
  307. function devel_admin_paths() {
  308. $paths = array(
  309. 'devel/*' => TRUE,
  310. 'node/*/devel' => TRUE,
  311. 'node/*/devel/*' => TRUE,
  312. 'comment/*/devel' => TRUE,
  313. 'comment/*/devel/*' => TRUE,
  314. 'user/*/devel' => TRUE,
  315. 'user/*/devel/*' => TRUE,
  316. 'taxonomy/term/*/devel' => TRUE,
  317. 'taxonomy/term/*/devel/*' => TRUE,
  318. );
  319. return $paths;
  320. }
  321. function devel_menu_need_destination() {
  322. return array('devel/cache/clear', 'devel/reinstall', 'devel/menu/reset', 'devel/variable', 'admin/reports/status/run-cron');
  323. }
  324. /**
  325. * An implementation of hook_menu_link_alter(). Flag this link as needing alter at display time.
  326. * This is more robust than setting alter in hook_menu().
  327. * @see devel_translated_menu_link_alter().
  328. *
  329. **/
  330. function devel_menu_link_alter(&$item) {
  331. if (in_array($item['link_path'], devel_menu_need_destination()) || $item['link_path'] == 'devel/menu/item') {
  332. $item['options']['alter'] = TRUE;
  333. }
  334. }
  335. /**
  336. * An implementation of hook_translated_menu_item_alter(). Append dynamic
  337. * querystring 'destination' to several of our own menu items.
  338. *
  339. **/
  340. function devel_translated_menu_link_alter(&$item) {
  341. if (in_array($item['href'], devel_menu_need_destination())) {
  342. $item['localized_options']['query'] = drupal_get_destination();
  343. }
  344. elseif ($item['href'] == 'devel/menu/item') {
  345. $item['localized_options']['query'] = array('path' => $_GET['q']);
  346. }
  347. }
  348. /**
  349. * Implementation of hook_theme()
  350. */
  351. function devel_theme() {
  352. return array(
  353. 'devel_querylog' => array(
  354. 'variables' => array('header' => array(), 'rows' => array()),
  355. ),
  356. 'devel_querylog_row' => array(
  357. 'variables' => array('row' => array()),
  358. ),
  359. );
  360. }
  361. /**
  362. * Implementation of hook_init().
  363. */
  364. function devel_init() {
  365. if (!devel_silent()) {
  366. if (user_access('access devel information')) {
  367. devel_set_handler(variable_get('devel_error_handler', DEVEL_ERROR_HANDLER_STANDARD));
  368. // We want to include the class early so that anyone may call krumo() as needed. See http://krumo.sourceforge.net/
  369. has_krumo();
  370. // See http://www.firephp.org/HQ/Install.htm
  371. $path = NULL;
  372. if (@include_once('fb.php')) {
  373. // FirePHPCore is in include_path. Probably a PEAR installation.
  374. $path = '';
  375. }
  376. elseif (module_exists('libraries')) {
  377. // Support Libraries API - http://drupal.org/project/libraries
  378. $firephp_path = libraries_get_path('FirePHPCore');
  379. $firephp_path = ($firephp_path ? $firephp_path . '/lib/FirePHPCore/' : '');
  380. $chromephp_path = libraries_get_path('chromephp');
  381. }
  382. else {
  383. $firephp_path = './' . drupal_get_path('module', 'devel') . '/FirePHPCore/lib/FirePHPCore/';
  384. $chromephp_path = './' . drupal_get_path('module', 'devel') . '/chromephp';
  385. }
  386. // Include FirePHP if it exists.
  387. if (!empty($firephp_path) && file_exists($firephp_path . 'fb.php')) {
  388. include_once $firephp_path . 'fb.php';
  389. include_once $firephp_path . 'FirePHP.class.php';
  390. }
  391. // Include ChromePHP if it exists.
  392. if (!empty($chromephp_path) && file_exists($chromephp_path .= '/ChromePhp.php')) {
  393. include_once $chromephp_path;
  394. }
  395. // Add CSS for query log if should be displayed.
  396. if (variable_get('devel_query_display', 0)) {
  397. drupal_add_css(drupal_get_path('module', 'devel') . '/devel.css');
  398. drupal_add_js(drupal_get_path('module', 'devel') . '/devel.js');
  399. }
  400. }
  401. }
  402. if (variable_get('devel_rebuild_theme_registry', FALSE)) {
  403. drupal_theme_rebuild();
  404. if (flood_is_allowed('devel_rebuild_registry_warning', 1)) {
  405. flood_register_event('devel_rebuild_registry_warning');
  406. if (!devel_silent() && user_access('access devel information')) {
  407. 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'))));
  408. }
  409. }
  410. }
  411. }
  412. function devel_set_message($msg, $type = NULL) {
  413. $function = function_exists('drush_log') ? 'drush_log' : 'drupal_set_message';
  414. $function($msg, $type);
  415. }
  416. // Return boolean. No need for cache here.
  417. function has_krumo() {
  418. // see README.txt or just download from http://krumo.sourceforge.net/
  419. @include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'devel') .'/krumo/class.krumo.php';
  420. return function_exists('krumo') && !drupal_is_cli();
  421. }
  422. /**
  423. * Decide whether or not to print a debug variable using krumo().
  424. *
  425. * @param $input
  426. * @return boolean
  427. */
  428. function merits_krumo($input) {
  429. return (is_object($input) || is_array($input)) && has_krumo() && variable_get('devel_krumo_skin', '') != 'disabled';
  430. }
  431. /**
  432. * Calls the http://www.firephp.org/ fb() function if it is found.
  433. *
  434. * @return void
  435. */
  436. function dfb() {
  437. if (function_exists('fb') && user_access('access devel information') && !headers_sent()) {
  438. $args = func_get_args();
  439. call_user_func_array('fb', $args);
  440. }
  441. }
  442. /**
  443. * Calls dfb() to output a backtrace.
  444. */
  445. function dfbt($label) {
  446. dfb($label, FirePHP::TRACE);
  447. }
  448. /**
  449. * Wrapper for ChromePHP Class log method
  450. */
  451. function dcp() {
  452. if (class_exists('ChromePhp') && user_access('access devel information')) {
  453. $args = func_get_args();
  454. call_user_func_array(array('ChromePhp', 'log'), $args);
  455. }
  456. }
  457. /**
  458. * Implements hook_watchdog().
  459. */
  460. function devel_watchdog(array $log_entry) {
  461. if (class_exists('FirePHP') && !drupal_is_cli()) {
  462. switch ($log_entry['severity']) {
  463. case WATCHDOG_EMERGENCY:
  464. case WATCHDOG_ALERT:
  465. case WATCHDOG_CRITICAL:
  466. case WATCHDOG_ERROR:
  467. $type = FirePHP::ERROR;
  468. break;
  469. case WATCHDOG_WARNING:
  470. $type = FirePHP::WARN;
  471. break;
  472. case WATCHDOG_NOTICE:
  473. case WATCHDOG_INFO:
  474. $type = FirePHP::INFO;
  475. break;
  476. case WATCHDOG_DEBUG:
  477. DEFAULT:
  478. $type = FirePHP::LOG;
  479. }
  480. }
  481. else {
  482. $type = 'watchdog';
  483. }
  484. $function = function_exists('decode_entities') ? 'decode_entities' : 'html_entity_decode';
  485. $watchdog = array(
  486. 'type' => $log_entry['type'],
  487. 'message' => $function(strtr($log_entry['message'], (array)$log_entry['variables'])),
  488. );
  489. if (isset($log_entry['link'])) {
  490. $watchdog['link'] = $log_entry['link'];
  491. }
  492. dfb($watchdog, $type);
  493. }
  494. function devel_set_handler($handler) {
  495. switch ($handler) {
  496. case DEVEL_ERROR_HANDLER_STANDARD:
  497. // do nothing
  498. break;
  499. case DEVEL_ERROR_HANDLER_BACKTRACE:
  500. if (has_krumo()) {
  501. set_error_handler('backtrace_error_handler');
  502. }
  503. break;
  504. case DEVEL_ERROR_HANDLER_NONE:
  505. restore_error_handler();
  506. break;
  507. }
  508. }
  509. function devel_silent() {
  510. // isset($_GET['q']) is needed when calling the front page. q is not set.
  511. // Don't interfere with private files/images.
  512. return
  513. function_exists('drupal_is_cli') && drupal_is_cli() ||
  514. (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'ApacheBench') !== FALSE) ||
  515. !empty($_REQUEST['XDEBUG_PROFILE']) ||
  516. isset($GLOBALS['devel_shutdown']) ||
  517. strstr($_SERVER['PHP_SELF'], 'update.php') ||
  518. (isset($_GET['q']) && (
  519. in_array($_GET['q'], array( 'admin/content/node-settings/rebuild')) ||
  520. substr($_GET['q'], 0, strlen('system/files')) == 'system/files' ||
  521. substr($_GET['q'], 0, strlen('batch')) == 'batch' ||
  522. substr($_GET['q'], 0, strlen('file/ajax')) == 'file/ajax')
  523. );
  524. }
  525. function devel_xhprof_enable() {
  526. if (devel_xhprof_is_enabled()) {
  527. if ($path = variable_get('devel_xhprof_directory', '')) {
  528. include_once $path . '/xhprof_lib/utils/xhprof_lib.php';
  529. include_once $path . '/xhprof_lib/utils/xhprof_runs.php';
  530. // @todo: consider a variable per-flag instead.
  531. xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
  532. }
  533. }
  534. }
  535. function devel_xhprof_is_enabled() {
  536. return extension_loaded('xhprof') && variable_get('devel_xhprof_enabled', FALSE);
  537. }
  538. /**
  539. * Implementation of hook_boot(). Runs even for cached pages.
  540. */
  541. function devel_boot() {
  542. // Initialize XHProf.
  543. devel_xhprof_enable();
  544. if (!devel_silent()) {
  545. if (variable_get('dev_mem', 0)) {
  546. global $memory_init;
  547. $memory_init = memory_get_usage();
  548. }
  549. if (devel_query_enabled()) {
  550. @include_once DRUPAL_ROOT . '/includes/database/log.inc';
  551. Database::startLog('devel');;
  552. }
  553. }
  554. // We need user_access() in the shutdown function. make sure it gets loaded.
  555. // Also prime the drupal_get_filename() static with user.module's location to
  556. // avoid a stray query.
  557. drupal_get_filename('module', 'user', 'modules/user/user.module');
  558. drupal_load('module', 'user');
  559. drupal_register_shutdown_function('devel_shutdown');
  560. }
  561. function backtrace_error_handler($error_level, $message, $filename, $line, $context) {
  562. // Hide stack trace and parameters from unqualified users.
  563. if (!user_access('access devel information')) {
  564. return _drupal_error_handler($error_level, $message, $filename, $line, $context);
  565. }
  566. // Don't respond to the error if it was suppressed with a '@'
  567. if (error_reporting() == 0) {
  568. return;
  569. }
  570. // Don't respond to warning caused by ourselves.
  571. if (preg_match('#Cannot modify header information - headers already sent by \\([^\\)]*[/\\\\]devel[/\\\\]#', $message)) {
  572. return;
  573. }
  574. if ($error_level & error_reporting()) {
  575. // Only write each distinct NOTICE message once, as repeats do not give any
  576. // further information and can choke the page output.
  577. if ($error_level == E_NOTICE) {
  578. static $written = array();
  579. if (!empty($written[$line][$filename][$message])) {
  580. return;
  581. }
  582. $written[$line][$filename][$message] = TRUE;
  583. }
  584. require_once DRUPAL_ROOT . '/includes/errors.inc';
  585. $types = drupal_error_levels();
  586. $type = $types[$error_level];
  587. $backtrace = debug_backtrace();
  588. array_shift($backtrace);
  589. $variables = array('%error' => $type[0], '%message' => $message, '%function' => $backtrace[0]['function'] .'()', '%file' => $filename, '%line' => $line);
  590. $counter = count($backtrace);
  591. // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher.
  592. // (This is Drupal's error_level, which is different from $error_level,
  593. // and we purposely ignore the difference between _SOME and _ALL,
  594. // see #970688!)
  595. if (variable_get('error_level', 1) >= 1) {
  596. foreach ($backtrace as $call) {
  597. $nicetrace[--$counter . ': ' . $call['function']] = $call;
  598. }
  599. print t('%error: %message in %function (line %line of %file).', $variables) ." =&gt;\n";
  600. krumo($nicetrace);
  601. }
  602. watchdog('php', '%error: %message in %function (line %line of %file).', $variables, $type[1]);
  603. }
  604. }
  605. /**
  606. * Implement hook_permission().
  607. */
  608. function devel_permission() {
  609. return array(
  610. 'access devel information' => array(
  611. 'description' => t('View developer output like variable printouts, query log, etc.'),
  612. 'title' => t('Access developer information'),
  613. 'restrict access' => TRUE,
  614. ),
  615. 'execute php code' => array(
  616. 'title' => t('Execute PHP code'),
  617. 'description' => t('Run arbitrary PHP from a block.'),
  618. 'restrict access' => TRUE,
  619. ),
  620. 'switch users' => array(
  621. 'title' => t('Switch users'),
  622. 'description' => t('Become any user on the site with just a click.'),
  623. 'restrict access' => TRUE,
  624. ),
  625. );
  626. }
  627. function devel_block_info() {
  628. $blocks['execute_php'] = array(
  629. 'info' => t('Execute PHP'),
  630. 'cache' => DRUPAL_NO_CACHE,
  631. );
  632. $blocks['switch_user'] = array(
  633. 'info' => t('Switch user'),
  634. 'cache' => DRUPAL_NO_CACHE,
  635. );
  636. return $blocks;
  637. }
  638. /**
  639. * Implementation of hook_block_configure().
  640. */
  641. function devel_block_configure($delta) {
  642. if ($delta == 'switch_user') {
  643. $form['list_size'] = array(
  644. '#type' => 'textfield',
  645. '#title' => t('Number of users to display in the list'),
  646. '#default_value' => variable_get('devel_switch_user_list_size', 10),
  647. '#size' => '3',
  648. '#maxlength' => '4',
  649. );
  650. $form['include_anon'] = array(
  651. '#type' => 'checkbox',
  652. '#title' => t('Include %anonymous', array('%anonymous' => format_username(drupal_anonymous_user()))),
  653. '#default_value' => variable_get('devel_switch_user_include_anon', FALSE),
  654. );
  655. $form['show_form'] = array(
  656. '#type' => 'checkbox',
  657. '#title' => t('Allow entering any user name'),
  658. '#default_value' => variable_get('devel_switch_user_show_form', TRUE),
  659. );
  660. return $form;
  661. }
  662. }
  663. function devel_block_save($delta, $edit = array()) {
  664. if ($delta == 'switch_user') {
  665. variable_set('devel_switch_user_list_size', $edit['list_size']);
  666. variable_set('devel_switch_user_include_anon', $edit['include_anon']);
  667. variable_set('devel_switch_user_show_form', $edit['show_form']);
  668. }
  669. }
  670. function devel_block_view($delta) {
  671. $block = array();
  672. switch ($delta) {
  673. case 'switch_user':
  674. $block = devel_block_switch_user();
  675. break;
  676. case 'execute_php':
  677. if (user_access('execute php code')) {
  678. $block['content'] = drupal_get_form('devel_execute_block_form');
  679. }
  680. break;
  681. }
  682. return $block;
  683. }
  684. function devel_block_switch_user() {
  685. $links = devel_switch_user_list();
  686. if (!empty($links) || user_access('switch users')) {
  687. $block['subject'] = t('Switch user');
  688. $build['devel_links'] = array('#theme' => 'links', '#links' => $links);
  689. if (variable_get('devel_switch_user_show_form', TRUE)) {
  690. $build['devel_form'] = drupal_get_form('devel_switch_user_form');
  691. }
  692. $block['content'] = $build;
  693. return $block;
  694. }
  695. }
  696. function devel_switch_user_list() {
  697. global $user;
  698. $links = array();
  699. if (user_access('switch users')) {
  700. $list_size = variable_get('devel_switch_user_list_size', 10);
  701. if ($include_anon = ($user->uid && variable_get('devel_switch_user_include_anon', FALSE))) {
  702. --$list_size;
  703. }
  704. $dest = drupal_get_destination();
  705. // Try to find at least $list_size users that can switch.
  706. // Inactive users are omitted from all of the following db selects.
  707. $roles = user_roles(TRUE, 'switch users');
  708. $query = db_select('users', 'u');
  709. $query->addField('u', 'uid');
  710. $query->addField('u', 'access');
  711. $query->distinct();
  712. $query->condition('u.uid', 0, '>');
  713. $query->condition('u.status', 0, '>');
  714. $query->orderBy('u.access', 'DESC');
  715. $query->range(0, $list_size);
  716. if (!isset($roles[DRUPAL_AUTHENTICATED_RID])) {
  717. $query->leftJoin('users_roles', 'r', 'u.uid = r.uid');
  718. $or_condition = db_or();
  719. $or_condition->condition('u.uid', 1);
  720. if (!empty($roles)) {
  721. $or_condition->condition('r.rid', array_keys($roles), 'IN');
  722. }
  723. $query->condition($or_condition);
  724. }
  725. $uids = $query->execute()->fetchCol();
  726. $accounts = user_load_multiple($uids);
  727. foreach ($accounts as $account) {
  728. $links[$account->uid] = array(
  729. 'title' => drupal_placeholder(format_username($account)),
  730. 'href' => 'devel/switch/'. $account->name,
  731. 'query' => $dest,
  732. 'attributes' => array('title' => t('This user can switch back.')),
  733. 'html' => TRUE,
  734. 'last_access' => $account->access,
  735. );
  736. }
  737. $num_links = count($links);
  738. if ($num_links < $list_size) {
  739. // If we don't have enough, add distinct uids until we hit $list_size.
  740. $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();
  741. $accounts = user_load_multiple($uids);
  742. foreach ($accounts as $account) {
  743. $links[$account->uid] = array(
  744. 'title' => format_username($account),
  745. 'href' => 'devel/switch/'. $account->name,
  746. 'query' => $dest,
  747. 'attributes' => array('title' => t('Caution: this user will be unable to switch back.')),
  748. 'last_access' => $account->access,
  749. );
  750. }
  751. uasort($links, '_devel_switch_user_list_cmp');
  752. }
  753. if ($include_anon) {
  754. $link = array(
  755. 'title' => format_username(drupal_anonymous_user()),
  756. 'href' => 'devel/switch',
  757. 'query' => $dest,
  758. 'attributes' => array('title' => t('Caution: the anonymous user will be unable to switch back.')),
  759. );
  760. if (user_access('switch users', drupal_anonymous_user())) {
  761. $link['title'] = drupal_placeholder($link['title']);
  762. $link['attributes'] = array('title' => t('This user can switch back.'));
  763. $link['html'] = TRUE;
  764. }
  765. $links[] = $link;
  766. }
  767. }
  768. return $links;
  769. }
  770. /**
  771. * Comparison helper function for uasort() in devel_switch_user_list().
  772. *
  773. * Sorts the Switch User links by the user's last access timestamp.
  774. */
  775. function _devel_switch_user_list_cmp($a, $b) {
  776. return $b['last_access'] - $a['last_access'];
  777. }
  778. function devel_switch_user_form() {
  779. $form['username'] = array(
  780. '#type' => 'textfield',
  781. '#description' => t('Enter username'),
  782. '#autocomplete_path' => 'user/autocomplete',
  783. '#maxlength' => USERNAME_MAX_LENGTH,
  784. '#size' => 16,
  785. );
  786. $form['submit'] = array(
  787. '#type' => 'submit',
  788. '#value' => t('Switch'),
  789. );
  790. return $form;
  791. }
  792. function devel_doc_function_form() {
  793. $version = devel_get_core_version(VERSION);
  794. $form['function'] = array(
  795. '#type' => 'textfield',
  796. '#description' => t('Enter function name for api lookup'),
  797. '#size' => 16,
  798. '#maxlength' => 255,
  799. );
  800. $form['version'] = array('#type' => 'value', '#value' => $version);
  801. $form['submit_button'] = array(
  802. '#type' => 'submit',
  803. '#value' => t('Submit'),
  804. );
  805. return $form;
  806. }
  807. function devel_doc_function_form_submit($form, &$form_state) {
  808. $version = $form_state['values']['version'];
  809. $function = $form_state['values']['function'];
  810. $api = variable_get('devel_api_url', 'api.drupal.org');
  811. $form_state['redirect'] = "http://$api/api/function/$function/$version";
  812. }
  813. function devel_switch_user_form_validate($form, &$form_state) {
  814. if (!$account = user_load_by_name($form_state['values']['username'])) {
  815. form_set_error('username', t('Username not found'));
  816. }
  817. }
  818. function devel_switch_user_form_submit($form, &$form_state) {
  819. $form_state['redirect'] = 'devel/switch/'. $form_state['values']['username'];
  820. }
  821. /**
  822. * Implements hook_drupal_goto_alter().
  823. */
  824. function devel_drupal_goto_alter($path, $options, $http_response_code) {
  825. global $user;
  826. if (isset($path) && !devel_silent()) {
  827. // The page we are leaving is a drupal_goto(). Present a redirection page
  828. // so that the developer can see the intermediate query log.
  829. // We don't want to load user module here, so keep function_exists() call.
  830. if (isset($user) && function_exists('user_access') && user_access('access devel information') && variable_get('devel_redirect_page', 0)) {
  831. $destination = function_exists('url') ? url($path, $options) : $path;
  832. $output = t_safe('<p>The user is being redirected to <a href="@destination">@destination</a>.</p>', array('@destination' => $destination));
  833. drupal_deliver_page($output);
  834. // Don't allow the automatic redirect to happen.
  835. exit();
  836. }
  837. else {
  838. $GLOBALS['devel_redirecting'] = TRUE;
  839. }
  840. }
  841. }
  842. /**
  843. * Implements hook_library_alter().
  844. */
  845. function devel_library_alter(&$libraries, $module) {
  846. // Use an uncompressed version of jQuery for debugging.
  847. if ($module === 'system' && variable_get('devel_use_uncompressed_jquery', FALSE) && isset($libraries['jquery'])) {
  848. // Make sure we're not changing the jQuery version used on the site.
  849. if (version_compare($libraries['jquery']['version'], '1.4.4', '=')) {
  850. $libraries['jquery']['js'] = array(
  851. drupal_get_path('module', 'devel') . '/jquery-1.4.4-uncompressed.js' => array('weight' => JS_LIBRARY - 20),
  852. );
  853. }
  854. else {
  855. if (!devel_silent() && user_access('access devel information')) {
  856. 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'])));
  857. }
  858. }
  859. }
  860. }
  861. /**
  862. * See devel_start() which registers this function as a shutdown function.
  863. */
  864. function devel_shutdown() {
  865. // Register the real shutdown function so it runs later than other shutdown functions.
  866. drupal_register_shutdown_function('devel_shutdown_real');
  867. global $devel_run_id;
  868. $devel_run_id = devel_xhprof_is_enabled() ? devel_shutdown_xhprof(): NULL;
  869. if ($devel_run_id && function_exists('drush_log')) {
  870. drush_log('xhprof link: ' . devel_xhprof_link($devel_run_id, 'url'), 'notice');
  871. }
  872. }
  873. function devel_page_alter($page) {
  874. if (variable_get('devel_page_alter', FALSE) && user_access('access devel information')) {
  875. dpm($page, 'page');
  876. }
  877. }
  878. // AJAX render reponses sometimers are sent as text/html so we have to catch them here
  879. // and disable our footer stuff.
  880. function devel_ajax_render_alter() {
  881. $GLOBALS['devel_shutdown'] = FALSE;
  882. }
  883. /**
  884. * See devel_shutdown() which registers this function as a shutdown function. Displays developer information in the footer.
  885. */
  886. function devel_shutdown_real() {
  887. global $user;
  888. $output = $txt = '';
  889. // Set $GLOBALS['devel_shutdown'] = FALSE in order to supress the
  890. // devel footer for a page. Not necessary if your page outputs any
  891. // of the Content-type http headers tested below (e.g. text/xml,
  892. // text/javascript, etc). This is is advised where applicable.
  893. if (!devel_silent() && !isset($GLOBALS['devel_shutdown']) && !isset($GLOBALS['devel_redirecting'])) {
  894. // Try not to break non html pages.
  895. if (function_exists('drupal_get_http_header')) {
  896. $header = drupal_get_http_header('content-type');
  897. if ($header) {
  898. $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values');
  899. foreach ($formats as $format) {
  900. if (strstr($header, $format)) {
  901. return;
  902. }
  903. }
  904. }
  905. }
  906. if (isset($user) && user_access('access devel information')) {
  907. $queries = (devel_query_enabled() ? Database::getLog('devel', 'default') : NULL);
  908. $output .= devel_shutdown_summary($queries);
  909. $output .= devel_shutdown_query($queries);
  910. }
  911. if ($output) {
  912. // TODO: gzip this text if we are sending a gzip page. see drupal_page_header().
  913. // For some reason, this is not actually printing for cached pages even though it gets executed
  914. // and $output looks good.
  915. print $output;
  916. }
  917. }
  918. }
  919. function devel_shutdown_summary($queries) {
  920. $sum = 0;
  921. $output = '';
  922. list($counts, $query_summary) = devel_query_summary($queries);
  923. if (variable_get('devel_query_display', FALSE)) {
  924. // Query log on.
  925. $output .= $query_summary;
  926. $output .= t_safe(' Queries exceeding @threshold ms are <span class="marker">highlighted</span>.', array('@threshold' => variable_get('devel_execution', 5)));
  927. }
  928. if (variable_get('dev_timer', 0)) {
  929. $output .= devel_timer();
  930. }
  931. if (devel_xhprof_is_enabled()) {
  932. $output .= ' ' . devel_xhprof_link($GLOBALS['devel_run_id']);
  933. }
  934. $output .= devel_shutdown_memory();
  935. if ($output) {
  936. return '<div class="dev-query">' . $output . '</div>';
  937. }
  938. }
  939. function devel_shutdown_xhprof() {
  940. $namespace = variable_get('site_name', ''); // namespace for your application
  941. $xhprof_data = xhprof_disable();
  942. $xhprof_runs = new XHProfRuns_Default();
  943. return $xhprof_runs->save_run($xhprof_data, $namespace);
  944. }
  945. function devel_xhprof_link($run_id, $type = 'link') {
  946. // @todo: render results from within Drupal.
  947. $xhprof_url = variable_get('devel_xhprof_url', '');
  948. $namespace = variable_get('site_name', ''); // namespace for your application
  949. if ($xhprof_url) {
  950. $url = $xhprof_url . "/index.php?run=$run_id&source=$namespace";
  951. return $type == 'url' ? $url : t('<a href="@xhprof">XHProf output</a>. ', array('@xhprof' => $url));
  952. }
  953. }
  954. function devel_shutdown_memory() {
  955. global $memory_init;
  956. if (variable_get('dev_mem', FALSE)) {
  957. $memory_shutdown = memory_get_usage();
  958. $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));
  959. $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>';
  960. // theme() may not be available. not t() either.
  961. return t_safe($msg, $args);
  962. }
  963. }
  964. function devel_shutdown_query($queries) {
  965. if (!empty($queries)) {
  966. if (function_exists('theme_get_registry') && theme_get_registry()) {
  967. // Safe to call theme('table).
  968. list($counts, $query_summary) = devel_query_summary($queries);
  969. $output = devel_query_table($queries, $counts);
  970. // Save all queries to a file in temp dir. Retrieved via AJAX.
  971. devel_query_put_contents($queries);
  972. }
  973. else {
  974. $output = '</div>' . dprint_r($queries, TRUE);
  975. }
  976. return $output;
  977. }
  978. }
  979. // Write the variables information to the a file. It will be retrieved on demand via AJAX.
  980. function devel_query_put_contents($queries) {
  981. $request_id = mt_rand(1, 1000000);
  982. $path = "temporary://devel_querylog";
  983. // Create the devel_querylog within the temp folder, if needed.
  984. file_prepare_directory($path, FILE_CREATE_DIRECTORY);
  985. // Occassionally wipe the querylog dir so that files don't accumulate.
  986. if (mt_rand(1, 1000) == 401) {
  987. devel_empty_dir($path);
  988. }
  989. $path .= "/$request_id.txt";
  990. $path = file_stream_wrapper_uri_normalize($path);
  991. // Save queries as a json array. Suppress errors due to recursion ()
  992. file_put_contents($path, @json_encode($queries));
  993. $settings['devel'] = array(
  994. // A random string that is sent to the browser. It enables the AJAX to retrieve queries from this request.
  995. 'request_id' => $request_id,
  996. );
  997. print '<script type="text/javascript">jQuery.extend(Drupal.settings, '. json_encode($settings) .");</script>\n";
  998. }
  999. function devel_query_enabled() {
  1000. return method_exists('Database', 'getLog') && variable_get('devel_query_display', FALSE);
  1001. }
  1002. function devel_query_summary($queries) {
  1003. if (variable_get('devel_query_display', FALSE) && is_array($queries)) {
  1004. $sum = 0;
  1005. foreach ($queries as $query) {
  1006. $text[] = $query['query'];
  1007. $sum += $query['time'];
  1008. }
  1009. $counts = array_count_values($text);
  1010. return array($counts, t_safe('Executed @queries queries in @time ms.', array('@queries' => count($queries), '@time' => round($sum * 1000, 2))));
  1011. }
  1012. }
  1013. function t_safe($string, $args) {
  1014. // get_t caused problems here with theme registry after changing on admin/build/modules. the theme_get_registry call is needed.
  1015. if (function_exists('t') && function_exists('theme_get_registry')) {
  1016. theme_get_registry();
  1017. return t($string, $args);
  1018. }
  1019. else {
  1020. strtr($string, $args);
  1021. }
  1022. }
  1023. function devel_get_core_version($version) {
  1024. $version_parts = explode('.', $version);
  1025. // Map from 4.7.10 -> 4.7
  1026. if ($version_parts[0] < 5) {
  1027. return $version_parts[0] .'.'. $version_parts[1];
  1028. }
  1029. // Map from 5.5 -> 5 or 6.0-beta2 -> 6
  1030. else {
  1031. return $version_parts[0];
  1032. }
  1033. }
  1034. // See http://drupal.org/node/126098
  1035. function devel_is_compatible_optimizer() {
  1036. ob_start();
  1037. phpinfo();
  1038. $info = ob_get_contents();
  1039. ob_end_clean();
  1040. // Match the Zend Optimizer version in the phpinfo information
  1041. $found = preg_match('/Zend&nbsp;Optimizer&nbsp;v([0-9])\.([0-9])\.([0-9])/', $info, $matches);
  1042. if ($matches) {
  1043. $major = $matches[1];
  1044. $minor = $matches[2];
  1045. $build = $matches[3];
  1046. if ($major >= 3) {
  1047. if ($minor >= 3) {
  1048. return TRUE;
  1049. }
  1050. elseif ($minor == 2 && $build >= 8) {
  1051. return TRUE;
  1052. }
  1053. else {
  1054. return FALSE;
  1055. }
  1056. }
  1057. else {
  1058. return FALSE;
  1059. }
  1060. }
  1061. else {
  1062. return TRUE;
  1063. }
  1064. }
  1065. /**
  1066. * Generates the execute block form.
  1067. */
  1068. function devel_execute_block_form() {
  1069. $form['execute'] = array(
  1070. '#type' => 'fieldset',
  1071. '#title' => t('Execute PHP Code'),
  1072. '#collapsible' => TRUE,
  1073. '#collapsed' => (!isset($_SESSION['devel_execute_code'])),
  1074. );
  1075. $form['#submit'] = array('devel_execute_form_submit');
  1076. return array_merge_recursive($form, devel_execute_form());
  1077. }
  1078. /**
  1079. * Generates the execute form.
  1080. */
  1081. function devel_execute_form() {
  1082. $form['execute']['code'] = array(
  1083. '#type' => 'textarea',
  1084. '#title' => t('PHP code to execute'),
  1085. '#description' => t('Enter some code. Do not use <code>&lt;?php ?&gt;</code> tags.'),
  1086. '#default_value' => (isset($_SESSION['devel_execute_code']) ? $_SESSION['devel_execute_code'] : ''),
  1087. '#rows' => 20,
  1088. );
  1089. $form['execute']['op'] = array('#type' => 'submit', '#value' => t('Execute'));
  1090. $form['#redirect'] = FALSE;
  1091. if (isset($_SESSION['devel_execute_code'])) {
  1092. unset($_SESSION['devel_execute_code']);
  1093. }
  1094. return $form;
  1095. }
  1096. /**
  1097. * Process PHP execute form submissions.
  1098. */
  1099. function devel_execute_form_submit($form, &$form_state) {
  1100. ob_start();
  1101. print eval($form_state['values']['code']);
  1102. $_SESSION['devel_execute_code'] = $form_state['values']['code'];
  1103. dsm(ob_get_clean());
  1104. }
  1105. /**
  1106. * Switch from original user to another user and back.
  1107. * We don't call session_save_session() because we really want to change users. Usually unsafe!
  1108. *
  1109. * @param $name The username to switch to, or NULL to log out.
  1110. */
  1111. function devel_switch_user($name = NULL) {
  1112. global $user;
  1113. if ($user->uid) {
  1114. module_invoke_all('user_logout', $user);
  1115. }
  1116. if (isset($name) && $account = user_load_by_name($name)) {
  1117. $old_uid = $user->uid;
  1118. $user = $account;
  1119. $user->timestamp = time() - 9999;
  1120. if (!$old_uid) {
  1121. // Switch from anonymous to authorized.
  1122. drupal_session_regenerate();
  1123. }
  1124. $edit = array();
  1125. user_module_invoke('login', $edit, $user);
  1126. }
  1127. elseif ($user->uid) {
  1128. session_destroy();
  1129. }
  1130. drupal_goto();
  1131. }
  1132. /**
  1133. * Print an object or array using either Krumo (if installed) or devel_print_object()
  1134. *
  1135. * @param $object
  1136. * array or object to print
  1137. * @param $prefix
  1138. * prefixing for output items
  1139. */
  1140. function kdevel_print_object($object, $prefix = NULL) {
  1141. return has_krumo() ? krumo_ob($object) : devel_print_object($object, $prefix);
  1142. }
  1143. // Save krumo htlm using output buffering.
  1144. function krumo_ob($object) {
  1145. ob_start();
  1146. krumo($object);
  1147. $output = ob_get_contents();
  1148. ob_end_clean();
  1149. return $output;
  1150. }
  1151. /**
  1152. * Display an object or array
  1153. *
  1154. * @param $object
  1155. * the object or array
  1156. * @param $prefix
  1157. * prefix for the output items (example "$node->", "$user->", "$")
  1158. * @param $header
  1159. * set to FALSE to suppress the output of the h3
  1160. */
  1161. function devel_print_object($object, $prefix = NULL, $header = TRUE) {
  1162. drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css');
  1163. $output = '<div class="devel-obj-output">';
  1164. if ($header) {
  1165. $output .= '<h3>'. t('Display of !type !obj', array('!type' => str_replace(array('$', '->'), '', $prefix), '!obj' => gettype($object))) .'</h3>';
  1166. }
  1167. $output .= _devel_print_object($object, $prefix);
  1168. $output .= '</div>';
  1169. return $output;
  1170. }
  1171. /**
  1172. * Recursive (and therefore magical) function goes through an array or object and
  1173. * returns a nicely formatted listing of its contents.
  1174. *
  1175. * @param $obj
  1176. * array or object to recurse through
  1177. * @param $prefix
  1178. * prefix for the output items (example "$node->", "$user->", "$")
  1179. * @param $parents
  1180. * used by recursion
  1181. * @param $object
  1182. * used by recursion
  1183. * @return
  1184. * fomatted html
  1185. *
  1186. * @todo
  1187. * currently there are problems sending an array with a varname
  1188. */
  1189. function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FALSE) {
  1190. static $root_type, $out_format;
  1191. // TODO: support objects with references. See http://drupal.org/node/234581.
  1192. if (isset($obj->view)) {
  1193. return;
  1194. }
  1195. if (!isset($root_type)) {
  1196. $root_type = gettype($obj);
  1197. if ($root_type == 'object') {
  1198. $object = TRUE;
  1199. }
  1200. }
  1201. if (is_object($obj)) {
  1202. $obj = (array)$obj;
  1203. }
  1204. if (is_array($obj)) {
  1205. $output = "<dl>\n";
  1206. foreach ($obj as $field => $value) {
  1207. if ($field == 'devel_flag_reference') {
  1208. continue;
  1209. }
  1210. if (!is_null($parents)) {
  1211. if ($object) {
  1212. $field = $parents .'->'. $field;
  1213. }
  1214. else {
  1215. if (is_int($field)) {
  1216. $field = $parents .'['. $field .']';
  1217. }
  1218. else {
  1219. $field = $parents .'[\''. $field .'\']';
  1220. }
  1221. }
  1222. }
  1223. $type = gettype($value);
  1224. $show_summary = TRUE;
  1225. $summary = NULL;
  1226. if ($show_summary) {
  1227. switch ($type) {
  1228. case 'string' :
  1229. case 'float' :
  1230. case 'integer' :
  1231. if (strlen($value) == 0) {
  1232. $summary = t("{empty}");
  1233. }
  1234. elseif (strlen($value) < 40) {
  1235. $summary = htmlspecialchars($value);
  1236. }
  1237. else {
  1238. $summary = format_plural(drupal_strlen($value), '1 character', '@count characters');
  1239. }
  1240. break;
  1241. case 'array' :
  1242. case 'object' :
  1243. $summary = format_plural(count((array)$value), '1 element', '@count elements');
  1244. break;
  1245. case 'boolean' :
  1246. $summary = $value ? t('TRUE') : t('FALSE');
  1247. break;
  1248. }
  1249. }
  1250. if (!is_null($summary)) {
  1251. $typesum = '('. $type .', <em>'. $summary .'</em>)';
  1252. }
  1253. else {
  1254. $typesum = '('. $type .')';
  1255. }
  1256. $output .= '<span class="devel-attr">';
  1257. $output .= "<dt><span class=\"field\">{$prefix}{$field}</span> $typesum</dt>\n";
  1258. $output .= "<dd>\n";
  1259. // Check for references.
  1260. if (is_array($value) && isset($value['devel_flag_reference'])) {
  1261. $value['devel_flag_reference'] = TRUE;
  1262. }
  1263. // Check for references to prevent errors from recursions.
  1264. if (is_array($value) && isset($value['devel_flag_reference']) && !$value['devel_flag_reference']) {
  1265. $value['devel_flag_reference'] = FALSE;
  1266. $output .= _devel_print_object($value, $prefix, $field);
  1267. }
  1268. elseif (is_object($value)) {
  1269. $value->devel_flag_reference = FALSE;
  1270. $output .= _devel_print_object((array)$value, $prefix, $field, TRUE);
  1271. }
  1272. else {
  1273. $value = is_bool($value) ? ($value ? 'TRUE' : 'FALSE') : $value;
  1274. $output .= htmlspecialchars(print_r($value, TRUE)) ."\n";
  1275. }
  1276. $output .= "</dd></span>\n";
  1277. }
  1278. $output .= "</dl>\n";
  1279. }
  1280. return $output;
  1281. }
  1282. /**
  1283. * Adds a table at the bottom of the page cataloguing data on all the database queries that were made to
  1284. * generate the page.
  1285. */
  1286. function devel_query_table($queries, $counts) {
  1287. $version = devel_get_core_version(VERSION);
  1288. $header = array ('ms', '#', 'where', 'ops', 'query', 'target');
  1289. $i = 0;
  1290. $api = variable_get('devel_api_url', 'api.drupal.org');
  1291. foreach ($queries as $query) {
  1292. $function = !empty($query['caller']['class']) ? $query['caller']['class'] . '::' : '';
  1293. $function .= $query['caller']['function'];
  1294. $count = isset($counts[$query['query']]) ? $counts[$query['query']] : 0;
  1295. $diff = round($query['time'] * 1000, 2);
  1296. if ($diff > variable_get('devel_execution', 5)) {
  1297. $cell[$i][] = array ('data' => $diff, 'class' => 'marker');
  1298. }
  1299. else {
  1300. $cell[$i][] = $diff;
  1301. }
  1302. $cell[$i][] = $count;
  1303. $cell[$i][] = l($function, "http://$api/api/function/$function/$version");
  1304. $ops[] = l('P', '', array('attributes' => array('title' => 'Show placeholders', 'class' => 'dev-placeholders', 'qid' => $i)));
  1305. $ops[] = l('A', '', array('attributes' => array('title' => 'Show arguments', 'class' => 'dev-arguments', 'qid' => $i)));
  1306. // EXPLAIN only valid for select queries.
  1307. if (strpos($query['query'], 'UPDATE') === FALSE && strpos($query['query'], 'INSERT') === FALSE && strpos($query['query'], 'DELETE') === FALSE) {
  1308. $ops[] = l('E', '', array('attributes' => array('title' => 'Show EXPLAIN', 'class' => 'dev-explain', 'qid' => $i)));
  1309. }
  1310. $cell[$i][] = implode(' ', $ops);
  1311. // 3 divs for each variation of the query. Last 2 are hidden by default.
  1312. $placeholders = '<div class="dev-placeholders">' . check_plain($query['query']) . "</div>\n";
  1313. $args = '<div class="dev-arguments" style="display: none;"></div>' . "\n";
  1314. $explain = '<div class="dev-explain" style="display: none;"></div>' . "\n";
  1315. $cell[$i][] = array(
  1316. 'id' => "devel-query-$i",
  1317. 'data' => $placeholders . $args . $explain,
  1318. );
  1319. $cell[$i][] = $query['target'];
  1320. $i++;
  1321. unset($diff, $count, $ops);
  1322. }
  1323. if (variable_get('devel_query_sort', DEVEL_QUERY_SORT_BY_SOURCE)) {
  1324. usort($cell, '_devel_table_sort');
  1325. }
  1326. return theme('devel_querylog', array('header' => $header, 'rows' => $cell));
  1327. }
  1328. function theme_devel_querylog_row($variables) {
  1329. $row = $variables['row'];
  1330. $i = 0;
  1331. $output = '';
  1332. foreach ($row as $cell) {
  1333. $i++;
  1334. if (is_array($cell)) {
  1335. $data = !empty($cell['data']) ? $cell['data'] : '';
  1336. unset($cell['data']);
  1337. $attr = $cell;
  1338. }
  1339. else {
  1340. $data = $cell;
  1341. $attr = array();
  1342. }
  1343. if (!empty($attr['class'])) {
  1344. $attr['class'] .= " cell cell-$i";
  1345. }
  1346. else {
  1347. $attr['class'] = "cell cell-$i";
  1348. }
  1349. $attr = drupal_attributes($attr);
  1350. $output .= "<div $attr>$data</div>";
  1351. }
  1352. return $output;
  1353. }
  1354. function theme_devel_querylog($variables) {
  1355. $header = $variables['header'];
  1356. $rows = $variables['rows'];
  1357. $output = '';
  1358. if (!empty($header)) {
  1359. $output .= "<div class='devel-querylog devel-querylog-header clear-block'>";
  1360. $output .= theme('devel_querylog_row', array('row' => $header));
  1361. $output .= "</div>";
  1362. }
  1363. if (!empty($rows)) {
  1364. $i = 0;
  1365. foreach ($rows as $row) {
  1366. $i++;
  1367. $zebra = ($i % 2) == 0 ? 'even' : 'odd';
  1368. $output .= "<div class='devel-querylog devel-querylog-$zebra clear-block'>";
  1369. $output .= theme('devel_querylog_row', array('row' => $row));
  1370. $output .= "</div>";
  1371. }
  1372. }
  1373. return $output;
  1374. }
  1375. function _devel_table_sort($a, $b) {
  1376. $a = is_array($a[0]) ? $a[0]['data'] : $a[0];
  1377. $b = is_array($b[0]) ? $b[0]['data'] : $b[0];
  1378. if ($a < $b) {
  1379. return 1;
  1380. }
  1381. if ($a > $b) {
  1382. return -1;
  1383. }
  1384. return 0;
  1385. }
  1386. /**
  1387. * Displays page execution time at the bottom of the page.
  1388. */
  1389. function devel_timer() {
  1390. $time = timer_read('page');
  1391. return t_safe(' Page execution time was @time ms.', array('@time' => $time));
  1392. }
  1393. // An alias for drupal_debug().
  1394. function dd($data, $label = NULL) {
  1395. return drupal_debug($data, $label);
  1396. }
  1397. // Log any variable to a drupal_debug.log in the site's temp directory.
  1398. // See http://drupal.org/node/314112
  1399. function drupal_debug($data, $label = NULL) {
  1400. ob_start();
  1401. print_r($data);
  1402. $string = ob_get_clean();
  1403. if ($label) {
  1404. $out = $label .': '. $string;
  1405. }
  1406. else {
  1407. $out = $string;
  1408. }
  1409. $out .= "\n";
  1410. // The temp directory does vary across multiple simpletest instances.
  1411. $file = 'temporary://drupal_debug.txt';
  1412. if (file_put_contents($file, $out, FILE_APPEND) === FALSE) {
  1413. drupal_set_message(t('The file could not be written.'), 'error');
  1414. return FALSE;
  1415. }
  1416. }
  1417. /**
  1418. * Prints the arguments passed into the current function
  1419. */
  1420. function dargs($always = TRUE) {
  1421. static $printed;
  1422. if ($always || !$printed) {
  1423. $bt = debug_backtrace();
  1424. print kdevel_print_object($bt[1]['args']);
  1425. $printed = TRUE;
  1426. }
  1427. }
  1428. /**
  1429. * Print a SQL string from a DBTNG Query object. Includes quoted arguments.
  1430. *
  1431. * @param $query
  1432. * A Query object.
  1433. * @param $return
  1434. * Whether to return or print the string. Default to FALSE.
  1435. * @param $name
  1436. * Optional name for identifying the output.
  1437. */
  1438. function dpq($query, $return = FALSE, $name = NULL) {
  1439. if (user_access('access devel information')) {
  1440. $query->preExecute();
  1441. $sql = (string) $query;
  1442. $quoted = array();
  1443. $connection = Database::getConnection();
  1444. foreach ((array)$query->arguments() as $key => $val) {
  1445. $quoted[$key] = $connection->quote($val);
  1446. }
  1447. $sql = strtr($sql, $quoted);
  1448. if ($return) {
  1449. return $sql;
  1450. }
  1451. else {
  1452. dpm($sql, $name);
  1453. }
  1454. }
  1455. }
  1456. /**
  1457. * Print a variable to the 'message' area of the page. Uses drupal_set_message()
  1458. */
  1459. function dpm($input, $name = NULL) {
  1460. if (user_access('access devel information')) {
  1461. $export = kprint_r($input, TRUE, $name);
  1462. drupal_set_message($export);
  1463. }
  1464. }
  1465. /**
  1466. * drupal_var_export() a variable to the 'message' area of the page. Uses drupal_set_message()
  1467. */
  1468. function dvm($input, $name = NULL) {
  1469. if (user_access('access devel information')) {
  1470. $export = dprint_r($input, TRUE, $name, 'drupal_var_export', FALSE);
  1471. drupal_set_message($export);
  1472. }
  1473. }
  1474. // legacy function that was poorly named. use dpm() instead, since the 'p' maps to 'print_r'
  1475. function dsm($input, $name = NULL) {
  1476. dpm($input, $name);
  1477. }
  1478. /**
  1479. * An alias for dprint_r(). Saves carpal tunnel syndrome.
  1480. */
  1481. function dpr($input, $return = FALSE, $name = NULL) {
  1482. return dprint_r($input, $return, $name);
  1483. }
  1484. /**
  1485. * An alias for kprint_r(). Saves carpal tunnel syndrome.
  1486. */
  1487. function kpr($input, $return = FALSE, $name = NULL) {
  1488. return kprint_r($input, $return, $name);
  1489. }
  1490. /**
  1491. * Like dpr, but uses drupal_var_export() instead
  1492. */
  1493. function dvr($input, $return = FALSE, $name = NULL) {
  1494. return dprint_r($input, $return, $name, 'drupal_var_export', FALSE);
  1495. }
  1496. function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') {
  1497. // We do not want to krumo() strings and integers and such
  1498. if (merits_krumo($input)) {
  1499. if (user_access('access devel information')) {
  1500. return $return ? (isset($name) ? $name .' => ' : '') . krumo_ob($input) : krumo($input);
  1501. }
  1502. }
  1503. else {
  1504. return dprint_r($input, $return, $name, $function);
  1505. }
  1506. }
  1507. /**
  1508. * Pretty-print a variable to the browser (no krumo).
  1509. * Displays only for users with proper permissions. If
  1510. * you want a string returned instead of a print, use the 2nd param.
  1511. */
  1512. function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check= TRUE) {
  1513. if (user_access('access devel information')) {
  1514. if ($name) {
  1515. $name .= ' => ';
  1516. }
  1517. if ($function == 'drupal_var_export') {
  1518. include_once DRUPAL_ROOT . '/includes/utility.inc';
  1519. $output = drupal_var_export($input);
  1520. }
  1521. else {
  1522. ob_start();
  1523. $function($input);
  1524. $output = ob_get_clean();
  1525. }
  1526. if ($check) {
  1527. $output = check_plain($output);
  1528. }
  1529. if (count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) {
  1530. // don't use fapi here because sometimes fapi will not be loaded
  1531. $printed_value = "<textarea rows=30 style=\"width: 100%;\">\n". $name . $output .'</textarea>';
  1532. }
  1533. else {
  1534. $printed_value = '<pre>'. $name . $output .'</pre>';
  1535. }
  1536. if ($return) {
  1537. return $printed_value;
  1538. }
  1539. else {
  1540. print $printed_value;
  1541. }
  1542. }
  1543. }
  1544. /**
  1545. * Prints a renderable array element to the screen using kprint_r().
  1546. *
  1547. * #pre_render and/or #post_render pass-through callback for kprint_r().
  1548. *
  1549. * @todo Investigate appending to #suffix.
  1550. * @todo Investigate label derived from #id, #title, #name, and #theme.
  1551. */
  1552. function devel_render() {
  1553. $args = func_get_args();
  1554. // #pre_render and #post_render pass the rendered $element as last argument.
  1555. kprint_r(end($args));
  1556. // #pre_render and #post_render expect the first argument to be returned.
  1557. return reset($args);
  1558. }
  1559. /**
  1560. * Print the function call stack.
  1561. */
  1562. function ddebug_backtrace() {
  1563. if (user_access('access devel information')) {
  1564. $trace = debug_backtrace();
  1565. array_shift($trace);
  1566. $count = count($trace);
  1567. foreach ($trace as $i => $call) {
  1568. $key = ($count - $i) . ': ' . $call['function'];
  1569. $rich_trace[$key] = $call;
  1570. }
  1571. if (has_krumo()) {
  1572. print krumo($rich_trace);
  1573. }
  1574. else {
  1575. dprint_r($rich_trace);
  1576. }
  1577. }
  1578. }
  1579. // Delete all files in a dir. http://www.plus2net.com/php_tutorial/php-files-delete.php
  1580. function devel_empty_dir($dir) {
  1581. foreach (new DirectoryIterator($dir) as $fileInfo) {
  1582. unlink($fileInfo->getPathname());
  1583. }
  1584. }
  1585. /*
  1586. * migration related functions
  1587. */
  1588. /**
  1589. * Regenerate the data in node_comment_statistics table. Technique comes from
  1590. * http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101
  1591. *
  1592. * @return void
  1593. **/
  1594. function devel_rebuild_node_comment_statistics() {
  1595. // Empty table
  1596. db_truncate('node_comment_statistics')->execute();
  1597. // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case when
  1598. // two comments on the same node share same timestamp.
  1599. $sql = "
  1600. INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) (
  1601. SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c
  1602. JOIN (
  1603. SELECT c.nid, MAX(c.created) AS created, COUNT(*) AS comment_count FROM {comment} c WHERE status = 1 GROUP BY c.nid
  1604. ) AS c2 ON c.nid = c2.nid AND c.created = c2.created
  1605. )";
  1606. db_query($sql, array(':published' => COMMENT_PUBLISHED));
  1607. // Insert records into the node_comment_statistics for nodes that are missing.
  1608. $query = db_select('node', 'n');
  1609. $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid');
  1610. $query->addField('n', 'changed', 'last_comment_timestamp');
  1611. $query->addField('n', 'uid', 'last_comment_uid');
  1612. $query->addField('n', 'nid');
  1613. $query->addExpression('0', 'comment_count');
  1614. $query->addExpression('NULL', 'last_comment_name');
  1615. $query->isNull('ncs.comment_count');
  1616. db_insert('node_comment_statistics')
  1617. ->from($query)
  1618. ->execute();
  1619. }