Lines 93.18% 41 / 44
Methods 76.92% 10 / 13
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 4 / 4 100.00% 1 / 1 2
 __destruct 100.00% 2 / 2 100.00% 1 / 1 2
 eof 100.00% 1 / 1 100.00% 1 / 1 1
 getChunk n/a 0 / 0 n/a 0 / 0 0
 putChunk n/a 0 / 0 n/a 0 / 0 0
 delete 83.33% 5 / 6 0.00% 0 / 1 5.12
 getMode 100.00% 1 / 1 100.00% 1 / 1 1
 setMode 100.00% 6 / 6 100.00% 1 / 1 2
 copyToSimple 66.66% 2 / 3 0.00% 0 / 1 2.15
 isOk 100.00% 1 / 1 100.00% 1 / 1 1
 deleteDir 93.75% 15 / 16 0.00% 0 / 1 9.02
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] moveTo 100.00% 1 / 1 100.00% 1 / 1 2
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] copyTo n/a 0 / 0 n/a 0 / 0 0
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] isEncrypted 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] getPath 100.00% 1 / 1 100.00% 1 / 1 1
 [phpMyFAQ\Attachment\Filesystem\AbstractEntry] __toString 100.00% 1 / 1 100.00% 1 / 1 1
36abstract class AbstractFile extends AbstractEntry
37{
38    public const string MODE_READ = 'rb';
39
40    public const string MODE_APPEND = 'ab';
41
42    public const string MODE_WRITE = 'wb';
43
44    /**
45     * Constructor.
46     *
47     * @param string $filepath path to file
48     * @param string $mode     mode for fopen
49     * @throws FileException
50     */
51    public function __construct(
52        string $filepath,
53        protected string $mode = self::MODE_READ,
54    ) {
55        $this->path = $filepath;
56
57        $this->handle = fopen($this->path, $this->mode);
58
59        if (!is_resource($this->handle)) {
60            throw new FileException('Could not open file: ' . $this->path);
61        }
62    }
63
64    /**
65     * Destructor.
66     */
67    public function __destruct()
68    {
69        if (is_resource($this->handle)) {
70            fclose($this->handle);
71        }
72    }
73
74    /**
75     * Either EOF was reached.
76     */
77    public function eof(): bool
78    {
79        return feof($this->handle);
80    }
81
82    /**
83     * Get the next file chunk.
84     */
85    abstract public function getChunk(): string;
86
87    /**
88     * Put chunk into file.
89     *
90     * @param string $chunk chunk to write
91     * @return int|bool bytes written or false
92     */
93    abstract public function putChunk(string $chunk): int|bool;
94
95    /**
96     * Deletes the file.
97     *
98     * @throws FileException
99     */
100    public function delete(): bool
101    {
102        if ($this->handle) {
103            fclose($this->handle);
104        }
105
106        // Read the uploaded temp path directly from $_FILES instead of
107        // rebuilding a Symfony Request. Request::createFromGlobals() eagerly
108        // constructs an UploadedFile with path validation, which throws a
109        // FileNotFoundException here because move_uploaded_file() has already
110        // moved the temp file away during the preceding copyTo() call.
111        $uploadedTmpName = $_FILES['userfile']['tmp_name'] ?? null;
112        if (is_string($uploadedTmpName) && $this->path !== $uploadedTmpName && file_exists($this->path)) {
113            return $this->deleteDir(dirname($this->path));
114        }
115
116        return true;
117    }
118
119    /**
120     * Return current file mode.
121     */
122    public function getMode(): string
123    {
124        return $this->mode;
125    }
126
127    /**
128     * Reopen a file in given mode.
129     *
130     * @param string $mode file mode
131     */
132    public function setMode($mode): bool
133    {
134        $retval = false;
135
136        if (in_array($mode, [self::MODE_WRITE, self::MODE_READ, self::MODE_APPEND], strict: true)) {
137            fclose($this->handle);
138            $this->handle = fopen($this->path, $mode);
139
140            $retval = is_resource($this->handle);
141        }
142
143        return $retval;
144    }
145
146    /**
147     * Simple copy file.
148     *
149     * @param string $target filepath
150     */
151    public function copyToSimple($target): bool
152    {
153        if (is_uploaded_file($this->path)) {
154            return move_uploaded_file($this->path, $target);
155        }
156
157        return copy($this->path, $target);
158    }
159
160    /**
161     * Self check.
162     */
163    public function isOk(): bool
164    {
165        return is_resource($this->handle);
166    }
167
168    /**
169     * Recursive deletion of path and file.
170     *
171     * @param string $path
172     *
173     * @throws FileException
174     */
175    public function deleteDir($path): bool
176    {
177        if (!file_exists($path)) {
178            throw new FileException(sprintf("Directory %s doesn't exist.", $path));
179        }
180
181        $it = new RecursiveIteratorIterator(
182            new RecursiveDirectoryIterator($path),
183            RecursiveIteratorIterator::CHILD_FIRST,
184        );
185
186        foreach ($it as $file) {
187            if (!$file instanceof \SplFileInfo || in_array($file->getBasename(), ['.', '..'], strict: true)) {
188                continue;
189            }
190
191            if ($file->isDir()) {
192                rmdir($file->getPathname());
193            }
194
195            if ($file->isFile() || $file->isLink()) {
196                if (!is_writable($file->getPathname())) {
197                    throw new FileException("File can't be deleted.");
198                }
199
200                unlink($file->getPathname());
201            }
202        }
203
204        return rmdir($path);
205    }
206}

Inherited from phpMyFAQ\Attachment\Filesystem\AbstractEntry

46    public function moveTo(object|string $entry): bool
47    {
48        return $this->copyTo($entry) && $this->delete();
49    }
56    abstract public function copyTo(object|string $entry): bool;
66    public function isEncrypted(): bool
67    {
68        return false;
69    }
74    public function getPath(): string
75    {
76        return $this->path;
77    }
82    public function __toString(): string
83    {
84        return $this->path;
85    }