2 // $Id: statistics.module 144 2007-03-28 07:52:20Z thierry $
6 * Logs access statistics for your site.
10 * Implementation of hook_help().
12 function statistics_help($section) {
14 case 'admin/help#statistics':
15 $output = '<p>'. t('The statistics module keeps track of numerous statistics of site usage. It counts how many times, and from where each of your posts is viewed. The statistics module can be used to learn many useful things about how users are interacting with each other and with your site.') .'</p>';
16 $output .= t('<p>Statistics module features</p>
18 <li>Logs show statistics for how many times your site and specific content on your site has been accessed.</li>
19 <li>Referrers tells you from where visitors came from (referrer URL).</li>
20 <li>Top pages shows you what\'s hot, what is the most popular content on your site.</li>
21 <li>Top users shows you the most active users for your site.</li>
22 <li>Recent hits displays information about the latest activity on your site.</li>
23 <li>Node count displays the number of times a node has been accessed in the node\'s link section next to <em># comments</em>.</li>
24 <li>Popular content block creates a block that can display the day\'s top viewed content, the all time top viewed content, and the last content viewed.</li>
27 $output .= t('<p>Configuring the statistics module</p>
29 <li>Enable access log allows you to turn the access log on and off. This log is used to store data about every page accessed, such as the remote host\'s IP address, where they came from (referrer), what node they\'ve viewed, and their user name. Enabling the log adds one database call per page displayed by Drupal.</li>
30 <li>Discard access logs older than allows you to configure how long an access log entry is saved, after which time it is deleted from the database table. To use this you need to run <em>cron.php</em></li>
31 <li>Enable node view counter allows you to turn on and off the node-counting functionality of this module. If it is turned on, an extra database query is added for each node displayed, which increments a counter.</li>
34 $output .= t('<p>You can</p>
36 <li>administer statistics <a href="%admin-settings-statistics">administer >> settings >> statistics</a>.</li>
37 <li>access statistics logs <a href="%admin-logs">administer >> logs</a>.</li>
38 <li>view recent hits <a href="%admin-logs-hits">administer >> logs >> recent hits</a>.</li>
39 <li>enable \'popular content\' block in block administration <a href="%admin-block">administer >> blocks </a> but only after you have enabled \'Count content views\' in settings.</li>
41 ', array('%admin-settings-statistics' => url('admin/settings/statistics'), '%admin-logs' => url('admin/logs'), '%admin-logs-hits' => url('admin/logs/hits'), '%admin-block' => url('admin/block')));
42 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%statistics">Statistics page</a>.', array('%statistics' => 'http://drupal.org/handbook/modules/statistics/')) .'</p>';
44 case 'admin/modules#description':
45 return t('Logs access statistics for your site.');
46 case 'admin/settings/statistics':
47 return t('<p>Settings for the statistical information that Drupal will keep about the site. See <a href="%statistics">site statistics</a> for the actual information.</p>', array('%statistics' => url('admin/logs/hits')));
48 case 'admin/logs/hits':
49 return t('<p>This page shows you the most recent hits.</p>');
50 case 'admin/logs/referrers':
51 return t('<p>This page shows you all external referrers. These are links pointing to your web site from outside your web site.</p>');
52 case 'admin/logs/visitors':
53 return t('<p>When you ban a visitor, you prevent his IP address from accessing your site. Unlike blocking a user, banning a visitor works even for anonymous users. The most common use for this is to block bots/web crawlers that are consuming too many resources.</p>');
58 * Implementation of hook_exit().
60 * This is where statistics are gathered on page accesses.
62 function statistics_exit() {
63 global $user, $recent_activity;
65 drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
67 if (variable_get('statistics_count_content_views', 0)) {
68 // We are counting content views.
69 if ((arg(0) == 'node') && is_numeric(arg(1)) && arg(2) == '') {
70 // A node has been viewed, so update the node's counters.
71 db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1));
72 // If we affected 0 rows, this is the first time viewing the node.
73 if (!db_affected_rows()) {
74 // We must create a new row to store counters for the new node.
75 db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', arg(1), time());
79 if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) {
80 // Log this page access.
81 db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), $_GET['q'], referer_uri(), $_SERVER['REMOTE_ADDR'], $user->uid, session_id(), timer_read('page'), time());
86 * Implementation of hook_perm().
88 function statistics_perm() {
89 return array('access statistics', 'view post access counter');
93 * Implementation of hook_link().
95 function statistics_link($type, $node = 0, $main = 0) {
99 if ($type != 'comment' && user_access('view post access counter')) {
100 $statistics = statistics_get($node->nid);
102 $links[] = format_plural($statistics['totalcount'], '1 read', '%count reads');
110 * Implementation of hook_menu().
112 function statistics_menu($may_cache) {
115 $access = user_access('access statistics');
117 $items[] = array('path' => 'admin/logs/hits', 'title' => t('recent hits'),
118 'callback' => 'statistics_recent_hits', 'access' => $access,
120 $items[] = array('path' => 'admin/logs/pages', 'title' => t('top pages'),
121 'callback' => 'statistics_top_pages', 'access' => $access,
123 $items[] = array('path' => 'admin/logs/visitors', 'title' => t('top visitors'),
124 'callback' => 'statistics_top_visitors', 'access' => $access,
126 $items[] = array('path' => 'admin/logs/referrers', 'title' => t('referrers'),
127 'callback' => 'statistics_top_referrers', 'access' => $access);
128 $items[] = array('path' => 'admin/logs/access', 'title' => t('details'),
129 'callback' => 'statistics_access_log', 'access' => $access,
130 'type' => MENU_CALLBACK);
133 if (arg(0) == 'user' && is_numeric(arg(1)) && variable_get('statistics_enable_access_log', 0)) {
134 $items[] = array('path' => 'user/'. arg(1) .'/track/navigation', 'title' => t('track page visits'),
135 'callback' => 'statistics_user_tracker', 'access' => $access,
136 'type' => MENU_LOCAL_TASK, 'weight' => 2);
138 if (arg(0) == 'node' && is_numeric(arg(1)) && variable_get('statistics_enable_access_log', 0)) {
139 $items[] = array('path' => 'node/'. arg(1) .'/track', 'title' => t('track'),
140 'callback' => 'statistics_node_tracker', 'access' => $access,
141 'type' => MENU_LOCAL_TASK, 'weight' => 2);
149 * Implementation of hook_user().
151 function statistics_user($op, &$edit, &$user) {
152 if ($op == 'delete') {
153 db_query('UPDATE {accesslog} SET uid = 0 WHERE uid = %d', $user->uid);
157 function statistics_access_log($aid) {
158 $result = db_query('SELECT a.*, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE aid = %d', $aid);
159 if ($access = db_fetch_object($result)) {
160 $output = '<table border="1" cellpadding="2" cellspacing="2">';
161 $output .= ' <tr><th>'. t('URL') ."</th><td>". l(url($access->path, NULL, NULL, TRUE), $access->path) ."</td></tr>";
162 $output .= ' <tr><th>'. t('Title') .'</th><td>'. $access->title .'</td></tr>'; // safe because it comes from drupal_get_title()
163 $output .= ' <tr><th>'. t('Referrer') ."</th><td>". ($access->url ? l($access->url, $access->url) : '') ."</td></tr>";
164 $output .= ' <tr><th>'. t('Date') .'</th><td>'. format_date($access->timestamp, 'large') .'</td></tr>';
165 $output .= ' <tr><th>'. t('User') .'</th><td>'. theme('username', $access) .'</td></tr>';
166 $output .= ' <tr><th>'. t('Hostname') .'</th><td>'. check_plain($access->hostname) .'</td></tr>';
167 $output .= '</table>';
175 function statistics_node_tracker() {
176 if ($node = node_load(arg(1))) {
179 array('data' => t('Time'), 'field' => 'a.timestamp', 'sort' => 'desc'),
180 array('data' => t('Referrer'), 'field' => 'a.url'),
181 array('data' => t('User'), 'field' => 'u.name'),
182 array('data' => t('Operations')));
184 $result = pager_query('SELECT a.aid, a.timestamp, a.url, a.uid, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE a.path LIKE \'node/%d%%\'' . tablesort_sql($header), 30, 0, NULL, $node->nid);
185 while ($log = db_fetch_object($result)) {
187 array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
188 _statistics_link($log->url),
189 theme('username', $log),
190 l(t('details'), "admin/logs/access/$log->aid"));
193 drupal_set_title(check_plain($node->title));
194 $output = theme('table', $header, $rows);
195 $output .= theme('pager', NULL, 30, 0);
203 function statistics_user_tracker() {
204 if ($account = user_load(array('uid' => arg(1)))) {
207 array('data' => t('Timestamp'), 'field' => 'timestamp', 'sort' => 'desc'),
208 array('data' => t('Page'), 'field' => 'path'),
209 array('data' => t('Operations')));
211 $result = pager_query('SELECT aid, timestamp, path, title FROM {accesslog} WHERE uid = %d' . tablesort_sql($header), 30, 0, NULL, $account->uid);
212 while ($log = db_fetch_object($result)) {
214 array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
215 _statistics_format_item($log->title, $log->path),
216 l(t('details'), "admin/logs/access/$log->aid"));
219 drupal_set_title($account->name);
220 $output = theme('table', $header, $rows);
221 $output .= theme('pager', NULL, 30, 0);
230 * Menu callback; presents the "recent hits" page.
232 function statistics_recent_hits() {
234 array('data' => t('Timestamp'), 'field' => 'a.timestamp', 'sort' => 'desc'),
235 array('data' => t('Page'), 'field' => 'a.path'),
236 array('data' => t('User'), 'field' => 'u.name'),
237 array('data' => t('Operations'))
240 $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);
242 $result = pager_query($sql, 30);
243 while ($log = db_fetch_object($result)) {
245 array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
246 _statistics_format_item($log->title, $log->path),
247 theme('username', $log),
248 l(t('details'), "admin/logs/access/$log->aid"));
251 $output = theme('table', $header, $rows);
252 $output .= theme('pager', NULL, 30, 0);
257 * Menu callback; presents the "top pages" page.
259 function statistics_top_pages() {
260 $sql = "SELECT COUNT(path) AS hits, path, title, AVG(timer) AS average_time, SUM(timer) AS total_time FROM {accesslog} GROUP BY path, title";
261 $sql_cnt = "SELECT COUNT(DISTINCT(path)) FROM {accesslog}";
264 array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
265 array('data' => t('Page'), 'field' => 'path'),
266 array('data' => t('Average page generation time'), 'field' => 'average_time'),
267 array('data' => t('Total page generation time'), 'field' => 'total_time')
269 $sql .= tablesort_sql($header);
270 $result = pager_query($sql, 30, 0, $sql_cnt);
272 while ($page = db_fetch_object($result)) {
273 $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)));
276 drupal_set_title(t('Top pages in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
277 $output = theme('table', $header, $rows);
278 $output .= theme('pager', NULL, 30, 0);
283 * Menu callback; presents the "top visitors" page.
285 function statistics_top_visitors() {
288 array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
289 array('data' => t('Visitor'), 'field' => 'u.name'),
290 array('data' => t('Total page generation time'), 'field' => 'total'),
291 array('data' => t('Operations'))
294 $sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname, a.uid, u.name, ac.aid". tablesort_sql($header);
295 $sql_cnt = "SELECT COUNT(DISTINCT(uid)) FROM {accesslog}";
296 $result = pager_query($sql, 30, 0, $sql_cnt);
298 while ($account = db_fetch_object($result)) {
299 $qs = drupal_get_destination();
300 $ban_link = $account->aid ? l(t('unban'), "admin/access/rules/delete/$account->aid", array(), $qs) : l(t('ban'), "admin/access/rules/add/$account->hostname/host", array(), $qs);
301 $rows[] = array($account->hits, ($account->uid ? theme('username', $account) : $account->hostname), format_interval(round($account->total / 1000)), $ban_link);
304 drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
305 $output = theme('table', $header, $rows);
306 $output .= theme('pager', NULL, 30, 0);
311 * Menu callback; presents the "referrer" page.
313 function statistics_top_referrers() {
314 $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE '%%%s%%' AND url <> '' GROUP BY url";
315 $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE '%%%s%%'";
316 drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
319 array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
320 array('data' => t('Url'), 'field' => 'url'),
321 array('data' => t('Last visit'), 'field' => 'last'),
324 $query .= tablesort_sql($header);
325 $result = pager_query($query, 30, 0, $query_cnt, $_SERVER['HTTP_HOST']);
327 while ($referrer = db_fetch_object($result)) {
328 $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('%time ago', array('%time' => format_interval(time() - $referrer->last))));
331 $output = theme('table', $header, $rows);
332 $output .= theme('pager', NULL, 30, 0);
337 * Implementation of hook_settings().
339 function statistics_settings() {
340 // access log settings:
341 $options = array('1' => t('Enabled'), '0' => t('Disabled'));
342 $form['access'] = array('#type' => 'fieldset', '#title' => t('Access log settings'));
343 $form['access']['statistics_enable_access_log'] = array('#type' => 'radios', '#title' => t('Enable access log'), '#default_value' => variable_get('statistics_enable_access_log', 0), '#options' => $options, '#description' => t('Log each page access. Required for referrer statistics.'));
344 $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
345 $form['access']['statistics_flush_accesslog_timer'] = array('#type' => 'select', '#title' => t('Discard access logs older than'), '#default_value' => variable_get('statistics_flush_accesslog_timer', 259200), '#options' => $period, '#description' => t('Older access log entries (including referrer statistics) will be automatically discarded. Requires crontab.'));
347 // count content views settings
348 $form['content'] = array('#type' => 'fieldset', '#title' => t('Content viewing counter settings'));
349 $form['content']['statistics_count_content_views'] = array('#type' => 'radios', '#title' => t('Count content views'), '#default_value' => variable_get('statistics_count_content_views', 0), '#options' => $options, '#description' => t('Increment a counter each time content is viewed.'));
355 * Implementation of hook_cron().
357 function statistics_cron() {
358 $statistics_timestamp = variable_get('statistics_day_timestamp', '');
360 if ((time() - $statistics_timestamp) >= 86400) {
361 /* reset day counts */
362 db_query('UPDATE {node_counter} SET daycount = 0');
363 variable_set('statistics_day_timestamp', time());
366 /* clean expired access logs */
367 db_query('DELETE FROM {accesslog} WHERE timestamp < %d', time() - variable_get('statistics_flush_accesslog_timer', 259200));
371 * Returns all time or today top or last viewed node(s).
375 * - 'totalcount': top viewed content of all time.
376 * - 'daycount': top viewed content for today.
377 * - 'timestamp': last viewed node.
380 * number of rows to be returned.
383 * A query result containing n.nid, n.title, u.uid, u.name of the selected node(s)
384 * or FALSE if the query could not be executed correctly.
386 function statistics_title_list($dbfield, $dbrows) {
387 return db_query_range(db_rewrite_sql("SELECT n.nid, n.title, u.uid, u.name FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE %s <> '0' AND n.status = 1 ORDER BY %s DESC"), 's.'. $dbfield, 's.'. $dbfield, 0, $dbrows);
392 * Retrieves a node's "view statistics".
398 * An array with three entries: [0]=totalcount, [1]=daycount, [2]=timestamp
399 * - totalcount: count of the total number of times that node has been viewed.
400 * - daycount: count of the total number of times that node has been viewed "today".
401 * For the daycount to be reset, cron must be enabled.
402 * - timestamp: timestamp of when that node was last viewed.
404 function statistics_get($nid) {
407 /* retrieves an array with both totalcount and daycount */
408 $statistics = db_fetch_array(db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = %d', $nid));
415 * Implementation of hook_block().
417 function statistics_block($op = 'list', $delta = 0, $edit = array()) {
420 if (variable_get('statistics_count_content_views', 0)) {
421 $blocks[0]['info'] = t('Popular content');
426 // Popular content block settings
427 $numbers = array('0' => t('Disabled')) + drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40));
428 $form['statistics_block_top_day_num'] = array('#type' => 'select', '#title' => t("Number of day's top views to display"), '#default_value' => variable_get('statistics_block_top_day_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "day" list.'));
429 $form['statistics_block_top_all_num'] = array('#type' => 'select', '#title' => t('Number of all time views to display'), '#default_value' => variable_get('statistics_block_top_all_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "all time" list.'));
430 $form['statistics_block_top_last_num'] = array('#type' => 'select', '#title' => t('Number of most recent views to display'), '#default_value' => variable_get('statistics_block_top_last_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "recently viewed" list.'));
434 variable_set('statistics_block_top_day_num', $edit['statistics_block_top_day_num']);
435 variable_set('statistics_block_top_all_num', $edit['statistics_block_top_all_num']);
436 variable_set('statistics_block_top_last_num', $edit['statistics_block_top_last_num']);
440 if (user_access('access content')) {
443 $daytop = variable_get('statistics_block_top_day_num', 0);
444 if ($daytop && ($result = statistics_title_list('daycount', $daytop)) && db_num_rows($result)) {
445 $content[] = node_title_list($result, t("Today's:"));
448 $alltimetop = variable_get('statistics_block_top_all_num', 0);
449 if ($alltimetop && ($result = statistics_title_list('totalcount', $alltimetop)) && db_num_rows($result)) {
450 $content[] = node_title_list($result, t('All time:'));
453 $lasttop = variable_get('statistics_block_top_last_num', 0);
454 if ($lasttop && ($result = statistics_title_list('timestamp', $lasttop)) && db_num_rows($result)) {
455 $content[] = node_title_list($result, t('Last viewed:'));
458 if (count($content)) {
459 $block['content'] = implode('<br />', $content);
460 $block['subject'] = t('Popular content');
468 * It is possible to adjust the width of columns generated by the
471 function _statistics_link($path, $width = 35) {
472 $title = drupal_get_path_alias($path);
473 $title = truncate_utf8($title, $width, FALSE, TRUE);
474 return l($title, $path);
477 function _statistics_format_item($title, $path) {
478 $path = ($path ? $path : '/');
479 $output = ($title ? "$title<br />" : '');
480 $output .= _statistics_link($path);
485 * Implementation of hook_nodeapi().
487 function statistics_nodeapi(&$node, $op, $arg = 0) {
490 // clean up statistics table when node is deleted
491 db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid);