xref: /plugin/statistics/admin.php (revision 2ee939eedbbb7aaf24774bef97e5194b1f3ea054)
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    var $opt    = '';
22    var $from   = '';
23    var $to     = '';
24    var $start  = '';
25    var $tlimit = '';
26
27    /**
28     * return some info
29     */
30    function getInfo(){
31        return confToHash(dirname(__FILE__).'/info.txt');
32    }
33
34    /**
35     * Access for managers allowed
36     */
37    function forAdminOnly(){
38        return false;
39    }
40
41    /**
42     * return sort order for position in admin menu
43     */
44    function getMenuSort() {
45        return 150;
46    }
47
48    /**
49     * handle user request
50     */
51    function handle() {
52        $this->opt = preg_replace('/[^a-z]+/','',$_REQUEST['opt']);
53        $this->start = (int) $_REQUEST['s'];
54        $this->setTimeframe($_REQUEST['f'],$_REQUEST['t']);
55    }
56
57    /**
58     * set limit clause
59     */
60    function setTimeframe($from,$to){
61        // fixme add better sanity checking here:
62        $from = preg_replace('/[^\d\-]+/','',$from);
63        $to   = preg_replace('/[^\d\-]+/','',$to);
64        if(!$from) $from = date('Y-m-d');
65        if(!$to)   $to   = date('Y-m-d');
66
67        //setup limit clause
68        $tlimit = "A.dt >= '$from 00:00:00' AND A.dt <= '$to 23:59:59'";
69        $this->tlimit = $tlimit;
70        $this->from   = $from;
71        $this->to     = $to;
72    }
73
74    /**
75     * fixme build statistics here
76     */
77    function html() {
78        $this->html_toc();
79        echo '<h1>Access Statistics</h1>';
80        $this->html_timeselect();
81
82        switch($this->opt){
83            case 'country':
84                $this->html_country();
85                break;
86            case 'page':
87                $this->html_page();
88                break;
89            case 'browser':
90                $this->html_browser();
91                break;
92            case 'os':
93                $this->html_os();
94                break;
95            case 'referer':
96                $this->html_referer();
97                break;
98            case 'newreferer':
99                $this->html_newreferer();
100                break;
101            case 'outlinks':
102                $this->html_outlinks();
103                break;
104            case 'resolution':
105                $this->html_resolution();
106                break;
107            case 'searchphrases':
108                $this->html_searchphrases();
109                break;
110            case 'searchwords':
111                $this->html_searchwords();
112                break;
113            case 'searchengines':
114                $this->html_searchengines();
115                break;
116            default:
117                $this->html_dashboard();
118        }
119    }
120
121    function html_toc(){
122        echo '<div class="toc">';
123        echo '<div class="tocheader toctoggle" id="toc__header">';
124        echo 'Detailed Statistics';
125        echo '</div>';
126        echo '<div id="toc__inside">';
127        echo '<ul class="toc">';
128
129        echo '<li><div class="li">';
130        echo '<a href="?do=admin&amp;page=statistics&amp;opt=&amp;f='.$this->from.'&amp;t='.$this->to.'">Dashboard</a>';
131        echo '</div></li>';
132
133        echo '<li><div class="li">';
134        echo '<a href="?do=admin&amp;page=statistics&amp;opt=page&amp;f='.$this->from.'&amp;t='.$this->to.'">Pages</a>';
135        echo '</div></li>';
136
137        echo '<li><div class="li">';
138        echo '<a href="?do=admin&amp;page=statistics&amp;opt=referer&amp;f='.$this->from.'&amp;t='.$this->to.'">Incoming Links</a>';
139        echo '</div></li>';
140
141        echo '<li><div class="li">';
142        echo '<a href="?do=admin&amp;page=statistics&amp;opt=newreferer&amp;f='.$this->from.'&amp;t='.$this->to.'">New Incoming Links</a>';
143        echo '</div></li>';
144
145        echo '<li><div class="li">';
146        echo '<a href="?do=admin&amp;page=statistics&amp;opt=outlinks&amp;f='.$this->from.'&amp;t='.$this->to.'">Outgoing Links</a>';
147        echo '</div></li>';
148
149        echo '<li><div class="li">';
150        echo '<a href="?do=admin&amp;page=statistics&amp;opt=searchphrases&amp;f='.$this->from.'&amp;t='.$this->to.'">Search Phrases</a>';
151        echo '</div></li>';
152
153        echo '<li><div class="li">';
154        echo '<a href="?do=admin&amp;page=statistics&amp;opt=searchwords&amp;f='.$this->from.'&amp;t='.$this->to.'">Search Words</a>';
155        echo '</div></li>';
156
157        echo '<li><div class="li">';
158        echo '<a href="?do=admin&amp;page=statistics&amp;opt=searchengines&amp;f='.$this->from.'&amp;t='.$this->to.'">Search Engines</a>';
159        echo '</div></li>';
160
161
162        echo '<li><div class="li">';
163        echo '<a href="?do=admin&amp;page=statistics&amp;opt=browser&amp;f='.$this->from.'&amp;t='.$this->to.'">Browsers</a>';
164        echo '</div></li>';
165
166        echo '<li><div class="li">';
167        echo '<a href="?do=admin&amp;page=statistics&amp;opt=os&amp;f='.$this->from.'&amp;t='.$this->to.'">Operating Systems</a>';
168        echo '</div></li>';
169
170        echo '<li><div class="li">';
171        echo '<a href="?do=admin&amp;page=statistics&amp;opt=country&amp;f='.$this->from.'&amp;t='.$this->to.'">Countries</a>';
172        echo '</div></li>';
173
174        echo '<li><div class="li">';
175        echo '<a href="?do=admin&amp;page=statistics&amp;opt=resolution&amp;f='.$this->from.'&amp;t='.$this->to.'">Resolution</a>';
176        echo '</div></li>';
177
178        echo '</ul>';
179        echo '</div>';
180        echo '</div>';
181    }
182
183    function html_pager($limit,$next){
184        echo '<div class="plg_stats_pager">';
185
186        if($this->start > 0){
187            $go = max($this->start - $limit, 0);
188            echo '<a href="?do=admin&amp;page=statistics&amp;opt='.$this->opt.'&amp;f='.$this->from.'&amp;t='.$this->to.'&amp;s='.$go.'" class="prev">previous page</a>';
189        }
190
191        if($next){
192            $go = $this->start + $limit;
193            echo '<a href="?do=admin&amp;page=statistics&amp;opt='.$this->opt.'&amp;f='.$this->from.'&amp;t='.$this->to.'&amp;s='.$go.'" class="next">next page</a>';
194        }
195        echo '</div>';
196    }
197
198    /**
199     * Print the time selection menu
200     */
201    function html_timeselect(){
202        $now   = date('Y-m-d');
203        $yday  = date('Y-m-d',time()-(60*60*24));
204        $week  = date('Y-m-d',time()-(60*60*24*7));
205        $month = date('Y-m-d',time()-(60*60*24*30));
206
207        echo '<div class="plg_stats_timeselect">';
208        echo '<span>Select the timeframe:</span>';
209        echo '<ul>';
210
211        echo '<li>';
212        echo '<a href="?do=admin&amp;page=statistics&amp;opt='.$this->opt.'&amp;f='.$now.'&amp;t='.$now.'">';
213        echo 'today';
214        echo '</a>';
215        echo '</li>';
216
217        echo '<li>';
218        echo '<a href="?do=admin&amp;page=statistics&amp;opt='.$this->opt.'&amp;f='.$yday.'&amp;t='.$yday.'">';
219        echo 'yesterday';
220        echo '</a>';
221        echo '</li>';
222
223        echo '<li>';
224        echo '<a href="?do=admin&amp;page=statistics&amp;opt='.$this->opt.'&amp;f='.$week.'&amp;t='.$now.'">';
225        echo 'last 7 days';
226        echo '</a>';
227        echo '</li>';
228
229        echo '<li>';
230        echo '<a href="?do=admin&amp;page=statistics&amp;opt='.$this->opt.'&amp;f='.$month.'&amp;t='.$now.'">';
231        echo 'last 30 days';
232        echo '</a>';
233        echo '</li>';
234
235        echo '</ul>';
236
237
238        echo '<form action="" method="get">';
239        echo '<input type="hidden" name="do" value="admin" />';
240        echo '<input type="hidden" name="page" value="statistics" />';
241        echo '<input type="hidden" name="opt" value="'.$this->opt.'" />';
242        echo '<input type="text" name="f" value="'.$this->from.'" class="edit" />';
243        echo '<input type="text" name="t" value="'.$this->to.'" class="edit" />';
244        echo '<input type="submit" value="go" class="button" />';
245        echo '</form>';
246
247        echo '</div>';
248    }
249
250
251    /**
252     * Print an introductionary screen
253     */
254    function html_dashboard(){
255        echo '<p>This page gives you a quick overview on what is happening in your Wiki. For detailed lists
256              choose a topic from the list.</p>';
257
258
259        echo '<div class="plg_stats_dashboard">';
260
261        // general info
262        echo '<div class="plg_stats_top">';
263        $result = $this->sql_aggregate($this->tlimit);
264        echo '<ul>';
265        echo '<li><span>'.$result['pageviews'].'</span> page views </li>';
266        echo '<li><span>'.$result['sessions'].'</span> visits (sessions) </li>';
267        echo '<li><span>'.$result['visitors'].'</span> unique visitors </li>';
268        echo '<li><span>'.$result['users'].'</span> logged in users</li>';
269
270        echo '</ul>';
271        echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=trend&amp;f='.$this->from.'&amp;t='.$this->to.'" />';
272        echo '</div>';
273
274
275        // top pages today
276        echo '<div>';
277        echo '<h2>Most popular pages</h2>';
278        $result = $this->sql_pages($this->tlimit,$this->start,15);
279        $this->html_resulttable($result);
280        echo '<a href="?do=admin&amp;page=statistics&amp;opt=page&amp;f='.$this->from.'&amp;t='.$this->to.'" class="more">more</a>';
281        echo '</div>';
282
283        // top referer today
284        echo '<div>';
285        echo '<h2>Newest incoming links</h2>';
286        $result = $this->sql_newreferer($this->tlimit,$this->start,15);
287        $this->html_resulttable($result);
288        echo '<a href="?do=admin&amp;page=statistics&amp;opt=newreferer&amp;f='.$this->from.'&amp;t='.$this->to.'" class="more">more</a>';
289        echo '</div>';
290
291        // top searches today
292        echo '<div>';
293        echo '<h2>Top search phrases</h2>';
294        $result = $this->sql_searchphrases($this->tlimit,$this->start,15);
295        $this->html_resulttable($result);
296        echo '<a href="?do=admin&amp;page=statistics&amp;opt=searchphrases&amp;f='.$this->from.'&amp;t='.$this->to.'" class="more">more</a>';
297        echo '</div>';
298
299        echo '</div>';
300    }
301
302    function html_country(){
303        echo '<div class="plg_stats_full">';
304        echo '<h2>Visitor\'s Countries</h2>';
305        echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=country&amp;f='.$this->from.'&amp;t='.$this->to.'" />';
306        $result = $this->sql_countries($this->tlimit,$this->start,150);
307        $this->html_resulttable($result,'',150);
308        echo '</div>';
309    }
310
311    function html_page(){
312        echo '<div class="plg_stats_full">';
313        echo '<h2>Popular Pages</h2>';
314        $result = $this->sql_pages($this->tlimit,$this->start,150);
315        $this->html_resulttable($result,'',150);
316        echo '</div>';
317    }
318
319    function html_browser(){
320        echo '<div class="plg_stats_full">';
321        echo '<h2>Browser Shootout</h2>';
322        echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=browser&amp;f='.$this->from.'&amp;t='.$this->to.'" />';
323        $result = $this->sql_browsers($this->tlimit,$this->start,150,true);
324        $this->html_resulttable($result,'',150);
325        echo '</div>';
326    }
327
328    function html_os(){
329        echo '<div class="plg_stats_full">';
330        echo '<h2>Operating Systems</h2>';
331        $result = $this->sql_os($this->tlimit,$this->start,150,true);
332        $this->html_resulttable($result,'',150);
333        echo '</div>';
334    }
335
336    function html_referer(){
337        echo '<div class="plg_stats_full">';
338        echo '<h2>Incoming Links</h2>';
339        $result = $this->sql_aggregate($this->tlimit);
340
341        $all    = $result['search']+$result['external']+$result['direct'];
342
343        if($all){
344            printf("<p>Of all %d external visits, %d (%.1f%%) were bookmarked (direct) accesses,
345                    %d (%.1f%%) came from search engines and %d (%.1f%%) were referred through
346                    links from other pages.</p>",$all,$result['direct'],(100*$result['direct']/$all),
347                    $result['search'],(100*$result['search']/$all),$result['external'],
348                    (100*$result['external']/$all));
349        }
350
351        $result = $this->sql_referer($this->tlimit,$this->start,150);
352        $this->html_resulttable($result,'',150);
353        echo '</div>';
354    }
355
356    function html_newreferer(){
357        echo '<div class="plg_stats_full">';
358        echo '<h2>New Incoming Links</h2>';
359        echo '<p>The following incoming links where first logged in the selected time frame,
360              and have never been seen before.</p>';
361
362        $result = $this->sql_newreferer($this->tlimit,$this->start,150);
363        $this->html_resulttable($result,'',150);
364        echo '</div>';
365    }
366
367    function html_outlinks(){
368        echo '<div class="plg_stats_full">';
369        echo '<h2>Outgoing Links</h2>';
370
371        $result = $this->sql_outlinks($this->tlimit,$this->start,150);
372        $this->html_resulttable($result,'',150);
373        echo '</div>';
374    }
375
376    function html_searchphrases(){
377        echo '<div class="plg_stats_full">';
378        echo '<h2>Search Phrases</h2>';
379
380        $result = $this->sql_searchphrases($this->tlimit,$this->start,150);
381        $this->html_resulttable($result,'',150);
382        echo '</div>';
383    }
384
385    function html_searchwords(){
386        echo '<div class="plg_stats_full">';
387        echo '<h2>Search Words</h2>';
388
389        $result = $this->sql_searchwords($this->tlimit,$this->start,150);
390        $this->html_resulttable($result,'',150);
391        echo '</div>';
392    }
393
394    function html_searchengines(){
395        echo '<div class="plg_stats_full">';
396        echo '<h2>Search Engines</h2>';
397
398        $result = $this->sql_searchengines($this->tlimit,$this->start,150);
399        $this->html_resulttable($result,'',150);
400        echo '</div>';
401    }
402
403
404    function html_resolution(){
405        echo '<div class="plg_stats_full">';
406        echo '<h2>Resolution</h2>';
407        $result = $this->sql_resolution($this->tlimit,$this->start,150);
408        $this->html_resulttable($result,'',150);
409
410        echo '<p>While the data above gives you some info about the resolution your visitors use, it does not tell you
411              much about about the real size of their browser windows. The graphic below shows the size distribution of
412              the view port (document area) of your visitor\'s browsers. Please note that this data can not be logged
413              in all browsers. Because users may resize their browser window while browsing your site the statistics may
414              be flawed. Take it with a grain of salt.</p>';
415
416        echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=view&amp;f='.$this->from.'&amp;t='.$this->to.'" />';
417        echo '</div>';
418    }
419
420
421    /**
422     * Display a result in a HTML table
423     */
424    function html_resulttable($result,$header='',$pager=0){
425        echo '<table>';
426        if(is_array($header)){
427            echo '<tr>';
428            foreach($header as $h){
429                echo '<th>'.hsc($h).'</th>';
430            }
431            echo '</tr>';
432        }
433
434        $count = 0;
435        if(is_array($result)) foreach($result as $row){
436            echo '<tr>';
437            foreach($row as $k => $v){
438                echo '<td class="plg_stats_X'.$k.'">';
439                if($k == 'page'){
440                    echo '<a href="'.wl($v).'" class="wikilink1">';
441                    echo hsc($v);
442                    echo '</a>';
443                }elseif($k == 'url'){
444                    $url = hsc($v);
445                    $url = preg_replace('/^https?:\/\/(www\.)?/','',$url);
446                    if(strlen($url) > 45){
447                        $url = substr($url,0,30).' &hellip; '.substr($url,-15);
448                    }
449                    echo '<a href="'.$v.'" class="urlextern">';
450                    echo $url;
451                    echo '</a>';
452                }elseif($k == 'lookup'){
453                    echo '<a href="http://www.google.com/search?q='.rawurlencode($v).'">';
454                    echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/search/google.png" alt="lookup in Google" border="0" />';
455                    echo '</a> ';
456
457                    echo '<a href="http://search.yahoo.com/search?p='.rawurlencode($v).'">';
458                    echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/search/yahoo.png" alt="lookup in Yahoo" border="0" />';
459                    echo '</a> ';
460
461                    echo '<a href="http://search.msn.com/results.aspx?q='.rawurlencode($v).'">';
462                    echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/search/msn.png" alt="lookup in MSN Live" border="0" />';
463                    echo '</a> ';
464
465                }elseif($k == 'engine'){
466                    include_once(dirname(__FILE__).'/inc/search_engines.php');
467                    echo $SearchEnginesHashLib[$v];
468                }elseif($k == 'browser'){
469                    include_once(dirname(__FILE__).'/inc/browsers.php');
470                    echo $BrowsersHashIDLib[$v];
471                }elseif($k == 'bflag'){
472                    include_once(dirname(__FILE__).'/inc/browsers.php');
473                    echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/browser/'.$BrowsersHashIcon[$v].'.png" alt="'.hsc($v).'" />';
474                }elseif($k == 'os'){
475                    if(empty($v)){
476                        echo 'unknown';
477                    }else{
478                        include_once(dirname(__FILE__).'/inc/operating_systems.php');
479                        echo $OSHashLib[$v];
480                    }
481                }elseif($k == 'osflag'){
482                    echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/os/'.hsc($v).'.png" alt="'.hsc($v).'" />';
483                }elseif($k == 'cflag'){
484                    echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/flags/'.hsc($v).'.png" alt="'.hsc($v).'" width="18" height="12" />';
485                }elseif($k == 'html'){
486                    echo $v;
487                }else{
488                    echo hsc($v);
489                }
490                echo '</td>';
491            }
492            echo '</tr>';
493
494            if($pager && ($count == $pager)) break;
495            $count++;
496        }
497        echo '</table>';
498
499        if($pager) $this->html_pager($pager,count($result) > $pager);
500    }
501
502    /**
503     * Create an image
504     */
505    function img_build($img){
506        include(dirname(__FILE__).'/inc/AGC.class.php');
507
508        switch($img){
509            case 'country':
510                // build top countries + other
511                $result = $this->sql_countries($this->tlimit,$this->start,0);
512                $data = array();
513                $top = 0;
514                foreach($result as $row){
515                    if($top < 7){
516                        $data[$row['country']] = $row['cnt'];
517                    }else{
518                        $data['other'] += $row['cnt'];
519                    }
520                    $top++;
521                }
522                $pie = new AGC(300, 200);
523                $pie->setProp("showkey",true);
524                $pie->setProp("showval",false);
525                $pie->setProp("showgrid",false);
526                $pie->setProp("type","pie");
527                $pie->setProp("keyinfo",1);
528                $pie->setProp("keysize",8);
529                $pie->setProp("keywidspc",-50);
530                $pie->setProp("key",array_keys($data));
531                $pie->addBulkPoints(array_values($data));
532                @$pie->graph();
533                $pie->showGraph();
534                break;
535            case 'browser':
536                // build top browsers + other
537                include_once(dirname(__FILE__).'/inc/browsers.php');
538
539                $result = $this->sql_browsers($this->tlimit,$this->start,0,false);
540                $data = array();
541                $top = 0;
542                foreach($result as $row){
543                    if($top < 5){
544                        $data[strip_tags($BrowsersHashIDLib[$row['ua_info']])] = $row['cnt'];
545                    }else{
546                        $data['other'] += $row['cnt'];
547                    }
548                    $top++;
549                }
550                $pie = new AGC(300, 200);
551                $pie->setProp("showkey",true);
552                $pie->setProp("showval",false);
553                $pie->setProp("showgrid",false);
554                $pie->setProp("type","pie");
555                $pie->setProp("keyinfo",1);
556                $pie->setProp("keysize",8);
557                $pie->setProp("keywidspc",-50);
558                $pie->setProp("key",array_keys($data));
559                $pie->addBulkPoints(array_values($data));
560                @$pie->graph();
561                $pie->showGraph();
562                break;
563            case 'view':
564
565                $graph = new AGC(400, 200);
566                $graph->setColor('color',0,'blue');
567                $graph->setColor('color',1,'red');
568                $graph->setProp("showkey",true);
569                $graph->setProp("key",'view port width',0);
570                $graph->setProp("key",'view port height',1);
571
572                $result = $this->sql_viewport($this->tlimit,0,0,true);
573                foreach($result as $row){
574                    $graph->addPoint($row['cnt'],$row['res_x'],0);
575                }
576
577                $result = $this->sql_viewport($this->tlimit,0,0,false);
578                foreach($result as $row){
579                    $graph->addPoint($row['cnt'],$row['res_y'],1);
580                }
581
582                @$graph->graph();
583                $graph->showGraph();
584
585                break;
586            case 'trend':
587                $hours  = ($this->from == $this->to);
588                $result = $this->sql_trend($this->tlimit,$hours);
589                $data1   = array();
590                $data2   = array();
591
592                $graph = new AGC(400, 150);
593                $graph->setProp("type","bar");
594                $graph->setProp("showgrid",false);
595                $graph->setProp("barwidth",.8);
596
597                $graph->setColor('color',0,'blue');
598                $graph->setColor('color',1,'red');
599                $graph->setColor('color',2,'yellow');
600
601                if($hours){
602                    //preset $hours
603                    for($i=0;$i<24;$i++){
604                        $data1[$i] = 0;
605                        $data2[$i] = 0;
606                        $data3[$i] = 0;
607                        $graph->setProp("scale",array(' 0h','   4h','   8h','    12h','    16h','    20h','    24h'));
608                    }
609                }else{
610                    $graph->setProp("scale",array(next(array_keys($data1)),$this->to));
611                }
612
613                foreach($result as $row){
614                    $data1[$row['time']] = $row['pageviews'];
615                    $data2[$row['time']] = $row['sessions'];
616                    $data3[$row['time']] = $row['visitors'];
617                }
618
619                foreach($data1 as $key => $val){
620                    $graph->addPoint($val,$key,0);
621                }
622                foreach($data2 as $key => $val){
623                    $graph->addPoint($val,$key,1);
624                }
625                foreach($data3 as $key => $val){
626                    $graph->addPoint($val,$key,2);
627                }
628
629                @$graph->graph();
630                $graph->showGraph();
631
632            default:
633                $this->sendGIF();
634        }
635    }
636
637
638    /**
639     * Return some aggregated statistics
640     */
641    function sql_aggregate($tlimit){
642        $data = array();
643
644        $sql = "SELECT ref_type, COUNT(*) as cnt
645                  FROM ".$this->getConf('db_prefix')."access as A
646                 WHERE $tlimit
647                   AND ua_type = 'browser'
648              GROUP BY ref_type";
649        $result = $this->runSQL($sql);
650
651        if(is_array($result)) foreach($result as $row){
652            if($row['ref_type'] == 'search')   $data['search']   = $row['cnt'];
653            if($row['ref_type'] == 'external') $data['external'] = $row['cnt'];
654            if($row['ref_type'] == 'internal') $data['internal'] = $row['cnt'];
655            if($row['ref_type'] == '')         $data['direct']   = $row['cnt'];
656        }
657
658        $sql = "SELECT COUNT(DISTINCT session) as sessions,
659                       COUNT(session) as views,
660                       COUNT(DISTINCT user) as users,
661                       COUNT(DISTINCT uid) as visitors
662                  FROM ".$this->getConf('db_prefix')."access as A
663                 WHERE $tlimit
664                   AND ua_type = 'browser'";
665        $result = $this->runSQL($sql);
666
667        $data['users']     = max($result[0]['users'] - 1,0); // subtract empty user
668        $data['sessions']  = $result[0]['sessions'];
669        $data['pageviews'] = $result[0]['views'];
670        $data['visitors']  = $result[0]['visitors'];
671
672        $sql = "SELECT COUNT(id) as robots
673                  FROM ".$this->getConf('db_prefix')."access as A
674                 WHERE $tlimit
675                   AND ua_type = 'robot'";
676        $result = $this->runSQL($sql);
677        $data['robots'] = $result[0]['robots'];
678
679        return $data;
680    }
681
682    /**
683     * standard statistics follow, only accesses made by browsers are counted
684     * for general stats like browser or OS only visitors not pageviews are counted
685     */
686    function sql_trend($tlimit,$hours=false){
687        if($hours){
688            $sql = "SELECT HOUR(dt) as time,
689                           COUNT(DISTINCT session) as sessions,
690                           COUNT(session) as pageviews,
691                           COUNT(DISTINCT uid) as visitors
692                      FROM ".$this->getConf('db_prefix')."access as A
693                     WHERE $tlimit
694                       AND ua_type = 'browser'
695                  GROUP BY HOUR(dt)
696                  ORDER BY time";
697        }else{
698            $sql = "SELECT DATE(dt) as time,
699                           COUNT(DISTINCT session) as sessions,
700                           COUNT(session) as pageviews,
701                            COUNT(DISTINCT uid) as visitors
702                      FROM ".$this->getConf('db_prefix')."access as A
703                     WHERE $tlimit
704                       AND ua_type = 'browser'
705                  GROUP BY DATE(dt)
706                  ORDER BY time";
707        }
708        return $this->runSQL($sql);
709    }
710
711    function sql_searchengines($tlimit,$start=0,$limit=20){
712        $sql = "SELECT COUNT(*) as cnt, engine
713                  FROM ".$this->getConf('db_prefix')."search as A
714                 WHERE $tlimit
715              GROUP BY engine
716              ORDER BY cnt DESC, engine".
717              $this->sql_limit($start,$limit);
718        return $this->runSQL($sql);
719    }
720
721    function sql_searchphrases($tlimit,$start=0,$limit=20){
722        $sql = "SELECT COUNT(*) as cnt, query, query as lookup
723                  FROM ".$this->getConf('db_prefix')."search as A
724                 WHERE $tlimit
725              GROUP BY query
726              ORDER BY cnt DESC, query".
727              $this->sql_limit($start,$limit);
728        return $this->runSQL($sql);
729    }
730
731    function sql_searchwords($tlimit,$start=0,$limit=20){
732        $sql = "SELECT COUNT(*) as cnt, word, word as lookup
733                  FROM ".$this->getConf('db_prefix')."search as A,
734                       ".$this->getConf('db_prefix')."searchwords as B
735                 WHERE $tlimit
736                   AND A.id = B.sid
737              GROUP BY word
738              ORDER BY cnt DESC, word".
739              $this->sql_limit($start,$limit);
740        return $this->runSQL($sql);
741    }
742
743    function sql_outlinks($tlimit,$start=0,$limit=20){
744        $sql = "SELECT COUNT(*) as cnt, link as url
745                  FROM ".$this->getConf('db_prefix')."outlinks as A
746                 WHERE $tlimit
747              GROUP BY link
748              ORDER BY cnt DESC, link".
749              $this->sql_limit($start,$limit);
750        return $this->runSQL($sql);
751    }
752
753    function sql_pages($tlimit,$start=0,$limit=20){
754        $sql = "SELECT COUNT(*) as cnt, page
755                  FROM ".$this->getConf('db_prefix')."access as A
756                 WHERE $tlimit
757                   AND ua_type = 'browser'
758              GROUP BY page
759              ORDER BY cnt DESC, page".
760              $this->sql_limit($start,$limit);
761        return $this->runSQL($sql);
762    }
763
764    function sql_referer($tlimit,$start=0,$limit=20){
765        $sql = "SELECT COUNT(*) as cnt, ref as url
766                  FROM ".$this->getConf('db_prefix')."access as A
767                 WHERE $tlimit
768                   AND ua_type = 'browser'
769                   AND ref_type = 'external'
770              GROUP BY ref_md5
771              ORDER BY cnt DESC, url".
772              $this->sql_limit($start,$limit);
773        return $this->runSQL($sql);
774    }
775
776    function sql_newreferer($tlimit,$start=0,$limit=20){
777        $sql = "SELECT COUNT(*) as cnt, ref as url
778                  FROM ".$this->getConf('db_prefix')."access as B,
779                       ".$this->getConf('db_prefix')."refseen as A
780                 WHERE $tlimit
781                   AND ua_type = 'browser'
782                   AND ref_type = 'external'
783                   AND A.ref_md5 = B.ref_md5
784              GROUP BY A.ref_md5
785              ORDER BY cnt DESC, url".
786              $this->sql_limit($start,$limit);
787        return $this->runSQL($sql);
788    }
789
790    function sql_countries($tlimit,$start=0,$limit=20){
791        $sql = "SELECT COUNT(DISTINCT session) as cnt, B.code AS cflag, B.country
792                  FROM ".$this->getConf('db_prefix')."access as A,
793                       ".$this->getConf('db_prefix')."iplocation as B
794                 WHERE $tlimit
795                   AND A.ip = B.ip
796              GROUP BY B.country
797              ORDER BY cnt DESC, B.country".
798              $this->sql_limit($start,$limit);
799        return $this->runSQL($sql);
800    }
801
802    function sql_browsers($tlimit,$start=0,$limit=20,$ext=true){
803        if($ext){
804            $sel = 'ua_info as bflag, ua_info as browser, ua_ver';
805            $grp = 'ua_info, ua_ver';
806        }else{
807            $grp = 'ua_info';
808            $sel = 'ua_info';
809        }
810
811        $sql = "SELECT COUNT(DISTINCT session) as cnt, $sel
812                  FROM ".$this->getConf('db_prefix')."access as A
813                 WHERE $tlimit
814                   AND ua_type = 'browser'
815              GROUP BY $grp
816              ORDER BY cnt DESC, ua_info".
817              $this->sql_limit($start,$limit);
818        return $this->runSQL($sql);
819    }
820
821    function sql_os($tlimit,$start=0,$limit=20){
822        $sql = "SELECT COUNT(DISTINCT session) as cnt, os as osflag, os
823                  FROM ".$this->getConf('db_prefix')."access as A
824                 WHERE $tlimit
825                   AND ua_type = 'browser'
826              GROUP BY os
827              ORDER BY cnt DESC, os".
828              $this->sql_limit($start,$limit);
829        return $this->runSQL($sql);
830    }
831
832    function sql_resolution($tlimit,$start=0,$limit=20){
833        $sql = "SELECT COUNT(DISTINCT session) as cnt, CONCAT(screen_x,'x',screen_y) as res
834                  FROM ".$this->getConf('db_prefix')."access as A
835                 WHERE $tlimit
836                   AND ua_type  = 'browser'
837                   AND screen_x != 0
838              GROUP BY screen_x, screen_y
839              ORDER BY cnt DESC, screen_x".
840              $this->sql_limit($start,$limit);
841        return $this->runSQL($sql);
842    }
843
844    function sql_viewport($tlimit,$start=0,$limit=20,$x=true){
845        if($x){
846            $col = 'view_x';
847            $res = 'res_x';
848        }else{
849            $col = 'view_y';
850            $res = 'res_y';
851        }
852
853        $sql = "SELECT COUNT(*) as cnt,
854                       ROUND($col/10)*10 as $res
855                  FROM ".$this->getConf('db_prefix')."access as A
856                 WHERE $tlimit
857                   AND ua_type  = 'browser'
858                   AND $col != 0
859              GROUP BY $res
860              ORDER BY cnt DESC, $res".
861              $this->sql_limit($start,$limit);
862        return $this->runSQL($sql);
863    }
864
865
866    /**
867     * Builds a limit clause
868     */
869    function sql_limit($start,$limit){
870        $start = (int) $start;
871        $limit = (int) $limit;
872        if($limit){
873            $limit += 1;
874            return " LIMIT $start,$limit";
875        }elseif($start){
876            return " OFFSET $start";
877        }
878        return '';
879    }
880
881    /**
882     * Return a link to the DB, opening the connection if needed
883     */
884    function dbLink(){
885        // connect to DB if needed
886        if(!$this->dblink){
887            $this->dblink = mysql_connect($this->getConf('db_server'),
888                                          $this->getConf('db_user'),
889                                          $this->getConf('db_password'));
890            if(!$this->dblink){
891                msg('DB Error: connection failed',-1);
892                return null;
893            }
894            // set utf-8
895            if(!mysql_db_query($this->getConf('db_database'),'set names utf8',$this->dblink)){
896                msg('DB Error: could not set UTF-8 ('.mysql_error($this->dblink).')',-1);
897                return null;
898            }
899        }
900        return $this->dblink;
901    }
902
903    /**
904     * Simple function to run a DB query
905     */
906    function runSQL($sql_string) {
907        $link = $this->dbLink();
908
909        $result = mysql_db_query($this->conf['db_database'],$sql_string,$link);
910        if(!$result){
911            msg('DB Error: '.mysql_error($link).' '.hsc($sql_string),-1);
912            return null;
913        }
914
915        $resultarray = array();
916
917        //mysql_db_query returns 1 on a insert statement -> no need to ask for results
918        if ($result != 1) {
919            for($i=0; $i< mysql_num_rows($result); $i++) {
920                $temparray = mysql_fetch_assoc($result);
921                $resultarray[]=$temparray;
922            }
923            mysql_free_result($result);
924        }
925
926        if (mysql_insert_id($link)) {
927            $resultarray = mysql_insert_id($link); //give back ID on insert
928        }
929
930        return $resultarray;
931    }
932
933    /**
934     * Returns a short name for a User Agent and sets type, version and os info
935     */
936    function ua_info($ua,&$type,&$ver,&$os){
937        $ua = strtr($ua,' +','__');
938        $ua = strtolower($ua);
939
940        // common browsers
941        $regvermsie     = '/msie([+_ ]|)([\d\.]*)/i';
942        $regvernetscape = '/netscape.?\/([\d\.]*)/i';
943        $regverfirefox  = '/firefox\/([\d\.]*)/i';
944        $regversvn      = '/svn\/([\d\.]*)/i';
945        $regvermozilla  = '/mozilla(\/|)([\d\.]*)/i';
946        $regnotie       = '/webtv|omniweb|opera/i';
947        $regnotnetscape = '/gecko|compatible|opera|galeon|safari/i';
948
949        $name = '';
950        # IE ?
951        if(preg_match($regvermsie,$ua,$m) && !preg_match($regnotie,$ua)){
952            $type = 'browser';
953            $ver  = $m[2];
954            $name = 'msie';
955        }
956        # Firefox ?
957        elseif (preg_match($regverfirefox,$ua,$m)){
958            $type = 'browser';
959            $ver  = $m[1];
960            $name = 'firefox';
961        }
962        # Subversion ?
963        elseif (preg_match($regversvn,$ua,$m)){
964            $type = 'rcs';
965            $ver  = $m[1];
966            $name = 'svn';
967        }
968        # Netscape 6.x, 7.x ... ?
969        elseif (preg_match($regvernetscape,$ua,$m)){
970            $type = 'browser';
971            $ver  = $m[1];
972            $name = 'netscape';
973        }
974        # Netscape 3.x, 4.x ... ?
975        elseif(preg_match($regvermozilla,$ua,$m) && !preg_match($regnotnetscape,$ua)){
976            $type = 'browser';
977            $ver  = $m[2];
978            $name = 'netscape';
979        }else{
980            include(dirname(__FILE__).'/inc/browsers.php');
981            foreach($BrowsersSearchIDOrder as $regex){
982                if(preg_match('/'.$regex.'/',$ua)){
983                    // it's a browser!
984                    $type = 'browser';
985                    $name = strtolower($regex);
986                    break;
987                }
988            }
989        }
990
991        // check versions for Safari and Opera
992        if($name == 'safari'){
993            if(preg_match('/safari\/([\d\.]*)/i',$ua,$match)){
994                $ver = $BrowsersSafariBuildToVersionHash[$match[1]];
995            }
996        }elseif($name == 'opera'){
997            if(preg_match('/opera[\/ ]([\d\.]*)/i',$ua,$match)){
998                $ver = $match[1];
999            }
1000        }
1001
1002
1003        // check OS for browsers
1004        if($type == 'browser'){
1005            include(dirname(__FILE__).'/inc/operating_systems.php');
1006            foreach($OSSearchIDOrder as $regex){
1007                if(preg_match('/'.$regex.'/',$ua)){
1008                    $os = $OSHashID[$regex];
1009                    break;
1010                }
1011            }
1012
1013        }
1014
1015        // are we done now?
1016        if($name) return $name;
1017
1018        include(dirname(__FILE__).'/inc/robots.php');
1019        foreach($RobotsSearchIDOrder as $regex){
1020            if(preg_match('/'.$regex.'/',$ua)){
1021                    // it's a robot!
1022                    $type = 'robot';
1023                    return strtolower($regex);
1024            }
1025        }
1026
1027        // dunno
1028        return '';
1029    }
1030
1031    /**
1032     * Log search queries
1033     */
1034    function log_search($referer,&$type){
1035        $referer = strtolower($referer);
1036        $ref     = strtr($referer,' +','__');
1037
1038        include(dirname(__FILE__).'/inc/search_engines.php');
1039
1040        foreach($SearchEnginesSearchIDOrder as $regex){
1041            if(preg_match('/'.$regex.'/',$ref)){
1042                if(!$NotSearchEnginesKeys[$regex] ||
1043                   !preg_match('/'.$NotSearchEnginesKeys[$regex].'/',$ref)){
1044                    // it's a search engine!
1045                    $type = 'search';
1046                    break;
1047                }
1048            }
1049        }
1050        if($type != 'search') return; // we're done here
1051
1052        // extract query
1053        $engine = $SearchEnginesHashID[$regex];
1054        $param = $SearchEnginesKnownUrl[$engine];
1055        if($param && preg_match('/'.$param.'(.*?)[&$]/',$referer,$match)){
1056            $query = array_pop($match);
1057        }elseif(preg_match('/'.$WordsToExtractSearchUrl.'(.*?)[&$]/',$referer,$match)){
1058            $query = array_pop($match);
1059        }
1060        if(!$query) return; // we failed
1061
1062        // clean the query
1063        $query = preg_replace('/^(cache|related):[^\+]+/','',$query);  // non-search queries
1064        $query = preg_replace('/%0[ad]/',' ',$query);                  // LF CR
1065        $query = preg_replace('/%2[02789abc]/',' ',$query);            // space " ' ( ) * + ,
1066        $query = preg_replace('/%3a/',' ',$query);                     // :
1067        $query = strtr($query,'+\'()"*,:','        ');                 // badly encoded
1068        $query = preg_replace('/ +/',' ',$query);                      // ws compact
1069        $query = trim($query);
1070        $query = urldecode($query);
1071        if(!utf8_check($query)) $query = utf8_encode($query);          // assume latin1 if not utf8
1072        $query = utf8_strtolower($query);
1073
1074        // log it!
1075        $page  = addslashes($_REQUEST['p']);
1076        $query = addslashes($query);
1077        $sql  = "INSERT INTO ".$this->getConf('db_prefix')."search
1078                    SET dt       = NOW(),
1079                        page     = '$page',
1080                        query    = '$query',
1081                        engine   = '$engine'";
1082        $id = $this->runSQL($sql);
1083        if(is_null($id)){
1084            global $MSG;
1085            print_r($MSG);
1086            return;
1087        }
1088
1089        // log single keywords
1090        $words = explode(' ',utf8_stripspecials($query,' ','\._\-:\*'));
1091        foreach($words as $word){
1092            if(!$word) continue;
1093            $word = addslashes($word);
1094            $sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."searchwords
1095                       SET sid  = $id,
1096                           word = '$word'";
1097            $ok = $this->runSQL($sql);
1098            if(is_null($ok)){
1099                global $MSG;
1100                print_r($MSG);
1101            }
1102        }
1103    }
1104
1105    /**
1106     * Resolve IP to country/city
1107     */
1108    function log_ip($ip){
1109        // check if IP already known and up-to-date
1110        $sql = "SELECT ip
1111                  FROM ".$this->getConf('db_prefix')."iplocation
1112                 WHERE ip ='".addslashes($ip)."'
1113                   AND lastupd > DATE_SUB(CURDATE(),INTERVAL 30 DAY)";
1114        $result = $this->runSQL($sql);
1115        if($result[0]['ip']) return;
1116
1117        $http = new DokuHTTPClient();
1118        $http->timeout = 10;
1119        $data = $http->get('http://api.hostip.info/get_html.php?ip='.$ip);
1120
1121        if(preg_match('/^Country: (.*?) \((.*?)\)\nCity: (.*?)$/s',$data,$match)){
1122            $country = addslashes(trim($match[1]));
1123            $code    = addslashes(strtolower(trim($match[2])));
1124            $city    = addslashes(trim($match[3]));
1125            $host    = addslashes(gethostbyaddr($ip));
1126            $ip      = addslashes($ip);
1127
1128            $sql = "REPLACE INTO ".$this->getConf('db_prefix')."iplocation
1129                        SET ip = '$ip',
1130                            country = '$country',
1131                            code    = '$code',
1132                            city    = '$city',
1133                            host    = '$host'";
1134            $this->runSQL($sql);
1135        }
1136    }
1137
1138    /**
1139     * log a click on an external link
1140     *
1141     * called from log.php
1142     */
1143    function log_outgoing(){
1144        if(!$_REQUEST['ol']) return;
1145
1146        $link_md5 = md5($link);
1147        $link     = addslashes($_REQUEST['ol']);
1148        $session  = addslashes(session_id());
1149        $page     = addslashes($_REQUEST['p']);
1150
1151        $sql  = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."outlinks
1152                    SET dt       = NOW(),
1153                        session  = '$session',
1154                        page     = '$page',
1155                        link_md5 = '$link_md5',
1156                        link     = '$link'";
1157        $ok = $this->runSQL($sql);
1158        if(is_null($ok)){
1159            global $MSG;
1160            print_r($MSG);
1161        }
1162    }
1163
1164    /**
1165     * log a page access
1166     *
1167     * called from log.php
1168     */
1169    function log_access(){
1170        if(!$_REQUEST['p']) return;
1171
1172        # FIXME check referer against blacklist and drop logging for bad boys
1173
1174        // handle referer
1175        $referer = trim($_REQUEST['r']);
1176        if($referer){
1177            $ref     = addslashes($referer);
1178            $ref_md5 = ($ref) ? md5($referer) : '';
1179            if(strpos($referer,DOKU_URL) === 0){
1180                $ref_type = 'internal';
1181            }else{
1182                $ref_type = 'external';
1183                $this->log_search($referer,$ref_type);
1184            }
1185        }else{
1186            $ref      = '';
1187            $ref_md5  = '';
1188            $ref_type = '';
1189        }
1190
1191        // handle user agent
1192        $agent   = trim($_SERVER['HTTP_USER_AGENT']);
1193
1194        $ua      = addslashes($agent);
1195        $ua_type = '';
1196        $ua_ver  = '';
1197        $os      = '';
1198        $ua_info = addslashes($this->ua_info($agent,$ua_type,$ua_ver,$os));
1199
1200        $page    = addslashes($_REQUEST['p']);
1201        $ip      = addslashes($_SERVER['REMOTE_ADDR']);
1202        $sx      = (int) $_REQUEST['sx'];
1203        $sy      = (int) $_REQUEST['sy'];
1204        $vx      = (int) $_REQUEST['vx'];
1205        $vy      = (int) $_REQUEST['vy'];
1206        $js      = (int) $_REQUEST['js'];
1207        $uid     = addslashes($_REQUEST['uid']);
1208        $user    = addslashes($_SERVER['REMOTE_USER']);
1209        $session = addslashes(session_id());
1210        if(!$uid) $uid = $session;
1211
1212        $sql  = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."access
1213                    SET dt       = NOW(),
1214                        page     = '$page',
1215                        ip       = '$ip',
1216                        ua       = '$ua',
1217                        ua_info  = '$ua_info',
1218                        ua_type  = '$ua_type',
1219                        ua_ver   = '$ua_ver',
1220                        os       = '$os',
1221                        ref      = '$ref',
1222                        ref_md5  = '$ref_md5',
1223                        ref_type = '$ref_type',
1224                        screen_x = '$sx',
1225                        screen_y = '$sy',
1226                        view_x   = '$vx',
1227                        view_y   = '$vy',
1228                        js       = '$js',
1229                        user     = '$user',
1230                        session  = '$session',
1231                        uid      = '$uid'";
1232        $ok = $this->runSQL($sql);
1233        if(is_null($ok)){
1234            global $MSG;
1235            print_r($MSG);
1236        }
1237
1238        $sql = "INSERT DELAYED IGNORE INTO ".$this->getConf('db_prefix')."refseen
1239                   SET ref_md5  = '$ref_md5',
1240                       dt       = NOW()";
1241        $ok = $this->runSQL($sql);
1242        if(is_null($ok)){
1243            global $MSG;
1244            print_r($MSG);
1245        }
1246
1247        // resolve the IP
1248        $this->log_ip($_SERVER['REMOTE_ADDR']);
1249    }
1250
1251    /**
1252     * Just send a 1x1 pixel blank gif to the browser
1253     *
1254     * @called from log.php
1255     *
1256     * @author Andreas Gohr <andi@splitbrain.org>
1257     * @author Harry Fuecks <fuecks@gmail.com>
1258     */
1259    function sendGIF(){
1260        $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7');
1261        header('Content-Type: image/gif');
1262        header('Content-Length: '.strlen($img));
1263        header('Connection: Close');
1264        print $img;
1265        flush();
1266        // Browser should drop connection after this
1267        // Thinks it's got the whole image
1268    }
1269
1270}
1271