PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
← All changes | Modules/Course/Http/Controllers/CourseController.php +148 -16 2.4.012.10.01 View file →
@@ -12,8 +12,9 @@
12 12 use FluentCommunity\Modules\Course\Model\CourseLesson;
13 13 use FluentCommunity\Modules\Course\Model\CourseTopic;
14 14 use FluentCommunity\App\Services\FeedsHelper;
15 15 use FluentCommunity\Modules\Course\Services\CourseHelper;
16 +use FluentCommunity\Modules\Course\Services\LessonVideoGateService;
16 17
17 18 class CourseController extends Controller
18 19 {
19 20 public function getCourses(Request $request)
@@ -71,8 +72,10 @@
71 72 $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count();
72 73 } else {
73 74 $course->studentsCount = 0;
74 75 }
76 +
77 + do_action_ref_array('fluent_community/course', [&$course]);
75 78 }
76 79
77 80 $data = [
78 81 'courses' => $courses,
@@ -86,8 +89,9 @@
86 89 {
87 90 $user = $this->getUser();
88 91
89 92 $course = Course::findOrFail($courseId);
93 + /** @var Course $course */
90 94
91 95 $isCourseCreator = $course->isCourseAdmin($user);
92 96
93 97 if ($course->status !== 'published' && !$isCourseCreator) {
@@ -107,8 +111,9 @@
107 111 {
108 112 $user = $this->getUser();
109 113
110 114 $course = Course::where('slug', $slug)->firstOrFail();
115 + /** @var Course $course */
111 116
112 117 $isCourseCreator = $course->isCourseAdmin($user);
113 118
114 119 if ($course->status !== 'published' && !$isCourseCreator) {
@@ -143,8 +148,9 @@
143 148 public function getLessonBySlug(Request $request, $courseSlug, $lessonSlug)
144 149 {
145 150 $user = $this->getUser();
146 151
152 + /** @var Course $course */
147 153 $course = Course::where('slug', $courseSlug)->firstOrFail();
148 154
149 155 $isCourseCreator = $course->isCourseAdmin($user);
150 156
@@ -189,16 +195,24 @@
189 195 $unlockDate = null;
190 196 }
191 197 }
192 198
193 - $canViewLesson = $hasAcessSectionAcess || $isCourseCreator || $hasPublicViewAccess;
194 - $canViewLesson = apply_filters('fluent_community/course/can_view_lesson', $canViewLesson, $lesson, $course, $this->getUser());
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'];
195 208
196 209 $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $user, [
197 210 'can_view' => $canViewLesson,
198 211 'parse_content' => $canViewLesson,
199 - 'is_locked' => !$hasAcessSectionAcess && !$isCourseCreator,
200 - 'unclock_date' => $unlockDate
212 + 'is_locked' => !$canViewLesson && !$isCourseCreator,
213 + 'unlock_date' => $unlockDate,
214 + 'lock_type' => $lockType
201 215 ]);
202 216
203 217 $data = [
204 218 'lesson' => $formattedLesson
@@ -210,8 +224,9 @@
210 224 private function processCourse(Course $course, $isCourseCreator, $intendtedLessonSlug = '')
211 225 {
212 226 $sections = CourseTopic::where('space_id', $course->id)
213 227 ->orderBy('priority', 'ASC')
228 + ->orderBy('id', 'ASC')
214 229 ->with(['lessons' => function ($query) use ($isCourseCreator) {
215 230 if (!$isCourseCreator) {
216 231 $query->where('status', 'published');
217 232 }
@@ -232,9 +247,14 @@
232 247 ]);
233 248 }
234 249
235 250 $course->is_course_admin = $isCourseCreator;
251 + $course->can_self_enroll = $isCourseCreator || ($course->status === 'published' && $course->privacy === 'public');
236 252
253 + $course = apply_filters('fluent_community/course/processed', $course, [
254 + 'is_enrolled' => !!$enrollment,
255 + ]);
256 +
237 257 $courseSettings = $course->settings;
238 258 if (Arr::get($courseSettings, 'course_details')) {
239 259 $courseSettings['course_details_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($courseSettings['course_details']));
240 260 $course->settings = $courseSettings;
@@ -262,12 +282,21 @@
262 282 $unlockDate = null;
263 283 }
264 284 }
265 285
286 + $sectionCtx = [
287 + 'enrollment' => $enrollment,
288 + 'is_admin' => $isCourseCreator,
289 + 'has_section_access' => $hasAcessSectionAcess,
290 + 'has_public_access' => $hasPublicViewAccess,
291 + ];
292 +
266 293 $formattedLessons = [];
267 294 foreach ($section->lessons as $lesson) {
268 - $canViewLesson = $hasAcessSectionAcess || $isCourseCreator || $hasPublicViewAccess;
269 - $canViewLesson = apply_filters('fluent_community/course/can_view_lesson', $canViewLesson, $lesson, $course, $this->getUser());
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'];
270 299
271 300 $parseContent = $intendtedLessonSlug === $lesson->slug && $canViewLesson;
272 301
273 302 $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $currentUser, [
@@ -272,14 +301,15 @@
272 301
273 302 $formattedLesson = CourseHelper::formatLessonData($course, $lesson, $currentUser, [
274 303 'can_view' => $canViewLesson,
275 304 'parse_content' => $parseContent,
276 - 'is_locked' => !$hasAcessSectionAcess && !$isCourseCreator,
277 - 'unclock_date' => $unlockDate
305 + 'is_locked' => !$canViewLesson && !$isCourseCreator,
306 + 'unlock_date' => $unlockDate,
307 + 'lock_type' => $lockType
278 308 ]);
279 309
280 310 $isLazyLoad = !$parseContent;
281 - if(!$canViewLesson) {
311 + if(!$canViewLesson && $lockType !== 'sequential') {
282 312 $isLazyLoad = false;
283 313 }
284 314
285 315 $formattedLesson['lazy_load'] = $isLazyLoad;
@@ -316,8 +346,9 @@
316 346 ]);
317 347 }
318 348
319 349 $course = Course::findOrFail($courseId);
350 + /** @var Course $course */
320 351
321 352 $isCourseCreator = $course->isCourseAdmin($user);
322 353
323 354 if ($course->status !== 'published' && !$isCourseCreator) {
@@ -382,14 +413,21 @@
382 413 'message' => __('Invalid state.', 'fluent-community')
383 414 ]);
384 415 }
385 416
386 - $isAllowed = apply_filters('fluent_community/is_allowed_to_complete_lesson', true, $lesson);
417 + $isAllowed = apply_filters('fluent_community/is_allowed_to_complete_lesson', true, $lesson, $state);
387 418
388 419 if (!$isAllowed) {
389 - return $this->sendError([
420 + $errorData = [
390 421 'message' => __('You are not allowed to complete this lesson.', 'fluent-community')
391 - ]);
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);
392 430 }
393 431
394 432 CourseHelper::updateLessonCompletion($lesson, get_current_user_id(), $state);
395 433 $track = CourseHelper::getCourseProgressTrack($courseId);
@@ -405,14 +443,96 @@
405 443 'is_completed' => $isCompleted
406 444 ];
407 445 }
408 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 +
409 529 public function getAllCourses(Request $request)
410 530 {
411 531 $user = $this->getUser();
412 532 $isCourseCreator = $user && $user->hasCourseCreatorAccess();
413 533
414 - $courses = Course::where(function ($q) use ($isCourseCreator, $user) {
534 + $coursesQuery = Course::where(function ($q) use ($isCourseCreator, $user) {
415 535 if ($isCourseCreator) {
416 536 $q->where('status', 'published')
417 537 ->orWhere('created_by', $user->ID);
418 538 } else {
@@ -417,11 +537,23 @@
417 537 ->orWhere('created_by', $user->ID);
418 538 } else {
419 539 $q->where('status', 'published');
420 540 }
421 - })
422 - ->with(['creator'])
423 - ->paginate();
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();
424 556
425 557 $formattedCourses = [];
426 558 foreach ($courses as $course) {
427 559 $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count();