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 +321 -114 2.5.02.11.0 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,42 @@
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 + 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 +
566 622 public function getSections(Request $request, $courseId)
567 623 {
568 624 $course = Course::findOrFail($courseId);
625 + /** @var Course $course */
569 626
570 627 $sectionsQuery = CourseTopic::where('space_id', $courseId)
571 628 ->orderBy('priority', 'ASC')
572 629 ->orderBy('id', 'ASC');
@@ -572,21 +629,23 @@
572 629 ->orderBy('id', 'ASC');
573 630
574 631 if (in_array('only_published', $request->get('conditions', []))) {
575 632 $sectionsQuery->where('status', 'published')
576 - ->with(['lessons' => function ($q) {
633 + ->with([
634 + 'lessons' => function ($q) {
577 635 $q->where('status', 'published');
578 - }]);
636 + },
637 + ]);
579 638 }
580 639
581 640 if (empty($request->get('conditions', []))) {
582 - $sectionsQuery->with(['lessons']);
641 + $sectionsQuery->with([ 'lessons' ]);
583 642 }
584 643
585 644 $sections = $sectionsQuery->get();
586 645
587 646 $data = [
588 - 'sections' => $sections
647 + 'sections' => $sections,
589 648 ];
590 649
591 650 if ($request->get('with_lock_screen')) {
592 651 $data['lockscreen'] = LockscreenService::getLockscreenSettings($course);
@@ -601,13 +660,13 @@
601 660 ->whereHas('course', function ($query) use ($courseId) {
602 661 $query->where('id', $courseId);
603 662 })
604 663 ->where('id', $topicId)
605 - ->with(['lessons'])
664 + ->with([ 'lessons' ])
606 665 ->firstOrFail();
607 666
608 667 $data = [
609 - 'topic' => $topic
668 + 'topic' => $topic,
610 669 ];
611 670
612 671 return apply_filters('fluent_community/admin_course_section_api_response', $data, $request->all());
613 672 }
@@ -620,9 +679,10 @@
620 679 ->whereIn('id', array_keys($indexes))
621 680 ->get();
622 681
623 682 foreach ($sections as $section) {
624 - if (!isset($indexes[$section->id])) continue;
683 + if (!isset($indexes[$section->id])) { continue;
684 + }
625 685 $section->priority = $indexes[$section->id];
626 686 $section->save();
627 687 }
628 688
@@ -627,9 +687,9 @@
627 687 }
628 688
629 689 return [
630 690 'sections' => $sections,
631 - 'message' => __('Section indexes have been updated successfully.', 'fluent-community')
691 + 'message' => __('Section indexes have been updated successfully.', 'fluent-community'),
632 692 ];
633 693 }
634 694
635 695 public function resetLessonIndexes(Request $request, $courseId, $sectionId)
@@ -641,9 +701,10 @@
641 701 ->whereIn('id', array_keys($indexes))
642 702 ->get();
643 703
644 704 foreach ($lessons as $lesson) {
645 - if (!isset($indexes[$lesson->id])) continue;
705 + if (!isset($indexes[$lesson->id])) { continue;
706 + }
646 707 $lesson->priority = $indexes[$lesson->id];
647 708 $lesson->save();
648 709 }
649 710
@@ -648,9 +709,9 @@
648 709 }
649 710
650 711 return [
651 712 'lessons' => $lessons,
652 - 'message' => __('Lesson indexes have been updated successfully.', 'fluent-community')
713 + 'message' => __('Lesson indexes have been updated successfully.', 'fluent-community'),
653 714 ];
654 715 }
655 716
656 717 public function moveLesson(Request $request, $courseId)
@@ -658,18 +719,17 @@
658 719 $lessonId = $request->getSafe('lesson_id', 'intval');
659 720 $sectionId = $request->getSafe('section_id', 'intval');
660 721
661 722 Course::findOrFail($courseId);
662 - CourseTopic::findOrFail($sectionId);
723 + $section = CourseTopic::where('space_id', $courseId)->findOrFail($sectionId);
724 + $lesson = CourseLesson::where('space_id', $courseId)->findOrFail($lessonId);
663 725
664 - $lesson = CourseLesson::findOrFail($lessonId);
665 -
666 726 $lesson->update([
667 - 'parent_id' => $sectionId
727 + 'parent_id' => $section->id,
668 728 ]);
669 729
670 730 return [
671 - 'message' => __('Lesson has been moved successfully', 'fluent-community')
731 + 'message' => __('Lesson has been moved successfully', 'fluent-community'),
672 732 ];
673 733 }
674 734
675 735 public function createSection(Request $request, $courseId)
@@ -674,30 +734,32 @@
674 734
675 735 public function createSection(Request $request, $courseId)
676 736 {
677 737 $this->validate($request->all(), [
678 - 'title' => 'required'
738 + 'title' => 'required',
679 739 ]);
680 740
681 741 $sectionData = [
682 742 'title' => $request->getSafe('title'),
683 743 'space_id' => $courseId,
684 - 'status' => 'published'
744 + 'status' => 'published',
685 745 ];
686 746
687 - Course::findOrFail($courseId);
747 + $course = Course::findOrFail($courseId);
688 748
689 749 $latestPriority = CourseTopic::where('type', 'course_section')->where('space_id', $courseId)->max('priority');
690 750
691 - $sectionData['priority'] = $latestPriority ? $latestPriority + 1 : 0;
751 + $sectionData['priority'] = (int) $latestPriority + 1;
692 752
693 753 $section = CourseTopic::create($sectionData);
694 754
695 755 $section->load('lessons');
696 756
757 + do_action('fluent_community/section/created', $section, $course);
758 +
697 759 return [
698 760 'message' => __('Section has been created successfully.', 'fluent-community'),
699 - 'section' => $section
761 + 'section' => $section,
700 762 ];
701 763 }
702 764
703 765 public function updateSection(Request $request, $courseId, $tipicId)
@@ -703,9 +765,9 @@
703 765 public function updateSection(Request $request, $courseId, $tipicId)
704 766 {
705 767 $this->validate($request->all(), [
706 768 'title' => 'required',
707 - 'status' => 'required|in:draft,published,archived'
769 + 'status' => 'required|in:draft,published,archived',
708 770 ]);
709 771
710 772 Course::findOrFail($courseId);
711 773
@@ -715,9 +777,9 @@
715 777
716 778
717 779 $topicData = [
718 780 'title' => $request->getSafe('title'),
719 - 'status' => $request->get('status')
781 + 'status' => $request->get('status'),
720 782 ];
721 783
722 784 $topic->update($topicData);
723 785
@@ -722,9 +784,9 @@
722 784 $topic->update($topicData);
723 785
724 786 return [
725 787 'message' => __('Topic has been updated successfully.', 'fluent-community'),
726 - 'topic' => $topic
788 + 'topic' => $topic,
727 789 ];
728 790 }
729 791
730 792 public function patchSection(Request $request, $courseId, $tipicId)
@@ -734,21 +796,32 @@
734 796 $topic = CourseTopic::where('space_id', $courseId)
735 797 ->where('id', $tipicId)
736 798 ->firstOrFail();
737 799
738 - $acceptedFields = ['title', 'status'];
800 + $acceptedFields = [ 'title', 'status' ];
739 801
740 802 if ($course->getCourseType() == 'scheduled') {
741 803 $acceptedFields[] = 'scheduled_at';
742 - } else if ($course->getCourseType() == 'structured') {
804 + } elseif ($course->getCourseType() == 'structured') {
743 805 $acceptedFields[] = 'reactions_count';
744 806 }
745 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.
746 811 $topicData = $request->only($acceptedFields);
747 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 +
748 821 if (!empty($topicData['scheduled_at'])) {
749 822 $topic->reactions_count = 0;
750 - } else if (isset($topicData['reactions_count'])) {
823 + } elseif (isset($topicData['reactions_count'])) {
751 824 $topic->scheduled_at = null;
752 825 $topic->reactions_count = $topicData['reactions_count'];
753 826 }
754 827
@@ -773,9 +846,9 @@
773 846 }
774 847
775 848 return [
776 849 'message' => __('Topic has been updated successfully.', 'fluent-community'),
777 - 'topic' => $topic
850 + 'topic' => $topic,
778 851 ];
779 852 }
780 853
781 854 public function copySection(Request $request, $toCourseId)
@@ -786,12 +859,13 @@
786 859 $fromCourse = Course::findOrFail($fromCourseId);
787 860
788 861 if (!$fromCourse->isCourseAdmin()) {
789 862 return $this->sendError([
790 - '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'),
791 864 ]);
792 865 }
793 866
867 + /** @var CourseTopic $originalSection */
794 868 $originalSection = CourseTopic::where('id', $sectionId)
795 869 ->where('space_id', $fromCourseId)
796 870 ->firstOrFail();
797 871
@@ -798,20 +872,26 @@
798 872 $toCourse = Course::findOrFail($toCourseId);
799 873
800 874 $latestPriority = CourseTopic::where('type', 'course_section')->where('space_id', $toCourse->id)->max('priority');
801 875
876 + /** @var CourseTopic $newSection */
802 877 $newSection = $originalSection->replicate();
803 878 $newSection->space_id = $toCourse->id;
804 - $newSection->priority = $latestPriority ? $latestPriority + 1 : 0;
879 + $newSection->priority = (int) $latestPriority + 1;
805 880 $newSection->save();
806 881
882 + do_action('fluent_community/section/created', $newSection, $toCourse);
883 +
807 884 $originalLessons = CourseLesson::where('parent_id', $originalSection->id)->get();
808 885 foreach ($originalLessons as $lesson) {
886 + /** @var CourseLesson $lesson */
809 887 $newLesson = $lesson->replicate();
810 888 $newLesson->space_id = $toCourse->id;
811 889 $newLesson->parent_id = $newSection->id;
812 890 $newLesson->save();
813 891 CourseHelper::copyLessonDocuments($lesson, $newLesson);
892 +
893 + do_action('fluent_community/lesson/created', $newLesson, $newSection);
814 894 }
815 895
816 896 $newSection->load('lessons');
817 897
@@ -816,9 +896,9 @@
816 896 $newSection->load('lessons');
817 897
818 898 return [
819 899 'message' => __('Section has been copied to the selected course', 'fluent-community'),
820 - 'section' => $newSection
900 + 'section' => $newSection,
821 901 ];
822 902 }
823 903
824 904 public function deleteSection(Request $request, $courseId, $sectionId)
@@ -824,9 +904,9 @@
824 904 public function deleteSection(Request $request, $courseId, $sectionId)
825 905 {
826 906 $topic = CourseTopic::where([
827 907 'id' => $sectionId,
828 - 'space_id' => $courseId
908 + 'space_id' => $courseId,
829 909 ])->firstOrFail();
830 910
831 911 do_action('fluent_community/section/before_deleted', $topic);
832 912
@@ -833,9 +913,9 @@
833 913 $topic->delete();
834 914
835 915 $lessons = CourseLesson::where([
836 916 'parent_id' => $sectionId,
837 - 'space_id' => $courseId
917 + 'space_id' => $courseId,
838 918 ])->get();
839 919
840 920 foreach ($lessons as $lesson) {
841 921 do_action('fluent_community/lesson/before_deleted', $lesson);
@@ -842,9 +922,9 @@
842 922 $lesson->delete();
843 923 }
844 924
845 925 return [
846 - 'message' => __('Section has been deleted successfully.', 'fluent-community')
926 + 'message' => __('Section has been deleted successfully.', 'fluent-community'),
847 927 ];
848 928 }
849 929
850 930 public function getLessons(Request $request, $courseId)
@@ -851,9 +931,10 @@
851 931 {
852 932 Course::findOrFail($courseId);
853 933
854 934 $lessons = CourseLesson::where('space_id', $courseId)
855 - ->orderBy('priority', 'ASC');
935 + ->orderBy('priority', 'ASC')
936 + ->orderBy('id', 'ASC');
856 937
857 938 $topicId = (int)$request->get('topic_id');
858 939
859 940 if ($topicId) {
@@ -862,9 +943,9 @@
862 943
863 944 $lessons = $lessons->get();
864 945
865 946 $data = [
866 - 'lessons' => $lessons
947 + 'lessons' => $lessons,
867 948 ];
868 949
869 950 return apply_filters('fluent_community/admin_course_lessons_api_response', $data, $request->all());
870 951 }
@@ -874,23 +955,26 @@
874 955 $lesson = CourseLesson::whereHas('course', function ($query) use ($courseId) {
875 956 $query->where('id', $courseId);
876 957 })
877 958 ->where('id', $lessonId)
878 - ->with(['topic', 'course'])
959 + ->with([ 'topic', 'course' ])
879 960 ->firstOrFail();
880 961
881 962 $data = [
882 - 'lesson' => $lesson
963 + 'lesson' => $lesson,
883 964 ];
884 965
885 966 return apply_filters('fluent_community/admin_course_lesson_api_response', $data, $request->all());
886 967 }
887 968
969 + /**
970 + * Expects `title` and `section_id` at the top level of the request.
971 + */
888 972 public function createLesson(Request $request, $courseId)
889 973 {
890 974 $this->validate($request->all(), [
891 975 'title' => 'required',
892 - 'section_id' => 'required'
976 + 'section_id' => 'required',
893 977 ]);
894 978
895 979 $sectionId = (int)$request->get('section_id');
896 980
@@ -903,9 +987,9 @@
903 987 $lessonData = [
904 988 'title' => $request->getSafe('title'),
905 989 'parent_id' => $topic->id,
906 990 'space_id' => $courseId,
907 - 'status' => 'draft'
991 + 'status' => 'draft',
908 992 ];
909 993
910 994 $latestPriority = CourseLesson::where('type', 'course_lesson')
911 995 ->where('parent_id', $sectionId)
@@ -911,9 +995,9 @@
911 995 ->where('parent_id', $sectionId)
912 996 ->where('space_id', $courseId)
913 997 ->max('priority');
914 998
915 - $lessonData['priority'] = $latestPriority ? $latestPriority + 1 : 0;
999 + $lessonData['priority'] = (int) $latestPriority + 1;
916 1000
917 1001 $lessonData = apply_filters('fluent_community/lesson/create_data', $lessonData, $request);
918 1002
919 1003 $lesson = CourseLesson::create($lessonData);
@@ -919,24 +1003,29 @@
919 1003 $lesson = CourseLesson::create($lessonData);
920 1004
921 1005 $lesson = CourseLesson::findOrFail($lesson->id);
922 1006
1007 + do_action('fluent_community/lesson/created', $lesson, $topic);
1008 +
923 1009 return [
924 1010 'message' => __('Lesson has been created successfully.', 'fluent-community'),
925 - 'lesson' => $lesson
1011 + 'lesson' => $lesson,
926 1012 ];
927 1013 }
928 1014
1015 + /**
1016 + * Expects the fields nested under `lesson`, referencing the section as `parent_id`.
1017 + */
929 1018 public function updateLesson(Request $request, $courseId, $lessionId)
930 1019 {
931 1020 Course::findOrFail($courseId);
932 1021
933 - $lessonData = $request->get('lesson');
1022 + $lessonData = (array)$request->get('lesson');
934 1023
935 1024 $this->validate($lessonData, [
936 1025 'title' => 'required',
937 1026 'parent_id' => 'required',
938 - 'status' => 'required|in:draft,published,archived'
1027 + 'status' => 'required|in:draft,published,archived',
939 1028 ]);
940 1029
941 1030 CourseTopic::whereHas('course', function ($query) use ($courseId) {
942 1031 $query->where('id', $courseId);
@@ -943,8 +1032,9 @@
943 1032 })
944 1033 ->where('id', $lessonData['parent_id'])
945 1034 ->firstOrFail();
946 1035
1036 + /** @var CourseLesson $lesson */
947 1037 $lesson = CourseLesson::where('id', $lessionId)
948 1038 ->where('space_id', $courseId)
949 1039 ->firstOrFail();
950 1040
@@ -952,12 +1042,12 @@
952 1042
953 1043 $updatedMeta = CourseHelper::sanitizeLessonMeta(Arr::get($lessonData, 'meta', []), $lesson);
954 1044 $updatedMeta['document_ids'] = Arr::get($lesson->meta, 'document_ids', []);
955 1045
956 - if ($mediaId = Arr::get($updatedMeta, 'featured_image_id')) {
1046 + if ($mediaId = Arr::get($updatedMeta, 'featured_image_id')) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
957 1047 if (!$lesson->isQuizType()) {
958 1048 $media = wp_get_attachment_image_url($mediaId);
959 - $lesson->featured_image = $media ?: null;
1049 + $lesson->featured_image = $media ? $media : null;
960 1050 } else {
961 1051 $media = Helper::getMediaFromUrl(sanitize_url($mediaId));
962 1052 if ($media && !$media->is_active) {
963 1053 Helper::removeMediaByUrl($lesson->featured_image, $lesson->id);
@@ -965,9 +1055,9 @@
965 1055 $media->update([
966 1056 'is_active' => true,
967 1057 'user_id' => get_current_user_id(),
968 1058 'sub_object_id' => $lesson->id,
969 - 'object_source' => 'quiz_thumbnail_' . $lesson->id
1059 + 'object_source' => 'quiz_thumbnail_' . $lesson->id,
970 1060 ]);
971 1061 }
972 1062 }
973 1063 } else {
@@ -975,14 +1065,18 @@
975 1065 $lesson->featured_image = null;
976 1066 }
977 1067
978 1068 $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)
1069 + 'title' => sanitize_text_field(Arr::get($lessonData, 'title')),
1070 + 'status' => Arr::get($lessonData, 'status'),
1071 + 'meta' => wp_parse_args($updatedMeta, $lesson->meta),
983 1072 ]);
984 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 +
985 1079 $updateData = apply_filters('fluent_community/lesson/update_data', $updateData, $lesson);
986 1080
987 1081 $lesson->fill($updateData);
988 1082 $dirtyFields = $lesson->getDirty();
@@ -990,8 +1084,12 @@
990 1084 if ($dirtyFields) {
991 1085 $lesson->save();
992 1086 $isNewlyPublished = $lesson->status === 'published' && $previousStatus !== 'published';
993 1087 do_action('fluent_community/lesson/updated', $lesson, $dirtyFields, $isNewlyPublished);
1088 +
1089 + if ($isNewlyPublished) {
1090 + do_action('fluent_community/lesson/published', $lesson);
1091 + }
994 1092 }
995 1093
996 1094 do_action('fluent_community/lesson/additional_media_updated', $request->all(), $lesson, $updateData);
997 1095
@@ -996,9 +1094,9 @@
996 1094 do_action('fluent_community/lesson/additional_media_updated', $request->all(), $lesson, $updateData);
997 1095
998 1096 return [
999 1097 'message' => __('Lesson has been updated successfully.', 'fluent-community'),
1000 - 'lesson' => $lesson
1098 + 'lesson' => $lesson,
1001 1099 ];
1002 1100 }
1003 1101
1004 1102 public function patchLesson(Request $request, $courseId, $lessionId)
@@ -1008,12 +1106,39 @@
1008 1106 })
1009 1107 ->where('id', $lessionId)
1010 1108 ->firstOrFail();
1011 1109
1012 - $acceptedFields = ['title', 'status', 'slug'];
1110 + $previousStatus = $lesson->status;
1013 1111
1014 - $lessonData = array_filter($request->only($acceptedFields));
1112 + $acceptedFields = [ 'title', 'status', 'slug' ];
1015 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 +
1016 1141 if (Arr::get($lessonData, 'status') === 'published' && $lesson->status !== 'published') {
1017 1142 if (empty($lesson->scheduled_at)) {
1018 1143 $lessonData['scheduled_at'] = current_time('mysql');
1019 1144 }
@@ -1018,18 +1143,32 @@
1018 1143 $lessonData['scheduled_at'] = current_time('mysql');
1019 1144 }
1020 1145 }
1021 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 +
1022 1152 if (!empty($lessonData)) {
1023 1153 $lesson->fill($lessonData);
1024 - if ($lesson->isDirty()) {
1154 + $dirtyFields = $lesson->getDirty();
1155 +
1156 + if ($dirtyFields) {
1025 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 + }
1026 1165 }
1027 1166 }
1028 1167
1029 1168 return [
1030 1169 'message' => __('Lesson has been updated successfully.', 'fluent-community'),
1031 - 'lesson' => $lesson
1170 + 'lesson' => $lesson,
1032 1171 ];
1033 1172 }
1034 1173
1035 1174 public function deleteLesson(Request $request, $courseId, $lessionId)
@@ -1044,17 +1183,76 @@
1044 1183
1045 1184 $lesson->delete();
1046 1185
1047 1186 return [
1048 - 'message' => __('Lesson has been deleted successfully.', 'fluent-community')
1187 + 'message' => __('Lesson has been deleted successfully.', 'fluent-community'),
1049 1188 ];
1050 1189 }
1051 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 +
1052 1250 public function getOtherUsers(Request $request, $courseId)
1053 1251 {
1054 1252 $selects = [
1055 1253 'ID',
1056 - 'display_name'
1254 + 'display_name',
1057 1255 ];
1058 1256
1059 1257 if (current_user_can('list_users')) {
1060 1258 $selects[] = 'user_email';
@@ -1059,9 +1257,9 @@
1059 1257 if (current_user_can('list_users')) {
1060 1258 $selects[] = 'user_email';
1061 1259 }
1062 1260
1063 - $userQuery = User::select(['ID'])
1261 + $userQuery = User::select([ 'ID' ])
1064 1262 ->whereDoesntHave('space_pivot', function ($q) use ($courseId) {
1065 1263 $q->where('space_id', $courseId);
1066 1264 })
1067 1265 ->limit(100)
@@ -1070,9 +1268,9 @@
1070 1268 if (is_multisite()) {
1071 1269 global $wpdb;
1072 1270 $blogId = get_current_blog_id();
1073 1271 $blogPrefix = $wpdb->get_blog_prefix($blogId);
1074 - $userQuery->whereHas('usermeta', function($q) use ($blogPrefix) {
1272 + $userQuery->whereHas('usermeta', function ($q) use ($blogPrefix) {
1075 1273 $q->where('meta_key', $blogPrefix . 'capabilities');
1076 1274 });
1077 1275 }
1078 1276
@@ -1084,9 +1282,9 @@
1084 1282 ->whereIn('ID', $userIds)
1085 1283 ->paginate(100);
1086 1284
1087 1285 $data = [
1088 - 'users' => $users
1286 + 'users' => $users,
1089 1287 ];
1090 1288
1091 1289 return apply_filters('fluent_community/admin_course_non_members_api_response', $data, $request->all());
1092 1290 }
@@ -1106,9 +1304,9 @@
1106 1304 $course->save();
1107 1305
1108 1306 return [
1109 1307 'message' => __('Links have been updated for the course', 'fluent-community'),
1110 - 'links' => $links
1308 + 'links' => $links,
1111 1309 ];
1112 1310 }
1113 1311
1114 1312 public function getMetaSettings(Request $request, $id)
@@ -1117,14 +1315,14 @@
1117 1315 $metaSettings = apply_filters('fluent_community/course/meta_fields', [], $course, $request->all());
1118 1316
1119 1317 if (!$metaSettings) {
1120 1318 return [
1121 - 'meta_settings' => null
1319 + 'meta_settings' => null,
1122 1320 ];
1123 1321 }
1124 1322
1125 1323 return [
1126 - 'meta_settings' => $metaSettings
1324 + 'meta_settings' => $metaSettings,
1127 1325 ];
1128 1326 }
1129 1327
1130 1328 public function getOtherInstructors(Request $request, $courseId)
@@ -1132,15 +1330,24 @@
1132 1330 $search = $request->getSafe('search');
1133 1331
1134 1332 Course::findOrFail($courseId);
1135 1333
1136 - $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)
1137 1344 ->limit(100)
1138 1345 ->searchBy($search)
1139 1346 ->get();
1140 1347
1141 1348 $data = [
1142 - 'instructors' => $instructors
1349 + 'instructors' => $instructors,
1143 1350 ];
1144 1351
1145 1352 return apply_filters('fluent_community/admin_course_other_instructors_api_response', $data, $request->all());
1146 1353 }