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