vendor/sulu/sulu/src/Sulu/Bundle/MediaBundle/Media/FormatManager/FormatManager.php line 71

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Sulu.
  4. *
  5. * (c) Sulu GmbH
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Sulu\Bundle\MediaBundle\Media\FormatManager;
  11. use Psr\Log\LoggerInterface;
  12. use Psr\Log\NullLogger;
  13. use Sulu\Bundle\MediaBundle\Entity\File;
  14. use Sulu\Bundle\MediaBundle\Entity\FileVersion;
  15. use Sulu\Bundle\MediaBundle\Entity\MediaInterface;
  16. use Sulu\Bundle\MediaBundle\Entity\MediaRepositoryInterface;
  17. use Sulu\Bundle\MediaBundle\Media\Exception\FormatNotFoundException;
  18. use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyException;
  19. use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyInvalidImageFormat;
  20. use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyInvalidUrl;
  21. use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyMediaNotFoundException;
  22. use Sulu\Bundle\MediaBundle\Media\Exception\InvalidMimeTypeForPreviewException;
  23. use Sulu\Bundle\MediaBundle\Media\FormatCache\FormatCacheInterface;
  24. use Sulu\Bundle\MediaBundle\Media\ImageConverter\ImageConverterInterface;
  25. use Symfony\Component\Filesystem\Filesystem;
  26. use Symfony\Component\HttpFoundation\RedirectResponse;
  27. use Symfony\Component\HttpFoundation\Response;
  28. /**
  29. * Sulu format manager for media.
  30. */
  31. class FormatManager implements FormatManagerInterface
  32. {
  33. /**
  34. * @var bool
  35. */
  36. private $saveImage = false;
  37. /**
  38. * @var Filesystem
  39. */
  40. private $fileSystem;
  41. /**
  42. * @var LoggerInterface
  43. */
  44. private $logger;
  45. /**
  46. * @param string $saveImage
  47. * @param array $responseHeaders
  48. * @param array $formats
  49. */
  50. public function __construct(
  51. private MediaRepositoryInterface $mediaRepository,
  52. private FormatCacheInterface $formatCache,
  53. private ImageConverterInterface $converter,
  54. $saveImage,
  55. private $responseHeaders,
  56. private $formats,
  57. ?LoggerInterface $logger = null
  58. ) {
  59. $this->saveImage = 'true' == $saveImage ? true : false;
  60. $this->fileSystem = new Filesystem();
  61. $this->logger = $logger ?: new NullLogger();
  62. }
  63. public function returnImage($id, $formatKey, $fileName /*, int<1, max>|null $version = null */)
  64. {
  65. /** @var int|null $version */
  66. $version = \func_num_args() > 3 ? \func_get_arg(3) : null;
  67. if (null === $version) {
  68. @trigger_deprecation('sulu/sulu', '2.5', 'The $version parameter in ' . __CLASS__ . '::' . __METHOD__ . ' is required.');
  69. }
  70. $setExpireHeaders = false;
  71. try {
  72. $info = \pathinfo($fileName);
  73. if (!isset($info['extension'])) {
  74. throw new ImageProxyInvalidUrl(\sprintf('No `extension` was found in the url "%s".', $fileName));
  75. }
  76. $imageFormat = $info['extension'];
  77. $media = $this->mediaRepository->findMediaByIdForRendering($id, $formatKey, $version);
  78. if (!$media) {
  79. throw new ImageProxyMediaNotFoundException(\sprintf('Media with id "%s" was not found.', $id));
  80. }
  81. $fileVersion = $this->getLatestFileVersion($media);
  82. $version = null !== $version ? $version : $fileVersion->getVersion(); // TODO remove this line in Sulu 3.0 currently bc layer when version is not given
  83. /** @var File|null $file */
  84. $file = $media->getFiles()[0] ?? null;
  85. $requestedFileVersion = $file?->getFileVersion($version);
  86. if (!$requestedFileVersion) {
  87. throw new ImageProxyMediaNotFoundException(\sprintf('Requested FileVersion "%s" for media with id "%s" was not found.', $version, $id));
  88. }
  89. $requestedFileVersionImageFormatName = $this->replaceExtension($requestedFileVersion->getName(), $imageFormat);
  90. if ($requestedFileVersionImageFormatName !== $fileName) {
  91. throw new ImageProxyMediaNotFoundException(\sprintf('FileVersion "%s" for media with id "%s" was not found.', $requestedFileVersion->getVersion(), $id));
  92. }
  93. if ($fileVersion->getVersion() !== $requestedFileVersion->getVersion()) {
  94. $formats = $this->getFormats($id, $fileVersion->getName(), $fileVersion->getVersion(), $fileVersion->getSubVersion(), $fileVersion->getMimeType());
  95. $formatUrl = $formats[$formatKey . '.' . $imageFormat] ?? null;
  96. if (null === $formatUrl) {
  97. throw new ImageProxyMediaNotFoundException(\sprintf('Image format "%s.%s" was not found for media with id "%s".', $formatKey, $imageFormat, $id));
  98. }
  99. return new RedirectResponse($formatUrl, 301);
  100. }
  101. $supportedImageFormats = $this->converter->getSupportedOutputImageFormats($fileVersion->getMimeType());
  102. if (empty($supportedImageFormats)) {
  103. throw new InvalidMimeTypeForPreviewException($fileVersion->getMimeType() ?? '-null-');
  104. }
  105. if (!\in_array($imageFormat, $supportedImageFormats)) {
  106. throw new ImageProxyInvalidImageFormat(
  107. \sprintf(
  108. 'Image format "%s" is not supported. Supported image formats are: "%s"',
  109. $imageFormat,
  110. \implode(', ', $supportedImageFormats)
  111. ));
  112. }
  113. // Convert Media to format.
  114. $responseContent = $this->converter->convert($fileVersion, $formatKey, $imageFormat);
  115. // HTTP Headers
  116. $status = 200;
  117. $setExpireHeaders = true;
  118. $finfo = new \finfo(\FILEINFO_MIME_TYPE);
  119. $mimeType = $finfo->buffer($responseContent);
  120. // Save image.
  121. if ($this->saveImage) {
  122. $this->formatCache->save(
  123. $responseContent,
  124. $media->getId(),
  125. $this->replaceExtension($fileVersion->getName(), $imageFormat),
  126. $formatKey
  127. );
  128. }
  129. } catch (ImageProxyException $e) {
  130. $this->logger->debug($e->getMessage(), ['exception' => $e]);
  131. $responseContent = null;
  132. $status = 404;
  133. $mimeType = null;
  134. }
  135. // Set header.
  136. $headers = $this->getResponseHeaders($mimeType, $setExpireHeaders);
  137. // Return image.
  138. return new Response($responseContent, $status, $headers);
  139. }
  140. public function getFormats($id, $fileName, $version, $subVersion, $mimeType)
  141. {
  142. $formats = [];
  143. $extensions = $this->converter->getSupportedOutputImageFormats($mimeType);
  144. if (empty($extensions)) {
  145. return [];
  146. }
  147. $originalExtension = $extensions[0];
  148. foreach ($this->formats as $format) {
  149. foreach ($extensions as $extension) {
  150. $formatUrl = $this->formatCache->getMediaUrl(
  151. $id,
  152. $this->replaceExtension($fileName, $extension),
  153. $format['key'],
  154. $version,
  155. $subVersion
  156. );
  157. if ($extension === $originalExtension) {
  158. $formats[$format['key']] = $formatUrl;
  159. }
  160. $formats[$format['key'] . '.' . $extension] = $formatUrl;
  161. }
  162. }
  163. return $formats;
  164. }
  165. public function purge($idMedia, $fileName, $mimeType)
  166. {
  167. $extensions = $this->converter->getSupportedOutputImageFormats($mimeType);
  168. if (empty($extensions)) {
  169. return true;
  170. }
  171. foreach ($this->formats as $format) {
  172. foreach ($extensions as $extension) {
  173. $this->formatCache->purge($idMedia, $this->replaceExtension($fileName, $extension), $format['key']);
  174. }
  175. }
  176. return true;
  177. }
  178. public function clearCache()
  179. {
  180. $this->formatCache->clear();
  181. }
  182. public function getFormatDefinition($formatKey, $locale = null)
  183. {
  184. if (!isset($this->formats[$formatKey])) {
  185. throw new FormatNotFoundException($formatKey);
  186. }
  187. $format = $this->formats[$formatKey];
  188. $title = $format['key'];
  189. if (\array_key_exists($locale, $format['meta']['title'])) {
  190. $title = $format['meta']['title'][$locale];
  191. } elseif (\count($format['meta']['title']) > 0) {
  192. $title = \array_values($format['meta']['title'])[0];
  193. }
  194. $formatArray = [
  195. 'internal' => $format['internal'],
  196. 'key' => $format['key'],
  197. 'title' => $title,
  198. 'scale' => $format['scale'],
  199. ];
  200. return $formatArray;
  201. }
  202. public function getFormatDefinitions($locale = null)
  203. {
  204. $definitionsArray = [];
  205. foreach ($this->formats as $format) {
  206. $definitionsArray[$format['key']] = $this->getFormatDefinition($format['key'], $locale);
  207. }
  208. return $definitionsArray;
  209. }
  210. /**
  211. * @param string $mimeType
  212. * @param bool $setExpireHeaders
  213. *
  214. * @return array
  215. */
  216. protected function getResponseHeaders($mimeType = '', $setExpireHeaders = false)
  217. {
  218. $headers = [];
  219. if (!empty($mimeType)) {
  220. $headers['Content-Type'] = $mimeType;
  221. }
  222. if (empty($this->responseHeaders)) {
  223. return $headers;
  224. }
  225. $headers = \array_merge(
  226. $headers,
  227. $this->responseHeaders
  228. );
  229. if (isset($this->responseHeaders['Expires']) && $setExpireHeaders) {
  230. $date = new \DateTime();
  231. $date->modify($this->responseHeaders['Expires']);
  232. $headers['Expires'] = $date->format('D, d M Y H:i:s \G\M\T');
  233. } else {
  234. // will remove exist set expire header
  235. $headers['Expires'] = null;
  236. $headers['Cache-Control'] = 'no-cache';
  237. $headers['Pragma'] = null;
  238. }
  239. return $headers;
  240. }
  241. /**
  242. * Replace extension.
  243. *
  244. * @param string $filename
  245. * @param string $newExtension
  246. *
  247. * @return string
  248. */
  249. private function replaceExtension($filename, $newExtension)
  250. {
  251. $info = \pathinfo($filename);
  252. return $info['filename'] . '.' . $newExtension;
  253. }
  254. /**
  255. * @return FileVersion
  256. *
  257. * @throws ImageProxyMediaNotFoundException
  258. */
  259. private function getLatestFileVersion(MediaInterface $media)
  260. {
  261. foreach ($media->getFiles() as $file) {
  262. $version = $file->getVersion();
  263. foreach ($file->getFileVersions() as $fileVersion) {
  264. if ($fileVersion->getVersion() == $version) {
  265. return $fileVersion;
  266. }
  267. }
  268. break;
  269. }
  270. throw new ImageProxyMediaNotFoundException('Media file version was not found');
  271. }
  272. }