policy_md/1764999905_abc.md */ function policy_pdf_md_path(string $pdfPath): string { return policy_md_upload_path(basename($pdfPath)); } } if (!function_exists('policy_md_is_valid')) { /** * Validate that markdown content looks like extracted policy text. */ function policy_md_is_valid(string $content): bool { $content = trim($content); if ($content === '' || strlen($content) < 500) { return false; } $errorMarkers = [ 'pdftotext:', 'GLIBCXX_', 'CXXABI_', 'no version information available', ]; foreach ($errorMarkers as $marker) { if (stripos($content, $marker) !== false) { return false; } } return true; } } if (!function_exists('policy_pdf_extract_text')) { /** * Extract plain text from a PDF using pdftotext (poppler). * Uses a clean environment so LAMPP lib paths do not break pdftotext. */ function policy_pdf_extract_text(string $pdfPath): string { $pdftotext = getenv('PDFTOTEXT_BIN') ?: '/usr/bin/pdftotext'; if (!is_executable($pdftotext) && $pdftotext === '/usr/bin/pdftotext') { $which = trim((string) shell_exec('command -v pdftotext 2>/dev/null')); if ($which !== '') { $pdftotext = $which; } } $command = sprintf( 'env -i PATH=/usr/bin:/bin LD_LIBRARY_PATH= %s -layout %s - 2>&1', escapeshellcmd($pdftotext), escapeshellarg($pdfPath) ); $text = shell_exec($command); if (!is_string($text)) { return ''; } $text = trim($text); if (!policy_md_is_valid($text)) { log_message('error', 'PDF text extraction failed or returned invalid content for: ' . $pdfPath); return ''; } return $text; } } if (!function_exists('convert_policy_pdf_to_markdown')) { /** * Convert a policy PDF to markdown, save to policy_md/, then read back from storage. * * Always pass $originalPdfFileName (DB/storage name) when $pdfPath is an S3 temp download * like /tmp/s3__original.pdf so the MD key matches the original PDF basename. * * @return array{status: string, message: string, content?: string, md_path?: string, md_file_name?: string} */ function convert_policy_pdf_to_markdown(string $pdfPath, bool $forceRefresh = false, ?string $originalPdfFileName = null): array { if (!is_file($pdfPath)) { return [ 'status' => 'failed', 'message' => "PDF file not found at {$pdfPath}", ]; } $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $pdfPath); finfo_close($finfo); if ($mimeType !== 'application/pdf') { return [ 'status' => 'failed', 'message' => "Unsupported file type ({$mimeType}). Only PDF is supported.", ]; } // Prefer the original storage/DB PDF name so S3 temp paths do not pollute the MD key. $sourcePdfName = ($originalPdfFileName !== null && $originalPdfFileName !== '') ? basename($originalPdfFileName) : basename($pdfPath); $mdPath = policy_md_upload_path($sourcePdfName); $mdFileName = basename($mdPath); $mdDir = dirname($mdPath); if (!is_dir($mdDir) && !mkdir($mdDir, 0777, true) && !is_dir($mdDir)) { log_message('error', "Unable to create markdown directory at {$mdDir}; will rely on storage put only"); } if (!$forceRefresh && policy_md_exists($mdFileName)) { $cached = policy_read_md($mdFileName); if ($cached !== '' && policy_md_is_valid($cached)) { log_message('info', "Using cached policy markdown: {$mdFileName}"); return [ 'status' => 'success', 'message' => 'Cached markdown loaded from storage', 'content' => $cached, 'md_path' => $mdPath, 'md_file_name' => $mdFileName, ]; } log_message('info', "Invalid cached markdown detected, reconverting: {$mdFileName}"); } $extractedText = policy_pdf_extract_text($pdfPath); if ($extractedText === '') { return [ 'status' => 'failed', 'message' => 'PDF text extraction failed. The file may be scanned, image-based, or pdftotext is unavailable on the server.', ]; } $markdown = "# Motor Policy Document\n\n" . $extractedText; // Local write is best-effort (S3 driver may not have write access under writable/). $localWritten = false; if (is_dir($mdDir) || (is_dir(dirname($mdDir)) && @mkdir($mdDir, 0777, true))) { $localWritten = @file_put_contents($mdPath, $markdown) !== false; if (!$localWritten) { log_message('error', "Local markdown write failed (continuing with storage put): {$mdPath}"); } } policy_put_md($mdFileName, $markdown); $savedContent = $localWritten ? (file_get_contents($mdPath) ?: $markdown) : $markdown; if (!policy_md_is_valid($savedContent)) { if ($localWritten) { @unlink($mdPath); } return [ 'status' => 'failed', 'message' => 'Saved markdown file is invalid after conversion', ]; } log_message('info', "Policy PDF converted to markdown: {$mdFileName} (local_written=" . ($localWritten ? '1' : '0') . ')'); return [ 'status' => 'success', 'message' => 'PDF converted to markdown and saved to storage', 'content' => $savedContent, 'md_path' => $mdPath, 'md_file_name' => $mdFileName, ]; } }