bool, reason => string, student_grade => string, is_sponsored => bool] */ public static function validateLessonAccess( ?int $studentId, ?string $nationalId, string $targetGrade, ?int $courseId = null, ?int $lessonId = null ): array { // 1. استرجاع بيانات الطالب وسجله الدراسي $student = null; if ($studentId) { $student = Database::selectOne( "SELECT s.*, sch.name as school_name, sch.type as school_type, sch.directorate_id, d.type as directorate_type FROM students s LEFT JOIN schools sch ON s.school_id = sch.id LEFT JOIN directorates d ON sch.directorate_id = d.id WHERE s.id = ? LIMIT 1", [$studentId] ); } elseif (!empty($nationalId)) { $student = Database::selectOne( "SELECT s.*, sch.name as school_name, sch.type as school_type, sch.directorate_id, d.type as directorate_type FROM students s LEFT JOIN schools sch ON s.school_id = sch.id LEFT JOIN directorates d ON sch.directorate_id = d.id WHERE s.national_id = ? LIMIT 1", [$nationalId] ); } // إذا لم يكن مسجلاً، نتحقق مما إذا كان الدرس معاينة مجانية (Preview) if (!$student) { if ($lessonId) { $lesson = Database::selectOne("SELECT is_free_preview FROM lessons WHERE id = ? LIMIT 1", [$lessonId]); if ($lesson && !empty($lesson['is_free_preview'])) { return [ 'allowed' => true, 'reason' => 'free_preview', 'student_grade' => 'guest', 'is_sponsored' => false ]; } } return [ 'allowed' => false, 'reason' => 'unauthenticated_or_not_found', 'message' => 'يرجى تسجيل الدخول أو إدخال الرقم الوطني للتحقق من الصلاحية الدراسية.', 'student_grade' => 'unknown', 'is_sponsored' => false ]; } $activeStudentGrade = self::normalizeGrade($student['grade_level'] ?? 'grade_10'); $normalizedTargetGrade = self::normalizeGrade($targetGrade); // 2. التحقق الجنائي الصارم من قفل الصف (Grade-Gate Restriction) // الطالب لا يستطيع مشاهدة حصص صفوف سابقة ولا صفوف لاحقة if ($activeStudentGrade !== $normalizedTargetGrade) { $targetGradeName = self::getGradeDisplayName($normalizedTargetGrade); $currentGradeName = self::getGradeDisplayName($activeStudentGrade); return [ 'allowed' => false, 'reason' => 'grade_mismatch', 'message' => "غير مصرح: أنت مسجل حالياً في ({$currentGradeName})، ولا يمكنك فتح حصص ({$targetGradeName}) حفاظاً على التركيز والمسار الأكاديمي المعتمد.", 'student_grade' => $activeStudentGrade, 'target_grade' => $normalizedTargetGrade, 'is_sponsored' => (bool)($student['is_school_sponsored'] ?? false) ]; } // 3. التحقق من النموذج المالي المزدوج: // أ. إذا كان الطالب تابعاً لمدارس الثقافة العسكرية أو مدرسة خاصة شريكة $isMilitaryCulture = ($student['directorate_type'] ?? '') === 'military_culture' || ($student['school_type'] ?? '') === 'military_culture'; $isPrivateSponsored = !empty($student['is_school_sponsored']) && !empty($student['school_id']); if ($isMilitaryCulture || $isPrivateSponsored) { // الطالب مشمول مجاناً 100% (صفر دينار) if ($courseId && !empty($student['id'])) { self::ensureSchoolIncludedPass((int)$student['id'], $courseId); } return [ 'allowed' => true, 'reason' => 'school_sponsored_free', 'message' => 'مشمول مجاناً عبر المنظومة المؤسسية الشريكة (مديرية الثقافة العسكرية / المدرسة المعتمدة).', 'student_grade' => $activeStudentGrade, 'is_sponsored' => true, 'fee_jod' => 0.00 ]; } // ب. إذا كان طالباً مستقلاً خارج المدارس الشريكة (External Student) // يلزم وجود تصريح ساري مدفوع (عبر كليك أو غيره) if ($courseId) { $activePass = Database::selectOne( "SELECT id, pass_type, expires_at, is_active FROM course_access_passes WHERE student_id = ? AND course_id = ? AND is_active = 1 AND (expires_at IS NULL OR expires_at > NOW()) LIMIT 1", [(int)$student['id'], $courseId] ); if ($activePass) { return [ 'allowed' => true, 'reason' => 'paid_pass_active', 'student_grade' => $activeStudentGrade, 'is_sponsored' => false, 'pass_id' => $activePass['id'] ]; } // فحص إذا كان الدرس معاينة مجانية if ($lessonId) { $lesson = Database::selectOne("SELECT is_free_preview FROM lessons WHERE id = ? LIMIT 1", [$lessonId]); if ($lesson && !empty($lesson['is_free_preview'])) { return [ 'allowed' => true, 'reason' => 'free_preview', 'student_grade' => $activeStudentGrade, 'is_sponsored' => false ]; } } return [ 'allowed' => false, 'reason' => 'payment_required', 'message' => 'يتطلب هذا المساق تفعيل الاشتراك عبر نظام الدفع الفوري (CliQ) للطلبة المستقلين خارج المدارس الشريكة.', 'student_grade' => $activeStudentGrade, 'is_sponsored' => false, 'course_id' => $courseId ]; } // افتراضياً لدروس المنهاج المعتمد لنفس الصف return [ 'allowed' => true, 'reason' => 'curriculum_default', 'student_grade' => $activeStudentGrade, 'is_sponsored' => false ]; } /** * ترفيع الطالب سنوياً إلى الصف اللاحق (مثال: من خامس إلى سادس) * وإلغاء صلاحية الوصول لحصص الصفوف السابقة */ public static function promoteStudent(int $studentId, string $newGradeLevel): array { $student = Database::selectOne("SELECT id, full_name, grade_level, national_id FROM students WHERE id = ? LIMIT 1", [$studentId]); if (!$student) { return ['status' => 'error', 'message' => 'الطالب غير موجود']; } $oldGrade = self::normalizeGrade($student['grade_level']); $promotedGrade = self::normalizeGrade($newGradeLevel); Database::query("UPDATE students SET grade_level = ?, updated_at = NOW() WHERE id = ?", [$promotedGrade, $studentId]); Database::query("UPDATE school_rosters SET grade_level = ? WHERE national_id = ?", [$promotedGrade, $student['national_id']]); // إلغاء تفعيل التصاريح السابقة الخاصة بالصف المنتهي Database::query( "UPDATE course_access_passes cap JOIN courses c ON cap.course_id = c.id SET cap.is_active = 0 WHERE cap.student_id = ? AND c.grade_level = ?", [$studentId, $oldGrade] ); $oldName = self::getGradeDisplayName($oldGrade); $newName = self::getGradeDisplayName($promotedGrade); return [ 'status' => 'success', 'student_id' => $studentId, 'student_name' => $student['full_name'], 'old_grade' => $oldGrade, 'promoted_grade' => $promotedGrade, 'message' => "تم ترفيع الطالب ({$student['full_name']}) بنجاح من ({$oldName}) إلى ({$newName}) وتحديث الصلاحيات." ]; } /** * ترفيع كشف المدرسة بالكامل دفعة واحدة (Batch Grade Promotion) */ public static function batchPromoteRoster(int $schoolId, string $fromGrade, string $toGrade): array { $from = self::normalizeGrade($fromGrade); $to = self::normalizeGrade($toGrade); $count = (int)Database::selectOne( "SELECT COUNT(*) as cnt FROM students WHERE school_id = ? AND grade_level = ?", [$schoolId, $from] )['cnt']; Database::query( "UPDATE students SET grade_level = ?, updated_at = NOW() WHERE school_id = ? AND grade_level = ?", [$to, $schoolId, $from] ); Database::query( "UPDATE school_rosters SET grade_level = ? WHERE school_id = ? AND grade_level = ?", [$to, $schoolId, $from] ); return [ 'status' => 'success', 'school_id' => $schoolId, 'from_grade' => $from, 'to_grade' => $to, 'promoted_count' => $count, 'message' => "تم ترفيع {$count} طالباً في المدرسة بنجاح وتحديث صلاحياتهم الأكاديمية." ]; } /** * التحقق من وجود تصريح مدرسي مجاني أو إنشاؤه تلقائياً لطلبة المدارس الشريكة */ private static function ensureSchoolIncludedPass(int $studentId, int $courseId): void { $existing = Database::selectOne( "SELECT id FROM course_access_passes WHERE student_id = ? AND course_id = ? LIMIT 1", [$studentId, $courseId] ); if (!$existing) { $course = Database::selectOne("SELECT teacher_id FROM courses WHERE id = ? LIMIT 1", [$courseId]); $teacherId = (int)($course['teacher_id'] ?? 1); Database::insert( "INSERT INTO course_access_passes (student_id, course_id, teacher_id, pass_type, price_paid_jod, expires_at, is_active) VALUES (?, ?, ?, 'school_included', 0.00, DATE_ADD(NOW(), INTERVAL 1 YEAR), 1)", [$studentId, $courseId, $teacherId] ); } } /** * توحيد أسماء ومفاتيح الصفوف */ public static function normalizeGrade(string $grade): string { $g = strtolower(trim($grade)); if ($g === '10' || $g === 'grade_10' || $g === 'grade10' || $g === 'عاشر' || $g === 'الصف العاشر') return 'grade_10'; if ($g === '9' || $g === 'grade_9' || $g === 'grade9' || $g === 'تاسع' || $g === 'الصف التاسع') return 'grade_9'; if ($g === '8' || $g === 'grade_8' || $g === 'grade8' || $g === 'ثامن' || $g === 'الصف الثامن') return 'grade_8'; if ($g === '7' || $g === 'grade_7' || $g === 'grade7' || $g === 'سابع' || $g === 'الصف السابع') return 'grade_7'; if ($g === '6' || $g === 'grade_6' || $g === 'grade6' || $g === 'سادس' || $g === 'الصف السادس') return 'grade_6'; if ($g === '5' || $g === 'grade_5' || $g === 'grade5' || $g === 'خامس' || $g === 'الصف الخامس') return 'grade_5'; if ($g === '11' || $g === 'grade_11' || $g === 'حادي عشر' || $g === 'أول ثانوي') return 'grade_11'; if ($g === '12' || $g === 'grade_12' || $g === 'tawjihi' || $g === 'tawjihi_2008' || $g === 'توجيهي') return 'grade_12'; return $g; } /** * اسم العرض للصف بالعربية */ public static function getGradeDisplayName(string $gradeKey): string { return match ($gradeKey) { 'grade_5' => 'الصف الخامس الأساسي', 'grade_6' => 'الصف السادس الأساسي', 'grade_7' => 'الصف السابع الأساسي', 'grade_8' => 'الصف الثامن الأساسي', 'grade_9' => 'الصف التاسع الأساسي', 'grade_10' => 'الصف العاشر الأساسي', 'grade_11' => 'الصف الحادي عشر (الأول ثانوي)', 'grade_12', 'tawjihi_2008' => 'الصف الثاني عشر (التوجيهي الوزاري)', default => $gradeKey, }; } }