vendor/sulu/sulu/src/Sulu/Bundle/MediaBundle/Controller/MediaStreamController.php line 88

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\Controller;
  11. use Sulu\Bundle\MediaBundle\Admin\MediaAdmin;
  12. use Sulu\Bundle\MediaBundle\Entity\Collection;
  13. use Sulu\Bundle\MediaBundle\Entity\FileVersion;
  14. use Sulu\Bundle\MediaBundle\Entity\MediaInterface;
  15. use Sulu\Bundle\MediaBundle\Entity\MediaRepositoryInterface;
  16. use Sulu\Bundle\MediaBundle\Media\DispositionType\DispositionTypeResolver;
  17. use Sulu\Bundle\MediaBundle\Media\Exception\FileVersionNotFoundException;
  18. use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyInvalidUrl;
  19. use Sulu\Bundle\MediaBundle\Media\Exception\ImageProxyUrlNotFoundException;
  20. use Sulu\Bundle\MediaBundle\Media\Exception\MediaException;
  21. use Sulu\Bundle\MediaBundle\Media\FormatCache\FormatCacheInterface;
  22. use Sulu\Bundle\MediaBundle\Media\FormatManager\FormatManagerInterface;
  23. use Sulu\Bundle\MediaBundle\Media\Manager\MediaManagerInterface;
  24. use Sulu\Bundle\MediaBundle\Media\Storage\StorageInterface;
  25. use Sulu\Component\PHPCR\PathCleanupInterface;
  26. use Sulu\Component\Security\Authorization\PermissionTypes;
  27. use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
  28. use Sulu\Component\Security\Authorization\SecurityCondition;
  29. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  30. use Symfony\Component\HttpFoundation\RedirectResponse;
  31. use Symfony\Component\HttpFoundation\Request;
  32. use Symfony\Component\HttpFoundation\Response;
  33. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  34. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  35. class MediaStreamController
  36. {
  37. public function __construct(
  38. protected DispositionTypeResolver $dispositionTypeResolver,
  39. protected MediaRepositoryInterface $mediaRepository,
  40. protected PathCleanupInterface $pathCleaner,
  41. protected FormatManagerInterface $formatManager,
  42. protected FormatCacheInterface $formatCache,
  43. protected MediaManagerInterface $mediaManager,
  44. protected StorageInterface $storage,
  45. protected ?SecurityCheckerInterface $securityChecker = null
  46. ) {
  47. }
  48. /**
  49. * @return Response
  50. */
  51. public function getImageAction(Request $request)
  52. {
  53. try {
  54. if (\ob_get_length()) {
  55. \ob_end_clean();
  56. }
  57. $url = $request->getPathInfo();
  58. // Some projects do not call this action with ?v=1-0 because they don't want query strings in the image urls ( unnecessary SEO mystic reasons )
  59. // To maintain compatibility with these projects, we will fallback to version 1-0 if no version is specified.
  60. $version = (string) $request->query->get('v', '1-0');
  61. $version = (int) (\explode('-', $version)[0] ?? '1');
  62. $mediaProperties = $this->formatCache->analyzedMediaUrl($url);
  63. } catch (ImageProxyUrlNotFoundException|ImageProxyInvalidUrl $e) {
  64. throw new NotFoundHttpException('Image create error. Code: ' . $e->getCode(), $e);
  65. }
  66. return $this->formatManager->returnImage(
  67. $mediaProperties['id'],
  68. $mediaProperties['format'],
  69. $mediaProperties['fileName'],
  70. $version,
  71. );
  72. }
  73. /**
  74. * @param int $id
  75. * @param string $slug
  76. *
  77. * @return Response
  78. */
  79. public function downloadAction(Request $request, $id, $slug)
  80. {
  81. try {
  82. if (\ob_get_length()) {
  83. \ob_end_clean();
  84. }
  85. $version = $request->get('v', null);
  86. $version = \is_numeric($version) ? ((int) $version) : null;
  87. $noCount = $request->get('no-count', false);
  88. $fileVersion = $this->getFileVersion($id, $version);
  89. if (!$fileVersion) {
  90. return new Response('Invalid version "' . $version . '" for media with ID "' . $id . '".', 404);
  91. }
  92. if ($fileVersion->getName() !== $slug) {
  93. return new Response('Invalid file name for media with ID "' . $id . '".', 404);
  94. }
  95. if ($this->securityChecker) {
  96. $this->securityChecker->checkPermission(
  97. new SecurityCondition(
  98. MediaAdmin::SECURITY_CONTEXT,
  99. null,
  100. Collection::class,
  101. $fileVersion->getFile()->getMedia()->getCollection()->getId()
  102. ),
  103. PermissionTypes::VIEW
  104. );
  105. }
  106. if ($request->query->has('inline')) {
  107. $forceInline = (bool) $request->get('inline', false);
  108. $dispositionType = $forceInline ? ResponseHeaderBag::DISPOSITION_INLINE : ResponseHeaderBag::DISPOSITION_ATTACHMENT;
  109. } else {
  110. $dispositionType = $this->dispositionTypeResolver->getByMimeType($fileVersion->getMimeType());
  111. }
  112. if (!$noCount) {
  113. $this->mediaManager->increaseDownloadCounter($fileVersion->getId());
  114. }
  115. $response = $this->getFileResponse($fileVersion, $request->getLocale(), $dispositionType);
  116. return $response;
  117. } catch (MediaException $e) {
  118. throw new NotFoundHttpException('File not found: ' . $e->getCode() . ' ' . $e->getMessage(), $e);
  119. }
  120. }
  121. protected function getFileResponse(
  122. FileVersion $fileVersion,
  123. string $locale,
  124. string $dispositionType = ResponseHeaderBag::DISPOSITION_ATTACHMENT
  125. ): Response {
  126. $storageOptions = $fileVersion->getStorageOptions();
  127. $storageType = $this->storage->getType($storageOptions);
  128. if (StorageInterface::TYPE_REMOTE === $storageType) {
  129. $response = new RedirectResponse($this->storage->getPath($storageOptions), 302);
  130. $response->setPrivate();
  131. return $response;
  132. } elseif (StorageInterface::TYPE_LOCAL === $storageType) {
  133. return $this->createBinaryFileResponse($fileVersion, $this->storage, $locale, $dispositionType);
  134. }
  135. throw new \RuntimeException(\sprintf('Storage type "%s" not supported.', $storageType));
  136. }
  137. private function createBinaryFileResponse(
  138. FileVersion $fileVersion,
  139. StorageInterface $storage,
  140. string $locale,
  141. string $dispositionType
  142. ): BinaryFileResponse {
  143. $fileName = $fileVersion->getName();
  144. $fileSize = $fileVersion->getSize();
  145. $storageOptions = $fileVersion->getStorageOptions();
  146. $mimeType = $fileVersion->getMimeType();
  147. $lastModified = $fileVersion->getCreated(); // use created as file itself is not changed when entity is changed
  148. $response = new BinaryFileResponse($storage->getPath($storageOptions));
  149. // Prepare headers
  150. $disposition = $response->headers->makeDisposition(
  151. $dispositionType,
  152. $fileName,
  153. $this->cleanUpFileName($fileName, $locale, $fileVersion->getExtension())
  154. );
  155. // Set headers for
  156. $file = $fileVersion->getFile();
  157. if ($fileVersion->getVersion() !== $file->getVersion()) {
  158. $latestFileVersion = $file->getLatestFileVersion();
  159. $response->headers->set(
  160. 'Link',
  161. \sprintf(
  162. '<%s>; rel="canonical"',
  163. $this->mediaManager->getUrl(
  164. $file->getMedia()->getId(),
  165. $latestFileVersion->getName(),
  166. $latestFileVersion->getVersion()
  167. )
  168. )
  169. );
  170. $response->headers->set('X-Robots-Tag', 'noindex, follow');
  171. }
  172. // Set headers
  173. $response->headers->set('Content-Type', !empty($mimeType) ? $mimeType : 'application/octet-stream');
  174. $response->headers->set('Content-Disposition', $disposition);
  175. $response->headers->set('Content-length', $fileSize);
  176. $response->headers->set('Last-Modified', $lastModified->format('D, d M Y H:i:s \G\M\T'));
  177. return $response;
  178. }
  179. /**
  180. * @param int $id
  181. * @param int|null $version
  182. *
  183. * @return FileVersion|null
  184. *
  185. * @throws FileVersionNotFoundException
  186. */
  187. protected function getFileVersion($id, $version)
  188. {
  189. /** @var MediaInterface|null $mediaEntity */
  190. $mediaEntity = $this->mediaRepository->findMediaByIdForRendering($id, null, $version);
  191. if (!$mediaEntity) {
  192. return null;
  193. }
  194. $file = $mediaEntity->getFiles()[0] ?? null;
  195. if (!$file) {
  196. return null;
  197. }
  198. if (!$version) {
  199. $version = $file->getVersion();
  200. }
  201. $fileVersion = $file->getFileVersion($version);
  202. if (!$fileVersion) {
  203. throw new FileVersionNotFoundException($id, $version);
  204. }
  205. return $fileVersion;
  206. }
  207. /**
  208. * Cleaned up filename.
  209. *
  210. * @param string $fileName
  211. * @param string $locale
  212. * @param string $extension
  213. *
  214. * @return string
  215. */
  216. private function cleanUpFileName($fileName, $locale, $extension)
  217. {
  218. $pathInfo = \pathinfo($fileName);
  219. $cleanedFileName = $this->pathCleaner->cleanup($pathInfo['filename'], $locale);
  220. if ($extension) {
  221. $cleanedFileName .= '.' . $extension;
  222. }
  223. return $cleanedFileName;
  224. }
  225. }