*/ private const ALLOWED_EXTENSIONS = [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff', 'zip', 'rar', '7z', 'tar', 'gz', ]; public function __invoke(Request $request, int $id): BinaryFileResponse { $artwork = Artwork::query()->find($id); if (! $artwork) { abort(404); } $filePath = $this->resolveOriginalPath($artwork); $ext = strtolower(ltrim((string) pathinfo($filePath, PATHINFO_EXTENSION), '.')); if ($filePath === '' || ! in_array($ext, self::ALLOWED_EXTENSIONS, true)) { abort(404); } $this->recordDownload($request, $artwork->id); $this->incrementDownloadCountIfAvailable($artwork->id); if (! File::isFile($filePath)) { Log::warning('Artwork original file missing for download.', [ 'artwork_id' => $artwork->id, 'ext' => $ext, 'resolved_path' => $filePath, ]); abort(404); } $downloadName = $this->buildDownloadFilename((string) $artwork->file_name, $ext); return response()->download($filePath, $downloadName); } private function resolveOriginalPath(Artwork $artwork): string { $relative = trim((string) $artwork->file_path, '/'); $prefix = trim((string) config('uploads.object_storage.prefix', 'artworks'), '/') . '/original/'; if ($relative !== '' && str_starts_with($relative, $prefix)) { $suffix = substr($relative, strlen($prefix)); $root = rtrim((string) config('uploads.local_originals_root'), DIRECTORY_SEPARATOR); return $root . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, (string) $suffix); } $hash = strtolower((string) $artwork->hash); $ext = strtolower(ltrim((string) $artwork->file_ext, '.')); if (! $this->isValidHash($hash) || $ext === '') { return ''; } $root = rtrim((string) config('uploads.local_originals_root'), DIRECTORY_SEPARATOR); return $root . DIRECTORY_SEPARATOR . substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2, 2) . DIRECTORY_SEPARATOR . $hash . '.' . $ext; } private function recordDownload(Request $request, int $artworkId): void { try { $ipAddress = $request->ip(); $ipBinary = $ipAddress ? @inet_pton($ipAddress) : false; ArtworkDownload::query()->create([ 'artwork_id' => $artworkId, 'user_id' => $request->user()?->id, 'ip' => $ipBinary !== false ? $ipBinary : null, 'ip_address' => $ipAddress, 'user_agent' => mb_substr((string) $request->userAgent(), 0, 1024), 'referer' => mb_substr((string) $request->headers->get('referer'), 0, 65535), ]); } catch (\Throwable $exception) { Log::warning('Failed to record artwork download analytics.', [ 'artwork_id' => $artworkId, 'error' => $exception->getMessage(), ]); } } private function incrementDownloadCountIfAvailable(int $artworkId): void { if (! Schema::hasColumn('artworks', 'download_count')) { return; } Artwork::query()->whereKey($artworkId)->increment('download_count'); } private function isValidHash(string $hash): bool { return $hash !== '' && preg_match('/^[a-f0-9]+$/', $hash) === 1; } private function buildDownloadFilename(string $fileName, string $ext): string { $name = trim($fileName); $name = str_replace(['/', '\\'], '-', $name); $name = preg_replace('/[\x00-\x1F\x7F]/', '', $name) ?? ''; $name = preg_replace('/\s+/', ' ', $name) ?? ''; $name = trim((string) $name, ". \t\n\r\0\x0B"); if ($name === '') { $name = 'artwork'; } if (strtolower((string) pathinfo($name, PATHINFO_EXTENSION)) !== $ext) { $name .= '.' . $ext; } return $name; } }