$row) { $nationalId = trim((string)($row['national_id'] ?? '')); $fullName = trim((string)($row['full_name'] ?? '')); $gradeLevel = trim((string)($row['grade_level'] ?? 'grade_10')); $stream = self::normalizeStream((string)($row['stream'] ?? 'general')); // 1. Validate 10-digit Jordanian National ID if (!preg_match('/^[0-9]{10}$/', $nationalId)) { $failedCount++; $errors[] = "السطر " . ($index + 1) . ": الرقم الوطني ($nationalId) غير صالح (يجب أن يتكون من 10 أرقام)."; continue; } if (empty($fullName)) { $failedCount++; $errors[] = "السطر " . ($index + 1) . ": اسم الطالب مطلوب."; continue; } // 2. Encrypt National ID and generate HMAC Blind Index $encryptedNationalId = Security::encrypt($nationalId); $blindIndex = Security::blindIndex($nationalId); $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); // Insert or update in DB try { // If students table is active Database::query( "INSERT INTO students (uuid, national_id, national_id_hash, full_name, grade_level, stream, school_id, is_school_sponsored, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW()) ON DUPLICATE KEY UPDATE national_id = VALUES(national_id), full_name = VALUES(full_name), grade_level = VALUES(grade_level), stream = VALUES(stream), school_id = VALUES(school_id)", [$uuid, $encryptedNationalId, $blindIndex, $fullName, $gradeLevel, $stream, $schoolId] ); $importedCount++; } catch (\Throwable $e) { $failedCount++; $errors[] = 'السطر ' . ($index + 1) . ': تعذر حفظ السجل في قاعدة البيانات.'; error_log('[SchoolRosterService] import failure: ' . $e->getMessage()); } } return [ 'status' => 'success', 'school_id' => $schoolId, 'total_received' => count($records), 'imported_count' => $importedCount, 'failed_count' => $failedCount, 'errors' => $errors, 'encryption_info'=> 'تم تشفير جميع الأرقام الوطنية بنجاح عبر خوارزمية AES-256-GCM السيادية ومؤشر HMAC الأعمى.', ]; } private static function normalizeStream(string $stream): string { return match (trim(mb_strtolower($stream))) { 'علمي', 'scientific' => 'scientific', 'أدبي', 'literary' => 'literary', 'مهني', 'vocational' => 'vocational', default => 'general', }; } }