Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

engine/lib/filestore.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
 * Get the contents of an uploaded file.
39
 * (Returns false if there was an issue.)
40
 *
41
 * @param string $input_name The name of the file input field on the submission form
42
 *
43
 * @return mixed|false The contents of the file, or false on failure.
44
 * @deprecated 2.3
45
 */
46
function get_uploaded_file($input_name) {
47
	elgg_deprecated_notice(__FUNCTION__ . ' has been deprecated and will be removed', '2.3');
48
	$inputs = elgg_get_uploaded_files($input_name);
49
	$input = array_shift($inputs);
50
	if (!$input || !$input->isValid()) {
51
		return false;
52
	}
53
	return file_get_contents($input->getPathname());
54
}
55
56
/**
57
 * Crops and resizes an image
58
 *
59
 * @param string $source      Path to source image
60
 * @param string $destination Path to destination
61
 *                            If not set, will modify the source image
62
 * @param array  $params      An array of cropping/resizing parameters
63
 *                             - INT 'w' represents the width of the new image
64
 *                               With upscaling disabled, this is the maximum width
65
 *                               of the new image (in case the source image is
66
 *                               smaller than the expected width)
67
 *                             - INT 'h' represents the height of the new image
68
 *                               With upscaling disabled, this is the maximum height
69
 *                             - INT 'x1', 'y1', 'x2', 'y2' represent optional cropping
70
 *                               coordinates. The source image will first be cropped
71
 *                               to these coordinates, and then resized to match
72
 *                               width/height parameters
73
 *                             - BOOL 'square' - square images will fill the
74
 *                               bounding box (width x height). In Imagine's terms,
75
 *                               this equates to OUTBOUND mode
76
 *                             - BOOL 'upscale' - if enabled, smaller images
77
 *                               will be upscaled to fit the bounding box.
78
 * @return bool
79
 * @since 2.3
80
 */
81
function elgg_save_resized_image($source, $destination = null, array $params = []) {
82
	return _elgg_services()->imageService->resize($source, $destination, $params);
83
}
84
85
/**
86
 * Gets the jpeg contents of the resized version of an uploaded image
87
 * (Returns false if the uploaded file was not an image)
88
 *
89
 * @param string $input_name The name of the file input field on the submission form
90
 * @param int    $maxwidth   The maximum width of the resized image
91
 * @param int    $maxheight  The maximum height of the resized image
92
 * @param bool   $square     If set to true, will take the smallest
93
 *                           of maxwidth and maxheight and use it to set the
94
 *                           dimensions on all size; the image will be cropped.
95
 * @param bool   $upscale    Resize images smaller than $maxwidth x $maxheight?
96
 *
97
 * @return false|mixed The contents of the resized image, or false on failure
98
 * @deprecated 2.3
99
 */
100
function get_resized_image_from_uploaded_file($input_name, $maxwidth, $maxheight,
101
		$square = false, $upscale = false) {
102
103
	elgg_deprecated_notice(__FUNCTION__ . ' has been deprecated. Use elgg_save_resized_image()', '2.3');
104
105
	$files = _elgg_services()->request->files;
106
	if (!$files->has($input_name)) {
107
		return false;
108
	}
109
110
	$file = $files->get($input_name);
111
	if (empty($file)) {
112
		// a file input was provided but no file uploaded
113
		return false;
114
	}
115
	if ($file->getError() !== 0) {
116
		return false;
117
	}
118
119
	return get_resized_image_from_existing_file($file->getPathname(), $maxwidth, $maxheight, $square, 0, 0, 0, 0, $upscale);
120
}
121
122
/**
123
 * Gets the jpeg contents of the resized version of an already uploaded image
124
 * (Returns false if the file was not an image)
125
 *
126
 * @param string $input_name The name of the file on the disk
127
 * @param int    $maxwidth   The desired width of the resized image
128
 * @param int    $maxheight  The desired height of the resized image
129
 * @param bool   $square     If set to true, takes the smallest of maxwidth and
130
 * 			                 maxheight and use it to set the dimensions on the new image.
131
 *                           If no crop parameters are set, the largest square that fits
132
 *                           in the image centered will be used for the resize. If square,
133
 *                           the crop must be a square region.
134
 * @param int    $x1         x coordinate for top, left corner
135
 * @param int    $y1         y coordinate for top, left corner
136
 * @param int    $x2         x coordinate for bottom, right corner
137
 * @param int    $y2         y coordinate for bottom, right corner
138
 * @param bool   $upscale    Resize images smaller than $maxwidth x $maxheight?
139
 *
140
 * @return false|mixed The contents of the resized image, or false on failure
141
 * @deprecated 2.3
142
 */
143
function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight,
144
			$square = false, $x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $upscale = false) {
145
146
	elgg_deprecated_notice(__FUNCTION__ . ' has been deprecated. Use elgg_save_resized_image()', '2.3');
147
148
	if (!is_readable($input_name)) {
149
		return false;
150
	}
151
152
	// we will write resized image to a temporary file and then delete it
153
	// need to add a valid image extension otherwise resizing fails
154
	$tmp_filename = tempnam(sys_get_temp_dir(), 'icon_resize');
155
	
156
	$params = [
157
		'w' => $maxwidth,
158
		'h' => $maxheight,
159
		'x1' => $x1,
160
		'y1' => $y1,
161
		'x2' => $x2,
162
		'y2' => $y2,
163
		'square' => $square,
164
		'upscale' => $upscale,
165
	];
166
167
	$image_bytes = false;
168
	if (elgg_save_resized_image($input_name, $tmp_filename, $params)) {
169
		$image_bytes = file_get_contents($tmp_filename);
170
	}
171
172
	unlink($tmp_filename);
173
174
	return $image_bytes;
175
}
176
177
/**
178
 * Calculate the parameters for resizing an image
179
 *
180
 * @param int   $width  Natural width of the image
181
 * @param int   $height Natural height of the image
182
 * @param array $params Resize parameters
183
 *                      - 'maxwidth' maximum width of the resized image
184
 *                      - 'maxheight' maximum height of the resized image
185
 *                      - 'upscale' allow upscaling
186
 *                      - 'square' constrain to a square
187
 *                      - 'x1', 'y1', 'x2', 'y2' cropping coordinates
188
 *
189
 * @return array|false
190
 * @since 1.7.2
191
 * @deprecated 2.3
192
 */
193
function get_image_resize_parameters($width, $height, array $params = []) {
194
195
	elgg_deprecated_notice(__FUNCTION__ . ' has been deprecated and will be removed from public API', '2.3');
196
197
	try {
198
		$params['w'] = elgg_extract('maxwidth', $params);
199
		$params['h'] = elgg_extract('maxheight', $params);
200
		unset($params['maxwidth']);
201
		unset($params['maxheight']);
202
		$params = _elgg_services()->imageService->normalizeResizeParameters($width, $height, $params);
203
		return [
204
			'newwidth' => $params['w'],
205
			'newheight' => $params['h'],
206
			'selectionwidth' => $params['x2'] - $params['x1'],
207
			'selectionheight' => $params['y2'] - $params['y1'],
208
			'xoffset' => $params['x1'],
209
			'yoffset' => $params['y1'],
210
		];
211
	} catch (\LogicException $ex) {
212
		elgg_log($ex->getMessage(), 'ERROR');
213
		return false;
214
	}
215
}
216
217
/**
218
 * Delete an \ElggFile file
219
 *
220
 * @param int $guid \ElggFile GUID
221
 *
222
 * @return bool
223
 */
224
function file_delete($guid) {
225
	$file = get_entity($guid);
226
	if (!$file || !$file->canEdit()) {
227
		return false;
228
	}
229
230
	$thumbnail = $file->thumbnail;
231
	$smallthumb = $file->smallthumb;
232
	$largethumb = $file->largethumb;
233 View Code Duplication
	if ($thumbnail) {
234
		$delfile = new \ElggFile();
235
		$delfile->owner_guid = $file->owner_guid;
236
		$delfile->setFilename($thumbnail);
237
		$delfile->delete();
238
	}
239 View Code Duplication
	if ($smallthumb) {
240
		$delfile = new \ElggFile();
241
		$delfile->owner_guid = $file->owner_guid;
242
		$delfile->setFilename($smallthumb);
243
		$delfile->delete();
244
	}
245 View Code Duplication
	if ($largethumb) {
246
		$delfile = new \ElggFile();
247
		$delfile->owner_guid = $file->owner_guid;
248
		$delfile->setFilename($largethumb);
249
		$delfile->delete();
250
	}
251
252
	return $file->delete();
253
}
254
255
/**
256
 * Delete a directory and all its contents
257
 *
258
 * @param string $directory Directory to delete
259
 *
260
 * @return bool
261
 */
262
function delete_directory($directory) {
263
	// sanity check: must be a directory
264
	if (!$handle = opendir($directory)) {
265
		return false;
266
	}
267
268
	// loop through all files
269
	while (($file = readdir($handle)) !== false) {
270
		if (in_array($file, ['.', '..'])) {
271
			continue;
272
		}
273
274
		$path = "$directory/$file";
275
		if (is_dir($path)) {
276
			// recurse down through directory
277
			if (!delete_directory($path)) {
278
				return false;
279
			}
280
		} else {
281
			// delete file
282
			unlink($path);
283
		}
284
	}
285
286
	// remove empty directory
287
	closedir($handle);
288
	return rmdir($directory);
289
}
290
291
/**
292
 * Removes all entity files
293
 *
294
 * @warning This only deletes the physical files and not their entities.
295
 * This will result in FileExceptions being thrown.  Don't use this function.
296
 *
297
 * @warning This must be kept in sync with \ElggDiskFilestore.
298
 *
299
 * @todo Remove this when all files are entities.
300
 *
301
 * @param \ElggEntity $entity An \ElggEntity
302
 *
303
 * @return void
304
 * @access private
305
 */
306
function _elgg_clear_entity_files($entity) {
307 1
	$dir = new \Elgg\EntityDirLocator($entity->guid);
308 1
	$file_path = _elgg_config()->dataroot . $dir;
309 1
	if (file_exists($file_path)) {
310
		delete_directory($file_path);
311
	}
312 1
}
313
314
/**
315
 * Returns the category of a file from its MIME type
316
 *
317
 * @param string $mime_type The MIME type
318
 *
319
 * @return string 'document', 'audio', 'video', or 'general' if the MIME type was unrecognized
320
 * @since 1.10
321
 */
322
function elgg_get_file_simple_type($mime_type) {
323 60
	$params = ['mime_type' => $mime_type];
324 60
	return elgg_trigger_plugin_hook('simple_type', 'file', $params, 'general');
325
}
326
327
/**
328
 * Register file-related handlers on "init, system" event
329
 *
330
 * @return void
331
 * @access private
332
 */
333
function _elgg_filestore_init() {
334
335
	// Fix MIME type detection for Microsoft zipped formats
336 93
	elgg_register_plugin_hook_handler('mime_type', 'file', '_elgg_filestore_detect_mimetype');
337
338
	// Parse category of file from MIME type
339 93
	elgg_register_plugin_hook_handler('simple_type', 'file', '_elgg_filestore_parse_simpletype');
340
341
	// Unit testing
342 93
	elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_filestore_test');
343
344
	// Handler for serving embedded icons
345 93
	elgg_register_page_handler('serve-icon', '_elgg_filestore_serve_icon_handler');
346
347
	// Touch entity icons if entity access id has changed
348 93
	elgg_register_event_handler('update:after', 'object', '_elgg_filestore_touch_icons');
349 93
	elgg_register_event_handler('update:after', 'group', '_elgg_filestore_touch_icons');
350
351
	// Move entity icons if entity owner has changed
352 93
	elgg_register_event_handler('update:after', 'object', '_elgg_filestore_move_icons');
353 93
	elgg_register_event_handler('update:after', 'group', '_elgg_filestore_move_icons');
354 93
}
355
356
/**
357
 * Fix MIME type detection for Microsoft zipped formats
358
 *
359
 * @param string $hook      "mime_type"
360
 * @param string $type      "file"
361
 * @param string $mime_type Detected MIME type
362
 * @param array  $params    Hook parameters
363
 *
364
 * @return string The MIME type
365
 * @access private
366
 */
367
function _elgg_filestore_detect_mimetype($hook, $type, $mime_type, $params) {
368
369 37
	$original_filename = elgg_extract('original_filename', $params);
370 37
	$ext = pathinfo($original_filename, PATHINFO_EXTENSION);
371
372 37
	return (new \Elgg\Filesystem\MimeTypeDetector())->fixDetectionErrors($mime_type, $ext);
373
}
374
375
/**
376
 * Parse a file category of file from a MIME type
377
 *
378
 * @param string $hook        "simple_type"
379
 * @param string $type        "file"
380
 * @param string $simple_type The category of file
381
 * @param array  $params      Hook parameters
382
 *
383
 * @return string 'document', 'audio', 'video', or 'general' if the MIME type is unrecognized
384
 * @access private
385
 */
386
function _elgg_filestore_parse_simpletype($hook, $type, $simple_type, $params) {
387
388 60
	$mime_type = elgg_extract('mime_type', $params);
389
390
	switch ($mime_type) {
391 60
		case "application/msword":
392 59
		case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
393 58
		case "application/pdf":
394 3
			return "document";
395
396 57
		case "application/ogg":
397 1
			return "audio";
398
	}
399
400 56
	if (preg_match('~^(audio|image|video)/~', $mime_type, $m)) {
401 51
		return $m[1];
402
	}
403 5
	if (0 === strpos($mime_type, 'text/') || false !== strpos($mime_type, 'opendocument')) {
404 3
		return "document";
405
	}
406
407
	// unrecognized MIME
408 2
	return $simple_type;
409
}
410
411
/**
412
 * Unit tests for files
413
 *
414
 * @param string $hook   unit_test
415
 * @param string $type   system
416
 * @param mixed  $value  Array of tests
417
 *
418
 * @return array
419
 * @access private
420
 */
421
function _elgg_filestore_test($hook, $type, $value) {
422
	global $CONFIG;
423
	$value[] = "{$CONFIG->path}engine/tests/ElggCoreFilestoreTest.php";
424
	return $value;
425
}
426
427
/**
428
 * Returns file's download URL
429
 *
430
 * @note This does not work for files with custom filestores.
431
 *
432
 * @param \ElggFile $file       File object or entity (must have the default filestore)
433
 * @param bool      $use_cookie Limit URL validity to current session only
434
 * @param string    $expires    URL expiration, as a string suitable for strtotime()
435
 * @return string
436
 */
437
function elgg_get_download_url(\ElggFile $file, $use_cookie = true, $expires = '+2 hours') {
438 1
	return $file->getDownloadURL($use_cookie, $expires);
439
}
440
441
/**
442
 * Returns file's URL for inline display
443
 * Suitable for displaying cacheable resources, such as user avatars
444
 *
445
 * @note This does not work for files with custom filestores.
446
 *
447
 * @param \ElggFile $file       File object or entity (must have the default filestore)
448
 * @param bool      $use_cookie Limit URL validity to current session only
449
 * @param string    $expires    URL expiration, as a string suitable for strtotime()
450
 * @return string
451
 */
452
function elgg_get_inline_url(\ElggFile $file, $use_cookie = false, $expires = '') {
453 2
	return $file->getInlineURL($use_cookie, $expires);
454
}
455
456
/**
457
 * Returns a URL suitable for embedding entity's icon in a text editor.
458
 * We can not use elgg_get_inline_url() for these purposes due to a URL structure
459
 * bound to user session and file modification time.
460
 * This function returns a generic (permanent) URL that will then be resolved to
461
 * an inline URL whenever requested.
462
 *
463
 * @param \ElggEntity $entity Entity
464
 * @param string      $size   Size
465
 * @return string
466
 * @since 2.2
467
 */
468
function elgg_get_embed_url(\ElggEntity $entity, $size) {
469
	return elgg_normalize_url("serve-icon/$entity->guid/$size");
470
}
471
472
/**
473
 * Handler for /serve-icon resources
474
 * /serve-icon/<entity_guid>/<size>
475
 *
476
 * @return void
477
 * @access private
478
 * @since 2.2
479
 */
480
function _elgg_filestore_serve_icon_handler() {
481
	$response = _elgg_services()->iconService->handleServeIconRequest();
482
	$response->send();
483
	exit;
484
}
485
486
/**
487
 * Reset icon URLs if access_id has changed
488
 *
489
 * @param string     $event  "update:after"
490
 * @param string     $type   "object"|"group"
491
 * @param ElggObject $entity Entity
492
 * @return void
493
 * @access private
494
 */
495
function _elgg_filestore_touch_icons($event, $type, $entity) {
496
	$original_attributes = $entity->getOriginalAttributes();
497
	if (!array_key_exists('access_id', $original_attributes)) {
498
		return;
499
	}
500
	if ($entity instanceof \ElggFile) {
501
		// we touch the file to invalidate any previously generated download URLs
502
		$entity->setModifiedTime();
503
	}
504
	$sizes = array_keys(elgg_get_icon_sizes($entity->getType(), $entity->getSubtype()));
505
	foreach ($sizes as $size) {
506
		$icon = $entity->getIcon($size);
507
		if ($icon->exists()) {
508
			$icon->setModifiedTime();
509
		}
510
	}
511
}
512
513
/**
514
 * Listen to entity ownership changes and update icon ownership by moving
515
 * icons to their new owner's directory on filestore.
516
 *
517
 * This will only transfer icons that have a custom location on filestore
518
 * and are owned by the entity's owner (instead of the entity itself).
519
 * Even though core icon service does not store icons in the entity's owner
520
 * directory, there are plugins that do (e.g. file plugin) - this handler
521
 * helps such plugins avoid ownership mismatch.
522
 *
523
 * @param string     $event  "update:after"
524
 * @param string     $type   "object"|"group"
525
 * @param ElggObject $entity Entity
526
 * @return void
527
 * @access private
528
 */
529
function _elgg_filestore_move_icons($event, $type, $entity) {
530
531
	$original_attributes = $entity->getOriginalAttributes();
532
	if (empty($original_attributes['owner_guid'])) {
533
		return;
534
	}
535
536
	$previous_owner_guid = $original_attributes['owner_guid'];
537
	$new_owner_guid = $entity->owner_guid;
538
539
	$sizes = elgg_get_icon_sizes($entity->getType(), $entity->getSubtype());
540
541
	foreach ($sizes as $size => $opts) {
542
		$new_icon = $entity->getIcon($size);
543
		if ($new_icon->owner_guid == $entity->guid) {
544
			// we do not need to update icons that are owned by the entity itself
545
			continue;
546
		}
547
548
		if ($new_icon->owner_guid != $new_owner_guid) {
549
			// a plugin implements some custom logic
550
			continue;
551
		}
552
553
		$old_icon = new \ElggIcon();
554
		$old_icon->owner_guid = $previous_owner_guid;
555
		$old_icon->setFilename($new_icon->getFilename());
556
		if (!$old_icon->exists()) {
557
			// there is no icon to move
558
			continue;
559
		}
560
561
		if ($new_icon->exists()) {
562
			// there is already a new icon
563
			// just removing the old one
564
			$old_icon->delete();
565
			elgg_log("Entity $entity->guid has been transferred to a new owner but an icon was "
566
				. "left behind under {$old_icon->getFilenameOnFilestore()}. "
567
				. "Old icon has been deleted", 'NOTICE');
568
			continue;
569
		}
570
571
		$old_icon->transfer($new_icon->owner_guid, $new_icon->getFilename());
572
		elgg_log("Entity $entity->guid has been transferred to a new owner. "
573
		. "Icon was moved from {$old_icon->getFilenameOnFilestore()} to {$new_icon->getFilenameOnFilestore()}.", 'NOTICE');
574
	}
575
}
576
577
/**
578
 * Returns an array of uploaded file objects regardless of upload status/errors
579
 *
580
 * @param string $input_name Form input name
581
 * @return UploadedFile[]|false
582
 */
583
function elgg_get_uploaded_files($input_name) {
584
	return _elgg_services()->uploads->getUploadedFiles($input_name);
585
}
586
587
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
0 ignored issues
show
The parameter $hooks is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
588
	$events->registerHandler('init', 'system', '_elgg_filestore_init', 100);
589
};
590