prod_check.admin.inc 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. <?php
  2. /**
  3. * Build status page.
  4. */
  5. function prod_check_status() {
  6. drupal_set_title(t('Production check status'));
  7. $output = '';
  8. // Execute all functions per set as defined in the functions array in
  9. // _prod_check_functions().
  10. $functions = _prod_check_functions();
  11. // Not needed here.
  12. unset($functions['prod_mon']);
  13. unset($functions['perf_data']);
  14. foreach ($functions as $set => $data) {
  15. $result = array();
  16. foreach ($data['functions'] as $function => $title) {
  17. $check = call_user_func($function);
  18. if (is_array($check) && !empty($check)) {
  19. $result = array_merge($result, $check);
  20. }
  21. }
  22. $output .= '<h2>' . t($data['title']) . '</h2>' . "\n";
  23. $output .= '<div class="description"><p><em>' . t($data['description']) . '</em></p></div>' . "\n";
  24. $output .= theme('prod_check_status_report', array('requirements' => $result));
  25. }
  26. return $output;
  27. }
  28. /**
  29. * Build settings form.
  30. */
  31. function prod_check_settings_form($form, &$form_state) {
  32. drupal_set_title(t('Production check settings'));
  33. $form = array();
  34. // Add stylesheets & CSS.
  35. $base = drupal_get_path('module', 'prod_check');
  36. $form['#attached'] = array(
  37. 'css' => array(
  38. 'type' => 'file',
  39. 'data' => $base . '/css/prod-check.css',
  40. ),
  41. 'js' => array(
  42. array (
  43. 'type' => 'file',
  44. 'data' => $base . '/js/jquery.equalheights.js',
  45. ),
  46. array (
  47. 'type' => 'file',
  48. 'data' => $base . '/js/jquery.maskedinput.min.js',
  49. ),
  50. array (
  51. 'type' => 'file',
  52. 'data' => $base . '/js/prod-check.js',
  53. ),
  54. ),
  55. );
  56. // E-mail settings.
  57. $form['prod_check_general'] = array(
  58. '#type' => 'fieldset',
  59. '#title' => t('General settings'),
  60. '#description' => t('Settings to allow certain checks to function properly.'),
  61. '#collapsible' => FALSE,
  62. );
  63. $form['prod_check_general']['prod_check_sitemail'] = array(
  64. '#type' => 'textfield',
  65. '#title' => t('Mail check'),
  66. '#default_value' => variable_get('prod_check_sitemail', ''),
  67. '#size' => 60,
  68. '#description' => t('Enter (part of) the e-mail address you always <strong>use when developing</strong> a website. This is used in a regular expression in the "Site e-mail", Contact and Webform modules check.'),
  69. '#required' => FALSE,
  70. );
  71. if (module_exists('dblog')) {
  72. $form['prod_check_general']['prod_check_dblog_php'] = array(
  73. '#type' => 'select',
  74. '#title' => t('Minimal watchdog severity for PHP errors'),
  75. '#default_value' => variable_get('prod_check_dblog_php', WATCHDOG_WARNING),
  76. '#options' => watchdog_severity_levels(),
  77. '#description' => t('Select the severity level from which to start reporting PHP errors being logged to the watchdog table.'),
  78. '#required' => TRUE,
  79. );
  80. $form['prod_check_general']['prod_check_dblog_php_threshold'] = array(
  81. '#type' => 'textfield',
  82. '#title' => t('Threshold for PHP errors'),
  83. '#size' => 2,
  84. '#default_value' => variable_get('prod_check_dblog_php_threshold', 1),
  85. '#description' => t('Enter the number of times a PHP error needs to occur before reporting a problem. E.g. entering 3 here will allow 2 occurences of the exact same PHP error without an error being reported.'),
  86. '#required' => TRUE,
  87. );
  88. }
  89. $form['prod_check_apc'] = array(
  90. '#type' => 'fieldset',
  91. '#title' => t('Advanced APC/OPcache settings'),
  92. '#description' => t('These settings are used in the !link functionality.', prod_check_link_array('advanced APC', 'admin/reports/status/apc-opc')),
  93. '#collapsible' => TRUE,
  94. '#collapsed' => TRUE,
  95. );
  96. // Cache full count threshold
  97. $form['prod_check_apc']['prod_check_apc_expunge'] = array(
  98. '#type' => 'textfield',
  99. '#title' => t('APC/OPcache cache full count threshold'),
  100. '#default_value' => variable_get('prod_check_apc_expunge', 0),
  101. '#size' => 2,
  102. '#description' => t('Issue a critical error when the cache full count is greater than the number entered here.'),
  103. '#required' => FALSE,
  104. );
  105. // APC user.
  106. $form['prod_check_apc']['prod_check_apcuser'] = array(
  107. '#type' => 'textfield',
  108. '#title' => t('APC advanced features username'),
  109. '#default_value' => variable_get('prod_check_apcuser', 'apc'),
  110. '#size' => 60,
  111. '#description' => t('The username for logging in to the APC settings page.'),
  112. '#required' => FALSE,
  113. );
  114. // APC password.
  115. $form['prod_check_apc']['prod_check_apcpass'] = array(
  116. '#type' => 'password_confirm',
  117. '#title' => t('APC advanced features password'),
  118. '#size' => 60,
  119. '#description' => t('The password for logging in to the APC settings page.'),
  120. '#required' => FALSE,
  121. );
  122. // Disabled module settings
  123. $form['prod_check_disabled'] = array(
  124. '#type' => 'fieldset',
  125. '#title' => t('Disabled modules'),
  126. '#description' => t('You can choose to disable checking for updates for disabled modules here.'),
  127. '#collapsible' => TRUE,
  128. '#collapsed' => TRUE,
  129. );
  130. $form['prod_check_disabled']['prod_check_exclude_disabled_modules'] = array(
  131. '#type' => 'checkbox',
  132. '#title' => t("Don't check for updates for disabled modules"),
  133. '#default_value' => variable_get('prod_check_exclude_disabled_modules', 0),
  134. '#description' => t('When checked, only enabled modules will be reported. Note that this can cause some modules that are used but not enabled (f.e. APC, memcache, domain,...) to get skipped. See the documentation on how to add disabled modules on a whitelist.'),
  135. '#required' => FALSE,
  136. );
  137. $modules = implode(', ', _prod_check_get_disabled_modules_whitelist());
  138. $form['prod_check_disabled']['prod_check_disabled_modules_whitelist'] = array(
  139. '#markup' => t('Currently these modules will be forcefully checked even when they are disabled: %modules', array('%modules' => $modules)),
  140. );
  141. // XMLRPC settings.
  142. $form['prod_check_xmlrpc'] = array(
  143. '#type' => 'fieldset',
  144. '#title' => t('Production monitor integration.'),
  145. '#description' => t('You can set up integration with the Production monitor module here.'),
  146. '#collapsible' => FALSE,
  147. );
  148. $form['prod_check_xmlrpc']['prod_check_enable_xmlrpc'] = array(
  149. '#type' => 'checkbox',
  150. '#title' => t('Enable XMLRPC API'),
  151. '#default_value' => variable_get('prod_check_enable_xmlrpc', 0),
  152. '#description' => t('Tick this box if you would like to the module to open up the XMLRPC api so that it can be queried externally to supply information to a base site for monitoring purposes.'),
  153. '#ajax' => array(
  154. 'callback' => 'prod_check_enable_xmlrpc',
  155. 'wrapper' => 'prod-check-xmlrpc',
  156. 'effect' => 'fade',
  157. ),
  158. '#required' => FALSE,
  159. );
  160. // The #value here is necessary for the markup field to be rendered :-(
  161. $form['prod_check_xmlrpc']['xmlrpc'] = array(
  162. '#type' => 'markup',
  163. '#prefix' => '<div id="prod-check-xmlrpc">',
  164. '#suffix' => '</div>',
  165. );
  166. // Only show when the checkbox above is selected.
  167. if (!isset($form_state['values']['prod_check_enable_xmlrpc'])) {
  168. $form_state['values']['prod_check_enable_xmlrpc'] = variable_get('prod_check_enable_xmlrpc', 0);
  169. }
  170. if ($form_state['values']['prod_check_enable_xmlrpc']) {
  171. $form['prod_check_xmlrpc']['xmlrpc']['prod_check_xmlrpc_key'] = array(
  172. '#type' => 'textfield',
  173. '#title' => t('API key'),
  174. '#default_value' => variable_get('prod_check_xmlrpc_key', prod_check_generate_key()),
  175. '#maxlength' => 128,
  176. '#size' => 60,
  177. '#description' => t('Enter a key here to ensure secure transfer of data over the API. Use a mixture of alphanumeric and special characters for increased security.'),
  178. '#required' => FALSE,
  179. );
  180. $form['prod_check_xmlrpc']['xmlrpc']['prod_check_module_list_day'] = array(
  181. '#type' => 'select',
  182. '#title' => t('Report module list every'),
  183. '#options' => array(t('Sunday'), t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday')),
  184. '#default_value' => variable_get('prod_check_module_list_day', 0),
  185. '#description' => t('Defines which day the module list will be fetchable by Production monitor for an update status check.'),
  186. '#required' => FALSE,
  187. );
  188. $form['prod_check_xmlrpc']['xmlrpc']['prod_check_module_list_time'] = array(
  189. '#type' => 'textfield',
  190. '#title' => t('at this time'),
  191. '#default_value' => variable_get('prod_check_module_list_time', '03:00'),
  192. '#maxlength' => 5,
  193. '#size' => 5,
  194. '#description' => t('Defines what time (HH:MM) the module list will be fetchable by Production monitor for an update status check.'),
  195. '#required' => FALSE,
  196. );
  197. // See http://drupal.org/node/1058896#comment-5594076 .
  198. if (variable_get('prod_check_module_list_lastrun', 0) != -1) {
  199. $form['prod_check_xmlrpc']['xmlrpc']['reset'] = array(
  200. '#prefix' => '<div class="clear">',
  201. '#type' => 'submit',
  202. '#value' => t('Force immediate module list reporting'),
  203. '#postfix' => '</div>',
  204. );
  205. }
  206. else {
  207. $form['prod_check_xmlrpc']['xmlrpc']['reset'] = array(
  208. '#markup' => '<div class="description clear">' . t('Production monitor will fetch the module list on next cron run or when manually invoked for this site!') . '</div>',
  209. );
  210. }
  211. }
  212. // Nagios settings.
  213. if (module_exists('nagios')) {
  214. $form['prod_check_nagios'] = array(
  215. '#type' => 'fieldset',
  216. '#title' => t('Nagios integration.'),
  217. '#description' => t('You can set up integration with the !link module here.', prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')),
  218. '#collapsible' => FALSE,
  219. );
  220. $form['prod_check_nagios']['prod_check_enable_nagios'] = array(
  221. '#type' => 'checkbox',
  222. '#title' => t('Enable Nagios integration'),
  223. '#description' => t('Tick this box if you want to enable integration with Nagios. The !link module is required for this to function.', array('!link' => l(t('Nagios'), 'http://drupal.org/project/nagios', array('attributes' => array('title' => t('Nagios')))))),
  224. '#default_value' => variable_get('prod_check_enable_nagios', 0),
  225. '#ajax' => array(
  226. 'callback' => 'prod_check_enable_nagios',
  227. 'wrapper' => 'prod-check-nagios',
  228. 'effect' => 'fade',
  229. ),
  230. '#required' => FALSE,
  231. );
  232. // The #value here is necessary for the markup field to be rendered :-(
  233. $form['prod_check_nagios']['nagios'] = array(
  234. '#type' => 'markup',
  235. '#prefix' => '<div id="prod-check-nagios">',
  236. '#suffix' => '</div>',
  237. );
  238. // Only show when the checkbox above is selected.
  239. if (!isset($form_state['values']['prod_check_enable_nagios'])) {
  240. $form_state['values']['prod_check_enable_nagios'] = variable_get('prod_check_enable_nagios', 0);
  241. }
  242. // TODO: find a way to detect when this is rendered so we can adjust the
  243. // prod-check.js and apply equalheights/width
  244. if ($form_state['values']['prod_check_enable_nagios']) {
  245. $form['prod_check_nagios']['nagios']['settings'] = _prod_check_functions_as_form();
  246. $options = variable_get('prod_check_nagios_checks', array());
  247. if (!empty($options)) {
  248. // Just to increase readability of the source here.
  249. $monitor_settings = &$form['prod_check_nagios']['nagios']['settings']['prod_check_settings']['monitor_settings'];
  250. // Set default values to last saved state
  251. foreach (element_children($monitor_settings) as $set) {
  252. if (isset($options[$set])) {
  253. $monitor_settings[$set]['#default_value'] = $options[$set];
  254. }
  255. else {
  256. // No settings available, so uncheck all.
  257. $monitor_settings[$set]['#default_value'] = array();
  258. }
  259. }
  260. }
  261. $form['prod_check_nagios']['nagios']['settings']['prod_check_nagios_unique'] = array(
  262. '#type' => 'select',
  263. '#title' => t('When Nagios unique ID not recieved'),
  264. '#description' => t('Select what should happen when the Nagios unique ID is not recieved by the nagios page.'),
  265. '#options' => array(
  266. 'default' => t('default Nagios module behavior'),
  267. '404' => t('throw a 404 error'),
  268. 'home' => t('redirect to homepege'),
  269. ),
  270. '#default_value' => variable_get('prod_check_nagios_unique', 'default'),
  271. '#required' => FALSE,
  272. );
  273. $form['prod_check_nagios']['nagios']['settings']['prod_check_nagios_takeover'] = array(
  274. '#markup' => '<p>' . t(
  275. 'If you want prod_check to take over the Nagios status page, you can edit the !settings and enter %callback. Only then will the setting above have any effect!',
  276. array(
  277. '!settings' => l(t('Nagios page callback'), 'admin/config/system/nagios'),
  278. '%callback' => 'prod_check_nagios_status_page',
  279. )
  280. ) . '</p>',
  281. );
  282. $form['prod_check_nagios']['nagios']['settings']['prod_check_nagios_verbose'] = array(
  283. '#type' => 'checkbox',
  284. '#title' => t('Show verbose status info'),
  285. '#description' => t('Tick this box if you want to see detailed information about every check. Useful for debugging or first setup, but <strong>not recommended for production use!</strong>'),
  286. '#default_value' => variable_get('prod_check_nagios_verbose', 0),
  287. '#required' => FALSE,
  288. );
  289. }
  290. }
  291. // Submit buttons.
  292. // Markup field for proper styling.
  293. $form['buttons'] = array(
  294. '#type' => 'markup',
  295. '#prefix' => '<div class="form-actions">',
  296. '#suffix' => '</div>',
  297. );
  298. $form['buttons']['submit'] = array(
  299. '#type' => 'submit',
  300. '#value' => t('Save configuration'),
  301. );
  302. $form['buttons']['reset'] = array(
  303. '#type' => 'submit',
  304. '#value' => t('Reset to defaults'),
  305. );
  306. return $form;
  307. }
  308. /**
  309. * Parse the functions array and return a form fieldset with different sets of
  310. * checkboxes so it can be inserted 'as is' in another form array for rendering.
  311. * This is primlarily still here for integration with Nagios. Though not in use
  312. * now since Nagios would create far too much needless variables in my personal
  313. * opinion. It's left in for easy future integration with the
  314. * hook_nagios_settings().
  315. */
  316. function _prod_check_functions_as_form($compatibility = 'all') {
  317. $form = array();
  318. $form['prod_check_settings'] = array(
  319. '#type' => 'fieldset',
  320. '#title' => t('Configure what data you wish to monitor with <strong>Nagios</strong> for this site.'),
  321. '#collapsible' => TRUE,
  322. '#collapsed' => FALSE,
  323. );
  324. $form['prod_check_settings']['monitor_settings'] = array(
  325. '#type' => 'markup',
  326. '#prefix' => '<div id="prod-check-settings">',
  327. '#suffix' => '</div>',
  328. '#tree' => TRUE,
  329. );
  330. $i = 1;
  331. $functions = _prod_check_functions();
  332. if ($compatibility == 'all') {
  333. unset($functions['prod_mon']);
  334. unset($functions['perf_data']);
  335. }
  336. foreach ($functions as $set => $data) {
  337. $rest = $i % 2;
  338. $form['prod_check_settings']['monitor_settings'][$set] = array(
  339. '#type' => 'checkboxes',
  340. '#title' => t($data['title']),
  341. '#description' => t($data['description']),
  342. '#options' => $data['functions'],
  343. '#default_value' => array_keys($data['functions']),
  344. '#prefix' => '<div class="prod-check-settings ' . (($rest) ? 'odd' : 'even') . '">',
  345. '#suffix' => '</div>',
  346. );
  347. $i++;
  348. }
  349. return $form;
  350. }
  351. /**
  352. * Callback to add xmlrpc settings.
  353. */
  354. function prod_check_enable_xmlrpc($form, &$form_state) {
  355. return $form['prod_check_xmlrpc']['xmlrpc'];
  356. }
  357. /**
  358. * Callback to add nagios settings.
  359. */
  360. function prod_check_enable_nagios($form, &$form_state) {
  361. return $form['prod_check_nagios']['nagios'];
  362. }
  363. /**
  364. * Validation for settings form.
  365. */
  366. function prod_check_settings_form_validate($form, &$form_state) {
  367. // Had to add CSS again here since it was lost on form errors. Weird, doesn't
  368. // seem logical...
  369. $base = drupal_get_path('module', 'prod_check');
  370. drupal_add_css($base . '/css/prod-check.css');
  371. drupal_add_js($base . '/js/jquery.equalheights.js');
  372. drupal_add_js($base . '/js/jquery.maskedinput.min.js');
  373. drupal_add_js($base . '/js/prod-check.js');
  374. if (module_exists('dblog')) {
  375. if (!is_numeric($form_state['values']['prod_check_dblog_php_threshold'])) {
  376. form_set_error('prod_check_dblog_php_threshold', t('The PHP error threshold should be numeric!'));
  377. }
  378. }
  379. if ($form_state['values']['prod_check_enable_xmlrpc']) {
  380. if (empty($form_state['values']['prod_check_xmlrpc_key'])) {
  381. form_set_error('prod_check_xmlrpc_key', t('When enabling the XPLRPC API, you <strong>must</strong> enter an API key!'));
  382. }
  383. }
  384. if (!empty($form_state['values']['prod_check_module_list_time'])) {
  385. // This check in case JavaScript is not enabled / malfunctioning.
  386. if (strpos($form_state['values']['prod_check_module_list_time'], ':') != 2) {
  387. form_set_error('prod_check_module_list_time', t('Time must be input in 24 hour format: HH:MM!'));
  388. }
  389. else {
  390. $time = explode(':', $form_state['values']['prod_check_module_list_time']);
  391. if (intval($time[0]) > 23) {
  392. form_set_error('prod_check_module_list_time', t('Hours must range from 00 (midnight) to 23!'));
  393. }
  394. if (intval($time[1]) > 59) {
  395. form_set_error('prod_check_module_list_time', t('Minutes must range from 00 to 59!'));
  396. }
  397. }
  398. }
  399. if (!is_numeric($form_state['values']['prod_check_apc_expunge'])) {
  400. form_set_error('prod_check_apc_expunge', t('APC/OPcache Cache full count threshold should be numeric!'));
  401. }
  402. if (isset($form_state['values']['prod_check_enable_nagios']) && $form_state['values']['prod_check_enable_nagios']) {
  403. $checks = array();
  404. foreach ($form_state['values']['monitor_settings'] as $set => $data) {
  405. foreach ($data as $check => $value) {
  406. if ($value) {
  407. $checks[$set][] = $value;
  408. }
  409. }
  410. }
  411. if (empty($checks)) {
  412. form_set_error('monitor_settings', t('When enabling Nagios support, you <strong>must</strong> tick at least one of the checkboxes!'));
  413. }
  414. }
  415. }
  416. /**
  417. * Submit for settings form.
  418. *
  419. * TODO: is it better to split all of these in separate functions and use
  420. * buttons with the #submit property? The latter is better readability wise I
  421. * guess.
  422. */
  423. function prod_check_settings_form_submit($form, &$form_state) {
  424. switch ($form_state['values']['op']) {
  425. case t('Force immediate module list reporting'):
  426. variable_set('prod_check_module_list_lastrun', -1);
  427. break;
  428. case t('Save configuration'):
  429. variable_set('prod_check_sitemail', $form_state['values']['prod_check_sitemail']);
  430. // PHP errors.
  431. if (module_exists('dblog')) {
  432. variable_set('prod_check_dblog_php', $form_state['values']['prod_check_dblog_php']);
  433. variable_set('prod_check_dblog_php_threshold', $form_state['values']['prod_check_dblog_php_threshold']);
  434. }
  435. // APC/OPcache.
  436. variable_set('prod_check_apc_expunge', $form_state['values']['prod_check_apc_expunge']);
  437. variable_set('prod_check_apcuser', $form_state['values']['prod_check_apcuser']);
  438. if (!empty($form_state['values']['prod_check_apcpass'])) {
  439. variable_set('prod_check_apcpass', $form_state['values']['prod_check_apcpass']);
  440. }
  441. else {
  442. variable_set('prod_check_apcpass', 'password');
  443. }
  444. variable_set('prod_check_exclude_disabled_modules', $form_state['values']['prod_check_exclude_disabled_modules']);
  445. if ($form_state['values']['prod_check_enable_xmlrpc']) {
  446. // Enable.
  447. variable_set('prod_check_enable_xmlrpc', $form_state['values']['prod_check_enable_xmlrpc']);
  448. variable_set('prod_check_xmlrpc_key', $form_state['values']['prod_check_xmlrpc_key']);
  449. variable_set('prod_check_module_list_day', $form_state['values']['prod_check_module_list_day']);
  450. variable_set('prod_check_module_list_time', $form_state['values']['prod_check_module_list_time']);
  451. }
  452. else {
  453. // Disable.
  454. variable_set('prod_check_enable_xmlrpc', 0);
  455. }
  456. // This is why we didn't use a system_settings_form().
  457. if (isset($form_state['values']['prod_check_enable_nagios']) && $form_state['values']['prod_check_enable_nagios']) {
  458. $checks = array();
  459. foreach ($form_state['values']['monitor_settings'] as $set => $data) {
  460. foreach ($data as $check => $value) {
  461. if ($value) {
  462. $checks[$set][] = $value;
  463. }
  464. }
  465. }
  466. // Enable.
  467. variable_set('prod_check_enable_nagios', $form_state['values']['prod_check_enable_nagios']);
  468. variable_set('prod_check_nagios_checks', $checks);
  469. variable_set('prod_check_nagios_unique', $form_state['values']['prod_check_nagios_unique']);
  470. variable_set('prod_check_nagios_verbose', $form_state['values']['prod_check_nagios_verbose']);
  471. }
  472. else {
  473. // Disable.
  474. variable_set('prod_check_enable_nagios', 0);
  475. }
  476. drupal_set_message(t('The configuration options have been saved.'));
  477. break;
  478. case t('Reset to defaults'):
  479. // This beats multiple variable_del() calls.
  480. // Don't delete prod_check_module_list_lastrun!
  481. // DELETE FROM {variable} WHERE name LIKE "prod_check\_%" AND name <> "prod_check_module_list_lastrun"'
  482. db_delete('variable')
  483. ->condition('name', 'prod_check\_%', 'LIKE')
  484. ->condition('name', 'prod_check_module_list_lastrun', '<>')
  485. ->execute();
  486. cache_clear_all('variables', 'cache_bootstrap');
  487. drupal_set_message(t('The configuration options have been reset to their default values.'));
  488. break;
  489. }
  490. }
  491. /**
  492. * Setup site for production mode.
  493. */
  494. function prod_check_prod_mode_form() {
  495. drupal_set_title(t('Switch to production mode'));
  496. $form = array();
  497. // Put this in hook_help() first but some themes hide the help text. It's too
  498. // important to be hidden, so moved it here.
  499. $form['help'] = array(
  500. '#markup' => '<p>' . t('Submitting this form will switch this website to <em>production mode</em>. This means that various settings will be adjusted in order to fix most of the problems reported on the status page. <strong>Some modules will be disabled as well!</strong>') . '</p>',
  501. );
  502. $form['site_mail'] = array(
  503. '#type' => 'textfield',
  504. '#title' => t('Site e-mail address'),
  505. '#description' => t('This field is optional and will be used to setup the default e-mail address of this site. <strong>Currently set to %mail.</strong>', array('%mail' => variable_get('site_mail', '[not set]'))),
  506. );
  507. if (module_exists('webform')) {
  508. $form['webform_default_from_address'] = array(
  509. '#type' => 'textfield',
  510. '#title' => t('Webform default from e-mail address'),
  511. '#description' => t('This field is optional and will be used to setup the default from e-mail address for <em>Webform</em>. <strong>Currently set to %mail.</strong>', array('%mail' => variable_get('webform_default_from_address', '[not set]'))),
  512. );
  513. }
  514. if (module_exists('googleanalytics')) {
  515. $form['googleanalytics_account'] = array(
  516. '#type' => 'textfield',
  517. '#title' => t('Google Analytics Web Property ID'),
  518. '#description' => t('This field is optional and will be used to setup the <em>Google Analytics</em> account. <strong>Currently set to %account.</strong>', array('%account' => variable_get('googleanalytics_account', '[not set]'))),
  519. );
  520. }
  521. $form['block_cache'] = array(
  522. '#type' => 'checkbox',
  523. '#title' => t('Enable <em>Block cache</em>'),
  524. '#description' => t("If ticked, this will enable block caching. <strong>This can cause unwanted results if custom blocks don't handle caching well!</strong>"),
  525. );
  526. if (module_exists('dblog')) {
  527. $form['dblog'] = array(
  528. '#type' => 'checkbox',
  529. '#title' => t('Disable <em>Database logging</em>'),
  530. '#description' => t('If ticked, this will disable the <em>dblog</em> module wich could generate too much overhead on high traffic sites.'),
  531. );
  532. }
  533. $form['nagios'] = array(
  534. '#type' => 'checkbox',
  535. '#title' => t('Enable <em>Nagios</em>'),
  536. '#description' => t('If ticked, this will enable the <em>Nagios</em> monitoring module if it is present.'),
  537. );
  538. $form['submit'] = array(
  539. '#type' => 'submit',
  540. '#value' => t('Enable production mode'),
  541. );
  542. return $form;
  543. }
  544. /**
  545. * Setup site for production mode: validation.
  546. */
  547. function prod_check_prod_mode_form_validate($form, &$form_state) {
  548. $checks = array('site_mail', 'webform_default_from_address');
  549. foreach ($checks as $field) {
  550. if (!empty($form_state['values'][$field])) {
  551. if (!valid_email_address($form_state['values'][$field])) {
  552. form_set_error($field, t('The e-mail address %mail is not valid.', array('%mail' => $form_state['values'][$field])));
  553. }
  554. }
  555. }
  556. // Google analytics.
  557. if (!empty($form_state['values']['googleanalytics_account'])) {
  558. if (!preg_match('/^UA-\d{4,}-\d+$/', $form_state['values']['googleanalytics_account'])) {
  559. form_set_error('googleanalytics_account', t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.'));
  560. }
  561. }
  562. }
  563. // TODO: add confirm form here? In the form of:
  564. // The following actions will be performed: [...] Are you sure you want to
  565. // continue?
  566. /**
  567. * Setup site for production mode: submit.
  568. */
  569. function prod_check_prod_mode_form_submit($form, &$form_state) {
  570. // Adjust settings.
  571. $variables = prod_check_prod_mode_settings($form_state['values']);
  572. drupal_set_message(t('The following settings have been changed: %variables.', array('%variables' => implode(', ', array_keys($variables)))));
  573. // Enable / disable modules.
  574. $modules = prod_check_prod_mode_modules($form_state['values']);
  575. if (!empty($modules['disable'])) {
  576. drupal_set_message(t('The following modules have been <strong>disabled</strong>: %modules.', array('%modules' => implode(', ', $modules['disable']))));
  577. }
  578. if (!empty($modules['enable'])) {
  579. drupal_set_message(t('The following modules have been <strong>enabled</strong>: %modules.', array('%modules' => implode(', ', $modules['enable']))));
  580. }
  581. $form_state['redirect'] = 'admin/reports/prod-check';
  582. }
  583. /**
  584. * Helper function to adjust settings. Also used by Drush.
  585. */
  586. function prod_check_prod_mode_settings($options) {
  587. $variables = array(
  588. // Error messages to display.
  589. 'error_level' => ERROR_REPORTING_HIDE,
  590. // Cache pages for anonymous users.
  591. 'cache' => 1,
  592. // Aggregate and compress CSS files.
  593. 'preprocess_css' => 1,
  594. // Aggregate JavaScript files.
  595. 'preprocess_js' => 1,
  596. );
  597. // No page compression when running Varnish.
  598. if (!module_exists('varnish') && !module_exists('steroids')) {
  599. // Compress cached pages.
  600. $variables['page_compression'] = 1;
  601. }
  602. // Site e-mail address.
  603. if (isset($options['site_mail']) && !empty($options['site_mail'])) {
  604. $variables['site_mail'] = $options['site_mail'];
  605. }
  606. // Webform default from e-mail address.
  607. if (isset($options['webform_default_from_address']) && !empty($options['webform_default_from_address'])) {
  608. $variables['webform_default_from_address'] = $options['webform_default_from_address'];
  609. }
  610. // Google analytics account.
  611. if (isset($options['googleanalytics_account']) && !empty($options['googleanalytics_account'])) {
  612. $variables['googleanalytics_account'] = $options['googleanalytics_account'];
  613. }
  614. // Cache blocks.
  615. if (isset($options['block_cache']) && !empty($options['block_cache'])) {
  616. $variables['block_cache'] = 1;
  617. }
  618. // Set variables. Wanted to do this in one query, but that was impossible due
  619. // to the fact that it is possible a variable does not exist in the database
  620. // yet and still have a value. Damn that default value in variable_get()!
  621. foreach ($variables as $variable => $value) {
  622. variable_set($variable, $value);
  623. }
  624. // Clear caches like the system_performance_settings() form does.
  625. drupal_clear_css_cache();
  626. drupal_clear_js_cache();
  627. // Instead of calling system_clear_page_cache_submit()
  628. cache_clear_all('*', 'cache_page', TRUE);
  629. return $variables;
  630. }
  631. /**
  632. * Helper function to enable / disable modules. Also used by Drush.
  633. */
  634. function prod_check_prod_mode_modules($options) {
  635. $modules['disable'] = array(
  636. 'devel',
  637. 'devel_generate',
  638. 'devel_node_access',
  639. 'devel_themer',
  640. 'update',
  641. );
  642. if (isset($options['dblog']) && $options['dblog']) {
  643. $modules['disable'][] = 'dblog';
  644. }
  645. // We do this primarily to prepare feedback to the user. module_disable() will
  646. // do a module_exists() check as well but only provides feedback using
  647. // watchdog().
  648. foreach ($modules['disable'] as $id => $module) {
  649. if (!module_exists($module)) {
  650. unset($modules['disable'][$id]);
  651. }
  652. }
  653. if (!empty($modules['disable'])) {
  654. module_disable($modules['disable']);
  655. }
  656. $modules['enable'] = array();
  657. if ($options['nagios']) {
  658. $modules['enable'][] = 'nagios';
  659. }
  660. // We cannot check if a module is available on the file system. At least,
  661. // there is not a function in Drupal that I know of to do this and I could not
  662. // find one. Hence this approach, until there's a better way.
  663. if (!empty($modules['enable'])) {
  664. module_enable($modules['enable']);
  665. foreach($modules['enable'] as $id => $module) {
  666. if (!module_exists($module)) {
  667. unset($modules['enable'][$id]);
  668. }
  669. }
  670. }
  671. return $modules;
  672. }
  673. /**
  674. * Generate default key if none is present.
  675. */
  676. function prod_check_generate_key($length = 25) {
  677. $chars = 'abcdefghijklmnopqrstuxyvwzABCDEFGHIJKLMNOPQRSTUXYVWZ+-*#&@!?';
  678. $size = strlen($chars);
  679. $key = '';
  680. for ($i = 0; $i < $length; $i++) {
  681. $key .= $chars[rand(0, $size - 1)];
  682. }
  683. return $key;
  684. }
  685. /**
  686. * Database status page.
  687. */
  688. function prod_check_dbstatus() {
  689. // Get active connection.
  690. $pdo = Database::getConnection();
  691. // Get database driver.
  692. $db_type = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
  693. $details = array(
  694. 'status' => array(),
  695. 'tables' => array(),
  696. 'databases' => array(),
  697. );
  698. $add_js = FALSE;
  699. $title = '';
  700. $db = $db_type;
  701. switch($db_type) {
  702. case 'pgsql':
  703. // Set title & version.
  704. $server = db_query('SELECT version()')->fetchField();
  705. $title = t('Running @version', array('@version' => $server));
  706. // Get detailed status.
  707. $details = _prod_check_dbstatus_pgsql($details);
  708. break;
  709. case 'mysql':
  710. $db = 'MySQL';
  711. // Get detailed status.
  712. $details = _prod_check_dbstatus_mysql($details);
  713. // NO break here!
  714. default:
  715. // Set title & version.
  716. $server = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
  717. $title = t('Running @db @version', array('@db' => $db, '@version' => $server));
  718. break;
  719. }
  720. // Get basic status.
  721. $status = '';
  722. try {
  723. $status = $pdo->getAttribute(PDO::ATTR_SERVER_INFO);
  724. if (is_array($status)) {
  725. $status = implode("<br />\n", $status);
  726. }
  727. else {
  728. $status = str_replace(' ', "<br />\n", $status);
  729. }
  730. } catch(Exception $e) {}
  731. // Get additional status.
  732. $additional = '';
  733. $attributes = array(
  734. 'AUTOCOMMIT' => 'Auto commit',
  735. 'PREFETCH' => 'Prefetch',
  736. 'TIMEOUT' => 'Timeout',
  737. 'ERRMODE' => 'Error mode',
  738. 'CLIENT_VERSION' => 'Client version',
  739. 'CONNECTION_STATUS' => 'Connection status',
  740. 'CASE' => 'Case',
  741. 'CURSOR_NAME' => 'Cursor name',
  742. 'CURSOR' => 'Cursor',
  743. 'ORACLE_NULLS' => 'Oracle nulls',
  744. 'PERSISTENT' => 'Persistent',
  745. 'STATEMENT_CLASS' => 'Statement class',
  746. 'FETCH_CATALOG_NAMES' => 'Fatch catalog names',
  747. 'FETCH_TABLE_NAMES' => 'Fetch table names',
  748. 'STRINGIFY_FETCHES' => 'Stringify fetches',
  749. 'MAX_COLUMN_LEN' => 'Max column length',
  750. 'DEFAULT_FETCH_MODE' => 'Default fetch mode',
  751. 'EMULATE_PREPARES' => 'Emulate prepares',
  752. );
  753. foreach ($attributes as $constant => $name) {
  754. try {
  755. $result = $pdo->getAttribute(constant("PDO::ATTR_$constant"));
  756. if (is_bool($result)) {
  757. $result = $result ? 'TRUE' : 'FALSE';
  758. }
  759. elseif (is_array($result) || is_object($result)) {
  760. $add_js = TRUE;
  761. include_once DRUPAL_ROOT . '/includes/utility.inc';
  762. $class = strtolower(str_replace('_', '-', $constant));
  763. $link = l(
  764. t('Show details'),
  765. 'admin/reports/status/database',
  766. array(
  767. 'attributes' => array(
  768. 'class' => array('show-more'),
  769. 'data-details' => $class
  770. )
  771. )
  772. );
  773. // Seemed a bit overkill to create a css file only for this display:none
  774. // thingy.
  775. $result = $link . '<pre class="' . $class . '" style="display:none;">' . drupal_var_export($result) . '</pre>';
  776. }
  777. $additional .= $name . ': ' . $result . "<br />\n";
  778. } catch (Exception $e) {}
  779. }
  780. $status = "$additional<br />\n$status";
  781. if ($add_js) {
  782. $base = drupal_get_path('module', 'prod_check');
  783. drupal_add_js($base . '/js/prod-check-database.js');
  784. }
  785. return theme('prod_check_dbstatus', array('title' => $title, 'status' => $status, 'details' => $details));
  786. }
  787. /**
  788. * Helper function to return MySQL detailed status info.
  789. */
  790. function _prod_check_dbstatus_mysql($details) {
  791. $db_name = '';
  792. // Feels like there should be a better way of getting the current database
  793. // name.
  794. $db_setting = Database::getConnectionInfo();
  795. foreach($db_setting as $params) {
  796. if(isset($params['database'])) {
  797. // We get the first name we find.
  798. $db_name = $params['database'];
  799. break;
  800. }
  801. }
  802. // Get detailed status.
  803. $rows = array();
  804. try {
  805. $result = db_query('SHOW STATUS');
  806. foreach ($result as $row) {
  807. $rows[] = array($row->Variable_name, $row->Value);
  808. }
  809. } catch (Exception $e) {}
  810. if ($rows) {
  811. $details['status'] = array(
  812. 'title' => t('Detailed status'),
  813. 'header' => array(
  814. t('Variable'),
  815. t('Value'),
  816. ),
  817. 'rows' => $rows,
  818. );
  819. }
  820. // Get all tables.
  821. $rows = array();
  822. try {
  823. // We cannot use the standard db_query with arguments here as the argument
  824. // should NOT be enclosed in quotes.
  825. $result = db_query(sprintf('SHOW TABLES FROM %s', $db_name));
  826. $property = 'Tables_in_' . $db_name;
  827. foreach ($result as $row) {
  828. $rows[] = array($row->$property);
  829. }
  830. } catch (Exception $e) {}
  831. if ($rows) {
  832. $details['tables'] = array(
  833. 'title' => t('Tables for active database %name', array('%name' => $db_name)),
  834. 'header' => array(
  835. t('Table'),
  836. ),
  837. 'rows' => $rows,
  838. );
  839. }
  840. // Get all databases.
  841. $rows = array();
  842. try {
  843. $result = db_query('SHOW DATABASES');
  844. foreach ($result as $row) {
  845. $rows[] = array($row->Database);
  846. }
  847. } catch (Exception $e) {}
  848. if ($rows) {
  849. $details['databases'] = array(
  850. 'title' => t('Available databases'),
  851. 'header' => array(
  852. t('Database'),
  853. ),
  854. 'rows' => $rows,
  855. );
  856. }
  857. return $details;
  858. }
  859. /**
  860. * Helper function to return PostgreSQL status info.
  861. *
  862. * Useful links for possible expansion:
  863. * http://www.php.net/manual/en/book.pgsql.php
  864. * http://www.alberton.info/postgresql_meta_info.html#.UbXmAFQW3eU
  865. * http://www.postgresql.org/docs/devel/static/catalog-pg-statistic.html
  866. * http://www.postgresql.org/docs/9.0/static/functions-info.html
  867. * http://www.postgresql.org/docs/9.0/static/functions-admin.html
  868. */
  869. function _prod_check_dbstatus_pgsql($db_name, $details) {
  870. // Get detailed status.
  871. $rows = array();
  872. try {
  873. // See http://www.postgresql.org/docs/9.0/static/view-pg-settings.html
  874. $result = db_query('SELECT * FROM pg_settings');
  875. foreach ($result as $row) {
  876. /* TODO: add some more detail here? This is available:
  877. Column | Type | Modifiers | Storage | Description
  878. ------------+------+-----------+----------+-------------
  879. name | text | | extended |
  880. setting | text | | extended |
  881. unit | text | | extended |
  882. category | text | | extended |
  883. short_desc | text | | extended |
  884. extra_desc | text | | extended |
  885. context | text | | extended |
  886. vartype | text | | extended |
  887. source | text | | extended |
  888. min_val | text | | extended |
  889. max_val | text | | extended | */
  890. $rows[] = array($row->name, $row->setting);
  891. }
  892. } catch (Exception $e) {}
  893. if ($rows) {
  894. $details['status'] = array(
  895. 'title' => t('Detailed status'),
  896. 'header' => array(
  897. t('Name'),
  898. t('Setting'),
  899. ),
  900. 'rows' => $rows,
  901. );
  902. }
  903. // Get all tables.
  904. $rows = array();
  905. try {
  906. // See http://www.postgresql.org/docs/9.0/static/catalog-pg-class.html
  907. // relclass: r = ordinary table, i = index, S = sequence, v = view, c = composite type, t = TOAST table
  908. $result = db_query("SELECT relname FROM pg_class WHERE relname !~ '^(pg_|sql_)' AND relkind = 'r'");
  909. foreach ($result as $row) {
  910. $rows[] = array($row->relname);
  911. }
  912. } catch (Exception $e) {}
  913. if ($rows) {
  914. $details['tables'] = array(
  915. 'title' => t('Tables for active database %name', array('%name' => $db_name)),
  916. 'header' => array(
  917. t('Table'),
  918. ),
  919. 'rows' => $rows,
  920. );
  921. }
  922. // Get all databases.
  923. $rows = array();
  924. try {
  925. $result = db_query('SELECT datname FROM pg_database');
  926. foreach ($result as $row) {
  927. $rows[] = array($row->datname);
  928. }
  929. } catch (Exception $e) {}
  930. if ($rows) {
  931. $details['databases'] = array(
  932. 'title' => t('Available databases'),
  933. 'header' => array(
  934. t('Database'),
  935. ),
  936. 'rows' => $rows,
  937. );
  938. }
  939. return $details;
  940. }
  941. /**
  942. * Integration of the APC status page.
  943. */
  944. function prod_check_apc_opc() {
  945. // APC.
  946. if (function_exists('apc_cache_info')) {
  947. define('ADMIN_USERNAME', variable_get('prod_check_apcuser', 'apc'));
  948. define('ADMIN_PASSWORD', variable_get('prod_check_apcpass', 'password'));
  949. include(drupal_get_path('module', 'prod_check') . '/includes/prod_check.apc.inc');
  950. exit;
  951. }
  952. // OPcache.
  953. elseif (function_exists('opcache_get_status')) {
  954. include(drupal_get_path('module', 'prod_check') . '/includes/prod_check.opcache.inc');
  955. exit;
  956. }
  957. else {
  958. return t('APC nor OPcache is installed on this webserver!');
  959. }
  960. }
  961. /**
  962. * Integration of the Memcache status page.
  963. */
  964. function prod_check_memcache() {
  965. // Memcache module defaults to 127.0.0.1:11211
  966. $memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
  967. global $MEMCACHE_SERVERS;
  968. $MEMCACHE_SERVERS = array_keys($memcache_servers);
  969. include(drupal_get_path('module', 'prod_check') . '/includes/prod_check.memcache.inc');
  970. exit;
  971. }