devel.module 71 KB

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