<?php
/**
 * Wiki farm animal class
 *
 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author   Etienne MELEARD <etienne.meleard@cru.fr>
 * @desc     Describes a farm animal, providing ways to interract with its config / users / files
 *           also provides static methods to create animals / get global infos about the farm.
 */

// Set the plugin include path depending if we are under DokuWiki or SOAPServer context
if(!defined('DOKU_FARM_PLUGIN')) define('DOKU_FARM_PLUGIN', defined('DOKU_FARMPLUGINLOADED') ? DOKU_INC.'lib/plugins/farm/' : './');

require_once(DOKU_FARM_PLUGIN.'infos.animal.class.php');

class dokuwiki_farm_animal {
	private $name = '';
	private $path = '';
	
	var $manager = null;
	private $infos = null;
	
	private $wikiconf = array();
	
	private static $farmconf = array();
	
	/**
	 * @param $name animal name
	 * @param $manager object that must handle error(), success(), nicesize(), getLang() ... calls
	 */
	function __construct($name = '', $manager = null) {
		self::getConf();
		if(self::exists($name)) {
			$this->name = $name;
			$this->manager = & $manager;
			$this->path = self::$farmconf['farmfsroot'].self::$farmconf['barn'].$this->name;
		}
	}
	
	/**
	 * Return animal name
	 * @return string
	 */
	public function getName() {
		return $this->name;
	}
	
	/**
	 * Return animal path in filesystem, based on farmfsroot farmconfig parameter
	 * @return string
	 */
	public function getPath() {
		return $this->path;
	}
	
	/**
	 * Return url of animal ressource, with respect of animal config (useslash/userewrite) and farm config (userewrite/farmrewrite)
	 * @param $id ressource id (eg. namespace ...)
	 * @param $media boolean indicating whether the ressource is a media or a page
	 * @param $xmlencode boolean indicating whether output xml entities must be encoded
	 * @return url string
	 */
	public function getUrl($id = '', $media = false, $xmlencode = true) {
		self::getConf();
		
		$wc = $this->getWikiConf();
		if($id) {
			$id = str_replace('/', ':', $id);
			if(isset($wc['editable']['useslash'])) if($wc['editable']['useslash']) $id = str_replace(':', '/', $id);
			if(isset($wc['protected']['useslash'])) if($wc['protected']['useslash']) $id = str_replace(':', '/', $id);
		}
		
		$aur = false;
		if(isset($wc['editable']['userewrite'])) if($wc['editable']['userewrite']) $aur = true;
		if(isset($wc['protected']['userewrite'])) if($wc['protected']['userewrite']) $aur = true;
		
		$url = self::$farmconf['farmwebroot'];
		
		if(!self::$farmconf['userewrite']) {
			$url .= self::$farmconf['farmer'];
			if(!$aur) {
				if(!$media) {
					$url .= 'doku.php?animal='.$this->getName();
					if($id) $url .= ($xmlencode ? '&amp;' : '&').'id='.$id;
				}else $url .= 'lib/exe/fetch.php?w=&l=&cache=cache&media='.$id;
			}else{
				if(!$media) {
					$url .= $id.'?animal='.$this->getName();
				}else $url .= '_media/'.$id.'?w=&h=&cache=cache';
			}
		}else{
			if(!self::$farmconf['farmrewrite']) $url .= self::$farmconf['barn'];
			$url .= $this->getName();
			if(!$aur) {
				if(!$media) {
					if($id) $url .= '/doku.php?id='.$id;
				}else $url .= 'lib/exe/fetch.php?w=&l=&cache=cache&media='.$id;
			}else{
				if(!$media) {
					$url .= '/'.$id;
				}else $url .= '_media/'.$id.'?w=&h=&cache=cache';
			}
		}
		return $url;
	}
	
	/**
	 * Return the value of an animal's metadata
	 * @param $name metadata identifier
	 * @param $default value to return if "name" metadata not found
	 * @return string
	 */
	public function getMetadata($name = '', $default = null) {
		global $conf;
		if(!self::exists($this->name)) return null;
		self::getConf();
		$metadata = array();
		foreach(explode("\n", @file_get_contents($this->path.'/animal.meta')) as $l) {
			$l = preg_split('`\s+:\s+`', $l);
			if(count($l) == 2) $metadata[$l[0]] = $l[1];
		}
		if($name == '') return $metadata;
		return isset($metadata[$name]) ? $metadata[$name] : $default;
	}
	
	/**
	 * Set animal's metadata
	 * @param $name metadata identifier
	 * @param $value metadata value
	 * @return success as boolean
	 */
	public function setMetadata($name = '', $value = '') {
		global $conf;
		if($name == '') return false;
		if(!self::exists($this->name)) return null;
		self::getConf();
		$metadata = array();
		foreach(explode("\n", @file_get_contents($this->path.'/animal.meta')) as $l) {
			$l = preg_split('`\s+:\s+`', $l);
			if(count($l) == 2) $metadata[$l[0]] = $l[1];
		}
		if(is_null($value)) {
			if(isset($metadata[$name])) unset($metadata[$name]);
		}else $metadata[$name] = $value;
		$out = array();
		foreach($metadata as $k => $v) $out[] = $k.' : '.$v;
		if($fp = fopen($this->path.'/animal.meta', 'w')) {
			fwrite($fp, implode("\n", $out));
			fclose($fp);
			return true;
		}
		return false;
	}
	
	/**
	 * Returns animal's status
	 * @return string
	 */
	public function getStatus() {
		return $this->getMetadata('status', 'open');
	}
	
	/**
	 * Returns animal's lock state
	 * @return string
	 */
	public function getLockState() {
		return $this->getMetadata('lockstate');
	}
	
	/**
	 * Returns an animal_infos object loaded from the animal
	 * @return dokuwiki_farm_animal_infos object
	 */
	public function getInfos() {
		if(!$this->infos) $this->infos = new dokuwiki_farm_animal_infos($this);
		return $this->infos;
	}
	
	/**
	 * Gather and returns the animal wiki configuration as a multidimensionnal array
	 * @return multidimensionnal associative array
	 */
	public function getWikiConf() {
		if(!self::exists($this->name)) return null;
		$getfromas = array(
			'local.protected.php' => 'protected',
			'local.php' => 'editable'
		);
		foreach($getfromas as $from => $as) {
			if(@file_exists($this->path.'/conf/'.$from)) {
				$conf = array();
				foreach(explode("\n", @file_get_contents($this->path.'/conf/'.$from)) as $line) {
					if(preg_match('`^\$conf\[([^]]+(\]\[[^]]+)?)\]\s*=\s*([^;]+);`', $line, $m)) {
						$path = array_map(create_function('$e', 'return preg_replace(\'`^(\\\'|")?([a-zA-Z0-9_-]+)(\\\'|")?$`i\', \'$2\', trim($e));'), explode('][', $m[1]));
						$value = $m[3];
						eval('$conf['.implode('][', $path).'] = $value;');
					}
				}
				$this->wikiconf[$as] = $conf;
			}
		}
		krsort($this->wikiconf);
		return $this->wikiconf;
	}
	
	/**
	 * Set one of the animal wiki configuration parameters
	 * @param $mode string pointing the target configuration
	 * @param $path array containing the successive path elents used to access the target configuration parameter
	 * @param $value value to be set
	 * @return success as boolean
	 */
	public function setWikiConf($mode = 'editable', $path = array(), $value = null) {
		if(!in_array($mode, array('editable', 'protected'))) return false;
		if(!count($path)) return false;
		if(!count($this->wikiconf)) $this->getWikiConf();
		$c = & $this->wikiconf[$mode];
		foreach($path as $p) {
			if(!isset($c[$p])) $c[$p] = array();
			$c = & $c[$p];
		}
		$c = $value;
		return true;
	}
	
	/**
	 * Turns a multidimensionnal animal wiki configuration array into an array of php code strings
	 * @param $head path above element
	 * @param $el array element
	 * @return configuration php strings
	 */
	private function buildconfrecursive($head, $el) {
		if(is_array($el)) {
			$r = array();
			foreach($el as $k => $v) $r = array_merge($r, $this->buildconfrecursive(array_merge($head, array($k)), $v));
			return $r;
		}
		if(is_bool($el)) $el = $el ? 'true' : 'false';
		elseif(is_numeric($el)) $el = "$el";
		elseif(
			!(substr($el, 0, 1) == "'" && substr($el, -1) == "'") &&
			!(substr($el, 0, 1) == '"' && substr($el, -1) == '"') &&
			!preg_match('`^DOKU_[A-Z_]+`', $el) &&
			!preg_match('`\.DOKU_[A-Z_]+$`', $el)
		) $el = "'".$el."'";
		return array('$conf[\''.implode("']['", $head).'\'] = '.$el.';');
	}
	
	/**
	 * Save the animal's wiki configuration
	 * @return success as boolean
	 */
	public function saveWikiConf() {
		if(!self::exists($this->name)) return false;
		$savefromto = array(
			'local.protected.php' => 'protected',
			'local.php' => 'editable'
		);
		$r = true;
		foreach($savefromto as $to => $from) {
			$conf = implode("\n", $this->buildconfrecursive(array(), $this->wikiconf[$from]));
			if($fp = @fopen($this->path.'/conf/'.$to, 'w')) {
				fwrite($fp, "<?php\n// Generated by farm plugin (".date('Y-m-d H:i:s').")\n\n".$conf."\n");
				fclose($fp);
			}else $r = false;
		}
		$this->wikiconf = array();
		return $r;
	}
	
	/**
	 * Returns the animal's raw wiki configuration as a single string
	 * @param $mode string pointing the target configuration
	 * @return configuration as string
	 */
	public function getRawWikiConf($mode = 'editable') {
		if(!self::exists($this->name)) return null;
		$rel = array(
			'protected' => 'local.protected.php',
			'editable' => 'local.php'
		);
		if(!in_array($mode, array_keys($rel))) return null;
		if(!@file_exists($this->path.'/conf/'.$rel[$mode])) return null;
		$r = array();
		foreach(explode("\n", @file_get_contents($this->path.'/conf/'.$rel[$mode])) as $line) {
			if(preg_match('`^(\$conf\[[^]]+(\]\[[^]]+)?\]\s*=\s*[^;]+);`', $line, $m)) $r[] = $m[1];
		}
		return implode("\n", $r);
	}
	
	/**
	 * Save animal wiki configuration from string
	 * @param $mode string pointing the target configuration
	 * @param $conf string
	 * @return success as boolean
	 */
	public function saveRawWikiConf($mode = 'editable', $conf = '') {
		if(!self::exists($this->name)) return false;
		$rel = array(
			'protected' => 'local.protected.php',
			'editable' => 'local.php'
		);
		if(!in_array($mode, array_keys($rel))) return false;
		if(!@file_exists($this->path.'/conf/'.$rel[$mode])) return false;
		$this->wikiconf = array();
		$conf = implode("\n", array_map(create_function('$e', '$e = trim($e); if(substr($e, -1) != \';\') $e .= \';\'; return $e;'), explode("\n", $conf)));
		if($fp = @fopen($this->path.'/conf/'.$rel[$mode], 'w')) {
			fwrite($fp, "<?php\n// Generated by farm plugin (".date('Y-m-d H:i:s').")\n\n".$conf."\n");
			fclose($fp);
			return true;
		}
		return false;
	}
	
	/**
	 * EXPERIMENTAL
	 * Returns the animal users as an array
	 * @return array of arrays containing 'pass', 'name', 'mail' and 'groups' (array) entries, may be empty if backend not compatible
	 */
	public function getUsers() {
		$users = array();
		
		global $conf;
		$fconf = $conf;
		
		$conf = array();
		
		// Load animal conf
		$wc = $this->getWikiConf();
		$conf = array_merge($wc['editable'], $wc['protected']);
		if(!isset($conf['authtype']) || empty($conf['authtype'])) $conf['authtype'] = 'plain';
		
		if($conf['authtype'] == 'plain') {
			foreach(explode("\n", @file_get_contents($this->path.'/conf/users.auth.php')) as $line) {
				$line = preg_replace('/#.*$/','',$line); //ignore comments
				$line = trim($line);
				if(empty($line)) continue;
				$row = split(':', $line, 5);
				$groups = split(',', $row[4]);
				$users[$row[0]] = array(
					'pass' => $row[1],
					'name' => urldecode($row[2]),
					'mail' => $row[3],
					'grps' => $groups
				);
			}
		}else{
			$auth_class = 'auth_'.$conf['authtype'];
			if(!class_exists($auth_class)) if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) include DOKU_INC.'auth/'.$conf['authtype'].'.class.php';
			
			if(class_exists($auth_class)) {
				$localauth = new $auth_class();
				$localauth->users = null;
				$users = $localauth->retrieveUsers();
			}
		}
		
		$conf = $fconf;
		
		return $users;
	}
	
	/**
	 * EXPERIMENTAL
	 * Add a user to the animal
	 * @param $user user login
	 * @param $pass user clear password
	 * @param $name user name
	 * @param $mail user email
	 * @param $grps array of groups the user is in
	 * @return success as boolean, also false if backend not compatible
	 */
	public function addUser($user, $pass, $name, $mail, $grps=null) {
		if($name == '') $name = $user;
		
		$users = $this->getUsers();
		if(isset($users[$user])) return false;
		
		global $conf;
		$fconf = $conf;
		$conf = array();
		
		// Load animal conf
		$wc = $this->getWikiConf();
		$conf = array_merge($wc['editable'], $wc['protected']);
		if(!isset($conf['authtype']) || empty($conf['authtype'])) $conf['authtype'] = 'plain';
		
		if($conf['authtype'] == 'plain') {
			$authfile = @file_get_contents($this->path.'/conf/users.auth.php');
			$pass = auth_cryptPassword($pass, isset($conf['passcrypt']) ? $conf['passcrypt'] : 'smd5');
			if(!is_array($grps)) $grps = array($conf['defaultgroup']);
			$groups = join(',', $grps);
			$userline = join(':', array($user, $pass, $name, $mail, $groups))."\n";
			if($authfile) $authfile .= $userline;
			if($authfile) {
				if($fp = fopen($this->path.'/conf/users.auth.php', 'w')) {
					fwrite($fp, $authfile);
					fclose($fp);
					$r = true;
				}else $r = false;
			}else $r = false;
		}else{
			$auth_class = 'auth_'.$conf['authtype'];
			if(!class_exists($auth_class)) if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) include DOKU_INC.'auth/'.$conf['authtype'].'.class.php';
			
			if(class_exists($auth_class)) {
				$localauth = new $auth_class();
				$localauth->users = null;
				$r = ($localauth->createUser($user, $pass, $name, $mail, $grps) != null);
			}else $r = false;
		}
		
		$conf = $fconf;
		
		return $r;
	}
	
	/**
	 * EXPERIMENTAL
	 * Delete a user from the animal
	 * @param $user user login
	 * @return success as boolean, also false if backend not compatible
	 */
	public function deleteUser($user) {
		if($user == '') return false;
		
		$users = $this->getUsers();
		if(!isset($users[$user])) return false;
		
		global $conf;
		$fconf = $conf;
		$conf = array();
		
		// Load animal conf
		$wc = $this->getWikiConf();
		$conf = array_merge($wc['editable'], $wc['protected']);
		if(!isset($conf['authtype']) || empty($conf['authtype'])) $conf['authtype'] = 'plain';
		
		if($conf['authtype'] == 'plain') {
			$out = array();
			foreach(explode("\n", @file_get_contents($this->path.'/conf/users.auth.php')) as $line) {
				if(substr($line, 0, strlen($user) + 1) != $user.':') $out[] = $line;
			}
			
			if($fp = fopen($this->path.'/conf/users.auth.php', 'w')) {
				fwrite($fp, implode("\n", $out));
				fclose($fp);
				$r = true;
			}else $r = false;
		}else{
			$auth_class = 'auth_'.$conf['authtype'];
			if(!class_exists($auth_class)) if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) include DOKU_INC.'auth/'.$conf['authtype'].'.class.php';
			
			if(class_exists($auth_class)) {
				$localauth = new $auth_class();
				$localauth->users = null;
				$r = ($localauth->deleteUsers(array($user)) > 0);
			}else $r = false;
		}
		
		$conf = $fconf;
		
		return $r;
	}
	
	/**
	 * Scans the filesystem to gather informations about files / directories
	 * @param $p path
	 * @param $l level of depth
	 * @param $bp start path to strip
	 * @param $pref base64 encoded path reference
	 * @return array of fs items descriptors
	 */
	private function scanfs($p='', $l=0, $bp='', $pref='') {
		if($p == '') return array();
		if($bp == '') $bp = $p;
		if(substr($bp, -1) != '/') $bp .= '/';
		$fs = array();
		foreach(scandir($p) as $i) {
			if($i == '.' || $i == '..') continue;
			if(@is_file($p.'/'.$i)) {
				$fi = stat($p.'/'.$i);
				$fs[base64_encode(substr($p.'/'.$i, strlen($bp)))] = array(
					'type' => 'file',
					'name' => $i,
					'size' => $fi['size'],
					'mtime' => $fi['mtime'],
					'lvl' => $l,
					'pref' => $pref
				);
			}
			if(@is_dir($p.'/'.$i)) {
				$fs[base64_encode(substr($p.'/'.$i, strlen($bp)))] = array(
					'type' => 'dir',
					'name' => $i,
					'lvl' => $l,
					'pref' => $pref
				);
				$fs = array_merge($fs, $this->scanfs($p.'/'.$i, $l+1, $bp, base64_encode(substr($p.'/'.$i, strlen($bp)))));
			}
		}
		return $fs;
	}
	
	/**
	 * Gather informations about the animal's filesystem
	 * @return array of fs items descriptors
	 */
	public function getFs() {
		if(!self::exists($this->name)) return false;
		return $this->scanfs($this->getPath());
	}
	
	/**
	 * Returns the content of a file in the animal's filesystem
	 * @return file content as a string
	 */
	public function getFileContent($file) {
		if(!self::exists($this->name)) return '';
		if(!@file_exists($this->path.'/'.$file)) return '';
		return @file_get_contents($this->path.'/'.$file);
	}
	
	/**
	 * Set the content of a file in the animal's filesystem
	 * @param $file file content as a string
	 * @param $ctn file content to be put as a string
	 * @param $delete boolean telling whether to delete the pointed file
	 * @return success as boolean
	 */
	public function setFileContent($file, $ctn='', $delete=false) {
		if(!self::exists($this->name)) return false;
		if($delete && !@file_exists($this->path.'/'.$file)) return false;
		if($delete) return @unlink($this->path.'/'.$file);
		if($fp = fopen($this->path.'/'.$file, 'w')) {
			fwrite($fp, $ctn);
			fclose($fp);
			return true;
		}else return false;
	}
	
	/**
	 * Deletes the animal, calling animal hooks
	 * @return success as boolean
	 */
	public function delete() {
		self::getConf();
		
		if(@file_exists(DOKU_FARM_PLUGIN.'hooks.animal.class.php')) {
			@include DOKU_FARM_PLUGIN.'hooks.animal.class.php';
		}
		
		$hookhandler = class_exists('dokuwiki_farm_animal_hooks') ? new dokuwiki_farm_animal_hooks($handler) : null;
		
		if($hookhandler) {
			if(!$hookhandler->trigger('delete', 'before', array($this))) {
				if($handler) $handler->errors[] = array('code' => 'animal_delete_eventhandlercancel_failure');
				return false;
			}
		}
		
		if($this->name == self::$farmconf['animaltemplate']) return false;
		$r = self::rmdir($this->path);
		if(!$r) return false;
		
		if($hookhandler) $hookhandler->trigger('delete', 'after', array($this->name));
		
		return true;
	}
	
	/**
	 * Creates a new animal, calling animal hooks
	 * @param $name name of the new animal
	 * @param $template template to use for clonning as string
	 * @param $host host to bind if virtual mode in use
	 * @param $handler handler object featuring a errors array property
	 * @return success as boolean
	 */
	public static function createNew($name = '', $template = null, $host = null, $handler = null) {
		self::getConf();
		
		if(@file_exists(DOKU_FARM_PLUGIN.'hooks.animal.class.php')) {
			@include DOKU_FARM_PLUGIN.'hooks.animal.class.php';
		}
		
		$hookhandler = class_exists('dokuwiki_farm_animal_hooks') ? new dokuwiki_farm_animal_hooks($handler) : null;
		
		if($hookhandler) {
			if(!$hookhandler->trigger('create', 'before', array($name, $template))) {
				if($handler) $handler->errors[] = array('code' => 'animal_new_eventhandlercancel_failure');
				return false;
			}
		}
		
		if($farmconf['farmmaxanimals'] > 0) {
			if(count(self::listAnimals()) >= $farmconf['farmmaxanimals']) {
				if($handler) $handler->errors[] = array('code' => 'animal_new_farmfull_failure');
				return false;
			}
		}
		
		if($farmconf['farmmaxsize'] > 0) {
			if(count(self::farmSize()) >= $farmconf['farmmaxsize']) {
				if($handler) $handler->errors[] = array('code' => 'animal_new_farmtoobig_failure');
				return false;
			}
		}
		
		if(!$template) $template = self::$farmconf['animaltemplate'];
		
		$name = strtr($name, "àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ", "aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY");
		$name = preg_replace('`\s+`', '_', $name);
		if(!preg_match('`^[a-z0-9._-]+`i', $name)) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_badname_failure');
			return false;
		}
		if(self::exists($name) || ($name == trim(self::$farmconf['farmer'], '/')) || @is_dir(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name)) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_namealreadyinuse_failure', 'data' => array($name));
			return false;
		}
		
		if(!self::exists($template)) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_templatenotfound_failure', 'data' => array($template));
			return false;
		}
		
		$cph = create_function('$path,$isfile', '
if(!$isfile) return;
$ctn = @file_get_contents($path);
if(!preg_match(\'\\{ANIMAL\\}\', $ctn)) return;
$ctn = str_replace(\'{ANIMAL}\', \''.$name.'\', $ctn);
if($fp = fopen($path, \'w\')) {
  fwrite($fp, $ctn);
  fclose($fp);
}
');

		if(!self::cp_r(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$template, self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name, false, array(), $cph)) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_templatecopy_failure');
			return false;
		}
		
		if($fp = fopen(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name.'/animal.meta', 'w')) {
			fwrite($fp, 'creation_date : '.time());
			fclose($fp);
		}else{
			if($handler) $handler->errors[] = array('code' => 'animal_new_metacreate_failure');
			return false;
		}
		
		if(!self::exists($name)) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_createdespite_failure');
			return false;
		}
		
		$a = new dokuwiki_farm_animal($name, $handler->manager);
		if(!$a->setWikiConf('protected', array('basedir'), 'DOKU_ANIMAL_BASEDIR.\''.$name.'\'') || !$a->setWikiConf('editable', array('title'), $name)) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_setconfig_failure');
			return false;
		}
		if(!$a->saveWikiConf()) {
			if($handler) $handler->errors[] = array('code' => 'animal_new_saveconfig_failure');
			return false;
		}
		
		if($host && self::$farmconf['virtual']) {
			$vh = @file_get_contents(DOKU_FARM_PLUGIN.'virtual_hosts.php');
			$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.'\');'));
			if(!count($e)) {
				$fp = fopen(DOKU_FARM_PLUGIN.'virtual_hosts.php', 'w');
				if($fp) {
					fwrite($fp, $vh."\n".$host.' '.$name);
					fclose($fp);
				}else{
					if($handler) $handler->errors[] = array('code' => 'animal_new_savevirtualhost_failure');
					return false;
				}
			}else{
				if($handler) $handler->errors[] = array('code' => 'animal_new_virtualhostexists_failure');
				return false;
			}
		}
		
		if($hookhandler) $hookhandler->trigger('create', 'after', array($a));
		
		return $name;
	}
	
	/**
	 * Gather the list of animals
	 * @return array of dokuwiki_farm_animal objects
	 */
	public static function listAnimals($manager = null) {
		self::getConf();
		$a = array();
		foreach(scandir(self::$farmconf['farmfsroot'].self::$farmconf['barn']) as $i) {
			if($i == '.' && $i == '..') continue;
			if(self::exists($i)) $a[] = new dokuwiki_farm_animal($i, $manager);
		}
		return $a;
	}
	
	/**
	 * Get the farm disk usage
	 * @return size in bytes
	 */
	public static function farmSize() {
		return array_sum(array_map(create_function('$a', 'return $a->getInfos()->getSize();'), self::listAnimals()));
	}
	
	/**
	 * Copy / move a directory contents
	 * @param $src source path
	 * @param $dest destination path
	 * @param $mv boolean telling whether copying or moving contents
	 * @param $avoid path of elemnts that musn't be copied / moved
	 * @param $copyhook function to handle cpoy operations
	 * @return success as boolean
	 */
	private static function cp_r($src, $dest, $mv=false, $avoid=array(), $copyhook = null) {
		if(in_array($src, $avoid)) return true;
		if(@is_file($src)) {
			$r = copy($src, $dest);
			chmod($dest, fileperms($src));
			if($mv) $r &= unlink($src);
			if($copyhook) $copyhook($dest, true);
			return $r;
		}
		
		if(!@is_dir($dest)) mkdir($dest, fileperms($src));
		
		$r = true;
		foreach(scandir($src) as $i) {
			if($i == '.' || $i == '..') continue;
			$r &= self::cp_r($src.'/'.$i, $dest.'/'.$i, $mv, $avoid);
		}
		
		if($mv) $r &= rmdir($src);
		if($copyhook) $copyhook($dest, false);

		return $r;
	}
	
	/**
	 * Removes a directory
	 * @param $p path to the directory
	 * @return success as boolean
	 */
	private static function rmdir($p) {
		$r = true;
		foreach(scandir($p) as $i) {
			if($i == '.' || $i == '..') continue;
			if(@is_dir($p.'/'.$i)) $r &= self::rmdir($p.'/'.$i);
			if(@is_file($p.'/'.$i)) $r &= unlink($p.'/'.$i);
			if(!$r) break;
		}
		if($r) rmdir($p);
		return $r;
	}
	
	/**
	 * Gather and return the farm configuration
	 * @return associative array of farm configuration parameters
	 */
	public static function getConf() {
		if(!self::$farmconf) {
			$farmconf = array();
			include(DOKU_FARM_PLUGIN.'config.php');
			self::$farmconf = $farmconf;
		}
		return self::$farmconf;
	}
	
	/**
	 * Check if an animal exists
	 * @param $name name to test
	 * @return boolean
	 */
	public static function exists($name = '') {
		if($name == '') return false;
		$name = strtr($name, "àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ", "aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY");
		$name = preg_replace('`\s+`', '_', $name);
		if(!preg_match('`^[a-z0-9._-]+`i', $name)) return false;
		self::getConf();
		return @is_dir(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name) && @file_exists(self::$farmconf['farmfsroot'].self::$farmconf['barn'].$name.'/animal.meta');
	}
}

?>
