Completed
Push — master ( 61c24d...075a0d )
by recca
07:28
created

elFinderVolumeFTP   D

Complexity

Total Complexity 325

Size/Duplication

Total Lines 1700
Duplicated Lines 8.82 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 150
loc 1700
rs 4.4102
c 0
b 0
f 0
wmc 325
lcom 2
cbo 2

46 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 1
B netmountPrepare() 0 14 5
A umount() 0 4 2
C init() 9 51 10
D configure() 5 30 10
C connect() 8 56 11
A ftpRawList() 0 15 4
F parseRaw() 9 82 28
A normalizeRawWindows() 0 19 3
D parsePermissions() 0 16 14
F cacheDir() 12 80 20
A ftpMode() 0 4 2
A _dirname() 0 7 1
A _basename() 0 6 1
A _joinPath() 0 4 1
D _normpath() 18 46 16
A _relpath() 13 13 3
A _abspath() 0 13 3
A _path() 0 4 2
A _inpath() 0 4 2
F _stat() 23 166 48
B _subdirs() 3 18 9
A _dimensions() 0 9 2
A _scandir() 0 12 3
C _fopen() 0 31 8
A _fclose() 0 7 2
A _mkdir() 0 11 3
A _mkfile() 0 13 4
A _symlink() 0 4 1
A _copy() 4 17 4
A _move() 0 6 2
A _unlink() 0 4 1
A _rmdir() 0 4 1
A _save() 0 8 2
A _getContents() 0 14 3
B _filePutContents() 0 18 5
A _checkArchivers() 0 4 1
A _chmod() 0 6 2
A _findSymlinks() 0 4 1
F _extract() 43 154 39
B _archive() 0 33 6
C ftp_scan_dir() 3 42 12
B tempDir() 0 23 4
B ftp_download_files() 0 31 6
C deleteDir() 0 31 8
D listFilesInDirectory() 0 34 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like elFinderVolumeFTP often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use elFinderVolumeFTP, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 11 and the first side effect is on line 3.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
elFinder::$netDrivers['ftp'] = 'FTP';
4
5
/**
6
 * Simple elFinder driver for FTP.
7
 *
8
 * @author Dmitry (dio) Levashov
9
 * @author Cem (discofever)
10
 **/
11
class elFinderVolumeFTP extends elFinderVolumeDriver
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
12
{
13
    /**
14
	 * Driver id
15
	 * Must be started from letter and contains [a-z0-9]
16
	 * Used as part of volume id.
17
	 *
18
	 * @var string
19
	 **/
20
	protected $driverId = 'f';
21
22
	/**
23
	 * FTP Connection Instance.
24
	 *
25
	 * @var ftp
26
	 **/
27
	protected $connect = null;
28
29
	/**
30
	 * Directory for tmp files
31
	 * If not set driver will try to use tmbDir as tmpDir.
32
	 *
33
	 * @var string
34
	 **/
35
	protected $tmpPath = '';
36
37
	/**
38
	 * Last FTP error message.
39
	 *
40
	 * @var string
41
	 **/
42
	protected $ftpError = '';
43
44
	/**
45
	 * FTP server output list as ftp on linux.
46
	 *
47
	 * @var bool
48
	 **/
49
	protected $ftpOsUnix;
50
51
	/**
52
	 * FTP LIST command option.
53
	 *
54
	 * @var string
55
	 */
56
	protected $ftpListOption = '-al';
57
58
	/**
59
	 * Is connected server Pure FTPd?
60
	 *
61
	 * @var bool
62
	 */
63
	protected $isPureFtpd = false;
64
65
	/**
66
	 * Tmp folder path.
67
	 *
68
	 * @var string
69
	 **/
70
	protected $tmp = '';
71
72
	/**
73
	 * FTP command `MLST` support.
74
	 *
75
	 * @var bool
76
	 */
77
	private $MLSTsupprt = false;
78
79
	/**
80
	 * Calling cacheDir() target path with non-MLST.
81
	 *
82
	 * @var string
83
	 */
84
	private $cacheDirTarget = '';
85
86
	/**
87
	 * Constructor
88
	 * Extend options with required fields.
89
	 *
90
	 * @author Dmitry (dio) Levashov
91
	 * @author Cem (DiscoFever)
92
	 */
93
	public function __construct()
94
	{
95
	    $opts = [
96
			'host' => 'localhost',
97
			'user' => '',
98
			'pass' => '',
99
			'port' => 21,
100
			'mode' => 'passive',
101
			'path' => '/',
102
			'timeout' => 20,
103
			'owner' => true,
104
			'tmbPath' => '',
105
			'tmpPath' => '',
106
			'separator' => '/',
107
			'dirMode' => 0755,
108
			'fileMode' => 0644,
109
			'rootCssClass' => 'elfinder-navbar-root-ftp',
110
			'ftpListOption' => '-al',
111
		];
112
	    $this->options = array_merge($this->options, $opts);
113
	    $this->options['mimeDetect'] = 'internal';
114
	}
115
116
	/**
117
	 * Prepare
118
	 * Call from elFinder::netmout() before volume->mount().
119
	 *
120
	 * @param $options
121
	 * @return array
122
	 * @author Naoki Sawada
123
	 */
124
	public function netmountPrepare($options)
0 ignored issues
show
Coding Style introduced by
netmountPrepare uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
125
	{
126
	    if (! empty($_REQUEST['encoding']) && iconv('UTF-8', $_REQUEST['encoding'], '') !== false) {
127
	        $options['encoding'] = $_REQUEST['encoding'];
128
	        if (! empty($_REQUEST['locale']) && setlocale(LC_ALL, $_REQUEST['locale'])) {
129
	            setlocale(LC_ALL, elFinder::$locale);
130
	            $options['locale'] = $_REQUEST['locale'];
131
	        }
132
	    }
133
	    $options['statOwner'] = true;
134
	    $options['allowChmodReadOnly'] = true;
135
136
	    return $options;
137
	}
138
139
	/*********************************************************************/
140
	/*                               FS API                              */
141
	/*********************************************************************/
142
143
	/**
144
	 * Close opened connection.
145
	 *
146
	 * @return void
147
	 * @author Dmitry (dio) Levashov
148
	 **/
149
	public function umount()
150
	{
151
	    $this->connect && ftp_close($this->connect);
152
	}
153
154
	/*********************************************************************/
155
	/*                        INIT AND CONFIGURE                         */
156
	/*********************************************************************/
157
158
	/**
159
	 * Prepare FTP connection
160
	 * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn.
161
	 *
162
	 * @return bool
163
	 * @author Dmitry (dio) Levashov
164
	 * @author Cem (DiscoFever)
165
	 **/
166
	protected function init()
167
	{
168 View Code Duplication
	    if (! $this->options['host']
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
		|| ! $this->options['port']) {
170
	        return $this->setError('Required options undefined.');
171
	    }
172
173
	    if (! $this->options['user']) {
174
	        $this->options['user'] = 'anonymous';
175
	        $this->options['pass'] = '';
176
	    }
177
	    if (! $this->options['path']) {
178
	        $this->options['path'] = '/';
179
	    }
180
181
		// make ney mount key
182
		$this->netMountKey = md5(implode('-', ['ftp', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user']]));
183
184
	    if (! function_exists('ftp_connect')) {
185
	        return $this->setError('FTP extension not loaded.');
186
	    }
187
188
		// remove protocol from host
189
		$scheme = parse_url($this->options['host'], PHP_URL_SCHEME);
190
191
	    if ($scheme) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $scheme of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
192
	        $this->options['host'] = substr($this->options['host'], strlen($scheme) + 3);
193
	    }
194
195
		// normalize root path
196
		$this->root = $this->options['path'] = $this->_normpath($this->options['path']);
197
198 View Code Duplication
	    if (empty($this->options['alias'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
	        $this->options['alias'] = $this->options['user'].'@'.$this->options['host'];
200
			// $num = elFinder::$volumesCnt-1;
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
201
			// $this->options['alias'] = $this->root == '/' || $this->root == '.' ? 'FTP folder '.$num : basename($this->root);
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
202
	    }
203
204
	    $this->rootName = $this->options['alias'];
205
	    $this->options['separator'] = '/';
206
207
	    if (is_null($this->options['syncChkAsTs'])) {
208
	        $this->options['syncChkAsTs'] = true;
209
	    }
210
211
	    if (isset($this->options['ftpListOption'])) {
212
	        $this->ftpListOption = $this->options['ftpListOption'];
213
	    }
214
215
	    return $this->connect();
216
	}
217
218
	/**
219
	 * Configure after successfull mount.
220
	 *
221
	 * @return void
222
	 * @author Dmitry (dio) Levashov
223
	 **/
224
	protected function configure()
225
	{
226
	    parent::configure();
227
228 View Code Duplication
	    if (! empty($this->options['tmpPath'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
	        if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
230
	            $this->tmp = $this->options['tmpPath'];
231
	        }
232
	    }
233
	    if (! $this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
234
	        $this->tmp = $tmp;
235
	    }
236
237
		// fallback of $this->tmp
238
		if (! $this->tmp && $this->tmbPathWritable) {
239
		    $this->tmp = $this->tmbPath;
240
		}
241
242
	    if (! $this->tmp) {
243
	        $this->disabled[] = 'mkfile';
244
	        $this->disabled[] = 'paste';
245
	        $this->disabled[] = 'duplicate';
246
	        $this->disabled[] = 'upload';
247
	        $this->disabled[] = 'edit';
248
	        $this->disabled[] = 'archive';
249
	        $this->disabled[] = 'extract';
250
	    }
251
252
		// echo $this->tmp;
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
253
	}
254
255
	/**
256
	 * Connect to ftp server.
257
	 *
258
	 * @return bool
259
	 * @author Dmitry (dio) Levashov
260
	 **/
261
	protected function connect()
262
	{
263 View Code Duplication
	    if (! ($this->connect = ftp_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) {
0 ignored issues
show
Documentation Bug introduced by
It seems like ftp_connect($this->optio...is->options['timeout']) of type resource is incompatible with the declared type object<ftp> of property $connect.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
264
	        return $this->setError('Unable to connect to FTP server '.$this->options['host']);
265
	    }
266 View Code Duplication
	    if (! ftp_login($this->connect, $this->options['user'], $this->options['pass'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
	        $this->umount();
268
269
	        return $this->setError('Unable to login into '.$this->options['host']);
270
	    }
271
272
		// try switch utf8 mode
273
		if ($this->encoding) {
274
		    ftp_raw($this->connect, 'OPTS UTF8 OFF');
275
		} else {
276
		    ftp_raw($this->connect, 'OPTS UTF8 ON');
277
		}
278
279
		// switch off extended passive mode - may be usefull for some servers
280
		ftp_raw($this->connect, 'epsv4 off');
281
		// enter passive mode if required
282
		$pasv = ($this->options['mode'] == 'passive');
283
	    if (! ftp_pasv($this->connect, $pasv)) {
284
	        if ($pasv) {
285
	            $this->options['mode'] = 'active';
286
	        }
287
	    }
288
289
		// enter root folder
290
		if (! ftp_chdir($this->connect, $this->root)
291
		|| $this->root != ftp_pwd($this->connect)) {
292
		    $this->umount();
293
294
		    return $this->setError('Unable to open root folder.');
295
		}
296
297
		// check for MLST support
298
		$features = ftp_raw($this->connect, 'FEAT');
299
	    if (! is_array($features)) {
300
	        $this->umount();
301
302
	        return $this->setError('Server does not support command FEAT.');
303
	    }
304
305
	    foreach ($features as $feat) {
306
	        if (strpos(trim($feat), 'MLST') === 0) {
307
	            $this->MLSTsupprt = true;
308
	            break;
309
	        }
310
	    }
311
312
	    $help = ftp_raw($this->connect, 'HELP');
313
	    $this->isPureFtpd = stripos(implode(' ', $help), 'Pure-FTPd') !== false;
314
315
	    return true;
316
	}
317
318
	/**
319
	 * Call ftp_rawlist with option prefix.
320
	 *
321
	 * @param string $path
322
	 * @return array
323
	 */
324
	protected function ftpRawList($path)
325
	{
326
	    if ($this->isPureFtpd) {
327
	        $path = str_replace(' ', '\ ', $path);
328
	    }
329
	    if ($this->ftpListOption) {
330
	        $path = $this->ftpListOption.' '.$path;
331
	    }
332
	    $res = ftp_rawlist($this->connect, $path);
333
	    if ($res === false) {
334
	        $res = [];
335
	    }
336
337
	    return $res;
338
	}
339
340
	/**
341
	 * Parse line from ftp_rawlist() output and return file stat (array).
342
	 *
343
	 * @param  string $raw line from ftp_rawlist() output
344
	 * @param $base
345
	 * @param bool $nameOnly
346
	 * @return array
347
	 * @author Dmitry Levashov
348
	 */
349
	protected function parseRaw($raw, $base, $nameOnly = false)
350
	{
351
	    static $now;
352
	    static $lastyear;
353
354
	    if (! $now) {
355
	        $now = time();
356
	        $lastyear = date('Y') - 1;
357
	    }
358
359
	    $info = preg_split("/\s+/", $raw, 9);
360
	    $stat = [];
361
362 View Code Duplication
	    if (! isset($this->ftpOsUnix)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
363
	        $this->ftpOsUnix = ! preg_match('/\d/', substr($info[0], 0, 1));
364
	    }
365
	    if (! $this->ftpOsUnix) {
366
	        $info = $this->normalizeRawWindows($raw);
367
	    }
368
369
	    if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') {
370
	        return false;
371
	    }
372
373
	    $name = $info[8];
374
375
	    if (preg_match('|(.+)\-\>(.+)|', $name, $m)) {
376
	        $name = trim($m[1]);
377
			// check recursive processing
378
			if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) {
379
			    return [];
380
			}
381
	        if (! $nameOnly) {
382
	            $target = trim($m[2]);
383
	            if (substr($target, 0, 1) !== $this->separator) {
384
	                $target = $this->getFullPath($target, $base);
385
	            }
386
	            $target = $this->_normpath($target);
387
	            $stat['name'] = $name;
388
	            $stat['target'] = $target;
389
390
	            return $stat;
391
	        }
392
	    }
393
394
	    if ($nameOnly) {
395
	        return ['name' => $name];
396
	    }
397
398
	    if (is_numeric($info[5]) && ! $info[6] && ! $info[7]) {
399
	        // by normalizeRawWindows()
400
			$stat['ts'] = $info[5];
401
	    } else {
402
	        $stat['ts'] = strtotime($info[5].' '.$info[6].' '.$info[7]);
403 View Code Duplication
	        if ($stat['ts'] && $stat['ts'] > $now && strpos($info[7], ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
404
	            $stat['ts'] = strtotime($info[5].' '.$info[6].' '.$lastyear.' '.$info[7]);
405
	        }
406
	        if (empty($stat['ts'])) {
407
	            $stat['ts'] = strtotime($info[6].' '.$info[5].' '.$info[7]);
408 View Code Duplication
	            if ($stat['ts'] && $stat['ts'] > $now && strpos($info[7], ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
	                $stat['ts'] = strtotime($info[6].' '.$info[5].' '.$lastyear.' '.$info[7]);
410
	            }
411
	        }
412
	    }
413
414
	    if ($this->options['statOwner']) {
415
	        $stat['owner'] = $info[2];
416
	        $stat['group'] = $info[3];
417
	        $stat['perm'] = substr($info[0], 1);
418
	        $stat['isowner'] = $stat['owner'] ? ($stat['owner'] == $this->options['user']) : $this->options['owner'];
419
	    }
420
	    $owner = isset($stat['owner']) ? $stat['owner'] : '';
421
422
	    $perm = $this->parsePermissions($info[0], $owner);
423
	    $stat['name'] = $name;
424
	    $stat['mime'] = substr(strtolower($info[0]), 0, 1) == 'd' ? 'directory' : $this->mimetype($stat['name'], true);
425
	    $stat['size'] = $stat['mime'] == 'directory' ? 0 : $info[4];
426
	    $stat['read'] = $perm['read'];
427
	    $stat['write'] = $perm['write'];
428
429
	    return $stat;
430
	}
431
432
	/**
433
	 * Normalize MS-DOS style FTP LIST Raw line.
434
	 *
435
	 * @param  string  $raw  line from FTP LIST (MS-DOS style)
436
	 * @return array
437
	 * @author Naoki Sawada
438
	 **/
439
	protected function normalizeRawWindows($raw)
440
	{
441
	    $info = array_pad([], 9, '');
442
	    $item = preg_replace('#\s+#', ' ', trim($raw), 3);
443
	    list($date, $time, $size, $name) = explode(' ', $item, 4);
444
	    $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
445
	    $dateObj = DateTime::createFromFormat($format, $date.$time);
446
	    $info[5] = strtotime($dateObj->format('Y-m-d H:i'));
447
	    $info[8] = $name;
448
	    if ($size === '<DIR>') {
449
	        $info[4] = 0;
450
	        $info[0] = 'drwxr-xr-x';
451
	    } else {
452
	        $info[4] = (int) $size;
453
	        $info[0] = '-rw-r--r--';
454
	    }
455
456
	    return $info;
457
	}
458
459
	/**
460
	 * Parse permissions string. Return array(read => true/false, write => true/false).
461
	 *
462
	 * @param  string $perm permissions string
463
	 * @param string $user
464
	 * @return string
465
	 * @author Dmitry (dio) Levashov
466
	 */
467
	protected function parsePermissions($perm, $user = '')
468
	{
469
	    $res = [];
0 ignored issues
show
Unused Code introduced by
$res is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
470
	    $parts = [];
471
	    $owner = $user ? ($user == $this->options['user']) : $this->options['owner'];
472
	    for ($i = 0, $l = strlen($perm); $i < $l; $i++) {
473
	        $parts[] = substr($perm, $i, 1);
474
	    }
475
476
	    $read = ($owner && $parts[1] == 'r') || $parts[4] == 'r' || $parts[7] == 'r';
477
478
	    return [
479
			'read' => $parts[0] == 'd' ? $read && (($owner && $parts[3] == 'x') || $parts[6] == 'x' || $parts[9] == 'x') : $read,
480
			'write' => ($owner && $parts[2] == 'w') || $parts[5] == 'w' || $parts[8] == 'w',
481
		];
482
	}
483
484
	/**
485
	 * Cache dir contents.
486
	 *
487
	 * @param  string  $path  dir path
488
	 * @return void
489
	 * @author Dmitry Levashov
490
	 **/
491
	protected function cacheDir($path)
492
	{
493
	    $this->dirsCache[$path] = [];
494
	    $hasDir = false;
495
496
	    $list = [];
497
	    $encPath = $this->convEncIn($path);
498
	    foreach ($this->ftpRawList($encPath) as $raw) {
499
	        if (($stat = $this->parseRaw($raw, $encPath))) {
500
	            $list[] = $stat;
501
	        }
502
	    }
503
	    $list = $this->convEncOut($list);
504
	    $prefix = ($path === $this->separator) ? $this->separator : $path.$this->separator;
505
	    $targets = [];
506
	    foreach ($list as $stat) {
507
	        $p = $prefix.$stat['name'];
508 View Code Duplication
	        if (isset($stat['target'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
509
	            // stat later
510
				$targets[$stat['name']] = $stat['target'];
511
	        } else {
512
	            $stat = $this->updateCache($p, $stat);
513
	            if (empty($stat['hidden'])) {
514
	                if (! $hasDir && $stat['mime'] === 'directory') {
515
	                    $hasDir = true;
516
	                }
517
	                $this->dirsCache[$path][] = $p;
518
	            }
519
	        }
520
	    }
521
		// stat link targets
522
		foreach ($targets as $name => $target) {
523
		    $stat = [];
524
		    $stat['name'] = $name;
525
		    $p = $prefix.$name;
526
		    $cacheDirTarget = $this->cacheDirTarget;
527
		    $this->cacheDirTarget = $this->convEncIn($target, true);
528
		    if ($tstat = $this->stat($target)) {
529
		        $stat['size'] = $tstat['size'];
530
		        $stat['alias'] = $target;
531
		        $stat['thash'] = $tstat['hash'];
532
		        $stat['mime'] = $tstat['mime'];
533
		        $stat['read'] = $tstat['read'];
534
		        $stat['write'] = $tstat['write'];
535
536
		        if (isset($tstat['ts'])) {
537
		            $stat['ts'] = $tstat['ts'];
538
		        }
539
		        if (isset($tstat['owner'])) {
540
		            $stat['owner'] = $tstat['owner'];
541
		        }
542
		        if (isset($tstat['group'])) {
543
		            $stat['group'] = $tstat['group'];
544
		        }
545
		        if (isset($tstat['perm'])) {
546
		            $stat['perm'] = $tstat['perm'];
547
		        }
548
		        if (isset($tstat['isowner'])) {
549
		            $stat['isowner'] = $tstat['isowner'];
550
		        }
551
		    } else {
552
		        $stat['mime'] = 'symlink-broken';
553
		        $stat['read'] = false;
554
		        $stat['write'] = false;
555
		        $stat['size'] = 0;
556
		    }
557
		    $this->cacheDirTarget = $cacheDirTarget;
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
	    if (isset($this->sessionCache['subdirs'])) {
568
	        $this->sessionCache['subdirs'][$path] = $hasDir;
569
	    }
570
	}
571
572
	/**
573
	 * Return ftp transfer mode for file.
574
	 *
575
	 * @param  string  $path  file path
576
	 * @return string
577
	 * @author Dmitry (dio) Levashov
578
	 **/
579
	protected function ftpMode($path)
580
	{
581
	    return strpos($this->mimetype($path), 'text/') === 0 ? FTP_ASCII : FTP_BINARY;
582
	}
583
584
	/*********************** paths/urls *************************/
585
586
	/**
587
	 * Return parent directory path.
588
	 *
589
	 * @param  string  $path  file path
590
	 * @return string
591
	 * @author Naoki Sawada
592
	 **/
593
	protected function _dirname($path)
594
	{
595
	    $parts = explode($this->separator, trim($path, $this->separator));
596
	    array_pop($parts);
597
598
	    return $this->separator.implode($this->separator, $parts);
599
	}
600
601
	/**
602
	 * Return file name.
603
	 *
604
	 * @param  string  $path  file path
605
	 * @return string
606
	 * @author Naoki Sawada
607
	 **/
608
	protected function _basename($path)
609
	{
610
	    $parts = explode($this->separator, trim($path, $this->separator));
611
612
	    return array_pop($parts);
613
	}
614
615
	/**
616
	 * Join dir name and file name and retur full path.
617
	 *
618
	 * @param  string  $dir
619
	 * @param  string  $name
620
	 * @return string
621
	 * @author Dmitry (dio) Levashov
622
	 **/
623
	protected function _joinPath($dir, $name)
624
	{
625
	    return rtrim($dir, $this->separator).$this->separator.$name;
626
	}
627
628
	/**
629
	 * Return normalized path, this works the same as os.path.normpath() in Python.
630
	 *
631
	 * @param  string  $path  path
632
	 * @return string
633
	 * @author Troex Nevelin
634
	 **/
635
	protected function _normpath($path)
636
	{
637
	    if (empty($path)) {
638
	        $path = '.';
639
	    }
640
		// path must be start with /
641
		$path = preg_replace('|^\.\/?|', $this->separator, $path);
642
	    $path = preg_replace('/^([^\/])/', '/$1', $path);
643
644
	    if ($path[0] === $this->separator) {
645
	        $initial_slashes = true;
646
	    } else {
647
	        $initial_slashes = false;
648
	    }
649
650 View Code Duplication
	    if (($initial_slashes)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
651
		&& (strpos($path, '//') === 0)
652
		&& (strpos($path, '///') === false)) {
653
	        $initial_slashes = 2;
654
	    }
655
656
	    $initial_slashes = (int) $initial_slashes;
657
658
	    $comps = explode($this->separator, $path);
659
	    $new_comps = [];
660 View Code Duplication
	    foreach ($comps as $comp) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
661
	        if (in_array($comp, ['', '.'])) {
662
	            continue;
663
	        }
664
665
	        if (($comp != '..')
666
			|| (! $initial_slashes && ! $new_comps)
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_comps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
667
			|| ($new_comps && (end($new_comps) == '..'))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_comps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
668
	            array_push($new_comps, $comp);
669
	        } elseif ($new_comps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_comps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
670
	            array_pop($new_comps);
671
	        }
672
	    }
673
	    $comps = $new_comps;
674
	    $path = implode($this->separator, $comps);
675
	    if ($initial_slashes) {
676
	        $path = str_repeat($this->separator, $initial_slashes).$path;
677
	    }
678
679
	    return $path ? $path : '.';
680
	}
681
682
	/**
683
	 * Return file path related to root dir.
684
	 *
685
	 * @param  string  $path  file path
686
	 * @return string
687
	 * @author Dmitry (dio) Levashov
688
	 **/
689 View Code Duplication
	protected function _relpath($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
690
	{
691
	    if ($path === $this->root) {
692
	        return '';
693
	    } else {
694
	        if (strpos($path, $this->root) === 0) {
695
	            return ltrim(substr($path, strlen($this->root)), $this->separator);
696
	        } else {
697
	            // for link
698
				return $path;
699
	        }
700
	    }
701
	}
702
703
	/**
704
	 * Convert path related to root dir into real path.
705
	 *
706
	 * @param  string  $path  file path
707
	 * @return string
708
	 * @author Dmitry (dio) Levashov
709
	 **/
710
	protected function _abspath($path)
711
	{
712
	    if ($path === $this->separator) {
713
	        return $this->root;
714
	    } else {
715
	        if ($path[0] === $this->separator) {
716
	            // for link
717
				return $path;
718
	        } else {
719
	            return $this->_joinPath($this->root, $path);
720
	        }
721
	    }
722
	}
723
724
	/**
725
	 * Return fake path started from root dir.
726
	 *
727
	 * @param  string  $path  file path
728
	 * @return string
729
	 * @author Dmitry (dio) Levashov
730
	 **/
731
	protected function _path($path)
732
	{
733
	    return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
734
	}
735
736
	/**
737
	 * Return true if $path is children of $parent.
738
	 *
739
	 * @param  string  $path    path to check
740
	 * @param  string  $parent  parent path
741
	 * @return bool
742
	 * @author Dmitry (dio) Levashov
743
	 **/
744
	protected function _inpath($path, $parent)
745
	{
746
	    return $path == $parent || strpos($path, $parent.$this->separator) === 0;
747
	}
748
749
	/***************** file stat ********************/
750
751
	/**
752
	 * Return stat for given path.
753
	 * Stat contains following fields:
754
	 * - (int)    size    file size in b. required
755
	 * - (int)    ts      file modification time in unix time. required
756
	 * - (string) mime    mimetype. required for folders, others - optionally
757
	 * - (bool)   read    read permissions. required
758
	 * - (bool)   write   write permissions. required
759
	 * - (bool)   locked  is object locked. optionally
760
	 * - (bool)   hidden  is object hidden. optionally
761
	 * - (string) alias   for symlinks - link target path relative to root path. optionally
762
	 * - (string) target  for symlinks - link target path. optionally.
763
	 *
764
	 * If file does not exists - returns empty array or false.
765
	 *
766
	 * @param  string  $path    file path
767
	 * @return array|false
768
	 * @author Dmitry (dio) Levashov
769
	 **/
770
	protected function _stat($path)
771
	{
772
	    $outPath = $this->convEncOut($path);
773
	    if (isset($this->cache[$outPath])) {
774
	        return $this->convEncIn($this->cache[$outPath]);
775
	    } else {
776
	        $this->convEncIn();
777
	    }
778
	    if (! $this->MLSTsupprt) {
779
	        if ($path === $this->root) {
780
	            $res = [
781
					'name' => $this->root,
782
					'mime' => 'directory',
783
					'dirs' => $this->_subdirs($path),
784
				];
785
	            if ($this->isMyReload()) {
786
	                $ts = 0;
787
	                foreach ($this->ftpRawList($path) as $str) {
788
	                    if (($stat = $this->parseRaw($str, $path))) {
789
	                        $ts = max($ts, $stat['ts']);
790
	                    }
791
	                }
792
	                if ($ts) {
793
	                    $res['ts'] = $ts;
794
	                }
795
	            }
796
797
	            return $res;
798
	        }
799
			// stat of system root
800
			if ($path === $this->separator) {
801
			    $res = [
802
					'name' => $this->separator,
803
					'mime' => 'directory',
804
					'dirs' => 1,
805
				];
806
			    $this->cache[$outPath] = $res;
807
808
			    return $res;
809
			}
810
	        $parentSubdirs = null;
811
	        $outParent = $this->convEncOut($this->_dirname($path));
812 View Code Duplication
	        if (isset($this->sessionCache['subdirs']) && isset($this->sessionCache['subdirs'][$outParent])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
813
	            $parentSubdirs = $this->sessionCache['subdirs'][$outParent];
814
	        }
815
	        $this->cacheDir($outParent);
816
	        if ($parentSubdirs) {
817
	            $this->sessionCache['subdirs'][$outParent] = $parentSubdirs;
818
	        }
819
820
	        $stat = $this->convEncIn(isset($this->cache[$outPath]) ? $this->cache[$outPath] : []);
821
	        if (! $this->mounted) {
822
	            // dispose incomplete cache made by calling `stat` by 'startPath' option
823
				$this->cache = [];
824
	        }
825
826
	        return $stat;
827
	    }
828
	    $raw = ftp_raw($this->connect, 'MLST '.$path);
829
	    if (is_array($raw) && count($raw) > 1 && substr(trim($raw[0]), 0, 1) == 2) {
830
	        $parts = explode(';', trim($raw[1]));
831
	        array_pop($parts);
832
	        $parts = array_map('strtolower', $parts);
833
	        $stat = [];
834
	        $mode = '';
835
	        foreach ($parts as $part) {
836
	            list($key, $val) = explode('=', $part);
837
838
	            switch ($key) {
839
					case 'type':
840
						$stat['mime'] = strpos($val, 'dir') !== false ? 'directory' : $this->mimetype($path);
841
						break;
842
843
					case 'size':
844
						$stat['size'] = $val;
845
						break;
846
847
					case 'modify':
848
						$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));
849
						$stat['ts'] = $ts;
850
						break;
851
852
					case 'unix.mode':
853
						$mode = strval($val);
854
						break;
855
856
					case 'unix.uid':
857
						$stat['owner'] = $val;
858
						break;
859
860
					case 'unix.gid':
861
						$stat['group'] = $val;
862
						break;
863
864
					case 'perm':
865
						$val = strtolower($val);
866
						$stat['read'] = (int) preg_match('/e|l|r/', $val);
867
						$stat['write'] = (int) preg_match('/w|m|c/', $val);
868
						if (! preg_match('/f|d/', $val)) {
869
						    $stat['locked'] = 1;
870
						}
871
						break;
872
				}
873
	        }
874
	        if (empty($stat['mime'])) {
875
	            return [];
876
	        }
877
	        if ($stat['mime'] == 'directory') {
878
	            $stat['size'] = 0;
879
	        }
880
881
	        if ($mode) {
882
	            $stat['perm'] = '';
883
	            if ($mode[0] === '0') {
884
	                $mode = substr($mode, 1);
885
	            }
886
887
	            $perm = [];
888
	            for ($i = 0; $i <= 2; $i++) {
889
	                $perm[$i] = [false, false, false];
890
	                $n = isset($mode[$i]) ? $mode[$i] : 0;
891
892 View Code Duplication
	                if ($n - 4 >= 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
893
	                    $perm[$i][0] = true;
894
	                    $n = $n - 4;
895
	                    $stat['perm'] .= 'r';
896
	                } else {
897
	                    $stat['perm'] .= '-';
898
	                }
899
900 View Code Duplication
	                if ($n - 2 >= 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
901
	                    $perm[$i][1] = true;
902
	                    $n = $n - 2;
903
	                    $stat['perm'] .= 'w';
904
	                } else {
905
	                    $stat['perm'] .= '-';
906
	                }
907
908 View Code Duplication
	                if ($n - 1 == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
909
	                    $perm[$i][2] = true;
910
	                    $stat['perm'] .= 'x';
911
	                } else {
912
	                    $stat['perm'] .= '-';
913
	                }
914
	            }
915
916
	            $stat['perm'] = trim($stat['perm']);
917
918
	            $owner = $this->options['owner'];
919
	            $read = ($owner && $perm[0][0]) || $perm[1][0] || $perm[2][0];
920
921
	            $stat['read'] = $stat['mime'] == 'directory' ? $read && (($owner && $perm[0][2]) || $perm[1][2] || $perm[2][2]) : $read;
922
	            $stat['write'] = ($owner && $perm[0][1]) || $perm[1][1] || $perm[2][1];
923
924
	            if ($this->options['statOwner']) {
925
	                $stat['isowner'] = $owner;
926
	            } else {
927
	                unset($stat['owner'], $stat['group'], $stat['perm']);
928
	            }
929
	        }
930
931
	        return $stat;
932
	    }
933
934
	    return [];
935
	}
936
937
	/**
938
	 * Return true if path is dir and has at least one childs directory.
939
	 *
940
	 * @param  string  $path  dir path
941
	 * @return bool
942
	 * @author Dmitry (dio) Levashov
943
	 **/
944
	protected function _subdirs($path)
945
	{
946
	    foreach ($this->ftpRawList($path) as $str) {
947
	        $info = preg_split('/\s+/', $str, 9);
948 View Code Duplication
	        if (! isset($this->ftpOsUnix)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
949
	            $this->ftpOsUnix = ! preg_match('/\d/', substr($info[0], 0, 1));
950
	        }
951
	        if (! $this->ftpOsUnix) {
952
	            $info = $this->normalizeRawWindows($str);
953
	        }
954
	        $name = isset($info[8]) ? trim($info[8]) : '';
955
	        if ($name && $name !== '.' && $name !== '..' && substr(strtolower($info[0]), 0, 1) === 'd') {
956
	            return true;
957
	        }
958
	    }
959
960
	    return false;
961
	}
962
963
	/**
964
	 * Return object width and height
965
	 * Ususaly used for images, but can be realize for video etc...
966
	 *
967
	 * @param  string  $path  file path
968
	 * @param  string  $mime  file mime type
969
	 * @return string|false
970
	 * @author Dmitry (dio) Levashov
971
	 **/
972
	protected function _dimensions($path, $mime)
973
	{
974
	    $ret = false;
975
	    if ($imgsize = $this->getImageSize($path, $mime)) {
976
	        $ret = $imgsize['dimensions'];
977
	    }
978
979
	    return $ret;
980
	}
981
982
	/******************** file/dir content *********************/
983
984
	/**
985
	 * Return files list in directory.
986
	 *
987
	 * @param  string  $path  dir path
988
	 * @return array
989
	 * @author Dmitry (dio) Levashov
990
	 * @author Cem (DiscoFever)
991
	 **/
992
	protected function _scandir($path)
993
	{
994
	    $files = [];
995
996
	    foreach ($this->ftpRawList($path) as $str) {
997
	        if (($stat = $this->parseRaw($str, $path, true))) {
998
	            $files[] = $this->_joinPath($path, $stat['name']);
999
	        }
1000
	    }
1001
1002
	    return $files;
1003
	}
1004
1005
	/**
1006
	 * Open file and return file pointer.
1007
	 *
1008
	 * @param  string $path file path
1009
	 * @param string $mode
1010
	 * @return false|resource
1011
	 * @internal param bool $write open file for writing
1012
	 * @author Dmitry (dio) Levashov
1013
	 */
1014
	protected function _fopen($path, $mode = 'rb')
1015
	{
1016
	    // try ftp stream wrapper
1017
		if ($this->options['mode'] == 'passive' && ini_get('allow_url_fopen')) {
1018
		    $url = 'ftp://'.$this->options['user'].':'.$this->options['pass'].'@'.$this->options['host'].':'.$this->options['port'].$path;
1019
		    if (strtolower($mode[0]) === 'w') {
1020
		        $context = stream_context_create(['ftp' => ['overwrite' => true]]);
1021
		        $fp = fopen($url, $mode, false, $context);
1022
		    } else {
1023
		        $fp = fopen($url, $mode);
1024
		    }
1025
		    if ($fp) {
1026
		        return $fp;
1027
		    }
1028
		}
1029
1030
	    if ($this->tmp) {
1031
	        $local = $this->getTempFile($path);
1032
	        $fp = fopen($local, 'wb');
1033
	        if (ftp_fget($this->connect, $fp, $path, FTP_BINARY)) {
1034
	            fclose($fp);
1035
	            $fp = fopen($local, $mode);
1036
1037
	            return $fp;
1038
	        }
1039
	        fclose($fp);
1040
	        is_file($local) && unlink($local);
1041
	    }
1042
1043
	    return false;
1044
	}
1045
1046
	/**
1047
	 * Close opened file.
1048
	 *
1049
	 * @param  resource $fp file pointer
1050
	 * @param string $path
1051
	 * @return bool
1052
	 * @author Dmitry (dio) Levashov
1053
	 */
1054
	protected function _fclose($fp, $path = '')
1055
	{
1056
	    fclose($fp);
1057
	    if ($path) {
1058
	        unlink($this->getTempFile($path));
1059
	    }
1060
	}
1061
1062
	/********************  file/dir manipulations *************************/
1063
1064
	/**
1065
	 * Create dir and return created dir path or false on failed.
1066
	 *
1067
	 * @param  string  $path  parent dir path
1068
	 * @param string  $name  new directory name
1069
	 * @return string|bool
1070
	 * @author Dmitry (dio) Levashov
1071
	 **/
1072
	protected function _mkdir($path, $name)
1073
	{
1074
	    $path = $this->_joinPath($path, $name);
1075
	    if (ftp_mkdir($this->connect, $path) === false) {
1076
	        return false;
1077
	    }
1078
1079
	    $this->options['dirMode'] && ftp_chmod($this->connect, $this->options['dirMode'], $path);
1080
1081
	    return $path;
1082
	}
1083
1084
	/**
1085
	 * Create file and return it's path or false on failed.
1086
	 *
1087
	 * @param  string  $path  parent dir path
1088
	 * @param string  $name  new file name
1089
	 * @return string|bool
1090
	 * @author Dmitry (dio) Levashov
1091
	 **/
1092
	protected function _mkfile($path, $name)
1093
	{
1094
	    if ($this->tmp) {
1095
	        $path = $this->_joinPath($path, $name);
1096
	        $local = $this->getTempFile();
1097
	        $res = touch($local) && ftp_put($this->connect, $path, $local, FTP_ASCII);
1098
	        unlink($local);
1099
1100
	        return $res ? $path : false;
1101
	    }
1102
1103
	    return false;
1104
	}
1105
1106
	/**
1107
	 * Create symlink. FTP driver does not support symlinks.
1108
	 *
1109
	 * @param  string $target link target
1110
	 * @param  string $path symlink path
1111
	 * @param string $name
1112
	 * @return bool
1113
	 * @author Dmitry (dio) Levashov
1114
	 */
1115
	protected function _symlink($target, $path, $name)
1116
	{
1117
	    return false;
1118
	}
1119
1120
	/**
1121
	 * Copy file into another file.
1122
	 *
1123
	 * @param  string  $source     source file path
1124
	 * @param  string  $targetDir  target directory path
1125
	 * @param  string  $name       new file name
1126
	 * @return bool
1127
	 * @author Dmitry (dio) Levashov
1128
	 **/
1129
	protected function _copy($source, $targetDir, $name)
1130
	{
1131
	    $res = false;
1132
1133
	    if ($this->tmp) {
1134
	        $local = $this->getTempFile();
1135
	        $target = $this->_joinPath($targetDir, $name);
1136
1137 View Code Duplication
	        if (ftp_get($this->connect, $local, $source, FTP_BINARY)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1138
			&& ftp_put($this->connect, $target, $local, $this->ftpMode($target))) {
1139
	            $res = $target;
1140
	        }
1141
	        unlink($local);
1142
	    }
1143
1144
	    return $res;
1145
	}
1146
1147
	/**
1148
	 * Move file into another parent dir.
1149
	 * Return new file path or false.
1150
	 *
1151
	 * @param  string $source source file path
1152
	 * @param $targetDir
1153
	 * @param  string $name file name
1154
	 * @return bool|string
1155
	 * @internal param string $target target dir path
1156
	 * @author Dmitry (dio) Levashov
1157
	 */
1158
	protected function _move($source, $targetDir, $name)
1159
	{
1160
	    $target = $this->_joinPath($targetDir, $name);
1161
1162
	    return ftp_rename($this->connect, $source, $target) ? $target : false;
1163
	}
1164
1165
	/**
1166
	 * Remove file.
1167
	 *
1168
	 * @param  string  $path  file path
1169
	 * @return bool
1170
	 * @author Dmitry (dio) Levashov
1171
	 **/
1172
	protected function _unlink($path)
1173
	{
1174
	    return ftp_delete($this->connect, $path);
1175
	}
1176
1177
	/**
1178
	 * Remove dir.
1179
	 *
1180
	 * @param  string  $path  dir path
1181
	 * @return bool
1182
	 * @author Dmitry (dio) Levashov
1183
	 **/
1184
	protected function _rmdir($path)
1185
	{
1186
	    return ftp_rmdir($this->connect, $path);
1187
	}
1188
1189
	/**
1190
	 * Create new file and write into it from file pointer.
1191
	 * Return new file path or false on error.
1192
	 *
1193
	 * @param  resource  $fp   file pointer
1194
	 * @param  string    $dir  target dir path
1195
	 * @param  string    $name file name
1196
	 * @param  array     $stat file stat (required by some virtual fs)
1197
	 * @return bool|string
1198
	 * @author Dmitry (dio) Levashov
1199
	 **/
1200
	protected function _save($fp, $dir, $name, $stat)
1201
	{
1202
	    $path = $this->_joinPath($dir, $name);
1203
1204
	    return ftp_fput($this->connect, $path, $fp, $this->ftpMode($path))
1205
			? $path
1206
			: false;
1207
	}
1208
1209
	/**
1210
	 * Get file contents.
1211
	 *
1212
	 * @param  string  $path  file path
1213
	 * @return string|false
1214
	 * @author Dmitry (dio) Levashov
1215
	 **/
1216
	protected function _getContents($path)
1217
	{
1218
	    $contents = '';
1219
	    if (($fp = $this->_fopen($path))) {
1220
	        while (! feof($fp)) {
1221
	            $contents .= fread($fp, 8192);
1222
	        }
1223
	        $this->_fclose($fp, $path);
1224
1225
	        return $contents;
1226
	    }
1227
1228
	    return false;
1229
	}
1230
1231
	/**
1232
	 * Write a string to a file.
1233
	 *
1234
	 * @param  string  $path     file path
1235
	 * @param  string  $content  new file content
1236
	 * @return bool
1237
	 * @author Dmitry (dio) Levashov
1238
	 **/
1239
	protected function _filePutContents($path, $content)
1240
	{
1241
	    $res = false;
1242
1243
	    if ($this->tmp) {
1244
	        $local = $this->getTempFile();
1245
1246
	        if (file_put_contents($local, $content, LOCK_EX) !== false
1247
			&& ($fp = fopen($local, 'rb'))) {
1248
	            clearstatcache();
1249
	            $res = ftp_fput($this->connect, $path, $fp, $this->ftpMode($path));
1250
	            fclose($fp);
1251
	        }
1252
	        file_exists($local) && unlink($local);
1253
	    }
1254
1255
	    return $res;
1256
	}
1257
1258
	/**
1259
	 * Detect available archivers.
1260
	 *
1261
	 * @return void
1262
	 **/
1263
	protected function _checkArchivers()
1264
	{
1265
	    $this->archivers = $this->getArchivers();
1266
	}
1267
1268
	/**
1269
	 * chmod availability.
1270
	 *
1271
	 * @param string $path
1272
	 * @param string $mode
1273
	 * @return bool
1274
	 */
1275
	protected function _chmod($path, $mode)
1276
	{
1277
	    $modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf('%04o', $mode));
1278
1279
	    return ftp_chmod($this->connect, $modeOct, $path);
1280
	}
1281
1282
	/**
1283
	 * Recursive symlinks search.
1284
	 *
1285
	 * @param  string  $path  file/dir path
1286
	 * @return bool
1287
	 * @author Dmitry (dio) Levashov
1288
	 **/
1289
	protected function _findSymlinks($path)
0 ignored issues
show
Unused Code introduced by
The parameter $path is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1290
	{
1291
	    die('Not yet implemented. (_findSymlinks)');
0 ignored issues
show
Coding Style Compatibility introduced by
The method _findSymlinks() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1292
	}
1293
1294
	/**
1295
	 * Extract files from archive.
1296
	 *
1297
	 * @param  string  $path  archive path
1298
	 * @param  array   $arc   archiver command and arguments (same as in $this->archivers)
1299
	 * @return true
1300
	 * @author Dmitry (dio) Levashov,
1301
	 * @author Alexey Sukhotin
1302
	 **/
1303
	protected function _extract($path, $arc)
1304
	{
1305
	    $dir = $this->tempDir();
1306
	    if (! $dir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dir of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1307
	        return false;
1308
	    }
1309
1310
	    $basename = $this->_basename($path);
1311
	    $localPath = $dir.DIRECTORY_SEPARATOR.$basename;
1312
1313
	    if (! ftp_get($this->connect, $localPath, $path, FTP_BINARY)) {
1314
	        //cleanup
1315
			$this->rmdirRecursive($dir);
1316
1317
	        return false;
1318
	    }
1319
1320
	    $this->unpackArchive($localPath, $arc);
1321
1322
	    $this->archiveSize = 0;
0 ignored issues
show
Bug introduced by
The property archiveSize does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1323
1324
		// find symlinks and check extracted items
1325
		$checkRes = $this->checkExtractItems($dir);
1326 View Code Duplication
	    if ($checkRes['symlinks']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1327
	        $this->rmdirRecursive($dir);
1328
1329
	        return $this->setError(array_merge($this->error, [elFinder::ERROR_ARC_SYMLINKS]));
1330
	    }
1331
	    $this->archiveSize = $checkRes['totalSize'];
1332
	    if ($checkRes['rmNames']) {
1333
	        foreach ($checkRes['rmNames'] as $name) {
1334
	            $this->addError(elFinder::ERROR_SAVE, $name);
1335
	        }
1336
	    }
1337
1338
	    $filesToProcess = self::listFilesInDirectory($dir, true);
1339
1340
		// no files - extract error ?
1341
		if (empty($filesToProcess)) {
1342
		    $this->rmdirRecursive($dir);
1343
1344
		    return false;
1345
		}
1346
1347
		// check max files size
1348 View Code Duplication
		if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1349
		    $this->rmdirRecursive($dir);
1350
1351
		    return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
1352
		}
1353
1354
	    $extractTo = $this->extractToNewdir; // 'auto', ture or false
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1355
1356
		// archive contains one item - extract in archive dir
1357
		$name = '';
1358
	    $src = $dir.DIRECTORY_SEPARATOR.$filesToProcess[0];
1359
	    if (($extractTo === 'auto' || ! $extractTo) && count($filesToProcess) === 1 && is_file($src)) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $extractTo (integer) and 'auto' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1360
	        $name = $filesToProcess[0];
1361
	    } elseif ($extractTo === 'auto' || $extractTo) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $extractTo (integer) and 'auto' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1362
	        // for several files - create new directory
1363
			// create unique name for directory
1364
			$src = $dir;
1365
	        $name = basename($path);
1366 View Code Duplication
	        if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1367
	            $name = substr($name, 0, strlen($name) - strlen($m[0]));
1368
	        }
1369
	        $test = $this->_joinPath(dirname($path), $name);
1370
	        if ($this->stat($test)) {
1371
	            $name = $this->uniqueName(dirname($path), $name, '-', false);
1372
	        }
1373
	    }
1374
1375
	    if ($name !== '' && is_file($src)) {
1376
	        $result = $this->_joinPath(dirname($path), $name);
1377
1378
	        if (! ftp_put($this->connect, $result, $src, FTP_BINARY)) {
1379
	            $this->rmdirRecursive($dir);
1380
1381
	            return false;
1382
	        }
1383
	    } else {
1384
	        $dstDir = $this->_dirname($path);
1385
	        $result = [];
1386
	        if (is_dir($src) && $name) {
1387
	            $target = $this->_joinPath($dstDir, $name);
1388
	            $_stat = $this->_stat($target);
1389 View Code Duplication
	            if ($_stat) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1390
	                if (! $this->options['copyJoin']) {
1391
	                    if ($_stat['mime'] === 'directory') {
1392
	                        $this->delTree($target);
1393
	                    } else {
1394
	                        $this->_unlink($target);
1395
	                    }
1396
	                    $_stat = false;
1397
	                } else {
1398
	                    $dstDir = $target;
1399
	                }
1400
	            }
1401
	            if (! $_stat && (! $dstDir = $this->_mkdir($dstDir, $name))) {
1402
	                $this->rmdirRecursive($dir);
1403
1404
	                return false;
1405
	            }
1406
	            $result[] = $dstDir;
1407
	        }
1408
	        foreach ($filesToProcess as $name) {
1409
	            $name = rtrim($name, DIRECTORY_SEPARATOR);
1410
	            $src = $dir.DIRECTORY_SEPARATOR.$name;
1411
	            if (is_dir($src)) {
1412
	                $p = dirname($name);
1413
	                if ($p === '.') {
1414
	                    $p = '';
1415
	                }
1416
	                $name = basename($name);
1417
	                $target = $this->_joinPath($this->_joinPath($dstDir, $p), $name);
0 ignored issues
show
Security Bug introduced by
It seems like $dstDir defined by $this->_mkdir($dstDir, $name) on line 1401 can also be of type false; however, elFinderVolumeFTP::_joinPath() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1418
	                $_stat = $this->_stat($target);
1419 View Code Duplication
	                if ($_stat) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1420
	                    if (! $this->options['copyJoin']) {
1421
	                        if ($_stat['mime'] === 'directory') {
1422
	                            $this->delTree($target);
1423
	                        } else {
1424
	                            $this->_unlink($target);
1425
	                        }
1426
	                        $_stat = false;
1427
	                    }
1428
	                }
1429
	                if (! $_stat && (! $target = $this->_mkdir($this->_joinPath($dstDir, $p), $name))) {
0 ignored issues
show
Security Bug introduced by
It seems like $dstDir defined by $this->_mkdir($dstDir, $name) on line 1401 can also be of type false; however, elFinderVolumeFTP::_joinPath() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1430
	                    $this->rmdirRecursive($dir);
1431
1432
	                    return false;
1433
	                }
1434 View Code Duplication
	            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1435
	                $target = $this->_joinPath($dstDir, $name);
0 ignored issues
show
Security Bug introduced by
It seems like $dstDir defined by $this->_mkdir($dstDir, $name) on line 1401 can also be of type false; however, elFinderVolumeFTP::_joinPath() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1436
	                if (! ftp_put($this->connect, $target, $src, FTP_BINARY)) {
1437
	                    $this->rmdirRecursive($dir);
1438
1439
	                    return false;
1440
	                }
1441
	            }
1442
	            $result[] = $target;
1443
	        }
1444
	        if (! $result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1445
	            $this->rmdirRecursive($dir);
1446
1447
	            return false;
1448
	        }
1449
	    }
1450
1451
	    is_dir($dir) && $this->rmdirRecursive($dir);
1452
1453
	    $this->clearcache();
1454
1455
	    return $result ? $result : false;
1456
	}
1457
1458
	/**
1459
	 * Create archive and return its path.
1460
	 *
1461
	 * @param  string  $dir    target dir
1462
	 * @param  array   $files  files names list
1463
	 * @param  string  $name   archive name
1464
	 * @param  array   $arc    archiver options
1465
	 * @return string|bool
1466
	 * @author Dmitry (dio) Levashov,
1467
	 * @author Alexey Sukhotin
1468
	 **/
1469
	protected function _archive($dir, $files, $name, $arc)
1470
	{
1471
	    // get current directory
1472
		$cwd = getcwd();
0 ignored issues
show
Unused Code introduced by
$cwd is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1473
1474
	    $tmpDir = $this->tempDir();
1475
	    if (! $tmpDir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tmpDir of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1476
	        return false;
1477
	    }
1478
1479
		//download data
1480
		if (! $this->ftp_download_files($dir, $files, $tmpDir)) {
1481
		    //cleanup
1482
			$this->rmdirRecursive($tmpDir);
1483
1484
		    return false;
1485
		}
1486
1487
	    $remoteArchiveFile = false;
1488
	    if ($path = $this->makeArchive($tmpDir, $files, $name, $arc)) {
1489
	        $remoteArchiveFile = $this->_joinPath($dir, $name);
1490
	        if (! ftp_put($this->connect, $remoteArchiveFile, $path, FTP_BINARY)) {
1491
	            $remoteArchiveFile = false;
1492
	        }
1493
	    }
1494
1495
		//cleanup
1496
		if (! $this->rmdirRecursive($tmpDir)) {
1497
		    return false;
1498
		}
1499
1500
	    return $remoteArchiveFile;
1501
	}
1502
1503
	/**
1504
	 * Gets an array of absolute remote FTP paths of files and
1505
	 * folders in $remote_directory omitting symbolic links.
1506
	 *
1507
	 * @param $remote_directory string remote FTP path to scan for file and folders recursively
1508
	 * @param $targets          array  Array of target item. `null` is to get all of items
1509
	 * @return array of elements each of which is an array of two elements:
1510
	 * <ul>
1511
	 * <li>$item['path'] - absolute remote FTP path</li>
1512
	 * <li>$item['type'] - either 'f' for file or 'd' for directory</li>
1513
	 * </ul>
1514
	 */
1515
	protected function ftp_scan_dir($remote_directory, $targets = null)
1516
	{
1517
	    $buff = $this->ftpRawList($remote_directory);
1518
	    $items = [];
1519
	    if ($targets && is_array($targets)) {
1520
	        $targets = array_flip($targets);
1521
	    } else {
1522
	        $targets = false;
1523
	    }
1524
	    foreach ($buff as $str) {
1525
	        $info = preg_split("/\s+/", $str, 9);
1526 View Code Duplication
	        if (! isset($this->ftpOsUnix)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1527
	            $this->ftpOsUnix = ! preg_match('/\d/', substr($info[0], 0, 1));
1528
	        }
1529
	        if (! $this->ftpOsUnix) {
1530
	            $info = $this->normalizeRawWindows($str);
1531
	        }
1532
	        $type = substr($info[0], 0, 1);
1533
	        $name = trim($info[8]);
1534
	        if ($name !== '.' && $name !== '..' && (! $targets || isset($targets[$name]))) {
1535
	            switch ($type) {
1536
					case 'l': //omit symbolic links
1537
					case 'd':
1538
						$remote_file_path = $this->_joinPath($remote_directory, $name);
1539
						$item = [];
1540
						$item['path'] = $remote_file_path;
1541
						$item['type'] = 'd'; // normal file
1542
						$items[] = $item;
1543
						$items = array_merge($items, $this->ftp_scan_dir($remote_file_path));
1544
						break;
1545
					default:
1546
						$remote_file_path = $this->_joinPath($remote_directory, $name);
1547
						$item = [];
1548
						$item['path'] = $remote_file_path;
1549
						$item['type'] = 'f'; // normal file
1550
						$items[] = $item;
1551
				}
1552
	        }
1553
	    }
1554
1555
	    return $items;
1556
	}
1557
1558
	/**
1559
	 * Create writable temporary directory and return path to it.
1560
	 * @return string path to the new temporary directory or false in case of error.
1561
	 */
1562
	private function tempDir()
1563
	{
1564
	    $tempPath = tempnam($this->tmp, 'elFinder');
1565
	    if (! $tempPath) {
1566
	        $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1567
1568
	        return false;
1569
	    }
1570
	    $success = unlink($tempPath);
1571
	    if (! $success) {
1572
	        $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1573
1574
	        return false;
1575
	    }
1576
	    $success = mkdir($tempPath, 0700, true);
1577
	    if (! $success) {
1578
	        $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1579
1580
	        return false;
1581
	    }
1582
1583
	    return $tempPath;
1584
	}
1585
1586
	/**
1587
	 * Downloads specified files from remote directory
1588
	 * if there is a directory among files it is downloaded recursively (omitting symbolic links).
1589
	 *
1590
	 * @param $remote_directory string remote FTP path to a source directory to download from.
1591
	 * @param array $files list of files to download from remote directory.
1592
	 * @param $dest_local_directory string destination folder to store downloaded files.
1593
	 * @return bool true on success and false on failure.
1594
	 */
1595
	private function ftp_download_files($remote_directory, array $files, $dest_local_directory)
1596
	{
1597
	    $contents = $this->ftp_scan_dir($remote_directory, $files);
1598
	    if (! isset($contents)) {
1599
	        $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1600
1601
	        return false;
1602
	    }
1603
	    $remoteDirLen = strlen($remote_directory);
1604
	    foreach ($contents as $item) {
1605
	        $relative_path = substr($item['path'], $remoteDirLen);
1606
	        $local_path = $dest_local_directory.DIRECTORY_SEPARATOR.$relative_path;
1607
	        switch ($item['type']) {
1608
				case 'd':
1609
					$success = mkdir($local_path);
1610
					break;
1611
				case 'f':
1612
					$success = ftp_get($this->connect, $local_path, $item['path'], FTP_BINARY);
1613
					break;
1614
				default:
1615
					$success = true;
1616
			}
1617
	        if (! $success) {
1618
	            $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1619
1620
	            return false;
1621
	        }
1622
	    }
1623
1624
	    return true;
1625
	}
1626
1627
	/**
1628
	 * Delete local directory recursively.
1629
	 * @param $dirPath string to directory to be erased.
1630
	 * @return bool true on success and false on failure.
1631
	 */
1632
	private function deleteDir($dirPath)
1633
	{
1634
	    if (! is_dir($dirPath)) {
1635
	        $success = unlink($dirPath);
1636
	    } else {
1637
	        $success = true;
1638
	        foreach (array_reverse(self::listFilesInDirectory($dirPath, false)) as $path) {
1639
	            $path = $dirPath.DIRECTORY_SEPARATOR.$path;
1640
	            if (is_link($path)) {
1641
	                unlink($path);
1642
	            } elseif (is_dir($path)) {
1643
	                $success = rmdir($path);
1644
	            } else {
1645
	                $success = unlink($path);
1646
	            }
1647
	            if (! $success) {
1648
	                break;
1649
	            }
1650
	        }
1651
	        if ($success) {
1652
	            $success = rmdir($dirPath);
1653
	        }
1654
	    }
1655
	    if (! $success) {
1656
	        $this->setError(elFinder::ERROR_RM, $dirPath);
1657
1658
	        return false;
1659
	    }
1660
1661
	    return $success;
1662
	}
1663
1664
	/**
1665
	 * Returns array of strings containing all files and folders in the specified local directory.
1666
	 * @param $dir
1667
	 * @param $omitSymlinks
1668
	 * @param string $prefix
1669
	 * @return array array of files and folders names relative to the $path
1670
	 * or an empty array if the directory $path is empty,
1671
	 * <br />
1672
	 * false if $path is not a directory or does not exist.
1673
	 * @throws Exception
1674
	 * @internal param string $path path to directory to scan.
1675
	 */
1676
	private static function listFilesInDirectory($dir, $omitSymlinks, $prefix = '')
1677
	{
1678
	    if (! is_dir($dir)) {
1679
	        return false;
1680
	    }
1681
	    $excludes = ['.', '..'];
1682
	    $result = [];
1683
	    $files = self::localScandir($dir);
1684
	    if (! $files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1685
	        return [];
1686
	    }
1687
	    foreach ($files as $file) {
1688
	        if (! in_array($file, $excludes)) {
1689
	            $path = $dir.DIRECTORY_SEPARATOR.$file;
1690
	            if (is_link($path)) {
1691
	                if ($omitSymlinks) {
1692
	                    continue;
1693
	                } else {
1694
	                    $result[] = $prefix.$file;
1695
	                }
1696
	            } elseif (is_dir($path)) {
1697
	                $result[] = $prefix.$file.DIRECTORY_SEPARATOR;
1698
	                $subs = self::listFilesInDirectory($path, $omitSymlinks, $prefix.$file.DIRECTORY_SEPARATOR);
1699
	                if ($subs) {
1700
	                    $result = array_merge($result, $subs);
1701
	                }
1702
	            } else {
1703
	                $result[] = $prefix.$file;
1704
	            }
1705
	        }
1706
	    }
1707
1708
	    return $result;
1709
	}
1710
} // END class
1711