Index: modules/statistics/statistics.module =================================================================== RCS file: /cvs/drupal/drupal/modules/statistics/statistics.module,v retrieving revision 1.299 diff -u -r1.299 statistics.module --- modules/statistics/statistics.module 8 Mar 2009 04:25:06 -0000 1.299 +++ modules/statistics/statistics.module 3 Apr 2009 21:18:03 -0000 @@ -38,6 +38,22 @@ } /** + * Implementation of hook_theme(). + */ +function statistics_theme() { + return array( + 'statistics_filter_form' => array( + 'arguments' => array('form' => NULL), + 'file' => 'statistics.admin.inc', + ), + 'statistics_filters' => array( + 'arguments' => array('form' => NULL), + 'file' => 'statistics.admin.inc', + ), + ); +} + +/** * Implementation of hook_exit(). * * This is where statistics are gathered on page accesses. @@ -324,6 +340,109 @@ } /** + * List statistics filters that can be applied. + */ +function statistics_filters() { + // Regular filters + $filters = array(); + + $filters['hits'] = array( + '#title' => t('recent hits'), + '#filters' => array( + 'page' => array( + 'title' => t('page'), + 'where' => 'a.path NOT LIKE \'%s\'', + 'join' => '', + ), + 'uid' => array( + 'title' => t('userid'), + 'where' => 'a.uid != %d', + 'join' => '', + ), + 'user' => array( + 'title' => t('username'), + 'where' => 'u.name NOT LIKE \'%s\'', + 'join' => '', + ), + ), + ); + $filters['pages'] = array( + '#title' => t('top pages'), + '#filters' => array( + 'path' => array( + 'title' => t('path'), + 'where' => 'path NOT LIKE \'%s\'', + 'join' => '', + ), + 'title' => array( + 'title' => t('title'), + 'where' => 'title NOT LIKE \'%s\'', + 'join' => '', + ), + ), + ); + $filters['referrers'] = array( + '#title' => t('top referrers'), + '#filters' => array( + 'url' => array( + 'title' => t('url'), + 'where' => 'url NOT LIKE \'%s\'', + 'join' => '', + ), + ), + ); + $filters['visitors'] = array( + '#title' => t('top visitors'), + '#filters' => array( + 'path' => array( + 'title' => t('username'), + 'where' => 'u.name NOT LIKE \'%s\'', + 'join' => '', + ), + 'hostname' => array( + 'title' => t('hostname'), + 'where' => 'a.hostname NOT LIKE \'%s\'', + 'join' => '', + ), + ), + ); + + return $filters; +} + +/** + * Build query for statistic filters based on session and statistic type. + * + * @param $type + * Type of filter. + */ +function statistics_build_filter_query($type) { + $filters = statistics_filters(); + // get type specific filters. + $filters = $filters[$type]['#filters']; + + if (!isset($_SESSION['statistics_filter'][$type])) { + return array('where' => '', 'join' => '', 'args' => array()); + } + + // Build query. + $where = $args = $join = array(); + foreach ($_SESSION['statistics_filter'][$type] as $filter) { + list($key, $value) = $filter; + $where[] = $filters[$key]['where']; + $join[] = $filters[$key]['join']; + $args[] = $value; + } + $where = !empty($where) ? ' AND ' . implode(' AND ', $where) : ''; + $join = !empty($join) ? ' ' . implode(' ', array_unique($join)) : ''; + + return array('where' => $where, + 'join' => $join, + 'args' => $args, + ); +} + +/** * It is possible to adjust the width of columns generated by the * statistics module. */ Index: modules/statistics/statistics.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/statistics/statistics.admin.inc,v retrieving revision 1.20 diff -u -r1.20 statistics.admin.inc --- modules/statistics/statistics.admin.inc 1 Apr 2009 20:00:47 -0000 1.20 +++ modules/statistics/statistics.admin.inc 3 Apr 2009 21:18:02 -0000 @@ -10,6 +10,9 @@ * Menu callback; presents the "recent hits" page. */ function statistics_recent_hits() { + $filtertype = 'hits'; + $filter = statistics_build_filter_query($filtertype); + $header = array( array('data' => t('Timestamp'), 'field' => 'a.timestamp', 'sort' => 'desc'), array('data' => t('Page'), 'field' => 'a.path'), @@ -17,10 +20,13 @@ array('data' => t('Operations')) ); - $sql = 'SELECT a.aid, a.path, a.title, a.uid, u.name, a.timestamp FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid' . tablesort_sql($header); + $sql = 'SELECT a.aid, a.path, a.title, a.uid, u.name, a.timestamp FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid' . $filter['join'] . ' WHERE 1 ' . $filter['where']; + $sql .= tablesort_sql($header); + $query_count = 'SELECT COUNT(DISTINCT a.aid) FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid' . $filter['join'] . ' WHERE 1 ' . $filter['where']; + $result = pager_query($sql, 30, 0, $query_count, $filter['args']); - $result = pager_query($sql, 30); $rows = array(); + while ($log = db_fetch_object($result)) { $rows[] = array( array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'), @@ -33,7 +39,8 @@ $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4)); } - $output = theme('table', $header, $rows); + $output = drupal_get_form('statistics_filter_form', 'hits'); + $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } @@ -42,19 +49,22 @@ * Menu callback; presents the "top pages" page. */ function statistics_top_pages() { - // MAX(title) avoids having empty node titles which otherwise causes duplicates in the top pages list - $sql = "SELECT COUNT(path) AS hits, path, MAX(title) AS title, AVG(timer) AS average_time, SUM(timer) AS total_time FROM {accesslog} GROUP BY path"; - $sql_cnt = "SELECT COUNT(DISTINCT(path)) FROM {accesslog}"; - + $filtertype = 'pages'; + $filter = statistics_build_filter_query($filtertype); + $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Page'), 'field' => 'path'), array('data' => t('Average page generation time'), 'field' => 'average_time'), array('data' => t('Total page generation time'), 'field' => 'total_time') ); - $sql .= tablesort_sql($header); - $result = pager_query($sql, 30, 0, $sql_cnt); + // MAX(title) avoids having empty node titles which otherwise causes duplicates in the top pages list + $sql = 'SELECT COUNT(path) AS hits, path, MAX(title) AS title, AVG(timer) AS average_time, SUM(timer) AS total_time FROM {accesslog}' . $filter['join'] . ' WHERE 1 ' . $filter['where'] . ' GROUP BY path'; + $sql .= tablesort_sql($header); + $sql_cnt = 'SELECT COUNT(DISTINCT(path)) FROM {accesslog}' . $filter['join'] . ' WHERE 1 ' . $filter['where']; + $result = pager_query($sql, 30, 0, $sql_cnt, $filter['args']); + $rows = array(); while ($page = db_fetch_object($result)) { $rows[] = array($page->hits, _statistics_format_item($page->title, $page->path), t('%time ms', array('%time' => round($page->average_time))), format_interval(round($page->total_time / 1000))); @@ -65,7 +75,8 @@ } drupal_set_title(t('Top pages in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))), PASS_THROUGH); - $output = theme('table', $header, $rows); + $output = drupal_get_form('statistics_filter_form', $filtertype); + $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } @@ -74,7 +85,9 @@ * Menu callback; presents the "top visitors" page. */ function statistics_top_visitors() { - + $filtertype = 'visitors'; + $filter = statistics_build_filter_query($filtertype); + $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Visitor'), 'field' => 'u.name'), @@ -82,10 +95,12 @@ array('data' => user_access('block IP addresses') ? t('Operations') : '', 'colspan' => 2), ); - $sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, bl.iid FROM {accesslog} a LEFT JOIN {blocked_ips} bl ON a.hostname = bl.ip LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname, a.uid, u.name, bl.iid" . tablesort_sql($header); - $sql_cnt = "SELECT COUNT(DISTINCT(CONCAT(CAST(uid AS char), hostname))) FROM {accesslog}"; - $result = pager_query($sql, 30, 0, $sql_cnt); - + $sql = 'SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, bl.iid FROM {accesslog} a LEFT JOIN {blocked_ips} bl ON a.hostname = bl.ip LEFT JOIN {users} u ON a.uid = u.uid'; + $sql .= $filter['join'] . ' WHERE 1 ' . $filter['where'] . 'GROUP BY a.hostname, a.uid, u.name, bl.iid'; + $sql .= tablesort_sql($header); + $sql_cnt = 'SELECT COUNT(DISTINCT(CONCAT(CAST(a.uid AS char), hostname))) FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid' . $filter['join'] . ' WHERE 1 ' . $filter['where']; + $result = pager_query($sql, 30, 0, $sql_cnt, $filter['args']); + $rows = array(); while ($account = db_fetch_object($result)) { $qs = drupal_get_destination(); @@ -98,7 +113,8 @@ } drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))), PASS_THROUGH); - $output = theme('table', $header, $rows); + $output = drupal_get_form('statistics_filter_form', $filtertype); + $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } @@ -107,19 +123,22 @@ * Menu callback; presents the "referrer" page. */ function statistics_top_referrers() { - $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE LOWER(url) NOT LIKE :host AND url <> '' GROUP BY url"; - $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND LOWER(url) NOT LIKE :host"; - drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))), PASS_THROUGH); - + $filtertype = 'referrers'; + $filter = statistics_build_filter_query($filtertype); + $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Url'), 'field' => 'url'), array('data' => t('Last visit'), 'field' => 'last'), ); - $query .= tablesort_sql($header); - $result = pager_query($query, 30, 0, $query_cnt, array(':host' => '%'. $_SERVER['HTTP_HOST'] .'%')); + $query = 'SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog}' . $filter['join'] . " WHERE url NOT LIKE '%s' AND url <> ''" . $filter['where'] . ' GROUP BY url'; + $query_cnt = 'SELECT COUNT(DISTINCT(url)) FROM {accesslog}' . $filter['join'] . " WHERE url <> '' AND url NOT LIKE '%s' " . $filter['where']; + drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))), PASS_THROUGH); + $query .= tablesort_sql($header); + $result = pager_query($query, 30, 0, $query_cnt, array_merge(array('%'. $_SERVER['HTTP_HOST'] .'%'), $filter['args'])); + $rows = array(); while ($referrer = db_fetch_object($result)) { $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval(REQUEST_TIME - $referrer->last)))); @@ -129,7 +148,8 @@ $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3)); } - $output = theme('table', $header, $rows); + $output = drupal_get_form('statistics_filter_form', $filtertype); + $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } @@ -175,6 +195,111 @@ } /** + * Form builder; Return form for logging filters. + * + * @param $type Type of logging. + * + * @ingroup forms + * @see statistics_filter_form_submit() + */ +function statistics_filter_form($form_state, $type) { + $session = isset($_SESSION['statistics_filter']) ? $_SESSION['statistics_filter'] : array(); + $session = (is_array($session) && isset($session[$type]) ) ? $session[$type] : array(); + $filters = statistics_filters(); + + $i = 0; + $form['filters'] = array( + '#type' => 'fieldset', + '#title' => t('Hide !type where', array('!type' => $filters[$type]['#title'])), + '#theme' => 'statistics_filters', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + $form['filters']['type'] = array( + '#type' => 'value', + '#value' => $type, + ); + + // get only type specific filters. + $filters = $filters[$type]['#filters']; + + foreach ($session as $filter) { + list($filtertype, $filtervalue) = $filter; + $params = array('%property' => $filters[$filtertype]['title'] , '%value' => $filtervalue); + if ($i++ > 0) { + $form['filters']['current'][] = array('#markup' => t('and where %property is %value', $params)); + } + else { + $form['filters']['current'][] = array('#markup' => t('%property is %value', $params)); + } + } + + foreach ($filters as $key => $filter) { + $names[$key] = $filter['title']; + $form['filters']['status'][$key] = array( + '#type' => 'textfield', + '#size' => 50, + ); + } + + $form['filters']['filter'] = array( + '#type' => 'radios', + '#options' => $names, + ); + + $form['filters']['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => (count($session) ? t('Refine') : t('Filter')), + ); + if (count($session)) { + $form['filters']['buttons']['undo'] = array( + '#type' => 'submit', + '#value' => t('Undo'), + ); + $form['filters']['buttons']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + ); + } + + drupal_add_js('misc/form.js'); + + return $form; +} + +/** + * Process result from statistics filter form. + */ +function statistics_filter_form_submit($form, &$form_state) { + $op = $form_state['values']['op']; + $type = $form_state['values']['type']; + $filters = statistics_filters(); + // get only type specific filters. + $filters = $filters[$type]['#filters']; + switch ($op) { + case t('Filter'): + case t('Refine'): + if (isset($form_state['values']['filter'])) { + $filter = $form_state['values']['filter']; + if (isset($form_state['values'][$filter])) { + $_SESSION['statistics_filter'][$type][] = array($filter, $form_state['values'][$filter]); + } + } + break; + case t('Undo'): + array_pop($_SESSION['statistics_filter'][$type]); + break; + case t('Reset'): + $_SESSION['statistics_filter'][$type] = array(); + break; + } + + $form_state['redirect'] = 'admin/reports/' . $type; + return; +} + +/** * Form builder; Configure access logging. * * @ingroup forms @@ -213,3 +338,48 @@ return system_settings_form($form, TRUE); } + +/** + * Theme user administration filter form. + * + * @ingroup themeable + */ +function theme_statistics_filter_form(&$form) { + $output = '