<?php

//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER["SCRIPT_NAME"],basename(__FILE__)) !== false) {
  header("location: index.php");
	exit;
}

/*
 * This class describe the main informations of a module
 */
class TikiMod {
	var $modname;
	var $name;
	var $type;
	var $revision;

	/* use with $type and $name, or $type only as modname ("$type-$name") */
	function TikiMod($type, $name=FALSE) {
		if ($name === FALSE) { // $type is a modname
			$this->modname=$type;
			list($this->type, $this->name) = explode('-', $type, 2);
		} else {
			$this->modname=$type.'-'.$name;
			$this->type=$type;
			$this->name=$name;
		}
	}
	
	/*
	 * return:
	 * -1 if $mod is newer
	 *  1 if $this is newer
	 *  0 if same revision
	 */
	function isnewerthan($mod) {
		if (ModsLib::revision_compare($this->revision, $mod->revision) > 0)
			return TRUE;
	}

}

/*
 * This class describe the informations of a module that are available
 * from a 00_list.txt style file
 */
class TikiModAvailable extends TikiMod {
	var $repository;      /* string */
	var $description;     /* array */
	var $licence;         /* string */
	var $version;         /* array */
	var $md5;             /* string */
	var $requires;        /* array */
	var $suggests;        /* array */
	var $conflicts;       /* array */

	function TikiModAvailable($type, $name=FALSE) {
		$this->TikiMod($type, $name);
	}

	/* convert $this mod as a line viewable in files like 00_list.txt */
	function toline() {
		$out='';

		$out.= "'". addslashes($this->type) ."',";
		$out.= "'". addslashes($this->name) ."',";
		$out.= "'". addslashes($this->revision) ."',";
		$out.= "'". addslashes($this->description) ."',";
		$out.= "'". addslashes($this->licence) ."',";
		$out.= "'". addslashes($this->version[0]) ."',"; // probably buggy isn't it?
		$out.= "'". addslashes($this->md5) ."',";
		
		$requires='';
		if (is_array($this->requires))
			foreach($this->requires as $elem)
				$requires.=($requires == '' ? '' : '&').$elem->tostring();

		$suggests='';
		if (is_array($this->suggests))
			foreach($this->suggests as $elem)
				$suggests.=($suggests == '' ? '' : '&').$elem->tostring();

		$conflicts='';
		if (is_array($this->conflicts))
			foreach($this->conflicts as $elem)
				$conflicts.=($conflicts == '' ? '' : '&').$elem->tostring();

		$deps = empty($requires) ? '' : 'requires:'.$requires;
		$deps.= empty($suggests) ? '' : (empty($deps) ? '' : ';') . 'suggests:'.$suggests;
		$deps.= empty($conflicts) ? '' : (empty($deps) ? '' : ';') . 'conflicts:'.$conflicts;
		
		$out.= "'".addslashes($deps)."'";

		return $out;
	}

	/* used by readdeps_line and read_list for importing dependences */
	function _decodedeps(&$array, $str) {
		$str=str_replace(' ', '', $str);
		$am=explode('&', $str);
		foreach($am as $dep) {
			$modname=preg_replace('/[<>=].*/', '', $dep);
			$moddep=new TikiModDepend($modname);
			$moddep->tests=array();
			$tests=array();
			preg_match_all('/[<>=]+[0-9.]+/', $dep, $tests);
			foreach($tests[0] as $test) {
				$moddep->tests[]=array('test' => preg_replace('/[0-9.]*$/', '', $test),
						       'revision' => preg_replace('/^[^0-9.]*/', '', $test));
			}
			$array[]=$moddep;
		}	
	}
	
	/* import from a dependences element string in 00_list.txt style file */
	function readdeps_line($line) {
		$meat=explode(';', $line);
		foreach($meat as $m) {
			unset($a);
			$a=FALSE;
			if (strpos($m, 'requires:') === 0) {
				$m=substr($m, 9);
				$a=&$this->requires;
			} elseif (strpos($m, 'suggests:') === 0) {
				$m=substr($m, 9);
				$a=&$this->suggests;
			} elseif (strpos($m, 'conflicts:') === 0) {
				$m=substr($m, 10);
				$a=&$this->conflicts;
			}

			if ($a !== FALSE) $this->_decodedeps($a, $m);
		}
	}
}

/*
 * This class contain full information of a module,
 * like there are available in it's .info.txt file
 */
class TikiModInfo extends TikiModAvailable {
	var $configuration;         /* array */
	var $configuration_help;    /* array */
	var $files;                 /* array */
	var $contributor;           /* array */
	var $lastmodif;             /* string */
	var $devurl;                /* array */
	var $docurl;                /* array */
	var $changelog;             /* array */
	var $author;                /* array */
	var $help;                  /* array */
	var $url;                   /* array */
	var $sql_upgrade;           /* array */
	var $sql_install;           /* array */
	var $sql_remove;            /* array */

	function TikiModInfo($type, $name=FALSE) {
		$this->TikiModAvailable($type, $name);
	}

	/* Import all datas from the .info.txt file */
	function readinfo($file) {
		if (!is_file($file)) {
			return sprintf(tra('File %s not found'),$file);
		}

		$fp = fopen($file,"r");
		$next = true;
		$lab = '';
		while (!feof($fp)) {
			$line = fgets($fp,1024);
			$line = trim($line);
			if (empty($line)) {
				$next = true;
				continue;
			}
			if (substr($line,0,1) == '#') continue;
			
			if ($next) {
				$lab = trim(strtr(strtolower($line),':',' '));
				$next = false;
				$localkey = '*';
				continue;
			}

			switch($lab) {
			case 'sql-upgrade':
			        if (substr($line, 0, 1) == ':') {
					$localkey=substr($line, 1);
				} else {
					$this->sql_upgrade[$localkey][] = trim($line);
				}
				break;
			case 'sql-install':
				$this->sql_install[] = trim($line);
				break;
			case 'sql-remove':
				$this->sql_remove[] = trim($line);
				break;
			case 'configuration':
				$this->configuration[] = split(',',trim($line));
				break;
			case 'configuration help':
				$this->configuration_help[] = split(',',trim($line));
				break;
			case 'files':
				$this->files[] = split(' +',trim($line));
				break;
			case 'contributor':
				$this->contributor[] = trim(preg_replace('/\$[^:]*:([^\$]*)\$/',"$1",trim($line)));
				break;
			case 'revision':
				if (empty($this->revision))
					$this->revision = trim(preg_replace('/\$[^:]*:([^\$]*)\$/',"$1",trim($line)));
				break;
			case 'lastmodif':
				if (empty($this->lastmodif))
					$this->lastmodif = trim(preg_replace('/\$[^:]*:([^\$]*)\$/',"$1",trim($line)));
				break;
			case 'version':
				$this->version[]=trim($line);
				break;
			case 'licence':
				$this->licence = trim($line);
				break;
			case 'devurl':
				$this->devurl[] = trim($line);
				break;
			case 'docurl':
				$this->docurl[] = trim($line);
				break;
			case 'description':
				$this->description .= empty($this->description) ? trim($line) : ' '.trim($line);
				break;
			case 'changelog':
				$this->changelog[] = trim($line);
				break;
			case 'author':
				$this->author[] = trim($line);
				break;
			case 'requires':
				$this->_decodedeps($this->requires, $line);
				break;
			case 'suggests':
				$this->_decodedeps($this->suggests, $line);
				break;
			case 'conflicts':
				$this->_decodedeps($this->conflicts, $line);
				break;
			case 'help':
				$this->_help[] = trim($line);
				break;
			case 'url':
				$this->_url[] = trim($line);
				break;
			default:
				die("key: $lab to add");
			}
		}

		fclose($fp);
		return false;
	}

	function writeinfo($mods_path) {
		die("not implemented");
	}

	/* read configuration file of this module */
	function readconf($mods_path) {
		if (!is_file($mods_path.'/Installed/'.$this->type.'-'.$this->name.'.conf.txt')) return false;
		$fp = fopen($mods_path.'/Installed/'.$this->type.'-'.$this->name.'.conf.txt',"r");
		$conf=array();
		$lab='';
		while (!feof($fp)) {
			$line = fgets($fp,1024);
			$line = trim($line);
			if (empty($line)) {
				$next = true;
				continue;
			}
			if (substr($line,0,1) == '#') continue;
			
			if ($next) {
				$lab = trim(strtr(strtolower($line),':',' '));
				$next = false;
				continue;
			}		
			
			if ($lab='') continue;
			$conf[$lab][]=$line;
		}
		return $conf;
	}

	/* write configuration file of this module */
	function writeconf($mods_path, $confs) {
		$fp = fopen($mods_path.'/Installed/'.$this->type.'-'.$this->name.'.conf.txt',"w");
		foreach ($confs as $k=>$v) {
			fputs($fp,"$k:\n$v\n\n");
		}
		fclose($fp);
	}

	/* construct a package (.tar.gz) of this module from every it's files */
	function package($mods_path, $info_file) {
		$oldir = getcwd();
		if (chdir($mods_path) === FALSE) {
			$err=sprintf(tra("Can't chdir to '%s'"), $mods_path);
			return $err;
		}
		include_once($oldir.'/lib/tar.class.php');

		$info_file = 'Packages/'.$this->type.'-'.$this->name.'.info.txt';
		$err=$this->readinfo($info_file);
		if ($err !== FALSE) {
			chdir($oldir);
			return $err;
		}

		if (is_file("Dist/".$this->type.'-'.$this->name.'-'.$this->revision.'.tgz')) {
			unlink("Dist/".$this->type.'-'.$this->name.'-'.$this->revision.'.tgz');
		}
		$tar = new tar;
		$tar->addFile($info_file);
		if (is_array($this->files)) {
			foreach ($this->files as $f) {
				$tar->addFile($f[0]);
			}
		}
		$filename = 'Dist/'.$this->type.'-'.$this->name.'-'.$this->revision.'.tgz';
		$tar->toTar($filename,1);
		if (!function_exists('md5_file')) {
			$this->md5 = md5(implode('', file($filename)));
		} else {
			$this->md5 = md5_file($filename);
		}
		chmod($filename, 0644); // needed on some servers
		
		chdir($oldir);
		return $err;
	}

}

/*
 * This class represent one dependence for an another package
 */
class TikiModDepend extends TikiMod {
	var $tests;

	function TikiModDepend($type, $name=FALSE) {
		$this->TikiMod($type, $name);
	}

	function tostring() {
		$out=$this->modname;
		foreach($this->tests as $test) {
			$out.=$test['test'].$test['revision'];
		}
		return $out;
	}

	/*
	 * Check if $mod is concerned by this depend
	 */
	function isitin($mod) {
		if ($mod->modname != $this->modname) return FALSE;
		if (!is_array($this->tests)) return TRUE;
		foreach($this->tests as $test) {
			switch($test['test']) {
			case '=':
				if (ModsLib::revision_compare($mod->revision, $test['revision']) != 0)
					return FALSE;
				break;
			case '<':
				if (ModsLib::revision_compare($mod->revision, $test['revision']) != -1)
					return FALSE;
				break;
			case '>':
				if (ModsLib::revision_compare($mod->revision, $test['revision']) != 1)
					return FALSE;
				break;
			case '<=':
			case '=<':
				if (ModsLib::revision_compare($mod->revision, $test['revision']) > 0)
					return FALSE;
				break;
			case '>=':
			case '=>':
				if (ModsLib::revision_compare($mod->revision, $test['revision']) < 0)
					return FALSE;
				break;
			}
		}
		return TRUE;
	}
}

/*
 * This is the class that manage every modules
 */
class ModsLib {

	var $feedback_listeners;
	var $types;
	var $versions;

	function __construct() { 
		$this->types = array();
		$this->feedback_listeners = array();
		$this->versions = array('Unspecified' => -1,'1.x' => 1.0,'1.9.x' => 1.9,'2.x' => 2.0,'3.x' => 3.0, '4.x' => 4.0);
	}

	function feedback_info($feedback) {
		foreach($this->feedback_listeners as $listener) {
			$listener(-1, $feedback);
		}
	}

	function feedback_warning($feedback) {
		foreach($this->feedback_listeners as $listener) {
			$listener(0, $feedback);
		}
	}

	function feedback_error($feedback) {
		foreach($this->feedback_listeners as $listener) {
			$listener(1, $feedback);
		}
	}

	function add_feedback_listener($listener) {
		$this->feedback_listeners[]=$listener;
	}

	function prepare_dir($path) {
		if ($path and $path != '/' and !is_dir(dirname($path))) $this->prepare_dir(dirname($path));
		if (!is_dir($path) and substr($path,0,1) != '.') mkdir($path,02777);
	}

	function dl_remote($remote,$file,$local) {
		$meat = $this->get_remote($remote."/Dist/".$file.".tgz");
		if ($meat === FALSE) return FALSE;

		$localfile = $local."/Cache/".$file.".tgz";
		if (is_file($localfile)) unlink($localfile);
		$fp = fopen($localfile,"wb");
		fwrite($fp,$meat);
		fclose($fp);
		require_once("lib/tar.class.php");
		$tar = new tar;
		if ($tar->openTAR($localfile)) {
			foreach ($tar->files as $f) {
				$this->prepare_dir(dirname($local.'/'.$f['name']));
				$fp = fopen($local.'/'.$f['name'],"wb");
				fwrite($fp,$f['file'],$f['size']);
				fclose($fp);
			}
		} else {
			$this->feedback_error(sprintf(tra('File %s is not a valid archive'),$localfile));
			return false;
		}
	}

	function get_remote($url) {
		global $tikilib;
		$this->feedback_info("downloading '$url'...");
		$buffer = $tikilib->httprequest($url, $reqmethod = "GET");
		if ( ! $buffer ) {
			$this->feedback_error(sprintf(tra('Impossible to open %s : %s'), $url, 'n/a'));
			return false;
		}

		return $buffer;
	}

	function refresh_remote($remote,$local) {
		$buffer = $this->get_remote($remote);
		if ( ! $buffer || $buffer{0} != "'" ) {
			$this->feedback_error(sprintf(tra('The content retrieved at %s is not a list of mods'), $remote, 'n/a'));
			return false;
		}
		$fl = fopen($local,'w');
		fputs($fl,$buffer);
		fclose($fl);
		return true;
	}

	function _publish($modpath,$public, $items,$add=true) {
		$fp = fopen($modpath.'/Packages/00_list.public.txt',"w");
		foreach ($public as $meat) {
			foreach ($meat as $p) {
				if ($add || !in_array($p->modname, $items)) {
					fputs($fp,$p->toline()."\n");
				}
			}
		}
		
		if (count($items) and $add) {
			foreach($items as $modname) {
				$mod=new TikiModInfo($modname);
				if (!isset($public[$mod->type])
				    || !isset($public[$mod->type][$mod->name])) {
					$this->feedback_info("packaging ".$mod->modname." ...");
					$err=$mod->package($modpath, 'Packages/'.$mod->type.'-'.$mod->name.'.info.txt');
					if ($err !== false) {
						$this->feedback_error($err);
						continue;
					}
					
					fputs($fp, $mod->toline()."\n");
				}
			}
		}
		fclose($fp);
	}
	
	function publish($modpath, $items) {
		$public = $this->read_list($modpath."/Packages/00_list.public.txt", 'public');
		$this->_publish($modpath, $public, $items, true);
	}

	function unpublish($modpath,$items) {
		$public = $this->read_list($modpath."/Packages/00_list.public.txt", 'public');
		$this->_publish($modpath, $public, $items, false);
	}

	function scan_dist($dir) {
		$back = array();
		$h = opendir($dir);
		while ($file = readdir($h)) {
			$file = basename($file);
			if (substr($file,-4,4) == '.tgz') {
				$lim = strrpos($file,'-');
				$modname = substr($file,0,$lim);
				$revision = substr(substr($file,$lim+1),0,-4);

				$b = new TikiMod($modname);
				$b->revision=$revision;
				if (isset($back[$modname])) {
					if ($this->revision_compare($back[$modname]->revision, $b->revision) < 0)
						$back[$modname] = $b;
				} else {
					$back[$modname] = $b;
				}
			}
		}
		return $back;
	}

	function rebuild_list($dir) {
		$list = array();
		$h = opendir($dir);
		while ($file = readdir($h)) {
			if (substr($file,-9,9) == '.info.txt') {
				$list[] = $file;
			}
		}
		closedir($h);
		$fp = fopen($dir.'/00_list.txt','w');
		if (count($list)) {
			foreach ($list as $l) {
				$l = substr($l,0,-9);
				$pos = strpos($l,'-');
				$type = substr($l,0,$pos);
				$name = substr($l,$pos+1);
				$detail = new TikiModInfo($type, $name);
				$err=$detail->readinfo($dir.'/'.$l.'.info.txt');
				if ($err !== FALSE) {
					$this->feedback_error($err);
					continue;
				}

				fputs($fp,$detail->toline()."\n");
			}
		} else {
				fputs($fp,"# nothing installed");
		}
		fclose($fp);
	}

	function read_list($file,$reponame,$type='',$find='',$simplelist=false) {
		$out = array();
		$fp = @ fopen($file,'r');
		if ($fp) {
			while (!feof($fp)) {
				$line = fgets($fp,4096);
				if (trim($line) and substr(trim($line),0,1) == "'") {
					$str = split("','",substr($line,1,strlen($line)-3));
					if (count($str) < 4) continue; // line must have at least 4 elements
					if (empty($str[0]) || empty($str[1]) || empty($str[2])) continue; // theses field must be not empty

					foreach($str as $k => $v) $str[$k]=stripslashes($v);
					$mod=new TikiModAvailable($str[0], $str[1]);
					$this->types[$mod->type] = true;
					if ((empty($type) or ($mod->type == $type)) and (empty($find) or (strpos($mod->name,$find) !== false))) {
						$mod->revision=$str[2];
						$mod->description=$str[3];
						$mod->licence=$str[4];
						$mod->repository=$reponame;

						// from a buggy past
						if (count($str) == 6 or count($str) == 7) {
							$col=5;
							while(isset($str[$col])) {
								$blah=stripslashes($str[$col]);
								if (preg_match('/^[0-9]+\.[0-9]+[0-9.]*$/', $blah))
									$mod->version[]=$blah;
								elseif (preg_match('/^[0-9abcdefABCDEF]{32}$/', $blah))
									$mod->md5=$blah;
								$col++;
							}
						}

						if (count($str) > 7) {
							// now, $str[5] MUST be version, and $str[6] MUST be md5
							$mod->version[]=$str[5];
							$mod->md5=$str[6];
							$mod->readdeps_line($str[7]);
						}

						if ($simplelist)
							$out[]=$mod->modname;
						else
							$out[$mod->type][$mod->name]=$mod;
					}
				}
			}
			fclose($fp);
		}
		return $out;
	}

	/*
	 * $a and $b are strings representation of the revisions numbers
	 * return:
	 *   1 if $a > $b
	 *  -1 if $a < $b
	 *   0 if $a == $b
	 */
	function revision_compare($a, $b) {
		$ra=explode('.', $a);
		$rb=explode('.', $b);
		for ($i=0, $max_counts = max(count($ra), count($rb)); $i<$max_counts; $i++) {
			$suba=isset($ra[$i]) ? (int)$ra[$i] : 0;
			$subb=isset($rb[$i]) ? (int)$rb[$i] : 0;
			if ($suba > $subb) return 1;
			if ($suba < $subb) return -1;
		}
		return 0;
	}

	/*
	 * Search in $list if the package is available, optionally by checking the revision
	 * $list must be the result of read_list()
	 */
	function get_depend_available($moddep, $list, $check_revision=FALSE) {
		if (isset($list[$moddep->type]) && isset($list[$moddep->type][$moddep->name])) {
			$mod=$list[$moddep->type][$moddep->name];
			if (!$check_revision || $moddep->isitin($mod)) return $mod;
		}
		return NULL;
	}

	/*
	 * Search from every repos the latest version of a package.
	 */
	function find_last_version($repos, $moddep) {
		$found=NULL;
		foreach($repos as $repo_name => $repo) {
			$mod=$this->get_depend_available($moddep, $repo, TRUE);
			if ($mod !== NULL) {
				if ($found === NULL || $mod->isnewerthan($found)) {
					$found=$mod;
				}
			}
		}
		return $found;
	}

	function find_deps($modspath, $mods_server, $modnames) {
		$deps=array("wanted" => array(),
			    "toinstall" => array(),
			    "toupgrade" => array(),
			    "suggests" => array(),
			    "conflicts" => array(),
			    "unavailable" => array());

		$querymod=new TikiModAvailable('fakemod', 'query');
		$querymod->requires=array();
		foreach($modnames as $modname) {
			$moddep=new TikiModDepend($modname);
			$querymod->requires[]=$moddep;
			$deps['wanted'][$modname]=$moddep;
		}

		$repos=array('installed' => $this->read_list($modspath."/Installed/00_list.txt", 'installed'),
			     'local' => $this->read_list($modspath."/Packages/00_list.txt", 'local'),
			     'remote' => $this->read_list($modspath."/Packages/00_list.". urlencode($mods_server).".txt", 'remote'));
		
		$this->_find_deps($repos, $querymod, $deps);

		/* now remove duplicates from suggests */
		foreach ($deps['suggests'] as $suggest) {
			if (isset($deps['toinstall'][$suggest[0]->modname]))
				unset($deps['suggests'][$suggest[0]->modname]);
		}

		/* now search conflicts from the requireds */
		foreach ($deps['toinstall'] as $mod) {
			if (!is_array($mod->conflicts)) continue;
			foreach($mod->conflicts as $moddep) {
				/* compare required conflicts with requireds */
				foreach($deps['toinstall'] as $dep) {
					if ($moddep->isitin($dep)) {
						if (!isset($deps['conflicts'][$dep->modname])) {
							$dep->errors[]="1/conflict avec ".$mod->modname;
							$deps['conflicts'][$dep->modname]=$dep;
						}
						if (!isset($deps['conflicts'][$mod->modname])) {
							$mod->errors[]="2/conflict avec ".$dep->modname;
							$deps['conflicts'][$mod->modname]=$mod;
						}
					}
				}
				/* compare required conflicts with installed */
				foreach($repos['installed'] as $meat) {
					foreach($meat as $dep) {
						if ($moddep->isitin($dep)) {
							$mod->errors[]="conflict avec ".$dep->modname;
							$dep->errors[]="conflict avec ".$mod->modname;
							if (!isset($deps['conflicts'][$dep->modname])) {
								$dep->errors[]="3/conflict avec ".$mod->modname;
								$deps['conflicts'][$dep->modname]=$dep;
							}
							if (!isset($deps['conflicts'][$mod->modname])) {
								$mod->errors[]="4/conflict avec ".$dep->modname;
								$deps['conflicts'][$mod->modname]=$mod;
							}
						}
					}
				}
			}
		}

		/* now search conflicts from the installeds */
		foreach ($repos['installed'] as $meat) {
			foreach ($meat as $mod) {
				if (!is_array($mod->conflicts)) continue;
				foreach($mod->conflicts as $moddep) {
					/* compare installed with requireds */
					foreach($deps['toinstall'] as $dep) {
						if ($moddep->isitin($dep)) {
							if (!isset($deps['conflicts'][$dep->modname])) {
								$mod->errors[]="5/conflict avec ".$dep->modname;
								$deps['conflicts'][$dep->modname]=$dep;
							}
							if (!isset($deps['conflicts'][$mod->modname])) {
								$dep->errors[]="6/conflict avec ".$mod->modname;
								$deps['conflicts'][$mod->modname]=$mod;
							}
						}
					}
				}
			}
		}

		/* move upgraded package to upgrade */
 		foreach($deps['toinstall'] as $k => $toinstall) {
 			if (isset($repos['installed'][$toinstall->type])
			    && isset($repos['installed'][$toinstall->type][$toinstall->name])) {
				$meat=array('from' => $repos['installed'][$toinstall->type][$toinstall->name],
					    'to' => $toinstall);
				$deps['toupgrade'][$toinstall->modname]=$meat;
				unset($deps['toinstall'][$k]);
			}
 		}

		return $deps;
	}

	function _find_deps($repos, $querymod, &$deps) {
		if (is_array($querymod->requires)) {
			foreach($querymod->requires as $moddep) {
				$mod=$this->find_last_version($repos, $moddep);
				if ($mod === NULL) {
					$moddep->errors[]="required by '".$querymod->name."' : Not available";
					$deps['unavailable'][]=$moddep;
				} else {
					if ($mod->repository == 'installed') continue;
					if (isset($deps['toinstall'][$mod->modname])) {
						if ($mod->isnewerthan($deps['toinstall'][$mod->modname])) {
							// if there is an older version in the deps['toinstall'],
							// this is because a previous requires was requiring
							// an older one. So we don't try to upgrade it, we
							// just try to see if it is compatible with this require.
							if (!$moddep->isitin($mod)) {
								// it is not compatible
								$moddep->errors[]="revision failure";
								$deps['unavailable'][]=$moddep;								
							} else {
								// it is compatible, let it.
							}
						} else {
							// not newer, let it
						}
					} else {
						$deps['toinstall'][$mod->modname]=$mod;
						$this->_find_deps($repos, $mod, $deps);
					}
				}
			}
		}

		if (is_array($querymod->suggests)) {
			foreach($querymod->suggests as $moddep) {
				$deps['suggests'][$moddep->modname][]=$moddep;
			}
		}
	}

	function find_deps_remove($modspath, $mods_server, $modnames) {
		$deps=array("wantedtoremove" => array(),
			    "toremove" => array());

		$repos=array('installed' => $this->read_list($modspath."/Installed/00_list.txt", 'installed'),
			     /*'local' => $this->read_list($modspath."/Packages/00_list.txt", 'local'),
			     'remote' => $this->read_list($modspath."/Packages/00_list.". urlencode($mods_server).".txt", 'remote')*/);
		
		foreach($modnames as $modname) {
			$mod=new TikiMod($modname);
			if (isset($repos['installed'][$mod->type][$mod->name])) {
				$mod=$repos['installed'][$mod->type][$mod->name];
				$deps['wantedtoremove'][$modname]=$mod;
				$this->_find_deps_remove($repos, $mod, $deps);
			} else {
				$deps['wantedtoremove'][$modname]=$mod;
			}
		}
		return $deps;
	}

	function _find_deps_remove($repos, $modtoremove, &$deps) {
		$deps['toremove'][$modtoremove->modname]=$modtoremove;
		foreach($repos['installed'] as $meat) {
			foreach($meat as $mod) {
				if (isset($deps['toremove'][$mod->modname])) continue;
				if (!is_array($mod->requires)) continue;
				foreach($mod->requires as $moddep) {
					if ($moddep->isitin($modtoremove)) {
						$this->_find_deps_remove($repos, $mod, $deps);
					}
				}
			}
		}
	}

	function install_with_deps($modspath, $mods_server, $deps) {

		/* download packages if necessary */

		foreach($deps['toinstall'] as $mod) {
			if ($mod->repository == 'remote') {
				$res=$this->dl_remote($mods_server,$mod->modname.'-'.$mod->revision,$modspath);
				if ($res === false) return false;
			}
		}

		foreach($deps['toupgrade'] as $meat) {
			$mod=$meat['to'];
			if ($mod->repository == 'remote') {
				$res=$this->dl_remote($mods_server,$mod->modname.'-'.$mod->revision,$modspath);
				if ($res === false) return false;
			}
		}

		// we reconstruct deps because now there are modules that are downloaded
		$this->rebuild_list($modspath."/Packages");
		
		/* install packages */

		foreach($deps['toinstall'] as $mod) {
			$this->install($modspath, $mod);
		}

		foreach($deps['toupgrade'] as $meat) {
			$this->remove($modspath, $meat['from'], true);
			$this->install($modspath, $meat['to'], $meat['from'], true);
		}
		
	}
	
	function remove_with_deps($modspath, $mods_server, $deps) {
		foreach($deps['toremove'] as $mod) {
			$this->remove($modspath, $mod);
		}
	}

	function install($path,$mod,$from=NULL,$upgrade=false) {
		$this->feedback_info("installing ".$mod->modname." (".$mod->revision.") ...");
		$file = $path.'/Packages/'.$mod->modname.'.info.txt';
		$info = new TikiModInfo($mod->modname);
		$err=$info->readinfo($file);
		if ($err !== FALSE) {
			$this->feedback_error($err);
			return;
		}
		$conf=array('_SERVER' => $_SERVER);
		if (is_array($info->configuration) and count($info->configuration)) {
			$conf = $info->readconf($path);
			if ($conf === false) {
				global $smarty;
				$conf=array('_SERVER' => $_SERVER);
				if (is_array($info->configuration_help) and count($info->configuration_help)) {
					$smarty->assign('help',implode("<br />\n",$info->configuration_help));
				} else {
					$smarty->assign('help','');
				}
				for ($i=0, $count_config = count($info->configuration); $i < $count_config; $i++) {
					$info->configuration[$i][2] = preg_replace('/\\$([_A-Z]*)/e','$conf[\'_SERVER\'][\'\\1\']',$info->configuration[$i][2]);
				}
				$smarty->assign('type',$info->type);
				$smarty->assign('package',$info->name);
				$smarty->assign('info',$info);
				$smarty->assign('mid','tiki-mods_config.tpl');
				$smarty->display('tiki.tpl');
				die;
			}
		}
		if ($upgrade and is_array($info->sql_upgrade) and count($info->sql_upgrade)) {
			uksort($info->sql_upgrade, array($this, 'revision_compare'));
			global $tikilib;
			foreach ($info->sql_upgrade as $v=>$vv) {
				if ($this->revision_compare($from->revision,$v) < 0) {
					foreach ($vv as $sql) {
						if (count($conf) and strpos($sql,'$')) {
							$sql = preg_replace('/\\$([_a-zA-Z0-9]*)/e','$conf[\'\\1\'][0]',$sql);
						}
						$this->feedback_error($from->revision." -> $v : $sql");
						$tikilib->query($sql,array());
					}
				}
			}
		} elseif (is_array($info->sql_install) and count($info->sql_install)) {
			global $tikilib;
			foreach ($info->sql_install as $sql) {
				if (count($conf) and strpos($sql,'$')) {
					$sql = preg_replace('/\\$([_a-zA-Z0-9]*)/e','$conf[\'\\1\'][0]',$sql);
				}
				$tikilib->query($sql,array());
			}
		}
		if (is_array($info->files) and count($info->files)) {
			foreach ($info->files as $f) {
				$this->prepare_dir(dirname($f[1]));
				if (is_file($f[1])) {
					if (is_file($f[1] . '.orig.' . $info->revision)) {
						@unlink($f[1] . '.orig.' . $info->revision);
					}
					rename($f[1],$f[1] . '.orig.' . $info->revision);
				}
				if (substr(basename($f[0]),0,7) == "sample:") {
					$text = implode('',file($path.$f[0]));
					$text = preg_replace('/\[:::\[([^\]]*)\]:::\]/e','$conf[\'\\1\'][0]',$text);
					$f[0] = str_replace('sample:','',$f[0]);
					$fp = fopen($path.'/'.$f[0],"w");
					fputs($fp,$text);
					fclose($fp);
					if (!(rename($path.'/'.$f[0],$f[1]) && chmod($f[1], 0644))) die("$f[0] to $f[1] impossible to copy");
				} else {
					if (!(copy($path.'/'.$f[0],$f[1]) && chmod($f[1], 0644))) die("$f[0] to $f[1] impossible to copy");
				}
			}
		}
		copy($file,$path.'/Installed/'.basename($file));
		$this->rebuild_list($path.'/Installed/');
	}

	function remove($path,$mod,$upgrade=false) {
		$this->feedback_info("removing ".$mod->modname." (".$mod->revision.") ...");
		$file = $path.'/Installed/'.$mod->modname.'.info.txt';
		if (is_file($file)) {
			$info = new TikiModInfo($mod->modname);
			$err = $info->readinfo($file);
			if ($err !== false) {
				$this->feedback_error($err);
				return false;
			}
			if (!$upgrade and is_array($info->sql_remove) and count($info->sql_remove)) {
				global $tikilib;
				foreach ($info->sql_remove as $sql) {
					$tikilib->query($sql,array());
				}
			}
			if (isset($info->files) and count($info->files)) {
				foreach ($info->files as $f) {
					if (!@unlink($f[1])) $this->feedback_warning(sprintf(tra("%s impossible to remove"), $f[1]));
					if (is_file($f[1] . '.orig.' . $info->revision)) {
						rename($f[1] . '.orig.' . $info->revision, $f[1]);
					}
				}
			}
			unlink($file);
			$this->rebuild_list($path.'/Installed/');
			if (is_file($path.'/Installed/'.$info->type.'-'.$info->name.'.conf.txt')) {
				unlink($path.'/Installed/'.$info->type.'-'.$info->name.'.conf.txt');
			}
			return $info->revision;
		} else {
			$this->feedback_warning("'$file' was not found for removing");
		}
	}
}

function newer($a,$b) {
	$aa = split('\.',$a);
	$bb = split('\.',$b);
	for ($i=0, $max_counts = max(count($aa), count($bb)); $i<$max_counts; $i++) {
		if (!isset($bb[$i])) { $bb[$i] = '0'; }
		if (!isset($aa[$i])) { $aa[$i] = '0'; }
		if ($aa[$i] != $bb[$i]) { return $aa[$i] > $bb[$i]? 1: -1; }
	} 
	return 0;
} 
$modslib = new ModsLib;
