Passed
Push — master ( 22ad80...fa0027 )
by Gabor
08:44
created

ServiceAdapter::download()   C

Complexity

Conditions 9
Paths 36

Size

Total Lines 53
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 0
loc 53
ccs 0
cts 45
cp 0
rs 6.8963
c 0
b 0
f 0
cc 9
eloc 35
nc 36
nop 3
crap 90

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @copyright 2012 - 2017 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
declare(strict_types = 1);
13
14
namespace WebHemi\Ftp\ServiceAdapter\Base;
15
16
use RuntimeException;
17
use WebHemi\Configuration\ServiceInterface as ConfigurationInterface;
18
use WebHemi\Ftp\ServiceInterface;
19
20
/**
21
 * Class ServiceAdapter.
22
 */
23
class ServiceAdapter implements ServiceInterface
24
{
25
    /** @var resource */
26
    private $connectionId = null;
27
    /** @var string */
28
    private $localPath = __DIR__;
29
    /** @var array */
30
    protected $options = [];
31
32
    /**
33
     * ServiceAdapter constructor.
34
     *
35
     * @param ConfigurationInterface $configuration
36
     */
37
    public function __construct(ConfigurationInterface $configuration)
38
    {
39
        $this->setOptions($configuration->getData('ftp'));
40
    }
41
42
    /**
43
     * Disconnected by garbage collection.
44
     */
45
    public function __destruct()
46
    {
47
        $this->disconnect();
48
    }
49
50
    /**
51
     * Connect and login to remote host.
52
     *
53
     * @return ServiceInterface
54
     */
55
    public function connect() : ServiceInterface
56
    {
57
        if ($this->getOption('secure', true)) {
58
            $this->connectionId = @ftp_ssl_connect($this->getOption('host'));
59
        } else {
60
            $this->connectionId = @ftp_connect($this->getOption('host'));
61
        }
62
63
        if (!$this->connectionId) {
64
            throw new RuntimeException(
65
                sprintf('Cannot establish connection to server: %s', $this->getOption('host')),
66
                1000
67
            );
68
        }
69
70
        $loginResult = @ftp_login($this->connectionId, $this->getOption('username'), $this->getOption('password'));
71
72
        if (!$loginResult) {
73
            throw new RuntimeException('Cannot connect to remote host: invalid credentials', 1001);
74
        }
75
76
        return $this;
77
    }
78
79
    /**
80
     * Disconnect from remote host.
81
     *
82
     * @return ServiceInterface
83
     */
84
    public function disconnect() : ServiceInterface
85
    {
86
        if (!empty($this->connectionId)) {
87
            ftp_close($this->connectionId);
88
            $this->connectionId = null;
89
        }
90
    }
91
92
    /**
93
     * Sets an option data.
94
     *
95
     * @param string $key
96
     * @param mixed $value
97
     * @return ServiceInterface
98
     */
99
    public function setOption(string $key, $value) : ServiceInterface
100
    {
101
        $this->options[$key] = $value;
102
        return $this;
103
    }
104
105
    /**
106
     * Sets a group of options.
107
     *
108
     * @param array $options
109
     * @return ServiceInterface
110
     */
111
    public function setOptions(array $options) : ServiceInterface
112
    {
113
        $this->options = array_merge($this->options, $options);
114
        return $this;
115
    }
116
117
    /**
118
     * Gets a specific option data.
119
     *
120
     * @param string $key
121
     * @param mixed $default
122
     * @return mixed
123
     */
124
    public function getOption(string $key, $default = null)
125
    {
126
        return $this->options[$key] ?? $default;
127
    }
128
129
    /**
130
     * Toggles connection security level.
131
     *
132
     * @param bool $state
133
     * @return ServiceInterface
134
     */
135
    public function setSecureConnection(bool $state) : ServiceInterface
136
    {
137
        $this->setOption('secure', (bool)$state);
138
139
        if (!empty($this->connectionId)) {
140
            ftp_close($this->connectionId);
141
            $this->connectionId = null;
142
            $this->connect();
143
        }
144
145
        return $this;
146
    }
147
148
    /**
149
     * Toggles connection passive mode.
150
     *
151
     * @param bool $state
152
     * @return ServiceInterface
153
     */
154
    public function setPassiveMode(bool $state) : ServiceInterface
155
    {
156
        ftp_pasv($this->connectionId, (bool)$state);
157
        return $this;
158
    }
159
160
    /**
161
     * Sets remote path.
162
     *
163
     * @param string $path
164
     * @return ServiceInterface
165
     */
166
    public function setRemotePath(string $path) : ServiceInterface
167
    {
168
        if (trim($this->getRemotePath(), '/') == trim($path, '/')) {
169
            return $this;
170
        }
171
172
        if (strpos($path, '/') !== 0) {
173
            $path = $this->getRemotePath() . $path;
174
        }
175
176
        $chdirResult = @ftp_chdir($this->connectionId, $path);
177
178
        if (!$chdirResult) {
179
            throw new RuntimeException(sprintf('No such directory on remote host: %s', $path), 1002);
180
        }
181
182
        return $this;
183
    }
184
185
    /**
186
     * Gets remote path.
187
     *
188
     * @return string
189
     */
190
    public function getRemotePath() : string
191
    {
192
        return ftp_pwd($this->connectionId) . '/';
193
    }
194
195
    /**
196
     * Sets local path.
197
     *
198
     * @param string $path
199
     * @return ServiceInterface
200
     */
201
    public function setLocalPath(string $path) : ServiceInterface
202
    {
203
        // if it's not an absolute path, we take it relative to the current folder
204
        if (strpos($path, '/') !== 0) {
205
            $path = __DIR__ . '/' . $path;
206
        }
207
208
        if (!realpath($path) || !is_dir($path)) {
209
            throw new RuntimeException(sprintf('No such directory: %s', $path), 1003);
210
        }
211
212
        if (!is_readable($path)) {
213
            throw new RuntimeException(sprintf('Cannot read directory: %s; Permission denied.', $path), 1004);
214
        }
215
216
        if (!is_writable($path)) {
217
            throw new RuntimeException(
218
                sprintf('Cannot write data into directory: %s; Permission denied.', $path),
219
                1005
220
            );
221
        }
222
223
        $this->localPath = $path;
224
225
        return $this;
226
    }
227
228
    /**
229
     * Gets local path.
230
     *
231
     * @return string
232
     */
233
    public function getLocalPath() : string
234
    {
235
        return $this->localPath;
236
    }
237
238
    /**
239
     * Lists remote path.
240
     *
241
     * @param null|string $path
242
     * @param bool|null $changeToDirectory
243
     * @return array
244
     */
245
    public function getRemoteFileList(? string $path, ? bool $changeToDirectory) : array
246
    {
247
        $fileList = [];
248
249
        if (!empty($path) && $changeToDirectory) {
250
            $this->setRemotePath($path);
251
            $path = '.';
252
        }
253
254
        $result = @ftp_rawlist($this->connectionId, $path);
255
256
        if (!is_array($result)) {
257
            throw new RuntimeException('Cannot retrieve file list', 1006);
258
        }
259
260
        foreach ($result as $fileRawData) {
261
            $fileData = [];
262
263
            preg_match(
264
                '/^(?P<rights>(?P<type>(-|d))[^\s]+)\s+(?P<symlinks>\d+)\s+(?P<user>[^\s]+)\s+(?P<group>[^\s]+)\s+'
265
                    .'(?P<size>\d+)\s+(?P<date>(?P<month>[^\s]+)\s+(?P<day>[^\s]+)\s+'
266
                    .'(?P<time>[^\s]+))\s+(?P<filename>.+)$/',
267
                $fileRawData,
268
                $fileData
269
            );
270
            $fileInfo = pathinfo($fileData['filename']);
271
272
            $fileList[] = [
273
                'type' => $fileData['type'] == 'd' ? 'directory' : ($fileData['type'] == 'l' ? 'symlink' : 'file'),
274
                'chmod' => $this->getOctalChmod($fileData['rights']),
275
                'symlinks' => $fileData['symlinks'],
276
                'user' => $fileData['user'],
277
                'group' => $fileData['group'],
278
                'size' => $fileData['size'],
279
                'date' => date(
280
                    'Y-m-d H:i:s',
281
                    strtotime(
282
                        strpos($fileData['time'], ':') !== false
283
                            ? $fileData['month'] . ' ' . $fileData['day'] . ' ' . date('Y') . ' ' . $fileData['time']
284
                            : $fileData['date'] . ' 12:00:00'
285
                    )
286
                ),
287
                'basename' => $fileInfo['basename'],
288
                'filename' => $fileInfo['filename'],
289
                'extension' => isset($fileInfo['extension']) ? $fileInfo['extension'] : '',
290
            ];
291
        }
292
293
        return $fileList;
294
    }
295
296
    /**
297
     * Converts file rights string into octal value.
298
     *
299
     * @param string $permissions The UNIX-style permission string, e.g.: 'drwxr-xr-x'
300
     * @return string
301
     */
302
    private function getOctalChmod(string $permissions) : string
303
    {
304
        $mode = 0;
305
        $mapper = [
306
            0 => [], // type like d as directory, l as link etc.
307
            // Owner
308
            1 => ['r' => 0400],
309
            2 => ['w' => 0200],
310
            3 => [
311
                'x' => 0100,
312
                's' => 04100,
313
                'S' => 04000
314
            ],
315
            // Group
316
            4 => ['r' => 040],
317
            5 => ['w' => 020],
318
            6 => [
319
                'x' => 010,
320
                's' => 02010,
321
                'S' => 02000
322
            ],
323
            // World
324
            7 => ['r' => 04],
325
            8 => ['w' => 02],
326
            9 => [
327
                'x' => 01,
328
                't' => 01001,
329
                'T' => 01000
330
            ],
331
        ];
332
333
        for ($i = 1; $i <= 9; $i++) {
334
            $mode += $mapper[$i][$permissions[$i]] ?? 0;
335
        }
336
337
        return (string)$mode;
338
    }
339
340
    /**
341
     * Uploads file to remote host.
342
     *
343
     * @see self::setRemotePath
344
     * @see self::setLocalPath
345
     *
346
     * @param string $sourceFileName
347
     * @param string $destinationFileName
348
     * @param int $fileMode
349
     * @return mixed
350
     */
351
    public function upload(
352
        string $sourceFileName,
353
        string $destinationFileName,
354
        int $fileMode = self::FILE_MODE_BINARY
355
    ) : ServiceInterface {
356
        $pathInfo = pathinfo($destinationFileName);
357
358
        if ($pathInfo['dirname'] != '.') {
359
            $this->setRemotePath($pathInfo['dirname']);
360
            $destinationFileName = $pathInfo['basename'];
361
        }
362
363
        $pathInfo = pathinfo($sourceFileName);
364
365
        if ($pathInfo['dirname'] != '.') {
366
            $this->setLocalPath($pathInfo['dirname']);
367
            $sourceFileName = $pathInfo['basename'];
368
        }
369
370
        if (!file_exists($this->localPath . '/' . $sourceFileName)) {
371
            throw new RuntimeException(sprintf('File not found: %s', $this->localPath . '/' . $sourceFileName), 1007);
372
        }
373
374
        $uploadResult = @ftp_put(
375
            $this->connectionId,
376
            $destinationFileName,
377
            $this->localPath . '/' . $sourceFileName,
378
            $fileMode
379
        );
380
381
        if (!$uploadResult) {
382
            throw new RuntimeException(sprintf('There was a problem while uploading file: %s', $sourceFileName), 1008);
383
        }
384
385
        return $this;
386
    }
387
388
    /**
389
     * Downloads file from remote host.
390
     *
391
     * @see self::setRemotePath
392
     * @see self::setLocalPath
393
     *
394
     * @param string $remoteFileName
395
     * @param string $localFileName
396
     * @param int $fileMode
397
     * @return mixed
398
     */
399
    public function download(
400
        string $remoteFileName,
401
        string&$localFileName,
402
        int $fileMode = self::FILE_MODE_BINARY
403
    ) : ServiceInterface {
404
        $pathInfo = pathinfo($remoteFileName);
405
406
        if ($pathInfo['dirname'] != '.') {
407
            $this->setRemotePath($pathInfo['dirname']);
408
            $remoteFileName = $pathInfo['basename'];
409
        }
410
411
        $pathInfo = pathinfo($localFileName);
412
413
        if ($pathInfo['dirname'] != '.') {
414
            $this->setLocalPath($pathInfo['dirname']);
415
            $localFileName = $pathInfo['basename'];
416
        }
417
418
        if (file_exists($this->localPath.'/'.$localFileName)) {
419
            $variant = 1;
420
            do {
421
                $localFileName = $pathInfo['filename'].'('.$variant.')'.(
422
                    !empty($pathInfo['extension'])
423
                        ? '.'.$pathInfo['extension']
424
                        : ''
425
                    );
426
            } while (file_exists($this->localPath.'/'.$localFileName) && $variant++ < 20);
427
428
            if ($variant >= 20) {
429
                throw new RuntimeException(
430
                    sprintf('Too many similar files in folder %s, please cleanup first.', $this->localPath),
431
                    1009
432
                );
433
            }
434
        }
435
436
        $downloadResult = @ftp_get(
437
            $this->connectionId,
438
            $this->localPath . '/' . $localFileName,
439
            $remoteFileName,
440
            $fileMode
441
        );
442
443
        if (!$downloadResult) {
444
            throw new RuntimeException(
445
                sprintf('There was a problem while downloading file: %s', $remoteFileName),
446
                1010
447
            );
448
        }
449
450
        return $this;
451
    }
452
453
    /**
454
     * Moves file on remote host.
455
     *
456
     * @param string $currentPath
457
     * @param string $newPath
458
     * @return ServiceInterface
459
     */
460
    public function moveRemoteFile(string $currentPath, string $newPath) : ServiceInterface
461
    {
462
        $result = @ftp_rename($this->connectionId, $currentPath, $newPath);
463
464
        if (!$result) {
465
            throw new RuntimeException(
466
                sprintf('Unable to move/rename file from %s to %s', $currentPath, $newPath),
467
                1011
468
            );
469
        }
470
471
        return $this;
472
    }
473
474
    /**
475
     * Deletes file on remote host.
476
     *
477
     * @param string $path
478
     * @return ServiceInterface
479
     */
480
    public function deleteRemoteFile(string $path) : ServiceInterface
481
    {
482
        $result = @ftp_delete($this->connectionId, $path);
483
484
        if (!$result) {
485
            throw new RuntimeException(sprintf('Unable to delete file on remote host: %s', $path), 1012);
486
        }
487
488
        return $this;
489
    }
490
}
491