devel.module 67 KB

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