1<?php
2/**
3 * Wiki farm animal class
4 *
5 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author   Etienne MELEARD <etienne.meleard@cru.fr>
7 * @desc     Describes a farm animal, providing ways to interract with its config / users / files
8 *           also provides static methods to create animals / get global infos about the farm.
9 */
10
11// Set the plugin include path depending if we are under DokuWiki or SOAPServer context
12if(!defined('DOKU_FARM_PLUGIN')) define('DOKU_FARM_PLUGIN', defined('DOKU_FARMPLUGINLOADED') ? DOKU_INC.'lib/plugins/farm/' : './');
13
14require_once(DOKU_FARM_PLUGIN.'infos.animal.class.php');
15
16class dokuwiki_farm_animal {
17	private $name = '';
18	private $path = '';
19
20	var $manager = null;
21	private $infos = null;
22
23	private $wikiconf = array();
24
25	private static $farmconf = array();
26
27	/**
28	 * @param $name animal name
29	 * @param $manager object that must handle error(), success(), nicesize(), getLang() ... calls
30	 */
31	function __construct($name = '', $manager = null) {
32		self::getConf();
33		if(self::exists($name)) {
34			$this->name = $name;
35			$this->manager = & $manager;
36			$this->path = self::$farmconf['farmfsroot'].self::$farmconf['barn'].$this->name;
37		}
38	}
39
40	/**
41	 * Return animal name
42	 * @return string
43	 */
44	public function getName() {
45		return $this->name;
46	}
47
48	/**
49	 * Return animal path in filesystem, based on farmfsroot farmconfig parameter
50	 * @return string
51	 */
52	public function getPath() {
53		return $this->path;
54	}
55
56	/**
57	 * Return url of animal ressource, with respect of animal config (useslash/userewrite) and farm config (userewrite/farmrewrite)
58	 * @param $id ressource id (eg. namespace ...)
59	 * @param $media boolean indicating whether the ressource is a media or a page
60	 * @param $xmlencode boolean indicating whether output xml entities must be encoded
61	 * @return url string
62	 */
63	public function getUrl($id = '', $media = false, $xmlencode = true) {
64		self::getConf();
65
66		$wc = $this->getWikiConf();
67		if($id) {
68			$id = str_replace('/', ':', $id);
69			if(isset($wc['editable']['useslash'])) if($wc['editable']['useslash']) $id = str_replace(':', '/', $id);
70			if(isset($wc['protected']['useslash'])) if($wc['protected']['useslash']) $id = str_replace(':', '/', $id);
71		}
72
73		$aur = false;
74		if(isset($wc['editable']['userewrite'])) if($wc['editable']['userewrite']) $aur = true;
75		if(isset($wc['protected']['userewrite'])) if($wc['protected']['userewrite']) $aur = true;
76
77		$url = self::$farmconf['farmwebroot'];
78
79		if(!self::$farmconf['userewrite']) {
80			$url .= self::$farmconf['farmer'];
81			if(!$aur) {
82				if(!$media) {
83					$url .= 'doku.php?animal='.$this->getName();
84					if($id) $url .= ($xmlencode ? '&amp;' : '&').'id='.$id;
85				}else $url .= 'lib/exe/fetch.php?w=&l=&cache=cache&media='.$id;
86			}else{
87				if(!$media) {
88					$url .= $id.'?animal='.$this->getName();
89				}else $url .= '_media/'.$id.'?w=&h=&cache=cache';
90			}
91		}else{
92			if(!self::$farmconf['farmrewrite']) $url .= self::$farmconf['barn'];
93			$url .= $this->getName();
94			if(!$aur) {
95				if(!$media) {
96					if($id) $url .= '/doku.php?id='.$id;
97				}else $url .= 'lib/exe/fetch.php?w=&l=&cache=cache&media='.$id;
98			}else{
99				if(!$media) {
100					$url .= '/'.$id;
101				}else $url .= '_media/'.$id.'?w=&h=&cache=cache';
102			}
103		}
104		return $url;
105	}
106
107	/**
108	 * Return the value of an animal's metadata
109	 * @param $name metadata identifier
110	 * @param $default value to return if "name" metadata not found
111	 * @return string
112	 */
113	public function getMetadata($name = '', $default = null) {
114		global $conf;
115		if(!self::exists($this->name)) return null;
116		self::getConf();
117		$metadata = array();
118		foreach(explode("\n", @file_get_contents($this->path.'/animal.meta')) as $l) {
119			$l = preg_split('`\s+:\s+`', $l);
120			if(count($l) == 2) $metadata[$l[0]] = $l[1];
121		}
122		if($name == '') return $metadata;
123		return isset($metadata[$name]) ? $metadata[$name] : $default;
124	}
125
126	/**
127	 * Set animal's metadata
128	 * @param $name metadata identifier
129	 * @param $value metadata value
130	 * @return success as boolean
131	 */
132	public function setMetadata($name = '', $value = '') {
133		global $conf;
134		if($name == '') return false;
135		if(!self::exists($this->name)) return null;
136		self::getConf();
137		$metadata = array();
138		foreach(explode("\n", @file_get_contents($this->path.'/animal.meta')) as $l) {
139			$l = preg_split('`\s+:\s+`', $l);
140			if(count($l) == 2) $metadata[$l[0]] = $l[1];
141		}
142		if(is_null($value)) {
143			if(isset($metadata[$name])) unset($metadata[$name]);
144		}else $metadata[$name] = $value;
145		$out = array();
146		foreach($metadata as $k => $v) $out[] = $k.' : '.$v;
147		if($fp = fopen($this->path.'/animal.meta', 'w')) {
148			fwrite($fp, implode("\n", $out));
149			fclose($fp);
150			return true;
151		}
152		return false;
153	}
154
155	/**
156	 * Returns animal's status
157	 * @return string
158	 */
159	public function getStatus() {
160		return $this->getMetadata('status', 'open');
161	}
162
163	/**
164	 * Returns animal's lock state
165	 * @return string
166	 */
167	public function getLockState() {
168		return $this->getMetadata('lockstate');
169	}
170
171	/**
172	 * Returns an animal_infos object loaded from the animal
173	 * @return dokuwiki_farm_animal_infos object
174	 */
175	public function getInfos() {
176		if(!$this->infos) $this->infos = new dokuwiki_farm_animal_infos($this);
177		return $this->infos;
178	}
179
180	/**
181	 * Gather and returns the animal wiki configuration as a multidimensionnal array
182	 * @return multidimensionnal associative array
183	 */
184	public function getWikiConf() {
185		if(!self::exists($this->name)) return null;
186		$getfromas = array(
187			'local.protected.php' => 'protected',
188			'local.php' => 'editable'
189		);
190		foreach($getfromas as $from => $as) {
191			if(@file_exists($this->path.'/conf/'.$from)) {
192				$conf = array();
193				foreach(explode("\n", @file_get_contents($this->path.'/conf/'.$from)) as $line) {
194					if(preg_match('`^\$conf\[([^]]+(\]\[[^]]+)?)\]\s*=\s*([^;]+);`', $line, $m)) {
195						$path = array_map(create_function('$e', 'return preg_replace(\'`^(\\\'|")?([a-zA-Z0-9_-]+)(\\\'|")?$`i\', \'$2\', trim($e));'), explode('][', $m[1]));
196						$value = $m[3];
197						eval('$conf['.implode('][', $path).'] = $value;');
198					}
199				}
200				$this->wikiconf[$as] = $conf;
201			}
202		}
203		krsort($this->wikiconf);
204		return $this->wikiconf;
205	}
206
207	/**
208	 * Set one of the animal wiki configuration parameters
209	 * @param $mode string pointing the target configuration
210	 * @param $path array containing the successive path elents used to access the target configuration parameter
211	 * @param $value value to be set
212	 * @return success as boolean
213	 */
214	public function setWikiConf($mode = 'editable', $path = array(), $value = null) {
215		if(!in_array($mode, array('editable', 'protected'))) return false;
216		if(!count($path)) return false;
217		if(!count($this->wikiconf)) $this->getWikiConf();
218		$c = & $this->wikiconf[$mode];
219		foreach($path as $p) {
220			if(!isset($c[$p])) $c[$p] = array();
221			$c = & $c[$p];
222		}
223		$c = $value;
224		return true;
225	}
226
227	/**
228	 * Turns a multidimensionnal animal wiki configuration array into an array of php code strings
229	 * @param $head path above element
230	 * @param $el array element
231	 * @return configuration php strings
232	 */
233	private function buildconfrecursive($head, $el) {
234		if(is_array($el)) {
235			$r = array();
236			foreach($el as $k => $v) $r = array_merge($r, $this->buildconfrecursive(array_merge($head, array($k)), $v));
237			return $r;
238		}
239		if(is_bool($el)) $el = $el ? 'true' : 'false';
240		elseif(is_numeric($el)) $el = "$el";
241		elseif(
242			!(substr($el, 0, 1) == "'" && substr($el, -1) == "'") &&
243			!(substr($el, 0, 1) == '"' && substr($el, -1) == '"') &&
244			!preg_match('`^DOKU_[A-Z_]+`', $el) &&
245			!preg_match('`\.DOKU_[A-Z_]+$`', $el)
246		) $el = "'".$el."'";
247		return array('$conf[\''.implode("']['", $head).'\'] = '.$el.';');
248	}
249
250	/**
251	 * Save the animal's wiki configuration
252	 * @return success as boolean
253	 */
254	public function saveWikiConf() {
255		if(!self::exists($this->name)) return false;
256		$savefromto = array(
257			'local.protected.php' => 'protected',
258			'local.php' => 'editable'
259		);
260		$r = true;
261		foreach($savefromto as $to => $from) {
262			$conf = implode("\n", $this->buildconfrecursive(array(), $this->wikiconf[$from]));
263			if($fp = @fopen($this->path.'/conf/'.$to, 'w')) {
264				fwrite($fp, "<?php\n// Generated by farm plugin (".date('Y-m-d H:i:s').")\n\n".$conf."\n");
265				fclose($fp);
266			}else $r = false;
267		}
268		$this->wikiconf = array();
269		return $r;
270	}
271
272	/**
273	 * Returns the animal's raw wiki configuration as a single string
274	 * @param $mode string pointing the target configuration
275	 * @return configuration as string
276	 */
277	public function getRawWikiConf($mode = 'editable') {
278		if(!self::exists($this->name)) return null;
279		$rel = array(
280			'protected' => 'local.protected.php',
281			'editable' => 'local.php'
282		);
283		if(!in_array($mode, array_keys($rel))) return null;
284		if(!@file_exists($this->path.'/conf/'.$rel[$mode])) return null;
285		$r = array();
286		foreach(explode("\n", @file_get_contents($this->path.'/conf/'.$rel[$mode])) as $line) {
287			if(preg_match('`^(\$conf\[[^]]+(\]\[[^]]+)?\]\s*=\s*[^;]+);`', $line, $m)) $r[] = $m[1];
288		}
289		return implode("\n", $r);
290	}
291
292	/**
293	 * Save animal wiki configuration from string
294	 * @param $mode string pointing the target configuration
295	 * @param $conf string
296	 * @return success as boolean
297	 */
298	public function saveRawWikiConf($mode = 'editable', $conf = '') {
299		if(!self::exists($this->name)) return false;
300		$rel = array(
301			'protected' => 'local.protected.php',
302			'editable' => 'local.php'
303		);
304		if(!in_array($mode, array_keys($rel))) return false;
305		if(!@file_exists($this->path.'/conf/'.$rel[$mode])) return false;
306		$this->wikiconf = array();
307		$conf = implode("\n", array_map(create_function('$e', '$e = trim($e); if(substr($e, -1) != \';\') $e .= \';\'; return $e;'), explode("\n", $conf)));
308		if($fp = @fopen($this->path.'/conf/'.$rel[$mode], 'w')) {
309			fwrite($fp, "<?php\n// Generated by farm plugin (".date('Y-m-d H:i:s').")\n\n".$conf."\n");
310			fclose($fp);
311			return true;
312		}
313		return false;
314	}
315
316	/**
317	 * EXPERIMENTAL
318	 * Returns the animal users as an array
319	 * @return array of arrays containing 'pass', 'name', 'mail' and 'groups' (array) entries, may be empty if backend not compatible
320	 */
321	public function getUsers() {
322		$users = array();
323
324		global $conf;
325		$fconf = $conf;
326
327		$conf = array();
328
329		// Load animal conf
330		$wc = $this->getWikiConf();
331		$conf = array_merge($wc['editable'], $wc['protected']);
332		if(!isset($conf['authtype']) || empty($conf['authtype'])) $conf['authtype'] = 'plain';
333
334		if($conf['authtype'] == 'plain') {
335			foreach(explode("\n", @file_get_contents($this->path.'/conf/users.auth.php')) as $line) {
336				$line = preg_replace('/#.*$/','',$line); //ignore comments
337				$line = trim($line);
338				if(empty($line)) continue;
339				$row = split(':', $line, 5);
340				$groups = split(',', $row[4]);
341				$users[$row[0]] = array(
342					'pass' => $row[1],
343					'name' => urldecode($row[2]),
344					'mail' => $row[3],
345					'grps' => $groups
346				);
347			}
348		}else{
349			$auth_class = 'auth_'.$conf['authtype'];
350			if(!class_exists($auth_class)) if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) include DOKU_INC.'auth/'.$conf['authtype'].'.class.php';
351
352			if(class_exists($auth_class)) {
353				$localauth = new $auth_class();
354				$localauth->users = null;
355				$users = $localauth->retrieveUsers();
356			}
357		}
358
359		$conf = $fconf;
360
361		return $users;
362	}
363
364	/**
365	 * EXPERIMENTAL
366	 * Add a user to the animal
367	 * @param $user user login
368	 * @param $pass user clear password
369	 * @param $name user name
370	 * @param $mail user email
371	 * @param $grps array of groups the user is in
372	 * @return success as boolean, also false if backend not compatible
373	 */
374	public function addUser($user, $pass, $name, $mail, $grps=null) {
375		if($name == '') $name = $user;
376
377		$users = $this->getUsers();
378		if(isset($users[$user])) return false;
379
380		global $conf;
381		$fconf = $conf;
382		$conf = array();
383
384		// Load animal conf
385		$wc = $this->getWikiConf();
386		$conf = array_merge($wc['editable'], $wc['protected']);
387		if(!isset($conf['authtype']) || empty($conf['authtype'])) $conf['authtype'] = 'plain';
388
389		if($conf['authtype'] == 'plain') {
390			$authfile = @file_get_contents($this->path.'/conf/users.auth.php');
391			$pass = auth_cryptPassword($pass, isset($conf['passcrypt']) ? $conf['passcrypt'] : 'smd5');
392			if(!is_array($grps)) $grps = array($conf['defaultgroup']);
393			$groups = join(',', $grps);
394			$userline = join(':', array($user, $pass, $name, $mail, $groups))."\n";
395			if($authfile) $authfile .= $userline;
396			if($authfile) {
397				if($fp = fopen($this->path.'/conf/users.auth.php', 'w')) {
398					fwrite($fp, $authfile);
399					fclose($fp);
400					$r = true;
401				}else $r = false;
402			}else $r = false;
403		}else{
404			$auth_class = 'auth_'.$conf['authtype'];
405			if(!class_exists($auth_class)) if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) include DOKU_INC.'auth/'.$conf['authtype'].'.class.php';
406
407			if(class_exists($auth_class)) {
408				$localauth = new $auth_class();
409				$localauth->users = null;
410				$r = ($localauth->createUser($user, $pass, $name, $mail, $grps) != null);
411			}else $r = false;
412		}
413
414		$conf = $fconf;
415
416		return $r;
417	}
418
419	/**
420	 * EXPERIMENTAL
421	 * Delete a user from the animal
422	 * @param $user user login
423	 * @return success as boolean, also false if backend not compatible
424	 */
425	public function deleteUser($user) {
426		if($user == '') return false;
427
428		$users = $this->getUsers();
429		if(!isset($users[$user])) return false;
430
431		global $conf;
432		$fconf = $conf;
433		$conf = array();
434
435		// Load animal conf
436		$wc = $this->getWikiConf();
437		$conf = array_merge($wc['editable'], $wc['protected']);
438		if(!isset($conf['authtype']) || empty($conf['authtype'])) $conf['authtype'] = 'plain';
439
440		if($conf['authtype'] == 'plain') {
441			$out = array();
442			foreach(explode("\n", @file_get_contents($this->path.'/conf/users.auth.php')) as $line) {
443				if(substr($line, 0, strlen($user) + 1) != $user.':') $out[] = $line;
444			}
445
446			if($fp = fopen($this->path.'/conf/users.auth.php', 'w')) {
447				fwrite($fp, implode("\n", $out));
448				fclose($fp);
449				$r = true;
450			}else $r = false;
451		}else{
452			$auth_class = 'auth_'.$conf['authtype'];
453			if(!class_exists($auth_class)) if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) include DOKU_INC.'auth/'.$conf['authtype'].'.class.php';
454
455			if(class_exists($auth_class)) {
456				$localauth = new $auth_class();
457				$localauth->users = null;
458				$r = ($localauth->deleteUsers(array($user)) > 0);
459			}else $r = false;
460		}
461
462		$conf = $fconf;
463
464		return $r;
465	}
466
467	/**
468	 * Scans the filesystem to gather informations about files / directories
469	 * @param $p path
470	 * @param $l level of depth
471	 * @param $bp start path to strip
472	 * @param $pref base64 encoded path reference
473	 * @return array of fs items descriptors
474	 */
475	private function scanfs($p='', $l=0, $bp='', $pref='') {
476		if($p == '') return array();
477		if($bp == '') $bp = $p;
478		if(substr($bp, -1) != '/') $bp .= '/';
479		$fs = array();
480		foreach(scandir($p) as $i) {
481			if($i == '.' || $i == '..') continue;
482			if(@is_file($p.'/'.$i)) {
483				$fi = stat($p.'/'.$i);
484				$fs[base64_encode(substr($p.'/'.$i, strlen($bp)))] = array(
485					'type' => 'file',
486					'name' => $i,
487					'size' => $fi['size'],
488					'mtime' => $fi['mtime'],
489					'lvl' => $l,
490					'pref' => $pref
491				);
492			}
493			if(@is_dir($p.'/'.$i)) {
494				$fs[base64_encode(substr($p.'/'.$i, strlen($bp)))] = array(
495					'type' => 'dir',
496					'name' => $i,
497					'lvl' => $l,
498					'pref' => $pref
499				);
500				$fs = array_merge($fs, $this->scanfs($p.'/'.$i, $l+1, $bp, base64_encode(substr($p.'/'.$i, strlen($bp)))));
501			}
502		}
503		return $fs;
504	}
505
506	/**
507	 * Gather informations about the animal's filesystem
508	 * @return array of fs items descriptors
509	 */
510	public function getFs() {
511		if(!self::exists($this->name)) return false;
512		return $this->scanfs($this->getPath());
513	}
514
515	/**
516	 * Returns the content of a file in the animal's filesystem
517	 * @return file content as a string
518	 */
519	public function getFileContent($file) {
520		if(!self::exists($this->name)) return '';
521		if(!@file_exists($this->path.'/'.$file)) return '';
522		return @file_get_contents($this->path.'/'.$file);
523	}
524
525	/**
526	 * Set the content of a file in the animal's filesystem
527	 * @param $file file content as a string
528	 * @param $ctn file content to be put as a string
529	 * @param $delete boolean telling whether to delete the pointed file
530	 * @return success as boolean
531	 */
532	public function setFileContent($file, $ctn='', $delete=false) {
533		if(!self::exists($this->name)) return false;
534		if($delete && !@file_exists($this->path.'/'.$file)) return false;
535		if($delete) return @unlink($this->path.'/'.$file);
536		if($fp = fopen($this->path.'/'.$file, 'w')) {
537			fwrite($fp, $ctn);
538			fclose($fp);
539			return true;
540		}else return false;
541	}
542
543	/**
544	 * Deletes the animal, calling animal hooks
545	 * @return success as boolean
546	 */
547	public function delete() {
548		self::getConf();
549
550		if(@file_exists(DOKU_FARM_PLUGIN.'hooks.animal.class.php')) {
551			@include DOKU_FARM_PLUGIN.'hooks.animal.class.php';
552		}
553
554		$hookhandler = class_exists('dokuwiki_farm_animal_hooks') ? new dokuwiki_farm_animal_hooks($handler) : null;
555
556		if($hookhandler) {
557			if(!$hookhandler->trigger('delete', 'before', array($this))) {
558				if($handler) $handler->errors[] = array('code' => 'animal_delete_eventhandlercancel_failure');
559				return false;
560			}
561		}
562
563		if($this->name == self::$farmconf['animaltemplate']) return false;
564		$r = self::rmdir($this->path);
565		if(!$r) return false;
566
567		if($hookhandler) $hookhandler->trigger('delete', 'after', array($this->name));
568
569		return true;
570	}
571
572	/**
573	 * Creates a new animal, calling animal hooks
574	 * @param $name name of the new animal
575	 * @param $template template to use for clonning as string
576	 * @param $host host to bind if virtual mode in use
577	 * @param $handler handler object featuring a errors array property
578	 * @return success as boolean
579	 */
580	public static function createNew($name = '', $template = null, $host = null, $handler = null) {
581		self::getConf();
582
583		if(@file_exists(DOKU_FARM_PLUGIN.'hooks.animal.class.php')) {
584			@include DOKU_FARM_PLUGIN.'hooks.animal.class.php';
585		}
586
587		$hookhandler = class_exists('dokuwiki_farm_animal_hooks') ? new dokuwiki_farm_animal_hooks($handler) : null;
588
589		if($hookhandler) {
590			if(!$hookhandler->trigger('create', 'before', array($name, $template))) {
591				if($handler) $handler->errors[] = array('code' => 'animal_new_eventhandlercancel_failure');
592				return false;
593			}
594		}
595
596		if($farmconf['farmmaxanimals'] > 0) {
597			if(count(self::listAnimals()) >= $farmconf['farmmaxanimals']) {
598				if($handler) $handler->errors[] = array('code' => 'animal_new_farmfull_failure');
599				return false;
600			}
601		}
602
603		if($farmconf['farmmaxsize'] > 0) {
604			if(count(self::farmSize()) >= $farmconf['farmmaxsize']) {
605				if($handler) $handler->errors[] = array('code' => 'animal_new_farmtoobig_failure');
606				return false;
607			}
608		}
609
610		if(!$template) $template = self::$farmconf['animaltemplate'];
611
612		$name = strtr($name, "àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ", "aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY");
613		$name = preg_replace('`\s+`', '_', $name);
614		if(!preg_match('`^[a-z0-9._-]+`i', $name)) {
615			if($handler) $handler->errors[] = array('code' => 'animal_new_badname_failure');
616			return false;
617		}
618		if(self::exists($name) || ($name == trim(self::$farmconf['farmer'], '/')) || @is_dir(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name)) {
619			if($handler) $handler->errors[] = array('code' => 'animal_new_namealreadyinuse_failure', 'data' => array($name));
620			return false;
621		}
622
623		if(!self::exists($template)) {
624			if($handler) $handler->errors[] = array('code' => 'animal_new_templatenotfound_failure', 'data' => array($template));
625			return false;
626		}
627
628		$cph = create_function('$path,$isfile', '
629if(!$isfile) return;
630$ctn = @file_get_contents($path);
631if(!preg_match(\'\\{ANIMAL\\}\', $ctn)) return;
632$ctn = str_replace(\'{ANIMAL}\', \''.$name.'\', $ctn);
633if($fp = fopen($path, \'w\')) {
634  fwrite($fp, $ctn);
635  fclose($fp);
636}
637');
638
639		if(!self::cp_r(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$template, self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name, false, array(), $cph)) {
640			if($handler) $handler->errors[] = array('code' => 'animal_new_templatecopy_failure');
641			return false;
642		}
643
644		if($fp = fopen(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name.'/animal.meta', 'w')) {
645			fwrite($fp, 'creation_date : '.time());
646			fclose($fp);
647		}else{
648			if($handler) $handler->errors[] = array('code' => 'animal_new_metacreate_failure');
649			return false;
650		}
651
652		if(!self::exists($name)) {
653			if($handler) $handler->errors[] = array('code' => 'animal_new_createdespite_failure');
654			return false;
655		}
656
657		$a = new dokuwiki_farm_animal($name, $handler->manager);
658		if(!$a->setWikiConf('protected', array('basedir'), 'DOKU_ANIMAL_BASEDIR.\''.$name.'\'') || !$a->setWikiConf('editable', array('title'), $name)) {
659			if($handler) $handler->errors[] = array('code' => 'animal_new_setconfig_failure');
660			return false;
661		}
662		if(!$a->saveWikiConf()) {
663			if($handler) $handler->errors[] = array('code' => 'animal_new_saveconfig_failure');
664			return false;
665		}
666
667		if($host && self::$farmconf['virtual']) {
668			$vh = @file_get_contents(DOKU_FARM_PLUGIN.'virtual_hosts.php');
669			$e = array_filter(array_merge(array(self::$farmconf['farmerhost']), array_map(create_function('$l', 'return preg_replace(\'`^([^\s]+)\s`\', \'$1\', $l);'), explode("\n", $vh))), create_function('$v', 'return ($v == \''.$host.'\');'));
670			if(!count($e)) {
671				$fp = fopen(DOKU_FARM_PLUGIN.'virtual_hosts.php', 'w');
672				if($fp) {
673					fwrite($fp, $vh."\n".$host.' '.$name);
674					fclose($fp);
675				}else{
676					if($handler) $handler->errors[] = array('code' => 'animal_new_savevirtualhost_failure');
677					return false;
678				}
679			}else{
680				if($handler) $handler->errors[] = array('code' => 'animal_new_virtualhostexists_failure');
681				return false;
682			}
683		}
684
685		if($hookhandler) $hookhandler->trigger('create', 'after', array($a));
686
687		return $name;
688	}
689
690	/**
691	 * Gather the list of animals
692	 * @return array of dokuwiki_farm_animal objects
693	 */
694	public static function listAnimals($manager = null) {
695		self::getConf();
696		$a = array();
697		foreach(scandir(self::$farmconf['farmfsroot'].self::$farmconf['barn']) as $i) {
698			if($i == '.' && $i == '..') continue;
699			if(self::exists($i)) $a[] = new dokuwiki_farm_animal($i, $manager);
700		}
701		return $a;
702	}
703
704	/**
705	 * Get the farm disk usage
706	 * @return size in bytes
707	 */
708	public static function farmSize() {
709		return array_sum(array_map(create_function('$a', 'return $a->getInfos()->getSize();'), self::listAnimals()));
710	}
711
712	/**
713	 * Copy / move a directory contents
714	 * @param $src source path
715	 * @param $dest destination path
716	 * @param $mv boolean telling whether copying or moving contents
717	 * @param $avoid path of elemnts that musn't be copied / moved
718	 * @param $copyhook function to handle cpoy operations
719	 * @return success as boolean
720	 */
721	private static function cp_r($src, $dest, $mv=false, $avoid=array(), $copyhook = null) {
722		if(in_array($src, $avoid)) return true;
723		if(@is_file($src)) {
724			$r = copy($src, $dest);
725			chmod($dest, fileperms($src));
726			if($mv) $r &= unlink($src);
727			if($copyhook) $copyhook($dest, true);
728			return $r;
729		}
730
731		if(!@is_dir($dest)) mkdir($dest, fileperms($src));
732
733		$r = true;
734		foreach(scandir($src) as $i) {
735			if($i == '.' || $i == '..') continue;
736			$r &= self::cp_r($src.'/'.$i, $dest.'/'.$i, $mv, $avoid);
737		}
738
739		if($mv) $r &= rmdir($src);
740		if($copyhook) $copyhook($dest, false);
741
742		return $r;
743	}
744
745	/**
746	 * Removes a directory
747	 * @param $p path to the directory
748	 * @return success as boolean
749	 */
750	private static function rmdir($p) {
751		$r = true;
752		foreach(scandir($p) as $i) {
753			if($i == '.' || $i == '..') continue;
754			if(@is_dir($p.'/'.$i)) $r &= self::rmdir($p.'/'.$i);
755			if(@is_file($p.'/'.$i)) $r &= unlink($p.'/'.$i);
756			if(!$r) break;
757		}
758		if($r) rmdir($p);
759		return $r;
760	}
761
762	/**
763	 * Gather and return the farm configuration
764	 * @return associative array of farm configuration parameters
765	 */
766	public static function getConf() {
767		if(!self::$farmconf) {
768			$farmconf = array();
769			include(DOKU_FARM_PLUGIN.'config.php');
770			self::$farmconf = $farmconf;
771		}
772		return self::$farmconf;
773	}
774
775	/**
776	 * Check if an animal exists
777	 * @param $name name to test
778	 * @return boolean
779	 */
780	public static function exists($name = '') {
781		if($name == '') return false;
782		$name = strtr($name, "àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ", "aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY");
783		$name = preg_replace('`\s+`', '_', $name);
784		if(!preg_match('`^[a-z0-9._-]+`i', $name)) return false;
785		self::getConf();
786		return @is_dir(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name) && @file_exists(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name.'/animal.meta');
787	}
788}
789
790?>
791