File indexing completed on 2025-05-04 05:31:20

0001 <?php
0002 /**
0003  * Base class for elFinder volume.
0004  * Provide 2 layers:
0005  *  1. Public API (commands)
0006  *  2. abstract fs API
0007  *
0008  * All abstract methods begin with "_"
0009  *
0010  * @author Dmitry (dio) Levashov
0011  * @author Troex Nevelin
0012  * @author Alexey Sukhotin
0013  **/
0014 abstract class elFinderVolumeDriver {
0015   
0016   /**
0017    * Driver id
0018    * Must be started from letter and contains [a-z0-9]
0019    * Used as part of volume id
0020    *
0021    * @var string
0022    **/
0023   protected $driverId = 'a';
0024   
0025   /**
0026    * Volume id - used as prefix for files hashes
0027    *
0028    * @var string
0029    **/
0030   protected $id = '';
0031   
0032   /**
0033    * Flag - volume "mounted" and available
0034    *
0035    * @var bool
0036    **/
0037   protected $mounted = false;
0038   
0039   /**
0040    * Root directory path
0041    *
0042    * @var string
0043    **/
0044   protected $root = '';
0045   
0046   /**
0047    * Root basename | alias
0048    *
0049    * @var string
0050    **/
0051   protected $rootName = '';
0052   
0053   /**
0054    * Default directory to open
0055    *
0056    * @var string
0057    **/
0058   protected $startPath = '';
0059   
0060   /**
0061    * Base URL
0062    *
0063    * @var string
0064    **/
0065   protected $URL = '';
0066   
0067   /**
0068    * Thumbnails dir path
0069    *
0070    * @var string
0071    **/
0072   protected $tmbPath = '';
0073   
0074   /**
0075    * Is thumbnails dir writable
0076    *
0077    * @var bool
0078    **/
0079   protected $tmbPathWritable = false;
0080   
0081   /**
0082    * Thumbnails base URL
0083    *
0084    * @var string
0085    **/
0086   protected $tmbURL = '';
0087   
0088   /**
0089    * Thumbnails size in px
0090    *
0091    * @var int
0092    **/
0093   protected $tmbSize = 48;
0094   
0095   /**
0096    * Image manipulation lib name
0097    * auto|imagick|mogtify|gd
0098    *
0099    * @var string
0100    **/
0101   protected $imgLib = 'auto';
0102   
0103   /**
0104    * Library to crypt files name
0105    *
0106    * @var string
0107    **/
0108   protected $cryptLib = '';
0109   
0110   /**
0111    * Archivers config
0112    *
0113    * @var array
0114    **/
0115   protected $archivers = array(
0116     'create'  => array(),
0117     'extract' => array()
0118   );
0119   
0120   /**
0121    * How many subdirs levels return for tree
0122    *
0123    * @var int
0124    **/
0125   protected $treeDeep = 1;
0126   
0127   /**
0128    * Errors from last failed action
0129    *
0130    * @var array
0131    **/
0132   protected $error = array();
0133   
0134   /**
0135    * Today 24:00 timestamp
0136    *
0137    * @var int
0138    **/
0139   protected $today = 0;
0140   
0141   /**
0142    * Yesterday 24:00 timestamp
0143    *
0144    * @var int
0145    **/
0146   protected $yesterday = 0;
0147   
0148   /**
0149    * Object configuration
0150    *
0151    * @var array
0152    **/
0153   protected $options = array(
0154     'id'              => '',
0155     // root directory path
0156     'path'            => '',
0157     // open this path on initial request instead of root path
0158     'startPath'       => '',
0159     // how many subdirs levels return per request
0160     'treeDeep'        => 1,
0161     // root url, not set to disable sending URL to client (replacement for old "fileURL" option)
0162     'URL'             => '',
0163     // directory separator. required by client to show paths correctly
0164     'separator'       => DIRECTORY_SEPARATOR,
0165     // library to crypt/uncrypt files names (not implemented)
0166     'cryptLib'        => '',
0167     // how to detect files mimetypes. (auto/internal/finfo/mime_content_type)
0168     'mimeDetect'      => 'auto',
0169     // mime.types file path (for mimeDetect==internal)
0170     'mimefile'        => '',
0171     // directory for thumbnails
0172     'tmbPath'         => '.tmb',
0173     // mode to create thumbnails dir
0174     'tmbPathMode'     => 0777,
0175     // thumbnails dir URL. Set it if store thumbnails outside root directory
0176     'tmbURL'          => '',
0177     // thumbnails size (px)
0178     'tmbSize'         => 48,
0179     // thumbnails crop (true - crop, false - scale image to fit thumbnail size)
0180     'tmbCrop'         => true,
0181     // thumbnails background color (hex #rrggbb or 'transparent')
0182     'tmbBgColor'      => '#ffffff',
0183     // image manipulations library
0184     'imgLib'          => 'auto',
0185     // on paste file -  if true - old file will be replaced with new one, if false new file get name - original_name-number.ext
0186     'copyOverwrite'   => true,
0187     // if true - join new and old directories content on paste
0188     'copyJoin'        => true,
0189     // on upload -  if true - old file will be replaced with new one, if false new file get name - original_name-number.ext
0190     'uploadOverwrite' => true,
0191     // mimetypes allowed to upload
0192     'uploadAllow'     => array(),
0193     // mimetypes not allowed to upload
0194     'uploadDeny'      => array(),
0195     // order to proccess uploadAllow and uploadDeny options
0196     'uploadOrder'     => array('deny', 'allow'),
0197     // maximum upload file size. NOTE - this is size for every uploaded files
0198     'uploadMaxSize'   => 0,
0199     // files dates format
0200     'dateFormat'      => 'j M Y H:i',
0201     // files time format
0202     'timeFormat'      => 'H:i',
0203     // if true - every folder will be check for children folders, otherwise all folders will be marked as having subfolders
0204     'checkSubfolders' => true,
0205     // allow to copy from this volume to other ones?
0206     'copyFrom'        => true,
0207     // allow to copy from other volumes to this one?
0208     'copyTo'          => true,
0209     // list of commands disabled on this root
0210     'disabled'        => array(),
0211     // regexp or function name to validate new file name
0212     'acceptedName'    => '/^\w[\w\s\.\%\-\(\)\[\]]*$/u',
0213     // function/class method to control files permissions
0214     'accessControl'   => null,
0215     // some data required by access control
0216     'accessControlData' => null,
0217     // default permissions. not set hidden/locked here - take no effect
0218     'defaults'     => array(
0219       'read'   => true,
0220       'write'  => true
0221     ),
0222     // files attributes
0223     'attributes'   => array(),
0224     // Allowed archive's mimetypes to create. Leave empty for all available types.
0225     'archiveMimes' => array(),
0226     // Manual config for archivers. See example below. Leave empty for auto detect
0227     'archivers'    => array(),
0228     // required to fix bug on macos
0229     'utf8fix'      => false,
0230      //                           й                 ё              Й               Ё              Ø         Å
0231     'utf8patterns' => array("\u0438\u0306", "\u0435\u0308", "\u0418\u0306", "\u0415\u0308", "\u00d8A", "\u030a"),
0232     'utf8replace'  => array("\u0439",        "\u0451",       "\u0419",       "\u0401",       "\u00d8", "\u00c5")
0233   );
0234 
0235   /**
0236    * Defaults permissions
0237    *
0238    * @var array
0239    **/
0240   protected $defaults = array(
0241     'read'   => true,
0242     'write'  => true,
0243     'locked' => false,
0244     'hidden' => false
0245   );
0246   
0247   /**
0248    * Access control function/class
0249    *
0250    * @var mixed
0251    **/
0252   protected $attributes = array();
0253   
0254   /**
0255    * Access control function/class
0256    *
0257    * @var mixed
0258    **/
0259   protected $access = null;
0260   
0261   /**
0262    * Mime types allowed to upload
0263    *
0264    * @var array
0265    **/
0266   protected $uploadAllow = array();
0267   
0268   /**
0269    * Mime types denied to upload
0270    *
0271    * @var array
0272    **/
0273   protected $uploadDeny = array();
0274   
0275   /**
0276    * Order to validate uploadAllow and uploadDeny
0277    *
0278    * @var array
0279    **/
0280   protected $uploadOrder = array();
0281   
0282   /**
0283    * Maximum allowed upload file size.
0284    * Set as number or string with unit - "10M", "500K", "1G"
0285    *
0286    * @var int|string
0287    **/
0288   protected $uploadMaxSize = 0;
0289   
0290   /**
0291    * Mimetype detect method
0292    *
0293    * @var string
0294    **/
0295   protected $mimeDetect = 'auto';
0296   
0297   /**
0298    * Flag - mimetypes from externail file was loaded
0299    *
0300    * @var bool
0301    **/
0302   private static $mimetypesLoaded = false;
0303   
0304   /**
0305    * Finfo object for mimeDetect == 'finfo'
0306    *
0307    * @var object
0308    **/
0309   protected $finfo = null;
0310   
0311   /**
0312    * List of disabled client's commands
0313    *
0314    * @var array
0315    **/
0316   protected $diabled = array();
0317   
0318   /**
0319    * default extensions/mimetypes for mimeDetect == 'internal' 
0320    *
0321    * @var array
0322    **/
0323   protected static $mimetypes = array(
0324     // applications
0325     'ai'    => 'application/postscript',
0326     'eps'   => 'application/postscript',
0327     'exe'   => 'application/x-executable',
0328     'doc'   => 'application/vnd.ms-word',
0329     'xls'   => 'application/vnd.ms-excel',
0330     'ppt'   => 'application/vnd.ms-powerpoint',
0331     'pps'   => 'application/vnd.ms-powerpoint',
0332     'pdf'   => 'application/pdf',
0333     'xml'   => 'application/xml',
0334     'odt'   => 'application/vnd.oasis.opendocument.text',
0335     'swf'   => 'application/x-shockwave-flash',
0336     'torrent' => 'application/x-bittorrent',
0337     'jar'   => 'application/x-jar',
0338     // archives
0339     'gz'    => 'application/x-gzip',
0340     'tgz'   => 'application/x-gzip',
0341     'bz'    => 'application/x-bzip2',
0342     'bz2'   => 'application/x-bzip2',
0343     'tbz'   => 'application/x-bzip2',
0344     'zip'   => 'application/zip',
0345     'rar'   => 'application/x-rar',
0346     'tar'   => 'application/x-tar',
0347     '7z'    => 'application/x-7z-compressed',
0348     // texts
0349     'txt'   => 'text/plain',
0350     'php'   => 'text/x-php',
0351     'html'  => 'text/html',
0352     'htm'   => 'text/html',
0353     'js'    => 'text/javascript',
0354     'css'   => 'text/css',
0355     'rtf'   => 'text/rtf',
0356     'rtfd'  => 'text/rtfd',
0357     'py'    => 'text/x-python',
0358     'java'  => 'text/x-java-source',
0359     'rb'    => 'text/x-ruby',
0360     'sh'    => 'text/x-shellscript',
0361     'pl'    => 'text/x-perl',
0362     'xml'   => 'text/xml',
0363     'sql'   => 'text/x-sql',
0364     'c'     => 'text/x-csrc',
0365     'h'     => 'text/x-chdr',
0366     'cpp'   => 'text/x-c++src',
0367     'hh'    => 'text/x-c++hdr',
0368     'log'   => 'text/plain',
0369     'csv'   => 'text/x-comma-separated-values',
0370     // images
0371     'bmp'   => 'image/x-ms-bmp',
0372     'jpg'   => 'image/jpeg',
0373     'jpeg'  => 'image/jpeg',
0374     'gif'   => 'image/gif',
0375     'png'   => 'image/png',
0376     'tif'   => 'image/tiff',
0377     'tiff'  => 'image/tiff',
0378     'tga'   => 'image/x-targa',
0379     'psd'   => 'image/vnd.adobe.photoshop',
0380     'ai'    => 'image/vnd.adobe.photoshop',
0381     'xbm'   => 'image/xbm',
0382     'pxm'   => 'image/pxm',
0383     //audio
0384     'mp3'   => 'audio/mpeg',
0385     'mid'   => 'audio/midi',
0386     'ogg'   => 'audio/ogg',
0387     'oga'   => 'audio/ogg',
0388     'm4a'   => 'audio/x-m4a',
0389     'wav'   => 'audio/wav',
0390     'wma'   => 'audio/x-ms-wma',
0391     // video
0392     'avi'   => 'video/x-msvideo',
0393     'dv'    => 'video/x-dv',
0394     'mp4'   => 'video/mp4',
0395     'mpeg'  => 'video/mpeg',
0396     'mpg'   => 'video/mpeg',
0397     'mov'   => 'video/quicktime',
0398     'wm'    => 'video/x-ms-wmv',
0399     'flv'   => 'video/x-flv',
0400     'mkv'   => 'video/x-matroska',
0401     'webm'  => 'video/webm',
0402     'ogv'   => 'video/ogg',
0403     'ogm'   => 'video/ogg'
0404     );
0405   
0406   /**
0407    * Directory separator - required by client
0408    *
0409    * @var string
0410    **/
0411   protected $separator = DIRECTORY_SEPARATOR;
0412   
0413   /**
0414    * Mimetypes allowed to display
0415    *
0416    * @var array
0417    **/
0418   protected $onlyMimes = array();
0419   
0420   /**
0421    * Store files moved or overwrited files info
0422    *
0423    * @var array
0424    **/
0425   protected $removed = array();
0426   
0427   /**
0428    * Cache storage
0429    *
0430    * @var array
0431    **/
0432   protected $cache = array();
0433   
0434   /**
0435    * Cache by folders
0436    *
0437    * @var array
0438    **/
0439   protected $dirsCache = array();
0440   
0441   /*********************************************************************/
0442   /*                            INITIALIZATION                         */
0443   /*********************************************************************/
0444   
0445   /**
0446    * Prepare driver before mount volume.
0447    * Return true if volume is ready.
0448    *
0449    * @return bool
0450    * @author Dmitry (dio) Levashov
0451    **/
0452   protected function init() {
0453     return true;
0454   } 
0455     
0456   /**
0457    * Configure after successfull mount.
0458    * By default set thumbnails path and image manipulation library.
0459    *
0460    * @return void
0461    * @author Dmitry (dio) Levashov
0462    **/
0463   protected function configure() {
0464     // set thumbnails path
0465     $path = $this->options['tmbPath'];
0466     if ($path) {
0467       if (!file_exists($path)) {
0468         if (@mkdir($path)) {
0469           chmod($path, $this->options['tmbPathMode']);
0470         } else {
0471           $path = '';
0472         }
0473       } 
0474       
0475       if (is_dir($path) && is_readable($path)) {
0476         $this->tmbPath = $path;
0477         $this->tmbPathWritable = is_writable($path);
0478       }
0479     }
0480 
0481     // set image manipulation library
0482     $type = preg_match('/^(imagick|gd|auto)$/i', $this->options['imgLib'])
0483       ? strtolower($this->options['imgLib'])
0484       : 'auto';
0485 
0486     if (($type == 'imagick' || $type == 'auto') && extension_loaded('imagick')) {
0487       $this->imgLib = 'imagick';
0488     } else {
0489       $this->imgLib = function_exists('gd_info') ? 'gd' : '';
0490     }
0491     
0492   }
0493   
0494   
0495   /*********************************************************************/
0496   /*                              PUBLIC API                           */
0497   /*********************************************************************/
0498   
0499   /**
0500    * Return driver id. Used as a part of volume id.
0501    *
0502    * @return string
0503    * @author Dmitry (dio) Levashov
0504    **/
0505   public function driverId() {
0506     return $this->driverId;
0507   }
0508   
0509   /**
0510    * Return volume id
0511    *
0512    * @return string
0513    * @author Dmitry (dio) Levashov
0514    **/
0515   public function id() {
0516     return $this->id;
0517   }
0518     
0519   /**
0520    * Return debug info for client
0521    *
0522    * @return array
0523    * @author Dmitry (dio) Levashov
0524    **/
0525   public function debug() {
0526     return array(
0527       'id'         => $this->id(),
0528       'name'       => strtolower(substr(get_class($this), strlen('elfinderdriver'))),
0529       'mimeDetect' => $this->mimeDetect,
0530       'imgLib'     => $this->imgLib
0531     );
0532   }
0533   
0534   /**
0535    * "Mount" volume.
0536    * Return true if volume available for read or write, 
0537    * false - otherwise
0538    *
0539    * @return bool
0540    * @author Dmitry (dio) Levashov
0541    * @author Alexey Sukhotin
0542    **/
0543   public function mount(array $opts) {
0544     if (!isset($opts['path']) || $opts['path'] === '') {
0545       return false;
0546     }
0547     
0548     $this->options = array_merge($this->options, $opts);
0549     $this->id = $this->driverId.(!empty($this->options['id']) ? $this->options['id'] : elFinder::$volumesCnt++).'_';
0550     $this->root = $this->_normpath($this->options['path']);
0551     $this->separator = isset($this->options['separator']) ? $this->options['separator'] : DIRECTORY_SEPARATOR;
0552     
0553     // default file attribute
0554     $this->defaults = array(
0555       'read'    => isset($this->options['defaults']['read'])  ? !!$this->options['defaults']['read']  : true,
0556       'write'   => isset($this->options['defaults']['write']) ? !!$this->options['defaults']['write'] : true,
0557       'locked'  => false,
0558       'hidden'  => false
0559     );
0560 
0561     // root attributes
0562     $this->attributes[] = array(
0563       'pattern' => '~^'.preg_quote(DIRECTORY_SEPARATOR).'$~',
0564       'locked'  => true,
0565       'hidden'  => false
0566     );
0567     // set files attributes
0568     if (!empty($this->options['attributes']) && is_array($this->options['attributes'])) {
0569       
0570       foreach ($this->options['attributes'] as $a) {
0571         // attributes must contain pattern and at least one rule
0572         if (!empty($a['pattern']) || count($a) > 1) {
0573           $this->attributes[] = $a;
0574         }
0575       }
0576     }
0577 
0578     if (!empty($this->options['accessControl'])) {
0579       if (is_string($this->options['accessControl']) 
0580       && function_exists($this->options['accessControl'])) {
0581         $this->access = $this->options['accessControl'];
0582       } elseif (is_array($this->options['accessControl']) 
0583       && count($this->options['accessControl']) > 1 
0584       && is_object($this->options['accessControl'][0])
0585       && method_exists($this->options['accessControl'][0], $this->options['accessControl'][1])) {
0586         $this->access = array($this->options['accessControl'][0], $this->options['accessControl'][1]);
0587       }
0588     }
0589     
0590     $this->today     = mktime(0,0,0, date('m'), date('d'), date('Y'));
0591     $this->yesterday = $this->today-86400;
0592     
0593     // debug($this->attributes);
0594     if (!$this->init()) {
0595       return false;
0596     }
0597     
0598     // check some options is arrays
0599     $this->uploadAllow = isset($this->options['uploadAllow']) && is_array($this->options['uploadAllow'])
0600       ? $this->options['uploadAllow']
0601       : array();
0602       
0603     $this->uploadDeny = isset($this->options['uploadDeny']) && is_array($this->options['uploadDeny'])
0604       ? $this->options['uploadDeny']
0605       : array();
0606 
0607     if (is_string($this->options['uploadOrder'])) { // telephat_mode on, compatibility with 1.x
0608       $parts = explode(',', isset($this->options['uploadOrder']) ? $this->options['uploadOrder'] : 'deny,allow');
0609       $this->uploadOrder = array(trim($parts[0]), trim($parts[1]));
0610     } else { // telephat_mode off
0611       $this->uploadOrder = $this->options['uploadOrder'];
0612     }
0613       
0614     if (!empty($this->options['uploadMaxSize'])) {
0615       $size = ''.$this->options['uploadMaxSize'];
0616       $unit = strtolower(substr($size, strlen($size) - 1));
0617       $n = 1;
0618       switch ($unit) {
0619         case 'k':
0620           $n = 1024;
0621           break;
0622         case 'm':
0623           $n = 1048576;
0624           break;
0625         case 'g':
0626           $n = 1073741824;
0627       }
0628       $this->uploadMaxSize = intval($size)*$n;
0629     }
0630       
0631     $this->disabled = isset($this->options['disabled']) && is_array($this->options['disabled'])
0632       ? $this->options['disabled']
0633       : array();
0634     
0635     $this->cryptLib   = $this->options['cryptLib'];
0636     $this->mimeDetect = $this->options['mimeDetect'];
0637 
0638     // find available mimetype detect method
0639     $type = strtolower($this->options['mimeDetect']);
0640     $type = preg_match('/^(finfo|mime_content_type|internal|auto)$/i', $type) ? $type : 'auto';
0641     $regexp = '/text\/x\-(php|c\+\+)/';
0642     
0643     if (($type == 'finfo' || $type == 'auto') 
0644     && class_exists('finfo')
0645     && preg_match($regexp, array_shift(explode(';', @finfo_file(finfo_open(FILEINFO_MIME), __FILE__))))) {
0646       $type = 'finfo';
0647       $this->finfo = finfo_open(FILEINFO_MIME);
0648     } elseif (($type == 'mime_content_type' || $type == 'auto') 
0649     && function_exists('mime_content_type')
0650     && preg_match($regexp, array_shift(explode(';', mime_content_type(__FILE__))))) {
0651       $type = 'mime_content_type';
0652     } else {
0653       $type = 'internal';
0654     }
0655     $this->mimeDetect = $type;
0656 
0657     // load mimes from external file for mimeDetect == 'internal'
0658     // based on Alexey Sukhotin idea and patch: http://elrte.org/redmine/issues/163
0659     // file must be in file directory or in parent one 
0660     if ($this->mimeDetect == 'internal' && !self::$mimetypesLoaded) {
0661       self::$mimetypesLoaded = true;
0662       $this->mimeDetect = 'internal';
0663       $file = false;
0664       if (!empty($this->options['mimefile']) && file_exists($this->options['mimefile'])) {
0665         $file = $this->options['mimefile'];
0666       } elseif (file_exists(dirname(__FILE__).DIRECTORY_SEPARATOR.'mime.types')) {
0667         $file = dirname(__FILE__).DIRECTORY_SEPARATOR.'mime.types';
0668       } elseif (file_exists(dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'mime.types')) {
0669         $file = dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'mime.types';
0670       }
0671 
0672       if ($file && file_exists($file)) {
0673         $mimecf = file($file);
0674 
0675         foreach ($mimecf as $line_num => $line) {
0676           if (!preg_match('/^\s*#/', $line)) {
0677             $mime = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
0678             for ($i = 1, $size = count($mime); $i < $size ; $i++) {
0679               if (!isset(self::$mimetypes[$mime[$i]])) {
0680                 self::$mimetypes[$mime[$i]] = $mime[0];
0681               }
0682             }
0683           }
0684         }
0685       }
0686     }
0687 
0688     $this->rootName = empty($this->options['alias']) ? $this->_basename($this->root) : $this->options['alias'];
0689     $root = $this->stat($this->root);
0690     
0691     if (!$root) {
0692       return $this->setError('Root folder does not exists.');
0693     }
0694     if (!$root['read'] && !$root['write']) {
0695       return $this->setError('Root folder has not read and write permissions.');
0696     }
0697     
0698     // debug($root);
0699     
0700     if ($root['read']) {
0701       // check startPath - path to open by default instead of root
0702       if ($this->options['startPath']) {
0703         $start = $this->stat($this->options['startPath']);
0704         if (!empty($start)
0705         && $start['mime'] == 'directory'
0706         && $start['read']
0707         && empty($start['hidden'])
0708         && $this->_inpath($this->options['startPath'], $this->root)) {
0709           $this->startPath = $this->options['startPath'];
0710           if (substr($this->startPath, -1, 1) == $this->options['separator']) {
0711             $this->startPath = substr($this->startPath, 0, -1);
0712           }
0713         }
0714       }
0715     } else {
0716       $this->options['URL']     = '';
0717       $this->options['tmbURL']  = '';
0718       $this->options['tmbPath'] = '';
0719       // read only volume
0720       array_unshift($this->attributes, array(
0721         'pattern' => '/.*/',
0722         'read'    => false
0723       ));
0724     }
0725     $this->treeDeep = $this->options['treeDeep'] > 0 ? (int)$this->options['treeDeep'] : 1;
0726     $this->tmbSize  = $this->options['tmbSize'] > 0 ? (int)$this->options['tmbSize'] : 48;
0727     $this->URL      = $this->options['URL'];
0728     if ($this->URL && preg_match("|[^/?&=]$|", $this->URL)) {
0729       $this->URL .= '/';
0730     }
0731 
0732     $this->tmbURL   = !empty($this->options['tmbURL']) ? $this->options['tmbURL'] : '';
0733     if ($this->tmbURL && preg_match("|[^/?&=]$|", $this->tmbURL)) {
0734       $this->tmbURL .= '/';
0735     }
0736     
0737     $this->nameValidator = is_string($this->options['acceptedName']) && !empty($this->options['acceptedName']) 
0738       ? $this->options['acceptedName']
0739       : '';
0740 
0741     $this->_checkArchivers();
0742     // manual control archive types to create
0743     if (!empty($this->options['archiveMimes']) && is_array($this->options['archiveMimes'])) {
0744       foreach ($this->archivers['create'] as $mime => $v) {
0745         if (!in_array($mime, $this->options['archiveMimes'])) {
0746           unset($this->archivers['create'][$mime]);
0747         }
0748       }
0749     }
0750     
0751     // manualy add archivers
0752     if (!empty($this->options['archivers']['create']) && is_array($this->options['archivers']['create'])) {
0753       foreach ($this->options['archivers']['create'] as $mime => $conf) {
0754         if (strpos($mime, 'application/') === 0 
0755         && !empty($conf['cmd']) 
0756         && isset($conf['argc']) 
0757         && !empty($conf['ext'])
0758         && !isset($this->archivers['create'][$mime])) {
0759           $this->archivers['create'][$mime] = $conf;
0760         }
0761       }
0762     }
0763     
0764     if (!empty($this->options['archivers']['extract']) && is_array($this->options['archivers']['extract'])) {
0765       foreach ($this->options['archivers']['extract'] as $mime => $conf) {
0766         if (substr($mime, 'application/') === 0 
0767         && !empty($cons['cmd']) 
0768         && isset($conf['argc']) 
0769         && !empty($conf['ext'])
0770         && !isset($this->archivers['extract'][$mime])) {
0771           $this->archivers['extract'][$mime] = $conf;
0772         }
0773       }
0774     }
0775 
0776     $this->configure();
0777     // echo $this->uploadMaxSize;
0778     // echo $this->options['uploadMaxSize'];
0779     return $this->mounted = true;
0780   }
0781   
0782   /**
0783    * Some "unmount" stuffs - may be required by virtual fs
0784    *
0785    * @return void
0786    * @author Dmitry (dio) Levashov
0787    **/
0788   public function umount() {
0789   }
0790   
0791   /**
0792    * Return error message from last failed action
0793    *
0794    * @return array
0795    * @author Dmitry (dio) Levashov
0796    **/
0797   public function error() {
0798     return $this->error;
0799   }
0800   
0801   /**
0802    * Set mimetypes allowed to display to client
0803    *
0804    * @param  array  $mimes
0805    * @return void
0806    * @author Dmitry (dio) Levashov
0807    **/
0808   public function setMimesFilter($mimes) {
0809     if (is_array($mimes)) {
0810       $this->onlyMimes = $mimes;
0811     }
0812   }
0813   
0814   /**
0815    * Return root folder hash
0816    *
0817    * @return string
0818    * @author Dmitry (dio) Levashov
0819    **/
0820   public function root() {
0821     return $this->encode($this->root);
0822   }
0823   
0824   /**
0825    * Return root or startPath hash
0826    *
0827    * @return string
0828    * @author Dmitry (dio) Levashov
0829    **/
0830   public function defaultPath() {
0831     return $this->encode($this->startPath ? $this->startPath : $this->root);
0832   }
0833     
0834   /**
0835    * Return volume options required by client:
0836    *
0837    * @return array
0838    * @author Dmitry (dio) Levashov
0839    **/
0840   public function options($hash) {
0841     return array(
0842       'path'          => $this->_path($this->decode($hash)),
0843       'url'           => $this->URL,
0844       'tmbUrl'        => $this->tmbURL,
0845       'disabled'      => $this->disabled,
0846       'separator'     => $this->separator,
0847       'copyOverwrite' => intval($this->options['copyOverwrite']),
0848       'archivers'     => array(
0849         'create'  => array_keys($this->archivers['create']),
0850         'extract' => array_keys($this->archivers['extract'])
0851       )
0852     );
0853   }
0854   
0855   /**
0856    * Return true if command disabled in options
0857    *
0858    * @param  string  $cmd  command name
0859    * @return bool
0860    * @author Dmitry (dio) Levashov
0861    **/
0862   public function commandDisabled($cmd) {
0863     return in_array($cmd, $this->disabled);
0864   }
0865   
0866   /**
0867    * Return true if mime is required mimes list
0868    *
0869    * @param  string     $mime   mime type to check
0870    * @param  array      $mimes  allowed mime types list or not set to use client mimes list
0871    * @param  bool|null  $empty  what to return on empty list
0872    * @return bool|null
0873    * @author Dmitry (dio) Levashov
0874    * @author Troex Nevelin
0875    **/
0876   public function mimeAccepted($mime, $mimes = array(), $empty = true) {
0877     $mimes = !empty($mimes) ? $mimes : $this->onlyMimes;
0878     if (empty($mimes)) {
0879       return $empty;
0880     }
0881     return $mime == 'directory'
0882       || in_array('all', $mimes)
0883       || in_array('All', $mimes)
0884       || in_array($mime, $mimes)
0885       || in_array(substr($mime, 0, strpos($mime, '/')), $mimes);
0886   }
0887   
0888   /**
0889    * Return true if voume is readable.
0890    *
0891    * @return bool
0892    * @author Dmitry (dio) Levashov
0893    **/
0894   public function isReadable() {
0895     $stat = $this->stat($this->root);
0896     return $stat['read'];
0897   }
0898   
0899   /**
0900    * Return true if copy from this volume allowed
0901    *
0902    * @return bool
0903    * @author Dmitry (dio) Levashov
0904    **/
0905   public function copyFromAllowed() {
0906     return !!$this->options['copyFrom'];
0907   }
0908   
0909   /**
0910    * Return file path related to root
0911    *
0912    * @param  string   $hash  file hash
0913    * @return string
0914    * @author Dmitry (dio) Levashov
0915    **/
0916   public function path($hash) {
0917     return $this->_path($this->decode($hash));
0918   }
0919   
0920   /**
0921    * Return file real path if file exists
0922    *
0923    * @param  string  $hash  file hash
0924    * @return string
0925    * @author Dmitry (dio) Levashov
0926    **/
0927   public function realpath($hash) {
0928     $path = $this->decode($hash);
0929     return $this->stat($path) ? $path : false;
0930   }
0931   
0932   /**
0933    * Return list of moved/overwrited files
0934    *
0935    * @return array
0936    * @author Dmitry (dio) Levashov
0937    **/
0938   public function removed() {
0939     return $this->removed;
0940   }
0941   
0942   /**
0943    * Clean removed files list
0944    *
0945    * @return void
0946    * @author Dmitry (dio) Levashov
0947    **/
0948   public function resetRemoved() {
0949     $this->removed = array();
0950   }
0951   
0952   /**
0953    * Return file/dir hash or first founded child hash with required attr == $val
0954    *
0955    * @param  string   $hash  file hash
0956    * @param  string   $attr  attribute name
0957    * @param  bool     $val   attribute value
0958    * @return string|false
0959    * @author Dmitry (dio) Levashov
0960    **/
0961   public function closest($hash, $attr, $val) {
0962     return ($path = $this->closestByAttr($this->decode($hash), $attr, $val)) ? $this->encode($path) : false;
0963   }
0964   
0965   /**
0966    * Return file info or false on error
0967    *
0968    * @param  string   $hash      file hash
0969    * @param  bool     $realpath  add realpath field to file info
0970    * @return array|false
0971    * @author Dmitry (dio) Levashov
0972    **/
0973   public function file($hash) {
0974     $path = $this->decode($hash);
0975     
0976     return ($file = $this->stat($path)) ? $file : $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
0977     
0978     if (($file = $this->stat($path)) != false) {
0979       if ($realpath) {
0980         $file['realpath'] = $path;
0981       }
0982       return $file;
0983     }
0984     return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
0985   }
0986   
0987   /**
0988    * Return folder info
0989    *
0990    * @param  string   $hash  folder hash
0991    * @param  bool     $hidden  return hidden file info
0992    * @return array|false
0993    * @author Dmitry (dio) Levashov
0994    **/
0995   public function dir($hash, $resolveLink=false) {
0996     if (($dir = $this->file($hash)) == false) {
0997       return $this->setError(elFinder::ERROR_DIR_NOT_FOUND);
0998     }
0999 
1000     if ($resolveLink && !empty($dir['thash'])) {
1001       $dir = $this->file($dir['thash']);
1002     }
1003     
1004     return $dir && $dir['mime'] == 'directory' && empty($dir['hidden']) 
1005       ? $dir 
1006       : $this->setError(elFinder::ERROR_NOT_DIR);
1007   }
1008   
1009   /**
1010    * Return directory content or false on error
1011    *
1012    * @param  string   $hash   file hash
1013    * @return array|false
1014    * @author Dmitry (dio) Levashov
1015    **/
1016   public function scandir($hash) {
1017     if (($dir = $this->dir($hash)) == false) {
1018       return false;
1019     }
1020     
1021     return $dir['read']
1022       ? $this->getScandir($this->decode($hash))
1023       : $this->setError(elFinder::ERROR_PERM_DENIED);
1024   }
1025 
1026   /**
1027    * Return dir files names list
1028    * 
1029    * @param  string  $hash   file hash
1030    * @return array
1031    * @author Dmitry (dio) Levashov
1032    **/
1033   public function ls($hash) {
1034     if (($dir = $this->dir($hash)) == false || !$dir['read']) {
1035       return false;
1036     }
1037     
1038     $list = array();
1039     $path = $this->decode($hash);
1040     
1041     foreach ($this->getScandir($path) as $stat) {
1042       if (empty($stat['hidden']) && $this->mimeAccepted($stat['mime'])) {
1043         $list[] = $stat['name'];
1044       }
1045     }
1046 
1047     return $list;
1048   }
1049 
1050   /**
1051    * Return subfolders for required folder or false on error
1052    *
1053    * @param  string   $hash  folder hash or empty string to get tree from root folder
1054    * @param  int      $deep  subdir deep
1055    * @param  string   $exclude  dir hash which subfolders must be exluded from result, required to not get stat twice on cwd subfolders
1056    * @return array|false
1057    * @author Dmitry (dio) Levashov
1058    **/
1059   public function tree($hash='', $deep=0, $exclude='') {
1060     $path = $hash ? $this->decode($hash) : $this->root;
1061     
1062     if (($dir = $this->stat($path)) == false || $dir['mime'] != 'directory') {
1063       return false;
1064     }
1065     
1066     $dirs = $this->gettree($path, $deep > 0 ? $deep -1 : $this->treeDeep-1, $this->decode($exclude));
1067     array_unshift($dirs, $dir);
1068     return $dirs;
1069   }
1070   
1071   /**
1072    * Return part of dirs tree from required dir up to root dir
1073    *
1074    * @param  string  $hash  directory hash
1075    * @return array
1076    * @author Dmitry (dio) Levashov
1077    **/
1078   public function parents($hash) {
1079     if (($current = $this->dir($hash)) == false) {
1080       return false;
1081     }
1082 
1083     $path = $this->decode($hash);
1084     $tree = array();
1085     
1086     while ($path && $path != $this->root) {
1087       $path = $this->_dirname($path);
1088       $stat = $this->stat($path);
1089       if (!empty($stat['hidden']) || !$stat['read']) {
1090         return false;
1091       }
1092       
1093       array_unshift($tree, $stat);
1094       if ($path != $this->root) {
1095         foreach ($this->gettree($path, 0) as $dir) {
1096           if (!in_array($dir, $tree)) {
1097             $tree[] = $dir;
1098           }
1099         }
1100       }
1101     }
1102 
1103     return $tree ? $tree : array($current);
1104   }
1105   
1106   /**
1107    * Create thumbnail for required file and return its name of false on failed
1108    *
1109    * @return string|false
1110    * @author Dmitry (dio) Levashov
1111    **/
1112   public function tmb($hash) {
1113     $path = $this->decode($hash);
1114     $stat = $this->stat($path);
1115     
1116     if (isset($stat['tmb'])) {
1117       return $stat['tmb'] == "1" ? $this->createTmb($path, $stat) : $stat['tmb'];
1118     }
1119     return false;
1120   }
1121   
1122   /**
1123    * Return file size / total directory size
1124    *
1125    * @param  string   file hash
1126    * @return int
1127    * @author Dmitry (dio) Levashov
1128    **/
1129   public function size($hash) {
1130     return $this->countSize($this->decode($hash));
1131   }
1132   
1133   /**
1134    * Open file for reading and return file pointer
1135    *
1136    * @param  string   file hash
1137    * @return Resource
1138    * @author Dmitry (dio) Levashov
1139    **/
1140   public function open($hash) {
1141     if (($file = $this->file($hash)) == false
1142     || $file['mime'] == 'directory') {
1143       return false;
1144     }
1145     
1146     return $this->_fopen($this->decode($hash), 'rb');
1147   }
1148   
1149   /**
1150    * Close file pointer
1151    *
1152    * @param  Resource  $fp   file pointer
1153    * @param  string    $hash file hash
1154    * @return void
1155    * @author Dmitry (dio) Levashov
1156    **/
1157   public function close($fp, $hash) {
1158     $this->_fclose($fp, $this->decode($hash));
1159   }
1160   
1161   /**
1162    * Create directory and return dir info
1163    *
1164    * @param  string   $dst  destination directory
1165    * @param  string   $name directory name
1166    * @return array|false
1167    * @author Dmitry (dio) Levashov
1168    **/
1169   public function mkdir($dst, $name) {
1170     if ($this->commandDisabled('mkdir')) {
1171       return $this->setError(elFinder::ERROR_PERM_DENIED);
1172     }
1173     
1174     if (!$this->nameAccepted($name)) {
1175       return $this->setError(elFinder::ERROR_INVALID_NAME);
1176     }
1177     
1178     if (($dir = $this->dir($dst)) == false) {
1179       return $this->setError(elFinder::ERROR_TRGDIR_NOT_FOUND, '#'.$dst);
1180     }
1181     
1182     if (!$dir['write']) {
1183       return $this->setError(elFinder::ERROR_PERM_DENIED);
1184     }
1185     
1186     $path = $this->decode($dst);
1187     $dst  = $this->_joinPath($path, $name);
1188     $stat = $this->stat($dst);
1189     if (!empty($stat)) {
1190       return $this->setError(elFinder::ERROR_EXISTS, $name);
1191     }
1192     $this->clearcache();
1193     return ($path = $this->_mkdir($path, $name)) ? $this->stat($path) : false;
1194   }
1195   
1196   /**
1197    * Create empty file and return its info
1198    *
1199    * @param  string   $dst  destination directory
1200    * @param  string   $name file name
1201    * @return array|false
1202    * @author Dmitry (dio) Levashov
1203    **/
1204   public function mkfile($dst, $name) {
1205     if ($this->commandDisabled('mkfile')) {
1206       return $this->setError(elFinder::ERROR_PERM_DENIED);
1207     }
1208     
1209     if (!$this->nameAccepted($name)) {
1210       return $this->setError(elFinder::ERROR_INVALID_NAME);
1211     }
1212     
1213     if (($dir = $this->dir($dst)) == false) {
1214       return $this->setError(elFinder::ERROR_TRGDIR_NOT_FOUND, '#'.$dst);
1215     }
1216     
1217     if (!$dir['write']) {
1218       return $this->setError(elFinder::ERROR_PERM_DENIED);
1219     }
1220     
1221     $path = $this->decode($dst);
1222 
1223     if ($this->stat($this->_joinPath($path, $name))) {
1224       return $this->setError(elFinder::ERROR_EXISTS, $name);
1225     }
1226     $this->clearcache();
1227     return ($path = $this->_mkfile($path, $name)) ? $this->stat($path) : false;
1228   }
1229   
1230   /**
1231    * Rename file and return file info
1232    *
1233    * @param  string  $hash  file hash
1234    * @param  string  $name  new file name
1235    * @return array|false
1236    * @author Dmitry (dio) Levashov
1237    **/
1238   public function rename($hash, $name) {
1239     if ($this->commandDisabled('rename')) {
1240       return $this->setError(elFinder::ERROR_PERM_DENIED);
1241     }
1242     
1243     if (!$this->nameAccepted($name)) {
1244       return $this->setError(elFinder::ERROR_INVALID_NAME, $name);
1245     }
1246     
1247     if (!($file = $this->file($hash))) {
1248       return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
1249     }
1250     
1251     if ($name == $file['name']) {
1252       return $file;
1253     }
1254     
1255     if (!empty($file['locked'])) {
1256       return $this->setError(elFinder::ERROR_LOCKED, $file['name']);
1257     }
1258     
1259     $path = $this->decode($hash);
1260     $dir  = $this->_dirname($path);
1261     $stat = $this->stat($this->_joinPath($dir, $name));
1262     if ($stat) {
1263       return $this->setError(elFinder::ERROR_EXISTS, $name);
1264     }
1265     
1266     if (!$this->_move($path, $dir, $name)) {
1267       return false;
1268     }
1269     
1270     if (!empty($stat['tmb']) && $stat['tmb'] != "1") {
1271       $this->rmTmb($stat['tmb']);
1272     }
1273     
1274     $path = $this->_joinPath($dir, $name);
1275 
1276     $this->clearcache();
1277     return $this->stat($path);
1278   }
1279   
1280   /**
1281    * Create file copy with suffix "copy number" and return its info
1282    *
1283    * @param  string   $hash    file hash
1284    * @param  string   $suffix  suffix to add to file name
1285    * @return array|false
1286    * @author Dmitry (dio) Levashov
1287    **/
1288   public function duplicate($hash, $suffix='copy') {
1289     if ($this->commandDisabled('duplicate')) {
1290       return $this->setError(elFinder::ERROR_COPY, '#'.$hash, elFinder::ERROR_PERM_DENIED);
1291     }
1292     
1293     if (($file = $this->file($hash)) == false) {
1294       return $this->setError(elFinder::ERROR_COPY, elFinder::ERROR_FILE_NOT_FOUND);
1295     }
1296 
1297     $path = $this->decode($hash);
1298     $dir  = $this->_dirname($path);
1299 
1300     return ($path = $this->copy($path, $dir, $this->uniqueName($dir, $this->_basename($path), ' '.$suffix.' '))) == false
1301       ? false
1302       : $this->stat($path);
1303   }
1304   
1305   /**
1306    * Save uploaded file. 
1307    * On success return array with new file stat and with removed file hash (if existed file was replaced)
1308    *
1309    * @param  Resource $fp      file pointer
1310    * @param  string   $dst     destination folder hash
1311    * @param  string   $src     file name
1312    * @param  string   $tmpname file tmp name - required to detect mime type
1313    * @return array|false
1314    * @author Dmitry (dio) Levashov
1315    **/
1316   public function upload($fp, $dst, $name, $tmpname) {
1317     if ($this->commandDisabled('upload')) {
1318       return $this->setError(elFinder::ERROR_PERM_DENIED);
1319     }
1320     
1321     if (($dir = $this->dir($dst)) == false) {
1322       return $this->setError(elFinder::ERROR_TRGDIR_NOT_FOUND, '#'.$dst);
1323     }
1324 
1325     if (!$dir['write']) {
1326       return $this->setError(elFinder::ERROR_PERM_DENIED);
1327     }
1328     
1329     if (!$this->nameAccepted($name)) {
1330       return $this->setError(elFinder::ERROR_INVALID_NAME);
1331     }
1332     
1333     $mime = $this->mimetype($this->mimeDetect == 'internal' ? $name : $tmpname); 
1334     if ($mime == 'unknown' && $this->mimeDetect == 'internal') {
1335       $mime = elFinderVolumeDriver::mimetypeInternalDetect($name);
1336     }
1337 
1338     // logic based on http://httpd.apache.org/docs/2.2/mod/mod_authz_host.html#order
1339     $allow  = $this->mimeAccepted($mime, $this->uploadAllow, null);
1340     $deny   = $this->mimeAccepted($mime, $this->uploadDeny,  null);
1341     $upload = true; // default to allow
1342     if (strtolower($this->uploadOrder[0]) == 'allow') { // array('allow', 'deny'), default is to 'deny'
1343       $upload = false; // default is deny
1344       if (!$deny && ($allow === true)) { // match only allow
1345         $upload = true;
1346       }// else (both match | no match | match only deny) { deny }
1347     } else { // array('deny', 'allow'), default is to 'allow' - this is the default rule
1348       $upload = true; // default is allow
1349       if (($deny === true) && !$allow) { // match only deny
1350         $upload = false;
1351       } // else (both match | no match | match only allow) { allow }
1352     }
1353     if (!$upload) {
1354       return $this->setError(elFinder::ERROR_UPLOAD_FILE_MIME);
1355     }
1356 
1357     if ($this->uploadMaxSize > 0 && filesize($tmpname) > $this->uploadMaxSize) {
1358       return $this->setError(elFinder::ERROR_UPLOAD_FILE_SIZE);
1359     }
1360 
1361     $dstpath = $this->decode($dst);
1362     $test    = $this->_joinPath($dstpath, $name);
1363     
1364     $file = $this->stat($test);
1365     $this->clearcache();
1366     
1367     if ($file) { // file exists
1368       if ($this->options['uploadOverwrite']) {
1369         if (!$file['write']) {
1370           return $this->setError(elFinder::ERROR_PERM_DENIED);
1371         } elseif ($file['mime'] == 'directory') {
1372           return $this->setError(elFinder::ERROR_NOT_REPLACE, $name);
1373         } 
1374         $this->remove($file);
1375       } else {
1376         $name = $this->uniqueName($dstpath, $name, '-', false);
1377       }
1378     }
1379     
1380     $w = $h = 0;
1381     if (strpos($mime, 'image') === 0 && ($s = getimagesize($tmpname))) {
1382       $w = $s[0];
1383       $h = $s[1];
1384     }
1385     // $this->clearcache();
1386     if (($path = $this->_save($fp, $dstpath, $name, $mime, $w, $h)) == false) {
1387       return false;
1388     }
1389     
1390     
1391 
1392     return $this->stat($path);
1393   }
1394   
1395   /**
1396    * Paste files
1397    *
1398    * @param  Object  $volume  source volume
1399    * @param  string  $source  file hash
1400    * @param  string  $dst     destination dir hash
1401    * @param  bool    $rmSrc   remove source after copy?
1402    * @return array|false
1403    * @author Dmitry (dio) Levashov
1404    **/
1405   public function paste($volume, $src, $dst, $rmSrc = false) {
1406     $err = $rmSrc ? elFinder::ERROR_MOVE : elFinder::ERROR_COPY;
1407     
1408     if ($this->commandDisabled('paste')) {
1409       return $this->setError($err, '#'.$src, elFinder::ERROR_PERM_DENIED);
1410     }
1411 
1412     if (($file = $volume->file($src, $rmSrc)) == false) {
1413       return $this->setError($err, '#'.$src, elFinder::ERROR_FILE_NOT_FOUND);
1414     }
1415 
1416     $name = $file['name'];
1417     $errpath = $volume->path($src);
1418     
1419     if (($dir = $this->dir($dst)) == false) {
1420       return $this->setError($err, $errpath, elFinder::ERROR_TRGDIR_NOT_FOUND, '#'.$dst);
1421     }
1422     
1423     if (!$dir['write'] || !$file['read']) {
1424       return $this->setError($err, $errpath, elFinder::ERROR_PERM_DENIED);
1425     }
1426 
1427     $destination = $this->decode($dst);
1428 
1429     if (($test = $volume->closest($src, $rmSrc ? 'locked' : 'read', $rmSrc))) {
1430       return $rmSrc
1431         ? $this->setError($err, $errpath, elFinder::ERROR_LOCKED, $volume->path($test))
1432         : $this->setError($err, $errpath, elFinder::ERROR_PERM_DENIED);
1433     }
1434 
1435     $test = $this->_joinPath($destination, $name);
1436     $stat = $this->stat($test);
1437     $this->clearcache();
1438     if ($stat) {
1439       if ($this->options['copyOverwrite']) {
1440         // do not replace file with dir or dir with file
1441         if (!$this->isSameType($file['mime'], $stat['mime'])) {
1442           return $this->setError(elFinder::ERROR_NOT_REPLACE, $this->_path($test));
1443         }
1444         // existed file is not writable
1445         if (!$stat['write']) {
1446           return $this->setError($err, $errpath, elFinder::ERROR_PERM_DENIED);
1447         }
1448         // existed file locked or has locked child
1449         if (($locked = $this->closestByAttr($test, 'locked', true))) {
1450           return $this->setError(elFinder::ERROR_LOCKED, $this->_path($locked));
1451         }
1452         // remove existed file
1453         if (!$this->remove($test)) {
1454           return $this->setError(elFinder::ERROR_REPLACE, $this->_path($test));
1455         }
1456       } else {
1457         $name = $this->uniqueName($destination, $name, ' ', false);
1458       }
1459     }
1460     
1461     // copy/move inside current volume
1462     if ($volume == $this) {
1463       $source = $this->decode($src);
1464       // do not copy into itself
1465       if ($this->_inpath($destination, $source)) {
1466         return $this->setError(elFinder::ERROR_COPY_INTO_ITSELF, $path);
1467       }
1468       $method = $rmSrc ? 'move' : 'copy';
1469       
1470       return ($path = $this->$method($source, $destination, $name)) ? $this->stat($path) : false;
1471     }
1472     
1473     
1474     // copy/move from another volume
1475     if (!$this->options['copyTo'] || !$volume->copyFromAllowed()) {
1476       return $this->setError(elFinder::ERROR_COPY, $errpath, elFinder::ERROR_PERM_DENIED);
1477     }
1478     
1479     if (($path = $this->copyFrom($volume, $src, $destination, $name)) == false) {
1480       return false;
1481     }
1482     
1483     if ($rmSrc) {
1484       if ($volume->rm($src)) {
1485         $this->removed[] = $file;
1486       } else {
1487         return $this->setError(elFinder::ERROR_MOVE, $errpath, elFinder::ERROR_RM_SRC);
1488       }
1489     }
1490     return $this->stat($path);
1491   }
1492   
1493   /**
1494    * Return file contents
1495    *
1496    * @param  string  $hash  file hash
1497    * @return string|false
1498    * @author Dmitry (dio) Levashov
1499    **/
1500   public function getContents($hash) {
1501     $file = $this->file($hash);
1502     
1503     if (!$file) {
1504       return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
1505     }
1506     
1507     if ($file['mime'] == 'directory') {
1508       return $this->setError(elFinder::ERROR_NOT_FILE);
1509     }
1510     
1511     if (!$file['read']) {
1512       return $this->setError(elFinder::ERROR_PERM_DENIED);
1513     }
1514     
1515     return $this->_getContents($this->decode($hash));
1516   }
1517   
1518   /**
1519    * Put content in text file and return file info.
1520    *
1521    * @param  string  $hash     file hash
1522    * @param  string  $content  new file content
1523    * @return array
1524    * @author Dmitry (dio) Levashov
1525    **/
1526   public function putContents($hash, $content) {
1527     if ($this->commandDisabled('edit')) {
1528       return $this->setError(elFinder::ERROR_PERM_DENIED);
1529     }
1530     
1531     $path = $this->decode($hash);
1532     
1533     if (!($file = $this->file($hash))) {
1534       return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
1535     }
1536     
1537     if (!$file['write']) {
1538       return $this->setError(elFinder::ERROR_PERM_DENIED);
1539     }
1540     $this->clearcache();
1541     return $this->_filePutContents($path, $content) ? $this->stat($path) : false;
1542   }
1543   
1544   /**
1545    * Extract files from archive
1546    *
1547    * @param  string  $hash  archive hash
1548    * @return array|bool
1549    * @author Dmitry (dio) Levashov, 
1550    * @author Alexey Sukhotin
1551    **/
1552   public function extract($hash) {
1553     if ($this->commandDisabled('extract')) {
1554       return $this->setError(elFinder::ERROR_PERM_DENIED);
1555     }
1556     
1557     if (($file = $this->file($hash)) == false) {
1558       return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
1559     }
1560     
1561     $archiver = isset($this->archivers['extract'][$file['mime']])
1562       ? $this->archivers['extract'][$file['mime']]
1563       : false;
1564       
1565     if (!$archiver) {
1566       return $this->setError(elFinder::ERROR_NOT_ARCHIVE);
1567     }
1568     
1569     $path   = $this->decode($hash);
1570     $parent = $this->stat($this->_dirname($path));
1571 
1572     if (!$file['read'] || !$parent['write']) {
1573       return $this->setError(elFinder::ERROR_PERM_DENIED);
1574     }
1575     $this->clearcache();
1576     return ($path = $this->_extract($path, $archiver)) ? $this->stat($path) : false;
1577   }
1578 
1579   /**
1580    * Add files to archive
1581    *
1582    * @return void
1583    **/
1584   public function archive($hashes, $mime) {
1585     if ($this->commandDisabled('archive')) {
1586       return $this->setError(elFinder::ERROR_PERM_DENIED);
1587     }
1588 
1589     $archiver = isset($this->archivers['create'][$mime])
1590       ? $this->archivers['create'][$mime]
1591       : false;
1592       
1593     if (!$archiver) {
1594       return $this->setError(elFinder::ERROR_ARCHIVE_TYPE);
1595     }
1596     
1597     $files = array();
1598     
1599     foreach ($hashes as $hash) {
1600       if (($file = $this->file($hash)) == false) {
1601         return $this->error(elFinder::ERROR_FILE_NOT_FOUND, '#'+$hash);
1602       }
1603       if (!$file['read']) {
1604         return $this->error(elFinder::ERROR_PERM_DENIED);
1605       }
1606       $path = $this->decode($hash);
1607       if (!isset($dir)) {
1608         $dir = $this->_dirname($path);
1609         $stat = $this->stat($dir);
1610         if (!$stat['write']) {
1611           return $this->error(elFinder::ERROR_PERM_DENIED);
1612         }
1613       }
1614       
1615       $files[] = $this->_basename($path);
1616     }
1617     
1618     $name = (count($files) == 1 ? $files[0] : 'Archive').'.'.$archiver['ext'];
1619     $name = $this->uniqueName($dir, $name, '');
1620     $this->clearcache();
1621     return ($path = $this->_archive($dir, $files, $name, $archiver)) ? $this->stat($path) : false;
1622   }
1623   
1624   /**
1625    * Resize image
1626    *
1627    * @param  string   $hash    image file
1628    * @param  int      $width   new width
1629    * @param  int      $height  new height
1630    * @param  int      $x       X start poistion for crop
1631    * @param  int      $y       Y start poistion for crop
1632    * @param  string   $mode    action how to mainpulate image
1633    * @return array|false
1634    * @author Dmitry (dio) Levashov
1635    * @author Alexey Sukhotin
1636    * @author nao-pon
1637    * @author Troex Nevelin
1638    **/
1639   public function resize($hash, $width, $height, $x, $y, $mode = 'resize', $bg = '', $degree = 0) {
1640     if ($this->commandDisabled('resize')) {
1641       return $this->setError(elFinder::ERROR_PERM_DENIED);
1642     }
1643     
1644     if (($file = $this->file($hash)) == false) {
1645       return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
1646     }
1647     
1648     if (!$file['write'] || !$file['read']) {
1649       return $this->setError(elFinder::ERROR_PERM_DENIED);
1650     }
1651     
1652     $path = $this->decode($hash);
1653     
1654     if (!$this->canResize($path, $file)) {
1655       return $this->setError(elFinder::ERROR_UNSUPPORT_TYPE);
1656     }
1657 
1658     switch($mode) {
1659       
1660       case 'propresize':
1661         $result = $this->imgResize($path, $width, $height, true, true);
1662         break;
1663 
1664       case 'crop':
1665         $result = $this->imgCrop($path, $width, $height, $x, $y);
1666         break;
1667 
1668       case 'fitsquare':
1669         $result = $this->imgSquareFit($path, $width, $height, 'center', 'middle', ($bg ? $bg : $this->options['tmbBgColor']));
1670         break;
1671 
1672       case 'rotate':
1673         $result = $this->imgRotate($path, $degree, ($bg ? $bg : $this->options['tmbBgColor']));
1674         break;
1675 
1676       default:
1677         $result = $this->imgResize($path, $width, $height, false, true);
1678         break;
1679     }
1680 
1681     if ($result) {
1682       if (!empty($file['tmb']) && $file['tmb'] != "1") {
1683         $this->rmTmb($file['tmb']);
1684       }
1685       $this->clearcache();
1686       return $this->stat($path);
1687     }
1688     
1689       return false;
1690   }
1691   
1692   /**
1693    * Remove file/dir
1694    *
1695    * @param  string  $hash  file hash
1696    * @return bool
1697    * @author Dmitry (dio) Levashov
1698    **/
1699   public function rm($hash) {
1700     return $this->commandDisabled('rm')
1701       ? array(elFinder::ERROR_ACCESS_DENIED)
1702       : $this->remove($this->decode($hash));
1703   }
1704   
1705   /**
1706    * Search files
1707    *
1708    * @param  string  $q  search string
1709    * @param  array   $mimes
1710    * @return array
1711    * @author Dmitry (dio) Levashov
1712    **/
1713   public function search($q, $mimes) {
1714     return $this->doSearch($this->root, $q, $mimes);
1715   }
1716   
1717   /**
1718    * Return image dimensions
1719    *
1720    * @param  string  $hash  file hash
1721    * @return array
1722    * @author Dmitry (dio) Levashov
1723    **/
1724   public function dimensions($hash) {
1725     if (($file = $this->file($hash)) == false) {
1726       return false;
1727     }
1728     
1729     return $this->_dimensions($this->decode($hash), $file['mime']);
1730   }
1731   
1732   /**
1733    * Save error message
1734    *
1735    * @param  array  error 
1736    * @return false
1737    * @author Dmitry(dio) Levashov
1738    **/
1739   protected function setError($error) {
1740     
1741     $this->error = array();
1742     
1743     foreach (func_get_args() as $err) {
1744       if (is_array($err)) {
1745         $this->error = array_merge($this->error, $err);
1746       } else {
1747         $this->error[] = $err;
1748       }
1749     }
1750     
1751     // $this->error = is_array($error) ? $error : func_get_args();
1752     return false;
1753   }
1754   
1755   /*********************************************************************/
1756   /*                               FS API                              */
1757   /*********************************************************************/
1758   
1759   /***************** paths *******************/
1760   
1761   /**
1762    * Encode path into hash
1763    *
1764    * @param  string  file path
1765    * @return string
1766    * @author Dmitry (dio) Levashov
1767    * @author Troex Nevelin
1768    **/
1769   protected function encode($path) {
1770     if ($path !== '') {
1771 
1772       // cut ROOT from $path for security reason, even if hacker decodes the path he will not know the root
1773       $p = $this->_relpath($path);
1774       // if reqesting root dir $path will be empty, then assign '/' as we cannot leave it blank for crypt
1775       if ($p === '')  {
1776         $p = DIRECTORY_SEPARATOR;
1777       }
1778 
1779       // TODO crypt path and return hash
1780       $hash = $this->crypt($p);
1781       // hash is used as id in HTML that means it must contain vaild chars
1782       // make base64 html safe and append prefix in begining
1783       $hash = strtr(base64_encode($hash), '+/=', '-_.');
1784       // remove dots '.' at the end, before it was '=' in base64
1785       $hash = rtrim($hash, '.'); 
1786       // append volume id to make hash unique
1787       return $this->id.$hash;
1788     }
1789   }
1790   
1791   /**
1792    * Decode path from hash
1793    *
1794    * @param  string  file hash
1795    * @return string
1796    * @author Dmitry (dio) Levashov
1797    * @author Troex Nevelin
1798    **/
1799   protected function decode($hash) {
1800     if (strpos($hash, $this->id) === 0) {
1801       // cut volume id after it was prepended in encode
1802       $h = substr($hash, strlen($this->id));
1803       // replace HTML safe base64 to normal
1804       $h = base64_decode(strtr($h, '-_.', '+/='));
1805       // TODO uncrypt hash and return path
1806       $path = $this->uncrypt($h); 
1807       // append ROOT to path after it was cut in encode
1808       return $this->_abspath($path);//$this->root.($path == DIRECTORY_SEPARATOR ? '' : DIRECTORY_SEPARATOR.$path); 
1809     }
1810   }
1811   
1812   /**
1813    * Return crypted path 
1814    * Not implemented
1815    *
1816    * @param  string  path
1817    * @return mixed
1818    * @author Dmitry (dio) Levashov
1819    **/
1820   protected function crypt($path) {
1821     return $path;
1822   }
1823   
1824   /**
1825    * Return uncrypted path 
1826    * Not implemented
1827    *
1828    * @param  mixed  hash
1829    * @return mixed
1830    * @author Dmitry (dio) Levashov
1831    **/
1832   protected function uncrypt($hash) {
1833     return $hash;
1834   }
1835   
1836   /**
1837    * Validate file name based on $this->options['acceptedName'] regexp
1838    *
1839    * @param  string  $name  file name
1840    * @return bool
1841    * @author Dmitry (dio) Levashov
1842    **/
1843   protected function nameAccepted($name) {
1844     if ($this->nameValidator) {
1845       if (function_exists($this->nameValidator)) {
1846         $f = $this->nameValidator;
1847         return $f($name);
1848       }
1849       return preg_match($this->nameValidator, $name);
1850     }
1851     return true;
1852   }
1853   
1854   /**
1855    * Return new unique name based on file name and suffix
1856    *
1857    * @param  string  $path    file path
1858    * @param  string  $suffix  suffix append to name
1859    * @return string
1860    * @author Dmitry (dio) Levashov
1861    **/
1862   public function uniqueName($dir, $name, $suffix = ' copy', $checkNum=true) {
1863     $ext  = '';
1864 
1865     if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
1866       $ext  = '.'.$m[1];
1867       $name = substr($name, 0,  strlen($name)-strlen($m[0]));
1868     } 
1869     
1870     if ($checkNum && preg_match('/('.$suffix.')(\d*)$/i', $name, $m)) {
1871       $i    = (int)$m[2];
1872       $name = substr($name, 0, strlen($name)-strlen($m[2]));
1873     } else {
1874       $i     = 1;
1875       $name .= $suffix;
1876     }
1877     $max = $i+100000;
1878 
1879     while ($i <= $max) {
1880       $n = $name.($i > 0 ? $i : '').$ext;
1881 
1882       if (!$this->stat($this->_joinPath($dir, $n))) {
1883         $this->clearcache();
1884         return $n;
1885       }
1886       $i++;
1887     }
1888     return $name.md5($dir).$ext;
1889   }
1890   
1891   /*********************** file stat *********************/
1892   
1893   /**
1894    * Check file attribute
1895    *
1896    * @param  string  $path  file path
1897    * @param  string  $name  attribute name (read|write|locked|hidden)
1898    * @param  bool    $val   attribute value returned by file system
1899    * @return bool
1900    * @author Dmitry (dio) Levashov
1901    **/
1902   protected function attr($path, $name, $val=false) {
1903     if (!isset($this->defaults[$name])) {
1904       return false;
1905     }
1906     
1907     
1908     $perm = null;
1909     
1910     if ($this->access) {
1911       if (is_array($this->access)) {
1912         $obj    = $this->access[0];
1913         $method = $this->access[1];
1914         $perm   = $obj->{$method}($name, $path, $this->options['accessControlData'], $this);
1915       } else {
1916         $func = $this->access;
1917         $perm = $func($name, $path, $this->options['accessControlData'], $this);
1918       }
1919       
1920       if ($perm !== null) {
1921         return !!$perm;
1922       }
1923     }
1924     
1925     for ($i = 0, $c = count($this->attributes); $i < $c; $i++) {
1926       $attrs = $this->attributes[$i];
1927       $p = $this->separator.$this->_relpath($path);
1928       if (isset($attrs[$name]) && isset($attrs['pattern']) && preg_match($attrs['pattern'], $p)) {
1929         $perm = $attrs[$name];
1930       } 
1931     }
1932     
1933     return $perm === null ? $this->defaults[$name] : !!$perm;
1934   }
1935   
1936   /**
1937    * Return fileinfo 
1938    *
1939    * @param  string  $path  file cache
1940    * @return array
1941    * @author Dmitry (dio) Levashov
1942    **/
1943   protected function stat($path) {
1944     return isset($this->cache[$path])
1945       ? $this->cache[$path]
1946       : $this->updateCache($path, $this->_stat($path));
1947   }
1948   
1949   /**
1950    * Put file stat in cache and return it
1951    *
1952    * @param  string  $path   file path
1953    * @param  array   $stat   file stat
1954    * @return array
1955    * @author Dmitry (dio) Levashov
1956    **/
1957   protected function updateCache($path, $stat) {
1958     if (empty($stat) || !is_array($stat)) {
1959       return $this->cache[$path] = array();
1960     }
1961 
1962     $stat['hash'] = $this->encode($path);
1963 
1964     $root = $path == $this->root;
1965     
1966     if ($root) {
1967       $stat['volumeid'] = $this->id;
1968       if ($this->rootName) {
1969         $stat['name'] = $this->rootName;
1970       }
1971     } else {
1972       if (empty($stat['name'])) {
1973         $stat['name'] = $this->_basename($path);
1974       }
1975       if (empty($stat['phash'])) {
1976         $stat['phash'] = $this->encode($this->_dirname($path));
1977       }
1978     }
1979     
1980     // fix name if required
1981     if ($this->options['utf8fix'] && $this->options['utf8patterns'] && $this->options['utf8replace']) {
1982       $stat['name'] = json_decode(str_replace($this->options['utf8patterns'], $this->options['utf8replace'], json_encode($stat['name'])));
1983     }
1984     
1985     
1986     if (empty($stat['mime'])) {
1987       $stat['mime'] = $this->mimetype($stat['name']);
1988     }
1989     
1990     // @todo move dateformat to client
1991     $stat['date'] = isset($stat['ts'])
1992       ? $this->formatDate($stat['ts'])
1993       : 'unknown';
1994       
1995     if (!isset($stat['size'])) {
1996       $stat['size'] = 'unknown';
1997     } 
1998 
1999     $stat['read']  = intval($this->attr($path, 'read', isset($stat['read']) ? !!$stat['read'] : false));
2000     $stat['write'] = intval($this->attr($path, 'write', isset($stat['write']) ? !!$stat['write'] : false));
2001     if ($root) {
2002       $stat['locked'] = 1;
2003     } elseif ($this->attr($path, 'locked', !empty($stat['locked']))) {
2004       $stat['locked'] = 1;
2005     } else {
2006       unset($stat['locked']);
2007     }
2008 
2009     if ($root) {
2010       unset($stat['hidden']);
2011     } elseif ($this->attr($path, 'hidden', !empty($stat['hidden'])) 
2012     || !$this->mimeAccepted($stat['mime'])) {
2013       $stat['hidden'] = $root ? 0 : 1;
2014     } else {
2015       unset($stat['hidden']);
2016     }
2017     
2018     if ($stat['read'] && empty($stat['hidden'])) {
2019       
2020       if ($stat['mime'] == 'directory') {
2021         // for dir - check for subdirs
2022 
2023         if ($this->options['checkSubfolders']) {
2024           if (isset($stat['dirs'])) {
2025             if ($stat['dirs']) {
2026               $stat['dirs'] = 1;
2027             } else {
2028               unset($stat['dirs']);
2029             }
2030           } elseif (!empty($stat['alias']) && !empty($stat['target'])) {
2031             $stat['dirs'] = isset($this->cache[$stat['target']])
2032               ? intval(isset($this->cache[$stat['target']]['dirs']))
2033               : $this->_subdirs($stat['target']);
2034             
2035           } elseif ($this->_subdirs($path)) {
2036             $stat['dirs'] = 1;
2037           }
2038         } else {
2039           $stat['dirs'] = 1;
2040         }
2041       } else {
2042         // for files - check for thumbnails
2043         $p = isset($stat['target']) ? $stat['target'] : $path;
2044         if ($this->tmbURL && !isset($stat['tmb']) && $this->canCreateTmb($p, $stat)) {
2045           $tmb = $this->gettmb($p, $stat);
2046           $stat['tmb'] = $tmb ? $tmb : 1;
2047         }
2048         
2049       }
2050     }
2051     
2052     if (!empty($stat['alias']) && !empty($stat['target'])) {
2053       $stat['thash'] = $this->encode($stat['target']);
2054       unset($stat['target']);
2055     }
2056 
2057     return $this->cache[$path] = $stat;
2058   }
2059   
2060   /**
2061    * Get stat for folder content and put in cache
2062    *
2063    * @param  string  $path
2064    * @return void
2065    * @author Dmitry (dio) Levashov
2066    **/
2067   protected function cacheDir($path) {
2068     $this->dirsCache[$path] = array();
2069 
2070     foreach ($this->_scandir($path) as $p) {
2071       if (($stat = $this->stat($p)) && empty($stat['hidden'])) {
2072         $this->dirsCache[$path][] = $p;
2073       }
2074     } 
2075   }
2076   
2077   /**
2078    * Clean cache
2079    *
2080    * @return void
2081    * @author Dmitry (dio) Levashov
2082    **/
2083   protected function clearcache() {
2084     $this->cache = $this->dirsCache = array();
2085   }
2086   
2087   /**
2088    * Return file mimetype
2089    *
2090    * @param  string  $path  file path
2091    * @return string
2092    * @author Dmitry (dio) Levashov
2093    **/
2094   protected function mimetype($path) {
2095     $type = '';
2096     
2097     if ($this->mimeDetect == 'finfo') {
2098       $type = @finfo_file($this->finfo, $path); 
2099     } elseif ($type == 'mime_content_type') {
2100       $type = mime_content_type($path);
2101     } else {
2102       $type = elFinderVolumeDriver::mimetypeInternalDetect($path);
2103     }
2104     
2105     $type = explode(';', $type);
2106     $type = trim($type[0]);
2107     
2108     if ($type == 'application/x-empty') {
2109       // finfo return this mime for empty files
2110       $type = 'text/plain';
2111     } elseif ($type == 'application/x-zip') {
2112       // http://elrte.org/redmine/issues/163
2113       $type = 'application/zip';
2114     }
2115     
2116     return $type == 'unknown' && $this->mimeDetect != 'internal'
2117       ? elFinderVolumeDriver::mimetypeInternalDetect($path)
2118       : $type;
2119     
2120   }
2121   
2122   /**
2123    * Detect file mimetype using "internal" method
2124    *
2125    * @param  string  $path  file path
2126    * @return string
2127    * @author Dmitry (dio) Levashov
2128    **/
2129   static protected function mimetypeInternalDetect($path) {
2130     $pinfo = pathinfo($path); 
2131     $ext   = isset($pinfo['extension']) ? strtolower($pinfo['extension']) : '';
2132     return isset(elFinderVolumeDriver::$mimetypes[$ext]) ? elFinderVolumeDriver::$mimetypes[$ext] : 'unknown';
2133     
2134   }
2135   
2136   /**
2137    * Return file/total directory size
2138    *
2139    * @param  string  $path  file path
2140    * @return int
2141    * @author Dmitry (dio) Levashov
2142    **/
2143   protected function countSize($path) {
2144     $stat = $this->stat($path);
2145 
2146     if (empty($stat) || !$stat['read'] || !empty($stat['hidden'])) {
2147       return 'unknown';
2148     }
2149     
2150     if ($stat['mime'] != 'directory') {
2151       return $stat['size'];
2152     }
2153     
2154     $subdirs = $this->options['checkSubfolders'];
2155     $this->options['checkSubfolders'] = true;
2156     $result = 0;
2157     foreach ($this->getScandir($path) as $stat) {
2158       $size = $stat['mime'] == 'directory' && $stat['read'] 
2159         ? $this->countSize($this->_joinPath($path, $stat['name'])) 
2160         : $stat['size'];
2161       if ($size > 0) {
2162         $result += $size;
2163       }
2164     }
2165     $this->options['checkSubfolders'] = $subdirs;
2166     return $result;
2167   }
2168   
2169   /**
2170    * Return true if all mimes is directory or files
2171    *
2172    * @param  string  $mime1  mimetype
2173    * @param  string  $mime2  mimetype
2174    * @return bool
2175    * @author Dmitry (dio) Levashov
2176    **/
2177   protected function isSameType($mime1, $mime2) {
2178     return ($mime1 == 'directory' && $mime1 == $mime2) || ($mime1 != 'directory' && $mime2 != 'directory');
2179   }
2180   
2181   /**
2182    * If file has required attr == $val - return file path,
2183    * If dir has child with has required attr == $val - return child path
2184    *
2185    * @param  string   $path  file path
2186    * @param  string   $attr  attribute name
2187    * @param  bool     $val   attribute value
2188    * @return string|false
2189    * @author Dmitry (dio) Levashov
2190    **/
2191   protected function closestByAttr($path, $attr, $val) {
2192     $stat = $this->stat($path);
2193     
2194     if (empty($stat)) {
2195       return false;
2196     }
2197     
2198     $v = isset($stat[$attr]) ? $stat[$attr] : false;
2199     
2200     if ($v == $val) {
2201       return $path;
2202     }
2203 
2204     return $stat['mime'] == 'directory'
2205       ? $this->childsByAttr($path, $attr, $val) 
2206       : false;
2207   }
2208   
2209   /**
2210    * Return first found children with required attr == $val
2211    *
2212    * @param  string   $path  file path
2213    * @param  string   $attr  attribute name
2214    * @param  bool     $val   attribute value
2215    * @return string|false
2216    * @author Dmitry (dio) Levashov
2217    **/
2218   protected function childsByAttr($path, $attr, $val) {
2219     foreach ($this->_scandir($path) as $p) {
2220       if (($_p = $this->closestByAttr($p, $attr, $val)) != false) {
2221         return $_p;
2222       }
2223     }
2224     return false;
2225   }
2226   
2227   /*****************  get content *******************/
2228   
2229   /**
2230    * Return required dir's files info.
2231    * If onlyMimes is set - return only dirs and files of required mimes
2232    *
2233    * @param  string  $path  dir path
2234    * @return array
2235    * @author Dmitry (dio) Levashov
2236    **/
2237   protected function getScandir($path) {
2238     $files = array();
2239     
2240     !isset($this->dirsCache[$path]) && $this->cacheDir($path);
2241 
2242     foreach ($this->dirsCache[$path] as $p) {
2243       if (($stat = $this->stat($p)) && empty($stat['hidden'])) {
2244         $files[] = $stat;
2245       }
2246     }
2247 
2248     return $files;
2249   }
2250   
2251   
2252   /**
2253    * Return subdirs tree
2254    *
2255    * @param  string  $path  parent dir path
2256    * @param  int     $deep  tree deep
2257    * @return array
2258    * @author Dmitry (dio) Levashov
2259    **/
2260   protected function gettree($path, $deep, $exclude='') {
2261     $dirs = array();
2262     
2263     !isset($this->dirsCache[$path]) && $this->cacheDir($path);
2264 
2265     foreach ($this->dirsCache[$path] as $p) {
2266       $stat = $this->stat($p);
2267       
2268       if ($stat && empty($stat['hidden']) && $path != $exclude && $stat['mime'] == 'directory') {
2269         $dirs[] = $stat;
2270         if ($deep > 0 && !empty($stat['dirs'])) {
2271           $dirs = array_merge($dirs, $this->gettree($p, $deep-1));
2272         }
2273       }
2274     }
2275 
2276     return $dirs;
2277   } 
2278     
2279   /**
2280    * Recursive files search
2281    *
2282    * @param  string  $path   dir path
2283    * @param  string  $q      search string
2284    * @param  array   $mimes
2285    * @return array
2286    * @author Dmitry (dio) Levashov
2287    **/
2288   protected function doSearch($path, $q, $mimes) {
2289     $result = array();
2290 
2291     foreach($this->_scandir($path) as $p) {
2292       $stat = $this->stat($p);
2293 
2294       if (!$stat) { // invalid links
2295         continue;
2296       }
2297 
2298       if (!empty($stat['hidden']) || !$this->mimeAccepted($stat['mime'])) {
2299         continue;
2300       }
2301       
2302       $name = $stat['name'];
2303 
2304       if ($this->stripos($name, $q) !== false) {
2305         $stat['path'] = $this->_path($p);
2306         if ($this->URL && !isset($stat['url'])) {
2307           $stat['url'] = $this->URL . str_replace($this->separator, '/', substr($p, strlen($this->root) + 1));
2308         }
2309         
2310         $result[] = $stat;
2311       }
2312       if ($stat['mime'] == 'directory' && $stat['read'] && !isset($stat['alias'])) {
2313         $result = array_merge($result, $this->doSearch($p, $q, $mimes));
2314       }
2315     }
2316     
2317     return $result;
2318   }
2319     
2320   /**********************  manuipulations  ******************/
2321     
2322   /**
2323    * Copy file/recursive copy dir only in current volume.
2324    * Return new file path or false.
2325    *
2326    * @param  string  $src   source path
2327    * @param  string  $dst   destination dir path
2328    * @param  string  $name  new file name (optionaly)
2329    * @return string|false
2330    * @author Dmitry (dio) Levashov
2331    **/
2332   protected function copy($src, $dst, $name) {
2333     $srcStat = $this->stat($src);
2334     $this->clearcache();
2335     
2336     if (!empty($srcStat['thash'])) {
2337       $target = $this->decode($srcStat['thash']);
2338       $stat   = $this->stat($target);
2339       $this->clearcache();
2340       return $stat && $this->_symlink($target, $dst, $name)
2341         ? $this->_joinPath($dst, $name)
2342         : $this->setError(elFinder::ERROR_COPY, $this->_path($src));
2343     } 
2344     
2345     if ($srcStat['mime'] == 'directory') {
2346       $test = $this->stat($this->_joinPath($dst, $name));
2347       
2348       if (($test && $test['mime'] != 'directory') || !$this->_mkdir($dst, $name)) {
2349         return $this->setError(elFinder::ERROR_COPY, $this->_path($src));
2350       }
2351       
2352       $dst = $this->_joinPath($dst, $name);
2353       
2354       foreach ($this->getScandir($src) as $stat) {
2355         if (empty($stat['hidden'])) {
2356           $name = $stat['name'];
2357           if (!$this->copy($this->_joinPath($src, $name), $dst, $name)) {
2358             return false;
2359           }
2360         }
2361       }
2362       $this->clearcache();
2363       return $dst;
2364     } 
2365 
2366     return $this->_copy($src, $dst, $name) 
2367       ? $this->_joinPath($dst, $name) 
2368       : $this->setError(elFinder::ERROR_COPY, $this->_path($src));
2369   }
2370   
2371   /**
2372    * Move file
2373    * Return new file path or false.
2374    *
2375    * @param  string  $src   source path
2376    * @param  string  $dst   destination dir path
2377    * @param  string  $name  new file name 
2378    * @return string|false
2379    * @author Dmitry (dio) Levashov
2380    **/
2381   protected function move($src, $dst, $name) {
2382     $stat = $this->stat($src);
2383     $stat['realpath'] = $src;
2384     $this->clearcache();
2385     
2386     if ($this->_move($src, $dst, $name)) {
2387       $this->removed[] = $stat;
2388       return $this->_joinPath($dst, $name);
2389     }
2390     
2391     return $this->setError(elFinder::ERROR_MOVE, $this->_path($src));
2392   }
2393   
2394   /**
2395    * Copy file from another volume.
2396    * Return new file path or false.
2397    *
2398    * @param  Object  $volume       source volume
2399    * @param  string  $src          source file hash
2400    * @param  string  $destination  destination dir path
2401    * @param  string  $name         file name
2402    * @return string|false
2403    * @author Dmitry (dio) Levashov
2404    **/
2405   protected function copyFrom($volume, $src, $destination, $name) {
2406     
2407     if (($source = $volume->file($src)) == false) {
2408       return $this->setError(elFinder::ERROR_COPY, '#'.$src, $volume->error());
2409     }
2410     
2411     $errpath = $volume->path($src);
2412     
2413     if (!$this->nameAccepted($source['name'])) {
2414       return $this->setError(elFinder::ERROR_COPY, $errpath, elFinder::ERROR_INVALID_NAME);
2415     }
2416         
2417     if (!$source['read']) {
2418       return $this->setError(elFinder::ERROR_COPY, $errpath, elFinder::ERROR_PERM_DENIED);
2419     }
2420     
2421     if ($source['mime'] == 'directory') {
2422       $stat = $this->stat($this->_joinPath($destination, $name));
2423       $this->clearcache();
2424       if ((!$stat || $stat['mime'] != 'directory') && !$this->_mkdir($destination, $name)) {
2425         return $this->setError(elFinder::ERROR_COPY, $errpath);
2426       }
2427       
2428       $path = $this->_joinPath($destination, $name);
2429       
2430       foreach ($volume->scandir($src) as $entr) {
2431         if (!$this->copyFrom($volume, $entr['hash'], $path, $entr['name'])) {
2432           return false;
2433         }
2434       }
2435       
2436     } else {
2437       $mime = $source['mime'];
2438       $w = $h = 0;
2439       if (strpos($mime, 'image') === 0 && ($dim = $volume->dimensions($src))) {
2440         $s = explode('x', $dim);
2441         $w = $s[0];
2442         $h = $s[1];
2443       }
2444       
2445       if (($fp = $volume->open($src)) == false
2446       || ($path = $this->_save($fp, $destination, $name, $mime, $w, $h)) == false) {
2447         $fp && $volume->close($fp, $src);
2448         return $this->setError(elFinder::ERROR_COPY, $errpath);
2449       }
2450       $volume->close($fp, $src);
2451     }
2452     
2453     return $path;
2454   }
2455     
2456   /**
2457    * Remove file/ recursive remove dir
2458    *
2459    * @param  string  $path   file path
2460    * @param  bool    $force  try to remove even if file locked
2461    * @return bool
2462    * @author Dmitry (dio) Levashov
2463    **/
2464   protected function remove($path, $force = false) {
2465     $stat = $this->stat($path);
2466     $stat['realpath'] = $path;
2467     if (!empty($stat['tmb']) && $stat['tmb'] != "1") {
2468       $this->rmTmb($stat['tmb']);
2469     }
2470     $this->clearcache();
2471     
2472     if (empty($stat)) {
2473       return $this->setError(elFinder::ERROR_RM, $this->_path($path), elFinder::ERROR_FILE_NOT_FOUND);
2474     }
2475     
2476     if (!$force && !empty($stat['locked'])) {
2477       return $this->setError(elFinder::ERROR_LOCKED, $this->_path($path));
2478     }
2479     
2480     if ($stat['mime'] == 'directory') {
2481       foreach ($this->_scandir($path) as $p) {
2482         $name = $this->_basename($p);
2483         if ($name != '.' && $name != '..' && !$this->remove($p)) {
2484           return false;
2485         }
2486       }
2487       if (!$this->_rmdir($path)) {
2488         return $this->setError(elFinder::ERROR_RM, $this->_path($path));
2489       }
2490       
2491     } else {
2492       if (!$this->_unlink($path)) {
2493         return $this->setError(elFinder::ERROR_RM, $this->_path($path));
2494       }
2495     }
2496 
2497     $this->removed[] = $stat;
2498     return true;
2499   }
2500   
2501 
2502   /************************* thumbnails **************************/
2503     
2504   /**
2505    * Return thumbnail file name for required file
2506    *
2507    * @param  array  $stat  file stat
2508    * @return string
2509    * @author Dmitry (dio) Levashov
2510    **/
2511   protected function tmbname($stat) {
2512     return $stat['hash'].$stat['ts'].'.png';
2513   }
2514   
2515   /**
2516    * Return thumnbnail name if exists
2517    *
2518    * @param  string  $path file path
2519    * @param  array   $stat file stat
2520    * @return string|false
2521    * @author Dmitry (dio) Levashov
2522    **/
2523   protected function gettmb($path, $stat) {
2524     if ($this->tmbURL && $this->tmbPath) {
2525       // file itself thumnbnail
2526       if (strpos($path, $this->tmbPath) === 0) {
2527         return basename($path);
2528       }
2529 
2530       $name = $this->tmbname($stat);
2531       if (file_exists($this->tmbPath.DIRECTORY_SEPARATOR.$name)) {
2532         return $name;
2533       }
2534     }
2535     return false;
2536   }
2537   
2538   /**
2539    * Return true if thumnbnail for required file can be created
2540    *
2541    * @param  string  $path  thumnbnail path 
2542    * @param  array   $stat  file stat
2543    * @return string|bool
2544    * @author Dmitry (dio) Levashov
2545    **/
2546   protected function canCreateTmb($path, $stat) {
2547     return $this->tmbPathWritable 
2548       && strpos($path, $this->tmbPath) === false // do not create thumnbnail for thumnbnail
2549       && $this->imgLib 
2550       && strpos($stat['mime'], 'image') === 0 
2551       && ($this->imgLib == 'gd' ? $stat['mime'] == 'image/jpeg' || $stat['mime'] == 'image/png' || $stat['mime'] == 'image/gif' : true);
2552   }
2553   
2554   /**
2555    * Return true if required file can be resized.
2556    * By default - the same as canCreateTmb
2557    *
2558    * @param  string  $path  thumnbnail path 
2559    * @param  array   $stat  file stat
2560    * @return string|bool
2561    * @author Dmitry (dio) Levashov
2562    **/
2563   protected function canResize($path, $stat) {
2564     return $this->canCreateTmb($path, $stat);
2565   }
2566   
2567   /**
2568    * Create thumnbnail and return it's URL on success
2569    *
2570    * @param  string  $path  file path
2571    * @param  string  $mime  file mime type
2572    * @return string|false
2573    * @author Dmitry (dio) Levashov
2574    **/
2575   protected function createTmb($path, $stat) {
2576     if (!$stat || !$this->canCreateTmb($path, $stat)) {
2577       return false;
2578     }
2579 
2580     $name = $this->tmbname($stat);
2581     $tmb  = $this->tmbPath.DIRECTORY_SEPARATOR.$name;
2582 
2583     // copy image into tmbPath so some drivers does not store files on local fs
2584     if (($src = $this->_fopen($path, 'rb')) == false) {
2585       return false;
2586     }
2587 
2588     if (($trg = fopen($tmb, 'wb')) == false) {
2589       $this->_fclose($src, $path);
2590       return false;
2591     }
2592 
2593     while (!feof($src)) {
2594       fwrite($trg, fread($src, 8192));
2595     }
2596 
2597     $this->_fclose($src, $path);
2598     fclose($trg);
2599 
2600     $result = false;
2601     
2602     $tmbSize = $this->tmbSize;
2603     
2604       if (($s = getimagesize($tmb)) == false) {
2605       return false;
2606     }
2607     
2608       /* If image smaller or equal thumbnail size - just fitting to thumbnail square */
2609       if ($s[0] <= $tmbSize && $s[1]  <= $tmbSize) {
2610          $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png' );
2611 
2612       } else {
2613 
2614         if ($this->options['tmbCrop']) {
2615         
2616             /* Resize and crop if image bigger than thumbnail */
2617             if (!(($s[0] > $tmbSize && $s[1] <= $tmbSize) || ($s[0] <= $tmbSize && $s[1] > $tmbSize) ) || ($s[0] > $tmbSize && $s[1] > $tmbSize)) {
2618             $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, false, 'png');
2619             }
2620 
2621         if (($s = getimagesize($tmb)) != false) {
2622           $x = $s[0] > $tmbSize ? intval(($s[0] - $tmbSize)/2) : 0;
2623           $y = $s[1] > $tmbSize ? intval(($s[1] - $tmbSize)/2) : 0;
2624           $result = $this->imgCrop($tmb, $tmbSize, $tmbSize, $x, $y, 'png');
2625         }
2626 
2627         } else {
2628             $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, true, $this->imgLib, 'png');
2629             $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png' );
2630           }
2631 
2632     }
2633     if (!$result) {
2634       unlink($tmb);
2635       return false;
2636     }
2637 
2638     return $name;
2639   }
2640   
2641   /**
2642    * Resize image
2643    *
2644    * @param  string   $path               image file
2645    * @param  int      $width              new width
2646    * @param  int      $height             new height
2647    * @param  bool     $keepProportions    crop image
2648    * @param  bool     $resizeByBiggerSide resize image based on bigger side if true
2649    * @param  string   $destformat         image destination format
2650    * @return string|false
2651    * @author Dmitry (dio) Levashov
2652    * @author Alexey Sukhotin
2653    **/
2654     protected function imgResize($path, $width, $height, $keepProportions = false, $resizeByBiggerSide = true, $destformat = null) {
2655     if (($s = @getimagesize($path)) == false) {
2656       return false;
2657     }
2658 
2659       $result = false;
2660       
2661     list($size_w, $size_h) = array($width, $height);
2662     
2663       if ($keepProportions == true) {
2664            
2665           list($orig_w, $orig_h, $new_w, $new_h) = array($s[0], $s[1], $width, $height);
2666         
2667           /* Calculating image scale width and height */
2668           $xscale = $orig_w / $new_w;
2669           $yscale = $orig_h / $new_h;
2670 
2671           /* Resizing by biggest side */
2672 
2673       if ($resizeByBiggerSide) {
2674 
2675             if ($orig_w > $orig_h) {
2676           $size_h = $orig_h * $width / $orig_w;
2677           $size_w = $width;
2678             } else {
2679                 $size_w = $orig_w * $height / $orig_h;
2680                 $size_h = $height;
2681         }
2682       
2683       } else {
2684             if ($orig_w > $orig_h) {
2685                 $size_w = $orig_w * $height / $orig_h;
2686                 $size_h = $height;
2687             } else {
2688           $size_h = $orig_h * $width / $orig_w;
2689           $size_w = $width;
2690         }
2691       }
2692       }
2693 
2694     switch ($this->imgLib) {
2695       case 'imagick':
2696         
2697         try {
2698           $img = new imagick($path);
2699         } catch (Exception $e) {
2700 
2701           return false;
2702         }
2703 
2704         $img->resizeImage($size_w, $size_h, Imagick::FILTER_LANCZOS, true);
2705           
2706         $result = $img->writeImage($path);
2707 
2708         return $result ? $path : false;
2709 
2710         break;
2711 
2712       case 'gd':
2713         if ($s['mime'] == 'image/jpeg') {
2714           $img = imagecreatefromjpeg($path);
2715         } elseif ($s['mime'] == 'image/png') {
2716           $img = imagecreatefrompng($path);
2717         } elseif ($s['mime'] == 'image/gif') {
2718           $img = imagecreatefromgif($path);
2719         } elseif ($s['mime'] == 'image/xbm') {
2720           $img = imagecreatefromxbm($path);
2721         }
2722 
2723         if ($img &&  false != ($tmp = imagecreatetruecolor($size_w, $size_h))) {
2724           if (!imagecopyresampled($tmp, $img, 0, 0, 0, 0, $size_w, $size_h, $s[0], $s[1])) {
2725               return false;
2726           }
2727     
2728           if ($destformat == 'jpg'  || ($destformat == null && $s['mime'] == 'image/jpeg')) {
2729             $result = imagejpeg($tmp, $path, 100);
2730           } else if ($destformat == 'gif' || ($destformat == null && $s['mime'] == 'image/gif')) {
2731             $result = imagegif($tmp, $path, 7);
2732           } else {
2733             $result = imagepng($tmp, $path, 7);
2734           }
2735 
2736           imagedestroy($img);
2737           imagedestroy($tmp);
2738 
2739           return $result ? $path : false;
2740 
2741         }
2742         break;
2743     }
2744     
2745     return false;
2746     }
2747   
2748   /**
2749    * Crop image
2750    *
2751    * @param  string   $path               image file
2752    * @param  int      $width              crop width
2753    * @param  int      $height             crop height
2754    * @param  bool     $x                  crop left offset
2755    * @param  bool     $y                  crop top offset
2756    * @param  string   $destformat         image destination format
2757    * @return string|false
2758    * @author Dmitry (dio) Levashov
2759    * @author Alexey Sukhotin
2760    **/
2761     protected function imgCrop($path, $width, $height, $x, $y, $destformat = null) {
2762     if (($s = @getimagesize($path)) == false) {
2763       return false;
2764     }
2765 
2766     $result = false;
2767     
2768     switch ($this->imgLib) {
2769       case 'imagick':
2770         
2771         try {
2772           $img = new imagick($path);
2773         } catch (Exception $e) {
2774 
2775           return false;
2776         }
2777 
2778         $img->cropImage($width, $height, $x, $y);
2779 
2780         $result = $img->writeImage($path);
2781 
2782         return $result ? $path : false;
2783 
2784         break;
2785 
2786       case 'gd':
2787         if ($s['mime'] == 'image/jpeg') {
2788           $img = imagecreatefromjpeg($path);
2789         } elseif ($s['mime'] == 'image/png') {
2790           $img = imagecreatefrompng($path);
2791         } elseif ($s['mime'] == 'image/gif') {
2792           $img = imagecreatefromgif($path);
2793         } elseif ($s['mime'] == 'image/xbm') {
2794           $img = imagecreatefromxbm($path);
2795         }
2796 
2797         if ($img &&  false != ($tmp = imagecreatetruecolor($width, $height))) {
2798 
2799           if (!imagecopy($tmp, $img, 0, 0, $x, $y, $width, $height)) {
2800             return false;
2801           }
2802           
2803           if ($destformat == 'jpg'  || ($destformat == null && $s['mime'] == 'image/jpeg')) {
2804             $result = imagejpeg($tmp, $path, 100);
2805           } else if ($destformat == 'gif' || ($destformat == null && $s['mime'] == 'image/gif')) {
2806             $result = imagegif($tmp, $path, 7);
2807           } else {
2808             $result = imagepng($tmp, $path, 7);
2809           }
2810 
2811           imagedestroy($img);
2812           imagedestroy($tmp);
2813 
2814           return $result ? $path : false;
2815 
2816         }
2817         break;
2818     }
2819 
2820     return false;
2821   }
2822 
2823   /**
2824    * Put image to square
2825    *
2826    * @param  string   $path               image file
2827    * @param  int      $width              square width
2828    * @param  int      $height             square height
2829    * @param  int      $align              reserved
2830    * @param  int      $valign             reserved
2831    * @param  string   $bgcolor            square background color in #rrggbb format
2832    * @param  string   $destformat         image destination format
2833    * @return string|false
2834    * @author Dmitry (dio) Levashov
2835    * @author Alexey Sukhotin
2836    **/
2837     protected function imgSquareFit($path, $width, $height, $align = 'center', $valign = 'middle', $bgcolor = '#0000ff', $destformat = null) {
2838     if (($s = @getimagesize($path)) == false) {
2839       return false;
2840     }
2841 
2842     $result = false;
2843 
2844     /* Coordinates for image over square aligning */
2845     $y = ceil(abs($height - $s[1]) / 2); 
2846     $x = ceil(abs($width - $s[0]) / 2);
2847     
2848     switch ($this->imgLib) {
2849       case 'imagick':
2850         try {
2851           $img = new imagick($path);
2852         } catch (Exception $e) {
2853           return false;
2854         }
2855 
2856         $img1 = new Imagick();
2857         $img1->newImage($width, $height, new ImagickPixel($bgcolor));
2858         $img1->setImageColorspace($img->getImageColorspace());
2859         $img1->setImageFormat($destformat != null ? $destformat : $img->getFormat());
2860         $img1->compositeImage( $img, imagick::COMPOSITE_OVER, $x, $y );
2861         $result = $img1->writeImage($path);
2862         return $result ? $path : false;
2863 
2864         break;
2865 
2866       case 'gd':
2867         if ($s['mime'] == 'image/jpeg') {
2868           $img = imagecreatefromjpeg($path);
2869         } elseif ($s['mime'] == 'image/png') {
2870           $img = imagecreatefrompng($path);
2871         } elseif ($s['mime'] == 'image/gif') {
2872           $img = imagecreatefromgif($path);
2873         } elseif ($s['mime'] == 'image/xbm') {
2874           $img = imagecreatefromxbm($path);
2875         }
2876 
2877         if ($img &&  false != ($tmp = imagecreatetruecolor($width, $height))) {
2878 
2879           if ($bgcolor == 'transparent') {
2880             list($r, $g, $b) = array(0, 0, 255);
2881           } else {
2882             list($r, $g, $b) = sscanf($bgcolor, "#%02x%02x%02x");
2883           }
2884 
2885           $bgcolor1 = imagecolorallocate($tmp, $r, $g, $b);
2886             
2887           if ($bgcolor == 'transparent') {
2888             $bgcolor1 = imagecolortransparent($tmp, $bgcolor1);
2889           }
2890 
2891           imagefill($tmp, 0, 0, $bgcolor1);
2892 
2893           if (!imagecopy($tmp, $img, $x, $y, 0, 0, $s[0], $s[1])) {
2894             return false;
2895           }
2896 
2897           if ($destformat == 'jpg'  || ($destformat == null && $s['mime'] == 'image/jpeg')) {
2898             $result = imagejpeg($tmp, $path, 100);
2899           } else if ($destformat == 'gif' || ($destformat == null && $s['mime'] == 'image/gif')) {
2900             $result = imagegif($tmp, $path, 7);
2901           } else {
2902             $result = imagepng($tmp, $path, 7);
2903           }
2904 
2905           imagedestroy($img);
2906           imagedestroy($tmp);
2907 
2908           return $result ? $path : false;
2909         }
2910         break;
2911     }
2912 
2913     return false;
2914   }
2915 
2916   /**
2917    * Rotate image
2918    *
2919    * @param  string   $path               image file
2920    * @param  int      $degree             rotete degrees
2921    * @param  string   $bgcolor            square background color in #rrggbb format
2922    * @param  string   $destformat         image destination format
2923    * @return string|false
2924    * @author nao-pon
2925    * @author Troex Nevelin
2926    **/
2927   protected function imgRotate($path, $degree, $bgcolor = '#ffffff', $destformat = null) {
2928     if (($s = @getimagesize($path)) == false) {
2929       return false;
2930     }
2931 
2932     $result = false;
2933 
2934     switch ($this->imgLib) {
2935       case 'imagick':
2936         try {
2937           $img = new imagick($path);
2938         } catch (Exception $e) {
2939           return false;
2940         }
2941 
2942         $img->rotateImage(new ImagickPixel($bgcolor), $degree);
2943         $result = $img->writeImage($path);
2944         return $result ? $path : false;
2945 
2946         break;
2947 
2948       case 'gd':
2949         if ($s['mime'] == 'image/jpeg') {
2950           $img = imagecreatefromjpeg($path);
2951         } elseif ($s['mime'] == 'image/png') {
2952           $img = imagecreatefrompng($path);
2953         } elseif ($s['mime'] == 'image/gif') {
2954           $img = imagecreatefromgif($path);
2955         } elseif ($s['mime'] == 'image/xbm') {
2956           $img = imagecreatefromxbm($path);
2957         }
2958 
2959         $degree = 360 - $degree;
2960         list($r, $g, $b) = sscanf($bgcolor, "#%02x%02x%02x");
2961         $bgcolor = imagecolorallocate($img, $r, $g, $b);
2962         $tmp = imageRotate($img, $degree, (int)$bgcolor);
2963 
2964         if ($destformat == 'jpg' || ($destformat == null && $s['mime'] == 'image/jpeg')) {
2965           $result = imagejpeg($tmp, $path, 100);
2966         } else if ($destformat == 'gif' || ($destformat == null && $s['mime'] == 'image/gif')) {
2967           $result = imagegif($tmp, $path, 7);
2968         } else {
2969           $result = imagepng($tmp, $path, 7);
2970         }
2971 
2972         imageDestroy($img);
2973         imageDestroy($tmp);
2974 
2975         return $result ? $path : false;
2976 
2977         break;
2978     }
2979 
2980     return false;
2981   }
2982 
2983   /**
2984    * Execute shell command
2985    *
2986    * @param  string  $command       command line
2987    * @param  array   $output        stdout strings
2988    * @param  array   $return_var    process exit code
2989    * @param  array   $error_output  stderr strings
2990    * @return int     exit code
2991    * @author Alexey Sukhotin
2992    **/
2993   protected function procExec($command , array &$output = null, &$return_var = -1, array &$error_output = null) {
2994 
2995     $descriptorspec = array(
2996       0 => array("pipe", "r"),  // stdin
2997       1 => array("pipe", "w"),  // stdout
2998       2 => array("pipe", "w")   // stderr
2999     );
3000 
3001     $process = proc_open($command, $descriptorspec, $pipes, null, null);
3002 
3003     if (is_resource($process)) {
3004 
3005       fclose($pipes[0]);
3006 
3007       $tmpout = '';
3008       $tmperr = '';
3009 
3010       $output = stream_get_contents($pipes[1]);
3011       $error_output = stream_get_contents($pipes[2]);
3012 
3013       fclose($pipes[1]);
3014       fclose($pipes[2]);
3015       $return_var = proc_close($process);
3016 
3017 
3018     }
3019     
3020     return $return_var;
3021     
3022   }
3023   
3024   /**
3025    * Remove thumbnail
3026    *
3027    * @param  string  $path  file path
3028    * @return void
3029    * @author Dmitry (dio) Levashov
3030    **/
3031   protected function rmTmb($tmb) {
3032     $tmb = $this->tmbPath.DIRECTORY_SEPARATOR.$tmb;
3033     file_exists($tmb) && @unlink($tmb);
3034     clearstatcache();
3035   }
3036 
3037   /*********************** misc *************************/
3038   
3039   /**
3040    * Return smart formatted date
3041    *
3042    * @param  int     $ts  file timestamp
3043    * @return string
3044    * @author Dmitry (dio) Levashov
3045    **/
3046   protected function formatDate($ts) {
3047     if ($ts > $this->today) {
3048       return 'Today '.date($this->options['timeFormat'], $ts);
3049     }
3050     
3051     if ($ts > $this->yesterday) {
3052       return 'Yesterday '.date($this->options['timeFormat'], $ts);
3053     } 
3054     
3055     return date($this->options['dateFormat'], $ts);
3056   }
3057 
3058   /**
3059   * Find position of first occurrence of string in a string with multibyte support
3060   *
3061   * @param  string  $haystack  The string being checked.
3062   * @param  string  $needle    The string to find in haystack.
3063   * @param  int     $offset    The search offset. If it is not specified, 0 is used.
3064   * @return int|bool
3065   * @author Alexey Sukhotin
3066   **/
3067   protected function stripos($haystack , $needle , $offset = 0) {
3068     if (function_exists('mb_stripos')) {
3069       return mb_stripos($haystack , $needle , $offset);
3070     } else if (function_exists('mb_strtolower') && function_exists('mb_strpos')) {
3071       return mb_strpos(mb_strtolower($haystack), mb_strtolower($needle), $offset);
3072     } 
3073     return stripos($haystack , $needle , $offset);
3074   }
3075 
3076   /**==================================* abstract methods *====================================**/
3077   
3078   /*********************** paths/urls *************************/
3079   
3080   /**
3081    * Return parent directory path
3082    *
3083    * @param  string  $path  file path
3084    * @return string
3085    * @author Dmitry (dio) Levashov
3086    **/
3087   abstract protected function _dirname($path);
3088 
3089   /**
3090    * Return file name
3091    *
3092    * @param  string  $path  file path
3093    * @return string
3094    * @author Dmitry (dio) Levashov
3095    **/
3096   abstract protected function _basename($path);
3097 
3098   /**
3099    * Join dir name and file name and return full path.
3100    * Some drivers (db) use int as path - so we give to concat path to driver itself
3101    *
3102    * @param  string  $dir   dir path
3103    * @param  string  $name  file name
3104    * @return string
3105    * @author Dmitry (dio) Levashov
3106    **/
3107   abstract protected function _joinPath($dir, $name);
3108 
3109   /**
3110    * Return normalized path 
3111    *
3112    * @param  string  $path  file path
3113    * @return string
3114    * @author Dmitry (dio) Levashov
3115    **/
3116   abstract protected function _normpath($path);
3117 
3118   /**
3119    * Return file path related to root dir
3120    *
3121    * @param  string  $path  file path
3122    * @return string
3123    * @author Dmitry (dio) Levashov
3124    **/
3125   abstract protected function _relpath($path);
3126   
3127   /**
3128    * Convert path related to root dir into real path
3129    *
3130    * @param  string  $path  rel file path
3131    * @return string
3132    * @author Dmitry (dio) Levashov
3133    **/
3134   abstract protected function _abspath($path);
3135   
3136   /**
3137    * Return fake path started from root dir.
3138    * Required to show path on client side.
3139    *
3140    * @param  string  $path  file path
3141    * @return string
3142    * @author Dmitry (dio) Levashov
3143    **/
3144   abstract protected function _path($path);
3145   
3146   /**
3147    * Return true if $path is children of $parent
3148    *
3149    * @param  string  $path    path to check
3150    * @param  string  $parent  parent path
3151    * @return bool
3152    * @author Dmitry (dio) Levashov
3153    **/
3154   abstract protected function _inpath($path, $parent);
3155   
3156   /**
3157    * Return stat for given path.
3158    * Stat contains following fields:
3159    * - (int)    size    file size in b. required
3160    * - (int)    ts      file modification time in unix time. required
3161    * - (string) mime    mimetype. required for folders, others - optionally
3162    * - (bool)   read    read permissions. required
3163    * - (bool)   write   write permissions. required
3164    * - (bool)   locked  is object locked. optionally
3165    * - (bool)   hidden  is object hidden. optionally
3166    * - (string) alias   for symlinks - link target path relative to root path. optionally
3167    * - (string) target  for symlinks - link target path. optionally
3168    *
3169    * If file does not exists - returns empty array or false.
3170    *
3171    * @param  string  $path    file path 
3172    * @return array|false
3173    * @author Dmitry (dio) Levashov
3174    **/
3175   abstract protected function _stat($path);
3176   
3177 
3178   /***************** file stat ********************/
3179 
3180     
3181   /**
3182    * Return true if path is dir and has at least one childs directory
3183    *
3184    * @param  string  $path  dir path
3185    * @return bool
3186    * @author Dmitry (dio) Levashov
3187    **/
3188   abstract protected function _subdirs($path);
3189   
3190   /**
3191    * Return object width and height
3192    * Ususaly used for images, but can be realize for video etc...
3193    *
3194    * @param  string  $path  file path
3195    * @param  string  $mime  file mime type
3196    * @return string
3197    * @author Dmitry (dio) Levashov
3198    **/
3199   abstract protected function _dimensions($path, $mime);
3200   
3201   /******************** file/dir content *********************/
3202 
3203   /**
3204    * Return files list in directory
3205    *
3206    * @param  string  $path  dir path
3207    * @return array
3208    * @author Dmitry (dio) Levashov
3209    **/
3210   abstract protected function _scandir($path);
3211   
3212   /**
3213    * Open file and return file pointer
3214    *
3215    * @param  string  $path  file path
3216    * @param  bool    $write open file for writing
3217    * @return resource|false
3218    * @author Dmitry (dio) Levashov
3219    **/
3220   abstract protected function _fopen($path, $mode="rb");
3221   
3222   /**
3223    * Close opened file
3224    * 
3225    * @param  resource  $fp    file pointer
3226    * @param  string    $path  file path
3227    * @return bool
3228    * @author Dmitry (dio) Levashov
3229    **/
3230   abstract protected function _fclose($fp, $path='');
3231   
3232   /********************  file/dir manipulations *************************/
3233   
3234   /**
3235    * Create dir and return created dir path or false on failed
3236    *
3237    * @param  string  $path  parent dir path
3238    * @param string  $name  new directory name
3239    * @return string|bool
3240    * @author Dmitry (dio) Levashov
3241    **/
3242   abstract protected function _mkdir($path, $name);
3243   
3244   /**
3245    * Create file and return it's path or false on failed
3246    *
3247    * @param  string  $path  parent dir path
3248    * @param string  $name  new file name
3249    * @return string|bool
3250    * @author Dmitry (dio) Levashov
3251    **/
3252   abstract protected function _mkfile($path, $name);
3253   
3254   /**
3255    * Create symlink
3256    *
3257    * @param  string  $source     file to link to
3258    * @param  string  $targetDir  folder to create link in
3259    * @param  string  $name       symlink name
3260    * @return bool
3261    * @author Dmitry (dio) Levashov
3262    **/
3263   abstract protected function _symlink($source, $targetDir, $name);
3264   
3265   /**
3266    * Copy file into another file (only inside one volume)
3267    *
3268    * @param  string  $source  source file path
3269    * @param  string  $target  target dir path
3270    * @param  string  $name    file name
3271    * @return bool
3272    * @author Dmitry (dio) Levashov
3273    **/
3274   abstract protected function _copy($source, $targetDir, $name);
3275   
3276   /**
3277    * Move file into another parent dir.
3278    * Return new file path or false.
3279    *
3280    * @param  string  $source  source file path
3281    * @param  string  $target  target dir path
3282    * @param  string  $name    file name
3283    * @return string|bool
3284    * @author Dmitry (dio) Levashov
3285    **/
3286   abstract protected function _move($source, $targetDir, $name);
3287   
3288   /**
3289    * Remove file
3290    *
3291    * @param  string  $path  file path
3292    * @return bool
3293    * @author Dmitry (dio) Levashov
3294    **/
3295   abstract protected function _unlink($path);
3296 
3297   /**
3298    * Remove dir
3299    *
3300    * @param  string  $path  dir path
3301    * @return bool
3302    * @author Dmitry (dio) Levashov
3303    **/
3304   abstract protected function _rmdir($path);
3305 
3306   /**
3307    * Create new file and write into it from file pointer.
3308    * Return new file path or false on error.
3309    *
3310    * @param  resource  $fp   file pointer
3311    * @param  string    $dir  target dir path
3312    * @param  string    $name file name
3313    * @return bool|string
3314    * @author Dmitry (dio) Levashov
3315    **/
3316   abstract protected function _save($fp, $dir, $name, $mime, $w, $h);
3317   
3318   /**
3319    * Get file contents
3320    *
3321    * @param  string  $path  file path
3322    * @return string|false
3323    * @author Dmitry (dio) Levashov
3324    **/
3325   abstract protected function _getContents($path);
3326   
3327   /**
3328    * Write a string to a file
3329    *
3330    * @param  string  $path     file path
3331    * @param  string  $content  new file content
3332    * @return bool
3333    * @author Dmitry (dio) Levashov
3334    **/
3335   abstract protected function _filePutContents($path, $content);
3336 
3337   /**
3338    * Extract files from archive
3339    *
3340    * @param  string  $path file path
3341    * @param  array   $arc  archiver options
3342    * @return bool
3343    * @author Dmitry (dio) Levashov, 
3344    * @author Alexey Sukhotin
3345    **/
3346   abstract protected function _extract($path, $arc);
3347 
3348   /**
3349    * Create archive and return its path
3350    *
3351    * @param  string  $dir    target dir
3352    * @param  array   $files  files names list
3353    * @param  string  $name   archive name
3354    * @param  array   $arc    archiver options
3355    * @return string|bool
3356    * @author Dmitry (dio) Levashov, 
3357    * @author Alexey Sukhotin
3358    **/
3359   abstract protected function _archive($dir, $files, $name, $arc);
3360 
3361   /**
3362    * Detect available archivers
3363    *
3364    * @return void
3365    * @author Dmitry (dio) Levashov, 
3366    * @author Alexey Sukhotin
3367    **/
3368   abstract protected function _checkArchivers();
3369   
3370 } // END class