1<?php
2/**
3 * Wiki farm animal infos and statistics class
4 *
5 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author   Etienne MELEARD <etienne.meleard@cru.fr>
7 * @desc     Gather and returns various infos and statistics about an animal
8 */
9
10if(!defined('DOKU_FARM_PLUGIN')) define('DOKU_FARM_PLUGIN', defined('DOKU_FARMPLUGINLOADED') ? DOKU_INC.'lib/plugins/farm/' : './');
11
12require_once DOKU_FARM_PLUGIN.'animal.class.php';
13
14class dokuwiki_farm_animal_infos {
15	var $animal = null;
16
17	/**
18	 * @param $animal animal object or animal name as string
19	 */
20	function __construct($animal = null) {
21		if(is_a($animal, 'dokuwiki_farm_animal')) {
22			if(dokuwiki_farm_animal::exists($animal->getName())) $this->animal = $animal;
23		}elseif(is_string($animal)) {
24			if(dokuwiki_farm_animal::exists($animal)) $this->animal = new dokuwiki_farm_animal($animal);
25		}
26	}
27
28	/**
29	 * Returns the size of a directory contents
30	 * @param $p path to the directory
31	 * @return directory disk usage in bytes
32	 */
33	private function dirsize($p = '.') {
34		$s = 0;
35		foreach(scandir($p) as $i) {
36			if($i == '.' || $i == '..') continue;
37			if(@is_file($p.'/'.$i)) $s += filesize($p.'/'.$i);
38			if(@is_dir($p.'/'.$i)) $s += $this->dirsize($p.'/'.$i);
39		}
40		return $s;
41	}
42
43	/**
44	 * Format data using a string template
45	 * @param $fmt string template
46	 * @param $opt array of replacers
47	 * @return formated string
48	 */
49	public function formatedprint($fmt, $opt) {
50		$rawreplace = array('url', 'link', 'namespace', 'user');
51		foreach($rawreplace as $k) if(isset($opt[$k])) $fmt = str_replace('{'.$k.'}', $opt[$k], $fmt);
52		if(isset($opt['date'])) {
53			$date = (int)$opt['date'];
54			$dfmt = isset($opt['dformat']) ? $opt['dformat'] : '%Y/%m/%d %H:%M';
55			$delta = abs(time() - $date);
56			$date = strftime($dfmt, $date);
57			$fmt = str_replace('{date}', $date, $fmt);
58			if(isset($opt['dunits'])) {
59				$triggers = array(
60					's' => array(1, 60),
61					'i' => array(60, 3600),
62					'h' => array(3600, 24*3600),
63					'd' => array(24*3600, 7*24*3600),
64					'w' => array(7*24*3600, 30.5*24*3600),
65					'm' => array(30.5*24*3600, 365.25*24*3600),
66					'y' => array(365.25*24*3600, 0)
67				);
68				$prec = null;
69				$str = '?';
70				foreach($triggers as $t => $v) {
71					if($v[1] && $delta >= $v[1]) {
72						$prec = $t;
73						continue;
74					}
75					$hg = floor($delta / $v[0]);
76					$lw = $prec ? floor(($delta % $v[0]) / $triggers[$prec][0]) : null;
77					$str = $hg.' '.$opt['dunits'][$t][($hg > 1) ? 1 : 0].($lw ? ' '.$lw.' '.$opt['dunits'][$prec][($lw > 1) ? 1 : 0] : '');
78					break;
79				}
80				$fmt = str_replace('{delta}', $str, $fmt);
81			}
82		}
83		if(isset($opt['size'])) $fmt = str_replace('{size}', $this->animal->manager->nicesize($opt['size']), $fmt);
84		$fmt = preg_replace('`\{[^}]+\}`', '?', $fmt);
85		return $fmt;
86	}
87
88	/**
89	 * Shorten a namespace identifier for rendering
90	 * @param $ns namespace identifier
91	 * @param $maxlen maximum length
92	 * @return shortened namespace identifier
93	 */
94	private function shortenNS($ns, $maxlen=32) {
95		$ns = str_replace('/', ':', $ns);
96		if(strlen($ns) <= $maxlen) return $ns;
97		if(preg_match('`^([^:]+:)([^:]+(:[^:]+))(:.+)$`', $ns, $m)) {
98			$st = $m[1];
99			$mid = $m[2];
100			$end = $m[4];
101			while(strlen($st.$end) + 3 <= $maxlen) {
102				$st .= substr($mid, 0, 1);
103				$end = substr($mid, -1, 1).$end;
104				$mid = substr($mid, 1, -1);
105			}
106			$ns = $st.'...'.$end;
107		}
108		if(strlen($ns) > $maxlen + 5) $ns = substr($ns, 0, floor($maxlen / 2)).'...'.substr($ns, -1 * floor($maxlen / 2));
109		return $ns;
110	}
111
112	/**
113	 * Get the disk usage of the related animal
114	 * @param $mode specific item family pointer
115	 * @param $nice boolean telling whether to nicely format the size or not
116	 * @return size as string
117	 */
118	public function getSize($mode='', $nice=false) {
119		if(!in_array($mode, array('pages', 'media', 'cache', 'index'))) $mode = '';
120		if(!$this->animal) return 0;
121		$s = $this->dirsize($this->animal->getPath().(($mode != '') ? '/data/'.$mode : ''));
122		if($nice) $s = $this->animal->manager->nicesize($s);
123		return $s;
124	}
125
126	/**
127	 * Gather the list of files in a directory and 'stat' them
128	 * @param $p path
129	 * @param $regexp PCRE used to match only specific files
130	 * @param $stripinitialpath boolean telling whether striping base path from items paths or not
131	 * @param $stripend integer used to specify if the identifiers must be truncated
132	 * @param $initialpath base path
133	 * @return associative array of 'stat' results
134	 */
135	public function filesinfo($p='.', $regexp='', $stripinitialpath=false, $stripend=0, $initialpath='') {
136		$r = array();
137		if($initialpath == '') $initialpath = $p;
138		foreach(scandir($p) as $i) {
139			if($i == '.' || $i == '..') continue;
140			$w = $p.'/'.$i;
141			if($stripinitialpath && $initialpath != '') {
142				$w = substr($w, strlen($initialpath));
143				while(substr($w, 0, 1) == '/') $w = substr($w, 1);
144			}
145			if($stripend) $w = substr($w, 0, -1 * $stripend);
146			if(@is_dir($p.'/'.$i)) $r = array_merge($r, $this->filesinfo($p.'/'.$i, $regexp, $stripinitialpath, $stripend, $initialpath));
147			if(@is_file($p.'/'.$i)) if($regexp == '' || preg_match('`'.$regexp.'`i', $i)) $r[$w] = stat($p.'/'.$i);
148		}
149		return $r;
150	}
151
152	/**
153	 * Returns the average size of the specified items
154	 * @param $f array of 'stat' results
155	 * @return average size
156	 */
157	private function avgsize($f) {
158		return round(array_sum(array_map(create_function('$e', 'return $e[\'size\'];'), $f)) / count($f));
159	}
160
161	/**
162	 * Returns the total size of the specified items
163	 * @param $f array of 'stat' results
164	 * @return total size
165	 */
166	private function totalsize($f) {
167		return array_sum(array_map(create_function('$e', 'return $e[\'size\'];'), $f));
168	}
169
170	/**
171	 * Returns the identifier of the biggest item from specified items
172	 * @param $f array of 'stat' results
173	 * @return file identifier as string
174	 */
175	private function biggest($f) {
176		uasort($f, create_function('$a,$b', 'return $b[\'size\'] - $a[\'size\'];'));
177		return key($f);
178	}
179
180	/**
181	 * Returns the identifier of the smallest item from specified items
182	 * @param $f array of 'stat' results
183	 * @return file identifier as string
184	 */
185	private function smallest($f) {
186		uasort($f, create_function('$a,$b', 'return $a[\'size\'] - $b[\'size\'];'));
187		return key($f);
188	}
189
190	/**
191	 * Returns a page metadata
192	 * @param $page page identifier
193	 * @return array of metadata (DokuWik style)
194	 */
195	private function getmeta($page) {
196		if(!$this->animal) return 0;
197		if($page == '') return null;
198		if(!@file_exists($this->animal->getPath().'/data/meta/'.$page.'.meta')) return null;
199		return unserialize(@file_get_contents($this->animal->getPath().'/data/meta/'.$page.'.meta'));
200	}
201
202	/**
203	 * Extract the last modification date from metadata
204	 * @param $m metadata array (DokuWik style)
205	 * @return unix timestamp
206	 */
207	private function getmdate($m) {
208		if(isset($m['last_change']['date'])) return (int)$m['last_change']['date'];
209		if(isset($m['current']['date']['modified'])) return (int)$m['current']['date']['modified'];
210		if(isset($m['current']['date']['created'])) return (int)$m['current']['date']['created'];
211		return 0;
212	}
213
214	/**
215	 * Returns the identifier of the oldest modified item from specified items
216	 * @param $f array of 'stat' results
217	 * @return file identifier as string
218	 */
219	private function oldest($f) {
220		$fm = array();
221		foreach($f as $ns => $i) $fm[$ns] = $this->getmdate($this->getmeta($ns));
222		uasort($fm, create_function('$a,$b', 'return $a - $b;'));
223		return key($fm);
224	}
225
226	/**
227	 * Returns the identifier of the latest modified item from specified items
228	 * @param $f array of 'stat' results
229	 * @return file identifier as string
230	 */
231	private function latest($f) {
232		$fm = array();
233		foreach($f as $ns => $i) $fm[$ns] = $this->getmdate($this->getmeta($ns));
234		uasort($fm, create_function('$a,$b', 'return $b - $a;'));
235		return key($fm);
236	}
237
238	/**
239	 * Build a namespace url
240	 * @param $ns namespace
241	 * @param $media boolean telling whether the pointed item is a media ressource or not
242	 * @return url string
243	 */
244	private function elurl($ns, $media=false) {
245		if(!$this->animal) return 0;
246		return $this->animal->getUrl($ns, $media);
247	}
248
249	/**
250	 * Build a namespace html link
251	 * @param $ns namespace
252	 * @param $media boolean telling whether the pointed item is a media ressource or not
253	 * @return string
254	 */
255	private function elhtmlurl($ns, $media=false) {
256		return '<a href="'.str_replace('&', '&amp;', $this->elurl($ns, $media)).'" target="_blank">'.$this->shortenNS($ns).'</a>';
257	}
258
259	/**
260	 * Returns animal's status
261	 * @return string
262	 */
263	public function getStatus() {
264		return $this->animal->getStatus();
265	}
266
267	/**
268	 * Returns animal's lock state
269	 * @return string
270	 */
271	public function getLockState() {
272		return $this->animal->getLockState();
273	}
274
275	/**
276	 * Returns animal's creation date
277	 * @param $nice boolean telling whether output must be formated
278	 * @return unix timestamp or formated date as string
279	 */
280	public function getCreation($nice=false) {
281		global $conf;
282		$d = (int)$this->animal->getMetadata('creation_date');
283		if(!$nice) return $d;
284		return $this->formatedprint(
285			$this->animal->manager->getLang('animal_info_creationfmt'),
286			array(
287				'date' => $d,
288				'dformat' => $conf['dformat'],
289				'dunits' => $this->animal->manager->getLang('timedeltaunits')
290			)
291		);
292	}
293
294	/**
295	 * Gather infos about animal's pages
296	 * @param $nice boolean telling whether output must be formated
297	 * @return array of infos descriptors
298	 */
299	public function pagesInfo($nice=false) {
300		if(!$this->animal) return 0;
301		global $conf;
302
303		$fi = $this->filesinfo($this->animal->getPath().'/data/pages', '.*\.txt', true, 4);
304		$info = array();
305
306		// general
307		$info['count'] = count($fi);
308
309		$info['average'] = $this->avgsize($fi);
310		if($nice) $info['average'] = $this->animal->manager->nicesize($info['average']);
311
312		$info['total'] = $this->totalsize($fi);
313		if($nice) $info['total'] = $this->animal->manager->nicesize($info['total']);
314
315		// biggest page
316		$bg = $this->biggest($fi);
317		if($nice) {
318			$info['biggest'] = $this->elhtmlurl($bg).' ('.$this->animal->manager->nicesize($fi[$bg]['size']).')';
319		}else{
320			$info['biggest'] = array(
321				'size' => $fi[$bg]['size'],
322				'url' => $this->elurl($bg),
323				'namespace' => $bg
324			);
325		}
326
327		// smallest page
328		$st = $this->smallest($fi);
329		if($nice) {
330			$info['smallest'] = $this->elhtmlurl($st).' ('.$this->animal->manager->nicesize($fi[$st]['size']).')';
331		}else{
332			$info['smallest'] = array(
333				'size' => $fi[$st]['size'],
334				'url' => $this->elurl($st),
335				'namespace' => $st
336			);
337		}
338
339		// oldest page (by edit date)
340		$ot = $this->oldest($fi);
341		$m = $this->getmeta($ot);
342		if($nice) {
343			$info['oldest'] = $this->formatedprint(
344				$this->animal->manager->getLang('animal_info_pageschangedescriptor'),
345				array(
346					'link' => $this->elhtmlurl($ot),
347					'user' => $m['current']['last_change']['user'],
348					'date' => $m['current']['last_change']['date'],
349					'dformat' => $conf['dformat'],
350					'dunits' => $this->animal->manager->getLang('timedeltaunits')
351				)
352			);
353		}else{
354			$info['oldest'] = array(
355				'date' => $m['current']['last_change']['date'],
356				'user' => $m['current']['last_change']['user'],
357				'url' => $this->elurl($ot),
358				'namespace' => $ot
359			);
360		}
361
362		// latest edited page
363		$lt = $this->latest($fi);
364		$m = $this->getmeta($lt);
365		if($nice) {
366			$info['latest'] = $this->formatedprint(
367				$this->animal->manager->getLang('animal_info_pageschangedescriptor'),
368				array(
369					'link' => $this->elhtmlurl($lt),
370					'user' => $m['current']['last_change']['user'],
371					'date' => $m['current']['last_change']['date'],
372					'dformat' => $conf['dformat'],
373					'dunits' => $this->animal->manager->getLang('timedeltaunits')
374				)
375			);
376		}else{
377			$info['latest'] = array(
378				'date' => $m['current']['last_change']['date'],
379				'user' => $m['current']['last_change']['user'],
380				'url' => $this->elurl($lt),
381				'namespace' => $lt
382			);
383		}
384
385		return $info;
386	}
387
388	/**
389	 * Gather infos about animal's medias
390	 * @param $nice boolean telling whether output must be formated
391	 * @return array of infos descriptors
392	 */
393	public function mediasInfo($nice=false) {
394		if(!$this->animal) return 0;
395		global $conf;
396
397		$fi = $this->filesinfo($this->animal->getPath().'/data/media', '', true);
398		$info = array();
399
400		// general
401		$info['count'] = count($fi);
402
403		$info['average'] = $this->avgsize($fi);
404		if($nice) $info['average'] = $this->animal->manager->nicesize($info['average']);
405
406		$info['total'] = $this->totalsize($fi);
407		if($nice) $info['total'] = $this->animal->manager->nicesize($info['total']);
408
409		// biggest media
410		$bg = $this->biggest($fi);
411		if($nice) {
412			$info['biggest'] = $this->elhtmlurl($bg, true).' ('.$this->animal->manager->nicesize($fi[$bg]['size']).')';
413		}else{
414			$info['biggest'] = array(
415				'size' => $fi[$bg]['size'],
416				'url' => $this->elurl($bg, true),
417				'namespace' => $bg
418			);
419		}
420
421		// smallest media
422		$st = $this->smallest($fi);
423		if($nice) {
424			$info['smallest'] = $this->elhtmlurl($st, true).' ('.$this->animal->manager->nicesize($fi[$st]['size']).')';
425		}else{
426			$info['smallest'] = array(
427				'size' => $fi[$st]['size'],
428				'url' => $this->elurl($st, true),
429				'namespace' => $st
430			);
431		}
432
433		return $info;
434	}
435}
436?>
437