Permalink
Browse files

feature(embed): adds serve-icon page handler

Adds a handler to serve embedded icons. Adds elgg_get_embed_url() to
generate embed URLs.

Adds a trait to inject the time in components for testing.

Fixes #9582
  • Loading branch information...
hypeJunction authored and mrclay committed Apr 1, 2016
1 parent 958b8b7 commit e4d09f82e5d3a1029864c35a90668dbe5ff596a8
@@ -97,3 +97,12 @@ that access settings are respected and users do not share download URLs with som
You can also invalidated all previously generated URLs by updating file's modified time, e.g.
by using ``touch()``.
Embedding Files
---------------
Please note that due to their nature inline and download URLs are not suitable for embedding.
Embed URLs must be permanent, whereas inline and download URLs are volatile (bound to user session
and file modification time).
To embed an entity icon, use ``elgg_get_embed_url()``.
@@ -49,6 +49,8 @@ New API for handling entity icons
* ``ElggEntity::deleteIcon()`` - deletes entity icons
* ``ElggEntity::getIconLastChange()`` - return modified time of the icon file
* ``ElggEntity::hasIcon()`` - checks if an icon with given size has been created
* ``elgg_get_embed_url()`` - can be used to return an embed URL for an entity's icon (served via `/serve-icon` handler)
From 2.0 to 2.1
===============
@@ -192,7 +192,7 @@ public function __construct(\Elgg\Config $config) {
});
$this->setFactory('iconService', function(ServiceProvider $c) {
return new \Elgg\EntityIconService($c->config, $c->hooks, $c->request, $c->logger);
return new \Elgg\EntityIconService($c->config, $c->hooks, $c->request, $c->logger, $c->entityTable);
});
$this->setClassName('input', \Elgg\Http\Input::class);
@@ -2,13 +2,17 @@
namespace Elgg;
use DateTime;
use Elgg\Database\EntityTable;
use Elgg\Filesystem\MimeTypeDetector;
use Elgg\Http\Request;
use ElggEntity;
use ElggFile;
use ElggIcon;
use InvalidParameterException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Response;
/**
* WARNING: API IN FLUX. DO NOT USE DIRECTLY.
@@ -19,6 +23,7 @@
* @since 2.2
*/
class EntityIconService {
use TimeUsing;
/**
* @var Config
@@ -40,19 +45,26 @@ class EntityIconService {
*/
private $logger;
/**
* @var EntityTable
*/
private $entities;
/**
* Constructor
*
* @param Config $config Config
* @param PluginHooksService $hooks Hook registration service
* @param Request $request Http request
* @param Logger $logger Logger
* @param Config $config Config
* @param PluginHooksService $hooks Hook registration service
* @param Request $request Http request
* @param Logger $logger Logger
* @param EntityTable $entities Entity table
*/
public function __construct(Config $config, PluginHooksService $hooks, Request $request, Logger $logger) {
public function __construct(Config $config, PluginHooksService $hooks, Request $request, Logger $logger, EntityTable $entities) {
$this->config = $config;
$this->hooks = $hooks;
$this->request = $request;
$this->logger = $logger;
$this->entities = $entities;
}
/**
@@ -132,7 +144,7 @@ public function saveIconFromLocalFile(ElggEntity $entity, $filename, $type = 'ic
* Saves icons using a file located in the data store as the source.
*
* @param ElggEntity $entity Entity to own the icons
* @param string $file An ElggFile instance
* @param ElggFile $file An ElggFile instance
* @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
* @param array $coords An array of cropping coordinates x1, y1, x2, y2
* @return bool
@@ -228,15 +240,15 @@ public function saveIcon(ElggEntity $entity, ElggFile $file, $type = 'icon', arr
'x2' => $x2,
'y2' => $y2,
], false);
if ($created === true) {
return $success();
}
$sizes = $this->getSizes($entity_type, $entity_subtype, $type);
foreach ($sizes as $size => $opts) {
$width = (int) elgg_extract('w', $opts);
$height = (int) elgg_extract('h', $opts);
$square = (bool) elgg_extract('square', $opts);
@@ -316,13 +328,13 @@ public function deleteIcon(ElggEntity $entity, $type = 'icon') {
if ($delete === false) {
return;
}
$sizes = array_keys($this->getSizes($entity->getType(), $entity->getSubtype(), $type));
foreach ($sizes as $size) {
$icon = $this->getIcon($entity, $size, $type);
$icon->delete();
}
if ($type == 'icon') {
unset($entity->icontime);
unset($entity->x1);
@@ -426,4 +438,73 @@ public function getSizes($entity_type = null, $entity_subtype = null, $type = 'i
return $sizes;
}
/**
* Handle request to /serve-icon handler
*
* @param bool $allow_removing_headers Alter PHP's global headers to allow caching
* @return BinaryFileResponse
*/
public function handleServeIconRequest($allow_removing_headers = true) {
$response = new Response();
$response->setExpires($this->getCurrentTime('-1 day'));
$response->prepare($this->request);
if ($allow_removing_headers) {
// clear cache-boosting headers set by PHP session
header_remove('Cache-Control');
header_remove('Pragma');
header_remove('Expires');
}
$path = implode('/', $this->request->getUrlSegments());
if (!preg_match('~serve-icon/(\d+)/(.*+)$~', $path, $m)) {
return $response->setStatusCode(400)->setContent('Malformatted request URL');
}
list(, $guid, $size) = $m;
$entity = $this->entities->get($guid);
if (!$entity instanceof \ElggEntity) {
return $response->setStatusCode(404)->setContent('Item does not exist');
}
$thumbnail = $entity->getIcon($size);
if (!$thumbnail->exists()) {
return $response->setStatusCode(404)->setContent('Icon does not exist');
}
$if_none_match = $this->request->headers->get('if_none_match');
if (!empty($if_none_match)) {
// strip mod_deflate suffixes
$this->request->headers->set('if_none_match', str_replace('-gzip', '', $if_none_match));
}
$filenameonfilestore = $thumbnail->getFilenameOnFilestore();
$last_updated = filemtime($filenameonfilestore);
$etag = '"' . $last_updated . '"';
$response->setPrivate()
->setEtag($etag)
->setExpires($this->getCurrentTime('+1 day'))
->setMaxAge(86400);
if ($response->isNotModified($this->request)) {
return $response;
}
$headers = [
'Content-Type' => (new MimeTypeDetector())->getType($filenameonfilestore),
];
$response = new BinaryFileResponse($filenameonfilestore, 200, $headers, false, 'inline');
$response->prepare($this->request);
$response->setPrivate()
->setEtag($etag)
->setExpires($this->getCurrentTime('+1 day'))
->setMaxAge(86400);
return $response;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Elgg;
use DateTime;
/**
* Adds methods for setting the current time (for testing)
*
* @access private
*/
trait TimeUsing {
/**
* @var DateTime
*/
private $time;
/**
* Get the (cloned) time. If setCurrentTime() has not been set, this will return a new DateTime().
*
* @see DateTime::modify
*
* @param string $modifier Time modifier
* @return DateTime
*/
public function getCurrentTime($modifier = '') {
$time = $this->time ? $this->time : new DateTime();
$time = clone $time;
if ($modifier) {
$time->modify($modifier);
}
return $time;
}
/**
* Set the current time.
*
* @param DateTime $time Current time (empty for now)
* @return void
*/
public function setCurrentTime(DateTime $time = null) {
if (!$time) {
$time = new DateTime();
}
$this->time = clone $time;
}
}
View
@@ -488,6 +488,9 @@ function _elgg_filestore_init() {
// Unit testing
elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_filestore_test');
// Handler for serving embedded icons
elgg_register_page_handler('serve-icon', '_elgg_filestore_serve_icon_handler');
// Touch entity icons if entity access id has changed
elgg_register_event_handler('update:after', 'object', '_elgg_filestore_touch_icons');
elgg_register_event_handler('update:after', 'group', '_elgg_filestore_touch_icons');
@@ -607,8 +610,38 @@ function elgg_get_inline_url(\ElggFile $file, $use_cookie = false, $expires = ''
}
/**
* Reset icon URLs if access_id has changed
* Returns a URL suitable for embedding entity's icon in a text editor.
* We can not use elgg_get_inline_url() for these purposes due to a URL structure
* bound to user session and file modification time.
* This function returns a generic (permanent) URL that will then be resolved to
* an inline URL whenever requested.
*
* @param \ElggEntity $entity Entity
* @param string $size Size
* @return string
* @since 2.2
*/
function elgg_get_embed_url(\ElggEntity $entity, $size) {
return elgg_normalize_url("serve-icon/$entity->guid/$size");
}
/**
* Handler for /serve-icon resources
* /serve-icon/<entity_guid>/<size>
*
* @return void
* @access private
* @since 2.2
*/
function _elgg_filestore_serve_icon_handler() {
$response = _elgg_services()->iconService->handleServeIconRequest();
$response->send();
exit;
}
/**
* Reset icon URLs if access_id has changed
*
* @param string $event "update:after"
* @param string $type "object"|"group"
* @param ElggObject $entity Entity
Oops, something went wrong.

0 comments on commit e4d09f8

Please sign in to comment.