123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989 |
- <?php
- /**
- * @file
- * Drupal Log Filter module
- */
- class LogFilter {
- /**
- * Numeric index of filter name request argument, if any.
- *
- * @type integer
- */
- const FILTER_NAME_ARG = 4;
- /**
- * @type integer
- */
- const TYPE_SOME_MAX = 102400;
- /**
- * @type integer
- */
- const PAGE_RANGE_DEFAULT = 20;
- /**
- * @type integer
- */
- const PAGE_RANGE_MAX = 1000;
- /**
- * @type integer
- */
- const LIST_MESSAGE_TRUNCATE = 100;
- /**
- * Does databases (MySQL) support LIMIT for sub queries?
- *
- * @type boolean
- */
- const DB_SUBQUERY_LIMIT = FALSE;
- /**
- * @type array $_errorCodes
- */
- protected static $_errorCodes = array(
- 'unknown' => 1,
- // Programmatic errors and wrong use of program.
- 'algo' => 100,
- 'use' => 101,
- // Missing permission.
- 'perm_general' => 200,
- 'form_expired' => 201,
- 'perm_filter_crud' => 202,
- 'perm_filter_restricted' => 203,
- // Database.
- 'db_general' => 500,
- // Misc.
- 'filter_name_composition' => 1001,
- 'filter_name_nonunique' => 1002,
- 'filter_doesnt_exist' => 1003,
- 'bad_filter_condition' => 1010,
- );
- /**
- * Traces and logs via Inspect tracer, or watchdog (no trace).
- *
- * @param Exception $xc
- * @param integer $severity
- * - default: WATCHDOG_ERROR
- * @return void
- */
- protected static function _errorHandler($xc, $severity = WATCHDOG_ERROR) {
- if (module_exists('inspect')) {
- inspect_trace($xc, array('category' => 'log_filter', 'severity' => $severity));
- }
- else {
- watchdog(
- 'log_filter',
- '(' . (int)$xc->getCode() . ') ' . check_plain($xc->getMessage()),
- NULL,
- $severity
- );
- }
- }
- /**
- * @type array
- */
- protected static $_fields = array(
- 'settings' => array(
- 'only_own',
- 'delete_logs_max',
- 'translate',
- 'pager_range',
- ),
- 'filter' => array(
- 'name', // Hidden.
- 'origin',
- 'name_suggest',
- 'description',
- 'require_admin',
- ),
- 'conditions' => array(
- 'time_range',
- 'time_from',
- 'time_from_proxy', // Skip in actual conditions.
- 'time_to',
- 'time_to_proxy', // Skip in actual conditions.
- 'severity',
- 'type_wildcard', // Skip in actual conditions.
- 'type',
- 'role', // Default in database: -1.
- 'uid', // Default in database: -1.
- 'hostname',
- 'location',
- 'referer',
- ),
- 'order_by' => array(
- 'orderby_',
- 'descending_',
- ),
- );
- /**
- * Defines log viewer form and GUI.
- *
- * @param array $form
- * @param array &$form_state
- * @return array
- */
- public static function viewerForm($form, &$form_state) {
- $path = drupal_get_path('module', 'log_filter');
- // Get Judy.
- drupal_add_library('judy', 'judy');
- // Get jQuery UI dialog.
- drupal_add_library('system', 'ui.dialog');
- // Get jQuery UI datepicker.
- drupal_add_library('system', 'ui.datepicker');
- // Get jQuery UI autocomplete for username.
- drupal_add_library('system', 'ui.autocomplete');
- // Have to include own datepicker localisation, because it doesnt seem to exist in neither core nor the Date Popup module.
- drupal_add_js(
- $path . '/js/jquery_ui_datepicker_i18n/jquery.ui.datepicker-' . $GLOBALS['language']->language . '.min.js',
- array('type' => 'file', 'group' => JS_DEFAULT, 'preprocess' => FALSE, 'every_page' => FALSE)
- );
- // Use the module's default css.
- if (($use_module_css = variable_get('log_filter_cssdefault', TRUE))) {
- drupal_add_css(
- $path . '/css/log_filter' . '.min' . '.css',
- array('type' => 'file', 'group' => CSS_DEFAULT, 'preprocess' => FALSE, 'every_page' => FALSE)
- );
- }
- // Add table header script.
- drupal_add_js('misc/tableheader.js');
- drupal_add_js(
- $path . '/js/log_filter' . '.min' . '.js',
- array('type' => 'file', 'group' => JS_DEFAULT, 'preprocess' => FALSE, 'every_page' => FALSE)
- );
- try {
- $allow_filter_edit = user_access('log_filter edit filters');
- // Get submitted vars from session, and remove them (for later form build and submission).
- $formSubmitted = $session = $settings = $submitted = $messages = NULL;
- if (module_exists('state')) {
- if (($session = State::sessionGet('module', 'log_filter')) && array_key_exists('submitted', $session)) {
- State::sessionRemove('module', 'log_filter', 'submitted');
- State::sessionRemove('module', 'log_filter', 'messages');
- }
- }
- else {
- drupal_session_start();
- if (!empty($_SESSION['module']) && !empty($_SESSION['module']['log_filter'])) {
- $session = $_SESSION['module']['log_filter'];
- unset($_SESSION['module']['log_filter']['submitted']);
- unset($_SESSION['module']['log_filter']['messages']);
- }
- }
- if ($session) {
- if (array_key_exists('settings', $session)) {
- $settings =& $session['settings'];
- }
- if (array_key_exists('submitted', $session)) {
- $formSubmitted = TRUE;
- $submitted =& $session['submitted'];
- }
- if (array_key_exists('messages', $session)) {
- $messages =& $session['messages'];
- }
- }
- // Get current filter name from url, if any.
- $filter_name = $submitted ? $submitted['filter']['name'] :
- (($le = strlen($v = arg(self::FILTER_NAME_ARG))) // Deliberately not drupal_...().
- && $le <= 32
- && $v != 'log_filter' && $v != 'default' && $v != 'adhoc'
- && preg_match('/^[a-z_][a-z\d_]+$/', $v) ? $v : '');
- $require_admin = $submitted ? $submitted['filter']['require_admin'] : FALSE;
- $user_admin_permission = user_access('log_filter administer');
- // Get mode: default | adhoc | stored (create|edit|delete_filter aren't allowed at form build).
- $mode = $submitted ? $submitted['mode'] : ($filter_name ? 'stored' : 'default');
- // Stored mode may degrade to default.
- if ($mode == 'stored') {
- $success = TRUE;
- if (!$filter_name) {
- throw new Exception('Filter name[' . $filter_name . '] cannot be empty in mode[stored].');
- }
- if (!($stored_filter = self::_readFilter($filter_name))) {
- $success = FALSE;
- if (!$formSubmitted) { // Don't put these errors twice.
- if ($stored_filter === FALSE) {
- drupal_set_message(
- t('The filter \'!name\' doesn\'t exist.', array('!name' => $filter_name)),
- 'warning'
- );
- }
- else {
- drupal_set_message(
- t('You\'re not allowed to use the filter named \'!name\'.', array('!name' => $filter_name)),
- 'warning'
- );
- }
- }
- $mode = 'default';
- }
- if ($success) {
- $filter = array(
- 'origin' => '',
- 'description' => $stored_filter['description'],
- 'require_admin' => $require_admin = $stored_filter['require_admin'],
- );
- $fields_conditions =& self::$_fields['conditions'];
- $conditions = array();
- foreach ($fields_conditions as $name) {
- switch ($name) {
- case 'time_range':
- case 'time_from':
- case 'time_to':
- case 'role':
- $conditions[$name] = ($v = $stored_filter[$name]) > 0 ? $v : '';
- case 'uid':
- $conditions[$name] = ($v = $stored_filter[$name]) > 0 || $v === '0' || $v === 0 ? $v : '';
- break;
- case 'severity':
- if (!strlen($v = $stored_filter['severity'])) { // Deliberately not drupal_...().
- $v = array('-1');
- }
- else {
- $v = str_replace(',0,', ',zero,', ',' . $v . ',');
- $v = explode(',', substr($v, 1, strlen($v) - 1)); // Deliberately not drupal_...().
- }
- $conditions['severity'] = $v;
- break;
- case 'type_wildcard':
- $conditions['type_wildcard'] = !$stored_filter['type'] ? TRUE : FALSE;
- break;
- case 'type':
- $conditions['type'] = ($v = $stored_filter['type']) ? explode(',', $v) : array();
- break;
- case 'time_from_proxy':
- case 'time_to_proxy':
- // Set by frontend, if non-empty time_from/time_to
- $conditions[$name] = '';
- break;
- default:
- $conditions[$name] = $stored_filter[$name];
- }
- }
- unset($fields_conditions);
- $order_by = array();
- if (($v = $stored_filter['order_by'])) {
- $le = count($arr = explode(',', $v));
- for ($i = 0; $i < $le; $i++) {
- $v = explode(':', $arr[$i]);
- $order_by[] = array(
- $v[0],
- $v[1] == 'DESC' // ~ To boolean.
- );
- }
- }
- $title = $filter_name . (($v = $filter['description']) ? ('<span> - ' . $v . '</span>') : '');
- }
- else {
- $mode = 'default';
- $filter_name = 'default';
- }
- unset($stored_filter);
- }
- // Do other modes.
- switch ($mode) {
- case 'default':
- $filter = array(
- 'origin' => '',
- 'description' => '',
- 'require_admin' => FALSE,
- );
- $fields_conditions =& self::$_fields['conditions'];
- $conditions = array();
- foreach ($fields_conditions as $name) {
- switch ($name) {
- case 'severity':
- $conditions[$name] = array('-1');
- break;
- case 'type_wildcard':
- $conditions[$name] = TRUE;
- break;
- case 'type':
- $conditions[$name] = array();
- break;
- default:
- $conditions[$name] = '';
- }
- }
- unset($fields_conditions);
- $order_by = array(
- array('time', TRUE),
- );
- $title = t('Default');
- break;
- case 'adhoc':
- $filter =& $submitted['filter'];
- $conditions =& $submitted['conditions'];
- if (!$conditions['severity']) {
- $conditions['severity'] = array('-1');
- }
- $order_by =& $submitted['order_by'];
- $title = t('Ad hoc') . (($v = $filter['origin']) ? ('<span> - ' . t('based on !origin', array('!origin' => $v)) . '</span>') : '');
- break;
- case 'stored':
- // Done earlier.
- break;
- case 'create':
- case 'edit':
- case 'delete_filter':
- throw new Exception('Mode[' . $mode . '] not allowed at form build.', self::$_errorCodes['algo']);
- break;
- default:
- throw new Exception('Unsupported mode[' . $mode . '].', self::$_errorCodes['algo']);
- }
- // Prepare some fields.
- // Type (frontend: type_some).
- if (($options_type = db_select('watchdog')
- ->fields('watchdog', array('type'))
- ->distinct()
- ->execute()->fetchCol())) {
- sort($options_type);
- foreach ($options_type as &$v) {
- $v = str_replace(array("\r", "\n", ','), ' ', $v);
- }
- unset($v); // Clear reference.
- $options_type = array_combine($options_type, $options_type);
- // The filter may contain types that do not exist currently.
- $prepend_types = array();
- if ($conditions['type']) {
- foreach ($conditions['type'] as $v) {
- if ($v) {
- if (isset($options_type[$v])) {
- unset($options_type[$v]);
- }
- $prepend_types[$v] = $v;
- }
- }
- if ($prepend_types) {
- $options_type = array_merge($prepend_types, $options_type);
- }
- }
- }
- elseif ($conditions['type']) {
- $options_type = $conditions['type'];
- }
- else { // Make sure that there's always at least a single option.
- $options_type = array('php' => 'php');
- }
- // Severity.
- $options_severity = array(
- '-1' => t('Any'),
- 'zero' => t('emergency'),
- '1' => t('alert'),
- '2' => t('critical'),
- '3' => t('error'),
- '4' => t('warning'),
- '5' => t('notice'),
- '6' => t('info'),
- '7' => t('debug'),
- );
- // Role.
- $options_role = user_roles();
- foreach ($options_role as &$v) {
- $v = t($v);
- }
- unset($v); // Clear reference.
- $options_role = array('' => t('Any')) + $options_role; // Union operator (+) doesnt re-index numerical keys like array_merge() does.
- // Order by.
- $options_order_by = array(
- '' => '',
- 'time' => t('Time'),
- 'severity' => t('Severity'),
- 'type' => t('Type'),
- 'role' => t('User role'),
- 'uid' => t('User ID'),
- 'hostname' => t('Visitor\'s hostname'),
- 'location' => t('Location'),
- 'referer' => t('Referrer'),
- );
- $length_order_by = count($order_by);
- // Only own filters.
- $value_only_own = $settings && array_key_exists('only_own', $settings) ? $settings['only_own'] : FALSE;
- // Filter selector.
- $uid = $GLOBALS['user']->uid;
- if ( ($options_filters = self::_listFilters(!$value_only_own ? NULL : array($uid), array('name'), array(array('name')), $uid) ) ) {
- $js_filters = '["' . join('","', $options_filters) . '"]';
- $options_filters = array_merge(
- array('' => t('Default')),
- array_combine($options_filters, $options_filters)
- );
- }
- else {
- $js_filters = '[]';
- $options_filters = array('' => t('Default'));
- }
- // Get current theme.
- $theme = $GLOBALS['theme'];
- // Build form.
- $form['#attributes'] = array('autocomplete' => 'off');
- $form['log_filter_filter_edit'] = array(
- '#type' => 'fieldset',
- '#title' => t('Filter') . ': <span id="log_filter_title_display">' . $title . '</span>',
- '#collapsible' => TRUE,
- '#collapsed' => FALSE,
- 'frontend_init' => array(
- '#type' => 'markup',
- '#markup' => '<script type="text/javascript"> LogFilter.init(' . (int)$use_module_css . ', "' . $theme . '"); </script>',
- ),
- // Control vars.
- 'log_filter_mode' => array(
- '#type' => 'hidden',
- '#default_value' => $mode,
- ),
- 'log_filter_name' => array(
- '#type' => 'hidden',
- '#default_value' => $filter_name,
- ),
- 'log_filter_origin' => array(
- '#type' => 'hidden',
- '#default_value' => $filter['origin'],
- ),
- // Conditions.
- // Time.
- 'log_filter_time_range' => array( // (3 open)
- '#type' => 'textfield',
- '#title' => t('Preceding hours'),
- '#default_value' => $conditions['time_range'] ? $conditions['time_range'] : '',
- '#size' => 2,
- '#prefix' => '<div id="log_filter_criteria"><div class="filter-conditions"><div class="form-item log-filter-time">'
- . '<label>' . t('Time') . '</label>',
- ),
- 'log_filter_time_from_proxy' => array(
- '#type' => 'textfield',
- '#title' => t('From') . '<span>?</span>',
- '#default_value' => '', // Frontend sets it, if non-empty time_from.
- '#size' => 10,
- '#prefix' => '<div class="log-filter-time-or">' . t('or') . '</div>',
- ),
- 'log_filter_time_from' => array(
- '#type' => 'hidden',
- '#default_value' => $conditions['time_from'],
- ),
- 'log_filter_time_to_proxy' => array(
- '#type' => 'textfield',
- '#title' => t('To'),
- '#default_value' => '', // Frontend sets it, if non-empty time_to.
- '#size' => 10,
- ),
- 'log_filter_time_to' => array( // End: log-filter-time (2 open).
- '#type' => 'hidden',
- '#default_value' => $conditions['time_to'],
- '#suffix' => '</div>',
- ),
- // Severity.
- 'log_filter_severity' => array(
- '#type' => 'checkboxes',
- '#title' => t('Severity'),
- '#multiple' => TRUE,
- '#options' => $options_severity,
- '#default_value' => $conditions['severity'],
- ),
- // Type.
- 'log_filter_type_wildcard' => array( // (3 open).
- '#type' => 'checkbox',
- '#title' => t('Any'),
- '#default_value' => $conditions['type_wildcard'],
- '#prefix' => '<div class="form-item log-filter-type">'
- . '<label>' . t('Type') . '</label>',
- ),
- 'log_filter_type_proxy' => array(
- '#type' => 'checkboxes',
- '#options' => $options_type,
- '#default_value' => array(),
- '#prefix' => '<div class="log-filter-type-container">',
- ),
- 'log_filter_type' => array( // End: log-filter-type (2 open).
- '#type' => 'textarea',
- '#default_value' => join("\n", $conditions['type']),
- '#resizable' => FALSE,
- '#suffix' => '</div></div>',
- ),
- // Various.
- 'log_filter_role' => array( // (4 open).
- '#type' => 'select',
- '#title' => t('User role'),
- '#options' => $options_role,
- '#default_value' => $conditions['role'],
- '#prefix' => '<div class="form-item log-filter-various"><div class="log-filter-user">',
- ),
- 'log_filter_uid' => array(
- '#type' => 'textfield',
- '#title' => t('ID'),
- '#default_value' => $conditions['uid'],
- '#size' => 11,
- ),
- 'log_filter_username' => array( // End: log-filter-user (3 open).
- '#type' => 'textfield',
- '#title' => t('Name'),
- '#default_value' => $conditions['uid'],
- '#size' => 20,
- // Can't use Drupal Form API autocomplete, because we need to pass value to the uid field upon selection.
- '#attributes' => array(
- 'class' => array('form-autocomplete'),
- ),
- '#suffix' => '</div>',
- ),
- 'log_filter_hostname' => array(
- '#type' => 'textfield',
- '#title' => t('Visitor\'s hostname') . '<span title="' . t('Use * to display in event list, without filtering') . '">?</span>',
- '#default_value' => $conditions['hostname'],
- '#size' => 64,
- ),
- 'log_filter_location' => array(
- '#type' => 'textfield',
- '#title' => t('Location') . '<span title="' . t('Use * to display in event list, without filtering') . '">?</span>',
- '#default_value' => $conditions['location'],
- '#size' => 64,
- ),
- 'log_filter_referer' => array( // End: filter-conditions, (1 open).
- '#type' => 'textfield',
- '#title' => t('Referrer') . '<span title="'
- . t('Use \'none\' for empty referrer.!nlUse * to display in event list, without filtering.', array('!nl' => "\n"))
- . '">?</span>',
- '#default_value' => $conditions['referer'],
- '#size' => 64,
- '#suffix' => '</div></div>',
- ),
- // Order by.
- 'log_filter_orderby_1' => array( // (2 open)
- '#type' => 'select',
- '#options' => $options_order_by,
- '#default_value' => $length_order_by ? array($order_by[0][0]) : array(),
- '#prefix' => '<div class="filter-orderby"><label>' . t('Order by') . '</label>',
- ),
- 'log_filter_descending_1' => array(
- '#type' => 'checkbox',
- '#default_value' => $length_order_by ? $order_by[0][1] : FALSE,
- '#attributes' => array(
- 'title' => t('Descending'),
- ),
- ),
- 'log_filter_orderby_2' => array(
- '#type' => 'select',
- '#options' => $options_order_by,
- '#default_value' => $length_order_by > 1 ? array($order_by[1][0]) : array(),
- ),
- 'log_filter_descending_2' => array(
- '#type' => 'checkbox',
- '#default_value' => $length_order_by > 1 ? $order_by[1][1] : FALSE,
- '#attributes' => array(
- 'title' => t('Descending'),
- ),
- ),
- 'log_filter_orderby_3' => array(
- '#type' => 'select',
- '#options' => $options_order_by,
- '#default_value' => $length_order_by > 2 ? array($order_by[2][0]) : array(),
- ),
- 'log_filter_descending_3' => array(
- '#type' => 'checkbox',
- '#default_value' => $length_order_by > 2 ? $order_by[2][1] : FALSE,
- '#attributes' => array(
- 'title' => t('Descending'),
- ),
- ),
- 'log_filter_reset' => array( // End: log-filter-reset, filter-orderby, log_filter_criteria (0 open).
- '#type' => 'button',
- '#name' => 'log_filter_reset',
- '#value' => t('Reset'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit', 'edit-reset'),
- ),
- '#prefix' => '<div class="log-filter-reset">',
- '#suffix' => '</div></div></div>',
- ),
- // Filters.
- array(
- '#type' => 'markup',
- '#markup' => '<div id="log_filter_filters"><div id="log_filter_box_filter"><div id="log_filter_filters_cell_0">',
- ),
- 'log_filter_filter' => array( // (2 open)
- '#type' => 'select',
- '#title' => t('Filter'),
- '#options' => $options_filters,
- '#default_value' => $filter_name,
- ),
- 'log_filter_only_own' => array(
- '#access' => $allow_filter_edit,
- '#type' => 'checkbox',
- '#title' => t('List my filters only'),
- '#default_value' => $value_only_own,
- ),
- array(
- '#type' => 'markup',
- '#markup' => '</div>' // End: log_filter_filters_cell_0
- . '<div id="log_filter_filters_cell_1">',
- ),
- 'log_filter_name_suggest' => array(
- '#access' => $allow_filter_edit,
- '#type' => 'textfield',
- '#title' => t('Name'),
- '#default_value' => '', // Only used frontend.
- '#size' => 32,
- '#attributes' => array(
- 'maxlength' => 32,
- ),
- ),
- 'log_filter_require_admin' => array(
- '#access' => $allow_filter_edit && $user_admin_permission,
- '#type' => 'checkbox',
- '#title' => t('Restrict access') . '<span title="' . ($title = t('Require the \'Administer log filtering\' permission.')) . '">?</span>',
- '#default_value' => $require_admin,
- '#attributes' => array(
- 'title' => $title,
- ),
- ),
- array( // End: log_filter_filters_cell_1
- '#type' => 'markup',
- '#markup' => '</div>',
- ),
- 'log_filter_description' => array(
- '#access' => $allow_filter_edit,
- '#type' => 'textarea',
- '#title' => t('Description'),
- '#default_value' => $filter['description'],
- '#rows' => 3,
- '#cols' => 32,
- '#resizable' => FALSE, // Otherwise styling too complicated, and browsers support it natively.
- ),
- array( // (3 open)
- '#type' => 'markup',
- '#markup' => '<div class="log-filter-edit-create">',
- ),
- 'log_filter_edit' => array(
- '#access' => $allow_filter_edit,
- '#type' => 'button',
- '#name' => 'log_filter_edit',
- '#value' => t('Edit'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit'),
- 'style' => 'display:none;',
- ),
- ),
- 'log_filter_create' => array( // (2 open)
- '#access' => $allow_filter_edit,
- '#type' => 'button',
- '#name' => 'log_filter_create',
- '#value' => t('Save as...'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit'),
- 'style' => 'display:none;',
- ),
- ),
- array(
- '#type' => 'markup',
- '#markup' => '</div>' // End. log-filter-edit-create
- . '<div class="log-filter-cancel-save'
- . (strpos(strtolower(PHP_OS), 'win') === 0 ? ' log-filter-reversed-button-sequence' : '') // Deliberately not drupal_...().
- . '">',
- ),
- 'log_filter_cancel' => array( // (3 open)
- '#access' => $allow_filter_edit,
- '#type' => 'button',
- '#name' => 'log_filter_cancel',
- '#value' => t('Cancel'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit', 'edit-cancel'),
- 'style' => 'display:none;',
- ),
- ),
- 'log_filter_save' => array(
- '#access' => $allow_filter_edit,
- '#type' => 'button',
- '#name' => 'log_filter_save',
- '#value' => t('Save'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit'),
- 'style' => 'display:none;',
- ),
- ),
- array(
- '#type' => 'markup',
- '#markup' => '</div>' // End: log-filter-cancel-save
- . '<div class="log-filter-delete">',
- ),
- 'log_filter_delete' => array(
- '#access' => $allow_filter_edit,
- '#type' => 'button',
- '#name' => 'log_filter_delete',
- '#value' => t('Delete filter'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit', 'edit-delete'),
- 'style' => 'display:none;',
- ),
- ),
- array(
- '#type' => 'markup',
- '#markup' => '</div>' // End: log-filter-delete
- . '</div>', // End: log_filter_box_filter
- ),
- array(
- '#access' => ($allow_delete_logs = user_access('log_filter remove logs')),
- '#type' => 'markup',
- '#markup' => '<div id="log_filter_box_delete_logs">',
- ),
- array(
- '#access' => !$allow_delete_logs,
- '#type' => 'markup',
- '#markup' => '<div id="log_filter_box_delete_logs" class="log-filter-delete-logs-none"> ',
- ),
- array( // (2 open)
- '#access' => $allow_delete_logs,
- '#type' => 'button',
- '#name' => 'log_filter_delete_logs_button',
- '#value' => t('Delete logs'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'type' => 'button', // Doesnt work; still type:submit.
- 'class' => array('form-submit', 'edit-delete'),
- 'style' => 'display:none;',
- ),
- ),
- array(
- '#access' => $allow_delete_logs,
- '#type' => 'textfield',
- '#name' => 'log_filter_delete_logs_max',
- '#title' => t('Max.'),
- '#default_value' => $settings && array_key_exists('delete_logs_max', $settings) ? $settings['delete_logs_max'] : '',
- '#attributes' => array(
- 'maxlength' => 11,
- ),
- ),
- array(
- '#type' => 'markup',
- '#markup' => '</div>' // End: log_filter_box_delete_logs
- . '</div>', // End: log_filter_filters
- ),
- );
- $form['log_filter_list_controls'] = array(
- 'frontend_setup' => array(
- '#type' => 'markup',
- '#markup' => '<script type="text/javascript">
- (function($) {
- if(!$) {
- return;
- }
- $(document).bind("ready", function() {
- var elm;
- // t() ruins this, because of some hocus pocus.
- $("div.form-item-log-filter-time-from-proxy label").get(0).setAttribute("title", "'
- . t('Date format: YYYY-MM-DD!newlineTime formats: N, NN, NNNN, NNNNNN, NN:NN, NN:NN:NN') . '".replace(/\!newline/, "\n"));
- // Go.
- LogFilter.setup(
- ' . $js_filters . ',
- ' . ($messages ? json_encode($messages) : 'null') . '
- );
- } );
- })(jQuery);
- </script>
- ',
- ),
- 'log_filter_update_list' => array(
- '#type' => 'button',
- '#name' => 'log_filter_update_list',
- '#value' => t('Update list'),
- '#button_type' => 'button', // Doesnt work; still type:submit.
- '#attributes' => array(
- 'class' => array('form-submit'),
- 'title' => t('[CTR + U / CMD + U]'),
- ),
- '#prefix' => '<div class="log-filter-button log-filter-update-list">',
- '#suffix' => '</div>',
- ),
- 'log_filter_pager_controls' => array(
- '#type' => 'markup',
- '#markup' => '<div class="log-filter-pager-controls">'
- . '<div id="log_filter_pager_first" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
- . t('First') . '">◄◄</div>'
- . '<div id="log_filter_pager_previous" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
- . t('Previous') . '">◄</div>'
- . '<div id="log_filter_pager_current" title="' . t('Update list') . '"> </div>'
- . '<div id="log_filter_pager_progress" class="ajax-progress"><div class="throbber"></div>' . t('Loading...') . '</div>'
- . '<div id="log_filter_pager_next" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
- . t('Next') . '">►</div>'
- . '<div id="log_filter_pager_last" class="log-filter-pager-button log-filter-pager-button-disabled" title="'
- . t('Last') . '">►►</div>'
- . '</div>',
- ),
- 'log_filter_pager_range' => array(
- '#type' => 'textfield',
- '#title' => t('Logs per page'),
- '#default_value' => $settings && array_key_exists('pager_range', $settings) && $settings['pager_range'] > 0 ?
- $settings['pager_range'] : variable_get('log_filter_pgrng', self::PAGE_RANGE_DEFAULT),
- '#size' => 4,
- ),
- 'log_filter_translate' => array(
- '#type' => 'checkbox',
- '#title' => t('Translate messages') . '<span title="' . ($title = t('Translating messages is heavy performance-wise.')) . '">?</span>',
- '#default_value' => $settings && array_key_exists('translate', $settings) ? $settings['translate'] : variable_get('log_filter_trnslt', 0),
- '#attributes' => array(
- 'title' => $title,
- ),
- ),
- 'actions' => array(
- '#type' => 'actions',
- 'submit' => array(
- '#type' => 'submit',
- '#value' => t('Update list'),
- '#attributes' => array('style' => 'display:none;'),
- ),
- ),
- '#prefix' => '<div id="log_filter_list_controls">',
- '#suffix' => '</div>',
- );
- $logList = '';
- for ($i = 97; $i < 97 + 26; $i++) {
- //$logList .= '<div class="' . str_repeat(chr($i), 3) . '">' . str_repeat(chr($i), 3) . '</div>';
- }
- $form['log_filter_log_list'] = array(
- '#type' => 'markup',
- '#markup' => '<div id="log_filter_log_list" class="scrollable">' . $logList . '</div>',
- );
- // Add our submit form;
- $form['#submit'][] = '_log_filter_form_submit';
- return $form;
- }
- catch (Exception $xc) {
- self::_errorHandler($xc);
- drupal_set_message($xc->getMessage(), 'error');
- return array();
- }
- }
- /**
- * Called when log viewer form submits.
- *
- * @param array $form
- * @param array &$form_state
- * @return void
- */
- public static function viewerFormSubmit($form, &$form_state) {
- try {
- $values =& $form_state['values'];
- $prefix = 'log_filter_';
- $messages = array();
- $settings = array(
- 'only_own' => !array_key_exists($prefix . 'only_own', $values) ? FALSE : $values[$prefix . 'only_own'],
- 'delete_logs_max' => !array_key_exists($prefix . 'delete_logs_max', $values) ? '' : $values[$prefix . 'delete_logs_max'],
- 'translate' => $values[$prefix . 'translate'],
- 'only_own' => !array_key_exists($prefix . 'only_own', $values) ? FALSE : $values[$prefix . 'only_own'],
- 'pager_range' => ($v = (int)$values[$prefix . 'pager_range']) > -1 ? ($v > self::PAGE_RANGE_MAX ? self::PAGE_RANGE_MAX : $v) :
- variable_get('log_filter_pgrng', self::PAGE_RANGE_DEFAULT),
- );
- $submitted = array(
- 'mode' => $values[$prefix . 'mode'],
- 'filter' => array(
- 'name' => '',
- 'origin' => '',
- 'name_suggest' => '',
- 'description' => '',
- 'require_admin' => !array_key_exists($prefix . 'require_admin', $values) ? FALSE : $values[$prefix . 'require_admin'],
- ),
- );
- $use_form_values = $save = FALSE;
- switch (($mode = $submitted['mode'])) {
- case 'default':
- // Use default values.
- break;
- case 'adhoc':
- // Get specs from form.
- $use_form_values = TRUE;
- $submitted['filter']['origin'] = $values[$prefix . 'origin']; // ~ Hidden field.
- break;
- case 'stored': // Saved filter.
- // Just get filter name; in stored mode we do absolutely nothing at submission but establishing the filter's name.
- // Whether the filter require_admin and user has that permission will be checked at form build - no reason to check twice.
- if (!($submitted['filter']['name'] = $filter_name = $values[$prefix . 'name'])) {
- throw new Exception('Mode[' . $mode . '], empty name[' . $filter_name . '].', self::$_errorCodes['filter_name_composition']);
- }
- break;
- case 'create': // Always AJAX-handled.
- throw new Exception('Mode[' . $mode . '] not allowed at form submission.', self::$_errorCodes['algo']);
- break;
- case 'edit':
- case 'delete_filter':
- // Get name.
- if (!($submitted['filter']['name'] = $filter_name = $values[$prefix . 'name'])) {
- throw new Exception('Mode[' . $mode . '], empty name[' . $filter_name . '].', self::$_errorCodes['filter_name_composition']);
- }
- $success = TRUE;
- // Check CRUD permission.
- if (!user_access('log_filter edit filters')) {
- $success = FALSE;
- // Horrible; have to make almost exactly same message, because of shortcomings of the localization regime.
- switch ($mode) {
- case 'edit':
- watchdog(
- 'log_filter',
- 'Won\'t edit the filter \'!name\' because user !user doesn\'t have \'log_filter edit filters\' permission.',
- array('!name' => $filter_name, '!user' => $GLOBALS['user']->name),
- WATCHDOG_WARNING
- );
- drupal_set_message(
- t('Cannot edit the filter \'!name\', because you don\'t have permission to edit log filters.', array('!name' => $filter_name)),
- 'warning'
- );
- break;
- default: // delete
- watchdog(
- 'log_filter',
- 'Won\'t delete the filter \'!name\' because user !user doesn\'t have \'log_filter edit filters\' permission.',
- array('!name' => $filter_name, '!user' => $GLOBALS['user']->name),
- WATCHDOG_WARNING
- );
- drupal_set_message(
- t('Cannot delete the filter \'!name\', because you don\'t have permission to edit log filters.', array('!name' => $filter_name)),
- 'warning'
- );
- }
- }
- // Check if exists, and get require_admin field.
- elseif (!($require_admin = db_select('log_filter')
- ->fields('log_filter', array('require_admin'))
- ->condition('name', $filter_name, '=')
- ->execute()->fetchField())
- ) {
- if ($require_admin === FALSE) { // Doesn't exist.
- $success = FALSE;
- /* drupal_set_message(
- t('The filter \'!name\' doesn\'t exist.', array('!name' => $filter_name)),
- 'warning'
- ); */
- $messages[] = array(
- t('The filter \'!name\' doesn\'t exist.', array('!name' => $filter_name)),
- 'warning'
- );
- }
- // else... the filter doesnt require admin permission.
- }
- elseif (!user_access('log_filter administer')) {
- $success = FALSE;
- }
- if ($success) {
- switch ($mode) {
- case 'edit':
- // Get specs from form, and save to database.
- $use_form_values = TRUE;
- $save = TRUE;
- $submitted['filter']['description'] = $values[$prefix . 'description'];
- // Change mode.
- $submitted['mode'] = $mode = 'stored';
- break;
- default: // delete
- global $user;
- db_delete('log_filter')->condition('name', $filter_name, '=')->execute();
- watchdog(
- 'log_filter',
- 'User (%uid) %name deleted the log filter \'!filter\'.',
- array('%uid' => $user->uid, '%name' => $user->name, '!filter' => $filter_name),
- WATCHDOG_INFO
- );
- $messages[] = array(
- t('Deleted the filter \'!name\'.', array('!name' => $filter_name))
- );
- // Change mode.
- $submitted['mode'] = $mode = 'default';
- }
- }
- else {
- $use_form_values = $save = FALSE;
- // Change mode.
- $submitted['mode'] = $mode = 'default';
- }
- break;
- default:
- throw new Exception('Unsupported mode[' . $mode . '].', self::$_errorCodes['algo']);
- }
- // Load values from form.
- if ($use_form_values) {
- $fields_conditions =& self::$_fields['conditions'];
- $conditions = array();
- foreach ($fields_conditions as $name) {
- switch ($name) {
- case 'time_range':
- $conditions[$name] = ($v = trim($values[$prefix . $name])) ? $v : '';
- break;
- case 'role':
- $conditions[$name] = ($v = trim($values[$prefix . $name])) > 0 ? $v : -1;
- break;
- case 'uid': // Accepts zero.
- $conditions[$name] = ($v = trim($values[$prefix . $name])) > 0 || $v === '0' || $v === 0 ? $v : -1;
- break;
- case 'time_from':
- $conditions[$name] = $conditions['time_range'] ? '' : (($v = trim($values[$prefix . $name])) ? $v : '');
- break;
- case 'time_from_proxy':
- if (!$save) { // Because save doesnt use the proxy field.
- $conditions[$name] = !$conditions['time_from'] ? '' : $values[$prefix . $name];
- }
- break;
- case 'time_to':
- $conditions[$name] = $conditions['time_range'] ? '' : (($v = trim($values[$prefix . $name])) ? $v : '');
- break;
- case 'time_to_proxy':
- if (!$save) { // Because save doesnt use the proxy field.y
- $conditions[$name] = !$conditions['time_to'] ? '' : $values[$prefix . $name];
- }
- break;
- case 'severity':
- $arr = $values[$prefix . $name];
- $vals = array();
- foreach ($arr as $k => $v) {
- if ($v) {
- if ('' . $k == '-1') {
- $vals = array();
- break;
- }
- else {
- $vals[] = $k;
- }
- }
- }
- $conditions[$name] = $vals;
- break;
- case 'type_wildcard':
- $conditions[$name] = $values[$prefix . $name] ? TRUE : FALSE;
- break;
- case 'type':
- // Dont remember type list (may be very long), if wildcard on.
- $conditions[$name] = $conditions['type_wildcard'] ? array() :
- array_combine($a = explode("\n", str_replace("\r", '', $values[$prefix . $name])), $a);
- if ($save) {
- unset($conditions['type_wildcard']);
- }
- break;
- default:
- $conditions[$name] = trim($values[$prefix . $name]);
- }
- }
- unset($fields_conditions);
- $submitted['conditions'] =& $conditions;
- $fields_order_by =& self::$_fields['order_by'];
- $order_by = array();
- for ($i = 1; $i < 10; $i++) {
- if (array_key_exists($key = $prefix . $fields_order_by[0] . $i, $values)) {
- if (($key = $values[ $key ])) {
- if (!$save) {
- $order_by[] = array($key, $values[ $prefix . $fields_order_by[1] . $i ]);
- }
- else {
- $order_by[] = array($key, $values[ $prefix . $fields_order_by[1] . $i ] ? 'DESC' : 'ASC');
- }
- }
- }
- else {
- break;
- }
- }
- $submitted['order_by'] =& $order_by;
- unset($order_by, $fields_order_by);
- }
- if ($save) { // edit mode.
- $success = TRUE;
- try {
- self::_saveFilter($filter_name, $submitted);
- }
- catch (Exception $xc) {
- self::_errorHandler($xc);
- $success = FALSE;
- $messages[] = array(
- t('Failed to update filter \'!name\'.', array('!name' => $filter_name)),
- 'warning'
- );
- }
- // Change mode.
- if ($success) {
- $submitted['mode'] = $mode = 'stored';
- }
- else {
- $submitted['mode'] = $mode = 'default';
- }
- }
- // Clear conditions and order_by from vars to be passed to session, unless adhoc filter.
- if ($mode != 'adhoc') {
- unset( $submitted['conditions'], $submitted['order_by'] );
- }
- // Pass to session.
- $session = array('settings' => $settings, 'submitted' => $submitted);
- if ($messages) {
- $session['messages'] = $messages;
- }
- if (module_exists('state')) {
- State::sessionSet('module', 'log_filter', $session);
- }
- else {
- drupal_session_start();
- if (!isset($_SESSION['module'])) {
- $_SESSION['module'] = array(
- 'log_filter' => $session,
- );
- }
- else {
- $_SESSION['module']['log_filter'] = $session;
- }
- }
- }
- catch (Exception $xc) {
- self::_errorHandler($xc);
- drupal_set_message($xc->getMessage(), 'error');
- }
- }
- /**
- * Filters off restricted filters if current user isnt allowed to administer filters.
- *
- * @throws PDOException
- * @param integer|string|array|NULL $creatorIds
- * - default: NULL (~ any user)
- * - integer|string: list only filters created by that user
- * - array: list only filters created by those users
- * @param array|NULL $fields
- * - default: all fields
- * @param arrayNULL $orderBy
- * - default: unordered
- * - array: multi-dimensional; every bucket being an array listing field name and 'ASC'|'DESC'
- * @param integer|string|NULL $latestAtTop
- * - default: not
- * - user id: place lastest changed of that user at the very top of the list (requires that $fields is falsy or has 'name' bucket)
- * @return array
- */
- protected static function _listFilters($creatorIds = NULL, $fields = NULL, $orderBy = NULL, $latestAtTop = NULL) {
- $uid = $GLOBALS['user']->uid;
- if ( ($creatorIds && !is_array($creatorIds)) || (!$creatorIds && $creatorIds !== NULL)) {
- $creatorIds = array($creatorIds);
- }
- $list = db_select('log_filter')
- ->fields('log_filter', $fields ? $fields : array('*'));
- if ($creatorIds) {
- $list->condition('creator', $creatorIds, 'IN');
- }
- if (!user_access('log_filter administer')) {
- $list->condition('require_admin', 0);
- }
- if ($orderBy) {
- foreach ($orderBy as $subArr) {
- $list->orderBy($subArr[0], !empty($subArr[1]) ? $subArr[1] : 'ASC');
- }
- }
- $list = ($singleColumned = ($fields && count($fields) == 1)) ? $list->execute()->fetchCol() : $list->execute()->fetchAll();
- if ($list && $latestAtTop !== NULL
- && (!$creatorIds || in_array($latestAtTop, $creatorIds))
- && (!$fields || in_array('name', $fields))
- ) {
- if (($listLatest = db_select('log_filter')
- ->fields('log_filter', array('name', 'changed'))
- ->condition('editor', $latestAtTop, '=')
- ->execute()
- ->fetchAll()
- )) {
- $latestChange = $latestName = 0;
- foreach ($listLatest as $subObj) {
- if ($subObj->changed > $latestChange) {
- $latestChange = $subObj->changed;
- $latestName = $subObj->name;
- }
- }
- if ($latestName) {
- if ($singleColumned) {
- if (($index = array_search($latestName, $list))) { // No need to check !== FALSE, because if zero there's nothing to be done.
- array_splice($list, $index, 1);
- array_unshift($list, $latestName);
- }
- }
- else {
- $index = 0;
- foreach ($list as $k => $subObj) {
- if ($subObj->name == $latestName) {
- $index = $k;
- break;
- }
- }
- if ($index) {
- $latest = array_splice($list, $index, 1);
- array_unshift($list, $latest);
- }
- }
- }
- }
- }
- return $list;
- }
- /**
- * Checks if the filter requires log_filter administrative permission and whether the user has that permission.
- *
- * @throws PDOException
- * @param string $name
- * @return array|boolean|NULL
- * - FALSE: the filter doesnt exist
- * - NULL: user not allowed to use that filter
- */
- protected static function _readFilter($name) {
- return !(
- $filter = db_select('log_filter')
- ->fields('log_filter')
- ->condition('name', $name, '=')
- ->execute()->fetchAssoc()
- ) ? FALSE : $filter['require_admin'] && !user_access('log_filter administer') ? NULL : $filter;
- }
- /**
- * Insert/update filter in database.
- *
- * @throws Exception
- * @param string $name
- * @param array $values
- * @param boolean $create
- * - default: FALSE
- * @return void
- * - throws error on failure
- */
- protected static function _saveFilter($name, $values, $create = FALSE) {
- $uid = $GLOBALS['user']->uid;
- if (!user_access('log_filter edit filters')) {
- throw new Exception(
- 'You\'re not allowed to edit filters.',
- self::$_errorCodes['perm_filter_crud']
- );
- }
- // Filter metadata and last updated by.
- $fields = array(
- 'editor' => $uid,
- 'changed' => REQUEST_TIME,
- 'require_admin' => !empty($values['filter']['require_admin']) ? 1 : 0,
- 'description' => $values['filter']['description'],
- );
- if ($create) {
- $fields['creator'] = $uid;
- $fields['created'] = REQUEST_TIME;
- }
- // Conditions.
- $names =& self::$_fields['conditions'];
- $conditions =& $values['conditions'];
- foreach ($names as $key) {
- switch ($key) {
- case 'severity':
- if (!empty($conditions[$key])) {
- if (!is_array($v = $conditions[$key])) {
- throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
- self::$_errorCodes['bad_filter_condition']);
- }
- // Use array values, but convert 'zero' to zero.
- // And make sure it's all integers.
- $fields[$key] = preg_replace('/[^\d,]/', '', str_replace('zero', '0', join(',', $v)));
- }
- else {
- $fields[$key] = '';
- }
- break;
- case 'type':
- if (!empty($conditions[$key])) {
- if (!is_array($v = $conditions[$key])) {
- throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
- self::$_errorCodes['bad_filter_condition']);
- }
- // Use array values, because key is simply numeric index.
- $fields[$key] = check_plain(join(',', $v));
- }
- else {
- $fields[$key] = '';
- }
- break;
- case 'time_range':
- $fields[$key] = !empty($conditions[$key]) && ($v = (int)$conditions[$key]) > 0 && $v < 10000 ? $v : 0;
- break;
- case 'time_from':
- case 'time_to':
- $fields[$key] = !empty($conditions[$key]) && ($v = (int)$conditions[$key]) > 0 && $v <= PHP_INT_MAX ? $v : 0;
- break;
- case 'role':
- case 'uid':
- $fields[$key] = array_key_exists($key, $conditions) ? (int)$conditions[$key] : -1;
- break;
- case 'hostname':
- case 'location':
- case 'referer':
- $fields[$key] = !empty($conditions[$key]) ? check_plain($conditions[$key]) : '';
- break;
- default:
- // Ignore.
- }
- }
- // Order by.
- $order_by = '';
- if (!empty($values['order_by'])) {
- $order_by = array();
- foreach ($values['order_by'] as $k => $v) {
- if (preg_match('/^[a-z\d_]{2,32}$/', $k) && ($v === 'DESC' || $v === 'ASC')) {
- $order_by[] = $k . ':' . $v;
- }
- }
- $fields['order_by'] = join(',', $order_by);
- }
- else {
- $fields['order_by'] = '';
- }
- // Use unique key on name column for testing uniqueness.
- if ($create) {
- $fields['name'] = $name;
- try {
- db_insert('log_filter')
- ->fields($fields)
- ->execute();
- }
- catch (PDOException $xc) {
- if ((int)$xc->getCode() == 23000) { // Fair chance that it's a duplicate key error, though 23000 may also (MySQL) mean null error.
- throw new Exception('Filter name[' . $name . '] already exists.', self::$_errorCodes['filter_name_nonunique']);
- }
- else {
- throw $xc;
- }
- }
- }
- elseif (!($filter = self::_readFilter($name))) {
- if ($filter === FALSE) {
- throw new Exception('Filter name[' . $name . '] doesnt exist.', self::$_errorCodes['filter_doesnt_exist']);
- }
- throw new Exception('User (' . $uid . ') ' . $GLOBALS['user']->name . ' is not allowed to use filter[' . $name . '].',
- self::$_errorCodes['perm_filter_restricted']);
- }
- elseif (
- !db_update('log_filter')
- ->fields($fields)
- ->condition('name', $name)
- ->execute()
- ) {
- throw new Exception('Failed to update filter[' . $name . '].', self::$_errorCodes['unknown']);
- }
- }
- /**
- * @param array &$conditions
- * - empty or invalid conditions will be removed from the array
- * @param array $order_by
- * @param integer $offset
- * - default: zero
- * - ignored until MySQL supports LIMIT for sub queries
- * @param integer $max
- * - default: zero
- * - ignored until MySQL supports LIMIT for sub queries
- * @param array|NULL|boolean $log_columns
- * - default: NULL (~ all watchdog columns)
- * - FALSE: count only
- * @param array|boolean $user_columns
- * - default: FALSE (~ no user columns)
- * @param boolean $deletion
- * - default: FALSE (~ not for deleting logs)
- * @return SelectQuery|SelectQueryInterface
- * @throws Exception
- * - PDOException
- */
- protected static function _logListQuery(&$conditions, $order_by, $offset = 0, $max = 0, $log_columns = NULL, $user_columns = FALSE, $deletion = FALSE) {
- $query = db_select('watchdog', 'w');
- if ($user_columns) {
- $query->leftJoin('users', 'u', 'w.uid = u.uid');
- }
- if (!$log_columns) {
- if ($log_columns !== FALSE) {
- $query->fields('w'); // Wildcard columns.
- //->fields('w', array('wid', 'uid', 'type', 'message', 'variables', 'severity', 'link', 'location', 'referer', 'hostname', 'timestamp'))
- }
- }
- else {
- $query->fields('w', $log_columns);
- }
- if ($user_columns) {
- $le = count($user_columns);
- for ($i = 0; $i < $le; $i++) {
- $query->addField('u', $user_columns[$i]);
- }
- }
- // Conditions.
- if ($deletion || $conditions) {
- // The wid (log id) condition is not part of the overall used conditions, so we have to handle it differently.
- // @todo: Explain just how, the code is certainly not self-explanatory in this sense.
- $wid = 0;
- if (isset($conditions['wid'])) {
- if (($v = (int)$conditions['wid']) && $v > -1 && $v <= PHP_INT_MAX) {
- $wid = $v;
- $query->condition('w.' . 'wid', $v, '=');
- // Remove all other conditions.
- array_splice($conditions, 0);
- $conditions['wid'] = $wid;
- }
- else {
- unset($conditions['wid']);
- }
- }
- if (!$wid) {
- $names =& self::$_fields['conditions'];
- // Use user id instead of role (anonymous user role doesnt really exists - only as a lack of any roles)?
- $is_anonymous = $not_anonymous = FALSE;
- if (isset($conditions['role'])) {
- switch ($conditions['role']) {
- case DRUPAL_ANONYMOUS_RID:
- $is_anonymous = TRUE;
- $conditions['uid'] = 0;
- break;
- case DRUPAL_AUTHENTICATED_RID:
- $not_anonymous = TRUE;
- $conditions['uid'] = 0;
- break;
- }
- }
- // Deleting events that log deletion of events is illegal.
- if ($deletion) {
- if (!empty($conditions['type']) && ($index = array_search('log_filter delete logs', $conditions['type'], TRUE)) !== FALSE) {
- if (count($conditions['type']) == 1) {
- unset($conditions['type']);
- }
- else {
- array_splice($conditions['type'], $index, 1);
- }
- }
- $query->condition('w.' . 'type', 'log_filter delete logs', '!=');
- }
- foreach ($names as $key) {
- if (isset($conditions[$key])) {
- if (!($v = $conditions[$key]) && $key != 'uid') {
- unset($conditions[$key]);
- }
- else {
- switch ($key) {
- case 'severity':
- if (!is_array($v)) {
- throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
- self::$_errorCodes['bad_filter_condition']);
- }
- // Convert 'zero' to zero.
- $query->condition('w.' . $key, explode(',', str_replace('zero', '0', join(',', $v))), 'IN');
- break;
- case 'type':
- if (!is_array($v)) {
- throw new Exception('Non-empty condition[' . $key . '], type[' . gettype($v) . '], must be array.',
- self::$_errorCodes['bad_filter_condition']);
- }
- $query->condition('w.' . $key, explode(',', check_plain(join(',', $v))), 'IN');
- break;
- case 'time_range':
- if (($v = (int)$v) > 0 && $v < 10000) {
- $query->condition('w.' . 'timestamp', REQUEST_TIME - ($v * 60 * 60), '>=');
- }
- else {
- unset($conditions[$key]);
- }
- break;
- case 'time_from':
- if (($v = (int)$v) > 0 && $v <= PHP_INT_MAX) {
- $query->condition('w.' . 'timestamp', $v, '>=');
- }
- else {
- unset($conditions[$key]);
- }
- break;
- case 'time_to':
- if (($v = (int)$v) > 0 && $v <= PHP_INT_MAX) {
- $query->condition('w.' . 'timestamp', $v, '<=');
- }
- else {
- unset($conditions[$key]);
- }
- break;
- case 'role':
- if (($v = (int)$v) > 0 && $v <= PHP_INT_MAX) {
- if (!$is_anonymous && !$not_anonymous) {
- $query->join('users_roles', 'ur', 'w.uid = ur.uid AND ur.rid = :rid', array(':rid' => $v));
- }
- }
- else {
- unset($conditions[$key]);
- }
- break;
- case 'uid':
- if (($v = (int)$v) > -1 && $v <= PHP_INT_MAX) {
- $query->condition('w.' . $key, $v, !$not_anonymous ? '=' : '!=');
- }
- else {
- unset($conditions[$key]);
- }
- break;
- case 'hostname':
- case 'location':
- if (($v = trim(check_plain($v))) && $v !== '*') {
- $query->condition('w.' . $key, $v, '=');
- }
- else {
- unset($conditions[$key]);
- }
- break;
- case 'referer': // Support strictly empty value 'none'
- if (($v = trim(check_plain($v))) && $v !== '*') {
- if ($v != 'none') {
- $query->condition('w.' . $key, $v, '=');
- }
- else {
- $query->condition(db_or()->condition('w.' . $key, '', '=')->isNull('w.' . $key));
- }
- }
- else {
- unset($conditions[$key]);
- }
- break;
- default:
- // Ignore.
- }
- }
- }
- }
- // Remove uid again, if only used as surrogate for role.
- if ($is_anonymous || $not_anonymous) {
- unset($conditions['uid']);
- }
- }
- }
- // Simple log listing defaults to hide previous log deletions, unless user actually want to list log deletions.
- if (!$deletion
- && (empty($conditions['type']) || array_search('log_filter delete logs', $conditions['type'], TRUE) === FALSE)
- && !variable_get('log_filter_showdeletions', FALSE)
- ) {
- $query->condition('w.' . 'type', 'log_filter delete logs', '!=');
- }
- if ($log_columns === FALSE) {
- return $query->countQuery();
- }
- // Order by.
- // Will always be qualified extra by log id order, to secure correct (sub) order.
- if (!empty($order_by)) {
- $timeAscending = FALSE;
- foreach($order_by as &$orderBy) {
- if (preg_match('/^[a-z\d_]{2,32}$/', $k = $orderBy[0]) && (($v = $orderBy[1]) === 'DESC' || $v === 'ASC')) {
- if ($k == 'time' || $k == 'timestamp') {
- $query->orderBy('w.timestamp', $v);
- if ($v == 'ASC') {
- $timeAscending = TRUE;
- }
- }
- else {
- $query->orderBy('w.' . $k, $v);
- }
- }
- }
- unset($orderBy);
- $query->orderBy('w.wid', !$timeAscending ? 'DESC' : 'ASC');
- }
- else {
- $query->orderBy('timestamp', 'DESC')->orderBy('w.wid', 'DESC');
- }
- if (($offset || $max) && self::DB_SUBQUERY_LIMIT) {
- // A range must have a max length, otherwise it's ignored.
- if (!$max) {
- $max = PHP_INT_MAX - $offset;
- }
- $query->range($offset, $max);
- }
- return $query;
- }
- /**
- * @param array &$conditions
- * @param array $order_by
- * @param integer $offset
- * - minus one: list $max number of items from end of total matching items
- * @param integer $max
- * @param boolean $translate
- * - default: FALSE
- * @return array
- */
- protected static function _listLogs(&$conditions, $order_by, $offset, $max, $translate = FALSE) {
- // Find total matching items.
- $total = (int)self::_logListQuery($conditions, $order_by, 0, 0, FALSE)->execute()->fetchField();
- // Max number of items from end of total matching items?
- if ($offset < 0) {
- $offset = $max ? $total - $max : 0;
- }
- $query = self::_logListQuery($conditions, $order_by, $offset, $max, NULL, array('name'));
- if (($offset || $max) && !self::DB_SUBQUERY_LIMIT) {
- // A range must have a max length, otherwise it's ignored.
- if (!$max) {
- $max = PHP_INT_MAX - $offset;
- }
- $query->range($offset, $max);
- }
- // Work on event buckets.
- if (($rows = $query->execute()->fetchAll())) {
- foreach($rows as &$event) {
- // Translate?
- if (($vars = $event->variables) === 'N;') {
- $event->variables = '';
- }
- elseif (!$translate) { // Do variables replacement frontend.
- $event->variables = unserialize($vars);
- }
- else { // Do translate if any variables
- $event->message = t($event->message, unserialize($vars));
- $event->variables = '';
- }
- // Filter link.
- if ($event->link) {
- $event->link = filter_xss($event->link);
- }
- }
- unset($event); // Clear reference.
- }
- // Tell which conditions were used, but not what their values where - except for the wid (log id) condition.
- $responseConditions = array_fill_keys(array_keys($conditions), true);
- if (!empty($conditions['wid'])) {
- $responseConditions['wid'] = $conditions['wid'];
- }
- return array(
- $rows,
- $responseConditions,
- $offset,
- $total,
- );
- }
- /**
- * Using a LIMIT for delete queryies is kind of hard since db_delete doesnt support LIMIT (because PostgresSQL doesnt support that),
- * and MySQL doesnt support LIMIT for sub queries.
- *
- * And MySQL doesnt support using the same table in a sub query and in an outer CRUD query.
- *
- * 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.
- * Does it in chunks of 40000 items; that should work even when db query max length is only half a megabyte.
- *
- * @param array $conditions
- * @param array $order_by
- * @param integer $offset
- * @param integer $max
- * @return integer
- */
- protected static function _deleteLogs($conditions, $order_by, $offset, $max) {
- global $user;
- // Deleting all logs unconditionally is not an option, because deleting logs of type 'log_filter delete logs' is illegal.
- //if (!$conditions && !$offset && !$max) {
- // $deleted = db_delete('watchdog')
- // ->execute();
- //}
- //else {
- $query = self::_logListQuery($conditions, $order_by, $offset, $max, array('wid'), FALSE, TRUE);
- if (($offset || $max) && !self::DB_SUBQUERY_LIMIT) {
- // A range must have a max length, otherwise it's ignored.
- if (!$max) {
- $max = PHP_INT_MAX - $offset;
- }
- $query->range($offset, $max);
- }
- $ids = $query->execute()->fetchCol();
- if (!($le = count($ids))) {
- return 0;
- }
- // This may fail because the list of ids may exceed query limit (query max length).
- if ($le > 40000) {
- $le = count($ids = array_chunk($ids, 40000));
- $deleted = 0;
- for ($i = 0; $i < $le; $i++) {
- $deleted += db_delete('watchdog')
- ->condition('wid', $ids[$i], 'IN')
- ->execute();
- }
- }
- else {
- $deleted = db_delete('watchdog')
- ->condition('wid', $ids, 'IN')
- ->execute();
- }
- //}
- watchdog(
- 'log_filter delete logs',
- 'User (%uid) %name deleted !deleted log events.',
- array('%uid' => $user->uid, '%name' => $user->name, '!deleted' => $deleted),
- WATCHDOG_NOTICE
- );
- return $deleted;
- }
- /**
- * Access permission: 'access site reports'.
- *
- * All actions require the POST var form_token.
- *
- * Expects (requires) POST vars on actions:
- * - filter_create|filter_edit: name, filter, conditions, order_by
- * - list_logs: conditions, order_by, offset, max, translate
- * - delete_logs: conditions, order_by, offset, max
- *
- * @see LogFilter::ajaxCallback
- * @param string $action
- * @return void
- * - sends 403 header if the expected POST vars arent set or their sanitized values evaluates to empty
- */
- public static function ajaxCallback($action) {
- if ( // Require permission.
- !user_access('access site reports')
- // Require valid composition of the 'action' parameter (presumably a GET var).
- || !$action || !($le = strlen($action)) // Deliberately not drupal_...().
- || $le > 32
- ) {
- header('HTTP/1.1 403 Forbidden');
- exit;
- }
- $action = '' . $action;
- $oResp = new stdClass();
- $oResp->action = check_plain($action); // Redundant; vs. code review.
- $oResp->error = '';
- $oResp->success = TRUE;
- $oResp->error_code = 0;
- try {
- switch ($action) {
- case 'username_autocomplete':
- $oResp = array();
- if (isset($_GET['term']) && strlen($needle = trim($_GET['term'])) && drupal_validate_utf8($needle)) {
- $maxResult = 9;
- //$oResp = array( array('value' => '8', 'label' => 'someuser'), );
- $uids = array();
- $users = db_select('users', 'u')
- ->fields('u', array('uid', 'name'))
- ->condition('name', db_like($needle) . '%', 'LIKE')
- ->orderBy('u.name', 'ASC')
- ->range(0, $maxResult)
- ->execute()
- ->fetchAll();
- if (($le = count($users))) {
- for ($i = 0; $i < $le; ++$i) {
- $oResp[] = array('value' => $uids[] = $users[$i]->uid, 'label' => $users[$i]->name);
- }
- }
- if ($le < $maxResult) {
- $users = db_select('users', 'u')
- ->fields('u', array('uid', 'name'));
- if ($uids) {
- $users = $users->condition('uid', $uids, 'NOT IN');
- }
- $users = $users->condition('name', '%' . db_like($needle) . '%', 'LIKE')
- ->orderBy('u.name', 'ASC')
- ->range(0, $maxResult - $le)
- ->execute()
- ->fetchAll();
- if (($le = count($users))) {
- for ($i = 0; $i < $le; ++$i) {
- $oResp[] = array('value' => $users[$i]->uid, 'label' => $users[$i]->name);
- }
- }
- }
- }
- break;
- case 'filter_create':
- case 'filter_edit':
- $conditions = $order_by = NULL;
- if (
- !array_key_exists('name', $_POST) || !($le = strlen($name = $_POST['name'])) // Deliberately not drupal_...(). And composition is checked later.
- || $le > 32
- || !array_key_exists('filter', $_POST) || !is_array($filter = $_POST['filter']) // It is an array!
- || !array_key_exists('require_admin', $filter) || !(($require_admin = (int)$filter['require_admin']) == 0 || $require_admin == 1)
- || !array_key_exists('description', $filter)
- || ( ($description = $filter['description']) !== '' && (!drupal_validate_utf8($description) || drupal_strlen($description) > 255) )
- // conditions may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
- // _saveFilter() checks/filters conditions buckets vs. xss.
- || (array_key_exists('conditions', $_POST) && ($conditions = $_POST['conditions']) && !is_array($conditions))
- // order_by may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
- // _saveFilter() checks/filters order_by buckets vs. xss.
- || (array_key_exists('order_by', $_POST) && ($order_by = $_POST['order_by']) && !is_array($order_by))
- ) {
- header('HTTP/1.1 403 Forbidden');
- exit;
- }
- if (!user_access('log_filter edit filters')) {
- $oResp->success = FALSE;
- $oResp->error_code = self::$_errorCodes['perm_filter_crud'];
- }
- elseif (!preg_match('/^[a-z_][a-z\d_]+$/', $name) || $name == 'default' || $name == 'adhoc') { // @IDE: var $name is declared.
- $oResp->success = FALSE;
- $oResp->error_code = self::$_errorCodes['filter_name_composition'];
- // $oResp->error = t('Invalid machine name[' . $name . '].'); Frontend creates own message.
- $oResp->name = check_plain($name);
- }
- else {
- $oResp->name = $name = check_plain(strtolower($name)); // Deliberately not drupal_...().
- $oResp->description = !$description ? '' : check_plain(trim(str_replace(array("\r", "\n", "\t"), ' ', $description)));
- self::_saveFilter(
- $name,
- array(
- 'filter' => array(
- 'require_admin' => $require_admin,
- 'description' => $description
- ),
- 'conditions' => $conditions ? $conditions : array(), // _saveFilter() checks/filters conditions buckets vs. xss.
- 'order_by' => $order_by ? $order_by : array(), // _saveFilter() checks/filters order_by buckets vs. xss.
- ),
- $action == 'filter_create'
- );
- }
- break;
- case 'list_logs':
- $conditions = $order_by = NULL;
- if (
- // conditions may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
- // _logListQuery() checks/filters conditions buckets vs. xss.
- (array_key_exists('conditions', $_POST) && ($conditions = $_POST['conditions']) && !is_array($conditions))
- // order_by may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
- // _logListQuery() checks/filters order_by buckets vs. xss.
- || (array_key_exists('order_by', $_POST) && ($order_by = $_POST['order_by']) && !is_array($order_by))
- || !array_key_exists('offset', $_POST) || ($offset = (int)$_POST['offset']) < -1 || $offset > PHP_INT_MAX
- || !array_key_exists('max', $_POST) || ($max = (int)$_POST['max']) < 0 || $max > PHP_INT_MAX
- || !array_key_exists('translate', $_POST)
- ) {
- header('HTTP/1.1 403 Forbidden');
- exit;
- }
- if ($max > self::PAGE_RANGE_MAX) {
- $max = self::PAGE_RANGE_MAX;
- }
- if (!$conditions) {
- $conditions = array();
- }
- $oResp->log_list = self::_listLogs(
- $conditions,
- $order_by ? $order_by : array(),
- $offset,
- $max,
- $translate = (bool)$_POST['translate']
- );
- // Save pager_range and translate to session.
- $session = array(
- 'settings' => array(
- 'pager_range' => $max > 0 ? $max : variable_get('log_filter_pgrng', self::PAGE_RANGE_DEFAULT),
- 'translate' => $translate,
- ),
- );
- if (module_exists('state')) {
- State::sessionSet('module', 'log_filter', $session);
- }
- else {
- drupal_session_start();
- if (!isset($_SESSION['module'])) {
- $_SESSION['module'] = array(
- 'log_filter' => $session,
- );
- }
- else {
- $_SESSION['module']['log_filter'] = $session;
- }
- }
- break;
- case 'delete_logs':
- $conditions = $order_by = NULL;
- if (
- // conditions may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
- // _logListQuery() checks/filters conditions buckets vs. xss.
- (array_key_exists('conditions', $_POST) && ($conditions = $_POST['conditions']) && !is_array($conditions))
- // order_by may not exist, because jQuery.ajax() >v1.4.4 doesn't POST empty object.
- // _logListQuery() checks/filters order_by buckets vs. xss.
- || (array_key_exists('order_by', $_POST) && ($order_by = $_POST['order_by']) && !is_array($order_by))
- || !array_key_exists('offset', $_POST) || ($offset = (int)$_POST['offset']) < -1 || $offset > PHP_INT_MAX
- || !array_key_exists('max', $_POST) || ($max = (int)$_POST['max']) < 0 || $max > PHP_INT_MAX
- ) {
- header('HTTP/1.1 403 Forbidden');
- exit;
- }
- $oResp->delete_logs = self::_deleteLogs(
- $conditions ? $conditions : array(),
- $order_by ? $order_by : array(),
- $offset,
- $max
- );
- break;
- default:
- $oResp->success = FALSE;
- $oResp->error_code = 1;
- $oResp->error = 'Unsupported action[' . $action . '].';
- }
- }
- catch (PDOException $xc) {
- self::_errorHandler($xc);
- $oResp->success = FALSE;
- $oResp->error_code = self::$_errorCodes['db_general'];
- }
- catch (Exception $xc) {
- self::_errorHandler($xc);
- $oResp->success = FALSE;
- if (($error_code = $xc->getCode()) && in_array($error_code, self::$_errorCodes)) {
- $oResp->error_code = $error_code;
- }
- else {
- $oResp->error_code = self::$_errorCodes['unknown'];
- }
- }
- header('Content-Type: application/json; charset=utf-8');
- header('Cache-Control: private, no-store, no-cache, must-revalidate');
- header('Expires: Thu, 01 Jan 1970 00:00:01 GMT');
- echo drupal_json_encode($oResp);
- flush();
- exit;
- }
- }
|