Elgg  Version 4.3
EntityIconService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
11 
19 
20  use Loggable;
21  use TimeUsing;
22 
26  private $config;
27 
31  private $hooks;
32 
36  private $entities;
37 
41  private $uploads;
42 
46  private $images;
47 
51  protected $mimetype;
52 
56  protected $request;
57 
69  public function __construct(
71  PluginHooksService $hooks,
72  EntityTable $entities,
73  UploadService $uploads,
74  ImageService $images,
75  MimeTypeService $mimetype,
77  ) {
78  $this->config = $config;
79  $this->hooks = $hooks;
80  $this->entities = $entities;
81  $this->uploads = $uploads;
82  $this->images = $images;
83  $this->mimetype = $mimetype;
84  $this->request = $request;
85  }
86 
97  public function saveIconFromUploadedFile(\ElggEntity $entity, $input_name, $type = 'icon', array $coords = []) {
98  $input = $this->uploads->getFile($input_name);
99  if (empty($input)) {
100  return false;
101  }
102 
103  // auto detect cropping coordinates
104  if (empty($coords)) {
105  $auto_coords = $this->detectCroppingCoordinates();
106  if (!empty($auto_coords)) {
107  $coords = $auto_coords;
108  }
109  }
110 
111  $tmp = new \ElggTempFile();
112  $tmp->setFilename(uniqid() . $input->getClientOriginalName());
113  $tmp->open('write');
114  $tmp->close();
115 
116  copy($input->getPathname(), $tmp->getFilenameOnFilestore());
117 
118  $tmp->mimetype = $this->mimetype->getMimeType($tmp->getFilenameOnFilestore());
119  $tmp->simpletype = $this->mimetype->getSimpleType($tmp->mimetype);
120 
121  $result = $this->saveIcon($entity, $tmp, $type, $coords);
122 
123  $tmp->delete();
124 
125  return $result;
126  }
127 
139  public function saveIconFromLocalFile(\ElggEntity $entity, $filename, $type = 'icon', array $coords = []) {
140  if (!file_exists($filename) || !is_readable($filename)) {
141  throw new InvalidParameterException(__METHOD__ . " expects a readable local file. $filename is not readable");
142  }
143 
144  $tmp = new \ElggTempFile();
145  $tmp->setFilename(uniqid() . basename($filename));
146  $tmp->open('write');
147  $tmp->close();
148 
149  copy($filename, $tmp->getFilenameOnFilestore());
150 
151  $tmp->mimetype = $this->mimetype->getMimeType($tmp->getFilenameOnFilestore());
152  $tmp->simpletype = $this->mimetype->getSimpleType($tmp->mimetype);
153 
154  $result = $this->saveIcon($entity, $tmp, $type, $coords);
155 
156  $tmp->delete();
157 
158  return $result;
159  }
160 
172  public function saveIconFromElggFile(\ElggEntity $entity, \ElggFile $file, $type = 'icon', array $coords = []) {
173  if (!$file->exists()) {
174  throw new InvalidParameterException(__METHOD__ . ' expects an instance of ElggFile with an existing file on filestore');
175  }
176 
177  $tmp = new \ElggTempFile();
178  $tmp->setFilename(uniqid() . basename($file->getFilenameOnFilestore()));
179  $tmp->open('write');
180  $tmp->close();
181 
182  copy($file->getFilenameOnFilestore(), $tmp->getFilenameOnFilestore());
183 
184  $tmp->mimetype = $this->mimetype->getMimeType($tmp->getFilenameOnFilestore(), $file->getMimeType() ?: '');
185  $tmp->simpletype = $this->mimetype->getSimpleType($tmp->mimetype);
186 
187  $result = $this->saveIcon($entity, $tmp, $type, $coords);
188 
189  $tmp->delete();
190 
191  return $result;
192  }
193 
204  public function saveIcon(\ElggEntity $entity, \ElggFile $file, $type = 'icon', array $coords = []) {
205 
206  $type = (string) $type;
207  if (!strlen($type)) {
208  $this->getLogger()->error('Icon type passed to ' . __METHOD__ . ' can not be empty');
209  return false;
210  }
211 
212  $entity_type = $entity->getType();
213 
214  $file = $this->hooks->trigger("entity:$type:prepare", $entity_type, [
215  'entity' => $entity,
216  'file' => $file,
217  ], $file);
218 
219  if (!$file instanceof \ElggFile || !$file->exists() || $file->getSimpleType() !== 'image') {
220  $this->getLogger()->error('Source file passed to ' . __METHOD__ . ' can not be resolved to a valid image');
221  return false;
222  }
223 
224  $this->prepareIcon($file->getFilenameOnFilestore());
225 
226  $x1 = (int) elgg_extract('x1', $coords);
227  $y1 = (int) elgg_extract('y1', $coords);
228  $x2 = (int) elgg_extract('x2', $coords);
229  $y2 = (int) elgg_extract('y2', $coords);
230 
231  $created = $this->hooks->trigger("entity:$type:save", $entity_type, [
232  'entity' => $entity,
233  'file' => $file,
234  'x1' => $x1,
235  'y1' => $y1,
236  'x2' => $x2,
237  'y2' => $y2,
238  ], false);
239 
240  // did someone else handle saving the icon?
241  if ($created !== true) {
242  // remove existing icons
243  $this->deleteIcon($entity, $type, true);
244 
245  // save master image
246  $store = $this->generateIcon($entity, $file, $type, $coords, 'master');
247 
248  if (!$store) {
249  $this->deleteIcon($entity, $type);
250  return false;
251  }
252  }
253 
254  if ($type == 'icon') {
255  $entity->icontime = time();
256  if ($x1 || $y1 || $x2 || $y2) {
257  $entity->x1 = $x1;
258  $entity->y1 = $y1;
259  $entity->x2 = $x2;
260  $entity->y2 = $y2;
261  }
262  } else {
263  if ($x1 || $y1 || $x2 || $y2) {
264  $entity->{"{$type}_coords"} = serialize([
265  'x1' => $x1,
266  'y1' => $y1,
267  'x2' => $x2,
268  'y2' => $y2,
269  ]);
270  }
271  }
272 
273  $this->hooks->trigger("entity:$type:saved", $entity->getType(), [
274  'entity' => $entity,
275  'x1' => $x1,
276  'y1' => $y1,
277  'x2' => $x2,
278  'y2' => $y2,
279  ]);
280 
281  return true;
282  }
283 
291  protected function prepareIcon($filename) {
292 
293  // fix orientation
294  $temp_file = new \ElggTempFile();
295  $temp_file->setFilename(uniqid() . basename($filename));
296 
297  copy($filename, $temp_file->getFilenameOnFilestore());
298 
299  $rotated = $this->images->fixOrientation($temp_file->getFilenameOnFilestore());
300 
301  if ($rotated) {
302  copy($temp_file->getFilenameOnFilestore(), $filename);
303  }
304 
305  $temp_file->delete();
306  }
307 
319  protected function generateIcon(\ElggEntity $entity, \ElggFile $file, $type = 'icon', $coords = [], $icon_size = '') {
320 
321  if (!$file->exists()) {
322  $this->getLogger()->error('Trying to generate an icon from a non-existing file');
323  return false;
324  }
325 
326  $x1 = (int) elgg_extract('x1', $coords);
327  $y1 = (int) elgg_extract('y1', $coords);
328  $x2 = (int) elgg_extract('x2', $coords);
329  $y2 = (int) elgg_extract('y2', $coords);
330 
331  $sizes = $this->getSizes($entity->getType(), $entity->getSubtype(), $type);
332 
333  if (!empty($icon_size) && !isset($sizes[$icon_size])) {
334  $this->getLogger()->warning("The provided icon size '{$icon_size}' doesn't exist for icon type '{$type}'");
335  return false;
336  }
337 
338  foreach ($sizes as $size => $opts) {
339  if (!empty($icon_size) && ($icon_size !== $size)) {
340  // only generate the given icon size
341  continue;
342  }
343 
344  // check if the icon config allows cropping
345  if (!(bool) elgg_extract('crop', $opts, true)) {
346  $coords = [
347  'x1' => 0,
348  'y1' => 0,
349  'x2' => 0,
350  'y2' => 0,
351  ];
352  }
353 
354  $icon = $this->getIcon($entity, $size, $type, false);
355 
356  // We need to make sure that file path is readable by
357  // Imagine\Image\ImagineInterface::save(), as it fails to
358  // build the directory structure on owner's filestore otherwise
359  $icon->open('write');
360  $icon->close();
361 
362  // Save the image without resizing or cropping if the
363  // image size value is an empty array
364  if (is_array($opts) && empty($opts)) {
365  copy($file->getFilenameOnFilestore(), $icon->getFilenameOnFilestore());
366  continue;
367  }
368 
369  $source = $file->getFilenameOnFilestore();
370  $destination = $icon->getFilenameOnFilestore();
371 
372  $resize_params = array_merge($opts, $coords);
373 
374  $image_service = _elgg_services()->imageService;
375  $image_service->setLogger($this->getLogger());
376 
377  if (!_elgg_services()->imageService->resize($source, $destination, $resize_params)) {
378  $this->getLogger()->error("Failed to create {$size} icon from
379  {$file->getFilenameOnFilestore()} with coords [{$x1}, {$y1}],[{$x2}, {$y2}]");
380  return false;
381  }
382  }
383 
384  return true;
385  }
386 
402  public function getIcon(\ElggEntity $entity, $size, $type = 'icon', $generate = true) {
403 
405 
406  $params = [
407  'entity' => $entity,
408  'size' => $size,
409  'type' => $type,
410  ];
411 
412  $entity_type = $entity->getType();
413 
414  $default_icon = new \ElggIcon();
415  $default_icon->owner_guid = $entity->guid;
416  $default_icon->setFilename("icons/$type/$size.jpg");
417 
418  $icon = $this->hooks->trigger("entity:$type:file", $entity_type, $params, $default_icon);
419  if (!$icon instanceof \ElggIcon) {
420  throw new InvalidParameterException("'entity:$type:file', $entity_type hook must return an instance of ElggIcon");
421  }
422 
423  if ($size !== 'master' && $this->hasWebPSupport()) {
424  if (pathinfo($icon->getFilename(), PATHINFO_EXTENSION) === 'jpg') {
425  $icon->setFilename(substr($icon->getFilename(), 0, -3) . 'webp');
426  }
427  }
428 
429  if ($icon->exists() || !$generate) {
430  return $icon;
431  }
432 
433  if ($size === 'master') {
434  // don't try to generate for master
435  return $icon;
436  }
437 
438  // try to generate icon based on master size
439  $master_icon = $this->getIcon($entity, 'master', $type, false);
440  if (!$master_icon->exists()) {
441  return $icon;
442  }
443 
444  if ($type === 'icon') {
445  $coords = [
446  'x1' => $entity->x1,
447  'y1' => $entity->y1,
448  'x2' => $entity->x2,
449  'y2' => $entity->y2,
450  ];
451  } else {
452  $coords = $entity->{"{$type}_coords"};
453  $coords = empty($coords) ? [] : unserialize($coords);
454  }
455 
456  $this->generateIcon($entity, $master_icon, $type, $coords, $size);
457 
458  return $icon;
459  }
460 
470  public function deleteIcon(\ElggEntity $entity, $type = 'icon', $retain_master = false) {
471  $delete = $this->hooks->trigger("entity:$type:delete", $entity->getType(), [
472  'entity' => $entity,
473  ], true);
474 
475  if ($delete === false) {
476  return false;
477  }
478 
479  $result = true;
480  $supported_extensions = [
481  'jpg',
482  ];
483  if ($this->images->hasWebPSupport()) {
484  $supported_extensions[] = 'webp';
485  }
486 
487  $sizes = array_keys($this->getSizes($entity->getType(), $entity->getSubtype(), $type));
488  foreach ($sizes as $size) {
489  if ($size === 'master' && $retain_master) {
490  continue;
491  }
492 
493  $icon = $this->getIcon($entity, $size, $type, false);
494  $result &= $icon->delete();
495 
496  // make sure we remove all supported images (jpg and webp)
497  $current_extension = pathinfo($icon->getFilename(), PATHINFO_EXTENSION);
498  $extensions = $supported_extensions;
499  foreach ($extensions as $extension) {
500  if ($current_extension === $extension) {
501  // already removed
502  continue;
503  }
504 
505  // replace the extension
506  $parts = explode('.', $icon->getFilename());
507  array_pop($parts);
508  $parts[] = $extension;
509 
510  // set new filename and remove the file
511  $icon->setFilename(implode('.', $parts));
512  $result &= $icon->delete();
513  }
514  }
515 
516  if ($type == 'icon') {
517  unset($entity->icontime);
518  unset($entity->x1);
519  unset($entity->y1);
520  unset($entity->x2);
521  unset($entity->y2);
522  } else {
523  unset($entity->{"{$type}_coords"});
524  }
525 
526  return $result;
527  }
528 
539  public function getIconURL(\ElggEntity $entity, $params = []) {
540  if (is_array($params)) {
541  $size = elgg_extract('size', $params, 'medium');
542  } else {
543  $size = is_string($params) ? $params : 'medium';
544  $params = [];
545  }
546 
548 
549  $params['entity'] = $entity;
550  $params['size'] = $size;
551 
552  $type = elgg_extract('type', $params) ? : 'icon';
553  $entity_type = $entity->getType();
554 
555  $url = $this->hooks->trigger("entity:{$type}:url", $entity_type, $params, null);
556  if ($url == null) {
557  if ($this->hasIcon($entity, $size, $type)) {
558  $icon = $this->getIcon($entity, $size, $type);
559  $default_use_cookie = (bool) elgg_get_config('session_bound_entity_icons');
560  $url = $icon->getInlineURL((bool) elgg_extract('use_cookie', $params, $default_use_cookie));
561  } else {
562  $url = $this->getFallbackIconUrl($entity, $params);
563  }
564  }
565 
566  if ($url) {
567  return elgg_normalize_url($url);
568  }
569  }
570 
579  public function getFallbackIconUrl(\ElggEntity $entity, array $params = []) {
580 
581  $type = elgg_extract('type', $params) ? : 'icon';
582  $size = elgg_extract('size', $params) ? : 'medium';
583 
584  $entity_type = $entity->getType();
585  $entity_subtype = $entity->getSubtype();
586 
587  $exts = ['svg', 'gif', 'png', 'jpg'];
588 
589  foreach ($exts as $ext) {
590  foreach ([$entity_subtype, 'default'] as $subtype) {
591  if ($ext == 'svg' && elgg_view_exists("$type/$entity_type/$subtype.svg", 'default')) {
592  return elgg_get_simplecache_url("$type/$entity_type/$subtype.svg");
593  }
594  if (elgg_view_exists("$type/$entity_type/$subtype/$size.$ext", 'default')) {
595  return elgg_get_simplecache_url("$type/$entity_type/$subtype/$size.$ext");
596  }
597  }
598  }
599 
600  if (elgg_view_exists("$type/default/$size.png", 'default')) {
601  return elgg_get_simplecache_url("$type/default/$size.png");
602  }
603  }
604 
614  public function getIconLastChange(\ElggEntity $entity, $size, $type = 'icon') {
615  $icon = $this->getIcon($entity, $size, $type);
616  if ($icon->exists()) {
617  return $icon->getModifiedTime();
618  }
619  }
620 
630  public function hasIcon(\ElggEntity $entity, $size, $type = 'icon') {
631  $icon = $this->getIcon($entity, $size, $type);
632  return $icon->exists() && $icon->getSize() > 0;
633  }
634 
645  public function getSizes($entity_type = null, $entity_subtype = null, $type = 'icon') {
646  $sizes = [];
647  if (!$type) {
648  $type = 'icon';
649  }
650  if ($type == 'icon') {
651  $sizes = $this->config->icon_sizes;
652  }
653  $params = [
654  'type' => $type,
655  'entity_type' => $entity_type,
656  'entity_subtype' => $entity_subtype,
657  ];
658  if ($entity_type) {
659  $sizes = $this->hooks->trigger("entity:$type:sizes", $entity_type, $params, $sizes);
660  }
661 
662  if (!is_array($sizes)) {
663  throw new InvalidParameterException("The icon size configuration for image type '$type' " .
664  "must be an associative array of image size names and their properties");
665  }
666 
667  // lazy generation of icons requires a 'master' size
668  // this ensures a default config for 'master' size
669  $sizes['master'] = elgg_extract('master', $sizes, [
670  'w' => 10240,
671  'h' => 10240,
672  'square' => false,
673  'upscale' => false,
674  'crop' => false,
675  ]);
676 
677  if (!isset($sizes['master']['crop'])) {
678  $sizes['master']['crop'] = false;
679  }
680 
681  return $sizes;
682  }
683 
691  protected function detectCroppingCoordinates() {
692 
693  $auto_coords = [
694  'x1' => get_input('x1'),
695  'x2' => get_input('x2'),
696  'y1' => get_input('y1'),
697  'y2' => get_input('y2'),
698  ];
699 
700  $auto_coords = array_filter($auto_coords, function($value) {
701  return !elgg_is_empty($value) && is_numeric($value);
702  });
703 
704  if (count($auto_coords) !== 4) {
705  return false;
706  }
707 
708  // make ints
709  array_walk($auto_coords, function (&$value) {
710  $value = (int) $value;
711  });
712 
713  return $auto_coords;
714  }
715 
721  protected function hasWebPSupport(): bool {
722  return in_array('image/webp', $this->request->getAcceptableContentTypes()) && $this->images->hasWebPSupport();
723  }
724 }
elgg_view_exists($view, $viewtype= '', $recurse=true)
Returns whether the specified view exists.
Definition: views.php:152
getSubtype()
Get the entity subtype.
deleteIcon(\ElggEntity $entity, $type= 'icon', $retain_master=false)
Removes all icon files and metadata for the passed type of icon.
$source
$input_name
Definition: crop.php:24
$params
Saves global plugin settings.
Definition: save.php:13
saveIconFromUploadedFile(\ElggEntity $entity, $input_name, $type= 'icon', array $coords=[])
Saves icons using an uploaded file as the source.
elgg_normalize_url($url)
Definition: output.php:153
saveIconFromElggFile(\ElggEntity $entity,\ElggFile $file, $type= 'icon', array $coords=[])
Saves icons using a file located in the data store as the source.
$request
Definition: livesearch.php:11
elgg_get_simplecache_url($view, $subview= '')
Get the URL for the cached view.
Definition: cache.php:139
if($icon===false) if($icon!== '') $icon_size
Definition: icon.php:22
saveIconFromLocalFile(\ElggEntity $entity, $filename, $type= 'icon', array $coords=[])
Saves icons using a local file as the source.
exists()
Returns if the file exists.
Definition: ElggFile.php:344
getSizes($entity_type=null, $entity_subtype=null, $type= 'icon')
Returns a configuration array of icon sizes.
getSimpleType()
Get the simple type of the file.
Definition: ElggFile.php:169
getFallbackIconUrl(\ElggEntity $entity, array $params=[])
Returns default/fallback icon.
$delete
hasIcon(\ElggEntity $entity, $size, $type= 'icon')
Returns if the entity has an icon of the passed type.
prepareIcon($filename)
Prepares an icon.
$type
Definition: delete.php:21
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:10
elgg_strtolower()
Wrapper function for mb_strtolower().
Definition: mb_wrapper.php:154
getIconURL(\ElggEntity $entity, $params=[])
Get the URL for this entity&#39;s icon.
$value
Definition: generic.php:51
elgg_is_empty($value)
Check if a value isn&#39;t empty, but allow 0 and &#39;0&#39;.
Definition: input.php:179
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
getFilenameOnFilestore()
Return the filename of this file as it is/will be stored on the filestore, which may be different to ...
Definition: ElggFile.php:111
detectCroppingCoordinates()
Automagicly detect cropping coordinates.
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
if(function_exists('apache_get_version')) $icon
Definition: generic.php:49
__construct(Config $config, PluginHooksService $hooks, EntityTable $entities, UploadService $uploads, ImageService $images, MimeTypeService $mimetype, HttpRequest $request)
Constructor.
$entity
Definition: reset.php:8
Entity icon service.
get_input($variable, $default=null, $filter_result=true)
Parameter input functions.
Definition: input.php:20
generateIcon(\ElggEntity $entity,\ElggFile $file, $type= 'icon', $coords=[], $icon_size= '')
Generate an icon for the given entity.
getMimeType()
Get the mime type of the file.
Definition: ElggFile.php:138
getIcon(\ElggEntity $entity, $size, $type= 'icon', $generate=true)
Returns entity icon as an ElggIcon object The icon file may or may not exist on filestore.
hasWebPSupport()
Checks if browser has WebP support and if the webserver is able to generate.
Image manipulation service.
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:547
getIconLastChange(\ElggEntity $entity, $size, $type= 'icon')
Returns the timestamp of when the icon was changed.
$size
Definition: thumb.php:23
$extensions
getLogger()
Returns logger.
Definition: Loggable.php:37
and give any other recipients of the Program a copy of this License along with the Program You may charge a fee for the physical act of transferring a copy
Definition: LICENSE.txt:140
getType()
Returns the entity type.
$subtype
Definition: delete.php:22
$input
Form field view.
Definition: field.php:13
saveIcon(\ElggEntity $entity,\ElggFile $file, $type= 'icon', array $coords=[])
Saves icons using a created temporary file.
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
$filename
Entity icon class.
Definition: ElggIcon.php:8
_elgg_services()
Get the global service provider.
Definition: elgglib.php:638
Public service related to MIME type detection.
Entity table database service.
Definition: EntityTable.php:26
elgg_get_config($name, $default=null)
Get an Elgg configuration value.
$extension
Definition: default.php:25
File upload handling service.