vendor/contao/image/src/DeferredResizer.php line 53

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of Contao.
  5. *
  6. * (c) Leo Feyer
  7. *
  8. * @license LGPL-3.0-or-later
  9. */
  10. namespace Contao\Image;
  11. use Contao\Image\Exception\InvalidArgumentException;
  12. use Contao\Image\Exception\RuntimeException;
  13. use Contao\Image\Metadata\MetadataReaderWriter;
  14. use Imagine\Image\Box;
  15. use Imagine\Image\ImagineInterface;
  16. use Imagine\Image\Point;
  17. use Symfony\Component\Filesystem\Filesystem;
  18. use Symfony\Component\Filesystem\Path;
  19. /**
  20. * @method __construct(string $cacheDir, string $secret, ResizeCalculator $calculator = null, Filesystem $filesystem = null, DeferredImageStorageInterface $storage = null, MetadataReaderWriter $metadataReaderWriter = null)
  21. */
  22. class DeferredResizer extends Resizer implements DeferredResizerInterface
  23. {
  24. /**
  25. * @var DeferredImageStorageInterface
  26. *
  27. * @internal
  28. */
  29. private $storage;
  30. /**
  31. * @param string $cacheDir
  32. * @param string $secret
  33. * @param ResizeCalculator|null $calculator
  34. * @param Filesystem|null $filesystem
  35. * @param DeferredImageStorageInterface|null $storage
  36. * @param MetadataReaderWriter|null $metadataReaderWriter
  37. */
  38. public function __construct(string $cacheDir/*, string $secret, ResizeCalculator $calculator = null, Filesystem $filesystem = null, DeferredImageStorageInterface $storage = null, MetadataReaderWriter $metadataReaderWriter = null*/)
  39. {
  40. if (\func_num_args() > 1 && \is_string(func_get_arg(1))) {
  41. $secret = func_get_arg(1);
  42. $calculator = \func_num_args() > 2 ? func_get_arg(2) : null;
  43. $filesystem = \func_num_args() > 3 ? func_get_arg(3) : null;
  44. $storage = \func_num_args() > 4 ? func_get_arg(4) : null;
  45. $metadataReaderWriter = \func_num_args() > 5 ? func_get_arg(5) : null;
  46. } else {
  47. trigger_deprecation('contao/image', '1.2', 'Not passing a secret to "%s()" has been deprecated and will no longer work in version 2.0.', __METHOD__);
  48. $secret = null;
  49. $calculator = \func_num_args() > 1 ? func_get_arg(1) : null;
  50. $filesystem = \func_num_args() > 2 ? func_get_arg(2) : null;
  51. $storage = \func_num_args() > 3 ? func_get_arg(3) : null;
  52. $metadataReaderWriter = \func_num_args() > 4 ? func_get_arg(4) : null;
  53. }
  54. if (null === $storage) {
  55. $storage = new DeferredImageStorageFilesystem($cacheDir);
  56. }
  57. if (!$storage instanceof DeferredImageStorageInterface) {
  58. throw new \TypeError(sprintf('%s(): Argument #5 ($storage) must be of type DeferredImageStorageInterface|null, %s given', __METHOD__, get_debug_type($storage)));
  59. }
  60. if (null === $secret) {
  61. parent::__construct($cacheDir, $calculator, $filesystem, $metadataReaderWriter);
  62. } else {
  63. parent::__construct($cacheDir, $secret, $calculator, $filesystem, $metadataReaderWriter);
  64. }
  65. $this->storage = $storage;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function getDeferredImage(string $targetPath, ImagineInterface $imagine): ?DeferredImageInterface
  71. {
  72. if (Path::isAbsolute($targetPath)) {
  73. if (!Path::isBasePath($this->cacheDir, $targetPath)) {
  74. return null;
  75. }
  76. $targetPath = Path::makeRelative($targetPath, $this->cacheDir);
  77. }
  78. if (!$this->storage->has($targetPath)) {
  79. return null;
  80. }
  81. try {
  82. $config = $this->storage->get($targetPath);
  83. } catch (\Throwable $exception) {
  84. // Ignore storage failure
  85. return null;
  86. }
  87. return new DeferredImage(
  88. Path::join($this->cacheDir, $targetPath),
  89. $imagine,
  90. new ImageDimensions(
  91. new Box(
  92. $config['coordinates']['crop']['width'],
  93. $config['coordinates']['crop']['height']
  94. )
  95. )
  96. );
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function resizeDeferredImage(DeferredImageInterface $image, bool $blocking = true): ?ImageInterface
  102. {
  103. if (!Path::isBasePath($this->cacheDir, $image->getPath())) {
  104. throw new InvalidArgumentException(sprintf('Path "%s" is not inside cache directory "%s"', $image->getPath(), $this->cacheDir));
  105. }
  106. $targetPath = Path::makeRelative($image->getPath(), $this->cacheDir);
  107. try {
  108. $config = $this->storage->getLocked($targetPath, $blocking);
  109. } catch (\Throwable $exception) {
  110. // Getting the lock might fail if the image was already generated
  111. if ($this->filesystem->exists($image->getPath())) {
  112. return $blocking ? new Image($image->getPath(), $image->getImagine(), $this->filesystem) : null;
  113. }
  114. throw $exception;
  115. }
  116. if (null === $config) {
  117. if ($blocking) {
  118. throw new RuntimeException(sprintf('Unable to acquire lock for "%s"', $targetPath));
  119. }
  120. return null;
  121. }
  122. try {
  123. $resizedImage = $this->executeDeferredResize($targetPath, $config, $image->getImagine());
  124. $this->storage->delete($targetPath);
  125. } catch (\Throwable $exception) {
  126. $this->storage->releaseLock($targetPath);
  127. throw $exception;
  128. }
  129. return $resizedImage;
  130. }
  131. /**
  132. * {@inheritdoc}
  133. */
  134. protected function processResize(ImageInterface $image, ResizeConfiguration $config, ResizeOptions $options): ImageInterface
  135. {
  136. // Resize the source image if it is deferred
  137. if ($image instanceof DeferredImageInterface) {
  138. $image = $this->resizeDeferredImage($image);
  139. }
  140. return parent::processResize($image, $config, $options);
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. protected function executeResize(ImageInterface $image, ResizeCoordinates $coordinates, string $path, ResizeOptions $options): ImageInterface
  146. {
  147. if (null !== $options->getTargetPath() || $options->getBypassCache()) {
  148. return parent::executeResize($image, $coordinates, $path, $options);
  149. }
  150. $this->storeResizeData($image->getPath(), $path, $coordinates, $options);
  151. return new DeferredImage($path, $image->getImagine(), new ImageDimensions($coordinates->getCropSize()));
  152. }
  153. private function storeResizeData(string $sourcePath, string $targetPath, ResizeCoordinates $coordinates, ResizeOptions $options): void
  154. {
  155. $targetPath = Path::makeRelative($targetPath, $this->cacheDir);
  156. if ($this->storage->has($targetPath)) {
  157. return;
  158. }
  159. $this->storage->set($targetPath, [
  160. 'path' => Path::makeRelative($sourcePath, $this->cacheDir),
  161. 'coordinates' => [
  162. 'size' => [
  163. 'width' => $coordinates->getSize()->getWidth(),
  164. 'height' => $coordinates->getSize()->getHeight(),
  165. ],
  166. 'crop' => [
  167. 'x' => $coordinates->getCropStart()->getX(),
  168. 'y' => $coordinates->getCropStart()->getY(),
  169. 'width' => $coordinates->getCropSize()->getWidth(),
  170. 'height' => $coordinates->getCropSize()->getHeight(),
  171. ],
  172. ],
  173. 'options' => [
  174. 'imagine_options' => $options->getImagineOptions(),
  175. 'preserve_copyright' => $options->getPreserveCopyrightMetadata(),
  176. ],
  177. ]);
  178. }
  179. private function executeDeferredResize(string $targetPath, array $config, ImagineInterface $imagine): ImageInterface
  180. {
  181. $coordinates = new ResizeCoordinates(
  182. new Box($config['coordinates']['size']['width'], $config['coordinates']['size']['height']),
  183. new Point($config['coordinates']['crop']['x'], $config['coordinates']['crop']['y']),
  184. new Box($config['coordinates']['crop']['width'], $config['coordinates']['crop']['height'])
  185. );
  186. $options = new ResizeOptions();
  187. $options->setImagineOptions($config['options']['imagine_options']);
  188. if (isset($config['options']['preserve_copyright'])) {
  189. $options->setPreserveCopyrightMetadata($config['options']['preserve_copyright']);
  190. }
  191. $path = Path::join($this->cacheDir, $config['path']);
  192. return parent::executeResize(
  193. new Image($path, $imagine, $this->filesystem),
  194. $coordinates,
  195. Path::join($this->cacheDir, $targetPath),
  196. $options
  197. );
  198. }
  199. }