1 | <?php |
||
2 | namespace Elgg\Filesystem\Directory; |
||
3 | |||
4 | use Elgg\Filesystem\Directory; |
||
5 | use Elgg\Filesystem\File; |
||
6 | use Elgg\Structs\Collection; |
||
7 | use League\Flysystem\Adapter\Local as LocalAdapter; |
||
8 | use League\Flysystem\Filesystem; |
||
9 | use League\Flysystem\Memory\MemoryAdapter; |
||
10 | |||
11 | /** |
||
12 | * A wrapper around Flysystem that implements Elgg's filesystem API. |
||
13 | * |
||
14 | * @since 1.10.0 |
||
15 | * |
||
16 | * @access private |
||
17 | */ |
||
18 | final class Fly implements Directory { |
||
19 | |||
20 | /** @var Filesystem */ |
||
21 | private $fs; |
||
22 | |||
23 | /** @var string */ |
||
24 | private $local_path; |
||
25 | |||
26 | /** @var string Path relative to the filesystem's root */ |
||
27 | private $chroot; |
||
28 | |||
29 | /** |
||
30 | * Use one of the static factory functions to create an instance. |
||
31 | * |
||
32 | * @param Filesystem $filesystem The underlying filesystem implementation. It must have the 'ListFiles' plugin. |
||
33 | * @param string $local_path Only applicable for local filesystem. |
||
34 | * @param string $chroot Path relative to the underlying filesystem root. |
||
35 | */ |
||
36 | 7 | public function __construct(Filesystem $filesystem, $local_path = '', $chroot = '') { |
|
37 | 7 | $this->fs = $filesystem; |
|
38 | 7 | $this->local_path = rtrim(strtr($local_path, '\\', '/'), "/\\"); |
|
39 | 7 | $this->chroot = $this->normalize($chroot); |
|
40 | 6 | } |
|
41 | |||
42 | /** |
||
43 | * {@inheritDoc} |
||
44 | */ |
||
45 | 3 | public function chroot($path) { |
|
46 | 3 | return new self($this->fs, $this->local_path, $path); |
|
47 | } |
||
48 | |||
49 | /** |
||
50 | * Whether this filesystem has an existing directory at the given path. |
||
51 | * |
||
52 | * @param string $path The path to the directory, relative to this filesystem. |
||
53 | * |
||
54 | * @return boolean |
||
55 | */ |
||
56 | 4 | private function isDirectory($path) { |
|
57 | 4 | $path = $this->getInternalPath($path); |
|
58 | 3 | return $this->fs->has($path) && $this->fs->get($path)->isDir(); |
|
59 | } |
||
60 | |||
61 | /** |
||
62 | * {@inheritDoc} |
||
63 | */ |
||
64 | 2 | public function isFile($path) { |
|
65 | 2 | $path = $this->getInternalPath($path); |
|
66 | 2 | return $this->fs->has($path) && $this->fs->get($path)->isFile(); |
|
67 | } |
||
68 | |||
69 | /** |
||
70 | * {@inheritDoc} |
||
71 | */ |
||
72 | 8 | public function getContents($path) { |
|
73 | 8 | return (string) $this->fs->read($this->getInternalPath($path)); |
|
74 | } |
||
75 | |||
76 | /** |
||
77 | * {@inheritDoc} |
||
78 | */ |
||
79 | 4 | public function getFile($path) { |
|
80 | 4 | if ($this->isDirectory($path)) { |
|
81 | throw new \RuntimeException("There is already a directory at that location: $path"); |
||
82 | } |
||
83 | |||
84 | 3 | return new File($this, $path); |
|
85 | } |
||
86 | |||
87 | /** |
||
88 | * {@inheritDoc} |
||
89 | */ |
||
90 | 3 | public function getFiles($path = '', $recursive = true) { |
|
91 | 3 | return $this->getEntries($path, $recursive, ['file']); |
|
92 | } |
||
93 | |||
94 | /** |
||
95 | * {@inheritDoc} |
||
96 | */ |
||
97 | 3 | public function getDirectories($path = '', $recursive = true) { |
|
98 | 3 | return $this->getEntries($path, $recursive, ['dir']); |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * List the files and directories in the given directory path. |
||
103 | * |
||
104 | * @param string $path The subdirectory path within this directory |
||
105 | * @param bool $recursive Find files and directories recursively |
||
106 | * @param string[] $types Entry types to return ('file' and/or 'dir') |
||
107 | * |
||
108 | * @return Collection<File|Directory> |
||
109 | * |
||
110 | * @throws \InvalidArgumentException |
||
111 | */ |
||
112 | 4 | protected function getEntries($path = '', $recursive = true, $types = ['file', 'dir']) { |
|
113 | 4 | $contents = $this->fs->listContents($this->getInternalPath($path), $recursive); |
|
114 | 4 | if (!$contents) { |
|
0 ignored issues
–
show
|
|||
115 | $contents = []; |
||
116 | } |
||
117 | |||
118 | 4 | $contents = array_filter($contents, function ($metadata) use ($types) { |
|
119 | 4 | return in_array($metadata['type'], $types); |
|
120 | 4 | }); |
|
121 | |||
122 | 4 | return Collection\InMemory::fromArray(array_map(function ($metadata) { |
|
123 | 4 | if ($metadata['type'] === 'file') { |
|
124 | 3 | return new File($this, $metadata['path']); |
|
125 | } |
||
126 | |||
127 | 3 | return new self($this->fs, $this->local_path, $metadata['path']); |
|
128 | 4 | }, $contents)); |
|
129 | } |
||
130 | |||
131 | /** |
||
132 | * {@inheritDoc} |
||
133 | */ |
||
134 | 15 | public function getPath($path = '') { |
|
135 | 15 | $path = $this->normalize($this->getInternalPath($path)); |
|
136 | 15 | return "{$this->local_path}/$path"; |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * Get a path suitable for passing to the underlying filesystem. |
||
141 | * |
||
142 | * @param string $path The path relative to this directory. |
||
143 | * |
||
144 | * @return string |
||
145 | * |
||
146 | * @throws \InvalidArgumentException |
||
147 | */ |
||
148 | 23 | private function getInternalPath($path) { |
|
149 | 23 | $path = strtr($path, '\\', '//'); |
|
150 | 23 | return $this->normalize("{$this->chroot}/$path"); |
|
151 | } |
||
152 | |||
153 | /** |
||
154 | * {@inheritDoc} |
||
155 | */ |
||
156 | public function includeFile($path) { |
||
157 | return include $this->getPath($path); |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * {@inheritDoc} |
||
162 | */ |
||
163 | 6 | public function putContents($path, $content) { |
|
164 | 6 | $this->fs->put($this->getInternalPath($path), $content); |
|
165 | 6 | } |
|
166 | |||
167 | /** |
||
168 | * Shorthand for generating a new local filesystem. |
||
169 | * |
||
170 | * @param string $path absolute path to directory on local filesystem. |
||
171 | * |
||
172 | * @return Directory |
||
173 | */ |
||
174 | public static function createLocal($path) { |
||
175 | $fs = new Filesystem(new LocalAdapter($path)); |
||
176 | return new self($fs, $path); |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Shorthand for generating a new in-memory-only filesystem. |
||
181 | * |
||
182 | * @return Directory |
||
183 | */ |
||
184 | 1 | public static function createInMemory() { |
|
185 | 1 | $fs = new Filesystem(new MemoryAdapter()); |
|
186 | 1 | return new self($fs); |
|
187 | } |
||
188 | |||
189 | /** |
||
190 | * Get a standardized form of the given path to work with internally. |
||
191 | * |
||
192 | * @param string $path A relative path within this filesystem |
||
193 | * |
||
194 | * @return string |
||
195 | * |
||
196 | * @throws \InvalidArgumentException |
||
197 | */ |
||
198 | 23 | private function normalize($path) { |
|
199 | |||
200 | 23 | $test_path = "/$path/"; |
|
201 | 23 | if (strpos($test_path, '/./') !== false || strpos($test_path, '/../') !== false) { |
|
202 | 1 | throw new \InvalidArgumentException('Paths cannot contain "." or ".."'); |
|
203 | } |
||
204 | |||
205 | 22 | return trim(strtr($path, '\\', '/'), "/"); |
|
206 | } |
||
207 | } |
||
208 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.