<?php
// Powered by Piminoff Nikita
// by pimnik98
// https://github.com/pimnik98/INIStorage

function ExceptionHandlerFileIO($e){
	global $core,$site,$user,$sys,$db,$FileIO,$starttime,$deb;
	$db->Query("INSERT INTO `fileio_log`(`uid`, `path`, `query`, `action`, `timeadd`, `fatal`, `post`) VALUES ('".$user->uid."','".$FileIO->getPath()."','".$core->FilterString($_SERVER["REQUEST_URI"])."','FileIO-Fatal','".(time())."','1','".(base64_encode(json_encode($_POST)))."')");
	include_once "../core/head.php";
	echo '<div class="title">FileIO Fatal Error</div>';
	echo '<div class="error">'.$e->getMessage().'<br>Сообщите администратору о данной проблеме!</div>';
	echo b("/filemgr.lm","Вернуться в начало","Возращает вас в самое начало файлового менеджера");
	echo hcp();
	include_once '../core/foot.php';
	die();
}
set_exception_handler("ExceptionHandlerFileIO");

function domainzip_replace($name){
	global $user;
	$name = str_replace($user->getData()->domain, "domain", $name);
	return $name;
}

class FileIO
{
	private $path  = "/virtual/";
	private $serverPath = "/server/";
	private $db = null;
	private $core = null;
	public  $Quota = null;
	private $site = null;
	private $max_space = 0;
	private $cur_space = 0;
	private $infoPath = [];
	public  $allow_ext = [
		"create"	=>
			["html","css","js","json","txt","xml","ini", "txt", "lm"],
		"load"		=>
			["html","css","js","json","txt","xml","ini", "lm","jpg","png","gif","zip","svg","ttf","woff","woff2","ico","mid","midi","mp3","tar","7z","gz","webp","eot"],
		"unzip"		=>	
			["html","css","js","json","txt","xml","ini", "lm","jpg","png","gif","zip","svg","ttf","woff","woff2","ico","mid","midi","mp3","tar","7z","gz","webp","eot"],
	];
	public $maxLoading = 4*1024*1024;


    /**
     * Server инициализация
     * @param string $file - Путь к файлу
     */
	 
    function __construct($core,$db,$site)
    {
        $this->core = $core;
		$this->Quota = $core->Quota;
		$this->db = $db;
		$this->site = $site;
    }
	
	// Установка текущего серверного путя
	function serverPath($path){
		$this->serverPath = $path;
	}
	
	// Установка текущего виртуального путя
	function setCurrentPath($path){
		$path = str_replace('\\', "",$path);
		$path = str_replace('//', "/",$path);
		$path = str_replace('../', "",$path);
		$path = str_replace('./', "",$path);
		$path = str_replace('\..\\', "",$path);
		$path = str_replace('\.\\', "",$path);
		$this->infoPath = pathinfo($path);
		$this->path = $path;
	}
	
	// Получение виртуального пути
	function getPath(){
		return $this->path;
	}
	
	// Получение полного пути для файловой системы
	function getRealPath(){
		return $this->serverPath.$this->getPath();
	}
	
	// Выводит в массиве список дирикторей
	function dirList($path=false){
		$array = [];
		$dir = opendir($path?$this->serverPath.$path:$this->getRealPath());
		while($file = readdir($dir)){
			if(is_dir($path?$path:$this->getRealPath().'/'.$file)){
				if($file != "." && $file != ".."){
					$array[] = [
						"type"	=>	"dir",
						"path"	=>	$this->getPath().$file."/",
						"name"	=>	$file,
						"count"	=>	$this->countFolder($this->getPath().$file."/"),
						"info"	=>	pathinfo($this->getRealPath().'/'.$file)
					];
				}
			} elseif(is_file($this->getRealPath().'/'.$file)){
				$array[] = [
						"type"	=>	"file",
						"path"	=>	$this->getPath().$file,
						"size"	=>	$this->RealFileSize(filesize($this->getRealPath().'/'.$file)),
						"name"	=>	$file,
						"isEdit"=>	$this->isFileEdit($this->getRealPath().'/'.$file),
						"edit"	=>	$this->core->RealTime(filemtime($this->getRealPath().'/'.$file)),
						"info"	=>	pathinfo($this->getRealPath().'/'.$file)
					];
					
			} else {
				
			}
		}
		return $array;
	}
	
	function sortDir($list,$type=0){
		if ($type == 0){
			return $list;
		} else {
			return $this->sortDirHead($list);
		}
	}
	
	function sortDirHead($list){
		$l = [];
		foreach ($list as $dir){
			if ($dir["type"] == "dir"){
				$l[] = $dir;
			}
		}
		foreach ($list as $file){
			if ($file["type"] == "file"){
				$l[] = $file;
			}
		}
		return $l;
	}
	
	// Текущий путь - Папка?
	function isDir($path=false){
		return is_dir($path?$path:$this->getRealPath());
	}
	
	// Текущий путь - Файл?
	function isFile($path=false){
		return is_file($path?$path:$this->getRealPath());
	}
	
	// Проверка доступен ли файл для редактирования
	function isFileEdit($path=false){
		$pi = ($path?pathinfo($path):$this->getInfoPath());
		if (!isset($pi["extension"])){
			return false;
		} 
		foreach ($this->allow_ext["create"] as $ext){
			if ($pi["extension"] == $ext){
				return true;
			}
		}
		return false;
	}
    
	// Регистрирует любые указанные события выполненые в файловом-менеджере
	function RegisterEvent($action="n/a"){
		// План -> [Время H:i:s] [login] [path] [get/post] [action] [all post]
		@$this->db->Query("INSERT INTO `fileio_log`(`uid`, `path`, `query`, `action`, `timeadd`, `fatal`, `post`) VALUES ('".$this->site->id."','".$this->getPath()."','".$this->core->FilterString($_SERVER["REQUEST_URI"])."','".$action."','".(time())."','0','".(base64_encode(json_encode($_POST)))."')");
	}
	
	// Получение статуса квоты
	function getStateQuota(){
		return [];
	}
	
	// Получение разрешения на запись
	function getWriteQuota(){
		return true;
	}
	
	// Получение информации о папке
	function getInfoPath(){
		return $this->infoPath;
	}
	
	// Текущий путь для файла
	function CurrentDir(){
		if (!isset($this->getInfoPath()["dirname"])){
			throw new Exception("Ошибка считывания пути.");
		}
		return $this->isRoot()?'/':$this->getInfoPath()["dirname"]."/";
	}
	
	// Проверяет является ли текущая папка ROOT папкой
	function isRoot(){
		if (!isset($this->getInfoPath()["dirname"])){
			throw new Exception("Ошибка считывания пути.");
		}
		return $this->getInfoPath()["basename"] ==""?true:false;
	}
	
	// Получение размера файла в читательном виде
	function RealFileSize($data){
		if($data) {
                if( $data < 1024 ) {
                        return $data . " б";
                } else if( $data < 1048576 ) {
                        return round( ( $data / 1024 ), 2 ) . " кб";
                } else if( $data < 1073741824 ) {            
                        return round( ( $data / 1048576 ), 2 ) . " мб";
                } else {    
                        return round( ( $data / 1073741824 ), 2 ) . " гб";
                }
        }
		return "0 б";
	}
	
	// Создание файла
	function createFile($name,$ext="txt",$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp = fopen($this->getRealPath().$this->core->FilterString($name).".".$ext,"w");
		fputs($fp,"");
		fclose($fp);
		if (!$fp){
			throw new Exception("Ошибка создания файла. Возможно недостаточно прав для совершения данной операции.");
		} else {
			$this->core->GoToPage("/filemgr.lm?path=".$this->getPath());
		}
	}
	
	function copyLoadFile($old,$new){
		if(copy($old,$this->getRealPath().$new)){
			return true;
		} else {
			return false;
		}
	}
	
	function createFileTemplate($name,$ext="txt",$tempPath=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		if (!copy($this->serverPath."/templates/".$tempPath.".html",$this->getRealPath().$this->core->FilterString($name).".".$ext)){
			throw new Exception("Ошибка создания файла по шаблону. Возможно недостаточно прав для совершения данной операции.");
		} else {
			$this->core->GoToPage("/filemgr.lm?path=".$this->getPath());
		}
	}
	
	// Создание папки
	function createDir($name,$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp = mkdir($this->getRealPath().$this->core->FilterString($name)."/");
		if (!$fp){
			throw new Exception("Ошибка создания папки. Возможно недостаточно прав для совершения данной операции.");
		} else {
			$this->core->GoToPage("/filemgr.lm?path=".$this->getPath());
		}
	}
	
	function delRecurceDir($path,$wait=false){
		$p = $path;
		$array = [];
		if (is_dir($p)){
			$array[] = ["type"=>"dir","path"=>$p];
			$os = scandir($p);
			foreach ($os as $o){
				if ($o != "." && $o != ".."){
					if (filetype($p."/".$o) == "dir"){
						$res = $this->delRecurceDir($p."/".$o."/");
						$array[] = $res;
					} else {
						$array[] = ["type"=>"file","path"=>$p."/".$o];
						unlink($p."/".$o);
					}
				}
			}
			rmdir($p);
		}
		if ($wait == false){
			$path = (!isset($this->getInfoPath()["dirname"]) || $this->getInfoPath()["dirname"] == '\\'?"/":$this->getInfoPath()["dirname"]);
			$this->core->GoToPage("/filemgr.lm?path=".$path);
		}
		return $array;
	}
	
	// Удаление папки
	function removeDir($path=false,$r=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		if ($path == $this->serverPath || $this->isRoot()){
			throw new Exception("Ошибка удаления папки. Системная дириктроя недоступна для удаления.");
		}
		if ($r){
			return $this->delRecurceDir($path);
		} else {
			$fp = @rmdir($path);
			if (!$fp){
				throw new Exception("Ошибка удаления папки. Возможно <b>папка</b> не пуста иди недостаточно прав для совершения данной операции.");
				} else {
					$path = (!isset($this->getInfoPath()["dirname"]) || $this->getInfoPath()["dirname"] == '\\'?"/":$this->getInfoPath()["dirname"]);
					$this->core->GoToPage("/filemgr.lm?path=".$path);
				}
			}	
	}
	
	// Удаление папки
	function removeFile($path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp = @unlink($path);
		if (!$fp){
			throw new Exception("Ошибка удаления файла. Возможно недостаточно прав для совершения данной операции.");
		} else {
			$this->core->GoToPage("/filemgr.lm?path=".($groot?'':$pi["dirname"])."/");
		}
	}
	
	function saveFile($text,$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp = fopen($path,"w");
		fputs($fp,$text);
		fclose($fp);
		if (!$fp){
			throw new Exception("Ошибка сохранения файла. Возможно недостаточно прав для совершения данной операции.");
		} else {
			return true;
		}
		return false;
	}
	
	function loadFile($path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$file = @file_get_contents($path);
		if ($file === false){
			throw new Exception("Ошибка чтения файла. Возможно недостаточно прав для совершения данной операции.");
		}
		return htmlspecialchars($file);
	}
	
	function loadLineFile($path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$file = @file($path);
		if ($file === false){
			throw new Exception("Ошибка чтения файла. Возможно недостаточно прав для совершения данной операции.");
		}
		return ($file);
	}
	
	function getLastEdit($path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$last = filemtime($path);
		if (!$last){
			return 'никогда';
		} else {
			return $this->core->RealTime($last);
		}
	}
	
	function countFolder($path){
		$path = $this->serverPath.$path;
		$c = ["dir"	=>	0,"file" =>	0];
		$dir = opendir($path);
		while($file = readdir($dir)){
			if(is_dir($path.'/'.$file)){
				if($file != "." && $file != ".."){
					$c["dir"]++;
				}
			} elseif(is_file($path.'/'.$file)){
				$c["file"]++;
			} else {
				
			}
		}
		return $c;
		
	}
	// Получение типа файла для ACE
	function getAceExt(){
		$pi = $this->infoPath;
		switch ($pi["extension"]){
			case "css":{
				return "css";
			}
			case "js": {
				return "javascript";
			}
			case "lm":
			case "html":{
				return "html";
			}
			case "xml":{
				return "xml";
			}
			case "ini":{
				return "ini";
			}
			default:{
				return "text";
			}
		}
	}
	
	function renameFile($new,$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$p = pathinfo($path);
		if (file_exists($p["dirname"]."/".$new)){
			return false; // File Exits
		} elseif(rename($path,$p["dirname"]."/".$new)) {
			return true;
		}
		return false;
	}
	
	function getListTempFiles($site){
		$la = glob(SITES_ROOT.'/'.$site.'/templates/*.html');
		$list = [];
		$list[] = ["file"=>null,"name"=>"(без шаблона)",null];
		foreach ($la as $l){
			
			$pp = $l;
			$l = str_replace(SITES_ROOT.'/'.$site.'/templates/',"",$l);
			$l = str_replace(".html","",$l);
			$tmp = new \Piminoff\INIStorage(SITES_ROOT.'/'.$site.'/templates/'.$l.'.ini',true,\Piminoff\INIStorage::MODE_NORMAL);
			$tmp->def("name",$l);
			$tmp->save(false,"gLark Templates HTML File Info");
			$list[] = ["file"=>$l,"name"=>$tmp->get("name"),"path"=>$pp];
		}
		return $list;
	}
	
	function isLoadExtFile($ext){
		foreach ($this->allow_ext["load"] as $e){
			if ($e == $ext){
				return true;
			}
		}
		return false;
	}
	
	function fileRenameLoad($oldname){
		$result = preg_replace('/[^A-z0-9\_-]/','_',$oldname);
		return $result;
	}
	
	function saveLineFile($file,$ai,$line,$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp=fopen($path,"a+");
		flock ($fp,LOCK_EX);
		ftruncate($fp,0);
		for($i=0; $i<sizeof($file); $i++){
			if ($ai!=$i){
				fputs($fp,$file[$i]);
			}else{
				fputs($fp,$line."\r\n");
			}
		}
		fflush ($fp);
		flock($fp,LOCK_UN);
		fclose($fp);
	}
	
	function delLineFile($file,$ai,$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp=fopen($path,"w");
		flock ($fp,LOCK_EX); 
		for ($i=0; $i< sizeof($file); $i++){ 
			if ($i==$ai){ 
				unset($file[$i]);
			}
		} 
		fputs($fp,implode($file)); 
		flock ($fp,LOCK_UN); 
		fclose($fp);  
	}
	
	
	function newLineFile($file,$ai,$line,$path=false){
		$path = $path?$this->serverPath.$path:$this->getRealPath();
		$fp=fopen($path,"a+");
		flock ($fp,LOCK_EX);
		ftruncate($fp,0);
		if (count($file) == 0){
			fputs($fp,$line."\r\n");
		} else {
			for($i=0; $i<sizeof($file); $i++){
				if ($ai!=$i){
					fputs($fp,$file[$i]);
				}else{
					fputs($fp,$file[$i].$line."\r\n");
				}
			}
		}
		fflush ($fp);
		flock($fp,LOCK_UN);
		fclose($fp);
	}
	
	
	function getZipList($file){
		$zip=new ZipArchive;
		$res = $zip->open($file);
		if ($res !== TRUE) {
			switch($res) {
				case ZipArchive::ER_NOZIP:
					return ["status"=>"error","msg"=>"Данный файл не является архивом"];
				case ZipArchive::ER_INCONS :
					return ["status"=>"error","msg"=>"Ошибка INSZIP"];
				case ZipArchive::ER_CRC :
					return ["status"=>"error","msg"=>"Контрольную сумму не удалось проверить у архива"];
				default:
					return ["status"=>"error","msg"=>"Данный файл не поддерживается данным сервером."];
			}
		}
		if ($res == FALSE){
			return false;
		}
		$com = $this->core->FilterString($zip->getArchiveComment());
		$data = [
			"list"			=> 	[],	// Массив с файлами
			"listAlready"	=>	[],	// Массив с файлами, которые будут допущены
			"errors"		=>	[
				"name"		=>	0,	// Ошибки связанные с именем файла или папки
				"size"		=>	0,	// Ошибки связанные с размером файла
			],
			"com"			=> 	$com,
			"size"			=>	0,	// Размер всего архива
		];
		$i = 0;
		while($name = $zip->getNameIndex($i)) {
			$name = $this->core->FilterString($name);
			$dis = $this->isUnZipFile($name);
			$f_p = pathinfo($name);
			$_d_pm = (preg_match("#^([A-z0-9_-])+$#ui",$f_p["filename"])?1:0);
			$_d_dm = (preg_match("#^([A-z0-9_-])+$#ui",$f_p["dirname"])?1:0);
			$stat = $zip->statIndex($i);
			$size = $stat["size"];
			$data["size"] += $size;
			$unzip = 1;
			$sizeTest = $this->isUnZipFileSize($size);
			if (!$dis){
				$data["errors"]["name"]++;
				$unzip = 0;
			} elseif (!$sizeTest){
				$data["errors"]["size"]++;
				$unzip = 0;
			} else {
				$data["listAlready"][$i] = [
					"name"	=>	$name,
					"size"	=>	$size,
					"unzip"	=>	1,
					"pm"	=>	$_d_pm,
					"dm"	=>	$_d_dm,
				];
			}
			$data["list"][$i] = [
					"name"	=>	$name,
					"size"	=>	$size,
					"unzip"	=>	$unzip,
					"pm"	=>	$_d_pm,
					"dm"	=>	$_d_dm,
				];
			$i++;
		}
		$zip->close();
		return $data;
	}
	
	function unZipData($file,$path,$array=[]){
		$zip=new ZipArchive;
		$zip->open($file);
		$errors = 0;
		$i = 0;
		$list_unzip = [];
		$sysPath = $this->serverPath.$this->CurrentDir();
		while($name = $zip->getNameIndex($i)) {
			$name = $this->core->FilterString($name);
			$dis = $this->isUnZipFile($name);
			$stat = $zip->statIndex($i);
			$size = $stat["size"];
			$sizeTest = $this->isUnZipFileSize($size);
			$select_file = isset($array[$i]);
			if ($dis && $sizeTest && $select_file){
				$list_unzip[] = $name;
			} else {
				$errors++;
			}
			$i++;
		}
		$zip->extractTo($sysPath, $list_unzip);
		$zip->close();
		return $list_unzip;
	}
	
	function isUnZipFile($name){
		$f_p = pathinfo($name);
		if (!isset($f_p["filename"]) || ($f_p["dirname"] != "." && !preg_match("#^([A-z0-9/_-])+$#ui",$f_p["dirname"])) || !preg_match("#^([A-z0-9_-])+$#ui",domainzip_replace($f_p["filename"])) || !isset($f_p["extension"])){
			return false;
		}
		$ext = $f_p["extension"];
		foreach ($this->allow_ext["load"] as $e){
			if ($e == $ext){
				return true;
			}
		}
		return false;
	}
	
	function isUnZipFileSize($f_s){
		if ($this->maxLoading < $f_s){
			return false;
		} elseif (!$this->Quota->isLoad($f_s)){
			return false;
		} 
		return true;
	}

	function GetExt($path=false){
		$pi = ($path?pathinfo($path):$this->getInfoPath());
		if (!isset($pi["extension"])){
			return "";
		} 
		return strtolower($pi["extension"]);
	}
}