Added route options to pass width and height in URI (for dynamic image sizes) #42

Closed
wants to merge 7 commits into
from
@@ -4,12 +4,19 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Liip\ImagineBundle\Imagine\Data\DataManager;
use Liip\ImagineBundle\Imagine\Filter\FilterManager;
+use Liip\ImagineBundle\Imagine\Filter\FilterConfiguration;
class ImagineController
{
+ /**
+ * @var FilterConfiguration
+ */
+ protected $filterConfiguration;
+
/**
* @var DataManager
*/
@@ -32,8 +39,9 @@ class ImagineController
* @param FilterManager $filterManager
* @param CacheManager $cacheManager
*/
- public function __construct(DataManager $dataManager, FilterManager $filterManager, CacheManager $cacheManager)
+ public function __construct(FilterConfiguration $filterConfiguration, DataManager $dataManager, FilterManager $filterManager, CacheManager $cacheManager)
{
+ $this->filterConfiguration = $filterConfiguration;
$this->dataManager = $dataManager;
$this->filterManager = $filterManager;
$this->cacheManager = $cacheManager;
@@ -52,10 +60,16 @@ public function __construct(DataManager $dataManager, FilterManager $filterManag
*/
public function filterAction(Request $request, $path, $filter)
{
+ $filterConfiguration = $this->filterConfiguration->updateFromRequest($filter, $request);
+
$targetPath = $this->cacheManager->resolve($request, $path, $filter);
if ($targetPath instanceof Response) {
return $targetPath;
}
+
+ if (false === $this->filterConfiguration->isValidAccessKey($filter, $path, $request)) {
+ throw new NotFoundHttpException('Incorrect hash');
+ }
$image = $this->dataManager->find($filter, $path);
$response = $this->filterManager->get($request, $filter, $image, $path);
@@ -48,6 +48,10 @@ public function getConfigTreeBuilder()
->scalarNode('cache')->defaultNull()->end()
->scalarNode('data_loader')->defaultNull()->end()
->scalarNode('controller_action')->defaultNull()->end()
+ ->arrayNode('route')
+ ->useAttributeAsKey('name')
+ ->prototype('variable')->end()
+ ->end()
->arrayNode('filters')
->useAttributeAsKey('name')
->prototype('array')
@@ -106,7 +106,7 @@ private function getResolver($filter)
* Gets filtered path for rendering in the browser
*
* @param string $path
- * @param string $filter
+ * @param array|string $filter Filter name and parameters for the router
* @param boolean $absolute
*
* @return string
@@ -115,10 +115,25 @@ public function getBrowserPath($targetPath, $filter, $absolute = false)
{
$params = array('path' => ltrim($targetPath, '/'));
+ // Params passed from template may be array
+ if (is_array($filter)) {
+ $filterName = $filter[0];
+
+ $config = $this->filterConfig->get($filterName);
+ if (!empty($config["route"]["hash"])) {
+ $params["hash"] = substr(md5($filter[1] . "|" . $filter[2] . "|" . $targetPath), 0, 4);
+ }
+
+ $params['width'] = $filter[1];
+ $params['height'] = $filter[2];
+ } else {
+ $filterName = $filter;
+ }
+
return str_replace(
urlencode($params['path']),
urldecode($params['path']),
- $this->router->generate('_imagine_'.$filter, $params, $absolute)
+ $this->router->generate('_imagine_'.$filterName, $params, $absolute)
);
}
@@ -2,6 +2,8 @@
namespace Liip\ImagineBundle\Imagine\Filter;
+use Symfony\Component\HttpFoundation\Request;
+
class FilterConfiguration
{
/**
@@ -41,4 +43,52 @@ public function set($filter, array $config)
{
return $this->filters[$filter] = $config;
}
+
+ /**
+ * Update filter configuration with values from Request object
+ *
+ * @param string $filter
+ * @param Request $request
+ *
+ * @return array
+ */
+ public function updateFromRequest($filter, Request $request)
+ {
+ $filterConfiguration = $this->get($filter);
+
+ if (!empty($filterConfiguration['route'])) {
+ array_walk_recursive($filterConfiguration['filters'], function(&$item, $key) use ($request) {
+ if ($item !== ($lookupItem = str_replace('$', '', $item)) && $request->get($lookupItem)) {
+ $item = $request->get($lookupItem);
+ }
+ });
+
+ $filterConfiguration = $this->set($filter, $filterConfiguration);
+ }
+
+ return $filterConfiguration;
+ }
+
+ /**
+ * Verify if provided access key (hash) is valid?
+ *
+ * @param string $filter
+ * @param string $path
+ * @param Request $request
+ *
+ * @return boolean
+ */
+ public function isValidAccessKey($filter, $path, Request $request)
+ {
+
+ $filterConfiguration = $this->get($filter);
+
+ if (!isset($filterConfiguration["route"]["hash"]) || false === $filterConfiguration["route"]["hash"]) {
+ return true;
+ }
+
+ $validHash = substr(md5($request->get('width') . "|" . $request->get('height') . "|" . $path), 0, 4);
+
+ return $request->get('hash') == $validHash;
@lsmith77

lsmith77 Dec 19, 2011

Owner

hmm i am not sure what you are validating here .. it would still allow someone to generate urls with arbitrary width/height. i guess you need to add some server secret.

@antonbabenko

antonbabenko Dec 19, 2011

I made it in the easiest and fastest way, but in order to use server secret I think it is better to introduce hash validation service, which should be configured in imagine.xml.

@lsmith77

lsmith77 Dec 19, 2011

Owner

yeah .. which is another reason to move this out of this class .. and instead make it an optional feature that can be used from the controller

+ }
}
View
@@ -148,6 +148,19 @@ Or if you're using PHP templates:
<img src="<?php $this['imagine']->filter('/relative/path/to/image.jpg', 'my_thumb', true) ?>" />
```
+It is also possible to specify width and height in template. Refer to *Routing* section for more information about required configuration.
+
+``` jinja
+<img src="{{ '/relative/path/to/image.jpg' | imagine_filter(['my_thumb', 200, 300]) }}" />
+```
+
+Or if you are using PHP templates:
+
+``` php
+<img src="<?php $this['imagine']->filter('/relative/path/to/image.jpg', array('my_thumb', 200, 300)) ?>" />
+```
+
+
Note: Using the ``dev`` environment you might find that the images are not properly rendered when
using the template helper. This is likely caused by having ``intercept_redirect`` enabled in your
application configuration. To ensure that the images are rendered disable this option:
@@ -219,6 +232,7 @@ Each filter set that you specify has the following options:
- `data_loader` - override the default data loader
- `controller_action` - override the default controller action
- `format` - hardcodes the output format (aka the requested format is ignored)
+ - `route` - optional array with `pattern` and `requirements` key. Used to pass variables like width/height in URI. Refer to *Routing* section for more information.
## Built-in Filters
@@ -393,3 +407,32 @@ With a custom data loader it is possible to dynamically modify the configuration
be applied to the image. To do this simple store the filter configuration along with the
image. Inside the data loader read this configuration and dynamically change the configuration
for the given filter inside the ``FilterConfiguration`` instance.
+
+## Routing
+
+By specifying route `pattern` and `requirements` it is possible to pass parameters from URI.
+This is useful for passing image sizes dynamically and use them inside ``FilterConfiguration`` instance (refer to *Dynamic filters* for more information).
+
+Configuration looks like this:
+
+``` yaml
+liip_imagine:
+ filter_sets:
+ my_thumb:
+ route:
+ hash: true # true or false
+ pattern: /{width}x{height}
+ requirements: { width: '[\d]{1,4}', height: '[\d]{1,4}' }
+ filters:
+ thumbnail: { size: [$width, $height], mode: inset }
+```
+
+Route variables `width` and `height` are populated in filters configuration.
+
+Note: When `hash` is set to false, then it allows everyone to generate images of any size (which is potential security breach).
+Make sure that you have `hash` set to true, when using routes. Be very careful with this option and always enable it on production environment!
+
+`hash` parameter is checked only when image of specified size has not been created yet.
+When image has been cached then `hash` is not verified (means that everybody can access cached image, as usually).
+
+Valid image URL looks like: `http://localhost/cache/my_thumb/140x250/image.jpg?hash=eab3`
@@ -74,6 +74,7 @@
<!-- Controller -->
<service id="liip_imagine.controller" class="%liip_imagine.controller.class%">
+ <argument type="service" id="liip_imagine.filter.configuration" />
<argument type="service" id="liip_imagine.data.manager" />
<argument type="service" id="liip_imagine.filter.manager" />
<argument type="service" id="liip_imagine.cache.manager" />
@@ -40,11 +40,19 @@ public function load($resource, $type = null)
} elseif ('' !== $filter) {
$pattern .= '/'.$filter;
}
+
+ if (!empty($config['route']['pattern'])) {
+ $pattern .= $config['route']['pattern'];
+ }
$defaults = array(
'_controller' => empty($config['controller_action']) ? $this->controllerAction : $config['controller_action'],
'filter' => $filter,
);
+
+ if (!empty($config['route']['requirements'])) {
+ $requirements = array_merge($requirements, $config['route']['requirements']);
+ }
$routes->add('_imagine_'.$filter, new Route(
$pattern.'/{path}',