These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Gaufrette\Adapter; |
||
4 | |||
5 | use Gaufrette\Adapter; |
||
6 | use Gaufrette\File; |
||
7 | use Gaufrette\Filesystem; |
||
8 | |||
9 | /** |
||
10 | * Ftp adapter. |
||
11 | * |
||
12 | * @author Antoine Hérault <[email protected]> |
||
13 | */ |
||
14 | class Ftp implements Adapter, |
||
15 | FileFactory, |
||
16 | ListKeysAware, |
||
17 | SizeCalculator |
||
18 | { |
||
19 | protected $connection = null; |
||
20 | protected $directory; |
||
21 | protected $host; |
||
22 | protected $port; |
||
23 | protected $username; |
||
24 | protected $password; |
||
25 | protected $passive; |
||
26 | protected $create; |
||
27 | protected $mode; |
||
28 | protected $ssl; |
||
29 | protected $timeout; |
||
30 | protected $fileData = array(); |
||
31 | protected $utf8; |
||
32 | |||
33 | /** |
||
34 | * @param string $directory The directory to use in the ftp server |
||
35 | * @param string $host The host of the ftp server |
||
36 | * @param array $options The options like port, username, password, passive, create, mode |
||
37 | */ |
||
38 | public function __construct($directory, $host, $options = array()) |
||
39 | { |
||
40 | if (!extension_loaded('ftp')) { |
||
41 | throw new \RuntimeException('Unable to use Gaufrette\Adapter\Ftp as the FTP extension is not available.'); |
||
42 | } |
||
43 | |||
44 | $this->directory = (string) $directory; |
||
45 | $this->host = $host; |
||
46 | $this->port = isset($options['port']) ? $options['port'] : 21; |
||
47 | $this->username = isset($options['username']) ? $options['username'] : null; |
||
48 | $this->password = isset($options['password']) ? $options['password'] : null; |
||
49 | $this->passive = isset($options['passive']) ? $options['passive'] : false; |
||
50 | $this->create = isset($options['create']) ? $options['create'] : false; |
||
51 | $this->mode = isset($options['mode']) ? $options['mode'] : FTP_BINARY; |
||
52 | $this->ssl = isset($options['ssl']) ? $options['ssl'] : false; |
||
53 | $this->timeout = isset($options['timeout']) ? $options['timeout'] : 90; |
||
54 | $this->utf8 = isset($options['utf8']) ? $options['utf8'] : false; |
||
55 | } |
||
56 | |||
57 | /** |
||
58 | * {@inheritdoc} |
||
59 | */ |
||
60 | public function read($key) |
||
61 | { |
||
62 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
63 | |||
64 | $temp = fopen('php://temp', 'r+'); |
||
65 | |||
66 | if (!ftp_fget($this->getConnection(), $temp, $this->computePath($key), $this->mode)) { |
||
67 | return false; |
||
68 | } |
||
69 | |||
70 | rewind($temp); |
||
71 | $contents = stream_get_contents($temp); |
||
72 | fclose($temp); |
||
73 | |||
74 | return $contents; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * {@inheritdoc} |
||
79 | */ |
||
80 | public function write($key, $content) |
||
81 | { |
||
82 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
83 | |||
84 | $path = $this->computePath($key); |
||
85 | $directory = \Gaufrette\Util\Path::dirname($path); |
||
86 | |||
87 | $this->ensureDirectoryExists($directory, true); |
||
88 | |||
89 | $temp = fopen('php://temp', 'r+'); |
||
90 | $size = fwrite($temp, $content); |
||
91 | rewind($temp); |
||
92 | |||
93 | if (!ftp_fput($this->getConnection(), $path, $temp, $this->mode)) { |
||
94 | fclose($temp); |
||
95 | |||
96 | return false; |
||
97 | } |
||
98 | |||
99 | fclose($temp); |
||
100 | |||
101 | return $size; |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * {@inheritdoc} |
||
106 | */ |
||
107 | public function rename($sourceKey, $targetKey) |
||
108 | { |
||
109 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
110 | |||
111 | $sourcePath = $this->computePath($sourceKey); |
||
112 | $targetPath = $this->computePath($targetKey); |
||
113 | |||
114 | $this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true); |
||
115 | |||
116 | return ftp_rename($this->getConnection(), $sourcePath, $targetPath); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * {@inheritdoc} |
||
121 | */ |
||
122 | public function exists($key) |
||
123 | { |
||
124 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
125 | |||
126 | $file = $this->computePath($key); |
||
127 | $lines = ftp_rawlist($this->getConnection(), '-al ' . \Gaufrette\Util\Path::dirname($file)); |
||
128 | |||
129 | if (false === $lines) { |
||
130 | return false; |
||
131 | } |
||
132 | |||
133 | $pattern = '{(?<!->) '.preg_quote(basename($file)).'( -> |$)}m'; |
||
134 | foreach ($lines as $line) { |
||
135 | if (preg_match($pattern, $line)) { |
||
136 | return true; |
||
137 | } |
||
138 | } |
||
139 | |||
140 | return false; |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * {@inheritdoc} |
||
145 | */ |
||
146 | public function keys() |
||
147 | { |
||
148 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
149 | |||
150 | $keys = $this->fetchKeys(); |
||
151 | |||
152 | return $keys['keys']; |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * {@inheritdoc} |
||
157 | */ |
||
158 | public function listKeys($prefix = '') |
||
159 | { |
||
160 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
161 | |||
162 | preg_match('/(.*?)[^\/]*$/', $prefix, $match); |
||
163 | $directory = rtrim($match[1], '/'); |
||
164 | |||
165 | $keys = $this->fetchKeys($directory, false); |
||
166 | |||
167 | if ($directory === $prefix) { |
||
168 | return $keys; |
||
169 | } |
||
170 | |||
171 | $filteredKeys = array(); |
||
172 | View Code Duplication | foreach (array('keys', 'dirs') as $hash) { |
|
0 ignored issues
–
show
|
|||
173 | $filteredKeys[$hash] = array(); |
||
174 | foreach ($keys[$hash] as $key) { |
||
175 | if (0 === strpos($key, $prefix)) { |
||
176 | $filteredKeys[$hash][] = $key; |
||
177 | } |
||
178 | } |
||
179 | } |
||
180 | |||
181 | return $filteredKeys; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * {@inheritdoc} |
||
186 | */ |
||
187 | public function mtime($key) |
||
188 | { |
||
189 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
190 | |||
191 | $mtime = ftp_mdtm($this->getConnection(), $this->computePath($key)); |
||
192 | |||
193 | // the server does not support this function |
||
194 | if (-1 === $mtime) { |
||
195 | throw new \RuntimeException('Server does not support ftp_mdtm function.'); |
||
196 | } |
||
197 | |||
198 | return $mtime; |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * {@inheritdoc} |
||
203 | */ |
||
204 | public function delete($key) |
||
205 | { |
||
206 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
207 | |||
208 | if ($this->isDirectory($key)) { |
||
209 | return ftp_rmdir($this->getConnection(), $this->computePath($key)); |
||
210 | } |
||
211 | |||
212 | return ftp_delete($this->getConnection(), $this->computePath($key)); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * {@inheritdoc} |
||
217 | */ |
||
218 | public function isDirectory($key) |
||
219 | { |
||
220 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
221 | |||
222 | return $this->isDir($this->computePath($key)); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Lists files from the specified directory. If a pattern is |
||
227 | * specified, it only returns files matching it. |
||
228 | * |
||
229 | * @param string $directory The path of the directory to list from |
||
230 | * |
||
231 | * @return array An array of keys and dirs |
||
232 | */ |
||
233 | public function listDirectory($directory = '') |
||
234 | { |
||
235 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
236 | |||
237 | $directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory); |
||
238 | |||
239 | $items = $this->parseRawlist( |
||
240 | ftp_rawlist($this->getConnection(), '-al '.$this->directory.$directory) ?: array() |
||
241 | ); |
||
242 | |||
243 | $fileData = $dirs = array(); |
||
244 | foreach ($items as $itemData) { |
||
245 | if ('..' === $itemData['name'] || '.' === $itemData['name']) { |
||
246 | continue; |
||
247 | } |
||
248 | |||
249 | $item = array( |
||
250 | 'name' => $itemData['name'], |
||
251 | 'path' => trim(($directory ? $directory.'/' : '').$itemData['name'], '/'), |
||
252 | 'time' => $itemData['time'], |
||
253 | 'size' => $itemData['size'], |
||
254 | ); |
||
255 | |||
256 | if ('-' === substr($itemData['perms'], 0, 1)) { |
||
257 | $fileData[$item['path']] = $item; |
||
258 | } elseif ('d' === substr($itemData['perms'], 0, 1)) { |
||
259 | $dirs[] = $item['path']; |
||
260 | } |
||
261 | } |
||
262 | |||
263 | $this->fileData = array_merge($fileData, $this->fileData); |
||
264 | |||
265 | return array( |
||
266 | 'keys' => array_keys($fileData), |
||
267 | 'dirs' => $dirs, |
||
268 | ); |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * {@inheritdoc} |
||
273 | */ |
||
274 | public function createFile($key, Filesystem $filesystem) |
||
275 | { |
||
276 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
277 | |||
278 | $file = new File($key, $filesystem); |
||
279 | |||
280 | if (!array_key_exists($key, $this->fileData)) { |
||
281 | $dirname = \Gaufrette\Util\Path::dirname($key); |
||
282 | $directory = $dirname == '.' ? '' : $dirname; |
||
283 | $this->listDirectory($directory); |
||
284 | } |
||
285 | |||
286 | if (isset($this->fileData[$key])) { |
||
287 | $fileData = $this->fileData[$key]; |
||
288 | |||
289 | $file->setName($fileData['name']); |
||
290 | $file->setSize($fileData['size']); |
||
291 | } |
||
292 | |||
293 | return $file; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * @param string $key |
||
298 | * |
||
299 | * @return int |
||
300 | * |
||
301 | * @throws \RuntimeException |
||
302 | */ |
||
303 | public function size($key) |
||
304 | { |
||
305 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
306 | |||
307 | if (-1 === $size = ftp_size($this->connection, $key)) { |
||
308 | throw new \RuntimeException(sprintf('Unable to fetch the size of "%s".', $key)); |
||
309 | } |
||
310 | |||
311 | return $size; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Ensures the specified directory exists. If it does not, and the create |
||
316 | * parameter is set to TRUE, it tries to create it. |
||
317 | * |
||
318 | * @param string $directory |
||
319 | * @param bool $create Whether to create the directory if it does not |
||
320 | * exist |
||
321 | * |
||
322 | * @throws RuntimeException if the directory does not exist and could not |
||
323 | * be created |
||
324 | */ |
||
325 | protected function ensureDirectoryExists($directory, $create = false) |
||
326 | { |
||
327 | if (!$this->isDir($directory)) { |
||
328 | if (!$create) { |
||
329 | throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $directory)); |
||
330 | } |
||
331 | |||
332 | $this->createDirectory($directory); |
||
333 | } |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Creates the specified directory and its parent directories. |
||
338 | * |
||
339 | * @param string $directory Directory to create |
||
340 | * |
||
341 | * @throws RuntimeException if the directory could not be created |
||
342 | */ |
||
343 | protected function createDirectory($directory) |
||
344 | { |
||
345 | // create parent directory if needed |
||
346 | $parent = \Gaufrette\Util\Path::dirname($directory); |
||
347 | if (!$this->isDir($parent)) { |
||
348 | $this->createDirectory($parent); |
||
349 | } |
||
350 | |||
351 | // create the specified directory |
||
352 | $created = ftp_mkdir($this->getConnection(), $directory); |
||
353 | if (false === $created) { |
||
354 | throw new \RuntimeException(sprintf('Could not create the \'%s\' directory.', $directory)); |
||
355 | } |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * @param string $directory - full directory path |
||
360 | * |
||
361 | * @return bool |
||
362 | */ |
||
363 | private function isDir($directory) |
||
364 | { |
||
365 | if ('/' === $directory) { |
||
366 | return true; |
||
367 | } |
||
368 | |||
369 | if (!@ftp_chdir($this->getConnection(), $directory)) { |
||
370 | return false; |
||
371 | } |
||
372 | |||
373 | // change directory again to return in the base directory |
||
374 | ftp_chdir($this->getConnection(), $this->directory); |
||
375 | |||
376 | return true; |
||
377 | } |
||
378 | |||
379 | private function fetchKeys($directory = '', $onlyKeys = true) |
||
380 | { |
||
381 | $directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory); |
||
382 | |||
383 | $lines = ftp_rawlist($this->getConnection(), '-alR '.$this->directory.$directory); |
||
384 | |||
385 | if (false === $lines) { |
||
386 | return array('keys' => array(), 'dirs' => array()); |
||
387 | } |
||
388 | |||
389 | $regexDir = '/'.preg_quote($this->directory.$directory, '/').'\/?(.+):$/u'; |
||
390 | $regexItem = '/^(?:([d\-\d])\S+)\s+\S+(?:(?:\s+\S+){5})?\s+(\S+)\s+(.+?)$/'; |
||
391 | |||
392 | $prevLine = null; |
||
393 | $directories = array(); |
||
394 | $keys = array('keys' => array(), 'dirs' => array()); |
||
395 | |||
396 | foreach ((array) $lines as $line) { |
||
397 | if ('' === $prevLine && preg_match($regexDir, $line, $match)) { |
||
398 | $directory = $match[1]; |
||
399 | unset($directories[$directory]); |
||
400 | View Code Duplication | if ($onlyKeys) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
401 | $keys = array( |
||
402 | 'keys' => array_merge($keys['keys'], $keys['dirs']), |
||
403 | 'dirs' => array(), |
||
404 | ); |
||
405 | } |
||
406 | } elseif (preg_match($regexItem, $line, $tokens)) { |
||
407 | $name = $tokens[3]; |
||
408 | |||
409 | if ('.' === $name || '..' === $name) { |
||
410 | continue; |
||
411 | } |
||
412 | |||
413 | $path = ltrim($directory.'/'.$name, '/'); |
||
414 | |||
415 | if ('d' === $tokens[1] || '<dir>' === $tokens[2]) { |
||
416 | $keys['dirs'][] = $path; |
||
417 | $directories[$path] = true; |
||
418 | } else { |
||
419 | $keys['keys'][] = $path; |
||
420 | } |
||
421 | } |
||
422 | $prevLine = $line; |
||
423 | } |
||
424 | |||
425 | View Code Duplication | if ($onlyKeys) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
426 | $keys = array( |
||
427 | 'keys' => array_merge($keys['keys'], $keys['dirs']), |
||
428 | 'dirs' => array(), |
||
429 | ); |
||
430 | } |
||
431 | |||
432 | foreach (array_keys($directories) as $directory) { |
||
433 | $keys = array_merge_recursive($keys, $this->fetchKeys($directory, $onlyKeys)); |
||
434 | } |
||
435 | |||
436 | return $keys; |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Parses the given raw list. |
||
441 | * |
||
442 | * @param array $rawlist |
||
443 | * |
||
444 | * @return array |
||
445 | */ |
||
446 | private function parseRawlist(array $rawlist) |
||
447 | { |
||
448 | $parsed = array(); |
||
449 | foreach ($rawlist as $line) { |
||
450 | $infos = preg_split("/[\s]+/", $line, 9); |
||
451 | |||
452 | if ($this->isLinuxListing($infos)) { |
||
453 | $infos[7] = (strrpos($infos[7], ':') != 2) ? ($infos[7].' 00:00') : (date('Y').' '.$infos[7]); |
||
454 | if ('total' !== $infos[0]) { |
||
455 | $parsed[] = array( |
||
456 | 'perms' => $infos[0], |
||
457 | 'num' => $infos[1], |
||
458 | 'size' => $infos[4], |
||
459 | 'time' => strtotime($infos[5].' '.$infos[6].'. '.$infos[7]), |
||
460 | 'name' => $infos[8], |
||
461 | ); |
||
462 | } |
||
463 | } elseif (count($infos) >= 4) { |
||
464 | $isDir = (boolean) ('<dir>' === $infos[2]); |
||
465 | $parsed[] = array( |
||
466 | 'perms' => $isDir ? 'd' : '-', |
||
467 | 'num' => '', |
||
468 | 'size' => $isDir ? '' : $infos[2], |
||
469 | 'time' => strtotime($infos[0].' '.$infos[1]), |
||
470 | 'name' => $infos[3], |
||
471 | ); |
||
472 | } |
||
473 | } |
||
474 | |||
475 | return $parsed; |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Computes the path for the given key. |
||
480 | * |
||
481 | * @param string $key |
||
482 | */ |
||
483 | private function computePath($key) |
||
484 | { |
||
485 | return rtrim($this->directory, '/').'/'.$key; |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * Indicates whether the adapter has an open ftp connection. |
||
490 | * |
||
491 | * @return bool |
||
492 | */ |
||
493 | private function isConnected() |
||
494 | { |
||
495 | return is_resource($this->connection); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Returns an opened ftp connection resource. If the connection is not |
||
500 | * already opened, it open it before. |
||
501 | * |
||
502 | * @return resource The ftp connection |
||
503 | */ |
||
504 | private function getConnection() |
||
505 | { |
||
506 | if (!$this->isConnected()) { |
||
507 | $this->connect(); |
||
508 | } |
||
509 | |||
510 | return $this->connection; |
||
511 | } |
||
512 | |||
513 | /** |
||
514 | * Opens the adapter's ftp connection. |
||
515 | * |
||
516 | * @throws RuntimeException if could not connect |
||
517 | */ |
||
518 | private function connect() |
||
519 | { |
||
520 | if ($this->ssl && !function_exists('ftp_ssl_connect')) { |
||
521 | throw new \RuntimeException('This Server Has No SSL-FTP Available.'); |
||
522 | } |
||
523 | |||
524 | // open ftp connection |
||
525 | if (!$this->ssl) { |
||
526 | $this->connection = ftp_connect($this->host, $this->port, $this->timeout); |
||
527 | } else { |
||
528 | $this->connection = ftp_ssl_connect($this->host, $this->port, $this->timeout); |
||
529 | } |
||
530 | |||
531 | if (!$this->connection) { |
||
532 | throw new \RuntimeException(sprintf('Could not connect to \'%s\' (port: %s).', $this->host, $this->port)); |
||
533 | } |
||
534 | |||
535 | if (defined('FTP_USEPASVADDRESS')) { |
||
536 | ftp_set_option($this->connection, FTP_USEPASVADDRESS, false); |
||
537 | } |
||
538 | |||
539 | $username = $this->username ?: 'anonymous'; |
||
540 | $password = $this->password ?: ''; |
||
541 | |||
542 | // login ftp user |
||
543 | if (!@ftp_login($this->connection, $username, $password)) { |
||
544 | $this->close(); |
||
545 | throw new \RuntimeException(sprintf('Could not login as %s.', $username)); |
||
546 | } |
||
547 | |||
548 | // switch to passive mode if needed |
||
549 | if ($this->passive && !ftp_pasv($this->connection, true)) { |
||
550 | $this->close(); |
||
551 | throw new \RuntimeException('Could not turn passive mode on.'); |
||
552 | } |
||
553 | |||
554 | // enable utf8 mode if configured |
||
555 | if($this->utf8 == true) { |
||
556 | ftp_raw($this->connection, "OPTS UTF8 ON"); |
||
557 | } |
||
558 | |||
559 | // ensure the adapter's directory exists |
||
560 | if ('/' !== $this->directory) { |
||
561 | try { |
||
562 | $this->ensureDirectoryExists($this->directory, $this->create); |
||
563 | } catch (\RuntimeException $e) { |
||
564 | $this->close(); |
||
565 | throw $e; |
||
566 | } |
||
567 | |||
568 | // change the current directory for the adapter's directory |
||
569 | if (!ftp_chdir($this->connection, $this->directory)) { |
||
570 | $this->close(); |
||
571 | throw new \RuntimeException(sprintf('Could not change current directory for the \'%s\' directory.', $this->directory)); |
||
572 | } |
||
573 | } |
||
574 | } |
||
575 | |||
576 | /** |
||
577 | * Closes the adapter's ftp connection. |
||
578 | */ |
||
579 | public function close() |
||
580 | { |
||
581 | if ($this->isConnected()) { |
||
582 | ftp_close($this->connection); |
||
583 | } |
||
584 | } |
||
585 | |||
586 | private function isLinuxListing($info) |
||
587 | { |
||
588 | return count($info) >= 9; |
||
589 | } |
||
590 | } |
||
591 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.