Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 157
0.00% covered (danger)
0.00%
0 / 44
CRAP
0.00% covered (danger)
0.00%
0 / 1
File
0.00% covered (danger)
0.00%
0 / 157
0.00% covered (danger)
0.00%
0 / 44
9312
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 index
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 ftpConnect
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 exists
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 put
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
306
 get
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 count
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 set
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 append
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 prepend
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 parent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sanitize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 created
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 changed
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 size
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 owner
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getOwner
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 permission
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getPermission
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 dirname
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 dirpath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 copy
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 move
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 delete
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 create
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 basename
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 extension
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isExisting
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getParent
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 createNode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 copyNode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 moveNode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 deleteNode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 putContent
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appendContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 prependContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContent
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExtension
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getDirName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDirPath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDirectory
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\System\File\Ftp
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\System\File\Ftp;
16
17use phpOMS\System\File\ContainerInterface;
18use phpOMS\System\File\ContentPutMode;
19use phpOMS\System\File\FileInterface;
20use phpOMS\System\File\Local\Directory as LocalDirectory;
21use phpOMS\System\File\Local\File as LocalFile;
22use phpOMS\System\File\PathException;
23use phpOMS\Uri\HttpUri;
24
25/**
26 * Filesystem class.
27 *
28 * Performing operations on the file system.
29 *
30 * All static implementations require a path/uri in the following form: ftp://user:pass@domain.com/path/subpath...
31 *
32 * @package phpOMS\System\File\Ftp
33 * @license OMS License 2.0
34 * @link    https://jingga.app
35 * @since   1.0.0
36 */
37class File extends FileAbstract implements FileInterface
38{
39    /**
40     * Create ftp connection
41     *
42     * @param HttpUri         $uri Ftp uri/path including username and password
43     * @param \FTP\Connection $con Connection
44     *
45     * @since 1.0.0
46     */
47    public function __construct(HttpUri $uri, \FTP\Connection $con = null)
48    {
49        $this->uri = $uri;
50        $this->con = $con ?? self::ftpConnect($this->uri);
51
52        parent::__construct($uri->getPath());
53
54        if ($this->con !== null && self::exists($this->con, $this->path)) {
55            $this->index();
56        }
57    }
58
59    /**
60     * {@inheritdoc}
61     */
62    public function index() : void
63    {
64        parent::index();
65
66        if ($this->con === null) {
67            return;
68        }
69
70        $this->size = (int) \ftp_size($this->con, $this->path);
71    }
72
73    /**
74     * Create ftp connection.
75     *
76     * @param HttpUri $http Uri
77     *
78     * @return null|\FTP\Connection
79     *
80     * @since 1.0.0
81     */
82    public static function ftpConnect(HttpUri $http) : ?\FTP\Connection
83    {
84        $con = \ftp_connect($http->host, $http->port, 10);
85        if ($con === false) {
86            return null;
87        }
88
89        $status = \ftp_login($con, $http->user, $http->pass);
90        if ($status === false) {
91            return null;
92        }
93
94        if ($http->getPath() !== '') {
95            @\ftp_chdir($con, $http->getPath());
96        }
97
98        return $con;
99    }
100
101    /**
102     * {@inheritdoc}
103     */
104    public static function exists(\FTP\Connection $con, string $path) : bool
105    {
106        if ($path === '/') {
107            return true;
108        }
109
110        $parent = LocalDirectory::parent($path);
111        $list   = \ftp_nlist($con, $parent === '' ? '/' : $parent);
112
113        if ($list === false) {
114            return false;
115        }
116
117        $pathName = LocalDirectory::name($path);
118        foreach ($list as $item) {
119            if ($pathName === LocalDirectory::name($item)) {
120                return true;
121            }
122        }
123
124        return false;
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    public static function put(\FTP\Connection $con, string $path, string $content, int $mode = ContentPutMode::REPLACE | ContentPutMode::CREATE) : bool
131    {
132        $exists = self::exists($con, $path);
133
134        if ((ContentPutMode::hasFlag($mode, ContentPutMode::APPEND) && $exists)
135            || (ContentPutMode::hasFlag($mode, ContentPutMode::PREPEND) && $exists)
136            || (ContentPutMode::hasFlag($mode, ContentPutMode::REPLACE) && $exists)
137            || (!$exists && ContentPutMode::hasFlag($mode, ContentPutMode::CREATE))
138        ) {
139            if (ContentPutMode::hasFlag($mode, ContentPutMode::APPEND) && $exists) {
140                $content .= self::get($con, $path);
141            } elseif (ContentPutMode::hasFlag($mode, ContentPutMode::PREPEND) && $exists) {
142                $content .= self::get($con, $path);
143            } elseif (!Directory::exists($con, \dirname($path))) {
144                Directory::create($con, \dirname($path), 0755, true);
145            }
146
147            $fp = \fopen('php://memory', 'r+');
148            if ($fp === false) {
149                return false; // @codeCoverageIgnore
150            }
151
152            $status = \fwrite($fp, $content);
153            if ($status === false) {
154                \fclose($fp);
155
156                return false;
157            }
158
159            \rewind($fp);
160
161            $status = @\ftp_fput($con, $path, $fp);
162            if ($status === false) {
163                \fclose($fp);
164
165                return false;
166            }
167
168            \fclose($fp);
169
170            \ftp_chmod($con, 0755, $path);
171
172            return true;
173        }
174
175        return false;
176    }
177
178    /**
179     * {@inheritdoc}
180     */
181    public static function get(\FTP\Connection $con, string $path) : string
182    {
183        if (!self::exists($con, $path)) {
184            return '';
185        }
186
187        $fp = \fopen('php://temp', 'r+');
188        if ($fp === false) {
189            return '';
190        }
191
192        $content = '';
193        if (@\ftp_fget($con, $fp, $path, \FTP_BINARY, 0)) {
194            \rewind($fp);
195            $content = \stream_get_contents($fp);
196        }
197
198        return $content === false ? '' : $content;
199    }
200
201    /**
202     * {@inheritdoc}
203     */
204    public static function count(\FTP\Connection $con, string $path, bool $recursive = true, array $ignore = []) : int
205    {
206        return 1;
207    }
208
209    /**
210     * {@inheritdoc}
211     */
212    public static function set(\FTP\Connection $con, string $path, string $content) : bool
213    {
214        return self::put($con, $path, $content, ContentPutMode::REPLACE | ContentPutMode::CREATE);
215    }
216
217    /**
218     * {@inheritdoc}
219     */
220    public static function append(\FTP\Connection $con, string $path, string $content) : bool
221    {
222        return self::put($con, $path, $content, ContentPutMode::APPEND | ContentPutMode::CREATE);
223    }
224
225    /**
226     * {@inheritdoc}
227     */
228    public static function prepend(\FTP\Connection $con, string $path, string $content) : bool
229    {
230        return self::put($con, $path, $content, ContentPutMode::PREPEND | ContentPutMode::CREATE);
231    }
232
233    /**
234     * {@inheritdoc}
235     */
236    public static function parent(string $path) : string
237    {
238        return Directory::parent(\dirname($path));
239    }
240
241    /**
242     * {@inheritdoc}
243     */
244    public static function sanitize(string $path, string $replace = '', string $invalid = '/[^\w\s\d\.\-_~,;\/\[\]\(\]]/') : string
245    {
246        return LocalFile::sanitize($path, $replace, $invalid);
247    }
248
249    /**
250     * {@inheritdoc}
251     */
252    public static function created(\FTP\Connection $con, string $path) : \DateTime
253    {
254        return self::changed($con, $path);
255    }
256
257    /**
258     * {@inheritdoc}
259     *
260     * @throws PathException
261     */
262    public static function changed(\FTP\Connection $con, string $path) : \DateTime
263    {
264        if (!self::exists($con, $path)) {
265            throw new PathException($path);
266        }
267
268        $changed = new \DateTime();
269        $time    = \ftp_mdtm($con, $path);
270
271        $changed->setTimestamp($time === false ? 0 : $time);
272
273        return $changed;
274    }
275
276    /**
277     * {@inheritdoc}
278     */
279    public static function size(\FTP\Connection $con, string $path, bool $recursive = true) : int
280    {
281        if (!self::exists($con, $path)) {
282            return -1;
283        }
284
285        return \ftp_size($con, $path);
286    }
287
288    /**
289     * {@inheritdoc}
290     *
291     * @throws PathException
292     */
293    public static function owner(\FTP\Connection $con, string $path) : string
294    {
295        if (!self::exists($con, $path)) {
296            throw new PathException($path);
297        }
298
299        return Directory::parseRawList($con, self::dirpath($path))[$path]['user'];
300    }
301
302    /**
303     * {@inheritdoc}
304     */
305    public function getOwner() : string
306    {
307        if ($this->con === null) {
308            return '';
309        }
310
311        $this->owner = Directory::parseRawList($this->con, self::dirpath($this->path))[$this->path]['user'];
312
313        return $this->owner;
314    }
315
316    /**
317     * {@inheritdoc}
318     */
319    public static function permission(\FTP\Connection $con, string $path) : int
320    {
321        if (!self::exists($con, $path)) {
322            return -1;
323        }
324
325        return Directory::parseRawList($con, self::dirpath($path))[$path]['permission'];
326    }
327
328    /**
329     * {@inheritdoc}
330     */
331    public function getPermission() : int
332    {
333        if ($this->con === null) {
334            return 0;
335        }
336
337        $this->permission = Directory::parseRawList($this->con, self::dirpath($this->path))[$this->path]['permission'];
338
339        return $this->permission;
340    }
341
342    /**
343     * Gets the directory name of a file.
344     *
345     * @param string $path path of the file to get the directory name for
346     *
347     * @return string returns the directory name of the file
348     *
349     * @since 1.0.0
350     */
351    public static function dirname(string $path) : string
352    {
353        return LocalFile::dirname($path);
354    }
355
356    /**
357     * Gets the directory path of a file.
358     *
359     * @param string $path path of the file to get the directory name for
360     *
361     * @return string returns the directory name of the file
362     *
363     * @since 1.0.0
364     */
365    public static function dirpath(string $path) : string
366    {
367        return LocalFile::dirpath($path);
368    }
369
370    /**
371     * {@inheritdoc}
372     */
373    public static function copy(\FTP\Connection $con, string $from, string $to, bool $overwrite = false) : bool
374    {
375        if (!self::exists($con, $from)
376            || (!$overwrite && self::exists($con, $to))
377        ) {
378            return false;
379        }
380
381        $download = self::get($con, $from);
382        $upload   = self::put($con, $to, $download);
383
384        if (!$upload) {
385            return false;
386        }
387
388        return self::exists($con, $to);
389    }
390
391    /**
392     * {@inheritdoc}
393     */
394    public static function move(\FTP\Connection $con, string $from, string $to, bool $overwrite = false) : bool
395    {
396        $result = self::copy($con, $from, $to, $overwrite);
397
398        if (!$result) {
399            return false;
400        }
401
402        self::delete($con, $from);
403
404        return true;
405    }
406
407    /**
408     * {@inheritdoc}
409     */
410    public static function delete(\FTP\Connection $con, string $path) : bool
411    {
412        if (!self::exists($con, $path)) {
413            return false;
414        }
415
416        try {
417            return \ftp_delete($con, $path);
418        } catch (\Throwable $_) {
419            return false;
420        }
421    }
422
423    /**
424     * {@inheritdoc}
425     */
426    public static function create(\FTP\Connection $con, string $path) : bool
427    {
428        return self::put($con, $path, '', ContentPutMode::CREATE);
429    }
430
431    /**
432     * {@inheritdoc}
433     */
434    public static function name(string $path) : string
435    {
436        return LocalFile::name($path);
437    }
438
439    /**
440     * {@inheritdoc}
441     */
442    public static function basename(string $path) : string
443    {
444        return LocalFile::basename($path);
445    }
446
447    /**
448     * {@inheritdoc}
449     */
450    public static function extension(string $path) : string
451    {
452        return LocalFile::extension($path);
453    }
454
455    /**
456     * Check if the file exists
457     *
458     * @return bool
459     *
460     * @since 1.0.0
461     */
462    public function isExisting() : bool
463    {
464        if ($this->con === null) {
465            return false;
466        }
467
468        return self::exists($this->con, $this->path);
469    }
470
471    /**
472     * Get the parent path of the resource.
473     *
474     * The parent resource path is always a directory.
475     *
476     * @return ContainerInterface
477     *
478     * @since 1.0.0
479     */
480    public function getParent() : ContainerInterface
481    {
482        $uri = clone $this->uri;
483        $uri->setPath(self::parent($this->path));
484
485        return new Directory($uri, true, $this->con);
486    }
487
488    /**
489     * Create resource at destination path.
490     *
491     * @return bool True on success and false on failure
492     *
493     * @since 1.0.0
494     */
495    public function createNode() : bool
496    {
497        if ($this->con === null) {
498            return false;
499        }
500
501        return self::create($this->con, $this->uri->getPath());
502    }
503
504    /**
505     * Copy resource to different location.
506     *
507     * @param string $to        Path of the resource to copy to
508     * @param bool   $overwrite Overwrite/replace existing file
509     *
510     * @return bool True on success and false on failure
511     *
512     * @since 1.0.0
513     */
514    public function copyNode(string $to, bool $overwrite = false) : bool
515    {
516        if ($this->con === null) {
517            return false;
518        }
519
520        return self::copy($this->con, $this->path, $to, $overwrite);
521    }
522
523    /**
524     * Move resource to different location.
525     *
526     * @param string $to        Path of the resource to move to
527     * @param bool   $overwrite Overwrite/replace existing file
528     *
529     * @return bool True on success and false on failure
530     *
531     * @since 1.0.0
532     */
533    public function moveNode(string $to, bool $overwrite = false) : bool
534    {
535        if ($this->con === null) {
536            return false;
537        }
538
539        return self::move($this->con, $this->path, $to, $overwrite);
540    }
541
542    /**
543     * Delete resource at destination path.
544     *
545     * @return bool True on success and false on failure
546     *
547     * @since 1.0.0
548     */
549    public function deleteNode() : bool
550    {
551        if ($this->con === null) {
552            return false;
553        }
554
555        return self::delete($this->con, $this->path);
556    }
557
558    /**
559     * Save content to file.
560     *
561     * @param string $content Content to save in file
562     * @param int    $mode    Mode (overwrite, append)
563     *
564     * @return bool
565     *
566     * @since 1.0.0
567     */
568    public function putContent(string $content, int $mode = ContentPutMode::APPEND | ContentPutMode::CREATE) : bool
569    {
570        if ($this->con === null) {
571            return false;
572        }
573
574        return self::put($this->con, $this->path, $content, $mode);
575    }
576
577    /**
578     * Save content to file.
579     *
580     * Creates new file if it doesn't exist or overwrites existing file.
581     *
582     * @param string $content Content to save in file
583     *
584     * @return bool
585     *
586     * @since 1.0.0
587     */
588    public function setContent(string $content) : bool
589    {
590        return $this->putContent($content, ContentPutMode::REPLACE | ContentPutMode::CREATE);
591    }
592
593    /**
594     * Save content to file.
595     *
596     * Creates new file if it doesn't exist or overwrites existing file.
597     *
598     * @param string $content Content to save in file
599     *
600     * @return bool
601     *
602     * @since 1.0.0
603     */
604    public function appendContent(string $content) : bool
605    {
606        return $this->putContent($content, ContentPutMode::APPEND);
607    }
608
609    /**
610     * Save content to file.
611     *
612     * Creates new file if it doesn't exist or overwrites existing file.
613     *
614     * @param string $content Content to save in file
615     *
616     * @return bool
617     *
618     * @since 1.0.0
619     */
620    public function prependContent(string $content) : bool
621    {
622        return $this->putContent($content, ContentPutMode::PREPEND);
623    }
624
625    /**
626     * Get content from file.
627     *
628     * @return string Content of file
629     *
630     * @since 1.0.0
631     */
632    public function getContent() : string
633    {
634        if ($this->con === null) {
635            return '';
636        }
637
638        return self::get($this->con, $this->path);
639    }
640
641    /**
642     * {@inheritdoc}
643     */
644    public function getName() : string
645    {
646        return \explode('.', $this->name)[0];
647    }
648
649    /**
650     * {@inheritdoc}
651     */
652    public function getExtension() : string
653    {
654        $extension = \explode('.', $this->name);
655
656        return $extension[1] ?? '';
657    }
658
659    /**
660     * Gets the directory name of a file.
661     *
662     * @return string returns the directory name of the file
663     *
664     * @since 1.0.0
665     */
666    public function getDirName() : string
667    {
668        return \basename(\dirname($this->path));
669    }
670
671    /**
672     * Gets the directory path of a file.
673     *
674     * @return string returns the directory path of the file
675     *
676     * @since 1.0.0
677     */
678    public function getDirPath() : string
679    {
680        return \dirname($this->path);
681    }
682
683    /**
684     * Get directory of the file
685     *
686     * @return ContainerInterface
687     *
688     * @since 1.0.0
689     */
690    public function getDirectory() : ContainerInterface
691    {
692        $uri = clone $this->uri;
693        $uri->setPath(self::dirpath($this->path));
694
695        return new Directory($uri, true, $this->con);
696    }
697}