Passed
Push — master ( 63153e...96b2fe )
by Jeroen
28:56 queued 12:37
created

EntityIconService::saveIcon()   C

Complexity

Conditions 16
Paths 11

Size

Total Lines 78
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 16.3934

Importance

Changes 0
Metric Value
cc 16
eloc 53
nc 11
nop 4
dl 0
loc 78
ccs 46
cts 52
cp 0.8846
crap 16.3934
rs 5.142
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Elgg;
4
5
use Elgg\Database\EntityTable;
6
use Elgg\Filesystem\MimeTypeDetector;
7
use Elgg\Http\Request;
8
use Elgg\UploadService;
9
use ElggEntity;
10
use ElggFile;
11
use ElggIcon;
12
use InvalidParameterException;
13
use Symfony\Component\HttpFoundation\BinaryFileResponse;
14
use Symfony\Component\HttpFoundation\Response;
15
16
/**
17
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
18
 *
19
 * Use the elgg_* versions instead.
20
 *
21
 * @access private
22
 * @since 2.2
23
 */
24
class EntityIconService {
25
	use TimeUsing;
26
27
	/**
28
	 * @var Config
29
	 */
30
	private $config;
31
32
	/**
33
	 * @var PluginHooksService
34
	 */
35
	private $hooks;
36
37
	/**
38
	 * @var Request
39
	 */
40
	private $request;
41
42
	/**
43
	 * @var Logger
44
	 */
45
	private $logger;
46
47
	/**
48
	 * @var EntityTable
49
	 */
50
	private $entities;
51
52
	/**
53
	 * @var UploadService
54
	 */
55
	private $uploads;
56
57
	/**
58
	 * Constructor
59
	 *
60
	 * @param Config             $config   Config
61
	 * @param PluginHooksService $hooks    Hook registration service
62
	 * @param Request            $request  Http request
63
	 * @param Logger             $logger   Logger
64
	 * @param EntityTable        $entities Entity table
65
	 * @param UploadService      $uploads  Upload service
66
	 */
67 63
	public function __construct(Config $config, PluginHooksService $hooks, Request $request, Logger $logger, EntityTable $entities, UploadService $uploads) {
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
68 63
		$this->config = $config;
69 63
		$this->hooks = $hooks;
70 63
		$this->request = $request;
71 63
		$this->logger = $logger;
72 63
		$this->entities = $entities;
73 63
		$this->uploads = $uploads;
74 63
	}
75
76
	/**
77
	 * Saves icons using an uploaded file as the source.
78
	 *
79
	 * @param ElggEntity $entity     Entity to own the icons
80
	 * @param string     $input_name Form input name
81
	 * @param string     $type       The name of the icon. e.g., 'icon', 'cover_photo'
82
	 * @param array      $coords     An array of cropping coordinates x1, y1, x2, y2
83
	 * @return bool
84
	 */
85 1
	public function saveIconFromUploadedFile(ElggEntity $entity, $input_name, $type = 'icon', array $coords = []) {
86 1
		$input = $this->uploads->getFile($input_name);
87 1
		if (empty($input)) {
88
			return false;
89
		}
90
91 1
		$tmp_filename = time() . $input->getClientOriginalName();
92 1
		$tmp = new ElggFile();
93 1
		$tmp->owner_guid = $entity->guid;
94 1
		$tmp->setFilename("tmp/$tmp_filename");
95 1
		$tmp->open('write');
96 1
		$tmp->close();
97
		// not using move_uploaded_file() for testing purposes
98 1
		copy($input->getPathname(), $tmp->getFilenameOnFilestore());
99
100 1
		$tmp->mimetype = (new MimeTypeDetector())->getType($tmp_filename, $input->getClientMimeType());
101 1
		$tmp->simpletype = elgg_get_file_simple_type($tmp->mimetype);
102
103 1
		$result = $this->saveIcon($entity, $tmp, $type, $coords);
104
105 1
		unlink($input->getPathname());
106 1
		$tmp->delete();
107
108 1
		return $result;
109
	}
110
111
	/**
112
	 * Saves icons using a local file as the source.
113
	 *
114
	 * @param ElggEntity $entity   Entity to own the icons
115
	 * @param string     $filename The full path to the local file
116
	 * @param string     $type     The name of the icon. e.g., 'icon', 'cover_photo'
117
	 * @param array      $coords   An array of cropping coordinates x1, y1, x2, y2
118
	 * @return bool
119
	 * @throws InvalidParameterException
120
	 */
121 5
	public function saveIconFromLocalFile(ElggEntity $entity, $filename, $type = 'icon', array $coords = []) {
122 5
		if (!file_exists($filename) || !is_readable($filename)) {
123 1
			throw new InvalidParameterException(__METHOD__ . " expects a readable local file. $filename is not readable");
124
		}
125
126 4
		$tmp_filename = time() . pathinfo($filename, PATHINFO_BASENAME);
127 4
		$tmp = new ElggFile();
128 4
		$tmp->owner_guid = $entity->guid;
129 4
		$tmp->setFilename("tmp/$tmp_filename");
130 4
		$tmp->open('write');
131 4
		$tmp->close();
132 4
		copy($filename, $tmp->getFilenameOnFilestore());
133
134 4
		$tmp->mimetype = (new MimeTypeDetector())->getType($tmp->getFilenameOnFilestore());
135 4
		$tmp->simpletype = elgg_get_file_simple_type($tmp->mimetype);
136
137 4
		$result = $this->saveIcon($entity, $tmp, $type, $coords);
138
139 4
		$tmp->delete();
140
141 4
		return $result;
142
	}
143
144
	/**
145
	 * Saves icons using a file located in the data store as the source.
146
	 *
147
	 * @param ElggEntity $entity Entity to own the icons
148
	 * @param ElggFile   $file   An ElggFile instance
149
	 * @param string     $type   The name of the icon. e.g., 'icon', 'cover_photo'
150
	 * @param array      $coords An array of cropping coordinates x1, y1, x2, y2
151
	 * @return bool
152
	 * @throws InvalidParameterException
153
	 */
154 40
	public function saveIconFromElggFile(ElggEntity $entity, ElggFile $file, $type = 'icon', array $coords = []) {
155 40
		if (!$file->exists()) {
156 1
			throw new InvalidParameterException(__METHOD__ . ' expects an instance of ElggFile with an existing file on filestore');
157
		}
158
159 39
		$tmp_filename = time() . pathinfo($file->getFilenameOnFilestore(), PATHINFO_BASENAME);
160 39
		$tmp = new ElggFile();
161 39
		$tmp->owner_guid = $entity->guid;
162 39
		$tmp->setFilename("tmp/$tmp_filename");
163 39
		$tmp->open('write');
164 39
		$tmp->close();
165 39
		copy($file->getFilenameOnFilestore(), $tmp->getFilenameOnFilestore());
166
167 39
		$tmp->mimetype = (new MimeTypeDetector())->getType($tmp->getFilenameOnFilestore(), $file->getMimeType());
168 39
		$tmp->simpletype = elgg_get_file_simple_type($tmp->mimetype);
169
170 39
		$result = $this->saveIcon($entity, $tmp, $type, $coords);
171
172 39
		$tmp->delete();
173
174 39
		return $result;
175
	}
176
177
	/**
178
	 * Saves icons using a created temporary file
179
	 *
180
	 * @param ElggEntity $entity Temporary ElggFile instance
181
	 * @param ElggFile   $file   Temporary ElggFile instance
182
	 * @param string     $type   The name of the icon. e.g., 'icon', 'cover_photo'
183
	 * @param array      $coords An array of cropping coordinates x1, y1, x2, y2
184
	 * @return bool
185
	 */
186 44
	public function saveIcon(ElggEntity $entity, ElggFile $file, $type = 'icon', array $coords = []) {
187
188 44
		$type = (string) $type;
189 44
		if (!strlen($type)) {
190
			$this->logger->error('Icon type passed to ' . __METHOD__ . ' can not be empty');
191
			return false;
192
		}
193
		
194 44
		$entity_type = $entity->getType();
195 44
		$entity_subtype = $entity->getSubtype();
0 ignored issues
show
Unused Code introduced by
$entity_subtype is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
196
		
197 44
		$file = $this->hooks->trigger("entity:$type:prepare", $entity_type, [
198 44
			'entity' => $entity,
199 44
			'file' => $file,
200 44
		], $file);
201
		
202 44
		if (!$file instanceof ElggFile || !$file->exists() || $file->getSimpleType() !== 'image') {
203
			$this->logger->error('Source file passed to ' . __METHOD__ . ' can not be resolved to a valid image');
204
			return false;
205
		}
206
		
207 44
		$x1 = (int) elgg_extract('x1', $coords);
208 44
		$y1 = (int) elgg_extract('y1', $coords);
209 44
		$x2 = (int) elgg_extract('x2', $coords);
210 44
		$y2 = (int) elgg_extract('y2', $coords);
211
		
212 44
		$created = $this->hooks->trigger("entity:$type:save", $entity_type, [
213 44
			'entity' => $entity,
214 44
			'file' => $file,
215 44
			'x1' => $x1,
216 44
			'y1' => $y1,
217 44
			'x2' => $x2,
218 44
			'y2' => $y2,
219 44
		], false);
220
221
		// did someone else handle saving the icon?
222 44
		if ($created !== true) {
223
			// remove existing icons
224 43
			$this->deleteIcon($entity, $type, true);
225
			
226
			// save master image
227 43
			$store = $this->generateIcon($entity, $file, $type, $coords, 'master');
228
			
229 43
			if (!$store) {
230
				$this->deleteIcon($entity, $type);
231
				return false;
232
			}
233
		}
234
235 44
		if ($type == 'icon') {
236 43
			$entity->icontime = time();
1 ignored issue
show
Documentation introduced by
The property icontime does not exist on object<ElggEntity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
237 43
			if ($x1 || $y1 || $x2 || $y2) {
238 6
				$entity->x1 = $x1;
1 ignored issue
show
Documentation introduced by
The property x1 does not exist on object<ElggEntity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
239 6
				$entity->y1 = $y1;
1 ignored issue
show
Documentation introduced by
The property y1 does not exist on object<ElggEntity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
240 6
				$entity->x2 = $x2;
1 ignored issue
show
Documentation introduced by
The property x2 does not exist on object<ElggEntity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
241 43
				$entity->y2 = $y2;
1 ignored issue
show
Documentation introduced by
The property y2 does not exist on object<ElggEntity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
242
			}
243
		} else {
244 1
			if ($x1 || $y1 || $x2 || $y2) {
245 1
				$entity->{"{$type}_coords"} = serialize([
246 1
					'x1' => $x1,
247 1
					'y1' => $y1,
248 1
					'x2' => $x2,
249 1
					'y2' => $y2,
250
				]);
251
			}
252
		}
253
		
254 44
		$this->hooks->trigger("entity:$type:saved", $entity->getType(), [
255 44
			'entity' => $entity,
256 44
			'x1' => $x1,
257 44
			'y1' => $y1,
258 44
			'x2' => $x2,
259 44
			'y2' => $y2,
260
		]);
261
		
262 44
		return true;
263
	}
264
	
265
	/**
266
	 * Generate an icon for the given entity
267
	 *
268
	 * @param ElggEntity $entity    Temporary ElggFile instance
269
	 * @param ElggFile   $file      Temporary ElggFile instance
270
	 * @param string     $type      The name of the icon. e.g., 'icon', 'cover_photo'
271
	 * @param array      $coords    An array of cropping coordinates x1, y1, x2, y2
272
	 * @param string     $icon_size The icon size to generate (leave empty to generate all supported sizes)
273
	 *
274
	 * @return bool
275
	 */
276 43
	protected function generateIcon(ElggEntity $entity, ElggFile $file, $type = 'icon', $coords = [], $icon_size = '') {
277
		
278 43
		if (!$file->exists()) {
279
			$this->logger->error('Trying to generate an icon from a non-existing file');
280
			return false;
281
		}
282
		
283 43
		$x1 = (int) elgg_extract('x1', $coords);
284 43
		$y1 = (int) elgg_extract('y1', $coords);
285 43
		$x2 = (int) elgg_extract('x2', $coords);
286 43
		$y2 = (int) elgg_extract('y2', $coords);
287
		
288 43
		$cropping_mode = ($x2 > $x1) && ($y2 > $y1);
289
		
290 43
		$sizes = $this->getSizes($entity->getType(), $entity->getSubtype(), $type);
291
		
292 43
		if (!empty($icon_size) && !isset($sizes[$icon_size])) {
293
			$this->logger->warning("The provided icon size '{$icon_size}' doesn't exist for icon type '{$type}'");
0 ignored issues
show
Bug introduced by
The method warning() does not seem to exist on object<Elgg\Logger>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
294
			return false;
295
		}
296
		
297 43
		foreach ($sizes as $size => $opts) {
298 43
			if (!empty($icon_size) && ($icon_size !== $size)) {
299
				// only generate the given icon size
300 43
				continue;
301
			}
302
			
303 43
			$square = (bool) elgg_extract('square', $opts);
304
305 43
			if ($type === 'icon' && $cropping_mode) {
306 6
				$cropping_ratio = ($x2 - $x1) / ($y2 - $y1);
307 6
				if ($cropping_ratio == 1 && $square === false) {
308
					// Do not crop out non-square icons if cropping coordinates are a square
309
					$coords = [
310 6
						'x1' => 0,
311
						'y1' => 0,
312
						'x2' => 0,
313
						'y2' => 0,
314
					];
315
				}
316
			}
317
318 43
			$icon = $this->getIcon($entity, $size, $type, false);
319
320
			// We need to make sure that file path is readable by
321
			// Imagine\Image\ImagineInterface::save(), as it fails to
322
			// build the directory structure on owner's filestore otherwise
323 43
			$icon->open('write');
324 43
			$icon->close();
325
			
326
			// Save the image without resizing or cropping if the
327
			// image size value is an empty array
328 43
			if (is_array($opts) && empty($opts)) {
329 1
				copy($file->getFilenameOnFilestore(), $icon->getFilenameOnFilestore());
330 1
				continue;
331
			}
332
333 43
			$source = $file->getFilenameOnFilestore();
334 43
			$destination = $icon->getFilenameOnFilestore();
335
336 43
			$resize_params = array_merge($opts, $coords);
337
338 43
			$image_service = _elgg_services()->imageService;
339 43
			$image_service->setLogger($this->logger);
340
341 43
			if (!_elgg_services()->imageService->resize($source, $destination, $resize_params)) {
342
				$this->logger->error("Failed to create {$size} icon from
343
					{$file->getFilenameOnFilestore()} with coords [{$x1}, {$y1}],[{$x2}, {$y2}]");
344 43
				return false;
345
			}
346
		}
347
348 43
		return true;
349
	}
350
351
	/**
352
	 * Returns entity icon as an ElggIcon object
353
	 * The icon file may or may not exist on filestore
354
	 *
355
	 * @note Returned ElggIcon object may be a placeholder. Use ElggIcon::exists() to validate if file has been written to filestore
356
	 *
357
	 * @param ElggEntity $entity   Entity that owns the icon
358
	 * @param string     $size     Size of the icon
359
	 * @param string     $type     The name of the icon. e.g., 'icon', 'cover_photo'
360
	 * @param bool       $generate Try to generate an icon based on master if size doesn't exists
361
	 *
362
	 * @return ElggIcon
363
	 *
364
	 * @throws InvalidParameterException
365
	 */
366 54
	public function getIcon(ElggEntity $entity, $size, $type = 'icon', $generate = true) {
367
368 54
		$size = elgg_strtolower($size);
369
370
		$params = [
371 54
			'entity' => $entity,
372 54
			'size' => $size,
373 54
			'type' => $type,
374
		];
375
376 54
		$entity_type = $entity->getType();
377
378 54
		$default_icon = new ElggIcon();
379 54
		$default_icon->owner_guid = $entity->guid;
380 54
		$default_icon->setFilename("icons/$type/$size.jpg");
381
382 54
		$icon = $this->hooks->trigger("entity:$type:file", $entity_type, $params, $default_icon);
383 54
		if (!$icon instanceof ElggIcon) {
384 1
			throw new InvalidParameterException("'entity:$type:file', $entity_type hook must return an instance of ElggIcon");
385
		}
386
		
387 53
		if ($icon->exists() || !$generate) {
388 53
			return $icon;
389
		}
390
		
391 46
		if ($size === 'master') {
392
			// don't try to generate for master
393 7
			return $icon;
394
		}
395
		
396
		// try to generate icon based on master size
397 46
		$master_icon = $this->getIcon($entity, 'master', $type, false);
398 46
		if (!$master_icon->exists()) {
399 11
			return $icon;
400
		}
401
		
402 36
		if ($type === 'icon') {
403
			$coords = [
404 35
				'x1' => $entity->x1,
405 35
				'y1' => $entity->y1,
406 35
				'x2' => $entity->x2,
407 35
				'y2' => $entity->y2,
408
			];
409
		} else {
410 1
			$coords = $entity->{"{$type}_coords"};
411 1
			$coords = empty($coords) ? [] : unserialize($coords);
412
		}
413
		
414 36
		$this->generateIcon($entity, $master_icon, $type, $coords, $size);
415
		
416 36
		return $icon;
417
	}
418
419
	/**
420
	 * Removes all icon files and metadata for the passed type of icon.
421
	 *
422
	 * @param ElggEntity $entity        Entity that owns icons
423
	 * @param string     $type          The name of the icon. e.g., 'icon', 'cover_photo'
424
	 * @param bool       $retain_master Keep the master icon (default: false)
425
	 *
426
	 * @return bool
427
	 */
428 44
	public function deleteIcon(ElggEntity $entity, $type = 'icon', $retain_master = false) {
429 44
		$delete = $this->hooks->trigger("entity:$type:delete", $entity->getType(), [
430 44
			'entity' => $entity,
431 44
		], true);
432
433 44
		if ($delete === false) {
434 1
			return;
435
		}
436
437 43
		$sizes = array_keys($this->getSizes($entity->getType(), $entity->getSubtype(), $type));
438 43
		foreach ($sizes as $size) {
439 43
			if ($size === 'master' && $retain_master) {
440 42
				continue;
441
			}
442
			
443 43
			$icon = $this->getIcon($entity, $size, $type, false);
444 43
			$icon->delete();
445
		}
446
447 43
		if ($type == 'icon') {
448 42
			unset($entity->icontime);
449 42
			unset($entity->x1);
450 42
			unset($entity->y1);
451 42
			unset($entity->x2);
452 42
			unset($entity->y2);
453
		}
454 43
	}
455
456
	/**
457
	 * Get the URL for this entity's icon
458
	 *
459
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook to customize the icon for an entity.
460
	 *
461
	 * @param ElggEntity $entity Entity that owns the icon
462
	 * @param mixed      $params A string defining the size of the icon (e.g. tiny, small, medium, large)
463
	 *                           or an array of parameters including 'size'
464
	 * @return string|void
465
	 */
466 3
	public function getIconURL(ElggEntity $entity, $params = []) {
467 3
		if (is_array($params)) {
468 1
			$size = elgg_extract('size', $params, 'medium');
469
		} else {
470 3
			$size = is_string($params) ? $params : 'medium';
471 3
			$params = [];
472
		}
473
474 3
		$size = elgg_strtolower($size);
475
476 3
		$params['entity'] = $entity;
477 3
		$params['size'] = $size;
478
479 3
		$type = elgg_extract('type', $params) ? : 'icon';
480 3
		$entity_type = $entity->getType();
481
482 3
		$url = $this->hooks->trigger("entity:$type:url", $entity_type, $params, null);
483 3
		if ($url == null) {
484 1
			if ($this->hasIcon($entity, $size, $type)) {
485 1
				$icon = $this->getIcon($entity, $size, $type);
486 1
				$url = $icon->getInlineURL(true);
487
			} else {
488
				$url = $this->getFallbackIconUrl($entity, $params);
489
			}
490
		}
491
492 3
		if ($url) {
493 3
			return elgg_normalize_url($url);
494
		}
495
	}
496
497
	/**
498
	 * Returns default/fallback icon
499
	 *
500
	 * @param ElggEntity $entity Entity
501
	 * @param array      $params Icon params
502
	 * @return string
503
	 */
504
	public function getFallbackIconUrl(ElggEntity $entity, array $params = []) {
505
506
		$type = elgg_extract('type', $params) ? : 'icon';
507
		$size = elgg_extract('size', $params) ? : 'medium';
508
		
509
		$entity_type = $entity->getType();
510
		$entity_subtype = $entity->getSubtype() ? : 'default';
511
512
		$exts = ['svg', 'gif', 'png', 'jpg'];
513
		
514
		foreach ($exts as $ext) {
515
			if ($ext == 'svg' && elgg_view_exists("$type/$entity_type/$entity_subtype.svg")) {
516
				return elgg_get_simplecache_url("$type/$entity_type/$entity_subtype.svg");
517
			}
518
			if (elgg_view_exists("$type/$entity_type/$entity_subtype/$size.$ext")) {
519
				return elgg_get_simplecache_url("$type/$entity_type/$entity_subtype/$size.$ext");
520
			}
521
		}
522
523
		if (elgg_view_exists("$type/default/$size.png")) {
524
			return elgg_get_simplecache_url("$type/default/$size.png");
525
		}
526
	}
527
528
	/**
529
	 * Returns the timestamp of when the icon was changed.
530
	 *
531
	 * @param ElggEntity $entity Entity that owns the icon
532
	 * @param string     $size   The size of the icon
533
	 * @param string     $type   The name of the icon. e.g., 'icon', 'cover_photo'
534
	 *
535
	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
536
	 */
537 1
	public function getIconLastChange(ElggEntity $entity, $size, $type = 'icon') {
538 1
		$icon = $this->getIcon($entity, $size, $type);
539 1
		if ($icon->exists()) {
540 1
			return $icon->getModifiedTime();
541
		}
542
	}
543
544
	/**
545
	 * Returns if the entity has an icon of the passed type.
546
	 *
547
	 * @param ElggEntity $entity Entity that owns the icon
548
	 * @param string     $size   The size of the icon
549
	 * @param string     $type   The name of the icon. e.g., 'icon', 'cover_photo'
550
	 * @return bool
551
	 */
552 14
	public function hasIcon(\ElggEntity $entity, $size, $type = 'icon') {
553 14
		return $this->getIcon($entity, $size, $type)->exists();
554
	}
555
556
	/**
557
	 * Returns a configuration array of icon sizes
558
	 *
559
	 * @param string $entity_type    Entity type
560
	 * @param string $entity_subtype Entity subtype
561
	 * @param string $type           The name of the icon. e.g., 'icon', 'cover_photo'
562
	 * @return array
563
	 * @throws InvalidParameterException
564
	 */
565 51
	public function getSizes($entity_type = null, $entity_subtype = null, $type = 'icon') {
566 51
		$sizes = [];
567 51
		if (!$type) {
568
			$type = 'icon';
569
		}
570 51
		if ($type == 'icon') {
571 49
			$sizes = $this->config->icon_sizes;
572
		}
573
		$params = [
574 51
			'type' => $type,
575 51
			'entity_type' => $entity_type,
576 51
			'entity_subtype' => $entity_subtype,
577
		];
578 51
		if ($entity_type) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $entity_type of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
579 50
			$sizes = $this->hooks->trigger("entity:$type:sizes", $entity_type, $params, $sizes);
580
		}
581
582 51
		if (!is_array($sizes)) {
583
			throw new InvalidParameterException("The icon size configuration for image type '$type' " .
584
				"must be an associative array of image size names and their properties");
585
		}
586
587
		// lazy generation of icons requires a 'master' size
588
		// this ensures a default config for 'master' size
589 51
		$sizes['master'] = elgg_extract('master', $sizes, [
590 51
			'w' => 2048,
591
			'h' => 2048,
592
			'square' => false,
593
			'upscale' => false,
594
		]);
595
		
596 51
		return $sizes;
597
	}
598
599
	/**
600
	 * Handle request to /serve-icon handler
601
	 *
602
	 * @param bool $allow_removing_headers Alter PHP's global headers to allow caching
603
	 * @return BinaryFileResponse
604
	 */
605 3
	public function handleServeIconRequest($allow_removing_headers = true) {
606
607 3
		$response = new Response();
608 3
		$response->setExpires($this->getCurrentTime('-1 day'));
609 3
		$response->prepare($this->request);
610
611 3
		if ($allow_removing_headers) {
612
			// clear cache-boosting headers set by PHP session
613
			header_remove('Cache-Control');
614
			header_remove('Pragma');
615
			header_remove('Expires');
616
		}
617
618 3
		$path = implode('/', $this->request->getUrlSegments());
619 3
		if (!preg_match('~serve-icon/(\d+)/(.*+)$~', $path, $m)) {
620 1
			return $response->setStatusCode(400)->setContent('Malformatted request URL');
621
		}
622
623 2
		list(, $guid, $size) = $m;
624
625 2
		$entity = $this->entities->get($guid);
626 2
		if (!$entity instanceof \ElggEntity) {
627 1
			return $response->setStatusCode(404)->setContent('Item does not exist');
628
		}
629
630 1
		$thumbnail = $entity->getIcon($size);
631 1
		if (!$thumbnail->exists()) {
632
			return $response->setStatusCode(404)->setContent('Icon does not exist');
633
		}
634
635 1
		$if_none_match = $this->request->headers->get('if_none_match');
636 1
		if (!empty($if_none_match)) {
637
			// strip mod_deflate suffixes
638 1
			$this->request->headers->set('if_none_match', str_replace('-gzip', '', $if_none_match));
639
		}
640
641 1
		$filenameonfilestore = $thumbnail->getFilenameOnFilestore();
642 1
		$last_updated = filemtime($filenameonfilestore);
643 1
		$etag = '"' . $last_updated . '"';
644
645 1
		$response->setPrivate()
646 1
			->setEtag($etag)
647 1
			->setExpires($this->getCurrentTime('+1 day'))
648 1
			->setMaxAge(86400);
649
650 1
		if ($response->isNotModified($this->request)) {
651 1
			return $response;
652
		}
653
654
		$headers = [
655 1
			'Content-Type' => (new MimeTypeDetector())->getType($filenameonfilestore),
656
		];
657 1
		$response = new BinaryFileResponse($filenameonfilestore, 200, $headers, false, 'inline');
658 1
		$response->prepare($this->request);
659
660 1
		$response->setPrivate()
661 1
			->setEtag($etag)
662 1
			->setExpires($this->getCurrentTime('+1 day'))
663 1
			->setMaxAge(86400);
664
665 1
		return $response;
666
	}
667
668
}
669