'Formats acceptés : PNG, JPEG ou GIF.', "{$field}.mimetypes" => 'Le fichier n\'est pas une image PNG, JPEG ou GIF valide.', "{$field}.dimensions" => 'Image illisible : le fichier est incomplet ou endommagé.', "{$field}.uploaded" => 'L\'envoi a échoué : fichier trop lourd pour le serveur.', "{$field}.max" => 'Image trop lourde (2 Mo maximum ; visez moins de 100 Ko pour une signature).', ]; } public function dir(): string { $dir = config('signature.image_path'); File::ensureDirectoryExists($dir); return $dir; } public function path(string $name): string { return $this->dir().DIRECTORY_SEPARATOR.basename($name); } public function exists(string $name): bool { return $name !== '' && $name === basename($name) && in_array(strtolower(pathinfo($name, PATHINFO_EXTENSION)), self::EXTENSIONS, true) && File::isFile($this->path($name)); } public function url(string $name): string { return config('signature.public_url').'/img/'.rawurlencode($name); } /** Largeur réelle en pixels, ou null si le fichier est illisible. */ public function width(string $name): ?int { $size = $this->exists($name) ? @getimagesize($this->path($name)) : false; return $size ? (int) $size[0] : null; } /** @return Collection */ public function all(): Collection { return collect(File::files($this->dir())) ->filter(fn ($f) => in_array(strtolower($f->getExtension()), self::EXTENSIONS, true)) ->map(function ($f) { $size = @getimagesize($f->getPathname()) ?: [null, null]; return [ 'name' => $f->getFilename(), 'url' => $this->url($f->getFilename()), 'kb' => (int) ceil($f->getSize() / 1024), 'width' => $size[0], 'height' => $size[1], 'modified' => $f->getMTime(), ]; }) ->sortByDesc('modified')->values(); } /** Enregistre un fichier déjà validé (uploadRules) sous un nom nettoyé, sans écraser l'existant. Retourne le nom final. */ public function store(UploadedFile $file, ?string $name = null): string { // Extension déduite du contenu réel, jamais du nom envoyé par le navigateur. $info = @getimagesize($file->getRealPath()); $ext = [IMAGETYPE_PNG => 'png', IMAGETYPE_JPEG => 'jpg', IMAGETYPE_GIF => 'gif'][$info[2] ?? 0] ?? null; abort_unless($ext, 422, 'Image illisible.'); $base = Str::slug(pathinfo($name ?: $file->getClientOriginalName(), PATHINFO_FILENAME)) ?: 'image'; $stored = "{$base}.{$ext}"; for ($i = 2; File::exists($this->path($stored)); $i++) { $stored = "{$base}-{$i}.{$ext}"; } $file->move($this->dir(), $stored); @chmod($this->path($stored), 0644); return $stored; } public function delete(string $name): void { File::delete($this->path($name)); } }