Skip to content

Commit

Permalink
Use "process source images" preset when downloading assets (#59)
Browse files Browse the repository at this point in the history
* wip

* wip

* Fix styling

* Make it optional. Add a config option.

* Show the toggle next to the download toggle.

* Refactor.

* Add a test

* Fix styling

* wip

---------

Co-authored-by: duncanmcclean <[email protected]>
  • Loading branch information
duncanmcclean and duncanmcclean authored Dec 19, 2024
1 parent 832abbb commit 28dc368
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 9 deletions.
2 changes: 1 addition & 1 deletion DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Before importing, you will need to do some preparation:
You can run the importer as many times as you like as you tweak the mappings. It'll update existing content and create new content as needed.

#### Queueing
If you're importing a lot of content, you may want to consider running a queue worker to handle the import in the background.
If you're importing a lot of content or downloading a lot of assets, you may want to consider running a queue worker to handle the import in the background.

Assuming you have Redis installed, you can update the `QUEUE_CONNECTION` in your `.env` file to `redis` and then run:

Expand Down
1 change: 1 addition & 0 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
'assets_download_when_missing_instructions' => 'If the asset can\'t be found in the asset container, should it be downloaded?',
'assets_folder_instructions' => 'By default, downloaded assets will use same folder structure as the original URL. You can specify a different folder here.',
'assets_related_field_instructions' => 'Which field does the data reference?',
'assets_process_downloaded_images_instructions' => 'Should downloaded images be processed using the asset container\'s source preset?',
'entries_create_when_missing_instructions' => 'Create the entry if it doesn\'t exist.',
'entries_related_field_instructions' => 'Which field does the data reference?',
'terms_create_when_missing_instructions' => 'Create the term if it doesn\'t exist.',
Expand Down
84 changes: 81 additions & 3 deletions src/Transformers/AssetsTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

namespace Statamic\Importer\Transformers;

use Facades\Statamic\Imaging\ImageValidator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Statamic\Assets\AssetUploader;
use Statamic\Contracts\Assets\AssetContainer as AssetContainerContract;
use Statamic\Facades\Asset;
use Statamic\Facades\AssetContainer;
use Statamic\Facades\Glide;
use Statamic\Facades\Path;
use Statamic\Importer\Sources\Csv;
use Statamic\Importer\Sources\Xml;
Expand Down Expand Up @@ -51,7 +55,9 @@ public function transform(string $value): null|string|array
->container($assetContainer)
->path($this->assetPath($assetContainer, $path));

$assetContainer->disk()->put($asset->path(), $request->body());
$this->config('process_downloaded_images')
? $this->processAssetUsingSourcePreset($asset, $path, $request->body())
: $assetContainer->disk()->put($asset->path(), $request->body());

$asset->save();
}
Expand Down Expand Up @@ -91,8 +97,68 @@ private function assetPath(AssetContainerContract $assetContainer, string $path)
return $path;
}

private function processAssetUsingSourcePreset($asset, string $path, string $contents): void
{
Storage::disk('local')->put($tempPath = 'statamic/temp-assets/'.Str::random().'.'.Str::afterLast($path, '.'), $contents);

$uploadedFile = new UploadedFile(
path: Storage::disk('local')->path($tempPath),
originalName: $asset->basename(),
mimeType: Storage::mimeType($tempPath)
);

$source = $this->processSourceFile($uploadedFile);

$asset->container()->disk()->put($asset->path(), $stream = fopen($source, 'r'));

if (is_resource($stream)) {
fclose($stream);
}

app('files')->delete($source);
Storage::disk('local')->delete($tempPath);
}

/**
* This method has been copied from Core's Uploader class. We don't need the rest of the
* Uploader, just this method so copying it was the easiest solution.
*/
private function processSourceFile(UploadedFile $file): string
{
if ($file->getMimeType() === 'image/gif') {
return $file->getRealPath();
}

if (! $preset = $this->preset()) {
return $file->getRealPath();
}

if (! ImageValidator::isValidImage($file->getClientOriginalExtension(), $file->getClientMimeType())) {
return $file->getRealPath();
}

$server = Glide::server([
'source' => $file->getPath(),
'cache' => $cache = storage_path('statamic/glide/tmp'),
'cache_with_file_extensions' => false,
]);

try {
return $cache.'/'.$server->makeImage($file->getFilename(), ['p' => $preset]);
} catch (\Exception $exception) {
return $file->getRealPath();
}
}

private function preset()
{
return AssetContainer::find($this->field->get('container'))->sourcePreset();
}

public function fieldItems(): array
{
$assetContainer = AssetContainer::find($this->field->get('container'));

$fieldItems = [
'related_field' => [
'type' => 'select',
Expand All @@ -116,18 +182,26 @@ public function fieldItems(): array
'display' => __('Download when missing?'),
'instructions' => __('importer::messages.assets_download_when_missing_instructions'),
'if' => ['related_field' => 'url'],
'width' => $assetContainer->sourcePreset() ? 50 : 100,
],
'process_downloaded_images' => [
'type' => 'toggle',
'display' => __('Process downloaded images?'),
'instructions' => __('importer::messages.assets_process_downloaded_images_instructions'),
'if' => ['related_field' => 'url', 'download_when_missing' => true],
'width' => 50,
],
'folder' => [
'type' => 'asset_folder',
'display' => __('Folder'),
'instructions' => __('importer::messages.assets_folder_instructions'),
'if' => ['download_when_missing' => true],
'container' => $this->field->get('container'),
'container' => $assetContainer->handle(),
'max_items' => 1,
],
];

if (AssetContainer::find($this->field->get('container'))->blueprint()->hasField('alt')) {
if ($assetContainer->blueprint()->hasField('alt')) {
$row = match ($this->import?->get('type')) {
'csv' => (new Csv($this->import))->getItems($this->import->get('path'))->first(),
'xml' => (new Xml($this->import))->getItems($this->import->get('path'))->first(),
Expand All @@ -144,6 +218,10 @@ public function fieldItems(): array
];
}

if (! $assetContainer->sourcePreset()) {
unset($fieldItems['process_downloaded_images']);
}

return $fieldItems;
}
}
25 changes: 21 additions & 4 deletions src/Transformers/BardTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function transform(string $value): array
'base_url' => $this->config['assets_base_url'] ?? null,
'download_when_missing' => $this->config['assets_download_when_missing'] ?? false,
'folder' => $this->config['assets_folder'] ?? null,
'process_downloaded_images' => $this->config['assets_process_downloaded_images'] ?? false,
]
);

Expand Down Expand Up @@ -89,8 +90,12 @@ private function isGutenbergValue(string $value): bool

public function fieldItems(): array
{
if ($this->field->get('container')) {
return [
$fieldItems = [];

if ($assetContainer = $this->field->get('container')) {
$assetContainer = AssetContainer::find($assetContainer);

$fieldItems = [
'assets_base_url' => [
'type' => 'text',
'display' => __('Assets Base URL'),
Expand All @@ -100,18 +105,30 @@ public function fieldItems(): array
'type' => 'toggle',
'display' => __('Download assets when missing?'),
'instructions' => __('importer::messages.assets_download_when_missing_instructions'),
'width' => $assetContainer->sourcePreset() ? 50 : 100,
],
'assets_process_downloaded_images' => [
'type' => 'toggle',
'display' => __('Process downloaded images?'),
'instructions' => __('importer::messages.assets_process_downloaded_images_instructions'),
'if' => ['assets_download_when_missing' => true],
'width' => 50,
],
'assets_folder' => [
'type' => 'asset_folder',
'display' => __('Folder'),
'instructions' => __('importer::messages.assets_folder_instructions'),
'if' => ['assets_download_when_missing' => true],
'container' => $this->field->get('container'),
'container' => $assetContainer->handle(),
'max_items' => 1,
],
];

if (! $assetContainer->sourcePreset()) {
unset($fieldItems['assets_process_downloaded_images']);
}
}

return [];
return $fieldItems;
}
}
2 changes: 2 additions & 0 deletions src/WordPress/Gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public static function toBard(array $config, Blueprint $blueprint, Field $field,
'base_url' => $config['assets_base_url'],
'download_when_missing' => $config['assets_download_when_missing'] ?? false,
'folder' => $config['assets_folder'] ?? null,
'process_downloaded_images' => $config['assets_process_downloaded_images'] ?? false,
]
);

Expand Down Expand Up @@ -130,6 +131,7 @@ public static function toBard(array $config, Blueprint $blueprint, Field $field,
'base_url' => $config['assets_base_url'] ?? null,
'download_when_missing' => $config['assets_download_when_missing'] ?? false,
'folder' => $config['assets_folder'] ?? null,
'process_downloaded_images' => $config['assets_process_downloaded_images'] ?? false,
]
);

Expand Down
43 changes: 42 additions & 1 deletion tests/Transformers/AssetsTransformerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
namespace Statamic\Importer\Tests\Transformers;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\AssetContainer;
use Statamic\Facades\Collection;
use Statamic\Facades\Glide;
use Statamic\Importer\Facades\Import;
use Statamic\Importer\Tests\TestCase;
use Statamic\Importer\Transformers\AssetsTransformer;
use Statamic\Support\Str;
use Statamic\Testing\Concerns\PreventsSavingStacheItemsToDisk;

class AssetsTransformerTest extends TestCase
Expand All @@ -32,7 +35,7 @@ protected function setUp(): void
$this->collection = tap(Collection::make('pages'))->save();

$this->blueprint = $this->collection->entryBlueprint();
$this->blueprint->ensureField('featured_image', ['type' => 'assets', 'max_files' => 1])->save();
$this->blueprint->ensureField('featured_image', ['type' => 'assets', 'container' => 'assets', 'max_files' => 1])->save();

$this->field = $this->blueprint->field('featured_image');

Expand Down Expand Up @@ -228,4 +231,42 @@ public function it_sets_alt_text_on_downloaded_asset()
$asset = $this->assetContainer->asset('2024/10/image.png');
$this->assertEquals('A photo taken by someone.', $asset->get('alt'));
}

#[Test]
public function it_processes_asset_using_source_preset()
{
Http::fake([
'https://example.com/wp-content/uploads/2024/10/image.png' => Http::response(UploadedFile::fake()->image('image.png')->size(100)->get()),
]);

Str::createRandomStringsUsing(fn () => 'temp');

File::ensureDirectoryExists(storage_path('statamic/glide/tmp/temp.png/random'));
File::put(storage_path('statamic/glide/tmp/temp.png/random/temp.png'), 'Transformed Image');

Glide::shouldReceive('server')->once()->andReturnSelf();
Glide::shouldReceive('makeImage')->once()->with('temp.png', ['p' => 'thumbnail'])->andReturn('temp.png/random/temp.png');

$this->assetContainer->disk('public')->sourcePreset('thumbnail')->save();

$transformer = new AssetsTransformer(
import: $this->import,
blueprint: $this->blueprint,
field: $this->field,
config: [
'related_field' => 'url',
'base_url' => 'https://example.com/wp-content/uploads',
'download_when_missing' => true,
'process_downloaded_images' => true,
],
);

$output = $transformer->transform('https://example.com/wp-content/uploads/2024/10/image.png');

$this->assertEquals('2024/10/image.png', $output);

Storage::disk('public')->assertExists('2024/10/image.png');

$this->assertFileDoesNotExist(storage_path('statamic/glide/tmp/temp.png/random/temp.png'));
}
}

0 comments on commit 28dc368

Please sign in to comment.