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}