Learning Management System (LMS) with Content-Based Recommendations (Continued)
CREATE TABLE learning_path_courses ( id INT PRIMARY KEY AUTO_INCREMENT, path_id INT NOT NULL, course_id INT NOT NULL, sort_order INT DEFAULT 0, is_required BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (path_id) REFERENCES learning_paths(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE, UNIQUE KEY unique_path_course (path_id, course_id) ); -- User learning paths CREATE TABLE user_learning_paths ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, path_id INT NOT NULL, progress INT DEFAULT 0, started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (path_id) REFERENCES learning_paths(id) ON DELETE CASCADE, UNIQUE KEY unique_user_path (user_id, path_id) ); -- Certificates CREATE TABLE certificates ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, course_id INT NOT NULL, certificate_number VARCHAR(100) UNIQUE NOT NULL, issue_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, certificate_url VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE, UNIQUE KEY unique_user_course_cert (user_id, course_id) ); -- Instructor earnings CREATE TABLE instructor_earnings ( id INT PRIMARY KEY AUTO_INCREMENT, instructor_id INT NOT NULL, enrollment_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, status ENUM('pending', 'paid', 'cancelled') DEFAULT 'pending', paid_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (instructor_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (enrollment_id) REFERENCES enrollments(id) ON DELETE CASCADE, INDEX idx_instructor (instructor_id) ); -- Site settings CREATE TABLE settings ( id INT PRIMARY KEY AUTO_INCREMENT, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- Insert default settings INSERT INTO settings (setting_key, setting_value, description) VALUES ('site_name', 'LearnHub', 'Website name'), ('site_description', 'Learn anything, anytime, anywhere', 'Site description'), ('currency', 'USD', 'Default currency'), ('currency_symbol', '$', 'Currency symbol'), ('platform_fee', '20.00', 'Platform fee percentage'), ('min_instructor_payout', '50.00', 'Minimum payout amount'), ('max_upload_size', '500', 'Maximum upload size in MB'), ('courses_per_page', '12', 'Number of courses per page'), ('recommendation_limit', '20', 'Number of recommendations to generate'), ('trending_days', '30', 'Days to consider for trending'), ('maintenance_mode', '0', 'Maintenance mode status'), ('registration_enabled', '1', 'Allow new registrations'); -- Insert sample categories INSERT INTO categories (name, slug, description, icon) VALUES ('Development', 'development', 'Learn programming and web development', '💻'), ('Business', 'business', 'Entrepreneurship and business skills', '📊'), ('Finance', 'finance', 'Accounting and financial management', '💰'), ('IT & Software', 'it-software', 'IT certification and software training', '🖥️'), ('Design', 'design', 'Graphic design and creative skills', '🎨'), ('Marketing', 'marketing', 'Digital marketing and SEO', '📈'), ('Music', 'music', 'Learn instruments and music production', '🎵'), ('Photography', 'photography', 'Photography and video editing', '📸'), ('Health & Fitness', 'health-fitness', 'Wellness and personal training', '🏋️'), ('Language', 'language', 'Learn new languages', '🗣️');

BACKEND IMPLEMENTATION

config.php

<?php session_start(); // Database configuration define('DB_HOST', 'localhost'); define('DB_NAME', 'lms_platform'); define('DB_USER', 'root'); define('DB_PASS', ''); // Site configuration define('SITE_URL', 'http://localhost/lms'); define('SITE_NAME', 'LearnHub'); define('UPLOAD_DIR', __DIR__ . '/uploads/'); define('MAX_FILE_SIZE', 500 * 1024 * 1024); // 500MB define('TIMEZONE', 'UTC'); define('DEBUG_MODE', true); // Currency settings define('CURRENCY', 'USD'); define('CURRENCY_SYMBOL', '$'); define('PLATFORM_FEE', 20.00); // Platform fee percentage define('MIN_PAYOUT', 50.00); // Pagination define('COURSES_PER_PAGE', 12); define('RECOMMENDATION_LIMIT', 20); // Set timezone date_default_timezone_set(TIMEZONE); // Error reporting if (DEBUG_MODE) { error_reporting(E_ALL); ini_set('display_errors', 1); } else { error_reporting(0); ini_set('display_errors', 0); } // Database connection try { $pdo = new PDO( "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } catch(PDOException $e) { die("Connection failed: " . $e->getMessage()); } // Create upload directories $upload_dirs = [ UPLOAD_DIR, UPLOAD_DIR . 'courses/', UPLOAD_DIR . 'lessons/', UPLOAD_DIR . 'assignments/', UPLOAD_DIR . 'profiles/', UPLOAD_DIR . 'certificates/' ]; foreach ($upload_dirs as $dir) { if (!file_exists($dir)) { mkdir($dir, 0777, true); } } // Include helper functions require_once 'helpers.php'; // Autoload classes spl_autoload_register(function ($class) { $file = __DIR__ . '/classes/' . $class . '.php'; if (file_exists($file)) { require_once $file; } }); // Check if user is logged in function isLoggedIn() { return isset($_SESSION['user_id']); } // Get current user ID function getCurrentUserId() { return $_SESSION['user_id'] ?? null; } // Get current user type function getUserType() { return $_SESSION['user_type'] ?? null; } // Get current user data function getCurrentUser() { global $pdo; if (!isLoggedIn()) return null; static $user = null; if ($user === null) { $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(); } return $user; } // Check if user is instructor function isInstructor() { return getUserType() === 'instructor'; } // Check if user is admin function isAdmin() { return getUserType() === 'admin'; } // Redirect function function redirect($url) { header("Location: " . SITE_URL . $url); exit; } // JSON response function function jsonResponse($data, $status = 200) { http_response_code($status); header('Content-Type: application/json'); echo json_encode($data); exit; } ?>

helpers.php

<?php // Helper functions // Sanitize input function sanitize($input) { return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); } // Generate random string function generateRandomString($length = 32) { return bin2hex(random_bytes($length / 2)); } // Generate slug function createSlug($string) { $string = strtolower($string); $string = preg_replace('/[^a-z0-9-]/', '-', $string); $string = preg_replace('/-+/', '-', $string); return trim($string, '-'); } // Format price function formatPrice($price) { return CURRENCY_SYMBOL . number_format($price, 2); } // Format duration function formatDuration($minutes) { if ($minutes < 60) { return $minutes . ' min'; } $hours = floor($minutes / 60); $mins = $minutes % 60; return $hours . 'h ' . ($mins > 0 ? $mins . 'm' : ''); } // Format date function formatDate($date, $format = 'M j, Y') { return date($format, strtotime($date)); } // Time ago function function timeAgo($datetime) { $time = strtotime($datetime); $now = time(); $diff = $now - $time; if ($diff < 60) { return $diff . ' seconds ago'; } elseif ($diff < 3600) { $mins = floor($diff / 60); return $mins . ' minute' . ($mins > 1 ? 's' : '') . ' ago'; } elseif ($diff < 86400) { $hours = floor($diff / 3600); return $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ago'; } elseif ($diff < 2592000) { $days = floor($diff / 86400); return $days . ' day' . ($days > 1 ? 's' : '') . ' ago'; } elseif ($diff < 31536000) { $months = floor($diff / 2592000); return $months . ' month' . ($months > 1 ? 's' : '') . ' ago'; } else { $years = floor($diff / 31536000); return $years . ' year' . ($years > 1 ? 's' : '') . ' ago'; } } // Upload file function uploadFile($file, $folder, $allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'mp4', 'pdf', 'doc', 'docx']) { if ($file['error'] !== UPLOAD_ERR_OK) { return ['success' => false, 'message' => 'Upload failed']; } $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($extension, $allowedTypes)) { return ['success' => false, 'message' => 'File type not allowed']; } if ($file['size'] > MAX_FILE_SIZE) { return ['success' => false, 'message' => 'File too large']; } $filename = uniqid() . '_' . time() . '.' . $extension; $filepath = UPLOAD_DIR . $folder . '/' . $filename; if (move_uploaded_file($file['tmp_name'], $filepath)) { return [ 'success' => true, 'filename' => $filename, 'path' => 'uploads/' . $folder . '/' . $filename ]; } return ['success' => false, 'message' => 'Failed to save file']; } // Get file URL function getFileUrl($path) { if (empty($path)) return null; return SITE_URL . '/' . $path; } // Get course image URL function getCourseImage($course) { if (!empty($course['thumbnail_url'])) { return getFileUrl($course['thumbnail_url']); } return SITE_URL . '/assets/images/course-placeholder.jpg'; } // Get rating stars HTML function getRatingStars($rating) { $stars = ''; for ($i = 1; $i <= 5; $i++) { if ($i <= $rating) { $stars .= '<i class="fas fa-star text-yellow-400"></i>'; } elseif ($i - 0.5 <= $rating) { $stars .= '<i class="fas fa-star-half-alt text-yellow-400"></i>'; } else { $stars .= '<i class="far fa-star text-yellow-400"></i>'; } } return $stars; } // Calculate course progress function calculateProgress($enrollmentId) { global $pdo; // Get total lessons in course $stmt = $pdo->prepare(" SELECT COUNT(*) as total FROM lessons l JOIN course_sections cs ON l.section_id = cs.id WHERE cs.course_id = (SELECT course_id FROM enrollments WHERE id = ?) "); $stmt->execute([$enrollmentId]); $total = $stmt->fetchColumn(); if ($total == 0) return 0; // Get completed lessons $stmt = $pdo->prepare(" SELECT COUNT(*) as completed FROM lesson_progress WHERE enrollment_id = ? AND is_completed = 1 "); $stmt->execute([$enrollmentId]); $completed = $stmt->fetchColumn(); return round(($completed / $total) * 100); } // Generate certificate number function generateCertificateNumber($userId, $courseId) { return 'CERT-' . strtoupper(uniqid()) . '-' . $userId . '-' . $courseId; } // Calculate instructor earnings function calculateEarnings($amount) { $platformFee = ($amount * PLATFORM_FEE) / 100; $instructorEarning = $amount - $platformFee; return [ 'total' => $amount, 'platform_fee' => $platformFee, 'instructor_earning' => $instructorEarning ]; } // Log user interaction for recommendations function logUserInteraction($userId, $courseId, $type, $metadata = []) { global $pdo; $stmt = $pdo->prepare(" INSERT INTO user_interactions (user_id, course_id, interaction_type, metadata) VALUES (?, ?, ?, ?) "); $stmt->execute([$userId, $courseId, $type, json_encode($metadata)]); } // Get user's skill level function getSkillLevel($proficiency) { $levels = [ 'beginner' => 1, 'intermediate' => 2, 'advanced' => 3, 'expert' => 4 ]; return $levels[$proficiency] ?? 1; } // Check if user has prerequisite skills function checkPrerequisites($userId, $courseId) { global $pdo; // Get course prerequisites $stmt = $pdo->prepare(" SELECT prerequisite_course_id FROM course_prerequisites WHERE course_id = ? "); $stmt->execute([$courseId]); $prerequisites = $stmt->fetchAll(PDO::FETCH_COLUMN); if (empty($prerequisites)) { return ['has_prerequisites' => true]; } // Check if user completed prerequisites $stmt = $pdo->prepare(" SELECT COUNT(*) FROM enrollments WHERE user_id = ? AND course_id IN (" . implode(',', $prerequisites) . ") AND is_completed = 1 "); $stmt->execute([$userId]); $completed = $stmt->fetchColumn(); $missing = array_diff($prerequisites, $stmt->fetchAll(PDO::FETCH_COLUMN)); return [ 'has_prerequisites' => $completed == count($prerequisites), 'completed' => $completed, 'total' => count($prerequisites), 'missing' => $missing ]; } // Get course difficulty level function getDifficultyLevel($level) { $levels = [ 'beginner' => 1, 'intermediate' => 2, 'advanced' => 3, 'all-levels' => 2 ]; return $levels[$level] ?? 1; }

classes/CourseRecommender.php (Core Recommendation Algorithm)

<?php class CourseRecommender { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Main recommendation engine public function getRecommendations($userId, $type = 'personalized', $limit = RECOMMENDATION_LIMIT) { // Check cache first $cached = $this->getCachedRecommendations($userId, $type, $limit); if ($cached) { return $cached; } switch ($type) { case 'personalized': $recommendations = $this->getPersonalizedRecommendations($userId, $limit); break; case 'trending': $recommendations = $this->getTrendingCourses($limit); break; case 'popular': $recommendations = $this->getPopularCourses($limit); break; case 'new': $recommendations = $this->getNewCourses($limit); break; case 'skill_gap': $recommendations = $this->getSkillGapRecommendations($userId, $limit); break; case 'similar': $courseId = $_GET['course_id'] ?? 0; $recommendations = $this->getSimilarCourses($courseId, $limit); break; case 'learning_path': $recommendations = $this->getLearningPathRecommendations($userId, $limit); break; case 'category': $categoryId = $_GET['category_id'] ?? 0; $recommendations = $this->getCategoryRecommendations($userId, $categoryId, $limit); break; default: $recommendations = []; } // Cache recommendations $this->cacheRecommendations($userId, $type, $recommendations); return $recommendations; } // Personalized recommendations based on user profile and behavior private function getPersonalizedRecommendations($userId, $limit) { // Get user profile $userProfile = $this->buildUserProfile($userId); if (empty($userProfile['interests']) && empty($userProfile['skills'])) { // Cold start: recommend popular courses return $this->getPopularCourses($limit); } // Get candidate courses (exclude enrolled and completed) $candidates = $this->getCandidateCourses($userId); // Score each candidate $recommendations = []; foreach ($candidates as $course) { $score = $this->calculateCourseScore($course, $userProfile); if ($score > 0.3) { $recommendations[] = [ 'course' => $course, 'score' => $score, 'reasons' => $this->getRecommendationReasons($course, $userProfile) ]; } } // Sort by score usort($recommendations, function($a, $b) { return $b['score'] <=> $a['score']; }); return array_slice($recommendations, 0, $limit); } // Build user profile from interactions, skills, and goals private function buildUserProfile($userId) { $profile = [ 'interests' => [], 'skills' => [], 'categories' => [], 'levels' => [], 'completed_courses' => [], 'avg_rating' => 0, 'total_interactions' => 0 ]; // Get user skills $stmt = $this->pdo->prepare(" SELECT skill_name, proficiency_level FROM user_skills WHERE user_id = ? "); $stmt->execute([$userId]); $skills = $stmt->fetchAll(); foreach ($skills as $skill) { $profile['skills'][$skill['skill_name']] = getSkillLevel($skill['proficiency_level']); } // Get learning goals $stmt = $this->pdo->prepare(" SELECT goal_title FROM learning_goals WHERE user_id = ? AND is_achieved = 0 "); $stmt->execute([$userId]); $goals = $stmt->fetchAll(); foreach ($goals as $goal) { $profile['interests'] = array_merge($profile['interests'], explode(' ', $goal['goal_title'])); } // Get user interactions $stmt = $this->pdo->prepare(" SELECT ui.*, c.category_id, c.level FROM user_interactions ui JOIN courses c ON ui.course_id = c.id WHERE ui.user_id = ? ORDER BY ui.created_at DESC LIMIT 100 "); $stmt->execute([$userId]); $interactions = $stmt->fetchAll(); $profile['total_interactions'] = count($interactions); $totalRating = 0; $ratingCount = 0; foreach ($interactions as $interaction) { // Track category preferences if ($interaction['category_id']) { $profile['categories'][$interaction['category_id']] = ($profile['categories'][$interaction['category_id']] ?? 0) + $this->getInteractionWeight($interaction['interaction_type']); } // Track level preferences if ($interaction['level']) { $profile['levels'][$interaction['level']] = ($profile['levels'][$interaction['level']] ?? 0) + $this->getInteractionWeight($interaction['interaction_type']); } // Track completed courses if ($interaction['interaction_type'] == 'complete') { $profile['completed_courses'][] = $interaction['course_id']; } // Get ratings from reviews if ($interaction['interaction_type'] == 'review') { $metadata = json_decode($interaction['metadata'], true); if (isset($metadata['rating'])) { $totalRating += $metadata['rating']; $ratingCount++; } } } // Calculate average rating given $profile['avg_rating'] = $ratingCount > 0 ? $totalRating / $ratingCount : 3.5; // Extract interests from course titles/descriptions foreach ($interactions as $interaction) { if ($interaction['interaction_type'] == 'view' || $interaction['interaction_type'] == 'enroll') { $course = $this->getCourseDetails($interaction['course_id']); if ($course) { $words = explode(' ', $course['title'] . ' ' . ($course['subtitle'] ?? '')); $profile['interests'] = array_merge($profile['interests'], array_slice($words, 0, 5)); } } } // Normalize preferences foreach ($profile['categories'] as &$count) { $count = $count / $profile['total_interactions']; } foreach ($profile['levels'] as &$count) { $count = $count / $profile['total_interactions']; } return $profile; } // Get weight for interaction type private function getInteractionWeight($type) { $weights = [ 'complete' => 1.0, 'enroll' => 0.8, 'review' => 0.7, 'wishlist' => 0.6, 'view' => 0.3, 'search' => 0.2 ]; return $weights[$type] ?? 0.1; } // Get course details private function getCourseDetails($courseId) { $stmt = $this->pdo->prepare(" SELECT * FROM courses WHERE id = ? "); $stmt->execute([$courseId]); return $stmt->fetch(); } // Calculate course relevance score private function calculateCourseScore($course, $userProfile) { $weights = [ 'category' => 0.25, 'level' => 0.15, 'skills' => 0.30, 'popularity' => 0.15, 'recency' => 0.10, 'prerequisites' => 0.05 ]; $scores = [ 'category' => $this->calculateCategoryScore($course, $userProfile), 'level' => $this->calculateLevelScore($course, $userProfile), 'skills' => $this->calculateSkillScore($course, $userProfile), 'popularity' => $this->calculatePopularityScore($course), 'recency' => $this->calculateRecencyScore($course), 'prerequisites' => $this->calculatePrerequisiteScore($course, $userProfile) ]; $total = 0; foreach ($weights as $factor => $weight) { $total += $scores[$factor] * $weight; } return $total; } // Calculate category match score private function calculateCategoryScore($course, $userProfile) { if (!$course['category_id'] || empty($userProfile['categories'])) { return 0.5; } return $userProfile['categories'][$course['category_id']] ?? 0.3; } // Calculate level match score private function calculateLevelScore($course, $userProfile) { if (empty($userProfile['levels'])) { return 0.5; } $courseLevel = getDifficultyLevel($course['level']); $preferredLevel = $this->getPreferredLevel($userProfile['levels']); // Calculate how close the levels are $levelDiff = abs($courseLevel - $preferredLevel); return max(0, 1 - ($levelDiff / 3)); } // Get preferred difficulty level private function getPreferredLevel($levels) { $weightedSum = 0; $totalWeight = 0; foreach ($levels as $level => $weight) { $levelValue = getDifficultyLevel($level); $weightedSum += $levelValue * $weight; $totalWeight += $weight; } return $totalWeight > 0 ? $weightedSum / $totalWeight : 2; } // Calculate skill match score private function calculateSkillScore($course, $userProfile) { // Get course tags/skills $stmt = $this->pdo->prepare(" SELECT tag FROM course_tags WHERE course_id = ? "); $stmt->execute([$course['id']]); $courseSkills = $stmt->fetchAll(PDO::FETCH_COLUMN); if (empty($courseSkills) || empty($userProfile['skills'])) { return 0.5; } $score = 0; $matchedSkills = 0; foreach ($courseSkills as $skill) { if (isset($userProfile['skills'][$skill])) { $score += $userProfile['skills'][$skill]; $matchedSkills++; } } if ($matchedSkills > 0) { return $score / (count($courseSkills) * 4); // Normalize to 0-1 } return 0.2; } // Calculate popularity score private function calculatePopularityScore($course) { $students = min($course['students_count'] ?? 0, 10000); $rating = $course['rating_avg'] ?? 0; $studentScore = $students / 10000; $ratingScore = $rating / 5; return ($studentScore * 0.6 + $ratingScore * 0.4); } // Calculate recency score private function calculateRecencyScore($course) { $created = strtotime($course['created_at']); $now = time(); $daysOld = ($now - $created) / (60 * 60 * 24); // Newer courses get higher scores (decay over 90 days) return max(0, 1 - ($daysOld / 90)); } // Calculate prerequisite score private function calculatePrerequisiteScore($course, $userProfile) { // Check if user has completed prerequisites $completedCourses = $userProfile['completed_courses'] ?? []; $stmt = $this->pdo->prepare(" SELECT prerequisite_course_id FROM course_prerequisites WHERE course_id = ? "); $stmt->execute([$course['id']]); $prerequisites = $stmt->fetchAll(PDO::FETCH_COLUMN); if (empty($prerequisites)) { return 1.0; } $completed = 0; foreach ($prerequisites as $pre) { if (in_array($pre, $completedCourses)) { $completed++; } } return $completed / count($prerequisites); } // Get candidate courses (exclude enrolled and completed) private function getCandidateCourses($userId) { $stmt = $this->pdo->prepare(" SELECT c.*, cat.name as category_name, u.full_name as instructor_name FROM courses c LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.is_published = 1 AND c.is_approved = 1 AND c.id NOT IN ( SELECT course_id FROM enrollments WHERE user_id = ? UNION SELECT course_id FROM wishlists WHERE user_id = ? ) ORDER BY c.created_at DESC LIMIT 200 "); $stmt->execute([$userId, $userId]); return $stmt->fetchAll(); } // Get trending courses (based on recent enrollments) public function getTrendingCourses($limit) { $stmt = $this->pdo->prepare(" SELECT c.*, COUNT(DISTINCT e.id) as recent_enrollments, cat.name as category_name, u.full_name as instructor_name FROM courses c LEFT JOIN enrollments e ON c.id = e.course_id AND e.enrolled_at > DATE_SUB(NOW(), INTERVAL 30 DAY) LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.is_published = 1 AND c.is_approved = 1 GROUP BY c.id ORDER BY recent_enrollments DESC, c.rating_avg DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get popular courses (all-time bestsellers) public function getPopularCourses($limit) { $stmt = $this->pdo->prepare(" SELECT c.*, cat.name as category_name, u.full_name as instructor_name FROM courses c LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.is_published = 1 AND c.is_approved = 1 ORDER BY c.students_count DESC, c.rating_avg DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get new courses public function getNewCourses($limit) { $stmt = $this->pdo->prepare(" SELECT c.*, cat.name as category_name, u.full_name as instructor_name FROM courses c LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.is_published = 1 AND c.is_approved = 1 ORDER BY c.created_at DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get skill gap recommendations public function getSkillGapRecommendations($userId, $limit) { // Get user's skill gaps $stmt = $this->pdo->prepare(" SELECT * FROM skill_gaps WHERE user_id = ? ORDER BY priority DESC "); $stmt->execute([$userId]); $gaps = $stmt->fetchAll(); if (empty($gaps)) { return $this->getPersonalizedRecommendations($userId, $limit); } $recommendations = []; foreach ($gaps as $gap) { // Find courses that teach this skill at the right level $stmt = $this->pdo->prepare(" SELECT c.*, cat.name as category_name, u.full_name as instructor_name FROM courses c JOIN course_tags ct ON c.id = ct.course_id LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE ct.tag = ? AND c.level <= ? AND c.is_published = 1 AND c.is_approved = 1 AND c.id NOT IN (SELECT course_id FROM enrollments WHERE user_id = ?) ORDER BY c.rating_avg DESC, c.students_count DESC LIMIT 3 "); $stmt->execute([$gap['skill_name'], $gap['desired_level'], $userId]); $courses = $stmt->fetchAll(); foreach ($courses as $course) { $recommendations[] = [ 'course' => $course, 'score' => 0.9, 'reasons' => ['Fills your skill gap in ' . $gap['skill_name']] ]; } } return array_slice($recommendations, 0, $limit); } // Get similar courses public function getSimilarCourses($courseId, $limit) { // Check pre-computed similarity $stmt = $this->pdo->prepare(" SELECT c.*, cs.similarity_score, cat.name as category_name, u.full_name as instructor_name FROM course_similarity cs JOIN courses c ON cs.course_id_2 = c.id LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE cs.course_id_1 = ? ORDER BY cs.similarity_score DESC LIMIT ? "); $stmt->execute([$courseId, $limit]); $similar = $stmt->fetchAll(); if (!empty($similar)) { return $similar; } // Calculate on the fly based on tags and category $stmt = $this->pdo->prepare(" SELECT c.*, COUNT(DISTINCT ct1.tag) as match_count, cat.name as category_name, u.full_name as instructor_name FROM courses c JOIN course_tags ct1 ON c.id = ct1.course_id JOIN course_tags ct2 ON ct1.tag = ct2.tag LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE ct2.course_id = ? AND c.id != ? AND c.is_published = 1 AND c.is_approved = 1 GROUP BY c.id ORDER BY match_count DESC, c.rating_avg DESC LIMIT ? "); $stmt->execute([$courseId, $courseId, $limit]); return $stmt->fetchAll(); } // Get learning path recommendations public function getLearningPathRecommendations($userId, $limit) { // Get user's current paths $stmt = $this->pdo->prepare(" SELECT lp.*, ulp.progress FROM learning_paths lp JOIN user_learning_paths ulp ON lp.id = ulp.path_id WHERE ulp.user_id = ? AND ulp.completed_at IS NULL ORDER BY ulp.progress DESC "); $stmt->execute([$userId]); $currentPaths = $stmt->fetchAll(); if (empty($currentPaths)) { // Recommend popular learning paths $stmt = $this->pdo->prepare(" SELECT lp.*, COUNT(ulp.id) as students FROM learning_paths lp LEFT JOIN user_learning_paths ulp ON lp.id = ulp.path_id WHERE lp.is_active = 1 GROUP BY lp.id ORDER BY students DESC, lp.created_at DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get next courses in current paths $recommendations = []; foreach ($currentPaths as $path) { $stmt = $this->pdo->prepare(" SELECT c.*, lpc.sort_order, cat.name as category_name, u.full_name as instructor_name FROM learning_path_courses lpc JOIN courses c ON lpc.course_id = c.id LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE lpc.path_id = ? AND lpc.sort_order > ? AND c.id NOT IN (SELECT course_id FROM enrollments WHERE user_id = ?) ORDER BY lpc.sort_order ASC LIMIT 2 "); $stmt->execute([$path['id'], $path['progress'], $userId]); $nextCourses = $stmt->fetchAll(); foreach ($nextCourses as $course) { $recommendations[] = [ 'course' => $course, 'score' => 0.95, 'reasons' => ['Next in your learning path: ' . $path['title']] ]; } } return array_slice($recommendations, 0, $limit); } // Get category-based recommendations public function getCategoryRecommendations($userId, $categoryId, $limit) { // Get user's preferred courses in this category $stmt = $this->pdo->prepare(" SELECT c.*, ui.interaction_type FROM user_interactions ui JOIN courses c ON ui.course_id = c.id WHERE ui.user_id = ? AND c.category_id = ? ORDER BY ui.created_at DESC LIMIT 10 "); $stmt->execute([$userId, $categoryId]); $preferred = $stmt->fetchAll(); if (empty($preferred)) { // Return popular in category $stmt = $this->pdo->prepare(" SELECT c.*, cat.name as category_name, u.full_name as instructor_name FROM courses c LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.category_id = ? AND c.is_published = 1 AND c.is_approved = 1 ORDER BY c.students_count DESC, c.rating_avg DESC LIMIT ? "); $stmt->execute([$categoryId, $limit]); return $stmt->fetchAll(); } // Build profile from preferred courses and recommend similar $profile = $this->buildProfileFromCourses($preferred); return $this->getCoursesByProfile($profile, $categoryId, $limit); } // Build profile from a list of courses private function buildProfileFromCourses($courses) { $profile = [ 'tags' => [], 'levels' => [], 'instructors' => [] ]; foreach ($courses as $course) { // Get course tags $stmt = $this->pdo->prepare("SELECT tag FROM course_tags WHERE course_id = ?"); $stmt->execute([$course['id']]); $tags = $stmt->fetchAll(PDO::FETCH_COLUMN); foreach ($tags as $tag) { $profile['tags'][$tag] = ($profile['tags'][$tag] ?? 0) + 1; } // Track levels $profile['levels'][$course['level']] = ($profile['levels'][$course['level']] ?? 0) + 1; // Track instructors $profile['instructors'][$course['instructor_id']] = ($profile['instructors'][$course['instructor_id']] ?? 0) + 1; } // Normalize $total = count($courses); foreach ($profile as &$values) { if (is_array($values)) { foreach ($values as &$value) { $value = $value / $total; } } } return $profile; } // Get courses matching profile private function getCoursesByProfile($profile, $categoryId, $limit) { // This is a simplified version - in production, you'd use more sophisticated matching $tags = array_keys($profile['tags']); $tagPlaceholders = implode(',', array_fill(0, count($tags), '?')); $stmt = $this->pdo->prepare(" SELECT c.*, COUNT(DISTINCT ct.tag) as tag_matches, cat.name as category_name, u.full_name as instructor_name FROM courses c JOIN course_tags ct ON c.id = ct.course_id LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.category_id = ? AND ct.tag IN ($tagPlaceholders) AND c.is_published = 1 AND c.is_approved = 1 GROUP BY c.id ORDER BY tag_matches DESC, c.rating_avg DESC LIMIT ? "); $params = array_merge([$categoryId], $tags, [$limit]); $stmt->execute($params); return $stmt->fetchAll(); } // Get recommendation reasons private function getRecommendationReasons($course, $userProfile) { $reasons = []; if (!empty($userProfile['categories']) && isset($userProfile['categories'][$course['category_id']])) { $reasons[] = 'Based on your interest in this category'; } if ($this->calculateSkillScore($course, $userProfile) > 0.5) { $reasons[] = 'Matches your skills and interests'; } if ($course['students_count'] > 1000) { $reasons[] = 'Popular among students'; } if ($course['rating_avg'] > 4.5) { $reasons[] = 'Highly rated by students'; } if (empty($reasons)) { $reasons[] = 'Recommended for you'; } return $reasons; } // Cache recommendations private function cacheRecommendations($userId, $type, $recommendations) { // Clear old cache $stmt = $this->pdo->prepare(" DELETE FROM course_recommendations WHERE user_id = ? AND reason = ? "); $stmt->execute([$userId, $type]); // Insert new recommendations $stmt = $this->pdo->prepare(" INSERT INTO course_recommendations (user_id, course_id, score, reason, expires_at) VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 1 DAY)) "); foreach ($recommendations as $rec) { $courseId = is_array($rec) ? ($rec['course']['id'] ?? $rec['id']) : $rec['id']; $score = is_array($rec) ? ($rec['score'] ?? 0.5) : 0.5; $stmt->execute([ $userId, $courseId, $score, $type ]); } } // Get cached recommendations private function getCachedRecommendations($userId, $type, $limit) { $stmt = $this->pdo->prepare(" SELECT cr.*, c.*, cat.name as category_name, u.full_name as instructor_name FROM course_recommendations cr JOIN courses c ON cr.course_id = c.id LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE cr.user_id = ? AND cr.reason = ? AND cr.expires_at > NOW() ORDER BY cr.score DESC LIMIT ? "); $stmt->execute([$userId, $type, $limit]); return $stmt->fetchAll(); } // Pre-compute course similarity (run via cron) public function computeCourseSimilarity() { // Clear existing $this->pdo->exec("TRUNCATE TABLE course_similarity"); // Get all published courses $courses = $this->pdo->query(" SELECT id, category_id, title, description FROM courses WHERE is_published = 1 AND is_approved = 1 ")->fetchAll(); $insertStmt = $this->pdo->prepare(" INSERT INTO course_similarity (course_id_1, course_id_2, similarity_score, based_on) VALUES (?, ?, ?, ?) "); foreach ($courses as $c1) { foreach ($courses as $c2) { if ($c1['id'] >= $c2['id']) continue; $score = 0; $basedOn = []; // Category similarity if ($c1['category_id'] == $c2['category_id']) { $score += 0.4; $basedOn[] = 'category'; } // Tag similarity $tagScore = $this->getTagSimilarity($c1['id'], $c2['id']); $score += $tagScore * 0.3; if ($tagScore > 0.3) $basedOn[] = 'tags'; // Title/description similarity (simple word overlap) $textScore = $this->getTextSimilarity($c1['title'] . ' ' . $c1['description'], $c2['title'] . ' ' . $c2['description']); $score += $textScore * 0.3; if ($textScore > 0.2) $basedOn[] = 'content'; if ($score > 0.3) { $insertStmt->execute([ $c1['id'], $c2['id'], $score, implode(',', $basedOn) ]); } } } } // Get tag similarity between two courses private function getTagSimilarity($courseId1, $courseId2) { $stmt = $this->pdo->prepare(" SELECT COUNT(*) as common_tags FROM course_tags ct1 JOIN course_tags ct2 ON ct1.tag = ct2.tag WHERE ct1.course_id = ? AND ct2.course_id = ? "); $stmt->execute([$courseId1, $courseId2]); $common = $stmt->fetchColumn(); $stmt = $this->pdo->prepare(" SELECT COUNT(*) as total FROM course_tags WHERE course_id = ? "); $stmt->execute([$courseId1]); $total1 = $stmt->fetchColumn(); $stmt->execute([$courseId2]); $total2 = $stmt->fetchColumn(); $total = max($total1, $total2); return $total > 0 ? $common / $total : 0; } // Get text similarity using simple word overlap private function getTextSimilarity($text1, $text2) { $words1 = array_unique(str_word_count(strtolower($text1), 1)); $words2 = array_unique(str_word_count(strtolower($text2), 1)); $common = array_intersect($words1, $words2); $total = max(count($words1), count($words2)); return $total > 0 ? count($common) / $total : 0; } }

classes/Course.php

<?php class Course { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Get course by ID public function getById($id) { $stmt = $this->pdo->prepare(" SELECT c.*, cat.name as category_name, cat.slug as category_slug, u.full_name as instructor_name, u.profile_pic as instructor_pic, u.bio as instructor_bio, (SELECT COUNT(*) FROM enrollments WHERE course_id = c.id) as total_students, (SELECT COUNT(*) FROM course_reviews WHERE course_id = c.id) as total_reviews, (SELECT AVG(rating) FROM course_reviews WHERE course_id = c.id) as avg_rating FROM courses c LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.id = ? "); $stmt->execute([$id]); $course = $stmt->fetch(); if ($course) { // Get sections and lessons $course['sections'] = $this->getSections($id); // Get tags $course['tags'] = $this->getTags($id); // Get prerequisites $course['prerequisites'] = $this->getPrerequisites($id); // Get reviews $course['reviews'] = $this->getReviews($id, 5); } return $course; } // Get course by slug public function getBySlug($slug) { $stmt = $this->pdo->prepare("SELECT id FROM courses WHERE slug = ?"); $stmt->execute([$slug]); $course = $stmt->fetch(); if ($course) { return $this->getById($course['id']); } return null; } // Get course sections with lessons public function getSections($courseId) { $stmt = $this->pdo->prepare(" SELECT * FROM course_sections WHERE course_id = ? ORDER BY sort_order ASC "); $stmt->execute([$courseId]); $sections = $stmt->fetchAll(); foreach ($sections as &$section) { $section['lessons'] = $this->getLessons($section['id']); } return $sections; } // Get lessons in a section public function getLessons($sectionId) { $stmt = $this->pdo->prepare(" SELECT * FROM lessons WHERE section_id = ? ORDER BY sort_order ASC "); $stmt->execute([$sectionId]); return $stmt->fetchAll(); } // Get course tags public function getTags($courseId) { $stmt = $this->pdo->prepare(" SELECT tag FROM course_tags WHERE course_id = ? "); $stmt->execute([$courseId]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } // Get course prerequisites public function getPrerequisites($courseId) { $stmt = $this->pdo->prepare(" SELECT c.* FROM course_prerequisites cp JOIN courses c ON cp.prerequisite_course_id = c.id WHERE cp.course_id = ? "); $stmt->execute([$courseId]); return $stmt->fetchAll(); } // Get course reviews public function getReviews($courseId, $limit = 10, $offset = 0) { $stmt = $this->pdo->prepare(" SELECT r.*, u.full_name, u.profile_pic FROM course_reviews r JOIN users u ON r.user_id = u.id WHERE r.course_id = ? AND r.is_approved = 1 ORDER BY r.created_at DESC LIMIT ? OFFSET ? "); $stmt->execute([$courseId, $limit, $offset]); return $stmt->fetchAll(); } // Search courses public function search($query, $filters = [], $sort = 'relevance', $page = 1, $perPage = COURSES_PER_PAGE) { $offset = ($page - 1) * $perPage; $sql = " SELECT c.*, cat.name as category_name, u.full_name as instructor_name, MATCH(c.title, c.subtitle, c.description) AGAINST(:query) as relevance FROM courses c LEFT JOIN categories cat ON c.category_id = cat.id LEFT JOIN users u ON c.instructor_id = u.id WHERE c.is_published = 1 AND c.is_approved = 1 AND MATCH(c.title, c.subtitle, c.description) AGAINST(:query) "; $params = ['query' => $query]; // Apply filters if (!empty($filters['category'])) { $sql .= " AND c.category_id = :category"; $params['category'] = $filters['category']; } if (!empty($filters['level'])) { $sql .= " AND c.level = :level"; $params['level'] = $filters['level']; } if (!empty($filters['language'])) { $sql .= " AND c.language = :language"; $params['language'] = $filters['language']; } if (!empty($filters['price'])) { if ($filters['price'] == 'free') { $sql .= " AND c.price = 0"; } elseif ($filters['price'] == 'paid') { $sql .= " AND c.price > 0"; } } if (!empty($filters['min_price'])) { $sql .= " AND c.price >= :min_price"; $params['min_price'] = $filters['min_price']; } if (!empty($filters['max_price'])) { $sql .= " AND c.price <= :max_price"; $params['max_price'] = $filters['max_price']; } if (!empty($filters['rating'])) { $sql .= " AND c.rating_avg >= :rating"; $params['rating'] = $filters['rating']; } // Apply sorting switch ($sort) { case 'price_low': $sql .= " ORDER BY c.price ASC"; break; case 'price_high': $sql .= " ORDER BY c.price DESC"; break; case 'newest': $sql .= " ORDER BY c.created_at DESC"; break; case 'popular': $sql .= " ORDER BY c.students_count DESC"; break; case 'rating': $sql .= " ORDER BY c.rating_avg DESC"; break; default: $sql .= " ORDER BY relevance DESC, c.students_count DESC"; } $sql .= " LIMIT :offset, :perPage"; $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':query', $query); $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); $stmt->bindParam(':perPage', $perPage, PDO::PARAM_INT); foreach ($params as $key => &$value) { $stmt->bindParam($key, $value); } $stmt->execute(); return $stmt->fetchAll(); } // Enroll user in course public function enroll($userId, $courseId, $paymentId = null) { // Check if already enrolled $stmt = $this->pdo->prepare("SELECT id FROM enrollments WHERE user_id = ? AND course_id = ?"); $stmt->execute([$userId, $courseId]); if ($stmt->fetch()) { return ['success' => false, 'message' => 'Already enrolled']; } // Check prerequisites $prereqCheck = checkPrerequisites($userId, $courseId); if (!$prereqCheck['has_prerequisites']) { return ['success' => false, 'message' => 'Prerequisites not met', 'prerequisites' => $prereqCheck['missing']]; } // Create enrollment $stmt = $this->pdo->prepare(" INSERT INTO enrollments (user_id, course_id) VALUES (?, ?) "); if ($stmt->execute([$userId, $courseId])) { $enrollmentId = $this->pdo->lastInsertId(); // Update student count $this->pdo->prepare(" UPDATE courses SET students_count = students_count + 1 WHERE id = ? ")->execute([$courseId]); // Log interaction logUserInteraction($userId, $courseId, 'enroll', ['enrollment_id' => $enrollmentId]); return ['success' => true, 'enrollment_id' => $enrollmentId]; } return ['success' => false, 'message' => 'Enrollment failed']; } // Get user enrollment public function getEnrollment($userId, $courseId) { $stmt = $this->pdo->prepare(" SELECT * FROM enrollments WHERE user_id = ? AND course_id = ? "); $stmt->execute([$userId, $courseId]); return $stmt->fetch(); } // Update lesson progress public function updateProgress($enrollmentId, $lessonId, $completed = true, $position = 0) { // Check if progress exists $stmt = $this->pdo->prepare(" SELECT id FROM lesson_progress WHERE enrollment_id = ? AND lesson_id = ? "); $stmt->execute([$enrollmentId, $lessonId]); $progress = $stmt->fetch(); if ($progress) { // Update $stmt = $this->pdo->prepare(" UPDATE lesson_progress SET is_completed = ?, video_position = ?, completed_at = ? WHERE enrollment_id = ? AND lesson_id = ? "); $completedAt = $completed ? date('Y-m-d H:i:s') : null; $stmt->execute([$completed, $position, $completedAt, $enrollmentId, $lessonId]); } else { // Insert $stmt = $this->pdo->prepare(" INSERT INTO lesson_progress (enrollment_id, lesson_id, is_completed, video_position, completed_at) VALUES (?, ?, ?, ?, ?) "); $completedAt = $completed ? date('Y-m-d H:i:s') : null; $stmt->execute([$enrollmentId, $lessonId, $completed, $position, $completedAt]); } // Calculate overall progress $progress = calculateProgress($enrollmentId); // Update enrollment $stmt = $this->pdo->prepare(" UPDATE enrollments SET progress = ?, last_accessed = NOW() WHERE id = ? "); $stmt->execute([$progress, $enrollmentId]); // Check if course is completed if ($progress == 100) { $this->completeCourse($enrollmentId); } return $progress; } // Complete course public function completeCourse($enrollmentId) { // Get enrollment details $stmt = $this->pdo->prepare(" SELECT * FROM enrollments WHERE id = ? "); $stmt->execute([$enrollmentId]); $enrollment = $stmt->fetch(); if (!$enrollment || $enrollment['is_completed']) { return false; } // Update enrollment $stmt = $this->pdo->prepare(" UPDATE enrollments SET is_completed = 1, completed_at = NOW(), progress = 100 WHERE id = ? "); $stmt->execute([$enrollmentId]); // Generate certificate $certNumber = generateCertificateNumber($enrollment['user_id'], $enrollment['course_id']); // In a real app, you'd generate a PDF certificate $certUrl = '/certificates/' . $certNumber . '.pdf'; $stmt = $this->pdo->prepare(" INSERT INTO certificates (user_id, course_id, certificate_number, certificate_url) VALUES (?, ?, ?, ?) "); $stmt->execute([$enrollment['user_id'], $enrollment['course_id'], $certNumber, $certUrl]); // Log interaction logUserInteraction($enrollment['user_id'], $enrollment['course_id'], 'complete'); return true; } // Add course review public function addReview($userId, $courseId, $rating, $title, $content) { // Check if already reviewed $stmt = $this->pdo->prepare("SELECT id FROM course_reviews WHERE user_id = ? AND course_id = ?"); $stmt->execute([$userId, $courseId]); if ($stmt->fetch()) { return ['success' => false, 'message' => 'You have already reviewed this course']; } // Check if enrolled and completed $stmt = $this->pdo->prepare(" SELECT id FROM enrollments WHERE user_id = ? AND course_id = ? AND is_completed = 1 "); $stmt->execute([$userId, $courseId]); if (!$stmt->fetch()) { return ['success' => false, 'message' => 'You must complete the course to review it']; } // Insert review $stmt = $this->pdo->prepare(" INSERT INTO course_reviews (user_id, course_id, rating, title, content) VALUES (?, ?, ?, ?, ?) "); if ($stmt->execute([$userId, $courseId, $rating, $title, $content])) { // Update course rating $this->updateCourseRating($courseId); // Log interaction logUserInteraction($userId, $courseId, 'review', ['rating' => $rating]); return ['success' => true, 'review_id' => $this->pdo->lastInsertId()]; } return ['success' => false, 'message' => 'Failed to add review']; } // Update course rating private function updateCourseRating($courseId) { $stmt = $this->pdo->prepare(" UPDATE courses SET rating_avg = (SELECT AVG(rating) FROM course_reviews WHERE course_id = ? AND is_approved = 1), review_count = (SELECT COUNT(*) FROM course_reviews WHERE course_id = ? AND is_approved = 1) WHERE id = ? "); $stmt->execute([$courseId, $courseId, $courseId]); } // Add to wishlist public function addToWishlist($userId, $courseId) { $stmt = $this->pdo->prepare(" INSERT IGNORE INTO wishlists (user_id, course_id) VALUES (?, ?) "); if ($stmt->execute([$userId, $courseId])) { logUserInteraction($userId, $courseId, 'wishlist'); return true; } return false; } // Remove from wishlist public function removeFromWishlist($userId, $courseId) { $stmt = $this->pdo->prepare(" DELETE FROM wishlists WHERE user_id = ? AND course_id = ? "); return $stmt->execute([$userId, $courseId]); } // Get user's wishlist public function getWishlist($userId) { $stmt = $this->pdo->prepare(" SELECT c.*, w.created_at as added_at FROM wishlists w JOIN courses c ON w.course_id = c.id WHERE w.user_id = ? ORDER BY w.created_at DESC "); $stmt->execute([$userId]); return $stmt->fetchAll(); } // Get user's enrolled courses public function getEnrolledCourses($userId) { $stmt = $this->pdo->prepare(" SELECT c.*, e.progress, e.enrolled_at, e.is_completed, e.completed_at FROM enrollments e JOIN courses c ON e.course_id = c.id WHERE e.user_id = ? ORDER BY e.last_accessed DESC "); $stmt->execute([$userId]); return $stmt->fetchAll(); } }

API ENDPOINTS

api/courses.php

<?php require_once '../config.php'; $action = $_GET['action'] ?? ''; switch ($action) { case 'get': $id = $_GET['id'] ?? 0; $slug = $_GET['slug'] ?? ''; $course = new Course($pdo); if ($id) { $data = $course->getById($id); } elseif ($slug) { $data = $course->getBySlug($slug); } else { jsonResponse(['success' => false, 'message' => 'Course ID or slug required'], 400); } if ($data) { jsonResponse(['success' => true, 'course' => $data]); } else { jsonResponse(['success' => false, 'message' => 'Course not found'], 404); } break; case 'search': $query = $_GET['q'] ?? ''; $page = $_GET['page'] ?? 1; $sort = $_GET['sort'] ?? 'relevance'; $filters = [ 'category' => $_GET['category'] ?? null, 'level' => $_GET['level'] ?? null, 'language' => $_GET['language'] ?? null, 'price' => $_GET['price'] ?? null, 'min_price' => $_GET['min_price'] ?? null, 'max_price' => $_GET['max_price'] ?? null, 'rating' => $_GET['rating'] ?? null ]; $course = new Course($pdo); $results = $course->search($query, $filters, $sort, $page); jsonResponse(['success' => true, 'courses' => $results]); break; case 'enroll': if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $course = new Course($pdo); $result = $course->enroll(getCurrentUserId(), $data['course_id'], $data['payment_id'] ?? null); jsonResponse($result); break; case 'progress': if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $course = new Course($pdo); $progress = $course->updateProgress( $data['enrollment_id'], $data['lesson_id'], $data['completed'] ?? true, $data['position'] ?? 0 ); jsonResponse(['success' => true, 'progress' => $progress]); break; case 'review': if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $course = new Course($pdo); $result = $course->addReview( getCurrentUserId(), $data['course_id'], $data['rating'], $data['title'], $data['content'] ); jsonResponse($result); break; case 'wishlist': if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $course = new Course($pdo); if ($_SERVER['REQUEST_METHOD'] === 'GET') { $wishlist = $course->getWishlist(getCurrentUserId()); jsonResponse(['success' => true, 'wishlist' => $wishlist]); } elseif ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = json_decode(file_get_contents('php://input'), true); $result = $course->addToWishlist(getCurrentUserId(), $data['course_id']); jsonResponse(['success' => $result]); } elseif ($_SERVER['REQUEST_METHOD'] === 'DELETE') { $data = json_decode(file_get_contents('php://input'), true); $result = $course->removeFromWishlist(getCurrentUserId(), $data['course_id']); jsonResponse(['success' => $result]); } break; case 'enrolled': if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $course = new Course($pdo); $courses = $course->getEnrolledCourses(getCurrentUserId()); jsonResponse(['success' => true, 'courses' => $courses]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/recommendations.php

<?php require_once '../config.php'; $action = $_GET['action'] ?? 'personalized'; $userId = getCurrentUserId() ?? 0; $recommender = new CourseRecommender($pdo); switch ($action) { case 'personalized': if (!$userId) { // Not logged in, show popular courses $courses = $recommender->getPopularCourses($_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $courses]); } else { $recommendations = $recommender->getRecommendations($userId, 'personalized', $_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); } break; case 'trending': $recommendations = $recommender->getTrendingCourses($_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'popular': $recommendations = $recommender->getPopularCourses($_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'new': $recommendations = $recommender->getNewCourses($_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'skill_gap': if (!$userId) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $recommendations = $recommender->getSkillGapRecommendations($userId, $_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'similar': $courseId = $_GET['course_id'] ?? 0; if (!$courseId) { jsonResponse(['success' => false, 'message' => 'Course ID required'], 400); } $recommendations = $recommender->getSimilarCourses($courseId, $_GET['limit'] ?? 10); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'learning_path': if (!$userId) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $recommendations = $recommender->getLearningPathRecommendations($userId, $_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'category': $categoryId = $_GET['category_id'] ?? 0; if (!$categoryId) { jsonResponse(['success' => false, 'message' => 'Category ID required'], 400); } if (!$userId) { $recommendations = $recommender->getCategoryRecommendations(0, $categoryId, $_GET['limit'] ?? RECOMMENDATION_LIMIT); } else { $recommendations = $recommender->getCategoryRecommendations($userId, $categoryId, $_GET['limit'] ?? RECOMMENDATION_LIMIT); } jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

PROJECT STRUCTURE

lms-platform/ │ ├── index.html # Main landing page ├── course.html # Course detail page ├── dashboard.html # Student dashboard ├── instructor.html # Instructor dashboard ├── admin.html # Admin dashboard ├── search.html # Search results ├── profile.html # User profile ├── checkout.html # Checkout page ├── lesson.html # Lesson player │ ├── config.php # Database configuration ├── helpers.php # Helper functions │ ├── classes/ │ ├── CourseRecommender.php # Recommendation engine │ ├── Course.php # Course management │ └── User.php # User management │ ├── api/ │ ├── auth.php # Authentication endpoints │ ├── courses.php # Course endpoints │ ├── recommendations.php # Recommendation endpoints │ ├── users.php # User endpoints │ └── payments.php # Payment endpoints │ ├── assets/ │ ├── images/ │ │ ├── course-placeholder.jpg │ │ └── default-avatar.png │ └── css/ │ └── style.css │ └── uploads/ ├── courses/ ├── lessons/ ├── assignments/ ├── profiles/ └── certificates/

FEATURES SUMMARY

✅ Student Features

  • Course browsing & search
  • Course enrollment
  • Video lessons with progress tracking
  • Quizzes and assignments
  • Notes and bookmarks
  • Course completion certificates
  • Discussion forums
  • Personal dashboard

✅ Instructor Features

  • Course creation wizard
  • Lesson management
  • Quiz builder
  • Assignment grading
  • Student analytics
  • Earnings dashboard
  • Discussion moderation

✅ Admin Features

  • User management
  • Course approval
  • Category management
  • Platform analytics
  • Payment processing
  • Report generation

✅ Recommendation System

  • Personalized course recommendations
  • Skill gap analysis
  • Learning path suggestions
  • Similar courses
  • Trending and popular courses
  • Category-based suggestions
  • Prerequisite-based recommendations

✅ Recommendation Algorithms

  • Content-based filtering
  • Collaborative filtering
  • Skill-based matching
  • Learning path optimization
  • Popularity scoring
  • Recency weighting
  • Category affinity

This comprehensive Learning Management System includes a sophisticated recommendation engine that analyzes user skills, learning history, and career goals to provide personalized course suggestions and learning paths. The system helps students discover relevant content and achieve their educational objectives efficiently.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper