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