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/CourseAdminController.php +341 -117 2.4.012.11.0 View file →
@@ -28,14 +28,28 @@
28 28 public function getCourses(Request $request)
29 29 {
30 30 $user = $this->getUser();
31 31
32 - $courses = Course::searchBy($request->getSafe('search'))
32 + $status = $request->getSafe('status');
33 + $sortBy = $request->getSafe('sort_by', 'sanitize_text_field', 'latest');
34 + $topicSlug = $request->getSafe('topic_slug');
35 +
36 + $query = Course::searchBy($request->getSafe('search'))
33 37 ->byAdminAccess($user->ID)
34 - ->orderBy('id', 'DESC')
35 - ->with(['owner'])
36 - ->paginate();
38 + ->byPostTopic($topicSlug);
37 39
40 + if ($status && in_array($status, [ 'published', 'draft' ])) {
41 + $query->where('status', $status);
42 + }
43 +
44 + if ($sortBy === 'alphabetical') {
45 + $query->orderBy('title', 'ASC');
46 + } else {
47 + $query->orderBy('created_at', 'DESC');
48 + }
49 +
50 + $courses = $query->with([ 'owner' ])->paginate();
51 +
38 52 foreach ($courses as $course) {
39 53 $course->students_count = $course->students()->count();
40 54 if (!$course->cover_photo) {
41 55 $course->cover_photo = FLUENT_COMMUNITY_PLUGIN_URL . 'assets/images/course-placeholder.jpg';
@@ -44,9 +58,10 @@
44 58 $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count();
45 59 }
46 60
47 61 $data = [
48 - 'courses' => $courses
62 + 'courses' => $courses,
63 + 'course_categories' => $request->get('with_categories') ? CourseHelper::getCourseCategories() : [],
49 64 ];
50 65
51 66 return apply_filters('fluent_community/admin_courses_api_response', $data, $request->all());
52 67 }
@@ -56,9 +71,9 @@
56 71 $this->validate($request->all(), [
57 72 'title' => 'required',
58 73 'description' => 'required',
59 74 'privacy' => 'required|in:public,private,secret',
60 - 'course_type' => 'required|in:self_paced,structured,scheduled'
75 + 'course_type' => 'required|in:self_paced,structured,scheduled',
61 76 ]);
62 77
63 78 $parentId = $request->get('parent_id');
64 79 if ($parentId) {
@@ -67,12 +82,12 @@
67 82 $serial = BaseSpace::max('serial') + 1;
68 83 }
69 84
70 85 $courseData = [
71 - 'parent_id' => $request->get('parent_id') ?: NULL,
86 + 'parent_id' => $request->get('parent_id') ?: null, // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
72 87 'title' => $request->getSafe('title', 'sanitize_text_field'),
73 88 'privacy' => $request->get('privacy'),
74 - 'description' => wp_kses_post($request->get('description')),
89 + 'description' => wp_kses_post((string) $request->get('description', '')),
75 90 'status' => $request->get('status', 'draft'),
76 91 'settings' => [
77 92 'course_type' => $request->get('course_type'),
78 93 'emoji' => CustomSanitizer::sanitizeEmoji($request->get('settings.emoji', '')),
@@ -79,17 +94,18 @@
79 94 'shape_svg' => CustomSanitizer::sanitizeSvg($request->get('settings.shape_svg', '')),
80 95 'disable_comments' => $request->get('settings.disable_comments') === 'yes' ? 'yes' : 'no',
81 96 'hide_members_count' => $request->get('settings.hide_members_count') === 'yes' ? 'yes' : 'no',
82 97 'course_layout' => $request->get('settings.course_layout') === 'modern' ? 'modern' : 'classic',
83 - 'course_details' => CustomSanitizer::unslashMarkdown(trim($request->get('settings.course_details'))),
98 + 'course_details' => CustomSanitizer::unslashMarkdown(trim((string) $request->get('settings.course_details', ''))),
84 99 'hide_instructor_view' => $request->get('settings.hide_instructor_view') === 'yes' ? 'yes' : 'no',
85 - 'show_instructor_students_count' => $request->get('settings.show_instructor_students_count') === 'yes' ? 'yes' : 'no'
100 + 'show_instructor_students_count' => $request->get('settings.show_instructor_students_count') === 'yes' ? 'yes' : 'no',
101 + 'sequential_lesson_order' => $request->get('settings.sequential_lesson_order') === 'yes' ? 'yes' : 'no',
86 102 ],
87 - 'serial' => $serial
103 + 'serial' => $serial,
88 104 ];
89 105
90 106 $lockScreenType = $request->get('settings.custom_lock_screen');
91 - if (!in_array($lockScreenType, ['yes', 'no', 'redirect']) || $request->get('privacy') != 'private') {
107 + if (!in_array($lockScreenType, [ 'yes', 'no', 'redirect' ]) || $request->get('privacy') != 'private') {
92 108 $lockScreenType = 'no';
93 109 }
94 110
95 111 $courseData['settings']['custom_lock_screen'] = $lockScreenType;
@@ -97,9 +113,9 @@
97 113 if ($lockScreenType === 'redirect') {
98 114 $redirectUrl = $request->get('settings.onboard_redirect_url');
99 115 if (!$redirectUrl || !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
100 116 return $this->sendError([
101 - 'message' => __('Course Redirect URL is not valid', 'fluent-community')
117 + 'message' => __('Course Redirect URL is not valid', 'fluent-community'),
102 118 ]);
103 119 }
104 120 $courseData['settings']['onboard_redirect_url'] = sanitize_url($redirectUrl);
105 121 }
@@ -109,9 +125,9 @@
109 125 }
110 126
111 127 $slug = $request->get('slug');
112 128
113 - $slug = $slug ?: $courseData['title'];
129 + $slug = $slug ? $slug : $courseData['title'];
114 130
115 131 $slug = preg_replace('/[^a-zA-Z0-9-_]/', '', $slug);
116 132
117 133 $slug = sanitize_title($slug, '');
@@ -131,9 +147,9 @@
131 147 do_action('fluent_community/course/before_create', $courseData);
132 148
133 149 $course = Course::create($courseData);
134 150
135 - $imageTypes = ['cover_photo', 'logo'];
151 + $imageTypes = [ 'cover_photo', 'logo' ];
136 152
137 153 $metaData = [];
138 154 foreach ($imageTypes as $type) {
139 155 if (!empty($request->get($type))) {
@@ -145,9 +161,9 @@
145 161 $media->update([
146 162 'is_active' => true,
147 163 'user_id' => get_current_user_id(),
148 164 'sub_object_id' => $course->id,
149 - 'object_source' => 'space_' . $type
165 + 'object_source' => 'space_' . $type,
150 166 ]);
151 167 }
152 168 }
153 169
@@ -166,16 +182,17 @@
166 182 do_action('fluent_community/course/created', $course);
167 183
168 184 return [
169 185 'message' => __('Course has been created successfully', 'fluent-community'),
170 - 'course' => $course
186 + 'course' => $course,
171 187 ];
172 188 }
173 189
174 190 public function findCourse(Request $request, $courseId)
175 191 {
192 + /** @var Course $course */
176 193 $course = Course::where('id', $courseId)
177 - ->with(['owner'])
194 + ->with([ 'owner' ])
178 195 ->firstOrFail();
179 196
180 197 $course->students_count = $course->students()->count();
181 198 $course->course_type = $course->settings['course_type'];
@@ -192,9 +209,9 @@
192 209
193 210 $course = apply_filters('fluent_community/course_info', $course, $request->all());
194 211
195 212 return [
196 - 'course' => $course
213 + 'course' => $course,
197 214 ];
198 215 }
199 216
200 217 public function updateCourse(Request $request, $courseId)
@@ -204,9 +221,9 @@
204 221 'description' => 'required',
205 222 'privacy' => 'required|in:public,private,secret',
206 223 'status' => 'required|in:draft,published,archived',
207 224 'course_type' => 'required|in:self_paced,structured,scheduled',
208 - 'created_by' => 'exists:users,ID'
225 + 'created_by' => 'exists:users,ID',
209 226 ]);
210 227
211 228 $course = Course::findOrFail($courseId);
212 229
@@ -212,12 +229,12 @@
212 229
213 230 $courseData = [
214 231 'title' => $request->getSafe('title', 'sanitize_text_field'),
215 232 'privacy' => $request->get('privacy'),
216 - 'description' => wp_kses_post($request->get('description')),
233 + 'description' => wp_kses_post((string) $request->get('description', '')),
217 234 'status' => $request->get('status'),
218 235 'cover_photo' => $request->getSafe('cover_photo', 'sanitize_url'),
219 - 'parent_id' => $request->get('parent_id') ?: NULL,
236 + 'parent_id' => $request->get('parent_id') ?: null, // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
220 237 ];
221 238
222 239 $slug = $request->get('slug');
223 240 if ($slug && $course->slug != $slug) {
@@ -228,9 +245,9 @@
228 245 ->exists();
229 246
230 247 if ($exist || !$slug) {
231 248 return $this->sendError([
232 - 'message' => __('Slug is already taken. Please use a different slug', 'fluent-community')
249 + 'message' => __('Slug is already taken. Please use a different slug', 'fluent-community'),
233 250 ]);
234 251 }
235 252
236 253 $courseData['slug'] = $slug;
@@ -239,9 +256,9 @@
239 256 if ($request->get('created_by') && Helper::isSiteAdmin()) {
240 257 $courseData['created_by'] = (int)$request->get('created_by');
241 258 }
242 259
243 - $imageTypes = ['cover_photo', 'logo'];
260 + $imageTypes = [ 'cover_photo', 'logo' ];
244 261
245 262 foreach ($imageTypes as $type) {
246 263 if (!empty($request->get($type))) {
247 264 $media = Helper::getMediaFromUrl($request->get($type));
@@ -252,9 +269,9 @@
252 269 $media->update([
253 270 'is_active' => true,
254 271 'user_id' => get_current_user_id(),
255 272 'sub_object_id' => $course->id,
256 - 'object_source' => 'space_' . $type
273 + 'object_source' => 'space_' . $type,
257 274 ]);
258 275 } else {
259 276 $courseData[$type] = null;
260 277 }
@@ -263,9 +280,9 @@
263 280 $existingSettings = $course->settings;
264 281 $existingSettings['course_type'] = $request->get('course_type');
265 282
266 283 $lockScreenType = $request->get('settings.custom_lock_screen');
267 - if (!in_array($lockScreenType, ['yes', 'no', 'redirect']) || $request->get('privacy') != 'private') {
284 + if (!in_array($lockScreenType, [ 'yes', 'no', 'redirect' ]) || $request->get('privacy') != 'private') {
268 285 $lockScreenType = 'no';
269 286 }
270 287 if ($lockScreenType == 'redirect') {
271 288 $redirectUrl = $request->get('settings.onboard_redirect_url');
@@ -270,9 +287,9 @@
270 287 if ($lockScreenType == 'redirect') {
271 288 $redirectUrl = $request->get('settings.onboard_redirect_url');
272 289 if (!$redirectUrl || !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
273 290 return $this->sendError([
274 - 'message' => __('Course Redirect URL is not valid', 'fluent-community')
291 + 'message' => __('Course Redirect URL is not valid', 'fluent-community'),
275 292 ]);
276 293 }
277 294 $existingSettings['onboard_redirect_url'] = sanitize_url($redirectUrl);
278 295 }
@@ -284,10 +301,12 @@
284 301 $existingSettings['hide_members_count'] = $request->get('settings.hide_members_count') === 'yes' ? 'yes' : 'no';
285 302 $existingSettings['hide_instructor_view'] = $request->get('settings.hide_instructor_view') === 'yes' ? 'yes' : 'no';
286 303 $existingSettings['show_instructor_students_count'] = $request->get('settings.show_instructor_students_count') === 'yes' ? 'yes' : 'no';
287 304 $existingSettings['show_paywalls'] = $request->get('settings.show_paywalls') === 'yes' ? 'yes' : 'no';
305 + $existingSettings['show_welcome_banner'] = $request->get('settings.show_welcome_banner') === 'yes' ? 'yes' : 'no';
288 306 $existingSettings['course_layout'] = $request->get('settings.course_layout') === 'modern' ? 'modern' : 'classic';
289 - $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 + $existingSettings['sequential_lesson_order'] = $request->get('settings.sequential_lesson_order') === 'yes' ? 'yes' : 'no';
290 309
291 310 if ($request->get('privacy') == 'public' && $existingSettings['course_type'] == 'self_paced') {
292 311 $existingSettings['public_lesson_view'] = $request->get('settings.public_lesson_view') == 'yes' ? 'yes' : 'no';
293 312 } else {
@@ -326,9 +345,9 @@
326 345 }
327 346
328 347 return [
329 348 'message' => __('Course has been updated successfully.', 'fluent-community'),
330 - 'course' => $course
349 + 'course' => $course,
331 350 ];
332 351 }
333 352
334 353 public function duplicateCourse(Request $request, $courseId)
@@ -342,8 +361,9 @@
342 361 $courseData['created_by'] = get_current_user_id();
343 362
344 363 do_action('fluent_community/course/before_create', $courseData);
345 364
365 + /** @var Course $newCourse */
346 366 $newCourse = $original->replicate();
347 367 $newCourse->title = $courseData['title'];
348 368 $newCourse->slug = $courseData['slug'];
349 369 $newCourse->status = $courseData['status'];
@@ -358,18 +378,24 @@
358 378 ->where('space_id', $original->id)
359 379 ->get();
360 380
361 381 foreach ($topics as $topic) {
382 + /** @var CourseTopic $topic */
362 383 $newTopic = $topic->replicate();
363 384 $newTopic->space_id = $newCourse->id;
364 385 $newTopic->save();
365 386
387 + do_action('fluent_community/section/created', $newTopic, $newCourse);
388 +
366 389 foreach ($topic->lessons as $lesson) {
390 + /** @var CourseLesson $lesson */
367 391 $newLesson = $lesson->replicate();
368 392 $newLesson->space_id = $newCourse->id;
369 393 $newLesson->parent_id = $newTopic->id;
370 394 $newLesson->save();
371 395 CourseHelper::copyLessonDocuments($lesson, $newLesson);
396 +
397 + do_action('fluent_community/lesson/created', $newLesson, $newTopic);
372 398 }
373 399 }
374 400
375 401 do_action('fluent_community/course/created', $newCourse);
@@ -375,9 +401,9 @@
375 401 do_action('fluent_community/course/created', $newCourse);
376 402
377 403 return [
378 404 'message' => __('Course duplicated successfully.', 'fluent-community'),
379 - 'course' => $newCourse
405 + 'course' => $newCourse,
380 406 ];
381 407 }
382 408
383 409 public function deleteCourse(Request $request, $courseId)
@@ -419,33 +445,34 @@
419 445
420 446 do_action('fluent_community/course/deleted', $courseId);
421 447
422 448 return [
423 - 'message' => __('Course has been deleted successfully along with all the associated data', 'fluent-community')
449 + 'message' => __('Course has been deleted successfully along with all the associated data', 'fluent-community'),
424 450 ];
425 451 }
426 452
427 453 public function getCourseComments(Request $request, $courseId)
428 454 {
429 - Course::findOrFail($courseId);
455 + $course = Course::findOrFail($courseId);
430 456
431 - $comments = Comment::whereHas('post', function ($q) use ($courseId) {
432 - return $q->where('space_id', $courseId);
433 - })
457 + $comments = Comment::byContentModerationAccessStatus($this->getUser(), $course)
458 + ->whereHas('post', function ($q) use ($courseId) {
459 + return $q->where('space_id', $courseId);
460 + })
434 461 ->orderBy('id', 'DESC')
435 462 ->with([
436 463 'post' => function ($q) {
437 - return $q->select(['id', 'title', 'slug']);
464 + return $q->select([ 'id', 'title', 'slug' ]);
438 465 },
439 466 'xprofile' => function ($q) {
440 467 $q->select(ProfileHelper::getXProfilePublicFields());
441 - }
468 + },
442 469 ])
443 470 ->paginate();
444 471
445 472 foreach ($comments as $comment) {
446 473 if ($comment->user) {
447 - $comment->user->makeHidden(['user_email']);
474 + $comment->user->makeHidden([ 'user_email' ]);
448 475 }
449 476 $likedIds = FeedsHelper::getLikedIdsByUserFeedId($comment->post_id, get_current_user_id());
450 477 if ($likedIds && in_array($comment->id, $likedIds)) {
451 478 $comment->liked = 1;
@@ -452,9 +479,9 @@
452 479 }
453 480 }
454 481
455 482 $data = [
456 - 'comments' => $comments
483 + 'comments' => $comments,
457 484 ];
458 485
459 486 return apply_filters('fluent_community/admin_course_comments_api_response', $data, $request->all());
460 487 }
@@ -464,13 +491,27 @@
464 491 Course::findOrFail($courseId);
465 492
466 493 $search = $request->getSafe('search');
467 494
468 - $students = XProfile::whereHas('space_pivot', function ($q) use ($courseId) {
469 - return $q->where('space_id', $courseId)
470 - ->where('role', 'student');
471 - })
472 - ->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)
473 514 ->whereHas('user')
474 515 ->with([
475 516 'space_pivot' => function ($q) use ($courseId) {
476 517 return $q->where('space_id', $courseId);
@@ -475,9 +516,15 @@
475 516 'space_pivot' => function ($q) use ($courseId) {
476 517 return $q->where('space_id', $courseId);
477 518 },
478 519 ])
479 - ->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)
480 527 ->paginate();
481 528
482 529 $studentUserIds = $students->pluck('user_id')->toArray();
483 530
@@ -487,9 +534,9 @@
487 534 $student->progress = $progressMap[$student->user_id] ?? 0;
488 535 }
489 536
490 537 $data = [
491 - 'students' => $students
538 + 'students' => $students,
492 539 ];
493 540
494 541 return apply_filters('fluent_community/admin_course_students_api_response', $data, $request->all());
495 542 }
@@ -498,9 +545,9 @@
498 545 {
499 546 $course = Course::findOrFail($courseId);
500 547
501 548 $this->validate($request->all(), [
502 - 'user_id' => 'required|exists:users,ID'
549 + 'user_id' => 'required|exists:users,ID',
503 550 ]);
504 551
505 552 $userId = (int)$request->get('user_id');
506 553 $targetUser = User::findOrFail($userId);
@@ -507,9 +554,9 @@
507 554 $xprofile = $targetUser->syncXProfile();
508 555
509 556 if ($xprofile && $xprofile->status != 'active') {
510 557 return $this->sendError([
511 - 'message' => __('Selected user is not active', 'fluent-community')
558 + 'message' => __('Selected user is not active', 'fluent-community'),
512 559 ]);
513 560 }
514 561
515 562 $enrolled = CourseHelper::enrollCourse($course, $userId, 'by_admin');
@@ -515,14 +562,14 @@
515 562 $enrolled = CourseHelper::enrollCourse($course, $userId, 'by_admin');
516 563
517 564 if (!$enrolled) {
518 565 return $this->sendError([
519 - 'message' => __('User is already added to this course.', 'fluent-community')
566 + 'message' => __('User is already added to this course.', 'fluent-community'),
520 567 ]);
521 568 }
522 569
523 570 return [
524 - 'message' => __('User has been added to this course', 'fluent-community')
571 + 'message' => __('User has been added to this course', 'fluent-community'),
525 572 ];
526 573 }
527 574
528 575 public function removeStudent(Request $request, $courseId, $studentId)
@@ -534,9 +581,9 @@
534 581 ->first();
535 582
536 583 if (!$student) {
537 584 return $this->sendError([
538 - 'message' => __('Selected user is not a student of this course', 'fluent-community')
585 + 'message' => __('Selected user is not a student of this course', 'fluent-community'),
539 586 ]);
540 587 }
541 588
542 589 Helper::removeFromSpace($course, $studentId, 'by_admin');
@@ -541,15 +588,42 @@
541 588
542 589 Helper::removeFromSpace($course, $studentId, 'by_admin');
543 590
544 591 return [
545 - 'message' => __('Student has been removed from this course', 'fluent-community')
592 + 'message' => __('Student has been removed from this course', 'fluent-community'),
546 593 ];
547 594 }
548 595
596 + public function resetStudentProgress(Request $request, $courseId, $studentId)
597 + {
598 + Course::findOrFail($courseId);
599 +
600 + $pivot = SpaceUserPivot::where('space_id', $courseId)
601 + ->where('user_id', $studentId)
602 + ->where('role', 'student')
603 + ->first();
604 +
605 + if (!$pivot) {
606 + return $this->sendError([
607 + 'message' => __('This student is not enrolled in this course.', 'fluent-community'),
608 + ]);
609 + }
610 +
611 + if (!CourseHelper::resetCourseProgress($courseId, (int) $studentId)) {
612 + return $this->sendError([
613 + 'message' => __("Student's progress could not be reset. Please try again.", 'fluent-community'),
614 + ]);
615 + }
616 +
617 + return [
618 + 'message' => __("Student's progress has been reset.", 'fluent-community'),
619 + ];
620 + }
621 +
549 622 public function getSections(Request $request, $courseId)
550 623 {
551 624 $course = Course::findOrFail($courseId);
625 + /** @var Course $course */
552 626
553 627 $sectionsQuery = CourseTopic::where('space_id', $courseId)
554 628 ->orderBy('priority', 'ASC')
555 629 ->orderBy('id', 'ASC');
@@ -555,21 +629,23 @@
555 629 ->orderBy('id', 'ASC');
556 630
557 631 if (in_array('only_published', $request->get('conditions', []))) {
558 632 $sectionsQuery->where('status', 'published')
559 - ->with(['lessons' => function ($q) {
633 + ->with([
634 + 'lessons' => function ($q) {
560 635 $q->where('status', 'published');
561 - }]);
636 + },
637 + ]);
562 638 }
563 639
564 640 if (empty($request->get('conditions', []))) {
565 - $sectionsQuery->with(['lessons']);
641 + $sectionsQuery->with([ 'lessons' ]);
566 642 }
567 643
568 644 $sections = $sectionsQuery->get();
569 645
570 646 $data = [
571 - 'sections' => $sections
647 + 'sections' => $sections,
572 648 ];
573 649
574 650 if ($request->get('with_lock_screen')) {
575 651 $data['lockscreen'] = LockscreenService::getLockscreenSettings($course);
@@ -584,13 +660,13 @@
584 660 ->whereHas('course', function ($query) use ($courseId) {
585 661 $query->where('id', $courseId);
586 662 })
587 663 ->where('id', $topicId)
588 - ->with(['lessons'])
664 + ->with([ 'lessons' ])
589 665 ->firstOrFail();
590 666
591 667 $data = [
592 - 'topic' => $topic
668 + 'topic' => $topic,
593 669 ];
594 670
595 671 return apply_filters('fluent_community/admin_course_section_api_response', $data, $request->all());
596 672 }
@@ -603,9 +679,10 @@
603 679 ->whereIn('id', array_keys($indexes))
604 680 ->get();
605 681
606 682 foreach ($sections as $section) {
607 - if (!isset($indexes[$section->id])) continue;
683 + if (!isset($indexes[$section->id])) { continue;
684 + }
608 685 $section->priority = $indexes[$section->id];
609 686 $section->save();
610 687 }
611 688
@@ -610,9 +687,9 @@
610 687 }
611 688
612 689 return [
613 690 'sections' => $sections,
614 - 'message' => __('Section indexes have been updated successfully.', 'fluent-community')
691 + 'message' => __('Section indexes have been updated successfully.', 'fluent-community'),
615 692 ];
616 693 }
617 694
618 695 public function resetLessonIndexes(Request $request, $courseId, $sectionId)
@@ -624,9 +701,10 @@
624 701 ->whereIn('id', array_keys($indexes))
625 702 ->get();
626 703
627 704 foreach ($lessons as $lesson) {
628 - if (!isset($indexes[$lesson->id])) continue;
705 + if (!isset($indexes[$lesson->id])) { continue;
706 + }
629 707 $lesson->priority = $indexes[$lesson->id];
630 708 $lesson->save();
631 709 }
632 710
@@ -631,9 +709,9 @@
631 709 }
632 710
633 711 return [
634 712 'lessons' => $lessons,
635 - 'message' => __('Lesson indexes have been updated successfully.', 'fluent-community')
713 + 'message' => __('Lesson indexes have been updated successfully.', 'fluent-community'),
636 714 ];
637 715 }
638 716
639 717 public function moveLesson(Request $request, $courseId)
@@ -641,18 +719,17 @@
641 719 $lessonId = $request->getSafe('lesson_id', 'intval');
642 720 $sectionId = $request->getSafe('section_id', 'intval');
643 721
644 722 Course::findOrFail($courseId);
645 - CourseTopic::findOrFail($sectionId);
723 + $section = CourseTopic::where('space_id', $courseId)->findOrFail($sectionId);
724 + $lesson = CourseLesson::where('space_id', $courseId)->findOrFail($lessonId);
646 725
647 - $lesson = CourseLesson::findOrFail($lessonId);
648 -
649 726 $lesson->update([
650 - 'parent_id' => $sectionId
727 + 'parent_id' => $section->id,
651 728 ]);
652 729
653 730 return [
654 - 'message' => __('Lesson has been moved successfully', 'fluent-community')
731 + 'message' => __('Lesson has been moved successfully', 'fluent-community'),
655 732 ];
656 733 }
657 734
658 735 public function createSection(Request $request, $courseId)
@@ -657,30 +734,32 @@
657 734
658 735 public function createSection(Request $request, $courseId)
659 736 {
660 737 $this->validate($request->all(), [
661 - 'title' => 'required'
738 + 'title' => 'required',
662 739 ]);
663 740
664 741 $sectionData = [
665 742 'title' => $request->getSafe('title'),
666 743 'space_id' => $courseId,
667 - 'status' => 'published'
744 + 'status' => 'published',
668 745 ];
669 746
670 - Course::findOrFail($courseId);
747 + $course = Course::findOrFail($courseId);
671 748
672 749 $latestPriority = CourseTopic::where('type', 'course_section')->where('space_id', $courseId)->max('priority');
673 750
674 - $sectionData['priority'] = $latestPriority ? $latestPriority + 1 : 0;
751 + $sectionData['priority'] = (int) $latestPriority + 1;
675 752
676 753 $section = CourseTopic::create($sectionData);
677 754
678 755 $section->load('lessons');
679 756
757 + do_action('fluent_community/section/created', $section, $course);
758 +
680 759 return [
681 760 'message' => __('Section has been created successfully.', 'fluent-community'),
682 - 'section' => $section
761 + 'section' => $section,
683 762 ];
684 763 }
685 764
686 765 public function updateSection(Request $request, $courseId, $tipicId)
@@ -686,9 +765,9 @@
686 765 public function updateSection(Request $request, $courseId, $tipicId)
687 766 {
688 767 $this->validate($request->all(), [
689 768 'title' => 'required',
690 - 'status' => 'required|in:draft,published,archived'
769 + 'status' => 'required|in:draft,published,archived',
691 770 ]);
692 771
693 772 Course::findOrFail($courseId);
694 773
@@ -698,9 +777,9 @@
698 777
699 778
700 779 $topicData = [
701 780 'title' => $request->getSafe('title'),
702 - 'status' => $request->get('status')
781 + 'status' => $request->get('status'),
703 782 ];
704 783
705 784 $topic->update($topicData);
706 785
@@ -705,9 +784,9 @@
705 784 $topic->update($topicData);
706 785
707 786 return [
708 787 'message' => __('Topic has been updated successfully.', 'fluent-community'),
709 - 'topic' => $topic
788 + 'topic' => $topic,
710 789 ];
711 790 }
712 791
713 792 public function patchSection(Request $request, $courseId, $tipicId)
@@ -717,21 +796,32 @@
717 796 $topic = CourseTopic::where('space_id', $courseId)
718 797 ->where('id', $tipicId)
719 798 ->firstOrFail();
720 799
721 - $acceptedFields = ['title', 'status'];
800 + $acceptedFields = [ 'title', 'status' ];
722 801
723 802 if ($course->getCourseType() == 'scheduled') {
724 803 $acceptedFields[] = 'scheduled_at';
725 - } else if ($course->getCourseType() == 'structured') {
804 + } elseif ($course->getCourseType() == 'structured') {
726 805 $acceptedFields[] = 'reactions_count';
727 806 }
728 807
808 + // Request::only() does not sanitize. Section titles currently render as escaped
809 + // text, so this is hygiene rather than a live sink, but keep the stored value
810 + // clean so a future v-html render cannot turn it into one.
729 811 $topicData = $request->only($acceptedFields);
730 812
813 + if (isset($topicData['title'])) {
814 + $topicData['title'] = sanitize_text_field($topicData['title']);
815 + }
816 +
817 + if (isset($topicData['status'])) {
818 + $topicData['status'] = sanitize_text_field($topicData['status']);
819 + }
820 +
731 821 if (!empty($topicData['scheduled_at'])) {
732 822 $topic->reactions_count = 0;
733 - } else if (isset($topicData['reactions_count'])) {
823 + } elseif (isset($topicData['reactions_count'])) {
734 824 $topic->scheduled_at = null;
735 825 $topic->reactions_count = $topicData['reactions_count'];
736 826 }
737 827
@@ -756,9 +846,9 @@
756 846 }
757 847
758 848 return [
759 849 'message' => __('Topic has been updated successfully.', 'fluent-community'),
760 - 'topic' => $topic
850 + 'topic' => $topic,
761 851 ];
762 852 }
763 853
764 854 public function copySection(Request $request, $toCourseId)
@@ -769,12 +859,13 @@
769 859 $fromCourse = Course::findOrFail($fromCourseId);
770 860
771 861 if (!$fromCourse->isCourseAdmin()) {
772 862 return $this->sendError([
773 - 'message' => __('You do not have permission to access this course', 'fluent-community')
863 + 'message' => __('You do not have permission to access this course', 'fluent-community'),
774 864 ]);
775 865 }
776 866
867 + /** @var CourseTopic $originalSection */
777 868 $originalSection = CourseTopic::where('id', $sectionId)
778 869 ->where('space_id', $fromCourseId)
779 870 ->firstOrFail();
780 871
@@ -781,20 +872,26 @@
781 872 $toCourse = Course::findOrFail($toCourseId);
782 873
783 874 $latestPriority = CourseTopic::where('type', 'course_section')->where('space_id', $toCourse->id)->max('priority');
784 875
876 + /** @var CourseTopic $newSection */
785 877 $newSection = $originalSection->replicate();
786 878 $newSection->space_id = $toCourse->id;
787 - $newSection->priority = $latestPriority ? $latestPriority + 1 : 0;
879 + $newSection->priority = (int) $latestPriority + 1;
788 880 $newSection->save();
789 881
882 + do_action('fluent_community/section/created', $newSection, $toCourse);
883 +
790 884 $originalLessons = CourseLesson::where('parent_id', $originalSection->id)->get();
791 885 foreach ($originalLessons as $lesson) {
886 + /** @var CourseLesson $lesson */
792 887 $newLesson = $lesson->replicate();
793 888 $newLesson->space_id = $toCourse->id;
794 889 $newLesson->parent_id = $newSection->id;
795 890 $newLesson->save();
796 891 CourseHelper::copyLessonDocuments($lesson, $newLesson);
892 +
893 + do_action('fluent_community/lesson/created', $newLesson, $newSection);
797 894 }
798 895
799 896 $newSection->load('lessons');
800 897
@@ -799,9 +896,9 @@
799 896 $newSection->load('lessons');
800 897
801 898 return [
802 899 'message' => __('Section has been copied to the selected course', 'fluent-community'),
803 - 'section' => $newSection
900 + 'section' => $newSection,
804 901 ];
805 902 }
806 903
807 904 public function deleteSection(Request $request, $courseId, $sectionId)
@@ -807,9 +904,9 @@
807 904 public function deleteSection(Request $request, $courseId, $sectionId)
808 905 {
809 906 $topic = CourseTopic::where([
810 907 'id' => $sectionId,
811 - 'space_id' => $courseId
908 + 'space_id' => $courseId,
812 909 ])->firstOrFail();
813 910
814 911 do_action('fluent_community/section/before_deleted', $topic);
815 912
@@ -816,9 +913,9 @@
816 913 $topic->delete();
817 914
818 915 $lessons = CourseLesson::where([
819 916 'parent_id' => $sectionId,
820 - 'space_id' => $courseId
917 + 'space_id' => $courseId,
821 918 ])->get();
822 919
823 920 foreach ($lessons as $lesson) {
824 921 do_action('fluent_community/lesson/before_deleted', $lesson);
@@ -825,9 +922,9 @@
825 922 $lesson->delete();
826 923 }
827 924
828 925 return [
829 - 'message' => __('Section has been deleted successfully.', 'fluent-community')
926 + 'message' => __('Section has been deleted successfully.', 'fluent-community'),
830 927 ];
831 928 }
832 929
833 930 public function getLessons(Request $request, $courseId)
@@ -834,9 +931,10 @@
834 931 {
835 932 Course::findOrFail($courseId);
836 933
837 934 $lessons = CourseLesson::where('space_id', $courseId)
838 - ->orderBy('priority', 'ASC');
935 + ->orderBy('priority', 'ASC')
936 + ->orderBy('id', 'ASC');
839 937
840 938 $topicId = (int)$request->get('topic_id');
841 939
842 940 if ($topicId) {
@@ -845,9 +943,9 @@
845 943
846 944 $lessons = $lessons->get();
847 945
848 946 $data = [
849 - 'lessons' => $lessons
947 + 'lessons' => $lessons,
850 948 ];
851 949
852 950 return apply_filters('fluent_community/admin_course_lessons_api_response', $data, $request->all());
853 951 }
@@ -857,23 +955,26 @@
857 955 $lesson = CourseLesson::whereHas('course', function ($query) use ($courseId) {
858 956 $query->where('id', $courseId);
859 957 })
860 958 ->where('id', $lessonId)
861 - ->with(['topic', 'course'])
959 + ->with([ 'topic', 'course' ])
862 960 ->firstOrFail();
863 961
864 962 $data = [
865 - 'lesson' => $lesson
963 + 'lesson' => $lesson,
866 964 ];
867 965
868 966 return apply_filters('fluent_community/admin_course_lesson_api_response', $data, $request->all());
869 967 }
870 968
969 + /**
970 + * Expects `title` and `section_id` at the top level of the request.
971 + */
871 972 public function createLesson(Request $request, $courseId)
872 973 {
873 974 $this->validate($request->all(), [
874 975 'title' => 'required',
875 - 'section_id' => 'required'
976 + 'section_id' => 'required',
876 977 ]);
877 978
878 979 $sectionId = (int)$request->get('section_id');
879 980
@@ -886,9 +987,9 @@
886 987 $lessonData = [
887 988 'title' => $request->getSafe('title'),
888 989 'parent_id' => $topic->id,
889 990 'space_id' => $courseId,
890 - 'status' => 'draft'
991 + 'status' => 'draft',
891 992 ];
892 993
893 994 $latestPriority = CourseLesson::where('type', 'course_lesson')
894 995 ->where('parent_id', $sectionId)
@@ -894,9 +995,9 @@
894 995 ->where('parent_id', $sectionId)
895 996 ->where('space_id', $courseId)
896 997 ->max('priority');
897 998
898 - $lessonData['priority'] = $latestPriority ? $latestPriority + 1 : 0;
999 + $lessonData['priority'] = (int) $latestPriority + 1;
899 1000
900 1001 $lessonData = apply_filters('fluent_community/lesson/create_data', $lessonData, $request);
901 1002
902 1003 $lesson = CourseLesson::create($lessonData);
@@ -902,24 +1003,29 @@
902 1003 $lesson = CourseLesson::create($lessonData);
903 1004
904 1005 $lesson = CourseLesson::findOrFail($lesson->id);
905 1006
1007 + do_action('fluent_community/lesson/created', $lesson, $topic);
1008 +
906 1009 return [
907 1010 'message' => __('Lesson has been created successfully.', 'fluent-community'),
908 - 'lesson' => $lesson
1011 + 'lesson' => $lesson,
909 1012 ];
910 1013 }
911 1014
1015 + /**
1016 + * Expects the fields nested under `lesson`, referencing the section as `parent_id`.
1017 + */
912 1018 public function updateLesson(Request $request, $courseId, $lessionId)
913 1019 {
914 1020 Course::findOrFail($courseId);
915 1021
916 - $lessonData = $request->get('lesson');
1022 + $lessonData = (array)$request->get('lesson');
917 1023
918 1024 $this->validate($lessonData, [
919 1025 'title' => 'required',
920 1026 'parent_id' => 'required',
921 - 'status' => 'required|in:draft,published,archived'
1027 + 'status' => 'required|in:draft,published,archived',
922 1028 ]);
923 1029
924 1030 CourseTopic::whereHas('course', function ($query) use ($courseId) {
925 1031 $query->where('id', $courseId);
@@ -926,8 +1032,9 @@
926 1032 })
927 1033 ->where('id', $lessonData['parent_id'])
928 1034 ->firstOrFail();
929 1035
1036 + /** @var CourseLesson $lesson */
930 1037 $lesson = CourseLesson::where('id', $lessionId)
931 1038 ->where('space_id', $courseId)
932 1039 ->firstOrFail();
933 1040
@@ -935,12 +1042,12 @@
935 1042
936 1043 $updatedMeta = CourseHelper::sanitizeLessonMeta(Arr::get($lessonData, 'meta', []), $lesson);
937 1044 $updatedMeta['document_ids'] = Arr::get($lesson->meta, 'document_ids', []);
938 1045
939 - if ($mediaId = Arr::get($updatedMeta, 'featured_image_id')) {
1046 + if ($mediaId = Arr::get($updatedMeta, 'featured_image_id')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
940 1047 if (!$lesson->isQuizType()) {
941 1048 $media = wp_get_attachment_image_url($mediaId);
942 - $lesson->featured_image = $media ?: null;
1049 + $lesson->featured_image = $media ? $media : null;
943 1050 } else {
944 1051 $media = Helper::getMediaFromUrl(sanitize_url($mediaId));
945 1052 if ($media && !$media->is_active) {
946 1053 Helper::removeMediaByUrl($lesson->featured_image, $lesson->id);
@@ -948,9 +1055,9 @@
948 1055 $media->update([
949 1056 'is_active' => true,
950 1057 'user_id' => get_current_user_id(),
951 1058 'sub_object_id' => $lesson->id,
952 - 'object_source' => 'quiz_thumbnail_' . $lesson->id
1059 + 'object_source' => 'quiz_thumbnail_' . $lesson->id,
953 1060 ]);
954 1061 }
955 1062 }
956 1063 } else {
@@ -958,14 +1065,18 @@
958 1065 $lesson->featured_image = null;
959 1066 }
960 1067
961 1068 $updateData = array_filter([
962 - 'title' => sanitize_text_field(Arr::get($lessonData, 'title')),
963 - 'message' => CourseHelper::santizeLessonBody(Arr::get($lessonData, 'message')),
964 - 'status' => Arr::get($lessonData, 'status'),
965 - 'meta' => wp_parse_args($updatedMeta, $lesson->meta)
1069 + 'title' => sanitize_text_field(Arr::get($lessonData, 'title')),
1070 + 'status' => Arr::get($lessonData, 'status'),
1071 + 'meta' => wp_parse_args($updatedMeta, $lesson->meta),
966 1072 ]);
967 1073
1074 + // message bypasses array_filter so an emptied lesson body still saves
1075 + if (array_key_exists('message', $lessonData)) {
1076 + $updateData['message'] = CourseHelper::santizeLessonBody((string) Arr::get($lessonData, 'message'));
1077 + }
1078 +
968 1079 $updateData = apply_filters('fluent_community/lesson/update_data', $updateData, $lesson);
969 1080
970 1081 $lesson->fill($updateData);
971 1082 $dirtyFields = $lesson->getDirty();
@@ -973,8 +1084,12 @@
973 1084 if ($dirtyFields) {
974 1085 $lesson->save();
975 1086 $isNewlyPublished = $lesson->status === 'published' && $previousStatus !== 'published';
976 1087 do_action('fluent_community/lesson/updated', $lesson, $dirtyFields, $isNewlyPublished);
1088 +
1089 + if ($isNewlyPublished) {
1090 + do_action('fluent_community/lesson/published', $lesson);
1091 + }
977 1092 }
978 1093
979 1094 do_action('fluent_community/lesson/additional_media_updated', $request->all(), $lesson, $updateData);
980 1095
@@ -979,9 +1094,9 @@
979 1094 do_action('fluent_community/lesson/additional_media_updated', $request->all(), $lesson, $updateData);
980 1095
981 1096 return [
982 1097 'message' => __('Lesson has been updated successfully.', 'fluent-community'),
983 - 'lesson' => $lesson
1098 + 'lesson' => $lesson,
984 1099 ];
985 1100 }
986 1101
987 1102 public function patchLesson(Request $request, $courseId, $lessionId)
@@ -991,12 +1106,39 @@
991 1106 })
992 1107 ->where('id', $lessionId)
993 1108 ->firstOrFail();
994 1109
995 - $acceptedFields = ['title', 'status', 'slug'];
1110 + $previousStatus = $lesson->status;
996 1111
997 - $lessonData = array_filter($request->only($acceptedFields));
1112 + $acceptedFields = [ 'title', 'status', 'slug' ];
998 1113
1114 + // empty title/slug/status must not overwrite, but a literal "0" is a valid value
1115 + // Request::only() is a plain array pick with no sanitization, so each field is
1116 + // sanitized here the same way createLesson/updateLesson do it. The title is
1117 + // rendered with v-html in the course views, so it must not carry markup.
1118 + $lessonData = array_filter($request->only($acceptedFields), function ($value) {
1119 + return $value !== null && $value !== '';
1120 + });
1121 +
1122 + if (isset($lessonData['title'])) {
1123 + $lessonData['title'] = sanitize_text_field($lessonData['title']);
1124 + }
1125 +
1126 + if (isset($lessonData['slug'])) {
1127 + // sanitize_title() alone let an author set a slug already taken by a
1128 + // sibling lesson, which getLessonBySlug() then resolves arbitrarily.
1129 + $lessonData['slug'] = CourseLesson::uniqueSlug(
1130 + $lessonData['slug'],
1131 + $lesson->space_id,
1132 + $lesson->id,
1133 + Arr::get($lessonData, 'title', $lesson->title)
1134 + );
1135 + }
1136 +
1137 + if (isset($lessonData['status']) && !in_array($lessonData['status'], ['draft', 'published', 'archived'], true)) {
1138 + unset($lessonData['status']);
1139 + }
1140 +
999 1141 if (Arr::get($lessonData, 'status') === 'published' && $lesson->status !== 'published') {
1000 1142 if (empty($lesson->scheduled_at)) {
1001 1143 $lessonData['scheduled_at'] = current_time('mysql');
1002 1144 }
@@ -1001,18 +1143,32 @@
1001 1143 $lessonData['scheduled_at'] = current_time('mysql');
1002 1144 }
1003 1145 }
1004 1146
1147 + // message bypasses the empty-value filter above so an emptied lesson body still saves
1148 + if ($request->exists('message')) {
1149 + $lessonData['message'] = CourseHelper::santizeLessonBody((string) $request->get('message'));
1150 + }
1151 +
1005 1152 if (!empty($lessonData)) {
1006 1153 $lesson->fill($lessonData);
1007 - if ($lesson->isDirty()) {
1154 + $dirtyFields = $lesson->getDirty();
1155 +
1156 + if ($dirtyFields) {
1008 1157 $lesson->save();
1158 +
1159 + $isNewlyPublished = $lesson->status === 'published' && $previousStatus !== 'published';
1160 + do_action('fluent_community/lesson/updated', $lesson, $dirtyFields, $isNewlyPublished);
1161 +
1162 + if ($isNewlyPublished) {
1163 + do_action('fluent_community/lesson/published', $lesson);
1164 + }
1009 1165 }
1010 1166 }
1011 1167
1012 1168 return [
1013 1169 'message' => __('Lesson has been updated successfully.', 'fluent-community'),
1014 - 'lesson' => $lesson
1170 + 'lesson' => $lesson,
1015 1171 ];
1016 1172 }
1017 1173
1018 1174 public function deleteLesson(Request $request, $courseId, $lessionId)
@@ -1027,17 +1183,76 @@
1027 1183
1028 1184 $lesson->delete();
1029 1185
1030 1186 return [
1031 - 'message' => __('Lesson has been deleted successfully.', 'fluent-community')
1187 + 'message' => __('Lesson has been deleted successfully.', 'fluent-community'),
1032 1188 ];
1033 1189 }
1034 1190
1191 + public function duplicateLesson(Request $request, $courseId, $lessonId)
1192 + {
1193 + Course::findOrFail($courseId);
1194 +
1195 + $lesson = CourseLesson::where('id', $lessonId)
1196 + ->where('space_id', $courseId)
1197 + ->firstOrFail();
1198 +
1199 + $siblings = CourseLesson::where('parent_id', $lesson->parent_id)
1200 + ->where('space_id', $courseId)
1201 + ->orderBy('priority', 'ASC')
1202 + ->orderBy('id', 'ASC')
1203 + ->get();
1204 +
1205 + $sourceIndex = $siblings->search(function ($sibling) use ($lesson) {
1206 + return (int)$sibling->id === (int)$lesson->id;
1207 + });
1208 +
1209 + if ($sourceIndex === false) {
1210 + $sourceIndex = $siblings->count() - 1;
1211 + }
1212 +
1213 + $existingTitles = $siblings->pluck('title')->toArray();
1214 + $duplicateTitle = $lesson->title . ' (Copy)';
1215 + if (in_array($duplicateTitle, $existingTitles, true)) {
1216 + $counter = 2;
1217 + while (in_array($lesson->title . ' (Copy ' . $counter . ')', $existingTitles, true)) {
1218 + ++$counter;
1219 + }
1220 + $duplicateTitle = $lesson->title . ' (Copy ' . $counter . ')';
1221 + }
1222 +
1223 + /** @var CourseLesson $newLesson */
1224 + $newLesson = $lesson->replicate();
1225 + $newLesson->title = $duplicateTitle;
1226 + $newLesson->slug = null;
1227 + $newLesson->priority = $sourceIndex + 1;
1228 + $newLesson->save();
1229 +
1230 + $orderedIds = $siblings->pluck('id')->toArray();
1231 + array_splice($orderedIds, $sourceIndex + 1, 0, [ $newLesson->id ]);
1232 +
1233 + foreach ($orderedIds as $index => $siblingId) {
1234 + CourseLesson::where('id', $siblingId)->update([ 'priority' => $index ]);
1235 + }
1236 +
1237 + $newLesson = CourseLesson::findOrFail($newLesson->id);
1238 +
1239 + CourseHelper::copyLessonDocuments($lesson, $newLesson);
1240 +
1241 + do_action('fluent_community/lesson/created', $newLesson, $newLesson->topic);
1242 + do_action('fluent_community/lesson/duplicated', $newLesson, $lesson);
1243 +
1244 + return [
1245 + 'message' => __('Lesson has been duplicated successfully.', 'fluent-community'),
1246 + 'lesson' => $newLesson,
1247 + ];
1248 + }
1249 +
1035 1250 public function getOtherUsers(Request $request, $courseId)
1036 1251 {
1037 1252 $selects = [
1038 1253 'ID',
1039 - 'display_name'
1254 + 'display_name',
1040 1255 ];
1041 1256
1042 1257 if (current_user_can('list_users')) {
1043 1258 $selects[] = 'user_email';
@@ -1042,9 +1257,9 @@
1042 1257 if (current_user_can('list_users')) {
1043 1258 $selects[] = 'user_email';
1044 1259 }
1045 1260
1046 - $userQuery = User::select(['ID'])
1261 + $userQuery = User::select([ 'ID' ])
1047 1262 ->whereDoesntHave('space_pivot', function ($q) use ($courseId) {
1048 1263 $q->where('space_id', $courseId);
1049 1264 })
1050 1265 ->limit(100)
@@ -1053,9 +1268,9 @@
1053 1268 if (is_multisite()) {
1054 1269 global $wpdb;
1055 1270 $blogId = get_current_blog_id();
1056 1271 $blogPrefix = $wpdb->get_blog_prefix($blogId);
1057 - $userQuery->whereHas('usermeta', function($q) use ($blogPrefix) {
1272 + $userQuery->whereHas('usermeta', function ($q) use ($blogPrefix) {
1058 1273 $q->where('meta_key', $blogPrefix . 'capabilities');
1059 1274 });
1060 1275 }
1061 1276
@@ -1067,9 +1282,9 @@
1067 1282 ->whereIn('ID', $userIds)
1068 1283 ->paginate(100);
1069 1284
1070 1285 $data = [
1071 - 'users' => $users
1286 + 'users' => $users,
1072 1287 ];
1073 1288
1074 1289 return apply_filters('fluent_community/admin_course_non_members_api_response', $data, $request->all());
1075 1290 }
@@ -1089,9 +1304,9 @@
1089 1304 $course->save();
1090 1305
1091 1306 return [
1092 1307 'message' => __('Links have been updated for the course', 'fluent-community'),
1093 - 'links' => $links
1308 + 'links' => $links,
1094 1309 ];
1095 1310 }
1096 1311
1097 1312 public function getMetaSettings(Request $request, $id)
@@ -1100,14 +1315,14 @@
1100 1315 $metaSettings = apply_filters('fluent_community/course/meta_fields', [], $course, $request->all());
1101 1316
1102 1317 if (!$metaSettings) {
1103 1318 return [
1104 - 'meta_settings' => null
1319 + 'meta_settings' => null,
1105 1320 ];
1106 1321 }
1107 1322
1108 1323 return [
1109 - 'meta_settings' => $metaSettings
1324 + 'meta_settings' => $metaSettings,
1110 1325 ];
1111 1326 }
1112 1327
1113 1328 public function getOtherInstructors(Request $request, $courseId)
@@ -1115,15 +1330,24 @@
1115 1330 $search = $request->getSafe('search');
1116 1331
1117 1332 Course::findOrFail($courseId);
1118 1333
1119 - $instructors = User::select(['ID', 'display_name', 'user_email'])
1334 + $selects = [
1335 + 'ID',
1336 + 'display_name',
1337 + ];
1338 +
1339 + if (current_user_can('list_users')) {
1340 + $selects[] = 'user_email';
1341 + }
1342 +
1343 + $instructors = User::select($selects)
1120 1344 ->limit(100)
1121 1345 ->searchBy($search)
1122 1346 ->get();
1123 1347
1124 1348 $data = [
1125 - 'instructors' => $instructors
1349 + 'instructors' => $instructors,
1126 1350 ];
1127 1351
1128 1352 return apply_filters('fluent_community/admin_course_other_instructors_api_response', $data, $request->all());
1129 1353 }