]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/AccessLog.php
rcs_id no longer makes sense with Subversion global version number
[SourceForge/phpwiki.git] / lib / AccessLog.php
1 <?php
2 // rcs_id('$Id$');
3 /*
4  * Copyright 2005, 2007 Reini Urban
5  *
6  * This file is part of PhpWiki.
7  *
8  * PhpWiki is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * PhpWiki is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with PhpWiki; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21  */
22
23 /**
24  * Read and write file and SQL accesslog. Write sequentially.
25  *
26  * Read from file per pagename: Hits
27  *
28  */
29
30 /**
31  * Create NCSA "combined" log entry for current request.
32  * Also needed for advanced spam prevention.
33  * global object holding global state (sql or file, entries, to dump)
34  */
35 class Request_AccessLog {
36     /**
37      * @param $logfile string  Log file name.
38      */
39     function Request_AccessLog ($logfile, $do_sql = false) {
40         //global $request; // request not yet initialized!
41
42         $this->logfile = $logfile;
43         if ($logfile and !is_writeable($logfile)) {
44             trigger_error
45                 (sprintf(_("%s is not writable."), _("The PhpWiki access log file"))
46                  . "\n"
47                  . sprintf(_("Please ensure that %s is writable, or redefine %s in config/config.ini."),
48                            sprintf(_("the file '%s'"), ACCESS_LOG),
49                            'ACCESS_LOG')
50                  , E_USER_NOTICE);
51         }
52         //$request->_accesslog =& $this;
53         //if (empty($request->_accesslog->entries))
54         register_shutdown_function("Request_AccessLogEntry_shutdown_function");
55         
56         if ($do_sql) {
57             if (!$request->_dbi->isSQL()) {
58                 trigger_error("Unsupported database backend for ACCESS_LOG_SQL.\nNeed DATABASE_TYPE=SQL or ADODB or PDO");
59             } else {
60                 global $DBParams;
61                 //$this->_dbi =& $request->_dbi;
62                 $this->logtable = (!empty($DBParams['prefix']) ? $DBParams['prefix'] : '')."accesslog";
63             }
64         }
65         $this->entries = array();
66         $this->entries[] = new Request_AccessLogEntry($this);
67     }
68
69     function _do($cmd, &$arg) {
70         if ($this->entries)
71             for ($i=0; $i < count($this->entries);$i++)
72                 $this->entries[$i]->$cmd($arg);
73     }
74     function push(&$request)   { $this->_do('push',$request); }
75     function setSize($arg)     { $this->_do('setSize',$arg); }
76     function setStatus($arg)   { $this->_do('setStatus',$arg); }
77     function setDuration($arg) { $this->_do('setDuration',$arg); }
78
79     /**
80      * Read sequentially all previous entries from the beginning.
81      * while ($logentry = Request_AccessLogEntry::read()) ;
82      * For internal log analyzers: RecentReferrers, WikiAccessRestrictions
83      */
84     function read() {
85         return $this->logtable ? $this->read_sql() : $this->read_file();
86     }
87
88     /**
89      * Return iterator of referer items reverse sorted (latest first).
90      */
91     function get_referer($limit=15, $external_only=false) {
92         if ($external_only) { // see stdlin.php:isExternalReferrer()
93             $base = SERVER_URL;
94             $blen = strlen($base);
95         }
96         if (!empty($this->_dbi)) {
97             // check same hosts in referer and request and remove them
98             $ext_where = " AND LEFT(referer,$blen) <> ".$this->_dbi->quote($base)
99                 ." AND LEFT(referer,$blen) <> LEFT(CONCAT(".$this->_dbi->quote(SERVER_URL).",request_uri),$blen)";
100             return $this->_read_sql_query("(referer <>'' AND NOT(ISNULL(referer)))"
101                                           .($external_only ? $ext_where : '')
102                                           ." ORDER BY time_stamp DESC"
103                                           .($limit ? " LIMIT $limit" : ""));
104         } else {
105             $iter = new WikiDB_Array_generic_iter(0);
106             $logs =& $iter->_array;
107             while ($logentry = $this->read_file()) {
108                 if (!empty($logentry->referer)
109                     and (!$external_only or (substr($logentry->referer,0,$blen) != $base)))
110                 {
111                     $iter->_array[] = $logentry;
112                     if ($limit and count($logs) > $limit)
113                         array_shift($logs);
114                 }
115             }
116             $logs = array_reverse($logs);
117             $logs = array_slice($logs,0,min($limit,count($logs)));
118             return $iter;
119         }
120     }
121
122     /**
123      * Return iterator of matching host items reverse sorted (latest first).
124      */
125     function get_host($host, $since_minutes=20) {
126         if ($this->logtable) {
127             // mysql specific only:
128             return $this->read_sql("request_host=".$this->_dbi->quote($host)
129                                    ." AND time_stamp > ". (time()-$since_minutes*60) 
130                                    ." ORDER BY time_stamp DESC");
131         } else {
132             $iter = new WikiDB_Array_generic_iter();
133             $logs =& $iter->_array;
134             $logentry = new Request_AccessLogEntry($this);
135             while ($logentry->read_file()) {
136                 if (!empty($logentry->referer)) {
137                     $iter->_array[] = $logentry;
138                     if ($limit and count($logs) > $limit)
139                         array_shift($logs);
140                     $logentry = new Request_AccessLogEntry($this);
141                 }
142             }
143             $logs = array_reverse($logs);
144             $logs = array_slice($logs,0,min($limit,count($logs)));
145             return $iter;
146         }
147     }
148
149     /**
150      * Read sequentially backwards all previous entries from log file.
151      * FIXME!
152      */
153     function read_file() {
154         global $request;
155         if ($this->logfile) $this->logfile = ACCESS_LOG; // support Request_AccessLog::read
156
157         if (empty($this->reader))       // start at the beginning
158             $this->reader = fopen($this->logfile, "r");
159         if ($s = fgets($this->reader)) {
160             $entry = new Request_AccessLogEntry($this);
161             $re = '/^(\S+)\s(\S+)\s(\S+)\s\[(.+?)\] "([^"]+)" (\d+) (\d+) "([^"]*)" "([^"]*)"$/';
162             if (preg_match($re, $s, $m)) {
163                 list(,$entry->host, $entry->ident, $entry->user, $entry->time,
164                      $entry->request, $entry->status, $entry->size,
165                      $entry->referer, $entry->user_agent) = $m;
166             }
167             return $entry;
168         } else { // until the end
169             fclose($this->reader);
170             return false;
171         }
172     }
173     function read_sql($where='') {
174         if (empty($this->sqliter))
175             $this->sqliter = $this->_read_sql_query($where);
176         return $this->sqliter->next();
177     }
178     function _read_sql_query($where='') {
179         global $request;
180         $dbh =& $request->_dbi;
181         $log_tbl =& $this->logtable;
182         return $dbh->genericSqlIter("SELECT *,request_uri as request,request_time as time,remote_user as user,"
183                                     ."remote_host as host,agent as user_agent"
184                                     ." FROM $log_tbl"
185                                     . ($where ? " WHERE $where" : ""));
186     }
187
188     /* done in request->finish() before the db is closed */
189     function write_sql() {
190         global $request;
191         $dbh =& $request->_dbi;
192         if (isset($this->entries) and $dbh and $dbh->isOpen())
193             foreach ($this->entries as $entry) {
194                 $entry->write_sql();
195             }
196     }
197     /* done in the shutdown callback */
198     function write_file() {
199         if (isset($this->entries) and $this->logfile)
200             foreach ($this->entries as $entry) {
201                 $entry->write_file();
202             }
203         unset($this->entries);
204     }
205     /* in an ideal world... */
206     function write() {
207         if ($this->logfile) $this->write_file();
208         if ($this->logtable) $this->write_sql();
209         unset($this->entries);
210     }
211 }
212
213 class Request_AccessLogEntry
214 {
215     /**
216      * Constructor.
217      *
218      * The log entry will be automatically appended to the log file or 
219      * SQL table when the current request terminates.
220      *
221      * If you want to modify a Request_AccessLogEntry before it gets
222      * written (e.g. via the setStatus and setSize methods) you should
223      * use an '&' on the constructor, so that you're working with the
224      * original (rather than a copy) object.
225      *
226      * <pre>
227      *    $log_entry = & new Request_AccessLogEntry("/tmp/wiki_access_log");
228      *    $log_entry->setStatus(401);
229      *    $log_entry->push($request);
230      * </pre>
231      *
232      *
233      */
234     function Request_AccessLogEntry (&$accesslog) {
235         $this->_accesslog = $accesslog;
236         $this->logfile = $accesslog->logfile;
237         $this->time = time();
238         $this->status = 200;    // see setStatus()
239         $this->size = 0;        // see setSize()
240     }
241
242     /**
243      * @param $request object  Request object for current request.
244      */
245     function push(&$request) {
246         $this->host  = $request->get('REMOTE_HOST');
247         $this->ident = $request->get('REMOTE_IDENT');
248         if (!$this->ident)
249             $this->ident = '-';
250         $user = $request->getUser();
251         if ($user->isAuthenticated())
252             $this->user = $user->UserName();
253         else
254             $this->user = '-';
255         $this->request = join(' ', array($request->get('REQUEST_METHOD'),
256                                          $request->get('REQUEST_URI'),
257                                          $request->get('SERVER_PROTOCOL')));
258         $this->referer = (string) $request->get('HTTP_REFERER');
259         $this->user_agent = (string) $request->get('HTTP_USER_AGENT');
260     }
261
262     /**
263      * Set result status code.
264      *
265      * @param $status integer  HTTP status code.
266      */
267     function setStatus ($status) {
268         $this->status = $status;
269     }
270     
271     /**
272      * Set response size.
273      *
274      * @param $size integer
275      */
276     function setSize ($size=0) {
277         $this->size = (int)$size;
278     }
279     function setDuration ($seconds) {
280         // Pear DB does not correctly quote , in floats using ?. e.g. in european locales.
281         // Workaround:
282         $this->duration = str_replace(",",".",sprintf("%f",$seconds));
283     }
284     
285     /**
286      * Get time zone offset.
287      *
288      * This is a static member function.
289      *
290      * @param $time integer Unix timestamp (defaults to current time).
291      * @return string Zone offset, e.g. "-0800" for PST.
292      */
293     function _zone_offset ($time = false) {
294         if (!$time)
295             $time = time();
296         $offset = date("Z", $time);
297         $negoffset = "";
298         if ($offset < 0) {
299             $negoffset = "-";
300             $offset = -$offset;
301         }
302         $offhours = floor($offset / 3600);
303         $offmins  = $offset / 60 - $offhours * 60;
304         return sprintf("%s%02d%02d", $negoffset, $offhours, $offmins);
305     }
306
307     /**
308      * Format time in NCSA format.
309      *
310      * This is a static member function.
311      *
312      * @param $time integer Unix timestamp (defaults to current time).
313      * @return string Formatted date & time.
314      */
315     function _ncsa_time($time = false) {
316         if (!$time)
317             $time = time();
318         return date("d/M/Y:H:i:s", $time) .
319             " " . $this->_zone_offset();
320     }
321
322     function write() {
323         if ($this->_accesslog->logfile) $this->write_file();
324         if ($this->_accesslog->logtable) $this->write_sql();
325     }
326
327     /**
328      * Write entry to log file.
329      */
330     function write_file() {
331         $entry = sprintf('%s %s %s [%s] "%s" %d %d "%s" "%s"',
332                          $this->host, $this->ident, $this->user,
333                          $this->_ncsa_time($this->time),
334                          $this->request, $this->status, $this->size,
335                          $this->referer, $this->user_agent);
336         if (!empty($this->_accesslog->reader)) {
337             fclose($this->_accesslog->reader);
338             unset($this->_accesslog->reader);
339         }
340         //Error log doesn't provide locking.
341         //error_log("$entry\n", 3, $this->logfile);
342         // Alternate method
343         if (($fp = fopen($this->logfile, "a"))) {
344             flock($fp, LOCK_EX);
345             fputs($fp, "$entry\n");
346             fclose($fp);
347         }
348     }
349
350     /* This is better been done by apache mod_log_sql */
351     /* If ACCESS_LOG_SQL & 2 we do write it by our own */
352     function write_sql() {
353         global $request;
354         
355         $dbh =& $request->_dbi;
356         if ($dbh and $dbh->isOpen() and $this->_accesslog->logtable) {
357             //$log_tbl =& $this->_accesslog->logtable;
358             if ($request->get('REQUEST_METHOD') == "POST") {
359                 // strangely HTTP_POST_VARS doesn't contain all posted vars.
360                 if (check_php_version(4,2))
361                     $args = $_POST; // copy not ref. clone not needed on hashes
362                 else
363                     $args = $GLOBALS['HTTP_POST_VARS'];
364                 // garble passwords
365                 if (!empty($args['auth']['passwd']))    $args['auth']['passwd'] = '<not displayed>';
366                 if (!empty($args['dbadmin']['passwd'])) $args['dbadmin']['passwd'] = '<not displayed>';
367                 if (!empty($args['pref']['passwd']))    $args['pref']['passwd'] = '<not displayed>';
368                 if (!empty($args['pref']['passwd2']))   $args['pref']['passwd2'] = '<not displayed>';
369                 $this->request_args = substr(serialize($args),0,254); // if VARCHAR(255) is used.
370             } else {
371                 $this->request_args = $request->get('QUERY_STRING'); 
372             }
373             $this->request_method = $request->get('REQUEST_METHOD');
374             $this->request_uri = $request->get('REQUEST_URI');
375             // duration problem: sprintf "%f" might use comma e.g. "100,201" in european locales
376             $dbh->_backend->write_accesslog($this);
377         }
378     }
379 }
380
381 /**
382  * Shutdown callback. Ensures that the file is written.
383  *
384  * @access private
385  * @see Request_AccessLogEntry
386  */
387 function Request_AccessLogEntry_shutdown_function () {
388     global $request;
389     
390     if (isset($request->_accesslog->entries) and $request->_accesslog->logfile)
391         foreach ($request->_accesslog->entries as $entry) {
392             $entry->write_file();
393         }
394     unset($request->_accesslog->entries);
395 }
396
397 // TODO: SQL access methods....
398 // (c) 2005 Charles Corrigan (the mysql parts)
399 // (c) 2006 Rein Urban (the postgresql parts)
400 // from AnalyseAccessLogSql.php
401 class Request_AccessLog_SQL
402 {
403
404     /**
405      * Build the query string
406      *
407      * FIXME: some or all of these queries may be MySQL specific / non-portable
408      * FIXME: properly quote the string args
409      *
410      * The column names displayed are generated from the actual query column
411      * names, so make sure that each column in the query is given a user
412      * friendly name. Note that the column names are passed to _() and so may be
413      * translated.
414      *
415      * If there are query specific where conditions, then the construction
416      * "    if ($where_conditions<>'')
417      *          $where_conditions = 'WHERE '.$where_conditions.' ';"
418      * should be changed to
419      * "    if ($where_conditions<>'')
420      *          $where_conditions = 'AND '.$where_conditions.' ';"
421      * and in the assignment to query have something like
422      * "    $query= "SELECT "
423      *          ."referer "
424      *          ."FROM $accesslog "
425      *          ."WHERE referer IS NOT NULL "
426      *          .$where_conditions
427      */
428     function _getQueryString(&$args) {
429         // extract any parametrised conditions from the arguments,
430         // in particular, how much history to select
431         $where_conditions = $this->_getWhereConditions($args);
432
433         // get the correct name for the table
434         //FIXME is there a more correct way to do this?
435         global $DBParams, $request;
436         $accesslog = (!empty($DBParams['prefix']) ? $DBParams['prefix'] : '')."accesslog";
437
438         $query = '';
439         $backend_type = $request->_dbi->_backend->backendType();
440         switch ($backend_type) {
441         case 'mysql': 
442             $Referring_URL = "left(referer,length(referer)-instr(reverse(referer),'?'))"; break;
443         case 'pgsql': 
444         case 'postgres7': 
445             $Referring_URL = "substr(referer,0,position('?' in referer))"; break;
446         default: 
447             $Referring_URL = "referer";
448         }
449         switch ($args['mode']) {
450         case 'referring_urls':
451             if ($where_conditions<>'')
452                 $where_conditions = 'WHERE '.$where_conditions.' ';
453             $query = "SELECT "
454                 . "$Referring_URL AS Referring_URL, "
455                 . "count(*) AS Referral_Count "
456                 . "FROM $accesslog "
457                 . $where_conditions
458                 . "GROUP BY Referring_URL";
459             break;
460         case 'external_referers':
461             $args['local_referrers'] = 'false';
462             $where_conditions = $this->_getWhereConditions($args);
463             if ($where_conditions<>'')
464                 $where_conditions = 'WHERE '.$where_conditions.' ';
465             $query = "SELECT "
466                 . "$Referring_URL AS Referring_URL, "
467                 . "count(*) AS Referral_Count "
468                 . "FROM $accesslog "
469                 . $where_conditions
470                 . "GROUP BY Referring_URL";
471             break;
472         case 'referring_domains':
473             if ($where_conditions<>'')
474                 $where_conditions = 'WHERE '.$where_conditions.' ';
475             switch ($backend_type) {
476             case 'mysql': 
477                 $Referring_Domain = "left(referer, if(locate('/', referer, 8) > 0,locate('/', referer, 8) -1, length(referer)))"; break;
478             case 'pgsql': 
479             case 'postgres7': 
480                 $Referring_Domain = "substr(referer,0,8) || regexp_replace(substr(referer,8), '/.*', '')"; break;
481             default: 
482                 $Referring_Domain = "referer"; break;
483             }
484             $query = "SELECT "
485                 . "$Referring_Domain AS Referring_Domain, "
486                 . "count(*) AS Referral_Count "
487                 . "FROM $accesslog "
488                 . $where_conditions
489                 . "GROUP BY Referring_Domain";
490             break;
491         case 'remote_hosts':
492             if ($where_conditions<>'')
493                 $where_conditions = 'WHERE '.$where_conditions.' ';
494             $query = "SELECT "
495                 ."remote_host AS Remote_Host, "
496                 ."count(*) AS Access_Count "
497                 ."FROM $accesslog "
498                 .$where_conditions
499                 ."GROUP BY Remote_Host";
500             break;
501         case 'users':
502             if ($where_conditions<>'')
503                 $where_conditions = 'WHERE '.$where_conditions.' ';
504             $query = "SELECT "
505                 ."remote_user AS User, "
506                 ."count(*) AS Access_Count "
507                 ."FROM $accesslog "
508                 .$where_conditions
509                 ."GROUP BY remote_user";
510             break;
511         case 'host_users':
512             if ($where_conditions<>'')
513                 $where_conditions = 'WHERE '.$where_conditions.' ';
514             $query = "SELECT "
515                 ."remote_host AS Remote_Host, "
516                 ."remote_user AS User, "
517                 ."count(*) AS Access_Count "
518                 ."FROM $accesslog "
519                 .$where_conditions
520                 ."GROUP BY remote_host, remote_user";
521             break;
522         case "search_bots":
523             // This queries for all entries in the SQL access log table that
524             // have a dns name that I know to be a web search engine crawler and
525             // categorises the results into time buckets as per the list below
526             // 0 - 1 minute - 60
527             // 1 - 1 hour   - 3600     = 60 * 60
528             // 2 - 1 day    - 86400    = 60 * 60 * 24
529             // 3 - 1 week   - 604800   = 60 * 60 * 24 * 7
530             // 4 - 1 month  - 2629800  = 60 * 60 * 24 * 365.25 / 12
531             // 5 - 1 year   - 31557600 = 60 * 60 * 24 * 365.25
532             $now = time();
533             $query = "SELECT "
534                 ."CASE WHEN $now-time_stamp<60 THEN '"._("0 - last minute")."' ELSE "
535                   ."CASE WHEN $now-time_stamp<3600 THEN '"._("1 - 1 minute to 1 hour")."' ELSE "
536                     ."CASE WHEN $now-time_stamp<86400 THEN '"._("2 - 1 hour to 1 day")."' ELSE "
537                       ."CASE WHEN $now-time_stamp<604800 THEN '"._("3 - 1 day to 1 week")."' ELSE "
538                         ."CASE WHEN $now-time_stamp<2629800 THEN '"._("4 - 1 week to 1 month")."' ELSE "
539                           ."CASE WHEN $now-time_stamp<31557600 THEN '"._("5 - 1 month to 1 year")."' ELSE "
540                             ."'"._("6 - more than 1 year")."' END END END END END END AS Time_Scale, "
541                 ."remote_host AS Remote_Host, "
542                 ."count(*) AS Access_Count "
543                 ."FROM $accesslog "
544                 ."WHERE (remote_host LIKE '%googlebot.com' "
545                 ."OR remote_host LIKE '%alexa.com' "
546                 ."OR remote_host LIKE '%inktomisearch.com' "
547                 ."OR remote_host LIKE '%msnbot.msn.com') "
548                 .($where_conditions ? 'AND '.$where_conditions : '')
549                 ."GROUP BY Time_Scale, remote_host";
550             break;
551         case "search_bots_hits":
552             // This queries for all entries in the SQL access log table that
553             // have a dns name that I know to be a web search engine crawler and
554             // displays the URI that was hit.
555             // If PHPSESSID appears in the URI, just display the URI to the left of this
556             $sessname = session_name();
557             switch ($backend_type) {
558             case 'mysql': 
559                 $Request_URI = "IF(instr(request_uri, '$sessname')=0, request_uri,left(request_uri, instr(request_uri, '$sessname')-2))";
560                 break;
561             case 'pgsql': 
562             case 'postgres7': 
563                 $Request_URI = "regexp_replace(request_uri, '$sessname.*', '')"; break;
564             default: 
565                 $Request_URI = 'request_uri'; break;
566             }
567             $now = time();
568             $query = "SELECT "
569                 ."CASE WHEN $now-time_stamp<60 THEN '"._("0 - last minute")."' ELSE "
570                   ."CASE WHEN $now-time_stamp<3600 THEN '"._("1 - 1 minute to 1 hour")."' ELSE "
571                     ."CASE WHEN $now-time_stamp<86400 THEN '"._("2 - 1 hour to 1 day")."' ELSE "
572                       ."CASE WHEN $now-time_stamp<604800 THEN '"._("3 - 1 day to 1 week")."' ELSE "
573                         ."CASE WHEN $now-time_stamp<2629800 THEN '"._("4 - 1 week to 1 month")."' ELSE "
574                           ."CASE WHEN $now-time_stamp<31557600 THEN '"._("5 - 1 month to 1 year")."' ELSE "
575                             ."'"._("6 - more than 1 year")."' END END END END END END AS Time_Scale, "
576                 ."remote_host AS Remote_Host, "
577                 ."$Request_URI AS Request_URI "
578                 ."FROM $accesslog "
579                 ."WHERE (remote_host LIKE '%googlebot.com' "
580                 ."OR remote_host LIKE '%alexa.com' "
581                 ."OR remote_host LIKE '%inktomisearch.com' "
582                 ."OR remote_host LIKE '%msnbot.msn.com') "
583                 .($where_conditions ? 'AND '.$where_conditions : '')
584                 ."ORDER BY time_stamp";
585         }
586         return $query;
587     }
588
589     /** Honeypot for xgettext. Those strings are translated dynamically.
590      */
591     function _locale_dummy() {
592         $dummy = array(
593                        // mode caption
594                        _("referring_urls"),
595                        _("external_referers"),
596                        _("referring_domains"),
597                        _("remote_hosts"),
598                        _("users"),
599                        _("host_users"),
600                        _("search_bots"),
601                        _("search_bots_hits"),
602                        // period header
603                        _("minutes"),
604                        _("hours"),
605                        _("days"),
606                        _("weeks"),
607                        );
608     }
609
610     function getDefaultArguments() {
611         return array(
612                      'mode'             => 'referring_domains',
613                      // referring_domains, referring_urls, remote_hosts, users, host_users, search_bots, search_bots_hits
614                      'caption'          => '', 
615                      // blank means use the mode as the caption/title for the output
616                      'local_referrers'  => 'true',  // only show external referring sites
617                      'period'           => '',      // the type of period to report:
618                      // may be weeks, days, hours, minutes, or blank for all
619                      'count'            => '0'      // the number of periods to report
620                      );
621     }
622
623
624     function table_output () {
625         $query = $this->_getQueryString($args);
626
627         if ($query=='')
628             return HTML::p(sprintf( _("Unrecognised parameter 'mode=%s'"),
629                                     $args['mode']));
630
631         // get the data back.
632         // Note that this must be done before the final generation ofthe table,
633         // otherwise the headers will not be ready
634         $tbody = $this->_getQueryResults($query, $dbi);
635
636         return HTML::table(array('border'        => 1,
637                                  'cellspacing'   => 1,
638                                  'cellpadding'   => 1),
639                     HTML::caption(HTML::h1(HTML::br(),$this->_getCaption($args))),
640                     HTML::thead($this->_theadrow),
641                     $tbody);
642     }
643
644     function _getQueryResults($query, &$dbi) {
645         $queryResult = $dbi->genericSqlIter($query);
646         if (!$queryResult) {
647             $tbody = HTML::tbody(HTML::tr(HTML::td(_("<empty>"))));
648         } else {
649             $tbody = HTML::tbody();
650             while ($row = $queryResult->next()) {
651                 $this->_setHeaders($row);
652                 $tr = HTML::tr();
653                 foreach ($row as $value) {
654                     // output a '-' for empty values, otherwise the table looks strange
655                     $tr->pushContent(HTML::td( empty($value) ? '-' : $value ));
656                 }
657                 $tbody->pushContent($tr);
658             }
659         }
660         $queryResult->free();
661         return $tbody;
662     }
663
664     function _setHeaders($row) {
665         if (!$this->_headerSet) {
666             foreach ($row as $key => $value) {
667                 $this->_theadrow->pushContent(HTML::th(_($key)));
668             }
669             $this->_headerSet = true;
670         }
671     }
672
673     function _getWhereConditions(&$args) {
674         $where_conditions = '';
675
676         if ($args['period']<>'') {
677             $since = 0;
678             if ($args['period']=='minutes') {
679                 $since = 60;
680             } elseif ($args['period']=='hours') {
681                 $since = 60 * 60;
682             } elseif ($args['period']=='days') {
683                 $since = 60 * 60 * 24;
684             } elseif ($args['period']=='weeks') {
685                 $since = 60 * 60 * 24 * 7;
686             }
687             $since = $since * $args['count'];
688             if ($since>0) {
689                 if ($where_conditions<>'')
690                     $where_conditions = $where_conditions.' AND ';
691                 $since = time() - $since;
692                 $where_conditions = $where_conditions."time_stamp > $since";
693             }
694         }
695
696         if ($args['local_referrers']<>'true') {
697             global $request;
698             if ($where_conditions<>'')
699                 $where_conditions = $where_conditions.' AND ';
700             $localhost = SERVER_URL;
701             $len = strlen($localhost);
702             $backend_type = $request->_dbi->_backend->backendType();
703             switch ($backend_type) {
704             case 'mysql': 
705                 $ref_localhost = "left(referer,$len)<>'$localhost'"; break;
706             case 'pgsql': 
707             case 'postgres7': 
708                 $ref_localhost = "substr(referer,0,$len)<>'$localhost'"; break;
709             default: 
710                 $ref_localhost = "";
711             }
712             $where_conditions = $where_conditions.$ref_localhost;
713         }
714
715         // The assumed contract is that there is a space at the end of the
716         // conditions string, so that following SQL clauses (such as GROUP BY) 
717         // will not cause a syntax error
718         if ($where_conditions<>'')
719             $where_conditions = $where_conditions.' ';
720
721         return $where_conditions;
722     }
723
724     function _getCaption(&$args) {
725         $caption = $args['caption'];
726         if ($caption=='')
727             $caption = gettext($args['mode']);
728         if ($args['period']<>'' && $args['count'])
729             $caption = $caption." - ".$args['count']." ". gettext($args['period']);
730         return $caption;
731     }
732
733 }
734
735 // For emacs users
736 // Local Variables:
737 //   mode: php
738 //   tab-width: 8
739 //   c-basic-offset: 4
740 //   c-hanging-comment-ender-p: nil
741 //   indent-tabs-mode: nil
742 // End:
743 // vim: expandtab shiftwidth=4:
744 ?>