forked from silverstripe/silverstripe-assets
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0cee400
commit 2c8704f
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Assets\Conversion; | ||
|
||
use LogicException; | ||
|
||
/** | ||
* An exception that represents a failure to convert a file in a FileConverter class. | ||
*/ | ||
class FileConversionException extends LogicException | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Assets\Conversion; | ||
|
||
use SilverStripe\Assets\Storage\DBFile; | ||
use SilverStripe\Core\Config\Configurable; | ||
|
||
class FileConversionManager | ||
{ | ||
use Configurable; | ||
|
||
private static $converters = []; | ||
|
||
/** | ||
* Undocumented function | ||
* | ||
* @throws FileConversionException if the conversion failed or there were no converters available | ||
*/ | ||
public function convert(DBFile|string $from, string $toFormat, array $options = []): DBFile | ||
{ | ||
// TODO uhhhh figure out a nicer way to organise the converters.... | ||
// we ideally don't want to even ask an image converter when the formats are doc and pdf, for example... | ||
foreach ($converters as $converter) { | ||
/** @var FileConverter $converter */ | ||
if ($converter->supportsConversion($from, $toFormat, $options)) { | ||
return $converter->convert($from, $toFormat, $options); | ||
} | ||
} | ||
throw new FileConversionException('No file converter available for this conversion.'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Assets\Conversion; | ||
|
||
use SilverStripe\Assets\Storage\DBFile; | ||
|
||
interface FileConverter | ||
{ | ||
/** | ||
* Checks whether this converter supports a conversion from one file type to another. | ||
* | ||
* @param DBFile|string $from A DBFile instance or specific file extension | ||
* @param array $options Any options defined for this converter which should apply to the conversion | ||
*/ | ||
public function supportsConversion(DBFile|string $from, string $toFormat, array $options = []): bool; | ||
|
||
/** | ||
* Converts the given DBFile instance to another file type. | ||
* | ||
* @param array $options Any options defined for this converter which should apply to the conversion | ||
* @throws FileConversionException if invalid options are passed, or the conversion is not supported or fails | ||
*/ | ||
public function convert(DBFile $original, string $toFormat, array $options = []): DBFile; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Assets\Conversion; | ||
|
||
use Intervention\Image\Exception\ImageException; | ||
use SilverStripe\Assets\Image_Backend; | ||
use SilverStripe\Assets\InterventionBackend; | ||
use SilverStripe\Assets\Storage\AssetStore; | ||
use SilverStripe\Assets\Storage\DBFile; | ||
use SilverStripe\Core\Injector\Injector; | ||
|
||
/** | ||
* Image converter powered by the Intervention Image library, assuming you haven't swapped out the ImageBackend implementation. | ||
*/ | ||
class InterventionImageConverter implements FileConverter | ||
{ | ||
public function supportsConversion(DBFile|string $from, string $toFormat, array $options = []): bool | ||
{ | ||
$unsupportedOptions = $this->validateOptions($options); | ||
if (!empty($unsupportedOptions)) { | ||
return false; | ||
} | ||
// If backend is not InterventionBackend, return false | ||
// GD and Imagick support different things so be dynamic between those | ||
if ($from instanceof DBFile) { | ||
$backend = $from->getImageBackend(); | ||
$from = $from->getExtension() ?: $from->getMimeType(); | ||
} else { | ||
$backend = Injector::inst()->get(Image_Backend::class); | ||
} | ||
// This converter requires intervention image | ||
if (!is_a($backend, InterventionBackend::class)) { | ||
return false; | ||
} | ||
/** @var InterventionBackend $backend */ | ||
$driver = $backend->getImageManager()->config['driver'] ?? null; | ||
return $this->supportedByIntervention($from, $toFormat, $driver); | ||
} | ||
|
||
public function convert(DBFile $original, string $toFormat, array $options = []): DBFile | ||
{ | ||
$problems = $this->validateOptions($options); | ||
if (!empty($problems)) { | ||
throw new FileConversionException('Invalid options provided: ' . implode(', ', $problems)); | ||
} | ||
$quality = $options['quality'] ?? null; | ||
try { | ||
$result = $original->manipulateExtension( | ||
$toFormat, | ||
function (AssetStore $store, string $filename, string $hash, string $variant) use ($original, $quality) { | ||
$backend = clone $original->getImageBackend(); | ||
if ($quality !== null) { | ||
$backend->setQuality($quality); | ||
} | ||
$config = ['conflict' => AssetStore::CONFLICT_USE_EXISTING]; | ||
$tuple = $backend->writeToStore($store, $filename, $hash, $variant, $config); | ||
return [$tuple, $backend]; | ||
} | ||
); | ||
} catch (ImageException $e) { | ||
throw new FileConversionException('Failed to convert: ' . $e->getMessage(), $e->getCode(), $e); | ||
} | ||
if ($result === null) { | ||
throw new FileConversionException('File conversion resulted in null. Check whether original file actually exists.'); | ||
} | ||
return $result; | ||
} | ||
|
||
private function validateOptions(array $options): array | ||
{ | ||
$problems = []; | ||
foreach ($options as $key => $value) { | ||
if ($key !== 'quality') { | ||
$problems[] = "unexpected option '$key'"; | ||
continue; | ||
} | ||
if (!is_int($value)) { | ||
$problems[] = "quality value must be an integer"; | ||
} | ||
} | ||
return $problems; | ||
} | ||
|
||
private function supportedByIntervention(string $from, string $to, string $driver): bool | ||
{ | ||
// If the driver is somehow not GD or Imagick, we have no way to know what it might support | ||
if ($driver !== 'gd' && $driver !== 'imagick') { | ||
return false; | ||
} | ||
|
||
foreach ([$from, $to] as $format) { | ||
// This follows the logic in intervention's AbstractEncoder::process() method | ||
// and the various methods in the Encoder classes for GD and Imagick | ||
switch (strtolower($format)) { | ||
case 'gif': | ||
case 'image/gif': | ||
// always supported | ||
break; | ||
case 'png': | ||
case 'image/png': | ||
case 'image/x-png': | ||
// always supported | ||
break; | ||
case 'jpg': | ||
case 'jpeg': | ||
case 'jfif': | ||
case 'image/jp2': | ||
case 'image/jpg': | ||
case 'image/jpeg': | ||
case 'image/pjpeg': | ||
case 'image/jfif': | ||
// always supported | ||
break; | ||
case 'tif': | ||
case 'tiff': | ||
case 'image/tiff': | ||
case 'image/tif': | ||
case 'image/x-tif': | ||
case 'image/x-tiff': | ||
if ($driver === 'gd') { | ||
false; | ||
} | ||
// always supported by imagick | ||
break; | ||
case 'bmp': | ||
case 'ms-bmp': | ||
case 'x-bitmap': | ||
case 'x-bmp': | ||
case 'x-ms-bmp': | ||
case 'x-win-bitmap': | ||
case 'x-windows-bmp': | ||
case 'x-xbitmap': | ||
case 'image/ms-bmp': | ||
case 'image/x-bitmap': | ||
case 'image/x-bmp': | ||
case 'image/x-ms-bmp': | ||
case 'image/x-win-bitmap': | ||
case 'image/x-windows-bmp': | ||
case 'image/x-xbitmap': | ||
if ($driver === 'gd' && !function_exists('imagebmp')) { | ||
return false; | ||
} | ||
// always supported by imagick | ||
break; | ||
case 'ico': | ||
case 'image/x-ico': | ||
case 'image/x-icon': | ||
case 'image/vnd.microsoft.icon': | ||
if ($driver === 'gd') { | ||
return false; | ||
} | ||
// always supported by imagick | ||
break; | ||
case 'psd': | ||
case 'image/vnd.adobe.photoshop': | ||
if ($driver === 'gd') { | ||
return false; | ||
} | ||
// always supported by imagick | ||
break; | ||
case 'webp': | ||
case 'image/webp': | ||
case 'image/x-webp': | ||
if ($driver === 'gd' && !function_exists('imagewebp')) { | ||
return false; | ||
} | ||
if ($driver === 'imagick' && !\Imagick::queryFormats('WEBP')) { | ||
return false; | ||
} | ||
break; | ||
case 'avif': | ||
case 'image/avif': | ||
if ($driver === 'gd' && !function_exists('imageavif')) { | ||
return false; | ||
} | ||
if ($driver === 'imagick' && !\Imagick::queryFormats('AVIF')) { | ||
return false; | ||
} | ||
break; | ||
case 'heic': | ||
case 'image/heic': | ||
case 'image/heif': | ||
if ($driver === 'gd') { | ||
return false; | ||
} | ||
if ($driver === 'imagick' && !\Imagick::queryFormats('HEIC')) { | ||
return false; | ||
} | ||
break; | ||
default: | ||
// Anything else is not supported | ||
return false; | ||
} | ||
} | ||
// If we get here both formats are in the explicit list above and are supported | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters