user()?->email === config('lodge.admin_email'), 403); } public function store(Request $request) { $this->authorizeAdmin($request); $request->validate([ 'photos' => ['required','array','min:1','max:10'], 'photos.*' => ['required','image','mimes:jpeg,png,webp','max:15360'], ]); if (!extension_loaded('gd') || !function_exists('imagewebp')) { throw ValidationException::withMessages(['photos' => 'PHP GD with WebP support is required. Install php8.5-gd.']); } foreach ($request->file('photos') as $file) { $raw = file_get_contents($file->getRealPath()); $source = @imagecreatefromstring($raw); unset($raw); if (!$source) { throw ValidationException::withMessages(['photos' => 'One image could not be decoded.']); } $width = imagesx($source); $height = imagesy($source); if ($width < 1 || $height < 1 || $width * $height > 45000000) { imagedestroy($source); throw ValidationException::withMessages(['photos' => 'Image dimensions are not supported (maximum 45 megapixels).']); } $id = (string) Str::uuid(); $gallery = "lodge/gallery/{$id}.webp"; $thumbnail = "lodge/thumb/{$id}.webp"; try { $this->saveWebp($source, $width, $height, 1600, 82, $gallery); $this->saveWebp($source, $width, $height, 480, 77, $thumbnail); LodgePhoto::create(['gallery_path'=>$gallery,'thumbnail_path'=>$thumbnail,'alt_text'=>'Cresta 31 lodge photograph']); } catch (\Throwable $e) { Storage::disk('public')->delete([$gallery,$thumbnail]); throw $e; } finally { imagedestroy($source); } } return back()->with('success', 'Photographs uploaded and optimised.'); } private function saveWebp($source, int $width, int $height, int $maxWidth, int $quality, string $path): void { $scale = min(1, $maxWidth / $width); $w = max(1, (int) round($width * $scale)); $h = max(1, (int) round($height * $scale)); $target = imagecreatetruecolor($w, $h); imagefill($target, 0, 0, imagecolorallocate($target, 255, 255, 255)); imagecopyresampled($target, $source, 0, 0, 0, 0, $w, $h, $width, $height); ob_start(); try { if (!imagewebp($target, null, $quality)) { throw new \RuntimeException('WebP conversion failed.'); } $bytes = ob_get_clean(); } catch (\Throwable $e) { ob_end_clean(); imagedestroy($target); throw $e; } imagedestroy($target); if (!Storage::disk('public')->put($path, $bytes)) { throw new \RuntimeException('Unable to save the optimised image.'); } } public function destroy(Request $request, LodgePhoto $photo) { $this->authorizeAdmin($request); $paths = [$photo->gallery_path, $photo->thumbnail_path]; $photo->delete(); Storage::disk('public')->delete($paths); return back()->with('success', 'Photograph deleted.'); } }