PluginProbe ʕ •ᴥ•ʔ
Advanced File Manager – Ultimate File Manager for WordPress And Document Library Solution / trunk
Advanced File Manager – Ultimate File Manager for WordPress And Document Library Solution vtrunk
trunk 4.1.5 4.1.6 5.0 5.2.13 5.2.14 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.4.0 5.4.1 5.4.10 5.4.11 5.4.12 5.4.2 5.4.3 5.4.4 5.4.5 5.4.6 5.4.7 5.4.8
file-manager-advanced / application / library / php / elFinderVolumeFTP.class.php
file-manager-advanced / application / library / php Last commit date
.tmp 10 months ago editors 4 weeks ago libs 7 years ago plugins 3 years ago resources 7 years ago MySQLStorage.sql 1 year ago autoload.php 10 months ago connector.maximal.php-dist 1 year ago connector.minimal.php-dist 1 year ago elFinder.class.php 4 weeks ago elFinderConnector.class.php 4 weeks ago elFinderFlysystemGoogleDriveNetmount.php 5 years ago elFinderPlugin.php 7 years ago elFinderSession.php 4 years ago elFinderSessionInterface.php 7 years ago elFinderVolumeBox.class.php 5 years ago elFinderVolumeDriver.class.php 4 weeks ago elFinderVolumeDropbox.class.php 1 year ago elFinderVolumeDropbox2.class.php 4 weeks ago elFinderVolumeFTP.class.php 5 years ago elFinderVolumeGoogleDrive.class.php 5 years ago elFinderVolumeGroup.class.php 7 years ago elFinderVolumeLocalFileSystem.class.php 2 years ago elFinderVolumeMySQL.class.php 10 months ago elFinderVolumeOneDrive.class.php 4 weeks ago elFinderVolumeSFTPphpseclib.class.php 4 weeks ago elFinderVolumeTrash.class.php 7 years ago elFinderVolumeTrashMySQL.class.php 7 years ago mime.types 3 years ago
elFinderVolumeFTP.class.php
1811 lines
1 <?php
2
3 /**
4 * Simple elFinder driver for FTP
5 *
6 * @author Dmitry (dio) Levashov
7 * @author Cem (discofever)
8 **/
9 class elFinderVolumeFTP extends elFinderVolumeDriver
10 {
11
12 /**
13 * Driver id
14 * Must be started from letter and contains [a-z0-9]
15 * Used as part of volume id
16 *
17 * @var string
18 **/
19 protected $driverId = 'f';
20
21 /**
22 * FTP Connection Instance
23 *
24 * @var resource a FTP stream
25 **/
26 protected $connect = null;
27
28 /**
29 * Directory for tmp files
30 * If not set driver will try to use tmbDir as tmpDir
31 *
32 * @var string
33 **/
34 protected $tmpPath = '';
35
36 /**
37 * Last FTP error message
38 *
39 * @var string
40 **/
41 protected $ftpError = '';
42
43 /**
44 * FTP server output list as ftp on linux
45 *
46 * @var bool
47 **/
48 protected $ftpOsUnix;
49
50 /**
51 * FTP LIST command option
52 *
53 * @var string
54 */
55 protected $ftpListOption = '-al';
56
57
58 /**
59 * Is connected server Pure FTPd?
60 *
61 * @var bool
62 */
63 protected $isPureFtpd = false;
64
65 /**
66 * Is connected server with FTPS?
67 *
68 * @var bool
69 */
70 protected $isFTPS = false;
71
72 /**
73 * Tmp folder path
74 *
75 * @var string
76 **/
77 protected $tmp = '';
78
79 /**
80 * FTP command `MLST` support
81 *
82 * @var bool
83 */
84 private $MLSTsupprt = false;
85
86 /**
87 * Calling cacheDir() target path with non-MLST
88 *
89 * @var string
90 */
91 private $cacheDirTarget = '';
92
93 /**
94 * Constructor
95 * Extend options with required fields
96 *
97 * @author Dmitry (dio) Levashov
98 * @author Cem (DiscoFever)
99 */
100 public function __construct()
101 {
102 $opts = array(
103 'host' => 'localhost',
104 'user' => '',
105 'pass' => '',
106 'port' => 21,
107 'mode' => 'passive',
108 'ssl' => false,
109 'path' => '/',
110 'timeout' => 20,
111 'owner' => true,
112 'tmbPath' => '',
113 'tmpPath' => '',
114 'separator' => '/',
115 'checkSubfolders' => -1,
116 'dirMode' => 0755,
117 'fileMode' => 0644,
118 'rootCssClass' => 'elfinder-navbar-root-ftp',
119 'ftpListOption' => '-al',
120 );
121 $this->options = array_merge($this->options, $opts);
122 $this->options['mimeDetect'] = 'internal';
123 }
124
125 /**
126 * Prepare
127 * Call from elFinder::netmout() before volume->mount()
128 *
129 * @param $options
130 *
131 * @return array volume root options
132 * @author Naoki Sawada
133 */
134 public function netmountPrepare($options)
135 {
136 if (!empty($_REQUEST['encoding']) && iconv('UTF-8', $_REQUEST['encoding'], '') !== false) {
137 $options['encoding'] = $_REQUEST['encoding'];
138 if (!empty($_REQUEST['locale']) && setlocale(LC_ALL, $_REQUEST['locale'])) {
139 setlocale(LC_ALL, elFinder::$locale);
140 $options['locale'] = $_REQUEST['locale'];
141 }
142 }
143 if (!empty($_REQUEST['FTPS'])) {
144 $options['ssl'] = true;
145 }
146 $options['statOwner'] = true;
147 $options['allowChmodReadOnly'] = true;
148 $options['acceptedName'] = '#^[^/\\?*:|"<>]*[^./\\?*:|"<>]$#';
149 return $options;
150 }
151
152 /*********************************************************************/
153 /* INIT AND CONFIGURE */
154 /*********************************************************************/
155
156 /**
157 * Prepare FTP connection
158 * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn
159 *
160 * @return bool
161 * @author Dmitry (dio) Levashov
162 * @author Cem (DiscoFever)
163 **/
164 protected function init()
165 {
166 if (!$this->options['host']
167 || !$this->options['port']) {
168 return $this->setError('Required options undefined.');
169 }
170
171 if (!$this->options['user']) {
172 $this->options['user'] = 'anonymous';
173 $this->options['pass'] = '';
174 }
175 if (!$this->options['path']) {
176 $this->options['path'] = '/';
177 }
178
179 // make ney mount key
180 $this->netMountKey = md5(join('-', array('ftp', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user'])));
181
182 if (!function_exists('ftp_connect')) {
183 return $this->setError('FTP extension not loaded.');
184 }
185
186 // remove protocol from host
187 $scheme = parse_url($this->options['host'], PHP_URL_SCHEME);
188
189 if ($scheme) {
190 $this->options['host'] = substr($this->options['host'], strlen($scheme) + 3);
191 }
192
193 // normalize root path
194 $this->root = $this->options['path'] = $this->_normpath($this->options['path']);
195
196 if (empty($this->options['alias'])) {
197 $this->options['alias'] = $this->options['user'] . '@' . $this->options['host'];
198 if (!empty($this->options['netkey'])) {
199 elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']);
200 }
201 }
202
203 $this->rootName = $this->options['alias'];
204 $this->options['separator'] = '/';
205
206 if (is_null($this->options['syncChkAsTs'])) {
207 $this->options['syncChkAsTs'] = true;
208 }
209
210 if (isset($this->options['ftpListOption'])) {
211 $this->ftpListOption = $this->options['ftpListOption'];
212 }
213
214 return $this->needOnline? $this->connect() : true;
215
216 }
217
218
219 /**
220 * Configure after successfull mount.
221 *
222 * @return void
223 * @throws elFinderAbortException
224 * @author Dmitry (dio) Levashov
225 */
226 protected function configure()
227 {
228 parent::configure();
229
230 if (!empty($this->options['tmpPath'])) {
231 if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
232 $this->tmp = $this->options['tmpPath'];
233 }
234 }
235 if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
236 $this->tmp = $tmp;
237 }
238
239 // fallback of $this->tmp
240 if (!$this->tmp && $this->tmbPathWritable) {
241 $this->tmp = $this->tmbPath;
242 }
243
244 if (!$this->tmp) {
245 $this->disabled[] = 'mkfile';
246 $this->disabled[] = 'paste';
247 $this->disabled[] = 'duplicate';
248 $this->disabled[] = 'upload';
249 $this->disabled[] = 'edit';
250 $this->disabled[] = 'archive';
251 $this->disabled[] = 'extract';
252 }
253
254 // echo $this->tmp;
255
256 }
257
258 /**
259 * Connect to ftp server
260 *
261 * @return bool
262 * @author Dmitry (dio) Levashov
263 **/
264 protected function connect()
265 {
266 $withSSL = empty($this->options['ssl']) ? '' : ' with SSL';
267 if ($withSSL) {
268 if (!function_exists('ftp_ssl_connect') || !($this->connect = ftp_ssl_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) {
269 return $this->setError('Unable to connect to FTP server ' . $this->options['host'] . $withSSL);
270 }
271 $this->isFTPS = true;
272 } else {
273 if (!($this->connect = ftp_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) {
274 return $this->setError('Unable to connect to FTP server ' . $this->options['host']);
275 }
276 }
277 if (!ftp_login($this->connect, $this->options['user'], $this->options['pass'])) {
278 $this->umount();
279 return $this->setError('Unable to login into ' . $this->options['host'] . $withSSL);
280 }
281
282 // try switch utf8 mode
283 if ($this->encoding) {
284 ftp_raw($this->connect, 'OPTS UTF8 OFF');
285 } else {
286 ftp_raw($this->connect, 'OPTS UTF8 ON');
287 }
288
289 $help = ftp_raw($this->connect, 'HELP');
290 $this->isPureFtpd = stripos(implode(' ', $help), 'Pure-FTPd') !== false;
291
292 if (!$this->isPureFtpd) {
293 // switch off extended passive mode - may be usefull for some servers
294 // this command, for pure-ftpd, doesn't work and takes a timeout in some pure-ftpd versions
295 ftp_raw($this->connect, 'epsv4 off');
296 }
297 // enter passive mode if required
298 $pasv = ($this->options['mode'] == 'passive');
299 if (!ftp_pasv($this->connect, $pasv)) {
300 if ($pasv) {
301 $this->options['mode'] = 'active';
302 }
303 }
304
305 // enter root folder
306 if (!ftp_chdir($this->connect, $this->root)
307 || $this->root != ftp_pwd($this->connect)) {
308 $this->umount();
309 return $this->setError('Unable to open root folder.');
310 }
311
312 // check for MLST support
313 $features = ftp_raw($this->connect, 'FEAT');
314 if (!is_array($features)) {
315 $this->umount();
316 return $this->setError('Server does not support command FEAT.');
317 }
318
319 foreach ($features as $feat) {
320 if (strpos(trim($feat), 'MLST') === 0) {
321 $this->MLSTsupprt = true;
322 break;
323 }
324 }
325
326 return true;
327 }
328
329 /**
330 * Call ftp_rawlist with option prefix
331 *
332 * @param string $path
333 *
334 * @return array
335 */
336 protected function ftpRawList($path)
337 {
338 if ($this->isPureFtpd) {
339 $path = str_replace(' ', '\ ', $path);
340 }
341 if ($this->ftpListOption) {
342 $path = $this->ftpListOption . ' ' . $path;
343 }
344 $res = ftp_rawlist($this->connect, $path);
345 if ($res === false) {
346 $res = array();
347 }
348 return $res;
349 }
350
351 /*********************************************************************/
352 /* FS API */
353 /*********************************************************************/
354
355 /**
356 * Close opened connection
357 *
358 * @return void
359 * @author Dmitry (dio) Levashov
360 **/
361 public function umount()
362 {
363 $this->connect && ftp_close($this->connect);
364 }
365
366
367 /**
368 * Parse line from ftp_rawlist() output and return file stat (array)
369 *
370 * @param string $raw line from ftp_rawlist() output
371 * @param $base
372 * @param bool $nameOnly
373 *
374 * @return array
375 * @author Dmitry Levashov
376 */
377 protected function parseRaw($raw, $base, $nameOnly = false)
378 {
379 static $now;
380 static $lastyear;
381
382 if (!$now) {
383 $now = time();
384 $lastyear = date('Y') - 1;
385 }
386
387 $info = preg_split("/\s+/", $raw, 8);
388 if (isset($info[7])) {
389 list($info[7], $info[8]) = explode(' ', $info[7], 2);
390 }
391 $stat = array();
392
393 if (!isset($this->ftpOsUnix)) {
394 $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
395 }
396 if (!$this->ftpOsUnix) {
397 $info = $this->normalizeRawWindows($raw);
398 }
399
400 if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') {
401 return false;
402 }
403
404 $name = $info[8];
405
406 if (preg_match('|(.+)\-\>(.+)|', $name, $m)) {
407 $name = trim($m[1]);
408 // check recursive processing
409 if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) {
410 return array();
411 }
412 if (!$nameOnly) {
413 $target = trim($m[2]);
414 if (substr($target, 0, 1) !== $this->separator) {
415 $target = $this->getFullPath($target, $base);
416 }
417 $target = $this->_normpath($target);
418 $stat['name'] = $name;
419 $stat['target'] = $target;
420 return $stat;
421 }
422 }
423
424 if ($nameOnly) {
425 return array('name' => $name);
426 }
427
428 if (is_numeric($info[5]) && !$info[6] && !$info[7]) {
429 // by normalizeRawWindows()
430 $stat['ts'] = $info[5];
431 } else {
432 $stat['ts'] = strtotime($info[5] . ' ' . $info[6] . ' ' . $info[7]);
433 if ($stat['ts'] && $stat['ts'] > $now && strpos($info[7], ':') !== false) {
434 $stat['ts'] = strtotime($info[5] . ' ' . $info[6] . ' ' . $lastyear . ' ' . $info[7]);
435 }
436 if (empty($stat['ts'])) {
437 $stat['ts'] = strtotime($info[6] . ' ' . $info[5] . ' ' . $info[7]);
438 if ($stat['ts'] && $stat['ts'] > $now && strpos($info[7], ':') !== false) {
439 $stat['ts'] = strtotime($info[6] . ' ' . $info[5] . ' ' . $lastyear . ' ' . $info[7]);
440 }
441 }
442 }
443
444 if ($this->options['statOwner']) {
445 $stat['owner'] = $info[2];
446 $stat['group'] = $info[3];
447 $stat['perm'] = substr($info[0], 1);
448 //
449 // if not exists owner in LS ftp ==> isowner = true
450 // if is defined as option : 'owner' => true isowner = true
451 //
452 // if exist owner in LS ftp and 'owner' => False isowner = result of owner(file) == user(logged with ftp)
453 //
454 $stat['isowner'] = isset($stat['owner']) ? ($this->options['owner'] ? true : ($stat['owner'] == $this->options['user'])) : true;
455 }
456
457 $owner_computed = isset($stat['isowner']) ? $stat['isowner'] : $this->options['owner'];
458 $perm = $this->parsePermissions($info[0], $owner_computed);
459 $stat['name'] = $name;
460 $stat['mime'] = substr(strtolower($info[0]), 0, 1) == 'd' ? 'directory' : $this->mimetype($stat['name'], true);
461 $stat['size'] = $stat['mime'] == 'directory' ? 0 : $info[4];
462 $stat['read'] = $perm['read'];
463 $stat['write'] = $perm['write'];
464
465 return $stat;
466 }
467
468 /**
469 * Normalize MS-DOS style FTP LIST Raw line
470 *
471 * @param string $raw line from FTP LIST (MS-DOS style)
472 *
473 * @return array
474 * @author Naoki Sawada
475 **/
476 protected function normalizeRawWindows($raw)
477 {
478 $info = array_pad(array(), 9, '');
479 $item = preg_replace('#\s+#', ' ', trim($raw), 3);
480 list($date, $time, $size, $name) = explode(' ', $item, 4);
481 $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
482 $dateObj = DateTime::createFromFormat($format, $date . $time);
483 $info[5] = strtotime($dateObj->format('Y-m-d H:i'));
484 $info[8] = $name;
485 if ($size === '<DIR>') {
486 $info[4] = 0;
487 $info[0] = 'drwxr-xr-x';
488 } else {
489 $info[4] = (int)$size;
490 $info[0] = '-rw-r--r--';
491 }
492 return $info;
493 }
494
495 /**
496 * Parse permissions string. Return array(read => true/false, write => true/false)
497 *
498 * @param string $perm permissions string 'rwx' + 'rwx' + 'rwx'
499 * ^ ^ ^
500 * | | +-> others
501 * | +---------> group
502 * +-----------------> owner
503 * The isowner parameter is computed by the caller.
504 * If the owner parameter in the options is true, the user is the actual owner of all objects even if che user used in the ftp Login
505 * is different from the file owner id.
506 * If the owner parameter is false to understand if the user is the file owner we compare the ftp user with the file owner id.
507 * @param Boolean $isowner . Tell if the current user is the owner of the object.
508 *
509 * @return array
510 * @author Dmitry (dio) Levashov
511 * @author Ugo Vierucci
512 */
513 protected function parsePermissions($perm, $isowner = true)
514 {
515 $res = array();
516 $parts = array();
517 for ($i = 0, $l = strlen($perm); $i < $l; $i++) {
518 $parts[] = substr($perm, $i, 1);
519 }
520
521 $read = ($isowner && $parts[1] == 'r') || $parts[4] == 'r' || $parts[7] == 'r';
522
523 return array(
524 'read' => $parts[0] == 'd' ? $read && (($isowner && $parts[3] == 'x') || $parts[6] == 'x' || $parts[9] == 'x') : $read,
525 'write' => ($isowner && $parts[2] == 'w') || $parts[5] == 'w' || $parts[8] == 'w'
526 );
527 }
528
529 /**
530 * Cache dir contents
531 *
532 * @param string $path dir path
533 *
534 * @return void
535 * @author Dmitry Levashov
536 **/
537 protected function cacheDir($path)
538 {
539 $this->dirsCache[$path] = array();
540 $hasDir = false;
541
542 $list = array();
543 $encPath = $this->convEncIn($path);
544 foreach ($this->ftpRawList($encPath) as $raw) {
545 if (($stat = $this->parseRaw($raw, $encPath))) {
546 $list[] = $stat;
547 }
548 }
549 $list = $this->convEncOut($list);
550 $prefix = ($path === $this->separator) ? $this->separator : $path . $this->separator;
551 $targets = array();
552 foreach ($list as $stat) {
553 $p = $prefix . $stat['name'];
554 if (isset($stat['target'])) {
555 // stat later
556 $targets[$stat['name']] = $stat['target'];
557 } else {
558 $stat = $this->updateCache($p, $stat);
559 if (empty($stat['hidden'])) {
560 if (!$hasDir && $stat['mime'] === 'directory') {
561 $hasDir = true;
562 }
563 $this->dirsCache[$path][] = $p;
564 }
565 }
566 }
567 // stat link targets
568 foreach ($targets as $name => $target) {
569 $stat = array();
570 $stat['name'] = $name;
571 $p = $prefix . $name;
572 $cacheDirTarget = $this->cacheDirTarget;
573 $this->cacheDirTarget = $this->convEncIn($target, true);
574 if ($tstat = $this->stat($target)) {
575 $stat['size'] = $tstat['size'];
576 $stat['alias'] = $target;
577 $stat['thash'] = $tstat['hash'];
578 $stat['mime'] = $tstat['mime'];
579 $stat['read'] = $tstat['read'];
580 $stat['write'] = $tstat['write'];
581
582 if (isset($tstat['ts'])) {
583 $stat['ts'] = $tstat['ts'];
584 }
585 if (isset($tstat['owner'])) {
586 $stat['owner'] = $tstat['owner'];
587 }
588 if (isset($tstat['group'])) {
589 $stat['group'] = $tstat['group'];
590 }
591 if (isset($tstat['perm'])) {
592 $stat['perm'] = $tstat['perm'];
593 }
594 if (isset($tstat['isowner'])) {
595 $stat['isowner'] = $tstat['isowner'];
596 }
597 } else {
598
599 $stat['mime'] = 'symlink-broken';
600 $stat['read'] = false;
601 $stat['write'] = false;
602 $stat['size'] = 0;
603
604 }
605 $this->cacheDirTarget = $cacheDirTarget;
606 $stat = $this->updateCache($p, $stat);
607 if (empty($stat['hidden'])) {
608 if (!$hasDir && $stat['mime'] === 'directory') {
609 $hasDir = true;
610 }
611 $this->dirsCache[$path][] = $p;
612 }
613 }
614
615 if (isset($this->sessionCache['subdirs'])) {
616 $this->sessionCache['subdirs'][$path] = $hasDir;
617 }
618 }
619
620 /**
621 * Return ftp transfer mode for file
622 *
623 * @param string $path file path
624 *
625 * @return string
626 * @author Dmitry (dio) Levashov
627 **/
628 protected function ftpMode($path)
629 {
630 return strpos($this->mimetype($path), 'text/') === 0 ? FTP_ASCII : FTP_BINARY;
631 }
632
633 /*********************** paths/urls *************************/
634
635 /**
636 * Return parent directory path
637 *
638 * @param string $path file path
639 *
640 * @return string
641 * @author Naoki Sawada
642 **/
643 protected function _dirname($path)
644 {
645 $parts = explode($this->separator, trim($path, $this->separator));
646 array_pop($parts);
647 return $this->separator . join($this->separator, $parts);
648 }
649
650 /**
651 * Return file name
652 *
653 * @param string $path file path
654 *
655 * @return string
656 * @author Naoki Sawada
657 **/
658 protected function _basename($path)
659 {
660 $parts = explode($this->separator, trim($path, $this->separator));
661 return array_pop($parts);
662 }
663
664 /**
665 * Join dir name and file name and retur full path
666 *
667 * @param string $dir
668 * @param string $name
669 *
670 * @return string
671 * @author Dmitry (dio) Levashov
672 **/
673 protected function _joinPath($dir, $name)
674 {
675 return rtrim($dir, $this->separator) . $this->separator . $name;
676 }
677
678 /**
679 * Return normalized path, this works the same as os.path.normpath() in Python
680 *
681 * @param string $path path
682 *
683 * @return string
684 * @author Troex Nevelin
685 **/
686 protected function _normpath($path)
687 {
688 if (empty($path)) {
689 $path = '.';
690 }
691 // path must be start with /
692 $path = preg_replace('|^\.\/?|', $this->separator, $path);
693 $path = preg_replace('/^([^\/])/', "/$1", $path);
694
695 if ($path[0] === $this->separator) {
696 $initial_slashes = true;
697 } else {
698 $initial_slashes = false;
699 }
700
701 if (($initial_slashes)
702 && (strpos($path, '//') === 0)
703 && (strpos($path, '///') === false)) {
704 $initial_slashes = 2;
705 }
706
707 $initial_slashes = (int)$initial_slashes;
708
709 $comps = explode($this->separator, $path);
710 $new_comps = array();
711 foreach ($comps as $comp) {
712 if (in_array($comp, array('', '.'))) {
713 continue;
714 }
715
716 if (($comp != '..')
717 || (!$initial_slashes && !$new_comps)
718 || ($new_comps && (end($new_comps) == '..'))) {
719 array_push($new_comps, $comp);
720 } elseif ($new_comps) {
721 array_pop($new_comps);
722 }
723 }
724 $comps = $new_comps;
725 $path = implode($this->separator, $comps);
726 if ($initial_slashes) {
727 $path = str_repeat($this->separator, $initial_slashes) . $path;
728 }
729
730 return $path ? $path : '.';
731 }
732
733 /**
734 * Return file path related to root dir
735 *
736 * @param string $path file path
737 *
738 * @return string
739 * @author Dmitry (dio) Levashov
740 **/
741 protected function _relpath($path)
742 {
743 if ($path === $this->root) {
744 return '';
745 } else {
746 if (strpos($path, $this->root) === 0) {
747 return ltrim(substr($path, strlen($this->root)), $this->separator);
748 } else {
749 // for link
750 return $path;
751 }
752 }
753 }
754
755 /**
756 * Convert path related to root dir into real path
757 *
758 * @param string $path file path
759 *
760 * @return string
761 * @author Dmitry (dio) Levashov
762 **/
763 protected function _abspath($path)
764 {
765 if ($path === $this->separator) {
766 return $this->root;
767 } else {
768 if ($path[0] === $this->separator) {
769 // for link
770 return $path;
771 } else {
772 return $this->_joinPath($this->root, $path);
773 }
774 }
775 }
776
777 /**
778 * Return fake path started from root dir
779 *
780 * @param string $path file path
781 *
782 * @return string
783 * @author Dmitry (dio) Levashov
784 **/
785 protected function _path($path)
786 {
787 return $this->rootName . ($path == $this->root ? '' : $this->separator . $this->_relpath($path));
788 }
789
790 /**
791 * Return true if $path is children of $parent
792 *
793 * @param string $path path to check
794 * @param string $parent parent path
795 *
796 * @return bool
797 * @author Dmitry (dio) Levashov
798 **/
799 protected function _inpath($path, $parent)
800 {
801 return $path == $parent || strpos($path, rtrim($parent, $this->separator) . $this->separator) === 0;
802 }
803
804 /***************** file stat ********************/
805 /**
806 * Return stat for given path.
807 * Stat contains following fields:
808 * - (int) size file size in b. required
809 * - (int) ts file modification time in unix time. required
810 * - (string) mime mimetype. required for folders, others - optionally
811 * - (bool) read read permissions. required
812 * - (bool) write write permissions. required
813 * - (bool) locked is object locked. optionally
814 * - (bool) hidden is object hidden. optionally
815 * - (string) alias for symlinks - link target path relative to root path. optionally
816 * - (string) target for symlinks - link target path. optionally
817 * If file does not exists - returns empty array or false.
818 *
819 * @param string $path file path
820 *
821 * @return array|false
822 * @author Dmitry (dio) Levashov
823 **/
824 protected function _stat($path)
825 {
826 $outPath = $this->convEncOut($path);
827 if (isset($this->cache[$outPath])) {
828 return $this->convEncIn($this->cache[$outPath]);
829 } else {
830 $this->convEncIn();
831 }
832 if (!$this->MLSTsupprt) {
833 if ($path === $this->root) {
834 $res = array(
835 'name' => $this->root,
836 'mime' => 'directory',
837 'dirs' => -1
838 );
839 if ($this->needOnline && (($this->ARGS['cmd'] === 'open' && $this->ARGS['target'] === $this->encode($this->root)) || $this->isMyReload())) {
840 $check = array(
841 'ts' => true,
842 'dirs' => true,
843 );
844 $ts = 0;
845 foreach ($this->ftpRawList($path) as $str) {
846 $info = preg_split('/\s+/', $str, 9);
847 if ($info[8] === '.') {
848 $info[8] = 'root';
849 if ($stat = $this->parseRaw(join(' ', $info), $path)) {
850 unset($stat['name']);
851 $res = array_merge($res, $stat);
852 if ($res['ts']) {
853 $ts = 0;
854 unset($check['ts']);
855 }
856 }
857 }
858 if ($check && ($stat = $this->parseRaw($str, $path))) {
859 if (isset($stat['ts']) && !empty($stat['ts'])) {
860 $ts = max($ts, $stat['ts']);
861 }
862 if (isset($stat['dirs']) && $stat['mime'] === 'directory') {
863 $res['dirs'] = 1;
864 unset($stat['dirs']);
865 }
866 if (!$check) {
867 break;
868 }
869 }
870 }
871 if ($ts) {
872 $res['ts'] = $ts;
873 }
874 $this->cache[$outPath] = $res;
875 }
876 return $res;
877 }
878
879 $pPath = $this->_dirname($path);
880 if ($this->_inPath($pPath, $this->root)) {
881 $outPPpath = $this->convEncOut($pPath);
882 if (!isset($this->dirsCache[$outPPpath])) {
883 $parentSubdirs = null;
884 if (isset($this->sessionCache['subdirs']) && isset($this->sessionCache['subdirs'][$outPPpath])) {
885 $parentSubdirs = $this->sessionCache['subdirs'][$outPPpath];
886 }
887 $this->cacheDir($outPPpath);
888 if ($parentSubdirs) {
889 $this->sessionCache['subdirs'][$outPPpath] = $parentSubdirs;
890 }
891 }
892 }
893
894 $stat = $this->convEncIn(isset($this->cache[$outPath]) ? $this->cache[$outPath] : array());
895 if (!$this->mounted) {
896 // dispose incomplete cache made by calling `stat` by 'startPath' option
897 $this->cache = array();
898 }
899 return $stat;
900 }
901 $raw = ftp_raw($this->connect, 'MLST ' . $path);
902 if (is_array($raw) && count($raw) > 1 && substr(trim($raw[0]), 0, 1) == 2) {
903 $parts = explode(';', trim($raw[1]));
904 array_pop($parts);
905 $parts = array_map('strtolower', $parts);
906 $stat = array();
907 $mode = '';
908 foreach ($parts as $part) {
909
910 list($key, $val) = explode('=', $part, 2);
911
912 switch ($key) {
913 case 'type':
914 if (strpos($val, 'dir') !== false) {
915 $stat['mime'] = 'directory';
916 } else if (strpos($val, 'link') !== false) {
917 $stat['mime'] = 'symlink';
918 break(2);
919 } else {
920 $stat['mime'] = $this->mimetype($path);
921 }
922 break;
923
924 case 'size':
925 $stat['size'] = $val;
926 break;
927
928 case 'modify':
929 $ts = mktime(intval(substr($val, 8, 2)), intval(substr($val, 10, 2)), intval(substr($val, 12, 2)), intval(substr($val, 4, 2)), intval(substr($val, 6, 2)), substr($val, 0, 4));
930 $stat['ts'] = $ts;
931 break;
932
933 case 'unix.mode':
934 $mode = strval($val);
935 break;
936
937 case 'unix.uid':
938 $stat['owner'] = $val;
939 break;
940
941 case 'unix.gid':
942 $stat['group'] = $val;
943 break;
944
945 case 'perm':
946 $val = strtolower($val);
947 $stat['read'] = (int)preg_match('/e|l|r/', $val);
948 $stat['write'] = (int)preg_match('/w|m|c/', $val);
949 if (!preg_match('/f|d/', $val)) {
950 $stat['locked'] = 1;
951 }
952 break;
953 }
954 }
955
956 if (empty($stat['mime'])) {
957 return array();
958 }
959
960 // do not use MLST to get stat of symlink
961 if ($stat['mime'] === 'symlink') {
962 $this->MLSTsupprt = false;
963 $res = $this->_stat($path);
964 $this->MLSTsupprt = true;
965 return $res;
966 }
967
968 if ($stat['mime'] === 'directory') {
969 $stat['size'] = 0;
970 }
971
972 if ($mode) {
973 $stat['perm'] = '';
974 if ($mode[0] === '0') {
975 $mode = substr($mode, 1);
976 }
977
978 $perm = array();
979 for ($i = 0; $i <= 2; $i++) {
980 $perm[$i] = array(false, false, false);
981 $n = isset($mode[$i]) ? $mode[$i] : 0;
982
983 if ($n - 4 >= 0) {
984 $perm[$i][0] = true;
985 $n = $n - 4;
986 $stat['perm'] .= 'r';
987 } else {
988 $stat['perm'] .= '-';
989 }
990
991 if ($n - 2 >= 0) {
992 $perm[$i][1] = true;
993 $n = $n - 2;
994 $stat['perm'] .= 'w';
995 } else {
996 $stat['perm'] .= '-';
997 }
998
999 if ($n - 1 == 0) {
1000 $perm[$i][2] = true;
1001 $stat['perm'] .= 'x';
1002 } else {
1003 $stat['perm'] .= '-';
1004 }
1005 }
1006
1007 $stat['perm'] = trim($stat['perm']);
1008 //
1009 // if not exists owner in LS ftp ==> isowner = true
1010 // if is defined as option : 'owner' => true isowner = true
1011 //
1012 // if exist owner in LS ftp and 'owner' => False isowner = result of owner(file) == user(logged with ftp)
1013
1014 $owner_computed = isset($stat['owner']) ? ($this->options['owner'] ? true : ($stat['owner'] == $this->options['user'])) : true;
1015
1016 $read = ($owner_computed && $perm[0][0]) || $perm[1][0] || $perm[2][0];
1017
1018 $stat['read'] = $stat['mime'] == 'directory' ? $read && (($owner_computed && $perm[0][2]) || $perm[1][2] || $perm[2][2]) : $read;
1019 $stat['write'] = ($owner_computed && $perm[0][1]) || $perm[1][1] || $perm[2][1];
1020
1021 if ($this->options['statOwner']) {
1022 $stat['isowner'] = $owner_computed;
1023 } else {
1024 unset($stat['owner'], $stat['group'], $stat['perm']);
1025 }
1026 }
1027
1028 return $stat;
1029
1030 }
1031
1032 return array();
1033 }
1034
1035 /**
1036 * Return true if path is dir and has at least one childs directory
1037 *
1038 * @param string $path dir path
1039 *
1040 * @return bool
1041 * @author Dmitry (dio) Levashov
1042 **/
1043 protected function _subdirs($path)
1044 {
1045
1046 foreach ($this->ftpRawList($path) as $str) {
1047 $info = preg_split('/\s+/', $str, 9);
1048 if (!isset($this->ftpOsUnix)) {
1049 $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
1050 }
1051 if (!$this->ftpOsUnix) {
1052 $info = $this->normalizeRawWindows($str);
1053 }
1054 $name = isset($info[8]) ? trim($info[8]) : '';
1055 if ($name && $name !== '.' && $name !== '..' && substr(strtolower($info[0]), 0, 1) === 'd') {
1056 return true;
1057 }
1058 }
1059 return false;
1060 }
1061
1062 /**
1063 * Return object width and height
1064 * Ususaly used for images, but can be realize for video etc...
1065 *
1066 * @param string $path file path
1067 * @param string $mime file mime type
1068 *
1069 * @return string|false
1070 * @throws ImagickException
1071 * @throws elFinderAbortException
1072 * @author Dmitry (dio) Levashov
1073 */
1074 protected function _dimensions($path, $mime)
1075 {
1076 $ret = false;
1077 if ($imgsize = $this->getImageSize($path, $mime)) {
1078 $ret = array('dim' => $imgsize['dimensions']);
1079 if (!empty($imgsize['url'])) {
1080 $ret['url'] = $imgsize['url'];
1081 }
1082 }
1083 return $ret;
1084 }
1085
1086 /******************** file/dir content *********************/
1087
1088 /**
1089 * Return files list in directory.
1090 *
1091 * @param string $path dir path
1092 *
1093 * @return array
1094 * @author Dmitry (dio) Levashov
1095 * @author Cem (DiscoFever)
1096 **/
1097 protected function _scandir($path)
1098 {
1099 $files = array();
1100
1101 foreach ($this->ftpRawList($path) as $str) {
1102 if (($stat = $this->parseRaw($str, $path, true))) {
1103 $files[] = $this->_joinPath($path, $stat['name']);
1104 }
1105 }
1106
1107 return $files;
1108 }
1109
1110 /**
1111 * Open file and return file pointer
1112 *
1113 * @param string $path file path
1114 * @param string $mode
1115 *
1116 * @return false|resource
1117 * @throws elFinderAbortException
1118 * @internal param bool $write open file for writing
1119 * @author Dmitry (dio) Levashov
1120 */
1121 protected function _fopen($path, $mode = 'rb')
1122 {
1123 // try ftp stream wrapper
1124 if ($this->options['mode'] === 'passive' && ini_get('allow_url_fopen')) {
1125 $url = ($this->isFTPS ? 'ftps' : 'ftp') . '://' . $this->options['user'] . ':' . $this->options['pass'] . '@' . $this->options['host'] . ':' . $this->options['port'] . $path;
1126 if (strtolower($mode[0]) === 'w') {
1127 $context = stream_context_create(array('ftp' => array('overwrite' => true)));
1128 $fp = fopen($url, $mode, false, $context);
1129 } else {
1130 $fp = fopen($url, $mode);
1131 }
1132 if ($fp) {
1133 return $fp;
1134 }
1135 }
1136
1137 if ($this->tmp) {
1138 $local = $this->getTempFile($path);
1139 $fp = fopen($local, 'wb');
1140 $ret = ftp_nb_fget($this->connect, $fp, $path, FTP_BINARY);
1141 while ($ret === FTP_MOREDATA) {
1142 elFinder::extendTimeLimit();
1143 $ret = ftp_nb_continue($this->connect);
1144 }
1145 if ($ret === FTP_FINISHED) {
1146 fclose($fp);
1147 $fp = fopen($local, $mode);
1148 return $fp;
1149 }
1150 fclose($fp);
1151 is_file($local) && unlink($local);
1152 }
1153
1154 return false;
1155 }
1156
1157 /**
1158 * Close opened file
1159 *
1160 * @param resource $fp file pointer
1161 * @param string $path
1162 *
1163 * @return void
1164 * @author Dmitry (dio) Levashov
1165 */
1166 protected function _fclose($fp, $path = '')
1167 {
1168 is_resource($fp) && fclose($fp);
1169 if ($path) {
1170 unlink($this->getTempFile($path));
1171 }
1172 }
1173
1174 /******************** file/dir manipulations *************************/
1175
1176 /**
1177 * Create dir and return created dir path or false on failed
1178 *
1179 * @param string $path parent dir path
1180 * @param string $name new directory name
1181 *
1182 * @return string|bool
1183 * @author Dmitry (dio) Levashov
1184 **/
1185 protected function _mkdir($path, $name)
1186 {
1187 $path = $this->_joinPath($path, $name);
1188 if (ftp_mkdir($this->connect, $path) === false) {
1189 return false;
1190 }
1191
1192 $this->options['dirMode'] && ftp_chmod($this->connect, $this->options['dirMode'], $path);
1193 return $path;
1194 }
1195
1196 /**
1197 * Create file and return it's path or false on failed
1198 *
1199 * @param string $path parent dir path
1200 * @param string $name new file name
1201 *
1202 * @return string|bool
1203 * @author Dmitry (dio) Levashov
1204 **/
1205 protected function _mkfile($path, $name)
1206 {
1207 if ($this->tmp) {
1208 $path = $this->_joinPath($path, $name);
1209 $local = $this->getTempFile();
1210 $res = touch($local) && ftp_put($this->connect, $path, $local, FTP_ASCII);
1211 unlink($local);
1212 return $res ? $path : false;
1213 }
1214 return false;
1215 }
1216
1217 /**
1218 * Create symlink. FTP driver does not support symlinks.
1219 *
1220 * @param string $target link target
1221 * @param string $path symlink path
1222 * @param string $name
1223 *
1224 * @return bool
1225 * @author Dmitry (dio) Levashov
1226 */
1227 protected function _symlink($target, $path, $name)
1228 {
1229 return false;
1230 }
1231
1232 /**
1233 * Copy file into another file
1234 *
1235 * @param string $source source file path
1236 * @param string $targetDir target directory path
1237 * @param string $name new file name
1238 *
1239 * @return bool
1240 * @author Dmitry (dio) Levashov
1241 **/
1242 protected function _copy($source, $targetDir, $name)
1243 {
1244 $res = false;
1245
1246 if ($this->tmp) {
1247 $local = $this->getTempFile();
1248 $target = $this->_joinPath($targetDir, $name);
1249
1250 if (ftp_get($this->connect, $local, $source, FTP_BINARY)
1251 && ftp_put($this->connect, $target, $local, $this->ftpMode($target))) {
1252 $res = $target;
1253 }
1254 unlink($local);
1255 }
1256
1257 return $res;
1258 }
1259
1260 /**
1261 * Move file into another parent dir.
1262 * Return new file path or false.
1263 *
1264 * @param string $source source file path
1265 * @param $targetDir
1266 * @param string $name file name
1267 *
1268 * @return bool|string
1269 * @internal param string $target target dir path
1270 * @author Dmitry (dio) Levashov
1271 */
1272 protected function _move($source, $targetDir, $name)
1273 {
1274 $target = $this->_joinPath($targetDir, $name);
1275 return ftp_rename($this->connect, $source, $target) ? $target : false;
1276 }
1277
1278 /**
1279 * Remove file
1280 *
1281 * @param string $path file path
1282 *
1283 * @return bool
1284 * @author Dmitry (dio) Levashov
1285 **/
1286 protected function _unlink($path)
1287 {
1288 return ftp_delete($this->connect, $path);
1289 }
1290
1291 /**
1292 * Remove dir
1293 *
1294 * @param string $path dir path
1295 *
1296 * @return bool
1297 * @author Dmitry (dio) Levashov
1298 **/
1299 protected function _rmdir($path)
1300 {
1301 return ftp_rmdir($this->connect, $path);
1302 }
1303
1304 /**
1305 * Create new file and write into it from file pointer.
1306 * Return new file path or false on error.
1307 *
1308 * @param resource $fp file pointer
1309 * @param string $dir target dir path
1310 * @param string $name file name
1311 * @param array $stat file stat (required by some virtual fs)
1312 *
1313 * @return bool|string
1314 * @author Dmitry (dio) Levashov
1315 **/
1316 protected function _save($fp, $dir, $name, $stat)
1317 {
1318 $path = $this->_joinPath($dir, $name);
1319 return ftp_fput($this->connect, $path, $fp, $this->ftpMode($path))
1320 ? $path
1321 : false;
1322 }
1323
1324 /**
1325 * Get file contents
1326 *
1327 * @param string $path file path
1328 *
1329 * @return string|false
1330 * @throws elFinderAbortException
1331 * @author Dmitry (dio) Levashov
1332 */
1333 protected function _getContents($path)
1334 {
1335 $contents = '';
1336 if (($fp = $this->_fopen($path))) {
1337 while (!feof($fp)) {
1338 $contents .= fread($fp, 8192);
1339 }
1340 $this->_fclose($fp, $path);
1341 return $contents;
1342 }
1343 return false;
1344 }
1345
1346 /**
1347 * Write a string to a file
1348 *
1349 * @param string $path file path
1350 * @param string $content new file content
1351 *
1352 * @return bool
1353 * @author Dmitry (dio) Levashov
1354 **/
1355 protected function _filePutContents($path, $content)
1356 {
1357 $res = false;
1358
1359 if ($this->tmp) {
1360 $local = $this->getTempFile();
1361
1362 if (file_put_contents($local, $content, LOCK_EX) !== false
1363 && ($fp = fopen($local, 'rb'))) {
1364 $file = $this->stat($this->convEncOut($path, false));
1365 if (!empty($file['thash'])) {
1366 $path = $this->decode($file['thash']);
1367 }
1368 clearstatcache();
1369 $res = ftp_fput($this->connect, $path, $fp, $this->ftpMode($path));
1370 fclose($fp);
1371 }
1372 file_exists($local) && unlink($local);
1373 }
1374
1375 return $res;
1376 }
1377
1378 /**
1379 * Detect available archivers
1380 *
1381 * @return void
1382 * @throws elFinderAbortException
1383 */
1384 protected function _checkArchivers()
1385 {
1386 $this->archivers = $this->getArchivers();
1387 return;
1388 }
1389
1390 /**
1391 * chmod availability
1392 *
1393 * @param string $path
1394 * @param string $mode
1395 *
1396 * @return bool
1397 */
1398 protected function _chmod($path, $mode)
1399 {
1400 $modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o", $mode));
1401 return ftp_chmod($this->connect, $modeOct, $path);
1402 }
1403
1404 /**
1405 * Extract files from archive
1406 *
1407 * @param string $path archive path
1408 * @param array $arc archiver command and arguments (same as in $this->archivers)
1409 *
1410 * @return true
1411 * @throws elFinderAbortException
1412 * @author Dmitry (dio) Levashov,
1413 * @author Alexey Sukhotin
1414 */
1415 protected function _extract($path, $arc)
1416 {
1417 $dir = $this->tempDir();
1418 if (!$dir) {
1419 return false;
1420 }
1421
1422 $basename = $this->_basename($path);
1423 $localPath = $dir . DIRECTORY_SEPARATOR . $basename;
1424
1425 if (!ftp_get($this->connect, $localPath, $path, FTP_BINARY)) {
1426 //cleanup
1427 $this->rmdirRecursive($dir);
1428 return false;
1429 }
1430
1431 $this->unpackArchive($localPath, $arc);
1432
1433 $this->archiveSize = 0;
1434
1435 // find symlinks and check extracted items
1436 $checkRes = $this->checkExtractItems($dir);
1437 if ($checkRes['symlinks']) {
1438 $this->rmdirRecursive($dir);
1439 return $this->setError(array_merge($this->error, array(elFinder::ERROR_ARC_SYMLINKS)));
1440 }
1441 $this->archiveSize = $checkRes['totalSize'];
1442 if ($checkRes['rmNames']) {
1443 foreach ($checkRes['rmNames'] as $name) {
1444 $this->addError(elFinder::ERROR_SAVE, $name);
1445 }
1446 }
1447
1448 $filesToProcess = self::listFilesInDirectory($dir, true);
1449
1450 // no files - extract error ?
1451 if (empty($filesToProcess)) {
1452 $this->rmdirRecursive($dir);
1453 return false;
1454 }
1455
1456 // check max files size
1457 if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
1458 $this->rmdirRecursive($dir);
1459 return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
1460 }
1461
1462 $extractTo = $this->extractToNewdir; // 'auto', ture or false
1463
1464 // archive contains one item - extract in archive dir
1465 $name = '';
1466 $src = $dir . DIRECTORY_SEPARATOR . $filesToProcess[0];
1467 if (($extractTo === 'auto' || !$extractTo) && count($filesToProcess) === 1 && is_file($src)) {
1468 $name = $filesToProcess[0];
1469 } else if ($extractTo === 'auto' || $extractTo) {
1470 // for several files - create new directory
1471 // create unique name for directory
1472 $src = $dir;
1473 $splits = elFinder::splitFileExtention(basename($path));
1474 $name = $splits[0];
1475 $test = $this->_joinPath(dirname($path), $name);
1476 if ($this->stat($test)) {
1477 $name = $this->uniqueName(dirname($path), $name, '-', false);
1478 }
1479 }
1480
1481 if ($name !== '' && is_file($src)) {
1482 $result = $this->_joinPath(dirname($path), $name);
1483
1484 if (!ftp_put($this->connect, $result, $src, FTP_BINARY)) {
1485 $this->rmdirRecursive($dir);
1486 return false;
1487 }
1488 } else {
1489 $dstDir = $this->_dirname($path);
1490 $result = array();
1491 if (is_dir($src) && $name) {
1492 $target = $this->_joinPath($dstDir, $name);
1493 $_stat = $this->_stat($target);
1494 if ($_stat) {
1495 if (!$this->options['copyJoin']) {
1496 if ($_stat['mime'] === 'directory') {
1497 $this->delTree($target);
1498 } else {
1499 $this->_unlink($target);
1500 }
1501 $_stat = false;
1502 } else {
1503 $dstDir = $target;
1504 }
1505 }
1506 if (!$_stat && (!$dstDir = $this->_mkdir($dstDir, $name))) {
1507 $this->rmdirRecursive($dir);
1508 return false;
1509 }
1510 $result[] = $dstDir;
1511 }
1512 foreach ($filesToProcess as $name) {
1513 $name = rtrim($name, DIRECTORY_SEPARATOR);
1514 $src = $dir . DIRECTORY_SEPARATOR . $name;
1515 if (is_dir($src)) {
1516 $p = dirname($name);
1517 if ($p === '.') {
1518 $p = '';
1519 }
1520 $name = basename($name);
1521 $target = $this->_joinPath($this->_joinPath($dstDir, $p), $name);
1522 $_stat = $this->_stat($target);
1523 if ($_stat) {
1524 if (!$this->options['copyJoin']) {
1525 if ($_stat['mime'] === 'directory') {
1526 $this->delTree($target);
1527 } else {
1528 $this->_unlink($target);
1529 }
1530 $_stat = false;
1531 }
1532 }
1533 if (!$_stat && (!$target = $this->_mkdir($this->_joinPath($dstDir, $p), $name))) {
1534 $this->rmdirRecursive($dir);
1535 return false;
1536 }
1537 } else {
1538 $target = $this->_joinPath($dstDir, $name);
1539 if (!ftp_put($this->connect, $target, $src, FTP_BINARY)) {
1540 $this->rmdirRecursive($dir);
1541 return false;
1542 }
1543 }
1544 $result[] = $target;
1545 }
1546 if (!$result) {
1547 $this->rmdirRecursive($dir);
1548 return false;
1549 }
1550 }
1551
1552 is_dir($dir) && $this->rmdirRecursive($dir);
1553
1554 $this->clearcache();
1555 return $result ? $result : false;
1556 }
1557
1558 /**
1559 * Create archive and return its path
1560 *
1561 * @param string $dir target dir
1562 * @param array $files files names list
1563 * @param string $name archive name
1564 * @param array $arc archiver options
1565 *
1566 * @return string|bool
1567 * @throws elFinderAbortException
1568 * @author Dmitry (dio) Levashov,
1569 * @author Alexey Sukhotin
1570 */
1571 protected function _archive($dir, $files, $name, $arc)
1572 {
1573 // get current directory
1574 $cwd = getcwd();
1575
1576 $tmpDir = $this->tempDir();
1577 if (!$tmpDir) {
1578 return false;
1579 }
1580
1581 //download data
1582 if (!$this->ftp_download_files($dir, $files, $tmpDir)) {
1583 //cleanup
1584 $this->rmdirRecursive($tmpDir);
1585 return false;
1586 }
1587
1588 $remoteArchiveFile = false;
1589 if ($path = $this->makeArchive($tmpDir, $files, $name, $arc)) {
1590 $remoteArchiveFile = $this->_joinPath($dir, $name);
1591 if (!ftp_put($this->connect, $remoteArchiveFile, $path, FTP_BINARY)) {
1592 $remoteArchiveFile = false;
1593 }
1594 }
1595
1596 //cleanup
1597 if (!$this->rmdirRecursive($tmpDir)) {
1598 return false;
1599 }
1600
1601 return $remoteArchiveFile;
1602 }
1603
1604 /**
1605 * Create writable temporary directory and return path to it.
1606 *
1607 * @return string path to the new temporary directory or false in case of error.
1608 */
1609 private function tempDir()
1610 {
1611 $tempPath = tempnam($this->tmp, 'elFinder');
1612 if (!$tempPath) {
1613 $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1614 return false;
1615 }
1616 $success = unlink($tempPath);
1617 if (!$success) {
1618 $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1619 return false;
1620 }
1621 $success = mkdir($tempPath, 0700, true);
1622 if (!$success) {
1623 $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1624 return false;
1625 }
1626 return $tempPath;
1627 }
1628
1629 /**
1630 * Gets an array of absolute remote FTP paths of files and
1631 * folders in $remote_directory omitting symbolic links.
1632 *
1633 * @param $remote_directory string remote FTP path to scan for file and folders recursively
1634 * @param $targets array Array of target item. `null` is to get all of items
1635 *
1636 * @return array of elements each of which is an array of two elements:
1637 * <ul>
1638 * <li>$item['path'] - absolute remote FTP path</li>
1639 * <li>$item['type'] - either 'f' for file or 'd' for directory</li>
1640 * </ul>
1641 */
1642 protected function ftp_scan_dir($remote_directory, $targets = null)
1643 {
1644 $buff = $this->ftpRawList($remote_directory);
1645 $items = array();
1646 if ($targets && is_array($targets)) {
1647 $targets = array_flip($targets);
1648 } else {
1649 $targets = false;
1650 }
1651 foreach ($buff as $str) {
1652 $info = preg_split("/\s+/", $str, 9);
1653 if (!isset($this->ftpOsUnix)) {
1654 $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
1655 }
1656 if (!$this->ftpOsUnix) {
1657 $info = $this->normalizeRawWindows($str);
1658 }
1659 $type = substr($info[0], 0, 1);
1660 $name = trim($info[8]);
1661 if ($name !== '.' && $name !== '..' && (!$targets || isset($targets[$name]))) {
1662 switch ($type) {
1663 case 'l' : //omit symbolic links
1664 case 'd' :
1665 $remote_file_path = $this->_joinPath($remote_directory, $name);
1666 $item = array();
1667 $item['path'] = $remote_file_path;
1668 $item['type'] = 'd'; // normal file
1669 $items[] = $item;
1670 $items = array_merge($items, $this->ftp_scan_dir($remote_file_path));
1671 break;
1672 default:
1673 $remote_file_path = $this->_joinPath($remote_directory, $name);
1674 $item = array();
1675 $item['path'] = $remote_file_path;
1676 $item['type'] = 'f'; // normal file
1677 $items[] = $item;
1678 }
1679 }
1680 }
1681 return $items;
1682 }
1683
1684 /**
1685 * Downloads specified files from remote directory
1686 * if there is a directory among files it is downloaded recursively (omitting symbolic links).
1687 *
1688 * @param $remote_directory string remote FTP path to a source directory to download from.
1689 * @param array $files list of files to download from remote directory.
1690 * @param $dest_local_directory string destination folder to store downloaded files.
1691 *
1692 * @return bool true on success and false on failure.
1693 */
1694 private function ftp_download_files($remote_directory, array $files, $dest_local_directory)
1695 {
1696 $contents = $this->ftp_scan_dir($remote_directory, $files);
1697 if (!isset($contents)) {
1698 $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1699 return false;
1700 }
1701 $remoteDirLen = strlen($remote_directory);
1702 foreach ($contents as $item) {
1703 $relative_path = substr($item['path'], $remoteDirLen);
1704 $local_path = $dest_local_directory . DIRECTORY_SEPARATOR . $relative_path;
1705 switch ($item['type']) {
1706 case 'd':
1707 $success = mkdir($local_path);
1708 break;
1709 case 'f':
1710 $success = ftp_get($this->connect, $local_path, $item['path'], FTP_BINARY);
1711 break;
1712 default:
1713 $success = true;
1714 }
1715 if (!$success) {
1716 $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1717 return false;
1718 }
1719 }
1720 return true;
1721 }
1722
1723 /**
1724 * Delete local directory recursively.
1725 *
1726 * @param $dirPath string to directory to be erased.
1727 *
1728 * @return bool true on success and false on failure.
1729 * @throws Exception
1730 */
1731 private function deleteDir($dirPath)
1732 {
1733 if (!is_dir($dirPath)) {
1734 $success = unlink($dirPath);
1735 } else {
1736 $success = true;
1737 foreach (array_reverse(elFinderVolumeFTP::listFilesInDirectory($dirPath, false)) as $path) {
1738 $path = $dirPath . DIRECTORY_SEPARATOR . $path;
1739 if (is_link($path)) {
1740 unlink($path);
1741 } else if (is_dir($path)) {
1742 $success = rmdir($path);
1743 } else {
1744 $success = unlink($path);
1745 }
1746 if (!$success) {
1747 break;
1748 }
1749 }
1750 if ($success) {
1751 $success = rmdir($dirPath);
1752 }
1753 }
1754 if (!$success) {
1755 $this->setError(elFinder::ERROR_RM, $dirPath);
1756 return false;
1757 }
1758 return $success;
1759 }
1760
1761 /**
1762 * Returns array of strings containing all files and folders in the specified local directory.
1763 *
1764 * @param $dir
1765 * @param $omitSymlinks
1766 * @param string $prefix
1767 *
1768 * @return array array of files and folders names relative to the $path
1769 * or an empty array if the directory $path is empty,
1770 * <br />
1771 * false if $path is not a directory or does not exist.
1772 * @throws Exception
1773 * @internal param string $path path to directory to scan.
1774 */
1775 private static function listFilesInDirectory($dir, $omitSymlinks, $prefix = '')
1776 {
1777 if (!is_dir($dir)) {
1778 return false;
1779 }
1780 $excludes = array(".", "..");
1781 $result = array();
1782 $files = self::localScandir($dir);
1783 if (!$files) {
1784 return array();
1785 }
1786 foreach ($files as $file) {
1787 if (!in_array($file, $excludes)) {
1788 $path = $dir . DIRECTORY_SEPARATOR . $file;
1789 if (is_link($path)) {
1790 if ($omitSymlinks) {
1791 continue;
1792 } else {
1793 $result[] = $prefix . $file;
1794 }
1795 } else if (is_dir($path)) {
1796 $result[] = $prefix . $file . DIRECTORY_SEPARATOR;
1797 $subs = elFinderVolumeFTP::listFilesInDirectory($path, $omitSymlinks, $prefix . $file . DIRECTORY_SEPARATOR);
1798 if ($subs) {
1799 $result = array_merge($result, $subs);
1800 }
1801
1802 } else {
1803 $result[] = $prefix . $file;
1804 }
1805 }
1806 }
1807 return $result;
1808 }
1809
1810 } // END class
1811