diff --git a/config/routes.yaml b/config/routes.yaml index aa8ac09..7f84db5 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -3,3 +3,11 @@ storyblok.webhook: controller: Torr\Storyblok\Webhook\Controller\WebhookController::webhookEndpoint defaults: urlSecret: ~ + +storyblok.asset-proxy: + path: /asset/{width}x{height}/{assetId}/{filename} + controller: Torr\Storyblok\Assets\Controller\AssetProxyController::proxyAsset + requirements: + width: \d* + height: \d* + assetId: \w+ diff --git a/config/services.yaml b/config/services.yaml index a763d7f..2de7662 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -22,3 +22,6 @@ services: $localeLevel: !abstract set via config $webhookSecret: !abstract set via config $allowUrlWebhookSecret: !abstract set via config + + Torr\Storyblok\Assets\Proxy\AssetProxy: + $storagePath: '%kernel.project_dir%/var/storyblok/assets' diff --git a/src/Assets/Controller/AssetProxyController.php b/src/Assets/Controller/AssetProxyController.php new file mode 100644 index 0000000..acfa633 --- /dev/null +++ b/src/Assets/Controller/AssetProxyController.php @@ -0,0 +1,40 @@ +getFilePath($path); + + if (null === $filePath) + { + throw $this->createNotFoundException("File not found"); + } + + return $this->file( + $filePath, + disposition: $request->query->has("download") + ? ResponseHeaderBag::DISPOSITION_ATTACHMENT + : ResponseHeaderBag::DISPOSITION_INLINE, + ); + } +} diff --git a/src/Assets/Prox/AssetProxy.php b/src/Assets/Prox/AssetProxy.php new file mode 100644 index 0000000..13b4e56 --- /dev/null +++ b/src/Assets/Prox/AssetProxy.php @@ -0,0 +1,126 @@ +storagePath, $path); + + if (!is_file($targetPath)) + { + $originUrl = \sprintf( + "https://a.storyblok.com/f/%s/%s?cv=%s", + $this->config->getSpaceId(), + ltrim($path, "/"), + $this->api->getSpaceInfo()->getCacheVersion(), + ); + + $this->logger->debug("Fetch proxied storyblok asset", [ + "targetPath" => $targetPath, + "originUrl" => $originUrl, + ]); + + $success = $this->fetchFileFromOrigin($originUrl, $targetPath); + + return $success + ? $targetPath + : null; + } + + $this->logger->debug("Found existing proxied storyblok asset", [ + "targetPath" => $targetPath, + ]); + + return $targetPath; + } + + /** + * Fetches a fresh file from the origin + * + * @return bool whether the file was download correctly + */ + private function fetchFileFromOrigin ( + string $originUrl, + string $targetPath, + ) : bool + { + // ensure directory exists + $this->filesystem->mkdir(\dirname($targetPath)); + + // open file handle + $fileHandle = fopen($targetPath, "wb+"); + + if (!$fileHandle) + { + $this->logger->critical("Could not create proxied asset file on disk: {targetPath}", [ + "targetPath" => $targetPath, + ]); + + return false; + } + + try + { + // start request + $response = $this->client->request( + "GET", + $originUrl, + (new HttpOptions()) + ->buffer(false) + ->toArray(), + ); + + // stream to file + foreach ($this->client->stream($response) as $chunk) + { + fwrite($fileHandle, $chunk->getContent()); + } + + fclose($fileHandle); + + return true; + } + catch (TransportExceptionInterface|HttpExceptionInterface $exception) + { + $this->logger->error("Failed to fetch Storyblok asset {originUrl}: {message}", [ + "message" => $exception->getMessage(), + "originUrl" => $originUrl, + "targetPath" => $targetPath, + "exception" => $exception, + ]); + + // be sure to close the file handle and clean up the incomplete file + fclose($fileHandle); + $this->filesystem->remove($targetPath); + + return false; + } + } +}