# fluent-community/trunk/Modules/Course/Http/Controllers/CourseController.php

FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS &amp; Online Courses, version trunk. 592 lines.

- Page: https://pluginprobe.com/plugins/fluent-community/trunk/code/Modules/Course/Http/Controllers/CourseController.php
- Raw: https://pluginprobe.com/plugins/fluent-community/trunk/raw/Modules/Course/Http/Controllers/CourseController.php
- Modified: 2026-08-19T15:36:26+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-community/trunk/code/Modules/Course/Http/Controllers/CourseController.php#L10-L20`.

```php
<?php

namespace FluentCommunity\Modules\Course\Http\Controllers;

use FluentCommunity\App\Http\Controllers\Controller;
use FluentCommunity\App\Models\SpaceUserPivot;
use FluentCommunity\App\Services\Helper;
use FluentCommunity\App\Services\LockscreenService;
use FluentCommunity\Framework\Http\Request\Request;
use FluentCommunity\Framework\Support\Arr;
use FluentCommunity\Modules\Course\Model\Course;
use FluentCommunity\Modules\Course\Model\CourseLesson;
use FluentCommunity\Modules\Course\Model\CourseTopic;
use FluentCommunity\App\Services\FeedsHelper;
use FluentCommunity\Modules\Course\Services\CourseHelper;
use FluentCommunity\Modules\Course\Services\LessonVideoGateService;

class CourseController extends Controller
{
    public function getCourses(Request $request)
    {
        $user = $this->getUser();
        $isCourseCreator = $user && $user->hasCourseCreatorAccess();
        $enrolledIds = CourseHelper::getEnrolledCourseIds();

        $search = $request->getSafe('search');
        $topicSlug = $request->getSafe('topic_slug');
        $isEnrolled = $request->getSafe('type') == 'enrolled';
        $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'alphabetical');

        $courses = Course::searchBy($search)
            ->where(function ($q) use ($isCourseCreator, $isEnrolled, $user) {
                if ($isCourseCreator && !$isEnrolled) {
                    $q->where('status', 'published')
                      ->orWhere('created_by', $user->ID);
                } else {
                    $q->where('status', 'published');
                }
            })
            ->byPostTopic($topicSlug)
            ->where(function ($q) use ($enrolledIds) {
                $q->whereIn('privacy', ['public', 'private']);
                $q->orWhereIn('id', $enrolledIds);
            })
            ->when($isEnrolled, function ($q) use ($enrolledIds) {
                $q->whereIn('id', $enrolledIds);
            })
            ->when($sortBy == 'alphabetical', function ($q) {
                $q->orderBy('title', 'ASC');
            })
            ->when($sortBy == 'oldest', function ($q) {
                $q->orderBy('created_at', 'ASC');
            })
            ->when($sortBy == 'latest', function ($q) {
                $q->orderBy('created_at', 'DESC');
            })
            ->paginate();

        foreach ($courses as $course) {
            $course->isEnrolled = CourseHelper::isEnrolled($course->id);
            if ($course->isEnrolled) {
                $course->progress = CourseHelper::getCourseProgress($course->id);
            }

            if (!$course->cover_photo) {
                $course->cover_photo = FLUENT_COMMUNITY_PLUGIN_URL . 'assets/images/course-placeholder.jpg';
            }

            $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count();
            $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count();
            if (Arr::get($course->settings, 'hide_members_count') != 'yes') {
                $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count();
            } else {
                $course->studentsCount = 0;
            }

            do_action_ref_array('fluent_community/course', [&$course]);
        }

        $data = [
            'courses'           => $courses,
            'course_categories' => $request->get('with_categories') ? CourseHelper::getCourseCategories() : []
        ];

        return apply_filters('fluent_community/courses_api_response', $data, $request->all());
    }

    public function getCourse(Request $request, $courseId)
    {
        $user = $this->getUser();

        $course = Course::findOrFail($courseId);
        /** @var Course $course */

        $isCourseCreator = $course->isCourseAdmin($user);

        if ($course->status !== 'published' && !$isCourseCreator) {
            return $this->sendError([
                'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community')
            ]);
        }

        $intendtedLessonSlug = $request->get('intended_lesson_slug');

        $data = $this->processCourse($course, $isCourseCreator, $intendtedLessonSlug);

        return apply_filters('fluent_community/get_course_api_response', $data, $request->all());
    }

    public function getCourseBySlug(Request $request, $slug)
    {
        $user = $this->getUser();

        $course = Course::where('slug', $slug)->firstOrFail();
        /** @var Course $course */

        $isCourseCreator = $course->isCourseAdmin($user);

        if ($course->status !== 'published' && !$isCourseCreator) {
            return $this->sendError([
                'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community')
            ]);
        }

        $hideInstructorView = Arr::get($course->settings, 'hide_instructor_view') == 'yes';
        $showStudentCount = Arr::get($course->settings, 'show_instructor_students_count') == 'yes';
        if (!$hideInstructorView) {
            $course->load(['creator']);
            if ($course->creator) {
                $creatorCourseIds = Course::where('created_by', $course->creator->user_id)->pluck('id');
                $course->creator->total_courses = count($creatorCourseIds);
                if ($showStudentCount) {
                    $course->creator->total_students = SpaceUserPivot::whereIn('space_id', $creatorCourseIds)->distinct('user_id')->count('user_id');
                }
                if ($course->creator->short_description) {
                    $course->creator->short_description_rendered = wp_kses_post(FeedsHelper::mdToHtml($course->creator->short_description));
                }
            }
        }

        $intendtedLessonSlug = $request->get('intended_lesson_slug');

        $data = $this->processCourse($course, $isCourseCreator, $intendtedLessonSlug);

        return apply_filters('fluent_community/course_api_response', $data, $request->all());
    }

    public function getLessonBySlug(Request $request, $courseSlug, $lessonSlug)
    {
        $user = $this->getUser();

        /** @var Course $course */
        $course = Course::where('slug', $courseSlug)->firstOrFail();

        $isCourseCreator = $course->isCourseAdmin($user);

        if ($course->status !== 'published' && !$isCourseCreator) {
            return $this->sendError([
                'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community')
            ]);
        }

        $lesson = CourseLesson::where('space_id', $course->id)
            ->where('slug', $lessonSlug)
            ->when(!$isCourseCreator, function ($q) {
                $q->where('status', 'published');
            })
            ->with(['topic'])
            ->firstOrFail();

        if (!$lesson || !$lesson->topic) {
            return $this->sendError([
                'message' => __('Lesson not found. maybe the lesson is not published yet!', 'fluent-community')
            ]);
        }

        $enrollment = CourseHelper::getCourseEnrollment($course->id);

        if ($course->privacy == 'secret' && !$enrollment && !$isCourseCreator) {
            return $this->sendError([
                'message' => __('You must need to be invited to view this course', 'fluent-community')
            ]);
        }

        $section = $lesson->topic;
        $hasAcessSectionAcess = !!$enrollment;
        $unlockDate = '';
        $courseType = $course->getCourseType();
        $hasPublicViewAccess = $course->privacy == 'public' && Arr::get($course->settings, 'public_lesson_view') == 'yes' && $courseType == 'self_paced';
        if ($hasAcessSectionAcess) {
            $unlockDate = CourseHelper::getSectionAccessDate($section, $courseType, $enrollment);
            if ($unlockDate && strtotime($unlockDate) > current_time('timestamp')) {
                $hasAcessSectionAcess = false;
            } else {
                $unlockDate = null;
            }
        }

        $initialCanView = $hasAcessSectionAcess || $isCourseCreator || $hasPublicViewAccess;
        $accessInfo     = CourseHelper::resolveLessonAccess($initialCanView, $lesson, $course, $this->getUser(), [
            'enrollment'         => $enrollment,
            'is_admin'           => $isCourseCreator,
            'has_section_access' => $hasAcessSectionAcess,
            'has_public_access'  => $hasPublicViewAccess,
        ]);
        $canViewLesson  = $accessInfo['can_view'];
        $lockType       = $accessInfo['lock_type'];

        $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $user, [
            'can_view'      => $canViewLesson,
            'parse_content' => $canViewLesson,
            'is_locked'     => !$canViewLesson && !$isCourseCreator,
            'unlock_date'   => $unlockDate,
            'lock_type'     => $lockType
        ]);

        $data = [
            'lesson' => $formattedLesson
        ];

        return apply_filters('fluent_community/course_lesson_api_response', $data, $request->all());
    }

    private function processCourse(Course $course, $isCourseCreator, $intendtedLessonSlug = '')
    {
        $sections = CourseTopic::where('space_id', $course->id)
            ->orderBy('priority', 'ASC')
            ->orderBy('id', 'ASC')
            ->with(['lessons' => function ($query) use ($isCourseCreator) {
                if (!$isCourseCreator) {
                    $query->where('status', 'published');
                }
            }])
            ->get();

        $enrollment = CourseHelper::getCourseEnrollment($course->id);

        $currentUser = Helper::getCurrentUser();

        if ($course->privacy == 'private' && !$enrollment) {
            $course->lockscreen_config = LockscreenService::getLockscreenConfig($course, null, true);
        }

        if ($course->privacy == 'secret' && !$enrollment && !$isCourseCreator) {
            return $this->sendError([
                'message' => __('You must need to be invited to view this course', 'fluent-community')
            ]);
        }

        $course->is_course_admin = $isCourseCreator;
        $course->can_self_enroll = $isCourseCreator || ($course->status === 'published' && $course->privacy === 'public');

        $course = apply_filters('fluent_community/course/processed', $course, [
            'is_enrolled' => !!$enrollment,
        ]);

        $courseSettings = $course->settings;
        if (Arr::get($courseSettings, 'course_details')) {
            $courseSettings['course_details_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($courseSettings['course_details']));
            $course->settings = $courseSettings;
        }

        $courseType = $course->getCourseType();

        $hasPublicViewAccess = $course->privacy == 'public' && Arr::get($course->settings, 'public_lesson_view') == 'yes' && $courseType == 'self_paced';

        $formattedSections = [];

        foreach ($sections as $section) {
            if ($section->lessons->isEmpty()) {
                continue;
            }

            $hasAcessSectionAcess = !!$enrollment;
            $unlockDate = '';

            if ($hasAcessSectionAcess) {
                $unlockDate = CourseHelper::getSectionAccessDate($section, $courseType, $enrollment);
                if ($unlockDate && strtotime($unlockDate) > current_time('timestamp')) {
                    $hasAcessSectionAcess = false;
                } else {
                    $unlockDate = null;
                }
            }

            $sectionCtx = [
                'enrollment'         => $enrollment,
                'is_admin'           => $isCourseCreator,
                'has_section_access' => $hasAcessSectionAcess,
                'has_public_access'  => $hasPublicViewAccess,
            ];

            $formattedLessons = [];
            foreach ($section->lessons as $lesson) {
                $initialCanView = $hasAcessSectionAcess || $isCourseCreator || $hasPublicViewAccess;
                $accessInfo     = CourseHelper::resolveLessonAccess($initialCanView, $lesson, $course, $this->getUser(), $sectionCtx);
                $canViewLesson  = $accessInfo['can_view'];
                $lockType       = $accessInfo['lock_type'];

                $parseContent = $intendtedLessonSlug === $lesson->slug && $canViewLesson;

                $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $currentUser, [
                    'can_view'      => $canViewLesson,
                    'parse_content' => $parseContent,
                    'is_locked'     => !$canViewLesson && !$isCourseCreator,
                    'unlock_date'   => $unlockDate,
                    'lock_type'     => $lockType
                ]);

                $isLazyLoad = !$parseContent;
                if(!$canViewLesson && $lockType !== 'sequential') {
                    $isLazyLoad = false;
                }

                $formattedLesson['lazy_load'] = $isLazyLoad;

                $formattedLessons[] = $formattedLesson;
            }

            $formattedSections[] = [
                'id'          => $section->id,
                'title'       => $section->title,
                'lessons'     => $formattedLessons,
                'created_at'  => $section->created_at,
                'slug'        => $section->slug,
                'type'        => 'section',
                'is_locked'   => !$hasAcessSectionAcess && !$isCourseCreator,
                'unlock_date' => $unlockDate
            ];
        }

        return [
            'course'   => $course,
            'sections' => $formattedSections,
            'track'    => CourseHelper::getCourseProgressTrack($course->id)
        ];
    }

    public function enrollCourse(Request $request, $courseId)
    {
        $user = $this->getUser();

        if (!$user) {
            return $this->sendError([
                'message' => __('You need to login to enroll in this course.', 'fluent-community')
            ]);
        }

        $course = Course::findOrFail($courseId);
        /** @var Course $course */

        $isCourseCreator = $course->isCourseAdmin($user);

        if ($course->status !== 'published' && !$isCourseCreator) {
            return $this->sendError([
                'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community')
            ]);
        }

        if (!$isCourseCreator && $course->privacy != 'public') {
            return $this->sendError([
                'message' => __('You are not allowed to enroll in this course.', 'fluent-community')
            ]);
        }

        $enrolled = CourseHelper::enrollCourse($course, get_current_user_id());

        if (!$enrolled) {
            return [
                'message' => __('You are already enrolled in this course.', 'fluent-community'),
                'track'   => CourseHelper::getCourseProgressTrack($courseId)
            ];
        }

        return [
            'message' => __('You have been enrolled in this course.', 'fluent-community'),
            'track'   => CourseHelper::getCourseProgressTrack($courseId)
        ];
    }

    public function updateCompletionLesson(Request $request, $courseId, $lessonId)
    {
        $course = Course::findOrFail($courseId);

        if ($course->status != 'published') {
            return $this->sendError([
                'message' => __('Course is not in published state.', 'fluent-community')
            ]);
        }


        if (!CourseHelper::isEnrolled($courseId)) {
            return $this->sendError([
                'message' => __('Please enroll this course first', 'fluent-community')
            ]);
        }

        $lesson = CourseLesson::where('space_id', $courseId)
            ->where('status', 'published')
            ->where('id', $lessonId)
            ->first();

        if (!$lesson) {
            return $this->sendError([
                'message' => __('Lesson is not on published state.', 'fluent-community')
            ]);
        }

        $state = $request->get('state');

        if (!in_array($state, ['completed', 'incomplete'])) {
            return $this->sendError([
                'message' => __('Invalid state.', 'fluent-community')
            ]);
        }

        $isAllowed = apply_filters('fluent_community/is_allowed_to_complete_lesson', true, $lesson, $state);

        if (!$isAllowed) {
            $errorData = [
                'message' => __('You are not allowed to complete this lesson.', 'fluent-community')
            ];

            if (LessonVideoGateService::isCompletionBlockedFor($lesson, Helper::getCurrentUser())) {
                $errorData['code'] = 'video_watch_required';
                $errorData['threshold'] = LessonVideoGateService::getThreshold($lesson);
            }

            return $this->sendError($errorData);
        }

        CourseHelper::updateLessonCompletion($lesson, get_current_user_id(), $state);
        $track = CourseHelper::getCourseProgressTrack($courseId);
        $isCompleted = $track['progress'] == 100;

        if ($isCompleted) {
            CourseHelper::completeCourse($course, $lesson, get_current_user_id());
        }

        return [
            'message'      => __('Lesson completion state updated.', 'fluent-community'),
            'track'        => $track,
            'is_completed' => $isCompleted
        ];
    }

    public function markLessonVideoWatched(Request $request, $courseId, $lessonId)
    {
        $course = Course::findOrFail($courseId);

        if ($course->status != 'published') {
            return $this->sendError([
                'message' => __('Course is not in published state.', 'fluent-community')
            ]);
        }

        if (!CourseHelper::isEnrolled($courseId)) {
            return $this->sendError([
                'message' => __('Please enroll this course first', 'fluent-community')
            ]);
        }

        $lesson = CourseLesson::where('space_id', $courseId)
            ->where('status', 'published')
            ->where('id', $lessonId)
            ->first();

        if (!$lesson) {
            return $this->sendError([
                'message' => __('Lesson is not on published state.', 'fluent-community')
            ]);
        }

        if (!LessonVideoGateService::isGatedLesson($lesson)) {
            return [
                'message'  => __('This lesson does not require video completion.', 'fluent-community'),
                'is_gated' => false,
                'watched'  => false
            ];
        }

        $watchedPercent = min(100, absint($request->get('watched_percent')));
        $threshold = LessonVideoGateService::getThreshold($lesson);

        if ($watchedPercent < $threshold) {
            return $this->sendError([
                /* translators: %d is the watched percentage required to complete the lesson */
                'message' => sprintf(__('Please watch at least %d%% of the video.', 'fluent-community'), $threshold)
            ]);
        }

        LessonVideoGateService::markWatched($lesson, get_current_user_id());

        return [
            'message'  => __('Video watch progress has been recorded.', 'fluent-community'),
            'is_gated' => true,
            'watched'  => true
        ];
    }

    public function resetMyProgress(Request $request, $courseId)
    {
        $course = Course::findOrFail($courseId);

        if ($course->status !== 'published') {
            return $this->sendError([
                'message' => __('Course is not in published state.', 'fluent-community')
            ]);
        }

        if (!CourseHelper::isEnrolled($courseId)) {
            return $this->sendError([
                'message' => __('Please enroll this course first', 'fluent-community')
            ]);
        }

        CourseHelper::resetCourseProgress($courseId, get_current_user_id());

        return [
            'message' => __('Your course progress has been reset.', 'fluent-community'),
            'track'   => [
                'completed_lessons' => [],
                'isEnrolled'        => true,
                'progress'          => 0
            ]
        ];
    }

    public function getAllCourses(Request $request)
    {
        $user = $this->getUser();
        $isCourseCreator = $user && $user->hasCourseCreatorAccess();

        $coursesQuery = Course::where(function ($q) use ($isCourseCreator, $user) {
                if ($isCourseCreator) {
                    $q->where('status', 'published')
                      ->orWhere('created_by', $user->ID);
                } else {
                    $q->where('status', 'published');
                }
            });

        if (!($user && $user->isCommunityModerator())) {
            $userId = get_current_user_id();
            $coursesQuery->where(function ($q) use ($userId) {
                $q->whereIn('privacy', ['public', 'private'])
                    ->orWhereHas('space_pivot', function ($sub) use ($userId) {
                        $sub->where('user_id', $userId)
                            ->where('role', 'student');
                    })
                    ->orWhere('created_by', $userId);
            });
        }

        $courses = $coursesQuery->with(['creator'])->paginate();

        $formattedCourses = [];
        foreach ($courses as $course) {
            $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count();
            $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count();

            if (Arr::get($course->settings, 'hide_members_count') != 'yes') {
                $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count();
            } else {
                $course->studentsCount = 0;
            }

            $showInstructorView = Arr::get($course->settings, 'hide_instructor_view') != 'yes';
            $showStudentCount = Arr::get($course->settings, 'show_instructor_students_count') == 'yes';
            if ($showInstructorView) {
                if ($course->creator) {
                    $creatorCourseIds = Course::where('created_by', $course->creator->user_id)->pluck('id');
                    $course->creator->total_courses = count($creatorCourseIds);
                    if ($showStudentCount) {
                        $course->creator->total_students = SpaceUserPivot::whereIn('space_id', $creatorCourseIds)->distinct('user_id')->count('user_id');
                    }
                    if ($course->creator->short_description) {
                        $course->creator->short_description_rendered = wp_kses_post(FeedsHelper::mdToHtml($course->creator->short_description));
                    }
                }
            }

            $formattedCourses[] = $this->processCourse($course, $course->isCourseAdmin($user));
        }

        return apply_filters('fluent_community/all_courses_api_response', [
            'courses' => $formattedCourses,
            'total'   => $courses->total()
        ], $request->all());
    }
}

```
