pdf-accessibility/api.php
DJP bf83a409bb Initial commit: Enterprise PDF Accessibility Checker
- Complete WCAG 2.1 accessibility checking system
- AI-powered analysis with Claude 4.5 and Google Vision
- Web interface with drag-and-drop upload
- REST API backend (PHP)
- Python checker with parallel processing
- Quick mode for fast scans (~10 seconds)
- Full mode with AI analysis (~2 minutes)
- .env file support for API keys
- Error logging and debugging tools
- Comprehensive documentation

Performance improvements:
- Parallel image processing (3x faster)
- Smart API timeouts (10s)
- Reduced DPI for faster conversions
- Real-time progress updates

🤖 Generated with Claude Code
2025-10-20 15:50:56 -04:00

375 lines
9.8 KiB
PHP

<?php
/**
* Enterprise PDF Accessibility Checker - API Backend
*
* Handles file uploads, job processing, and result retrieval
*/
// Configuration
define('UPLOAD_DIR', __DIR__ . '/uploads');
define('RESULTS_DIR', __DIR__ . '/results');
define('PYTHON_SCRIPT', __DIR__ . '/enterprise_pdf_checker.py');
define('MAX_FILE_SIZE', 50 * 1024 * 1024); // 50MB
define('ALLOWED_EXTENSIONS', ['pdf']);
// Create directories if they don't exist
if (!is_dir(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true);
if (!is_dir(RESULTS_DIR)) mkdir(RESULTS_DIR, 0755, true);
// CORS headers for API
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json');
// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
// Get action
$action = $_GET['action'] ?? $_POST['action'] ?? '';
switch ($action) {
case 'upload':
handleUpload();
break;
case 'check':
handleCheck();
break;
case 'status':
handleStatus();
break;
case 'result':
handleResult();
break;
case 'list':
handleList();
break;
case 'delete':
handleDelete();
break;
case 'debug':
handleDebug();
break;
default:
error('Invalid action');
}
/**
* Handle file upload
*/
function handleUpload() {
if (!isset($_FILES['pdf'])) {
error('No file uploaded');
}
$file = $_FILES['pdf'];
// Validate file
if ($file['error'] !== UPLOAD_ERR_OK) {
error('Upload error: ' . $file['error']);
}
if ($file['size'] > 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 = '/Users/daveporter/Desktop/CODING-2024/PDF-Accessibility-checker/venv/bin/python3';
$python_bin = file_exists($venv_python) ? $venv_python : 'python3';
$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));
// 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 = '/Users/daveporter/Desktop/CODING-2024/PDF-Accessibility-checker/venv/bin/python3';
exec($venv_python . ' --version 2>&1', $python_version);
$debug_info['python_version'] = implode("\n", $python_version);
success($debug_info);
}
/**
* 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;
}