vendor/liip/imagine-bundle/Imagine/Cache/CacheManager.php line 109

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the `liip/LiipImagineBundle` project.
  4. *
  5. * (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
  6. *
  7. * For the full copyright and license information, please view the LICENSE.md
  8. * file that was distributed with this source code.
  9. */
  10. namespace Liip\ImagineBundle\Imagine\Cache;
  11. use Liip\ImagineBundle\Binary\BinaryInterface;
  12. use Liip\ImagineBundle\Events\CacheResolveEvent;
  13. use Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface;
  14. use Liip\ImagineBundle\Imagine\Filter\FilterConfiguration;
  15. use Liip\ImagineBundle\ImagineEvents;
  16. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  17. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  18. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  19. use Symfony\Component\Routing\RouterInterface;
  20. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
  21. class CacheManager
  22. {
  23. /**
  24. * @var FilterConfiguration
  25. */
  26. protected $filterConfig;
  27. /**
  28. * @var RouterInterface
  29. */
  30. protected $router;
  31. /**
  32. * @var ResolverInterface[]
  33. */
  34. protected $resolvers = [];
  35. /**
  36. * @var SignerInterface
  37. */
  38. protected $signer;
  39. /**
  40. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  41. */
  42. protected $dispatcher;
  43. /**
  44. * @var string
  45. */
  46. protected $defaultResolver;
  47. /**
  48. * @var bool
  49. */
  50. private $webpGenerate;
  51. /**
  52. * Constructs the cache manager to handle Resolvers based on the provided FilterConfiguration.
  53. *
  54. * @param string $defaultResolver
  55. * @param bool $webpGenerate
  56. */
  57. public function __construct(
  58. FilterConfiguration $filterConfig,
  59. RouterInterface $router,
  60. SignerInterface $signer,
  61. EventDispatcherInterface $dispatcher,
  62. $defaultResolver = null,
  63. $webpGenerate = false
  64. ) {
  65. $this->filterConfig = $filterConfig;
  66. $this->router = $router;
  67. $this->signer = $signer;
  68. $this->dispatcher = $dispatcher;
  69. $this->defaultResolver = $defaultResolver ?: 'default';
  70. $this->webpGenerate = $webpGenerate;
  71. }
  72. /**
  73. * Adds a resolver to handle cached images for the given filter.
  74. *
  75. * @param string $filter
  76. */
  77. public function addResolver($filter, ResolverInterface $resolver)
  78. {
  79. $this->resolvers[$filter] = $resolver;
  80. if ($resolver instanceof CacheManagerAwareInterface) {
  81. $resolver->setCacheManager($this);
  82. }
  83. }
  84. /**
  85. * Gets filtered path for rendering in the browser.
  86. * It could be the cached one or an url of filter action.
  87. *
  88. * @param string $path The path where the resolved file is expected
  89. * @param string $filter
  90. * @param string $resolver
  91. * @param int $referenceType
  92. *
  93. * @return string
  94. */
  95. public function getBrowserPath($path, $filter, array $runtimeConfig = [], $resolver = null, $referenceType = UrlGeneratorInterface::ABSOLUTE_URL)
  96. {
  97. if (!empty($runtimeConfig)) {
  98. $rcPath = $this->getRuntimePath($path, $runtimeConfig);
  99. return !$this->webpGenerate && $this->isStored($rcPath, $filter, $resolver) ?
  100. $this->resolve($rcPath, $filter, $resolver) :
  101. $this->generateUrl($path, $filter, $runtimeConfig, $resolver, $referenceType);
  102. }
  103. return !$this->webpGenerate && $this->isStored($path, $filter, $resolver) ?
  104. $this->resolve($path, $filter, $resolver) :
  105. $this->generateUrl($path, $filter, [], $resolver, $referenceType);
  106. }
  107. /**
  108. * Get path to runtime config image.
  109. *
  110. * @param string $path
  111. *
  112. * @return string
  113. */
  114. public function getRuntimePath($path, array $runtimeConfig)
  115. {
  116. $path = ltrim($path, '/');
  117. return 'rc/'.$this->signer->sign($path, $runtimeConfig).'/'.$path;
  118. }
  119. /**
  120. * Returns a web accessible URL.
  121. *
  122. * @param string $path The path where the resolved file is expected
  123. * @param string $filter The name of the imagine filter in effect
  124. * @param string $resolver
  125. * @param int $referenceType The type of reference to be generated (one of the UrlGenerator constants)
  126. *
  127. * @return string
  128. */
  129. public function generateUrl($path, $filter, array $runtimeConfig = [], $resolver = null, $referenceType = UrlGeneratorInterface::ABSOLUTE_URL)
  130. {
  131. $params = [
  132. 'path' => ltrim($path, '/'),
  133. 'filter' => $filter,
  134. ];
  135. if ($resolver) {
  136. $params['resolver'] = $resolver;
  137. }
  138. if (empty($runtimeConfig)) {
  139. $filterUrl = $this->router->generate('liip_imagine_filter', $params, $referenceType);
  140. } else {
  141. $params['filters'] = $runtimeConfig;
  142. $params['hash'] = $this->signer->sign($path, $runtimeConfig);
  143. $filterUrl = $this->router->generate('liip_imagine_filter_runtime', $params, $referenceType);
  144. }
  145. return $filterUrl;
  146. }
  147. /**
  148. * Checks whether the path is already stored within the respective Resolver.
  149. *
  150. * @param string $path
  151. * @param string $filter
  152. * @param string $resolver
  153. *
  154. * @return bool
  155. */
  156. public function isStored($path, $filter, $resolver = null)
  157. {
  158. return $this->getResolver($filter, $resolver)->isStored($path, $filter);
  159. }
  160. /**
  161. * Resolves filtered path for rendering in the browser.
  162. *
  163. * @param string $path
  164. * @param string $filter
  165. * @param string $resolver
  166. *
  167. * @throws NotFoundHttpException if the path can not be resolved
  168. *
  169. * @return string The url of resolved image
  170. */
  171. public function resolve($path, $filter, $resolver = null)
  172. {
  173. if (false !== mb_strpos($path, '/../') || 0 === mb_strpos($path, '../')) {
  174. throw new NotFoundHttpException(sprintf("Source image was searched with '%s' outside of the defined root path", $path));
  175. }
  176. $preEvent = new CacheResolveEvent($path, $filter);
  177. $this->dispatchWithBC($preEvent, ImagineEvents::PRE_RESOLVE);
  178. $url = $this->getResolver($preEvent->getFilter(), $resolver)->resolve($preEvent->getPath(), $preEvent->getFilter());
  179. $postEvent = new CacheResolveEvent($preEvent->getPath(), $preEvent->getFilter(), $url);
  180. $this->dispatchWithBC($postEvent, ImagineEvents::POST_RESOLVE);
  181. return $postEvent->getUrl();
  182. }
  183. /**
  184. * @see ResolverInterface::store
  185. *
  186. * @param string $path
  187. * @param string $filter
  188. * @param string $resolver
  189. */
  190. public function store(BinaryInterface $binary, $path, $filter, $resolver = null)
  191. {
  192. $this->getResolver($filter, $resolver)->store($binary, $path, $filter);
  193. }
  194. /**
  195. * @param string|string[]|null $paths
  196. * @param string|string[]|null $filters
  197. */
  198. public function remove($paths = null, $filters = null)
  199. {
  200. if (null === $filters) {
  201. $filters = array_keys($this->filterConfig->all());
  202. } elseif (!\is_array($filters)) {
  203. $filters = [$filters];
  204. }
  205. if (!\is_array($paths)) {
  206. $paths = [$paths];
  207. }
  208. $paths = array_filter($paths);
  209. $filters = array_filter($filters);
  210. $mapping = new \SplObjectStorage();
  211. foreach ($filters as $filter) {
  212. $resolver = $this->getResolver($filter, null);
  213. $list = isset($mapping[$resolver]) ? $mapping[$resolver] : [];
  214. $list[] = $filter;
  215. $mapping[$resolver] = $list;
  216. }
  217. foreach ($mapping as $resolver) {
  218. $resolver->remove($paths, $mapping[$resolver]);
  219. }
  220. }
  221. /**
  222. * Gets a resolver for the given filter.
  223. *
  224. * In case there is no specific resolver, but a default resolver has been configured, the default will be returned.
  225. *
  226. * @param string $filter
  227. * @param string $resolver
  228. *
  229. * @throws \OutOfBoundsException If neither a specific nor a default resolver is available
  230. *
  231. * @return ResolverInterface
  232. */
  233. protected function getResolver($filter, $resolver)
  234. {
  235. // BC
  236. if (!$resolver) {
  237. $config = $this->filterConfig->get($filter);
  238. $resolverName = empty($config['cache']) ? $this->defaultResolver : $config['cache'];
  239. } else {
  240. $resolverName = $resolver;
  241. }
  242. if (!isset($this->resolvers[$resolverName])) {
  243. throw new \OutOfBoundsException(sprintf('Could not find resolver "%s" for "%s" filter type', $resolverName, $filter));
  244. }
  245. return $this->resolvers[$resolverName];
  246. }
  247. /**
  248. * BC Layer for Symfony < 4.3
  249. */
  250. private function dispatchWithBC(CacheResolveEvent $event, string $eventName): void
  251. {
  252. if ($this->dispatcher instanceof ContractsEventDispatcherInterface) {
  253. $this->dispatcher->dispatch($event, $eventName);
  254. } else {
  255. $this->dispatcher->dispatch($eventName, $event);
  256. }
  257. }
  258. }