← All changes
|
Modules/Course/Http/Controllers/CourseAdminController.php
+78
-18
2.7.7
→
2.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'), |