*/ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); if(!defined('QUICK_STATS')) define ('QUICK_STATS',DOKU_PLUGIN . 'quickstats/'); //require_once('GEOIP/ccArraysDat.php'); //error_reporting(E_ALL); //ini_set('display_errors','1'); /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_quickstats extends DokuWiki_Syntax_Plugin { private $page_file; private $ip_file; private $misc_data_file; private $pages; private $ips; private $misc_data; private $cc_arrays; private $long_names =-1; private $show_date; private $ua_file; private $ua_data; private $giCity; private $SEP = '/'; private $helper; function __construct() { $this->long_names = $this->getConf('long_names'); if(!isset($this->long_names) || $this->long_names <= 0) $this->long_names = false; $this->show_date=$this->getConf('show_date'); if( preg_match('/WINNT/i', PHP_OS) ) { $this->SEP='\\'; } $this->helper = & plugin_load('helper', 'quickstats'); $this->cc_arrays = $this->helper->get_cc_arrays(); } /** * Get an associative array with plugin info. */ function getInfo(){ $pname = $this->getPluginName(); $info = DOKU_PLUGIN.'/'.$pname.'/plugin.info.txt'; if(@file_exists($info)) { return parent::getInfo(); } return array( 'author' => 'Myron Turner', 'email' => 'turnermm02@shaw.ca', 'date' => '2011-11-02', 'name' => 'Quickstats Plugin', 'desc' => 'Output browser/user stats to wiki page', 'url' => 'http://www.dokuwiki.org/plugin:quickstats', ); } /** * Get the type of syntax this plugin defines. */ function getType(){ return 'substition'; } /** * What kind of syntax do we allow (optional) */ // function getAllowedTypes() { // return array(); // } /** * Define how this plugin is handled regarding paragraphs. * * normal: The plugin can be used inside paragraphs. * block: Open paragraphs need to be closed before plugin output. * stack (Special case): Plugin wraps other paragraphs. */ function getPType(){ return 'block'; } /** * Where to sort in? * */ function getSort(){ return 100; } /** * Connect lookup pattern to lexer. * * @param $aMode String The desired rendermode. * @return none * @public * @see render() */ function connectTo($mode) { $this->Lexer->addSpecialPattern('~~QUICKSTATS:.*?~~',$mode,'plugin_quickstats'); } // function postConnect() { // $this->Lexer->addExitPattern('','plugin_quickstats'); // } /** * Handler to prepare matched data for the rendering process. * */ function handle($match, $state, $pos, Doku_Handler $handler){ global $ID; $this->helper->writeCache($ID); switch ($state) { case DOKU_LEXER_SPECIAL : $match = trim(substr($match,13,-2)); if($match) { if($match == "total") { return array('total',"",""); } $depth = false; if(strpos($match,';;') !== false) { list($match,$depth) = explode(';;',$match); } $date = ""; if(strpos($match,'&') !== false) { /* catch syntax errors assumes single parameter with trailing or prepended & */ if($match[strlen($match)-1] == '&' || $match[0] == '&') { $match = trim($match,'&'); if($this->is_date_string($match)) { return array('basics',$match,$depth); } return array('basics',"",$depth); } /* process valid paramter string */ list($m1,$m2) = explode('&',$match,2); if($this->is_date_string($m1)) { $date=$m1; $match = $m2; } else { $date=$m2; $match = $m1; } } else if($this->is_date_string($match)) { $date = $match; $match = 'basics'; } } else { return array('basics',"",$depth); } return array(strtolower($match),$date,$depth); break; } return array(); } function is_date_string($str) { return preg_match('/\d+_\d\d\d/',$str); } /** * Handle the actual output creation. */ function render($mode, Doku_Renderer $renderer, $data) { if($mode == 'xhtml'){ list($which, $date_str,$depth) = $data; if($which == 'total') { $renderer->doc .= "" . $this->getTotal() .""; return true; } $this->row_depth('all'); if($depth) { $this->row_depth($depth); } $this->load_data($date_str,$which); if($which == 'basics') { $renderer->doc .= "
" ; } else { $class = "quickstats $which"; $renderer->doc .= "
"; } switch ($which) { case 'basics': $this->misc_data_xhtml($renderer); $this->pages_xhtml($renderer); break; case 'ip': $this->ip_xhtml($renderer); break; case 'pages': $this->pages_xhtml($renderer,true); break; case 'misc': $this->misc_data_xhtml($renderer,true,'misc'); break; case 'countries': $this->misc_data_xhtml($renderer,true,'country'); break; case 'ua': $this->ua_xhtml($renderer); break; } $renderer->doc .= "
" ; return true; } return false; } function sort(&$array) { if(!isset($array)) { $array = array(); return; } uasort($array, 'QuickStatsCmp'); } function extended_row($num=" ", $cells, $styles="") { $style = ""; if($styles) $style = "style = '$styles' "; $row = "$num  "; foreach($cells as $cell_data) { $row .= "$cell_data"; } $row .= ''; return $row; } function row($name,$val,$num=" ",$date=false,$is_ip=false) { $title = ""; $ns = $name; if($is_ip && $this->giCity) { $record = geoip_record_by_addr($this->giCity, $name); $title = $record->country_name; if(isset($this->ua_data[$name])) { $title .= ' (' . $this->ua_data[$name][1] .')'; } } elseif($this->long_names && (@strlen($name) > $this->long_names)) { $title = "$name"; $name = substr($name,0,$this->long_names) . '...'; } if($date) { $date = date('r',$date); $title = "$title $date"; } if($title && $is_ip) { $name = "$name"; } else if(is_numeric($num) && $date !== false) { $name = "$name"; } else if ($title) { $name = "$name"; } return "$num  $name    $val\n"; } function row_depth($new_depth=false) { STATIC $depth = false; if($new_depth !== false) { $depth = $new_depth; return; } return $depth; } function load_data($date_str=null,$which) { global $uasort_ip; $today = getdate(); if($date_str) { list($mon,$yr) = explode('_',$date_str); $today['mon'] = $mon; $today['year'] = $yr; } $ns_prefix = "quickstats:"; $ns = $ns_prefix . $today['mon'] . '_' . $today['year'] . ':'; $this->page_file = metaFN($ns . 'pages' ,'.ser'); $this->ip_file = metaFN($ns . 'ip' , '.ser'); $this->misc_data_file = metaFN($ns . 'misc_data' , '.ser'); $this->ua_file = metaFN($ns . 'ua' , '.ser'); if($which == 'basics' || $which == 'pages') { $this->pages = unserialize(io_readFile($this->page_file,false)); if(!$this->pages) $this->pages = array(); } if($which == 'basics' || $which == 'ip') { $this->ips = unserialize(io_readFile($this->ip_file,false)); if(!$this->ips) $this->ips = array(); } if($which == 'basics' || $which == 'countries' || $which == 'misc') { $this->misc_data = unserialize(io_readFile($this->misc_data_file,false)); if(!$this->misc_data) $this->misc_data = array(); } if($which == 'ua' || $which == 'ip') { $this->ua_data = unserialize(io_readFile($this->ua_file,false)); if(!$this->ua_data) $this->ua_data = array(); if($which == 'ip') { $this->ips = unserialize(io_readFile($this->ip_file,false)); if(!$this->ips) $this->ips = array(); } else { $uasort_ip = unserialize(io_readFile($this->ip_file,false)); if(!$uasort_ip) $uasort_ip = array(); } } } function geoipcity_ini() { if($this->getConf('geoplugin')) { return; } require_once("GEOIP/geoipcity.inc"); if($this->getConf('geoip_local') && file_exists(QUICK_STATS. 'GEOIP/GeoLiteCity.dat')) { $this->giCity = geoip_open(QUICK_STATS. 'GEOIP/GeoLiteCity.dat',GEOIP_STANDARD); } else { $gcity_dir = $this->getConf('geoip_dir'); $gcity_dat=rtrim($gcity_dir, "\040,/\\") . $this->SEP . 'GeoLiteCity.dat'; if(!file_exists( $gcity_dat)) return; $this->giCity = geoip_open($gcity_dat,GEOIP_STANDARD); } } function table($data,&$renderer,$numbers=true,$date=false,$ip_array=false) { if($numbers !== false) $num = 0; else $num = " "; if($ip_array) $this->geoipcity_ini(); $ttl = 0; $depth = $this->row_depth(); if($depth == 'all') $depth = 0; if($ip_array) { $this->theader($renderer, 'IP'); } else if ($date && $numbers) { $this->theader($renderer, 'Page'); } else $renderer->doc .= "\n"; foreach($data as $item=>$count) { if($numbers) $num++; $ttl += $count; if($depth && $num > $depth) continue; $md5 =md5($item); $date_str = (is_array($date) && isset($date[$md5]) ) ? $date[$md5] : false; $renderer->doc .= $this->row($item,$count,$num,$date_str, $ip_array); } $renderer->doc .= "
\n"; return $ttl; } function theader(&$renderer,$name,$accesses='Accesses',$num=" Num ",$other="") { if($accesses=='Accesses') $accesses=$this->getLang('accesses'); $renderer->doc .= "\n"; $js = ""; $num = $js . $num . ''; $name = $js . $name . ''; $accesses = $js . $accesses . ''; $renderer->doc .= ''; if($other) { $other = $js . $other . ''; $renderer->doc .= ''; } $renderer->doc .=''; } function ip_xhtml(&$renderer) { $uniq = $this->ips['uniq']; unset($this->ips['uniq']); $this->sort($this->ips); // $renderer->doc .= '
'; $renderer->doc .= '' .$this->getLang('uniq_ip') .''; $total_accesses = $this->table($this->ips,$renderer,true,true,true); $renderer->doc .= "" .$this->getLang('ttl_accesses') . "$total_accesses
\n"; $renderer->doc .= "" .$this->getLang('ttl_uniq_ip') ."$uniq
\n"; // $renderer->doc .= "
\n"; } function pages_xhtml(&$renderer, $no_align=false) { if(!$this->pages) return array(); $this->sort($this->pages['page']); if($no_align) { $renderer->doc .= '
'; } else { //$renderer->doc .= '
'; $renderer->doc .= '
'; } $renderer->doc .= ''. $this->getLang('label_page_access') .''; $date =($this->show_date && isset($this->pages['date'] )) ? $this->pages['date'] : false; $page_count = $this->table($this->pages['page'],$renderer,true,$date); $renderer->doc .= "" . $this->getLang('pages_accessed') . count($this->pages['page']) . "
"; $renderer->doc .= "". $this->getLang('ttl_accesses') . $this->pages['site_total'] .''; $renderer->doc .= "
\n"; } function misc_data_xhtml(&$renderer,$no_align=false,$which='all') { $renderer->doc .= "\n"; if($which == 'all' || $which == 'misc') { $browsers = $this->misc_data['browser']; $platform = $this->misc_data['platform']; $version = $this->misc_data['version']; $this->sort($browsers); $this->sort($platform); $this->sort($version); $renderer->doc .= "\n\n\n"; if($no_align) { $renderer->doc .= '
'; } else { //$renderer->doc .= '
'; $renderer->doc .= '
'; } $renderer->doc .="\n\n"; $renderer->doc .= '
' . $this->getLang('browsers') .''; $num=0; $renderer->doc .= "
'. $num .''.$name .'' . $accesses .''. $other . '
\n"; foreach($browsers as $browser=>$val) { $num++; $renderer->doc .= $this->row($browser, $val,$num); $renderer->doc .= "'; } $renderer->doc .= "
"; $v = $this->get_subversions($browser,$version); $this->table($v,$renderer, false,false); $renderer->doc .= '
\n\n"; $renderer->doc .= 'Platforms'; $this->table($platform,$renderer); $renderer->doc .= "
\n\n\n"; } if($which == 'misc') return; $countries = $this->misc_data['country']; $this->sort($countries); if($no_align) { $renderer->doc .= '
'; } else { // $renderer->doc .= "
"; $renderer->doc .= "
"; } $renderer->doc .= 'Countries'; $this->theader($renderer, $this->getLang('country') ); $num = 0; $total = 0; $depth = $this->row_depth(); if($depth == 'all') $depth = false; foreach($countries as $cc=>$count) { if(!$cc) continue; $num++; $total+=$count; $cntry=$this->cc_arrays->get_country_name($cc) ; if($depth == false) { $renderer->doc .= $this->row($cntry,$count,$num); } else if ($num <= $depth) { $renderer->doc .= $this->row($cntry,$count,$num); } } $renderer->doc .= ''; $renderer->doc .= "" .$this->getLang('ttl_countries') . count($this->misc_data['country']) . "
"; $renderer->doc .= "" . $this->getLang('ttl_accesses') ."$total
"; $renderer->doc .= "
\n"; } function getTotal() { $meta_path = $this->helper->metaFilePath(true) ; $page_totals = unserialize(io_readFile($meta_path . 'page_totals.ser')); $page_accessesTotal = 0; if(!$page_totals) $page_totals = array(); if(!empty($page_totals)) { foreach($page_totals as $ttl) { $page_accessesTotal+=$ttl; } } return $page_accessesTotal; } function get_subversions($a,$b) { $tmp = array(); foreach($b as $key=>$val) { if(strpos($key,$a) !== false) { $tmp[$key] = $val; } } $this->sort($tmp); return $tmp; } /* this sorts ua array by ip accesses * the keys to both ua and ip arrays are the ip addresses * $a and $b in ua_Cmp($a,$b) are ip addresses, so $uasort_ip[$a] = number of accesses for ip $a */ function ua_sort(&$array) { global $uasort_ip; function ua_Cmp($a, $b) { global $uasort_ip; $na = $uasort_ip[$a]; $nb = $uasort_ip[$b]; if ($na == $nb) { return 0; } return ($na > $nb) ? -1 : 1; } uksort($array, 'ua_Cmp'); } function ua_xhtml(&$renderer) { global $uasort_ip; // sorted IP=>acceses $depth = $this->row_depth(); if($depth == 'all') $depth = false; $asize = count($this->ua_data); if($depth !== false) { $this->ua_sort($this->ua_data); if($depth > $asize) $depth = $asize; $header = " ($depth/$asize) "; } else { $header = " ($asize/$asize) "; } $total_accesses = $this->ua_data['counts'] ; unset($this->ua_data['counts']); $renderer->doc .="\n\n
\n"; $styles = " padding-bottom: 4px; "; $renderer->doc .= '
'. $this->getLang('browsers_and_ua') . $header .''; $n = 0; $this->theader($renderer,'IP', $this->getLang('country')," " . $this->getLang('accesses'). " ", " User Agents "); foreach($this->ua_data as $ip=>$data) { $n++; if($depth !== false && $n > $depth) break; $cc = array_shift($data); $country=$this->cc_arrays->get_country_name($cc) ; $uas = '    ' . implode(', ',$data); $renderer->doc .= $this->extended_row($uasort_ip[$ip], array($ip, "  $country",$uas), $styles); } $renderer->doc .= "\n"; // Output total table $renderer->doc .= '
' . $this->getLang('ttl_accesses_ua') .'
'; $n=0; $this->theader($renderer,"    Agents    "); foreach($total_accesses as $agt=>$cnt) { $n++; if($depth !== false && $n > $depth) continue; $renderer->doc .= "$n$agt   $cnt\n"; } $renderer->doc .= "
\n\n"; } } function QuickStatsCmp($a, $b) { if ($a == $b) { return 0; } return ($a > $b) ? -1 : 1; } ?>