diff --git a/plugins/ImageEncoder/ImageEncoder.php b/plugins/ImageEncoder/ImageEncoder.php index f84263992a..798a9ec5db 100644 --- a/plugins/ImageEncoder/ImageEncoder.php +++ b/plugins/ImageEncoder/ImageEncoder.php @@ -19,13 +19,17 @@ namespace Plugin\ImageEncoder; +use App\Core\Cache; +use App\Core\DB\DB; use App\Core\Event; +use App\Core\GSFile; use function App\Core\I18n\_m; use App\Core\Log; use App\Core\Modules\Plugin; use App\Entity\Attachment; use App\Entity\AttachmentThumbnail; use App\Util\Common; +use App\Util\TemporaryFile; use Exception; use Jcupitt\Vips; @@ -47,11 +51,80 @@ class ImageEncoder extends Plugin * * @return bool */ - public function onAttachmentValidation(\SplFileInfo &$file, ?string &$mimetype = null): bool + public function onAttachmentValidation(\SplFileInfo &$file, ?string &$mimetype, ?string &$title): bool { $original_mimetype = $mimetype; - // TODO: Encode in place - //$mimetype = self::preferredType(); + if (GSFile::mimetypeMajor($original_mimetype) != 'image') { + // Nothing concerning us + return Event::next; + } + + $type = self::preferredType(); + $extension = image_type_to_extension($type, include_dot: true); + $temp = new TemporaryFile(prefix: null, suffix: $extension); // This handles deleting the file if some error occurs + $mimetype = image_type_to_mime_type($type); + if ($mimetype != $original_mimetype) { + // If title seems to be a filename with an extension + if (preg_match('/\.[a-z0-9]/i', $title) === 1) { + $title = substr($title, 0, strrpos($title, '.') - 1) . $extension; + } + } + + $image = Vips\Image::newFromFile($file->getRealPath(), ['access' => 'sequential']); + $width = Common::clamp($image->width, 0, Common::config('attachments', 'max_width')); + $height = Common::clamp($image->height, 0, Common::config('attachments', 'max_height')); + $image = $image->crop(0, 0, $width, $height); + $image->writeToFile($temp->getRealPath()); + + $filesize = $temp->getSize(); + $filepath = $file->getRealPath(); + @unlink($filepath); + + $file_quota = Common::config('attachments', 'file_quota'); + if ($filesize > $file_quota) { + // TRANS: Message given if an upload is larger than the configured maximum. + throw new ClientException(_m('No file may be larger than {quota} bytes and the file you sent was {size} bytes. ' . + 'Try to upload a smaller version.', ['quota' => $file_quota, 'size' => $filesize])); + } + + $user = Common::user(); + $query = <<getId() . 'file-quota'; + $user_total = Cache::get($cache_key_user_total, fn () => DB::dql($query, ['actor_id' => $user->getId()])[0]['total']); + Cache::set($cache_key_user_total, $user_total + $filesize); + + if ($user_total + $filesize > $user_quota) { + // TRANS: Message given if an upload would exceed user quota. + throw new ClientException(_m('A file this large would exceed your user quota of {quota} bytes.', ['quota' => $user_quota])); + } + } + + $query .= ' AND MONTH(at.modified) = MONTH(CURRENT_DATE())' + . ' AND YEAR(at.modified) = YEAR(CURRENT_DATE())'; + + $monthly_quota = Common::config('attachments', 'monthly_quota'); + if ($monthly_quota != false) { + $cache_key_user_monthly = 'user-' . $user->getId() . 'monthly-file-quota'; + $monthly_total = Cache::get($cache_key_user_monthly, fn () => DB::dql($query, ['actor_id' => $user->getId()])[0]['total']); + Cache::set($cache_key_user_monthly, $monthly_total + $filesize); + + if ($monthly_total + $filesize > $monthly_quota) { + // TRANS: Message given if an upload would exceed user quota. + throw new ClientException(_m('A file this large would exceed your monthly quota of {quota} bytes.', ['quota' => $monthly_quota])); + } + } + + $temp->commit($filepath); + return Event::stop; } @@ -86,8 +159,6 @@ class ImageEncoder extends Plugin $old_limit = ini_set('memory_limit', Common::config('attachments', 'memory_limit')); try { try { - // -1 means load all pages, 'sequential' access means decode pixels on demand - // $image = Vips\Image::newFromFile(self::getPath($attachment), ['n' => -1, 'access' => 'sequential']); $image = Vips\Image::thumbnail($source, $width, ['height' => $height]); } catch (Exception $e) { Log::error(__METHOD__ . ' encountered exception: ' . print_r($e, true));