<?php
/**
 * Define a fileor directory element.
 * @author Valentin CARRUESCO
 * @category Plugin
 * @license MIT
 */
class Element extends Entity{
	public $id,$path,$label,$type,$extension,$size,$creatorSite,$icon,$thumbnail,$childNumber,$link;
	protected $TABLE_NAME = 'document_element';
	public $fields =
	array(
		'id' => 'key',
		'path' => 'longstring',
		'label' => 'string',
		'type' => 'string',
		'extension' => 'string',
		'size' => 'string',
		'creatorSite' => 'int'
	);

	//Récupération de la racine de la GED
	public static function root(){
		return File::dir().'documents'.SLASH;
	}

	public  function save(){
		$this->path = str_replace('\\','/',$this->path);
		parent::save();
	}

	//Récuperation du chemin ou du flux de thumbnail associé à l'élement
	public function thumbnail(){
		if(!in_array($this->extension, array('jpg','png','jpeg','gif','bmp','jfif'))) return $this->icon();
		if(!file_exists(self::root().'.thumbnails')) mkdir(self::root().'.thumbnails',755,true);

		$thumbname = str_replace(array('\\','./'),array('/',''),$this->path);
		$thumbnail = self::root().'.thumbnails'.SLASH.base64_encode($thumbname).'.'.$this->extension;

		if(!file_exists($thumbnail)){
			$osPath = File::convert_decoding($this->path);
			copy(self::root().$osPath,$thumbnail);
			Image::resize($thumbnail,200,200);
		}
		$path ='data:image/'.$this->extension.';base64,'.base64_encode(file_get_contents($thumbnail));

		return $path;
	}

	//Récuperation du chemin di'one à l'élement
	public function icon(){
		$path = 'plugin/document/img/file-types/';
		$icon = $this->type == 'directory' ? 'folder.svg' : 'document.svg';

		switch($this->extension){
			case 'docx':
			case 'doc':
			case 'dot':
			case 'dotx':
				$icon = 'word.svg';
			break;
			case 'xls':
			case 'xlsx':
			case 'csv':
				$icon = 'excel.svg';
			break;
			case 'ppt':
			case 'pptx':
				$icon = 'powerpoint.svg';
			break;
			case 'pdf':
				$icon = 'pdf.svg';
			break;
			case 'mp3':
			case 'wav':
			case 'mp4':
			case 'flac':
				$icon = 'music.svg';
			break;
			case 'html':
			case 'htm':
				$icon = 'url.svg';
			break;
			case 'msg':
				$icon = 'mail.svg';
			break;
			case 'psd':
				$icon = 'photoshop.svg';
			break;
			case 'rss':
				$icon = 'feed.svg';
			break;
			case 'mdb':
				$icon = 'access.svg';
			break;
			case 'bat':
			case 'sh':
			case 'cmd':
				$icon = 'gear.svg';
			break;
			case 'php':
			case 'js':
			case 'java':
			case 'class':
			case 'css':
			case 'c':
			case 'cpp':
			case 'py':
				$icon = 'code.svg';
			break;
			case 'jpg':
			case 'jpeg':
			case 'png':
			case 'bmp':
			case 'gif':
			case 'svg':
			case 'jfif':
				$icon = 'image.svg';
			break;
		}

		$path .= $icon;

		return $path;
	}

	//Récuperation d'un element base depuis un chemin physique, ou d'un objet element rensiegné a partir du path si non existant en base (prend du utf8 en entré)
	public static function fromPath($path){

		$element = new self();

		//convertion utf8 ISO-8859-1
		$osPath = File::convert_decoding($path);
		$infos = pathinfo($path);
		//Gestion label

		$element->label = mt_basename($path);

		$element->path = str_replace(array(self::root(),'\\'),array('','/'),$path);
		$element->type = is_dir($osPath) ? 'directory' : 'file';
		$element->link = is_link($osPath);
		$exists = file_exists($osPath);

		if($element->type == 'directory'){
			$path .= SLASH;
			$fi = new FilesystemIterator($osPath, FilesystemIterator::SKIP_DOTS);
			$element->childNumber = iterator_count($fi);
			$element->size =  0;
		} else {
			if(isset($infos['extension'])) $element->extension = $infos['extension'];
			$element->size = !$exists ? 0 : filesize($osPath);
		}

		$element->updated =  !$exists ? 0 : filemtime($osPath);
		$element->created =  !$exists ? 0 : filectime($osPath);

		$relativePath = trim(str_replace(Element::root(),'',$path),SLASH);
		if ($baseElement = Element::load(array('path'=>str_replace('\\','/',$relativePath)   ))) {
			$element->creator = $baseElement->creator;
			$element->id = $baseElement->id;
		}
		$element->path = trim($element->path,'/');

		return $element;
	}

	//Récuperation de la liste des élements d'un repertoire (tiens compte des droits utilisateur) (attends de l'utf8)
	public static function browse($scanned,$folderOnly = false){
		global $myUser,$myFirm;
		require_once(__DIR__.SLASH.'ElementRight.class.php');

		$osScanned = File::convert_decoding($scanned);
		$elements = array();
		User::check_access('document','read');


		//Récuperation du tableau des rang utilisateur sous forme d'id.
		$ranks = array();
		if(!$myUser->superadmin){
			foreach($myUser->ranks[$myFirm->id] as $rank)
				$ranks[] = $rank->id;
		}

		$existingPathes = array();

		//Fix glob et utilisation d'un dossier avec des [] (glob utilise ce char pour de la regex, on doit escape)
		$osScanned = str_replace(array('[',']'),array('\[','\]'), $osScanned);
		$osScanned = str_replace(array('\[','\]'), array('[[]','[]]'), $osScanned);


		//Pour chaque fichier physique existant
		foreach(glob($osScanned) as $osPath){

			$osPath = str_replace(SLASH.'.'.SLASH, SLASH, $osPath);
			$basePath = File::convert_encoding($osPath);
			if((substr(mt_basename($osPath), 0,1)=='.') || ($folderOnly && !is_dir($osPath))) continue;


			//Récuperation/création de l'element objet correspondant (frompath n'accepte que l'utf8, on lui donne le basepath)
			$line = Element::fromPath($basePath);

			$existingPathes[] = $line->path;

			//Si l'element correspondant n'existe pas en base, on le créé
			if($line->id == ''){
				$line->creator = 'anonymous';
				$line->save();
			}

			//Si l'utilisateur n'est pas le créateur, pas un super admin et n'a pas le droit configure sur les documents on check les partages de droits
			if($myUser->login != $line->creator && !$myUser->can('document','configure')  && !$myUser->superadmin){
				$can = false;
				$pathes = array();
				$path = '';

				//Récuperation des chemins dont on doit récuperer les droits :
				// ex : pour le fichier a/b/c les chemins sont a, a/b, et a/b/c
				foreach(explode('/',$line->path) as $i=>$pathCrumb){
					if($i!=0) $path.= '/';
					$path.= $pathCrumb;
					$pathes[] = $path;
				}

				//On récupere les droits pour chaques chemin
				foreach(ElementRight::staticQuery('SELECT er.*,el.path as '.Element::tableName().'_join_path
					FROM {{table}} er
					LEFT JOIN '.Element::tableName().' el ON el.id=er.element
					WHERE (er.entity=? OR (er.entity=? AND er.uid=?) OR (er.entity=? AND er.uid IN ('.str_repeat('?,',count($ranks)-1).'?)))
					AND element IN(SELECT id from '.Element::tableName().' WHERE path IN ('.str_repeat('?,',count($pathes)-1).'?)) ',array_merge(array("all","user",$myUser->login,"rank"),$ranks,$pathes),true,1) as $right){

					$element = $right->join('element');


					//Si les droits sont sur un dossier parent et sont recursifs OU si les droits sont sur le fichier concerné on continue la verification
					if($right->element == $line->id || ($right->recursive && strpos($line->path, $element->path)!==false ) )
						$can = true;
				}
				if(!$can) continue;
			}
			$elements[] = $line;
		}


		return $elements;
	}

	//Téléchargement d'un fichier ou d'un repertoire (prends de l'utf 8 en entrée)
	public static function download($path){
		$osPath = File::convert_decoding($path);

		if(!file_exists($osPath)) return '';
		$element = Element::fromPath($path);

		if(!self::hasRight($element,'read')) throw new Exception("Permissions insuffisantes",403);

		if(is_dir($osPath)){
			$filename = rand().'-'.time().'.zip';
			$filepath = sys_get_temp_dir().SLASH.$filename;
			$zip = new ZipArchive;

			$res = $zip->open($filepath, ZipArchive::CREATE);
			if ($res === TRUE) {
				//Fix glob et utilisation d'un dossier avec des [] (glob utilise ce char pour de la regex, on doit escape)
				$osPath = str_replace(array('[',']'),array('\[','\]'), $osPath);
				$osPath = str_replace(array('\[','\]'), array('[[]','[]]'), $osPath);
				$ressources = glob_recursive($osPath.SLASH.'*') ;
				foreach($ressources as $resource){
					if(is_dir($resource)) continue;
					$filename = $resource;
					$filename = File::convert_encoding($filename);
					$filename =  str_replace($path,mt_basename($path),$filename);
					$filename=iconv("UTF-8", "IBM850", $filename);
					if(!$zip->addFromString($filename, file_get_contents($resource))) throw new Exception("Impossible de compresser le fichier ".$path);
				}

				if(count($ressources)==0)
					$zip->addEmptyDir('.');

				$zip->close();
			}
			$stream = file_get_contents($filepath);
			unlink($filepath);
		}else{
			$stream = file_get_contents($osPath);
		}
		return $stream;
	}

	//Creation d'un raccourcis (prends de l'utf 8 en entrée)
	public static function shortcut($path,$destination,$readonly = false){

		$osDestination = File::convert_decoding($destination);
		if(file_exists($osDestination)) return false;
		$element = Element::fromPath($destination);
		if($readonly){
			require_once(__DIR__.SLASH.'ElementRight.class.php');
			$right = new ElementRight();
			$right->element = $element->id;
			$right->read = true;
			$right->edit = false;
			$right->delete = false;
			$right->recursive = false;
			$right->save();
		}
		return symlink($path,$osDestination);
	}

	//Supression d'un répértoire ou d'un fichier en base et en physique (prends de l'utf 8 en entrée)
	public static function remove($path){

		require_once(__DIR__.SLASH.'ElementRight.class.php');
		$osPath = File::convert_decoding($path);
		if(!file_exists($osPath)) return;
		$element = Element::fromPath($path);
		$dbPath = str_replace('\\','/',$element->path);
		if(!self::hasRight($element,'edit')) throw new Exception("Permissions insuffisantes",403);

		if(is_dir($osPath) && !is_link($osPath)) {



			self::remove_dir_recursive($osPath);
			Element::staticQuery('DELETE FROM {{table}} WHERE `path` = ? OR `path` LIKE ?',array($dbPath,$dbPath.'/%'));
		} else {
			unlink($osPath);
			Element::delete(array('path'=>$dbPath));
		}
		ElementRight::delete(array('element'=>$element->id));
	}

	//Copie d'un répértoire ou d'un fichier en base et en physique (prends de l'utf 8 en entrée)
	public static function copy($path,$to, $checkExist = true){

		$osPath = File::convert_decoding($path);
		$osTo = File::convert_decoding($to);

		if(!file_exists($osPath)) return;
		$element = Element::fromPath($path);
		if($checkExist && file_exists($osTo)) return false;

		$parentPathTo = dirname($to);
		$parentOsPathTo = dirname($osTo);
		if(!file_exists($parentOsPathTo))  throw new Exception("Dossier de destination inexistant",404);
		$parentTo = Element::fromPath($parentPathTo);
		if(!self::hasRight($parentTo,'read')) throw new Exception("Permissions insuffisantes",403);
		if(!self::hasRight($element,'read')) throw new Exception("Permissions insuffisantes",403);

		$dbPath = str_replace('\\','/',$element->path);

		if($element->type == 'directory'){
			//Si c'est un dossier, on récupere tous les élements commendant par ce nom afin de gerer leurs chemins
			$baseElement = Element::staticQuery('SELECT * FROM {{table}} WHERE `path` = ? OR `path` LIKE ?',array($dbPath,$dbPath.'/%'), true);
		} else {
			$baseElement = Element::load(array('path'=>$dbPath));
			if(is_object($baseElement) && $baseElement->id != 0) $element->id = $baseElement->id;
		}

		if(!File::copy($osPath,$osTo)) throw new Exception('Erreur lors de la copie...');
		$newelement = Element::fromPath($to);

		if(is_array($baseElement)){
			foreach ($baseElement as $elem) {
				if($elem->path == $dbPath) $elem->label = $element->label;
				$elem->path = str_replace($dbPath, $newelement->path, $elem->path);
				$elem->id = 0;
				$elem->save();
			}
		} else {
			$baseElement->path = $newelement->path;
			$baseElement->label = $newelement->label;
			$baseElement->id = 0;
			$baseElement->save();
		}
	}

	//Déplacement d'un répertoire ou d'un fichier en base et en physique
	public static function move($path,$to, $label=''){

		$osPath = File::convert_decoding($path);
		$osTo = File::convert_decoding($to);

		if(!file_exists($osPath)) return;

		$element = Element::fromPath($path);


		//Si le dossier se termine par un ., on supprime ce . (non pris en charge par l'os)
		if($element->type=="directory" && substr($to, -1,1) =='.')
			$to = substr($to, 0,strlen($to)-1);


		if($to == $path) return $element;

		if(!self::hasRight($element,'edit')) throw new Exception("Permissions insuffisantes",403);

		$parentPathTo = dirname($to);
		$parentPathOsTo = dirname($osTo);
		if(!file_exists($parentPathOsTo))  throw new Exception("Dossier de destination inexistant",404);
		$parentTo = Element::fromPath($parentPathTo);
		if(!self::hasRight($parentTo,'edit')) throw new Exception("Permissions insuffisantes",403);


		$dbPath = str_replace('\\','/',$element->path);

			if(file_exists($osTo)) return false;

			if($element->type == 'directory'){

				$element->path = $oldPath = $element->path;

				//Si c'est un dossier, on récupere tous les élements commencant par ce nom afin de modifier leurs chemins
				$baseElement = Element::staticQuery('SELECT * FROM {{table}} WHERE `path` = ? OR `path` LIKE ?',array($dbPath,$dbPath.'/%'), true);
			} else {
				$baseElement = Element::load(array('path'=>$dbPath));
				if(is_object($baseElement) && $baseElement->id != 0) $element->id = $baseElement->id;
			}
			try {
				if(!rename($osPath,$osTo))
					throw new Exception('Un élément du même nom existe déjà.');
			} catch (Exception $e) {
				return false;
			}
			$toElement = Element::fromPath($to);
			//Si type est image, on supprime l'ancienne miniature
			if(in_array($element->extension, array('jpg','png','jpeg','gif','bmp')) && file_exists(self::root().'.thumbnails'.SLASH.base64_encode($element->path).'.'.$element->extension))
				unlink(self::root().'.thumbnails'.SLASH.base64_encode($element->path).'.'.$element->extension);

			if(is_array($baseElement)){
				foreach ($baseElement as $elem) {
					if($elem->path == $oldPath)
						$elem->label = $toElement->label;

					$elem->path = str_replace($dbPath, $toElement->path, $elem->path);
					$elem->save();
				}
			} else {
				$baseElement->path = $toElement->path;
				$baseElement->label = $toElement->label;
				$baseElement->save();
			}


		return $toElement;
	}

	//Supprime les caractères interdits ou non utf8 (ex le "’" de word )
	public static function convertName($name){
		$name = iconv("UTF-8","UTF-8//IGNORE",$name);
		$name = str_replace(array('’','»','«'), array("'",'"','"'), $name);
		return $name;
	}

	//Ajout d'un fichier en base et en physique (prend de l'utf8 en entré pour le path)
	public static function addFile($path,$stream){
		//on filtre les noms non utf8 ou interdits
		$path = dirname($path).SLASH.self::convertName(basename($path));
		//on enregistre le fichier dans l'encodage du système
		$osPath = File::convert_decoding($path);
		$parentPath = dirname($path);
		$parentOsPath = File::convert_decoding($parentPath);
		if(!file_exists($parentOsPath))  throw new Exception("Dossier de destination inexistant",404);
		$parent = Element::fromPath($parentPath);

		if(!self::hasRight($parent,'edit')) throw new Exception("Permissions insuffisantes",403);

		file_put_contents($osPath, $stream);

		$element = Element::fromPath($path, array('preview'=>false));

		if(!self::hasRight($element,'edit')) throw new Exception("Permissions insuffisantes",403);

		$element->save();
		return $element;
	}

	//Ajout d'un dossier en base et en physique  (prend de l'utf8 en entré pour le path)
	public static function addFolder($path, $recursive = false, $checkRight=true, $creator = null){

		//on filtre les noms non utf8 ou interdits
		$path = dirname($path).SLASH.self::convertName(basename($path));

		$osPath = File::convert_decoding($path);
		$parentPath = dirname($path);
		$osParentPath = dirname($osPath);
		if(!file_exists($osParentPath)) {
			if(!$recursive){
				throw new Exception("Dossier parent ".$parentPath."  inexistant",404);
			} else {
				self::addFolder($parentPath, $recursive, $checkRight, $creator);
			}
		}
		$parent = Element::fromPath($parentPath);

		if($checkRight && !self::hasRight($parent,'edit')) throw new Exception("Permissions insuffisantes",403);

		if(file_exists($osPath)) return;
		mkdir($osPath,0755,$recursive);
		$element = Element::fromPath($path);
		if(isset($creator)) $element->creator = $creator;
		$element->save();
		return $element;
	}

	public static function hasRight($element,$type){
		global $myUser,$myFirm;
		require_once(__DIR__.SLASH.'ElementRight.class.php');
		$documentRight = '';
		switch($type){
			case 'read':
				$documentRight = 'read';
			break;
			case 'delete':
			case 'edit':
				$documentRight = 'edit';
			break;
		}

		if(!$myUser->can('document',$type)) return false;
		if($myUser->login == $element->creator || $myUser->can('document','configure') || $myUser->superadmin == 1) return true;

		$allPathes = array();
		$rootPath = '';
		foreach (explode('/',$element->path) as $i=>$crumb) {
			$rootPath .= ($i==0?'':'/').$crumb;
			$allPathes[] = $rootPath;
		}

		$userRanks = array();
		foreach ($myUser->ranks[$myFirm->id] as $rank)
			$userRanks[] = $rank->id;


		$data = array();
		$query = 'SELECT dr.recursive,dr.edit,dr.read,de.path
		FROM {{table}} dr
		LEFT JOIN '.Element::tableName().' de ON de.id = dr.element
		WHERE dr.element IN(
			SELECT id
			FROM '.Element::tableName().'
			WHERE path IN ('.str_repeat('?,',count($allPathes)-1).'?)
		)  AND (dr.entity=? OR (dr.entity=? AND dr.uid=?) ';
		$data = array_merge($allPathes, array("all","user",$myUser->login));

		if(count($userRanks)!=0){
			$query .= ' OR (dr.entity=? AND dr.uid IN ('.str_repeat('?,',count($userRanks)-1).'?)) ';
			$data = array_merge($data,array("rank"),$userRanks);
		}

		$query .= ') ORDER BY CHAR_LENGTH(de.path)';

		$result = ElementRight::staticQuery($query,$data);

		$rights = array(
			'edit'=> false,
			'read'=> false
		);
		foreach($result->fetchAll() as $line){

			//si le droit n'est pas récursif et que le chemin associé n'est pas exactement cleui ciblé on ignore ce droit
			if($line['recursive'] != 1  && $line['path']!=$element->path) continue;

			if($line['edit'] == 1) $rights['edit'] = true;
			if($line['read'] == 1) $rights['read'] = true;

		}

		if(!$rights[$type]) return false;

		return true;
	}

	public static function remove_dir_recursive($path) {

		//Fix glob et utilisation d'un dossier avec des [] (glob utilise ce char pour de la regex, on doit escape)
		$globPath = $path.SLASH.'{,.}*';
		$globPath = str_replace(array('[',']'),array('\[','\]'), $globPath);
		$globPath = str_replace(array('\[','\]'), array('[[]','[]]'), $globPath);

		foreach(glob($globPath,GLOB_BRACE) as $element){
			if(mt_basename($element) == '.' || mt_basename($element) == '..') continue;
			if(is_dir($element)) {
				self::remove_dir_recursive($element);
			} else {
				unlink($element);
			}
		}
		rmdir($path);
	}



}
?>
