Skip to content

Use "process source images" preset when downloading assets #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 19, 2024
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 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'));
}
}
Loading