Source of file LegacyFileIDHelper.php
Size: 8,664 Bytes - Last Modified: 2021-12-23T10:27:40+00:00
/var/www/docs.ssmods.com/process/src/src/FilenameParsing/LegacyFileIDHelper.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 | <?php namespace SilverStripe\Assets\FilenameParsing; use Exception; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Injector\Injectable; /** * Parsed SS3 style legacy asset URLs. e.g.: `Uploads/_resampled/ResizedImageWzYwLDgwXQ/sam.jpg` * * SS3 legacy paths are no longer used in SilverStripe 4, but a way to parse them is needed for redirecting old SS3 * urls. * * @internal This is still an evolving API. It may change in the next minor release. */ class LegacyFileIDHelper implements FileIDHelper { use Injectable; use Configurable; /** * List of SilverStripe 3 image method names that can appear in variants. Prior to SilverStripe 3.3, variants were * incoded in the filename with dashes. e.g.: `_resampled/FitW10-sam.jpg` rather than `_resampled/FitW10/sam.jpg`. * @config */ private static $ss3_image_variant_methods = [ 'fit', 'fill', 'pad', 'scalewidth', 'scaleheight', 'setratiosize', 'setwidth', 'setheight', 'setsize', 'cmsthumbnail', 'assetlibrarypreview', 'assetlibrarythumbnail', 'stripthumbnail', 'paddedimage', 'formattedimage', 'resizedimage', 'croppedimage', 'cropheight', ]; /** @var bool */ private $failNewerVariant; /** * @param bool $failNewerVariant Whether FileID mapping to newer SS4 formats should be parsed. */ public function __construct($failNewerVariant = true) { $this->failNewerVariant = $failNewerVariant; } public function buildFileID($filename, $hash = null, $variant = null, $cleanfilename = true) { if ($filename instanceof ParsedFileID) { $variant = $filename->getVariant(); $filename = $filename->getFilename(); } $name = basename($filename); // Split extension $extension = null; if (($pos = strpos($name, '.')) !== false) { $extension = substr($name, $pos); $name = substr($name, 0, $pos); } $fileID = $name; // Add directory $dirname = ltrim(dirname($filename), '.'); // Add variant if ($variant) { $fileID = '_resampled/' . str_replace('_', '/', $variant) . '/' . $fileID; } if ($dirname) { $fileID = $dirname . '/' . $fileID; } // Add extension if ($extension) { $fileID .= $extension; } return $fileID; } public function cleanFilename($filename) { // Swap backslash for forward slash $filename = str_replace('\\', '/', $filename); // There's not really any relevant cleaning rule for legacy. It's not important any way because we won't be // generating legacy URLs, aside from maybe for testing. return $filename; } /** * @note LegacyFileIDHelper is meant to fail when parsing newer format fileIDs with a variant e.g.: * `subfolder/abcdef7890/sam__resizeXYZ.jpg`. When parsing fileIDs without a variant, it should return the same * results as natural paths. This behavior can be disabled by setting `failNewerVariant` to false on the * constructor. */ public function parseFileID($fileID) { if ($this->failNewerVariant) { $pattern = '#^(?<folder>([^/]+/)*?)(_resampled/(?<variant>([^.]+))/)?((?<basename>((?<!__)[^/.])+))(?<extension>(\..+)*)$#i'; } else { $pattern = '#^(?<folder>([^/]+/)*?)(_resampled/(?<variant>([^.]+))/)?((?<basename>([^/.])+))(?<extension>(\..+)*)$#i'; } // not a valid file (or not a part of the filesystem) if (!preg_match($pattern, $fileID, $matches)) { return null; } // Can't have a resampled folder without a variant if (empty($matches['variant']) && strpos($fileID, '_resampled') !== false) { return $this->parseSilverStripe30VariantFileID($fileID); } $filename = $matches['folder'] . $matches['basename'] . $matches['extension']; return new ParsedFileID( $filename, '', isset($matches['variant']) ? str_replace('/', '_', $matches['variant']) : '', $fileID ); } /** * Try to parse a FileID as a pre-SS33 variant. From SS3.0 to SS3.2 the variants were prefixed in the file name, * rather than encoded into folders. * @param string $fileID Variant file ID. Variantless FileID should have been parsed by `parseFileID`. * @return ParsedFileID|null */ private function parseSilverStripe30VariantFileID($fileID) { $ss3Methods = $this->getImageVariantMethods(); $variantPartialRegex = implode('|', $ss3Methods); if ($this->failNewerVariant) { $pattern = '#^(?<folder>([^/]+/)*?)(_resampled/(?<variant>((((' . $variantPartialRegex . ')[^.-]+))-)+))?((?<basename>((?<!__)[^/.])+))(?<extension>(\..+)*)$#i'; } else { $pattern = '#^(?<folder>([^/]+/)*?)(_resampled/(?<variant>((((' . $variantPartialRegex . ')[^.-]+))-)+))?((?<basename>([^/.])+))(?<extension>(\..+)*)$#i'; } // not a valid file (or not a part of the filesystem) if (!preg_match($pattern, $fileID, $matches)) { return null; } // Our SS3 variant can be confused with regular filenames, let's minimise the risk of this by making // sure all our variants use a valid SS3 variant expression $variant = trim($matches['variant'], '-'); $possibleVariants = explode('-', $variant); $validVariants = []; $validVariantRegex = '#^(' . $variantPartialRegex . ')(?<base64>(.+))$#i'; // Loop through the possible variants until we find an invalid one while ($possible = array_shift($possibleVariants)) { // Find the base64 encoded argument attached to the image method if (preg_match($validVariantRegex, $possible, $variantMatches)) { try { // Our base 64 encoded string always decodes to a string representation of php array // So we're assuming it always starts with a `[` and ends with a `]` $base64Str = $variantMatches['base64']; $argumentString = base64_decode($base64Str); if ($argumentString && preg_match('/^\[.*\]$/', $argumentString)) { $validVariants[] = $possible; continue; } } catch (Exception $ex) { // If we get an error in the regex or in the base64 decode, assume our possible variant is invalid. } } array_unshift($possibleVariants, $possible); break; } // Can't have a resampled folder without a variant if (empty($validVariants)) { return null; } // Reconcatenate our variants $variant = implode('_', $validVariants); // Our invalid variants are part of the filename $invalidVariant = $possibleVariants ? implode('-', $possibleVariants) . '-' : ''; $filename = $matches['folder'] . $invalidVariant . $matches['basename'] . $matches['extension']; return new ParsedFileID($filename, '', $variant, $fileID); } /** * Get a list of possible variant methods. * @return string[] */ private function getImageVariantMethods() { $variantMethods = self::config()->get('ss3_image_variant_methods'); // Sort the variant methods by descending order of string length. // This is important because the regex will match the string in order of appearance. // e.g. `paddedimageW10` could be confused for `pad` with a base64 string of `dedimageW10` usort($variantMethods, function ($a, $b) { return strlen($b) - strlen($a); }); return $variantMethods; } public function isVariantOf($fileID, ParsedFileID $original) { $variant = $this->parseFileID($fileID); return $variant && $variant->getFilename() == $original->getFilename(); } public function lookForVariantIn(ParsedFileID $parsedFileID) { $folder = dirname($parsedFileID->getFilename()); if ($folder == '.') { $folder = ''; } else { $folder .= '/'; } return $folder . '_resampled'; } public function lookForVariantRecursive(): bool { return true; } } |