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\Ftp\ServiceInterface; |
18
|
|
|
use WebHemi\Ftp\ServiceAdapter\AbstractServiceAdapter; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Class ServiceAdapter. |
22
|
|
|
* |
23
|
|
|
* @codeCoverageIgnore - don't test third party library. |
24
|
|
|
*/ |
25
|
|
|
class ServiceAdapter extends AbstractServiceAdapter |
26
|
|
|
{ |
27
|
|
|
/** @var resource */ |
28
|
|
|
private $connectionId = null; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Disconnected by garbage collection. |
32
|
|
|
*/ |
33
|
|
|
public function __destruct() |
34
|
|
|
{ |
35
|
|
|
$this->disconnect(); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Connect and login to remote host. |
40
|
|
|
* |
41
|
|
|
* @return ServiceInterface |
42
|
|
|
*/ |
43
|
|
|
public function connect() : ServiceInterface |
44
|
|
|
{ |
45
|
|
|
if ($this->getOption('secure', true)) { |
46
|
|
|
$this->connectionId = @ftp_ssl_connect($this->getOption('host')); |
47
|
|
|
} else { |
48
|
|
|
$this->connectionId = @ftp_connect($this->getOption('host')); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
if (!$this->connectionId) { |
52
|
|
|
throw new RuntimeException( |
53
|
|
|
sprintf('Cannot establish connection to server: %s', $this->getOption('host')), |
54
|
|
|
1000 |
55
|
|
|
); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
$loginResult = @ftp_login($this->connectionId, $this->getOption('username'), $this->getOption('password')); |
59
|
|
|
|
60
|
|
|
if (!$loginResult) { |
61
|
|
|
throw new RuntimeException('Cannot connect to remote host: invalid credentials', 1001); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
return $this; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Disconnect from remote host. |
69
|
|
|
* |
70
|
|
|
* @return ServiceInterface |
71
|
|
|
*/ |
72
|
|
|
public function disconnect() : ServiceInterface |
73
|
|
|
{ |
74
|
|
|
if (!empty($this->connectionId)) { |
75
|
|
|
ftp_close($this->connectionId); |
76
|
|
|
$this->connectionId = null; |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Toggles connection security level. |
82
|
|
|
* |
83
|
|
|
* @param bool $state |
84
|
|
|
* @return ServiceInterface |
85
|
|
|
*/ |
86
|
|
|
public function setSecureConnection(bool $state) : ServiceInterface |
87
|
|
|
{ |
88
|
|
|
$this->setOption('secure', (bool) $state); |
89
|
|
|
|
90
|
|
|
if (!empty($this->connectionId)) { |
91
|
|
|
ftp_close($this->connectionId); |
92
|
|
|
$this->connectionId = null; |
93
|
|
|
$this->connect(); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
return $this; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Toggles connection passive mode. |
101
|
|
|
* |
102
|
|
|
* @param bool $state |
103
|
|
|
* @return ServiceInterface |
104
|
|
|
*/ |
105
|
|
|
public function setPassiveMode(bool $state) : ServiceInterface |
106
|
|
|
{ |
107
|
|
|
ftp_pasv($this->connectionId, (bool) $state); |
108
|
|
|
return $this; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Sets remote path. |
113
|
|
|
* |
114
|
|
|
* @param string $path |
115
|
|
|
* @return ServiceInterface |
116
|
|
|
*/ |
117
|
|
|
public function setRemotePath(string $path) : ServiceInterface |
118
|
|
|
{ |
119
|
|
|
if (trim($this->getRemotePath(), '/') == trim($path, '/')) { |
120
|
|
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
if (strpos($path, '/') !== 0) { |
124
|
|
|
$path = $this->getRemotePath().$path; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$chdirResult = @ftp_chdir($this->connectionId, $path); |
128
|
|
|
|
129
|
|
|
if (!$chdirResult) { |
130
|
|
|
throw new RuntimeException(sprintf('No such directory on remote host: %s', $path), 1002); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
return $this; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Gets remote path. |
138
|
|
|
* |
139
|
|
|
* @return string |
140
|
|
|
*/ |
141
|
|
|
public function getRemotePath() : string |
142
|
|
|
{ |
143
|
|
|
return ftp_pwd($this->connectionId).'/'; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Lists remote path. |
148
|
|
|
* |
149
|
|
|
* @param null|string $path |
150
|
|
|
* @param bool|null $changeToDirectory |
151
|
|
|
* @return array |
152
|
|
|
*/ |
153
|
|
|
public function getRemoteFileList(? string $path, ? bool $changeToDirectory) : array |
154
|
|
|
{ |
155
|
|
|
$fileList = []; |
156
|
|
|
|
157
|
|
|
if (!empty($path) && $changeToDirectory) { |
158
|
|
|
$this->setRemotePath($path); |
159
|
|
|
$path = '.'; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$result = @ftp_rawlist($this->connectionId, $path); |
163
|
|
|
|
164
|
|
|
if (!is_array($result)) { |
165
|
|
|
throw new RuntimeException('Cannot retrieve file list', 1006); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
foreach ($result as $fileRawData) { |
169
|
|
|
$fileData = []; |
170
|
|
|
|
171
|
|
|
preg_match( |
172
|
|
|
'/^(?P<rights>(?P<type>(-|d))[^\s]+)\s+(?P<symlinks>\d+)\s+(?P<user>[^\s]+)\s+(?P<group>[^\s]+)\s+' |
173
|
|
|
.'(?P<size>\d+)\s+(?P<date>(?P<month>[^\s]+)\s+(?P<day>[^\s]+)\s+' |
174
|
|
|
.'(?P<time>[^\s]+))\s+(?P<filename>.+)$/', |
175
|
|
|
$fileRawData, |
176
|
|
|
$fileData |
177
|
|
|
); |
178
|
|
|
$fileInfo = pathinfo($fileData['filename']); |
179
|
|
|
|
180
|
|
|
$fileList[] = [ |
181
|
|
|
'type' => $this->getFileType($fileData['type']), |
182
|
|
|
'chmod' => $this->getOctalChmod($fileData['rights']), |
183
|
|
|
'symlinks' => $fileData['symlinks'], |
184
|
|
|
'user' => $fileData['user'], |
185
|
|
|
'group' => $fileData['group'], |
186
|
|
|
'size' => $fileData['size'], |
187
|
|
|
'date' => $this->getFileDate($fileData), |
188
|
|
|
'basename' => $fileInfo['basename'], |
189
|
|
|
'filename' => $fileInfo['filename'], |
190
|
|
|
'extension' => $fileInfo['extension'] ?? '', |
191
|
|
|
]; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $fileList; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @param string $fileData |
199
|
|
|
* @return string |
200
|
|
|
*/ |
201
|
|
|
private function getFileType(string $fileData) : string |
202
|
|
|
{ |
203
|
|
|
switch ($fileData) { |
204
|
|
|
case 'd': |
|
|
|
|
205
|
|
|
$fileType = 'directory'; |
206
|
|
|
break; |
207
|
|
|
|
208
|
|
|
case 'l': |
|
|
|
|
209
|
|
|
$fileType = 'symlink'; |
210
|
|
|
break; |
211
|
|
|
|
212
|
|
|
default: |
213
|
|
|
$fileType = 'file'; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return $fileType; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @param array $fileData |
221
|
|
|
* @return string |
222
|
|
|
*/ |
223
|
|
|
private function getFileDate(array $fileData) : string |
224
|
|
|
{ |
225
|
|
|
if (strpos($fileData['time'], ':') !== false) { |
226
|
|
|
$date = $fileData['month'].' '.$fileData['day'].' '.date('Y').' '.$fileData['time']; |
227
|
|
|
} else { |
228
|
|
|
$date = $fileData['date'].' 12:00:00'; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$time = strtotime($date); |
232
|
|
|
|
233
|
|
|
return date('Y-m-d H:i:s', $time); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Uploads file to remote host. |
238
|
|
|
* |
239
|
|
|
* @see self::setRemotePath |
240
|
|
|
* @see self::setLocalPath |
241
|
|
|
* |
242
|
|
|
* @param string $sourceFileName |
243
|
|
|
* @param string $destinationFileName |
244
|
|
|
* @param int $fileMode |
245
|
|
|
* @return mixed |
246
|
|
|
*/ |
247
|
|
|
public function upload( |
248
|
|
|
string $sourceFileName, |
249
|
|
|
string $destinationFileName, |
250
|
|
|
int $fileMode = self::FILE_MODE_BINARY |
251
|
|
|
) : ServiceInterface { |
252
|
|
|
$this->checkLocalFile($sourceFileName); |
253
|
|
|
$this->checkRemoteFile($destinationFileName); |
254
|
|
|
|
255
|
|
|
if (!file_exists($this->localPath.'/'.$sourceFileName)) { |
256
|
|
|
throw new RuntimeException(sprintf('File not found: %s', $this->localPath.'/'.$sourceFileName), 1007); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$uploadResult = @ftp_put( |
260
|
|
|
$this->connectionId, |
261
|
|
|
$destinationFileName, |
262
|
|
|
$this->localPath.'/'.$sourceFileName, |
263
|
|
|
$fileMode |
264
|
|
|
); |
265
|
|
|
|
266
|
|
|
if (!$uploadResult) { |
267
|
|
|
throw new RuntimeException(sprintf('There was a problem while uploading file: %s', $sourceFileName), 1008); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
return $this; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Downloads file from remote host. |
275
|
|
|
* |
276
|
|
|
* @see self::setRemotePath |
277
|
|
|
* @see self::setLocalPath |
278
|
|
|
* |
279
|
|
|
* @param string $remoteFileName |
280
|
|
|
* @param string $localFileName |
281
|
|
|
* @param int $fileMode |
282
|
|
|
* @return mixed |
283
|
|
|
*/ |
284
|
|
|
public function download( |
285
|
|
|
string $remoteFileName, |
286
|
|
|
string&$localFileName, |
287
|
|
|
int $fileMode = self::FILE_MODE_BINARY |
288
|
|
|
) : ServiceInterface { |
289
|
|
|
$this->checkRemoteFile($remoteFileName); |
290
|
|
|
$this->checkLocalFile($localFileName, true); |
291
|
|
|
|
292
|
|
|
$downloadResult = @ftp_get( |
293
|
|
|
$this->connectionId, |
294
|
|
|
$this->localPath.'/'.$localFileName, |
295
|
|
|
$remoteFileName, |
296
|
|
|
$fileMode |
297
|
|
|
); |
298
|
|
|
|
299
|
|
|
if (!$downloadResult) { |
300
|
|
|
throw new RuntimeException( |
301
|
|
|
sprintf('There was a problem while downloading file: %s', $remoteFileName), |
302
|
|
|
1010 |
303
|
|
|
); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
return $this; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Check remote file name. |
311
|
|
|
* |
312
|
|
|
* @param string $remoteFileName |
313
|
|
|
*/ |
314
|
|
|
protected function checkRemoteFile(string&$remoteFileName) : void |
315
|
|
|
{ |
316
|
|
|
$pathInfo = pathinfo($remoteFileName); |
317
|
|
|
|
318
|
|
|
if ($pathInfo['dirname'] != '.') { |
319
|
|
|
$this->setRemotePath($pathInfo['dirname']); |
320
|
|
|
$remoteFileName = $pathInfo['basename']; |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Moves file on remote host. |
326
|
|
|
* |
327
|
|
|
* @param string $currentPath |
328
|
|
|
* @param string $newPath |
329
|
|
|
* @return ServiceInterface |
330
|
|
|
*/ |
331
|
|
|
public function moveRemoteFile(string $currentPath, string $newPath) : ServiceInterface |
332
|
|
|
{ |
333
|
|
|
$result = @ftp_rename($this->connectionId, $currentPath, $newPath); |
334
|
|
|
|
335
|
|
|
if (!$result) { |
336
|
|
|
throw new RuntimeException( |
337
|
|
|
sprintf('Unable to move/rename file from %s to %s', $currentPath, $newPath), |
338
|
|
|
1011 |
339
|
|
|
); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
return $this; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Deletes file on remote host. |
347
|
|
|
* |
348
|
|
|
* @param string $path |
349
|
|
|
* @return ServiceInterface |
350
|
|
|
*/ |
351
|
|
|
public function deleteRemoteFile(string $path) : ServiceInterface |
352
|
|
|
{ |
353
|
|
|
$result = @ftp_delete($this->connectionId, $path); |
354
|
|
|
|
355
|
|
|
if (!$result) { |
356
|
|
|
throw new RuntimeException(sprintf('Unable to delete file on remote host: %s', $path), 1012); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return $this; |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.