xref: /plugin/statistics/admin.php (revision 54f6c432a6875831742324eaa410b4f4bd14f817)
1<?php
2/**
3 * statistics plugin
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <gohr@cosmocode.de>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
13require_once(DOKU_PLUGIN.'admin.php');
14
15/**
16 * All DokuWiki plugins to extend the admin function
17 * need to inherit from this class
18 */
19class admin_plugin_statistics extends DokuWiki_Admin_Plugin {
20    var $dblink = null;
21
22    /**
23     * return some info
24     */
25    function getInfo(){
26        return confToHash(dirname(__FILE__).'/info.txt');
27    }
28
29    /**
30     * Access for managers allowed
31     */
32    function forAdminOnly(){
33        return false;
34    }
35
36    /**
37     * return sort order for position in admin menu
38     */
39    function getMenuSort() {
40        return 150;
41    }
42
43    /**
44     * handle user request
45     */
46    function handle() {
47    }
48
49    /**
50     * fixme build statistics here
51     */
52    function html() {
53        // fixme build a navigation menu in a TOC here
54
55        switch($_REQUEST['opt']){
56
57            default:
58                echo $this->locale_xhtml('intro');
59                echo $this->html_dashboard();
60        }
61    }
62
63    function html_timeselect(){
64        echo '<form>';
65
66        echo '</form>';
67    }
68
69
70    function html_dashboard(){
71
72        // top pages today
73        $sql = "SELECT page, COUNT(*) as cnt
74                  FROM ".$this->getConf('db_prefix')."access
75                 WHERE DATE(dt) = CURDATE()
76                   AND ua_type = 'browser'
77              GROUP BY page
78              ORDER BY cnt DESC, page
79                 LIMIT 20";
80        $result = $this->runSQL($sql);
81        $this->html_resulttable($result,array('Pages','Count'));
82
83        // top referer today
84        $sql = "SELECT ref as url, COUNT(*) as cnt
85                  FROM ".$this->getConf('db_prefix')."access
86                 WHERE DATE(dt) = CURDATE()
87                   AND ua_type = 'browser'
88                   AND ref_type = 'external'
89              GROUP BY ref_md5
90              ORDER BY cnt DESC, url
91                 LIMIT 20";
92        $result = $this->runSQL($sql);
93        $this->html_resulttable($result,array('Incoming Links','Count'));
94
95        // top countries today
96        $sql = "SELECT B.country, COUNT(*) as cnt
97                  FROM ".$this->getConf('db_prefix')."access as A,
98                       ".$this->getConf('db_prefix')."iplocation as B
99                 WHERE DATE(A.dt) = CURDATE()
100                   AND A.ip = B.ip
101              GROUP BY B.country
102              ORDER BY cnt DESC, B.country
103                 LIMIT 20";
104        $result = $this->runSQL($sql);
105        $this->html_resulttable($result,array('Countries','Count'));
106    }
107
108    /**
109     * Display a result in a HTML table
110     */
111    function html_resulttable($result,$header){
112        echo '<table>';
113        echo '<tr>';
114        foreach($header as $h){
115            echo '<th>'.hsc($h).'</th>';
116        }
117        echo '</tr>';
118
119        foreach($result as $row){
120            echo '<tr>';
121            foreach($row as $k => $v){
122                echo '<td class="stats_'.$k.'">';
123                if($k == 'page'){
124                    echo '<a href="'.wl($v).'" class="wikilink1">';
125                    echo hsc($v);
126                    echo '</a>';
127                }elseif($k == 'url'){
128                    $url = hsc($v);
129                    if(strlen($url) > 50){
130                        $url = substr($url,0,30).' &hellip; '.substr($url,-20);
131                    }
132                    echo '<a href="'.$v.'" class="urlextern">';
133                    echo $url;
134                    echo '</a>';
135                }elseif($k == 'html'){
136                    echo $v;
137                }else{
138                    echo hsc($v);
139                }
140                echo '</td>';
141            }
142            echo '</tr>';
143        }
144        echo '</table>';
145    }
146
147
148    /**
149     * Return a link to the DB, opening the connection if needed
150     */
151    function dbLink(){
152        // connect to DB if needed
153        if(!$this->dblink){
154            $this->dblink = mysql_connect($this->getConf('db_server'),
155                                          $this->getConf('db_user'),
156                                          $this->getConf('db_password'));
157            if(!$this->dblink){
158                msg('DB Error: connection failed',-1);
159                return null;
160            }
161            // set utf-8
162            if(!mysql_db_query($this->getConf('db_database'),'set names utf8',$this->dblink)){
163                msg('DB Error: could not set UTF-8 ('.mysql_error($this->dblink).')',-1);
164                return null;
165            }
166        }
167        return $this->dblink;
168    }
169
170    /**
171     * Simple function to run a DB query
172     */
173    function runSQL($sql_string) {
174        $link = $this->dbLink();
175
176        $result = mysql_db_query($this->conf['db_database'],$sql_string,$link);
177        if(!$result){
178            msg('DB Error: '.mysql_error($link),-1);
179            return null;
180        }
181
182        $resultarray = array();
183
184        //mysql_db_query returns 1 on a insert statement -> no need to ask for results
185        if ($result != 1) {
186            for($i=0; $i< mysql_num_rows($result); $i++) {
187                $temparray = mysql_fetch_assoc($result);
188                $resultarray[]=$temparray;
189            }
190            mysql_free_result($result);
191        }
192
193        if (mysql_insert_id($link)) {
194            $resultarray = mysql_insert_id($link); //give back ID on insert
195        }
196
197        return $resultarray;
198    }
199
200    /**
201     * Returns a short name for a User Agent and sets type, version and os info
202     */
203    function ua_info($ua,&$type,&$ver,&$os){
204        $ua = strtr($ua,' +','__');
205        $ua = strtolower($ua);
206
207        // common browsers
208        $regvermsie     = '/msie([+_ ]|)([\d\.]*)/i';
209        $regvernetscape = '/netscape.?\/([\d\.]*)/i';
210        $regverfirefox  = '/firefox\/([\d\.]*)/i';
211        $regversvn      = '/svn\/([\d\.]*)/i';
212        $regvermozilla  = '/mozilla(\/|)([\d\.]*)/i';
213        $regnotie       = '/webtv|omniweb|opera/i';
214        $regnotnetscape = '/gecko|compatible|opera|galeon|safari/i';
215
216        $name = '';
217        # IE ?
218        if(preg_match($regvermsie,$ua,$m) && !preg_match($regnotie,$ua)){
219            $type = 'browser';
220            $ver  = $m[2];
221            $name = 'msie';
222        }
223        # Firefox ?
224        elseif (preg_match($regverfirefox,$ua,$m)){
225            $type = 'browser';
226            $ver  = $m[1];
227            $name = 'firefox';
228        }
229        # Subversion ?
230        elseif (preg_match($regversvn,$ua,$m)){
231            $type = 'rcs';
232            $ver  = $m[1];
233            $name = 'svn';
234        }
235        # Netscape 6.x, 7.x ... ?
236        elseif (preg_match($regvernetscape,$ua,$m)){
237            $type = 'browser';
238            $ver  = $m[1];
239            $name = 'netscape';
240        }
241        # Netscape 3.x, 4.x ... ?
242        elseif(preg_match($regvermozilla,$ua,$m) && !preg_match($regnotnetscape,$ua)){
243            $type = 'browser';
244            $ver  = $m[2];
245            $name = 'netscape';
246        }else{
247            include(dirname(__FILE__).'/inc/browsers.php');
248            foreach($BrowsersSearchIDOrder as $regex){
249                if(preg_match('/'.$regex.'/',$ua)){
250                    // it's a browser!
251                    $type = 'browser';
252                    $name = strtolower($regex);
253                    break;
254                }
255            }
256        }
257
258        // check OS for browsers
259        if($type == 'browser'){
260            include(dirname(__FILE__).'/inc/operating_systems.php');
261            foreach($OSSearchIDOrder as $regex){
262                if(preg_match('/'.$regex.'/',$ua)){
263                    $os = $OSHashID[$regex];
264                    break;
265                }
266            }
267
268        }
269
270        // are we done now?
271        if($name) return $name;
272
273        include(dirname(__FILE__).'/inc/robots.php');
274        foreach($RobotsSearchIDOrder as $regex){
275            if(preg_match('/'.$regex.'/',$ua)){
276                    // it's a robot!
277                    $type = 'robot';
278                    return strtolower($regex);
279            }
280        }
281
282        // dunno
283        return '';
284    }
285
286    /**
287     *
288     * @fixme: put search engine queries in seperate table here
289     */
290    function log_search($referer,&$type){
291        $referer = strtr($referer,' +','__');
292        $referer = strtolower($referer);
293
294        include(dirname(__FILE__).'/inc/search_engines.php');
295
296        foreach($SearchEnginesSearchIDOrder as $regex){
297            if(preg_match('/'.$regex.'/',$referer)){
298                if(!$NotSearchEnginesKeys[$regex] ||
299                   !preg_match('/'.$NotSearchEnginesKeys[$regex].'/',$referer)){
300                    // it's a search engine!
301                    $type = 'search';
302                    break;
303                }
304            }
305        }
306        if($type != 'search') return; // we're done here
307
308        #fixme now do the keyword magic!
309    }
310
311    /**
312     * Resolve IP to country/city
313     */
314    function log_ip($ip){
315        // check if IP already known and up-to-date
316        $sql = "SELECT ip
317                  FROM ".$this->getConf('db_prefix')."iplocation
318                 WHERE ip ='".addslashes($ip)."'
319                   AND lastupd > DATE_SUB(CURDATE(),INTERVAL 30 DAY)";
320        $result = $this->runSQL($sql);
321        if($result[0]['ip']) return;
322
323        $http = new DokuHTTPClient();
324        $http->timeout = 10;
325        $data = $http->get('http://api.hostip.info/get_html.php?ip='.$ip);
326
327        if(preg_match('/^Country: (.*?) \((.*?)\)\nCity: (.*?)$/s',$data,$match)){
328            $country = addslashes(trim($match[1]));
329            $code    = addslashes(strtolower(trim($match[2])));
330            $city    = addslashes(trim($match[3]));
331            $host    = addslashes(gethostbyaddr($ip));
332            $ip      = addslashes($ip);
333
334            $sql = "REPLACE INTO ".$this->getConf('db_prefix')."iplocation
335                        SET ip = '$ip',
336                            country = '$country',
337                            code    = '$code',
338                            city    = '$city',
339                            host    = '$host'";
340            $this->runSQL($sql);
341        }
342    }
343
344    /**
345     * log a page access
346     *
347     * called from log.php
348     */
349    function log_access(){
350        if(!$_REQUEST['p']) return;
351
352        # FIXME check referer against blacklist and drop logging for bad boys
353
354        // handle referer
355        $referer = trim($_REQUEST['r']);
356        if($referer){
357            $ref     = addslashes($referer);
358            $ref_md5 = ($ref) ? md5($referer) : '';
359            if(strpos($referer,DOKU_URL) === 0){
360                $ref_type = 'internal';
361            }else{
362                $ref_type = 'external';
363                $this->log_search($referer,$ref_type);
364            }
365        }else{
366            $ref      = '';
367            $ref_md5  = '';
368            $ref_type = '';
369        }
370
371        // handle user agent
372        $agent   = trim($_SERVER['HTTP_USER_AGENT']);
373
374        $ua      = addslashes($agent);
375        $ua_type = '';
376        $ua_ver  = '';
377        $os      = '';
378        $ua_info = addslashes($this->ua_info($agent,$ua_type,$ua_ver,$os));
379
380        $page    = addslashes($_REQUEST['p']);
381        $ip      = addslashes($_SERVER['REMOTE_ADDR']);
382        $sx      = (int) $_REQUEST['sx'];
383        $sy      = (int) $_REQUEST['sy'];
384        $vx      = (int) $_REQUEST['vx'];
385        $vy      = (int) $_REQUEST['vy'];
386        $user    = addslashes($_SERVER['REMOTE_USER']);
387        $session = addslashes(session_id());
388
389        $sql  = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."access
390                    SET page     = '$page',
391                        ip       = '$ip',
392                        ua       = '$ua',
393                        ua_info  = '$ua_info',
394                        ua_type  = '$ua_type',
395                        ua_ver   = '$ua_ver',
396                        os       = '$os',
397                        ref      = '$ref',
398                        ref_md5  = '$ref_md5',
399                        ref_type = '$ref_type',
400                        screen_x = '$sx',
401                        screen_y = '$sy',
402                        view_x   = '$vx',
403                        view_y   = '$vy',
404                        user     = '$user',
405                        session  = '$session'";
406        $ok = $this->runSQL($sql);
407        if(is_null($ok)){
408            global $MSG;
409            print_r($MSG);
410        }
411
412        // resolve the IP
413        $this->log_ip($_SERVER['REMOTE_ADDR']);
414    }
415
416    /**
417     * Just send a 1x1 pixel blank gif to the browser
418     *
419     * @called from log.php
420     *
421     * @author Andreas Gohr <andi@splitbrain.org>
422     * @author Harry Fuecks <fuecks@gmail.com>
423     */
424    function sendGIF(){
425        $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7');
426        header('Content-Type: image/gif');
427        header('Content-Length: '.strlen($img));
428        header('Connection: Close');
429        print $img;
430        flush();
431        // Browser should drop connection after this
432        // Thinks it's got the whole image
433    }
434
435}
436