feat: implement initial project, document, and resource management modules with routing, controllers, and UI components
This commit is contained in:
@@ -118,22 +118,134 @@ class DocumentController extends Controller
|
||||
|
||||
public function download(Document $document)
|
||||
{
|
||||
if (!$document->current_file_path || !Storage::disk('local')->exists($document->current_file_path)) {
|
||||
$disk = $document->current_file_path ? $this->resolveDisk($document->current_file_path) : null;
|
||||
|
||||
if (!$disk) {
|
||||
return back()->with('error', 'File not found.');
|
||||
}
|
||||
|
||||
return Storage::disk('local')->download($document->current_file_path, $document->current_file_name);
|
||||
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[] = '<p>' . e($text) . '</p>';
|
||||
}
|
||||
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[] = '<td>' . e($extractText($cell)) . '</td>';
|
||||
}
|
||||
if ($cellsHtml !== []) {
|
||||
$rowsHtml[] = '<tr>' . implode('', $cellsHtml) . '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($rowsHtml !== []) {
|
||||
$content[] = '<table><tbody>' . implode('', $rowsHtml) . '</tbody></table>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html = '<!doctype html><html><head><meta charset="utf-8"><title>' . e($fileName) . '</title>'
|
||||
. '<style>*{box-sizing:border-box}html{background:#f3f4f6}body{font-family:Arial,sans-serif;line-height:1.6;color:#1f2937;width:8.5in;min-height:11in;margin:16px auto;padding:.6in;background:#fff;box-shadow:0 2px 12px rgba(15,23,42,.14)}p{margin:0 0 14px;white-space:pre-wrap}table{width:100%;margin:18px 0;border-collapse:collapse;font-size:12px}th,td{border:1px solid #cbd5e1;padding:6px 8px;text-align:left;vertical-align:top}td{min-width:80px}@media(max-width:900px){body{width:100%;min-height:100vh;margin:0;padding:28px 24px;box-shadow:none}}</style>'
|
||||
. '</head><body>' . implode('', $content) . '</body></html>';
|
||||
|
||||
return response($html, 200, [
|
||||
'Content-Type' => 'text/html; charset=UTF-8',
|
||||
'Content-Disposition' => 'inline; filename="' . addcslashes(pathinfo($fileName, PATHINFO_FILENAME) . '.html', '"\\') . '"',
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolveDisk(string $path)
|
||||
{
|
||||
foreach (['local', 'public'] as $diskName) {
|
||||
$disk = Storage::disk($diskName);
|
||||
|
||||
if ($disk->exists($path)) {
|
||||
return $disk;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function downloadVersion(Document $document, int $versionId)
|
||||
{
|
||||
$version = $document->versions()->findOrFail($versionId);
|
||||
|
||||
if (!Storage::disk('local')->exists($version->file_path)) {
|
||||
$disk = $this->resolveDisk($version->file_path);
|
||||
|
||||
if (!$disk) {
|
||||
return back()->with('error', 'Version file not found.');
|
||||
}
|
||||
|
||||
return Storage::disk('local')->download($version->file_path, $version->file_name);
|
||||
return $disk->download($version->file_path, $version->file_name);
|
||||
}
|
||||
|
||||
public function versions(Document $document)
|
||||
@@ -149,10 +261,12 @@ class DocumentController extends Controller
|
||||
{
|
||||
// Delete all version files
|
||||
foreach ($document->versions as $version) {
|
||||
Storage::disk('local')->delete($version->file_path);
|
||||
$disk = $this->resolveDisk($version->file_path);
|
||||
$disk?->delete($version->file_path);
|
||||
}
|
||||
if ($document->current_file_path) {
|
||||
Storage::disk('local')->delete($document->current_file_path);
|
||||
$disk = $this->resolveDisk($document->current_file_path);
|
||||
$disk?->delete($document->current_file_path);
|
||||
}
|
||||
|
||||
$document->delete();
|
||||
|
||||
Reference in New Issue
Block a user