disk ?: 'public'; $relPath = $this->normalizePath($document->path ?? ''); // Handle DOC/DOCX previews for inline viewing if ($inline) { $previewResponse = $this->tryPreview($document); if ($previewResponse) { return $previewResponse; } } // Try to find the file using multiple path candidates $found = $this->findFile($disk, $relPath); if (! $found) { // Try public/ fallback $found = $this->tryPublicFallback($relPath); if (! $found) { abort(404, 'Document file not found'); } } $headers = $this->buildHeaders($document, $inline); // Try streaming first $stream = Storage::disk($disk)->readStream($found); if ($stream !== false) { return response()->stream(function () use ($stream) { fpassthru($stream); }, 200, $headers); } // Fallbacks on readStream failure return $this->fallbackStream($disk, $found, $document, $relPath, $headers); } /** * Normalize path for Windows and legacy prefixes. */ protected function normalizePath(string $path): string { $path = str_replace('\\', '/', $path); $path = ltrim($path, '/'); if (str_starts_with($path, 'public/')) { $path = substr($path, 7); } return $path; } /** * Build path candidates to try. */ protected function buildPathCandidates(string $relPath, ?string $documentPath): array { $candidates = [$relPath]; $raw = $documentPath ? ltrim(str_replace('\\', '/', $documentPath), '/') : null; if ($raw && $raw !== $relPath) { $candidates[] = $raw; } if (str_starts_with($relPath, 'storage/')) { $candidates[] = substr($relPath, 8); } if ($raw && str_starts_with($raw, 'storage/')) { $candidates[] = substr($raw, 8); } return array_unique($candidates); } /** * Try to find file using path candidates. */ protected function findFile(string $disk, string $relPath, ?string $documentPath = null): ?string { $candidates = $this->buildPathCandidates($relPath, $documentPath); foreach ($candidates as $cand) { if (Storage::disk($disk)->exists($cand)) { return $cand; } } return null; } /** * Try public/ fallback path. */ protected function tryPublicFallback(string $relPath): ?string { $publicFull = public_path($relPath); $real = @realpath($publicFull); $publicRoot = @realpath(public_path()); $realN = $real ? str_replace('\\\\', '/', $real) : null; $rootN = $publicRoot ? str_replace('\\\\', '/', $publicRoot) : null; if ($realN && $rootN && str_starts_with($realN, $rootN) && is_file($real)) { return $real; } return null; } /** * Try to stream preview for DOC/DOCX files. */ protected function tryPreview(Document $document): StreamedResponse|Response|null { $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); if (! in_array($ext, ['doc', 'docx'])) { return null; } $previewDisk = config('files.preview_disk', 'public'); if ($document->preview_path && Storage::disk($previewDisk)->exists($document->preview_path)) { $stream = Storage::disk($previewDisk)->readStream($document->preview_path); if ($stream !== false) { $previewNameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); return response()->stream(function () use ($stream) { fpassthru($stream); }, 200, [ 'Content-Type' => $document->preview_mime ?: 'application/pdf', 'Content-Disposition' => 'inline; filename="'.addslashes($previewNameBase.'.pdf').'"', 'Cache-Control' => 'private, max-age=0, no-cache', 'Pragma' => 'no-cache', ]); } } // Queue preview generation if not available \App\Jobs\GenerateDocumentPreview::dispatch($document->id); return response('Preview is being generated. Please try again shortly.', 202); } /** * Build response headers. */ protected function buildHeaders(Document $document, bool $inline): array { $nameBase = $document->name ?: pathinfo($document->original_name ?: $document->file_name, PATHINFO_FILENAME); $ext = strtolower(pathinfo($document->original_name ?: $document->file_name, PATHINFO_EXTENSION)); $name = $ext ? ($nameBase.'.'.$ext) : $nameBase; return [ 'Content-Type' => $document->mime_type ?: 'application/octet-stream', 'Content-Disposition' => ($inline ? 'inline' : 'attachment').'; filename="'.addslashes($name).'"', 'Cache-Control' => 'private, max-age=0, no-cache', 'Pragma' => 'no-cache', ]; } /** * Fallback streaming methods when readStream fails. */ protected function fallbackStream(string $disk, string $found, Document $document, string $relPath, array $headers): StreamedResponse|Response { // Fallback 1: get() the bytes directly try { $bytes = Storage::disk($disk)->get($found); if (! is_null($bytes) && $bytes !== false) { return response($bytes, 200, $headers); } } catch (\Throwable $e) { // Continue to next fallback } // Fallback 2: open via absolute storage path $abs = null; try { if (method_exists(Storage::disk($disk), 'path')) { $abs = Storage::disk($disk)->path($found); } } catch (\Throwable $e) { $abs = null; } if ($abs && is_file($abs)) { $fp = @fopen($abs, 'rb'); if ($fp !== false) { return response()->stream(function () use ($fp) { fpassthru($fp); }, 200, $headers); } } // Fallback 3: serve from public path if available $publicFull = public_path($found); $real = @realpath($publicFull); if ($real && is_file($real)) { $fp = @fopen($real, 'rb'); if ($fp !== false) { return response()->stream(function () use ($fp) { fpassthru($fp); }, 200, $headers); } } abort(404, 'Document file could not be streamed'); } }