1<?php 2 /** 3 * Wiki Statistics Plugin: Displays some wiki stats 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @original author Paco Avila (Monkiki) <monkiki@gmail.com> 7 * @author Emanuele <emanuele45@interfree.it> 8 * @contributor Thomas <thomas(dot)delhomenie(at)gmail(dot)com> 9 * @patched by Matthieu <matthieu(dot)rioteau(at)skf(dot)com> 10 * (2009/11/10) - Patch correct bad behavior in "bymonth" function where comparison of months considered October (#10) to be lesser than February (#2) because of leading zeros suppression 11 * (2010/01/05) - Still problems with date comparison -> solved by comparing integer representation of dates 12 * - Missing double quotes in "histoContribByMonth" function 13 * - Bad behavior of "toBeCounted" function when no namespace is excluded 14 * (2010/01/29) - Bad behavior of "toBeCounted" function when no namespace is excluded (cont'd) 15 * - In "getAllChanges" function, add "htmlspecialchars" on summary storage so that summaries with single quote inside don't trigger PHP error 16 * @patched by Matthias Grimm <matthiasgrimm(at)users(dot)sourceforge(dot)net> 17 * (2009/12/11) - Patch correct full name not correctly displayed on HoF with non-plain auth system, now all authentication systems are supported + code cleanup 18 * @patched by Frank M.G. Joergense <frank(at)gajda(dot)dk> 19 * (19/02/2010) - Method to list all events - Creates, Edits, Deletes and Reverts 20 */ 21 22if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 23if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 24require_once(DOKU_PLUGIN.'syntax.php'); 25 26// Standard inclusions 27if(!class_exists('syntax_plugin_charter')){ 28 if(!class_exists('pData')){ 29 include(DOKU_PLUGIN.'wikistatistics/pChart/pData.class'); 30 } 31 if(!class_exists('pChart')){ 32 include(DOKU_PLUGIN.'wikistatistics/pChart/pChart.class'); 33 } 34} 35 36/** 37 * All DokuWiki plugins to extend the parser/rendering mechanism 38 * need to inherit from this class 39 */ 40class syntax_plugin_wikistatistics extends DokuWiki_Syntax_Plugin { 41 var $allChanges = array(); 42 var $allPagesSizes = array(); 43 var $localExcludedns = ''; 44 var $localExcludedns_pattern = ''; 45 var $localExcludedpg_pattern = ''; 46 47 var $excludedNs = ''; 48 var $excludedNsPattern = ''; 49 var $excludedpg_pattern = ''; 50 51 var $initOpt = ''; 52 53 /** 54 * return some info 55 */ 56 function getInfo(){ 57 return array( 58 'author' => 'Emanuele, Thomas', 59 'email' => 'emanuele45@interfree.it', 60 'date' => '2010-01-24', 61 'name' => 'WikiStatistics', 62 'desc' => 'Display statistics about the Wiki and their users', 63 'url' => 'http://lacroa.altervista.org/dokucount/', 64 ); 65 } 66 67 /** 68 * What kind of syntax are we? 69 */ 70 function getType(){ 71 return 'substition'; 72 } 73 74 /** 75 * Paragraph Type 76 * 77 * Defines how this syntax is handled regarding paragraphs. This is important 78 * for correct XHTML nesting. Should return one of the following: 79 * 80 * 'normal' - The plugin can be used inside paragraphs 81 * 'block' - Open paragraphs need to be closed before plugin output 82 * 'stack' - Special case. Plugin wraps other paragraphs. 83 * 84 * @see Doku_Handler_Block 85 */ 86 function getPType() { 87 return 'normal'; 88 } 89 90 /** 91 * Where to sort in? 92 */ 93 function getSort(){ 94 return 210; 95 } 96 97 /** 98 * Connect pattern to lexer 99 */ 100 function connectTo($mode) { 101 $this->Lexer->addSpecialPattern('{{wikistatistics>.*?}}',$mode,'plugin_wikistatistics'); // <= current syntax 102 } 103 104 /** 105 * Handle the match 106 */ 107 function handle($match, $state, $pos, &$handler){ 108 //convert $match into an array of parameters 109 110 $match = trim($match, '{}'); 111 $match = substr($match,strpos($match,'>')+1); 112 113 $explodedMatch = explode(' ', $match); 114 115 $params = array(); 116 117 for($i = 0; $i < sizeof($explodedMatch); $i++) { 118 $param = trim($explodedMatch[$i]); 119 $key = substr($param,0,strpos($param,'=')); 120 $value = substr($param,strpos($param,'=')+1); 121 $params[$key] = $value; 122 } 123 124 return $params; 125 } 126 127 /** 128 * Create output 129 */ 130 function render($mode, &$renderer, $data) { 131 if($mode == 'xhtml') 132 { 133 $this->varinit($data); 134 135 switch ($data['type']) { 136 case 'topcontrib': //HoF 137 case 'hof': 138 $renderer->doc .= $this->getTopContrib(); 139 break; 140 case 'histocontrib': 141 $renderer->doc .= $this->histoContrib(); 142 break; 143 case 'pages': //Total number of pages 144 $renderer->doc .= $this->countPages($this->initOpt['ns']); 145 break; 146 case 'users': //Total number of users 147 $renderer->doc .= $this->countUsers(); 148 break; 149 case 'pagessizes': // 150 $renderer->doc .= $this->pagesSizes();//TODO add $this->initOpt['ns'] 151 break; 152 case 'hofpagessizes': 153 $renderer->doc .= $this->getTopPagesSizes();//TODO add $this->initOpt['ns'] 154 break; 155 case 'topedit': 156 $renderer->doc .= $this->getTopChanged($this->initOpt['ns']); 157 break; 158 case 'lessedit': 159 $renderer->doc .= $this->getTopChanged($this->initOpt['ns'],true); 160 break; 161 } 162 unset($this->allChanges); 163 return true; 164 } 165 return false; 166 167 } 168 169 /** 170 * Hall of Fame 171 */ 172 function getTopContrib() { 173 global $auth; 174 175 // nb of rows to display 176 // if missing, 10 will be taken as default value 177 $nbOfRows = (isset($this->initOpt['nbOfRows']) && is_numeric($this->initOpt['nbOfRows'])) ? $this->initOpt['nbOfRows'] : 10; 178 179 $ret = ' 180 <table class="wikistat info_hof inline"> 181 <caption class="hof_caption">'.$this->getLang('ws_hof').'</caption> 182 <tr> 183 <th class="centeralign">'.$this->getLang('ws_position').'</th> 184 <th class="centeralign">'.$this->getLang('ws_name').'</th> 185 <th class="centeralign">'.$this->getLang('ws_editnumb').'</th> 186 </tr>'; 187 188 $this->getAllChanges(); 189 190 $usersedits = array(); 191 192 // loop through all changes to count number of edits by user 193 foreach($this->allChanges as $singleChange) { 194 if ($singleChange['user'] != "" && $this->toBeCounted($singleChange['id'])) { 195 if($singleChange['type'] != "D" && $singleChange['type'] != "R" ) { 196 $usersedits[$singleChange['user']]++; 197 } 198 } 199 } 200 201 // use full name or pseudo ? 202 $useFullName = ($this->initOpt['namecol'] == 'fullname'); 203 204 foreach($usersedits as $username => $nbofedits) { 205 if($useFullName) { 206 // get full user name from auth object 207 $info = $auth->getUserData($username); 208 $userDisplayName[$username] = (isset($info) && $info) ? hsc($info['name']) : hsc($username); 209 } else { 210 $userDisplayName[$username] = hsc($username); 211 } 212 } 213 214 // Sort the data with volume descending, edition ascending 215 // Add $data as the last parameter, to sort by the common key 216 array_multisort($usersedits, SORT_DESC, $userDisplayName, SORT_ASC); 217 218 foreach ($usersedits as $userid => $edits) { 219 $evenodd = $i++ % 2 ? "hof_evenrow" : "hof_oddrow"; 220 if ($nbOfRows == '-1' || $i <= $nbOfRows) {//$this->getConf('ws_topcontrib')+1 || $this->getConf('ws_topcontrib') == -1) { 221 $ret .= ' 222 <tr class="'.$evenodd.'"> 223 <td class="hof_row_pos"><b>'.$i.'</b></td> 224 <td class="hof_row_name">'.$userDisplayName[$userid].'</td> 225 <td class="hof_row_num">'.$edits.'</td> 226 </tr>'; 227 } 228 } 229 230 $ret .= ' 231 </table>'; 232 233 return $ret; 234 } 235 236 function filterChanges($by='user', $onlyif=false, $andnot=array('type' => 'D', 'type' => 'R')){ 237 $filter = array(); 238 239 // loop through all changes to count number of edits by user 240 foreach($this->allChanges as $singleChange) { 241 if ($singleChange[$by] != "" && $this->toBeCounted($singleChange['id'])) { 242 $add = true; 243 if(is_array($onlyif)){ 244 foreach($onlyif as $key => $value){ 245 if($singleChange[$key]==$value){ 246 $add=true; 247 break; 248 } 249 } 250 } 251 if(is_array($andnot)){ 252 foreach($andnot as $key => $value){ 253 if($singleChange[$key]==$value){ 254 $add=false; 255 break; 256 } 257 } 258 } 259 if($add) 260 $filter[$singleChange[$by]]++; 261 } 262 } 263 return $filter; 264 } 265 266 /** 267 * Most changed pages 268 */ 269 function getTopChanged($ns='',$sort_asc=false) { 270 global $conf; 271 272 $this->getAllChanges('',0,0,$ns); 273 $edits = $this->filterChanges('id'); 274 arsort($edits); 275 if($sort_asc) $edits=array_reverse($edits); 276 277 // nb of rows to display 278 // if missing, 10 will be taken as default value 279 $nbOfRows = (isset($this->initOpt['nbOfRows']) && is_numeric($this->initOpt['nbOfRows'])) ? $this->initOpt['nbOfRows'] : 10; 280 281 $ret = ' 282 <table class="wikistat info_hof inline"> 283 <caption class="hof_caption">'.$this->getLang('ws_hofpagesedits') . (!empty($ns) ? $this->getLang('ws_for_ns') . $ns : '') . '</caption> 284 <tr> 285 <th class="centeralign">'.$this->getLang('ws_position').'</th> 286 <th class="centeralign">'.$this->getLang('ws_page').'</th> 287 <th class="centeralign">'.$this->getLang('ws_editnumb').'</th> 288 </tr>'; 289 290 $i = 0; 291 foreach($edits as $page => $pageedit) { 292 $evenodd = $i++ % 2 ? 'hof_evenrow' : 'hof_oddrow'; 293 if ($nbOfRows == '-1' || $i <= $nbOfRows) { 294 $ret .= ' 295 <tr class="'.$evenodd.'"> 296 <td class="hof_row_pos"><b>'.$i.'</b></td> 297 <td class="hof_row_name">'.html_wikilink(':'.$page).'</td> 298 <td class="hof_row_num">'.$pageedit.'</td> 299 </tr>'; 300 } 301 } 302 303 $ret .= ' 304 </table>'; 305 306 return $ret; 307 } 308 309 /**' 310 * Edits charts 311 */ 312 function histoContrib() { 313 switch ($this->initOpt['mode']) { 314 case 'bymonth': 315 return $this->histoContribByMonth(); 316 break; 317 case 'byyear': 318 break; 319 case 'monthbyday': 320 $period = $this->initOpt['period']; 321 if($period == '') { 322 $dt = getdate(); 323 $month = $dt['mon']; 324 $year = $dt['year']; 325 } else { 326 $month = substr($period,0,strpos($period,"/")); 327 $year = substr($period,strpos($period,"/")+1); 328 } 329 return $this->histoContribMonthByDay($year, $month); 330 break; 331 case 'lastmonthbyday': 332 $dt = getdate(); 333 $month = $dt['mon']; 334 $year = $dt['year']; 335 if($month == 1) { 336 $month = 12; 337 $year--; 338 } else { 339 $month--; 340 } 341 342 return $this->histoContribMonthByDay($year, $month); 343 break; 344 case 'lastyear': 345 break; 346 case 'allevents': 347 return $this->histoContribByMonthAll(); 348 break; 349 } 350 351 } 352 353 /** 354 * Method to list all events - Creates, Edits, Deletes and Reverts 355 * author Frank M.G. Joergensen, frank(at)gajda(dot)dk 356 */ 357 function histoContribByMonthAll() { 358 global $conf; 359 $monthYear = array(); 360 361 $this->getAllChanges(); 362 363 foreach($this->allChanges as $changeEvent) { 364 if ($changeEvent['user'] != "" && $this->toBeCounted($changeEvent['page'])) { 365 $dateConv = date("Ym", $changeEvent['date']); 366 if($changeEvent['type'] == "R") $monthYear[$dateConv]['R']++; 367 if($changeEvent['type'] == "D") $monthYear[$dateConv]['D']++; 368 if($changeEvent['type'] == "E") $monthYear[$dateConv]['E']++; 369 if($changeEvent['type'] == "C") $monthYear[$dateConv]['C']++; 370 } 371 } 372 373 ksort($monthYear); 374 $ret = '<table class="wikistat info_hof inline"> 375 <caption class="hof_caption">'.$this->getLang('ws_events').'</caption> 376 <tr> 377 <th class="centeralign">'.$this->getLang('ws_dateYm').'</th> 378 <th class="centeralign">'.$this->getLang('ws_created').'</th> 379 <th class="centeralign">'.$this->getLang('ws_edited').'</th> 380 <th class="centeralign">'.$this->getLang('ws_deleted').'</th> 381 <th class="centeralign">'.$this->getLang('ws_reverted').'</th> 382 </tr> 383 '; 384 if(count($monthYear)){ 385 $i = 0; 386 foreach($monthYear as $key => $value){ 387 if(is_array($value)){ 388 $evenodd = $i++ % 2 ? 'hof_evenrow' : 'hof_oddrow'; 389 $ret .= '<tr class="'.$evenodd.'"> 390 <td class="hof_row_pos">'; 391 $ret .= $key; 392 $ret .= '</td> 393 <td class="hof_row_pos">'; 394 $ret .= $value['C']; 395 $ret .= '</td> 396 <td class="hof_row_pos">'; 397 $ret .= $value['E']; 398 $ret .= '</td> 399 <td class="hof_row_pos">'; 400 $ret .= $value['D']; 401 $ret .= '</td> 402 <td class="hof_row_pos">'; 403 $ret .= $value['R']; 404 $ret .= '</td> 405 </tr> 406 '; 407 } 408 } 409 $ret .= '</table>'; 410 } 411 return $ret; 412 } 413 414 /**' 415 * Bar graph of the number of contrib by day 416 * for the month $month of the year $year 417 */ 418 function histoContribMonthByDay($year, $month) { 419 420 global $conf; 421 422 $month = intval($month); 423 424 $date_value = array(); 425 426 $this->getAllChanges(); 427 428 foreach($this->allChanges as $singleChange) { 429 if ($singleChange['user'] != "" && $this->toBeCounted($singleChange['page'])) { 430 if($singleChange['type'] != "D" && $singleChange['type'] != "R" ) { 431 if(date("n/Y", $singleChange['date']) == $month."/".$year) { 432 $date_value[date("j", $singleChange['date'])]++; 433 } 434 } 435 } 436 } 437 438 // getting last day of the current month 439 $monthTime = mktime(0, 0, 0, $month, 1, $year); 440 $lastDayOfMonth = date("t", $monthTime); 441 442 // Dataset definition 443 $dataSet = new pData; 444 $tabDays = array(); 445 $tabVals = array(); 446 for ($i = 1; $i <= $lastDayOfMonth; $i++) { 447 $tabDays[$i] = $i; 448 $tabVals[$i] = $date_value[$i]; 449 } 450 451 $dataSet->AddPoint($tabVals, "Serie1"); 452 $dataSet->AddPoint($tabDays, "Serie2"); 453 $dataSet->AddSerie("Serie1"); 454 $dataSet->SetAbsciseLabelSerie("Serie2"); 455 456 // Graph width 457 $width = $this->initOpt['width']; 458 if($width == '') { 459 $width = 700; 460 } 461 462 // Graph height 463 $height = $this->initOpt['height']; 464 if($height == '') { 465 $height = 230; 466 } 467 468 $heightForAngle = 0; 469 $absLabelAngle = $this->initOpt['absLabelAngle']; 470 if($absLabelAngle > 0 && $absLabelAngle <= 90) { 471 $heightForAngle = $absLabelAngle/10; 472 } else { 473 $absLabelAngle = 0; 474 } 475 476 // Initialize the graph 477 $chart = new pChart($width,$height+$heightForAngle); 478 $chart->setGraphArea($this->initOpt['spleft'],$this->initOpt['sptop'],$width-20,$height-30); 479 480 // load colour palette from currently active template if exists. 481 // Otherwise fall back to default colour palette 482 if (@file_exists(DOKU_TPLINC.'palette.txt')) 483 $chart->loadColorPalette(DOKU_TPLINC.'palette.txt'); 484 else 485 $chart->loadColorPalette(DOKU_PLUGIN.'wikistatistics/palette.txt'); 486 487 $chart->setFontProperties(DOKU_PLUGIN.'wikistatistics/Fonts/tahoma.ttf',10); 488 $chart->drawFilledRoundedRectangle(7,7,$width-7,$height-7+$heightForAngle,5,240,240,240); 489 $chart->drawRoundedRectangle(5,5,$width-5,$height-5+$heightForAngle,5,230,230,230); 490 $chart->drawGraphArea(252,252,252); 491 // definition of drawScale method : drawScale($Data,$DataDescription,$ScaleMode,$R,$G,$B,$DrawTicks=TRUE,$Angle=0,$Decimals=1,$WithMargin=FALSE,$SkipLabels=1,$RightScale=FALSE) 492 $chart->drawScale($dataSet->GetData(),$dataSet->GetDataDescription(),SCALE_NORMAL,150,150,150,TRUE,$absLabelAngle,2,TRUE); 493 $chart->drawGrid(4,TRUE,230,230,230,255); 494 495 // Draw the bar graph 496 $chart->drawBarGraph($dataSet->GetData(),$dataSet->GetDataDescription(),TRUE); 497 498 // Finish the graph 499 $chart->setFontProperties(DOKU_PLUGIN.'wikistatistics/Fonts/tahoma.ttf',10); 500 $chart->drawTitle(0,0,$this->getLang('ws_histocontribmonthbydaytitle').' '.date('F', $monthTime).' '.date('Y', $monthTime),50,50,50,$width,35); 501 502 if (!is_dir($conf['mediadir'].'/wikistatistics')) 503 { 504 io_mkdir_p($conf['mediadir'].'/wikistatistics'); //Using dokuwiki framework 505 } 506 $chart->Render($conf['mediadir']."/wikistatistics/histocontrib_{$month}_{$year}.png"); 507 508 $url = ml("wikistatistics:histocontrib_{$month}_{$year}.png"); //Using dokuwiki framework 509 510 $ret .= ' 511 <img src="' . $url . '" alt="' . $this->getLang('ws_histocontribmonthbydaytitle').' '.date('F', $monthTime).' '.date('Y', $monthTime) . '" title="' . $this->getLang('ws_histocontribmonthbydaytitle').' '.date('F', $monthTime).' '.date('Y', $monthTime) . '"/>'; 512 513 return $ret; 514 } 515 516 /**' 517 * Bar graph of the number of contrib by month 518 */ 519 function histoContribByMonth() { 520 521 global $conf; 522 523 $date_value = array(); 524 $dateMin = ''; 525 526 $this->getAllChanges(); 527 528 foreach($this->allChanges as $singleChange) { 529 if ($singleChange['user'] != "" && $this->toBeCounted($singleChange['page'])) { 530 if($singleChange['type'] != "D" && $singleChange['type'] != "R" ) { 531 $dateContrib = date("m/Y", $singleChange['date']); 532 $date_value[$dateContrib]++; 533 if($dateMin == '' || $singleChange['date'] < $dateMin) { 534 $dateMin = $singleChange['date']; 535 } 536 } 537 } 538 } 539 540 $dateMin = date("m/Y",$dateMin); 541 542 // getting current month : end of the graph 543 $monthNow = date("m/Y"); 544 545 // Dataset definition 546 $dataSet = new pData; 547 $tabMonths = array(); 548 $tabVals = array(); 549 550 $previousMonth = ''; 551 $currentMonth = $dateMin; 552 $i = 0; 553 $out = false; 554 555 // since the month of the first contrib to now... 556 while($previousMonth == '' || $previousMonth != $monthNow) { 557 $tabMonths[$i] = $currentMonth; 558 $tabVals[$i] = $date_value[$currentMonth]; 559 560 $previousMonth = $currentMonth; 561 562 // calculate next month 563 $m = substr($currentMonth,0,strpos($currentMonth,"/")); 564 $y = substr($currentMonth,strpos($currentMonth,"/")+1); 565 if($m == 12) { 566 $m = 1; 567 $y++; 568 } else { 569 $m++; 570 } 571 $currentMonth = ($m < 10 ? '0' : '').$m.'/'.$y; 572 $i++; 573 } 574 575 $dataSet->AddPoint($tabVals, 'Serie1'); 576 $dataSet->AddPoint($tabMonths, 'Serie2'); 577 $dataSet->AddSerie('Serie1'); 578 $dataSet->SetAbsciseLabelSerie('Serie2'); 579 580 // Graph width 581 $width = $this->initOpt['width']; 582 if($width == '') { 583 $width = 700; 584 } 585 586 // Graph height 587 $height = $this->initOpt['height']; 588 if($height == '') { 589 $height = 230; 590 } 591 592 $heightForAngle = 0; 593 $absLabelAngle = $this->initOpt['absLabelAngle']; 594 if($absLabelAngle > 0 && $absLabelAngle <= 90) { 595 $heightForAngle = $absLabelAngle/2; 596 } else { 597 $absLabelAngle = 0; 598 } 599 600 // Initialize the graph 601 $chart = new pChart($width,$height+$heightForAngle); 602 $chart->setGraphArea($this->initOpt['spleft'],$this->initOpt['sptop'],$width-20,$height-30); 603 604 // load colour palette from currently active template if exists. 605 // Otherwise fall back to default colour palette 606 if (@file_exists(DOKU_TPLINC.'palette.txt')) 607 $chart->loadColorPalette(DOKU_TPLINC.'palette.txt'); 608 else 609 $chart->loadColorPalette(DOKU_PLUGIN.'wikistatistics/palette.txt'); 610 611 $chart->setFontProperties(DOKU_PLUGIN.'wikistatistics/Fonts/tahoma.ttf',10); 612 $chart->drawFilledRoundedRectangle(7,7,$width-7,$height-7+$heightForAngle,5,240,240,240); 613 $chart->drawRoundedRectangle(5,5,$width-5,$height-5+$heightForAngle,5,230,230,230); 614 $chart->drawGraphArea(252,252,252); 615 $chart->drawScale($dataSet->GetData(),$dataSet->GetDataDescription(),SCALE_NORMAL,150,150,150,TRUE,$absLabelAngle,2,TRUE); 616 $chart->drawGrid(4,TRUE,230,230,230,255); 617 618 // Draw the bar graph 619 $chart->drawBarGraph($dataSet->GetData(),$dataSet->GetDataDescription(),TRUE); 620 621 // Finish the graph 622 $chart->setFontProperties(DOKU_PLUGIN.'wikistatistics/Fonts/tahoma.ttf',10); 623 $chart->drawTitle(0,0,$this->getLang('ws_histocontribbymonthtitle'),50,50,50,$width,35); 624 625 if (!is_dir($conf['mediadir'] . '/wikistatistics')) 626 { 627 io_mkdir_p($conf['mediadir'] . '/wikistatistics'); //Using dokuwiki framework 628 } 629 $chart->Render($conf['mediadir'].'/wikistatistics/histocontrib_bymonth.png'); 630 631 $url = ml('wikistatistics:histocontrib_bymonth.png'); //Using dokuwiki framework 632 633 $ret .= ' 634 <img src="' . $url . '" alt="' . $this->getLang('ws_histocontribbymonthtitle') . '" title="' . $this->getLang('ws_histocontribbymonthtitle') . '"/>'; 635 636 return $ret; 637 } 638 639 /** 640 * Count the pages in the namespace $ns 641 */ 642 function countPages($ns='') { 643 global $conf; 644 645 // root directory of all pages 646 $rootPath = $conf['datadir']; 647 648 $path = ''; 649 650 // go to namespace directory if specified 651 if($ns != '') { 652 $nsArray = split(":",$ns); 653 654 foreach ($nsArray as $namespace) { 655 $path .= "/".$namespace; 656 } 657 } 658 659 return $this->_pages_xhtml_r($path, $rootPath); 660 } 661 662 /**' 663 * Recursive method to count the number of pages under $path 664 */ 665 function _pages_xhtml_r($path, $rootPath) { 666 $nbPages = 0; 667 668 $nsPath = str_replace('/',':',$path); 669 if($path == '' || $this->toBeCounted($nsPath)) { 670 if (is_dir($rootPath.$path)) { 671 if($pdir = opendir($rootPath.$path)) { 672 while ($file = readdir($pdir)) { 673 if ($file != "." && $file != "..") { 674 675 $filePath = $path."/".$file; 676 677 if (is_file($rootPath.$filePath)) { 678 $filens = substr($filePath, 0, strrpos($filePath , '.txt')); 679 680 $filens = str_replace('/',':',$filens); 681 if($this->toBeCounted($filens)) { 682 $nbPages++; 683 } 684 } else { 685 $nbPages += $this->_pages_xhtml_r($filePath, $rootPath); 686 } 687 } 688 } 689 closedir($pdir); 690 } 691 } 692 } 693 694 return $nbPages; 695 } 696 697 /**' 698 * Check if the namespace $ns has to be excluded or not 699 */ 700 function toBeCounted($ns) { 701 //namespace 702 $ns = (preg_match("/^:/",$ns)) ? substr($ns,1) : $ns; 703 704 $nstocheck = ""; 705 706 $ns_split = split(":",$ns); 707 foreach($ns_split as $nspart){ 708 if($nstocheck != "") { 709 $nstocheck .= ":"; 710 } 711 $nstocheck .= $nspart; 712 if (!is_null($this->excludedNs) && in_array($nstocheck,$this->excludedNs)) { 713 return false; 714 } 715 } 716 717 if(@preg_match($this->excludedNsPattern,$ns)){ 718 return false; 719 } 720 721 return true; 722 } 723 724 function cw_array_count($a) { 725 if(!is_array($a)) return $a; 726 foreach($a as $key=>$value) 727 $totale += $this->cw_array_count($value); 728 return $totale; 729 } 730 731 732 /**' 733 * Count the users 734 */ 735 function countUsers() { 736 global $auth; 737 738 $nbUsers = 0; 739 740 if($this->initOpt['filter'] == 'active') { 741 // only active users (those who contributed at least once) 742 743 $users = array(); 744 $this->getAllChanges(); 745 746 foreach($this->allChanges as $singleChange) { 747 $user = $singleChange['user']; 748 if ($user != "" && !in_array($user, $users)) { 749 $users[] = $user; 750 } 751 } 752 $nbUsers = sizeof($users); 753 } else { 754 // all users 755 756 // if the auth module implements the getUserCount function, use it ! 757 // it's not the case for ldap auth for example. 758 if($auth->canDo('getUserCount')) { 759 $nbUsers = $auth->getUserCount(array()); 760 } 761 } 762 763 return $nbUsers; 764 } 765 766 767 function getAllChanges($directory='',$first=0,$num=0,$ns='',$flags=0) { 768 global $conf; 769 770 $cache_file = $conf['mediadir'].'/wikistatistics/cache_changes' . ($ns!='' ? '_' . str_replace(':','_',$ns) : '') . '.php'; 771 772 if(!empty($ns)){ 773 $directory=str_replace('//','/',dirname($conf['changelog']) . '/' . str_replace(':','/',$ns)); 774 } 775 776 if(!$this->getConf('ws_cacheresults')){ 777 @unlink($cache_file); 778 $this->lastUpdate = 0; 779 } 780 if (!is_dir($conf['mediadir'].'/wikistatistics')) 781 { 782 io_mkdir_p($conf['mediadir'].'/wikistatistics'); //Using dokuwiki framework 783 } 784 if(@file_exists($cache_file)){ 785 include($cache_file); 786 } 787 788 if(time() > $this->lastUpdate + $this->getConf('ws_cacheexpire')){ 789 @unlink($cache_file); 790 unset($this->allChanges); 791 $i=0; 792 793 $this->parseChanges($directory,$first,$num,$ns,$flags); 794 795 $fp = @fopen( $cache_file, 'w' ); 796 @fwrite( $fp, '<?php 797 $this->lastUpdate = ' . time() . ';'); 798 foreach ($this->allChanges as $change){ 799 @fwrite( $fp, ' 800 $this->allChanges['.$i.'] = array( 801 \'date\' => ' . $change['date'] . ', 802 \'ip\' => \'' . $change['ip'] . '\', 803 \'type\' => \'' . $change['type'] . '\', 804 \'id\' => \'' . $change['id'] . '\', 805 \'user\' => \'' . $change['user'] . '\', 806 \'sum\' => \'' . htmlspecialchars($change['sum'],ENT_QUOTES) . '\', 807 \'extra\' => \'' . $change['extra'] . '\', 808 );'); 809 $i++; 810 } 811 @fclose( $fp ); 812 include($cache_file); 813 } 814 } 815 816 function parseChanges($directory='',$first=0,$num=0,$ns='',$flags=0) { 817 global $conf; 818 $metapath = $conf['metadir']; 819 $count = 0; 820 if(empty($directory)) { 821 $directory=dirname($conf['changelog']); 822 } 823 824 //Counting files variable initialization 825 $count=0; 826 827 /* 828 * open the directory and take an instance of it to handle var 829 */ 830 if ($handle = opendir($directory)) { 831 $sub = substr($directory,strpos($directory,$metapath)+strlen($metapath)+1); 832 $sub = str_replace('/',':',$sub); 833 if($this->toBeCounted($sub,'ns')) { 834 while (false !== ($file = readdir($handle))) { 835 836 if ($file != "." && $file != "..") { 837 if (is_file($directory."/".$file)) { 838 // Determining extensions of files 839 $file_ext = substr($file, strrpos($file, ".")+1); 840 841 if($file_ext == 'changes' && $file != '_dokuwiki.changes') { 842 $lines = @file("$directory/$file"); 843 for($i = count($lines)-1; $i >= 0; $i--) { 844 $rec = parseChangelogLine($lines[$i]); 845 if($rec !== false) { 846 if(--$first >= 0) continue; // skip first entries 847 $this->allChanges[] = $rec; 848 $count++; 849 // break when we have enough entries 850 if(!$num==0) { 851 if($count >= $num) { 852 break; 853 } 854 } 855 } 856 } 857 } 858 } else if (is_dir("$directory/$file")) { 859 $this->parseChanges("$directory/$file"); 860 } 861 } 862 } 863 } 864 865 closedir($handle); 866 } 867 } 868 869 function pagesSizes($ns = '') { 870 global $conf; 871 872 // root directory of all pages 873 $rootPath = $conf['datadir']; 874 875 $path = ''; 876 877 // go to namespace directory if specified 878 if($ns != '') { 879 $nsArray = split(':',$ns); 880 881 foreach ($nsArray as $namespace) { 882 $path .= '/'.$namespace; 883 } 884 } 885 886 // Max level 887 $depthLevel = $this->initOpt['depthlevel']; 888 if($depthLevel == '') { 889 $depthLevel = 0; 890 } 891 892 $pagesSizes = $this->getAllPagesSizes($path, $rootPath, $depthLevel); 893 894 $nss = array(); 895 $sizes = array(); 896 foreach($pagesSizes as $ns => $size) { 897 $nss[] = $ns." (".$size.")"; 898 $sizes[] = $size; 899 } 900 901 // Graph width 902 $width = $this->initOpt['width']; 903 if($width == '') { 904 $width = 530; 905 } 906 907 // Graph height 908 $height = $this->initOpt['height']; 909 if($height == '') { 910 $height = 200; 911 } 912 913 $legendX = $width-min(220, round($width/3)); 914 $graphX = round($legendX/2); 915 $graphY = round($height/2); 916 $graphR = round(($graphX<$graphY)?$graphX:min($graphX,$graphY*1.6))-50; 917 918 // Dataset definition 919 $DataSet = new pData; 920 $DataSet->AddPoint($sizes,'sizes'); 921 $DataSet->AddPoint($nss,'namespaces'); 922 $DataSet->AddAllSeries(); 923 $DataSet->SetAbsciseLabelSerie('namespaces'); 924 925 // Initialise the graph 926 $chart = new pChart($width,$height); 927 $chart->drawFilledRoundedRectangle(7,7,$width-7,$height-7,5,240,240,240); 928 $chart->drawRoundedRectangle(5,5,$width-5,$height-5,5,230,230,230); 929 930 // load colour palette from currently active template if exists. 931 // Otherwise fall back to default colour palette 932 if (@file_exists(DOKU_TPLINC.'palette.txt')) 933 $chart->loadColorPalette(DOKU_TPLINC.'palette.txt'); 934 else 935 $chart->loadColorPalette(DOKU_PLUGIN.'wikistatistics/palette.txt'); 936 937 // Draw the pie chart 938 $chart->setFontProperties(DOKU_PLUGIN.'wikistatistics/Fonts/tahoma.ttf',10); 939 // drawPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$EnhanceColors=TRUE,$Skew=60,$SpliceHeight=20,$SpliceDistance=0,$Decimals=0) 940 $chart->drawPieGraph($DataSet->GetData(),$DataSet->GetDataDescription(),$graphX,$graphY,$graphR,PIE_PERCENTAGE,TRUE,50,20,5); 941 $chart->drawPieLegend($legendX,15,$DataSet->GetData(),$DataSet->GetDataDescription(),250,250,250); 942 943 $chart->Render($conf['mediadir'].'/wikistatistics/pagessizes.png'); 944 945 $url = ml('wikistatistics:pagessizes.png'); //Using dokuwiki framework 946 947 $ret = ' 948 <img src="' . $url . '" alt="' . $this->getLang('ws_pagesize') . '" title="' . $this->getLang('ws_pagesize') . '"/>'; 949 950 return $ret; 951 952 } 953 954 /** 955 * Calculate the size of all pages 956 */ 957 function getAllPagesSizes($path, $rootPath, $depthLevel) { 958 $pagesSizes = array(); 959 960 $nsPath = str_replace('/',':',$path); 961 if($path == '' || $this->toBeCounted($nsPath)) { 962 if (is_dir($rootPath.$path)) { 963 if($pdir = opendir($rootPath.$path)) { 964 while ($file = readdir($pdir)) { 965 if ($file != '.' && $file != '..') { 966 967 $filePath = "$path/$file"; 968 969 if (is_file($rootPath.$filePath)) { 970 $filens = substr($filePath, 0, strrpos($filePath , '.txt')); 971 972 $filens = str_replace('/',':',$filens); 973 if(substr($filens, 0, 1) == ':') { 974 $filens = substr($filens, 1); 975 } 976 if($this->toBeCounted($filens)) { 977 if($depthLevel > 0) { 978 $nsArray = split(':', $filens); 979 $filens = ''; 980 for($i=0; $i<sizeof($nsArray) && $i<$depthLevel; $i++) { 981 if($i > 0) { 982 $filens .= ':'; 983 } 984 $filens .= $nsArray[$i]; 985 986 } 987 } 988 $pagesSizes[$filens] = filesize($rootPath.$filePath); 989 } 990 } else { 991 foreach($this->getAllPagesSizes($filePath, $rootPath, $depthLevel) as $ns => $size) { 992 $pagesSizes[$ns] += $size; 993 } 994 995 } 996 } 997 } 998 closedir($pdir); 999 } 1000 } 1001 } 1002 1003 return $pagesSizes; 1004 } 1005 1006 function getTopPagesSizes($path = '') { 1007 global $conf; 1008 1009 // root directory of all pages 1010 $rootPath = $conf['datadir']; 1011 1012 // nb of rows to display 1013 // if missing, 10 will be taken as default value 1014 $nbOfRows = (isset($this->initOpt['nbOfRows']) && is_numeric($this->initOpt['nbOfRows'])) ? $this->initOpt['nbOfRows'] : 10; 1015 1016 $pagesSizes = $this->getAllPagesSizes($path, $rootPath, 0); 1017 1018 arsort($pagesSizes); 1019 1020 $ret = ' 1021 <table class="wikistat info_hof inline"> 1022 <caption class="hof_caption">'.$this->getLang('ws_hofpagessizes').'</caption> 1023 <tr> 1024 <th class="centeralign">'.$this->getLang('ws_position').'</th> 1025 <th class="centeralign">'.$this->getLang('ws_page').'</th> 1026 <th class="centeralign">'.$this->getLang('ws_size').'</th> 1027 </tr>'; 1028 1029 $i = 0; 1030 foreach($pagesSizes as $page => $pagesize) { 1031 $evenodd = $i++ % 2 ? 'hof_evenrow' : 'hof_oddrow'; 1032 if ($nbOfRows == '-1' || $i <= $nbOfRows) { 1033 $ret .= ' 1034 <tr class="'.$evenodd.'"> 1035 <td class="hof_row_pos"><b>'.$i.'</b></td> 1036 <td class="hof_row_name">'.html_wikilink(':'.$page).'</td> 1037 <td class="hof_row_num">'.$pagesize.'</td> 1038 </tr>'; 1039 } 1040 } 1041 1042 $ret .= ' 1043 </table>'; 1044 1045 return $ret; 1046 } 1047 1048 /* 1049 * 1050 * Variable's initialization 1051 * 1052 */ 1053 function varinit($params) { 1054 $this->allChanges = array(); //Reset the counter 1055 1056 $this->initOpt=$params; 1057 1058 // param ws_excludedns 1059 $excludedNs = split(",",$this->getConf('ws_excludedns')); 1060 foreach ($excludedNs as $key => $value) { 1061 if (is_null($value) || $value=="") { 1062 unset($excludedNs[$key]); 1063 } 1064 } 1065 1066 $this->excludedNs = (count($excludedNs)>0) ? $excludedNs : null; 1067 // param ws_excludedns_pattern 1068 $excludedns_pattern = split(",",$this->getConf('ws_excludedns_pattern')); 1069 $this->excludedNsPattern = '/'; 1070 for($i=0;$i<count($excludedns_pattern);$i++){ 1071 if($excludedns_pattern[$i]!='') 1072 $this->excludedNsPattern .= $excludedns_pattern[$i].'|'; 1073 } 1074 $this->excludedNsPattern = substr($this->excludedNsPattern, 0, -1).'/'; 1075 if($this->excludedNsPattern=='/'){$this->excludedNsPattern='';} 1076 1077 $this->initOpt['spleft'] = isset($this->initOpt['spleft']) ? $this->initOpt['spleft'] : '40'; 1078 $this->initOpt['sptop'] = isset($this->initOpt['sptop']) ? $this->initOpt['sptop'] : '30'; 1079 } 1080}