Elgg /
Elgg
| 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) { |
||||||
|
2 ignored issues
–
show
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...
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; |
||||||
|
1 ignored issue
–
show
|
|||||||
| 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) { |
||||||
|
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...
|
|||||||
| 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) { |
||||||
|
1 ignored issue
–
show
The parameter
$hooks 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...
|
|||||||
| 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: