Skip to content

Commit

Permalink
ENH Refactor FileIDHelper classes
Browse files Browse the repository at this point in the history
The trait is a bit ugly - much nicer to have that functionality in an
abstract class that the concrete helpers can extend.

This also means we can refactor those classes a bit to reduce
unnecessary code duplication.
  • Loading branch information
GuySartorelli committed Jan 17, 2024
1 parent 8d5eb94 commit f75efd4
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 221 deletions.
153 changes: 153 additions & 0 deletions src/FilenameParsing/AbstractFileIDHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace SilverStripe\Assets\FilenameParsing;

use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable;

abstract class AbstractFileIDHelper implements FileIDHelper
{
use Injectable;

/**
* A variant type for encoding a variant filename with a different extension than the original.
*/
public const EXTENSION_REWRITE_VARIANT = 'extRewrite';

/**
* use the original file's extension
*/
private const EXT_ORIGINAL = 0;

/**
* use the variant file's extension
*/
private const EXT_VARIANT = 1;

public function buildFileID($filename, $hash = null, $variant = null, $cleanfilename = true)
{
if ($filename instanceof ParsedFileID) {
$hash = $filename->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('/(?<basename>.+)\.(?<ext>[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(?<base64>.+)$/", $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;
}
80 changes: 0 additions & 80 deletions src/FilenameParsing/AlternativeFileExtensionTrait.php

This file was deleted.

5 changes: 0 additions & 5 deletions src/FilenameParsing/FileIDHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
83 changes: 13 additions & 70 deletions src/FilenameParsing/HashFileIDHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = '#^(?<folder>([^/]+/)*)(?<hash>[a-f0-9]{10})/(?<basename>((?<!__)[^/.])+)(__(?<variant>[^.]+))?(?<extension>(\..+)*)$#';
Expand Down Expand Up @@ -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
Expand All @@ -136,9 +84,4 @@ private function truncate($hash)
{
return substr($hash ?? '', 0, self::HASH_TRUNCATE_LENGTH);
}

public function lookForVariantRecursive(): bool
{
return false;
}
}
Loading

0 comments on commit f75efd4

Please sign in to comment.