OSDN Git Service

初回コミット(v2.6.17.1)
[magic3/magic3.git] / scripts / elfinder-2.0-rc1 / php / elFinderVolumeDriver.class.php
1 <?php
2 /**
3  * Base class for elFinder volume.
4  * Provide 2 layers:
5  *  1. Public API (commands)
6  *  2. abstract fs API
7  *
8  * All abstract methods begin with "_"
9  *
10  * @author Dmitry (dio) Levashov
11  * @author Troex Nevelin
12  * @author Alexey Sukhotin
13  **/
14 abstract class elFinderVolumeDriver {
15         
16         /**
17          * Driver id
18          * Must be started from letter and contains [a-z0-9]
19          * Used as part of volume id
20          *
21          * @var string
22          **/
23         protected $driverId = 'a';
24         
25         /**
26          * Volume id - used as prefix for files hashes
27          *
28          * @var string
29          **/
30         protected $id = '';
31         
32         /**
33          * Flag - volume "mounted" and available
34          *
35          * @var bool
36          **/
37         protected $mounted = false;
38         
39         /**
40          * Root directory path
41          *
42          * @var string
43          **/
44         protected $root = '';
45         
46         /**
47          * Root basename | alias
48          *
49          * @var string
50          **/
51         protected $rootName = '';
52         
53         /**
54          * Default directory to open
55          *
56          * @var string
57          **/
58         protected $startPath = '';
59         
60         /**
61          * Base URL
62          *
63          * @var string
64          **/
65         protected $URL = '';
66         
67         /**
68          * Thumbnails dir path
69          *
70          * @var string
71          **/
72         protected $tmbPath = '';
73         
74         /**
75          * Is thumbnails dir writable
76          *
77          * @var bool
78          **/
79         protected $tmbPathWritable = false;
80         
81         /**
82          * Thumbnails base URL
83          *
84          * @var string
85          **/
86         protected $tmbURL = '';
87         
88         /**
89          * Thumbnails size in px
90          *
91          * @var int
92          **/
93         protected $tmbSize = 48;
94         
95         /**
96          * Image manipulation lib name
97          * auto|imagick|mogtify|gd
98          *
99          * @var string
100          **/
101         protected $imgLib = 'auto';
102         
103         /**
104          * Library to crypt files name
105          *
106          * @var string
107          **/
108         protected $cryptLib = '';
109         
110         /**
111          * Archivers config
112          *
113          * @var array
114          **/
115         protected $archivers = array(
116                 'create'  => array(),
117                 'extract' => array()
118         );
119         
120         /**
121          * How many subdirs levels return for tree
122          *
123          * @var int
124          **/
125         protected $treeDeep = 1;
126         
127         /**
128          * Errors from last failed action
129          *
130          * @var array
131          **/
132         protected $error = array();
133         
134         /**
135          * Today 24:00 timestamp
136          *
137          * @var int
138          **/
139         protected $today = 0;
140         
141         /**
142          * Yesterday 24:00 timestamp
143          *
144          * @var int
145          **/
146         protected $yesterday = 0;
147         
148         /**
149          * Object configuration
150          *
151          * @var array
152          **/
153         protected $options = array(
154                 'id'              => '',
155                 // root directory path
156                 'path'            => '',
157                 // open this path on initial request instead of root path
158                 'startPath'       => '',
159                 // how many subdirs levels return per request
160                 'treeDeep'        => 1,
161                 // root url, not set to disable sending URL to client (replacement for old "fileURL" option)
162                 'URL'             => '',
163                 // directory separator. required by client to show paths correctly
164                 'separator'       => DIRECTORY_SEPARATOR,
165                 // library to crypt/uncrypt files names (not implemented)
166                 'cryptLib'        => '',
167                 // how to detect files mimetypes. (auto/internal/finfo/mime_content_type)
168                 'mimeDetect'      => 'auto',
169                 // mime.types file path (for mimeDetect==internal)
170                 'mimefile'        => '',
171                 // directory for thumbnails
172                 'tmbPath'         => '.tmb',
173                 // mode to create thumbnails dir
174                 'tmbPathMode'     => 0777,
175                 // thumbnails dir URL. Set it if store thumbnails outside root directory
176                 'tmbURL'          => '',
177                 // thumbnails size (px)
178                 'tmbSize'         => 48,
179                 // thumbnails crop (true - crop, false - scale image to fit thumbnail size)
180                 'tmbCrop'         => true,
181                 // thumbnails background color (hex #rrggbb or 'transparent')
182                 'tmbBgColor'      => '#ffffff',
183                 // image manipulations library
184                 'imgLib'          => 'auto',
185                 // on paste file -  if true - old file will be replaced with new one, if false new file get name - original_name-number.ext
186                 'copyOverwrite'   => true,
187                 // if true - join new and old directories content on paste
188                 'copyJoin'        => true,
189                 // on upload -  if true - old file will be replaced with new one, if false new file get name - original_name-number.ext
190                 'uploadOverwrite' => true,
191                 // mimetypes allowed to upload
192                 'uploadAllow'     => array(),
193                 // mimetypes not allowed to upload
194                 'uploadDeny'      => array(),
195                 // order to proccess uploadAllow and uploadDeny options
196                 'uploadOrder'     => array('deny', 'allow'),
197                 // maximum upload file size. NOTE - this is size for every uploaded files
198                 'uploadMaxSize'   => 0,
199                 // files dates format
200                 'dateFormat'      => 'j M Y H:i',
201                 // files time format
202                 'timeFormat'      => 'H:i',
203                 // if true - every folder will be check for children folders, otherwise all folders will be marked as having subfolders
204                 'checkSubfolders' => true,
205                 // allow to copy from this volume to other ones?
206                 'copyFrom'        => true,
207                 // allow to copy from other volumes to this one?
208                 'copyTo'          => true,
209                 // list of commands disabled on this root
210                 'disabled'        => array(),
211                 // regexp or function name to validate new file name
212                 'acceptedName'    => '/^\w[\w\s\.\%\-\(\)\[\]]*$/u',
213                 // function/class method to control files permissions
214                 'accessControl'   => null,
215                 // some data required by access control
216                 'accessControlData' => null,
217                 // default permissions. not set hidden/locked here - take no effect
218                 'defaults'     => array(
219                         'read'   => true,
220                         'write'  => true
221                 ),
222                 // files attributes
223                 'attributes'   => array(),
224                 // Allowed archive's mimetypes to create. Leave empty for all available types.
225                 'archiveMimes' => array(),
226                 // Manual config for archivers. See example below. Leave empty for auto detect
227                 'archivers'    => array(),
228                 // required to fix bug on macos
229                 'utf8fix'      => false,
230                  //                           й                 ё              Й               Ё              Ø         Å
231                 'utf8patterns' => array("\u0438\u0306", "\u0435\u0308", "\u0418\u0306", "\u0415\u0308", "\u00d8A", "\u030a"),
232                 'utf8replace'  => array("\u0439",        "\u0451",       "\u0419",       "\u0401",       "\u00d8", "\u00c5")
233         );
234
235         /**
236          * Defaults permissions
237          *
238          * @var array
239          **/
240         protected $defaults = array(
241                 'read'   => true,
242                 'write'  => true,
243                 'locked' => false,
244                 'hidden' => false
245         );
246         
247         /**
248          * Access control function/class
249          *
250          * @var mixed
251          **/
252         protected $attributes = array();
253         
254         /**
255          * Access control function/class
256          *
257          * @var mixed
258          **/
259         protected $access = null;
260         
261         /**
262          * Mime types allowed to upload
263          *
264          * @var array
265          **/
266         protected $uploadAllow = array();
267         
268         /**
269          * Mime types denied to upload
270          *
271          * @var array
272          **/
273         protected $uploadDeny = array();
274         
275         /**
276          * Order to validate uploadAllow and uploadDeny
277          *
278          * @var array
279          **/
280         protected $uploadOrder = array();
281         
282         /**
283          * Maximum allowed upload file size.
284          * Set as number or string with unit - "10M", "500K", "1G"
285          *
286          * @var int|string
287          **/
288         protected $uploadMaxSize = 0;
289         
290         /**
291          * Mimetype detect method
292          *
293          * @var string
294          **/
295         protected $mimeDetect = 'auto';
296         
297         /**
298          * Flag - mimetypes from externail file was loaded
299          *
300          * @var bool
301          **/
302         private static $mimetypesLoaded = false;
303         
304         /**
305          * Finfo object for mimeDetect == 'finfo'
306          *
307          * @var object
308          **/
309         protected $finfo = null;
310         
311         /**
312          * List of disabled client's commands
313          *
314          * @var array
315          **/
316         protected $diabled = array();
317         
318         /**
319          * default extensions/mimetypes for mimeDetect == 'internal' 
320          *
321          * @var array
322          **/
323         protected static $mimetypes = array(
324                 // applications
325                 'ai'    => 'application/postscript',
326                 'eps'   => 'application/postscript',
327                 'exe'   => 'application/x-executable',
328                 'doc'   => 'application/vnd.ms-word',
329                 'xls'   => 'application/vnd.ms-excel',
330                 'ppt'   => 'application/vnd.ms-powerpoint',
331                 'pps'   => 'application/vnd.ms-powerpoint',
332                 'pdf'   => 'application/pdf',
333                 'xml'   => 'application/xml',
334                 'odt'   => 'application/vnd.oasis.opendocument.text',
335                 'swf'   => 'application/x-shockwave-flash',
336                 'torrent' => 'application/x-bittorrent',
337                 'jar'   => 'application/x-jar',
338                 // archives
339                 'gz'    => 'application/x-gzip',
340                 'tgz'   => 'application/x-gzip',
341                 'bz'    => 'application/x-bzip2',
342                 'bz2'   => 'application/x-bzip2',
343                 'tbz'   => 'application/x-bzip2',
344                 'zip'   => 'application/zip',
345                 'rar'   => 'application/x-rar',
346                 'tar'   => 'application/x-tar',
347                 '7z'    => 'application/x-7z-compressed',
348                 // texts
349                 'txt'   => 'text/plain',
350                 'php'   => 'text/x-php',
351                 'html'  => 'text/html',
352                 'htm'   => 'text/html',
353                 'js'    => 'text/javascript',
354                 'css'   => 'text/css',
355                 'rtf'   => 'text/rtf',
356                 'rtfd'  => 'text/rtfd',
357                 'py'    => 'text/x-python',
358                 'java'  => 'text/x-java-source',
359                 'rb'    => 'text/x-ruby',
360                 'sh'    => 'text/x-shellscript',
361                 'pl'    => 'text/x-perl',
362                 'xml'   => 'text/xml',
363                 'sql'   => 'text/x-sql',
364                 'c'     => 'text/x-csrc',
365                 'h'     => 'text/x-chdr',
366                 'cpp'   => 'text/x-c++src',
367                 'hh'    => 'text/x-c++hdr',
368                 'log'   => 'text/plain',
369                 'csv'   => 'text/x-comma-separated-values',
370                 // images
371                 'bmp'   => 'image/x-ms-bmp',
372                 'jpg'   => 'image/jpeg',
373                 'jpeg'  => 'image/jpeg',
374                 'gif'   => 'image/gif',
375                 'png'   => 'image/png',
376                 'tif'   => 'image/tiff',
377                 'tiff'  => 'image/tiff',
378                 'tga'   => 'image/x-targa',
379                 'psd'   => 'image/vnd.adobe.photoshop',
380                 'ai'    => 'image/vnd.adobe.photoshop',
381                 'xbm'   => 'image/xbm',
382                 'pxm'   => 'image/pxm',
383                 //audio
384                 'mp3'   => 'audio/mpeg',
385                 'mid'   => 'audio/midi',
386                 'ogg'   => 'audio/ogg',
387                 'oga'   => 'audio/ogg',
388                 'm4a'   => 'audio/x-m4a',
389                 'wav'   => 'audio/wav',
390                 'wma'   => 'audio/x-ms-wma',
391                 // video
392                 'avi'   => 'video/x-msvideo',
393                 'dv'    => 'video/x-dv',
394                 'mp4'   => 'video/mp4',
395                 'mpeg'  => 'video/mpeg',
396                 'mpg'   => 'video/mpeg',
397                 'mov'   => 'video/quicktime',
398                 'wm'    => 'video/x-ms-wmv',
399                 'flv'   => 'video/x-flv',
400                 'mkv'   => 'video/x-matroska',
401                 'webm'  => 'video/webm',
402                 'ogv'   => 'video/ogg',
403                 'ogm'   => 'video/ogg'
404                 );
405         
406         /**
407          * Directory separator - required by client
408          *
409          * @var string
410          **/
411         protected $separator = DIRECTORY_SEPARATOR;
412         
413         /**
414          * Mimetypes allowed to display
415          *
416          * @var array
417          **/
418         protected $onlyMimes = array();
419         
420         /**
421          * Store files moved or overwrited files info
422          *
423          * @var array
424          **/
425         protected $removed = array();
426         
427         /**
428          * Cache storage
429          *
430          * @var array
431          **/
432         protected $cache = array();
433         
434         /**
435          * Cache by folders
436          *
437          * @var array
438          **/
439         protected $dirsCache = array();
440         
441         /*********************************************************************/
442         /*                            INITIALIZATION                         */
443         /*********************************************************************/
444         
445         /**
446          * Prepare driver before mount volume.
447          * Return true if volume is ready.
448          *
449          * @return bool
450          * @author Dmitry (dio) Levashov
451          **/
452         protected function init() {
453                 return true;
454         }       
455                 
456         /**
457          * Configure after successfull mount.
458          * By default set thumbnails path and image manipulation library.
459          *
460          * @return void
461          * @author Dmitry (dio) Levashov
462          **/
463         protected function configure() {
464                 // set thumbnails path
465                 $path = $this->options['tmbPath'];
466                 if ($path) {
467                         if (!file_exists($path)) {
468                                 if (@mkdir($path)) {
469                                         chmod($path, $this->options['tmbPathMode']);
470                                 } else {
471                                         $path = '';
472                                 }
473                         } 
474                         
475                         if (is_dir($path) && is_readable($path)) {
476                                 $this->tmbPath = $path;
477                                 $this->tmbPathWritable = is_writable($path);
478                         }
479                 }
480
481                 // set image manipulation library
482                 $type = preg_match('/^(imagick|gd|auto)$/i', $this->options['imgLib'])
483                         ? strtolower($this->options['imgLib'])
484                         : 'auto';
485
486                 if (($type == 'imagick' || $type == 'auto') && extension_loaded('imagick')) {
487                         $this->imgLib = 'imagick';
488                 } else {
489                         $this->imgLib = function_exists('gd_info') ? 'gd' : '';
490                 }
491                 
492         }
493         
494         
495         /*********************************************************************/
496         /*                              PUBLIC API                           */
497         /*********************************************************************/
498         
499         /**
500          * Return driver id. Used as a part of volume id.
501          *
502          * @return string
503          * @author Dmitry (dio) Levashov
504          **/
505         public function driverId() {
506                 return $this->driverId;
507         }
508         
509         /**
510          * Return volume id
511          *
512          * @return string
513          * @author Dmitry (dio) Levashov
514          **/
515         public function id() {
516                 return $this->id;
517         }
518                 
519         /**
520          * Return debug info for client
521          *
522          * @return array
523          * @author Dmitry (dio) Levashov
524          **/
525         public function debug() {
526                 return array(
527                         'id'         => $this->id(),
528                         'name'       => strtolower(substr(get_class($this), strlen('elfinderdriver'))),
529                         'mimeDetect' => $this->mimeDetect,
530                         'imgLib'     => $this->imgLib
531                 );
532         }
533         
534         /**
535          * "Mount" volume.
536          * Return true if volume available for read or write, 
537          * false - otherwise
538          *
539          * @return bool
540          * @author Dmitry (dio) Levashov
541          * @author Alexey Sukhotin
542          **/
543         public function mount(array $opts) {
544                 if (!isset($opts['path']) || $opts['path'] === '') {
545                         return false;
546                 }
547                 
548                 $this->options = array_merge($this->options, $opts);
549                 $this->id = $this->driverId.(!empty($this->options['id']) ? $this->options['id'] : elFinder::$volumesCnt++).'_';
550                 $this->root = $this->_normpath($this->options['path']);
551                 $this->separator = isset($this->options['separator']) ? $this->options['separator'] : DIRECTORY_SEPARATOR;
552                 
553                 // default file attribute
554                 $this->defaults = array(
555                         'read'    => isset($this->options['defaults']['read'])  ? !!$this->options['defaults']['read']  : true,
556                         'write'   => isset($this->options['defaults']['write']) ? !!$this->options['defaults']['write'] : true,
557                         'locked'  => false,
558                         'hidden'  => false
559                 );
560
561                 // root attributes
562                 $this->attributes[] = array(
563                         'pattern' => '~^'.preg_quote(DIRECTORY_SEPARATOR).'$~',
564                         'locked'  => true,
565                         'hidden'  => false
566                 );
567                 // set files attributes
568                 if (!empty($this->options['attributes']) && is_array($this->options['attributes'])) {
569                         
570                         foreach ($this->options['attributes'] as $a) {
571                                 // attributes must contain pattern and at least one rule
572                                 if (!empty($a['pattern']) || count($a) > 1) {
573                                         $this->attributes[] = $a;
574                                 }
575                         }
576                 }
577
578                 if (!empty($this->options['accessControl'])) {
579                         if (is_string($this->options['accessControl']) 
580                         && function_exists($this->options['accessControl'])) {
581                                 $this->access = $this->options['accessControl'];
582                         } elseif (is_array($this->options['accessControl']) 
583                         && count($this->options['accessControl']) > 1 
584                         && is_object($this->options['accessControl'][0])
585                         && method_exists($this->options['accessControl'][0], $this->options['accessControl'][1])) {
586                                 $this->access = array($this->options['accessControl'][0], $this->options['accessControl'][1]);
587                         }
588                 }
589                 
590                 $this->today     = mktime(0,0,0, date('m'), date('d'), date('Y'));
591                 $this->yesterday = $this->today-86400;
592                 
593                 // debug($this->attributes);
594                 if (!$this->init()) {
595                         return false;
596                 }
597                 
598                 // check some options is arrays
599                 $this->uploadAllow = isset($this->options['uploadAllow']) && is_array($this->options['uploadAllow'])
600                         ? $this->options['uploadAllow']
601                         : array();
602                         
603                 $this->uploadDeny = isset($this->options['uploadDeny']) && is_array($this->options['uploadDeny'])
604                         ? $this->options['uploadDeny']
605                         : array();
606
607                 if (is_string($this->options['uploadOrder'])) { // telephat_mode on, compatibility with 1.x
608                         $parts = explode(',', isset($this->options['uploadOrder']) ? $this->options['uploadOrder'] : 'deny,allow');
609                         $this->uploadOrder = array(trim($parts[0]), trim($parts[1]));
610                 } else { // telephat_mode off
611                         $this->uploadOrder = $this->options['uploadOrder'];
612                 }
613                         
614                 if (!empty($this->options['uploadMaxSize'])) {
615                         $size = ''.$this->options['uploadMaxSize'];
616                         $unit = strtolower(substr($size, strlen($size) - 1));
617                         $n = 1;
618                         switch ($unit) {
619                                 case 'k':
620                                         $n = 1024;
621                                         break;
622                                 case 'm':
623                                         $n = 1048576;
624                                         break;
625                                 case 'g':
626                                         $n = 1073741824;
627                         }
628                         $this->uploadMaxSize = intval($size)*$n;
629                 }
630                         
631                 $this->disabled = isset($this->options['disabled']) && is_array($this->options['disabled'])
632                         ? $this->options['disabled']
633                         : array();
634                 
635                 $this->cryptLib   = $this->options['cryptLib'];
636                 $this->mimeDetect = $this->options['mimeDetect'];
637
638                 // find available mimetype detect method
639                 $type = strtolower($this->options['mimeDetect']);
640                 $type = preg_match('/^(finfo|mime_content_type|internal|auto)$/i', $type) ? $type : 'auto';
641                 $regexp = '/text\/x\-(php|c\+\+)/';
642                 
643                 if (($type == 'finfo' || $type == 'auto') 
644                 && class_exists('finfo')
645                 && preg_match($regexp, array_shift(explode(';', @finfo_file(finfo_open(FILEINFO_MIME), __FILE__))))) {
646                         $type = 'finfo';
647                         $this->finfo = finfo_open(FILEINFO_MIME);
648                 } elseif (($type == 'mime_content_type' || $type == 'auto') 
649                 && function_exists('mime_content_type')
650                 && preg_match($regexp, array_shift(explode(';', mime_content_type(__FILE__))))) {
651                         $type = 'mime_content_type';
652                 } else {
653                         $type = 'internal';
654                 }
655                 $this->mimeDetect = $type;
656
657                 // load mimes from external file for mimeDetect == 'internal'
658                 // based on Alexey Sukhotin idea and patch: http://elrte.org/redmine/issues/163
659                 // file must be in file directory or in parent one 
660                 if ($this->mimeDetect == 'internal' && !self::$mimetypesLoaded) {
661                         self::$mimetypesLoaded = true;
662                         $this->mimeDetect = 'internal';
663                         $file = false;
664                         if (!empty($this->options['mimefile']) && file_exists($this->options['mimefile'])) {
665                                 $file = $this->options['mimefile'];
666                         } elseif (file_exists(dirname(__FILE__).DIRECTORY_SEPARATOR.'mime.types')) {
667                                 $file = dirname(__FILE__).DIRECTORY_SEPARATOR.'mime.types';
668                         } elseif (file_exists(dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'mime.types')) {
669                                 $file = dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'mime.types';
670                         }
671
672                         if ($file && file_exists($file)) {
673                                 $mimecf = file($file);
674
675                                 foreach ($mimecf as $line_num => $line) {
676                                         if (!preg_match('/^\s*#/', $line)) {
677                                                 $mime = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
678                                                 for ($i = 1, $size = count($mime); $i < $size ; $i++) {
679                                                         if (!isset(self::$mimetypes[$mime[$i]])) {
680                                                                 self::$mimetypes[$mime[$i]] = $mime[0];
681                                                         }
682                                                 }
683                                         }
684                                 }
685                         }
686                 }
687
688                 $this->rootName = empty($this->options['alias']) ? $this->_basename($this->root) : $this->options['alias'];
689                 $root = $this->stat($this->root);
690                 
691                 if (!$root) {
692                         return $this->setError('Root folder does not exists.');
693                 }
694                 if (!$root['read'] && !$root['write']) {
695                         return $this->setError('Root folder has not read and write permissions.');
696                 }
697                 
698                 // debug($root);
699                 
700                 if ($root['read']) {
701                         // check startPath - path to open by default instead of root
702                         if ($this->options['startPath']) {
703                                 $start = $this->stat($this->options['startPath']);
704                                 if (!empty($start)
705                                 && $start['mime'] == 'directory'
706                                 && $start['read']
707                                 && empty($start['hidden'])
708                                 && $this->_inpath($this->options['startPath'], $this->root)) {
709                                         $this->startPath = $this->options['startPath'];
710                                         if (substr($this->startPath, -1, 1) == $this->options['separator']) {
711                                                 $this->startPath = substr($this->startPath, 0, -1);
712                                         }
713                                 }
714                         }
715                 } else {
716                         $this->options['URL']     = '';
717                         $this->options['tmbURL']  = '';
718                         $this->options['tmbPath'] = '';
719                         // read only volume
720                         array_unshift($this->attributes, array(
721                                 'pattern' => '/.*/',
722                                 'read'    => false
723                         ));
724                 }
725                 $this->treeDeep = $this->options['treeDeep'] > 0 ? (int)$this->options['treeDeep'] : 1;
726                 $this->tmbSize  = $this->options['tmbSize'] > 0 ? (int)$this->options['tmbSize'] : 48;
727                 $this->URL      = $this->options['URL'];
728                 if ($this->URL && preg_match("|[^/?&=]$|", $this->URL)) {
729                         $this->URL .= '/';
730                 }
731
732                 $this->tmbURL   = !empty($this->options['tmbURL']) ? $this->options['tmbURL'] : '';
733                 if ($this->tmbURL && preg_match("|[^/?&=]$|", $this->tmbURL)) {
734                         $this->tmbURL .= '/';
735                 }
736                 
737                 $this->nameValidator = is_string($this->options['acceptedName']) && !empty($this->options['acceptedName']) 
738                         ? $this->options['acceptedName']
739                         : '';
740
741                 $this->_checkArchivers();
742                 // manual control archive types to create
743                 if (!empty($this->options['archiveMimes']) && is_array($this->options['archiveMimes'])) {
744                         foreach ($this->archivers['create'] as $mime => $v) {
745                                 if (!in_array($mime, $this->options['archiveMimes'])) {
746                                         unset($this->archivers['create'][$mime]);
747                                 }
748                         }
749                 }
750                 
751                 // manualy add archivers
752                 if (!empty($this->options['archivers']['create']) && is_array($this->options['archivers']['create'])) {
753                         foreach ($this->options['archivers']['create'] as $mime => $conf) {
754                                 if (strpos($mime, 'application/') === 0 
755                                 && !empty($conf['cmd']) 
756                                 && isset($conf['argc']) 
757                                 && !empty($conf['ext'])
758                                 && !isset($this->archivers['create'][$mime])) {
759                                         $this->archivers['create'][$mime] = $conf;
760                                 }
761                         }
762                 }
763                 
764                 if (!empty($this->options['archivers']['extract']) && is_array($this->options['archivers']['extract'])) {
765                         foreach ($this->options['archivers']['extract'] as $mime => $conf) {
766                                 if (substr($mime, 'application/') === 0 
767                                 && !empty($cons['cmd']) 
768                                 && isset($conf['argc']) 
769                                 && !empty($conf['ext'])
770                                 && !isset($this->archivers['extract'][$mime])) {
771                                         $this->archivers['extract'][$mime] = $conf;
772                                 }
773                         }
774                 }
775
776                 $this->configure();
777                 // echo $this->uploadMaxSize;
778                 // echo $this->options['uploadMaxSize'];
779                 return $this->mounted = true;
780         }
781         
782         /**
783          * Some "unmount" stuffs - may be required by virtual fs
784          *
785          * @return void
786          * @author Dmitry (dio) Levashov
787          **/
788         public function umount() {
789         }
790         
791         /**
792          * Return error message from last failed action
793          *
794          * @return array
795          * @author Dmitry (dio) Levashov
796          **/
797         public function error() {
798                 return $this->error;
799         }
800         
801         /**
802          * Set mimetypes allowed to display to client
803          *
804          * @param  array  $mimes
805          * @return void
806          * @author Dmitry (dio) Levashov
807          **/
808         public function setMimesFilter($mimes) {
809                 if (is_array($mimes)) {
810                         $this->onlyMimes = $mimes;
811                 }
812         }
813         
814         /**
815          * Return root folder hash
816          *
817          * @return string
818          * @author Dmitry (dio) Levashov
819          **/
820         public function root() {
821                 return $this->encode($this->root);
822         }
823         
824         /**
825          * Return root or startPath hash
826          *
827          * @return string
828          * @author Dmitry (dio) Levashov
829          **/
830         public function defaultPath() {
831                 return $this->encode($this->startPath ? $this->startPath : $this->root);
832         }
833                 
834         /**
835          * Return volume options required by client:
836          *
837          * @return array
838          * @author Dmitry (dio) Levashov
839          **/
840         public function options($hash) {
841                 return array(
842                         'path'          => $this->_path($this->decode($hash)),
843                         'url'           => $this->URL,
844                         'tmbUrl'        => $this->tmbURL,
845                         'disabled'      => $this->disabled,
846                         'separator'     => $this->separator,
847                         'copyOverwrite' => intval($this->options['copyOverwrite']),
848                         'archivers'     => array(
849                                 'create'  => array_keys($this->archivers['create']),
850                                 'extract' => array_keys($this->archivers['extract'])
851                         )
852                 );
853         }
854         
855         /**
856          * Return true if command disabled in options
857          *
858          * @param  string  $cmd  command name
859          * @return bool
860          * @author Dmitry (dio) Levashov
861          **/
862         public function commandDisabled($cmd) {
863                 return in_array($cmd, $this->disabled);
864         }
865         
866         /**
867          * Return true if mime is required mimes list
868          *
869          * @param  string     $mime   mime type to check
870          * @param  array      $mimes  allowed mime types list or not set to use client mimes list
871          * @param  bool|null  $empty  what to return on empty list
872          * @return bool|null
873          * @author Dmitry (dio) Levashov
874          * @author Troex Nevelin
875          **/
876         public function mimeAccepted($mime, $mimes = array(), $empty = true) {
877                 $mimes = !empty($mimes) ? $mimes : $this->onlyMimes;
878                 if (empty($mimes)) {
879                         return $empty;
880                 }
881                 return $mime == 'directory'
882                         || in_array('all', $mimes)
883                         || in_array('All', $mimes)
884                         || in_array($mime, $mimes)
885                         || in_array(substr($mime, 0, strpos($mime, '/')), $mimes);
886         }
887         
888         /**
889          * Return true if voume is readable.
890          *
891          * @return bool
892          * @author Dmitry (dio) Levashov
893          **/
894         public function isReadable() {
895                 $stat = $this->stat($this->root);
896                 return $stat['read'];
897         }
898         
899         /**
900          * Return true if copy from this volume allowed
901          *
902          * @return bool
903          * @author Dmitry (dio) Levashov
904          **/
905         public function copyFromAllowed() {
906                 return !!$this->options['copyFrom'];
907         }
908         
909         /**
910          * Return file path related to root
911          *
912          * @param  string   $hash  file hash
913          * @return string
914          * @author Dmitry (dio) Levashov
915          **/
916         public function path($hash) {
917                 return $this->_path($this->decode($hash));
918         }
919         
920         /**
921          * Return file real path if file exists
922          *
923          * @param  string  $hash  file hash
924          * @return string
925          * @author Dmitry (dio) Levashov
926          **/
927         public function realpath($hash) {
928                 $path = $this->decode($hash);
929                 return $this->stat($path) ? $path : false;
930         }
931         
932         /**
933          * Return list of moved/overwrited files
934          *
935          * @return array
936          * @author Dmitry (dio) Levashov
937          **/
938         public function removed() {
939                 return $this->removed;
940         }
941         
942         /**
943          * Clean removed files list
944          *
945          * @return void
946          * @author Dmitry (dio) Levashov
947          **/
948         public function resetRemoved() {
949                 $this->removed = array();
950         }
951         
952         /**
953          * Return file/dir hash or first founded child hash with required attr == $val
954          *
955          * @param  string   $hash  file hash
956          * @param  string   $attr  attribute name
957          * @param  bool     $val   attribute value
958          * @return string|false
959          * @author Dmitry (dio) Levashov
960          **/
961         public function closest($hash, $attr, $val) {
962                 return ($path = $this->closestByAttr($this->decode($hash), $attr, $val)) ? $this->encode($path) : false;
963         }
964         
965         /**
966          * Return file info or false on error
967          *
968          * @param  string   $hash      file hash
969          * @param  bool     $realpath  add realpath field to file info
970          * @return array|false
971          * @author Dmitry (dio) Levashov
972          **/
973         public function file($hash) {
974                 $path = $this->decode($hash);
975                 
976                 return ($file = $this->stat($path)) ? $file : $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
977                 
978                 if (($file = $this->stat($path)) != false) {
979                         if ($realpath) {
980                                 $file['realpath'] = $path;
981                         }
982                         return $file;
983                 }
984                 return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
985         }
986         
987         /**
988          * Return folder info
989          *
990          * @param  string   $hash  folder hash
991          * @param  bool     $hidden  return hidden file info
992          * @return array|false
993          * @author Dmitry (dio) Levashov
994          **/
995         public function dir($hash, $resolveLink=false) {
996                 if (($dir = $this->file($hash)) == false) {
997                         return $this->setError(elFinder::ERROR_DIR_NOT_FOUND);
998                 }
999
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