← All changes
|
Modules/Course/Http/Controllers/CourseController.php
+394
-81
1.0.93
→
2.10.01
View file →
| @@ -3,15 +3,18 @@ | ||
| 3 | 3 | namespace FluentCommunity\Modules\Course\Http\Controllers; |
| 4 | 4 | |
| 5 | 5 | use FluentCommunity\App\Http\Controllers\Controller; |
| 6 | 6 | use FluentCommunity\App\Models\SpaceUserPivot; |
| 7 | -use FluentCommunity\App\Models\User; | |
| 7 | +use FluentCommunity\App\Services\Helper; | |
| 8 | 8 | use FluentCommunity\App\Services\LockscreenService; |
| 9 | 9 | use FluentCommunity\Framework\Http\Request\Request; |
| 10 | +use FluentCommunity\Framework\Support\Arr; | |
| 10 | 11 | use FluentCommunity\Modules\Course\Model\Course; |
| 11 | 12 | use FluentCommunity\Modules\Course\Model\CourseLesson; |
| 12 | 13 | use FluentCommunity\Modules\Course\Model\CourseTopic; |
| 14 | +use FluentCommunity\App\Services\FeedsHelper; | |
| 13 | 15 | use FluentCommunity\Modules\Course\Services\CourseHelper; |
| 16 | +use FluentCommunity\Modules\Course\Services\LessonVideoGateService; | |
| 14 | 17 | |
| 15 | 18 | class CourseController extends Controller |
| 16 | 19 | { |
| 17 | 20 | public function getCourses(Request $request) |
| @@ -16,34 +19,44 @@ | ||
| 16 | 19 | { |
| 17 | 20 | public function getCourses(Request $request) |
| 18 | 21 | { |
| 19 | 22 | $user = $this->getUser(); |
| 20 | - $isModerator = $user && $user->isCommunityModerator(); | |
| 23 | + $isCourseCreator = $user && $user->hasCourseCreatorAccess(); | |
| 24 | + $enrolledIds = CourseHelper::getEnrolledCourseIds(); | |
| 21 | 25 | |
| 22 | - if ($request->get('type') == 'enrolled') { | |
| 23 | - $courses = Course::searchBy($request->get('search')) | |
| 24 | - ->where('status', 'published') | |
| 25 | - ->orderBy('title', 'ASC') | |
| 26 | - ->byPostTopic($request->get('topic_slug')) | |
| 27 | - ->whereIn('id', CourseHelper::getEnrolledCourseIds()) | |
| 28 | - ->paginate(); | |
| 29 | - } else { | |
| 30 | - $courses = Course::searchBy($request->get('search')) | |
| 31 | - ->when(!$isModerator, function ($q) { | |
| 26 | + $search = $request->getSafe('search'); | |
| 27 | + $topicSlug = $request->getSafe('topic_slug'); | |
| 28 | + $isEnrolled = $request->getSafe('type') == 'enrolled'; | |
| 29 | + $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'alphabetical'); | |
| 30 | + | |
| 31 | + $courses = Course::searchBy($search) | |
| 32 | + ->where(function ($q) use ($isCourseCreator, $isEnrolled, $user) { | |
| 33 | + if ($isCourseCreator && !$isEnrolled) { | |
| 34 | + $q->where('status', 'published') | |
| 35 | + ->orWhere('created_by', $user->ID); | |
| 36 | + } else { | |
| 32 | 37 | $q->where('status', 'published'); |
| 33 | - }) | |
| 34 | - ->byPostTopic($request->get('topic_slug')) | |
| 35 | - ->orderBy('title', 'ASC') | |
| 36 | - ->where(function ($q) { | |
| 37 | - $q->whereIn('privacy', ['public', 'private']); | |
| 38 | - if ($enrolledIds = CourseHelper::getEnrolledCourseIds()) { | |
| 39 | - $q->orWhereIn('id', $enrolledIds); | |
| 40 | - } | |
| 41 | - }) | |
| 42 | - ->paginate(); | |
| 43 | - } | |
| 38 | + } | |
| 39 | + }) | |
| 40 | + ->byPostTopic($topicSlug) | |
| 41 | + ->where(function ($q) use ($enrolledIds) { | |
| 42 | + $q->whereIn('privacy', ['public', 'private']); | |
| 43 | + $q->orWhereIn('id', $enrolledIds); | |
| 44 | + }) | |
| 45 | + ->when($isEnrolled, function ($q) use ($enrolledIds) { | |
| 46 | + $q->whereIn('id', $enrolledIds); | |
| 47 | + }) | |
| 48 | + ->when($sortBy == 'alphabetical', function ($q) { | |
| 49 | + $q->orderBy('title', 'ASC'); | |
| 50 | + }) | |
| 51 | + ->when($sortBy == 'oldest', function ($q) { | |
| 52 | + $q->orderBy('created_at', 'ASC'); | |
| 53 | + }) | |
| 54 | + ->when($sortBy == 'latest', function ($q) { | |
| 55 | + $q->orderBy('created_at', 'DESC'); | |
| 56 | + }) | |
| 57 | + ->paginate(); | |
| 44 | 58 | |
| 45 | - | |
| 46 | 59 | foreach ($courses as $course) { |
| 47 | 60 | $course->isEnrolled = CourseHelper::isEnrolled($course->id); |
| 48 | 61 | if ($course->isEnrolled) { |
| 49 | 62 | $course->progress = CourseHelper::getCourseProgress($course->id); |
| @@ -54,59 +67,168 @@ | ||
| 54 | 67 | } |
| 55 | 68 | |
| 56 | 69 | $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count(); |
| 57 | 70 | $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count(); |
| 58 | - $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count(); | |
| 71 | + if (Arr::get($course->settings, 'hide_members_count') != 'yes') { | |
| 72 | + $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count(); | |
| 73 | + } else { | |
| 74 | + $course->studentsCount = 0; | |
| 75 | + } | |
| 76 | + | |
| 77 | + do_action_ref_array('fluent_community/course', [&$course]); | |
| 59 | 78 | } |
| 60 | 79 | |
| 61 | - return [ | |
| 80 | + $data = [ | |
| 62 | 81 | 'courses' => $courses, |
| 63 | - 'course_categories' => $request->get('with_categories') ? CourseHelper::getCourseCategories() : [] | |
| 82 | + 'course_categories' => $request->get('with_categories') ? CourseHelper::getCourseCategories() : [] | |
| 64 | 83 | ]; |
| 84 | + | |
| 85 | + return apply_filters('fluent_community/courses_api_response', $data, $request->all()); | |
| 65 | 86 | } |
| 66 | 87 | |
| 67 | 88 | public function getCourse(Request $request, $courseId) |
| 68 | 89 | { |
| 69 | 90 | $user = $this->getUser(); |
| 70 | - $isModerator = $user && $user->isCommunityModerator(); | |
| 71 | 91 | |
| 72 | - $course = Course::when(!$isModerator, function ($q) { | |
| 73 | - $q->where('status', 'published'); | |
| 74 | - })->find($courseId); | |
| 92 | + $course = Course::findOrFail($courseId); | |
| 93 | + /** @var Course $course */ | |
| 75 | 94 | |
| 76 | - if (!$course) { | |
| 95 | + $isCourseCreator = $course->isCourseAdmin($user); | |
| 96 | + | |
| 97 | + if ($course->status !== 'published' && !$isCourseCreator) { | |
| 77 | 98 | return $this->sendError([ |
| 78 | - 'message' => 'Course not found. maybe the course is not published yet!' | |
| 99 | + 'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community') | |
| 79 | 100 | ]); |
| 80 | 101 | } |
| 81 | 102 | |
| 82 | - return $this->processCourse($course, $isModerator); | |
| 103 | + $intendtedLessonSlug = $request->get('intended_lesson_slug'); | |
| 104 | + | |
| 105 | + $data = $this->processCourse($course, $isCourseCreator, $intendtedLessonSlug); | |
| 106 | + | |
| 107 | + return apply_filters('fluent_community/get_course_api_response', $data, $request->all()); | |
| 83 | 108 | } |
| 84 | 109 | |
| 85 | 110 | public function getCourseBySlug(Request $request, $slug) |
| 86 | 111 | { |
| 87 | 112 | $user = $this->getUser(); |
| 88 | - $isModerator = $user && $user->isCommunityModerator(); | |
| 89 | 113 | |
| 90 | - $course = Course::when(!$isModerator, function ($q) { | |
| 91 | - $q->where('status', 'published'); | |
| 92 | - })->where('slug', $slug)->firstOrFail(); | |
| 114 | + $course = Course::where('slug', $slug)->firstOrFail(); | |
| 115 | + /** @var Course $course */ | |
| 93 | 116 | |
| 94 | - if (!$course) { | |
| 117 | + $isCourseCreator = $course->isCourseAdmin($user); | |
| 118 | + | |
| 119 | + if ($course->status !== 'published' && !$isCourseCreator) { | |
| 95 | 120 | return $this->sendError([ |
| 96 | - 'message' => 'Course not found. maybe the course is not published yet!' | |
| 121 | + 'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community') | |
| 97 | 122 | ]); |
| 98 | 123 | } |
| 99 | 124 | |
| 100 | - return $this->processCourse($course, $isModerator); | |
| 125 | + $hideInstructorView = Arr::get($course->settings, 'hide_instructor_view') == 'yes'; | |
| 126 | + $showStudentCount = Arr::get($course->settings, 'show_instructor_students_count') == 'yes'; | |
| 127 | + if (!$hideInstructorView) { | |
| 128 | + $course->load(['creator']); | |
| 129 | + if ($course->creator) { | |
| 130 | + $creatorCourseIds = Course::where('created_by', $course->creator->user_id)->pluck('id'); | |
| 131 | + $course->creator->total_courses = count($creatorCourseIds); | |
| 132 | + if ($showStudentCount) { | |
| 133 | + $course->creator->total_students = SpaceUserPivot::whereIn('space_id', $creatorCourseIds)->distinct('user_id')->count('user_id'); | |
| 134 | + } | |
| 135 | + if ($course->creator->short_description) { | |
| 136 | + $course->creator->short_description_rendered = wp_kses_post(FeedsHelper::mdToHtml($course->creator->short_description)); | |
| 137 | + } | |
| 138 | + } | |
| 139 | + } | |
| 140 | + | |
| 141 | + $intendtedLessonSlug = $request->get('intended_lesson_slug'); | |
| 142 | + | |
| 143 | + $data = $this->processCourse($course, $isCourseCreator, $intendtedLessonSlug); | |
| 144 | + | |
| 145 | + return apply_filters('fluent_community/course_api_response', $data, $request->all()); | |
| 101 | 146 | } |
| 102 | 147 | |
| 103 | - private function processCourse($course, $isModerator) | |
| 148 | + public function getLessonBySlug(Request $request, $courseSlug, $lessonSlug) | |
| 104 | 149 | { |
| 150 | + $user = $this->getUser(); | |
| 151 | + | |
| 152 | + /** @var Course $course */ | |
| 153 | + $course = Course::where('slug', $courseSlug)->firstOrFail(); | |
| 154 | + | |
| 155 | + $isCourseCreator = $course->isCourseAdmin($user); | |
| 156 | + | |
| 157 | + if ($course->status !== 'published' && !$isCourseCreator) { | |
| 158 | + return $this->sendError([ | |
| 159 | + 'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community') | |
| 160 | + ]); | |
| 161 | + } | |
| 162 | + | |
| 163 | + $lesson = CourseLesson::where('space_id', $course->id) | |
| 164 | + ->where('slug', $lessonSlug) | |
| 165 | + ->when(!$isCourseCreator, function ($q) { | |
| 166 | + $q->where('status', 'published'); | |
| 167 | + }) | |
| 168 | + ->with(['topic']) | |
| 169 | + ->firstOrFail(); | |
| 170 | + | |
| 171 | + if (!$lesson || !$lesson->topic) { | |
| 172 | + return $this->sendError([ | |
| 173 | + 'message' => __('Lesson not found. maybe the lesson is not published yet!', 'fluent-community') | |
| 174 | + ]); | |
| 175 | + } | |
| 176 | + | |
| 177 | + $enrollment = CourseHelper::getCourseEnrollment($course->id); | |
| 178 | + | |
| 179 | + if ($course->privacy == 'secret' && !$enrollment && !$isCourseCreator) { | |
| 180 | + return $this->sendError([ | |
| 181 | + 'message' => __('You must need to be invited to view this course', 'fluent-community') | |
| 182 | + ]); | |
| 183 | + } | |
| 184 | + | |
| 185 | + $section = $lesson->topic; | |
| 186 | + $hasAcessSectionAcess = !!$enrollment; | |
| 187 | + $unlockDate = ''; | |
| 188 | + $courseType = $course->getCourseType(); | |
| 189 | + $hasPublicViewAccess = $course->privacy == 'public' && Arr::get($course->settings, 'public_lesson_view') == 'yes' && $courseType == 'self_paced'; | |
| 190 | + if ($hasAcessSectionAcess) { | |
| 191 | + $unlockDate = CourseHelper::getSectionAccessDate($section, $courseType, $enrollment); | |
| 192 | + if ($unlockDate && strtotime($unlockDate) > current_time('timestamp')) { | |
| 193 | + $hasAcessSectionAcess = false; | |
| 194 | + } else { | |
| 195 | + $unlockDate = null; | |
| 196 | + } | |
| 197 | + } | |
| 198 | + | |
| 199 | + $initialCanView = $hasAcessSectionAcess || $isCourseCreator || $hasPublicViewAccess; | |
| 200 | + $accessInfo = CourseHelper::resolveLessonAccess($initialCanView, $lesson, $course, $this->getUser(), [ | |
| 201 | + 'enrollment' => $enrollment, | |
| 202 | + 'is_admin' => $isCourseCreator, | |
| 203 | + 'has_section_access' => $hasAcessSectionAcess, | |
| 204 | + 'has_public_access' => $hasPublicViewAccess, | |
| 205 | + ]); | |
| 206 | + $canViewLesson = $accessInfo['can_view']; | |
| 207 | + $lockType = $accessInfo['lock_type']; | |
| 208 | + | |
| 209 | + $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $user, [ | |
| 210 | + 'can_view' => $canViewLesson, | |
| 211 | + 'parse_content' => $canViewLesson, | |
| 212 | + 'is_locked' => !$canViewLesson && !$isCourseCreator, | |
| 213 | + 'unlock_date' => $unlockDate, | |
| 214 | + 'lock_type' => $lockType | |
| 215 | + ]); | |
| 216 | + | |
| 217 | + $data = [ | |
| 218 | + 'lesson' => $formattedLesson | |
| 219 | + ]; | |
| 220 | + | |
| 221 | + return apply_filters('fluent_community/course_lesson_api_response', $data, $request->all()); | |
| 222 | + } | |
| 223 | + | |
| 224 | + private function processCourse(Course $course, $isCourseCreator, $intendtedLessonSlug = '') | |
| 225 | + { | |
| 105 | 226 | $sections = CourseTopic::where('space_id', $course->id) |
| 106 | 227 | ->orderBy('priority', 'ASC') |
| 107 | - ->with(['lessons' => function ($query) use ($isModerator) { | |
| 108 | - if (!$isModerator) { | |
| 228 | + ->orderBy('id', 'ASC') | |
| 229 | + ->with(['lessons' => function ($query) use ($isCourseCreator) { | |
| 230 | + if (!$isCourseCreator) { | |
| 109 | 231 | $query->where('status', 'published'); |
| 110 | 232 | } |
| 111 | 233 | }]) |
| 112 | 234 | ->get(); |
| @@ -111,22 +233,40 @@ | ||
| 111 | 233 | }]) |
| 112 | 234 | ->get(); |
| 113 | 235 | |
| 114 | 236 | $enrollment = CourseHelper::getCourseEnrollment($course->id); |
| 115 | - $formattedSections = []; | |
| 116 | 237 | |
| 238 | + $currentUser = Helper::getCurrentUser(); | |
| 239 | + | |
| 117 | 240 | if ($course->privacy == 'private' && !$enrollment) { |
| 118 | - $course->lockscreen_config = LockscreenService::getLockscreenConfig($course); | |
| 241 | + $course->lockscreen_config = LockscreenService::getLockscreenConfig($course, null, true); | |
| 119 | 242 | } |
| 120 | 243 | |
| 121 | - if ($course->privacy == 'secret' && !$enrollment && !$isModerator) { | |
| 244 | + if ($course->privacy == 'secret' && !$enrollment && !$isCourseCreator) { | |
| 122 | 245 | return $this->sendError([ |
| 123 | - 'message' => 'You must need to be invited to view this course' | |
| 246 | + 'message' => __('You must need to be invited to view this course', 'fluent-community') | |
| 124 | 247 | ]); |
| 125 | 248 | } |
| 126 | 249 | |
| 250 | + $course->is_course_admin = $isCourseCreator; | |
| 251 | + $course->can_self_enroll = $isCourseCreator || ($course->status === 'published' && $course->privacy === 'public'); | |
| 252 | + | |
| 253 | + $course = apply_filters('fluent_community/course/processed', $course, [ | |
| 254 | + 'is_enrolled' => !!$enrollment, | |
| 255 | + ]); | |
| 256 | + | |
| 257 | + $courseSettings = $course->settings; | |
| 258 | + if (Arr::get($courseSettings, 'course_details')) { | |
| 259 | + $courseSettings['course_details_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($courseSettings['course_details'])); | |
| 260 | + $course->settings = $courseSettings; | |
| 261 | + } | |
| 262 | + | |
| 127 | 263 | $courseType = $course->getCourseType(); |
| 128 | 264 | |
| 265 | + $hasPublicViewAccess = $course->privacy == 'public' && Arr::get($course->settings, 'public_lesson_view') == 'yes' && $courseType == 'self_paced'; | |
| 266 | + | |
| 267 | + $formattedSections = []; | |
| 268 | + | |
| 129 | 269 | foreach ($sections as $section) { |
| 130 | 270 | if ($section->lessons->isEmpty()) { |
| 131 | 271 | continue; |
| 132 | 272 | } |
| @@ -135,9 +275,9 @@ | ||
| 135 | 275 | $unlockDate = ''; |
| 136 | 276 | |
| 137 | 277 | if ($hasAcessSectionAcess) { |
| 138 | 278 | $unlockDate = CourseHelper::getSectionAccessDate($section, $courseType, $enrollment); |
| 139 | - if (!$unlockDate || strtotime($unlockDate) > current_time('timestamp')) { | |
| 279 | + if ($unlockDate && strtotime($unlockDate) > current_time('timestamp')) { | |
| 140 | 280 | $hasAcessSectionAcess = false; |
| 141 | 281 | } else { |
| 142 | 282 | $unlockDate = null; |
| 143 | 283 | } |
| @@ -142,32 +282,40 @@ | ||
| 142 | 282 | $unlockDate = null; |
| 143 | 283 | } |
| 144 | 284 | } |
| 145 | 285 | |
| 286 | + $sectionCtx = [ | |
| 287 | + 'enrollment' => $enrollment, | |
| 288 | + 'is_admin' => $isCourseCreator, | |
| 289 | + 'has_section_access' => $hasAcessSectionAcess, | |
| 290 | + 'has_public_access' => $hasPublicViewAccess, | |
| 291 | + ]; | |
| 292 | + | |
| 146 | 293 | $formattedLessons = []; |
| 147 | 294 | foreach ($section->lessons as $lesson) { |
| 148 | - if ($hasAcessSectionAcess || $isModerator) { | |
| 149 | - $content = $lesson->message_rendered; | |
| 150 | - if (!$content && $lesson->message) { | |
| 151 | - $content = apply_filters('widget_block_content', do_blocks($lesson->message)); | |
| 152 | - } | |
| 153 | - } else { | |
| 154 | - $content = ''; | |
| 295 | + $initialCanView = $hasAcessSectionAcess || $isCourseCreator || $hasPublicViewAccess; | |
| 296 | + $accessInfo = CourseHelper::resolveLessonAccess($initialCanView, $lesson, $course, $this->getUser(), $sectionCtx); | |
| 297 | + $canViewLesson = $accessInfo['can_view']; | |
| 298 | + $lockType = $accessInfo['lock_type']; | |
| 299 | + | |
| 300 | + $parseContent = $intendtedLessonSlug === $lesson->slug && $canViewLesson; | |
| 301 | + | |
| 302 | + $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $currentUser, [ | |
| 303 | + 'can_view' => $canViewLesson, | |
| 304 | + 'parse_content' => $parseContent, | |
| 305 | + 'is_locked' => !$canViewLesson && !$isCourseCreator, | |
| 306 | + 'unlock_date' => $unlockDate, | |
| 307 | + 'lock_type' => $lockType | |
| 308 | + ]); | |
| 309 | + | |
| 310 | + $isLazyLoad = !$parseContent; | |
| 311 | + if(!$canViewLesson && $lockType !== 'sequential') { | |
| 312 | + $isLazyLoad = false; | |
| 155 | 313 | } |
| 156 | 314 | |
| 157 | - $formattedLessons[] = [ | |
| 158 | - 'id' => $lesson->id, | |
| 159 | - 'title' => $lesson->title, | |
| 160 | - 'content' => $content, | |
| 161 | - 'slug' => $lesson->slug, | |
| 162 | - 'course_id' => $lesson->space_id, | |
| 163 | - 'section_id' => $lesson->parent_id, | |
| 164 | - 'created_at' => $lesson->created_at, | |
| 165 | - 'meta' => $lesson->meta, | |
| 166 | - 'comments_count' => $lesson->comments_count, | |
| 167 | - 'is_locked' => !$hasAcessSectionAcess && !$isModerator, | |
| 168 | - 'unclock_date' => $unlockDate | |
| 169 | - ]; | |
| 315 | + $formattedLesson['lazy_load'] = $isLazyLoad; | |
| 316 | + | |
| 317 | + $formattedLessons[] = $formattedLesson; | |
| 170 | 318 | } |
| 171 | 319 | |
| 172 | 320 | $formattedSections[] = [ |
| 173 | 321 | 'id' => $section->id, |
| @@ -175,9 +323,9 @@ | ||
| 175 | 323 | 'lessons' => $formattedLessons, |
| 176 | 324 | 'created_at' => $section->created_at, |
| 177 | 325 | 'slug' => $section->slug, |
| 178 | 326 | 'type' => 'section', |
| 179 | - 'is_locked' => !$hasAcessSectionAcess && !$isModerator, | |
| 327 | + 'is_locked' => !$hasAcessSectionAcess && !$isCourseCreator, | |
| 180 | 328 | 'unlock_date' => $unlockDate |
| 181 | 329 | ]; |
| 182 | 330 | } |
| 183 | 331 | |
| @@ -197,16 +345,21 @@ | ||
| 197 | 345 | 'message' => __('You need to login to enroll in this course.', 'fluent-community') |
| 198 | 346 | ]); |
| 199 | 347 | } |
| 200 | 348 | |
| 201 | - $isModerator = $user->isCommunityModerator(); | |
| 202 | - $course = Course::when(!$isModerator, function ($q) { | |
| 203 | - $q->where('status', 'published'); | |
| 204 | - })->findOrFail($courseId); | |
| 349 | + $course = Course::findOrFail($courseId); | |
| 350 | + /** @var Course $course */ | |
| 205 | 351 | |
| 352 | + $isCourseCreator = $course->isCourseAdmin($user); | |
| 206 | 353 | |
| 207 | - if (!$isModerator && $course->privacy != 'public') { | |
| 354 | + if ($course->status !== 'published' && !$isCourseCreator) { | |
| 208 | 355 | return $this->sendError([ |
| 356 | + 'message' => __('Course not found. maybe the course is not published yet!', 'fluent-community') | |
| 357 | + ]); | |
| 358 | + } | |
| 359 | + | |
| 360 | + if (!$isCourseCreator && $course->privacy != 'public') { | |
| 361 | + return $this->sendError([ | |
| 209 | 362 | 'message' => __('You are not allowed to enroll in this course.', 'fluent-community') |
| 210 | 363 | ]); |
| 211 | 364 | } |
| 212 | 365 | |
| @@ -230,9 +383,9 @@ | ||
| 230 | 383 | $course = Course::findOrFail($courseId); |
| 231 | 384 | |
| 232 | 385 | if ($course->status != 'published') { |
| 233 | 386 | return $this->sendError([ |
| 234 | - 'message' => __('Course is not on published state.', 'fluent-community') | |
| 387 | + 'message' => __('Course is not in published state.', 'fluent-community') | |
| 235 | 388 | ]); |
| 236 | 389 | } |
| 237 | 390 | |
| 238 | 391 | |
| @@ -237,9 +390,9 @@ | ||
| 237 | 390 | |
| 238 | 391 | |
| 239 | 392 | if (!CourseHelper::isEnrolled($courseId)) { |
| 240 | 393 | return $this->sendError([ |
| 241 | - 'message' => 'You are not enrolled in this action.' | |
| 394 | + 'message' => __('Please enroll this course first', 'fluent-community') | |
| 242 | 395 | ]); |
| 243 | 396 | } |
| 244 | 397 | |
| 245 | 398 | $lesson = CourseLesson::where('space_id', $courseId) |
| @@ -248,9 +401,9 @@ | ||
| 248 | 401 | ->first(); |
| 249 | 402 | |
| 250 | 403 | if (!$lesson) { |
| 251 | 404 | return $this->sendError([ |
| 252 | - 'message' => 'Lesson is not on published state.' | |
| 405 | + 'message' => __('Lesson is not on published state.', 'fluent-community') | |
| 253 | 406 | ]); |
| 254 | 407 | } |
| 255 | 408 | |
| 256 | 409 | $state = $request->get('state'); |
| @@ -256,23 +409,183 @@ | ||
| 256 | 409 | $state = $request->get('state'); |
| 257 | 410 | |
| 258 | 411 | if (!in_array($state, ['completed', 'incomplete'])) { |
| 259 | 412 | return $this->sendError([ |
| 260 | - 'message' => 'Invalid state.' | |
| 413 | + 'message' => __('Invalid state.', 'fluent-community') | |
| 261 | 414 | ]); |
| 262 | 415 | } |
| 263 | 416 | |
| 417 | + $isAllowed = apply_filters('fluent_community/is_allowed_to_complete_lesson', true, $lesson, $state); | |
| 418 | + | |
| 419 | + if (!$isAllowed) { | |
| 420 | + $errorData = [ | |
| 421 | + 'message' => __('You are not allowed to complete this lesson.', 'fluent-community') | |
| 422 | + ]; | |
| 423 | + | |
| 424 | + if (LessonVideoGateService::isCompletionBlockedFor($lesson, Helper::getCurrentUser())) { | |
| 425 | + $errorData['code'] = 'video_watch_required'; | |
| 426 | + $errorData['threshold'] = LessonVideoGateService::getThreshold($lesson); | |
| 427 | + } | |
| 428 | + | |
| 429 | + return $this->sendError($errorData); | |
| 430 | + } | |
| 431 | + | |
| 264 | 432 | CourseHelper::updateLessonCompletion($lesson, get_current_user_id(), $state); |
| 265 | 433 | $track = CourseHelper::getCourseProgressTrack($courseId); |
| 266 | 434 | $isCompleted = $track['progress'] == 100; |
| 267 | 435 | |
| 268 | 436 | if ($isCompleted) { |
| 269 | - CourseHelper::completeCourse($course, get_current_user_id()); | |
| 437 | + CourseHelper::completeCourse($course, $lesson, get_current_user_id()); | |
| 270 | 438 | } |
| 271 | 439 | |
| 272 | 440 | return [ |
| 273 | - 'message' => 'Lesson completion state updated.', | |
| 441 | + 'message' => __('Lesson completion state updated.', 'fluent-community'), | |
| 274 | 442 | 'track' => $track, |
| 275 | 443 | 'is_completed' => $isCompleted |
| 276 | 444 | ]; |
| 445 | + } | |
| 446 | + | |
| 447 | + public function markLessonVideoWatched(Request $request, $courseId, $lessonId) | |
| 448 | + { | |
| 449 | + $course = Course::findOrFail($courseId); | |
| 450 | + | |
| 451 | + if ($course->status != 'published') { | |
| 452 | + return $this->sendError([ | |
| 453 | + 'message' => __('Course is not in published state.', 'fluent-community') | |
| 454 | + ]); | |
| 455 | + } | |
| 456 | + | |
| 457 | + if (!CourseHelper::isEnrolled($courseId)) { | |
| 458 | + return $this->sendError([ | |
| 459 | + 'message' => __('Please enroll this course first', 'fluent-community') | |
| 460 | + ]); | |
| 461 | + } | |
| 462 | + | |
| 463 | + $lesson = CourseLesson::where('space_id', $courseId) | |
| 464 | + ->where('status', 'published') | |
| 465 | + ->where('id', $lessonId) | |
| 466 | + ->first(); | |
| 467 | + | |
| 468 | + if (!$lesson) { | |
| 469 | + return $this->sendError([ | |
| 470 | + 'message' => __('Lesson is not on published state.', 'fluent-community') | |
| 471 | + ]); | |
| 472 | + } | |
| 473 | + | |
| 474 | + if (!LessonVideoGateService::isGatedLesson($lesson)) { | |
| 475 | + return [ | |
| 476 | + 'message' => __('This lesson does not require video completion.', 'fluent-community'), | |
| 477 | + 'is_gated' => false, | |
| 478 | + 'watched' => false | |
| 479 | + ]; | |
| 480 | + } | |
| 481 | + | |
| 482 | + $watchedPercent = min(100, absint($request->get('watched_percent'))); | |
| 483 | + $threshold = LessonVideoGateService::getThreshold($lesson); | |
| 484 | + | |
| 485 | + if ($watchedPercent < $threshold) { | |
| 486 | + return $this->sendError([ | |
| 487 | + /* translators: %d is the watched percentage required to complete the lesson */ | |
| 488 | + 'message' => sprintf(__('Please watch at least %d%% of the video.', 'fluent-community'), $threshold) | |
| 489 | + ]); | |
| 490 | + } | |
| 491 | + | |
| 492 | + LessonVideoGateService::markWatched($lesson, get_current_user_id()); | |
| 493 | + | |
| 494 | + return [ | |
| 495 | + 'message' => __('Video watch progress has been recorded.', 'fluent-community'), | |
| 496 | + 'is_gated' => true, | |
| 497 | + 'watched' => true | |
| 498 | + ]; | |
| 499 | + } | |
| 500 | + | |
| 501 | + public function resetMyProgress(Request $request, $courseId) | |
| 502 | + { | |
| 503 | + $course = Course::findOrFail($courseId); | |
| 504 | + | |
| 505 | + if ($course->status !== 'published') { | |
| 506 | + return $this->sendError([ | |
| 507 | + 'message' => __('Course is not in published state.', 'fluent-community') | |
| 508 | + ]); | |
| 509 | + } | |
| 510 | + | |
| 511 | + if (!CourseHelper::isEnrolled($courseId)) { | |
| 512 | + return $this->sendError([ | |
| 513 | + 'message' => __('Please enroll this course first', 'fluent-community') | |
| 514 | + ]); | |
| 515 | + } | |
| 516 | + | |
| 517 | + CourseHelper::resetCourseProgress($courseId, get_current_user_id()); | |
| 518 | + | |
| 519 | + return [ | |
| 520 | + 'message' => __('Your course progress has been reset.', 'fluent-community'), | |
| 521 | + 'track' => [ | |
| 522 | + 'completed_lessons' => [], | |
| 523 | + 'isEnrolled' => true, | |
| 524 | + 'progress' => 0 | |
| 525 | + ] | |
| 526 | + ]; | |
| 527 | + } | |
| 528 | + | |
| 529 | + public function getAllCourses(Request $request) | |
| 530 | + { | |
| 531 | + $user = $this->getUser(); | |
| 532 | + $isCourseCreator = $user && $user->hasCourseCreatorAccess(); | |
| 533 | + | |
| 534 | + $coursesQuery = Course::where(function ($q) use ($isCourseCreator, $user) { | |
| 535 | + if ($isCourseCreator) { | |
| 536 | + $q->where('status', 'published') | |
| 537 | + ->orWhere('created_by', $user->ID); | |
| 538 | + } else { | |
| 539 | + $q->where('status', 'published'); | |
| 540 | + } | |
| 541 | + }); | |
| 542 | + | |
| 543 | + if (!($user && $user->isCommunityModerator())) { | |
| 544 | + $userId = get_current_user_id(); | |
| 545 | + $coursesQuery->where(function ($q) use ($userId) { | |
| 546 | + $q->whereIn('privacy', ['public', 'private']) | |
| 547 | + ->orWhereHas('space_pivot', function ($sub) use ($userId) { | |
| 548 | + $sub->where('user_id', $userId) | |
| 549 | + ->where('role', 'student'); | |
| 550 | + }) | |
| 551 | + ->orWhere('created_by', $userId); | |
| 552 | + }); | |
| 553 | + } | |
| 554 | + | |
| 555 | + $courses = $coursesQuery->with(['creator'])->paginate(); | |
| 556 | + | |
| 557 | + $formattedCourses = []; | |
| 558 | + foreach ($courses as $course) { | |
| 559 | + $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count(); | |
| 560 | + $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count(); | |
| 561 | + | |
| 562 | + if (Arr::get($course->settings, 'hide_members_count') != 'yes') { | |
| 563 | + $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count(); | |
| 564 | + } else { | |
| 565 | + $course->studentsCount = 0; | |
| 566 | + } | |
| 567 | + | |
| 568 | + $showInstructorView = Arr::get($course->settings, 'hide_instructor_view') != 'yes'; | |
| 569 | + $showStudentCount = Arr::get($course->settings, 'show_instructor_students_count') == 'yes'; | |
| 570 | + if ($showInstructorView) { | |
| 571 | + if ($course->creator) { | |
| 572 | + $creatorCourseIds = Course::where('created_by', $course->creator->user_id)->pluck('id'); | |
| 573 | + $course->creator->total_courses = count($creatorCourseIds); | |
| 574 | + if ($showStudentCount) { | |
| 575 | + $course->creator->total_students = SpaceUserPivot::whereIn('space_id', $creatorCourseIds)->distinct('user_id')->count('user_id'); | |
| 576 | + } | |
| 577 | + if ($course->creator->short_description) { | |
| 578 | + $course->creator->short_description_rendered = wp_kses_post(FeedsHelper::mdToHtml($course->creator->short_description)); | |
| 579 | + } | |
| 580 | + } | |
| 581 | + } | |
| 582 | + | |
| 583 | + $formattedCourses[] = $this->processCourse($course, $course->isCourseAdmin($user)); | |
| 584 | + } | |
| 585 | + | |
| 586 | + return apply_filters('fluent_community/all_courses_api_response', [ | |
| 587 | + 'courses' => $formattedCourses, | |
| 588 | + 'total' => $courses->total() | |
| 589 | + ], $request->all()); | |
| 277 | 590 | } |
| 278 | 591 | } |