1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of SebastianFeldmann\Ftp. |
4
|
|
|
* |
5
|
|
|
* (c) Sebastian Feldmann <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
namespace SebastianFeldmann\Ftp; |
11
|
|
|
|
12
|
|
|
use RuntimeException; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Class Client |
16
|
|
|
* |
17
|
|
|
* @package SebastianFeldmann\Ftp |
18
|
|
|
* @author Sebastian Feldmann <[email protected]> |
19
|
|
|
* @link https://github.com/sebastianfeldmann/ftp |
20
|
|
|
* @since Class available since Release 0.9.0 |
21
|
|
|
* |
22
|
|
|
* @method bool cdUp() - Changes to the parent directory |
23
|
|
|
* @method string chDir(string $directory) - Changes the current directory on a FTP server |
24
|
|
|
* @method string mdtm(string $file) - Returns last modification time from given file |
25
|
|
|
* @method bool mkDir(string $path) - Create a directory on the FTP server |
26
|
|
|
* @method array mlsd(string $path) - Return list of file info arrays (>= php 7.2.0) |
27
|
|
|
* @method array nlist(string $path) - Returns list of files in given dir |
28
|
|
|
* @method bool pasv(bool $passive) - Sets the ftp passive mode on or off |
29
|
|
|
* @method bool put(string $name, string $file, int $mode) - Uploads a file to the FTP server |
30
|
|
|
* @method string pwd() - Returns current working directory |
31
|
|
|
* @method int size(string $file) - Returns given files sizes in bytes |
32
|
|
|
*/ |
33
|
|
|
class Client |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* PHP FTP connection resource. |
37
|
|
|
* |
38
|
|
|
* @var resource |
39
|
|
|
*/ |
40
|
|
|
private $connection; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Host to connect to. |
44
|
|
|
* |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
private $host; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Port to connect to. |
51
|
|
|
* |
52
|
|
|
* @var int |
53
|
|
|
*/ |
54
|
|
|
private $port; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* User to login. |
58
|
|
|
* |
59
|
|
|
* @var string |
60
|
|
|
*/ |
61
|
|
|
private $user; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Password to login. |
65
|
|
|
* |
66
|
|
|
* @var string |
67
|
|
|
*/ |
68
|
|
|
private $password; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Use passive ftp mode |
72
|
|
|
* |
73
|
|
|
* @var bool |
74
|
|
|
*/ |
75
|
|
|
private $passive; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Use ftps connection |
79
|
|
|
* |
80
|
|
|
* @var bool |
81
|
|
|
*/ |
82
|
|
|
private $isSecure; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Client constructor. |
86
|
|
|
* |
87
|
|
|
* @param string $url |
88
|
|
|
* @param bool $passive |
89
|
|
|
* @param bool $isSecure |
90
|
|
|
*/ |
91
|
1 |
|
public function __construct(string $url, bool $passive = false, bool $isSecure = false) |
92
|
|
|
{ |
93
|
1 |
|
if (!extension_loaded('ftp')) { |
94
|
|
|
throw new RuntimeException('FTP extension is not loaded.'); |
95
|
|
|
} |
96
|
1 |
|
$this->passive = $passive; |
97
|
1 |
|
$this->isSecure = $isSecure; |
98
|
1 |
|
$this->setup($url); |
99
|
1 |
|
$this->login(); |
100
|
1 |
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Determine if file is a directory. |
104
|
|
|
* |
105
|
|
|
* @param string $name |
106
|
|
|
* @return bool |
107
|
|
|
*/ |
108
|
1 |
|
public function isDir(string $name) |
109
|
|
|
{ |
110
|
1 |
|
$current = $this->pwd(); |
111
|
|
|
try { |
112
|
1 |
|
$this->chDir($name); |
113
|
1 |
|
$this->chDir($current); |
114
|
1 |
|
return true; |
115
|
1 |
|
} catch (\Exception $e) { |
116
|
|
|
// do nothing |
117
|
|
|
} |
118
|
1 |
|
return false; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Returns to the home directory. |
123
|
|
|
* |
124
|
|
|
* @return void |
125
|
|
|
*/ |
126
|
|
|
public function chHome() |
127
|
|
|
{ |
128
|
|
|
$this->chDir(''); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Return list of all files in directory. |
133
|
|
|
* |
134
|
|
|
* @param string $path |
135
|
|
|
* @return \SebastianFeldmann\Ftp\File[] |
136
|
|
|
* @throws \Exception |
137
|
|
|
*/ |
138
|
1 |
|
public function ls(string $path = '') : array |
139
|
|
|
{ |
140
|
1 |
|
return version_compare(PHP_VERSION, '7.2.0', '>=') |
141
|
|
|
? $this->ls72($path) |
142
|
1 |
|
: $this->lsLegacy($path); |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Return list of all files in directory for php 7.2.0 and higher. |
148
|
|
|
* |
149
|
|
|
* @param string $path |
150
|
|
|
* @return \SebastianFeldmann\Ftp\File[] |
151
|
|
|
* @throws \Exception |
152
|
|
|
*/ |
153
|
|
|
private function ls72(string $path) : array |
154
|
|
|
{ |
155
|
|
|
$list = []; |
156
|
|
|
foreach ($this->mlsd($path) as $fileInfo) { |
157
|
|
|
$list[] = new File($fileInfo); |
158
|
|
|
} |
159
|
|
|
return $list; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Return list of all files in directory for php version below 7.2.0. |
164
|
|
|
* |
165
|
|
|
* @param string $path |
166
|
|
|
* @return \SebastianFeldmann\Ftp\File[] |
167
|
|
|
* @throws \Exception |
168
|
|
|
*/ |
169
|
1 |
|
private function lsLegacy(string $path) : array |
170
|
|
|
{ |
171
|
1 |
|
$list = []; |
172
|
1 |
|
foreach ($this->nlist($path) as $name) { |
173
|
1 |
|
$type = $this->isDir($name) ? 'dir' : 'file'; |
174
|
1 |
|
$size = $this->size($name); |
175
|
1 |
|
$mtime = $this->mdtm($name); |
176
|
|
|
|
177
|
1 |
|
if ($mtime == -1) { |
178
|
|
|
throw new RuntimeException('FTP server doesnt support \'ftp_mdtm\''); |
179
|
|
|
} |
180
|
|
|
|
181
|
1 |
|
$list[] = new File(['name' => $name, 'modify' => $mtime, 'type' => $type, 'size' => $size]); |
182
|
|
|
} |
183
|
1 |
|
return $list; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Return list of directories in given path. |
188
|
|
|
* |
189
|
|
|
* @param string $path |
190
|
|
|
* @return array |
191
|
|
|
* @throws \Exception |
192
|
|
|
*/ |
193
|
|
|
public function lsDirs(string $path = '') : array |
194
|
|
|
{ |
195
|
|
|
return array_filter( |
196
|
|
|
$this->ls($path), |
197
|
|
|
function(File $file) { |
198
|
|
|
return $file->isDir(); |
199
|
|
|
} |
200
|
|
|
); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Return list of files in given path. |
205
|
|
|
* |
206
|
|
|
* @param string $path |
207
|
|
|
* @return array |
208
|
|
|
* @throws \Exception |
209
|
|
|
*/ |
210
|
|
|
public function lsFiles(string $path = '') : array |
211
|
|
|
{ |
212
|
|
|
return array_filter( |
213
|
|
|
$this->ls($path), |
214
|
|
|
function(File $file) { |
215
|
|
|
return $file->isFile(); |
216
|
|
|
} |
217
|
|
|
); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Upload local file to ftp server. |
222
|
|
|
* |
223
|
|
|
* @param string $file Path to local file that should be uploaded. |
224
|
|
|
* @param string $path Path to store the file under. |
225
|
|
|
* @param string $name Filename on the ftp server. |
226
|
|
|
* @return void |
227
|
|
|
*/ |
228
|
|
|
public function uploadFile(string $file, string $path, string $name) |
229
|
|
|
{ |
230
|
|
|
// to store the file we have to make sure the directory exists |
231
|
|
|
foreach ($this->extractDirectories($path) as $dir) { |
232
|
|
|
// if change to directory fails |
233
|
|
|
// create the dir and change into it afterwards |
234
|
|
|
try { |
235
|
|
|
$this->chDir($dir); |
236
|
|
|
} catch (\Exception $e) { |
237
|
|
|
$this->mkDir($dir); |
238
|
|
|
$this->chDir($dir); |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
if (!$this->put($name, $file, FTP_BINARY)) { |
242
|
|
|
$error = error_get_last(); |
243
|
|
|
$message = $error['message']; |
244
|
|
|
throw new RuntimeException(sprintf('error uploading file: %s - %s', $file, $message)); |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Setup local member variables by parsing the ftp url. |
250
|
|
|
* |
251
|
|
|
* @param string $url |
252
|
|
|
*/ |
253
|
1 |
|
private function setup(string $url) |
254
|
|
|
{ |
255
|
1 |
|
$parts = \parse_url($url); |
256
|
1 |
|
$this->host = $parts['host'] ?? ''; |
257
|
1 |
|
$this->port = $parts['port'] ?? 21; |
258
|
1 |
|
$this->user = $parts['user'] ?? ''; |
259
|
1 |
|
$this->password = $parts['pass'] ?? ''; |
260
|
1 |
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Setup ftp connection |
264
|
|
|
* |
265
|
|
|
* @throws \RuntimeException |
266
|
|
|
*/ |
267
|
1 |
|
private function login() |
268
|
|
|
{ |
269
|
1 |
|
if (empty($this->host)) { |
270
|
|
|
throw new RuntimeException('no host to connect to'); |
271
|
|
|
} |
272
|
|
|
|
273
|
1 |
|
$old = error_reporting(0); |
274
|
1 |
|
$link = $this->isSecure |
275
|
|
|
? ftp_ssl_connect($this->host, $this->port) |
276
|
1 |
|
: ftp_connect($this->host, $this->port); |
277
|
|
|
|
278
|
1 |
|
if (!$link) { |
|
|
|
|
279
|
|
|
error_reporting($old); |
280
|
|
|
throw new RuntimeException(sprintf('unable to connect to ftp server %s', $this->host)); |
281
|
|
|
} |
282
|
|
|
|
283
|
1 |
|
$this->connection = $link; |
|
|
|
|
284
|
1 |
|
if (!ftp_login($this->connection, $this->user, $this->password)) { |
285
|
|
|
error_reporting($old); |
286
|
|
|
throw new RuntimeException( |
287
|
|
|
sprintf('authentication failed for %s@%s', $this->user, $this->host) |
288
|
|
|
); |
289
|
|
|
} |
290
|
|
|
// set passive mode if needed |
291
|
1 |
|
$this->pasv($this->passive); |
292
|
1 |
|
error_reporting($old); |
293
|
1 |
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Return list of remote directories to travers. |
297
|
|
|
* |
298
|
|
|
* @param string $path |
299
|
|
|
* @return array |
300
|
|
|
*/ |
301
|
|
|
private function extractDirectories(string $path) : array |
302
|
|
|
{ |
303
|
|
|
$remoteDirs = []; |
304
|
|
|
if (!empty($path)) { |
305
|
|
|
$remoteDirs = explode('/', $path); |
306
|
|
|
// fix empty first array element for absolute path |
307
|
|
|
if (substr($path, 0, 1) === '/') { |
308
|
|
|
$remoteDirs[0] = '/'; |
309
|
|
|
} |
310
|
|
|
$remoteDirs = array_filter($remoteDirs); |
311
|
|
|
} |
312
|
|
|
return $remoteDirs; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Handle all ftp_* functions. |
317
|
|
|
* |
318
|
|
|
* @param string $name |
319
|
|
|
* @param array $args |
320
|
|
|
* @return mixed |
321
|
|
|
*/ |
322
|
1 |
|
public function __call($name, $args) |
323
|
|
|
{ |
324
|
1 |
|
$function = 'ftp_' . strtolower($name); |
325
|
1 |
|
if (!function_exists($function)) { |
326
|
|
|
throw new RuntimeException(sprintf('invalid method call: %s', $function)); |
327
|
|
|
} |
328
|
1 |
|
$old = error_reporting(0); |
329
|
1 |
|
array_unshift($args, $this->connection); |
330
|
1 |
|
if (!$result = call_user_func_array($function, $args)) { |
331
|
1 |
|
$error = error_get_last(); |
332
|
1 |
|
error_reporting($old); |
333
|
1 |
|
throw new RuntimeException($error['message']); |
334
|
|
|
} |
335
|
1 |
|
error_reporting($old); |
336
|
1 |
|
return $result; |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.