Elgg  Version 2.2
 All Classes Namespaces Files Functions Variables Pages
EntityIconService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
5 use DateTime;
9 use ElggEntity;
10 use ElggFile;
11 use ElggIcon;
13 use Symfony\Component\HttpFoundation\BinaryFileResponse;
14 use Symfony\Component\HttpFoundation\File\UploadedFile;
15 use Symfony\Component\HttpFoundation\Response;
16 
26  use TimeUsing;
27 
31  private $config;
32 
36  private $hooks;
37 
41  private $request;
42 
46  private $logger;
47 
51  private $entities;
52 
62  public function __construct(Config $config, PluginHooksService $hooks, Request $request, Logger $logger, EntityTable $entities) {
63  $this->config = $config;
64  $this->hooks = $hooks;
65  $this->request = $request;
66  $this->logger = $logger;
67  $this->entities = $entities;
68  }
69 
79  public function saveIconFromUploadedFile(ElggEntity $entity, $input_name, $type = 'icon', array $coords = array()) {
80  $files = $this->request->files;
81  if (!$files->has($input_name)) {
82  return false;
83  }
84 
85  $input = $files->get($input_name);
86  if (!$input instanceof UploadedFile || !$input->isValid()) {
87  return false;
88  }
89 
90  $tmp_filename = time() . $input->getClientOriginalName();
91  $tmp = new ElggFile();
92  $tmp->owner_guid = $entity->guid;
93  $tmp->setFilename("tmp/$tmp_filename");
94  $tmp->open('write');
95  $tmp->close();
96  // not using move_uploaded_file() for testing purposes
97  copy($input->getPathname(), $tmp->getFilenameOnFilestore());
98 
99  $tmp->mimetype = (new MimeTypeDetector())->getType($tmp_filename, $input->getClientMimeType());
100  $tmp->simpletype = elgg_get_file_simple_type($tmp->mimetype);
101 
102  $result = $this->saveIcon($entity, $tmp, $type, $coords);
103 
104  unlink($input->getPathname());
105  $tmp->delete();
106 
107  return $result;
108  }
109 
120  public function saveIconFromLocalFile(ElggEntity $entity, $filename, $type = 'icon', array $coords = array()) {
121  if (!file_exists($filename) || !is_readable($filename)) {
122  throw new InvalidParameterException(__METHOD__ . " expects a readable local file. $filename is not readable");
123  }
124 
125  $tmp_filename = time() . pathinfo($filename, PATHINFO_BASENAME);
126  $tmp = new ElggFile();
127  $tmp->owner_guid = $entity->guid;
128  $tmp->setFilename("tmp/$tmp_filename");
129  $tmp->open('write');
130  $tmp->close();
131  copy($filename, $tmp->getFilenameOnFilestore());
132 
133  $tmp->mimetype = (new MimeTypeDetector())->getType($tmp->getFilenameOnFilestore());
134  $tmp->simpletype = elgg_get_file_simple_type($tmp->mimetype);
135 
136  $result = $this->saveIcon($entity, $tmp, $type, $coords);
137 
138  $tmp->delete();
139 
140  return $result;
141  }
142 
153  public function saveIconFromElggFile(ElggEntity $entity, ElggFile $file, $type = 'icon', array $coords = array()) {
154  if (!$file->exists()) {
155  throw new InvalidParameterException(__METHOD__ . ' expects an instance of ElggFile with an existing file on filestore');
156  }
157 
158  $tmp_filename = time() . pathinfo($file->getFilenameOnFilestore(), PATHINFO_BASENAME);
159  $tmp = new ElggFile();
160  $tmp->owner_guid = $entity->guid;
161  $tmp->setFilename("tmp/$tmp_filename");
162  $tmp->open('write');
163  $tmp->close();
164  copy($file->getFilenameOnFilestore(), $tmp->getFilenameOnFilestore());
165 
166  $tmp->mimetype = (new MimeTypeDetector())->getType($tmp->getFilenameOnFilestore(), $file->getMimeType());
167  $tmp->simpletype = elgg_get_file_simple_type($tmp->mimetype);
168 
169  $result = $this->saveIcon($entity, $tmp, $type, $coords);
170 
171  $tmp->delete();
172 
173  return $result;
174  }
175 
185  public function saveIcon(ElggEntity $entity, ElggFile $file, $type = 'icon', array $coords = array()) {
186 
187  $entity_type = $entity->getType();
188  $entity_subtype = $entity->getSubtype();
189 
190  $x1 = (int) elgg_extract('x1', $coords);
191  $y1 = (int) elgg_extract('y1', $coords);
192  $x2 = (int) elgg_extract('x2', $coords);
193  $y2 = (int) elgg_extract('y2', $coords);
194 
195  $file = $this->hooks->trigger("entity:$type:prepare", $entity_type, [
196  'entity' => $entity,
197  'file' => $file,
198  ], $file);
199 
200  if (!$file instanceof ElggFile || !$file->exists() || $file->getSimpleType() !== 'image') {
201  $this->logger->error('Source file passed to ' . __METHOD__ . ' can not be resolved to a valid image');
202  return false;
203  }
204 
205  $cropping_mode = $x1 || $y1 || $x2 || $y2;
206  if (!$cropping_mode) {
207  $this->deleteIcon($entity, $type);
208  }
209 
210  $success = function() use ($entity, $type, $x1, $y1, $x2, $y2) {
211  if ($type == 'icon') {
212  $entity->icontime = time();
213  if ($x1 || $y1 || $x2 || $y2) {
214  $entity->x1 = $x1;
215  $entity->y1 = $y1;
216  $entity->x2 = $x2;
217  $entity->y2 = $y2;
218  }
219  }
220  $this->hooks->trigger("entity:$type:saved", $entity->getType(), [
221  'entity' => $entity,
222  'x1' => $x1,
223  'y1' => $y1,
224  'x2' => $x2,
225  'y2' => $y2,
226  ]);
227  return true;
228  };
229 
230  $fail = function() use ($entity, $type) {
231  $this->deleteIcon($entity, $type);
232  return false;
233  };
234 
235  $created = $this->hooks->trigger("entity:$type:save", $entity_type, [
236  'entity' => $entity,
237  'file' => $file,
238  'x1' => $x1,
239  'y1' => $y1,
240  'x2' => $x2,
241  'y2' => $y2,
242  ], false);
243 
244  if ($created === true) {
245  return $success();
246  }
247 
248  $sizes = $this->getSizes($entity_type, $entity_subtype, $type);
249 
250  foreach ($sizes as $size => $opts) {
251 
252  $width = (int) elgg_extract('w', $opts);
253  $height = (int) elgg_extract('h', $opts);
254  $square = (bool) elgg_extract('square', $opts);
255  $upscale = (bool) elgg_extract('upscale', $opts);
256 
257  if ($type === 'icon' && $cropping_mode && $square === false && $size !== 'large') {
258  // In cropping mode, we want to preserve non-square images the way they are.
259  // For BC, we need to crop the large icon into a square if cropping coordinates are provided.
260  // There is a problem with cropping large icons however. See #9663 and EntityIconServiceTest::testIconDimensionsAfterResize
261  continue;
262  }
263 
264  $icon = $this->getIcon($entity, $size, $type);
265 
266  // Save the image without resizing or cropping if the
267  // image size value is an empty array
268  if (is_array($opts) && empty($opts)) {
269  copy($file->getFilenameOnFilestore(), $icon->getFilenameOnFilestore());
270  continue;
271  }
272 
273  $image_bytes = get_resized_image_from_existing_file($file->getFilenameOnFilestore(), $width, $height, $square, $x1, $y1, $x2, $y2, $upscale);
274  if ($image_bytes) {
275  $icon->open("write");
276  $icon->write($image_bytes);
277  $icon->close();
278  unset($image_bytes);
279  } else {
280  $this->logger->error("Failed to create {$size} icon from {$file->getFilenameOnFilestore()} with coords [{$x1}, {$y1}],[{$x2}, {$y2}]");
281  return $fail();
282  }
283  }
284 
285  return $success();
286  }
287 
300  public function getIcon(ElggEntity $entity, $size, $type = 'icon') {
301 
303 
304  $params = [
305  'entity' => $entity,
306  'size' => $size,
307  'type' => $type,
308  ];
309 
310  $entity_type = $entity->getType();
311 
312  $default_icon = new ElggIcon();
313  $default_icon->owner_guid = $entity->guid;
314  $default_icon->setFilename("icons/$type/$size.jpg");
315 
316  $icon = $this->hooks->trigger("entity:$type:file", $entity_type, $params, $default_icon);
317  if (!$icon instanceof ElggIcon) {
318  throw new InvalidParameterException("'entity:$type:file', $entity_type hook must return an instance of ElggIcon");
319  }
320 
321  return $icon;
322  }
323 
331  public function deleteIcon(ElggEntity $entity, $type = 'icon') {
332  $delete = $this->hooks->trigger("entity:$type:delete", $entity->getType(), [
333  'entity' => $entity,
334  ], true);
335 
336  if ($delete === false) {
337  return;
338  }
339 
340  $sizes = array_keys($this->getSizes($entity->getType(), $entity->getSubtype(), $type));
341  foreach ($sizes as $size) {
342  $icon = $this->getIcon($entity, $size, $type);
343  $icon->delete();
344  }
345 
346  if ($type == 'icon') {
347  unset($entity->icontime);
348  unset($entity->x1);
349  unset($entity->y1);
350  unset($entity->x2);
351  unset($entity->y2);
352  }
353  }
354 
365  public function getIconURL(ElggEntity $entity, $params = array()) {
366  if (is_array($params)) {
367  $size = elgg_extract('size', $params, 'medium');
368  } else {
369  $size = is_string($params) ? $params : 'medium';
370  $params = array();
371  }
372 
374 
375  $params['entity'] = $entity;
376  $params['size'] = $size;
377 
378  $type = elgg_extract('type', $params) ? : 'icon';
379  $entity_type = $entity->getType();
380 
381  $url = $this->hooks->trigger("entity:$type:url", $entity_type, $params, null);
382  if ($url == null) {
383  $icon = $this->getIcon($entity, $size, $type);
384  $url = elgg_get_inline_url($icon, true);
385  if (!$url && $type == 'icon') {
386  $url = elgg_get_simplecache_url("icons/default/$size.png");
387  }
388  }
389 
390  return elgg_normalize_url($url);
391  }
392 
402  public function getIconLastChange(ElggEntity $entity, $size, $type = 'icon') {
403  $icon = $this->getIcon($entity, $size, $type);
404  if ($icon->exists()) {
405  return $icon->getModifiedTime();
406  }
407  }
408 
417  public function hasIcon(\ElggEntity $entity, $size, $type = 'icon') {
418  return $this->getIcon($entity, $size, $type)->exists();
419  }
420 
429  public function getSizes($entity_type = null, $entity_subtype = null, $type = 'icon') {
430  $sizes = [];
431  if (!$type) {
432  $type = 'icon';
433  }
434  if ($type == 'icon') {
435  $sizes = $this->config->get('icon_sizes');
436  }
437  $params = [
438  'type' => $type,
439  'entity_type' => $entity_type,
440  'entity_subtype' => $entity_subtype,
441  ];
442  if ($entity_type) {
443  $sizes = $this->hooks->trigger("entity:$type:sizes", $entity_type, $params, $sizes);
444  }
445 
446  if (!is_array($sizes)) {
447  throw new InvalidParameterException("The icon size configuration for image type '$type' " .
448  "must be an associative array of image size names and their properties");
449  }
450 
451  if (empty($sizes)) {
452  $this->logger->error("Failed to find size configuration for image of type '$type' for entity type " .
453  "'$entity_type'. Use the 'entity:$type:sizes, $entity_type' hook to define the icon sizes");
454 
455  }
456 
457  return $sizes;
458  }
459 
466  public function handleServeIconRequest($allow_removing_headers = true) {
467 
468  $response = new Response();
469  $response->setExpires($this->getCurrentTime('-1 day'));
470  $response->prepare($this->request);
471 
472  if ($allow_removing_headers) {
473  // clear cache-boosting headers set by PHP session
474  header_remove('Cache-Control');
475  header_remove('Pragma');
476  header_remove('Expires');
477  }
478 
479  $path = implode('/', $this->request->getUrlSegments());
480  if (!preg_match('~serve-icon/(\d+)/(.*+)$~', $path, $m)) {
481  return $response->setStatusCode(400)->setContent('Malformatted request URL');
482  }
483 
484  list(, $guid, $size) = $m;
485 
486  $entity = $this->entities->get($guid);
487  if (!$entity instanceof \ElggEntity) {
488  return $response->setStatusCode(404)->setContent('Item does not exist');
489  }
490 
491  $thumbnail = $entity->getIcon($size);
492  if (!$thumbnail->exists()) {
493  return $response->setStatusCode(404)->setContent('Icon does not exist');
494  }
495 
496  $if_none_match = $this->request->headers->get('if_none_match');
497  if (!empty($if_none_match)) {
498  // strip mod_deflate suffixes
499  $this->request->headers->set('if_none_match', str_replace('-gzip', '', $if_none_match));
500  }
501 
502  $filenameonfilestore = $thumbnail->getFilenameOnFilestore();
503  $last_updated = filemtime($filenameonfilestore);
504  $etag = '"' . $last_updated . '"';
505 
506  $response->setPrivate()
507  ->setEtag($etag)
508  ->setExpires($this->getCurrentTime('+1 day'))
509  ->setMaxAge(86400);
510 
511  if ($response->isNotModified($this->request)) {
512  return $response;
513  }
514 
515  $headers = [
516  'Content-Type' => (new MimeTypeDetector())->getType($filenameonfilestore),
517  ];
518  $response = new BinaryFileResponse($filenameonfilestore, 200, $headers, false, 'inline');
519  $response->prepare($this->request);
520 
521  $response->setPrivate()
522  ->setEtag($etag)
523  ->setExpires($this->getCurrentTime('+1 day'))
524  ->setMaxAge(86400);
525 
526  return $response;
527  }
528 
529 }
getSubtype()
Get the entity subtype.
Elgg HTTP request.
Definition: Request.php:12
get($name)
Return the value of an attribute or metadata.
Definition: ElggEntity.php:286
if(!array_key_exists($filename, $text_files)) $file
$m
Definition: metadata.php:11
elgg_normalize_url($url)
Definition: output.php:290
if(!$owner||!($owner instanceof ElggUser)||!$owner->canEdit()) $input
Definition: edit.php:19
$input_name
Definition: item.php:14
elgg_get_simplecache_url($view, $subview= '')
Get the URL for the cached view.
Definition: cache.php:136
$headers
Definition: default.php:14
$path
Definition: details.php:88
Access to configuration values.
Definition: Config.php:11
exists()
Returns if the file exists.
Definition: ElggFile.php:396
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:182
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:26
hasIcon(\ElggEntity $entity, $size, $type= 'icon')
Returns if the entity has an icon of the passed type.
$guid
Removes an admin notice.
getIconURL(ElggEntity $entity, $params=array())
Get the URL for this entity's icon.
elgg_strtolower()
Wrapper function for mb_strtolower().
Definition: mb_wrapper.php:174
saveIconFromLocalFile(ElggEntity $entity, $filename, $type= 'icon', array $coords=array())
Saves icons using a local file as the source.
$url
Definition: exceptions.php:24
getIcon($size, $type= 'icon')
Returns entity icon as an ElggIcon object The icon file may or may not exist on filestore.
if(!$owner) $icon
Definition: default.php:16
$params
Definition: login.php:72
getFilenameOnFilestore()
Return the filename of this file as it is/will be stored on the filestore, which may be different to ...
Definition: ElggFile.php:94
get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight, $square=false, $x1=0, $y1=0, $x2=0, $y2=0, $upscale=false)
Gets the jpeg contents of the resized version of an already uploaded image (Returns false if the file...
Definition: filestore.php:115
handleServeIconRequest($allow_removing_headers=true)
Handle request to /serve-icon handler.
elgg_get_inline_url(\ElggFile $file, $use_cookie=false, $expires= '')
Returns file's URL for inline display Suitable for displaying cacheable resources, such as user avatars.
Definition: filestore.php:613
saveIconFromUploadedFile(ElggEntity $entity, $input_name, $type= 'icon', array $coords=array())
Saves icons using an uploaded file as the source.
WARNING: API IN FLUX.
__construct(Config $config, PluginHooksService $hooks, Request $request, Logger $logger, EntityTable $entities)
Constructor.
getMimeType()
Get the mime type of the file.
Definition: ElggFile.php:120
deleteIcon(ElggEntity $entity, $type= 'icon')
Removes all icon files and metadata for the passed type of icon.
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:1310
getIconLastChange(ElggEntity $entity, $size, $type= 'icon')
Returns the timestamp of when the icon was changed.
saveIconFromElggFile(ElggEntity $entity, ElggFile $file, $type= 'icon', array $coords=array())
Saves icons using a file located in the data store as the source.
$size
Definition: default.php:20
Detect the MIME type of a file.
getType()
Returns the entity type.
saveIcon(ElggEntity $entity, ElggFile $file, $type= 'icon', array $coords=array())
Saves icons using a created temporary file.
$entity
Definition: delete.php:7
$filename
Entity icon class.
Definition: ElggIcon.php:6
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:11
if(!$owner||!($owner instanceof ElggUser)||!$owner->canEdit()) $coords
Definition: crop.php:15
foreach($resources as $id=> $href) if(!empty($resources_html)) $files
Definition: details.php:141
getIcon(ElggEntity $entity, $size, $type= 'icon')
Returns entity icon as an ElggIcon object The icon file may or may not exist on filestore.
elgg_get_file_simple_type($mime_type)
Returns the category of a file from its MIME type.
Definition: filestore.php:462
if(!$display_name) $type
Definition: delete.php:27