205 lines
6.6 KiB
PHP
205 lines
6.6 KiB
PHP
<?php
|
|
|
|
if (!function_exists('policy_md_upload_path')) {
|
|
/**
|
|
* Full path to the saved markdown file for a policy PDF file name.
|
|
*/
|
|
function policy_md_upload_path(string $pdfFileName): string
|
|
{
|
|
$mdFileName = preg_replace('/\.pdf$/i', '.md', $pdfFileName);
|
|
|
|
if ($mdFileName === $pdfFileName) {
|
|
$mdFileName .= '.md';
|
|
}
|
|
|
|
return WRITEPATH . 'uploads/policy/policy_md/' . $mdFileName;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('policy_pdf_md_path')) {
|
|
/**
|
|
* Resolve markdown path using the same base name as the PDF file.
|
|
* e.g. 1764999905_abc.pdf -> 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_<uniqid>_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,
|
|
];
|
|
}
|
|
}
|