diff --git a/Controller/ImagineController.php b/Controller/ImagineController.php index ab8f131ed..638d8f17f 100644 --- a/Controller/ImagineController.php +++ b/Controller/ImagineController.php @@ -2,14 +2,11 @@ namespace Liip\ImagineBundle\Controller; -use Imagine\Image\ImagineInterface; +use Imagine\Exception\RuntimeException; use Liip\ImagineBundle\Imagine\Cache\CacheManager; use Liip\ImagineBundle\Imagine\Data\DataManager; use Liip\ImagineBundle\Imagine\Filter\FilterManager; - use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; class ImagineController { @@ -50,16 +47,20 @@ public function __construct(DataManager $dataManager, FilterManager $filterManag */ public function filterAction($path, $filter) { - if (!$this->cacheManager->isStored($path, $filter)) { - $binary = $this->dataManager->find($filter, $path); + try { + if (!$this->cacheManager->isStored($path, $filter)) { + $binary = $this->dataManager->find($filter, $path); - $this->cacheManager->store( - $this->filterManager->applyFilter($binary, $filter), - $path, - $filter - ); - } + $this->cacheManager->store( + $this->filterManager->applyFilter($binary, $filter), + $path, + $filter + ); + } - return new RedirectResponse($this->cacheManager->resolve($path, $filter), 301); + return new RedirectResponse($this->cacheManager->resolve($path, $filter), 301); + } catch (RuntimeException $e) { + throw new \RuntimeException(sprintf('Unable to create image for path "%s" and filter "%s". Message was "%s"', $path, $filter, $e->getMessage()), 0, $e); + } } } diff --git a/Imagine/Cache/Resolver/ProxyResolver.php b/Imagine/Cache/Resolver/ProxyResolver.php new file mode 100644 index 000000000..e07dda50a --- /dev/null +++ b/Imagine/Cache/Resolver/ProxyResolver.php @@ -0,0 +1,84 @@ + + */ +class ProxyResolver implements ResolverInterface +{ + /** + * @var ResolverInterface + */ + protected $resolver; + + /** + * a list of proxy hosts (picks a random one for each generation to seed browser requests among multiple hosts) + * + * @var array + */ + protected $hosts = array(); + + /** + * @param ResolverInterface $resolver + * @param string[] $hosts + */ + public function __construct(ResolverInterface $resolver, array $hosts) + { + $this->resolver = $resolver; + $this->hosts = $hosts; + } + + /** + * {@inheritDoc} + */ + public function resolve($path, $filter) + { + return $this->rewriteUrl($this->resolver->resolve($path, $filter)); + } + + /** + * {@inheritDoc} + */ + public function store(BinaryInterface $binary, $targetPath, $filter) + { + return $this->resolver->store($binary, $targetPath, $filter); + } + + /** + * {@inheritDoc} + */ + public function isStored($path, $filter) + { + return $this->resolver->isStored($path, $filter); + } + + /** + * {@inheritDoc} + */ + public function remove(array $paths, array $filters) + { + return $this->resolver->remove($paths, $filters); + } + + /** + * @param $url + * + * @return string + */ + protected function rewriteUrl($url) + { + if (empty($this->hosts)) { + return $url; + } + + $host = parse_url($url, PHP_URL_SCHEME).'://'.parse_url($url, PHP_URL_HOST); + $proxyHost = $this->hosts[rand(0, count($this->hosts) - 1)]; + + return str_replace($host, $proxyHost, $url); + } +} diff --git a/Imagine/Data/Loader/GridFSLoader.php b/Imagine/Data/Loader/GridFSLoader.php index b3bea65d5..d62c83608 100644 --- a/Imagine/Data/Loader/GridFSLoader.php +++ b/Imagine/Data/Loader/GridFSLoader.php @@ -34,14 +34,12 @@ public function find($id) { $image = $this->dm ->getRepository($this->class) - ->findAll() - ->getCollection() - ->findOne(array("_id" => new \MongoId($id))); + ->find(new \MongoId($id)); if (!$image) { throw new NotFoundHttpException(sprintf('Source image not found with id "%s"', $id)); } - return $image['file']->getBytes(); + return $image->getFile()->getBytes(); } } diff --git a/Imagine/Filter/Loader/BackgroundFilterLoader.php b/Imagine/Filter/Loader/BackgroundFilterLoader.php index d787cbdc8..d6273f031 100644 --- a/Imagine/Filter/Loader/BackgroundFilterLoader.php +++ b/Imagine/Filter/Loader/BackgroundFilterLoader.php @@ -1,6 +1,7 @@ imagine->create($image->getSize(), $background); + $size = $image->getSize(); + + if (isset($options['size'])) { + list($width, $height) = $options['size']; + + $size = new Box($width, $height); + $topLeft = new Point(($width - $image->getSize()->getWidth()) / 2, ($height - $image->getSize()->getHeight()) / 2); + } + + $canvas = $this->imagine->create($size, $background); return $canvas->paste($image, $topLeft); } diff --git a/Resources/doc/cache-resolver/proxy.md b/Resources/doc/cache-resolver/proxy.md new file mode 100644 index 000000000..8488399cb --- /dev/null +++ b/Resources/doc/cache-resolver/proxy.md @@ -0,0 +1,26 @@ +# ProxyResolver + +The ProxyResolver is a `decorator` for every other Resolver + +This Resolver adds the possibility to use Proxy Hosts for your Assets. +If no Proxy Domains are set, it behaves like the underlying `Resolver` + +## Set Proxy Domains + +In order to use this Resolver you must create a Service and inject some domains and your underlying Resolver + +``` yaml +services: + acme.imagine.cache.resolver.proxy: + class: Liip\ImagineBundle\Imagine\Cache\Resolver\ProxyResolver + arguments: + - "@acme.imagine.cache.resolver.amazon_s3" + - [ 'http://images0.domain.com', 'http://images1.domain.com','http://images2.domain.com' ] + tags: + - { name: 'liip_imagine.cache.resolver', resolver: 'proxy' } +``` + +Now your Resolver would generate following Urls `http://images0.domain.com/thumbs/article_thumb/foo.jpg` instead of the original path from the underlying Resolver `bucket.s3.awsamazoncloud.com/thumbs/article_thumb/foo.jpg` for every relevant Action. + +- [Back to cache resolvers](../cache-resolvers.md) +- [Back to the index](../index.md) diff --git a/Resources/doc/cache-resolvers.md b/Resources/doc/cache-resolvers.md index 8c68a559a..f6a0441ed 100644 --- a/Resources/doc/cache-resolvers.md +++ b/Resources/doc/cache-resolvers.md @@ -3,6 +3,7 @@ * [AmazonS3](cache-resolver/amazons3.md) * [AwsS3](cache-resolver/aws_s3.md) - for SDK version 2 * [CacheResolver](cache-resolver/cache.md) +* [ProxyResolver](cache-resolver/proxy.md) # Custom cache resolver diff --git a/Resources/doc/filters.md b/Resources/doc/filters.md index 15e64181e..4402ec67c 100644 --- a/Resources/doc/filters.md +++ b/Resources/doc/filters.md @@ -107,6 +107,16 @@ liip_imagine: background: { color: '#00FFFF' } ``` +If you provide a `size` it will create a new image (this size and given color), and apply the original image on top: + +``` yaml +liip_imagine: + filter_sets: + my_thumb: + filters: + background: { size: [1026, 684], color: '#fff' } +``` + ### The `watermark` filter The watermark filter pastes a second image onto your image while keeping its ratio. @@ -116,13 +126,14 @@ Configuration looks like this: liip_image: filter_sets: my_image: - watermark: - # Relative path to the watermark file (prepended with "%kernel.root_dir%/") - image: Resources/data/watermark.png - # Size of the watermark relative to the origin images size - size: 0.5 - # Position: One of topleft,top,topright,left,center,right,bottomleft,bottom,bottomright - position: center + filters: + watermark: + # Relative path to the watermark file (prepended with "%kernel.root_dir%/") + image: Resources/data/watermark.png + # Size of the watermark relative to the origin images size + size: 0.5 + # Position: One of topleft,top,topright,left,center,right,bottomleft,bottom,bottomright + position: center ``` ### The `auto_rotate` filter diff --git a/Tests/Imagine/Cache/Resolver/ProxyResolverTest.php b/Tests/Imagine/Cache/Resolver/ProxyResolverTest.php new file mode 100644 index 000000000..1355897ce --- /dev/null +++ b/Tests/Imagine/Cache/Resolver/ProxyResolverTest.php @@ -0,0 +1,117 @@ + + */ +class ProxyResolverTest extends \Phpunit_Framework_TestCase +{ + /** + * @var ResolverInterface + */ + private $primaryResolver; + + /** + * @var ProxyResolver + */ + private $resolver; + + public function setUp() + { + $this->primaryResolver = $this->getMock('Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface'); + + $this->resolver = new ProxyResolver($this->primaryResolver, array('http://images.example.com')); + } + + public function testProxyCallAndRewriteReturnedUrlOnResolve() + { + $expectedPath = '/foo/bar/bazz.png'; + $expectedFilter = 'test'; + + $this->primaryResolver + ->expects($this->once()) + ->method('resolve') + ->with($expectedPath, $expectedFilter) + ->will($this->returnValue('http://foo.com/thumbs/foo/bar/bazz.png')) + ; + + $result = $this->resolver->resolve($expectedPath, $expectedFilter); + + $this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result); + } + + public function testProxyCallAndRewriteReturnedUrlEvenSchemesDiffersOnResolve() + { + $expectedPath = '/foo/bar/bazz.png'; + $expectedFilter = 'test'; + + $this->primaryResolver + ->expects($this->once()) + ->method('resolve') + ->with($expectedPath, $expectedFilter) + ->will($this->returnValue('http://foo.com/thumbs/foo/bar/bazz.png')) + ; + + $result = $this->resolver->resolve($expectedPath, $expectedFilter); + + $this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result); + } + + public function testProxyCallAndReturnedValueOnIsStored() + { + $expectedPath = 'thePath'; + $expectedFilter = 'theFilter'; + + $this->primaryResolver + ->expects($this->once()) + ->method('isStored') + ->with($expectedPath, $expectedFilter) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->resolver->isStored($expectedPath, $expectedFilter)); + } + + public function testProxyCallOnStore() + { + $expectedPath = 'thePath'; + $expectedFilter = 'theFilter'; + $expectedBinary = new Binary('aContent', 'image/png', 'png'); + + $this->primaryResolver + ->expects($this->once()) + ->method('store') + ->with($expectedBinary, $expectedPath, $expectedFilter) + ; + + $this->resolver->store($expectedBinary, $expectedPath, $expectedFilter); + } + + public function testProxyCallOnRemove() + { + $expectedPaths = array('thePath'); + $expectedFilters = array('theFilter'); + + $this->primaryResolver + ->expects($this->once()) + ->method('remove') + ->with($expectedPaths, $expectedFilters) + ; + + $this->resolver->remove($expectedPaths, $expectedFilters); + } +} \ No newline at end of file