MAX_FILE_SIZE) { error('File too large. Max size: ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB'); } $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($ext, ALLOWED_EXTENSIONS)) { error('Invalid file type. Only PDF files allowed.'); } // Generate unique ID $job_id = uniqid('pdf_', true); $filename = $job_id . '.pdf'; $filepath = UPLOAD_DIR . '/' . $filename; // Move file if (!move_uploaded_file($file['tmp_name'], $filepath)) { error('Failed to save file'); } // Create job metadata $job_data = [ 'job_id' => $job_id, 'original_filename' => $file['name'], 'uploaded_at' => date('Y-m-d H:i:s'), 'file_size' => $file['size'], 'status' => 'uploaded', 'filepath' => $filepath ]; file_put_contents( RESULTS_DIR . '/' . $job_id . '.meta.json', json_encode($job_data, JSON_PRETTY_PRINT) ); success([ 'job_id' => $job_id, 'filename' => $file['name'], 'message' => 'File uploaded successfully' ]); } /** * Handle PDF accessibility check */ function handleCheck() { $job_id = $_POST['job_id'] ?? ''; if (empty($job_id)) { error('Job ID required'); } $meta_file = RESULTS_DIR . '/' . $job_id . '.meta.json'; if (!file_exists($meta_file)) { error('Job not found'); } $job_data = json_decode(file_get_contents($meta_file), true); // Build command - use venv Python with absolute path $pdf_path = $job_data['filepath']; $output_path = RESULTS_DIR . '/' . $job_id . '.result.json'; // Use absolute venv path for MAMP $venv_python = __DIR__ . '/venv/bin/python3'; $python_bin = file_exists($venv_python) ? $venv_python : 'python3'; // Note: Python script will auto-generate page images when --output is specified $cmd = escapeshellcmd($python_bin . ' ' . PYTHON_SCRIPT) . ' ' . escapeshellarg($pdf_path) . ' ' . '--output ' . escapeshellarg($output_path); // Handle quick mode $quick_mode = $_POST['quick_mode'] ?? false; if ($quick_mode) { $cmd .= ' --quick'; } // Handle API keys - accept both formats $anthropic_key = $_POST['anthropic_key'] ?? getenv('ANTHROPIC_API_KEY'); $google_key = $_POST['google_key'] ?? $_POST['google_credentials'] ?? getenv('GOOGLE_API_KEY') ?? getenv('GOOGLE_APPLICATION_CREDENTIALS'); if ($anthropic_key) { $cmd .= ' --anthropic-key ' . escapeshellarg($anthropic_key); } if ($google_key) { // Check if it's a file path or an API key if (file_exists($google_key)) { // It's a JSON credentials file $cmd .= ' --google-credentials ' . escapeshellarg($google_key); } else { // It's an API key string $cmd .= ' --google-key ' . escapeshellarg($google_key); } } // Update status $job_data['status'] = 'processing'; $job_data['started_at'] = date('Y-m-d H:i:s'); $job_data['command'] = $cmd; // Store for debugging file_put_contents($meta_file, json_encode($job_data, JSON_PRETTY_PRINT)); // Set PATH to include Homebrew (for poppler) $env_path = getenv('PATH'); $poppler_paths = '/opt/homebrew/bin:/usr/local/bin'; putenv("PATH={$poppler_paths}:{$env_path}"); // Log errors to a file for debugging $error_log = RESULTS_DIR . '/' . $job_id . '.error.log'; $cmd .= ' > ' . escapeshellarg($error_log) . ' 2>&1 &'; exec($cmd, $output, $return_code); success([ 'job_id' => $job_id, 'status' => 'processing', 'message' => 'Check started', 'debug' => [ 'command' => $cmd, 'return_code' => $return_code ] ]); } /** * Check job status */ function handleStatus() { $job_id = $_GET['job_id'] ?? ''; if (empty($job_id)) { error('Job ID required'); } $meta_file = RESULTS_DIR . '/' . $job_id . '.meta.json'; $result_file = RESULTS_DIR . '/' . $job_id . '.result.json'; $error_log = RESULTS_DIR . '/' . $job_id . '.error.log'; if (!file_exists($meta_file)) { error('Job not found'); } $job_data = json_decode(file_get_contents($meta_file), true); // Check if result exists if (file_exists($result_file)) { $job_data['status'] = 'completed'; $job_data['completed_at'] = date('Y-m-d H:i:s', filemtime($result_file)); // Update meta file_put_contents($meta_file, json_encode($job_data, JSON_PRETTY_PRINT)); } else if (file_exists($error_log)) { // Check if there are errors $error_content = file_get_contents($error_log); if (!empty($error_content) && $job_data['status'] == 'processing') { // Check if it's been more than 5 minutes $started = strtotime($job_data['started_at']); if (time() - $started > 300) { $job_data['status'] = 'failed'; $job_data['error'] = 'Process timeout or error'; $job_data['error_log'] = substr($error_content, -1000); // Last 1000 chars } } } success($job_data); } /** * Get check results */ function handleResult() { $job_id = $_GET['job_id'] ?? ''; if (empty($job_id)) { error('Job ID required'); } $result_file = RESULTS_DIR . '/' . $job_id . '.result.json'; if (!file_exists($result_file)) { error('Results not found. Check may still be processing.'); } $result = json_decode(file_get_contents($result_file), true); success($result); } /** * List all jobs */ function handleList() { $jobs = []; $files = glob(RESULTS_DIR . '/*.meta.json'); foreach ($files as $file) { $job_data = json_decode(file_get_contents($file), true); // Check if completed $result_file = str_replace('.meta.json', '.result.json', $file); if (file_exists($result_file)) { $job_data['status'] = 'completed'; } $jobs[] = $job_data; } // Sort by upload time (newest first) usort($jobs, function($a, $b) { return strtotime($b['uploaded_at']) - strtotime($a['uploaded_at']); }); success(['jobs' => $jobs]); } /** * Delete a job */ function handleDelete() { $job_id = $_POST['job_id'] ?? $_GET['job_id'] ?? ''; if (empty($job_id)) { error('Job ID required'); } $meta_file = RESULTS_DIR . '/' . $job_id . '.meta.json'; if (!file_exists($meta_file)) { error('Job not found'); } $job_data = json_decode(file_get_contents($meta_file), true); // Delete files @unlink($job_data['filepath']); @unlink($meta_file); @unlink(RESULTS_DIR . '/' . $job_id . '.result.json'); success(['message' => 'Job deleted']); } /** * Debug endpoint */ function handleDebug() { $job_id = $_GET['job_id'] ?? ''; if (empty($job_id)) { error('Job ID required'); } $meta_file = RESULTS_DIR . '/' . $job_id . '.meta.json'; $result_file = RESULTS_DIR . '/' . $job_id . '.result.json'; $error_log = RESULTS_DIR . '/' . $job_id . '.error.log'; $debug_info = [ 'job_id' => $job_id, 'meta_exists' => file_exists($meta_file), 'result_exists' => file_exists($result_file), 'error_log_exists' => file_exists($error_log), 'files' => [] ]; if (file_exists($meta_file)) { $debug_info['meta'] = json_decode(file_get_contents($meta_file), true); } if (file_exists($error_log)) { $debug_info['error_log'] = file_get_contents($error_log); } if (file_exists($result_file)) { $debug_info['result_size'] = filesize($result_file); } // Test Python $venv_python = __DIR__ . '/venv/bin/python3'; exec($venv_python . ' --version 2>&1', $python_version); $debug_info['python_version'] = implode("\n", $python_version); success($debug_info); } /** * Serve page images */ function handleImage() { $job_id = $_GET['job_id'] ?? ''; $page_num = $_GET['page'] ?? ''; if (empty($job_id) || empty($page_num)) { error('Job ID and page number required'); } // Find the image file $images_dir = RESULTS_DIR . '/' . $job_id . '.result_images'; $image_file = $images_dir . '/page_' . intval($page_num) . '.png'; if (!file_exists($image_file)) { http_response_code(404); header('Content-Type: application/json'); echo json_encode(['success' => false, 'error' => 'Image not found']); exit; } // Serve the image header('Content-Type: image/png'); header('Cache-Control: public, max-age=86400'); // Cache for 1 day readfile($image_file); exit; } /** * Auto-remediate PDF accessibility issues */ function handleRemediate() { $job_id = $_POST['job_id'] ?? ''; if (empty($job_id)) { error('Job ID required'); } $meta_file = RESULTS_DIR . '/' . $job_id . '.meta.json'; $result_file = RESULTS_DIR . '/' . $job_id . '.result.json'; if (!file_exists($meta_file) || !file_exists($result_file)) { error('Job not found'); } $job_data = json_decode(file_get_contents($meta_file), true); $result_data = json_decode(file_get_contents($result_file), true); // Check if there are fixable issues if (!isset($result_data['auto_fixable_count']) || $result_data['auto_fixable_count'] == 0) { error('No auto-fixable issues found'); } $original_pdf = $job_data['filepath']; $remediated_pdf = UPLOAD_DIR . '/' . $job_id . '_remediated.pdf'; // Use absolute venv path $venv_python = __DIR__ . '/venv/bin/python3'; $python_bin = file_exists($venv_python) ? $venv_python : 'python3'; $remediation_script = __DIR__ . '/pdf_remediation.py'; // Build command - apply all safe fixes $cmd = escapeshellcmd($python_bin . ' ' . $remediation_script) . ' ' . escapeshellarg($original_pdf) . ' ' . '--output ' . escapeshellarg($remediated_pdf) . ' ' . '--all'; // Set PATH for poppler $env_path = getenv('PATH'); $poppler_paths = '/opt/homebrew/bin:/usr/local/bin'; putenv("PATH={$poppler_paths}:{$env_path}"); // Run remediation $error_log = RESULTS_DIR . '/' . $job_id . '.remediation.log'; $cmd .= ' > ' . escapeshellarg($error_log) . ' 2>&1'; exec($cmd, $output, $return_code); // Check if remediation succeeded if ($return_code !== 0 || !file_exists($remediated_pdf)) { $log_content = file_exists($error_log) ? file_get_contents($error_log) : 'Unknown error'; error('Remediation failed: ' . substr($log_content, -500)); } // Store remediated file info $job_data['remediated_pdf'] = $remediated_pdf; $job_data['remediated_at'] = date('Y-m-d H:i:s'); file_put_contents($meta_file, json_encode($job_data, JSON_PRETTY_PRINT)); success([ 'job_id' => $job_id, 'remediated_pdf' => basename($remediated_pdf), 'original_filename' => $job_data['original_filename'], 'fixes_applied' => $result_data['auto_fixable_count'], 'download_url' => 'api.php?action=download&job_id=' . $job_id . '&type=remediated', 'message' => 'PDF remediated successfully' ]); } /** * Download original or remediated PDF */ function handleDownload() { $job_id = $_GET['job_id'] ?? ''; $type = $_GET['type'] ?? 'original'; // 'original' or 'remediated' if (empty($job_id)) { error('Job ID required'); } $meta_file = RESULTS_DIR . '/' . $job_id . '.meta.json'; if (!file_exists($meta_file)) { error('Job not found'); } $job_data = json_decode(file_get_contents($meta_file), true); if ($type === 'remediated') { if (!isset($job_data['remediated_pdf']) || !file_exists($job_data['remediated_pdf'])) { error('Remediated PDF not found'); } $file_path = $job_data['remediated_pdf']; $filename = pathinfo($job_data['original_filename'], PATHINFO_FILENAME) . '_fixed.pdf'; } else { $file_path = $job_data['filepath']; $filename = $job_data['original_filename']; } // Serve the file header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . filesize($file_path)); readfile($file_path); exit; } /** * Send success response */ function success($data) { echo json_encode([ 'success' => true, 'data' => $data ]); exit; } /** * Send error response */ function error($message) { http_response_code(400); echo json_encode([ 'success' => false, 'error' => $message ]); exit; }