search) { $query->where('title', 'like', "%{$search}%"); } if ($categoryId = $request->category_id) { $query->where('category_id', $categoryId); } if ($projectId = $request->project_id) { $query->where('project_id', $projectId); } if ($status = $request->status) { $query->where('status', $status); } if ($type = $request->documentable_type) { $query->where('documentable_type', $type); } $documents = $query->latest()->paginate(20)->withQueryString(); $categories = DocumentCategory::all(); $projects = Project::select('id', 'name')->get(); return Inertia::render('DocumentManagement::Documents/Index', [ 'documents' => $documents, 'categories' => $categories, 'projects' => $projects, 'filters' => $request->only(['search', 'category_id', 'project_id', 'status', 'documentable_type']), ]); } public function upload(Request $request) { $validated = $request->validate([ 'title' => 'required|string|max:255', 'category_id' => 'required|exists:document_categories,id', 'project_id' => 'nullable|exists:projects,id', 'description' => 'nullable|string', 'documentable_type' => 'nullable|string', 'documentable_id' => 'nullable|integer', 'file' => 'required|file|max:51200|mimes:pdf', ]); $file = $request->file('file'); $category = DocumentCategory::find($validated['category_id']); $path = $file->store("documents/" . str()->slug($category->name), 'local'); $document = Document::create([ 'title' => $validated['title'], 'category_id' => $validated['category_id'], 'project_id' => $validated['project_id'] ?? null, 'description' => $validated['description'] ?? null, 'documentable_type' => $validated['documentable_type'] ?? null, 'documentable_id' => $validated['documentable_id'] ?? null, 'status' => 'Approved', 'uploaded_by' => $request->user()->id, 'current_file_path' => $path, 'current_file_name' => $file->getClientOriginalName(), 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'version_count' => 1, ]); // Create initial version $document->versions()->create([ 'version_number' => 1, 'file_path' => $path, 'file_name' => $file->getClientOriginalName(), 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'uploaded_by' => $request->user()->id, 'change_notes' => 'Initial upload', ]); return back()->with('success', 'Document uploaded successfully.'); } public function uploadVersion(Request $request, Document $document) { $validated = $request->validate([ 'file' => 'required|file|max:51200|mimes:pdf', 'change_notes' => 'nullable|string|max:500', ]); $file = $request->file('file'); $category = $document->category; $path = $file->store("documents/" . str()->slug($category->name), 'local'); $document->addVersion( $path, $file->getClientOriginalName(), $file->getMimeType(), $file->getSize(), $request->user()->id, $validated['change_notes'] ?? null, ); // Keep status as Approved when a new version is uploaded $document->update(['status' => 'Approved']); return back()->with('success', 'New document version uploaded successfully.'); } public function download(Document $document) { $disk = $document->current_file_path ? $this->resolveDisk($document->current_file_path) : null; if (!$disk) { return back()->with('error', 'File not found.'); } return $disk->download($document->current_file_path, $document->current_file_name); } public function preview(Document $document) { $disk = $document->current_file_path ? $this->resolveDisk($document->current_file_path) : null; if (!$disk) { abort(404, 'File not found.'); } $mimeType = $document->mime_type ?: $disk->mimeType($document->current_file_path); $extension = strtolower(pathinfo($document->current_file_name ?: $document->current_file_path, PATHINFO_EXTENSION)); $isDocx = $extension === 'docx' || $mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; if ($isDocx) { return $this->renderDocxPreview($disk->path($document->current_file_path), $document->current_file_name ?: 'document.docx'); } if (!in_array($mimeType, ['application/pdf'], true) && !str_starts_with((string) $mimeType, 'image/')) { abort(404, 'This file type does not support inline preview.'); } return response()->file($disk->path($document->current_file_path), [ 'Content-Type' => $mimeType, 'Content-Disposition' => 'inline; filename="' . addcslashes($document->current_file_name ?: 'document', '"\\') . '"', ]); } private function renderDocxPreview(string $path, string $fileName) { if (!class_exists(\ZipArchive::class)) { abort(501, 'DOCX preview requires the PHP ZIP extension.'); } $archive = new \ZipArchive(); if ($archive->open($path) !== true) { abort(422, 'The DOCX file could not be opened.'); } $documentXml = $archive->getFromName('word/document.xml'); $archive->close(); if (!$documentXml) { abort(422, 'The DOCX document content could not be read.'); } $xml = new \DOMDocument(); $xml->loadXML($documentXml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING); $xpath = new \DOMXPath($xml); $content = []; $extractText = static function (\DOMNode $node) use ($xpath): string { $text = ''; foreach ($xpath->query('.//*[local-name()="t"]', $node) as $textNode) { $text .= $textNode->textContent; } return trim($text); }; $body = $xpath->query('//*[local-name()="body"]')->item(0); foreach ($body?->childNodes ?? [] as $block) { if ($block->localName === 'p') { $text = $extractText($block); if ($text !== '') { $content[] = '
' . e($text) . '
'; } continue; } if ($block->localName === 'tbl') { $rowsHtml = []; foreach ($xpath->query('./*[local-name()="tr"]', $block) as $row) { $cellsHtml = []; foreach ($xpath->query('./*[local-name()="tc"]', $row) as $cell) { $cellsHtml[] = '