Completed
Push — 2.x ( 516b1a...db0ebf )
by Naoki
07:34
created

elFinderVolumeFTP::_fopen()   C

Complexity

Conditions 7
Paths 14

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 29
rs 6.7273
cc 7
eloc 20
nc 14
nop 2
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
	 * Tmp folder path
53
	 *
54
	 * @var string
55
	 **/
56
	protected $tmp = '';
57
	
58
	/**
59
	 * Net mount key
60
	 *
61
	 * @var string
62
	 **/
63
	public $netMountKey = '';
64
	
65
	/**
66
	 * FTP command `MLST` support
67
	 * 
68
	 * @var bool
69
	 */
70
	private $MLSTsupprt = false;
71
	
72
	/**
73
	 * Calling cacheDir() target path with non-MLST
74
	 * 
75
	 * @var string
76
	 */
77
	private $cacheDirTarget = '';
78
	
79
	/**
80
	 * Constructor
81
	 * Extend options with required fields
82
	 *
83
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
84
	 * @author Dmitry (dio) Levashov
85
	 * @author Cem (DiscoFever)
86
	 **/
87
	public function __construct() {
88
		$opts = array(
89
			'host'          => 'localhost',
90
			'user'          => '',
91
			'pass'          => '',
92
			'port'          => 21,
93
			'mode'        	=> 'passive',
94
			'path'			=> '/',
95
			'timeout'		=> 20,
96
			'owner'         => true,
97
			'tmbPath'       => '',
98
			'tmpPath'       => '',
99
			'dirMode'       => 0755,
100
			'fileMode'      => 0644,
101
			'rootCssClass'  => 'elfinder-navbar-root-ftp'
102
			
103
		);
104
		$this->options = array_merge($this->options, $opts); 
105
		$this->options['mimeDetect'] = 'internal';
106
		$this->options['maxArcFilesSize'] = 0;     // max allowed archive files size (0 - no limit)
107
	}
108
	
109
	/**
110
	 * Prepare
111
	 * Call from elFinder::netmout() before volume->mount()
112
	 *
113
	 * @return Array
114
	 * @author Naoki Sawada
115
	 **/
116
	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...
117
		if (!empty($_REQUEST['encoding']) && @iconv('UTF-8', $_REQUEST['encoding'], '') !== false) {
118
			$options['encoding'] = $_REQUEST['encoding'];
119
			if (!empty($_REQUEST['locale']) && @setlocale(LC_ALL, $_REQUEST['locale'])) {
120
				setlocale(LC_ALL, elFinder::$locale);
121
				$options['locale'] = $_REQUEST['locale'];
122
			}
123
		}
124
		$options['statOwner'] = true;
125
		$options['allowChmodReadOnly'] = true;
126
		return $options;
127
	}
128
	
129
	/*********************************************************************/
130
	/*                        INIT AND CONFIGURE                         */
131
	/*********************************************************************/
132
	
133
	/**
134
	 * Prepare FTP connection
135
	 * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn
136
	 *
137
	 * @return bool
138
	 * @author Dmitry (dio) Levashov
139
	 * @author Cem (DiscoFever)
140
	 **/
141
	protected function init() {
142
		if (!$this->options['host'] 
143
		||  !$this->options['port']) {
144
			return $this->setError('Required options undefined.');
145
		}
146
		
147
		if (!$this->options['user']) {
148
			$this->options['user'] = 'anonymous';
149
			$this->options['pass'] = '';
150
		}
151
		if (!$this->options['path']) {
152
			$this->options['path'] = '/';
153
		}
154
		
155
		// make ney mount key
156
		$this->netMountKey = md5(join('-', array('ftp', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user'])));
157
158
		if (!function_exists('ftp_connect')) {
159
			return $this->setError('FTP extension not loaded.');
160
		}
161
162
		// remove protocol from host
163
		$scheme = parse_url($this->options['host'], PHP_URL_SCHEME);
164
165
		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...
166
			$this->options['host'] = substr($this->options['host'], strlen($scheme)+3);
167
		}
168
169
		// normalize root path
170
		$this->root = $this->options['path'] = $this->_normpath($this->options['path']);
171
		
172 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...
173
			$this->options['alias'] = $this->options['user'].'@'.$this->options['host'];
174
			// $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...
175
			// $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...
176
		}
177
178
		$this->rootName = $this->options['alias'];
179
		$this->options['separator'] = '/';
180
		
181
		return $this->connect();
182
		
183
	}
184
185
186
	/**
187
	 * Configure after successfull mount.
188
	 *
189
	 * @return void
190
	 * @author Dmitry (dio) Levashov
191
	 **/
192
	protected function configure() {
193
		parent::configure();
194
		
195 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...
196
			if ((is_dir($this->options['tmpPath']) || @mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
197
				$this->tmp = $this->options['tmpPath'];
198
			}
199
		}
200
		
201
		if (!$this->tmp && $this->tmbPath) {
202
			$this->tmp = $this->tmbPath;
203
		}
204
		
205
		if (!$this->tmp) {
206
			$this->disabled[] = 'mkfile';
207
			$this->disabled[] = 'paste';
208
			$this->disabled[] = 'duplicate';
209
			$this->disabled[] = 'upload';
210
			$this->disabled[] = 'edit';
211
			$this->disabled[] = 'archive';
212
			$this->disabled[] = 'extract';
213
		}
214
		
215
		// 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...
216
		
217
	}
218
	
219
	/**
220
	 * Connect to ftp server
221
	 *
222
	 * @return bool
223
	 * @author Dmitry (dio) Levashov
224
	 **/
225
	protected function connect() {
226 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...
227
			return $this->setError('Unable to connect to FTP server '.$this->options['host']);
228
		}
229 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...
230
			$this->umount();
231
			return $this->setError('Unable to login into '.$this->options['host']);
232
		}
233
		
234
		// try switch utf8 mode
235
		if ($this->encoding) {
236
			@ftp_exec($this->connect, 'OPTS UTF8 OFF');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
237
		} else {
238
			@ftp_exec($this->connect, 'OPTS UTF8 ON' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
239
		}
240
		
241
		// switch off extended passive mode - may be usefull for some servers
242
		@ftp_exec($this->connect, 'epsv4 off' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
243
		// enter passive mode if required
244
		ftp_pasv($this->connect, $this->options['mode'] == 'passive');
245
246
		// enter root folder
247
		if (!ftp_chdir($this->connect, $this->root) 
248
		|| $this->root != ftp_pwd($this->connect)) {
249
			$this->umount();
250
			return $this->setError('Unable to open root folder.');
251
		}
252
		
253
		// check for MLST support
254
		$features = ftp_raw($this->connect, 'FEAT');
255
		if (!is_array($features)) {
256
			$this->umount();
257
			return $this->setError('Server does not support command FEAT.');
258
		}
259
260
		foreach ($features as $feat) {
261
			if (strpos(trim($feat), 'MLST') === 0) {
262
				$this->MLSTsupprt = true;
263
				break;
264
			}
265
		}
266
		
267
		return true;
268
	}
269
	
270
	/*********************************************************************/
271
	/*                               FS API                              */
272
	/*********************************************************************/
273
274
	/**
275
	 * Close opened connection
276
	 *
277
	 * @return void
278
	 * @author Dmitry (dio) Levashov
279
	 **/
280
	public function umount() {
281
		$this->connect && @ftp_close($this->connect);
282
	}
283
284
285
	/**
286
	 * Parse line from ftp_rawlist() output and return file stat (array)
287
	 *
288
	 * @param  string  $raw  line from ftp_rawlist() output
289
	 * @return array
290
	 * @author Dmitry Levashov
291
	 **/
292
	protected function parseRaw($raw, $base, $nameOnly = false) {
293
		$info = preg_split("/\s+/", $raw, 9);
294
		$stat = array();
295
296 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...
297
			$this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
298
		}
299
		if (!$this->ftpOsUnix) {
300
			$info = $this->normalizeRawWindows($raw);
301
		}
302
		
303
		if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') {
304
			return false;
305
		}
306
		
307
		$name = $info[8];
308
		
309
		if (preg_match('|(.+)\-\>(.+)|', $name, $m)) {
310
			$name   = trim($m[1]);
311
			// check recursive processing
312
			if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) {
313
				return array();
314
			}
315
			if (!$nameOnly) {
316
				$target = trim($m[2]);
317
				if (substr($target, 0, 1) !== $this->separator) {
318
					$target = $this->getFullPath($target, $base);
319
				}
320
				$target = $this->_normpath($target);
321
				$stat['name']   = $name;
322
				$stat['target'] = $target;
323
				return $stat;
324
			}
325
		}
326
		
327
		if ($nameOnly) {
328
			return array('name' => $name);
329
		}
330
		
331
		if (is_numeric($info[5]) && !$info[6] && !$info[7]) {
332
			// by normalizeRawWindows()
333
			$stat['ts'] = $info[5];
334
		} else {
335
			$stat['ts'] = strtotime($info[5].' '.$info[6].' '.$info[7]);
336
			if (empty($stat['ts'])) {
337
				$stat['ts'] = strtotime($info[6].' '.$info[5].' '.$info[7]);
338
			}
339
		}
340
		
341
		$stat['owner'] = '';
342
		if ($this->options['statOwner']) {
343
			$stat['owner'] = $info[2];
344
			$stat['group'] = $info[3];
345
			$stat['perm']  = substr($info[0], 1);
346
			$stat['isowner'] = $stat['owner']? ($stat['owner'] == $this->options['user']) : $this->options['owner'];
347
		}
348
		
349
		$perm = $this->parsePermissions($info[0], $stat['owner']);
350
		$stat['name']  = $name;
351
		$stat['mime']  = substr(strtolower($info[0]), 0, 1) == 'd' ? 'directory' : $this->mimetype($stat['name']);
352
		$stat['size']  = $stat['mime'] == 'directory' ? 0 : $info[4];
353
		$stat['read']  = $perm['read'];
354
		$stat['write'] = $perm['write'];
355
		
356
		return $stat;
357
	}
358
	
359
	/**
360
	 * Normalize MS-DOS style FTP LIST Raw line
361
	 *
362
	 * @param  string  $raw  line from FTP LIST (MS-DOS style)
363
	 * @return array
364
	 * @author Naoki Sawada
365
	 **/
366
	protected function normalizeRawWindows($raw) {
367
		$info = array_pad(array(), 9, '');
368
		$item = preg_replace('#\s+#', ' ', trim($raw), 3);
369
		list($date, $time, $size, $name) = explode(' ', $item, 4);
370
		$format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
371
		$dateObj = DateTime::createFromFormat($format, $date.$time);
0 ignored issues
show
Bug introduced by
The method createFromFormat() does not exist on DateTime. Did you maybe mean format()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
372
		$info[5] = strtotime($dateObj->format('Y-m-d H:i'));
373
		$info[8] = $name;
374
		if ($size === '<DIR>') {
375
			$info[4] = 0;
376
			$info[0] = 'drwxr-xr-x';
377
		} else {
378
			$info[4] = (int)$size;
379
			$info[0] = '-rw-r--r--';
380
		}
381
		return $info;
382
	}
383
	
384
	/**
385
	 * Parse permissions string. Return array(read => true/false, write => true/false)
386
	 *
387
	 * @param  string  $perm  permissions string
388
	 * @return string
389
	 * @author Dmitry (dio) Levashov
390
	 **/
391
	protected function parsePermissions($perm, $user = '') {
392
		$res   = array();
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...
393
		$parts = array();
394
		$owner = $user? ($user == $this->options['user']) : $this->options['owner'];
395
		for ($i = 0, $l = strlen($perm); $i < $l; $i++) {
396
			$parts[] = substr($perm, $i, 1);
397
		}
398
399
		$read = ($owner && $parts[1] == 'r') || $parts[4] == 'r' || $parts[7] == 'r';
400
		
401
		return array(
402
			'read'  => $parts[0] == 'd' ? $read && (($owner && $parts[3] == 'x') || $parts[6] == 'x' || $parts[9] == 'x') : $read,
403
			'write' => ($owner && $parts[2] == 'w') || $parts[5] == 'w' || $parts[8] == 'w'
404
		);
405
	}
406
	
407
	/**
408
	 * Cache dir contents
409
	 *
410
	 * @param  string  $path  dir path
411
	 * @return void
412
	 * @author Dmitry Levashov
413
	 **/
414
 	protected function cacheDir($path) {
415
 		$this->dirsCache[$path] = array();
416
417
		$list = array();
418
		$encPath = $this->convEncIn($path);
419
		foreach (ftp_rawlist($this->connect, $encPath) as $raw) {
420
			if (($stat = $this->parseRaw($raw, $encPath))) {
421
				$list[] = $stat;
422
			}
423
		}
424
		$list = $this->convEncOut($list);
425
		$prefix = ($path === $this->separator)? $this->separator : $path . $this->separator;
426
		$targets = array();
427
		foreach($list as $stat) {
428
			$p = $prefix . $stat['name'];
429
			if (isset($stat['target'])) {
430
				// stat later
431
				$targets[$stat['name']] = $stat['target'];
432 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...
433
				$stat = $this->updateCache($p, $stat);
434
				if (empty($stat['hidden'])) {
435
					$this->dirsCache[$path][] = $p;
436
				}
437
			}
438
		}
439
		// stat link targets
440
		foreach($targets as $name => $target) {
441
			$stat = array();
442
			$stat['name'] = $name;
443
			$p = $prefix . $name;
444
			$cacheDirTarget = $this->cacheDirTarget;
445
			$this->cacheDirTarget = $this->convEncIn($target, true);
446
			if ($tstat = $this->stat($target)) {
447
				$stat['size']  = $tstat['size'];
448
				$stat['alias'] = $target;
449
				$stat['thash'] = $tstat['hash'];
450
				$stat['mime']  = $tstat['mime'];
451
				$stat['read']  = $tstat['read'];
452
				$stat['write']  = $tstat['write'];
453
				
454
				if (isset($tstat['ts']))      { $stat['ts']      = $tstat['ts']; }
455
				if (isset($tstat['owner']))   { $stat['owner']   = $tstat['owner']; }
456
				if (isset($tstat['group']))   { $stat['group']   = $tstat['group']; }
457
 				if (isset($tstat['perm']))    { $stat['perm']    = $tstat['perm']; }
458
 				if (isset($tstat['isowner'])) { $stat['isowner'] = $tstat['isowner']; }
459
			} else {
460
				
461
				$stat['mime']  = 'symlink-broken';
462
				$stat['read']  = false;
463
				$stat['write'] = false;
464
				$stat['size']  = 0;
465
				
466
			}
467
			$this->cacheDirTarget = $cacheDirTarget;
468
			$stat = $this->updateCache($p, $stat);
469
			if (empty($stat['hidden'])) {
470
				$this->dirsCache[$path][] = $p;
471
			}
472
		}
473
474
	}
475
476
	/**
477
	 * Return ftp transfer mode for file
478
	 *
479
	 * @param  string  $path  file path
480
	 * @return string
481
	 * @author Dmitry (dio) Levashov
482
	 **/
483
	protected function ftpMode($path) {
484
		return strpos($this->mimetype($path), 'text/') === 0 ? FTP_ASCII : FTP_BINARY;
485
	}
486
487
	/*********************** paths/urls *************************/
488
	
489
	/**
490
	 * Return parent directory path
491
	 *
492
	 * @param  string  $path  file path
493
	 * @return string
494
	 * @author Dmitry (dio) Levashov
495
	 **/
496
	protected function _dirname($path) {
497
		return dirname($path);
498
	}
499
500
	/**
501
	 * Return file name
502
	 *
503
	 * @param  string  $path  file path
504
	 * @return string
505
	 * @author Dmitry (dio) Levashov
506
	 **/
507
	protected function _basename($path) {
508
		return basename($path);
509
	}
510
511
	/**
512
	 * Join dir name and file name and retur full path
513
	 *
514
	 * @param  string  $dir
515
	 * @param  string  $name
516
	 * @return string
517
	 * @author Dmitry (dio) Levashov
518
	 **/
519
	protected function _joinPath($dir, $name) {
520
		return rtrim($dir, $this->separator).$this->separator.$name;
521
	}
522
	
523
	/**
524
	 * Return normalized path, this works the same as os.path.normpath() in Python
525
	 *
526
	 * @param  string  $path  path
527
	 * @return string
528
	 * @author Troex Nevelin
529
	 **/
530
	protected function _normpath($path) {
531
		if (empty($path)) {
532
			$path = '.';
533
		}
534
		// path must be start with /
535
		$path = preg_replace('|^\.\/?|', $this->separator, $path);
536
		$path = preg_replace('/^([^\/])/', "/$1", $path);
537
538
		if ($path[0] === $this->separator) {
539
			$initial_slashes = true;
540
		} else {
541
			$initial_slashes = false;
542
		}
543
			
544 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...
545
		&& (strpos($path, '//') === 0) 
546
		&& (strpos($path, '///') === false)) {
547
			$initial_slashes = 2;
548
		}
549
			
550
		$initial_slashes = (int) $initial_slashes;
551
552
		$comps = explode($this->separator, $path);
553
		$new_comps = array();
554 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...
555
			if (in_array($comp, array('', '.'))) {
556
				continue;
557
			}
558
				
559
			if (($comp != '..') 
560
			|| (!$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...
561
			|| ($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...
562
				array_push($new_comps, $comp);
563
			} 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...
564
				array_pop($new_comps);
565
			}
566
		}
567
		$comps = $new_comps;
568
		$path = implode($this->separator, $comps);
569
		if ($initial_slashes) {
570
			$path = str_repeat($this->separator, $initial_slashes) . $path;
571
		}
572
		
573
		return $path ? $path : '.';
574
	}
575
	
576
	/**
577
	 * Return file path related to root dir
578
	 *
579
	 * @param  string  $path  file path
580
	 * @return string
581
	 * @author Dmitry (dio) Levashov
582
	 **/
583 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...
584
		if ($path === $this->root) {
585
			return '';
586
		} else {
587
			if (strpos($path, $this->root) === 0) {
588
				return ltrim(substr($path, strlen($this->root)), $this->separator);
589
			} else {
590
				// for link
591
				return $path;
592
			}
593
		}
594
	}
595
	
596
	/**
597
	 * Convert path related to root dir into real path
598
	 *
599
	 * @param  string  $path  file path
600
	 * @return string
601
	 * @author Dmitry (dio) Levashov
602
	 **/
603
	protected function _abspath($path) {
604
		if ($path === $this->separator) {
605
			return $this->root;
606
		} else {
607
			if ($path[0] === $this->separator) {
608
				// for link
609
				return $path;
610
			} else {
611
				return $this->_joinPath($this->root, $path);
612
			}
613
		}
614
	}
615
	
616
	/**
617
	 * Return fake path started from root dir
618
	 *
619
	 * @param  string  $path  file path
620
	 * @return string
621
	 * @author Dmitry (dio) Levashov
622
	 **/
623
	protected function _path($path) {
624
		return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
625
	}
626
	
627
	/**
628
	 * Return true if $path is children of $parent
629
	 *
630
	 * @param  string  $path    path to check
631
	 * @param  string  $parent  parent path
632
	 * @return bool
633
	 * @author Dmitry (dio) Levashov
634
	 **/
635
	protected function _inpath($path, $parent) {
636
		return $path == $parent || strpos($path, $parent. $this->separator) === 0;
637
	}
638
	
639
	/***************** file stat ********************/
640
	/**
641
	 * Return stat for given path.
642
	 * Stat contains following fields:
643
	 * - (int)    size    file size in b. required
644
	 * - (int)    ts      file modification time in unix time. required
645
	 * - (string) mime    mimetype. required for folders, others - optionally
646
	 * - (bool)   read    read permissions. required
647
	 * - (bool)   write   write permissions. required
648
	 * - (bool)   locked  is object locked. optionally
649
	 * - (bool)   hidden  is object hidden. optionally
650
	 * - (string) alias   for symlinks - link target path relative to root path. optionally
651
	 * - (string) target  for symlinks - link target path. optionally
652
	 *
653
	 * If file does not exists - returns empty array or false.
654
	 *
655
	 * @param  string  $path    file path 
656
	 * @return array|false
657
	 * @author Dmitry (dio) Levashov
658
	 **/
659
	protected function _stat($path) {
660
		$outPath = $this->convEncOut($path);
661
		if (isset($this->cache[$outPath])) {
662
			return $this->convEncIn($this->cache[$outPath]);
663
		} else {
664
			$this->convEncIn();
665
		}
666
		if (!$this->MLSTsupprt) {
667
			if ($path === $this->root) {
668
				return array(
669
					'name' => $this->root,
670
					'mime' => 'directory',
671
					'dirs' => $this->_subdirs($path)
672
				);
673
			}
674
			$this->cacheDir($this->convEncOut($this->_dirname($path)));
675
			return $this->convEncIn(isset($this->cache[$outPath])? $this->cache[$outPath] : array());
676
		}
677
		$raw = ftp_raw($this->connect, 'MLST ' . $path);
678
		if (is_array($raw) && count($raw) > 1 && substr(trim($raw[0]), 0, 1) == 2) {
679
			$parts = explode(';', trim($raw[1]));
680
			array_pop($parts);
681
			$parts = array_map('strtolower', $parts);
682
			$stat  = array();
683
			foreach ($parts as $part) {
684
685
				list($key, $val) = explode('=', $part);
686
687
				switch ($key) {
688
					case 'type':
689
						$stat['mime'] = strpos($val, 'dir') !== false ? 'directory' : $this->mimetype($path);
690
						break;
691
692
					case 'size':
693
						$stat['size'] = $val;
694
						break;
695
696
					case 'modify':
697
						$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));
698
						$stat['ts'] = $ts;
699
						break;
700
701
					case 'unix.mode':
702
						$stat['chmod'] = $val;
703
						break;
704
705
					case 'perm':
706
						$val = strtolower($val);
707
						$stat['read']  = (int)preg_match('/e|l|r/', $val);
708
						$stat['write'] = (int)preg_match('/w|m|c/', $val);
709
						if (!preg_match('/f|d/', $val)) {
710
							$stat['locked'] = 1;
711
						}
712
						break;
713
				}
714
			}
715
			if (empty($stat['mime'])) {
716
				return array();
717
			}
718
			if ($stat['mime'] == 'directory') {
719
				$stat['size'] = 0;
720
			}
721
			
722
			if (isset($stat['chmod'])) {
723
				$stat['perm'] = '';
724
				if ($stat['chmod'][0] == 0) {
725
					$stat['chmod'] = substr($stat['chmod'], 1);
726
				}
727
728
				for ($i = 0; $i <= 2; $i++) {
729
					$perm[$i] = array(false, false, false);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$perm was never initialized. Although not strictly required by PHP, it is generally a good practice to add $perm = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
730
					$n = isset($stat['chmod'][$i]) ? $stat['chmod'][$i] : 0;
731
					
732 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...
733
						$perm[$i][0] = true;
0 ignored issues
show
Bug introduced by
The variable $perm does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
734
						$n = $n - 4;
735
						$stat['perm'] .= 'r';
736
					} else {
737
						$stat['perm'] .= '-';
738
					}
739
					
740 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...
741
						$perm[$i][1] = true;
742
						$n = $n - 2;
743
						$stat['perm'] .= 'w';
744
					} else {
745
						$stat['perm'] .= '-';
746
					}
747
748 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...
749
						$perm[$i][2] = true;
750
						$stat['perm'] .= 'x';
751
					} else {
752
						$stat['perm'] .= '-';
753
					}
754
					
755
					$stat['perm'] .= ' ';
756
				}
757
				
758
				$stat['perm'] = trim($stat['perm']);
759
760
				$owner = $this->options['owner'];
761
				$read = ($owner && $perm[0][0]) || $perm[1][0] || $perm[2][0];
762
763
				$stat['read']  = $stat['mime'] == 'directory' ? $read && (($owner && $perm[0][2]) || $perm[1][2] || $perm[2][2]) : $read;
764
				$stat['write'] = ($owner && $perm[0][1]) || $perm[1][1] || $perm[2][1];
765
				unset($stat['chmod']);
766
767
			}
768
			
769
			return $stat;
770
			
771
		}
772
		
773
		return array();
774
	}
775
	
776
	/**
777
	 * Return true if path is dir and has at least one childs directory
778
	 *
779
	 * @param  string  $path  dir path
780
	 * @return bool
781
	 * @author Dmitry (dio) Levashov
782
	 **/
783
	protected function _subdirs($path) {
784
		
785
		foreach (ftp_rawlist($this->connect, $path) as $str) {
786
			$info = preg_split('/\s+/', $str, 9);
787 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...
788
				$this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
789
			}
790
			if (!$this->ftpOsUnix) {
791
				$info = $this->normalizeRawWindows($str);
792
			}
793
			$name = isset($info[8])? trim($info[8]) : '';
794
			if ($name && $name !== '.' && $name !== '..' && substr(strtolower($info[0]), 0, 1) === 'd') {
795
				return true;
796
			}
797
		}
798
		return false;
799
	}
800
	
801
	/**
802
	 * Return object width and height
803
	 * Ususaly used for images, but can be realize for video etc...
804
	 *
805
	 * @param  string  $path  file path
806
	 * @param  string  $mime  file mime type
807
	 * @return string|false
808
	 * @author Dmitry (dio) Levashov
809
	 **/
810 View Code Duplication
	protected function _dimensions($path, $mime) {
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...
811
		$ret = false;
812
		if ($imgsize = $this->getImageSize($path, $mime)) {
813
			$ret = $imgsize['dimensions'];
814
		}
815
		return $ret;
816
	}
817
	
818
	/******************** file/dir content *********************/
819
		
820
	/**
821
	 * Return files list in directory.
822
	 *
823
	 * @param  string  $path  dir path
824
	 * @return array
825
	 * @author Dmitry (dio) Levashov
826
	 * @author Cem (DiscoFever)
827
	 **/
828
	protected function _scandir($path) {
829
		$files = array();
830
831
 		foreach (ftp_rawlist($this->connect, $path) as $str) {
832
 			if (($stat = $this->parseRaw($str, $path, true))) {
833
 				$files[] = $this->_joinPath($path, $stat['name']);
834
 			}
835
 		}
836
837
 		return $files;
838
	}
839
		
840
	/**
841
	 * Open file and return file pointer
842
	 *
843
	 * @param  string  $path  file path
844
	 * @param  bool    $write open file for writing
0 ignored issues
show
Bug introduced by
There is no parameter named $write. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
845
	 * @return resource|false
846
	 * @author Dmitry (dio) Levashov
847
	 **/
848
	protected function _fopen($path, $mode='rb') {
849
		// try ftp stream wrapper
850
		if (ini_get('allow_url_fopen')) {
851
			$url = 'ftp://'.$this->options['user'].':'.$this->options['pass'].'@'.$this->options['host'].':'.$this->options['port'].$path;
852
			if (strtolower($mode[0]) === 'w') {
853
				$context = stream_context_create(array('ftp' => array('overwrite' => true)));
854
				$fp = @fopen($url, $mode, false, $context);
855
			} else {
856
				$fp = @fopen($url, $mode);
857
			}
858
			if ($fp) {
859
				return $fp;
860
			}
861
		}
862
		
863
		if ($this->tmp) {
864
			$local = $this->getTempFile($path);
865
			$fp = @fopen($local, 'wb');
866
			if (ftp_fget($this->connect, $fp, $path, FTP_BINARY)) {
867
				fclose($fp);
868
				$fp = fopen($local, $mode);
869
				return $fp;
870
			}
871
			@fclose($fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
872
			is_file($local) && @unlink($local);
873
		}
874
		
875
		return false;
876
	}
877
	
878
	/**
879
	 * Close opened file
880
	 *
881
	 * @param  resource  $fp  file pointer
882
	 * @return bool
883
	 * @author Dmitry (dio) Levashov
884
	 **/
885
	protected function _fclose($fp, $path='') {
886
		@fclose($fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
887
		if ($path) {
888
			@unlink($this->getTempFile($path));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
889
		}
890
	}
891
	
892
	/********************  file/dir manipulations *************************/
893
	
894
	/**
895
	 * Create dir and return created dir path or false on failed
896
	 *
897
	 * @param  string  $path  parent dir path
898
	 * @param string  $name  new directory name
899
	 * @return string|bool
900
	 * @author Dmitry (dio) Levashov
901
	 **/
902
	protected function _mkdir($path, $name) {
903
		$path = $this->_joinPath($path, $name);
904
		if (ftp_mkdir($this->connect, $path) === false) {
905
			return false;
906
		} 
907
		
908
		$this->options['dirMode'] && @ftp_chmod($this->connect, $this->options['dirMode'], $path);
909
		return $path;
910
	}
911
	
912
	/**
913
	 * Create file and return it's path or false on failed
914
	 *
915
	 * @param  string  $path  parent dir path
916
	 * @param string  $name  new file name
917
	 * @return string|bool
918
	 * @author Dmitry (dio) Levashov
919
	 **/
920
	protected function _mkfile($path, $name) {
921
		if ($this->tmp) {
922
			$path = $this->_joinPath($path, $name);
923
			$local = $this->getTempFile();
924
			$res = touch($local) && ftp_put($this->connect, $path, $local, FTP_ASCII);
925
			@unlink($local);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
926
			return $res ? $path : false;
927
		}
928
		return false;
929
	}
930
	
931
	/**
932
	 * Create symlink. FTP driver does not support symlinks.
933
	 *
934
	 * @param  string  $target  link target
935
	 * @param  string  $path    symlink path
936
	 * @return bool
937
	 * @author Dmitry (dio) Levashov
938
	 **/
939
	protected function _symlink($target, $path, $name) {
940
		return false;
941
	}
942
	
943
	/**
944
	 * Copy file into another file
945
	 *
946
	 * @param  string  $source     source file path
947
	 * @param  string  $targetDir  target directory path
948
	 * @param  string  $name       new file name
949
	 * @return bool
950
	 * @author Dmitry (dio) Levashov
951
	 **/
952
	protected function _copy($source, $targetDir, $name) {
953
		$res = false;
954
		
955
		if ($this->tmp) {
956
			$local  = $this->getTempFile();
957
			$target = $this->_joinPath($targetDir, $name);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_joinPath($targetDir, $name); of type string adds the type string to the return on line 966 which is incompatible with the return type declared by the abstract method elFinderVolumeDriver::_copy of type boolean.
Loading history...
958
959 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...
960
			&& ftp_put($this->connect, $target, $local, $this->ftpMode($target))) {
961
				$res = $target;
962
			}
963
			@unlink($local);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
964
		}
965
		
966
		return $res;
967
	}
968
	
969
	/**
970
	 * Move file into another parent dir.
971
	 * Return new file path or false.
972
	 *
973
	 * @param  string  $source  source file path
974
	 * @param  string  $target  target dir path
0 ignored issues
show
Documentation introduced by
There is no parameter named $target. Did you maybe mean $targetDir?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
975
	 * @param  string  $name    file name
976
	 * @return string|bool
977
	 * @author Dmitry (dio) Levashov
978
	 **/
979
	protected function _move($source, $targetDir, $name) {
980
		$target = $this->_joinPath($targetDir, $name);
981
		return ftp_rename($this->connect, $source, $target) ? $target : false;
982
	}
983
		
984
	/**
985
	 * Remove file
986
	 *
987
	 * @param  string  $path  file path
988
	 * @return bool
989
	 * @author Dmitry (dio) Levashov
990
	 **/
991
	protected function _unlink($path) {
992
		return ftp_delete($this->connect, $path);
993
	}
994
995
	/**
996
	 * Remove dir
997
	 *
998
	 * @param  string  $path  dir path
999
	 * @return bool
1000
	 * @author Dmitry (dio) Levashov
1001
	 **/
1002
	protected function _rmdir($path) {
1003
		return ftp_rmdir($this->connect, $path);
1004
	}
1005
	
1006
	/**
1007
	 * Create new file and write into it from file pointer.
1008
	 * Return new file path or false on error.
1009
	 *
1010
	 * @param  resource  $fp   file pointer
1011
	 * @param  string    $dir  target dir path
1012
	 * @param  string    $name file name
1013
	 * @param  array     $stat file stat (required by some virtual fs)
1014
	 * @return bool|string
1015
	 * @author Dmitry (dio) Levashov
1016
	 **/
1017
	protected function _save($fp, $dir, $name, $stat) {
1018
		$path = $this->_joinPath($dir, $name);
1019
		return ftp_fput($this->connect, $path, $fp, $this->ftpMode($path))
1020
			? $path
1021
			: false;
1022
	}
1023
	
1024
	/**
1025
	 * Get file contents
1026
	 *
1027
	 * @param  string  $path  file path
1028
	 * @return string|false
1029
	 * @author Dmitry (dio) Levashov
1030
	 **/
1031
	protected function _getContents($path) {
1032
		$contents = '';
1033
		if (($fp = $this->_fopen($path))) {
1034
			while (!feof($fp)) {
1035
			  $contents .= fread($fp, 8192);
1036
			}
1037
			$this->_fclose($fp, $path);
1038
			return $contents;
1039
		}
1040
		return false;
1041
	}
1042
	
1043
	/**
1044
	 * Write a string to a file
1045
	 *
1046
	 * @param  string  $path     file path
1047
	 * @param  string  $content  new file content
1048
	 * @return bool
1049
	 * @author Dmitry (dio) Levashov
1050
	 **/
1051
	protected function _filePutContents($path, $content) {
1052
		$res = false;
1053
1054
		if ($this->tmp) {
1055
			$local = $this->getTempFile();
1056
			
1057 View Code Duplication
			if (@file_put_contents($local, $content, LOCK_EX) !== 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...
1058
			&& ($fp = @fopen($local, 'rb'))) {
1059
				clearstatcache();
1060
				$res  = ftp_fput($this->connect, $path, $fp, $this->ftpMode($path));
1061
				@fclose($fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1062
			}
1063
			file_exists($local) && @unlink($local);
1064
		}
1065
1066
		return $res;
1067
	}
1068
1069
	/**
1070
	 * Detect available archivers
1071
	 *
1072
	 * @return void
1073
	 **/
1074
	protected function _checkArchivers() {
1075
		$this->archivers = $this->getArchivers();
1076
		return;
1077
	}
1078
1079
	/**
1080
	 * chmod availability
1081
	 *
1082
	 * @return bool
1083
	 **/
1084
	protected function _chmod($path, $mode) {
1085
		$modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o",$mode));
1086
		return @ftp_chmod($this->connect, $modeOct, $path);
1087
	}
1088
1089
	/**
1090
	 * Recursive symlinks search
1091
	 *
1092
	 * @param  string  $path  file/dir path
1093
	 * @return bool
1094
	 * @author Dmitry (dio) Levashov
1095
	 **/
1096
	protected function _findSymlinks($path) {
1097
		if (is_link($path)) {
1098
			return true;
1099
		}
1100
		if (is_dir($path)) {
1101 View Code Duplication
			foreach (scandir($path) as $name) {
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...
1102
				if ($name != '.' && $name != '..') {
1103
					$p = $path.DIRECTORY_SEPARATOR.$name;
1104
					if (is_link($p)) {
1105
						return true;
1106
					}
1107
					if (is_dir($p) && $this->_findSymlinks($p)) {
1108
						return true;
1109
					} elseif (is_file($p)) {
1110
						$this->archiveSize += sprintf('%u', filesize($p));
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...
1111
					}
1112
				}
1113
			}
1114
		} else {
1115
			$this->archiveSize += sprintf('%u', filesize($path));
1116
		}
1117
		
1118
		return false;
1119
	}
1120
1121
	/**
1122
	 * Extract files from archive
1123
	 *
1124
	 * @param  string  $path  archive path
1125
	 * @param  array   $arc   archiver command and arguments (same as in $this->archivers)
1126
	 * @return true
1127
	 * @author Dmitry (dio) Levashov,
1128
	 * @author Alexey Sukhotin
1129
	 **/
1130
	protected function _extract($path, $arc)
1131
	{
1132
		$dir = $this->tempDir();
1133
		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...
1134
			return false;
1135
		}
1136
1137
		$basename = $this->_basename($path);
1138
		$localPath = $dir . DIRECTORY_SEPARATOR . $basename;
1139
1140
		if (!ftp_get($this->connect, $localPath, $path, FTP_BINARY)) {
1141
			//cleanup
1142
			$this->rmdirRecursive($dir);
1143
			return false;
1144
		}
1145
1146
		$this->unpackArchive($localPath, $arc);
1147
		
1148
		$filesToProcess = elFinderVolumeFTP::listFilesInDirectory($dir, true);
1149
		
1150
		// no files - extract error ?
1151
		if (empty($filesToProcess)) {
1152
			return false;
1153
		}
1154
		
1155
		$this->archiveSize = 0;
1156
		
1157
		// find symlinks
1158
		$symlinks = $this->_findSymlinks($dir);
1159
		
1160 View Code Duplication
		if ($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...
1161
			$this->rmdirRecursive($dir);
1162
			return $this->setError(array_merge($this->error, array(elFinder::ERROR_ARC_SYMLINKS)));
1163
		}
1164
1165
		// check max files size
1166 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...
1167
			$this->rmdirRecursive($dir);
1168
			return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
1169
		}
1170
		
1171
		$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...
1172
		
1173
		// archive contains one item - extract in archive dir
1174
		$name = '';
1175
		$src = $dir . DIRECTORY_SEPARATOR . $filesToProcess[0];
1176
		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...
1177
			$name = $filesToProcess[0];
1178
		} else if ($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...
1179
			// for several files - create new directory
1180
			// create unique name for directory
1181
			$src = $dir;
1182
			$name = basename($path);
1183 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...
1184
				$name = substr($name, 0,  strlen($name)-strlen($m[0]));
1185
			}
1186
			$test = $this->_joinPath(dirname($path), $name);
1187
			if ($this->stat($test)) {
1188
				$name = $this->uniqueName(dirname($path), $name, '-', false);
1189
			}
1190
		}
1191
		
1192
		if ($name !== '' && is_file($src)) {
1193
			$result = $this->_joinPath(dirname($path), $name);
1194
1195
			if (! ftp_put($this->connect, $result, $src, FTP_BINARY)) {
1196
				$this->rmdirRecursive($dir);
1197
				return false;
1198
			}
1199
		} else {
1200
			$dstDir = $this->_dirname($path);
1201
			$result = array();
1202
			if (is_dir($src)) {
1203
				if (!$dstDir = $this->_mkdir($dstDir, $name)) {
1204
					$this->rmdirRecursive($dir);
1205
					return false;
1206
				}
1207
				$result[] = $dstDir;
1208
			}
1209
			foreach($filesToProcess as $name) {
1210
				$name = rtrim($name, DIRECTORY_SEPARATOR);
1211
				$src = $dir . DIRECTORY_SEPARATOR . $name;
1212
				if (is_dir($src)) {
1213
					$p = dirname($name);
1214
					$name = basename($name);
1215
					if (! $target = $this->_mkdir($this->_joinPath($dstDir, $p), $name)) {
1216
						$this->rmdirRecursive($dir);
1217
						return false;
1218
					}
1219 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...
1220
					$target = $this->_joinPath($dstDir, $name);
1221
					if (! ftp_put($this->connect, $target, $src, FTP_BINARY)) {
1222
						$this->rmdirRecursive($dir);
1223
						return false;
1224
					}
1225
				}
1226
				$result[] = $target;
1227
			}
1228
			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...
1229
				$this->rmdirRecursive($dir);
1230
				return false;
1231
			}
1232
		}
1233
		
1234
		is_dir($dir) && $this->rmdirRecursive($dir);
1235
		
1236
		$this->clearcache();
1237
		return $result? $result : false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result ? $result : false; (string|array|false) is incompatible with the return type declared by the abstract method elFinderVolumeDriver::_extract of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1238
	}
1239
1240
	/**
1241
	 * Create archive and return its path
1242
	 *
1243
	 * @param  string  $dir    target dir
1244
	 * @param  array   $files  files names list
1245
	 * @param  string  $name   archive name
1246
	 * @param  array   $arc    archiver options
1247
	 * @return string|bool
1248
	 * @author Dmitry (dio) Levashov,
1249
	 * @author Alexey Sukhotin
1250
	 **/
1251
	protected function _archive($dir, $files, $name, $arc)
1252
	{
1253
		// get current directory
1254
		$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...
1255
1256
		$tmpDir = $this->tempDir();
1257
		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...
1258
			return false;
1259
		}
1260
1261
		//download data
1262
		if (!$this->ftp_download_files($dir, $files, $tmpDir)) {
1263
			//cleanup
1264
			$this->rmdirRecursive($tmpDir);
1265
			return false;
1266
		}
1267
1268
		$remoteArchiveFile = false;
1269
		if ($path = $this->makeArchive($tmpDir, $files, $name, $arc)) {
1270
			$remoteArchiveFile = $this->_joinPath($dir, $name);
1271
			if (!ftp_put($this->connect, $remoteArchiveFile, $path, FTP_BINARY)) {
1272
				$remoteArchiveFile = false;
1273
			}
1274
		}
1275
1276
		//cleanup
1277
		if(!$this->rmdirRecursive($tmpDir)) {
1278
			return false;
1279
		}
1280
1281
		return $remoteArchiveFile;
1282
	}
1283
1284
	/**
1285
	 * Create writable temporary directory and return path to it.
1286
	 * @return string path to the new temporary directory or false in case of error.
1287
	 */
1288
	private function tempDir()
1289
	{
1290
		$tempPath = tempnam($this->tmp, 'elFinder');
1291
		if (!$tempPath) {
1292
			$this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1293
			return false;
1294
		}
1295
		$success = unlink($tempPath);
1296
		if (!$success) {
1297
			$this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1298
			return false;
1299
		}
1300
		$success = mkdir($tempPath, 0700, true);
1301
		if (!$success) {
1302
			$this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1303
			return false;
1304
		}
1305
		return $tempPath;
1306
	}
1307
1308
	/**
1309
	 * Gets an array of absolute remote FTP paths of files and
1310
	 * folders in $remote_directory omitting symbolic links.
1311
	 * 
1312
	 * @param $remote_directory string remote FTP path to scan for file and folders recursively
1313
	 * @param $targets          array  Array of target item. `null` is to get all of items
1314
	 * @return array of elements each of which is an array of two elements:
1315
	 * <ul>
1316
	 * <li>$item['path'] - absolute remote FTP path</li>
1317
	 * <li>$item['type'] - either 'f' for file or 'd' for directory</li>
1318
	 * </ul>
1319
	 */
1320
	protected function ftp_scan_dir($remote_directory, $targets = null)
1321
	{
1322
		$buff = ftp_rawlist($this->connect, $remote_directory);
1323
		$items = array();
1324
		if ($targets && is_array($targets)) {
1325
			$targets = array_flip($targets);
1326
		} else {
1327
			$targets = false;
1328
		}
1329
		foreach ($buff as $str) {
1330
			$info = preg_split("/\s+/", $str, 9);
1331 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...
1332
				$this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
1333
			}
1334
			if (!$this->ftpOsUnix) {
1335
				$info = $this->normalizeRawWindows($str);
1336
			}
1337
			$type = substr($info[0], 0, 1);
1338
			$name = trim($info[8]);
1339
			if ($name !== '.' && $name !== '..' && (!$targets || isset($targets[$name]))) {
1340
				switch ($type) {
1341
					case 'l' : //omit symbolic links
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1342
					case 'd' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1343
						$remote_file_path = $this->_joinPath($remote_directory, $name);
1344
						$item = array();
1345
						$item['path'] = $remote_file_path;
1346
						$item['type'] = 'd'; // normal file
1347
						$items[] = $item;
1348
						$items = array_merge($items, $this->ftp_scan_dir($remote_file_path));
1349
						break;
1350
					default:
1351
						$remote_file_path = $this->_joinPath($remote_directory, $name);
1352
						$item = array();
1353
						$item['path'] = $remote_file_path;
1354
						$item['type'] = 'f'; // normal file
1355
						$items[] = $item;
1356
				}
1357
			}
1358
		}
1359
		return $items;
1360
	}
1361
1362
	/**
1363
	 * Downloads specified files from remote directory
1364
	 * if there is a directory among files it is downloaded recursively (omitting symbolic links).
1365
	 * 
1366
	 * @param $remote_directory string remote FTP path to a source directory to download from.
1367
	 * @param array $files list of files to download from remote directory.
1368
	 * @param $dest_local_directory string destination folder to store downloaded files.
1369
	 * @return bool true on success and false on failure.
1370
	 */
1371
	private function ftp_download_files($remote_directory, array $files, $dest_local_directory)
1372
	{
1373
		$contents = $this->ftp_scan_dir($remote_directory, $files);
1374
		if (!isset($contents)) {
1375
			$this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1376
			return false;
1377
		}
1378
		$remoteDirLen = strlen($remote_directory);
1379
		foreach ($contents as $item) {
1380
			$relative_path = substr($item['path'], $remoteDirLen);
1381
			$local_path = $dest_local_directory . DIRECTORY_SEPARATOR . $relative_path;
1382
			switch ($item['type']) {
1383
				case 'd':
1384
					$success = mkdir($local_path);
1385
					break;
1386
				case 'f':
1387
					$success = ftp_get($this->connect, $local_path, $item['path'], FTP_BINARY);
1388
					break;
1389
				default:
1390
					$success = true;
1391
			}
1392
			if (!$success) {
1393
				$this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1394
				return false;
1395
			}
1396
		}
1397
		return true;
1398
	}
1399
1400
	/**
1401
	 * Delete local directory recursively.
1402
	 * @param $dirPath string to directory to be erased.
1403
	 * @return bool true on success and false on failure.
1404
	 */
1405
	private function deleteDir($dirPath)
1406
	{
1407
		if (!is_dir($dirPath)) {
1408
			$success = unlink($dirPath);
1409
		} else {
1410
			$success = true;
1411
			foreach (array_reverse(elFinderVolumeFTP::listFilesInDirectory($dirPath, false)) as $path) {
1412
				$path = $dirPath . DIRECTORY_SEPARATOR . $path;
1413
				if(is_link($path)) {
1414
					unlink($path);
1415
				} else if (is_dir($path)) {
1416
					$success = rmdir($path);
1417
				} else {
1418
					$success = unlink($path);
1419
				}
1420
				if (!$success) {
1421
					break;
1422
				}
1423
			}
1424
			if($success) {
1425
				$success = rmdir($dirPath);
1426
			}
1427
		}
1428
		if(!$success) {
1429
			$this->setError(elFinder::ERROR_RM, $dirPath);
1430
			return false;
1431
		}
1432
		return $success;
1433
	}
1434
1435
	/**
1436
	 * Returns array of strings containing all files and folders in the specified local directory.
1437
	 * @param $dir
1438
	 * @param string $prefix
1439
	 * @internal param string $path path to directory to scan.
1440
	 * @return array array of files and folders names relative to the $path
1441
	 * or an empty array if the directory $path is empty,
1442
	 * <br />
1443
	 * false if $path is not a directory or does not exist.
1444
	 */
1445
	private static function listFilesInDirectory($dir, $omitSymlinks, $prefix = '')
1446
	{
1447
		if (!is_dir($dir)) {
1448
			return false;
1449
		}
1450
		$excludes = array(".","..");
1451
		$result = array();
1452
		$files = scandir($dir);
1453
		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...
1454
			return array();
1455
		}
1456
		foreach($files as $file) {
1457
			if(!in_array($file, $excludes)) {
1458
				$path = $dir.DIRECTORY_SEPARATOR.$file;
1459
				if(is_link($path)) {
1460
					if($omitSymlinks) {
1461
						continue;
1462
					} else {
1463
						$result[] = $prefix.$file;
1464
					}
1465
				} else if(is_dir($path)) {
1466
					$result[] = $prefix.$file.DIRECTORY_SEPARATOR;
1467
					$subs = elFinderVolumeFTP::listFilesInDirectory($path, $omitSymlinks, $prefix.$file.DIRECTORY_SEPARATOR);
1468
					if($subs) {
1469
						$result = array_merge($result, $subs);
1470
					}
1471
					
1472
				} else {
1473
					$result[] = $prefix.$file;
1474
				}
1475
			}
1476
		}
1477
		return $result;
1478
	}
1479
1480
} // END class
1481
1482