diff --git a/extend.php b/extend.php index 0f84140d..fe320078 100644 --- a/extend.php +++ b/extend.php @@ -25,12 +25,14 @@ use Flarum\Post\Event\Revised; use Flarum\Settings\Event\Deserializing; use Flarum\User\User; +use FoF\Upload\Events\File\WasSaved; use FoF\Upload\Events\File\WillBeUploaded; use FoF\Upload\Exceptions\ExceptionHandler; use FoF\Upload\Exceptions\InvalidUploadException; use FoF\Upload\Extend\SvgSanitizer; use FoF\Upload\Extenders\LoadFilesRelationship; use FoF\Upload\Helpers\Util; +use s9e\TextFormatter\Configurator; return [ (new Extend\Frontend('admin')) @@ -86,7 +88,8 @@ ->listen(Deserializing::class, Listeners\AddAvailableOptionsInAdmin::class) ->listen(Posted::class, Listeners\LinkImageToPostOnSave::class) ->listen(Revised::class, Listeners\LinkImageToPostOnSave::class) - ->listen(WillBeUploaded::class, Listeners\AddImageProcessor::class), + ->listen(WillBeUploaded::class, Listeners\AddImageProcessor::class) + ->listen(WasSaved::class, Listeners\AddImageMetadataProcessor::class), (new Extend\Filesystem()) ->disk('private-shared', Extenders\PrivateSharedDiskConfig::class), @@ -108,6 +111,11 @@ ->attributes(Extenders\AddUserAttributes::class), (new Extend\Formatter()) + + ->configure(function (Configurator $config) { + // Remove the check that prevents dynamic content in CSS + $config->templateChecker->remove('DisallowUnsafeDynamicCSS'); + }) ->render(Formatter\ImagePreview\FormatImagePreview::class) ->render(Formatter\TextPreview\FormatTextPreview::class), diff --git a/migrations/2025_08_06_000000_add_image_metadata.php b/migrations/2025_08_06_000000_add_image_metadata.php new file mode 100644 index 00000000..cdf7fb4f --- /dev/null +++ b/migrations/2025_08_06_000000_add_image_metadata.php @@ -0,0 +1,40 @@ + function (Builder $schema) { + if ($schema->hasTable('fof_upload_image_metadata')) { + return; + } + $schema->create('fof_upload_image_metadata', function (Blueprint $table) { + $table->unsignedInteger('upload_id'); + $table->uuid('file_id'); + $table->unsignedInteger('image_width'); + $table->unsignedInteger('image_height'); + + $table->primary('upload_id'); + + // Add foreign key constraint + $table->foreign('upload_id') + ->references('id') + ->on('flarum_fof_upload_files') + ->onDelete('cascade'); + }); + }, + 'down' => function (Builder $schema) { + $schema->dropIfExists('fof_upload_image_metadata'); + }, +]; + diff --git a/resources/templates/image-preview.blade.php b/resources/templates/image-preview.blade.php index bcb8d809..a80aeb64 100644 --- a/resources/templates/image-preview.blade.php +++ b/resources/templates/image-preview.blade.php @@ -1 +1,9 @@ -{@alt} + diff --git a/src/File.php b/src/File.php index b7cbe74b..61a1ca42 100644 --- a/src/File.php +++ b/src/File.php @@ -133,4 +133,12 @@ public function human_filesize(string $bytes, int $decimals = 0): string return sprintf("%.{$decimals}f", (int) $bytes / pow(1024, $factor)).@$size[$factor]; } + + + public function imageMetadata(): \Illuminate\Database\Eloquent\Relations\HasOne + { + return $this->hasOne(ImageMetadata::class, 'upload_id', 'id'); + } + + } diff --git a/src/Formatter/ImagePreview/FormatImagePreview.php b/src/Formatter/ImagePreview/FormatImagePreview.php index b765597d..fcc23425 100644 --- a/src/Formatter/ImagePreview/FormatImagePreview.php +++ b/src/Formatter/ImagePreview/FormatImagePreview.php @@ -12,6 +12,7 @@ namespace FoF\Upload\Formatter\ImagePreview; +use FoF\Upload\ImageMetadata; use FoF\Upload\Repositories\FileRepository; use Illuminate\Support\Arr; use s9e\TextFormatter\Renderer; @@ -57,6 +58,13 @@ public function __invoke(Renderer $renderer, $context, string $xml) $attributes['title'] = $file->base_name; } + // Add aspect ratio if image metadata exists + $imageMetadata = ImageMetadata::byFile($file); + + if ($imageMetadata && $imageMetadata->image_width && $imageMetadata->image_height) { + $attributes['aspectRatio'] = $imageMetadata->image_width . "/" . $imageMetadata->image_height; + } + return $attributes; }); } diff --git a/src/ImageMetadata.php b/src/ImageMetadata.php new file mode 100644 index 00000000..7f74c0bf --- /dev/null +++ b/src/ImageMetadata.php @@ -0,0 +1,60 @@ +where('uuid', $uuid); + } + + public static function byFile($file): Builder|\Illuminate\Database\Eloquent\Model|null + { + if (!$file) { + return null; + } + + return static::query()->where('upload_id', $file->id)->first(); + } + + /** + * Relation to the File model + */ + public function file(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(File::class, 'upload_id', 'id'); + } + + /** + * Static helper to find a metadata row by upload_id + */ + public static function byUploadId(int $uploadId): \Illuminate\Database\Eloquent\Builder|null + { + return static::query()->where('upload_id', $uploadId)->first(); + } +} diff --git a/src/Listeners/AddImageMetadataProcessor.php b/src/Listeners/AddImageMetadataProcessor.php new file mode 100644 index 00000000..817a39de --- /dev/null +++ b/src/Listeners/AddImageMetadataProcessor.php @@ -0,0 +1,41 @@ +validateMime($event->mime)) { + $this->processor->addMetadata($event->file, $event->uploadedFile, $event->mime); + } + } + + protected function validateMime($mime): bool + { + return true; + if ($mime == 'image/jpeg' || $mime == 'image/png' || $mime == 'image/gif' || $mime == 'image/svg+xml') { + return true; + } + + return false; + } +} diff --git a/src/Processors/ImageProcessor.php b/src/Processors/ImageProcessor.php index 857c5970..13d9142c 100644 --- a/src/Processors/ImageProcessor.php +++ b/src/Processors/ImageProcessor.php @@ -92,4 +92,22 @@ protected function watermark(Image $image) ); } } + + public function addMetadata(File &$file, UploadedFile &$upload, string &$mime): void + { + try { + $image = (new ImageManager())->make('assets/files'.DIRECTORY_SEPARATOR.$file->path); + } catch (NotReadableException $e) { + throw new ValidationException(['upload' => 'Corrupted image']); + } + + $file->imageMetadata()->create([ + 'upload_id' => $file->id, + 'file_id' => $file->uuid ?? '', + 'image_width' => $image->width(), + 'image_height' => $image->height(), + ]); + $file->save(); + + } }