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.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 1.1.0 All 77 releases
← All changes | Modules/Course/Http/Controllers/CourseAdminController.php +78 -18 2.7.72.10.01 View file →
@@ -85,9 +85,9 @@
85 85 $courseData = [
86 86 'parent_id' => $request->get('parent_id') ?: null, // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
87 87 'title' => $request->getSafe('title', 'sanitize_text_field'),
88 88 'privacy' => $request->get('privacy'),
89 - 'description' => wp_kses_post($request->get('description')),
89 + 'description' => wp_kses_post((string) $request->get('description', '')),
90 90 'status' => $request->get('status', 'draft'),
91 91 'settings' => [
92 92 'course_type' => $request->get('course_type'),
93 93 'emoji' => CustomSanitizer::sanitizeEmoji($request->get('settings.emoji', '')),
@@ -94,9 +94,9 @@
94 94 'shape_svg' => CustomSanitizer::sanitizeSvg($request->get('settings.shape_svg', '')),
95 95 'disable_comments' => $request->get('settings.disable_comments') === 'yes' ? 'yes' : 'no',
96 96 'hide_members_count' => $request->get('settings.hide_members_count') === 'yes' ? 'yes' : 'no',
97 97 'course_layout' => $request->get('settings.course_layout') === 'modern' ? 'modern' : 'classic',
98 - 'course_details' => CustomSanitizer::unslashMarkdown(trim($request->get('settings.course_details'))),
98 + 'course_details' => CustomSanitizer::unslashMarkdown(trim((string) $request->get('settings.course_details', ''))),
99 99 'hide_instructor_view' => $request->get('settings.hide_instructor_view') === 'yes' ? 'yes' : 'no',
100 100 'show_instructor_students_count' => $request->get('settings.show_instructor_students_count') === 'yes' ? 'yes' : 'no',
101 101 'sequential_lesson_order' => $request->get('settings.sequential_lesson_order') === 'yes' ? 'yes' : 'no',
102 102 ],
@@ -229,9 +229,9 @@
229 229
230 230 $courseData = [
231 231 'title' => $request->getSafe('title', 'sanitize_text_field'),
232 232 'privacy' => $request->get('privacy'),
233 - 'description' => wp_kses_post($request->get('description')),
233 + 'description' => wp_kses_post((string) $request->get('description', '')),
234 234 'status' => $request->get('status'),
235 235 'cover_photo' => $request->getSafe('cover_photo', 'sanitize_url'),
236 236 'parent_id' => $request->get('parent_id') ?: null, // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
237 237 ];
@@ -303,9 +303,9 @@
303 303 $existingSettings['show_instructor_students_count'] = $request->get('settings.show_instructor_students_count') === 'yes' ? 'yes' : 'no';
304 304 $existingSettings['show_paywalls'] = $request->get('settings.show_paywalls') === 'yes' ? 'yes' : 'no';
305 305 $existingSettings['show_welcome_banner'] = $request->get('settings.show_welcome_banner') === 'yes' ? 'yes' : 'no';
306 306 $existingSettings['course_layout'] = $request->get('settings.course_layout') === 'modern' ? 'modern' : 'classic';
307 - $existingSettings['course_details'] = CustomSanitizer::unslashMarkdown(trim($request->get('settings.course_details')));
307 + $existingSettings['course_details'] = CustomSanitizer::unslashMarkdown(trim((string) $request->get('settings.course_details', '')));
308 308 $existingSettings['sequential_lesson_order'] = $request->get('settings.sequential_lesson_order') === 'yes' ? 'yes' : 'no';
309 309
310 310 if ($request->get('privacy') == 'public' && $existingSettings['course_type'] == 'self_paced') {
311 311 $existingSettings['public_lesson_view'] = $request->get('settings.public_lesson_view') == 'yes' ? 'yes' : 'no';
@@ -383,8 +383,10 @@
383 383 $newTopic = $topic->replicate();
384 384 $newTopic->space_id = $newCourse->id;
385 385 $newTopic->save();
386 386
387 + do_action('fluent_community/section/created', $newTopic, $newCourse);
388 +
387 389 foreach ($topic->lessons as $lesson) {
388 390 /** @var CourseLesson $lesson */
389 391 $newLesson = $lesson->replicate();
390 392 $newLesson->space_id = $newCourse->id;
@@ -390,8 +392,10 @@
390 392 $newLesson->space_id = $newCourse->id;
391 393 $newLesson->parent_id = $newTopic->id;
392 394 $newLesson->save();
393 395 CourseHelper::copyLessonDocuments($lesson, $newLesson);
396 +
397 + do_action('fluent_community/lesson/created', $newLesson, $newTopic);
394 398 }
395 399 }
396 400
397 401 do_action('fluent_community/course/created', $newCourse);
@@ -487,13 +491,27 @@
487 491 Course::findOrFail($courseId);
488 492
489 493 $search = $request->getSafe('search');
490 494
491 - $students = XProfile::whereHas('space_pivot', function ($q) use ($courseId) {
492 - return $q->where('space_id', $courseId)
493 - ->where('role', 'student');
494 - })
495 - ->searchBy($search)
495 + $defaultDirections = [
496 + 'display_name' => 'ASC',
497 + 'created_at' => 'DESC',
498 + 'last_activity' => 'DESC',
499 + ];
500 +
501 + $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'created_at');
502 + $sortColumn = in_array($sortBy, array_keys($defaultDirections), true) ? $sortBy : 'created_at';
503 + $sortDir = strtoupper($request->getSafe('sort_dir', 'sanitize_text_field', ''));
504 + $sortDirection = in_array($sortDir, ['ASC', 'DESC'], true) ? $sortDir : $defaultDirections[$sortColumn];
505 + $orderColumn = $sortColumn === 'created_at'
506 + ? 'fcom_space_user.created_at'
507 + : 'fcom_xprofile.' . $sortColumn;
508 +
509 + $publicFields = array_map(function ($field) {
510 + return 'fcom_xprofile.' . $field;
511 + }, ProfileHelper::getXProfilePublicFields());
512 +
513 + $students = XProfile::searchBy($search)
496 514 ->whereHas('user')
497 515 ->with([
498 516 'space_pivot' => function ($q) use ($courseId) {
499 517 return $q->where('space_id', $courseId);
@@ -498,9 +516,15 @@
498 516 'space_pivot' => function ($q) use ($courseId) {
499 517 return $q->where('space_id', $courseId);
500 518 },
501 519 ])
502 - ->select(ProfileHelper::getXProfilePublicFields())
520 + ->join('fcom_space_user', function ($join) use ($courseId) {
521 + $join->on('fcom_space_user.user_id', '=', 'fcom_xprofile.user_id')
522 + ->where('fcom_space_user.space_id', $courseId)
523 + ->where('fcom_space_user.role', 'student');
524 + })
525 + ->select($publicFields)
526 + ->orderBy($orderColumn, $sortDirection)
503 527 ->paginate();
504 528
505 529 $studentUserIds = $students->pluck('user_id')->toArray();
506 530
@@ -691,14 +715,13 @@
691 715 $lessonId = $request->getSafe('lesson_id', 'intval');
692 716 $sectionId = $request->getSafe('section_id', 'intval');
693 717
694 718 Course::findOrFail($courseId);
695 - CourseTopic::findOrFail($sectionId);
719 + $section = CourseTopic::where('space_id', $courseId)->findOrFail($sectionId);
720 + $lesson = CourseLesson::where('space_id', $courseId)->findOrFail($lessonId);
696 721
697 - $lesson = CourseLesson::findOrFail($lessonId);
698 -
699 722 $lesson->update([
700 - 'parent_id' => $sectionId,
723 + 'parent_id' => $section->id,
701 724 ]);
702 725
703 726 return [
704 727 'message' => __('Lesson has been moved successfully', 'fluent-community'),
@@ -716,9 +739,9 @@
716 739 'space_id' => $courseId,
717 740 'status' => 'published',
718 741 ];
719 742
720 - Course::findOrFail($courseId);
743 + $course = Course::findOrFail($courseId);
721 744
722 745 $latestPriority = CourseTopic::where('type', 'course_section')->where('space_id', $courseId)->max('priority');
723 746
724 747 $sectionData['priority'] = (int) $latestPriority + 1;
@@ -726,8 +749,10 @@
726 749 $section = CourseTopic::create($sectionData);
727 750
728 751 $section->load('lessons');
729 752
753 + do_action('fluent_community/section/created', $section, $course);
754 +
730 755 return [
731 756 'message' => __('Section has been created successfully.', 'fluent-community'),
732 757 'section' => $section,
733 758 ];
@@ -849,8 +874,10 @@
849 874 $newSection->space_id = $toCourse->id;
850 875 $newSection->priority = (int) $latestPriority + 1;
851 876 $newSection->save();
852 877
878 + do_action('fluent_community/section/created', $newSection, $toCourse);
879 +
853 880 $originalLessons = CourseLesson::where('parent_id', $originalSection->id)->get();
854 881 foreach ($originalLessons as $lesson) {
855 882 /** @var CourseLesson $lesson */
856 883 $newLesson = $lesson->replicate();
@@ -857,8 +884,10 @@
857 884 $newLesson->space_id = $toCourse->id;
858 885 $newLesson->parent_id = $newSection->id;
859 886 $newLesson->save();
860 887 CourseHelper::copyLessonDocuments($lesson, $newLesson);
888 +
889 + do_action('fluent_community/lesson/created', $newLesson, $newSection);
861 890 }
862 891
863 892 $newSection->load('lessons');
864 893
@@ -932,8 +961,11 @@
932 961
933 962 return apply_filters('fluent_community/admin_course_lesson_api_response', $data, $request->all());
934 963 }
935 964
965 + /**
966 + * Expects `title` and `section_id` at the top level of the request.
967 + */
936 968 public function createLesson(Request $request, $courseId)
937 969 {
938 970 $this->validate($request->all(), [
939 971 'title' => 'required',
@@ -967,8 +999,10 @@
967 999 $lesson = CourseLesson::create($lessonData);
968 1000
969 1001 $lesson = CourseLesson::findOrFail($lesson->id);
970 1002
1003 + do_action('fluent_community/lesson/created', $lesson, $topic);
1004 +
971 1005 return [
972 1006 'message' => __('Lesson has been created successfully.', 'fluent-community'),
973 1007 'lesson' => $lesson,
974 1008 ];
@@ -973,13 +1007,16 @@
973 1007 'lesson' => $lesson,
974 1008 ];
975 1009 }
976 1010
1011 + /**
1012 + * Expects the fields nested under `lesson`, referencing the section as `parent_id`.
1013 + */
977 1014 public function updateLesson(Request $request, $courseId, $lessionId)
978 1015 {
979 1016 Course::findOrFail($courseId);
980 1017
981 - $lessonData = $request->get('lesson');
1018 + $lessonData = (array)$request->get('lesson');
982 1019
983 1020 $this->validate($lessonData, [
984 1021 'title' => 'required',
985 1022 'parent_id' => 'required',
@@ -1043,8 +1080,12 @@
1043 1080 if ($dirtyFields) {
1044 1081 $lesson->save();
1045 1082 $isNewlyPublished = $lesson->status === 'published' && $previousStatus !== 'published';
1046 1083 do_action('fluent_community/lesson/updated', $lesson, $dirtyFields, $isNewlyPublished);
1084 +
1085 + if ($isNewlyPublished) {
1086 + do_action('fluent_community/lesson/published', $lesson);
1087 + }
1047 1088 }
1048 1089
1049 1090 do_action('fluent_community/lesson/additional_media_updated', $request->all(), $lesson, $updateData);
1050 1091
@@ -1061,8 +1102,10 @@
1061 1102 })
1062 1103 ->where('id', $lessionId)
1063 1104 ->firstOrFail();
1064 1105
1106 + $previousStatus = $lesson->status;
1107 +
1065 1108 $acceptedFields = [ 'title', 'status', 'slug' ];
1066 1109
1067 1110 // empty title/slug/status must not overwrite, but a literal "0" is a valid value
1068 1111 // Request::only() is a plain array pick with no sanitization, so each field is
@@ -1076,9 +1119,16 @@
1076 1119 $lessonData['title'] = sanitize_text_field($lessonData['title']);
1077 1120 }
1078 1121
1079 1122 if (isset($lessonData['slug'])) {
1080 - $lessonData['slug'] = sanitize_title($lessonData['slug']);
1123 + // sanitize_title() alone let an author set a slug already taken by a
1124 + // sibling lesson, which getLessonBySlug() then resolves arbitrarily.
1125 + $lessonData['slug'] = CourseLesson::uniqueSlug(
1126 + $lessonData['slug'],
1127 + $lesson->space_id,
1128 + $lesson->id,
1129 + Arr::get($lessonData, 'title', $lesson->title)
1130 + );
1081 1131 }
1082 1132
1083 1133 if (isset($lessonData['status']) && !in_array($lessonData['status'], ['draft', 'published', 'archived'], true)) {
1084 1134 unset($lessonData['status']);
@@ -1096,10 +1146,19 @@
1096 1146 }
1097 1147
1098 1148 if (!empty($lessonData)) {
1099 1149 $lesson->fill($lessonData);
1100 - if ($lesson->isDirty()) {
1150 + $dirtyFields = $lesson->getDirty();
1151 +
1152 + if ($dirtyFields) {
1101 1153 $lesson->save();
1154 +
1155 + $isNewlyPublished = $lesson->status === 'published' && $previousStatus !== 'published';
1156 + do_action('fluent_community/lesson/updated', $lesson, $dirtyFields, $isNewlyPublished);
1157 +
1158 + if ($isNewlyPublished) {
1159 + do_action('fluent_community/lesson/published', $lesson);
1160 + }
1102 1161 }
1103 1162 }
1104 1163
1105 1164 return [
@@ -1174,8 +1233,9 @@
1174 1233 $newLesson = CourseLesson::findOrFail($newLesson->id);
1175 1234
1176 1235 CourseHelper::copyLessonDocuments($lesson, $newLesson);
1177 1236
1237 + do_action('fluent_community/lesson/created', $newLesson, $newLesson->topic);
1178 1238 do_action('fluent_community/lesson/duplicated', $newLesson, $lesson);
1179 1239
1180 1240 return [
1181 1241 'message' => __('Lesson has been duplicated successfully.', 'fluent-community'),