LogFilter.inc 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989
  1. <?php
  2. /**
  3. * @file
  4. * Drupal Log Filter module
  5. */
  6. class LogFilter {
  7. /**
  8. * Numeric index of filter name request argument, if any.
  9. *
  10. * @type integer
  11. */
  12. const FILTER_NAME_ARG = 4;
  13. /**
  14. * @type integer
  15. */
  16. const TYPE_SOME_MAX = 102400;
  17. /**
  18. * @type integer
  19. */
  20. const PAGE_RANGE_DEFAULT = 20;
  21. /**
  22. * @type integer
  23. */
  24. const PAGE_RANGE_MAX = 1000;
  25. /**
  26. * @type integer
  27. */
  28. const LIST_MESSAGE_TRUNCATE = 100;
  29. /**
  30. * Does databases (MySQL) support LIMIT for sub queries?
  31. *
  32. * @type boolean
  33. */
  34. const DB_SUBQUERY_LIMIT = FALSE;
  35. /**
  36. * @type array $_errorCodes
  37. */
  38. protected static $_errorCodes = array(
  39. 'unknown' => 1,
  40. // Programmatic errors and wrong use of program.
  41. 'algo' => 100,
  42. 'use' => 101,
  43. // Missing permission.
  44. 'perm_general' => 200,
  45. 'form_expired' => 201,
  46. 'perm_filter_crud' => 202,
  47. 'perm_filter_restricted' => 203,
  48. // Database.
  49. 'db_general' => 500,
  50. // Misc.
  51. 'filter_name_composition' => 1001,
  52. 'filter_name_nonunique' => 1002,
  53. 'filter_doesnt_exist' => 1003,
  54. 'bad_filter_condition' => 1010,
  55. );
  56. /**
  57. * Traces and logs via Inspect tracer, or watchdog (no trace).
  58. *
  59. * @param Exception $xc
  60. * @param integer $severity
  61. * - default: WATCHDOG_ERROR
  62. * @return void
  63. */
  64. protected static function _errorHandler($xc, $severity = WATCHDOG_ERROR) {
  65. if (module_exists('inspect')) {
  66. inspect_trace($xc, array('category' => 'log_filter', 'severity' => $severity));
  67. }
  68. else {
  69. watchdog(
  70. 'log_filter',
  71. '(' . (int)$xc->getCode() . ') ' . check_plain($xc->getMessage()),
  72. NULL,
  73. $severity
  74. );
  75. }
  76. }
  77. /**
  78. * @type array
  79. */
  80. protected static $_fields = array(
  81. 'settings' => array(
  82. 'only_own',
  83. 'delete_logs_max',
  84. 'translate',
  85. 'pager_range',
  86. ),
  87. 'filter' => array(
  88. 'name', // Hidden.
  89. 'origin',
  90. 'name_suggest',
  91. 'description',
  92. 'require_admin',
  93. ),
  94. 'conditions' => array(
  95. 'time_range',
  96. 'time_from',
  97. 'time_from_proxy', // Skip in actual conditions.
  98. 'time_to',
  99. 'time_to_proxy', // Skip in actual conditions.
  100. 'severity',
  101. 'type_wildcard', // Skip in actual conditions.
  102. 'type',
  103. 'role', // Default in database: -1.
  104. 'uid', // Default in database: -1.
  105. 'hostname',
  106. 'location',
  107. 'referer',
  108. ),
  109. 'order_by' => array(
  110. 'orderby_',
  111. 'descending_',
  112. ),
  113. );
  114. /**
  115. * Defines log viewer form and GUI.
  116. *
  117. * @param array $form
  118. * @param array &$form_state
  119. * @return array
  120. */
  121. public static function viewerForm($form, &$form_state) {
  122. $path = drupal_get_path('module', 'log_filter');
  123. // Get Judy.
  124. drupal_add_library('judy', 'judy');
  125. // Get jQuery UI dialog.
  126. drupal_add_library('system', 'ui.dialog');
  127. // Get jQuery UI datepicker.
  128. drupal_add_library('system', 'ui.datepicker');
  129. // Get jQuery UI autocomplete for username.
  130. drupal_add_library('system', 'ui.autocomplete');
  131. // Have to include own datepicker localisation, because it doesnt seem to exist in neither core nor the Date Popup module.
  132. drupal_add_js(
  133. $path . '/js/jquery_ui_datepicker_i18n/jquery.ui.datepicker-' . $GLOBALS['language']->language . '.min.js',
  134. array('type' => 'file', 'group' => JS_DEFAULT, 'preprocess' => FALSE, 'every_page' => FALSE)
  135. );
  136. // Use the module's default css.
  137. if (($use_module_css = variable_get('log_filter_cssdefault', TRUE))) {
  138. drupal_add_css(
  139. $path . '/css/log_filter' . '.min' . '.css',
  140. array('type' => 'file', 'group' => CSS_DEFAULT, 'preprocess' => FALSE, 'every_page' => FALSE)
  141. );
  142. }
  143. // Add table header script.
  144. drupal_add_js('misc/tableheader.js');
  145. drupal_add_js(
  146. $path . '/js/log_filter' . '.min' . '.js',
  147. array('type' => 'file', 'group' => JS_DEFAULT, 'preprocess' => FALSE, 'every_page' => FALSE)
  148. );
  149. try {
  150. $allow_filter_edit = user_access('log_filter edit filters');
  151. // Get submitted vars from session, and remove them (for later form build and submission).
  152. $formSubmitted = $session = $settings = $submitted = $messages = NULL;
  153. if (module_exists('state')) {
  154. if (($session = State::sessionGet('module', 'log_filter')) && array_key_exists('submitted', $session)) {
  155. State::sessionRemove('module', 'log_filter', 'submitted');
  156. State::sessionRemove('module', 'log_filter', 'messages');
  157. }
  158. }
  159. else {
  160. drupal_session_start();
  161. if (!empty($_SESSION['module']) && !empty($_SESSION['module']['log_filter'])) {
  162. $session = $_SESSION['module']['log_filter'];
  163. unset($_SESSION['module']['log_filter']['submitted']);
  164. unset($_SESSION['module']['log_filter']['messages']);
  165. }
  166. }
  167. if ($session) {
  168. if (array_key_exists('settings', $session)) {
  169. $settings =& $session['settings'];
  170. }
  171. if (array_key_exists('submitted', $session)) {
  172. $formSubmitted = TRUE;
  173. $submitted =& $session['submitted'];
  174. }
  175. if (array_key_exists('messages', $session)) {
  176. $messages =& $session['messages'];
  177. }
  178. }
  179. // Get current filter name from url, if any.
  180. $filter_name = $submitted ? $submitted['filter']['name'] :
  181. (($le = strlen($v = arg(self::FILTER_NAME_ARG))) // Deliberately not drupal_...().
  182. && $le <= 32
  183. && $v != 'log_filter' && $v != 'default' && $v != 'adhoc'
  184. && preg_match('/^[a-z_][a-z\d_]+$/', $v) ? $v : '');
  185. $require_admin = $submitted ? $submitted['filter']['require_admin'] : FALSE;
  186. $user_admin_permission = user_access('log_filter administer');
  187. // Get mode: default | adhoc | stored (create|edit|delete_filter aren't allowed at form build).
  188. $mode = $submitted ? $submitted['mode'] : ($filter_name ? 'stored' : 'default');
  189. // Stored mode may degrade to default.
  190. if ($mode == 'stored') {
  191. $success = TRUE;
  192. if (!$filter_name) {
  193. throw new Exception('Filter name[' . $filter_name . '] cannot be empty in mode[stored].');
  194. }
  195. if (!($stored_filter = self::_readFilter($filter_name))) {
  196. $success = FALSE;
  197. if (!$formSubmitted) { // Don't put these errors twice.
  198. if ($stored_filter === FALSE) {
  199. drupal_set_message(
  200. t('The filter \'!name\' doesn\'t exist.', array('!name' => $filter_name)),
  201. 'warning'
  202. );
  203. }
  204. else {
  205. drupal_set_message(
  206. t('You\'re not allowed to use the filter named \'!name\'.', array('!name' => $filter_name)),
  207. 'warning'
  208. );
  209. }
  210. }
  211. $mode = 'default';
  212. }
  213. if ($success) {
  214. $filter = array(
  215. 'origin' => '',
  216. 'description' => $stored_filter['description'],
  217. 'require_admin' => $require_admin = $stored_filter['require_admin'],
  218. );
  219. $fields_conditions =& self::$_fields['conditions'];
  220. $conditions = array();
  221. foreach ($fields_conditions as $name) {
  222. switch ($name) {
  223. case 'time_range':
  224. case 'time_from':
  225. case 'time_to':
  226. case 'role':
  227. $conditions[$name] = ($v = $stored_filter[$name]) > 0 ? $v : '';
  228. case 'uid':
  229. $conditions[$name] = ($v = $stored_filter[$name]) > 0 || $v === '0' || $v === 0 ? $v : '';
  230. break;
  231. case 'severity':
  232. if (!strlen($v = $stored_filter['severity'])) { // Deliberately not drupal_...().
  233. $v = array('-1');
  234. }
  235. else {
  236. $v = str_replace(',0,', ',zero,', ',' . $v . ',');
  237. $v = explode(',', substr($v, 1, strlen($v) - 1)); // Deliberately not drupal_...().
  238. }
  239. $conditions['severity'] = $v;
  240. break;
  241. case 'type_wildcard':
  242. $conditions['type_wildcard'] = !$stored_filter['type'] ? TRUE : FALSE;
  243. break;
  244. case 'type':
  245. $conditions['type'] = ($v = $stored_filter['type']) ? explode(',', $v) : array();
  246. break;
  247. case 'time_from_proxy':
  248. case 'time_to_proxy':
  249. // Set by frontend, if non-empty time_from/time_to
  250. $conditions[$name] = '';
  251. break;
  252. default:
  253. $conditions[$name] = $stored_filter[$name];
  254. }
  255. }
  256. unset($fields_conditions);
  257. $order_by = array();
  258. if (($v = $stored_filter['order_by'])) {
  259. $le = count($arr = explode(',', $v));
  260. for ($i = 0; $i < $le; $i++) {
  261. $v = explode(':', $arr[$i]);
  262. $order_by[] = array(
  263. $v[0],
  264. $v[1] == 'DESC' // ~ To boolean.
  265. );
  266. }
  267. }
  268. $title = $filter_name . (($v = $filter['description']) ? ('<span> - ' . $v . '</span>') : '');
  269. }
  270. else {
  271. $mode = 'default';
  272. $filter_name = 'default';
  273. }
  274. unset($stored_filter);
  275. }
  276. // Do other modes.
  277. switch ($mode) {
  278. case 'default':
  279. $filter = array(
  280. 'origin' => '',
  281. 'description' => '',
  282. 'require_admin' => FALSE,
  283. );
  284. $fields_conditions =& self::$_fields['conditions'];
  285. $conditions = array();
  286. foreach ($fields_conditions as $name) {
  287. switch ($name) {
  288. case 'severity':
  289. $conditions[$name] = array('-1');
  290. break;
  291. case 'type_wildcard':
  292. $conditions[$name] = TRUE;
  293. break;
  294. case 'type':
  295. $conditions[$name] = array();
  296. break;
  297. default:
  298. $conditions[$name] = '';
  299. }
  300. }
  301. unset($fields_conditions);
  302. $order_by = array(
  303. array('time', TRUE),
  304. );
  305. $title = t('Default');
  306. break;
  307. case 'adhoc':
  308. $filter =& $submitted['filter'];
  309. $conditions =& $submitted['conditions'];
  310. if (!$conditions['severity']) {
  311. $conditions['severity'] = array('-1');
  312. }
  313. $order_by =& $submitted['order_by'];
  314. $title = t('Ad hoc') . (($v = $filter['origin']) ? ('<span> - ' . t('based on !origin', array('!origin' => $v)) . '</span>') : '');
  315. break;
  316. case 'stored':
  317. // Done earlier.
  318. break;
  319. case 'create':
  320. case 'edit':
  321. case 'delete_filter':
  322. throw new Exception('Mode[' . $mode . '] not allowed at form build.', self::$_errorCodes['algo']);
  323. break;
  324. default:
  325. throw new Exception('Unsupported mode[' . $mode . '].', self::$_errorCodes['algo']);
  326. }
  327. // Prepare some fields.
  328. // Type (frontend: type_some).
  329. if (($options_type = db_select('watchdog')
  330. ->fields('watchdog', array('type'))
  331. ->distinct()
  332. ->execute()->fetchCol())) {
  333. sort($options_type);
  334. foreach ($options_type as &$v) {
  335. $v = str_replace(array("\r", "\n", ','), ' ', $v);
  336. }
  337. unset($v); // Clear reference.
  338. $options_type = array_combine($options_type, $options_type);
  339. // The filter may contain types that do not exist currently.
  340. $prepend_types = array();
  341. if ($conditions['type']) {
  342. foreach ($conditions['type'] as $v) {
  343. if ($v) {
  344. if (isset($options_type[$v])) {
  345. unset($options_type[$v]);
  346. }
  347. $prepend_types[$v] = $v;
  348. }
  349. }
  350. if ($prepend_types) {
  351. $options_type = array_merge($prepend_types, $options_type);
  352. }
  353. }
  354. }
  355. elseif ($conditions['type']) {
  356. $options_type = $conditions['type'];
  357. }
  358. else { // Make sure that there's always at least a single option.
  359. $options_type = array('php' => 'php');
  360. }
  361. // Severity.
  362. $options_severity = array(
  363. '-1' => t('Any'),
  364. 'zero' => t('emergency'),
  365. '1' => t('alert'),
  366. '2' => t('critical'),
  367. '3' => t('error'),
  368. '4' => t('warning'),
  369. '5' => t('notice'),
  370. '6' => t('info'),
  371. '7' => t('debug'),
  372. );
  373. // Role.
  374. $options_role = user_roles();
  375. foreach ($options_role as &$v) {
  376. $v = t($v);
  377. }
  378. unset($v); // Clear reference.
  379. $options_role = array('' => t('Any')) + $options_role; // Union operator (+) doesnt re-index numerical keys like array_merge() does.
  380. // Order by.
  381. $options_order_by = array(
  382. '' => '',
  383. 'time' => t('Time'),
  384. 'severity' => t('Severity'),
  385. 'type' => t('Type'),
  386. 'role' => t('User role'),
  387. 'uid' => t('User ID'),
  388. 'hostname' => t('Visitor\'s hostname'),
  389. 'location' => t('Location'),
  390. 'referer' => t('Referrer'),
  391. );
  392. $length_order_by = count($order_by);
  393. // Only own filters.
  394. $value_only_own = $settings && array_key_exists('only_own', $settings) ? $settings['only_own'] : FALSE;
  395. // Filter selector.
  396. $uid = $GLOBALS['user']->uid;
  397. if ( ($options_filters = self::_listFilters(!$value_only_own ? NULL : array($uid), array('name'), array(array('name')), $uid) ) ) {
  398. $js_filters = '["' . join('","', $options_filters) . '"]';
  399. $options_filters = array_merge(
  400. array('' => t('Default')),
  401. array_combine($options_filters, $options_filters)
  402. );
  403. }
  404. else {
  405. $js_filters = '[]';
  406. $options_filters = array('' => t('Default'));
  407. }
  408. // Get current theme.
  409. $theme = $GLOBALS['theme'];
  410. // Build form.
  411. $form['#attributes'] = array('autocomplete' => 'off');
  412. $form['log_filter_filter_edit'] = array(
  413. '#type' => 'fieldset',
  414. '#title' => t('Filter') . ': <span id="log_filter_title_display">' . $title . '</span>',
  415. '#collapsible' => TRUE,
  416. '#collapsed' => FALSE,
  417. 'frontend_init' => array(
  418. '#type' => 'markup',
  419. '#markup' => '<script type="text/javascript"> LogFilter.init(' . (int)$use_module_css . ', "' . $theme . '"); </script>',
  420. ),
  421. // Control vars.
  422. 'log_filter_mode' => array(
  423. '#type' => 'hidden',
  424. '#default_value' => $mode,
  425. ),
  426. 'log_filter_name' => array(
  427. '#type' => 'hidden',
  428. '#default_value' => $filter_name,
  429. ),
  430. 'log_filter_origin' => array(
  431. '#type' => 'hidden',
  432. '#default_value' => $filter['origin'],
  433. ),
  434. // Conditions.
  435. // Time.
  436. 'log_filter_time_range' => array( // (3 open)
  437. '#type' => 'textfield',
  438. '#title' => t('Preceding hours'),
  439. '#default_value' => $conditions['time_range'] ? $conditions['time_range'] : '',
  440. '#size' => 2,
  441. '#prefix' => '<div id="log_filter_criteria"><div class="filter-conditions"><div class="form-item log-filter-time">'
  442. . '<label>' . t('Time') . '</label>',
  443. ),
  444. 'log_filter_time_from_proxy' => array(
  445. '#type' => 'textfield',
  446. '#title' => t('From') . '<span>?</span>',
  447. '#default_value' => '', // Frontend sets it, if non-empty time_from.
  448. '#size' => 10,
  449. '#prefix' => '<div class="log-filter-time-or">' . t('or') . '</div>',
  450. ),
  451. 'log_filter_time_from' => array(
  452. '#type' => 'hidden',
  453. '#default_value' => $conditions['time_from'],
  454. ),
  455. 'log_filter_time_to_proxy' => array(
  456. '#type' => 'textfield',
  457. '#title' => t('To'),
  458. '#default_value' => '', // Frontend sets it, if non-empty time_to.
  459. '#size' => 10,
  460. ),
  461. 'log_filter_time_to' => array( // End: log-filter-time (2 open).
  462. '#type' => 'hidden',
  463. '#default_value' => $conditions['time_to'],
  464. '#suffix' => '</div>',
  465. ),
  466. // Severity.
  467. 'log_filter_severity' => array(
  468. '#type' => 'checkboxes',
  469. '#title' => t('Severity'),
  470. '#multiple' => TRUE,
  471. '#options' => $options_severity,
  472. '#default_value' => $conditions['severity'],
  473. ),
  474. // Type.
  475. 'log_filter_type_wildcard' => array( // (3 open).
  476. '#type' => 'checkbox',
  477. '#title' => t('Any'),
  478. '#default_value' => $conditions['type_wildcard'],
  479. '#prefix' => '<div class="form-item log-filter-type">'
  480. . '<label>' . t('Type') . '</label>',
  481. ),
  482. 'log_filter_type_proxy' => array(
  483. '#type' => 'checkboxes',
  484. '#options' => $options_type,
  485. '#default_value' => array(),
  486. '#prefix' => '<div class="log-filter-type-container">',
  487. ),
  488. 'log_filter_type' => array( // End: log-filter-type (2 open).
  489. '#type' => 'textarea',
  490. '#default_value' => join("\n", $conditions['type']),
  491. '#resizable' => FALSE,
  492. '#suffix' => '</div></div>',
  493. ),
  494. // Various.
  495. 'log_filter_role' => array( // (4 open).
  496. '#type' => 'select',
  497. '#title' => t('User role'),
  498. '#options' => $options_role,
  499. '#default_value' => $conditions['role'],
  500. '#prefix' => '<div class="form-item log-filter-various"><div class="log-filter-user">',
  501. ),
  502. 'log_filter_uid' => array(
  503. '#type' => 'textfield',
  504. '#title' => t('ID'),
  505. '#default_value' => $conditions['uid'],
  506. '#size' => 11,
  507. ),
  508. 'log_filter_username' => array( // End: log-filter-user (3 open).
  509. '#type' => 'textfield',
  510. '#title' => t('Name'),
  511. '#default_value' => $conditions['uid'],
  512. '#size' => 20,
  513. // Can't use Drupal Form API autocomplete, because we need to pass value to the uid field upon selection.
  514. '#attributes' => array(
  515. 'class' => array('form-autocomplete'),
  516. ),
  517. '#suffix' => '</div>',
  518. ),
  519. 'log_filter_hostname' => array(
  520. '#type' => 'textfield',
  521. '#title' => t('Visitor\'s hostname') . '<span title="' . t('Use * to display in event list, without filtering') . '">?</span>',
  522. '#default_value' => $conditions['hostname'],
  523. '#size' => 64,
  524. ),
  525. 'log_filter_location' => array(
  526. '#type' => 'textfield',
  527. '#title' => t('Location') . '<span title="' . t('Use * to display in event list, without filtering') . '">?</span>',
  528. '#default_value' => $conditions['location'],
  529. '#size' => 64,
  530. ),
  531. 'log_filter_referer' => array( // End: filter-conditions, (1 open).
  532. '#type' => 'textfield',
  533. '#title' => t('Referrer') . '<span title="'
  534. . t('Use \'none\' for empty referrer.!nlUse * to display in event list, without filtering.', array('!nl' => "\n"))
  535. . '">?</span>',
  536. '#default_value' => $conditions['referer'],
  537. '#size' => 64,
  538. '#suffix' => '</div></div>',
  539. ),
  540. // Order by.
  541. 'log_filter_orderby_1' => array( // (2 open)
  542. '#type' => 'select',
  543. '#options' => $options_order_by,
  544. '#default_value' => $length_order_by ? array($order_by[0][0]) : array(),
  545. '#prefix' => '<div class="filter-orderby"><label>' . t('Order by') . '</label>',
  546. ),
  547. 'log_filter_descending_1' => array(
  548. '#type' => 'checkbox',
  549. '#default_value' => $length_order_by ? $order_by[0][1] : FALSE,
  550. '#attributes' => array(
  551. 'title' => t('Descending'),
  552. ),
  553. ),
  554. 'log_filter_orderby_2' => array(
  555. '#type' => 'select',
  556. '#options' => $options_order_by,
  557. '#default_value' => $length_order_by > 1 ? array($order_by[1][0]) : array(),
  558. ),
  559. 'log_filter_descending_2' => array(
  560. '#type' => 'checkbox',
  561. '#default_value' => $length_order_by > 1 ? $order_by[1][1] : FALSE,
  562. '#attributes' => array(
  563. 'title' => t('Descending'),
  564. ),
  565. ),
  566. 'log_filter_orderby_3' => array(
  567. '#type' => 'select',
  568. '#options' => $options_order_by,
  569. '#default_value' => $length_order_by > 2 ? array($order_by[2][0]) : array(),
  570. ),
  571. 'log_filter_descending_3' => array(
  572. '#type' => 'checkbox',
  573. '#default_value' => $length_order_by > 2 ? $order_by[2][1] : FALSE,
  574. '#attributes' => array(
  575. 'title' => t('Descending'),
  576. ),
  577. ),
  578. 'log_filter_reset' => array( // End: log-filter-reset, filter-orderby, log_filter_criteria (0 open).
  579. '#type' => 'button',
  580. '#name' => 'log_filter_reset',
  581. '#value' => t('Reset'),
  582. '#button_type' => 'button', // Doesnt work; still type:submit.
  583. '#attributes' => array(
  584. 'type' => 'button', // Doesnt work; still type:submit.
  585. 'class' => array('form-submit', 'edit-reset'),
  586. ),
  587. '#prefix' => '<div class="log-filter-reset">',
  588. '#suffix' => '</div></div></div>',
  589. ),
  590. // Filters.
  591. array(
  592. '#type' => 'markup',
  593. '#markup' => '<div id="log_filter_filters"><div id="log_filter_box_filter"><div id="log_filter_filters_cell_0">',
  594. ),
  595. 'log_filter_filter' => array( // (2 open)
  596. '#type' => 'select',
  597. '#title' => t('Filter'),
  598. '#options' => $options_filters,
  599. '#default_value' => $filter_name,
  600. ),
  601. 'log_filter_only_own' => array(
  602. '#access' => $allow_filter_edit,
  603. '#type' => 'checkbox',
  604. '#title' => t('List my filters only'),
  605. '#default_value' => $value_only_own,
  606. ),
  607. array(
  608. '#type' => 'markup',
  609. '#markup' => '</div>' // End: log_filter_filters_cell_0
  610. . '<div id="log_filter_filters_cell_1">',
  611. ),
  612. 'log_filter_name_suggest' => array(
  613. '#access' => $allow_filter_edit,
  614. '#type' => 'textfield',
  615. '#title' => t('Name'),
  616. '#default_value' => '', // Only used frontend.
  617. '#size' => 32,
  618. '#attributes' => array(
  619. 'maxlength' => 32,
  620. ),
  621. ),
  622. 'log_filter_require_admin' => array(
  623. '#access' => $allow_filter_edit && $user_admin_permission,
  624. '#type' => 'checkbox',
  625. '#title' => t('Restrict access') . '<span title="' . ($title = t('Require the \'Administer log filtering\' permission.')) . '">?</span>',
  626. '#default_value' => $require_admin,
  627. '#attributes' => array(
  628. 'title' => $title,
  629. ),
  630. ),
  631. array( // End: log_filter_filters_cell_1
  632. '#type' => 'markup',
  633. '#markup' => '</div>',
  634. ),
  635. 'log_filter_description' => array(
  636. '#access' => $allow_filter_edit,
  637. '#type' => 'textarea',
  638. '#title' => t('Description'),
  639. '#default_value' => $filter['description'],
  640. '#rows' => 3,
  641. '#cols' => 32,
  642. '#resizable' => FALSE, // Otherwise styling too complicated, and browsers support it natively.
  643. ),
  644. array( // (3 open)
  645. '#type' => 'markup',
  646. '#markup' => '<div class="log-filter-edit-create">',
  647. ),
  648. 'log_filter_edit' => array(
  649. '#access' => $allow_filter_edit,
  650. '#type' => 'button',
  651. '#name' => 'log_filter_edit',
  652. '#value' => t('Edit'),
  653. '#button_type' => 'button', // Doesnt work; still type:submit.
  654. '#attributes' => array(
  655. 'type' => 'button', // Doesnt work; still type:submit.
  656. 'class' => array('form-submit'),
  657. 'style' => 'display:none;',
  658. ),
  659. ),
  660. 'log_filter_create' => array( // (2 open)
  661. '#access' => $allow_filter_edit,
  662. '#type' => 'button',
  663. '#name' => 'log_filter_create',
  664. '#value' => t('Save as...'),
  665. '#button_type' => 'button', // Doesnt work; still type:submit.
  666. '#attributes' => array(
  667. 'type' => 'button', // Doesnt work; still type:submit.
  668. 'class' => array('form-submit'),
  669. 'style' => 'display:none;',
  670. ),
  671. ),
  672. array(
  673. '#type' => 'markup',
  674. '#markup' => '</div>' // End. log-filter-edit-create
  675. . '<div class="log-filter-cancel-save'
  676. . (strpos(strtolower(PHP_OS), 'win') === 0 ? ' log-filter-reversed-button-sequence' : '') // Deliberately not drupal_...().
  677. . '">',
  678. ),
  679. 'log_filter_cancel' => array( // (3 open)
  680. '#access' => $allow_filter_edit,
  681. '#type' => 'button',
  682. '#name' => 'log_filter_cancel',
  683. '#value' => t('Cancel'),
  684. '#button_type' => 'button', // Doesnt work; still type:submit.
  685. '#attributes' => array(
  686. 'type' => 'button', // Doesnt work; still type:submit.
  687. 'class' => array('form-submit', 'edit-cancel'),
  688. 'style' => 'display:none;',
  689. ),
  690. ),
  691. 'log_filter_save' => array(
  692. '#access' => $allow_filter_edit,
  693. '#type' => 'button',
  694. '#name' => 'log_filter_save',
  695. '#value' => t('Save'),
  696. '#button_type' => 'button', // Doesnt work; still type:submit.
  697. '#attributes' => array(
  698. 'type' => 'button', // Doesnt work; still type:submit.
  699. 'class' => array('form-submit'),
  700. 'style' => 'display:none;',
  701. ),
  702. ),
  703. array(
  704. '#type' => 'markup',
  705. '#markup' => '</div>' // End: log-filter-cancel-save
  706. . '<div class="log-filter-delete">',
  707. ),
  708. 'log_filter_delete' => array(
  709. '#access' => $allow_filter_edit,
  710. '#type' => 'button',
  711. '#name' => 'log_filter_delete',
  712. '#value' => t('Delete filter'),
  713. '#button_type' => 'button', // Doesnt work; still type:submit.
  714. '#attributes' => array(
  715. 'type' => 'button', // Doesnt work; still type:submit.
  716. 'class' => array('form-submit', 'edit-delete'),
  717. 'style' => 'display:none;',
  718. ),
  719. ),
  720. array(
  721. '#type' => 'markup',
  722. '#markup' => '</div>' // End: log-filter-delete
  723. . '</div>', // End: log_filter_box_filter
  724. ),
  725. array(
  726. '#access' => ($allow_delete_logs = user_access('log_filter remove logs')),
  727. '#type' => 'markup',
  728. '#markup' => '<div id="log_filter_box_delete_logs">',
  729. ),
  730. array(
  731. '#access' => !$allow_delete_logs,
  732. '#type' => 'markup',
  733. '#markup' => '<div id="log_filter_box_delete_logs" class="log-filter-delete-logs-none">&nbsp;',
  734. ),
  735. array( // (2 open)
  736. '#access' => $allow_delete_logs,
  737. '#type' => 'button',
  738. '#name' => 'log_filter_delete_logs_button',
  739. '#value' => t('Delete logs'),
  740. '#button_type' => 'button', // Doesnt work; still type:submit.
  741. '#attributes' => array(
  742. 'type' => 'button', // Doesnt work; still type:submit.
  743. 'class' => array('form-submit', 'edit-delete'),
  744. 'style' => 'display:none;',
  745. ),
  746. ),
  747. array(
  748. '#access' => $allow_delete_logs,
  749. '#type' => 'textfield',
  750. '#name' => 'log_filter_delete_logs_max',
  751. '#title' => t('Max.'),
  752. '#default_value' => $settings && array_key_exists('delete_logs_max', $settings) ? $settings['delete_logs_max'] : '',
  753. '#attributes' => array(
  754. 'maxlength' => 11,
  755. ),
  756. ),
  757. array(
  758. '#type' => 'markup',
  759. '#markup' => '</div>' // End: log_filter_box_delete_logs
  760. . '</div>', // End: log_filter_filters
  761. ),
  762. );
  763. $form['log_filter_list_controls'] = array(
  764. 'frontend_setup' => array(
  765. '#type' => 'markup',
  766. '#markup' => '<script type="text/javascript">
  767. (function($) {
  768. if(!$) {
  769. return;
  770. }
  771. $(document).bind("ready", function() {
  772. var elm;
  773. // t() ruins this, because of some hocus pocus.
  774. $("div.form-item-log-filter-time-from-proxy label").get(0).setAttribute("title", "'
  775. . t('Date format: YYYY-MM-DD!newlineTime formats: N, NN, NNNN, NNNNNN, NN:NN, NN:NN:NN') . '".replace(/\!newline/, "\n"));
  776. // Go.
  777. LogFilter.setup(
  778. ' . $js_filters . ',
  779. ' . ($messages ? json_encode($messages) : 'null') . '
  780. );
  781. } );
  782. })(jQuery);
  783. </script>
  784. ',
  785. ),
  786. 'log_filter_update_list' => array(
  787. '#type' => 'button',
  788. '#name' => 'log_filter_update_list',
  789. '#value' => t('Update list'),
  790. '#button_type' => 'button', // Doesnt work; still type:submit.
  791. '#attributes' => array(
  792. 'class' => array('form-submit'),
  793. 'title' => t('[CTR + U / CMD + U]'),
  794. ),
  795. '#prefix' => '<div class="log-filter-button log-filter-update-list">',
  796. '#suffix' => '</div>',
  797. ),
  798. 'log_filter_pager_controls' => array(
  799. '#type' => 'markup',
  800. '#markup' => '<div class="log-filter-pager-controls">'
  801. . '<div id="log_filter_pager_first" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
  802. . t('First') . '">&#x25c4;&#x25c4;</div>'
  803. . '<div id="log_filter_pager_previous" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
  804. . t('Previous') . '">&#x25c4;</div>'
  805. . '<div id="log_filter_pager_current" title="' . t('Update list') . '">&nbsp;</div>'
  806. . '<div id="log_filter_pager_progress" class="ajax-progress"><div class="throbber"></div>' . t('Loading...') . '</div>'
  807. . '<div id="log_filter_pager_next" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
  808. . t('Next') . '">&#x25ba;</div>'
  809. . '<div id="log_filter_pager_last" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
  810. . t('Last') . '">&#x25ba;&#x25ba;</div>'
  811. . '</div>',
  812. ),
  813. 'log_filter_pager_range' => array(
  814. '#type' => 'textfield',
  815. '#title' => t('Logs per page'),
  816. '#default_value' => $settings && array_key_exists('pager_range', $settings) && $settings['pager_range'] > 0 ?
  817. $settings['pager_range'] : variable_get('log_filter_pgrng', self::PAGE_RANGE_DEFAULT),
  818. '#size' => 4,
  819. ),
  820. 'log_filter_translate' => array(
  821. '#type' => 'checkbox',
  822. '#title' => t('Translate messages') . '<span title="' . ($title = t('Translating messages is heavy performance-wise.')) . '">?</span>',
  823. '#default_value' => $settings && array_key_exists('translate', $settings) ? $settings['translate'] : variable_get('log_filter_trnslt', 0),
  824. '#attributes' => array(
  825. 'title' => $title,
  826. ),
  827. ),
  828. 'actions' => array(
  829. '#type' => 'actions',
  830. 'submit' => array(
  831. '#type' => 'submit',
  832. '#value' => t('Update list'),
  833. '#attributes' => array('style' => 'display:none;'),
  834. ),
  835. ),
  836. '#prefix' => '<div id="log_filter_list_controls">',
  837. '#suffix' => '</div>',
  838. );
  839. $logList = '';
  840. for ($i = 97; $i < 97 + 26; $i++) {
  841. //$logList .= '<div class="' . str_repeat(chr($i), 3) . '">' . str_repeat(chr($i), 3) . '</div>';
  842. }
  843. $form['log_filter_log_list'] = array(
  844. '#type' => 'markup',
  845. '#markup' => '<div id="log_filter_log_list" class="scrollable">' . $logList . '</div>',
  846. );
  847. // Add our submit form;
  848. $form['#submit'][] = '_log_filter_form_submit';
  849. return $form;
  850. }
  851. catch (Exception $xc) {
  852. self::_errorHandler($xc);
  853. drupal_set_message($xc->getMessage(), 'error');
  854. return array();
  855. }
  856. }
  857. /**
  858. * Called when log viewer form submits.
  859. *
  860. * @param array $form
  861. * @param array &$form_state
  862. * @return void
  863. */
  864. public static function viewerFormSubmit($form, &$form_state) {
  865. try {
  866. $values =& $form_state['values'];
  867. $prefix = 'log_filter_';
  868. $messages = array();
  869. $settings = array(
  870. 'only_own' => !array_key_exists($prefix . 'only_own', $values) ? FALSE : $values[$prefix . 'only_own'],
  871. 'delete_logs_max' => !array_key_exists($prefix . 'delete_logs_max', $values) ? '' : $values[$prefix . 'delete_logs_max'],
  872. 'translate' => $values[$prefix . 'translate'],
  873. 'only_own' => !array_key_exists($prefix . 'only_own', $values) ? FALSE : $values[$prefix . 'only_own'],
  874. 'pager_range' => ($v = (int)$values[$prefix . 'pager_range']) > -1 ? ($v > self::PAGE_RANGE_MAX ? self::PAGE_RANGE_MAX : $v) :
  875. variable_get('log_filter_pgrng', self::PAGE_RANGE_DEFAULT),
  876. );
  877. $submitted = array(
  878. 'mode' => $values[$prefix . 'mode'],
  879. 'filter' => array(
  880. 'name' => '',
  881. 'origin' => '',
  882. 'name_suggest' => '',
  883. 'description' => '',
  884. 'require_admin' => !array_key_exists($prefix . 'require_admin', $values) ? FALSE : $values[$prefix . 'require_admin'],
  885. ),
  886. );
  887. $use_form_values = $save = FALSE;
  888. switch (($mode = $submitted['mode'])) {
  889. case 'default':
  890. // Use default values.
  891. break;
  892. case 'adhoc':
  893. // Get specs from form.
  894. $use_form_values = TRUE;
  895. $submitted['filter']['origin'] = $values[$prefix . 'origin']; // ~ Hidden field.
  896. break;
  897. case 'stored': // Saved filter.
  898. // Just get filter name; in stored mode we do absolutely nothing at submission but establishing the filter's name.
  899. // Whether the filter require_admin and user has that permission will be checked at form build - no reason to check twice.
  900. if (!($submitted['filter']['name'] = $filter_name = $values[$prefix . 'name'])) {
  901. throw new Exception('Mode[' . $mode . '], empty name[' . $filter_name . '].', self::$_errorCodes['filter_name_composition']);
  902. }
  903. break;
  904. case 'create': // Always AJAX-handled.
  905. throw new Exception('Mode[' . $mode . '] not allowed at form submission.', self::$_errorCodes['algo']);
  906. break;
  907. case 'edit':
  908. case 'delete_filter':
  909. // Get name.
  910. if (!($submitted['filter']['name'] = $filter_name = $values[$prefix . 'name'])) {
  911. throw new Exception('Mode[' . $mode . '], empty name[' . $filter_name . '].', self::$_errorCodes['filter_name_composition']);
  912. }
  913. $success = TRUE;
  914. // Check CRUD permission.
  915. if (!user_access('log_filter edit filters')) {
  916. $success = FALSE;
  917. // Horrible; have to make almost exactly same message, because of shortcomings of the localization regime.
  918. switch ($mode) {
  919. case 'edit':
  920. watchdog(
  921. 'log_filter',
  922. 'Won\'t edit the filter \'!name\' because user !user doesn\'t have \'log_filter edit filters\' permission.',
  923. array('!name' => $filter_name, '!user' => $GLOBALS['user']->name),
  924. WATCHDOG_WARNING
  925. );
  926. drupal_set_message(
  927. t('Cannot edit the filter \'!name\', because you don\'t have permission to edit log filters.', array('!name' => $filter_name)),
  928. 'warning'
  929. );
  930. break;
  931. default: // delete
  932. watchdog(
  933. 'log_filter',
  934. 'Won\'t delete the filter \'!name\' because user !user doesn\'t have \'log_filter edit filters\' permission.',
  935. array('!name' => $filter_name, '!user' => $GLOBALS['user']->name),
  936. WATCHDOG_WARNING
  937. );
  938. drupal_set_message(
  939. t('Cannot delete the filter \'!name\', because you don\'t have permission to edit log filters.', array('!name' => $filter_name)),
  940. 'warning'
  941. );
  942. }
  943. }
  944. // Check if exists, and get require_admin field.
  945. elseif (!($require_admin = db_select('log_filter')
  946. ->fields('log_filter', array('require_admin'))
  947. ->condition('name', $filter_name, '=')
  948. ->execute()->fetchField())
  949. ) {
  950. if ($require_admin === FALSE) { // Doesn't exist.
  951. $success = FALSE;
  952. /* drupal_set_message(
  953. t('The filter \'!name\' doesn\'t exist.', array('!name' => $filter_name)),
  954. 'warning'
  955. ); */
  956. $messages[] = array(
  957. t('The filter \'!name\' doesn\'t exist.', array('!name' => $filter_name)),
  958. 'warning'
  959. );
  960. }
  961. // else... the filter doesnt require admin permission.
  962. }
  963. elseif (!user_access('log_filter administer')) {
  964. $success = FALSE;
  965. }
  966. if ($success) {
  967. switch ($mode) {
  968. case 'edit':
  969. // Get specs from form, and save to database.
  970. $use_form_values = TRUE;
  971. $save = TRUE;
  972. $submitted['filter']['description'] = $values[$prefix . 'description'];
  973. // Change mode.
  974. $submitted['mode'] = $mode = 'stored';
  975. break;
  976. default: // delete
  977. global $user;
  978. db_delete('log_filter')->condition('name', $filter_name, '=')->execute();
  979. watchdog(
  980. 'log_filter',
  981. 'User (%uid) %name deleted the log filter \'!filter\'.',
  982. array('%uid' => $user->uid, '%name' => $user->name, '!filter' => $filter_name),
  983. WATCHDOG_INFO
  984. );
  985. $messages[] = array(
  986. t('Deleted the filter \'!name\'.', array('!name' => $filter_name))
  987. );
  988. // Change mode.
  989. $submitted['mode'] = $mode = 'default';
  990. }
  991. }
  992. else {
  993. $use_form_values = $save = FALSE;
  994. // Change mode.
  995. $submitted['mode'] = $mode = 'default';
  996. }
  997. break;
  998. default:
  999. throw new Exception('Unsupported mode[' . $mode . '].', self::$_errorCodes['algo']);
  1000. }
  1001. // Load values from form.
  1002. if ($use_form_values) {
  1003. $fields_conditions =& self::$_fields['conditions'];
  1004. $conditions = array();
  1005. foreach ($fields_conditions as $name) {
  1006. switch ($name) {
  1007. case 'time_range':
  1008. $conditions[$name] = ($v = trim($values[$prefix . $name])) ? $v : '';
  1009. break;
  1010. case 'role':
  1011. $conditions[$name] = ($v = trim($values[$prefix . $name])) > 0 ? $v : -1;
  1012. break;
  1013. case 'uid': // Accepts zero.
  1014. $conditions[$name] = ($v = trim($values[$prefix . $name])) > 0 || $v === '0' || $v === 0 ? $v : -1;
  1015. break;
  1016. case 'time_from':
  1017. $conditions[$name] = $conditions['time_range'] ? '' : (($v = trim($values[$prefix . $name])) ? $v : '');
  1018. break;
  1019. case 'time_from_proxy':
  1020. if (!$save) { // Because save doesnt use the proxy field.
  1021. $conditions[$name] = !$conditions['time_from'] ? '' : $values[$prefix . $name];
  1022. }
  1023. break;
  1024. case 'time_to':
  1025. $conditions[$name] = $conditions['time_range'] ? '' : (($v = trim($values[$prefix . $name])) ? $v : '');
  1026. break;
  1027. case 'time_to_proxy':
  1028. if (!$save) { // Because save doesnt use the proxy field.y
  1029. $conditions[$name] = !$conditions['time_to'] ? '' : $values[$prefix . $name];
  1030. }
  1031. break;
  1032. case 'severity':
  1033. $arr = $values[$prefix . $name];
  1034. $vals = array();
  1035. foreach ($arr as $k => $v) {
  1036. if ($v) {
  1037. if ('' . $k == '-1') {
  1038. $vals = array();
  1039. break;
  1040. }
  1041. else {
  1042. $vals[] = $k;
  1043. }
  1044. }
  1045. }
  1046. $conditions[$name] = $vals;
  1047. break;
  1048. case 'type_wildcard':
  1049. $conditions[$name] = $values[$prefix . $name] ? TRUE : FALSE;
  1050. break;
  1051. case 'type':
  1052. // Dont remember type list (may be very long), if wildcard on.
  1053. $conditions[$name] = $conditions['type_wildcard'] ? array() :
  1054. array_combine($a = explode("\n", str_replace("\r", '', $values[$prefix . $name])), $a);
  1055. if ($save) {
  1056. unset($conditions['type_wildcard']);
  1057. }
  1058. break;
  1059. default:
  1060. $conditions[$name] = trim($values[$prefix . $name]);
  1061. }
  1062. }
  1063. unset($fields_conditions);
  1064. $submitted['conditions'] =& $conditions;
  1065. $fields_order_by =& self::$_fields['order_by'];
  1066. $order_by = array();
  1067. for ($i = 1; $i < 10; $i++) {
  1068. if (array_key_exists($key = $prefix . $fields_order_by[0] . $i, $values)) {
  1069. if (($key = $values[ $key ])) {
  1070. if (!$save) {
  1071. $order_by[] = array($key, $values[ $prefix . $fields_order_by[1] . $i ]);
  1072. }
  1073. else {
  1074. $order_by[] = array($key, $values[ $prefix . $fields_order_by[1] . $i ] ? 'DESC' : 'ASC');
  1075. }
  1076. }
  1077. }
  1078. else {
  1079. break;
  1080. }
  1081. }
  1082. $submitted['order_by'] =& $order_by;
  1083. unset($order_by, $fields_order_by);
  1084. }
  1085. if ($save) { // edit mode.
  1086. $success = TRUE;
  1087. try {
  1088. self::_saveFilter($filter_name, $submitted);
  1089. }
  1090. catch (Exception $xc) {
  1091. self::_errorHandler($xc);
  1092. $success = FALSE;
  1093. $messages[] = array(
  1094. t('Failed to update filter \'!name\'.', array('!name' => $filter_name)),
  1095. 'warning'
  1096. );
  1097. }
  1098. // Change mode.
  1099. if ($success) {
  1100. $submitted['mode'] = $mode = 'stored';
  1101. }
  1102. else {
  1103. $submitted['mode'] = $mode = 'default';
  1104. }
  1105. }
  1106. // Clear conditions and order_by from vars to be passed to session, unless adhoc filter.
  1107. if ($mode != 'adhoc') {
  1108. unset( $submitted['conditions'], $submitted['order_by'] );
  1109. }
  1110. // Pass to session.
  1111. $session = array('settings' => $settings, 'submitted' => $submitted);
  1112. if ($messages) {
  1113. $session['messages'] = $messages;
  1114. }
  1115. if (module_exists('state')) {
  1116. State::sessionSet('module', 'log_filter', $session);
  1117. }
  1118. else {
  1119. drupal_session_start();
  1120. if (!isset($_SESSION['module'])) {
  1121. $_SESSION['module'] = array(
  1122. 'log_filter' => $session,
  1123. );
  1124. }
  1125. else {
  1126. $_SESSION['module']['log_filter'] = $session;
  1127. }
  1128. }
  1129. }
  1130. catch (Exception $xc) {
  1131. self::_errorHandler($xc);
  1132. drupal_set_message($xc->getMessage(), 'error');
  1133. }
  1134. }
  1135. /**
  1136. * Filters off restricted filters if current user isnt allowed to administer filters.
  1137. *
  1138. * @throws PDOException
  1139. * @param integer|string|array|NULL $creatorIds
  1140. * - default: NULL (~ any user)
  1141. * - integer|string: list only filters created by that user
  1142. * - array: list only filters created by those users
  1143. * @param array|NULL $fields
  1144. * - default: all fields
  1145. * @param arrayNULL $orderBy
  1146. * - default: unordered
  1147. * - array: multi-dimensional; every bucket being an array listing field name and 'ASC'|'DESC'
  1148. * @param integer|string|NULL $latestAtTop
  1149. * - default: not
  1150. * - user id: place lastest changed of that user at the very top of the list (requires that $fields is falsy or has 'name' bucket)
  1151. * @return array
  1152. */
  1153. protected static function _listFilters($creatorIds = NULL, $fields = NULL, $orderBy = NULL, $latestAtTop = NULL) {
  1154. $uid = $GLOBALS['user']->uid;
  1155. if ( ($creatorIds && !is_array($creatorIds)) || (!$creatorIds && $creatorIds !== NULL)) {
  1156. $creatorIds = array($creatorIds);
  1157. }
  1158. $list = db_select('log_filter')
  1159. ->fields('log_filter', $fields ? $fields : array('*'));
  1160. if ($creatorIds) {
  1161. $list->condition('creator', $creatorIds, 'IN');
  1162. }
  1163. if (!user_access('log_filter administer')) {
  1164. $list->condition('require_admin', 0);
  1165. }
  1166. if ($orderBy) {
  1167. foreach ($orderBy as $subArr) {
  1168. $list->orderBy($subArr[0], !empty($subArr[1]) ? $subArr[1] : 'ASC');
  1169. }
  1170. }
  1171. $list = ($singleColumned = ($fields && count($fields) == 1)) ? $list->execute()->fetchCol() : $list->execute()->fetchAll();
  1172. if ($list && $latestAtTop !== NULL
  1173. && (!$creatorIds || in_array($latestAtTop, $creatorIds))
  1174. && (!$fields || in_array('name', $fields))
  1175. ) {
  1176. if (($listLatest = db_select('log_filter')
  1177. ->fields('log_filter', array('name', 'changed'))
  1178. ->condition('editor', $latestAtTop, '=')
  1179. ->execute()
  1180. ->fetchAll()
  1181. )) {
  1182. $latestChange = $latestName = 0;
  1183. foreach ($listLatest as $subObj) {
  1184. if ($subObj->changed > $latestChange) {
  1185. $latestChange = $subObj->changed;
  1186. $latestName = $subObj->name;
  1187. }
  1188. }
  1189. if ($latestName) {
  1190. if ($singleColumned) {
  1191. if (($index = array_search($latestName, $list))) { // No need to check !== FALSE, because if zero there's nothing to be done.
  1192. array_splice($list, $index, 1);
  1193. array_unshift($list, $latestName);
  1194. }
  1195. }
  1196. else {
  1197. $index = 0;
  1198. foreach ($list as $k => $subObj) {
  1199. if ($subObj->name == $latestName) {
  1200. $index = $k;
  1201. break;
  1202. }
  1203. }
  1204. if ($index) {
  1205. $latest = array_splice($list, $index, 1);
  1206. array_unshift($list, $latest);
  1207. }
  1208. }
  1209. }
  1210. }
  1211. }
  1212. return $list;
  1213. }
  1214. /**
  1215. * Checks if the filter requires log_filter administrative permission and whether the user has that permission.
  1216. *
  1217. * @throws PDOException
  1218. * @param string $name
  1219. * @return array|boolean|NULL
  1220. * - FALSE: the filter doesnt exist
  1221. * - NULL: user not allowed to use that filter
  1222. */
  1223. protected static function _readFilter($name) {
  1224. return !(
  1225. $filter = db_select('log_filter')
  1226. ->fields('log_filter')
  1227. ->condition('name', $name, '=')
  1228. ->execute()->fetchAssoc()
  1229. ) ? FALSE : $filter['require_admin'] && !user_access('log_filter administer') ? NULL : $filter;
  1230. }
  1231. /**
  1232. * Insert/update filter in database.
  1233. *
  1234. * @throws Exception
  1235. * @param string $name
  1236. * @param array $values
  1237. * @param boolean $create
  1238. * - default: FALSE
  1239. * @return void
  1240. * - throws error on failure
  1241. */
  1242. protected static function _saveFilter($name, $values, $create = FALSE) {
  1243. $uid = $GLOBALS['user']->uid;
  1244. if (!user_access('log_filter edit filters')) {
  1245. throw new Exception(
  1246. 'You\'re not allowed to edit filters.',
  1247. self::$_errorCodes['perm_filter_crud']
  1248. );
  1249. }
  1250. // Filter metadata and last updated by.
  1251. $fields = array(
  1252. 'editor' => $uid,
  1253. 'changed' => REQUEST_TIME,
  1254. 'require_admin' => !empty($values['filter']['require_admin']) ? 1 : 0,
  1255. 'description' => $values['filter']['description'],
  1256. );
  1257. if ($create) {
  1258. $fields['creator'] = $uid;
  1259. $fields['created'] = REQUEST_TIME;
  1260. }
  1261. // Conditions.
  1262. $names =& self::$_fields['conditions'];
  1263. $conditions =& $values['conditions'];
  1264. foreach ($names as $key) {
  1265. switch ($key) {
  1266. case 'severity':
  1267. if (!empty($conditions[$key])) {
  1268. if (!is_array($v = $conditions[$key])) {
  1269. throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
  1270. self::$_errorCodes['bad_filter_condition']);
  1271. }
  1272. // Use array values, but convert 'zero' to zero.
  1273. // And make sure it's all integers.
  1274. $fields[$key] = preg_replace('/[^\d,]/', '', str_replace('zero', '0', join(',', $v)));
  1275. }
  1276. else {
  1277. $fields[$key] = '';
  1278. }
  1279. break;
  1280. case 'type':
  1281. if (!empty($conditions[$key])) {
  1282. if (!is_array($v = $conditions[$key])) {
  1283. throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
  1284. self::$_errorCodes['bad_filter_condition']);
  1285. }
  1286. // Use array values, because key is simply numeric index.
  1287. $fields[$key] = check_plain(join(',', $v));
  1288. }
  1289. else {
  1290. $fields[$key] = '';
  1291. }
  1292. break;
  1293. case 'time_range':
  1294. $fields[$key] = !empty($conditions[$key]) && ($v = (int)$conditions[$key]) > 0 && $v < 10000 ? $v : 0;
  1295. break;
  1296. case 'time_from':
  1297. case 'time_to':
  1298. $fields[$key] = !empty($conditions[$key]) && ($v = (int)$conditions[$key]) > 0 && $v <= PHP_INT_MAX ? $v : 0;
  1299. break;
  1300. case 'role':
  1301. case 'uid':
  1302. $fields[$key] = array_key_exists($key, $conditions) ? (int)$conditions[$key] : -1;
  1303. break;
  1304. case 'hostname':
  1305. case 'location':
  1306. case 'referer':
  1307. $fields[$key] = !empty($conditions[$key]) ? check_plain($conditions[$key]) : '';
  1308. break;
  1309. default:
  1310. // Ignore.
  1311. }
  1312. }
  1313. // Order by.
  1314. $order_by = '';
  1315. if (!empty($values['order_by'])) {
  1316. $order_by = array();
  1317. foreach ($values['order_by'] as $k => $v) {
  1318. if (preg_match('/^[a-z\d_]{2,32}$/', $k) && ($v === 'DESC' || $v === 'ASC')) {
  1319. $order_by[] = $k . ':' . $v;
  1320. }
  1321. }
  1322. $fields['order_by'] = join(',', $order_by);
  1323. }
  1324. else {
  1325. $fields['order_by'] = '';
  1326. }
  1327. // Use unique key on name column for testing uniqueness.
  1328. if ($create) {
  1329. $fields['name'] = $name;
  1330. try {
  1331. db_insert('log_filter')
  1332. ->fields($fields)
  1333. ->execute();
  1334. }
  1335. catch (PDOException $xc) {
  1336. if ((int)$xc->getCode() == 23000) { // Fair chance that it's a duplicate key error, though 23000 may also (MySQL) mean null error.
  1337. throw new Exception('Filter name[' . $name . '] already exists.', self::$_errorCodes['filter_name_nonunique']);
  1338. }
  1339. else {
  1340. throw $xc;
  1341. }
  1342. }
  1343. }
  1344. elseif (!($filter = self::_readFilter($name))) {
  1345. if ($filter === FALSE) {
  1346. throw new Exception('Filter name[' . $name . '] doesnt exist.', self::$_errorCodes['filter_doesnt_exist']);
  1347. }
  1348. throw new Exception('User (' . $uid . ') ' . $GLOBALS['user']->name . ' is not allowed to use filter[' . $name . '].',
  1349. self::$_errorCodes['perm_filter_restricted']);
  1350. }
  1351. elseif (
  1352. !db_update('log_filter')
  1353. ->fields($fields)
  1354. ->condition('name', $name)
  1355. ->execute()
  1356. ) {
  1357. throw new Exception('Failed to update filter[' . $name . '].', self::$_errorCodes['unknown']);
  1358. }
  1359. }
  1360. /**
  1361. * @param array &$conditions
  1362. * - empty or invalid conditions will be removed from the array
  1363. * @param array $order_by
  1364. * @param integer $offset
  1365. * - default: zero
  1366. * - ignored until MySQL supports LIMIT for sub queries
  1367. * @param integer $max
  1368. * - default: zero
  1369. * - ignored until MySQL supports LIMIT for sub queries
  1370. * @param array|NULL|boolean $log_columns
  1371. * - default: NULL (~ all watchdog columns)
  1372. * - FALSE: count only
  1373. * @param array|boolean $user_columns
  1374. * - default: FALSE (~ no user columns)
  1375. * @param boolean $deletion
  1376. * - default: FALSE (~ not for deleting logs)
  1377. * @return SelectQuery|SelectQueryInterface
  1378. * @throws Exception
  1379. * - PDOException
  1380. */
  1381. protected static function _logListQuery(&$conditions, $order_by, $offset = 0, $max = 0, $log_columns = NULL, $user_columns = FALSE, $deletion = FALSE) {
  1382. $query = db_select('watchdog', 'w');
  1383. if ($user_columns) {
  1384. $query->leftJoin('users', 'u', 'w.uid = u.uid');
  1385. }
  1386. if (!$log_columns) {
  1387. if ($log_columns !== FALSE) {
  1388. $query->fields('w'); // Wildcard columns.
  1389. //->fields('w', array('wid', 'uid', 'type', 'message', 'variables', 'severity', 'link', 'location', 'referer', 'hostname', 'timestamp'))
  1390. }
  1391. }
  1392. else {
  1393. $query->fields('w', $log_columns);
  1394. }
  1395. if ($user_columns) {
  1396. $le = count($user_columns);
  1397. for ($i = 0; $i < $le; $i++) {
  1398. $query->addField('u', $user_columns[$i]);
  1399. }
  1400. }
  1401. // Conditions.
  1402. if ($deletion || $conditions) {
  1403. // The wid (log id) condition is not part of the overall used conditions, so we have to handle it differently.
  1404. // @todo: Explain just how, the code is certainly not self-explanatory in this sense.
  1405. $wid = 0;
  1406. if (isset($conditions['wid'])) {
  1407. if (($v = (int)$conditions['wid']) && $v > -1 && $v <= PHP_INT_MAX) {
  1408. $wid = $v;
  1409. $query->condition('w.' . 'wid', $v, '=');
  1410. // Remove all other conditions.
  1411. array_splice($conditions, 0);
  1412. $conditions['wid'] = $wid;
  1413. }
  1414. else {
  1415. unset($conditions['wid']);
  1416. }
  1417. }
  1418. if (!$wid) {
  1419. $names =& self::$_fields['conditions'];
  1420. // Use user id instead of role (anonymous user role doesnt really exists - only as a lack of any roles)?
  1421. $is_anonymous = $not_anonymous = FALSE;
  1422. if (isset($conditions['role'])) {
  1423. switch ($conditions['role']) {
  1424. case DRUPAL_ANONYMOUS_RID:
  1425. $is_anonymous = TRUE;
  1426. $conditions['uid'] = 0;
  1427. break;
  1428. case DRUPAL_AUTHENTICATED_RID:
  1429. $not_anonymous = TRUE;
  1430. $conditions['uid'] = 0;
  1431. break;
  1432. }
  1433. }
  1434. // Deleting events that log deletion of events is illegal.
  1435. if ($deletion) {
  1436. if (!empty($conditions['type']) && ($index = array_search('log_filter delete logs', $conditions['type'], TRUE)) !== FALSE) {
  1437. if (count($conditions['type']) == 1) {
  1438. unset($conditions['type']);
  1439. }
  1440. else {
  1441. array_splice($conditions['type'], $index, 1);
  1442. }
  1443. }
  1444. $query->condition('w.' . 'type', 'log_filter delete logs', '!=');
  1445. }
  1446. foreach ($names as $key) {
  1447. if (isset($conditions[$key])) {
  1448. if (!($v = $conditions[$key]) && $key != 'uid') {
  1449. unset($conditions[$key]);
  1450. }
  1451. else {
  1452. switch ($key) {
  1453. case 'severity':
  1454. if (!is_array($v)) {
  1455. throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
  1456. self::$_errorCodes['bad_filter_condition']);
  1457. }
  1458. // Convert 'zero' to zero.
  1459. $query->condition('w.' . $key, explode(',', str_replace('zero', '0', join(',', $v))), 'IN');
  1460. break;
  1461. case 'type':
  1462. if (!is_array($v)) {
  1463. throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
  1464. self::$_errorCodes['bad_filter_condition']);
  1465. }
  1466. $query->condition('w.' . $key, explode(',', check_plain(join(',', $v))), 'IN');
  1467. break;
  1468. case 'time_range':
  1469. if (($v = (int)$v) > 0 && $v < 10000) {
  1470. $query->condition('w.' . 'timestamp', REQUEST_TIME - ($v * 60 * 60), '>=');
  1471. }
  1472. else {
  1473. unset($conditions[$key]);
  1474. }
  1475. break;
  1476. case 'time_from':
  1477. if (($v = (int)$v) > 0 && $v <= PHP_INT_MAX) {
  1478. $query->condition('w.' . 'timestamp', $v, '>=');
  1479. }
  1480. else {
  1481. unset($conditions[$key]);
  1482. }
  1483. break;
  1484. case 'time_to':
  1485. if (($v = (int)$v) > 0 && $v <= PHP_INT_MAX) {
  1486. $query->condition('w.' . 'timestamp', $v, '<=');
  1487. }
  1488. else {
  1489. unset($conditions[$key]);
  1490. }
  1491. break;
  1492. case 'role':
  1493. if (($v = (int)$v) > 0 && $v <= PHP_INT_MAX) {
  1494. if (!$is_anonymous && !$not_anonymous) {
  1495. $query->join('users_roles', 'ur', 'w.uid = ur.uid AND ur.rid = :rid', array(':rid' => $v));
  1496. }
  1497. }
  1498. else {
  1499. unset($conditions[$key]);
  1500. }
  1501. break;
  1502. case 'uid':
  1503. if (($v = (int)$v) > -1 && $v <= PHP_INT_MAX) {
  1504. $query->condition('w.' . $key, $v, !$not_anonymous ? '=' : '!=');
  1505. }
  1506. else {
  1507. unset($conditions[$key]);
  1508. }
  1509. break;
  1510. case 'hostname':
  1511. case 'location':
  1512. if (($v = trim(check_plain($v))) && $v !== '*') {
  1513. $query->condition('w.' . $key, $v, '=');
  1514. }
  1515. else {
  1516. unset($conditions[$key]);
  1517. }
  1518. break;
  1519. case 'referer': // Support strictly empty value 'none'
  1520. if (($v = trim(check_plain($v))) && $v !== '*') {
  1521. if ($v != 'none') {
  1522. $query->condition('w.' . $key, $v, '=');
  1523. }
  1524. else {
  1525. $query->condition(db_or()->condition('w.' . $key, '', '=')->isNull('w.' . $key));
  1526. }
  1527. }
  1528. else {
  1529. unset($conditions[$key]);
  1530. }
  1531. break;
  1532. default:
  1533. // Ignore.
  1534. }
  1535. }
  1536. }
  1537. }
  1538. // Remove uid again, if only used as surrogate for role.
  1539. if ($is_anonymous || $not_anonymous) {
  1540. unset($conditions['uid']);
  1541. }
  1542. }
  1543. }
  1544. // Simple log listing defaults to hide previous log deletions, unless user actually want to list log deletions.
  1545. if (!$deletion
  1546. && (empty($conditions['type']) || array_search('log_filter delete logs', $conditions['type'], TRUE) === FALSE)
  1547. && !variable_get('log_filter_showdeletions', FALSE)
  1548. ) {
  1549. $query->condition('w.' . 'type', 'log_filter delete logs', '!=');
  1550. }
  1551. if ($log_columns === FALSE) {
  1552. return $query->countQuery();
  1553. }
  1554. // Order by.
  1555. // Will always be qualified extra by log id order, to secure correct (sub) order.
  1556. if (!empty($order_by)) {
  1557. $timeAscending = FALSE;
  1558. foreach($order_by as &$orderBy) {
  1559. if (preg_match('/^[a-z\d_]{2,32}$/', $k = $orderBy[0]) && (($v = $orderBy[1]) === 'DESC' || $v === 'ASC')) {
  1560. if ($k == 'time' || $k == 'timestamp') {
  1561. $query->orderBy('w.timestamp', $v);
  1562. if ($v == 'ASC') {
  1563. $timeAscending = TRUE;
  1564. }
  1565. }
  1566. else {
  1567. $query->orderBy('w.' . $k, $v);
  1568. }
  1569. }
  1570. }
  1571. unset($orderBy);
  1572. $query->orderBy('w.wid', !$timeAscending ? 'DESC' : 'ASC');
  1573. }
  1574. else {
  1575. $query->orderBy('timestamp', 'DESC')->orderBy('w.wid', 'DESC');
  1576. }
  1577. if (($offset || $max) && self::DB_SUBQUERY_LIMIT) {
  1578. // A range must have a max length, otherwise it's ignored.
  1579. if (!$max) {
  1580. $max = PHP_INT_MAX - $offset;
  1581. }
  1582. $query->range($offset, $max);
  1583. }
  1584. return $query;
  1585. }
  1586. /**
  1587. * @param array &$conditions
  1588. * @param array $order_by
  1589. * @param integer $offset
  1590. * - minus one: list $max number of items from end of total matching items
  1591. * @param integer $max
  1592. * @param boolean $translate
  1593. * - default: FALSE
  1594. * @return array
  1595. */
  1596. protected static function _listLogs(&$conditions, $order_by, $offset, $max, $translate = FALSE) {
  1597. // Find total matching items.
  1598. $total = (int)self::_logListQuery($conditions, $order_by, 0, 0, FALSE)->execute()->fetchField();
  1599. // Max number of items from end of total matching items?
  1600. if ($offset < 0) {
  1601. $offset = $max ? $total - $max : 0;
  1602. }
  1603. $query = self::_logListQuery($conditions, $order_by, $offset, $max, NULL, array('name'));
  1604. if (($offset || $max) && !self::DB_SUBQUERY_LIMIT) {
  1605. // A range must have a max length, otherwise it's ignored.
  1606. if (!$max) {
  1607. $max = PHP_INT_MAX - $offset;
  1608. }
  1609. $query->range($offset, $max);
  1610. }
  1611. // Work on event buckets.
  1612. if (($rows = $query->execute()->fetchAll())) {
  1613. foreach($rows as &$event) {
  1614. // Translate?
  1615. if (($vars = $event->variables) === 'N;') {
  1616. $event->variables = '';
  1617. }
  1618. elseif (!$translate) { // Do variables replacement frontend.
  1619. $event->variables = unserialize($vars);
  1620. }
  1621. else { // Do translate if any variables
  1622. $event->message = t($event->message, unserialize($vars));
  1623. $event->variables = '';
  1624. }
  1625. // Filter link.
  1626. if ($event->link) {
  1627. $event->link = filter_xss($event->link);
  1628. }
  1629. }
  1630. unset($event); // Clear reference.
  1631. }
  1632. // Tell which conditions were used, but not what their values where - except for the wid (log id) condition.
  1633. $responseConditions = array_fill_keys(array_keys($conditions), true);
  1634. if (!empty($conditions['wid'])) {
  1635. $responseConditions['wid'] = $conditions['wid'];
  1636. }
  1637. return array(
  1638. $rows,
  1639. $responseConditions,
  1640. $offset,
  1641. $total,
  1642. );
  1643. }
  1644. /**
  1645. * Using a LIMIT for delete queryies is kind of hard since db_delete doesnt support LIMIT (because PostgresSQL doesnt support that),
  1646. * and MySQL doesnt support LIMIT for sub queries.
  1647. *
  1648. * And MySQL doesnt support using the same table in a sub query and in an outer CRUD query.
  1649. *
  1650. * So we have to do a separate select getting the relevant ids, and then do a delete using that - potentially too long - list of ids.
  1651. * Does it in chunks of 40000 items; that should work even when db query max length is only half a megabyte.
  1652. *
  1653. * @param array $conditions
  1654. * @param array $order_by
  1655. * @param integer $offset
  1656. * @param integer $max
  1657. * @return integer
  1658. */
  1659. protected static function _deleteLogs($conditions, $order_by, $offset, $max) {
  1660. global $user;
  1661. // Deleting all logs unconditionally is not an option, because deleting logs of type 'log_filter delete logs' is illegal.
  1662. //if (!$conditions && !$offset && !$max) {
  1663. // $deleted = db_delete('watchdog')
  1664. // ->execute();
  1665. //}
  1666. //else {
  1667. $query = self::_logListQuery($conditions, $order_by, $offset, $max, array('wid'), FALSE, TRUE);
  1668. if (($offset || $max) && !self::DB_SUBQUERY_LIMIT) {
  1669. // A range must have a max length, otherwise it's ignored.
  1670. if (!$max) {
  1671. $max = PHP_INT_MAX - $offset;
  1672. }
  1673. $query->range($offset, $max);
  1674. }
  1675. $ids = $query->execute()->fetchCol();
  1676. if (!($le = count($ids))) {
  1677. return 0;
  1678. }
  1679. // This may fail because the list of ids may exceed query limit (query max length).
  1680. if ($le > 40000) {
  1681. $le = count($ids = array_chunk($ids, 40000));
  1682. $deleted = 0;
  1683. for ($i = 0; $i < $le; $i++) {
  1684. $deleted += db_delete('watchdog')
  1685. ->condition('wid', $ids[$i], 'IN')
  1686. ->execute();
  1687. }
  1688. }
  1689. else {
  1690. $deleted = db_delete('watchdog')
  1691. ->condition('wid', $ids, 'IN')
  1692. ->execute();
  1693. }
  1694. //}
  1695. watchdog(
  1696. 'log_filter delete logs',
  1697. 'User (%uid) %name deleted !deleted log events.',
  1698. array('%uid' => $user->uid, '%name' => $user->name, '!deleted' => $deleted),
  1699. WATCHDOG_NOTICE
  1700. );
  1701. return $deleted;
  1702. }
  1703. /**
  1704. * Access permission: 'access site reports'.
  1705. *
  1706. * All actions require the POST var form_token.
  1707. *
  1708. * Expects (requires) POST vars on actions:
  1709. * - filter_create|filter_edit: name, filter, conditions, order_by
  1710. * - list_logs: conditions, order_by, offset, max, translate
  1711. * - delete_logs: conditions, order_by, offset, max
  1712. *
  1713. * @see LogFilter::ajaxCallback
  1714. * @param string $action
  1715. * @return void
  1716. * - sends 403 header if the expected POST vars arent set or their sanitized values evaluates to empty
  1717. */
  1718. public static function ajaxCallback($action) {
  1719. if ( // Require permission.
  1720. !user_access('access site reports')
  1721. // Require valid composition of the 'action' parameter (presumably a GET var).
  1722. || !$action || !($le = strlen($action)) // Deliberately not drupal_...().
  1723. || $le > 32
  1724. ) {
  1725. header('HTTP/1.1 403 Forbidden');
  1726. exit;
  1727. }
  1728. $action = '' . $action;
  1729. $oResp = new stdClass();
  1730. $oResp->action = check_plain($action); // Redundant; vs. code review.
  1731. $oResp->error = '';
  1732. $oResp->success = TRUE;
  1733. $oResp->error_code = 0;
  1734. try {
  1735. switch ($action) {
  1736. case 'username_autocomplete':
  1737. $oResp = array();
  1738. if (isset($_GET['term']) && strlen($needle = trim($_GET['term'])) && drupal_validate_utf8($needle)) {
  1739. $maxResult = 9;
  1740. //$oResp = array( array('value' => '8', 'label' => 'someuser'), );
  1741. $uids = array();
  1742. $users = db_select('users', 'u')
  1743. ->fields('u', array('uid', 'name'))
  1744. ->condition('name', db_like($needle) . '%', 'LIKE')
  1745. ->orderBy('u.name', 'ASC')
  1746. ->range(0, $maxResult)
  1747. ->execute()
  1748. ->fetchAll();
  1749. if (($le = count($users))) {
  1750. for ($i = 0; $i < $le; ++$i) {
  1751. $oResp[] = array('value' => $uids[] = $users[$i]->uid, 'label' => $users[$i]->name);
  1752. }
  1753. }
  1754. if ($le < $maxResult) {
  1755. $users = db_select('users', 'u')
  1756. ->fields('u', array('uid', 'name'));
  1757. if ($uids) {
  1758. $users = $users->condition('uid', $uids, 'NOT IN');
  1759. }
  1760. $users = $users->condition('name', '%' . db_like($needle) . '%', 'LIKE')
  1761. ->orderBy('u.name', 'ASC')
  1762. ->range(0, $maxResult - $le)
  1763. ->execute()
  1764. ->fetchAll();
  1765. if (($le = count($users))) {
  1766. for ($i = 0; $i < $le; ++$i) {
  1767. $oResp[] = array('value' => $users[$i]->uid, 'label' => $users[$i]->name);
  1768. }
  1769. }
  1770. }
  1771. }
  1772. break;
  1773. case 'filter_create':
  1774. case 'filter_edit':
  1775. $conditions = $order_by = NULL;
  1776. if (
  1777. !array_key_exists('name', $_POST) || !($le = strlen($name = $_POST['name'])) // Deliberately not drupal_...(). And composition is checked later.
  1778. || $le > 32
  1779. || !array_key_exists('filter', $_POST) || !is_array($filter = $_POST['filter']) // It is an array!
  1780. || !array_key_exists('require_admin', $filter) || !(($require_admin = (int)$filter['require_admin']) == 0 || $require_admin == 1)
  1781. || !array_key_exists('description', $filter)
  1782. || ( ($description = $filter['description']) !== '' && (!drupal_validate_utf8($description) || drupal_strlen($description) > 255) )
  1783. // conditions may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
  1784. // _saveFilter() checks/filters conditions buckets vs. xss.
  1785. || (array_key_exists('conditions', $_POST) && ($conditions = $_POST['conditions']) && !is_array($conditions))
  1786. // order_by may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
  1787. // _saveFilter() checks/filters order_by buckets vs. xss.
  1788. || (array_key_exists('order_by', $_POST) && ($order_by = $_POST['order_by']) && !is_array($order_by))
  1789. ) {
  1790. header('HTTP/1.1 403 Forbidden');
  1791. exit;
  1792. }
  1793. if (!user_access('log_filter edit filters')) {
  1794. $oResp->success = FALSE;
  1795. $oResp->error_code = self::$_errorCodes['perm_filter_crud'];
  1796. }
  1797. elseif (!preg_match('/^[a-z_][a-z\d_]+$/', $name) || $name == 'default' || $name == 'adhoc') { // @IDE: var $name is declared.
  1798. $oResp->success = FALSE;
  1799. $oResp->error_code = self::$_errorCodes['filter_name_composition'];
  1800. // $oResp->error = t('Invalid machine name[' . $name . '].'); Frontend creates own message.
  1801. $oResp->name = check_plain($name);
  1802. }
  1803. else {
  1804. $oResp->name = $name = check_plain(strtolower($name)); // Deliberately not drupal_...().
  1805. $oResp->description = !$description ? '' : check_plain(trim(str_replace(array("\r", "\n", "\t"), ' ', $description)));
  1806. self::_saveFilter(
  1807. $name,
  1808. array(
  1809. 'filter' => array(
  1810. 'require_admin' => $require_admin,
  1811. 'description' => $description
  1812. ),
  1813. 'conditions' => $conditions ? $conditions : array(), // _saveFilter() checks/filters conditions buckets vs. xss.
  1814. 'order_by' => $order_by ? $order_by : array(), // _saveFilter() checks/filters order_by buckets vs. xss.
  1815. ),
  1816. $action == 'filter_create'
  1817. );
  1818. }
  1819. break;
  1820. case 'list_logs':
  1821. $conditions = $order_by = NULL;
  1822. if (
  1823. // conditions may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
  1824. // _logListQuery() checks/filters conditions buckets vs. xss.
  1825. (array_key_exists('conditions', $_POST) && ($conditions = $_POST['conditions']) && !is_array($conditions))
  1826. // order_by may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
  1827. // _logListQuery() checks/filters order_by buckets vs. xss.
  1828. || (array_key_exists('order_by', $_POST) && ($order_by = $_POST['order_by']) && !is_array($order_by))
  1829. || !array_key_exists('offset', $_POST) || ($offset = (int)$_POST['offset']) < -1 || $offset > PHP_INT_MAX
  1830. || !array_key_exists('max', $_POST) || ($max = (int)$_POST['max']) < 0 || $max > PHP_INT_MAX
  1831. || !array_key_exists('translate', $_POST)
  1832. ) {
  1833. header('HTTP/1.1 403 Forbidden');
  1834. exit;
  1835. }
  1836. if ($max > self::PAGE_RANGE_MAX) {
  1837. $max = self::PAGE_RANGE_MAX;
  1838. }
  1839. if (!$conditions) {
  1840. $conditions = array();
  1841. }
  1842. $oResp->log_list = self::_listLogs(
  1843. $conditions,
  1844. $order_by ? $order_by : array(),
  1845. $offset,
  1846. $max,
  1847. $translate = (bool)$_POST['translate']
  1848. );
  1849. // Save pager_range and translate to session.
  1850. $session = array(
  1851. 'settings' => array(
  1852. 'pager_range' => $max > 0 ? $max : variable_get('log_filter_pgrng', self::PAGE_RANGE_DEFAULT),
  1853. 'translate' => $translate,
  1854. ),
  1855. );
  1856. if (module_exists('state')) {
  1857. State::sessionSet('module', 'log_filter', $session);
  1858. }
  1859. else {
  1860. drupal_session_start();
  1861. if (!isset($_SESSION['module'])) {
  1862. $_SESSION['module'] = array(
  1863. 'log_filter' => $session,
  1864. );
  1865. }
  1866. else {
  1867. $_SESSION['module']['log_filter'] = $session;
  1868. }
  1869. }
  1870. break;
  1871. case 'delete_logs':
  1872. $conditions = $order_by = NULL;
  1873. if (
  1874. // conditions may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
  1875. // _logListQuery() checks/filters conditions buckets vs. xss.
  1876. (array_key_exists('conditions', $_POST) && ($conditions = $_POST['conditions']) && !is_array($conditions))
  1877. // order_by may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
  1878. // _logListQuery() checks/filters order_by buckets vs. xss.
  1879. || (array_key_exists('order_by', $_POST) && ($order_by = $_POST['order_by']) && !is_array($order_by))
  1880. || !array_key_exists('offset', $_POST) || ($offset = (int)$_POST['offset']) < -1 || $offset > PHP_INT_MAX
  1881. || !array_key_exists('max', $_POST) || ($max = (int)$_POST['max']) < 0 || $max > PHP_INT_MAX
  1882. ) {
  1883. header('HTTP/1.1 403 Forbidden');
  1884. exit;
  1885. }
  1886. $oResp->delete_logs = self::_deleteLogs(
  1887. $conditions ? $conditions : array(),
  1888. $order_by ? $order_by : array(),
  1889. $offset,
  1890. $max
  1891. );
  1892. break;
  1893. default:
  1894. $oResp->success = FALSE;
  1895. $oResp->error_code = 1;
  1896. $oResp->error = 'Unsupported action[' . $action . '].';
  1897. }
  1898. }
  1899. catch (PDOException $xc) {
  1900. self::_errorHandler($xc);
  1901. $oResp->success = FALSE;
  1902. $oResp->error_code = self::$_errorCodes['db_general'];
  1903. }
  1904. catch (Exception $xc) {
  1905. self::_errorHandler($xc);
  1906. $oResp->success = FALSE;
  1907. if (($error_code = $xc->getCode()) && in_array($error_code, self::$_errorCodes)) {
  1908. $oResp->error_code = $error_code;
  1909. }
  1910. else {
  1911. $oResp->error_code = self::$_errorCodes['unknown'];
  1912. }
  1913. }
  1914. header('Content-Type: application/json; charset=utf-8');
  1915. header('Cache-Control: private, no-store, no-cache, must-revalidate');
  1916. header('Expires: Thu, 01 Jan 1970 00:00:01 GMT');
  1917. echo drupal_json_encode($oResp);
  1918. flush();
  1919. exit;
  1920. }
  1921. }