documentId); if (!$doc) return; $disk = $doc->disk ?: 'public'; if (!Storage::disk($disk)->exists($doc->path)) return; $ext = strtolower(pathinfo($doc->original_name ?: $doc->file_name, PATHINFO_EXTENSION)); if (!in_array($ext, ['doc', 'docx'])) return; // only convert office docs here // Prepare temp files - keep original extension so LibreOffice can detect filter $tmpBase = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'doc_in_' . uniqid(); $tmpIn = $tmpBase . '.' . $ext; // e.g., .doc or .docx file_put_contents($tmpIn, Storage::disk($disk)->get($doc->path)); $outDir = sys_get_temp_dir(); // Ensure exec is available if (!function_exists('exec')) { Log::error('Preview generation failed: exec() not available in this PHP environment', ['document_id' => $doc->id]); return; } $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); if (in_array('exec', $disabled, true)) { Log::error('Preview generation failed: exec() is disabled in php.ini (disable_functions)', ['document_id' => $doc->id]); return; } // Run soffice headless to convert to PDF $binCfg = config('files.libreoffice_bin'); $bin = $binCfg ? (string) $binCfg : 'soffice'; // Windows quoting differs from POSIX. Build command parts safely. $isWin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; if ($isWin) { $binPart = '"' . $bin . '"'; $outDirPart = '"' . $outDir . '"'; $inPart = '"' . $tmpIn . '"'; } else { $binPart = escapeshellcmd($bin); $outDirPart = escapeshellarg($outDir); $inPart = escapeshellarg($tmpIn); } // Use a temporary user profile to avoid permissions/profile lock issues $loProfileDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'lo_profile_' . $doc->id; if (!is_dir($loProfileDir)) { @mkdir($loProfileDir, 0700, true); } $loProfileUri = 'file:///' . ltrim(str_replace('\\', '/', $loProfileDir), '/'); $cmd = sprintf( '%s --headless --norestore --nolockcheck -env:UserInstallation=%s --convert-to pdf --outdir %s %s', $binPart, $isWin ? '"' . $loProfileUri . '"' : escapeshellarg($loProfileUri), $outDirPart, $inPart ); // Capture stderr as well for diagnostics $cmdWithStderr = $cmd . ' 2>&1'; Log::info('Starting LibreOffice preview conversion', [ 'document_id' => $doc->id, 'cmd' => $cmd, 'is_windows' => $isWin, ]); $out = []; $ret = 0; exec($cmdWithStderr, $out, $ret); if ($ret !== 0) { Log::warning('Preview generation failed', [ 'document_id' => $doc->id, 'ret' => $ret, 'cmd' => $cmd, 'output' => implode("\n", $out), ]); @unlink($tmpIn); return; } $pdfPathLocal = $tmpIn . '.pdf'; // LibreOffice writes output with source filename base; derive path $base = pathinfo($tmpIn, PATHINFO_FILENAME); $pdfPathLocal = $outDir . DIRECTORY_SEPARATOR . $base . '.pdf'; if (!file_exists($pdfPathLocal)) { // fallback: try with original name base $origBase = pathinfo($doc->original_name ?: $doc->file_name, PATHINFO_FILENAME); $try = $outDir . DIRECTORY_SEPARATOR . $origBase . '.pdf'; if (file_exists($try)) $pdfPathLocal = $try; } if (!file_exists($pdfPathLocal)) { Log::warning('Preview generation did not produce expected PDF output', [ 'document_id' => $doc->id, 'out_dir' => $outDir, 'tmp_base' => $base, 'command' => $cmd, 'output' => implode("\n", $out), ]); @unlink($tmpIn); return; } // Store preview PDF to configured disk inside configured previews base path $previewDisk = config('files.preview_disk', 'public'); $base = trim(config('files.preview_base', 'previews/cases'), '/'); $previewDir = $base . '/' . ($doc->documentable?->uuid ?? 'unknown'); $stored = Storage::disk($previewDisk)->put($previewDir . '/' . ($doc->uuid) . '.pdf', file_get_contents($pdfPathLocal)); if ($stored) { $doc->preview_path = $previewDir . '/' . $doc->uuid . '.pdf'; $doc->preview_mime = 'application/pdf'; $doc->preview_generated_at = now(); $doc->save(); } @unlink($tmpIn); @unlink($pdfPathLocal); // Clean up temporary LO profile directory try { if (is_dir($loProfileDir)) { @rmdir($loProfileDir); } } catch (\Throwable $e) { // ignore } } }