Passed
Push — master ( ccc9fa...bee35f )
by Gabor
03:14
created

AbstractServiceAdapter::setOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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;
15
16
use RuntimeException;
17
use WebHemi\Configuration\ServiceInterface as ConfigurationInterface;
18
use WebHemi\Ftp\ServiceInterface;
19
20
/**
21
 * Class AbstractServiceAdapter
22
 */
23
abstract class AbstractServiceAdapter implements ServiceInterface
24
{
25
    /** @var string */
26
    protected $localPath = __DIR__;
27
    /** @var array */
28
    protected $options = [];
29
30
    /**
31
     * ServiceAdapter constructor.
32
     *
33
     * @param ConfigurationInterface $configuration
34
     */
35 9
    public function __construct(ConfigurationInterface $configuration)
36
    {
37 9
        $this->setOptions($configuration->getData('ftp'));
38 9
    }
39
40
    /**
41
     * Connect and login to remote host.
42
     *
43
     * @return ServiceInterface
44
     */
45
    abstract public function connect() : ServiceInterface;
46
47
    /**
48
     * Disconnect from remote host.
49
     *
50
     * @return ServiceInterface
51
     */
52
    abstract public function disconnect() : ServiceInterface;
53
54
    /**
55
     * Sets an option data.
56
     *
57
     * @param string $key
58
     * @param mixed $value
59
     * @return ServiceInterface
60
     */
61 1
    public function setOption(string $key, $value) : ServiceInterface
62
    {
63 1
        $this->options[$key] = $value;
64 1
        return $this;
65
    }
66
67
    /**
68
     * Sets a group of options.
69
     *
70
     * @param array $options
71
     * @return ServiceInterface
72
     */
73 9
    public function setOptions(array $options) : ServiceInterface
74
    {
75 9
        $this->options = array_merge($this->options, $options);
76 9
        return $this;
77
    }
78
79
    /**
80
     * Gets a specific option data.
81
     *
82
     * @param string $key
83
     * @param mixed $default
84
     * @return mixed
85
     */
86 1
    public function getOption(string $key, $default = null)
87
    {
88 1
        return $this->options[$key] ?? $default;
89
    }
90
91
    /**
92
     * Toggles connection security level.
93
     *
94
     * @param bool $state
95
     * @return ServiceInterface
96
     */
97
    abstract public function setSecureConnection(bool $state) : ServiceInterface;
98
99
    /**
100
     * Toggles connection passive mode.
101
     *
102
     * @param bool $state
103
     * @return ServiceInterface
104
     */
105
    abstract public function setPassiveMode(bool $state) : ServiceInterface;
106
107
    /**
108
     * Sets remote path.
109
     *
110
     * @param string $path
111
     * @return ServiceInterface
112
     */
113
    abstract public function setRemotePath(string $path) : ServiceInterface;
114
115
    /**
116
     * Gets remote path.
117
     *
118
     * @return string
119
     */
120
    abstract public function getRemotePath() : string;
121
122
    /**
123
     * Sets local path.
124
     *
125
     * @param string $path
126
     * @return ServiceInterface
127
     */
128 6
    public function setLocalPath(string $path) : ServiceInterface
129
    {
130
        // if it's not an absolute path, we take it relative to the current folder
131 6
        if (strpos($path, '/') !== 0) {
132 1
            $path = __DIR__.'/'.$path;
133
        }
134
135 6
        if (!realpath($path) || !is_dir($path)) {
136 2
            throw new RuntimeException(sprintf('No such directory: %s', $path), 1003);
137
        }
138
139
        // @codeCoverageIgnoreStart
140
        // docker is root. Can't test these cases...
141
        if (!is_readable($path)) {
142
            throw new RuntimeException(sprintf('Cannot read directory: %s; Permission denied.', $path), 1004);
143
        }
144
145
        if (!is_writable($path)) {
146
            throw new RuntimeException(
147
                sprintf('Cannot write data into directory: %s; Permission denied.', $path),
148
                1005
149
            );
150
        }
151
        // @codeCoverageIgnoreEnd
152
153 2
        $this->localPath = $path;
154
155 2
        return $this;
156
    }
157
158
    /**
159
     * Checks local file, and generates new unique name if necessary.
160
     *
161
     * @param string $localFileName
162
     * @param bool $forceUnique
163
     * @throws RuntimeException
164
     */
165 1
    protected function checkLocalFile(string&$localFileName, bool $forceUnique = false) : void
166
    {
167 1
        $pathInfo = pathinfo($localFileName);
168
169 1
        if ($pathInfo['dirname'] != '.') {
170 1
            $this->setLocalPath($pathInfo['dirname']);
171
        }
172
173 1
        $localFileName = $pathInfo['basename'];
174
175 1
        if (!$forceUnique) {
176 1
            return;
177
        }
178
179 1
        $variant = 0;
180 1
        $fileName = $pathInfo['filename'];
181 1
        $extension = $pathInfo['extension'];
182
183 1
        while (file_exists($this->localPath.'/'.$fileName.'.'.$extension) && $variant++ < 20) {
184 1
            $fileName = $pathInfo['filename'].'.('.($variant).')';
185
        }
186
187 1
        if (preg_match('/\.\(20\)$/', $fileName)) {
188 1
            throw new RuntimeException(
189 1
                sprintf('Too many similar files in folder %s, please cleanup first.', $this->localPath),
190 1
                1009
191
            );
192
        }
193
194 1
        $localFileName = $fileName.'.'.$extension;
195 1
    }
196
197
    /**
198
     * Converts file rights string into octal value.
199
     *
200
     * @param string $permissions The UNIX-style permission string, e.g.: 'drwxr-xr-x'
201
     * @return string
202
     */
203 1
    protected function getOctalChmod(string $permissions) : string
204
    {
205 1
        $mode = 0;
206
        $mapper = [
207 1
            0 => [], // type like d as directory, l as link etc.
208
            // Owner
209
            1 => ['r' => 0400],
210
            2 => ['w' => 0200],
211
            3 => [
212
                'x' => 0100,
213
                's' => 04100,
214
                'S' => 04000
215
            ],
216
            // Group
217
            4 => ['r' => 040],
218
            5 => ['w' => 020],
219
            6 => [
220
                'x' => 010,
221
                's' => 02010,
222
                'S' => 02000
223
            ],
224
            // World
225
            7 => ['r' => 04],
226
            8 => ['w' => 02],
227
            9 => [
228
                'x' => 01,
229
                't' => 01001,
230
                'T' => 01000
231
            ],
232
        ];
233
234 1
        for ($i = 1; $i <= 9; $i++) {
235 1
            $mode += $mapper[$i][$permissions[$i]] ?? 00;
236
        }
237
238 1
        return '0'.decoct($mode);
239
    }
240
241
    /**
242
     * Check remote file name.
243
     *
244
     * @param string $remoteFileName
245
     */
246
    abstract protected function checkRemoteFile(string&$remoteFileName) : void;
247
248
    /**
249
     * Gets local path.
250
     *
251
     * @return string
252
     */
253 1
    public function getLocalPath() : string
254
    {
255 1
        return $this->localPath;
256
    }
257
258
    /**
259
     * Lists remote path.
260
     *
261
     * @param null|string $path
262
     * @param bool|null $changeToDirectory
263
     * @return array
264
     */
265
    abstract public function getRemoteFileList(? string $path, ? bool $changeToDirectory) : array;
266
267
    /**
268
     * Uploads file to remote host.
269
     *
270
     * @see self::setRemotePath
271
     * @see self::setLocalPath
272
     *
273
     * @param string $sourceFileName
274
     * @param string $destinationFileName
275
     * @param int $fileMode
276
     * @return mixed
277
     */
278
    abstract public function upload(
279
        string $sourceFileName,
280
        string $destinationFileName,
281
        int $fileMode = self::FILE_MODE_BINARY
282
    ) : ServiceInterface;
283
284
    /**
285
     * Downloads file from remote host.
286
     *
287
     * @see self::setRemotePath
288
     * @see self::setLocalPath
289
     *
290
     * @param string $remoteFileName
291
     * @param string $localFileName
292
     * @param int $fileMode
293
     * @return mixed
294
     */
295
    abstract public function download(
296
        string $remoteFileName,
297
        string&$localFileName,
298
        int $fileMode = self::FILE_MODE_BINARY
299
    ) : ServiceInterface;
300
301
    /**
302
     * Moves file on remote host.
303
     *
304
     * @param string $currentPath
305
     * @param string $newPath
306
     * @return ServiceInterface
307
     */
308
    abstract public function moveRemoteFile(string $currentPath, string $newPath) : ServiceInterface;
309
310
    /**
311
     * Deletes file on remote host.
312
     *
313
     * @param string $path
314
     * @return ServiceInterface
315
     */
316
    abstract public function deleteRemoteFile(string $path) : ServiceInterface;
317
}
318