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