<?php

// set locale to UTF8 to correct multibyte characters 
setlocale(LC_ALL, 'en_US.UTF8');

interface elFinderILogger {
	public function log($cmd, $ok, $context, $err='', $errorData = array());
}

class elFinder {
	
	/**
	 * object options
	 *
	 * @var array
	 **/
	protected $_options = array(
		'root'         => '',           // path to root directory
		'URL'          => '',           // root directory URL
		'rootAlias'    => 'Home',       // display this instead of root directory name
		'disabled'     => array(),      // list of not allowed commands
		'dotFiles'     => false,        // display dot files
		'dirSize'      => true,         // count total directories sizes
		'fileMode'     => 0666,         // new files mode
		'dirMode'      => 0777,         // new folders mode
		'mimeDetect'   => 'auto',       // files mimetypes detection method (finfo, mime_content_type, linux (file -ib), bsd (file -Ib), internal (by extensions))
		'uploadAllow'  => array(),      // mimetypes which allowed to upload
		'uploadDeny'   => array(),      // mimetypes which not allowed to upload
		'uploadOrder'  => 'deny,allow', // order to proccess uploadAllow and uploadAllow options
		'imgLib'       => 'auto',       // image manipulation library (imagick, mogrify, gd)
		'tmbDir'       => '.tmb',       // directory name for image thumbnails. Set to "" to avoid thumbnails generation
		'tmbCleanProb' => 1,            // how frequiently clean thumbnails dir (0 - never, 200 - every init request)
		'tmbAtOnce'    => 5,            // number of thumbnails to generate per request
		'tmbSize'      => 48,           // images thumbnails size (px)
		'tmbCrop'      => true,         // crop thumbnails (true - crop, false - scale image to fit thumbnail size)
		'tmbBgColor'   => '#ffffff',    // thumbnail background color
		'fileURL'      => true,         // display file URL in "get info"
		'dateFormat'   => 'j M Y H:i',  // file modification date format
		'logger'       => null,         // object logger
		'aclObj'       => null,         // acl object (not implemented yet)
		'aclRole'      => 'user',       // role for acl
		'defaults'     => array(        // default permisions
			'read'   => true,
			'write'  => true,
			'rm'     => true
			),
		'perms'        => array(),      // individual folders/files permisions     
		'debug'        => false,        // send debug to client
		'archiveMimes' => array(),      // allowed archive's mimetypes to create. Leave empty for all available types.
		'archivers'    => array()       // info about archivers to use. See example below. Leave empty for auto detect
		// 'archivers' => array(
		// 	'create' => array(
		// 		'application/x-gzip' => array(
		// 			'cmd' => 'tar',
		// 			'argc' => '-czf',
		// 			'ext'  => 'tar.gz'
		// 			)
		// 		),
		// 	'extract' => array(
		// 		'application/x-gzip' => array(
		// 			'cmd'  => 'tar',
		// 			'argc' => '-xzf',
		// 			'ext'  => 'tar.gz'
		// 			),
		// 		'application/x-bzip2' => array(
		// 			'cmd'  => 'tar',
		// 			'argc' => '-xjf',
		// 			'ext'  => 'tar.bz'
		// 			)
		// 		)
		// 	)
		);
	
	/**
	 * mapping $_GET['cmd]/$_POST['cmd] to class methods
	 *
	 * @var array
	 **/
	protected $_commands = array(
		'open'      => '_open',
		'reload'    => '_reload',
		'mkdir'     => '_mkdir',
		'mkfile'    => '_mkfile',
		'rename'    => '_rename',
		'upload'    => '_upload',
		'paste'     => '_paste',
		'rm'        => '_rm',
		'duplicate' => '_duplicate',
		'read'      => '_fread',
		'edit'      => '_edit',
		'archive'   => '_archive',
		'extract'   => '_extract',
		'resize'    => '_resize',
		'tmb'       => '_thumbnails',
		'ping'      => '_ping'
		);
		
	/**
	 * List of commands to log
	 *
	 * @var string
	 **/
	public $_loggedCommands = array('mkdir', 'mkfile', 'rename', 'upload', 'paste', 'rm', 'duplicate', 'edit', 'resize');
	
	/**
	 * Context to log command
	 *
	 * @var string
	 **/
	protected $_logContext = array();
		
	/**
	 * extensions/mimetypes for _mimetypeDetect = 'internal' 
	 *
	 * @var array
	 **/
	protected $_mimeTypes = array(
		//applications
		'ai'    => 'application/postscript',
		'eps'   => 'application/postscript',
		'exe'   => 'application/octet-stream',
		'doc'   => 'application/vnd.ms-word',
		'xls'   => 'application/vnd.ms-excel',
		'ppt'   => 'application/vnd.ms-powerpoint',
		'pps'   => 'application/vnd.ms-powerpoint',
		'pdf'   => 'application/pdf',
		'xml'   => 'application/xml',
		'odt'   => 'application/vnd.oasis.opendocument.text',
		'swf'   => 'application/x-shockwave-flash',
		// archives
		'gz'    => 'application/x-gzip',
		'tgz'   => 'application/x-gzip',
		'bz'    => 'application/x-bzip2',
		'bz2'   => 'application/x-bzip2',
		'tbz'   => 'application/x-bzip2',
		'zip'   => 'application/zip',
		'rar'   => 'application/x-rar',
		'tar'   => 'application/x-tar',
		'7z'    => 'application/x-7z-compressed',
		// texts
		'txt'   => 'text/plain',
		'php'   => 'text/x-php',
		'html'  => 'text/html',
		'htm'   => 'text/html',
		'js'    => 'text/javascript',
		'css'   => 'text/css',
		'rtf'   => 'text/rtf',
		'rtfd'  => 'text/rtfd',
		'py'    => 'text/x-python',
		'java'  => 'text/x-java-source',
		'rb'    => 'text/x-ruby',
		'sh'    => 'text/x-shellscript',
		'pl'    => 'text/x-perl',
		'sql'   => 'text/x-sql',
		// images
		'bmp'   => 'image/x-ms-bmp',
		'jpg'   => 'image/jpeg',
		'jpeg'  => 'image/jpeg',
		'gif'   => 'image/gif',
		'png'   => 'image/png',
		'tif'   => 'image/tiff',
		'tiff'  => 'image/tiff',
		'tga'   => 'image/x-targa',
		'psd'   => 'image/vnd.adobe.photoshop',
		//audio
		'mp3'   => 'audio/mpeg',
		'mid'   => 'audio/midi',
		'ogg'   => 'audio/ogg',
		'mp4a'  => 'audio/mp4',
		'wav'   => 'audio/wav',
		'wma'   => 'audio/x-ms-wma',
		// video
		'avi'   => 'video/x-msvideo',
		'dv'    => 'video/x-dv',
		'mp4'   => 'video/mp4',
		'mpeg'  => 'video/mpeg',
		'mpg'   => 'video/mpeg',
		'mov'   => 'video/quicktime',
		'wm'    => 'video/x-ms-wmv',
		'flv'   => 'video/x-flv',
		'mkv'   => 'video/x-matroska'
		);
	
	/**
	 * undocumented class variable
	 *
	 * @var string
	 **/
	protected $_time = 0;
				
	/**
	 * Additional data about error
	 *
	 * @var array
	 **/
	protected $_errorData = array();
		
	/**
	 * undocumented class variable
	 *
	 * @var string
	 **/
	protected $_fakeRoot = '';
	
	/**
	 * Command result to send to client
	 *
	 * @var array
	 **/
	protected $_result = array();
		
	/**
	 * undocumented class variable
	 *
	 * @var string
	 **/
	protected $_today = 0;
			
	/**
	 * undocumented class variable
	 *
	 * @var string
	 **/
	protected $_yesterday = 0;
			
	/**
	 * constructor
	 *
	 * @param  array object options
	 * @return void
	 **/
	public function __construct($options=array()) {
		foreach ($this->_options as $k=>$v) {
			if (isset($options[$k])) {
				$this->_options[$k] = is_array($this->_options[$k]) 
					? array_merge($this->_options[$k], $options[$k]) 
					: $options[$k];
			}
		}

		if (substr($this->_options['root'], -1) == DIRECTORY_SEPARATOR) {
			$this->_options['root'] = substr($this->_options['root'], 0, -1);
		}

		$this->_time = $this->_options['debug'] ? $this->_utime() : 0;

		$this->_fakeRoot = !$this->_options['rootAlias'] 
			? $this->_options['root'] 
			: dirname($this->_options['root']).DIRECTORY_SEPARATOR.$this->_options['rootAlias'];
			
		if (!empty($this->_options['disabled'])) {
			$no = array('open', 'reload', 'tmb', 'ping');
			foreach ($this->_options['disabled'] as $k => $c) {
				if (!isset($this->_commands[$c]) || in_array($c, $no)) {
					unset($this->_options['disabled'][$k]);
				} else {
					unset($this->_commands[$c]);
				}
			}
		}
				
		if ($this->_options['tmbDir']) {
			$tmbDir = $this->_options['root'].DIRECTORY_SEPARATOR.$this->_options['tmbDir'];
			$this->_options['tmbDir'] = is_dir($tmbDir) || @mkdir($tmbDir, $this->_options['dirMode']) ? $tmbDir : '';
		}
		if ($this->_options['tmbDir']) {
			if (!in_array($this->_options['imgLib'], array('imagick', 'mogrify', 'gd'))) {
				$this->_options['imgLib'] = $this->_getImgLib();
			}
		}
		$this->_today = mktime(0,0,0, date('m'), date('d'), date('Y'));
		$this->_yesterday = $this->_today-86400;
	}

	/**
	 * Proccess client request and output json
	 *
	 * @return void
	 **/
	public function run() {
		if (!function_exists('json_encode')) {
			exit('{"error":"PHP JSON module not installed"}');
		}
		if (empty($this->_options['root']) || !is_dir($this->_options['root'])) {
			exit(json_encode(array('error' => 'Invalid backend configuration')));
		}
		if (!$this->_isAllowed($this->_options['root'], 'read')) {
			exit(json_encode(array('error' => 'Access denied')));
		}
		
		$cmd = '';
		if (!empty($_POST['cmd'])) {
			$cmd = trim($_POST['cmd']);
		} elseif (!empty($_GET['cmd'])) {
			$cmd = trim($_GET['cmd']);
		}
		if (!$cmd && $_SERVER["REQUEST_METHOD"] == 'POST') {
			header("Content-Type: text/html");
			$this->_result['error'] = 'Data exceeds the maximum allowed size';
			exit(json_encode($this->_result));
		}
		
		if ($cmd && (empty($this->_commands[$cmd]) || !method_exists($this, $this->_commands[$cmd]))) {
			exit(json_encode(array('error' => 'Unknown command')));
		}
		
		if (isset($_GET['init'])) {
			
			$ts = $this->_utime();
			$this->_result['disabled'] = array_values($this->_options['disabled']);
			
			$this->_result['params'] = array(
				'dotFiles'   => $this->_options['dotFiles'],
				'uplMaxSize' => ini_get('upload_max_filesize'),
				'archives'   => array(),
				'extract'    => array(),
				'url'        => $this->_options['fileURL'] ? $this->_options['URL'] : ''
				);
			if (isset($this->_commands['archive']) || isset($this->_commands['extract'])) {
				$this->_checkArchivers();
				if (isset($this->_commands['archive'])) {
					$this->_result['params']['archives'] = $this->_options['archiveMimes'];
				}
				if (isset($this->_commands['extract'])) {
					$this->_result['params']['extract'] = array_keys($this->_options['archivers']['extract']);
				}
			}
			// clean thumbnails dir
			if ($this->_options['tmbDir']) {
				srand((double) microtime() * 1000000);
				if (rand(1, 200) <= $this->_options['tmbCleanProb']) {
					$ts2 = $this->_utime();
					$ls = scandir($this->_options['tmbDir']);
					for ($i=0, $s = count($ls); $i < $s; $i++) { 
						if ('.' != $ls[$i] && '..' != $ls[$i]) {
							@unlink($this->_options['tmbDir'].DIRECTORY_SEPARATOR.$ls[$i]);
						}
					}
				}
			}
			
		}
		
		if ($this->_options['debug']) {
			$this->_result['debug'] = array(
				'time'       => $this->_utime() - $this->_time,
				'mimeDetect' => $this->_options['mimeDetect'],
				'imgLib'     => $this->_options['imgLib']
				);
			if ($this->_options['dirSize']) {
				$this->_result['debug']['dirSize'] = true;
				$this->_result['debug']['du'] = @$this->_options['du'];
			}
		}

		if ($cmd) {
			$this->{$this->_commands[$cmd]}();
		} else {
			$this->_open();
		}

		header("Content-Type: ".($cmd == 'upload' ? 'text/html' : 'application/json'));
		header("Connection: close");
		echo json_encode($this->_result);
		
		if (!empty($this->_options['logger']) && in_array($cmd, $this->_loggedCommands)) {
			$this->_options['logger']->log($cmd, empty($this->_result['error']), $this->_logContext, !empty($this->_result['error']) ? $this->_result['error'] : '', !empty($this->_result['errorData']) ? $this->_result['errorData'] : array()); 
		}
		exit();
	}

	
	/************************************************************/
	/**                   elFinder commands                    **/
	/************************************************************/
	
	/**
	 * Return current dir content to client or output file content to browser
	 *
	 * @return void
	 **/
	protected function _open()
	{
		if (isset($_GET['current'])) { // read file
			if (empty($_GET['current']) 
			||  empty($_GET['target'])
			||  false == ($dir = $this->_findDir(trim($_GET['current'])))
			||  false == ($file = $this->_find(trim($_GET['target']), $dir))
			||  is_dir($file)
			) {
				header('HTTP/1.x 404 Not Found'); 
				exit('File not found');
			}
			if (!$this->_isAllowed($dir, 'read') || !$this->_isAllowed($file, 'read')) {
				header('HTTP/1.x 403 Access Denied'); 
				exit('Access denied');
			}
			
			if (filetype($file) == 'link') {
				$file = $this->_readlink($file);
				if (!$file || is_dir($file)) {
					header('HTTP/1.x 404 Not Found'); 
					exit('File not found');
				}
				if (!$this->_isAllowed(dirname($file), 'read') || !$this->_isAllowed($file, 'read')) {
					header('HTTP/1.x 403 Access Denied'); 
					exit('Access denied');
				}
			}
			
			$mime  = $this->_mimetype($file);
			$parts = explode('/', $mime);
			$disp  = $parts[0] == 'image' || $parts[0] == 'text' ? 'inline' : 'attachments';
			
			header("Content-Type: ".$mime);
			header("Content-Disposition: ".$disp."; filename=".basename($file));
			header("Content-Location: ".str_replace($this->_options['root'], '', $file));
			header('Content-Transfer-Encoding: binary');
			header("Content-Length: ".filesize($file));
			header("Connection: close");
			readfile($file);
			exit();
			
		} else { // enter directory
			$path = $this->_options['root'];
			if (!empty($_GET['target'])) {
				if (false == ($p = $this->_findDir(trim($_GET['target'])))) {
					if (!isset($_GET['init'])) {
						$this->_result['error'] = 'Invalid parameters';
					}
				} elseif (!$this->_isAllowed($p, 'read')) {
					if (!isset($_GET['init'])) {
						$this->_result['error'] = 'Access denied';
					}
				} else {
					$path = $p;
				}
			}
			$this->_content($path, isset($_GET['tree']));
		}
	}


	/**
	 * Rename file/folder
	 *
	 * @return void
	 **/
	protected function _rename()
	{
		if (empty($_GET['current']) 
		||  empty($_GET['target'])
		||  false == ($dir = $this->_findDir(trim($_GET['current'])))
		||  false == ($target = $this->_find(trim($_GET['target']), $dir))
		) {
			$this->_result['error'] = 'File not found';
		} elseif (false == ($name = $this->_checkName($_GET['name'])) ) {
			$this->_result['error'] = 'Invalid name';
		} elseif (!$this->_isAllowed($dir, 'write')) {
			$this->_result['error'] = 'Access denied';
		} elseif (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
			$this->_result['error'] = 'File or folder with the same name already exists';
		} elseif (!rename($target, $dir.DIRECTORY_SEPARATOR.$name)) {
			$this->_result['error'] = 'Unable to rename file';
		} else {
			$this->_rmTmb($target);
			$this->_logContext['from'] = $target;
			$this->_logContext['to']   = $dir.DIRECTORY_SEPARATOR.$name;
			$this->_result['select']   = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
			$this->_content($dir, is_dir($dir.DIRECTORY_SEPARATOR.$name));
		}
	}
	
	
	/**
	 * Create new folder
	 *
	 * @return void
	 **/
	protected function _mkdir()
	{
		if (empty($_GET['current']) ||  false == ($dir = $this->_findDir(trim($_GET['current'])))) {
			return $this->_result['error'] = 'Invalid parameters';
		} 
		$this->_logContext['dir'] = $dir.DIRECTORY_SEPARATOR.$_GET['name'];
		if (!$this->_isAllowed($dir, 'write')) {
			$this->_result['error'] = 'Access denied';
		} elseif (false == ($name = $this->_checkName($_GET['name'])) ) {
			$this->_result['error'] = 'Invalid name';
		} elseif (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
			$this->_result['error'] = 'File or folder with the same name already exists';
		} elseif (!@mkdir($dir.DIRECTORY_SEPARATOR.$name, $this->_options['dirMode'])) {
			$this->_result['error'] = 'Unable to create folder';
		} else {
			$this->_logContext['dir'] = $dir.DIRECTORY_SEPARATOR.$name;
			$this->_result['select']  = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
			$this->_content($dir, true);
		}
	}
	
	/**
	 * Create new empty file
	 *
	 * @return void
	 **/
	protected function _mkfile()
	{
		if (empty($_GET['current']) 
		||  false == ($dir = $this->_findDir(trim($_GET['current'])))) {
			return $this->_result['error'] = 'Invalid parameters';
		} 
		$this->_logContext['file'] = $dir.DIRECTORY_SEPARATOR.$_GET['name'];
		if (!$this->_isAllowed($dir, 'write')) {
			$this->_result['error'] = 'Access denied';
		} elseif (false == ($name = $this->_checkName($_GET['name'])) ) {
			$this->_result['error'] = 'Invalid name';
		} elseif (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
			$this->_result['error'] = 'File or folder with the same name already exists';
		} else {
			$f = $dir.DIRECTORY_SEPARATOR.$name;
			$this->_logContext['file'] = $f;
			if (false != ($fp = @fopen($f, 'wb'))) {
				fwrite($fp, "");
				fclose($fp);
				$this->_result['select'] = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
				$this->_content($dir);
			} else {
				$this->_result['error'] = 'Unable to create file';
			}
		} 
	}
	
	/**
	 * Remove files/folders
	 *
	 * @return void
	 **/
	protected function _rm()
	{
		if (empty($_GET['current']) 
		||  false == ($dir = $this->_findDir(trim($_GET['current']))) 
		|| (empty($_GET['targets']) || !is_array($_GET['targets']))) {
			return $this->_result['error'] = 'Invalid parameters';
		} 

		$this->_logContext['targets'] = array();
		foreach ($_GET['targets'] as $hash) {
			if (false != ($f = $this->_find($hash, $dir))) {
				$this->_remove($f);
				$this->_logContext['targets'][] = $f;
			}
		}
		if (!empty($this->_result['errorData'])) {
			$this->_result['error'] = 'Unable to remove file';
		}
		$this->_content($dir, true);
	}
	
	/**
	 * Upload files
	 *
	 * @return void
	 **/
	protected function _upload()
	{

		if (empty($_POST['current']) 
		|| false == ($dir = $this->_findDir(trim($_POST['current'])))) {
			return $this->_result['error'] = 'Invalid parameters';
		} 
		if (!$this->_isAllowed($dir, 'write')) {
			return $this->_result['error'] = 'Access denied';
		}
		if (empty($_FILES['upload']))
		{
			return $this->_result['error'] = 'No file to upload';
		}
		
		$this->_logContext['upload'] = array();
		$this->_result['select'] = array();
		$total = 0;
		for ($i=0, $s = count($_FILES['upload']['name']); $i < $s; $i++) { 
			if (!empty($_FILES['upload']['name'][$i])) {
				$total++;
				$this->_logContext['upload'][] = $_FILES['upload']['name'][$i];
				if ($_FILES['upload']['error'][$i] > 0) {
					$error = 'Unable to upload file';
					switch ($_FILES['upload']['error'][$i]) {
						case UPLOAD_ERR_INI_SIZE:
						case UPLOAD_ERR_FORM_SIZE:
							$error = 'File exceeds the maximum allowed filesize';
							break;
						case UPLOAD_ERR_EXTENSION:
							$error = 'Not allowed file type';
							break;
					}
					$this->_errorData($_FILES['upload']['name'][$i], $error);
				} elseif (false == ($name = $this->_checkName($_FILES['upload']['name'][$i]))) {
					$this->_errorData($_FILES['upload']['name'][$i], 'Invalid name');
				} elseif (!$this->_isUploadAllow($_FILES['upload']['name'][$i], $_FILES['upload']['tmp_name'][$i])) {
					$this->_errorData($_FILES['upload']['name'][$i], 'Not allowed file type');					
				} else {
					$name = $this->_checkName($_FILES['upload']['name'][$i]);
					$file = $dir.DIRECTORY_SEPARATOR.$name;
					if (!@move_uploaded_file($_FILES['upload']['tmp_name'][$i], $file)) {
						$this->_errorData($_FILES['upload']['name'][$i], 'Unable to save uploaded file');	
					} else {
						@chmod($file, $this->_options['fileMode']);
						$this->_result['select'][] = $this->_hash($file);
					}
				}
			}
		}
		
		$errCnt = !empty($this->_result['errorData']) ? count($this->_result['errorData']) : 0;
		
		if ($errCnt == $total) {
			$this->_result['error'] = 'Unable to upload files';
		} else {
			if ($errCnt>0) {
				$this->_result['error'] = 'Some files was not uploaded';	
			}
			$this->_content($dir);
		}
		
	}
	
	/**
	 * Copy/move files/folders
	 *
	 * @return void
	 **/
	protected function _paste()
	{
		if (empty($_GET['current']) 
		|| false == ($current = $this->_findDir(trim($_GET['current'])))
		|| empty($_GET['src'])
		|| false == ($src = $this->_findDir(trim($_GET['src'])))
		|| empty($_GET['dst'])
		|| false == ($dst = $this->_findDir(trim($_GET['dst'])))
		|| empty($_GET['targets']) || !is_array($_GET['targets'])
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		$cut = !empty($_GET['cut']);
		$this->_logContext['src']  = array();
		$this->_logContext['dest'] = $dst;
		$this->_logContext['cut']  = $cut;
		
		
		if (!$this->_isAllowed($dst, 'write') || !$this->_isAllowed($src, 'read')) {
			return $this->_result['error'] = 'Access denied';
		}

		foreach ($_GET['targets'] as $hash) {
			if (false == ($f = $this->_find($hash, $src))) {
				return $this->_result['error'] = 'File not found' && $this->_content($current, true);
			}
			$this->_logContext['src'][] = $f;
			$_dst = $dst.DIRECTORY_SEPARATOR.basename($f);

			if (0 === strpos($dst, $f)) {
				return $this->_result['error'] = 'Unable to copy into itself' && $this->_content($current, true);
			} elseif (file_exists($_dst)) {
				return $this->_result['error'] = 'File or folder with the same name already exists' && $this->_content($current, true);
			} elseif ($cut && !$this->_isAllowed($f, 'rm')) {
				return $this->_result['error'] = 'Access denied' && $this->_content($current, true);
			}

			if ($cut) {
				if (!@rename($f, $_dst)) {
					return $this->_result['error'] = 'Unable to move files' && $this->_content($current, true);
				} elseif (!is_dir($f)) {
					$this->_rmTmb($f);
				}
			} elseif (!$this->_copy($f, $_dst)) {
				return $this->_result['error'] = 'Unable to copy files' && $this->_content($current, true);
			}
		}
		$this->_content($current, true);
	}
	
	/**
	 * Create file/folder copy with suffix - "copy"
	 *
	 * @return void
	 **/
	protected function _duplicate()
	{
		if (empty($_GET['current']) 
		|| false == ($current = $this->_findDir(trim($_GET['current'])))
		|| empty($_GET['target'])
		|| false == ($target = $this->_find(trim($_GET['target']), $current))
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		$this->_logContext['target'] = $target;
		if (!$this->_isAllowed($current, 'write') || !$this->_isAllowed($target, 'read')) {
			return $this->_result['error'] = 'Access denied';
		}
		$dup = $this->_uniqueName($target);
		if (!$this->_copy($target, $dup)) {
			return $this->_result['error'] = 'Unable to create file copy';
		}
		$this->_result['select'] = array($this->_hash($dup));
		$this->_content($current, is_dir($target));
	}
	
	/**
	 * Resize image
	 *
	 * @return void
	 **/
	protected function _resize()
	{
		if (empty($_GET['current']) 
		|| false == ($current = $this->_findDir(trim($_GET['current'])))
		|| empty($_GET['target'])
		|| false == ($target = $this->_find(trim($_GET['target']), $current))
		|| empty($_GET['width'])  || 0 >= ($width  = intval($_GET['width']))
		|| empty($_GET['height']) || 0 >= ($height = intval($_GET['height']))
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		$this->_logContext = array(
			'target' => $target,
			'width'  => $width,
			'height' => $height
			);
		if (!$this->_isAllowed($target, 'write')) {
			return $this->_result['error'] = 'Access denied';
		} 
		if (0 !== strpos($this->_mimetype($target), 'image')) {
			return $this->_result['error'] = 'File is not an image';
		}
		if (!$this->_resizeImg($target, $width, $height)) {
			return $this->_result['error'] = 'Unable to resize image';
		} 
		$this->_result['select'] = array($this->_hash($target));
		$this->_content($current);
	}
		
	/**
	 * Create images thumbnails 
	 *
	 * @return void
	 **/
	protected function _thumbnails()
	{
		if (!empty($this->_options['tmbDir']) && !empty($_GET['current']) && false != ($current = $this->_findDir(trim($_GET['current'])))) {
			$this->_result['current'] = $this->_hash($current);
			$this->_result['images'] = array();
			$ls = scandir($current);
			$cnt = 0;
			$max = $this->_options['tmbAtOnce'] > 0 ? intval($this->_options['tmbAtOnce']) : 5;
			for ($i=0; $i < count($ls); $i++) { 
				if ($this->_isAccepted($ls[$i])) {
					$path = $current.DIRECTORY_SEPARATOR.$ls[$i];
					if (is_readable($path) && $this->_canCreateTmb($this->_mimetype($path))) {
						$tmb = $this->_tmbPath($path);
						if (!file_exists($tmb)) {
							if ($cnt>=$max) {
								return $this->_result['tmb'] = true; 
							} elseif ($this->_tmb($path, $tmb)) {
								$this->_result['images'][$this->_hash($path)] = $this->_path2url($tmb);
								$cnt++;
							}
						}
					}
				}
			}
		} 
	}
	
	/**
	 * Return file content to client
	 *
	 * @return void
	 **/
	protected function _fread()
	{
		if (empty($_GET['current']) 
		|| false == ($current = $this->_findDir(trim($_GET['current'])))
		|| empty($_GET['target'])
		|| false == ($target = $this->_find(trim($_GET['target']), $current))
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		if (!$this->_isAllowed($target, 'read')) {
			return $this->_result['error'] = 'Access denied';
		}
		$this->_result['content'] = @file_get_contents($target);
	}
	
	/**
	 * Save data into text file. 
	 *
	 * @return void
	 **/
	protected function _edit()
	{
		if (empty($_POST['current']) 
		|| false == ($current = $this->_findDir(trim($_POST['current'])))
		|| empty($_POST['target'])
		|| false == ($target = $this->_find(trim($_POST['target']), $current))
		|| !isset($_POST['content'])
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		$this->_logContext['target'] = $target;
		if (!$this->_isAllowed($target, 'write')) {
			return $this->_result['error'] = 'Access denied';
		}
		if (false === file_put_contents($target, trim($_POST['content']))) {
			return $this->_result['error'] = 'Unable to write to file';
		}
		$this->_result['target'] = $this->_info($target);
		// $this->_result['select'] = array($this->_hash($target));
	}
	
	/**
	 * Create archive of selected type
	 *
	 * @return void
	 **/
	protected function _archive()
	{
		$this->_checkArchivers();
		if (empty($this->_options['archivers']['create']) 
		|| empty($_GET['type']) 
		|| empty($this->_options['archivers']['create'][$_GET['type']])
		|| !in_array($_GET['type'], $this->_options['archiveMimes'])) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		
		if (empty($_GET['current']) 
		||  empty($_GET['targets'])
		|| !is_array($_GET['targets'])
		||  false == ($dir = $this->_findDir(trim($_GET['current'])))
		|| !$this->_isAllowed($dir, 'write')
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		
		$files = array();
		$argc  = '';
		foreach ($_GET['targets'] as $hash) {
			if (false == ($f = $this->_find($hash, $dir))) {
				return $this->_result['error'] = 'File not found';
			}
			$files[] = $f;
			$argc .= escapeshellarg(basename($f)).' ';
		}
		$arc  = $this->_options['archivers']['create'][$_GET['type']];
		$name = count($files) == 1 ? basename($files[0]) : $_GET['name'];
		$name = basename($this->_uniqueName($name.'.'.$arc['ext'], ''));
		
		$cwd = getcwd();
		chdir($dir);
		$cmd = $arc['cmd'].' '.$arc['argc'].' '.escapeshellarg($name).' '.$argc;
		exec($cmd, $o, $c);
		chdir($cwd);
		if (file_exists($dir.DIRECTORY_SEPARATOR.$name)) {
			$this->_content($dir);
			$this->_result['select'] = array($this->_hash($dir.DIRECTORY_SEPARATOR.$name));
		} else {
			$this->_result['error'] = 'Unable to create archive';
		}
	}
	
	/**
	 * Extract files from archive
	 *
	 * @return void
	 **/
	protected function _extract()
	{
		if (empty($_GET['current']) 
		|| false == ($current = $this->_findDir(trim($_GET['current'])))
		|| empty($_GET['target'])
		|| false == ($file = $this->_find(trim($_GET['target']), $current))
		|| !$this->_isAllowed($current, 'write')
		) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		$this->_checkArchivers();
		$mime = $this->_mimetype($file);
		if (empty($this->_options['archivers']['extract'][$mime])) {
			return $this->_result['error'] = 'Invalid parameters';
		}
		$cwd = getcwd();
		$arc = $this->_options['archivers']['extract'][$mime];
		$cmd = $arc['cmd'].' '.$arc['argc'].' '.escapeshellarg(basename($file));
		chdir(dirname($file));
		exec($cmd, $o, $c);
		chdir($cwd);
		if ($c == 0) {
			$this->_content($current, true);
		} else {
			$this->_result['error'] = 'Unable to extract files from archive';
		}
	}
	
	
	/**
	 * Send header Connection: close. Required by safari to fix bug http://www.webmasterworld.com/macintosh_webmaster/3300569.htm
	 *
	 * @return void
	 **/
	protected function _ping()
	{
		exit(header("Connection: close"));
	}
	/************************************************************/
	/**                    "content" methods                   **/
	/************************************************************/
	/**
	 * Set current dir info, content and [dirs tree]
	 *
	 * @param  string  $path  current dir path
	 * @param  bool    $tree  set dirs tree?
	 * @return void
	 **/
	protected function _content($path, $tree=false)
	{
		$this->_cwd($path);
		$this->_cdc($path);
		if ($tree) {
			$this->_result['tree'] = $this->_tree($this->_options['root']);
		}
	}

	/**
	 * Set current dir info
	 *
	 * @param  string  $path  current dir path
	 * @return void
	 **/
	protected function _cwd($path)
	{
		$rel  = $this->_options['rootAlias'] ? $this->_options['rootAlias'] : basename($this->_options['root']);
		if ($path == $this->_options['root']) {
			$name = $rel;
		} else {
			$name = basename($path);
			$rel .= DIRECTORY_SEPARATOR.substr($path, strlen($this->_options['root'])+1);
		}
		$this->_result['cwd'] = array(
			'hash'       => $this->_hash($path),
			'name'       => $name,
			'mime'       => 'directory',
			'rel'        => $rel,
			'size'       => 0,
			'date'       => date($this->_options['dateFormat'], filemtime($path)),
			'read'       => true,
			'write'      => $this->_isAllowed($path, 'write'),
			'rm'         => $path == $this->_options['root'] ? false : $this->_isAllowed($path, 'rm')
			);
	}

	
	/**
	 * Set current dir content
	 *
	 * @param  string  $path  current dir path
	 * @return void
	 **/
	protected function _cdc($path)
	{
		$dirs = $files = array();
		$ls = scandir($path);
		for ($i=0; $i < count($ls); $i++) { 
			if ($this->_isAccepted($ls[$i])) {
				$info = $this->_info($path.DIRECTORY_SEPARATOR.$ls[$i]);
				if ($info['mime'] == 'directory') {
					$dirs[] = $info;
				} else {
					$files[] = $info;
				}
			}
		}
		$this->_result['cdc'] = array_merge($dirs, $files);
	}
	
	/**
	 * Return file/folder info
	 *
	 * @param  string  $path  file path
	 * @return array
	 **/
	protected function _info($path)
	{
		$type = filetype($path);
		$stat =  $type == 'link' ? lstat($path) : stat($path);

		if ($stat['mtime'] > $this->_today) {
			$d = 'Today '.date('H:i', $stat['mtime']);
		} elseif ($stat['mtime'] > $this->_yesterday) {
			$d = 'Yesterday '.date('H:i', $stat['mtime']);
		} else {
			$d = date($this->_options['dateFormat'], $stat['mtime']);
		}

		$info = array(
			'name'  => htmlspecialchars(basename($path)),
			'hash'  => $this->_hash($path),
			'mime'  => $type == 'dir' ? 'directory' : $this->_mimetype($path),
			'date'  => $d, 
			'size'  => $type == 'dir' ? $this->_dirSize($path) : $stat['size'],
			'read'  => $this->_isAllowed($path, 'read'),
			'write' => $this->_isAllowed($path, 'write'),
			'rm'    => $this->_isAllowed($path, 'rm'),
			);

		if ($type == 'link') {
			if (false == ($lpath = $this->_readlink($path))) {
				$info['mime'] = 'symlink-broken';
				return $info;
			}
			if (is_dir($lpath)) {
				$info['mime']  = 'directory';
			} else {
				$info['parent'] = $this->_hash(dirname($lpath));
				$info['mime']   = $this->_mimetype($lpath);
			}
			$info['link']   = $this->_hash($lpath);
			$info['linkTo'] = ($this->_options['rootAlias'] ? $this->_options['rootAlias'] : basename($this->_options['root'])).substr($lpath, strlen($this->_options['root']));
			$info['read']   = $this->_isAllowed($lpath, 'read');
			$info['write']  = $this->_isAllowed($lpath, 'write');
			$info['rm']     = $this->_isAllowed($lpath, 'rm');
		} else {
			$lpath = '';
		}
		
		if ($info['mime'] != 'directory') {
			if ($this->_options['fileURL'] && $info['read']) {
				$info['url'] = $this->_path2url($lpath ? $lpath : $path);
			}
			
			if (0 === ($p = strpos($info['mime'], 'image'))) {
				if (false != ($s = getimagesize($path))) {
					$info['dim'] = $s[0].'x'.$s[1];
				}
				if ($info['read']) {
					$info['resize'] = isset($info['dim']) && $this->_canCreateTmb($info['mime']);
					$tmb = $this->_tmbPath($path);

					if (file_exists($tmb)) {
						$info['tmb']  = $this->_path2url($tmb);
					} elseif ($info['resize']) {
						$this->_result['tmb'] = true;
					}
					
				}
			}
		}
		return $info;
	}
	
	/**
	 * Return directory tree (multidimensional array)
	 *
	 * @param  string  $path  directory path
	 * @return array
	 **/
	protected function _tree($path)
	{
		$dir = array(
			'hash'  => $this->_hash($path),
			'name'  => $path == $this->_options['root'] && $this->_options['rootAlias'] ? $this->_options['rootAlias'] : basename($path),
			'read'  => $this->_isAllowed($path, 'read'),
			'write' => $this->_isAllowed($path, 'write'),
			'dirs'  => array()
			);

		if ($dir['read'] && (false != ($ls = scandir($path)))) {
			for ($i=0; $i < count($ls); $i++) {
				$p = $path.DIRECTORY_SEPARATOR.$ls[$i]; 
				if ($this->_isAccepted($ls[$i]) && is_dir($p) && !is_link($p)) {
					$dir['dirs'][] = $this->_tree($p);
				}
			}
		}
		return $dir;
	}
	
	/************************************************************/
	/**                      fs methods                        **/
	/************************************************************/

	/**
	 * Return name for duplicated file/folder or new archive
	 *
	 * @param  string  $f       file/folder name
	 * @param  string  $suffix  file name suffix
	 * @return string
	 **/
	protected function _uniqueName($f, $suffix=' copy')
	{
		$dir  = dirname($f);
		$name = basename($f);
		$ext = '';

		if (!is_dir($f)) {
			if (preg_match('/\.(tar\.gz|tar\.bz|tar\.bz2|[a-z0-9]{1,4})$/i', $name, $m)) {
				$ext = '.'.$m[1];
				$name = substr($name, 0,  strlen($name)-strlen($m[0]));
			}
		}
		
		if (preg_match('/('.$suffix.')(\d*)$/i', $name, $m)) {
			$i = (int)$m[2];
			$name = substr($name, 0, strlen($name)-strlen($m[2]));
		} else {
			$name .= $suffix;
			$i = 0;
			$n = $dir.DIRECTORY_SEPARATOR.$name.$ext;
			if (!file_exists($n)) {
				return $n;
			}
		}
		
		while ($i++ <= 10000) {
			$n = $dir.DIRECTORY_SEPARATOR.$name.$i.$ext;
			if (!file_exists($n)) {
				return $n;
			}
		}
		return $dir.DIRECTORY_SEPARATOR.$name.md5($f).$ext;
	}

	/**
	 * Remove file or folder (recursively)
	 *
	 * @param  string  $path  fole/folder path
	 * @return void
	 **/
	protected function _remove($path)
	{
		if (!$this->_isAllowed($path, 'rm')) {
			return $this->_errorData($path, 'Access denied');
		}
		if (!is_dir($path)) {
			if (!@unlink($path)) {
				$this->_errorData($path, 'Unable to remove file');
			} else {
				$this->_rmTmb($path);
			}
		} else {
			$ls = scandir($path);
			for ($i=0; $i < count($ls); $i++) { 
				if ('.' != $ls[$i] && '..' != $ls[$i]) {
					$this->_remove($path.DIRECTORY_SEPARATOR.$ls[$i]);
				}
			}
			if (!@rmdir($path)) {
				return $this->_errorData($path, 'Unable to remove file');
			}
		}
		return true;
	}
	
	/**
	 * Copy file/folder (recursively)
	 *
	 * @param  string  $src  file/folder to copy
	 * @param  string  $trg  destination name
	 * @return bool
	 **/
	protected function _copy($src, $trg)
	{
		if (!$this->_isAllowed($src, 'read')) {
			return $this->_errorData($src, 'Access denied');
		}
		
		$dir = dirname($trg);
		
		if (!$this->_isAllowed($dir, 'write')) {
			return $this->_errorData($dir, 'Access denied');
		}
		if (file_exists($trg)) {
			return $this->_errorData($src, 'File or folder with the same name already exists');
		}
		
		if (!is_dir($src)) {
			if (!@copy($src, $trg)) {
				return $this->_errorData($src, 'Unable to copy files');
			} 
			@chmod($trg, $this->_options['fileMode']);
		} else {
			
			if (!@mkdir($trg, $this->_options['dirMode'])) {
				return $this->_errorData($src, 'Unable to copy files');
			}
			
			$ls = scandir($src);
			for ($i=0; $i < count($ls); $i++) { 
				if ('.' != $ls[$i] && '..' != $ls[$i]) {
					$_src = $src.DIRECTORY_SEPARATOR.$ls[$i];
					$_trg = $trg.DIRECTORY_SEPARATOR.$ls[$i];
					if (is_dir($_src)) {
						if (!$this->_copy($_src, $_trg)) {
							return $this->_errorData($_src, 'Unable to copy files');
						}
					} else {
						if (!@copy($_src, $_trg)) {
							return $this->_errorData($_src, 'Unable to copy files');
						}
						@chmod($_trg, $this->_options['fileMode']);
					}
				}
			}
		}
		return true;
	}
	
	/**
	 * Check new file name for invalid simbols. Return name if valid
	 *
	 * @return string  $n  file name
	 * @return string
	 **/
	protected function _checkName($n)
	{
		$n = strip_tags(trim($n));
		if (!$this->_options['dotFiles'] && '.' == substr($n, 0, 1)) {
			return false;
		}
		return preg_match('|^[^\\/\<\>:]+$|', $n) ? $n : false;
	}
	
	/**
	 * Find folder by hash in required folder and subfolders
	 *
	 * @param  string  $hash  folder hash
	 * @param  string  $path  folder path to search in
	 * @return string
	 **/
	protected function _findDir($hash, $path='')
	{
		if (!$path) {
			$path = $this->_options['root'];
			if ($this->_hash($path) == $hash) {
				return $path;
			}
		}
		
		if (false != ($ls = scandir($path))) {
			for ($i=0; $i < count($ls); $i++) { 
				$p = $path.DIRECTORY_SEPARATOR.$ls[$i];
				if (is_link($p))
				{
					$link = $this->_readlink($p);
					//$this->_result['debug']['findDir_'.$p] = 'link to '.$link;
				}
				if ($this->_isAccepted($ls[$i]) && is_dir($p) && (!is_link($p))) {
					if ($this->_hash($p) == $hash || false != ($p = $this->_findDir($hash, $p))) {
						return $p;
					}
				}
			}
		}
	}
	
	/**
	 * Find file/folder by hash in required folder
	 *
	 * @param  string  $hash  file/folder hash
	 * @param  string  $path  folder path to search in
	 **/
	protected function _find($hash, $path)
	{
		if (false != ($ls = scandir($path))) {
			for ($i=0; $i < count($ls); $i++) { 
				if ($this->_isAccepted($ls[$i])) {
					$p = $path.DIRECTORY_SEPARATOR.$ls[$i];
					if ($this->_hash($p) == $hash) {
						return $p;
					}
				}
			}
		}
	}
	
	
	/**
	 * Return path of file on which link point to, if exists in root directory
	 *
	 * @param  string  $path  symlink path
	 * @return string
	 **/
	protected function _readlink($path)
	{
		$target = readlink($path);
		if ('/' != substr($target, 0, 1)) {
			$target = dirname($path).DIRECTORY_SEPARATOR.$target;
		}
		$target = $this->_normpath($target);
		$root   = $this->_normpath($this->_options['root']);
		return $target && file_exists($target) && 0 === strpos($target, $root) ? $target : false;
	}

	/**
	 * Count total directory size if this allowed in options
	 *
	 * @param  string  $path  directory path
	 * @return int
	 **/
	protected function _dirSize($path)
	{
		$size = 0;
		if (!$this->_options['dirSize'] || !$this->_isAllowed($path, 'read')) {
			return filesize($path);
		} 
		if (!isset($this->_options['du'])) {
			$this->_options['du'] = function_exists('exec')
				? exec('du -h '.escapeshellarg(__FILE__), $o, $s) > 0 && $s == 0
				: false;
		}
		if ($this->_options['du']) {
			$size = intval(exec('du -k '.escapeshellarg($path)))*1024;
		} else {
			$ls = scandir($path);
			for ($i=0; $i < count($ls); $i++) { 
				if ($this->_isAccepted($ls[$i])) {
					$p = $path.DIRECTORY_SEPARATOR.$ls[$i];
					$size += filetype($p) == 'dir' && $this->_isAllowed($p, 'read') ? $this->_dirSize($p) : filesize($p);
				}
			}
		}
		return $size;
	}
	
	/**
	 * Return file mimetype
	 *
	 * @param  string  $path  file path
	 * @return string
	 **/
	protected function _mimetype($path)
	{
		if (empty($this->_options['mimeDetect']) || $this->_options['mimeDetect'] == 'auto') {
			$this->_options['mimeDetect'] = $this->_getMimeDetect();
		}
		
		switch ($this->_options['mimeDetect']) {
			case 'finfo':
				if (empty($this->_finfo)) {
					$this->_finfo = finfo_open(FILEINFO_MIME);
				}
				$type = @finfo_file($this->_finfo, $path);
				break;
			case 'php':   
			 	$type = mime_content_type($path);
				break;
			case 'linux':  
				$type = exec('file -ib '.escapeshellarg($path));
				break;
			case 'bsd':   
				$type = exec('file -Ib '.escapeshellarg($path));
				break;
			default:
				$pinfo = pathinfo($path); 
				$ext = isset($pinfo['extension']) ? strtolower($pinfo['extension']) : '';
				$type = isset($this->_mimeTypes[$ext]) ? $this->_mimeTypes[$ext] : 'unknown;';
		}
		$type = explode(';', $type); 
		
		if ($this->_options['mimeDetect'] != 'internal' && $type[0] == 'application/octet-stream') {
			$pinfo = pathinfo($path); 
			$ext = isset($pinfo['extension']) ? strtolower($pinfo['extension']) : '';
			if (!empty($ext) && !empty($this->_mimeTypes[$ext])) {
				$type[0] = $this->_mimeTypes[$ext];
			}
		}
		
		return $type[0];
	}
	
	/************************************************************/
	/**                   image manipulation                   **/
	/************************************************************/

	/**
	 * Create image thumbnail
	 *
	 * @param  string  $img  image file
	 * @param  string  $tmb  thumbnail name
	 * @return bool
	 **/
	protected function _tmb($img, $tmb)
	{
		if (false == ($s = getimagesize($img))) {
			return false;
		}
		$tmbSize = $this->_options['tmbSize'];

		if ($this->_options['tmbCrop'] == false) {

			/* Calculating image scale width and height */
			$xscale = $s[0] / $tmbSize;
			$yscale = $s[1] / $tmbSize;

			if ($yscale > $xscale) {
				$newwidth = round($s[0] * (1 / $yscale));
				$newheight = round($s[1] * (1 / $yscale));
			} else {
				$newwidth = round($s[0] * (1 / $xscale));
				$newheight = round($s[1] * (1 / $xscale));
			}

			/* Keeping original dimensions if image fitting into thumbnail without scale */
			if ($s[0] <= $tmbSize && $s[1] <= $tmbSize) {
				$newwidth = $s[0];
				$newheight = $s[1];
			}

			/* Calculating coordinates for aligning thumbnail */
			$align_y = ceil(($tmbSize - $newheight) / 2); 
			$align_x = ceil(($tmbSize - $newwidth) / 2);
		}
    
    
    
		switch ($this->_options['imgLib']) {
			case 'imagick':
				try {
					$_img = new imagick($img);
				} catch (Exception $e) {
					return false;
				}

				$_img->contrastImage(1);

				if ($this->_options['tmbCrop'] == false) {
					$img1 = new Imagick();
					$img1->newImage($tmbSize, $tmbSize, new ImagickPixel($this->_options['tmbBgColor']));
					$img1->setImageFormat('png');
					$_img->resizeImage($newwidth, $newheight, NULL, true);
					$img1->compositeImage( $_img, imagick::COMPOSITE_OVER, $align_x, $align_y );
					return $img1->writeImage($tmb);
				} else {
					return $_img->cropThumbnailImage($tmbSize, $tmbSize) && $_img->writeImage($tmb);
				}
				break;

			case 'mogrify':
				if (@copy($img, $tmb)) {
					list($x, $y, $size) = $this->_cropPos($s[0], $s[1]);
					// exec('mogrify -crop '.$size.'x'.$size.'+'.$x.'+'.$y.' -scale '.$tmbSize.'x'.$tmbSize.'! '.escapeshellarg($tmb), $o, $c);

					$mogrifyArgs = 'mogrify -resize ' . $tmbSize . 'x' . $tmbSize;

					if ($this->_options['tmbCrop'] == false) {
						$mogrifyArgs .= ' -gravity center -background "' . $this->_options['tmbBgColor'] . '" -extent ' . $tmbSize . 'x' . $tmbSize;
					}

					if ($this->_options['tmbCrop'] == false) {
						$mogrifyArgs .= ' ' . escapeshellarg($tmb);
					}

					exec($mogrifyArgs, $o, $c);

					if (file_exists($tmb)) {
						return true;
					} elseif ($c == 0) {
						// find tmb for psd and animated gif
						$mime = $this->_mimetype($img);
						if ($mime == 'image/vnd.adobe.photoshop' || $mime = 'image/gif') {
							$pinfo = pathinfo($tmb);
							$test = $pinfo['dirname'].DIRECTORY_SEPARATOR.$pinfo['filename'].'-0.'.$pinfo['extension'];
							if (file_exists($test)) {
								return rename($test, $tmb);
							}
						}
					}
				}
				break;
			
			case 'gd':
				if ($s['mime'] == 'image/jpeg') {
					$_img = imagecreatefromjpeg($img);
				} elseif ($s['mime'] == 'image/png') {
					$_img = imagecreatefrompng($img);
				} elseif ($s['mime'] == 'image/gif') {
					$_img = imagecreatefromgif($img);
				} 
				if (!$_img || false == ($_tmb = imagecreatetruecolor($tmbSize, $tmbSize))) {
					return false;
				}

				if ($this->_options['tmbCrop'] == false) {

					list($r,$g,$b) = sscanf($this->_options['tmbBgColor'], "#%02x%02x%02x");

					imagefill($_tmb, 0, 0, imagecolorallocate($_tmb, $r, $g, $b));

					if (!imagecopyresampled($_tmb, $_img, $align_x, $align_y, 0, 0, $newwidth, $newheight, $s[0], $s[1])) {
						return false;
					}
        
				} else {
					list($x, $y, $size) = $this->_cropPos($s[0], $s[1]);
					if (!imagecopyresampled($_tmb, $_img, 0, 0, $x, $y, $tmbSize, $tmbSize, $size, $size)) {
						return false;
					}
				}

				$r = imagepng($_tmb, $tmb, 7);
				imagedestroy($_img);
				imagedestroy($_tmb);
				return $r;
				break;
		}
	}
	
	/**
	 * Remove image thumbnail
	 *
	 * @param  string  $img  image file
	 * @return void
	 **/
	protected function _rmTmb($img)
	{
		if ($this->_options['tmbDir'] && false != ($tmb = $this->_tmbPath($img)) && file_exists($tmb)) {
			@unlink($tmb);
		}
	}
	
	/**
	 * Return x/y coord for crop image thumbnail
	 *
	 * @param  int  $w  image width
	 * @param  int  $h  image height	
	 * @return array
	 **/
	protected function _cropPos($w, $h)
	{
		$x = $y = 0;
		$size = min($w, $h);
		if ($w > $h) {
			$x = ceil(($w - $h)/2);
		} else {
			$y = ceil(($h - $w)/2);
		}
		return array($x, $y, $size);
	}
	
	/**
	 * Resize image
	 *
	 * @param  string  $img  image path
	 * @param  int     $w    image width
	 * @param  int     $h    image height
	 * @return bool
	 **/
	protected function _resizeImg($img, $w, $h)
	{
		if (false == ($s = getimagesize($img))) {
			return false;
		}
		
		switch ($this->_options['imgLib']) {
			case 'imagick':
				if (false != ($_img = new imagick($img))) {
					return $_img->cropThumbnailImage($w, $h) && $_img->writeImage($img);
				}
				break;
			case 'mogrify':
				exec('mogrify -scale '.$w.'x'.$h.'! '.escapeshellarg($img), $o, $c);
				return 0 == $c;
				break;
			case 'gd':
				if ($s['mime'] == 'image/jpeg') {
					$_img = imagecreatefromjpeg($img);
				} elseif ($s['mime'] = 'image/png') {
					$_img = imagecreatefrompng($img);
				} elseif ($s['mime'] = 'image/gif') {
					$_img = imagecreatefromgif($img);
				} 
				if (!$_img || false == ($_out = imagecreatetruecolor($w, $h))) {
					return false;
				}
				if (!imagecopyresampled($_out, $_img, 0, 0, 0, 0, $w, $h, $s[0], $s[1])) {
					return false;
				}
				if ($s['mime'] == 'image/jpeg') {
					$r = imagejpeg($_out, $img, 100);
				} else if ($s['mime'] = 'image/png') {
					$r = imagepng($_out, $img, 7);
				} else {
					$r = imagegif($_out, $img, 7);
				}
				imagedestroy($_img);
				imagedestroy($_out);
				return $r;
				break;
		}
				
		
	}
	
	/**
	 * Return true if we can create thumbnail for file with this mimetype
	 *
	 * @param  string  $mime  file mimetype
	 * @return bool
	 **/
	protected function _canCreateTmb($mime)
	{
		if ($this->_options['tmbDir'] && $this->_options['imgLib'] && 0 === strpos($mime, 'image')) {
			if ('gd' == $this->_options['imgLib']) {
				return $mime == 'image/jpeg' || $mime == 'image/png' || $mime == 'image/gif';
			}
			return true;
		}
	}
	
	/**
	 * Return image thumbnail path. For thumbnail return itself 
	 *
	 * @param  string  $path  image path
	 * @return string
	 **/
	protected function _tmbPath($path)
	{
		$tmb = '';
		if ($this->_options['tmbDir']) {
			$tmb = dirname($path) != $this->_options['tmbDir']
				? $this->_options['tmbDir'].DIRECTORY_SEPARATOR.$this->_hash($path).'.png'
				: $path;
		}
		return $tmb;
	}
	
	/************************************************************/
	/**                       access control                   **/
	/************************************************************/
	
	/**
	 * Return true if file's mimetype is allowed for upload
	 *
	 * @param  string  $name    file name
	 * @param  string  $tmpName uploaded file tmp name
	 * @return bool
	 **/
	protected function _isUploadAllow($name, $tmpName)
	{
		$allow = false;
		$deny  = false;
		$mime  = $this->_mimetype($this->_options['mimeDetect'] != 'internal' ? $tmpName : $name);

		if (in_array('all', $this->_options['uploadAllow'])) {
			$allow = true;
		} else {
			foreach ($this->_options['uploadAllow'] as $type) {
				if (0 === strpos($mime, $type)) {
					$allow = true;
				}
			}
		}

		if (in_array('all', $this->_options['uploadDeny'])) {
			$deny = true;
		} else {
			foreach ($this->_options['uploadDeny'] as $type) {
				if (0 === strpos($mime, $type)) {
					$deny = true;
				}
			}
		}

		$this->_result['debug']['_isUploadAllow'][$name] = $mime;

		if (0 === strpos($this->_options['uploadOrder'], 'allow')) { // ,deny
			if ($deny == true) {
				return false;
			} elseif ($allow == true) {
				return true;
			} else {
				return false;
			}
		} else { // deny,allow
			if ($allow == true) {
				return true;
			} elseif ($deny == true) {
				return false;
			} else {
				return true;
			}
		}
	}

	/**
	 * Return true if file name is not . or ..
	 * If file name begins with . return value according to $this->_options['dotFiles']
	 *
	 * @param  string  $file  file name
	 * @return bool
	 **/
	protected function _isAccepted($file)
	{
		if ('.' == $file || '..' == $file) {
			return false;
		}
		if (!$this->_options['dotFiles'] && '.' == substr($file, 0, 1)) {
			return false;
		}
		return true;
	}

	/**
	 * Return true if requeired action allowed to file/folder
	 *
	 * @param  string  $path    file/folder path
	 * @param  string  $action  action name (read/write/rm)
	 * @return void
	 **/
	protected function _isAllowed($path, $action) {
		
		switch ($action) {
			case 'read':
				if (!is_readable($path)) {
					return false;
				}
				break;
			case 'write':
				if (!is_writable($path)) {
					return false;
				}
				break;
			case 'rm':
				if (!is_writable(dirname($path))) {
					return false;
				}
				break;
		}
		
		// if ($this->_options['aclObj']) {
		// 	
		// }
		$path = substr($path, strlen($this->_options['root'])+1);
		// echo "$path\n";
		foreach ($this->_options['perms'] as $regex => $rules) {
			
			if (preg_match($regex, $path)) {
				if (isset($rules[$action])) {
					return $rules[$action];
				}
			}
		}
		return isset($this->_options['defaults'][$action]) ? $this->_options['defaults'][$action] : false;
	}
	
	/************************************************************/
	/**                          utilites                      **/
	/************************************************************/
	
	/**
	 * Return image manipalation library name
	 *
	 * @return string
	 **/
	protected function _getImgLib()
	{
		if (extension_loaded('imagick')) {
			return 'imagick';
		} elseif (function_exists('exec')) {
			exec('mogrify --version', $o, $c);
			if ($c == 0) {
				return 'mogrify';
			}
		}
		return function_exists('gd_info') ? 'gd' : '';
	}
	
	/**
	 * Return list of available archivers
	 *
	 * @return array
	 **/
	protected function _checkArchivers()
	{
		if (!function_exists('exec')) {
			$this->_options['archivers'] = $this->_options['archive'] = array();
			return;
		}
		$arcs = array(
			'create'  => array(),
			'extract' => array()
			);
		
		exec('tar --version', $o, $ctar);
		if ($ctar == 0) {
			$arcs['create']['application/x-tar']  = array('cmd' => 'tar', 'argc' => '-cf', 'ext' => 'tar');
			$arcs['extract']['application/x-tar'] = array('cmd' => 'tar', 'argc' => '-xf', 'ext' => 'tar');
			$test = exec('gzip --version', $o, $c);
			if ($c == 0) {
				$arcs['create']['application/x-gzip']  = array('cmd' => 'tar', 'argc' => '-czf', 'ext' => 'tgz');
				$arcs['extract']['application/x-gzip'] = array('cmd' => 'tar', 'argc' => '-xzf', 'ext' => 'tgz');
			}
			$test = exec('bzip2 --version', $o, $c);
			if ($c == 0) {
				$arcs['create']['application/x-bzip2']  = array('cmd' => 'tar', 'argc' => '-cjf', 'ext' => 'tbz');
				$arcs['extract']['application/x-bzip2'] = array('cmd' => 'tar', 'argc' => '-xjf', 'ext' => 'tbz');
			}
		}
		
		exec('zip --version', $o, $c);
		if ($c == 0) {
			$arcs['create']['application/zip']  = array('cmd' => 'zip', 'argc' => '-r9', 'ext' => 'zip');
		}
		
		exec('unzip --help', $o, $c);
		if ($c == 0) {
			$arcs['extract']['application/zip'] = array('cmd' => 'unzip', 'argc' => '',  'ext' => 'zip');
		} 
		
		exec('rar --version', $o, $c);
		if ($c == 0 || $c == 7) {
			$arcs['create']['application/x-rar']  = array('cmd' => 'rar', 'argc' => 'a -inul', 'ext' => 'rar');
			$arcs['extract']['application/x-rar'] = array('cmd' => 'rar', 'argc' => 'x -y',    'ext' => 'rar');
		} else {
			$test = exec('unrar', $o, $c);
			if ($c==0 || $c == 7) {
				$arcs['extract']['application/x-rar'] = array('cmd' => 'unrar', 'argc' => 'x -y', 'ext' => 'rar');
			}
		}
		
		exec('7za --help', $o, $c);
		if ($c == 0) {
			$arcs['create']['application/x-7z-compressed']  = array('cmd' => '7za', 'argc' => 'a', 'ext' => '7z');
			$arcs['extract']['application/x-7z-compressed'] = array('cmd' => '7za', 'argc' => 'e -y', 'ext' => '7z');
			
			if (empty($arcs['create']['application/x-gzip'])) {
				$arcs['create']['application/x-gzip'] = array('cmd' => '7za', 'argc' => 'a -tgzip', 'ext' => 'tar.gz');
			}
			if (empty($arcs['extract']['application/x-gzip'])) {
				$arcs['extract']['application/x-gzip'] = array('cmd' => '7za', 'argc' => 'e -tgzip -y', 'ext' => 'tar.gz');
			}
			if (empty($arcs['create']['application/x-bzip2'])) {
				$arcs['create']['application/x-bzip2'] = array('cmd' => '7za', 'argc' => 'a -tbzip2', 'ext' => 'tar.bz');
			}
			if (empty($arcs['extract']['application/x-bzip2'])) {
				$arcs['extract']['application/x-bzip2'] = array('cmd' => '7za', 'argc' => 'a -tbzip2 -y', 'ext' => 'tar.bz');
			}
			if (empty($arcs['create']['application/zip'])) {
				$arcs['create']['application/zip'] = array('cmd' => '7za', 'argc' => 'a -tzip -l', 'ext' => 'zip');
			}
			if (empty($arcs['extract']['application/zip'])) {
				$arcs['extract']['application/zip'] = array('cmd' => '7za', 'argc' => 'e -tzip -y', 'ext' => 'zip');
			}
			if (empty($arcs['create']['application/x-tar'])) {
				$arcs['create']['application/x-tar'] = array('cmd' => '7za', 'argc' => 'a -ttar -l', 'ext' => 'tar');
			}
			if (empty($arcs['extract']['application/x-tar'])) {
				$arcs['extract']['application/x-tar'] = array('cmd' => '7za', 'argc' => 'e -ttar -y', 'ext' => 'tar');
			}
		}
		
		$this->_options['archivers'] = $arcs;
		foreach ($this->_options['archiveMimes'] as $k=>$mime) {
			if (!isset($this->_options['archivers']['create'][$mime])) {
				unset($this->_options['archiveMimes'][$k]);
			}
		}
		if (empty($this->_options['archiveMimes'])) {
			$this->_options['archiveMimes'] = array_keys($this->_options['archivers']['create']);
		}
	}
	
	
	/**
	 * Return mimetype detect method name
	 *
	 * @return string
	 **/
	protected function _getMimeDetect()
	{
		if (class_exists('finfo')) {
			return 'finfo';
		} elseif (function_exists('mime_content_type') && (mime_content_type(__FILE__) == 'text/x-php' || mime_content_type(__FILE__) == 'text/x-c++')) {
			return 'mime_content_type';
		} elseif (function_exists('exec')) {
			$type = exec('file -ib '.escapeshellarg(__FILE__));
			if (0 === strpos($type, 'text/x-php') || 0 === strpos($type, 'text/x-c++'))
			{
				return 'linux';
			}
			$type = exec('file -Ib '.escapeshellarg(__FILE__));
			if (0 === strpos($type, 'text/x-php') || 0 === strpos($type, 'text/x-c++'))
			{
				return 'bsd';
			}
		}
		return 'internal';
	}
	
	
	/**
	 * Return file path hash
	 *
	 * @param  string  $path 
	 * @return string
	 **/
	protected function _hash($path)
	{
		return md5($path);
	}
	
	/**
	 * Return file URL
	 *
	 * @param  string  $path 
	 * @return string
	 **/
	protected function _path2url($path)
	{
		$dir  = substr(dirname($path), strlen($this->_options['root'])+1);
		$file = rawurlencode(basename($path));
		return $this->_options['URL'].($dir ? str_replace(DIRECTORY_SEPARATOR, '/', $dir).'/' : '').$file;
	}

	/**
	 * Return normalized path, this works the same as os.path.normpath() in Python
	 *
	 * @param  string  $path  path
	 * @return string
	 **/
	protected function _normpath($path)
	{
		if (empty($path))
			return '.';

		if (strpos($path, '/') === 0)
			$initial_slashes = true;
		else
			$initial_slashes = false;
		if (
			($initial_slashes) &&
			(strpos($path, '//') === 0) &&
			(strpos($path, '///') === false)
		)
			$initial_slashes = 2;
		$initial_slashes = (int) $initial_slashes;

		$comps = explode('/', $path);
		$new_comps = array();
		foreach ($comps as $comp)
		{
			if (in_array($comp, array('', '.')))
				continue;
			if (
				($comp != '..') ||
				(!$initial_slashes && !$new_comps) ||
				($new_comps && (end($new_comps) == '..'))
			)
				array_push($new_comps, $comp);
			elseif ($new_comps)
				array_pop($new_comps);
		}
		$comps = $new_comps;
		$path = implode('/', $comps);
		if ($initial_slashes)
			$path = str_repeat('/', $initial_slashes) . $path;
		if ($path)
			return $path;
		else
			return '.';
	}

	/**
	 * Pack error message in $this->_result['errorData']
	 *
	 * @param string  $path  path to file
	 * @param string  $msg   error message
	 * @return bool always false
	 **/
	protected function _errorData($path, $msg)
	{
		$path = preg_replace('|^'.preg_quote($this->_options['root']).'|', $this->_fakeRoot, $path);
		if (!isset($this->_result['errorData'])) {
			$this->_result['errorData'] = array();
		}
		$this->_result['errorData'][$path] = $msg;
		return false;
	}	
	
	protected function _utime()
	{
		$time = explode(" ", microtime());
		return (double)$time[1] + (double)$time[0];
	}	
	
}
