PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
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 +152 -16 2.4.012.11.0 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,100 @@
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 + if (!CourseHelper::resetCourseProgress($courseId, get_current_user_id())) {
518 + return $this->sendError([
519 + 'message' => __('Your course progress could not be reset. Please try again.', 'fluent-community')
520 + ]);
521 + }
522 +
523 + return [
524 + 'message' => __('Your course progress has been reset.', 'fluent-community'),
525 + 'track' => [
526 + 'completed_lessons' => [],
527 + 'isEnrolled' => true,
528 + 'progress' => 0
529 + ]
530 + ];
531 + }
532 +
409 533 public function getAllCourses(Request $request)
410 534 {
411 535 $user = $this->getUser();
412 536 $isCourseCreator = $user && $user->hasCourseCreatorAccess();
413 537
414 - $courses = Course::where(function ($q) use ($isCourseCreator, $user) {
538 + $coursesQuery = Course::where(function ($q) use ($isCourseCreator, $user) {
415 539 if ($isCourseCreator) {
416 540 $q->where('status', 'published')
417 541 ->orWhere('created_by', $user->ID);
418 542 } else {
@@ -417,11 +541,23 @@
417 541 ->orWhere('created_by', $user->ID);
418 542 } else {
419 543 $q->where('status', 'published');
420 544 }
421 - })
422 - ->with(['creator'])
423 - ->paginate();
545 + });
546 +
547 + if (!($user && $user->isCommunityModerator())) {
548 + $userId = get_current_user_id();
549 + $coursesQuery->where(function ($q) use ($userId) {
550 + $q->whereIn('privacy', ['public', 'private'])
551 + ->orWhereHas('space_pivot', function ($sub) use ($userId) {
552 + $sub->where('user_id', $userId)
553 + ->where('role', 'student');
554 + })
555 + ->orWhere('created_by', $userId);
556 + });
557 + }
558 +
559 + $courses = $coursesQuery->with(['creator'])->paginate();
424 560
425 561 $formattedCourses = [];
426 562 foreach ($courses as $course) {
427 563 $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count();