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

elFinder::detectFileExtension()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 4
nop 2
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * elFinder - file manager for web.
5
 * Core class.
6
 *
7
 * @author Dmitry (dio) Levashov
8
 * @author Troex Nevelin
9
 * @author Alexey Sukhotin
10
 **/
11
class elFinder
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
    // Errors messages
14
	const ERROR_UNKNOWN = 'errUnknown';
15
    const ERROR_UNKNOWN_CMD = 'errUnknownCmd';
16
    const ERROR_CONF = 'errConf';
17
    const ERROR_CONF_NO_JSON = 'errJSON';
18
    const ERROR_CONF_NO_VOL = 'errNoVolumes';
19
    const ERROR_INV_PARAMS = 'errCmdParams';
20
    const ERROR_OPEN = 'errOpen';
21
    const ERROR_DIR_NOT_FOUND = 'errFolderNotFound';
22
    const ERROR_FILE_NOT_FOUND = 'errFileNotFound';     // 'File not found.'
23
	const ERROR_TRGDIR_NOT_FOUND = 'errTrgFolderNotFound'; // 'Target folder "$1" not found.'
24
	const ERROR_NOT_DIR = 'errNotFolder';
25
    const ERROR_NOT_FILE = 'errNotFile';
26
    const ERROR_PERM_DENIED = 'errPerm';
27
    const ERROR_LOCKED = 'errLocked';        // '"$1" is locked and can not be renamed, moved or removed.'
28
	const ERROR_EXISTS = 'errExists';        // 'File named "$1" already exists.'
29
	const ERROR_INVALID_NAME = 'errInvName';       // 'Invalid file name.'
30
	const ERROR_INVALID_DIRNAME = 'errInvDirname';    // 'Invalid folder name.'
31
	const ERROR_MKDIR = 'errMkdir';
32
    const ERROR_MKFILE = 'errMkfile';
33
    const ERROR_RENAME = 'errRename';
34
    const ERROR_COPY = 'errCopy';
35
    const ERROR_MOVE = 'errMove';
36
    const ERROR_COPY_FROM = 'errCopyFrom';
37
    const ERROR_COPY_TO = 'errCopyTo';
38
    const ERROR_COPY_ITSELF = 'errCopyInItself';
39
    const ERROR_REPLACE = 'errReplace';          // 'Unable to replace "$1".'
40
	const ERROR_RM = 'errRm';               // 'Unable to remove "$1".'
41
	const ERROR_RM_SRC = 'errRmSrc';            // 'Unable remove source file(s)'
42
	const ERROR_MKOUTLINK = 'errMkOutLink';        // 'Unable to create a link to outside the volume root.'
43
	const ERROR_UPLOAD = 'errUpload';           // 'Upload error.'
44
	const ERROR_UPLOAD_FILE = 'errUploadFile';       // 'Unable to upload "$1".'
45
	const ERROR_UPLOAD_NO_FILES = 'errUploadNoFiles';    // 'No files found for upload.'
46
	const ERROR_UPLOAD_TOTAL_SIZE = 'errUploadTotalSize';  // 'Data exceeds the maximum allowed size.'
47
	const ERROR_UPLOAD_FILE_SIZE = 'errUploadFileSize';   // 'File exceeds maximum allowed size.'
48
	const ERROR_UPLOAD_FILE_MIME = 'errUploadMime';       // 'File type not allowed.'
49
	const ERROR_UPLOAD_TRANSFER = 'errUploadTransfer';   // '"$1" transfer error.'
50
	const ERROR_UPLOAD_TEMP = 'errUploadTemp';       // 'Unable to make temporary file for upload.'
51
	const ERROR_ACCESS_DENIED = 'errAccess';
52
    const ERROR_NOT_REPLACE = 'errNotReplace';       // Object "$1" already exists at this location and can not be replaced with object of another type.
53
	const ERROR_SAVE = 'errSave';
54
    const ERROR_EXTRACT = 'errExtract';
55
    const ERROR_ARCHIVE = 'errArchive';
56
    const ERROR_NOT_ARCHIVE = 'errNoArchive';
57
    const ERROR_ARCHIVE_TYPE = 'errArcType';
58
    const ERROR_ARC_SYMLINKS = 'errArcSymlinks';
59
    const ERROR_ARC_MAXSIZE = 'errArcMaxSize';
60
    const ERROR_RESIZE = 'errResize';
61
    const ERROR_RESIZESIZE = 'errResizeSize';
62
    const ERROR_UNSUPPORT_TYPE = 'errUsupportType';
63
    const ERROR_CONV_UTF8 = 'errConvUTF8';
64
    const ERROR_NOT_UTF8_CONTENT = 'errNotUTF8Content';
65
    const ERROR_NETMOUNT = 'errNetMount';
66
    const ERROR_NETUNMOUNT = 'errNetUnMount';
67
    const ERROR_NETMOUNT_NO_DRIVER = 'errNetMountNoDriver';
68
    const ERROR_NETMOUNT_FAILED = 'errNetMountFailed';
69
    const ERROR_SESSION_EXPIRES = 'errSessionExpires';
70
    const ERROR_CREATING_TEMP_DIR = 'errCreatingTempDir';
71
    const ERROR_FTP_DOWNLOAD_FILE = 'errFtpDownloadFile';
72
    const ERROR_FTP_UPLOAD_FILE = 'errFtpUploadFile';
73
    const ERROR_FTP_MKDIR = 'errFtpMkdir';
74
    const ERROR_ARCHIVE_EXEC = 'errArchiveExec';
75
    const ERROR_EXTRACT_EXEC = 'errExtractExec';
76
    const ERROR_SEARCH_TIMEOUT = 'errSearchTimeout';    // 'Timed out while searching "$1". Search result is partial.'
77
	const ERROR_REAUTH_REQUIRE = 'errReauthRequire';  // 'Re-authorization is required.'
78
	const ERROR_MAX_TARGTES = 'errMaxTargets'; // 'Max number of selectable items is $1.'
79
80
	/**
81
	 * elFinder instance.
82
	 *
83
	 * @var object
84
	 */
85
	public static $instance = null;
86
87
	/**
88
	 * Network mount drivers.
89
	 *
90
	 * @var array
91
	 */
92
	public static $netDrivers = [];
93
94
	/**
95
	 * elFinder global locale.
96
	 *
97
	 * @var string
98
	 */
99
	public static $locale = '';
100
101
	/**
102
	 * elFinderVolumeDriver default mime.type file path.
103
	 *
104
	 * @var string
105
	 */
106
	public static $defaultMimefile = '';
107
108
	/**
109
	 * A file save destination path when a temporary content URL is required
110
	 * on a network volume or the like
111
	 * It can be overwritten by volume route setting.
112
	 *
113
	 * @var string
114
	 */
115
	public static $tmpLinkPath = '';
116
117
	/**
118
	 * A file save destination URL when a temporary content URL is required
119
	 * on a network volume or the like
120
	 * It can be overwritten by volume route setting.
121
	 *
122
	 * @var string
123
	 */
124
	public static $tmpLinkUrl = '';
125
126
	/**
127
	 * Temporary content URL lifetime (seconds).
128
	 *
129
	 * @var int
130
	 */
131
	public static $tmpLinkLifeTime = 3600;
132
133
	/**
134
	 * elFinder global sessionCacheKey.
135
	 *
136
	 * @deprecated
137
	 * @var string
138
	 */
139
	public static $sessionCacheKey = '';
140
141
	/**
142
	 * Mounted volumes count
143
	 * Required to create unique volume id.
144
	 *
145
	 * @var int
146
	 **/
147
	public static $volumesCnt = 1;
148
149
	/**
150
	 * Max allowed numbar of @var targets (0 - no limit).
151
	 *
152
	 * @var int
153
	 */
154
	public $maxTargets = 1000;
155
156
	/**
157
	 * Errors from PHP.
158
	 *
159
	 * @var array
160
	 **/
161
	public static $phpErrors = [];
162
163
	/**
164
	 * Errors from not mounted volumes.
165
	 *
166
	 * @var array
167
	 **/
168
	public $mountErrors = [];
169
170
	/**
171
	 * API version number.
172
	 *
173
	 * @var string
174
	 **/
175
	protected $version = '2.1';
176
177
	/**
178
	 * Storages (root dirs).
179
	 *
180
	 * @var array
181
	 **/
182
	protected $volumes = [];
183
	/**
184
	 * elFinder session wrapper object.
185
	 *
186
	 * @var elFinderSessionInterface
187
	 */
188
	protected $session;
189
190
	/**
191
	 * elFinder base64encodeSessionData
192
	 * elFinder save session data as `UTF-8`
193
	 * If the session storage mechanism of the system does not allow `UTF-8`
194
	 * And it must be `true` option 'base64encodeSessionData' of elFinder.
195
	 *
196
	 * @var bool
197
	 */
198
	protected static $base64encodeSessionData = false;
199
200
	/**
201
	 * elFinder common tempraly path.
202
	 *
203
	 * @var string
204
	 **/
205
	protected static $commonTempPath = '';
206
207
	/**
208
	 * Additional volume root options for network mounting volume.
209
	 *
210
	 * @var array
211
	 */
212
	protected $optionsNetVolumes = [];
213
214
	/**
215
	 * Session key of net mount volumes.
216
	 *
217
	 * @deprecated
218
	 * @var string
219
	 */
220
	protected $netVolumesSessionKey = '';
221
222
	/**
223
	 * Default root (storage).
224
	 *
225
	 * @var elFinderStorageDriver
226
	 **/
227
	protected $default = null;
228
229
	/**
230
	 * Commands and required arguments list.
231
	 *
232
	 * @var array
233
	 **/
234
	protected $commands = [
235
		'open' => ['target' => false, 'tree' => false, 'init' => false, 'mimes' => false, 'compare' => false],
236
		'ls' => ['target' => true, 'mimes' => false, 'intersect' => false],
237
		'tree' => ['target' => true],
238
		'parents' => ['target' => true, 'until' => false],
239
		'tmb' => ['targets' => true],
240
		'file' => ['target' => true, 'download' => false],
241
		'zipdl' => ['targets' => true, 'download' => false],
242
		'size' => ['targets' => true],
243
		'mkdir' => ['target' => true, 'name' => false, 'dirs' => false],
244
		'mkfile' => ['target' => true, 'name' => true, 'mimes' => false],
245
		'rm' => ['targets' => true],
246
		'rename' => ['target' => true, 'name' => true, 'mimes' => false],
247
		'duplicate' => ['targets' => true, 'suffix' => false],
248
		'paste' => ['dst' => true, 'targets' => true, 'cut' => false, 'mimes' => false, 'renames' => false, 'hashes' => false, 'suffix' => false],
249
		'upload' => ['target' => true, 'FILES' => true, 'mimes' => false, 'html' => false, 'upload' => false, 'name' => false, 'upload_path' => false, 'chunk' => false, 'cid' => false, 'node' => false, 'renames' => false, 'hashes' => false, 'suffix' => false, 'mtime' => false, 'overwrite' => false],
250
		'get' => ['target' => true, 'conv' => false],
251
		'put' => ['target' => true, 'content' => '', 'mimes' => false, 'encoding' => false],
252
		'archive' => ['targets' => true, 'type' => true, 'mimes' => false, 'name' => false],
253
		'extract' => ['target' => true, 'mimes' => false, 'makedir' => false],
254
		'search' => ['q' => true, 'mimes' => false, 'target' => false],
255
		'info' => ['targets' => true, 'compare' => false],
256
		'dim' => ['target' => true],
257
		'resize' => ['target' => true, 'width' => false, 'height' => false, 'mode' => false, 'x' => false, 'y' => false, 'degree' => false, 'quality' => false, 'bg' => false],
258
		'netmount' => ['protocol' => true, 'host' => true, 'path' => false, 'port' => false, 'user' => false, 'pass' => false, 'alias' => false, 'options' => false],
259
		'url' => ['target' => true, 'options' => false],
260
		'callback' => ['node' => true, 'json' => false, 'bind' => false, 'done' => false],
261
		'chmod' => ['targets' => true, 'mode' => true],
262
		'subdirs' => ['targets' => true],
263
	];
264
265
	/**
266
	 * Plugins instance.
267
	 *
268
	 * @var array
269
	 **/
270
	protected $plugins = [];
271
272
	/**
273
	 * Commands listeners.
274
	 *
275
	 * @var array
276
	 **/
277
	protected $listeners = [];
278
279
	/**
280
	 * script work time for debug.
281
	 *
282
	 * @var string
283
	 **/
284
	protected $time = 0;
285
	/**
286
	 * Is elFinder init correctly?
287
	 *
288
	 * @var bool
289
	 **/
290
	protected $loaded = false;
291
	/**
292
	 * Send debug to client?
293
	 *
294
	 * @var string
295
	 **/
296
	protected $debug = false;
297
298
	/**
299
	 * Call `session_write_close()` before exec command?
300
	 *
301
	 * @var bool
302
	 */
303
	protected $sessionCloseEarlier = true;
304
305
	/**
306
	 * SESSION use commands @see __construct().
307
	 *
308
	 * @var array
309
	 */
310
	protected $sessionUseCmds = [];
311
312
	/**
313
	 * session expires timeout.
314
	 *
315
	 * @var int
316
	 **/
317
	protected $timeout = 0;
318
319
	/**
320
	 * Temp dir path for Upload.
321
	 *
322
	 * @var string
323
	 */
324
	protected $uploadTempPath = '';
325
326
	/**
327
	 * Max allowed archive files size (0 - no limit).
328
	 *
329
	 * @var int
330
	 */
331
	protected $maxArcFilesSize = 0;
332
333
	/**
334
	 * undocumented class variable.
335
	 *
336
	 * @var string
337
	 **/
338
	protected $uploadDebug = '';
339
340
	/**
341
	 * URL for callback output window for CORS
342
	 * redirect to this URL when callback output.
343
	 *
344
	 * @var string URL
345
	 */
346
	protected $callbackWindowURL = '';
347
348
	/**
349
	 * hash of items to unlock on command completion.
350
	 *
351
	 * @var array hashes
352
	 */
353
	protected $autoUnlocks = [];
354
355
	/**
356
	 * Item locking expiration (seconds)
357
	 * Default: 3600 secs.
358
	 *
359
	 * @var int
360
	 */
361
	protected $itemLockExpire = 3600;
362
363
	/**
364
	 * Is session closed.
365
	 *
366
	 * @deprecated
367
	 * @var bool
368
	 */
369
	private static $sessionClosed = false;
370
371
	/**
372
	 * Constructor.
373
	 *
374
	 * @param  array  elFinder and roots configurations
375
	 * @author Dmitry (dio) Levashov
376
	 */
377
	public function __construct($opts)
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER 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...
Coding Style introduced by
__construct uses the super-global variable $_GET 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...
Coding Style introduced by
__construct uses the super-global variable $_POST 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...
378
	{
379
	    // set error handler of WARNING, NOTICE
380
		$errLevel = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_STRICT | E_RECOVERABLE_ERROR;
381
	    if (defined('E_DEPRECATED')) {
382
	        $errLevel |= E_DEPRECATED | E_USER_DEPRECATED;
383
	    }
384
	    set_error_handler('elFinder::phpErrorHandler', $errLevel);
385
386
		// convert PATH_INFO to GET query
387
		if (! empty($_SERVER['PATH_INFO'])) {
388
		    $_ps = explode('/', trim($_SERVER['PATH_INFO'], '/'));
389
		    if (! isset($_GET['cmd'])) {
390
		        $_cmd = $_ps[0];
391
		        if (isset($this->commands[$_cmd])) {
392
		            $_GET['cmd'] = $_cmd;
393
		            $_i = 1;
394
		            foreach (array_keys($this->commands[$_cmd]) as $_k) {
395
		                if (isset($_ps[$_i])) {
396
		                    if (! isset($_GET[$_k])) {
397
		                        $_GET[$_k] = $_ps[$_i];
398
		                    }
399
		                } else {
400
		                    break;
401
		                }
402
		            }
403
		        }
404
		    }
405
		}
406
407
		// set elFinder instance
408
		self::$instance = $this;
409
410
		// setup debug mode
411
		$this->debug = (isset($opts['debug']) && $opts['debug'] ? true : false);
0 ignored issues
show
Documentation Bug introduced by
The property $debug was declared of type string, but isset($opts['debug']) &&...'debug'] ? true : false is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
412
	    if ($this->debug) {
413
	        error_reporting(defined('ELFINDER_DEBUG_ERRORLEVEL') ? ELFINDER_DEBUG_ERRORLEVEL : -1);
414
	        ini_set('diaplay_errors', '1');
415
	    }
416
417
	    if (! interface_exists('elFinderSessionInterface')) {
418
	        include_once dirname(__FILE__).'/elFinderSessionInterface.php';
419
	    }
420
421
		// session handler
422
		if (! empty($opts['session']) && $opts['session'] instanceof elFinderSessionInterface) {
423
		    $this->session = $opts['session'];
424
		} else {
425
		    $sessionOpts = [
426
				'base64encode' => ! empty($opts['base64encodeSessionData']),
427
				'keys' => [
428
					'default' => ! empty($opts['sessionCacheKey']) ? $opts['sessionCacheKey'] : 'elFinderCaches',
429
					'netvolume' => ! empty($opts['netVolumesSessionKey']) ? $opts['netVolumesSessionKey'] : 'elFinderNetVolumes',
430
				],
431
			];
432
		    if (! class_exists('elFinderSession')) {
433
		        include_once dirname(__FILE__).'/elFinderSession.php';
434
		    }
435
		    $this->session = new elFinderSession($sessionOpts);
436
		}
437
		// try session start | restart
438
		$this->session->start();
439
440
	    $sessionUseCmds = [];
441
	    if (isset($opts['sessionUseCmds']) && is_array($opts['sessionUseCmds'])) {
442
	        $sessionUseCmds = $opts['sessionUseCmds'];
443
	    }
444
445
		// set self::$volumesCnt by HTTP header "X-elFinder-VolumesCntStart"
446
		if (isset($_SERVER['HTTP_X_ELFINDER_VOLUMESCNTSTART']) && ($volumesCntStart = intval($_SERVER['HTTP_X_ELFINDER_VOLUMESCNTSTART']))) {
447
		    self::$volumesCnt = $volumesCntStart;
448
		}
449
450
	    $this->time = $this->utime();
0 ignored issues
show
Documentation Bug introduced by
The property $time was declared of type string, but $this->utime() is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
451
	    $this->sessionCloseEarlier = isset($opts['sessionCloseEarlier']) ? (bool) $opts['sessionCloseEarlier'] : true;
452
	    $this->sessionUseCmds = array_flip($sessionUseCmds);
453
	    $this->timeout = (isset($opts['timeout']) ? $opts['timeout'] : 0);
454
	    $this->uploadTempPath = (isset($opts['uploadTempPath']) ? $opts['uploadTempPath'] : '');
455
	    $this->callbackWindowURL = (isset($opts['callbackWindowURL']) ? $opts['callbackWindowURL'] : '');
456
	    $this->maxTargets = (isset($opts['maxTargets']) ? intval($opts['maxTargets']) : $this->maxTargets);
0 ignored issues
show
Documentation Bug introduced by
It seems like isset($opts['maxTargets'...']) : $this->maxTargets can also be of type integer. However, the property $maxTargets is declared as type object<targets>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
457
	    self::$commonTempPath = (isset($opts['commonTempPath']) ? $opts['commonTempPath'] : './.tmp');
458
	    if (! is_writable(self::$commonTempPath)) {
459
	        self::$commonTempPath = sys_get_temp_dir();
460
	        if (! is_writable(self::$commonTempPath)) {
461
	            self::$commonTempPath = '';
462
	        }
463
	    }
464
	    if (! empty($opts['tmpLinkPath'])) {
465
	        self::$tmpLinkPath = $opts['tmpLinkPath'];
466
	    }
467
	    if (! empty($opts['tmpLinkUrl'])) {
468
	        self::$tmpLinkUrl = $opts['tmpLinkUrl'];
469
	    }
470
	    if (! empty($opts['tmpLinkLifeTime'])) {
471
	        self::$tmpLinkLifeTime = $opts['tmpLinkLifeTime'];
472
	    }
473
	    $this->maxArcFilesSize = isset($opts['maxArcFilesSize']) ? intval($opts['maxArcFilesSize']) : 0;
474
	    $this->optionsNetVolumes = (isset($opts['optionsNetVolumes']) && is_array($opts['optionsNetVolumes'])) ? $opts['optionsNetVolumes'] : [];
475
	    if (isset($opts['itemLockExpire'])) {
476
	        $this->itemLockExpire = intval($opts['itemLockExpire']);
477
	    }
478
479
		// deprecated settings
480
		$this->netVolumesSessionKey = ! empty($opts['netVolumesSessionKey']) ? $opts['netVolumesSessionKey'] : 'elFinderNetVolumes';
0 ignored issues
show
Deprecated Code introduced by
The property elFinder::$netVolumesSessionKey has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
481
	    self::$sessionCacheKey = ! empty($opts['sessionCacheKey']) ? $opts['sessionCacheKey'] : 'elFinderCaches';
0 ignored issues
show
Deprecated Code introduced by
The property elFinder::$sessionCacheKey has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
482
483
		// check session cache
484
		$_optsMD5 = md5(json_encode($opts['roots']));
485
	    if ($this->session->get('_optsMD5') !== $_optsMD5) {
486
	        $this->session->set('_optsMD5', $_optsMD5);
487
	    }
488
489
		// setlocale and global locale regists to elFinder::locale
490
		self::$locale = ! empty($opts['locale']) ? $opts['locale'] : 'en_US.UTF-8';
491
	    if (false === setlocale(LC_ALL, self::$locale)) {
492
	        self::$locale = setlocale(LC_ALL, '');
493
	    }
494
495
		// set defaultMimefile
496
		self::$defaultMimefile = (isset($opts['defaultMimefile']) ? $opts['defaultMimefile'] : '');
497
498
		// bind events listeners
499
		if (! empty($opts['bind']) && is_array($opts['bind'])) {
500
		    $_req = $_SERVER['REQUEST_METHOD'] == 'POST' ? $_POST : $_GET;
501
		    $_reqCmd = isset($_req['cmd']) ? $_req['cmd'] : '';
502
		    foreach ($opts['bind'] as $cmd => $handlers) {
503
		        $doRegist = (strpos($cmd, '*') !== false);
504
		        if (! $doRegist) {
505
		            $_getcmd = create_function('$cmd', 'list($ret) = explode(\'.\', $cmd);return trim($ret);');
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
506
		            $doRegist = ($_reqCmd && in_array($_reqCmd, array_map($_getcmd, explode(' ', $cmd))));
507
		        }
508
		        if ($doRegist) {
509
		            // for backward compatibility
510
					if (! is_array($handlers)) {
511
					    $handlers = [$handlers];
512
					} else {
513
					    if (count($handlers) === 2 && is_object($handlers[0])) {
514
					        $handlers = [$handlers];
515
					    }
516
					}
517
		            foreach ($handlers as $handler) {
518
		                if ($handler) {
519
		                    if (is_string($handler) && strpos($handler, '.')) {
520
		                        list($_domain, $_name, $_method) = array_pad(explode('.', $handler), 3, '');
521
		                        if (strcasecmp($_domain, 'plugin') === 0) {
522
		                            if ($plugin = $this->getPluginInstance($_name, isset($opts['plugin'][$_name]) ? $opts['plugin'][$_name] : [])
523
											and method_exists($plugin, $_method)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
524
		                                $this->bind($cmd, [$plugin, $_method]);
525
		                            }
526
		                        }
527
		                    } else {
528
		                        $this->bind($cmd, $handler);
529
		                    }
530
		                }
531
		            }
532
		        }
533
		    }
534
		}
535
536
	    if (! isset($opts['roots']) || ! is_array($opts['roots'])) {
537
	        $opts['roots'] = [];
538
	    }
539
540
		// check for net volumes stored in session
541
		$netVolumes = $this->getNetVolumes();
542
	    foreach ($netVolumes as $key => $root) {
543
	        if (! isset($root['id'])) {
544
	            // given fixed unique id
545
				if (! $root['id'] = $this->getNetVolumeUniqueId($netVolumes)) {
546
				    $this->mountErrors[] = 'Netmount Driver "'.$root['driver'].'" : Could\'t given volume id.';
547
				    continue;
548
				}
549
	        }
550
	        $opts['roots'][$key] = $root;
551
	    }
552
553
		// "mount" volumes
554
		foreach ($opts['roots'] as $i => $o) {
555
		    $class = 'elFinderVolume'.(isset($o['driver']) ? $o['driver'] : '');
556
557
		    if (class_exists($class)) {
558
		        $volume = new $class();
559
560
		        try {
561
		            if ($this->maxArcFilesSize && (empty($o['maxArcFilesSize']) || $this->maxArcFilesSize < $o['maxArcFilesSize'])) {
562
		                $o['maxArcFilesSize'] = $this->maxArcFilesSize;
563
		            }
564
					// pass session handler
565
					$volume->setSession($this->session);
566
		            if ($volume->mount($o)) {
567
		                // unique volume id (ends on "_") - used as prefix to files hash
568
						$id = $volume->id();
569
570
		                $this->volumes[$id] = $volume;
571
		                if ((! $this->default || $volume->root() !== $volume->defaultPath()) && $volume->isReadable()) {
572
		                    $this->default = $this->volumes[$id];
573
		                }
574
		            } else {
575
		                $this->removeNetVolume($i, $volume);
576
		                $this->mountErrors[] = 'Driver "'.$class.'" : '.implode(' ', $volume->error());
577
		            }
578
		        } catch (Exception $e) {
579
		            $this->removeNetVolume($i, $volume);
580
		            $this->mountErrors[] = 'Driver "'.$class.'" : '.$e->getMessage();
581
		        }
582
		    } else {
583
		        $this->removeNetVolume($i, $volume);
0 ignored issues
show
Bug introduced by
The variable $volume 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...
584
		        $this->mountErrors[] = 'Driver "'.$class.'" does not exist';
585
		    }
586
		}
587
588
		// if at least one readable volume - ii desu >_<
589
		$this->loaded = ! empty($this->default);
590
591
		// restore error handler for now
592
		restore_error_handler();
593
	}
594
595
	/**
596
	 * Return elFinder session wrapper instance.
597
	 *
598
	 * @return  object  elFinderSessionInterface
599
	 **/
600
	public function getSession()
601
	{
602
	    return $this->session;
603
	}
604
605
	/**
606
	 * Return true if fm init correctly.
607
	 *
608
	 * @return bool
609
	 * @author Dmitry (dio) Levashov
610
	 **/
611
	public function loaded()
612
	{
613
	    return $this->loaded;
614
	}
615
616
	/**
617
	 * Return version (api) number.
618
	 *
619
	 * @return string
620
	 * @author Dmitry (dio) Levashov
621
	 **/
622
	public function version()
623
	{
624
	    return $this->version;
625
	}
626
627
	/**
628
	 * Add handler to elFinder command.
629
	 *
630
	 * @param  string  command name
631
	 * @param  string|array  callback name or array(object, method)
632
	 * @return elFinder
633
	 * @author Dmitry (dio) Levashov
634
	 **/
635
	public function bind($cmd, $handler)
636
	{
637
	    $allCmds = array_keys($this->commands);
638
	    $cmds = [];
639
	    foreach (explode(' ', $cmd) as $_cmd) {
640
	        if ($_cmd !== '') {
641
	            if ($all = strpos($_cmd, '*') !== false) {
0 ignored issues
show
Unused Code introduced by
$all 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...
642
	                list(, $sub) = array_pad(explode('.', $_cmd), 2, '');
643
	                if ($sub) {
644
	                    $sub = str_replace('\'', '\\\'', $sub);
645
	                    $addSub = create_function('$cmd', 'return $cmd . \'.\' . trim(\''.$sub.'\');');
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
646
	                    $cmds = array_merge($cmds, array_map($addSub, $allCmds));
647
	                } else {
648
	                    $cmds = array_merge($cmds, $allCmds);
649
	                }
650
	            } else {
651
	                $cmds[] = $_cmd;
652
	            }
653
	        }
654
	    }
655
	    $cmds = array_unique($cmds);
656
657
	    foreach ($cmds as $cmd) {
658
	        if (! isset($this->listeners[$cmd])) {
659
	            $this->listeners[$cmd] = [];
660
	        }
661
662
	        if (is_callable($handler)) {
663
	            $this->listeners[$cmd][] = $handler;
664
	        }
665
	    }
666
667
	    return $this;
668
	}
669
670
	/**
671
	 * Remove event (command exec) handler.
672
	 *
673
	 * @param  string  command name
674
	 * @param  string|array  callback name or array(object, method)
675
	 * @return elFinder
676
	 * @author Dmitry (dio) Levashov
677
	 **/
678
	public function unbind($cmd, $handler)
679
	{
680
	    if (! empty($this->listeners[$cmd])) {
681
	        foreach ($this->listeners[$cmd] as $i => $h) {
682
	            if ($h === $handler) {
683
	                unset($this->listeners[$cmd][$i]);
684
685
	                return $this;
686
	            }
687
	        }
688
	    }
689
690
	    return $this;
691
	}
692
693
	/**
694
	 * Return true if command exists.
695
	 *
696
	 * @param  string  command name
697
	 * @return bool
698
	 * @author Dmitry (dio) Levashov
699
	 **/
700
	public function commandExists($cmd)
701
	{
702
	    return $this->loaded && isset($this->commands[$cmd]) && method_exists($this, $cmd);
703
	}
704
705
	/**
706
	 * Return root - file's owner (public func of volume()).
707
	 *
708
	 * @param  string  file hash
709
	 * @return elFinderStorageDriver
710
	 * @author Naoki Sawada
711
	 */
712
	public function getVolume($hash)
713
	{
714
	    return $this->volume($hash);
715
	}
716
717
	/**
718
	 * Return command required arguments info.
719
	 *
720
	 * @param  string  command name
721
	 * @return array
722
	 * @author Dmitry (dio) Levashov
723
	 **/
724
	public function commandArgsList($cmd)
725
	{
726
	    return $this->commandExists($cmd) ? $this->commands[$cmd] : [];
727
	}
728
729
	/**
730
	 * Exec command and return result.
731
	 *
732
	 * @param  string  $cmd  command name
733
	 * @param  array   $args command arguments
734
	 * @return array
735
	 * @author Dmitry (dio) Levashov
736
	 **/
737
	public function exec($cmd, $args)
738
	{
739
	    // set error handler of WARNING, NOTICE
740
		set_error_handler('elFinder::phpErrorHandler', E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE);
741
742
	    if (! $this->loaded) {
743
	        return ['error' => $this->error(self::ERROR_CONF, self::ERROR_CONF_NO_VOL)];
744
	    }
745
746
	    if ($this->session_expires()) {
747
	        return ['error' => $this->error(self::ERROR_SESSION_EXPIRES)];
748
	    }
749
750
	    if (! $this->commandExists($cmd)) {
751
	        return ['error' => $this->error(self::ERROR_UNKNOWN_CMD)];
752
	    }
753
754
	    if (! empty($args['mimes']) && is_array($args['mimes'])) {
755
	        foreach ($this->volumes as $id => $v) {
756
	            $this->volumes[$id]->setMimesFilter($args['mimes']);
757
	        }
758
	    }
759
760
		// regist shutdown function as fallback
761
		register_shutdown_function([$this, 'itemAutoUnlock']);
762
763
		// detect destination dirHash and volume
764
		$dstVolume = false;
765
	    $dst = ! empty($args['target']) ? $args['target'] : (! empty($args['dst']) ? $args['dst'] : '');
766
	    if ($dst) {
767
	        $dstVolume = $this->volume($dst);
768
	    } elseif (isset($args['targets']) && is_array($args['targets']) && isset($args['targets'][0])) {
769
	        $dst = $args['targets'][0];
770
	        $dstVolume = $this->volume($dst);
771
	        if ($dstVolume && ($_stat = $dstVolume->file($dst)) && ! empty($_stat['phash'])) {
772
	            $dst = $_stat['phash'];
773
	        } else {
774
	            $dst = '';
775
	        }
776
	    } elseif ($cmd === 'open') {
777
	        // for initial open without args `target`
778
			$dstVolume = $this->default;
779
	        $dst = $dstVolume->defaultPath();
780
	    }
781
782
	    $result = null;
783
784
		// call pre handlers for this command
785
		$args['sessionCloseEarlier'] = isset($this->sessionUseCmds[$cmd]) ? false : $this->sessionCloseEarlier;
786
	    if (! empty($this->listeners[$cmd.'.pre'])) {
787
	        foreach ($this->listeners[$cmd.'.pre'] as $handler) {
788
	            $_res = call_user_func_array($handler, [$cmd, &$args, $this, $dstVolume]);
789
	            if (is_array($_res)) {
790
	                if (! empty($_res['preventexec'])) {
791
	                    $result = ['error' => true];
792 View Code Duplication
	                    if ($cmd === 'upload' & ! empty($args['node'])) {
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: ($cmd === 'upload') & !empty($args['node']), Probably Intended Meaning: $cmd === ('upload' & !empty($args['node']))

When comparing the result of a bit operation, we suggest to add explicit parenthesis and not to rely on PHP’s built-in operator precedence to ensure the code behaves as intended and to make it more readable.

Let’s take a look at these examples:

// Returns always int(0).
return 0 === $foo & 4;
return (0 === $foo) & 4;

// More likely intended return: true/false
return 0 === ($foo & 4);
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...
793
	                        $result['callback'] = [
794
								'node' => $args['node'],
795
								'bind' => $cmd,
796
							];
797
	                    }
798
	                    if (! empty($_res['results']) && is_array($_res['results'])) {
799
	                        $result = array_merge($result, $_res['results']);
800
	                    }
801
	                    break;
802
	                }
803
	            }
804
	        }
805
	    }
806
807
		// unlock session data for multiple access
808
		if ($this->sessionCloseEarlier && $args['sessionCloseEarlier']) {
809
		    $this->session->close();
810
			// deprecated property
811
			self::$sessionClosed = true;
0 ignored issues
show
Deprecated Code introduced by
The property elFinder::$sessionClosed has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
812
		}
813
814
	    if (substr(PHP_OS, 0, 3) === 'WIN') {
815
	        // set time out
816
			self::extendTimeLimit(300);
817
	    }
818
819
	    if (! is_array($result)) {
820
	        try {
821
	            $result = $this->$cmd($args);
822
	        } catch (Exception $e) {
823
	            $result = [
824
					'error' => htmlspecialchars($e->getMessage()),
825
					'sync' => true,
826
				];
827
	        }
828
	    }
829
830
		// check change dstDir
831
		$changeDst = false;
832
	    if ($dst && $dstVolume && (! empty($result['added']) || ! empty($result['removed']))) {
833
	        $changeDst = true;
834
	    }
835
836
	    foreach ($this->volumes as $volume) {
837
	        $removed = $volume->removed();
838 View Code Duplication
	        if (! empty($removed)) {
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...
839
	            if (! isset($result['removed'])) {
840
	                $result['removed'] = [];
841
	            }
842
	            $result['removed'] = array_merge($result['removed'], $removed);
843
	            if (! $changeDst && $dst && $dstVolume && $volume === $dstVolume) {
844
	                $changeDst = true;
845
	            }
846
	        }
847
	        $added = $volume->added();
848 View Code Duplication
	        if (! empty($added)) {
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...
849
	            if (! isset($result['added'])) {
850
	                $result['added'] = [];
851
	            }
852
	            $result['added'] = array_merge($result['added'], $added);
853
	            if (! $changeDst && $dst && $dstVolume && $volume === $dstVolume) {
854
	                $changeDst = true;
855
	            }
856
	        }
857
	        $volume->resetResultStat();
858
	    }
859
860
		// dstDir is changed
861
		if ($changeDst) {
862
		    if ($dstDir = $dstVolume->dir($dst)) {
863
		        if (! isset($result['changed'])) {
864
		            $result['changed'] = [];
865
		        }
866
		        $result['changed'][] = $dstDir;
867
		    }
868
		}
869
870
		// call handlers for this command
871
		if (! empty($this->listeners[$cmd])) {
872
		    foreach ($this->listeners[$cmd] as $handler) {
873
		        if (call_user_func_array($handler, [$cmd, &$result, $args, $this, $dstVolume])) {
874
		            // handler return true to force sync client after command completed
875
					$result['sync'] = true;
876
		        }
877
		    }
878
		}
879
880
		// replace removed files info with removed files hashes
881
		if (! empty($result['removed'])) {
882
		    $removed = [];
883
		    foreach ($result['removed'] as $file) {
884
		        $removed[] = $file['hash'];
885
		    }
886
		    $result['removed'] = array_unique($removed);
887
		}
888
		// remove hidden files and filter files by mimetypes
889
		if (! empty($result['added'])) {
890
		    $result['added'] = $this->filter($result['added']);
891
		}
892
		// remove hidden files and filter files by mimetypes
893
		if (! empty($result['changed'])) {
894
		    $result['changed'] = $this->filter($result['changed']);
895
		}
896
897
	    if ($this->debug || ! empty($args['debug'])) {
898
	        $result['debug'] = [
899
				'connector' => 'php',
900
				'phpver' => PHP_VERSION,
901
				'time' => $this->utime() - $this->time,
902
				'memory' => (function_exists('memory_get_peak_usage') ? ceil(memory_get_peak_usage() / 1024).'Kb / ' : '').ceil(memory_get_usage() / 1024).'Kb / '.ini_get('memory_limit'),
903
				'upload' => $this->uploadDebug,
904
				'volumes' => [],
905
				'mountErrors' => $this->mountErrors,
906
				'phpErrors' => self::$phpErrors,
907
			];
908
	        self::$phpErrors = [];
909
910
	        foreach ($this->volumes as $id => $volume) {
911
	            $result['debug']['volumes'][] = $volume->debug();
912
	        }
913
	    }
914
915
	    foreach ($this->volumes as $volume) {
916
	        $volume->saveSessionCache();
917
	        $volume->umount();
918
	    }
919
920
		// unlock locked items
921
		$this->itemAutoUnlock();
922
923
	    if (! empty($result['callback'])) {
924
	        $result['callback']['json'] = json_encode($result);
925
	        $this->callback($result['callback']);
926
	    } else {
927
	        return $result;
928
	    }
929
	    //TODO: Add return statement here
930
	}
931
932
	/**
933
	 * Return file real path.
934
	 *
935
	 * @param  string  $hash  file hash
936
	 * @return string
937
	 * @author Dmitry (dio) Levashov
938
	 **/
939
	public function realpath($hash)
940
	{
941
	    if (($volume = $this->volume($hash)) == false) {
942
	        return false;
943
	    }
944
945
	    return $volume->realpath($hash);
946
	}
947
948
	/**
949
	 * Update sesstion value of a NetVolume option.
950
	 *
951
	 * @param string $netKey
952
	 * @param string $optionKey
953
	 * @param mixed  $val
954
	 */
955
	public function updateNetVolumeOption($netKey, $optionKey, $val)
956
	{
957
	    $netVolumes = $this->getNetVolumes();
958
	    if (is_string($netKey) && isset($netVolumes[$netKey]) && is_string($optionKey)) {
959
	        $netVolumes[$netKey][$optionKey] = $val;
960
	        $this->saveNetVolumes($netVolumes);
961
	    }
962
	}
963
964
	/***************************************************************************/
965
	/*                                 commands                                */
966
	/***************************************************************************/
967
968
	/**
969
	 * Normalize error messages.
970
	 *
971
	 * @return array
972
	 * @author Dmitry (dio) Levashov
973
	 **/
974
	public function error()
975
	{
976
	    $errors = [];
977
978
	    foreach (func_get_args() as $msg) {
979
	        if (is_array($msg)) {
980
	            $errors = array_merge($errors, $msg);
981
	        } else {
982
	            $errors[] = $msg;
983
	        }
984
	    }
985
986
	    return count($errors) ? $errors : [self::ERROR_UNKNOWN];
987
	}
988
989
	/**
990
	 * PHP error handler, catch error types only E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE.
991
	 *
992
	 * @param int    $errno
993
	 * @param string $errstr
994
	 * @param string $errfile
995
	 * @param int    $errline
996
	 * @return void|bool
997
	 */
998
	public static function phpErrorHandler($errno, $errstr, $errfile, $errline)
999
	{
1000
	    static $base = null;
1001
1002
	    if (is_null($base)) {
1003
	        $base = dirname(__FILE__).DIRECTORY_SEPARATOR;
1004
	    }
1005
1006
	    if (! (error_reporting() & $errno)) {
1007
	        return;
1008
	    }
1009
1010
	    $errfile = str_replace($base, '', $errfile);
1011
1012
	    $proc = false;
1013
	    switch ($errno) {
1014
			case E_WARNING:
1015 View Code Duplication
			case E_USER_WARNING:
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...
1016
				self::$phpErrors[] = "WARNING: $errstr in $errfile line $errline.";
1017
				$proc = true;
1018
				break;
1019
1020
			case E_NOTICE:
1021 View Code Duplication
			case E_USER_NOTICE:
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...
1022
				self::$phpErrors[] = "NOTICE: $errstr in $errfile line $errline.";
1023
				$proc = true;
1024
				break;
1025
		}
1026
1027
	    return $proc;
1028
	}
1029
1030
	/**
1031
	 * unlock locked items on command completion.
1032
	 *
1033
	 * @return void
1034
	 */
1035
	public function itemAutoUnlock()
1036
	{
1037
	    if ($this->autoUnlocks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->autoUnlocks 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...
1038
	        foreach ($this->autoUnlocks as $hash) {
1039
	            $this->itemUnlock($hash);
1040
	        }
1041
	        $this->autoUnlocks = [];
1042
	    }
1043
	}
1044
1045
	/***************************************************************************/
1046
	/*                           static  utils                                 */
1047
	/***************************************************************************/
1048
1049
	/**
1050
	 * Return Is Animation Gif.
1051
	 *
1052
	 * @param  string $path server local path of target image
1053
	 * @return bool
1054
	 */
1055
	public static function isAnimationGif($path)
1056
	{
1057
	    list($width, $height, $type, $attr) = getimagesize($path);
0 ignored issues
show
Unused Code introduced by
The assignment to $width is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $height is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $attr is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1058
	    switch ($type) {
1059
			case IMAGETYPE_GIF:
1060
				break;
1061
			default:
1062
				return false;
1063
		}
1064
1065
	    $imgcnt = 0;
1066
	    $fp = fopen($path, 'rb');
1067
	    fread($fp, 4);
1068
	    $c = fread($fp, 1);
1069
	    if (ord($c) != 0x39) {  // GIF89a
1070
			return false;
1071
	    }
1072
1073
	    while (! feof($fp)) {
1074
	        do {
1075
	            $c = fread($fp, 1);
1076
	        } while (ord($c) != 0x21 && ! feof($fp));
1077
1078
	        if (feof($fp)) {
1079
	            break;
1080
	        }
1081
1082
	        $c2 = fread($fp, 2);
1083
	        if (bin2hex($c2) == 'f904') {
1084
	            $imgcnt++;
1085
	        }
1086
1087
	        if (feof($fp)) {
1088
	            break;
1089
	        }
1090
	    }
1091
1092
	    if ($imgcnt > 1) {
1093
	        return true;
1094
	    } else {
1095
	        return false;
1096
	    }
1097
	}
1098
1099
	/**
1100
	 * Return Is seekable stream resource.
1101
	 *
1102
	 * @param resource $resource
1103
	 * @return bool
1104
	 */
1105
	public static function isSeekableStream($resource)
1106
	{
1107
	    $metadata = stream_get_meta_data($resource);
1108
1109
	    return $metadata['seekable'];
1110
	}
1111
1112
	/**
1113
	 * Rewind stream resource.
1114
	 *
1115
	 * @param resource $resource
1116
	 * @return void
1117
	 */
1118
	public static function rewind($resource)
1119
	{
1120
	    self::isSeekableStream($resource) && rewind($resource);
1121
	}
1122
1123
	/**
1124
	 * serialize and base64_encode of session data (If needed).
1125
	 *
1126
	 * @deprecated
1127
	 * @param  mixed $var target variable
1128
	 * @author Naoki Sawada
1129
	 * @return mixed|string
1130
	 */
1131
	public static function sessionDataEncode($var)
1132
	{
1133
	    if (self::$base64encodeSessionData) {
1134
	        $var = base64_encode(serialize($var));
1135
	    }
1136
1137
	    return $var;
1138
	}
1139
1140
	/**
1141
	 * base64_decode and unserialize of session data  (If needed).
1142
	 *
1143
	 * @deprecated
1144
	 * @param  mixed $var target variable
1145
	 * @param  bool $checkIs data type for check (array|string|object|int)
1146
	 * @author Naoki Sawada
1147
	 * @return bool|mixed
1148
	 */
1149
	public static function sessionDataDecode(&$var, $checkIs = null)
1150
	{
1151
	    if (self::$base64encodeSessionData) {
1152
	        $data = unserialize(base64_decode($var));
1153
	    } else {
1154
	        $data = $var;
1155
	    }
1156
	    $chk = true;
1157
	    if ($checkIs) {
1158
	        switch ($checkIs) {
1159
				case 'array':
1160
					$chk = is_array($data);
1161
					break;
1162
				case 'string':
1163
					$chk = is_string($data);
1164
					break;
1165
				case 'object':
1166
					$chk = is_object($data);
1167
					break;
1168
				case 'int':
1169
					$chk = is_int($data);
1170
					break;
1171
			}
1172
	    }
1173
	    if (! $chk) {
1174
	        unset($var);
1175
1176
	        return false;
1177
	    }
1178
1179
	    return $data;
1180
	}
1181
1182
	/**
1183
	 * Call session_write_close() if session is restarted.
1184
	 *
1185
	 * @deprecated
1186
	 * @return void
1187
	 */
1188
	public static function sessionWrite()
1189
	{
1190
	    if (session_id()) {
1191
	        session_write_close();
1192
	    }
1193
	}
1194
1195
	/**
1196
	 * Return elFinder static variable.
1197
	 *
1198
	 * @param $key
1199
	 * @return mixed|null
1200
	 */
1201
	public static function getStaticVar($key)
1202
	{
1203
	    return isset(self::$$key) ? self::$$key : null;
1204
	}
1205
1206
	/**
1207
	 * Extend PHP execution time limit.
1208
	 *
1209
	 * @param int $time
1210
	 * @return void
1211
	 */
1212
	public static function extendTimeLimit($time = null)
1213
	{
1214
	    static $defLimit = null;
1215
	    if (is_null($defLimit)) {
1216
	        $defLimit = ini_get('max_execution_time');
1217
	    }
1218
	    if ($defLimit != 0) {
1219
	        $time = is_null($time) ? $defLimit : max($defLimit, $time);
1220
	        set_time_limit($time);
1221
	    }
1222
	}
1223
1224
	/**
1225
	 * Return bytes from php.ini value.
1226
	 *
1227
	 * @param string $iniName
1228
	 * @param string $val
1229
	 * @return number
1230
	 */
1231
	public static function getIniBytes($iniName = '', $val = '')
1232
	{
1233
	    if ($iniName !== '') {
1234
	        $val = ini_get($iniName);
1235
	        if ($val === false) {
1236
	            return 0;
1237
	        }
1238
	    }
1239
	    $val = trim($val, "bB \t\n\r\0\x0B");
1240
	    $last = strtolower($val[strlen($val) - 1]);
1241
	    switch ($last) {
1242
			case 't':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1243
				$val *= 1024;
1244
			case 'g':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1245
				$val *= 1024;
1246
			case 'm':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1247
				$val *= 1024;
1248
			case 'k':
1249
				$val *= 1024;
1250
		}
1251
1252
	    return (int) $val;
1253
	}
1254
1255
	/**
1256
	 * Get script url.
1257
	 *
1258
	 * @return string full URL
1259
	 *
1260
	 * @author Naoki Sawada
1261
	 */
1262
	public static function getConnectorUrl()
0 ignored issues
show
Coding Style introduced by
getConnectorUrl uses the super-global variable $_SERVER 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...
1263
	{
1264
	    $https = (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off');
1265
	    $url = ($https ? 'https://' : 'http://')
1266
			.$_SERVER['SERVER_NAME']                                              // host
1267
			.(((! $https && $_SERVER['SERVER_PORT'] == 80) || ($https && $_SERVER['SERVER_PORT'] == 443)) ? '' : (':'.$_SERVER['SERVER_PORT']))  // port
1268
			.$_SERVER['REQUEST_URI'];                                             // path & query
1269
		list($url) = explode('?', $url);
1270
1271
	    return $url;
1272
	}
1273
1274
	/**
1275
	 * Get stream resource pointer by URL.
1276
	 *
1277
	 * @param array  $data  array('target'=>'URL', 'headers' => array())
1278
	 * @param number $redirectLimit
1279
	 * @return resource|bool
1280
	 *
1281
	 * @author Naoki Sawada
1282
	 */
1283
	public static function getStreamByUrl($data, $redirectLimit = 5)
1284
	{
1285
	    if (isset($data['target'])) {
1286
	        $data = [
1287
				'cnt' => 0,
1288
				'url' => $data['target'],
1289
				'headers' => isset($data['headers']) ? $data['headers'] : [],
1290
				'cookies' => [],
1291
			];
1292
	    }
1293
	    if ($data['cnt'] > $redirectLimit) {
1294
	        return false;
1295
	    }
1296
	    $dlurl = $data['url'];
1297
	    $data['url'] = '';
1298
	    $headers = $data['headers'];
1299
1300
	    if ($dlurl) {
1301
	        $url = parse_url($dlurl);
1302
	        $ports = [
1303
				'http' => '80',
1304
				'ssl' => '443',
1305
				'ftp' => '21',
1306
			];
1307
	        $url['scheme'] = strtolower($url['scheme']);
1308
	        if ($url['scheme'] === 'https') {
1309
	            $url['scheme'] = 'ssl';
1310
	        }
1311
	        if (! isset($url['port']) && isset($ports[$url['scheme']])) {
1312
	            $url['port'] = $ports[$url['scheme']];
1313
	        }
1314
	        if (! isset($url['port'])) {
1315
	            return false;
1316
	        }
1317
	        $cookies = [];
1318
	        if ($data['cookies']) {
1319
	            foreach ($data['cookies'] as $d => $c) {
1320
	                if (strpos($url['host'], $d) !== false) {
1321
	                    $cookies[] = $c;
1322
	                }
1323
	            }
1324
	        }
1325
1326
	        $query = isset($url['query']) ? '?'.$url['query'] : '';
1327
	        $stream = stream_socket_client($url['scheme'].'://'.$url['host'].':'.$url['port']);
1328
	        stream_set_timeout($stream, 300);
1329
	        fwrite($stream, "GET {$url['path']}{$query} HTTP/1.1\r\n");
1330
	        fwrite($stream, "Host: {$url['host']}\r\n");
1331
	        foreach ($headers as $header) {
1332
	            fwrite($stream, trim($header, "\r\n")."\r\n");
1333
	        }
1334
	        fwrite($stream, "Connection: Close\r\n");
1335
	        if ($cookies) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cookies 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...
1336
	            fwrite($stream, 'Cookie: '.implode('; ', $cookies)."\r\n");
1337
	        }
1338
	        fwrite($stream, "\r\n");
1339
	        while (($res = trim(fgets($stream))) !== '') {
1340
	            // find redirect
1341
				if (preg_match('/^Location: (.+)$/', $res, $m)) {
1342
				    $data['url'] = $m[1];
1343
				}
1344
				// fetch cookie
1345
				if (strpos($res, 'Set-Cookie:') === 0) {
1346
				    $domain = $url['host'];
1347
				    if (preg_match('/^Set-Cookie:(.+)(?:domain=\s*([^ ;]+))?/i', $res, $c1)) {
1348
				        if (! empty($c1[2])) {
1349
				            $domain = trim($c1[2]);
1350
				        }
1351
				        if (preg_match('/([^ ]+=[^;]+)/', $c1[1], $c2)) {
1352
				            $data['cookies'][$domain] = $c2[1];
1353
				        }
1354
				    }
1355
				}
1356
	        }
1357
	        if ($data['url']) {
1358
	            ++$data['cnt'];
1359
	            fclose($stream);
1360
1361
	            return self::getStreamByUrl($data, $redirectLimit);
1362
	        }
1363
1364
	        return $stream;
1365
	    }
1366
1367
	    return false;
1368
	}
1369
1370
	/**
1371
	 * Call curl_exec() with supported redirect on `safe_mode` or `open_basedir`.
1372
	 *
1373
	 * @param resource    $curl
1374
	 * @param array       $options
1375
	 * @param array       $headers
1376
	 *
1377
	 * @throws \Exception
1378
	 *
1379
	 * @return mixed
1380
	 *
1381
	 * @author Naoki Sawada
1382
	 */
1383
	public static function curlExec($curl, $options = [], $headers = [])
1384
	{
1385
	    if ($followLocation = (! ini_get('safe_mode') && ! ini_get('open_basedir'))) {
1386
	        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1387
	    }
1388
1389
	    if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options 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...
1390
	        curl_setopt_array($curl, $options);
1391
	    }
1392
1393
	    if ($headers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $headers 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...
1394
	        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1395
	    }
1396
1397
	    $result = curl_exec($curl);
1398
1399
	    if (! $followLocation && $redirect = curl_getinfo($curl, CURLINFO_REDIRECT_URL)) {
1400
	        if ($stream = self::getStreamByUrl(['target' => $redirect, 'headers' => $headers])) {
1401
	            $result = stream_get_contents($stream);
1402
	        }
1403
	    }
1404
1405 View Code Duplication
	    if ($result === 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...
1406
	        if (curl_errno($curl)) {
1407
	            throw new \Exception('curl_exec() failed: '.curl_error($curl));
1408
	        } else {
1409
	            throw new \Exception('curl_exec(): empty response');
1410
	        }
1411
	    }
1412
1413
	    curl_close($curl);
1414
1415
	    return $result;
1416
	}
1417
1418
	/**
1419
	 * Return network volumes config.
1420
	 *
1421
	 * @return array
1422
	 * @author Dmitry (dio) Levashov
1423
	 */
1424
	protected function getNetVolumes()
1425
	{
1426
	    if ($data = $this->session->get('netvolume', [])) {
1427
	        return $data;
1428
	    }
1429
1430
	    return [];
1431
	}
1432
1433
	/**
1434
	 * Save network volumes config.
1435
	 *
1436
	 * @param  array  $volumes  volumes config
1437
	 * @return void
1438
	 * @author Dmitry (dio) Levashov
1439
	 */
1440
	protected function saveNetVolumes($volumes)
1441
	{
1442
	    $this->session->set('netvolume', $volumes);
1443
	}
1444
1445
	/**
1446
	 * Remove netmount volume.
1447
	 *
1448
	 * @param string $key netvolume key
1449
	 * @param object $volume volume driver instance
1450
	 * @return bool
1451
	 */
1452
	protected function removeNetVolume($key, $volume)
1453
	{
1454
	    $netVolumes = $this->getNetVolumes();
1455
	    $res = true;
1456
	    if (is_object($volume) && method_exists($volume, 'netunmount')) {
1457
	        $res = $volume->netunmount($netVolumes, $key);
1458
	        $volume->clearSessionCache();
1459
	    }
1460
	    if ($res) {
1461
	        if (is_string($key) && isset($netVolumes[$key])) {
1462
	            unset($netVolumes[$key]);
1463
	            $this->saveNetVolumes($netVolumes);
1464
1465
	            return true;
1466
	        }
1467
	    }
1468
1469
	    return false;
1470
	}
1471
1472
	/**
1473
	 * Get plugin instance & set to $this->plugins.
1474
	 *
1475
	 * @param  string $name   Plugin name (dirctory name)
1476
	 * @param  array  $opts   Plugin options (optional)
1477
	 * @return object | bool Plugin object instance Or false
1478
	 * @author Naoki Sawada
1479
	 */
1480
	protected function getPluginInstance($name, $opts = [])
1481
	{
1482
	    $key = strtolower($name);
1483
	    if (! isset($this->plugins[$key])) {
1484
	        $class = 'elFinderPlugin'.$name;
1485
			// to try auto load
1486
			if (! class_exists($class)) {
1487
			    $p_file = dirname(__FILE__).DIRECTORY_SEPARATOR.'plugins'.DIRECTORY_SEPARATOR.$name.DIRECTORY_SEPARATOR.'plugin.php';
1488
			    if (is_file($p_file)) {
1489
			        include_once $p_file;
1490
			    }
1491
			}
1492
	        if (class_exists($class, false)) {
1493
	            $this->plugins[$key] = new $class($opts);
1494
	        } else {
1495
	            $this->plugins[$key] = false;
1496
	        }
1497
	    }
1498
1499
	    return $this->plugins[$key];
1500
	}
1501
1502
    protected function netmount($args)
1503
    {
1504
        $options = [];
1505
        $protocol = $args['protocol'];
1506
1507
        if ($protocol === 'netunmount') {
1508
            if (! empty($args['user']) && $volume = $this->volume($args['user'])) {
1509
                if ($this->removeNetVolume($args['host'], $volume)) {
1510
                    return ['removed' => [['hash' => $volume->root()]]];
1511
                }
1512
            }
1513
1514
            return ['sync' => true, 'error' => $this->error(self::ERROR_NETUNMOUNT)];
1515
        }
1516
1517
        $driver = isset(self::$netDrivers[$protocol]) ? self::$netDrivers[$protocol] : '';
1518
        $class = 'elFinderVolume'.$driver;
1519
1520
        if (! class_exists($class)) {
1521
            return ['error' => $this->error(self::ERROR_NETMOUNT, $args['host'], self::ERROR_NETMOUNT_NO_DRIVER)];
1522
        }
1523
1524
        if (! $args['path']) {
1525
            $args['path'] = '/';
1526
        }
1527
1528
        foreach ($args as $k => $v) {
1529
            if ($k != 'options' && $k != 'protocol' && $v) {
1530
                $options[$k] = $v;
1531
            }
1532
        }
1533
1534
        if (is_array($args['options'])) {
1535
            foreach ($args['options'] as $key => $value) {
1536
                $options[$key] = $value;
1537
            }
1538
        }
1539
1540
        $volume = new $class();
1541
1542
		// pass session handler
1543
		$volume->setSession($this->session);
1544
1545
        if (method_exists($volume, 'netmountPrepare')) {
1546
            $options = $volume->netmountPrepare($options);
1547
            if (isset($options['exit'])) {
1548
                if ($options['exit'] === 'callback') {
1549
                    $this->callback($options['out']);
1550
                }
1551
1552
                return $options;
1553
            }
1554
        }
1555
1556
        $netVolumes = $this->getNetVolumes();
1557
1558
        if (! isset($options['id'])) {
1559
            // given fixed unique id
1560
			if (! $options['id'] = $this->getNetVolumeUniqueId($netVolumes)) {
1561
			    return ['error' => $this->error(self::ERROR_NETMOUNT, $args['host'], 'Could\'t given volume id.')];
1562
			}
1563
        }
1564
1565
		// load additional volume root options
1566 View Code Duplication
		if (! empty($this->optionsNetVolumes['*'])) {
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...
1567
		    $options = array_merge($options, $this->optionsNetVolumes['*']);
1568
		}
1569 View Code Duplication
        if (! empty($this->optionsNetVolumes[$protocol])) {
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...
1570
            $options = array_merge($options, $this->optionsNetVolumes[$protocol]);
1571
        }
1572
1573
        if (! $key = $volume->netMountKey) {
1574
            $key = md5($protocol.'-'.serialize($options));
1575
        }
1576
        $options['netkey'] = $key;
1577
1578
        if ($volume->mount($options)) {
1579
            $options['driver'] = $driver;
1580
            $netVolumes[$key] = $options;
1581
            $this->saveNetVolumes($netVolumes);
1582
            $rootstat = $volume->file($volume->root());
1583
1584
            return ['added' => [$rootstat]];
1585
        } else {
1586
            $this->removeNetVolume(null, $volume);
1587
1588
            return ['error' => $this->error(self::ERROR_NETMOUNT, $args['host'], implode(' ', $volume->error()))];
1589
        }
1590
    }
1591
1592
	/**
1593
	 * "Open" directory
1594
	 * Return array with following elements
1595
	 *  - cwd          - opened dir info
1596
	 *  - files        - opened dir content [and dirs tree if $args[tree]]
1597
	 *  - api          - api version (if $args[init])
1598
	 *  - uplMaxSize   - if $args[init]
1599
	 *  - error        - on failed.
1600
	 *
1601
	 * @param  array  command arguments
1602
	 * @return array
1603
	 * @author Dmitry (dio) Levashov
1604
	 **/
1605
	protected function open($args)
1606
	{
1607
	    $target = $args['target'];
1608
	    $init = ! empty($args['init']);
1609
	    $tree = ! empty($args['tree']);
1610
	    $volume = $this->volume($target);
1611
	    $cwd = $volume ? $volume->dir($target) : false;
1612
	    $hash = $init ? 'default folder' : '#'.$target;
1613
	    $sleep = 0;
0 ignored issues
show
Unused Code introduced by
$sleep 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...
1614
	    $compare = '';
1615
1616
		// on init request we can get invalid dir hash -
1617
		// dir which can not be opened now, but remembered by client,
1618
		// so open default dir
1619
		if ((! $cwd || ! $cwd['read']) && $init) {
1620
		    $volume = $this->default;
1621
		    $target = $volume->defaultPath();
1622
		    $cwd = $volume->dir($target);
1623
		}
1624
1625
	    if (! $cwd) {
1626
	        return ['error' => $this->error(self::ERROR_OPEN, $hash, self::ERROR_DIR_NOT_FOUND)];
1627
	    }
1628
	    if (! $cwd['read']) {
1629
	        return ['error' => $this->error(self::ERROR_OPEN, $hash, self::ERROR_PERM_DENIED)];
1630
	    }
1631
1632
	    $files = [];
1633
1634
		// get current working directory files list
1635 View Code Duplication
		if (($ls = $volume->scandir($cwd['hash'])) === 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...
1636
		    return ['error' => $this->error(self::ERROR_OPEN, $cwd['name'], $volume->error())];
1637
		}
1638
1639
	    if (isset($cwd['dirs']) && $cwd['dirs'] != 1) {
1640
	        $cwd = $volume->dir($target);
1641
	    }
1642
1643
		// get other volume root
1644
		if ($tree) {
1645
		    foreach ($this->volumes as $id => $v) {
1646
		        $files[] = $v->file($v->root());
1647
		    }
1648
		}
1649
1650
		// long polling mode
1651
		if ($args['compare']) {
1652
		    $sleep = max(1, (int) $volume->getOption('lsPlSleep'));
1653
		    $standby = (int) $volume->getOption('plStandby');
1654
		    if ($standby > 0 && $sleep > $standby) {
1655
		        $standby = $sleep;
1656
		    }
1657
		    $limit = max(0, floor($standby / $sleep)) + 1;
1658
		    do {
1659
		        self::extendTimeLimit(30 + $sleep);
1660
		        $_mtime = 0;
1661
		        foreach ($ls as $_f) {
1662
		            $_mtime = max($_mtime, $_f['ts']);
1663
		        }
1664
		        $compare = strval(count($ls)).':'.strval($_mtime);
1665
		        if ($compare !== $args['compare']) {
1666
		            break;
1667
		        }
1668
		        if (--$limit) {
1669
		            sleep($sleep);
1670
		            $volume->clearstatcache();
1671
		            if (($ls = $volume->scandir($cwd['hash'])) === false) {
1672
		                break;
1673
		            }
1674
		        }
1675
		    } while ($limit);
1676 View Code Duplication
		    if ($ls === 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...
1677
		        return ['error' => $this->error(self::ERROR_OPEN, $cwd['name'], $volume->error())];
1678
		    }
1679
		}
1680
1681
	    if ($ls) {
1682
	        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...
1683
	            $files = array_merge($files, $ls);
1684
	        } else {
1685
	            $files = $ls;
1686
	        }
1687
	    }
1688
1689
	    $result = [
1690
			'cwd' => $cwd,
1691
			'options' => $volume->options($cwd['hash']),
1692
			'files' => $files,
1693
		];
1694
1695
	    if ($compare) {
1696
	        $result['cwd']['compare'] = $compare;
1697
	    }
1698
1699
	    if (! empty($args['init'])) {
1700
	        $result['api'] = $this->version;
1701
	        $result['uplMaxSize'] = ini_get('upload_max_filesize');
1702
	        $result['uplMaxFile'] = ini_get('max_file_uploads');
1703
	        $result['netDrivers'] = array_keys(self::$netDrivers);
1704
	        $result['maxTargets'] = $this->maxTargets;
1705
	        if ($volume) {
1706
	            $result['cwd']['root'] = $volume->root();
1707
	        }
1708
	    }
1709
1710
	    return $result;
1711
	}
1712
1713
	/**
1714
	 * Return dir files names list.
1715
	 *
1716
	 * @param  array  command arguments
1717
	 * @return array
1718
	 * @author Dmitry (dio) Levashov
1719
	 **/
1720
	protected function ls($args)
1721
	{
1722
	    $target = $args['target'];
1723
	    $intersect = isset($args['intersect']) ? $args['intersect'] : [];
1724
1725
	    if (($volume = $this->volume($target)) == false
1726
		|| ($list = $volume->ls($target, $intersect)) === false) {
1727
	        return ['error' => $this->error(self::ERROR_OPEN, '#'.$target)];
1728
	    }
1729
1730
	    return ['list' => $list];
1731
	}
1732
1733
	/**
1734
	 * Return subdirs for required directory.
1735
	 *
1736
	 * @param  array  command arguments
1737
	 * @return array
1738
	 * @author Dmitry (dio) Levashov
1739
	 **/
1740
	protected function tree($args)
1741
	{
1742
	    $target = $args['target'];
1743
1744
	    if (($volume = $this->volume($target)) == false
1745
		|| ($tree = $volume->tree($target)) == false) {
1746
	        return ['error' => $this->error(self::ERROR_OPEN, '#'.$target)];
1747
	    }
1748
1749
	    return ['tree' => $tree];
1750
	}
1751
1752
	/**
1753
	 * Return parents dir for required directory.
1754
	 *
1755
	 * @param  array  command arguments
1756
	 * @return array
1757
	 * @author Dmitry (dio) Levashov
1758
	 **/
1759
	protected function parents($args)
1760
	{
1761
	    $target = $args['target'];
1762
	    $until = $args['until'];
1763
1764
	    if (($volume = $this->volume($target)) == false
1765
		|| ($tree = $volume->parents($target, false, $until)) == false) {
1766
	        return ['error' => $this->error(self::ERROR_OPEN, '#'.$target)];
1767
	    }
1768
1769
	    return ['tree' => $tree];
1770
	}
1771
1772
	/**
1773
	 * Return new created thumbnails list.
1774
	 *
1775
	 * @param  array  command arguments
1776
	 * @return array
1777
	 * @author Dmitry (dio) Levashov
1778
	 **/
1779
	protected function tmb($args)
1780
	{
1781
	    $result = ['images' => []];
1782
	    $targets = $args['targets'];
1783
1784
	    foreach ($targets as $target) {
1785
	        self::extendTimeLimit();
1786
1787
	        if (($volume = $this->volume($target)) != false
1788
			&& (($tmb = $volume->tmb($target)) != false)) {
1789
	            $result['images'][$target] = $tmb;
1790
	        }
1791
	    }
1792
1793
	    return $result;
1794
	}
1795
1796
	/**
1797
	 * Download files/folders as an archive file.
1798
	 *
1799
	 * 1st: Return srrsy contains download archive file info
1800
	 * 2nd: Return array contains opened file pointer, root itself and required headers
1801
	 *
1802
	 * @param  array  command arguments
1803
	 * @return array
1804
	 * @author Naoki Sawada
1805
	 **/
1806
	protected function zipdl($args)
0 ignored issues
show
Coding Style introduced by
zipdl uses the super-global variable $_SERVER 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...
1807
	{
1808
	    $targets = $args['targets'];
1809
	    $download = ! empty($args['download']);
1810
	    $h404 = 'HTTP/1.x 404 Not Found';
1811
1812
	    if (! $download) {
1813
	        //1st: Return srrsy contains download archive file info
1814
			$error = [self::ERROR_ARCHIVE];
1815
	        if (($volume = $this->volume($targets[0])) !== false) {
1816
	            if ($dlres = $volume->zipdl($targets)) {
1817
	                $path = $dlres['path'];
1818
	                register_shutdown_function(create_function('$f', 'connection_status() && is_file($f) && unlink($f);'), $path);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
1819
	                if (count($targets) === 1) {
1820
	                    $name = basename($volume->path($targets[0]));
1821
	                } else {
1822
	                    $name = $dlres['prefix'].'_Files';
1823
	                }
1824
	                $name .= '.'.$dlres['ext'];
1825
	                $result = [
1826
						'zipdl' => [
1827
							'file' => basename($path),
1828
							'name' => $name,
1829
							'mime' => $dlres['mime'],
1830
						],
1831
					];
1832
1833
	                return $result;
1834
	            }
1835
	            $error = array_merge($error, $volume->error());
1836
	        }
1837
1838
	        return ['error' => $error];
1839
	    } else {
1840
	        // 2nd: Return array contains opened file pointer, root itself and required headers
1841
			if (count($targets) !== 4 || ($volume = $this->volume($targets[0])) == false) {
1842
			    return ['error' => 'File not found', 'header' => $h404, 'raw' => true];
1843
			}
1844
	        $file = $targets[1];
1845
	        $path = $volume->getTempPath().DIRECTORY_SEPARATOR.$file;
1846
	        register_shutdown_function(create_function('$f', 'is_file($f) && unlink($f);'), $path);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
1847
	        if (! is_readable($path)) {
1848
	            return ['error' => 'File not found', 'header' => $h404, 'raw' => true];
1849
	        }
1850
	        $name = $targets[2];
1851
	        $mime = $targets[3];
1852
1853
	        $filenameEncoded = rawurlencode($name);
1854 View Code Duplication
	        if (strpos($filenameEncoded, '%') === false) { // ASCII only
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...
1855
				$filename = 'filename="'.$name.'"';
1856
	        } else {
1857
	            $ua = $_SERVER['HTTP_USER_AGENT'];
1858
	            if (preg_match('/MSIE [4-8]/', $ua)) { // IE < 9 do not support RFC 6266 (RFC 2231/RFC 5987)
1859
					$filename = 'filename="'.$filenameEncoded.'"';
1860
	            } elseif (strpos($ua, 'Chrome') === false && strpos($ua, 'Safari') !== false && preg_match('#Version/[3-5]#', $ua)) { // Safari < 6
1861
					$filename = 'filename="'.str_replace('"', '', $name).'"';
1862
	            } else { // RFC 6266 (RFC 2231/RFC 5987)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
1863
					$filename = 'filename*=UTF-8\'\''.$filenameEncoded;
1864
	            }
1865
	        }
1866
1867
	        $fp = fopen($path, 'rb');
1868
	        $file = fstat($fp);
1869
	        $result = [
1870
				'pointer' => $fp,
1871
				'header' => [
1872
					'Content-Type: '.$mime,
1873
					'Content-Disposition: attachment; '.$filename,
1874
					'Content-Transfer-Encoding: binary',
1875
					'Content-Length: '.$file['size'],
1876
					'Accept-Ranges: none',
1877
					'Connection: close',
1878
				],
1879
			];
1880
1881
	        return $result;
1882
	    }
1883
	}
1884
1885
	/**
1886
	 * Required to output file in browser when volume URL is not set
1887
	 * Return array contains opened file pointer, root itself and required headers.
1888
	 *
1889
	 * @param  array  command arguments
1890
	 * @return array
1891
	 * @author Dmitry (dio) Levashov
1892
	 **/
1893
	protected function file($args)
0 ignored issues
show
Coding Style introduced by
file uses the super-global variable $_SERVER 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...
1894
	{
1895
	    $target = $args['target'];
1896
	    $download = ! empty($args['download']);
1897
	    $h403 = 'HTTP/1.x 403 Access Denied';
1898
	    $h404 = 'HTTP/1.x 404 Not Found';
1899
1900 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
1901
	        return ['error' => 'File not found', 'header' => $h404, 'raw' => true];
1902
	    }
1903
1904 View Code Duplication
	    if (($file = $volume->file($target)) == 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...
1905
	        return ['error' => 'File not found', 'header' => $h404, 'raw' => true];
1906
	    }
1907
1908
	    if (! $file['read']) {
1909
	        return ['error' => 'Access denied', 'header' => $h403, 'raw' => true];
1910
	    }
1911
1912 View Code Duplication
	    if (($fp = $volume->open($target)) == 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...
1913
	        return ['error' => 'File not found', 'header' => $h404, 'raw' => true];
1914
	    }
1915
1916
		// allow change MIME type by 'file.pre' callback functions
1917
		$mime = isset($args['mime']) ? $args['mime'] : $file['mime'];
1918
	    if ($download) {
1919
	        $disp = 'attachment';
1920
	    } else {
1921
	        $dispInlineRegex = $volume->getOption('dispInlineRegex');
1922
	        $inlineRegex = false;
1923
	        if ($dispInlineRegex) {
1924
	            $inlineRegex = '#'.str_replace('#', '\\#', $dispInlineRegex).'#';
1925
	            try {
1926
	                preg_match($inlineRegex, '');
1927
	            } catch (Exception $e) {
1928
	                $inlineRegex = false;
1929
	            }
1930
	        }
1931
	        if (! $inlineRegex) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inlineRegex of type string|false 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...
1932
	            $inlineRegex = '#^(?:(?:image|text)|application/x-shockwave-flash$)#';
1933
	        }
1934
	        $disp = preg_match($inlineRegex, $mime) ? 'inline' : 'attachment';
1935
	    }
1936
1937
	    $filenameEncoded = rawurlencode($file['name']);
1938 View Code Duplication
	    if (strpos($filenameEncoded, '%') === false) { // ASCII only
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...
1939
			$filename = 'filename="'.$file['name'].'"';
1940
	    } else {
1941
	        $ua = $_SERVER['HTTP_USER_AGENT'];
1942
	        if (preg_match('/MSIE [4-8]/', $ua)) { // IE < 9 do not support RFC 6266 (RFC 2231/RFC 5987)
1943
				$filename = 'filename="'.$filenameEncoded.'"';
1944
	        } elseif (strpos($ua, 'Chrome') === false && strpos($ua, 'Safari') !== false && preg_match('#Version/[3-5]#', $ua)) { // Safari < 6
1945
				$filename = 'filename="'.str_replace('"', '', $file['name']).'"';
1946
	        } else { // RFC 6266 (RFC 2231/RFC 5987)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
1947
				$filename = 'filename*=UTF-8\'\''.$filenameEncoded;
1948
	        }
1949
	    }
1950
1951
	    $result = [
1952
			'volume' => $volume,
1953
			'pointer' => $fp,
1954
			'info' => $file,
1955
			'header' => [
1956
				'Content-Type: '.$mime,
1957
				'Content-Disposition: '.$disp.'; '.$filename,
1958
				'Content-Transfer-Encoding: binary',
1959
				'Content-Length: '.$file['size'],
1960
				'Connection: close',
1961
			],
1962
		];
1963
	    if (isset($file['url']) && $file['url'] && $file['url'] != 1) {
1964
	        $result['header'][] = 'Content-Location: '.$file['url'];
1965
	    }
1966
1967
	    return $result;
1968
	}
1969
1970
	/**
1971
	 * Count total files size.
1972
	 *
1973
	 * @param  array  command arguments
1974
	 * @return array
1975
	 * @author Dmitry (dio) Levashov
1976
	 **/
1977
	protected function size($args)
1978
	{
1979
	    $size = 0;
1980
	    $files = 0;
1981
	    $dirs = 0;
1982
	    $itemCount = true;
1983
1984
	    foreach ($args['targets'] as $target) {
1985
	        if (($volume = $this->volume($target)) == false
1986
			|| ($file = $volume->file($target)) == false
1987
			|| ! $file['read']) {
1988
	            return ['error' => $this->error(self::ERROR_OPEN, '#'.$target)];
1989
	        }
1990
1991
	        $volRes = $volume->size($target);
1992
	        if (is_array($volRes)) {
1993
	            if (! empty($volRes['size'])) {
1994
	                $size += $volRes['size'];
1995
	            }
1996
	            if ($itemCount) {
1997
	                if (! empty($volRes['files'])) {
1998
	                    $files += $volRes['files'];
1999
	                }
2000
	                if (! empty($volRes['dirs'])) {
2001
	                    $dirs += $volRes['dirs'];
2002
	                }
2003
	            }
2004
	        } elseif (is_numeric($volRes)) {
2005
	            $size += $volRes;
2006
	            $files = $dirs = 'unknown';
2007
	            $itemCount = false;
2008
	        }
2009
	    }
2010
2011
	    return ['size' => $size, 'fileCnt' => $files, 'dirCnt' => $dirs];
2012
	}
2013
2014
	/**
2015
	 * Create directory.
2016
	 *
2017
	 * @param  array  command arguments
2018
	 * @return array
2019
	 * @author Dmitry (dio) Levashov
2020
	 **/
2021
	protected function mkdir($args)
2022
	{
2023
	    $target = $args['target'];
2024
	    $name = $args['name'];
2025
	    $dirs = $args['dirs'];
2026
	    if ($name === '' && ! $dirs) {
2027
	        return ['error' => $this->error(self::ERROR_INV_PARAMS, 'mkdir')];
2028
	    }
2029
2030 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
2031
	        return ['error' => $this->error(self::ERROR_MKDIR, $name, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target)];
2032
	    }
2033
	    if ($dirs) {
2034
	        sort($dirs);
2035
	        $reset = null;
2036
	        $mkdirs = [];
2037
	        foreach ($dirs as $dir) {
2038
	            $tgt = &$mkdirs;
2039
	            $_names = explode('/', trim($dir, '/'));
2040
	            foreach ($_names as $_key => $_name) {
2041
	                if (! isset($tgt[$_name])) {
2042
	                    $tgt[$_name] = [];
2043
	                }
2044
	                $tgt = &$tgt[$_name];
2045
	            }
2046
	            $tgt = &$reset;
2047
	        }
2048
	        $res = $this->ensureDirsRecursively($volume, $target, $mkdirs);
0 ignored issues
show
Documentation introduced by
$mkdirs is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2049
	        if ($res['error']) {
2050
	            $errors = $volume->error();
2051
	            if ($res['makes']) {
2052
	                $this->rm(['targets' => $res['makes']]);
2053
	            }
2054
2055
	            return ['error' => $this->error(self::ERROR_MKDIR, $res['error'][0], $errors)];
2056
	        } else {
2057
	            return ['added' => $res['stats'], 'hashes' => $res['hashes']];
2058
	        }
2059
	    } else {
2060
	        return ($dir = $volume->mkdir($target, $name)) == false
2061
				? ['error' => $this->error(self::ERROR_MKDIR, $name, $volume->error())]
2062
				: ['added' => [$dir]];
2063
	    }
2064
	}
2065
2066
	/**
2067
	 * Create empty file.
2068
	 *
2069
	 * @param  array  command arguments
2070
	 * @return array
2071
	 * @author Dmitry (dio) Levashov
2072
	 **/
2073
	protected function mkfile($args)
2074
	{
2075
	    $target = $args['target'];
2076
	    $name = $args['name'];
2077
2078 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
2079
	        return ['error' => $this->error(self::ERROR_MKFILE, $name, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target)];
2080
	    }
2081
2082
	    return ($file = $volume->mkfile($target, $args['name'])) == false
2083
			? ['error' => $this->error(self::ERROR_MKFILE, $name, $volume->error())]
2084
			: ['added' => [$file]];
2085
	}
2086
2087
	/**
2088
	 * Rename file.
2089
	 *
2090
	 * @param  array  $args
2091
	 * @return array
2092
	 * @author Dmitry (dio) Levashov
2093
	 **/
2094
	protected function rename($args)
2095
	{
2096
	    $target = $args['target'];
2097
	    $name = $args['name'];
2098
2099 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
2100
		|| ($rm = $volume->file($target)) == false) {
2101
	        return ['error' => $this->error(self::ERROR_RENAME, '#'.$target, self::ERROR_FILE_NOT_FOUND)];
2102
	    }
2103
	    $rm['realpath'] = $volume->realpath($target);
2104
2105
	    if ($this->itemLocked($target)) {
2106
	        return ['error' => $this->error(self::ERROR_LOCKED, $rm['name'])];
2107
	    }
2108
2109
	    return ($file = $volume->rename($target, $name)) == false
2110
			? ['error' => $this->error(self::ERROR_RENAME, $rm['name'], $volume->error())]
2111
			: ['added' => [$file], 'removed' => [$rm]];
2112
	}
2113
2114
	/**
2115
	 * Duplicate file - create copy with "copy %d" suffix.
2116
	 *
2117
	 * @param array  $args  command arguments
2118
	 * @return array
2119
	 * @author Dmitry (dio) Levashov
2120
	 **/
2121
	protected function duplicate($args)
2122
	{
2123
	    $targets = is_array($args['targets']) ? $args['targets'] : [];
2124
	    $result = ['added' => []];
2125
	    $suffix = empty($args['suffix']) ? 'copy' : $args['suffix'];
2126
2127
	    $this->itemLock($targets);
2128
2129
	    foreach ($targets as $target) {
2130
	        self::extendTimeLimit();
2131
2132 View Code Duplication
	        if (($volume = $this->volume($target)) == 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...
2133
			|| ($src = $volume->file($target)) == false) {
2134
	            $result['warning'] = $this->error(self::ERROR_COPY, '#'.$target, self::ERROR_FILE_NOT_FOUND);
2135
	            break;
2136
	        }
2137
2138
	        if (($file = $volume->duplicate($target, $suffix)) == false) {
2139
	            $result['warning'] = $this->error($volume->error());
2140
	            break;
2141
	        }
2142
2143
	        $result['added'][] = $file;
2144
	    }
2145
2146
	    return $result;
2147
	}
2148
2149
	/**
2150
	 * Remove dirs/files.
2151
	 *
2152
	 * @param array  command arguments
2153
	 * @return array
2154
	 * @author Dmitry (dio) Levashov
2155
	 **/
2156
	protected function rm($args)
2157
	{
2158
	    $targets = is_array($args['targets']) ? $args['targets'] : [];
2159
	    $result = ['removed' => []];
2160
2161
	    foreach ($targets as $target) {
2162
	        self::extendTimeLimit();
2163
2164 View Code Duplication
	        if (($volume = $this->volume($target)) == 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...
2165
	            $result['warning'] = $this->error(self::ERROR_RM, '#'.$target, self::ERROR_FILE_NOT_FOUND);
2166
	            break;
2167
	        }
2168
2169 View Code Duplication
	        if ($this->itemLocked($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...
2170
	            $rm = $volume->file($target);
2171
	            $result['warning'] = $this->error(self::ERROR_LOCKED, $rm['name']);
2172
	            break;
2173
	        }
2174
2175
	        if (! $volume->rm($target)) {
2176
	            $result['warning'] = $this->error($volume->error());
2177
	            break;
2178
	        }
2179
	    }
2180
2181
	    return $result;
2182
	}
2183
2184
	/**
2185
	 * Return has subdirs.
2186
	 *
2187
	 * @param  array  command arguments
2188
	 * @return array
2189
	 * @author Dmitry Naoki Sawada
2190
	 **/
2191
	protected function subdirs($args)
2192
	{
2193
	    $result = ['subdirs' => []];
2194
	    $targets = $args['targets'];
2195
2196
	    foreach ($targets as $target) {
2197 View Code Duplication
	        if (($volume = $this->volume($target)) !== 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...
2198
	            $result['subdirs'][$target] = $volume->subdirs($target) ? 1 : 0;
2199
	        }
2200
	    }
2201
2202
	    return $result;
2203
	}
2204
2205
	/**
2206
	 * Get remote contents.
2207
	 *
2208
	 * @param  string   $url     target url
2209
	 * @param  int      $timeout timeout (sec)
2210
	 * @param  int      $redirect_max redirect max count
2211
	 * @param  string   $ua
2212
	 * @param  resource $fp
2213
	 * @return string or bool(false)
2214
	 * @retval string contents
2215
	 * @rettval false  error
2216
	 * @author Naoki Sawada
2217
	 **/
2218
	protected function get_remote_contents(&$url, $timeout = 30, $redirect_max = 5, $ua = 'Mozilla/5.0', $fp = null)
2219
	{
2220
	    $method = (function_exists('curl_exec') && ! ini_get('safe_mode') && ! ini_get('open_basedir')) ? 'curl_get_contents' : 'fsock_get_contents';
2221
2222
	    return $this->$method($url, $timeout, $redirect_max, $ua, $fp);
2223
	}
2224
2225
	 /**
2226
	  * Get remote contents with cURL.
2227
	  *
2228
	  * @param  string   $url     target url
2229
	  * @param  int      $timeout timeout (sec)
2230
	  * @param  int      $redirect_max redirect max count
2231
	  * @param  string   $ua
2232
	  * @param  resource $outfp
2233
	  * @return string or bool(false)
2234
	  * @retval string contents
2235
	  * @retval false  error
2236
	  * @author Naoki Sawada
2237
	  **/
2238
	 protected function curl_get_contents(&$url, $timeout, $redirect_max, $ua, $outfp)
2239
	 {
2240
	     $ch = curl_init();
2241
	     curl_setopt($ch, CURLOPT_URL, $url);
2242
	     curl_setopt($ch, CURLOPT_HEADER, false);
2243
	     if ($outfp) {
2244
	         curl_setopt($ch, CURLOPT_FILE, $outfp);
2245
	     } else {
2246
	         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
2247
	         curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
2248
	     }
2249
	     curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 1);
2250
	     curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, $timeout);
2251
	     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
2252
	     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
2253
	     curl_setopt($ch, CURLOPT_MAXREDIRS, $redirect_max);
2254
	     curl_setopt($ch, CURLOPT_USERAGENT, $ua);
2255
	     $result = curl_exec($ch);
2256
	     $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
2257
	     curl_close($ch);
2258
2259
	     return $outfp ? $outfp : $result;
2260
	 }
2261
2262
	/**
2263
	 * Get remote contents with fsockopen().
2264
	 *
2265
	 * @param  string   $url          url
2266
	 * @param  int      $timeout      timeout (sec)
2267
	 * @param  int      $redirect_max redirect max count
2268
	 * @param  string   $ua
2269
	 * @param  resource $outfp
2270
	 * @return string or bool(false)
2271
	 * @retval string contents
2272
	 * @retval false  error
2273
	 * @author Naoki Sawada
2274
	 */
2275
	protected function fsock_get_contents(&$url, $timeout, $redirect_max, $ua, $outfp)
2276
	{
2277
	    $connect_timeout = 3;
2278
	    $connect_try = 3;
2279
	    $method = 'GET';
2280
	    $readsize = 4096;
2281
	    $ssl = '';
2282
2283
	    $getSize = null;
2284
	    $headers = '';
2285
2286
	    $arr = parse_url($url);
2287
	    if (! $arr) {
2288
	        // Bad request
2289
			return false;
2290
	    }
2291
	    if ($arr['scheme'] === 'https') {
2292
	        $ssl = 'ssl://';
2293
	    }
2294
2295
		// query
2296
		$arr['query'] = isset($arr['query']) ? '?'.$arr['query'] : '';
2297
		// port
2298
		$arr['port'] = isset($arr['port']) ? $arr['port'] : ($ssl ? 443 : 80);
2299
2300
	    $url_base = $arr['scheme'].'://'.$arr['host'].':'.$arr['port'];
2301
	    $url_path = isset($arr['path']) ? $arr['path'] : '/';
2302
	    $uri = $url_path.$arr['query'];
2303
2304
	    $query = $method.' '.$uri." HTTP/1.0\r\n";
2305
	    $query .= 'Host: '.$arr['host']."\r\n";
2306
	    $query .= "Accept: */*\r\n";
2307
	    $query .= "Connection: close\r\n";
2308
	    if (! empty($ua)) {
2309
	        $query .= 'User-Agent: '.$ua."\r\n";
2310
	    }
2311
	    if (! is_null($getSize)) {
2312
	        $query .= 'Range: bytes=0-'.($getSize - 1)."\r\n";
2313
	    }
2314
2315
	    $query .= $headers;
2316
2317
	    $query .= "\r\n";
2318
2319
	    $fp = $connect_try_count = 0;
2320
	    while (! $fp && $connect_try_count < $connect_try) {
2321
	        $errno = 0;
2322
	        $errstr = '';
2323
	        $fp = fsockopen(
2324
				$ssl.$arr['host'],
2325
				$arr['port'],
2326
				$errno, $errstr, $connect_timeout);
2327
	        if ($fp) {
2328
	            break;
2329
	        }
2330
	        $connect_try_count++;
2331
	        if (connection_aborted()) {
2332
	            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method fsock_get_contents() 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...
2333
	        }
2334
	        sleep(1); // wait 1sec
2335
	    }
2336
2337
	    $fwrite = 0;
0 ignored issues
show
Unused Code introduced by
$fwrite 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...
2338
	    for ($written = 0; $written < strlen($query); $written += $fwrite) {
2339
	        $fwrite = fwrite($fp, substr($query, $written));
2340
	        if (! $fwrite) {
2341
	            break;
2342
	        }
2343
	    }
2344
2345
	    $response = '';
0 ignored issues
show
Unused Code introduced by
$response 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...
2346
2347
	    if ($timeout) {
2348
	        socket_set_timeout($fp, $timeout);
2349
	    }
2350
2351
	    $_response = '';
2352
	    $header = '';
2353
	    while ($_response !== "\r\n") {
2354
	        $_response = fgets($fp, $readsize);
2355
	        $header .= $_response;
2356
	    }
2357
2358
	    $rccd = array_pad(explode(' ', $header, 2), 2, ''); // array('HTTP/1.1','200')
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
2359
		$rc = (int) $rccd[1];
2360
2361
	    $ret = false;
2362
		// Redirect
2363
		switch ($rc) {
2364
			case 307: // Temporary Redirect
2365
			case 303: // See Other
2366
			case 302: // Moved Temporarily
2367
			case 301: // Moved Permanently
2368
				$matches = [];
2369
				if (preg_match('/^Location: (.+?)(#.+)?$/im', $header, $matches) && --$redirect_max > 0) {
2370
				    $_url = $url;
2371
				    $url = trim($matches[1]);
2372
				    $hash = isset($matches[2]) ? trim($matches[2]) : '';
0 ignored issues
show
Unused Code introduced by
$hash 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...
2373
				    if (! preg_match('/^https?:\//', $url)) { // no scheme
2374
						if ($url[0] != '/') { // Relative path
2375
							// to Absolute path
2376
							$url = substr($url_path, 0, strrpos($url_path, '/')).'/'.$url;
2377
						}
2378
						// add sheme,host
2379
						$url = $url_base.$url;
2380
				    }
2381
				    if ($_url !== $url) {
2382
				        fclose($fp);
2383
2384
				        return $this->fsock_get_contents($url, $timeout, $redirect_max, $ua, $outfp);
2385
				    }
2386
				}
2387
				break;
2388
			case 200:
2389
				$ret = true;
2390
		}
2391
	    if (! $ret) {
2392
	        fclose($fp);
2393
2394
	        return false;
2395
	    }
2396
2397
	    $body = '';
2398
	    if (! $outfp) {
2399
	        $outfp = fopen('php://temp', 'rwb');
2400
	        $body = true;
2401
	    }
2402
	    while (fwrite($outfp, fread($fp, $readsize))) {
2403
	        if ($timeout) {
2404
	            $_status = socket_get_status($fp);
2405
	            if ($_status['timed_out']) {
2406
	                fclose($outfp);
2407
	                fclose($fp);
2408
2409
	                return false; // Request Time-out
2410
	            }
2411
	        }
2412
	    }
2413
	    if ($body) {
2414
	        rewind($outfp);
2415
	        $body = stream_get_contents($outfp);
2416
	        fclose($outfp);
2417
	        $outfp = null;
2418
	    }
2419
2420
	    fclose($fp);
2421
2422
	    return $outfp ? $outfp : $body; // Data
2423
	}
2424
2425
	/**
2426
	 * Parse Data URI scheme.
2427
	 *
2428
	 * @param  string $str
2429
	 * @param  array  $extTable
2430
	 * @param  array  $args
2431
	 * @return array
2432
	 * @author Naoki Sawada
2433
	 */
2434
	protected function parse_data_scheme($str, $extTable, $args = null)
2435
	{
2436
	    $data = $name = '';
2437
	    if ($fp = fopen('data://'.substr($str, 5), 'rb')) {
2438
	        if ($data = stream_get_contents($fp)) {
2439
	            $meta = stream_get_meta_data($fp);
2440
	            $ext = isset($extTable[$meta['mediatype']]) ? '.'.$extTable[$meta['mediatype']] : '';
2441
				// Set name if name eq 'image.png' and $args has 'name' array, e.g. clipboard data
2442
				if (is_array($args['name']) && isset($args['name'][0])) {
2443
				    $name = $args['name'][0];
2444
				    if ($ext) {
2445
				        $name = preg_replace('/\.[^.]*$/', '', $name);
2446
				    }
2447
				} else {
2448
				    $name = substr(md5($data), 0, 8);
2449
				}
2450
	            $name .= $ext;
2451
	        }
2452
	        fclose($fp);
2453
	    }
2454
2455
	    return [$data, $name];
2456
	}
2457
2458
	/**
2459
	 * Detect file MIME Type by local path.
2460
	 *
2461
	 * @param  string $path Local path
2462
	 * @return string file MIME Type
2463
	 * @author Naoki Sawada
2464
	 */
2465
	protected function detectMimeType($path)
2466
	{
2467
	    static $type, $finfo, $volume;
2468
	    if (! $type) {
2469
	        $keys = array_keys($this->volumes);
2470
	        $volume = $this->volumes[$keys[0]];
2471
2472
	        if (class_exists('finfo', false)) {
2473
	            $tmpFileInfo = explode(';', finfo_file(finfo_open(FILEINFO_MIME), __FILE__));
2474
	        } else {
2475
	            $tmpFileInfo = false;
2476
	        }
2477
	        $regexp = '/text\/x\-(php|c\+\+)/';
2478
	        if ($tmpFileInfo && preg_match($regexp, array_shift($tmpFileInfo))) {
2479
	            $type = 'finfo';
2480
	            $finfo = finfo_open(FILEINFO_MIME);
2481
	        } elseif (function_exists('mime_content_type')
2482
					&& preg_match($regexp, array_shift(explode(';', mime_content_type(__FILE__))))) {
0 ignored issues
show
Bug introduced by
explode(';', mime_content_type(__FILE__)) cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
2483
	            $type = 'mime_content_type';
2484
	        } elseif (function_exists('getimagesize')) {
2485
	            $type = 'getimagesize';
2486
	        } else {
2487
	            $type = 'none';
2488
	        }
2489
	    }
2490
2491
	    $mime = '';
2492
	    if ($type === 'finfo') {
2493
	        $mime = finfo_file($finfo, $path);
2494
	    } elseif ($type === 'mime_content_type') {
2495
	        $mime = mime_content_type($path);
2496
	    } elseif ($type === 'getimagesize') {
2497
	        if ($img = getimagesize($path)) {
2498
	            $mime = $img['mime'];
2499
	        }
2500
	    }
2501
2502
	    if ($mime) {
2503
	        $mime = explode(';', $mime);
2504
	        $mime = trim($mime[0]);
2505
2506
	        if (in_array($mime, ['application/x-empty', 'inode/x-empty'])) {
2507
	            // finfo return this mime for empty files
2508
				$mime = 'text/plain';
2509
	        } elseif ($mime == 'application/x-zip') {
2510
	            // http://elrte.org/redmine/issues/163
2511
				$mime = 'application/zip';
2512
	        }
2513
	    }
2514
2515
	    return $mime ? $mime : 'unknown';
2516
	}
2517
2518
	/**
2519
	 * Detect file type extension by local path.
2520
	 *
2521
	 * @param  object $volume elFinderVolumeDriver instance
2522
	 * @param  string $path Local path
2523
	 * @return string file type extension with dot
2524
	 * @author Naoki Sawada
2525
	 */
2526
	protected function detectFileExtension($volume, $path)
2527
	{
2528
	    $mime = $this->detectMimeType($path);
2529
	    $ext = $mime !== 'unknown' ? $volume->getExtentionByMime($mime) : '';
2530
2531
	    return $ext ? ('.'.$ext) : '';
2532
	}
2533
2534
	/**
2535
	 * chmod.
2536
	 *
2537
	 * @param array  command arguments
2538
	 * @return array
2539
	 * @author David Bartle
2540
	 **/
2541
	protected function chmod($args)
2542
	{
2543
	    $targets = $args['targets'];
2544
	    $mode = intval((string) $args['mode'], 8);
2545
2546
	    if (! is_array($targets)) {
2547
	        $targets = [$targets];
2548
	    }
2549
2550
	    $result = [];
2551
2552 View Code Duplication
	    if (($volume = $this->volume($targets[0])) == 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...
2553
	        $result['error'] = $this->error(self::ERROR_CONF_NO_VOL);
2554
2555
	        return $result;
2556
	    }
2557
2558
	    $this->itemLock($targets);
2559
2560
	    $files = [];
2561
	    $errors = [];
2562
	    foreach ($targets as $target) {
2563
	        self::extendTimeLimit();
2564
2565
	        $file = $volume->chmod($target, $mode);
2566
	        if ($file) {
2567
	            $files = array_merge($files, is_array($file) ? $file : [$file]);
2568
	        } else {
2569
	            $errors = array_merge($errors, $volume->error());
2570
	        }
2571
	    }
2572
2573
	    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...
2574
	        $result['changed'] = $files;
2575
	        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors 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...
2576
	            $result['warning'] = $this->error($errors);
2577
	        }
2578
	    } else {
2579
	        $result['error'] = $this->error($errors);
2580
	    }
2581
2582
	    return $result;
2583
	}
2584
2585
	/**
2586
	 * Save uploaded files.
2587
	 *
2588
	 * @param  array
2589
	 * @return array
2590
	 * @author Dmitry (dio) Levashov
2591
	 **/
2592
	protected function upload($args)
0 ignored issues
show
Coding Style introduced by
upload uses the super-global variable $GLOBALS 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...
2593
	{
2594
	    $ngReg = '/[\/\\?*:|"<>]/';
2595
	    $target = $args['target'];
2596
	    $volume = $this->volume($target);
2597
	    $files = isset($args['FILES']['upload']) && is_array($args['FILES']['upload']) ? $args['FILES']['upload'] : [];
2598
	    $header = empty($args['html']) ? [] : ['header' => 'Content-Type: text/html; charset=utf-8'];
2599
	    $result = array_merge(['added' => []], $header);
2600
	    $paths = $args['upload_path'] ? $args['upload_path'] : [];
2601
	    $chunk = $args['chunk'] ? $args['chunk'] : '';
2602
	    $cid = $args['cid'] ? (int) $args['cid'] : '';
2603
	    $mtimes = $args['mtime'] ? $args['mtime'] : [];
2604
2605
	    if (! $volume) {
2606
	        return array_merge(['error' => $this->error(self::ERROR_UPLOAD, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target)], $header);
2607
	    }
2608
2609
		// check $chunk
2610
		if (strpos($chunk, '/') !== false || strpos($chunk, '\\') !== false) {
2611
		    return ['error' => $this->error(self::ERROR_UPLOAD)];
2612
		}
2613
2614
	    if ($args['overwrite'] !== '') {
2615
	        $volume->setUploadOverwrite($args['overwrite']);
2616
	    }
2617
2618
	    $renames = $hashes = [];
2619
	    $suffix = '~';
2620
	    if ($args['renames'] && is_array($args['renames'])) {
2621
	        $renames = array_flip($args['renames']);
2622 View Code Duplication
	        if (is_string($args['suffix']) && ! preg_match($ngReg, $args['suffix'])) {
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...
2623
	            $suffix = $args['suffix'];
2624
	        }
2625
	    }
2626
	    if ($args['hashes'] && is_array($args['hashes'])) {
2627
	        $hashes = array_flip($args['hashes']);
2628
	    }
2629
2630
	    $this->itemLock($target);
2631
2632
		// regist Shutdown function
2633
		$GLOBALS['elFinderTempFiles'] = [];
2634
// 		if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
2635
// 			$shutdownfunc = function(){ // <- Parse error on PHP < 5.3 ;-(
2636
// 				foreach(array_keys($GLOBALS['elFinderTempFiles']) as $f){
2637
// 					unlink($f);
2638
// 				}
2639
// 			};
2640
// 		} else {
2641
			$shutdownfunc = create_function('', '
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
2642
				foreach(array_keys($GLOBALS[\'elFinderTempFiles\']) as $f){
2643
					is_file($f) && unlink($f);
2644
				}
2645
			');
2646
//		}
2647
		register_shutdown_function($shutdownfunc);
2648
2649
		// file extentions table by MIME
2650
		$extTable = array_flip(array_unique($volume->getMimeTable()));
2651
2652
	    if (empty($files)) {
2653
	        if (isset($args['upload']) && is_array($args['upload']) && ($tempDir = $this->getTempDir($volume->getTempPath()))) {
2654
	            $names = [];
2655
	            foreach ($args['upload'] as $i => $url) {
2656
	                // check chunked file upload commit
2657
					if ($chunk) {
2658
					    if ($url === 'chunkfail' && $args['mimes'] === 'chunkfail') {
2659
					        $this->checkChunkedFile(null, $chunk, $cid, $tempDir);
2660
					        if (preg_match('/^(.+)(\.\d+_(\d+))\.part$/s', $chunk, $m)) {
2661
					            $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $m[1], self::ERROR_UPLOAD_TRANSFER);
2662
					        }
2663
2664
					        return $result;
2665
					    } else {
2666
					        $tmpfname = $tempDir.'/'.$chunk;
2667
					        $files['tmp_name'][$i] = $tmpfname;
2668
					        $files['name'][$i] = $url;
2669
					        $files['error'][$i] = 0;
2670
					        $GLOBALS['elFinderTempFiles'][$tmpfname] = true;
2671
					        break;
2672
					    }
2673
					}
2674
2675
	                $tmpfname = $tempDir.DIRECTORY_SEPARATOR.'ELF_FATCH_'.md5($url.microtime(true));
2676
2677
	                $_name = '';
2678
					// check is data:
2679
					if (substr($url, 0, 5) === 'data:') {
2680
					    list($data, $args['name'][$i]) = $this->parse_data_scheme($url, $extTable, $args);
2681
					} else {
2682
					    $fp = fopen($tmpfname, 'wb');
2683
					    $data = $this->get_remote_contents($url, 30, 5, 'Mozilla/5.0', $fp);
2684
					    $_name = preg_replace('~^.*?([^/#?]+)(?:\?.*)?(?:#.*)?$~', '$1', rawurldecode($url));
2685
						// Check `Content-Disposition` response header
2686
						if ($data && ($headers = get_headers($url, true)) && ! empty($headers['Content-Disposition'])) {
2687
						    if (preg_match('/filename\*?=(?:([a-zA-Z0-9_-]+?)\'\')?"?([a-z0-9_.~%-]+)"?/i', $headers['Content-Disposition'], $m)) {
2688
						        $_name = rawurldecode($m[2]);
2689
						        if ($m[1] && strtoupper($m[1]) !== 'UTF-8' && function_exists('mb_convert_encoding')) {
2690
						            $_name = mb_convert_encoding($_name, 'UTF-8', $m[1]);
2691
						        }
2692
						    }
2693
						}
2694
					}
2695
	                if ($data) {
2696
	                    if (isset($args['name'][$i])) {
2697
	                        $_name = $args['name'][$i];
2698
	                    }
2699
	                    if ($_name) {
2700
	                        $_ext = '';
2701
	                        if (preg_match('/(\.[a-z0-9]{1,7})$/', $_name, $_match)) {
2702
	                            $_ext = $_match[1];
2703
	                        }
2704
	                        if ((is_resource($data) && fclose($data)) || file_put_contents($tmpfname, $data)) {
2705
	                            $GLOBALS['elFinderTempFiles'][$tmpfname] = true;
2706
	                            $_name = preg_replace($ngReg, '_', $_name);
2707
	                            list($_a, $_b) = array_pad(explode('.', $_name, 2), 2, '');
2708
	                            if ($_b === '') {
2709
	                                if ($_ext) {
2710
	                                    rename($tmpfname, $tmpfname.$_ext);
2711
	                                    $tmpfname = $tmpfname.$_ext;
2712
	                                }
2713
	                                $_b = $this->detectFileExtension($volume, $tmpfname);
2714
	                                $_name = $_a.$_b;
2715
	                            } else {
2716
	                                $_b = '.'.$_b;
2717
	                            }
2718
	                            if (isset($names[$_name])) {
2719
	                                $_name = $_a.'_'.$names[$_name]++.$_b;
2720
	                            } else {
2721
	                                $names[$_name] = 1;
2722
	                            }
2723
	                            $files['tmp_name'][$i] = $tmpfname;
2724
	                            $files['name'][$i] = $_name;
2725
	                            $files['error'][$i] = 0;
2726
								// set to auto rename
2727
								$volume->setUploadOverwrite(false);
2728
	                        } else {
2729
	                            unlink($tmpfname);
2730
	                        }
2731
	                    }
2732
	                }
2733
	            }
2734
	        }
2735
	        if (empty($files)) {
2736
	            return array_merge(['error' => $this->error(self::ERROR_UPLOAD, self::ERROR_UPLOAD_NO_FILES)], $header);
2737
	        }
2738
	    }
2739
2740
	    $addedDirs = [];
2741
	    foreach ($files['name'] as $i => $name) {
2742
	        if (($error = $files['error'][$i]) > 0) {
2743
	            $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, $error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE ? self::ERROR_UPLOAD_FILE_SIZE : self::ERROR_UPLOAD_TRANSFER);
2744
	            $this->uploadDebug = 'Upload error code: '.$error;
2745
	            break;
2746
	        }
2747
2748
	        $tmpname = $files['tmp_name'][$i];
2749
	        $thash = ($paths && isset($paths[$i])) ? $paths[$i] : '';
2750
	        $mtime = isset($mtimes[$i]) ? $mtimes[$i] : 0;
2751
	        if ($name === 'blob') {
2752
	            if ($chunk) {
2753
	                if ($tempDir = $this->getTempDir($volume->getTempPath())) {
2754
	                    list($tmpname, $name) = $this->checkChunkedFile($tmpname, $chunk, $cid, $tempDir, $volume);
2755
	                    if ($tmpname) {
2756
	                        if ($name === false) {
2757
	                            preg_match('/^(.+)(\.\d+_(\d+))\.part$/s', $chunk, $m);
2758
	                            $result['error'] = $this->error(self::ERROR_UPLOAD_FILE, $m[1], $tmpname);
2759
	                            $result['_chunkfailure'] = true;
2760
	                            $this->uploadDebug = 'Upload error: '.$tmpname;
2761
	                        } elseif ($name) {
2762
	                            $result['_chunkmerged'] = basename($tmpname);
2763
	                            $result['_name'] = $name;
2764
	                            $result['_mtime'] = $mtime;
2765
	                        }
2766
	                    }
2767
	                } else {
2768
	                    $result['error'] = $this->error(self::ERROR_UPLOAD_FILE, $chunk, self::ERROR_UPLOAD_TRANSFER);
2769
	                    $this->uploadDebug = 'Upload error: unable open tmp file';
2770
	                }
2771
2772
	                return $result;
2773
	            } else {
2774
	                // for form clipboard with Google Chrome or Opera
2775
					$name = 'image.png';
2776
	            }
2777
	        }
2778
2779
			// Set name if name eq 'image.png' and $args has 'name' array, e.g. clipboard data
2780
			if (strtolower(substr($name, 0, 5)) === 'image' && is_array($args['name']) && isset($args['name'][$i])) {
2781
			    $type = $files['type'][$i];
2782
			    $name = $args['name'][$i];
2783
			    $ext = isset($extTable[$type]) ? '.'.$extTable[$type] : '';
2784
			    if ($ext) {
2785
			        $name = preg_replace('/\.[^.]*$/', '', $name);
2786
			    }
2787
			    $name .= $ext;
2788
			}
2789
2790
			// do hook function 'upload.presave'
2791
			if (! empty($this->listeners['upload.presave'])) {
2792
			    foreach ($this->listeners['upload.presave'] as $handler) {
2793
			        call_user_func_array($handler, [&$thash, &$name, $tmpname, $this, $volume]);
2794
			    }
2795
			}
2796
2797
	        if ($mtime) {
2798
	            // for keep timestamp option in the LocalFileSystem volume
2799
				touch($tmpname, $mtime);
2800
	        }
2801
2802
	        if (($fp = fopen($tmpname, 'rb')) == false) {
2803
	            $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, self::ERROR_UPLOAD_TRANSFER);
2804
	            $this->uploadDebug = 'Upload error: unable open tmp file';
2805 View Code Duplication
	            if (! is_uploaded_file($tmpname)) {
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...
2806
	                if (unlink($tmpname)) {
2807
	                    unset($GLOBALS['elFinderTempFiles'][$tmpfname]);
2808
	                }
2809
	                continue;
2810
	            }
2811
	            break;
2812
	        }
2813
	        $rnres = [];
2814
	        if ($thash !== '' && $thash !== $target) {
2815
	            if ($dir = $volume->dir($thash)) {
2816
	                $_target = $thash;
2817
	                if (! isset($addedDirs[$thash])) {
2818
	                    $addedDirs[$thash] = true;
2819
	                    $result['added'][] = $dir;
2820
	                }
2821
	            } else {
2822
	                $result['error'] = $this->error(self::ERROR_UPLOAD, self::ERROR_TRGDIR_NOT_FOUND, 'hash@'.$thash);
2823
	                break;
2824
	            }
2825
	        } else {
2826
	            $_target = $target;
2827
				// file rename for backup
2828
				if (isset($renames[$name])) {
2829
				    $dir = $volume->realpath($_target);
2830
				    if (isset($hashes[$name])) {
2831
				        $hash = $hashes[$name];
2832
				    } else {
2833
				        $hash = $volume->getHash($dir, $name);
2834
				    }
2835
				    $rnres = $this->rename(['target' => $hash, 'name' => $volume->uniqueName($dir, $name, $suffix, true, 0)]);
2836
				    if (! empty($rnres['error'])) {
2837
				        $result['warning'] = $rnres['error'];
2838
				        break;
2839
				    }
2840
				}
2841
	        }
2842
	        if (! $_target || ($file = $volume->upload($fp, $_target, $name, $tmpname, $hashes)) === false) {
2843
	            $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, $volume->error());
2844
	            fclose($fp);
2845 View Code Duplication
	            if (! is_uploaded_file($tmpname)) {
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...
2846
	                if (unlink($tmpname)) {
2847
	                    unset($GLOBALS['elFinderTempFiles'][$tmpname]);
2848
	                }
2849
	                continue;
2850
	            }
2851
	            break;
2852
	        }
2853
2854
	        is_resource($fp) && fclose($fp);
2855
	        if (! is_uploaded_file($tmpname)) {
2856
	            clearstatcache();
2857
	            if (! is_file($tmpname) || unlink($tmpname)) {
2858
	                unset($GLOBALS['elFinderTempFiles'][$tmpname]);
2859
	            }
2860
	        }
2861
	        $result['added'][] = $file;
2862
	        if ($rnres) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rnres 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...
2863
	            $result = array_merge_recursive($result, $rnres);
2864
	        }
2865
	    }
2866
	    if ($GLOBALS['elFinderTempFiles']) {
2867
	        foreach (array_keys($GLOBALS['elFinderTempFiles']) as $_temp) {
2868
	            unlink($_temp);
2869
	        }
2870
	    }
2871
	    $result['removed'] = $volume->removed();
2872
2873 View Code Duplication
	    if (! empty($args['node'])) {
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...
2874
	        $result['callback'] = [
2875
				'node' => $args['node'],
2876
				'bind' => 'upload',
2877
			];
2878
	    }
2879
2880
	    return $result;
2881
	}
2882
2883
	/**
2884
	 * Copy/move files into new destination.
2885
	 *
2886
	 * @param  array  command arguments
2887
	 * @return array
2888
	 * @author Dmitry (dio) Levashov
2889
	 **/
2890
	protected function paste($args)
2891
	{
2892
	    $dst = $args['dst'];
2893
	    $targets = is_array($args['targets']) ? $args['targets'] : [];
2894
	    $cut = ! empty($args['cut']);
2895
	    $error = $cut ? self::ERROR_MOVE : self::ERROR_COPY;
2896
	    $result = ['changed' => [], 'added' => [], 'removed' => [], 'warning' => []];
2897
2898
	    if (($dstVolume = $this->volume($dst)) == false) {
2899
	        return ['error' => $this->error($error, '#'.$targets[0], self::ERROR_TRGDIR_NOT_FOUND, '#'.$dst)];
2900
	    }
2901
2902
	    $this->itemLock($dst);
2903
2904
	    $hashes = $renames = [];
2905
	    $suffix = '~';
2906
	    if (! empty($args['renames'])) {
2907
	        $renames = array_flip($args['renames']);
2908 View Code Duplication
	        if (is_string($args['suffix']) && ! preg_match('/[\/\\?*:|"<>]/', $args['suffix'])) {
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...
2909
	            $suffix = $args['suffix'];
2910
	        }
2911
	    }
2912
	    if (! empty($args['hashes'])) {
2913
	        $hashes = array_flip($args['hashes']);
2914
	    }
2915
2916
	    foreach ($targets as $target) {
2917
	        self::extendTimeLimit();
2918
2919
	        if (($srcVolume = $this->volume($target)) == false) {
2920
	            $result['warning'] = array_merge($result['warning'], $this->error($error, '#'.$target, self::ERROR_FILE_NOT_FOUND));
2921
	            continue;
2922
	        }
2923
2924
	        $rnres = [];
2925
	        if ($renames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $renames 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...
2926
	            $file = $srcVolume->file($target);
2927
	            if (isset($renames[$file['name']])) {
2928
	                $dir = $dstVolume->realpath($dst);
2929
	                if (isset($hashes[$file['name']])) {
2930
	                    $hash = $hashes[$file['name']];
2931
	                } else {
2932
	                    $hash = $dstVolume->getHash($dir, $file['name']);
2933
	                }
2934
	                $rnres = $this->rename(['target' => $hash, 'name' => $dstVolume->uniqueName($dir, $file['name'], $suffix, true, 0)]);
2935
	                if (! empty($rnres['error'])) {
2936
	                    $result['warning'] = array_merge($result['warning'], $rnres['error']);
2937
	                    continue;
2938
	                }
2939
	            }
2940
	        }
2941
2942
	        if ($cut && $this->itemLocked($target)) {
2943
	            $rm = $srcVolume->file($target);
2944
	            $result['warning'] = array_merge($result['warning'], $this->error(self::ERROR_LOCKED, $rm['name']));
2945
	            continue;
2946
	        }
2947
2948
	        if (($file = $dstVolume->paste($srcVolume, $target, $dst, $cut, $hashes)) == false) {
2949
	            $result['warning'] = array_merge($result['warning'], $this->error($dstVolume->error()));
2950
	            continue;
2951
	        }
2952
2953
	        $dirChange = ! empty($file['dirChange']);
2954
	        unset($file['dirChange']);
2955
	        if ($dirChange) {
2956
	            $result['changed'][] = $file;
2957
	        } else {
2958
	            $result['added'][] = $file;
2959
	        }
2960
	        if ($rnres) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rnres 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...
2961
	            $result = array_merge_recursive($result, $rnres);
2962
	        }
2963
	    }
2964
	    if (count($result['warning']) < 1) {
2965
	        unset($result['warning']);
2966
	    }
2967
2968
	    return $result;
2969
	}
2970
2971
	/**
2972
	 * Return file content.
2973
	 *
2974
	 * @param  array  $args  command arguments
2975
	 * @return array
2976
	 * @author Dmitry (dio) Levashov
2977
	 **/
2978
	protected function get($args)
2979
	{
2980
	    $target = $args['target'];
2981
	    $volume = $this->volume($target);
2982
	    $enc = false;
2983
2984
	    if (! $volume || ($file = $volume->file($target)) == false) {
2985
	        return ['error' => $this->error(self::ERROR_OPEN, '#'.$target, self::ERROR_FILE_NOT_FOUND)];
2986
	    }
2987
2988 View Code Duplication
	    if (($content = $volume->getContents($target)) === 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...
2989
	        return ['error' => $this->error(self::ERROR_OPEN, $volume->path($target), $volume->error())];
2990
	    }
2991
2992
	    $mime = isset($file['mime']) ? $file['mime'] : '';
2993
	    if ($mime && strtolower(substr($mime, 0, 4)) === 'text') {
2994
	        $enc = '';
2995
	        if ($content !== '') {
2996
	            if (! $args['conv'] || $args['conv'] == '1') {
2997
	                // detect encoding
2998
					if (function_exists('mb_detect_encoding')) {
2999
					    if ($enc = mb_detect_encoding($content, mb_detect_order(), true)) {
3000
					        $encu = strtoupper($enc);
3001
					        if ($encu === 'UTF-8' || $encu === 'ASCII') {
3002
					            $enc = '';
3003
					        }
3004
					    } else {
3005
					        $enc = 'unknown';
3006
					    }
3007
					} elseif (! preg_match('//u', $content)) {
3008
					    $enc = 'unknown';
3009
					}
3010
	                if ($enc === 'unknown') {
3011
	                    $enc = $volume->getOption('encoding');
3012
	                    if (! $enc || strtoupper($enc) === 'UTF-8') {
3013
	                        $enc = 'unknown';
3014
	                    }
3015
	                }
3016
	                if ($enc && $enc !== 'unknown') {
3017
	                    $utf8 = iconv($enc, 'UTF-8', $content);
3018
	                    if ($utf8 === false && function_exists('mb_convert_encoding')) {
3019
	                        $utf8 = mb_convert_encoding($content, 'UTF-8', $enc);
3020
	                        if (mb_convert_encoding($utf8, $enc, 'UTF-8') !== $content) {
3021
	                            $enc = 'unknown';
3022
	                        }
3023
	                    } else {
3024
	                        if ($utf8 === false || iconv('UTF-8', $enc, $utf8) !== $content) {
3025
	                            $enc = 'unknown';
3026
	                        }
3027
	                    }
3028
	                    if ($enc !== 'unknown') {
3029
	                        $content = $utf8;
3030
	                    }
3031
	                }
3032
	                if ($enc) {
3033
	                    if ($args['conv'] == '1') {
3034
	                        $args['conv'] = '';
3035
	                        if ($enc === 'unknown') {
3036
	                            $content = false;
3037
	                        }
3038
	                    } elseif ($enc === 'unknown') {
3039
	                        return ['doconv' => $enc];
3040
	                    }
3041
	                }
3042
	            }
3043
	            if ($args['conv']) {
3044
	                $enc = $args['conv'];
3045
	                if (strtoupper($enc) !== 'UTF-8') {
3046
	                    $_content = $content;
3047
	                    $content = iconv($enc, 'UTF-8', $content);
3048
	                    if ($content === false && function_exists('mb_convert_encoding')) {
3049
	                        $content = mb_convert_encoding($_content, 'UTF-8', $enc);
3050
	                    }
3051
	                } else {
3052
	                    $enc = '';
3053
	                }
3054
	            }
3055
	        }
3056
	    } else {
3057
	        $content = 'data:'.($mime ? $mime : 'application/octet-stream').';base64,'.base64_encode($content);
3058
	    }
3059
3060
	    if ($enc !== false) {
3061
	        if ($content !== false) {
3062
	            $json = json_encode($content);
3063
	        }
3064
	        if ($content === false || $json === false || strlen($json) < strlen($content)) {
0 ignored issues
show
Bug introduced by
The variable $json 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...
3065
	            return ['error' => $this->error(self::ERROR_CONV_UTF8, self::ERROR_NOT_UTF8_CONTENT, $volume->path($target))];
3066
	        }
3067
	    }
3068
3069
	    $res = ['content' => $content];
3070
	    if ($enc) {
3071
	        $res['encoding'] = $enc;
3072
	    }
3073
3074
	    return $res;
3075
	}
3076
3077
	/**
3078
	 * Save content into text file.
3079
	 *
3080
	 * @param $args
3081
	 * @return array
3082
	 * @author Dmitry (dio) Levashov
3083
	 */
3084
	protected function put($args)
3085
	{
3086
	    $target = $args['target'];
3087
3088 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
3089
		|| ($file = $volume->file($target)) == false) {
3090
	        return ['error' => $this->error(self::ERROR_SAVE, '#'.$target, self::ERROR_FILE_NOT_FOUND)];
3091
	    }
3092
3093
	    $this->itemLock($target);
3094
3095
	    if (preg_match('~^https?://~i', $args['content'])) {
3096
	        $fp = $this->get_remote_contents($args['content'], 30, 5, 'Mozilla/5.0', tmpfile());
3097
	        if (! $fp) {
3098
	            return  ['error' => self::ERROR_SAVE, $args['content'], self::ERROR_FILE_NOT_FOUND];
3099
	        }
3100
	        $fmeta = stream_get_meta_data($fp);
3101
	        $mime = $this->detectMimeType($fmeta['uri']);
3102
	        $args['content'] = 'data:'.$mime.';base64,'.base64_encode(file_get_contents($fmeta['uri']));
3103
	    } elseif (! empty($args['encoding'])) {
3104
	        $content = iconv('UTF-8', $args['encoding'], $args['content']);
3105
	        if ($content === false && function_exists('mb_detect_encoding')) {
3106
	            $content = mb_convert_encoding($args['content'], $args['encoding'], 'UTF-8');
3107
	        }
3108
	        if ($content !== false) {
3109
	            $args['content'] = $content;
3110
	        }
3111
	    }
3112 View Code Duplication
	    if (($file = $volume->putContents($target, $args['content'])) == 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...
3113
	        return ['error' => $this->error(self::ERROR_SAVE, $volume->path($target), $volume->error())];
3114
	    }
3115
3116
	    return ['changed' => [$file]];
3117
	}
3118
3119
	/**
3120
	 * Extract files from archive.
3121
	 *
3122
	 * @param  array  $args  command arguments
3123
	 * @return array
3124
	 * @author Dmitry (dio) Levashov,
3125
	 * @author Alexey Sukhotin
3126
	 **/
3127
	protected function extract($args)
3128
	{
3129
	    $target = $args['target'];
3130
	    $mimes = ! empty($args['mimes']) && is_array($args['mimes']) ? $args['mimes'] : [];
0 ignored issues
show
Unused Code introduced by
$mimes 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...
3131
	    $error = [self::ERROR_EXTRACT, '#'.$target];
0 ignored issues
show
Unused Code introduced by
$error 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...
3132
	    $makedir = isset($args['makedir']) ? (bool) $args['makedir'] : null;
3133
3134 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
3135
		|| ($file = $volume->file($target)) == false) {
3136
	        return ['error' => $this->error(self::ERROR_EXTRACT, '#'.$target, self::ERROR_FILE_NOT_FOUND)];
3137
	    }
3138
3139
	    $res = [];
3140
	    if ($file = $volume->extract($target, $makedir)) {
3141
	        $res['added'] = isset($file['read']) ? [$file] : $file;
3142
	        if ($err = $volume->error()) {
3143
	            $res['warning'] = $err;
3144
	        }
3145
	    } else {
3146
	        $res['error'] = $this->error(self::ERROR_EXTRACT, $volume->path($target), $volume->error());
3147
	    }
3148
3149
	    return $res;
3150
	}
3151
3152
	/**
3153
	 * Create archive.
3154
	 *
3155
	 * @param  array  $args  command arguments
3156
	 * @return array
3157
	 * @author Dmitry (dio) Levashov,
3158
	 * @author Alexey Sukhotin
3159
	 **/
3160
	protected function archive($args)
3161
	{
3162
	    $type = $args['type'];
0 ignored issues
show
Unused Code introduced by
$type 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...
3163
	    $targets = isset($args['targets']) && is_array($args['targets']) ? $args['targets'] : [];
3164
	    $name = isset($args['name']) ? $args['name'] : '';
3165
3166 View Code Duplication
	    if (($volume = $this->volume($targets[0])) == 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...
3167
	        return $this->error(self::ERROR_ARCHIVE, self::ERROR_TRGDIR_NOT_FOUND);
3168
	    }
3169
3170
	    foreach ($targets as $target) {
3171
	        $this->itemLock($target);
3172
	    }
3173
3174
	    return ($file = $volume->archive($targets, $args['type'], $name))
3175
			? ['added' => [$file]]
3176
			: ['error' => $this->error(self::ERROR_ARCHIVE, $volume->error())];
3177
	}
3178
3179
	/**
3180
	 * Search files.
3181
	 *
3182
	 * @param  array  $args  command arguments
3183
	 * @return array
3184
	 * @author Dmitry Levashov
3185
	 **/
3186
	protected function search($args)
3187
	{
3188
	    $q = trim($args['q']);
3189
	    $mimes = ! empty($args['mimes']) && is_array($args['mimes']) ? $args['mimes'] : [];
3190
	    $target = ! empty($args['target']) ? $args['target'] : null;
3191
	    $result = [];
3192
	    $errors = [];
3193
3194
	    if ($target) {
3195 View Code Duplication
	        if ($volume = $this->volume($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...
3196
	            $result = $volume->search($q, $mimes, $target);
3197
	            $errors = array_merge($errors, $volume->error());
3198
	        }
3199 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...
3200
	        foreach ($this->volumes as $volume) {
3201
	            $result = array_merge($result, $volume->search($q, $mimes));
3202
	            $errors = array_merge($errors, $volume->error());
3203
	        }
3204
	    }
3205
3206
	    $result = ['files' => $result];
3207
	    if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors 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...
3208
	        $result['warning'] = $errors;
3209
	    }
3210
3211
	    return $result;
3212
	}
3213
3214
	/**
3215
	 * Return file info (used by client "places" ui).
3216
	 *
3217
	 * @param  array  $args  command arguments
3218
	 * @return array
3219
	 * @author Dmitry Levashov
3220
	 **/
3221
	protected function info($args)
3222
	{
3223
	    $files = [];
3224
	    $sleep = 0;
0 ignored issues
show
Unused Code introduced by
$sleep 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...
3225
	    $compare = null;
3226
		// long polling mode
3227
		if ($args['compare'] && count($args['targets']) === 1) {
3228
		    $compare = intval($args['compare']);
3229
		    $hash = $args['targets'][0];
3230
		    if ($volume = $this->volume($hash)) {
3231
		        $standby = (int) $volume->getOption('plStandby');
3232
		        $_compare = false;
3233
		        if (($syncCheckFunc = $volume->getOption('syncCheckFunc')) && is_callable($syncCheckFunc)) {
3234
		            $_compare = call_user_func_array($syncCheckFunc, [$volume->realpath($hash), $standby, $compare, $volume, $this]);
3235
		        }
3236
		        if ($_compare !== false) {
3237
		            $compare = $_compare;
3238
		        } else {
3239
		            $sleep = max(1, (int) $volume->getOption('tsPlSleep'));
3240
		            $limit = max(1, $standby / $sleep) + 1;
3241
		            do {
3242
		                self::extendTimeLimit(30 + $sleep);
3243
		                $volume->clearstatcache();
3244
		                if (($info = $volume->file($hash)) != false) {
3245
		                    if ($info['ts'] != $compare) {
3246
		                        $compare = $info['ts'];
3247
		                        break;
3248
		                    }
3249
		                } else {
3250
		                    $compare = 0;
3251
		                    break;
3252
		                }
3253
		                if (--$limit) {
3254
		                    sleep($sleep);
3255
		                }
3256
		            } while ($limit);
3257
		        }
3258
		    }
3259
		} else {
3260
		    foreach ($args['targets'] as $hash) {
3261
		        if (($volume = $this->volume($hash)) != false
3262
				&& ($info = $volume->file($hash)) != false) {
3263
		            $info['path'] = $volume->path($hash);
3264
		            $files[] = $info;
3265
		        }
3266
		    }
3267
		}
3268
3269
	    $result = ['files' => $files];
3270
	    if (! is_null($compare)) {
3271
	        $result['compare'] = strval($compare);
3272
	    }
3273
3274
	    return $result;
3275
	}
3276
3277
	/**
3278
	 * Return image dimensions.
3279
	 *
3280
	 * @param  array  $args  command arguments
3281
	 * @return array
3282
	 * @author Dmitry (dio) Levashov
3283
	 **/
3284
	protected function dim($args)
3285
	{
3286
	    $target = $args['target'];
3287
3288
	    if (($volume = $this->volume($target)) != false) {
3289
	        $dim = $volume->dimensions($target);
3290
3291
	        return $dim ? ['dim' => $dim] : [];
3292
	    }
3293
3294
	    return [];
3295
	}
3296
3297
	/**
3298
	 * Resize image.
3299
	 *
3300
	 * @param  array  command arguments
3301
	 * @return array
3302
	 * @author Dmitry (dio) Levashov
3303
	 * @author Alexey Sukhotin
3304
	 **/
3305
	protected function resize($args)
3306
	{
3307
	    $target = $args['target'];
3308
	    $width = (int) $args['width'];
3309
	    $height = (int) $args['height'];
3310
	    $x = (int) $args['x'];
3311
	    $y = (int) $args['y'];
3312
	    $mode = $args['mode'];
3313
	    $bg = $args['bg'];
3314
	    $degree = (int) $args['degree'];
3315
	    $quality = (int) $args['quality'];
3316
3317 View Code Duplication
	    if (($volume = $this->volume($target)) == 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...
3318
		|| ($file = $volume->file($target)) == false) {
3319
	        return ['error' => $this->error(self::ERROR_RESIZE, '#'.$target, self::ERROR_FILE_NOT_FOUND)];
3320
	    }
3321
3322
	    if ($mode !== 'rotate' && ($width < 1 || $height < 1)) {
3323
	        return ['error' => $this->error(self::ERROR_RESIZESIZE)];
3324
	    }
3325
3326
	    return ($file = $volume->resize($target, $width, $height, $x, $y, $mode, $bg, $degree, $quality))
3327
			? ['changed' => [$file]]
3328
			: ['error' => $this->error(self::ERROR_RESIZE, $volume->path($target), $volume->error())];
3329
	}
3330
3331
	/**
3332
	 * Return content URL.
3333
	 *
3334
	 * @param  array  $args  command arguments
3335
	 * @return array
3336
	 * @author Naoki Sawada
3337
	 **/
3338
	protected function url($args)
3339
	{
3340
	    $target = $args['target'];
3341
	    $options = isset($args['options']) ? $args['options'] : [];
3342
	    if (($volume = $this->volume($target)) != false) {
3343
	        if (! $volume->commandDisabled('url')) {
3344
	            $url = $volume->getContentUrl($target, $options);
3345
3346
	            return $url ? ['url' => $url] : [];
3347
	        }
3348
	    }
3349
3350
	    return [];
3351
	}
3352
3353
	/**
3354
	 * Output callback result with JavaScript that control elFinder
3355
	 * or HTTP redirect to callbackWindowURL.
3356
	 *
3357
	 * @param  array  command arguments
3358
	 * @author Naoki Sawada
3359
	 */
3360
	protected function callback($args)
3361
	{
3362
	    $checkReg = '/[^a-zA-Z0-9;._-]/';
3363
	    $node = (isset($args['node']) && ! preg_match($checkReg, $args['node'])) ? $args['node'] : '';
3364
	    $json = (isset($args['json']) && json_decode($args['json'])) ? $args['json'] : '{}';
3365
	    $bind = (isset($args['bind']) && ! preg_match($checkReg, $args['bind'])) ? $args['bind'] : '';
3366
	    $done = (! empty($args['done']));
3367
3368
	    while (ob_get_level()) {
3369
	        if (! ob_end_clean()) {
3370
	            break;
3371
	        }
3372
	    }
3373
3374
	    if ($done || ! $this->callbackWindowURL) {
3375
	        $script = '';
3376
	        if ($node) {
3377
	            if ($bind) {
3378
	                $trigger = 'elf.trigger(\''.$bind.'\', data);';
3379
	                $triggerdone = 'elf.trigger(\''.$bind.'done\');';
3380
	                $triggerfail = 'elf.trigger(\''.$bind.'fail\', data);';
3381
	            } else {
3382
	                $trigger = $triggerdone = $triggerfail = '';
3383
	            }
3384
	            $script .= '
3385
					var w = window.opener || window.parent || window;
3386
					try {
3387
						var elf = w.document.getElementById(\''.$node.'\').elfinder;
3388
						if (elf) {
3389
							var data = '.$json.';
3390
							if (data.error) {
3391
								'.$triggerfail.'
3392
								elf.error(data.error);
3393
							} else {
3394
								data.warning && elf.error(data.warning);
3395
								data.removed && data.removed.length && elf.remove(data);
3396
								data.added   && data.added.length   && elf.add(data);
3397
								data.changed && data.changed.length && elf.change(data);
3398
								'.$trigger.'
3399
								'.$triggerdone.'
3400
								data.sync && elf.sync();
3401
							}
3402
						}
3403
					} catch(e) {
3404
						// for CORS
3405
						w.postMessage && w.postMessage(JSON.stringify({bind:\''.$bind.'\',data:'.$json.'}), \'*\');
3406
					}';
3407
	        }
3408
	        $script .= 'window.close();';
3409
3410
	        $out = '<!DOCTYPE html><html><head><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><script>'.$script.'</script></head><body><a href="#" onlick="window.close();return false;">Close this window</a></body></html>';
3411
3412
	        header('Content-Type: text/html; charset=utf-8');
3413
	        header('Content-Length: '.strlen($out));
3414
	        header('Cache-Control: private');
3415
	        header('Pragma: no-cache');
3416
3417
	        echo $out;
3418
	    } else {
3419
	        $url = $this->callbackWindowURL;
3420
	        $url .= ((strpos($url, '?') === false) ? '?' : '&')
3421
				 .'&node='.rawurlencode($node)
3422
				 .(($json !== '{}') ? ('&json='.rawurlencode($json)) : '')
3423
				 .($bind ? ('&bind='.rawurlencode($bind)) : '')
3424
				 .'&done=1';
3425
3426
	        header('Location: '.$url);
3427
	    }
3428
	    exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method callback() 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...
3429
	}
3430
3431
	/***************************************************************************/
3432
	/*                                   utils                                 */
3433
	/***************************************************************************/
3434
3435
	/**
3436
	 * Return root - file's owner.
3437
	 *
3438
	 * @param  string  file hash
3439
	 * @return elFinderStorageDriver
3440
	 * @author Dmitry (dio) Levashov
3441
	 **/
3442
	protected function volume($hash)
3443
	{
3444
	    foreach ($this->volumes as $id => $v) {
3445
	        if (strpos(''.$hash, $id) === 0) {
3446
	            return $this->volumes[$id];
3447
	        }
3448
	    }
3449
3450
	    return false;
3451
	}
3452
3453
	/**
3454
	 * Return files info array.
3455
	 *
3456
	 * @param  array  $data  one file info or files info
3457
	 * @return array
3458
	 * @author Dmitry (dio) Levashov
3459
	 **/
3460
	protected function toArray($data)
3461
	{
3462
	    return isset($data['hash']) || ! is_array($data) ? [$data] : $data;
3463
	}
3464
3465
	/**
3466
	 * Return fils hashes list.
3467
	 *
3468
	 * @param  array  $files  files info
3469
	 * @return array
3470
	 * @author Dmitry (dio) Levashov
3471
	 **/
3472
	protected function hashes($files)
3473
	{
3474
	    $ret = [];
3475
	    foreach ($files as $file) {
3476
	        $ret[] = $file['hash'];
3477
	    }
3478
3479
	    return $ret;
3480
	}
3481
3482
	/**
3483
	 * Remove from files list hidden files and files with required mime types.
3484
	 *
3485
	 * @param  array  $files  files info
3486
	 * @return array
3487
	 * @author Dmitry (dio) Levashov
3488
	 **/
3489
	protected function filter($files)
3490
	{
3491
	    $exists = [];
3492
	    foreach ($files as $i => $file) {
3493
	        if (isset($exists[$file['hash']]) || ! empty($file['hidden']) || ! $this->default->mimeAccepted($file['mime'])) {
3494
	            unset($files[$i]);
3495
	        }
3496
	        $exists[$file['hash']] = true;
3497
	    }
3498
3499
	    return array_values($files);
3500
	}
3501
3502
    protected function utime()
3503
    {
3504
        $time = explode(' ', microtime());
3505
3506
        return (float) $time[1] + (float) $time[0];
3507
    }
3508
3509
	/**
3510
	 * Return Network mount volume unique ID.
3511
	 *
3512
	 * @param  array   $netVolumes  Saved netvolumes array
3513
	 * @param  string  $prefix      Id prefix
3514
	 * @return string|false
3515
	 * @author Naoki Sawada
3516
	 **/
3517
	protected function getNetVolumeUniqueId($netVolumes = null, $prefix = 'nm')
3518
	{
3519
	    $id = false;
0 ignored issues
show
Unused Code introduced by
$id 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...
3520
	    if (is_null($netVolumes)) {
3521
	        $netVolumes = $this->getNetVolumes();
3522
	    }
3523
	    $ids = [];
3524
	    foreach ($netVolumes as $vOps) {
3525
	        if (isset($vOps['id']) && strpos($vOps['id'], $prefix) === 0) {
3526
	            $ids[$vOps['id']] = true;
3527
	        }
3528
	    }
3529
	    if (! $ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids 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...
3530
	        $id = $prefix.'1';
3531
	    } else {
3532
	        $i = 0;
3533
	        while (isset($ids[$prefix.++$i]) && $i < 10000);
3534
	        $id = $prefix.$i;
3535
	        if (isset($ids[$id])) {
3536
	            $id = false;
3537
	        }
3538
	    }
3539
3540
	    return $id;
3541
	}
3542
3543
	/**
3544
	 * Is item locked?
3545
	 *
3546
	 * @param string $hash
3547
	 * @return bool
3548
	 */
3549
	protected function itemLocked($hash)
3550
	{
3551
	    if (! self::$commonTempPath) {
3552
	        return false;
3553
	    }
3554
	    $lock = self::$commonTempPath.DIRECTORY_SEPARATOR.$hash.'.lock';
3555
	    if (file_exists($lock)) {
3556
	        if (filemtime($lock) + $this->itemLockExpire < time()) {
3557
	            unlink($lock);
3558
3559
	            return false;
3560
	        }
3561
3562
	        return true;
3563
	    }
3564
3565
	    return false;
3566
	}
3567
3568
	/**
3569
	 * Do lock target item.
3570
	 *
3571
	 * @param array|string $hashes
3572
	 * @param bool $autoUnlock
3573
	 * @return bool
3574
	 */
3575
	protected function itemLock($hashes, $autoUnlock = true)
3576
	{
3577
	    if (! self::$commonTempPath) {
3578
	        return false;
3579
	    }
3580
	    if (! is_array($hashes)) {
3581
	        $hashes = [$hashes];
3582
	    }
3583
	    foreach ($hashes as $hash) {
3584
	        $lock = self::$commonTempPath.DIRECTORY_SEPARATOR.$hash.'.lock';
3585
	        if ($this->itemLocked($hash)) {
3586
	            $cnt = file_get_contents($lock) + 1;
3587
	        } else {
3588
	            $cnt = 1;
3589
	        }
3590
	        if (file_put_contents($lock, $cnt, LOCK_EX)) {
3591
	            if ($autoUnlock) {
3592
	                $this->autoUnlocks[] = $hash;
3593
	            }
3594
	        }
3595
	    }
3596
	}
3597
3598
	/**
3599
	 * Do unlock target item.
3600
	 *
3601
	 * @param string $hash
3602
	 * @return bool
3603
	 */
3604
	protected function itemUnlock($hash)
3605
	{
3606
	    if (! $this->itemLocked($hash)) {
3607
	        return true;
3608
	    }
3609
	    $lock = self::$commonTempPath.DIRECTORY_SEPARATOR.$hash.'.lock';
3610
	    $cnt = file_get_contents($lock);
3611
	    if (--$cnt < 1) {
3612
	        unlink($lock);
3613
	    } else {
3614
	        file_put_contents($lock, $cnt, LOCK_EX);
3615
	    }
3616
	}
3617
3618
	/**
3619
	 * Ensure directories recursively.
3620
	 *
3621
	 * @param  object  $volume  Volume object
3622
	 * @param  string  $target  Target hash
3623
	 * @param  string  $dirs    Array of directory tree to ensure
3624
	 * @param  string  $path    Relative path form target hash
3625
	 * @return array|false      array('stats' => array([stat of maked directory]), 'hashes' => array('[path]' => '[hash]'), 'makes' => array([New directory hashes]), 'error' => array([Error name]))
3626
	 * @author Naoki Sawada
3627
	 **/
3628
	protected function ensureDirsRecursively($volume, $target, $dirs, $path = '')
3629
	{
3630
	    $res = ['stats' => [], 'hashes' => [], 'makes' => [], 'error' => []];
3631
	    foreach ($dirs as $name => $sub) {
0 ignored issues
show
Bug introduced by
The expression $dirs of type string is not traversable.
Loading history...
3632
	        $name = (string) $name;
3633
	        $newDir = null;
3634
	        if ((($parent = $volume->realpath($target)) && ($dir = $volume->dir($volume->getHash($parent, $name)))) || ($newDir = $volume->mkdir($target, $name))) {
3635
	            $_path = $path.'/'.$name;
3636
	            if ($newDir) {
3637
	                $res['makes'][] = $newDir['hash'];
3638
	                $dir = $newDir;
3639
	            }
3640
	            $res['stats'][] = $dir;
3641
	            $res['hashes'][$_path] = $dir['hash'];
3642
	            if (count($sub)) {
3643
	                $res = array_merge_recursive($res, $this->ensureDirsRecursively($volume, $dir['hash'], $sub, $_path));
3644
	                if ($res['error']) {
3645
	                    break;
3646
	                }
3647
	            }
3648
	        } else {
3649
	            $res['error'][] = $name;
3650
	        }
3651
	    }
3652
3653
	    return $res;
3654
	}
3655
3656
    private function session_expires()
3657
    {
3658
        if (! $last = $this->session->get(':LAST_ACTIVITY')) {
3659
            $this->session->set(':LAST_ACTIVITY', time());
3660
3661
            return false;
3662
        }
3663
3664
        if (($this->timeout > 0) && (time() - $last > $this->timeout)) {
3665
            return true;
3666
        }
3667
3668
        $this->session->set(':LAST_ACTIVITY', time());
3669
3670
        return false;
3671
    }
3672
3673
	/**
3674
	 * Get temporary directory path.
3675
	 *
3676
	 * @param  string $volumeTempPath
3677
	 * @return string
3678
	 * @author Naoki Sawada
3679
	 */
3680
	private function getTempDir($volumeTempPath = null)
3681
	{
3682
	    $testDirs = [];
3683
	    if ($this->uploadTempPath) {
3684
	        $testDirs[] = rtrim(realpath($this->uploadTempPath), DIRECTORY_SEPARATOR);
3685
	    }
3686
	    if ($volumeTempPath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $volumeTempPath of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
3687
	        $testDirs[] = rtrim(realpath($volumeTempPath), DIRECTORY_SEPARATOR);
3688
	    }
3689
	    if (function_exists('sys_get_temp_dir')) {
3690
	        $testDirs[] = sys_get_temp_dir();
3691
	    }
3692
	    $tempDir = '';
3693
	    foreach ($testDirs as $testDir) {
3694
	        if (! $testDir || ! is_dir($testDir)) {
3695
	            continue;
3696
	        }
3697
	        if (is_writable($testDir)) {
3698
	            $tempDir = $testDir;
3699
	            $gc = time() - 3600;
3700
	            foreach (glob($tempDir.DIRECTORY_SEPARATOR.'ELF*') as $cf) {
3701
	                if (filemtime($cf) < $gc) {
3702
	                    unlink($cf);
3703
	                }
3704
	            }
3705
	            break;
3706
	        }
3707
	    }
3708
3709
	    return $tempDir;
3710
	}
3711
3712
	/**
3713
	 * Check chunked upload files.
3714
	 *
3715
	 * @param string $tmpname uploaded temporary file path
3716
	 * @param string $chunk uploaded chunk file name
3717
	 * @param string $cid uploaded chunked file id
3718
	 * @param string $tempDir temporary dirctroy path
3719
	 * @param null $volume
3720
	 * @return array or (empty, empty)
3721
	 * @author Naoki Sawada
3722
	 */
3723
	private function checkChunkedFile($tmpname, $chunk, $cid, $tempDir, $volume = null)
0 ignored issues
show
Coding Style introduced by
checkChunkedFile uses the super-global variable $_POST 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...
3724
	{
3725
	    if (preg_match('/^(.+)(\.\d+_(\d+))\.part$/s', $chunk, $m)) {
3726
	        $fname = $m[1];
3727
	        $encname = md5($cid.'_'.$fname);
3728
	        $base = $tempDir.DIRECTORY_SEPARATOR.'ELF'.$encname;
3729
	        $clast = intval($m[3]);
3730
	        if (is_null($tmpname)) {
3731
	            ignore_user_abort(true);
3732
	            sleep(10); // wait 10 sec
3733
				// chunked file upload fail
3734
				foreach (glob($base.'*') as $cf) {
3735
				    unlink($cf);
3736
				}
3737
	            ignore_user_abort(false);
3738
3739
	            return;
3740
	        }
3741
3742
	        $range = isset($_POST['range']) ? trim($_POST['range']) : '';
3743
	        if ($range && preg_match('/^(\d+),(\d+),(\d+)$/', $range, $ranges)) {
3744
	            $start = $ranges[1];
3745
	            $len = $ranges[2];
3746
	            $size = $ranges[3];
3747
	            $tmp = $base.'.part';
3748
	            $csize = filesize($tmpname);
3749
3750
	            $tmpExists = is_file($tmp);
3751
	            if (! $tmpExists) {
3752
	                // check upload max size
3753
					$uploadMaxSize = $volume->getUploadMaxSize();
0 ignored issues
show
Bug introduced by
The method getUploadMaxSize cannot be called on $volume (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
3754
	                if ($uploadMaxSize > 0 && $size > $uploadMaxSize) {
3755
	                    return [self::ERROR_UPLOAD_FILE_SIZE, false];
3756
	                }
3757
					// make temp file
3758
					$ok = false;
3759
	                if ($fp = fopen($tmp, 'wb')) {
3760
	                    flock($fp, LOCK_EX);
3761
	                    $ok = ftruncate($fp, $size);
3762
	                    flock($fp, LOCK_UN);
3763
	                    fclose($fp);
3764
	                    touch($base);
3765
	                }
3766
	                if (! $ok) {
3767
	                    unlink($tmp);
3768
3769
	                    return [self::ERROR_UPLOAD_TEMP, false];
3770
	                }
3771
	            } else {
3772
	                // wait until makeing temp file (for anothor session)
3773
					$cnt = 1200; // Time limit 120 sec
3774
					while (! is_file($base) && --$cnt) {
3775
					    usleep(100000); // wait 100ms
3776
					}
3777
	                if (! $cnt) {
3778
	                    return [self::ERROR_UPLOAD_TEMP, false];
3779
	                }
3780
	            }
3781
3782
				// check size info
3783
				if ($len != $csize || $start + $len > $size || ($tmpExists && $size != filesize($tmp))) {
3784
				    return [self::ERROR_UPLOAD_TEMP, false];
3785
				}
3786
3787
				// write chunk data
3788
				$writelen = 0;
0 ignored issues
show
Unused Code introduced by
$writelen 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...
3789
	            $src = fopen($tmpname, 'rb');
3790
	            $fp = fopen($tmp, 'cb');
3791
	            fseek($fp, $start);
3792
	            $writelen = stream_copy_to_stream($src, $fp, $len);
3793
	            fclose($fp);
3794
	            fclose($src);
3795
	            if ($writelen != $len) {
3796
	                return [self::ERROR_UPLOAD_TEMP, false];
3797
	            }
3798
3799
				// write counts
3800
				file_put_contents($base, "\0", FILE_APPEND | LOCK_EX);
3801
3802
	            if (filesize($base) >= $clast + 1) {
3803
	                // Completion
3804
					unlink($base);
3805
3806
	                return [$tmp, $fname];
3807
	            }
3808
	        } else {
3809
	            // old way
3810
				$part = $base.$m[2];
3811
	            if (move_uploaded_file($tmpname, $part)) {
3812
	                chmod($part, 0600);
3813
	                if ($clast < count(glob($base.'*'))) {
3814
	                    $parts = [];
3815
	                    for ($i = 0; $i <= $clast; $i++) {
3816
	                        $name = $base.'.'.$i.'_'.$clast;
3817
	                        if (is_readable($name)) {
3818
	                            $parts[] = $name;
3819
	                        } else {
3820
	                            $parts = null;
3821
	                            break;
3822
	                        }
3823
	                    }
3824
	                    if ($parts) {
3825
	                        if (! is_file($base)) {
3826
	                            touch($base);
3827
	                            if ($resfile = tempnam($tempDir, 'ELF')) {
3828
	                                $target = fopen($resfile, 'wb');
3829
	                                foreach ($parts as $f) {
3830
	                                    $fp = fopen($f, 'rb');
3831
	                                    while (! feof($fp)) {
3832
	                                        fwrite($target, fread($fp, 8192));
3833
	                                    }
3834
	                                    fclose($fp);
3835
	                                    unlink($f);
3836
	                                }
3837
	                                fclose($target);
3838
	                                unlink($base);
3839
3840
	                                return [$resfile, $fname];
3841
	                            }
3842
	                            unlink($base);
3843
	                        }
3844
	                    }
3845
	                }
3846
	            }
3847
	        }
3848
	    }
3849
3850
	    return ['', ''];
3851
	}
3852
} // END class
3853