Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
59.46% |
66 / 111 |
|
60.00% |
15 / 25 |
CRAP | |
0.00% |
0 / 1 |
FileSystem | |
59.46% |
66 / 111 |
|
60.00% |
15 / 25 |
122.35 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
findAllFilesAbsolutePaths | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
getAttributes | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
exists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
fileExists | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
read | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
readStream | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
listContents | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
lastModified | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
fileSize | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
mimeType | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
visibility | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
write | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
writeStream | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
setVisibility | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
delete | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
deleteDirectory | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
createDirectory | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
move | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
copy | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getRelativePath | |
58.82% |
10 / 17 |
|
0.00% |
0 / 1 |
8.51 | |||
getProjectRelativePath | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
isSymlinkedFile | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
isSubDirOf | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
normalize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * This class extends Flysystem's Filesystem class to add some additional functionality, particularly around |
4 | * symlinks which are not supported by Flysystem. |
5 | * |
6 | * TODO: Delete and modify operations on files in symlinked directories should fail with a warning. |
7 | * |
8 | * @see https://github.com/thephpleague/flysystem/issues/599 |
9 | */ |
10 | |
11 | namespace BrianHenryIE\Strauss\Helpers; |
12 | |
13 | use BrianHenryIE\Strauss\Files\FileBase; |
14 | use Elazar\Flystream\StripProtocolPathNormalizer; |
15 | use League\Flysystem\DirectoryListing; |
16 | use League\Flysystem\FileAttributes; |
17 | use League\Flysystem\FilesystemOperator; |
18 | use League\Flysystem\FilesystemReader; |
19 | use League\Flysystem\PathNormalizer; |
20 | use League\Flysystem\StorageAttributes; |
21 | |
22 | class FileSystem implements FilesystemOperator, FlysystemBackCompatInterface |
23 | { |
24 | use FlysystemBackCompatTrait; |
25 | |
26 | protected FilesystemOperator $flysystem; |
27 | protected PathNormalizer $normalizer; |
28 | |
29 | protected string $workingDir; |
30 | |
31 | /** |
32 | * TODO: maybe restrict the constructor to only accept a LocalFilesystemAdapter. |
33 | * |
34 | * TODO: Check are any of these methods unused |
35 | */ |
36 | public function __construct(FilesystemOperator $flysystem, string $workingDir) |
37 | { |
38 | $this->flysystem = $flysystem; |
39 | $this->normalizer = new StripProtocolPathNormalizer('mem'); |
40 | |
41 | $this->workingDir = $workingDir; |
42 | } |
43 | |
44 | /** |
45 | * @param string[] $fileAndDirPaths |
46 | * |
47 | * @return string[] |
48 | */ |
49 | public function findAllFilesAbsolutePaths(array $fileAndDirPaths): array |
50 | { |
51 | $files = []; |
52 | |
53 | foreach ($fileAndDirPaths as $path) { |
54 | if (!$this->directoryExists($path)) { |
55 | $files[] = $path; |
56 | continue; |
57 | } |
58 | |
59 | $directoryListing = $this->listContents( |
60 | $path, |
61 | FilesystemReader::LIST_DEEP |
62 | ); |
63 | |
64 | /** @var FileAttributes[] $files */ |
65 | $fileAttributesArray = $directoryListing->toArray(); |
66 | |
67 | $f = array_map(fn($file) => '/'.$file->path(), $fileAttributesArray); |
68 | |
69 | $files = array_merge($files, $f); |
70 | } |
71 | |
72 | return $files; |
73 | } |
74 | |
75 | public function getAttributes(string $absolutePath): ?StorageAttributes |
76 | { |
77 | $fileDirectory = realpath(dirname($absolutePath)); |
78 | |
79 | $absolutePath = $this->normalizer->normalizePath($absolutePath); |
80 | |
81 | // Unsupported symbolic link encountered at location //home |
82 | // \League\Flysystem\SymbolicLinkEncountered |
83 | $dirList = $this->listContents($fileDirectory)->toArray(); |
84 | foreach ($dirList as $file) { // TODO: use the generator. |
85 | if ($file->path() === $absolutePath) { |
86 | return $file; |
87 | } |
88 | } |
89 | |
90 | return null; |
91 | } |
92 | |
93 | public function exists(string $location): bool |
94 | { |
95 | return $this->fileExists($location) || $this->directoryExists($location); |
96 | } |
97 | |
98 | public function fileExists(string $location): bool |
99 | { |
100 | return $this->flysystem->fileExists( |
101 | $this->normalizer->normalizePath($location) |
102 | ); |
103 | } |
104 | |
105 | public function read(string $location): string |
106 | { |
107 | return $this->flysystem->read( |
108 | $this->normalizer->normalizePath($location) |
109 | ); |
110 | } |
111 | |
112 | public function readStream(string $location) |
113 | { |
114 | return $this->flysystem->readStream( |
115 | $this->normalizer->normalizePath($location) |
116 | ); |
117 | } |
118 | |
119 | public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing |
120 | { |
121 | return $this->flysystem->listContents( |
122 | $this->normalizer->normalizePath($location), |
123 | $deep |
124 | ); |
125 | } |
126 | |
127 | public function lastModified(string $path): int |
128 | { |
129 | return $this->flysystem->lastModified( |
130 | $this->normalizer->normalizePath($path) |
131 | ); |
132 | } |
133 | |
134 | public function fileSize(string $path): int |
135 | { |
136 | return $this->flysystem->fileSize( |
137 | $this->normalizer->normalizePath($path) |
138 | ); |
139 | } |
140 | |
141 | public function mimeType(string $path): string |
142 | { |
143 | return $this->flysystem->mimeType( |
144 | $this->normalizer->normalizePath($path) |
145 | ); |
146 | } |
147 | |
148 | public function visibility(string $path): string |
149 | { |
150 | return $this->flysystem->visibility( |
151 | $this->normalizer->normalizePath($path) |
152 | ); |
153 | } |
154 | |
155 | public function write(string $location, string $contents, array $config = []): void |
156 | { |
157 | $this->flysystem->write( |
158 | $this->normalizer->normalizePath($location), |
159 | $contents, |
160 | $config |
161 | ); |
162 | } |
163 | |
164 | public function writeStream(string $location, $contents, array $config = []): void |
165 | { |
166 | $this->flysystem->writeStream( |
167 | $this->normalizer->normalizePath($location), |
168 | $contents, |
169 | $config |
170 | ); |
171 | } |
172 | |
173 | public function setVisibility(string $path, string $visibility): void |
174 | { |
175 | $this->flysystem->setVisibility( |
176 | $this->normalizer->normalizePath($path), |
177 | $visibility |
178 | ); |
179 | } |
180 | |
181 | public function delete(string $location): void |
182 | { |
183 | $this->flysystem->delete( |
184 | $this->normalizer->normalizePath($location) |
185 | ); |
186 | } |
187 | |
188 | public function deleteDirectory(string $location): void |
189 | { |
190 | $this->flysystem->deleteDirectory( |
191 | $this->normalizer->normalizePath($location) |
192 | ); |
193 | } |
194 | |
195 | public function createDirectory(string $location, array $config = []): void |
196 | { |
197 | $this->flysystem->createDirectory( |
198 | $this->normalizer->normalizePath($location), |
199 | $config |
200 | ); |
201 | } |
202 | |
203 | public function move(string $source, string $destination, array $config = []): void |
204 | { |
205 | $this->flysystem->move( |
206 | $this->normalizer->normalizePath($source), |
207 | $this->normalizer->normalizePath($destination), |
208 | $config |
209 | ); |
210 | } |
211 | |
212 | public function copy(string $source, string $destination, array $config = []): void |
213 | { |
214 | $this->flysystem->copy( |
215 | $this->normalizer->normalizePath($source), |
216 | $this->normalizer->normalizePath($destination), |
217 | $config |
218 | ); |
219 | } |
220 | |
221 | /** |
222 | * |
223 | * /path/to/this/dir, /path/to/file.php => ../../file.php |
224 | * /path/to/here, /path/to/here/dir/file.php => dir/file.php |
225 | * |
226 | * @param string $fromAbsoluteDirectory |
227 | * @param string $toAbsolutePath |
228 | * @return string |
229 | */ |
230 | public function getRelativePath(string $fromAbsoluteDirectory, string $toAbsolutePath): string |
231 | { |
232 | $fromAbsoluteDirectory = $this->normalizer->normalizePath($fromAbsoluteDirectory); |
233 | $toAbsolutePath = $this->normalizer->normalizePath($toAbsolutePath); |
234 | |
235 | $fromDirectoryParts = array_filter(explode('/', $fromAbsoluteDirectory)); |
236 | $toPathParts = array_filter(explode('/', $toAbsolutePath)); |
237 | foreach ($fromDirectoryParts as $key => $part) { |
238 | if ($part === $toPathParts[$key]) { |
239 | unset($toPathParts[$key]); |
240 | unset($fromDirectoryParts[$key]); |
241 | } else { |
242 | break; |
243 | } |
244 | if (count($fromDirectoryParts) === 0 || count($toPathParts) === 0) { |
245 | break; |
246 | } |
247 | } |
248 | |
249 | $relativePath = |
250 | str_repeat('../', count($fromDirectoryParts)) |
251 | . implode('/', $toPathParts); |
252 | |
253 | if ($this->directoryExists($toAbsolutePath)) { |
254 | $relativePath .= '/'; |
255 | } |
256 | |
257 | return $relativePath; |
258 | } |
259 | |
260 | public function getProjectRelativePath(string $absolutePath): string |
261 | { |
262 | |
263 | // What will happen with strings that are not paths?! |
264 | |
265 | return $this->getRelativePath( |
266 | $this->workingDir, |
267 | $absolutePath |
268 | ); |
269 | } |
270 | |
271 | |
272 | /** |
273 | * Check does the filepath point to a file outside the working directory. |
274 | * If `realpath()` fails to resolve the path, assume it's a symlink. |
275 | */ |
276 | public function isSymlinkedFile(FileBase $file): bool |
277 | { |
278 | $realpath = realpath($file->getSourcePath()); |
279 | |
280 | return ! $realpath || ! str_starts_with($realpath, $this->workingDir); |
281 | } |
282 | |
283 | /** |
284 | * Does the subdir path start with the dir path? |
285 | */ |
286 | public function isSubDirOf(string $dir, string $subdir): bool |
287 | { |
288 | return str_starts_with( |
289 | $this->normalizer->normalizePath($subdir), |
290 | $this->normalizer->normalizePath($dir) |
291 | ); |
292 | } |
293 | |
294 | public function normalize(string $path) |
295 | { |
296 | return $this->normalizer->normalizePath($path); |
297 | } |
298 | } |