diff --git a/src/FilenameParsing/AlternativeFileExtensionTrait.php b/src/FilenameParsing/AlternativeFileExtensionTrait.php new file mode 100644 index 00000000..537fe9b6 --- /dev/null +++ b/src/FilenameParsing/AlternativeFileExtensionTrait.php @@ -0,0 +1,80 @@ +swapExtension($filename, $variant, 1); + } + + /** + * Rewrite a filename to use the original extension for the provided variant. + * @param string $filename + * @param string $variant + * @return string + */ + public function restoreOriginalExtension(string $filename, string $variant): string + { + return $this->swapExtension($filename, $variant, 0); + } + + /** + * Construct the original or alternative filname extension for the given filename and variant string. + * @param string $filename Original filename without variant + * @param string $variant Full variant list + * @param int $extIndex Wether we want the original extension (0) or the new extension (1) + * @return string + */ + private function swapExtension( + string $filename, + string $variant, + int $extIndex + ): string { + // If there's no variant at all, we can rewrite the filenmane + if (empty($variant)) { + return $filename; + } + + // Split variant string in variant list + $subVariants = explode('_', $variant); + + // Split our filename into a filename and extension part + if (!preg_match('/(.+)\.([a-z\d]+)$/i', $filename, $matches)) { + return $filename; + } + [$_, $filenameWitoutExtension, $extension] = $matches; + + // Loop our variant list until we find our special file extension swap variant + foreach ($subVariants as $subVariant) { + $extSwapVariant = FileIDHelper::EXTENSION_REWRITE_VARIANT; + if (preg_match("/^$extSwapVariant(.+)$/", $subVariant, $matches)) { + [$_, $base64] = $matches; + + /** + * This array always contain 2 values: The orignial extension and the new extension + * @var array $extensionData + */ + $extensionData = Convert::base64url_decode($base64); + $extension = $extensionData[$extIndex]; + } + } + + return $filenameWitoutExtension . '.' . $extension; + } +} diff --git a/src/FilenameParsing/FileIDHelper.php b/src/FilenameParsing/FileIDHelper.php index 8c7de879..88d0fc56 100644 --- a/src/FilenameParsing/FileIDHelper.php +++ b/src/FilenameParsing/FileIDHelper.php @@ -7,6 +7,10 @@ */ interface FileIDHelper { + /** + * A special variant type that can be used to encode a variant filename with a different extension. + */ + public const EXTENSION_REWRITE_VARIANT = 'extRewrite'; /** * Map file tuple (hash, name, variant) to a filename to be used by flysystem diff --git a/src/FilenameParsing/HashFileIDHelper.php b/src/FilenameParsing/HashFileIDHelper.php index aafa2ca3..5e1bca0b 100644 --- a/src/FilenameParsing/HashFileIDHelper.php +++ b/src/FilenameParsing/HashFileIDHelper.php @@ -17,6 +17,7 @@ class HashFileIDHelper implements FileIDHelper { use Injectable; + use AlternativeFileExtensionTrait; /** * Default length at which hashes are truncated. @@ -39,6 +40,11 @@ public function buildFileID($filename, $hash = null, $variant = null, $cleanfile if ($cleanfilename) { $filename = $this->cleanFilename($filename); } + + if ($variant) { + $filename = $this->rewriteVariantExtension($filename, $variant); + } + $name = basename($filename ?? ''); // Split extension @@ -88,10 +94,16 @@ public function parseFileID($fileID) } $filename = $matches['folder'] . $matches['basename'] . $matches['extension']; + $variant = isset($matches['variant']) ? $matches['variant'] : ''; + + if (isset($variant)) { + $filename = $this->restoreOriginalExtension($filename, $variant); + } + return new ParsedFileID( $filename, $matches['hash'], - isset($matches['variant']) ? $matches['variant'] : '', + $variant, $fileID ); } diff --git a/src/FilenameParsing/NaturalFileIDHelper.php b/src/FilenameParsing/NaturalFileIDHelper.php index a3055626..7235f4af 100644 --- a/src/FilenameParsing/NaturalFileIDHelper.php +++ b/src/FilenameParsing/NaturalFileIDHelper.php @@ -14,6 +14,7 @@ class NaturalFileIDHelper implements FileIDHelper { use Injectable; + use AlternativeFileExtensionTrait; public function buildFileID($filename, $hash = null, $variant = null, $cleanfilename = true) { @@ -27,6 +28,11 @@ public function buildFileID($filename, $hash = null, $variant = null, $cleanfile if ($cleanfilename) { $filename = $this->cleanFilename($filename); } + + if ($variant) { + $filename = $this->rewriteVariantExtension($filename, $variant); + } + $name = basename($filename ?? ''); // Split extension @@ -57,7 +63,6 @@ public function buildFileID($filename, $hash = null, $variant = null, $cleanfile return $fileID; } - public function cleanFilename($filename) { // Swap backslash for forward slash @@ -77,10 +82,16 @@ public function parseFileID($fileID) } $filename = $matches['folder'] . $matches['basename'] . $matches['extension']; + $variant = isset($matches['variant']) ? $matches['variant'] : ''; + + if (isset($variant)) { + $filename = $this->restoreOriginalExtension($filename, $variant); + } + return new ParsedFileID( $filename, '', - isset($matches['variant']) ? $matches['variant'] : '', + $variant, $fileID ); } diff --git a/src/ImageBackendFactory.php b/src/ImageBackendFactory.php index 81738ff7..8449e09e 100644 --- a/src/ImageBackendFactory.php +++ b/src/ImageBackendFactory.php @@ -37,9 +37,15 @@ public function __construct(Factory $creator) */ public function create($service, array $params = []) { - /** @var AssetContainer $assetContainer */ + /** @var AssetContainer|null $assetContainer */ $assetContainer = reset($params); - if (!$assetContainer instanceof AssetContainer) { + + // If no asset container was passed in, create a new uncached image backend + if (!$assetContainer) { + return $this->creator->create($service, $params); + } + + if (!($assetContainer instanceof AssetContainer)) { throw new BadMethodCallException("Can only create Image_Backend for " . AssetContainer::class); } diff --git a/src/ImageManipulation.php b/src/ImageManipulation.php index dd56c050..feec6901 100644 --- a/src/ImageManipulation.php +++ b/src/ImageManipulation.php @@ -4,6 +4,7 @@ use InvalidArgumentException; use LogicException; +use SilverStripe\Assets\FilenameParsing\FileIDHelper; use SilverStripe\Assets\Storage\AssetContainer; use SilverStripe\Assets\Storage\AssetStore; use SilverStripe\Assets\Storage\DBFile; @@ -840,6 +841,18 @@ public function isHeight($height) return $this->getHeight() === $height; } + /** + * @param string $newExtension + * @param callable $callback + * @return DBFile The manipulated file + */ + public function manipulateExtension(string $newExtension, callable $callback) + { + $pathParts = pathinfo($this->getFilename()); + $variant = $this->variantName(FileIDHelper::EXTENSION_REWRITE_VARIANT, $pathParts['extension'], $newExtension); + return $this->manipulate($variant, $callback); + } + /** * Wrapper for manipulate that passes in and stores Image_Backend objects instead of tuples * diff --git a/src/InterventionBackend.php b/src/InterventionBackend.php index 0758e662..1d3dea76 100644 --- a/src/InterventionBackend.php +++ b/src/InterventionBackend.php @@ -366,8 +366,10 @@ public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $v throw new BadMethodCallException("Cannot write corrupt file to store"); } + // Make sure we're using the extension of the variant file, which can differ from the original file + $url = $assetStore->getAsURL($filename, $hash, $variant, false); + $extension = pathinfo($url, PATHINFO_EXTENSION); // Save file - $extension = pathinfo($filename ?? '', PATHINFO_EXTENSION); $result = $assetStore->setFromString( $resource->encode($extension, $this->getQuality())->getEncoded(), $filename,