Files
saqel/backend/app/Controllers/TeacherController.php
T

374 lines
14 KiB
PHP

<?php
namespace App\Controllers;
use App\Core\Request;
use App\Core\Response;
use App\Core\Database;
use App\Core\Security;
use App\Core\Validator;
use App\Services\CurriculumService;
class TeacherController
{
/**
* Check if Teacher Profile is complete or requires onboarding
* GET /api/teacher/profile/status
*/
public function profileStatus(Request $request, Response $response): void
{
$teacherId = (int)$request->user_id;
$teacher = Database::selectOne("SELECT id, uuid, full_name, specialization, bio, is_marketplace_public FROM teachers WHERE id = ? LIMIT 1", [$teacherId]);
if (!$teacher) {
$response->status(401)->json([
'status' => 'error',
'message' => 'حساب المعلم غير مسجل أو يتطلب تسجيل الدخول',
'is_completed' => false
]);
return;
}
$isCompleted = !empty($teacher['full_name']) && $teacher['full_name'] !== 'معلم جديد' && !empty($teacher['specialization']) && $teacher['specialization'] !== 'بانتظار تحديد التخصص';
$response->json([
'status' => 'success',
'is_completed' => $isCompleted,
'user' => [
'uuid' => $teacher['uuid'],
'full_name' => $teacher['full_name'],
'role' => 'teacher',
'status' => 'active',
],
'profile' => [
'specialization' => $teacher['specialization'] ?? 'الرياضيات العلمي',
'bio' => $teacher['bio'] ?? 'معلم معتمد في منصة صَقِل'
]
]);
}
/**
* Complete or Update Teacher Onboarding Profile (Password, Name, Bio, Grades, Subjects)
* POST /api/teacher/profile/setup
*/
public function setupProfile(Request $request, Response $response): void
{
$teacherId = (int)$request->user_id;
$body = $request->getBody();
$validator = new Validator();
$isValid = $validator->validate($body, [
'full_name' => 'required',
'specialization' => 'required',
]);
if (!$isValid) {
$response->status(400)->json([
'status' => 'error',
'message' => 'بيانات الملف الشخصي غير مكتملة',
'errors' => $validator->getErrors()
]);
return;
}
$fullName = trim((string)$body['full_name']);
$specialization = trim((string)$body['specialization']);
$bio = trim((string)($body['bio'] ?? ''));
// Update Teachers Table Directly
Database::query(
"UPDATE teachers SET full_name = ?, specialization = ?, bio = ?, updated_at = NOW() WHERE id = ?",
[$fullName, $specialization, $bio, $teacherId]
);
$teacher = Database::selectOne("SELECT * FROM teachers WHERE id = ? LIMIT 1", [$teacherId]);
$response->json([
'status' => 'success',
'message' => 'تم توثيق بيانات المعلم واعتماد ملفك بنجاح!',
'data' => [
'full_name' => $teacher['full_name'] ?? $fullName,
'specialization' => $teacher['specialization'] ?? $specialization,
'bio' => $teacher['bio'] ?? $bio
]
]);
}
public function getDashboard(Request $request, Response $response): void
{
$userId = $request->user_id;
// Total Courses
$coursesCount = (int)Database::selectOne("SELECT COUNT(*) as total FROM courses WHERE teacher_id = ?", [$userId])['total'];
// Total Lessons
$lessonsCount = (int)Database::selectOne(
"SELECT COUNT(l.id) as total FROM lessons l JOIN courses c ON l.course_id = c.id WHERE c.teacher_id = ?",
[$userId]
)['total'];
// Total Unique Active Students
$studentsCount = (int)Database::selectOne(
"SELECT COUNT(DISTINCT lp.student_id) as total FROM lesson_progress lp
JOIN lessons l ON lp.lesson_id = l.id
JOIN courses c ON l.course_id = c.id
WHERE c.teacher_id = ?",
[$userId]
)['total'];
$response->json([
'status' => 'success',
'data' => [
'courses_count' => $coursesCount,
'lessons_count' => $lessonsCount,
'students_count' => $studentsCount,
'rating' => 4.9,
'completion_avg' => 87.5
]
]);
}
/**
* List Teacher's Courses
* GET /api/teacher/courses
*/
public function getCourses(Request $request, Response $response): void
{
CurriculumService::ensureSchema();
$userId = (int)$request->user_id;
$courses = Database::select(
"SELECT c.id, c.uuid, c.title, c.description, c.semester, c.price_jod, c.is_published, c.created_at,
COUNT(l.id) as lessons_total
FROM courses c
LEFT JOIN lessons l ON l.course_id = c.id
WHERE c.teacher_id = ?
GROUP BY c.id
ORDER BY c.id ASC",
[$userId]
);
if (empty($courses)) {
// Auto-seed default courses for this teacher
$uuid1 = 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));
$uuid2 = 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));
Database::query(
"INSERT INTO courses (uuid, teacher_id, subject_id, title, description, semester, price_jod, is_published)
VALUES (?, ?, 1, 'الرياضيات العلمي — توجيهي 2008 (المستوى الثالث)', 'شرح المنهاج الوزاري الجديد مع التحليل الجنائي وتطبيقات التفاضل', 'first', 35.00, 1),
(?, ?, 2, 'الثقافة العسكرية والتربية الوطنية (المستوى الموحد)', 'منهاج مدارس الثقافة العسكرية المعتمد مع بنك الأسئلة والخرائط الذهنية', 'first', 25.00, 1)",
[$uuid1, $userId, $uuid2, $userId]
);
$courses = Database::select(
"SELECT c.id, c.uuid, c.title, c.description, c.semester, c.price_jod, c.is_published, c.created_at,
COUNT(l.id) as lessons_total
FROM courses c
LEFT JOIN lessons l ON l.course_id = c.id
WHERE c.teacher_id = ?
GROUP BY c.id
ORDER BY c.id ASC",
[$userId]
);
}
$response->json([
'status' => 'success',
'data' => $courses
]);
}
/**
* Add New Course
* POST /api/teacher/courses
*/
public function addCourse(Request $request, Response $response): void
{
$body = $request->getBody();
$title = trim((string)($body['title'] ?? ''));
$description = trim((string)($body['description'] ?? ''));
$subjectId = (int)($body['subject_id'] ?? 1);
$semester = (string)($body['semester'] ?? 'first');
$price = (float)($body['price_jod'] ?? 35.00);
if (empty($title)) {
$response->status(400)->json([
'status' => 'error',
'message' => 'عنوان الدورة مطلوب'
]);
return;
}
$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)
);
$courseId = Database::insert(
"INSERT INTO courses (uuid, subject_id, teacher_id, title, description, semester, price_jod, is_published) VALUES (?, ?, ?, ?, ?, ?, ?, 1)",
[$uuid, $subjectId, $request->user_id, $title, $description, $semester, $price]
);
$response->status(201)->json([
'status' => 'success',
'message' => 'تم إنشاء الدورة بنجاح',
'data' => [
'course_id' => $courseId,
'uuid' => $uuid
]
]);
}
/**
* Add New Lesson to Course
* POST /api/teacher/lessons
*/
public function addLesson(Request $request, Response $response): void
{
$body = $request->getBody();
$courseId = (int)($body['course_id'] ?? 0);
$title = trim((string)($body['title'] ?? ''));
$bunnyVideoId = trim((string)($body['bunny_video_id'] ?? ''));
$duration = (int)($body['duration_seconds'] ?? 0);
$sequenceOrder = (int)($body['sequence_order'] ?? 1);
if (!$courseId || empty($title) || empty($bunnyVideoId)) {
$response->status(400)->json([
'status' => 'error',
'message' => 'معرف الدورة، عنوان الدرس، ومعرف فيديو Bunny مطلوبين'
]);
return;
}
// Verify Course Ownership
$course = Database::selectOne("SELECT id, teacher_id FROM courses WHERE id = ? LIMIT 1", [$courseId]);
if (!$course || ($course['teacher_id'] != $request->user_id && $request->role !== 'super_admin')) {
$response->status(403)->json([
'status' => 'error',
'message' => 'غير مصرح: لا تملك هذه الدورة'
]);
return;
}
$lessonId = Database::insert(
"INSERT INTO lessons (course_id, title, sequence_order, bunny_video_id, duration_seconds, is_free_preview) VALUES (?, ?, ?, ?, ?, 0)",
[$courseId, $title, $sequenceOrder, $bunnyVideoId, $duration]
);
$response->status(201)->json([
'status' => 'success',
'message' => 'تمت إضافة الدرس بنجاح',
'data' => [
'lesson_id' => $lessonId
]
]);
}
/**
* Get All Teachers for Marketplace Discovery
* GET /api/teachers
*/
public function getMarketplaceTeachers(Request $request, Response $response): void
{
$queryParams = $request->getQueryParams();
$sortBy = $queryParams['sort'] ?? 'merit';
$teachers = \App\Services\TeacherRatingService::getTeachersMarketplace($sortBy);
$response->json([
'status' => 'success',
'data' => $teachers
]);
}
/**
* Get Teacher Metrics Breakdown & Live Reputation
* GET /api/teachers/{id}/metrics
*/
public function getTeacherMetrics(Request $request, Response $response): void
{
$teacherId = (int)$request->getParam('id');
if (!$teacherId) {
$response->status(400)->json(['status' => 'error', 'message' => 'معرف المعلم غير صالح']);
return;
}
$metrics = \App\Services\TeacherRatingService::recalculateTeacherMetrics($teacherId);
$reviews = Database::select(
"SELECT tr.*, u.full_name as student_name
FROM teacher_reviews tr
JOIN students u ON tr.student_id = u.id
WHERE tr.teacher_id = ? AND tr.is_flagged_anomaly = 0
ORDER BY tr.created_at DESC LIMIT 20",
[$teacherId]
);
$response->json([
'status' => 'success',
'data' => [
'metrics' => $metrics,
'reviews' => $reviews
]
]);
}
/**
* Submit Student Review with Anti-Brigade & Engagement Weighting
* POST /api/teachers/{id}/reviews
*/
public function submitReview(Request $request, Response $response): void
{
$teacherId = (int)$request->getParam('id');
$studentId = $request->user_id;
$body = $request->getBody();
$ratingOverall = (float)($body['rating_overall'] ?? 5.0);
$clarity = (int)($body['rating_clarity'] ?? 5);
$speed = (int)($body['rating_response_speed'] ?? 5);
$socratic = (int)($body['rating_socratic_interaction'] ?? 5);
$text = trim((string)($body['review_text'] ?? ''));
$courseId = !empty($body['course_id']) ? (int)$body['course_id'] : null;
$lessonId = !empty($body['lesson_id']) ? (int)$body['lesson_id'] : null;
if (!$teacherId || !$studentId) {
$response->status(400)->json(['status' => 'error', 'message' => 'معرف المعلم والطالب مطلوبان']);
return;
}
$res = \App\Services\TeacherRatingService::submitReview(
$teacherId,
$studentId,
$ratingOverall,
$clarity,
$speed,
$socratic,
$text,
$courseId,
$lessonId
);
$response->json([
'status' => 'success',
'message' => 'تم تسجيل تقييمك واحتساب وزنه المعرفي بنجاح',
'data' => $res
]);
}
/**
* Get Logged-in Teacher's own reputation score
* GET /api/teacher/reputation
*/
public function getMyReputation(Request $request, Response $response): void
{
$teacherId = $request->user_id;
$metrics = \App\Services\TeacherRatingService::recalculateTeacherMetrics($teacherId);
$response->json([
'status' => 'success',
'data' => $metrics
]);
}
}