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) { |
|
|
|
|
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
|
|
|
|