Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractAdapter
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
8 / 8
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setBaseDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBaseDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrentDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 chdir
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 mkdir
n/a
0 / 0
n/a
0 / 0
0
 rmdir
n/a
0 / 0
n/a
0 / 0
0
 listAll
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 listDirs
n/a
0 / 0
n/a
0 / 0
0
 listFiles
n/a
0 / 0
n/a
0 / 0
0
 putFile
n/a
0 / 0
n/a
0 / 0
0
 putFileContents
n/a
0 / 0
n/a
0 / 0
0
 putFileStream
n/a
0 / 0
n/a
0 / 0
0
 uploadFile
n/a
0 / 0
n/a
0 / 0
0
 copyFile
n/a
0 / 0
n/a
0 / 0
0
 copyFileToExternal
n/a
0 / 0
n/a
0 / 0
0
 copyFileFromExternal
n/a
0 / 0
n/a
0 / 0
0
 moveFileToExternal
n/a
0 / 0
n/a
0 / 0
0
 moveFileFromExternal
n/a
0 / 0
n/a
0 / 0
0
 renameFile
n/a
0 / 0
n/a
0 / 0
0
 replaceFileContents
n/a
0 / 0
n/a
0 / 0
0
 deleteFile
n/a
0 / 0
n/a
0 / 0
0
 fetchFile
n/a
0 / 0
n/a
0 / 0
0
 fetchFileStream
n/a
0 / 0
n/a
0 / 0
0
 fetchFileInfo
n/a
0 / 0
n/a
0 / 0
0
 getTemporaryUrl
n/a
0 / 0
n/a
0 / 0
0
 fileExists
n/a
0 / 0
n/a
0 / 0
0
 isDir
n/a
0 / 0
n/a
0 / 0
0
 isFile
n/a
0 / 0
n/a
0 / 0
0
 getFileSize
n/a
0 / 0
n/a
0 / 0
0
 getFileType
n/a
0 / 0
n/a
0 / 0
0
 getFileMTime
n/a
0 / 0
n/a
0 / 0
0
 md5File
n/a
0 / 0
n/a
0 / 0
0
 scrub
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 searchFilter
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Storage\Adapter;
16
17use Pop\Storage\StorageInterface;
18use Pop\Storage\Exception\PathTraversalException;
19
20/**
21 * Storage adapter abstract class
22 *
23 * @category   Pop
24 * @package    Pop\Storage
25 * @author     Nick Sagona, III <nick@popphp.org>
26 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
27 * @license    https://www.popphp.org/license     New BSD License
28 * @version    3.0.0
29 */
30abstract class AbstractAdapter implements StorageInterface
31{
32
33    /**
34     * Storage base directory
35     * @var ?string
36     */
37    protected ?string $baseDirectory = null;
38
39    /**
40     * Current directory
41     * @var ?string
42     */
43    protected ?string $directory = null;
44
45    /**
46     * Constructor
47     *
48     * @param string $directory
49     */
50    public function __construct(string $directory)
51    {
52        $this->setBaseDir($directory);
53        $this->chdir();
54    }
55
56    /**
57     * Set base directory
58     *
59     * @param  ?string $directory
60     * @return void
61     */
62    public function setBaseDir(?string $directory = null): void
63    {
64        $this->baseDirectory = $directory;
65    }
66
67    /**
68     * Get base directory
69     *
70     * @return ?string
71     */
72    public function getBaseDir(): ?string
73    {
74        return $this->baseDirectory;
75    }
76
77    /**
78     * Get current directory
79     *
80     * @return ?string
81     */
82    public function getCurrentDir(): ?string
83    {
84        return $this->directory;
85    }
86
87    /**
88     * Change directory
89     *
90     * @param  ?string $directory
91     * @return void
92     */
93    public function chdir(?string $directory = null): void
94    {
95        if ($directory === null) {
96            $this->directory = $this->baseDirectory;
97        } else {
98            $this->directory = $this->baseDirectory . DIRECTORY_SEPARATOR . $this->scrub($directory);
99        }
100    }
101
102    /**
103     * Make directory
104     *
105     * @param  string $directory
106     * @return void
107     */
108    abstract public function mkdir(string $directory): void;
109
110    /**
111     * Remove a directory
112     *
113     * @param  string $directory
114     * @return void
115     */
116    abstract public function rmdir(string $directory): void;
117
118    /**
119     * List all
120     *
121     * @param  ?string $search
122     * @param  bool    $recursive
123     * @return array
124     */
125    function listAll(?string $search = null, bool $recursive = false): array
126    {
127        return array_merge($this->listDirs($search, $recursive), $this->listFiles($search, $recursive));
128    }
129
130    /**
131     * List directories
132     *
133     * @param  ?string $search
134     * @param  bool    $recursive
135     * @return array
136     */
137    abstract public function listDirs(?string $search = null, bool $recursive = false): array;
138
139    /**
140     * List files
141     *
142     * @param  ?string $search
143     * @param  bool    $recursive
144     * @return array
145     */
146    abstract public function listFiles(?string $search = null, bool $recursive = false): array;
147
148    /**
149     * Put file
150     *
151     * @param  string $fileFrom
152     * @param  bool   $copy
153     * @return void
154     */
155    abstract public function putFile(string $fileFrom, bool $copy = true): void;
156
157    /**
158     * Put file contents
159     *
160     * @param  string $filename
161     * @param  string $fileContents
162     * @return void
163     */
164    abstract public function putFileContents(string $filename, string $fileContents): void;
165
166    /**
167     * Put file from a stream resource
168     *
169     * @param  string $filename
170     * @param  mixed  $resource
171     * @return void
172     */
173    abstract public function putFileStream(string $filename, mixed $resource): void;
174
175    /**
176     * Upload file from server request $_FILES['file']
177     *
178     * @param  array $file
179     * @return void
180     */
181    abstract public function uploadFile(array $file): void;
182
183    /**
184     * Copy file
185     *
186     * @param  string $sourceFile
187     * @param  string $destFile
188     * @return void
189     */
190    abstract public function copyFile(string $sourceFile, string $destFile): void;
191
192
193    /**
194     * Copy file to a location external to the current location
195     *
196     * @param  string $sourceFile
197     * @param  string $externalFile
198     * @return void
199     */
200    abstract public function copyFileToExternal(string $sourceFile, string $externalFile): void;
201
202    /**
203     * Copy file from a location external to the current location
204     *
205     * @param  string $externalFile
206     * @param  string $destFile
207     * @return void
208     */
209    abstract public function copyFileFromExternal(string $externalFile, string $destFile): void;
210
211    /**
212     * Move file to a location external to the current location
213     *
214     * @param  string $sourceFile
215     * @param  string $externalFile
216     * @return void
217     */
218    abstract public function moveFileToExternal(string $sourceFile, string $externalFile): void;
219
220    /**
221     * Move file from a location external to the current location
222     *
223     * @param  string $externalFile
224     * @param  string $destFile
225     * @return void
226     */
227    abstract public function moveFileFromExternal(string $externalFile, string $destFile): void;
228
229    /**
230     * Rename file
231     *
232     * @param  string $oldFile
233     * @param  string $newFile
234     * @return void
235     */
236    abstract public function renameFile(string $oldFile, string $newFile): void;
237
238    /**
239     * Replace file
240     *
241     * @param  string $filename
242     * @param  string $fileContents
243     * @return void
244     */
245    abstract public function replaceFileContents(string $filename, string $fileContents): void;
246
247    /**
248     * Delete file
249     *
250     * @param  string $filename
251     * @return void
252     */
253    abstract public function deleteFile(string $filename): void;
254
255    /**
256     * Fetch file contents
257     *
258     * @param  string $filename
259     * @return mixed
260     */
261    abstract public function fetchFile(string $filename): mixed;
262
263    /**
264     * Fetch file as a stream resource
265     *
266     * @param  string $filename
267     * @return mixed
268     */
269    abstract public function fetchFileStream(string $filename): mixed;
270
271    /**
272     * Fetch file info
273     *
274     * @param  string $filename
275     * @return array
276     */
277    abstract public function fetchFileInfo(string $filename): array;
278
279    /**
280     * Get a temporary (presigned) URL for the file, valid for $expiresInSeconds
281     *
282     * @param  string $filename
283     * @param  int    $expiresInSeconds
284     * @return string
285     */
286    abstract public function getTemporaryUrl(string $filename, int $expiresInSeconds = 900): string;
287
288    /**
289     * File exists
290     *
291     * @param  string $filename
292     * @return bool
293     */
294    abstract public function fileExists(string $filename): bool;
295
296    /**
297     * Check if is a dir
298     *
299     * @param  string $directory
300     * @return bool
301     */
302    abstract public function isDir(string $directory): bool;
303
304    /**
305     * Check if is a file
306     *
307     * @param  string $filename
308     * @return bool
309     */
310    abstract public function isFile(string $filename): bool;
311
312    /**
313     * Get file size
314     *
315     * @param  string $filename
316     * @return int
317     */
318    abstract public function getFileSize(string $filename): int;
319
320    /**
321     * Get file type
322     *
323     * @param  string $filename
324     * @return string
325     */
326    abstract public function getFileType(string $filename): string;
327
328    /**
329     * Get file modified time
330     *
331     * @param  string $filename
332     * @return int|string
333     */
334    abstract public function getFileMTime(string $filename): int|string;
335
336    /**
337     * Create MD5 checksum of the file
338     *
339     * @param  string $filename
340     * @return string
341     */
342    abstract public function md5File(string $filename): string;
343
344    /**
345     * Scrub value of leading dots or slashes
346     *
347     * @param  string $value
348     * @throws PathTraversalException
349     * @return string
350     */
351    protected function scrub(string $value): string
352    {
353        if (str_starts_with($value, '/') || str_starts_with($value, '\\')) {
354            $value = substr($value, 1);
355        } else if (str_starts_with($value, './') || str_starts_with($value, '.\\')) {
356            $value = substr($value, 2);
357        }
358
359        foreach (preg_split('#[/\\\\]#', $value) as $segment) {
360            if ($segment === '..') {
361                throw new PathTraversalException(
362                    'Error: The path \'' . $value . '\' is not allowed to traverse outside of the storage directory.'
363                );
364            }
365        }
366
367        return $value;
368    }
369
370    /**
371     * Search and filter values
372     *
373     * @param  array  $objects
374     * @param  string $search
375     * @return array
376     */
377    protected function searchFilter(array $objects, string $search): array
378    {
379        if (str_starts_with($search, '*')) {
380            $search  = substr($search, 1);
381            $objects = array_filter($objects, function ($value) use ($search) {
382                return str_ends_with($value, $search);
383            });
384        } else if (str_ends_with($search, '*')) {
385            $search  = substr($search, 0, -1);
386            $objects = array_filter($objects, function ($value) use ($search) {
387                return str_starts_with($value, $search);
388            });
389        } else {
390            $objects = array_filter($objects, function ($value) use ($search) {
391                return ($value == $search);
392            });
393        }
394
395        return $objects;
396    }
397
398}