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); |
||||||
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) { |
||||||
2 ignored issues
–
show
The parameter
$hook is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
|
|||||||
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) { |
||||||
2 ignored issues
–
show
The parameter
$hook is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
The parameter
$type is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
|
|||||||
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) { |
||||||
1 ignored issue
–
show
The parameter
$event is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
The parameter
$type is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
|
|||||||
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 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.