1 | <?php |
||
2 | /** |
||
3 | * Elgg filestore. |
||
4 | * This file contains functions for saving and retrieving data from files. |
||
5 | * |
||
6 | * @package Elgg.Core |
||
7 | * @subpackage DataModel.FileStorage |
||
8 | */ |
||
9 | |||
10 | use Symfony\Component\HttpFoundation\File\UploadedFile; |
||
11 | |||
12 | /** |
||
13 | * Get the size of the specified directory. |
||
14 | * |
||
15 | * @param string $dir The full path of the directory |
||
16 | * @param int $total_size Add to current dir size |
||
17 | * |
||
18 | * @return int The size of the directory in bytes |
||
19 | */ |
||
20 | function get_dir_size($dir, $total_size = 0) { |
||
21 | $handle = @opendir($dir); |
||
22 | while ($file = @readdir($handle)) { |
||
23 | if (in_array($file, ['.', '..'])) { |
||
24 | continue; |
||
25 | } |
||
26 | if (is_dir($dir . $file)) { |
||
27 | $total_size = get_dir_size($dir . $file . "/", $total_size); |
||
28 | } else { |
||
29 | $total_size += filesize($dir . $file); |
||
30 | } |
||
31 | } |
||
32 | @closedir($handle); |
||
0 ignored issues
–
show
|
|||
33 | |||
34 | return($total_size); |
||
35 | } |
||
36 | |||
37 | /** |
||
38 | * Crops and resizes an image |
||
39 | * |
||
40 | * @param string $source Path to source image |
||
41 | * @param string $destination Path to destination |
||
42 | * If not set, will modify the source image |
||
43 | * @param array $params An array of cropping/resizing parameters |
||
44 | * - INT 'w' represents the width of the new image |
||
45 | * With upscaling disabled, this is the maximum width |
||
46 | * of the new image (in case the source image is |
||
47 | * smaller than the expected width) |
||
48 | * - INT 'h' represents the height of the new image |
||
49 | * With upscaling disabled, this is the maximum height |
||
50 | * - INT 'x1', 'y1', 'x2', 'y2' represent optional cropping |
||
51 | * coordinates. The source image will first be cropped |
||
52 | * to these coordinates, and then resized to match |
||
53 | * width/height parameters |
||
54 | * - BOOL 'square' - square images will fill the |
||
55 | * bounding box (width x height). In Imagine's terms, |
||
56 | * this equates to OUTBOUND mode |
||
57 | * - BOOL 'upscale' - if enabled, smaller images |
||
58 | * will be upscaled to fit the bounding box. |
||
59 | * @return bool |
||
60 | * @since 2.3 |
||
61 | */ |
||
62 | function elgg_save_resized_image($source, $destination = null, array $params = []) { |
||
63 | return _elgg_services()->imageService->resize($source, $destination, $params); |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * Delete a directory and all its contents |
||
68 | * |
||
69 | * @param string $directory Directory to delete |
||
70 | * |
||
71 | * @return bool |
||
72 | */ |
||
73 | function delete_directory($directory) { |
||
74 | |||
75 | 214 | if (!file_exists($directory)) { |
|
76 | 213 | return true; |
|
77 | } |
||
78 | |||
79 | 2 | if (!is_dir($directory)) { |
|
80 | return false; |
||
81 | } |
||
82 | |||
83 | // sanity check: must be a directory |
||
84 | 2 | if (!$handle = opendir($directory)) { |
|
85 | return false; |
||
86 | } |
||
87 | |||
88 | // loop through all files |
||
89 | 2 | while (($file = readdir($handle)) !== false) { |
|
90 | 2 | if (in_array($file, ['.', '..'])) { |
|
91 | 2 | continue; |
|
92 | } |
||
93 | |||
94 | 2 | $path = "$directory/$file"; |
|
95 | 2 | if (is_dir($path)) { |
|
96 | // recurse down through directory |
||
97 | 2 | if (!delete_directory($path)) { |
|
98 | 2 | return false; |
|
99 | } |
||
100 | } else { |
||
101 | // delete file |
||
102 | 1 | unlink($path); |
|
103 | } |
||
104 | } |
||
105 | |||
106 | // remove empty directory |
||
107 | 2 | closedir($handle); |
|
108 | 2 | return rmdir($directory); |
|
109 | } |
||
110 | |||
111 | /** |
||
112 | * Returns the category of a file from its MIME type |
||
113 | * |
||
114 | * @param string $mime_type The MIME type |
||
115 | * |
||
116 | * @return string 'document', 'audio', 'video', or 'general' if the MIME type was unrecognized |
||
117 | * @since 1.10 |
||
118 | */ |
||
119 | function elgg_get_file_simple_type($mime_type) { |
||
120 | 47 | $params = ['mime_type' => $mime_type]; |
|
121 | 47 | return elgg_trigger_plugin_hook('simple_type', 'file', $params, 'general'); |
|
122 | } |
||
123 | |||
124 | /** |
||
125 | * Register file-related handlers on "init, system" event |
||
126 | * |
||
127 | * @return void |
||
128 | * @access private |
||
129 | */ |
||
130 | function _elgg_filestore_init() { |
||
131 | |||
132 | // Fix MIME type detection for Microsoft zipped formats |
||
133 | 93 | elgg_register_plugin_hook_handler('mime_type', 'file', '_elgg_filestore_detect_mimetype'); |
|
134 | |||
135 | // Parse category of file from MIME type |
||
136 | 93 | elgg_register_plugin_hook_handler('simple_type', 'file', '_elgg_filestore_parse_simpletype'); |
|
137 | |||
138 | // Unit testing |
||
139 | 93 | elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_filestore_test'); |
|
140 | |||
141 | // Handler for serving embedded icons |
||
142 | 93 | elgg_register_page_handler('serve-icon', '_elgg_filestore_serve_icon_handler'); |
|
143 | |||
144 | // Touch entity icons if entity access id has changed |
||
145 | 93 | elgg_register_event_handler('update:after', 'object', '_elgg_filestore_touch_icons'); |
|
146 | 93 | elgg_register_event_handler('update:after', 'group', '_elgg_filestore_touch_icons'); |
|
147 | |||
148 | // Move entity icons if entity owner has changed |
||
149 | 93 | elgg_register_event_handler('update:after', 'object', '_elgg_filestore_move_icons'); |
|
150 | 93 | elgg_register_event_handler('update:after', 'group', '_elgg_filestore_move_icons'); |
|
151 | 93 | } |
|
152 | |||
153 | /** |
||
154 | * Fix MIME type detection for Microsoft zipped formats |
||
155 | * |
||
156 | * @param string $hook "mime_type" |
||
157 | * @param string $type "file" |
||
158 | * @param string $mime_type Detected MIME type |
||
159 | * @param array $params Hook parameters |
||
160 | * |
||
161 | * @return string The MIME type |
||
162 | * @access private |
||
163 | */ |
||
164 | function _elgg_filestore_detect_mimetype($hook, $type, $mime_type, $params) { |
||
165 | |||
166 | 37 | $original_filename = elgg_extract('original_filename', $params); |
|
167 | 37 | $ext = pathinfo($original_filename, PATHINFO_EXTENSION); |
|
168 | |||
169 | 37 | return (new \Elgg\Filesystem\MimeTypeDetector())->fixDetectionErrors($mime_type, $ext); |
|
170 | } |
||
171 | |||
172 | /** |
||
173 | * Parse a file category of file from a MIME type |
||
174 | * |
||
175 | * @param string $hook "simple_type" |
||
176 | * @param string $type "file" |
||
177 | * @param string $simple_type The category of file |
||
178 | * @param array $params Hook parameters |
||
179 | * |
||
180 | * @return string 'document', 'audio', 'video', or 'general' if the MIME type is unrecognized |
||
181 | * @access private |
||
182 | */ |
||
183 | function _elgg_filestore_parse_simpletype($hook, $type, $simple_type, $params) { |
||
184 | |||
185 | 47 | $mime_type = elgg_extract('mime_type', $params); |
|
186 | |||
187 | 47 | switch ($mime_type) { |
|
188 | 1 | case "application/msword": |
|
189 | 1 | case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": |
|
190 | 1 | case "application/pdf": |
|
191 | return "document"; |
||
192 | |||
193 | 1 | case "application/ogg": |
|
194 | return "audio"; |
||
195 | } |
||
196 | |||
197 | 47 | if (preg_match('~^(audio|image|video)/~', $mime_type, $m)) { |
|
198 | 46 | return $m[1]; |
|
199 | } |
||
200 | 1 | if (0 === strpos($mime_type, 'text/') || false !== strpos($mime_type, 'opendocument')) { |
|
201 | return "document"; |
||
202 | } |
||
203 | |||
204 | // unrecognized MIME |
||
205 | 1 | return $simple_type; |
|
206 | } |
||
207 | |||
208 | /** |
||
209 | * Unit tests for files |
||
210 | * |
||
211 | * @param string $hook 'unit_test' |
||
212 | * @param string $type 'system' |
||
213 | * @param mixed $value Array of tests |
||
214 | * |
||
215 | * @return array |
||
216 | * @access private |
||
217 | * @codeCoverageIgnore |
||
218 | */ |
||
219 | function _elgg_filestore_test($hook, $type, $value) { |
||
220 | $value[] = ElggCoreFilestoreTest::class; |
||
221 | return $value; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * Returns file's download URL |
||
226 | * |
||
227 | * @note This does not work for files with custom filestores. |
||
228 | * |
||
229 | * @param \ElggFile $file File object or entity (must have the default filestore) |
||
230 | * @param bool $use_cookie Limit URL validity to current session only |
||
231 | * @param string $expires URL expiration, as a string suitable for strtotime() |
||
232 | * @return string |
||
233 | */ |
||
234 | function elgg_get_download_url(\ElggFile $file, $use_cookie = true, $expires = '+2 hours') { |
||
235 | return $file->getDownloadURL($use_cookie, $expires); |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * Returns file's URL for inline display |
||
240 | * Suitable for displaying cacheable resources, such as user avatars |
||
241 | * |
||
242 | * @note This does not work for files with custom filestores. |
||
243 | * |
||
244 | * @param \ElggFile $file File object or entity (must have the default filestore) |
||
245 | * @param bool $use_cookie Limit URL validity to current session only |
||
246 | * @param string $expires URL expiration, as a string suitable for strtotime() |
||
247 | * @return string |
||
248 | */ |
||
249 | function elgg_get_inline_url(\ElggFile $file, $use_cookie = false, $expires = '') { |
||
250 | 1 | return $file->getInlineURL($use_cookie, $expires); |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * Returns a URL suitable for embedding entity's icon in a text editor. |
||
255 | * We can not use elgg_get_inline_url() for these purposes due to a URL structure |
||
256 | * bound to user session and file modification time. |
||
257 | * This function returns a generic (permanent) URL that will then be resolved to |
||
258 | * an inline URL whenever requested. |
||
259 | * |
||
260 | * @param \ElggEntity $entity Entity |
||
261 | * @param string $size Size |
||
262 | * @return string |
||
263 | * @since 2.2 |
||
264 | */ |
||
265 | function elgg_get_embed_url(\ElggEntity $entity, $size) { |
||
266 | return elgg_normalize_url("serve-icon/$entity->guid/$size"); |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Handler for /serve-icon resources |
||
271 | * /serve-icon/<entity_guid>/<size> |
||
272 | * |
||
273 | * @return void |
||
274 | * @access private |
||
275 | * @since 2.2 |
||
276 | */ |
||
277 | function _elgg_filestore_serve_icon_handler() { |
||
278 | $response = _elgg_services()->iconService->handleServeIconRequest(); |
||
279 | $response->send(); |
||
280 | exit; |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Reset icon URLs if access_id has changed |
||
285 | * |
||
286 | * @param string $event "update:after" |
||
287 | * @param string $type "object"|"group" |
||
288 | * @param ElggObject $entity Entity |
||
289 | * @return void |
||
290 | * @access private |
||
291 | */ |
||
292 | function _elgg_filestore_touch_icons($event, $type, $entity) { |
||
293 | 14 | $original_attributes = $entity->getOriginalAttributes(); |
|
294 | 14 | if (!array_key_exists('access_id', $original_attributes)) { |
|
295 | 9 | return; |
|
296 | } |
||
297 | 5 | if ($entity instanceof \ElggFile) { |
|
298 | // we touch the file to invalidate any previously generated download URLs |
||
299 | $entity->setModifiedTime(); |
||
300 | } |
||
301 | 5 | $sizes = array_keys(elgg_get_icon_sizes($entity->getType(), $entity->getSubtype())); |
|
302 | 5 | foreach ($sizes as $size) { |
|
303 | 5 | $icon = $entity->getIcon($size); |
|
304 | 5 | if ($icon->exists()) { |
|
305 | 5 | $icon->setModifiedTime(); |
|
306 | } |
||
307 | } |
||
308 | 5 | } |
|
309 | |||
310 | /** |
||
311 | * Listen to entity ownership changes and update icon ownership by moving |
||
312 | * icons to their new owner's directory on filestore. |
||
313 | * |
||
314 | * This will only transfer icons that have a custom location on filestore |
||
315 | * and are owned by the entity's owner (instead of the entity itself). |
||
316 | * Even though core icon service does not store icons in the entity's owner |
||
317 | * directory, there are plugins that do (e.g. file plugin) - this handler |
||
318 | * helps such plugins avoid ownership mismatch. |
||
319 | * |
||
320 | * @param string $event "update:after" |
||
321 | * @param string $type "object"|"group" |
||
322 | * @param ElggObject $entity Entity |
||
323 | * @return void |
||
324 | * @access private |
||
325 | */ |
||
326 | function _elgg_filestore_move_icons($event, $type, $entity) { |
||
327 | |||
328 | 14 | $original_attributes = $entity->getOriginalAttributes(); |
|
329 | 14 | if (empty($original_attributes['owner_guid'])) { |
|
330 | 13 | return; |
|
331 | } |
||
332 | |||
333 | 1 | $previous_owner_guid = $original_attributes['owner_guid']; |
|
334 | 1 | $new_owner_guid = $entity->owner_guid; |
|
335 | |||
336 | 1 | $sizes = elgg_get_icon_sizes($entity->getType(), $entity->getSubtype()); |
|
337 | |||
338 | 1 | foreach ($sizes as $size => $opts) { |
|
339 | 1 | $new_icon = $entity->getIcon($size); |
|
340 | 1 | if ($new_icon->owner_guid == $entity->guid) { |
|
341 | // we do not need to update icons that are owned by the entity itself |
||
342 | 1 | continue; |
|
343 | } |
||
344 | |||
345 | if ($new_icon->owner_guid != $new_owner_guid) { |
||
346 | // a plugin implements some custom logic |
||
347 | continue; |
||
348 | } |
||
349 | |||
350 | $old_icon = new \ElggIcon(); |
||
351 | $old_icon->owner_guid = $previous_owner_guid; |
||
352 | $old_icon->setFilename($new_icon->getFilename()); |
||
353 | if (!$old_icon->exists()) { |
||
354 | // there is no icon to move |
||
355 | continue; |
||
356 | } |
||
357 | |||
358 | if ($new_icon->exists()) { |
||
359 | // there is already a new icon |
||
360 | // just removing the old one |
||
361 | $old_icon->delete(); |
||
362 | elgg_log("Entity $entity->guid has been transferred to a new owner but an icon was " |
||
363 | . "left behind under {$old_icon->getFilenameOnFilestore()}. " |
||
364 | . "Old icon has been deleted", 'NOTICE'); |
||
365 | continue; |
||
366 | } |
||
367 | |||
368 | $old_icon->transfer($new_icon->owner_guid, $new_icon->getFilename()); |
||
369 | elgg_log("Entity $entity->guid has been transferred to a new owner. " |
||
370 | . "Icon was moved from {$old_icon->getFilenameOnFilestore()} to {$new_icon->getFilenameOnFilestore()}.", 'NOTICE'); |
||
371 | } |
||
372 | 1 | } |
|
373 | |||
374 | /** |
||
375 | * Returns an array of uploaded file objects regardless of upload status/errors |
||
376 | * |
||
377 | * @param string $input_name Form input name |
||
378 | * @return UploadedFile[]|false |
||
379 | */ |
||
380 | function elgg_get_uploaded_files($input_name) { |
||
381 | return _elgg_services()->uploads->getFiles($input_name); |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Returns a single valid uploaded file object |
||
386 | * |
||
387 | * @param string $input_name Form input name |
||
388 | * @param bool $check_for_validity If there is an uploaded file, is it required to be valid |
||
389 | * |
||
390 | * @return UploadedFile|false |
||
391 | */ |
||
392 | function elgg_get_uploaded_file($input_name, $check_for_validity = true) { |
||
393 | return _elgg_services()->uploads->getFile($input_name, $check_for_validity); |
||
394 | } |
||
395 | |||
396 | /** |
||
397 | * Returns a ElggTempFile which can handle writing/reading of data to a temporary file location |
||
398 | * |
||
399 | * @return ElggTempFile |
||
400 | * @since 3.0 |
||
401 | */ |
||
402 | function elgg_get_temp_file() { |
||
403 | 1 | return new ElggTempFile(); |
|
404 | } |
||
405 | |||
406 | /** |
||
407 | * @see \Elgg\Application::loadCore Do not do work here. Just register for events. |
||
408 | */ |
||
409 | return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) { |
||
410 | 18 | $events->registerHandler('init', 'system', '_elgg_filestore_init', 100); |
|
411 | }; |
||
412 |
If you suppress an error, we recommend checking for the error condition explicitly: