Completed
Pushba8b60...962252
passed — Build
created

Ftp::listDirectoryContentsRecursive()   A

↳ Parent: Ftp

Complexity

Conditions 4
Paths 6

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 13
Code Lines 7

Code Coverage

Tests 0
CRAP Score 20

Importance

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