Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"laravel/prompts": "^0.3.0",
"league/commonmark": "^2.2",
"league/csv": "^9.0",
"league/glide": "^2.3",
"league/glide": "^3.0",
"maennchen/zipstream-php": "^3.1",
"michelf/php-smartypants": "^1.8.1",
"nesbot/carbon": "^3.0",
Expand Down
17 changes: 1 addition & 16 deletions config/assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,12 @@
|--------------------------------------------------------------------------
|
| The driver that will be used under the hood for image manipulation.
| Supported: "gd" or "imagick" (if installed on your server)
| Supported: "gd", "imagick" or a class name of a custom driver.
|
*/

'driver' => 'gd',

/*
|--------------------------------------------------------------------------
| Additional Image Extensions
|--------------------------------------------------------------------------
|
| Define any additional image file extensions you would like Statamic to
| process. You should ensure that both your server and the selected
| image manipulation driver properly supports these extensions.
|
*/

'additional_extensions' => [
// 'heic',
],

/*
|--------------------------------------------------------------------------
| Save Cached Images
Expand Down
23 changes: 8 additions & 15 deletions src/Imaging/ImageValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

namespace Statamic\Imaging;

use Statamic\Support\Str;
use Intervention\Image\Interfaces\DriverInterface;
use Symfony\Component\Mime\MimeTypes;

class ImageValidator
{
public function __construct(private DriverInterface $driver)
{
}

/**
* Check if image has valid extension and mimetype.
*
Expand Down Expand Up @@ -39,22 +43,11 @@ public function isValidImage($extension, $mimeType)
*/
public function isValidExtension($extension)
{
$driver = config('statamic.assets.image_manipulation.driver');

if ($driver == 'gd') {
$allowed = ['jpeg', 'jpg', 'png', 'gif', 'webp'];
} elseif ($driver == 'imagick') {
$allowed = ['jpeg', 'jpg', 'png', 'gif', 'tif', 'bmp', 'psd', 'webp'];
} else {
throw new \Exception("Unsupported image manipulation driver [$driver]");
if (! $extension) {
return false;
}

$additional = config('statamic.assets.image_manipulation.additional_extensions', []);

return collect($allowed)
->merge($additional)
->map(fn ($extension) => Str::lower($extension))
->contains(Str::lower($extension));
return $this->driver->supports($extension);
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/Providers/GlideServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Statamic\Providers;

use Illuminate\Support\ServiceProvider;
use Intervention\Image\ImageManager;
use League\Glide\Server;
use Statamic\Contracts\Imaging\ImageManipulator;
use Statamic\Contracts\Imaging\UrlBuilder;
Expand All @@ -11,6 +12,7 @@
use Statamic\Imaging\GlideImageManipulator;
use Statamic\Imaging\GlideUrlBuilder;
use Statamic\Imaging\ImageGenerator;
use Statamic\Imaging\ImageValidator;
use Statamic\Imaging\PresetGenerator;
use Statamic\Imaging\StaticUrlBuilder;

Expand Down Expand Up @@ -39,6 +41,18 @@ public function register()
$app->make(ImageGenerator::class)
);
});

$this->app->bind(ImageValidator::class, function ($app) {
$driver = config('statamic.assets.image_manipulation.driver', 'gd');

$imageManager = match ($driver) {
'gd' => ImageManager::gd(),
'imagick' => ImageManager::imagick(),
default => ImageManager::withDriver($driver),
};

return new ImageValidator($imageManager->driver());
});
}

private function getBuilder()
Expand Down
60 changes: 0 additions & 60 deletions tests/Assets/AssetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1953,66 +1953,6 @@ public function it_doesnt_process_or_error_when_uploading_non_glideable_file_wit
Event::assertDispatched(AssetSaved::class);
}

#[Test]
public function it_can_process_a_custom_image_format()
{
Event::fake();

config(['statamic.assets.image_manipulation.presets.small' => [
'w' => '15',
'h' => '15',
]]);

// Normally pdf files (for example) are not supported by gd or imagick. However, imagick does
// does actually support over 100 formats with extra configuration (eg. via ghostscript).
// Thus, we allow the user to configure additional extensions in their assets config.
config(['statamic.assets.image_manipulation.additional_extensions' => [
'pdf',
]]);

$this->container->sourcePreset('small');

$asset = (new Asset)->container($this->container)->path('path/to/asset.pdf')->syncOriginal();

Facades\AssetContainer::shouldReceive('findByHandle')->with('test_container')->andReturn($this->container);
Storage::disk('test')->assertMissing('path/to/asset.pdf');

$file = UploadedFile::fake()->image('asset.pdf', 20, 30);

// Ensure a glide server is instantiated and `makeImage()` is called...
Facades\Glide::partialMock()
->shouldReceive('server->makeImage')
->andReturn($file->getFilename())
->once();

// Since we're mocking the glide server, and since the uploader's `write()` method expects
// this location, we need to force it into that storage path for this test to pass...
File::move($file->getRealPath(), $tempUploadedFilePath = storage_path('statamic/glide/tmp').'/'.$file->getFilename());

// Perform the upload...
$return = $asset->upload($file);

// Now we'll delete that temporary UploadedFile, because we moved it into the app's storage above, and
// it's normally not supposed to be there. This is necessary to prevent state issues across tests...
File::delete($tempUploadedFilePath);

$this->assertEquals($asset, $return);
$this->assertDirectoryExists($glideDir = storage_path('statamic/glide/tmp'));
$this->assertEmpty(app('files')->allFiles($glideDir)); // no temp files
Storage::disk('test')->assertExists('path/to/asset.pdf');
$this->assertEquals('path/to/asset.pdf', $asset->path());
Event::assertDispatched(AssetUploaded::class, function ($event) use ($asset) {
return $event->asset = $asset;
});
Event::assertDispatched(AssetSaved::class);
$meta = $asset->meta();

// Normally we assert changes to the meta, but we cannot in this test because we can't guarantee
// the test suite has imagick with ghostscript installed (required for pdf files, for example).
// $this->assertEquals(10, $meta['width']);
// $this->assertEquals(15, $meta['height']);
}

#[Test]
public function it_appends_timestamp_to_uploaded_files_filename_if_it_already_exists()
{
Expand Down
81 changes: 8 additions & 73 deletions tests/Imaging/ImageValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ class ImageValidatorTest extends TestCase
#[Test]
public function it_checks_if_image_has_valid_extension_and_mimetype()
{
config(['statamic.assets.image_manipulation.driver' => 'imagick']);

config(['statamic.assets.image_manipulation.additional_extensions' => [
'svg',
'pdf',
'eps',
]]);
config(['statamic.assets.image_manipulation.driver' => 'gd']);

// We'll test `isValidExtension()` functionality separately below, and just mock here...
ImageValidator::shouldReceive('isValidExtension')->andReturnTrue()->times(24);
Expand Down Expand Up @@ -52,76 +46,17 @@ public function it_checks_if_image_has_valid_extension_and_mimetype()
}

#[Test]
public function it_checks_if_image_extension_is_allowed_for_manipulation_with_gd_driver()
public function it_checks_if_image_extension_is_allowed_for_manipulation()
{
config(['statamic.assets.image_manipulation.driver' => 'gd']);

$this->assertTrue(ImageValidator::isValidExtension('jpeg'));
$this->assertTrue(ImageValidator::isValidExtension('jpg'));
$this->assertTrue(ImageValidator::isValidExtension('png'));
$this->assertTrue(ImageValidator::isValidExtension('gif'));
$this->assertTrue(ImageValidator::isValidExtension('webp'));

// Supported by imagick only...
$this->assertFalse(ImageValidator::isValidExtension('tif'));
$this->assertFalse(ImageValidator::isValidExtension('bmp'));
$this->assertFalse(ImageValidator::isValidExtension('psd'));

// Supported by imagick only, but requires `additional_extensions` configuration...
$this->assertFalse(ImageValidator::isValidExtension('svg'));
$this->assertFalse(ImageValidator::isValidExtension('pdf'));
$this->assertFalse(ImageValidator::isValidExtension('eps'));
}

#[Test]
public function it_checks_if_image_extension_is_allowed_for_manipulation_with_imagick_driver()
{
config(['statamic.assets.image_manipulation.driver' => 'imagick']);

$this->assertTrue(ImageValidator::isValidExtension('jpeg'));
$this->assertTrue(ImageValidator::isValidExtension('jpg'));
$this->assertTrue(ImageValidator::isValidExtension('png'));
$this->assertTrue(ImageValidator::isValidExtension('gif'));
$this->assertTrue(ImageValidator::isValidExtension('webp'));
$this->assertTrue(ImageValidator::isValidExtension('tif'));
$this->assertTrue(ImageValidator::isValidExtension('bmp'));
$this->assertTrue(ImageValidator::isValidExtension('psd'));

// Supported by imagick, but requires `additional_extensions` configuration...
$this->assertFalse(ImageValidator::isValidExtension('svg'));
$this->assertFalse(ImageValidator::isValidExtension('pdf'));
$this->assertFalse(ImageValidator::isValidExtension('eps'));
$this->assertFalse(ImageValidator::isValidExtension('avif'));
}

#[Test]
public function it_checks_if_custom_image_extension_is_allowed_for_manipulation_with_proper_config()
{
config(['statamic.assets.image_manipulation.driver' => 'imagick']);

config(['statamic.assets.image_manipulation.additional_extensions' => [
'svg',
'pdf',
'eps',
'avif',
]]);

$this->assertTrue(ImageValidator::isValidExtension('jpeg'));
$this->assertTrue(ImageValidator::isValidExtension('jpg'));
$this->assertTrue(ImageValidator::isValidExtension('png'));
$this->assertTrue(ImageValidator::isValidExtension('gif'));
$this->assertTrue(ImageValidator::isValidExtension('webp'));
$this->assertTrue(ImageValidator::isValidExtension('tif'));
$this->assertTrue(ImageValidator::isValidExtension('bmp'));
$this->assertTrue(ImageValidator::isValidExtension('psd'));
$mock = \Mockery::mock(\Intervention\Image\Interfaces\DriverInterface::class);
$mock->shouldReceive('supports')->with('one')->andReturnTrue();
$mock->shouldReceive('supports')->with('two')->andReturnFalse();

// Should now be supported due to `additional_extensions` config...
$this->assertTrue(ImageValidator::isValidExtension('svg'));
$this->assertTrue(ImageValidator::isValidExtension('pdf'));
$this->assertTrue(ImageValidator::isValidExtension('eps'));
$this->assertTrue(ImageValidator::isValidExtension('avif'));
$imageValidator = new \Statamic\Imaging\ImageValidator($mock);

// Not configured, should still be false...
$this->assertFalse(ImageValidator::isValidExtension('exe'));
$this->assertTrue($imageValidator->isValidExtension('one'));
$this->assertFalse($imageValidator->isValidExtension('two'));
}
}