diff --git a/src/FilenameParsing/AbstractFileIDHelper.php b/src/FilenameParsing/AbstractFileIDHelper.php new file mode 100644 index 00000000..2e55fbb9 --- /dev/null +++ b/src/FilenameParsing/AbstractFileIDHelper.php @@ -0,0 +1,153 @@ +getHash(); + $variant = $filename->getVariant(); + $filename = $filename->getFilename(); + } + + $this->validateFileParts($filename, $hash, $variant); + + // Since we use double underscore to delimit variants, eradicate them from filename + if ($cleanfilename) { + $filename = $this->cleanFilename($filename); + } + + if ($variant) { + $filename = $this->rewriteVariantExtension($filename, $variant); + } + + $name = basename($filename ?? ''); + + // Split extension + $extension = null; + if (($pos = strpos($name ?? '', '.')) !== false) { + $extension = substr($name ?? '', $pos ?? 0); + $name = substr($name ?? '', 0, $pos); + } + + $fileID = $this->getFileIDBase($name, $filename, $hash, $variant); + + // Add directory + $dirname = ltrim(dirname($filename ?? ''), '.'); + if ($dirname) { + $fileID = $dirname . '/' . $fileID; + } + + // Add variant + if ($variant) { + $fileID .= '__' . $variant; + } + + // Add extension + if ($extension) { + $fileID .= $extension; + } + + return $fileID; + } + + /** + * Get the original filename with the extension rewritten to be the same as the variant file extension. + * + * @param string $filename Original filename without variant + */ + public function rewriteVariantExtension(string $filename, string $variant): string + { + return $this->swapExtension($filename, $variant, self::EXT_VARIANT); + } + + /** + * Get the variant filename with the extension rewritten to be the same as the original file extension. + * + * @param string $filename Original filename without variant + */ + public function restoreOriginalExtension(string $filename, string $variant): string + { + return $this->swapExtension($filename, $variant, self::EXT_ORIGINAL); + } + + /** + * Get the original file's filename with the extension rewritten to be the same as either the original + * or the variant extension. + * + * @param string $filename Original filename without variant + * @param int $extIndex One of self::EXT_ORIGINAL or self::EXT_VARIANT + */ + 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; + } + $filenameWithoutExtension = $matches['basename']; + $extension = $matches['ext']; + + // Loop our variant list until we find our special file extension swap variant + // Reverse the list first so the variant extension we find is the last extension rewrite variant in a chain + $extSwapVariant = preg_quote(self::EXTENSION_REWRITE_VARIANT, '/'); + foreach (array_reverse($subVariants) as $subVariant) { + if (preg_match("/^$extSwapVariant(?.+)$/", $subVariant, $matches)) { + // This array always contain 2 values: The original extension at index 0 and the variant extension at index 1 + /** @var array $extensionData */ + $extensionData = Convert::base64url_decode($matches['base64']); + $extension = $extensionData[$extIndex]; + break; + } + } + + return $filenameWithoutExtension . '.' . $extension; + } + + public function cleanFilename($filename) + { + // Swap backslash for forward slash + $filename = str_replace('\\', '/', $filename ?? ''); + + // Since we use double underscore to delimit variants, eradicate them from filename + return preg_replace('/_{2,}/', '_', $filename ?? ''); + } + + public function lookForVariantRecursive(): bool + { + return false; + } + + abstract protected function getFileIDBase($shortFilename, $fullFilename, $hash, $variant): string; + + abstract protected function validateFileParts($filename, $hash, $variant): void; +} diff --git a/src/FilenameParsing/AlternativeFileExtensionTrait.php b/src/FilenameParsing/AlternativeFileExtensionTrait.php deleted file mode 100644 index 537fe9b6..00000000 --- a/src/FilenameParsing/AlternativeFileExtensionTrait.php +++ /dev/null @@ -1,80 +0,0 @@ -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 88d0fc56..f33148a3 100644 --- a/src/FilenameParsing/FileIDHelper.php +++ b/src/FilenameParsing/FileIDHelper.php @@ -7,11 +7,6 @@ */ 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 5e1bca0b..c2692e78 100644 --- a/src/FilenameParsing/HashFileIDHelper.php +++ b/src/FilenameParsing/HashFileIDHelper.php @@ -3,7 +3,6 @@ namespace SilverStripe\Assets\FilenameParsing; use InvalidArgumentException; -use SilverStripe\Core\Injector\Injectable; /** * Parsed Hash path URLs. Hash paths group a file and its variant under a directory based on a hash generated from the @@ -14,76 +13,13 @@ * * e.g.: `Uploads/a1312bc34d/sam__ResizedImageWzYwLDgwXQ.jpg` */ -class HashFileIDHelper implements FileIDHelper +class HashFileIDHelper extends AbstractFileIDHelper { - use Injectable; - use AlternativeFileExtensionTrait; - /** * Default length at which hashes are truncated. */ const HASH_TRUNCATE_LENGTH = 10; - public function buildFileID($filename, $hash = null, $variant = null, $cleanfilename = true) - { - if ($filename instanceof ParsedFileID) { - $hash = $filename->getHash(); - $variant = $filename->getVariant(); - $filename = $filename->getFilename(); - } - - if (empty($hash)) { - throw new InvalidArgumentException('HashFileIDHelper::buildFileID requires an $hash value.'); - } - - // Since we use double underscore to delimit variants, eradicate them from filename - if ($cleanfilename) { - $filename = $this->cleanFilename($filename); - } - - if ($variant) { - $filename = $this->rewriteVariantExtension($filename, $variant); - } - - $name = basename($filename ?? ''); - - // Split extension - $extension = null; - if (($pos = strpos($name ?? '', '.')) !== false) { - $extension = substr($name ?? '', $pos ?? 0); - $name = substr($name ?? '', 0, $pos); - } - - $fileID = $this->truncate($hash) . '/' . $name; - - // Add directory - $dirname = ltrim(dirname($filename ?? ''), '.'); - if ($dirname) { - $fileID = $dirname . '/' . $fileID; - } - - // Add variant - if ($variant) { - $fileID .= '__' . $variant; - } - - // Add extension - if ($extension) { - $fileID .= $extension; - } - - return $fileID; - } - - public function cleanFilename($filename) - { - // Swap backslash for forward slash - $filename = str_replace('\\', '/', $filename ?? ''); - - // Since we use double underscore to delimit variants, eradicate them from filename - return preg_replace('/_{2,}/', '_', $filename ?? ''); - } - public function parseFileID($fileID) { $pattern = '#^(?([^/]+/)*)(?[a-f0-9]{10})/(?((?[^.]+))?(?(\..+)*)$#'; @@ -127,6 +63,18 @@ public function lookForVariantIn(ParsedFileID $parsedFileID) return $folder . $this->truncate($parsedFileID->getHash()); } + protected function validateFileParts($filename, $hash, $variant): void + { + if (empty($hash)) { + throw new InvalidArgumentException('HashFileIDHelper::buildFileID requires an $hash value.'); + } + } + + protected function getFileIDBase($shortFilename, $fullFilename, $hash, $variant): string + { + return $this->truncate($hash) . '/' . $shortFilename; + } + /** * Truncate a hash to a predefined length * @param $hash @@ -136,9 +84,4 @@ private function truncate($hash) { return substr($hash ?? '', 0, self::HASH_TRUNCATE_LENGTH); } - - public function lookForVariantRecursive(): bool - { - return false; - } } diff --git a/src/FilenameParsing/NaturalFileIDHelper.php b/src/FilenameParsing/NaturalFileIDHelper.php index 7235f4af..02280da8 100644 --- a/src/FilenameParsing/NaturalFileIDHelper.php +++ b/src/FilenameParsing/NaturalFileIDHelper.php @@ -2,8 +2,6 @@ namespace SilverStripe\Assets\FilenameParsing; -use SilverStripe\Core\Injector\Injectable; - /** * Parsed Natural path URLs. Natural path is the same hashless path that appears in the CMS. * @@ -11,67 +9,8 @@ * * e.g.: `Uploads/sam__ResizedImageWzYwLDgwXQ.jpg` */ -class NaturalFileIDHelper implements FileIDHelper +class NaturalFileIDHelper extends AbstractFileIDHelper { - use Injectable; - use AlternativeFileExtensionTrait; - - public function buildFileID($filename, $hash = null, $variant = null, $cleanfilename = true) - { - if ($filename instanceof ParsedFileID) { - $hash = $filename->getHash(); - $variant = $filename->getVariant(); - $filename = $filename->getFilename(); - } - - // Since we use double underscore to delimit variants, eradicate them from filename - if ($cleanfilename) { - $filename = $this->cleanFilename($filename); - } - - if ($variant) { - $filename = $this->rewriteVariantExtension($filename, $variant); - } - - $name = basename($filename ?? ''); - - // Split extension - $extension = null; - if (($pos = strpos($name ?? '', '.')) !== false) { - $extension = substr($name ?? '', $pos ?? 0); - $name = substr($name ?? '', 0, $pos); - } - - $fileID = $name; - - // Add directory - $dirname = ltrim(dirname($filename ?? ''), '.'); - if ($dirname) { - $fileID = $dirname . '/' . $fileID; - } - - // Add variant - if ($variant) { - $fileID .= '__' . $variant; - } - - // Add extension - if ($extension) { - $fileID .= $extension; - } - - return $fileID; - } - - public function cleanFilename($filename) - { - // Swap backslash for forward slash - $filename = str_replace('\\', '/', $filename ?? ''); - - // Since we use double underscore to delimit variants, eradicate them from filename - return preg_replace('/_{2,}/', '_', $filename ?? ''); - } - public function parseFileID($fileID) { $pattern = '#^(?([^/]+/)*)(?((?[^.]+))?(?(\..+)*)$#'; @@ -108,8 +47,13 @@ public function lookForVariantIn(ParsedFileID $parsedFileID) return $folder == '.' ? '' : $folder; } - public function lookForVariantRecursive(): bool + protected function getFileIDBase($shortFilename, $fullFilename, $hash, $variant): string + { + return $shortFilename; + } + + protected function validateFileParts($filename, $hash, $variant): void { - return false; + // no-op } } diff --git a/src/ImageManipulation.php b/src/ImageManipulation.php index feec6901..54f0a457 100644 --- a/src/ImageManipulation.php +++ b/src/ImageManipulation.php @@ -4,7 +4,7 @@ use InvalidArgumentException; use LogicException; -use SilverStripe\Assets\FilenameParsing\FileIDHelper; +use SilverStripe\Assets\FilenameParsing\AbstractFileIDHelper; use SilverStripe\Assets\Storage\AssetContainer; use SilverStripe\Assets\Storage\AssetStore; use SilverStripe\Assets\Storage\DBFile; @@ -849,7 +849,7 @@ public function isHeight($height) public function manipulateExtension(string $newExtension, callable $callback) { $pathParts = pathinfo($this->getFilename()); - $variant = $this->variantName(FileIDHelper::EXTENSION_REWRITE_VARIANT, $pathParts['extension'], $newExtension); + $variant = $this->variantName(AbstractFileIDHelper::EXTENSION_REWRITE_VARIANT, $pathParts['extension'], $newExtension); return $this->manipulate($variant, $callback); }