Ftp   F
last analyzed

Complexity

Total Complexity 79

Size/Duplication

Total Lines 560
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 99.49%

Importance

Changes 0
Metric Value
wmc 79
lcom 1
cbo 5
dl 0
loc 560
ccs 195
cts 196
cp 0.9949
rs 2.08
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getMimetype() 0 10 2
A getTimestamp() 0 6 2
A read() 0 12 2
A readStream() 0 14 2
A setVisibility() 0 10 3
A listDirectoryContents() 0 13 5
A write() 0 17 2
A update() 0 4 1
A updateStream() 0 4 1
A rename() 0 4 1
A delete() 0 4 1
A connect() 0 18 3
A setTransferMode() 0 6 1
A setSsl() 0 6 1
A setPassive() 0 4 1
A setIgnorePassiveAddress() 0 4 1
A setRecurseManually() 0 4 1
A setUtf8() 0 4 1
A setUtf8Mode() 0 11 3
A setConnectionPassiveMode() 0 12 4
A setConnectionRoot() 0 15 3
A login() 0 19 2
A disconnect() 0 8 2
A writeStream() 0 16 3
A deleteDir() 0 17 5
A createDir() 0 19 3
B getMetadata() 0 28 7
A isConnected() 0 12 4
A isPureFtpdServer() 0 6 1
A createActualDirectory() 0 17 5
A listDirectoryContentsRecursive() 0 15 4
A ftpRawlist() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like Ftp 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 Ftp, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace League\Flysystem\Adapter;
4
5
use ErrorException;
6
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
7
use League\Flysystem\AdapterInterface;
8
use League\Flysystem\Config;
9
use League\Flysystem\Util;
10
use League\Flysystem\Util\MimeType;
11
use RuntimeException;
12
13
class Ftp extends AbstractFtpAdapter
14
{
15
    use StreamedCopyTrait;
16
17
    /**
18
     * @var int
19
     */
20
    protected $transferMode = FTP_BINARY;
21
22
    /**
23
     * @var null|bool
24
     */
25
    protected $ignorePassiveAddress = null;
26
27
    /**
28
     * @var bool
29
     */
30
    protected $recurseManually = false;
31
32
    /**
33
     * @var bool
34
     */
35
    protected $utf8 = false;
36
37
    /**
38
     * @var array
39
     */
40
    protected $configurable = [
41
        'host',
42
        'port',
43
        'username',
44
        'password',
45
        'ssl',
46
        'timeout',
47
        'root',
48
        'permPrivate',
49
        'permPublic',
50
        'passive',
51
        'transferMode',
52
        'systemType',
53
        'ignorePassiveAddress',
54
        'recurseManually',
55
        'utf8',
56
        'enableTimestampsOnUnixListings',
57
    ];
58
59
    /**
60
     * @var bool
61
     */
62
    protected $isPureFtpd;
63
64
    /**
65
     * Set the transfer mode.
66
     *
67
     * @param int $mode
68
     *
69
     * @return $this
70
     */
71 3
    public function setTransferMode($mode)
72
    {
73 3
        $this->transferMode = $mode;
74
75 3
        return $this;
76
    }
77
78
    /**
79
     * Set if Ssl is enabled.
80
     *
81
     * @param bool $ssl
82
     *
83
     * @return $this
84
     */
85 117
    public function setSsl($ssl)
86
    {
87 117
        $this->ssl = (bool) $ssl;
88
89 117
        return $this;
90
    }
91
92
    /**
93
     * Set if passive mode should be used.
94
     *
95
     * @param bool $passive
96
     */
97 99
    public function setPassive($passive = true)
98
    {
99 99
        $this->passive = $passive;
100 99
    }
101
102
    /**
103
     * @param bool $ignorePassiveAddress
104
     */
105 3
    public function setIgnorePassiveAddress($ignorePassiveAddress)
106
    {
107 3
        $this->ignorePassiveAddress = $ignorePassiveAddress;
108 3
    }
109
110
    /**
111
     * @param bool $recurseManually
112
     */
113 84
    public function setRecurseManually($recurseManually)
114
    {
115 84
        $this->recurseManually = $recurseManually;
116 84
    }
117
118
    /**
119
     * @param bool $utf8
120
     */
121 6
    public function setUtf8($utf8)
122
    {
123 6
        $this->utf8 = (bool) $utf8;
124 6
    }
125
126
    /**
127
     * Connect to the FTP server.
128
     */
129 108
    public function connect()
130
    {
131 108
        if ($this->ssl) {
132 102
            $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
133
        } else {
134 6
            $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
135
        }
136
137 108
        if ( ! $this->connection) {
138 6
            throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort());
139
        }
140
141 102
        $this->login();
142 99
        $this->setUtf8Mode();
143 96
        $this->setConnectionPassiveMode();
144 93
        $this->setConnectionRoot();
145 90
        $this->isPureFtpd = $this->isPureFtpdServer();
146 90
    }
147
148
    /**
149
     * Set the connection to UTF-8 mode.
150
     */
151 99
    protected function setUtf8Mode()
152
    {
153 99
        if ($this->utf8) {
154 6
            $response = ftp_raw($this->connection, "OPTS UTF8 ON");
155 6
            if (substr($response[0], 0, 3) !== '200') {
156 3
                throw new RuntimeException(
157 3
                    'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort()
158
                );
159
            }
160
        }
161 96
    }
162
163
    /**
164
     * Set the connections to passive mode.
165
     *
166
     * @throws RuntimeException
167
     */
168 96
    protected function setConnectionPassiveMode()
169
    {
170 96
        if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) {
171 3
            ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress);
172
        }
173
174 96
        if ( ! ftp_pasv($this->connection, $this->passive)) {
175 3
            throw new RuntimeException(
176 3
                'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
177
            );
178
        }
179 93
    }
180
181
    /**
182
     * Set the connection root.
183
     */
184 93
    protected function setConnectionRoot()
185
    {
186 93
        $root = $this->getRoot();
187 93
        $connection = $this->connection;
188
189 93
        if ($root && ! ftp_chdir($connection, $root)) {
0 ignored issues
show
Bug Best Practice introduced by mhlavac
The expression $root 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...
190 3
            throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot());
191
        }
192
193
        // Store absolute path for further reference.
194
        // This is needed when creating directories and
195
        // initial root was a relative path, else the root
196
        // would be relative to the chdir'd path.
197 90
        $this->root = ftp_pwd($connection);
198 90
    }
199
200
    /**
201
     * Login.
202
     *
203
     * @throws RuntimeException
204
     */
205
    protected function login()
206
    {
207 102
        set_error_handler(function () {
208 102
        });
209 102
        $isLoggedIn = ftp_login(
210 102
            $this->connection,
211 102
            $this->getUsername(),
212 102
            $this->getPassword()
213
        );
214 102
        restore_error_handler();
215
216 102
        if ( ! $isLoggedIn) {
217 3
            $this->disconnect();
218 3
            throw new RuntimeException(
219 3
                'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
220 3
                ) . ', username: ' . $this->getUsername()
221
            );
222
        }
223 99
    }
224
225
    /**
226
     * Disconnect from the FTP server.
227
     */
228 120
    public function disconnect()
229
    {
230 120
        if (is_resource($this->connection)) {
231 9
            ftp_close($this->connection);
232
        }
233
234 120
        $this->connection = null;
235 120
    }
236
237
    /**
238
     * @inheritdoc
239
     */
240 9
    public function write($path, $contents, Config $config)
241
    {
242 9
        $stream = fopen('php://temp', 'w+b');
243 9
        fwrite($stream, $contents);
244 9
        rewind($stream);
245 9
        $result = $this->writeStream($path, $stream, $config);
246 9
        fclose($stream);
247
248 9
        if ($result === false) {
249 6
            return false;
250
        }
251
252 6
        $result['contents'] = $contents;
253 6
        $result['mimetype'] = Util::guessMimeType($path, $contents);
254
255 6
        return $result;
256
    }
257
258
    /**
259
     * @inheritdoc
260
     */
261 9
    public function writeStream($path, $resource, Config $config)
262
    {
263 9
        $this->ensureDirectory(Util::dirname($path));
264
265 9
        if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) {
266 6
            return false;
267
        }
268
269 6
        if ($visibility = $config->get('visibility')) {
270 6
            $this->setVisibility($path, $visibility);
271
        }
272
273 6
        $type = 'file';
274
275 6
        return compact('type', 'path', 'visibility');
276
    }
277
278
    /**
279
     * @inheritdoc
280
     */
281 6
    public function update($path, $contents, Config $config)
282
    {
283 6
        return $this->write($path, $contents, $config);
284
    }
285
286
    /**
287
     * @inheritdoc
288
     */
289 3
    public function updateStream($path, $resource, Config $config)
290
    {
291 3
        return $this->writeStream($path, $resource, $config);
292
    }
293
294
    /**
295
     * @inheritdoc
296
     */
297 3
    public function rename($path, $newpath)
298
    {
299 3
        return ftp_rename($this->getConnection(), $path, $newpath);
300
    }
301
302
    /**
303
     * @inheritdoc
304
     */
305 3
    public function delete($path)
306
    {
307 3
        return ftp_delete($this->getConnection(), $path);
308
    }
309
310
    /**
311
     * @inheritdoc
312
     */
313 3
    public function deleteDir($dirname)
314
    {
315 3
        $connection = $this->getConnection();
316 3
        $contents = array_reverse($this->listDirectoryContents($dirname, false));
317
318 3
        foreach ($contents as $object) {
319 3
            if ($object['type'] === 'file') {
320 3
                if ( ! ftp_delete($connection, $object['path'])) {
321 3
                    return false;
322
                }
323 3
            } elseif ( ! $this->deleteDir($object['path'])) {
324 2
                return false;
325
            }
326
        }
327
328 3
        return ftp_rmdir($connection, $dirname);
329
    }
330
331
    /**
332
     * @inheritdoc
333
     */
334 6
    public function createDir($dirname, Config $config)
335
    {
336 6
        $connection = $this->getConnection();
337 6
        $directories = explode('/', $dirname);
338
339 6
        foreach ($directories as $directory) {
340 6
            if (false === $this->createActualDirectory($directory, $connection)) {
341 3
                $this->setConnectionRoot();
342
343 3
                return false;
344
            }
345
346 6
            ftp_chdir($connection, $directory);
347
        }
348
349 3
        $this->setConnectionRoot();
350
351 3
        return ['type' => 'dir', 'path' => $dirname];
352
    }
353
354
    /**
355
     * Create a directory.
356
     *
357
     * @param string   $directory
358
     * @param resource $connection
359
     *
360
     * @return bool
361
     */
362 6
    protected function createActualDirectory($directory, $connection)
363
    {
364
        // List the current directory
365 6
        $listing = ftp_nlist($connection, '.') ?: [];
366
367 6
        foreach ($listing as $key => $item) {
368 6
            if (preg_match('~^\./.*~', $item)) {
369 6
                $listing[$key] = substr($item, 2);
370
            }
371
        }
372
373 6
        if (in_array($directory, $listing, true)) {
374 3
            return true;
375
        }
376
377 6
        return (boolean) ftp_mkdir($connection, $directory);
378
    }
379
380
    /**
381
     * @inheritdoc
382
     */
383 33
    public function getMetadata($path)
384
    {
385 33
        if ($path === '') {
386 3
            return ['type' => 'dir', 'path' => ''];
387
        }
388
389 30
        if (@ftp_chdir($this->getConnection(), $path) === true) {
390 3
            $this->setConnectionRoot();
391
392 3
            return ['type' => 'dir', 'path' => $path];
393
        }
394
395 30
        $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path));
396
397 30
        if (empty($listing) || in_array('total 0', $listing, true)) {
398 6
            return false;
399
        }
400
401 24
        if (preg_match('/.* not found/', $listing[0])) {
402 6
            return false;
403
        }
404
405 18
        if (preg_match('/^total [0-9]*$/', $listing[0])) {
406 3
            array_shift($listing);
407
        }
408
409 18
        return $this->normalizeObject($listing[0], '');
410
    }
411
412
    /**
413
     * @inheritdoc
414
     */
415 9
    public function getMimetype($path)
416
    {
417 9
        if ( ! $metadata = $this->getMetadata($path)) {
418 6
            return false;
419
        }
420
421 6
        $metadata['mimetype'] = MimeType::detectByFilename($path);
422
423 6
        return $metadata;
424
    }
425
426
    /**
427
     * @inheritdoc
428
     */
429 12
    public function getTimestamp($path)
430
    {
431 12
        $timestamp = ftp_mdtm($this->getConnection(), $path);
432
433 12
        return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false;
434
    }
435
436
    /**
437
     * @inheritdoc
438
     */
439 6
    public function read($path)
440
    {
441 6
        if ( ! $object = $this->readStream($path)) {
442 3
            return false;
443
        }
444
445 3
        $object['contents'] = stream_get_contents($object['stream']);
446 3
        fclose($object['stream']);
447 3
        unset($object['stream']);
448
449 3
        return $object;
450
    }
451
452
    /**
453
     * @inheritdoc
454
     */
455 6
    public function readStream($path)
456
    {
457 6
        $stream = fopen('php://temp', 'w+b');
458 6
        $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
459 6
        rewind($stream);
460
461 6
        if ( ! $result) {
462 3
            fclose($stream);
463
464 3
            return false;
465
        }
466
467 3
        return ['type' => 'file', 'path' => $path, 'stream' => $stream];
468
    }
469
470
    /**
471
     * @inheritdoc
472
     */
473 9
    public function setVisibility($path, $visibility)
474
    {
475 9
        $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate();
476
477 9
        if ( ! ftp_chmod($this->getConnection(), $mode, $path)) {
478 6
            return false;
479
        }
480
481 6
        return compact('path', 'visibility');
482
    }
483
484
    /**
485
     * @inheritdoc
486
     *
487
     * @param string $directory
488
     */
489 33
    protected function listDirectoryContents($directory, $recursive = true)
490
    {
491 33
        $directory = str_replace('*', '\\*', $directory);
492
493 33
        if ($recursive && $this->recurseManually) {
494 3
            return $this->listDirectoryContentsRecursive($directory);
495
        }
496
497 30
        $options = $recursive ? '-alnR' : '-aln';
498 30
        $listing = $this->ftpRawlist($options, $directory);
499
500 30
        return $listing ? $this->normalizeListing($listing, $directory) : [];
501
    }
502
503
    /**
504
     * @inheritdoc
505
     *
506
     * @param string $directory
507
     */
508 3
    protected function listDirectoryContentsRecursive($directory)
509
    {
510 3
        $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory);
511 3
        $output = [];
512
513 3
        foreach ($listing as $item) {
514 3
            $output[] = $item;
515 3
            if ($item['type'] !== 'dir') {
516 3
                continue;
517
            }
518 3
            $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path']));
519
        }
520
521 3
        return $output;
522
    }
523
524
    /**
525
     * Check if the connection is open.
526
     *
527
     * @return bool
528
     *
529
     * @throws ErrorException
530
     */
531 84
    public function isConnected()
532
    {
533
        try {
534 84
            return is_resource($this->connection) && ftp_rawlist($this->connection, $this->getRoot()) !== false;
535 6
        } catch (ErrorException $e) {
536 6
            if (strpos($e->getMessage(), 'ftp_rawlist') === false) {
537 3
                throw $e;
538
            }
539
540 3
            return false;
541
        }
542
    }
543
544
    /**
545
     * @return bool
546
     */
547 90
    protected function isPureFtpdServer()
548
    {
549 90
        $response = ftp_raw($this->connection, 'HELP');
550
551 90
        return stripos(implode(' ', $response), 'Pure-FTPd') !== false;
552
    }
553
554
    /**
555
     * The ftp_rawlist function with optional escaping.
556
     *
557
     * @param string $options
558
     * @param string $path
559
     *
560
     * @return array
561
     */
562 60
    protected function ftpRawlist($options, $path)
563
    {
564 60
        $connection = $this->getConnection();
565
566 60
        if ($this->isPureFtpd) {
567
            $path = str_replace(' ', '\ ', $path);
568
        }
569
570 60
        return ftp_rawlist($connection, $options . ' ' . $path);
571
    }
572
}
573