*/
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 .= ''. $num .' | '.$name .' | ' . $accesses .' | ';
if($other) {
$other = $js . $other . '';
$renderer->doc .= ''. $other . ' | ';
}
$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 .= "
\n";
foreach($browsers as $browser=>$val) {
$num++;
$renderer->doc .= $this->row($browser, $val,$num);
$renderer->doc .= "";
$v = $this->get_subversions($browser,$version);
$this->table($v,$renderer, false,false);
$renderer->doc .= ' |
';
}
$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;
}
?>