elFinder   D
last analyzed

Complexity

Total Complexity 811

Size/Duplication

Total Lines 3842
Duplicated Lines 4.45 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 171
loc 3842
rs 4.4102
c 0
b 0
f 0
wmc 811
lcom 1
cbo 2

78 Methods

Rating   Name   Duplication   Size   Complexity  
F __construct() 0 217 78
A getSession() 0 4 1
A loaded() 0 4 1
A version() 0 4 1
C bind() 0 34 8
A unbind() 0 14 4
A commandExists() 0 4 3
A getVolume() 0 4 1
A commandArgsList() 0 4 2
F exec() 24 194 63
A realpath() 0 8 2
A updateNetVolumeOption() 0 8 4
A error() 0 14 4
C phpErrorHandler() 8 31 7
A itemAutoUnlock() 0 9 3
D isAnimationGif() 0 43 10
A isSeekableStream() 0 6 1
A rewind() 0 4 2
A sessionDataEncode() 0 8 2
C sessionDataDecode() 0 32 8
A sessionWrite() 0 6 2
A getStaticVar() 0 4 2
A extendTimeLimit() 0 11 4
C getIniBytes() 0 23 7
B getConnectorUrl() 0 11 7
F getStreamByUrl() 0 86 22
D curlExec() 7 34 10
A getNetVolumes() 0 8 2
A saveNetVolumes() 0 4 1
B removeNetVolume() 0 19 6
B getPluginInstance() 0 21 5
F netmount() 6 89 23
F open() 6 107 27
A ls() 0 12 4
A tree() 0 11 3
A parents() 0 12 3
A tmb() 0 16 4
C zipdl() 12 78 13
D file() 21 76 19
C size() 0 36 11
C mkdir() 3 44 11
A mkfile() 3 13 3
B rename() 4 19 5
C duplicate() 5 27 7
B rm() 9 27 6
A subdirs() 3 13 4
A get_remote_contents() 0 6 4
A curl_get_contents() 0 23 3
F fsock_get_contents() 0 149 35
C parse_data_scheme() 0 23 7
D detectMimeType() 0 52 16
A detectFileExtension() 0 7 3
C chmod() 5 43 8
F upload() 21 290 89
F paste() 3 80 20
F get() 3 98 41
D put() 7 34 10
C extract() 4 24 9
B archive() 3 18 7
C search() 10 27 8
C info() 0 55 15
A dim() 0 12 3
C resize() 4 25 7
B url() 0 14 5
F callback() 0 70 16
A volume() 0 10 3
A toArray() 0 4 3
A hashes() 0 9 2
B filter() 0 12 5
A utime() 0 6 1
D getNetVolumeUniqueId() 0 25 9
A itemLocked() 0 18 4
C itemLock() 0 22 7
A itemUnlock() 0 13 3
C ensureDirsRecursively() 0 27 8
A session_expires() 0 16 4
D getTempDir() 0 31 10
D checkChunkedFile() 0 129 30

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

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
59% 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 ;-(
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
2636
        // 				foreach(array_keys($GLOBALS['elFinderTempFiles']) as $f){
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
2637
        // 					unlink($f);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
2638
        // 				}
2639
        // 			};
2640
        // 		} else {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
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