Update: 2026-07-30 02:27:45

This commit is contained in:
Hamza-Ayed
2026-07-30 02:27:45 +03:00
parent 5f62455113
commit ca4a7c2e70
56 changed files with 3391 additions and 709 deletions
+38 -5
View File
@@ -84,7 +84,31 @@ try {
}
}
$extension = pathinfo($_FILES['invoice']['name'], PATHINFO_EXTENSION);
// 4a. Validate the file BEFORE storing it. The client-supplied
// $_FILES['invoice']['type'] is attacker-controlled, so sniff the real type
// off the temp file and derive the extension from that, never from the name.
$allowedMimeTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
'image/heic' => 'heic',
'image/heif' => 'heif',
'application/pdf' => 'pdf',
];
$maxSize = 10 * 1024 * 1024; // 10MB
if ($_FILES['invoice']['size'] > $maxSize) {
json_error('حجم الملف أكبر من 10 ميغابايت', 422);
exit;
}
$detectedMime = @mime_content_type($_FILES['invoice']['tmp_name']) ?: '';
if (!isset($allowedMimeTypes[$detectedMime])) {
json_error('نوع الملف غير مدعوم. المسموح: صور (JPG, PNG, WEBP, HEIC) و PDF', 422);
exit;
}
$extension = $allowedMimeTypes[$detectedMime];
$fileName = bin2hex(random_bytes(8)) . '_' . time() . '.' . $extension;
$targetFile = $uploadDir . $fileName;
@@ -94,13 +118,17 @@ try {
}
// 5. Run AI Extraction
$mimeType = $_FILES['invoice']['type'];
$mimeType = $detectedMime;
$fileContent = file_get_contents($targetFile);
$base64Data = base64_encode($fileContent);
$extracted = AI::extractInvoiceData($base64Data, $mimeType);
// extractInvoices() returns EVERY invoice in the image. extractInvoiceData()
// silently kept only the first, so a photo holding two receipts lost one.
AI::setTenantContext($tenantId);
$extractedInvoices = AI::extractInvoices($base64Data, $mimeType);
AI::setTenantContext(null);
if (!$extracted) {
if (empty($extractedInvoices)) {
$invoiceId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
$stmt = $db->prepare("
INSERT INTO invoices (
@@ -115,9 +143,14 @@ try {
}
// 6. Save Extracted Data
// Multiple invoices in one image each consume a credit, so make sure the
// tenant can actually cover the whole set before writing any of them.
if ($decoded['role'] !== 'super_admin' && count($extractedInvoices) > 1) {
QuotaMiddleware::checkInvoiceQuota($tenantId, count($extractedInvoices));
}
$db->beginTransaction();
$extractedInvoices = $extracted['invoices'] ?? [$extracted];
$savedIds = [];
foreach ($extractedInvoices as $inv) {