PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.7
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.7
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / app / Http / Controllers / ProfileController.php

ProfileController.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.7.7, at app/Http/Controllers/ProfileController.php

930 lines 36.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\App\Http\Controllers;
4
5 use FluentCommunity\App\Functions\Utility;
6 use FluentCommunity\App\Models\Comment;
7 use FluentCommunity\App\Models\Feed;
8 use FluentCommunity\App\Models\NotificationSubscription;
9 use FluentCommunity\App\Models\Space;
10 use FluentCommunity\App\Models\SpaceGroup;
11 use FluentCommunity\App\Models\SpaceUserPivot;
12 use FluentCommunity\App\Models\User;
13 use FluentCommunity\App\Models\XProfile;
14 use FluentCommunity\App\Services\CustomSanitizer;
15 use FluentCommunity\App\Services\FeedsHelper;
16 use FluentCommunity\App\Services\Helper;
17 use FluentCommunity\App\Services\NotificationPref;
18 use FluentCommunity\App\Services\ProfileHelper;
19 use FluentCommunity\Framework\Http\Request\Request;
20 use FluentCommunity\Framework\Support\Arr;
21 use FluentCommunity\Modules\Course\Model\CourseLesson;
22 use FluentCommunity\Modules\Course\Model\CourseTopic;
23 use FluentCommunity\Modules\Course\Services\CourseHelper;
24
25 class ProfileController extends Controller
26 {
27 public function getProfile(Request $request, $userName)
28 {
29 /** @var XProfile $xprofile */
30 $xprofile = XProfile::where('username', $userName)
31 ->firstOrFail();
32
33 if ($xprofile->status != 'active' && !Helper::isModerator()) {
34 return $this->sendError([
35 'message' => __('This profile is not active', 'fluent-community')
36 ], 403);
37 }
38
39 $canViewProfile = Utility::canViewUserProfile($xprofile->user_id);
40
41 $user = get_user_by('ID', $xprofile->user_id);
42
43 $profile = [
44 'user_id' => $xprofile->user_id,
45 'is_verified' => $xprofile->is_verified,
46 'display_name' => $xprofile->display_name,
47 'username' => $xprofile->username,
48 'avatar' => $xprofile->avatar,
49 'has_custom_avatar' => $xprofile->hasCustomAvatar(),
50 'cover_photo' => Arr::get($xprofile->meta, 'cover_photo'),
51 'headline' => Arr::get($xprofile->meta, 'headline', ''),
52 'total_points' => $xprofile->total_points,
53 'badge_slugs' => (array)Arr::get($xprofile->meta, 'badge_slug', []),
54 'status' => $xprofile->status,
55 'is_restricted' => !$canViewProfile,
56 'canViewUserSpaces' => ProfileHelper::canViewUserSpaces($xprofile->user_id, $this->getUser())
57 ];
58
59 if (Utility::showLastActivity()) {
60 $profile['last_activity'] = $xprofile->last_activity;
61 }
62
63 if ($canViewProfile) {
64 $profile['website'] = Arr::get($xprofile->meta, 'website');
65 $profile['created_at'] = $xprofile->created_at->format('Y-m-d H:i:s');
66 $profile['social_links'] = (object) Arr::get($xprofile->meta, 'social_links', []);
67 $profile['compilation_score'] = $xprofile->getCompletionScore();
68 $profile['short_description_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($xprofile->short_description));
69 }
70
71 $currentUserId = get_current_user_id();
72
73 $isOwn = $xprofile->user_id == $currentUserId;
74
75 $isAdmin = Helper::isSiteAdmin($currentUserId);
76
77 if ($isOwn || $isAdmin) {
78 $enableUserSync = Utility::getPrivacySetting('enable_user_sync') === 'yes';
79 $nameArray = explode(' ', trim($xprofile->display_name));
80 $xprofileFirstName = array_shift($nameArray);
81 $xprofileLastName = implode(' ', $nameArray);
82
83 $profile['email'] = $user->user_email;
84 $profile['first_name'] = $enableUserSync ? $user->first_name : $xprofileFirstName;
85 $profile['last_name'] = $enableUserSync ? $user->last_name : $xprofileLastName;
86 $profile['short_description'] = $xprofile->short_description;
87 $profile['can_change_username'] = $isAdmin || Utility::getPrivacySetting('can_customize_username') === 'yes';
88 $profile['can_change_email'] = current_user_can('edit_users') || (Utility::getPrivacySetting('can_change_email') === 'yes' && $isOwn);
89 $profile['can_change_password'] = $isOwn && Utility::getPrivacySetting('can_change_password') === 'yes';
90 }
91
92 $profileBaseUrl = Helper::baseUrl('u/' . $xprofile->username . '/');
93
94 $profile['profile_navs'] = [
95 [
96 'slug' => 'user_profile',
97 'title' => __('About', 'fluent-community'),
98 'url' => $profileBaseUrl,
99 'wrapper_class' => 'fcom_profile_about',
100 'route' => [
101 'name' => 'user_profile'
102 ]
103 ],
104 [
105 'slug' => 'user_profile_feeds',
106 'title' => __('Posts', 'fluent-community'),
107 'wrapper_class' => 'fcom_profile_posts',
108 'url' => $profileBaseUrl . 'posts',
109 'route' => [
110 'name' => 'user_profile_feeds'
111 ]
112 ]
113 ];
114
115 if ($profile['canViewUserSpaces']) {
116 $profile['profile_navs'][] = [
117 'slug' => 'user_spaces',
118 'wrapper_class' => 'fcom_profile_spaces',
119 'title' => __('Spaces', 'fluent-community'),
120 'url' => $profileBaseUrl . 'spaces',
121 'route' => [
122 'name' => 'user_spaces'
123 ]
124 ];
125
126 if (Helper::isFeatureEnabled('course_module')) {
127 $profile['profile_navs'][] = [
128 'slug' => 'user_courses',
129 'wrapper_class' => 'fcom_profile_courses',
130 'title' => __('Courses', 'fluent-community'),
131 'url' => $profileBaseUrl . 'courses',
132 'route' => [
133 'name' => 'user_courses'
134 ]
135 ];
136 }
137 }
138
139 $profile['profile_navs'][] = [
140 'slug' => 'user_comments',
141 'wrapper_class' => 'fcom_profile_comments',
142 'title' => __('Comments', 'fluent-community'),
143 'url' => $profileBaseUrl . 'comments',
144 'route' => [
145 'name' => 'user_comments'
146 ]
147 ];
148
149 $profile['profile_nav_actions'] = [];
150
151 $profile = apply_filters('fluent_community/profile_view_data', $profile, $xprofile, $isAdmin);
152
153 return [
154 'profile' => $profile
155 ];
156 }
157
158 public function patchProfile(Request $request, $userName)
159 {
160 $xprofile = $this->verfifyAndGetProfile($userName);
161
162 $updateData = $request->get('data', []);
163
164 if (!empty($updateData['status']) && $updateData['status'] === 'deactivated' && $xprofile->status === 'active') {
165 // handle deactivation
166 $canDeactivate = Utility::getPrivacySetting('can_deactive_account') === 'yes' || Helper::isSiteAdmin();
167 if (!$canDeactivate) {
168 return $this->sendError([
169 'message' => __('You are not allowed to deactivate this account.', 'fluent-community')
170 ]);
171 }
172
173 $xprofile->status = '';
174 $xprofile->save();
175 update_user_meta($xprofile->user_id, '_fcom_deactivated_at', current_time('mysql'));
176 do_action('fluent_community/profile_deactivated', $xprofile);
177
178 return [
179 'message' => __('Your profile has been deactivated successfully.', 'fluent-community')
180 ];
181 }
182
183 $mediaTypes = ['cover_photo', 'avatar'];
184
185 foreach ($mediaTypes as $type) {
186 if (!empty($updateData[$type])) {
187 $media = Helper::getMediaFromUrl($updateData[$type]);
188 if (!$media || $media->is_active) {
189 return $this->sendError([
190 'message' => __('Invalid media image. Please upload a new one.', 'fluent-community')
191 ]);
192 }
193
194 $updateData[$type] = $media->public_url;
195
196 $media->update([
197 'is_active' => true,
198 'user_id' => $xprofile->user_id,
199 'object_source' => 'user_' . $type
200 ]);
201 }
202 }
203
204 $deletedMedias = [];
205
206 if (isset($updateData['avatar'])) {
207
208 if ($xprofile->hasCustomAvatar()) {
209 $deletedMedias[] = $xprofile->attributes['avatar'];
210 }
211
212 $xprofile->avatar = $updateData['avatar'];
213
214 if (defined('FLUENTCRM')) {
215 $contact = $xprofile->contact;
216
217 if ($contact) {
218 $contact->update([
219 'avatar' => $updateData['avatar'] ?: null
220 ]);
221 }
222 }
223
224 if (empty($updateData['avatar'])) {
225 Utility::forgetCache('user_avatar_' . $xprofile->user_id);
226 }
227 }
228
229 if (isset($updateData['cover_photo'])) {
230 $deletedMedias[] = Arr::get($xprofile->meta, 'cover_photo');
231 $xprofile->meta = wp_parse_args(['cover_photo' => $updateData['cover_photo']], $xprofile->meta);
232 }
233
234 $xprofile->save();
235
236 if ($deletedMedias = array_filter($deletedMedias)) {
237 do_action('fluent_community/remove_medias_by_url', $deletedMedias, [
238 'user_id' => $xprofile->user_id,
239 'object_sources' => ['user_avatar', 'user_cover_photo']
240 ]);
241 }
242
243 return [
244 'message' => __('Profile updated', 'fluent-community')
245 ];
246 }
247
248 public function updateProfile(Request $request, $userName)
249 {
250 $currentUser = $this->getUser(true);
251 $data = $request->get('data', []);
252
253 /** @var XProfile $xProfile */
254 $xProfile = XProfile::where('username', $userName)->firstOrFail();
255
256 if ($xProfile->user_id != get_current_user_id()) {
257 if(!$currentUser->isCommunityModerator()) {
258 return $this->sendError([
259 'message' => __('You are not allowed to update this profile', 'fluent-community')
260 ]);
261 }
262 }
263
264 $this->validate($data, [
265 'first_name' => 'required',
266 ], [
267 'first_name.required' => __('First name is required', 'fluent-community')
268 ]);
269
270 $updateData = Arr::only($data, ['first_name', 'last_name', 'short_description', 'website']);
271
272 $updateData = apply_filters('fluent_community/update_profile_data', $updateData, $data, $xProfile, $currentUser);
273
274 $meta = $xProfile->meta;
275
276 $userNameChanged = false;
277
278 if ($currentUser->isCommunityModerator()) {
279 $updateData['is_verified'] = Arr::get($data, 'is_verified') ? 1 : 0;
280 $updateData['status'] = Arr::get($data, 'status', 'active');
281 $userName = Arr::get($data, 'username');
282
283 if (user_can($xProfile->user_id, 'list_users')) {
284 $updateData['status'] = 'active';
285 }
286
287 if ($userName) {
288 // Check if username is exit or not
289 $userName = CustomSanitizer::sanitizeUserName($userName);
290
291 if (!$userName) {
292 return $this->sendError([
293 'message' => __('Invalid username. Only Latin characters with _ & - are allowed.', 'fluent-community')
294 ]);
295 }
296
297 if (XProfile::where('username', $userName)->where('user_id', '!=', $xProfile->user_id)->exists()) {
298 return $this->sendError([
299 'message' => __('Community Username already taken by someone else', 'fluent-community')
300 ]);
301 }
302
303 $userExist = get_user_by('user_login', $userName);
304
305 if ($userExist && $userExist->ID != $xProfile->user_id) {
306 return $this->sendError([
307 'message' => __('Username already taken by someone else. Please use a different username.', 'fluent-community')
308 ]);
309 }
310
311 $updateData['username'] = $userName;
312 $userNameChanged = $userName != $xProfile->username;
313 }
314
315 if (Helper::isFeatureEnabled('user_badge')) {
316 $badgeSlug = (array)Arr::get($data, 'badge_slugs', []);
317 $meta['badge_slug'] = $badgeSlug;
318 }
319 } else if (Utility::getPrivacySetting('can_customize_username')) {
320 $userName = Arr::get($data, 'username');
321
322 if ($xProfile->username != $userName) {
323 $userName = strtolower(CustomSanitizer::sanitizeUserName($userName));
324 if (!$userName) {
325 return $this->sendError([
326 'message' => __('Invalid username. Only Latin characters with _ & - are allowed.', 'fluent-community')
327 ]);
328 }
329
330 if (XProfile::where('username', $userName)->where('user_id', '!=', $xProfile->user_id)->exists()) {
331 return $this->sendError([
332 'message' => __('Community Username already taken by someone else', 'fluent-community')
333 ]);
334 }
335
336 if (strlen($userName) < 3) {
337 return $this->sendError([
338 'message' => __('Username should be at least 3 characters long.', 'fluent-community')
339 ]);
340 }
341
342 $reservedUserNames = ProfileHelper::getReservedUserNames();
343 if (in_array($userName, $reservedUserNames)) {
344 return $this->sendError([
345 'message' => __('Please use another username. This username is reserved', 'fluent-community')
346 ]);
347 }
348
349 $updateData['username'] = $userName;
350 $userNameChanged = true;
351 }
352 }
353
354 $updateData['display_name'] = trim(sanitize_text_field(Arr::get($data, 'first_name') . ' ' . Arr::get($data, 'last_name')));
355
356 $updateData['short_description'] = CustomSanitizer::unslashMarkdown(sanitize_textarea_field(trim(Arr::get($data, 'short_description'))));
357 $meta['website'] = sanitize_url(Arr::get($data, 'website'));
358 $meta['headline'] = sanitize_text_field(trim(Arr::get($data, 'headline', '')));
359 $socialLinks = Arr::get($data, 'social_links', []);
360
361 $maxDescriptionLength = apply_filters('fluent_community/max_profile_description_length', 5000);
362 if ($updateData['short_description'] && strlen($updateData['short_description']) > $maxDescriptionLength) {
363 return $this->sendError([
364 'message' => sprintf(
365 /* translators: %d: Maximum number of characters allowed in the profile bio. */
366 __('Profile bio should not exceed %d characters.', 'fluent-community'),
367 $maxDescriptionLength
368 )
369 ]);
370 }
371
372 $maxHeadlineLength = apply_filters('fluent_community/max_profile_headline_length', 60);
373 if ($meta['headline'] && mb_strlen($meta['headline']) > $maxHeadlineLength) {
374 return $this->sendError([
375 'message' => sprintf(
376 /* translators: %d: Maximum number of characters allowed in the profile headline. */
377 __('Headline should not exceed %d characters.', 'fluent-community'),
378 $maxHeadlineLength
379 )
380 ]);
381 }
382
383 if ($socialLinks) {
384 $socialLinks = array_filter($socialLinks);
385 $formattedSocialLinkes = [];
386 $socialLinkProviders = ProfileHelper::socialLinkProviders(true);
387 foreach ($socialLinks as $linkName => $socialLink) {
388 if (isset($socialLinkProviders[$linkName])) {
389 $formattedSocialLinkes[$linkName] = sanitize_text_field(trim($socialLink));
390 }
391 }
392 $meta['social_links'] = $formattedSocialLinkes;
393 }
394
395 $meta['short_description_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($updateData['short_description']));
396
397 $updateData['meta'] = $meta;
398
399 $xProfile->fill($updateData);
400 $xProfile->save();
401
402 // Let's update the user's details
403 $xProfile->user->updateCustomData($updateData);
404 $xProfile->compilation_score = $xProfile->getCompletionScore();
405
406 if ($userNameChanged) {
407 return [
408 'message' => __('Profile has been updated', 'fluent-community'),
409 'profile' => $xProfile,
410 'redirect_url' => Helper::baseUrl('u/' . $xProfile->username . '/update')
411 ];
412 }
413
414 $isOwn = $xProfile->user_id == get_current_user_id();
415 $canEditUsers = current_user_can('edit_users');
416 if ($canEditUsers || (Utility::getPrivacySetting('can_change_email') === 'yes' && $isOwn)) {
417 $emailAddress = Arr::get($data, 'email');
418
419 if ($emailAddress && is_email($emailAddress) && $emailAddress != $xProfile->user->user_email) {
420 $owner_id = email_exists($emailAddress);
421 if ($owner_id && $owner_id != $xProfile->user_id) {
422 return $this->sendError([
423 'message' => __('Email address already taken by someone else. Please use a different email address.', 'fluent-community')
424 ]);
425 }
426
427 // Let's check if it's their own
428 $requireVerification = $isOwn && !$canEditUsers;
429 if ($requireVerification) {
430 $currentUser = get_user_by('ID', $xProfile->user_id);
431 ProfileHelper::sendConfirmationOnProfileEmailChange($currentUser, $emailAddress);
432 return [
433 'message' => __('Email address change is pending. Please check your inbox to verify the new email address.', 'fluent-community'),
434 'profile' => $xProfile
435 ];
436 }
437
438 wp_update_user([
439 'user_email' => $emailAddress,
440 'ID' => $xProfile->user_id
441 ]);
442 }
443 }
444
445 return [
446 'message' => __('Profile has been updated', 'fluent-community'),
447 'profile' => $xProfile
448 ];
449 }
450
451 public function changePassword(Request $request, $userName)
452 {
453 $xProfile = XProfile::where('username', $userName)->firstOrFail();
454
455 // Password can only be changed by the account owner, never by moderators/admins here.
456 if ($xProfile->user_id != get_current_user_id()) {
457 return $this->sendError([
458 'message' => __('You are not allowed to change this password', 'fluent-community')
459 ]);
460 }
461
462 if (Utility::getPrivacySetting('can_change_password') !== 'yes') {
463 return $this->sendError([
464 'message' => __('Password change is disabled', 'fluent-community')
465 ]);
466 }
467
468 $data = $request->get('data', []);
469
470 $this->validate($data, [
471 'current_password' => 'required',
472 'new_password' => 'required',
473 'confirm_password' => 'required',
474 ], [
475 'current_password.required' => __('Current password is required', 'fluent-community'),
476 'new_password.required' => __('New password is required', 'fluent-community'),
477 'confirm_password.required' => __('Please confirm your new password', 'fluent-community'),
478 ]);
479
480 // Passwords are used verbatim; sanitizing would corrupt valid characters.
481 $currentPassword = (string) Arr::get($data, 'current_password');
482 $newPassword = (string) Arr::get($data, 'new_password');
483 $confirmPassword = (string) Arr::get($data, 'confirm_password');
484
485 if (strlen($newPassword) < 4) {
486 return $this->sendError([
487 'message' => __('New password must be at least 4 characters long', 'fluent-community')
488 ]);
489 }
490
491 if ($newPassword !== $confirmPassword) {
492 return $this->sendError([
493 'message' => __('New password and confirmation do not match', 'fluent-community')
494 ]);
495 }
496
497 if ($newPassword === $currentPassword) {
498 return $this->sendError([
499 'message' => __('New password must be different from your current password', 'fluent-community')
500 ]);
501 }
502
503 $user = get_user_by('id', $xProfile->user_id);
504
505 if (!$user || !wp_check_password($currentPassword, $user->user_pass, $user->ID)) {
506 return $this->sendError([
507 'message' => __('Your current password is incorrect', 'fluent-community')
508 ]);
509 }
510
511 wp_set_password($newPassword, $user->ID);
512
513 // wp_set_password destroys every session for the user, which also invalidates the
514 // REST nonce the SPA holds. Re-issue the cookie to keep the session, capturing the
515 // fresh logged-in cookie so the nonces we mint below bind to the new session token.
516 $newLoggedInCookie = '';
517 $captureLoggedInCookie = function ($loggedInCookie) use (&$newLoggedInCookie) {
518 $newLoggedInCookie = $loggedInCookie;
519 };
520 add_action('set_logged_in_cookie', $captureLoggedInCookie);
521
522 wp_set_current_user($user->ID);
523 wp_set_auth_cookie($user->ID, true);
524
525 remove_action('set_logged_in_cookie', $captureLoggedInCookie);
526
527 if ($newLoggedInCookie) {
528 $_COOKIE[LOGGED_IN_COOKIE] = $newLoggedInCookie;
529 }
530
531 do_action('fluent_community/user/password_changed', $user->ID);
532
533 return [
534 'message' => __('Your password has been changed successfully', 'fluent-community'),
535 'rest_nonce' => wp_create_nonce('wp_rest'),
536 'ajax_nonce' => wp_create_nonce('fluent_community_ajax_nonce'),
537 ];
538 }
539
540 public function getAllMemberships(Request $request, $userName)
541 {
542 /** @var XProfile $xProfile */
543 $xProfile = XProfile::where('username', $userName)->firstOrFail();
544
545 $currentUser = $this->getUser();
546
547 if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
548 return $this->sendError([
549 'message' => __('You are not allowed to view this profile\'s membership.', 'fluent-community'),
550 'permission_failed' => true
551 ]);
552 }
553
554 $memberships = $xProfile->space_pivot()
555 ->where('status', 'active')
556 ->pluck('space_id');
557
558 return apply_filters('fluent_community/profile_all_memberships_api_response', [
559 'memberships' => $memberships
560 ], $request->all());
561 }
562
563 public function getSpaces(Request $request, $userName)
564 {
565 /** @var XProfile $xProfile */
566 $xProfile = XProfile::where('username', $userName)->firstOrFail();
567 $currentUser = $this->getUser();
568
569 if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
570 return $this->sendError([
571 'message' => __('You are not allowed to view this profile\'s spaces.', 'fluent-community'),
572 'permission_failed' => true
573 ]);
574 }
575
576 if ($xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator())) {
577 $spaces = $xProfile->spaces()
578 ->wherePivot('status', 'active')
579 ->get();
580 } else {
581 $spaces = $xProfile->spaces()
582 ->whereIn('privacy', ['public', 'private'])
583 ->wherePivot('status', 'active')
584 ->get();
585 }
586
587 foreach ($spaces as $space) {
588 $shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes';
589 $canViewMembers = $currentUser && $space->verifyUserPermisson($currentUser, 'can_view_members', false);
590 if ($shouldHideMembersCount && !$canViewMembers) {
591 $space->members_count = 0;
592 continue;
593 }
594 $space->members_count = $space->members()->count();
595 }
596
597 $data = [
598 'spaces' => $spaces
599 ];
600
601 return apply_filters('fluent_community/profile_spaces_api_response', $data, $request->all());
602 }
603
604 public function getCourses(Request $request, $userName)
605 {
606 if (!Helper::isFeatureEnabled('course_module')) {
607 return $this->sendError([
608 'message' => __('Course module is disabled.', 'fluent-community')
609 ]);
610 }
611
612 /** @var XProfile $xProfile */
613 $xProfile = XProfile::where('username', $userName)->firstOrFail();
614 $currentUser = $this->getUser();
615
616 if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
617 return $this->sendError([
618 'message' => __('You are not allowed to view this profile\'s courses.', 'fluent-community'),
619 'permission_failed' => true
620 ]);
621 }
622
623 $hasAllAccess = $xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator());
624
625 $courses = $xProfile->courses()
626 ->wherePivot('status', 'active')
627 ->where('fcom_spaces.status', 'published')
628 ->when(!$hasAllAccess, function ($q) {
629 $q->whereIn('fcom_spaces.privacy', ['public', 'private']);
630 })
631 ->get();
632
633 foreach ($courses as $course) {
634 $course->isEnrolled = CourseHelper::isEnrolled($course->id, $xProfile->user_id);
635 if ($course->isEnrolled) {
636 $course->progress = CourseHelper::getCourseProgress($course->id, $xProfile->user_id);
637 }
638
639 if (!$course->cover_photo) {
640 $course->cover_photo = FLUENT_COMMUNITY_PLUGIN_URL . 'assets/images/course-placeholder.jpg';
641 }
642
643 $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count();
644 $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count();
645 if (Arr::get($course->settings, 'hide_members_count') != 'yes') {
646 $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count();
647 } else {
648 $course->studentsCount = 0;
649 }
650
651 do_action_ref_array('fluent_community/course', [&$course]);
652 }
653
654 $data = [
655 'courses' => $courses
656 ];
657
658 return apply_filters('fluent_community/profile_courses_api_response', $data, $request->all());
659 }
660
661 public function getComments(Request $request, $userName)
662 {
663 $xProfile = XProfile::where('username', $userName)->first();
664
665 if (!$xProfile) {
666 return $this->sendError([
667 'message' => __('Profile not found', 'fluent-community')
668 ]);
669 }
670
671 $currentUser = $this->getUser();
672 $hasAllAccess = $xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator());
673
674 $comments = Comment::where('user_id', $xProfile->user_id)
675 ->where('status', 'published')
676 ->with([
677 'post' => function ($q) use ($currentUser) {
678 // Eager load the full feed so the post opens in the modal without a per-click fetch.
679 $q->select(array_merge(Feed::$publicColumns, ['message']))
680 ->with(Feed::withPublicRelations($currentUser));
681 }
682 ])
683 ->when(!$hasAllAccess, function ($q) {
684 $q->whereHas('post', function ($query) {
685 $query->byUserAccess(get_current_user_id());
686 $query->where('type', 'text');
687 });
688 })
689 ->orderBy('id', 'desc')
690 ->paginate();
691
692 $posts = $comments->getCollection()
693 ->pluck('post')
694 ->filter()
695 ->unique('id')
696 ->values();
697
698 if ($posts->isNotEmpty()) {
699 FeedsHelper::transformFeedsCollection($posts);
700 }
701
702 $data = [
703 'comments' => $comments,
704 'xprofile' => $xProfile
705 ];
706
707 return apply_filters('fluent_community/profile_comments_api_response', $data, $request->all());
708 }
709
710 public function getNotificationPreferance(Request $request, $userName)
711 {
712 $emailPref = Utility::getEmailNotificationSettings();
713
714 $xProfile = $this->verfifyAndGetProfile($userName);
715
716 $globalPreferances = NotificationPref::getGlobalPrefs();
717
718 $userPrefs = NotificationSubscription::where('user_id', $xProfile->user_id)
719 ->select(['notification_type', 'is_read', 'object_id'])
720 ->get();
721
722 $userGlobalPrefs = [];
723 $spaceWisePrefs = [];
724 foreach ($userPrefs as $pref) {
725 if (!$pref->object_id) {
726 if ($pref->notification_type === 'message_email_frequency') {
727 $maps = [
728 0 => 'disabled',
729 1 => 'hourly',
730 2 => 'daily',
731 3 => 'weekly'
732 ];
733
734 if (isset($maps[$pref->is_read])) {
735 $userGlobalPrefs[$pref->notification_type] = $maps[$pref->is_read];
736 } else {
737 $userGlobalPrefs[$pref->notification_type] = 'default';
738 }
739 continue;
740 }
741 $userGlobalPrefs[$pref->notification_type] = $pref->is_read ? 'yes' : 'no';
742 } else {
743 if (empty($spaceWisePrefs[$pref->object_id])) {
744 $spaceWisePrefs[$pref->object_id] = [];
745 }
746 $spaceWisePrefs[$pref->object_id][$pref->notification_type] = $pref->is_read;
747 }
748 }
749
750 $messagingConfig = Utility::getOption('_messaging_settings', []);
751 $isGlobalPerUser = Arr::get($messagingConfig, 'messaging_email_frequency') == 'disabled';
752
753 $userGlobalPrefsDefaults = [
754 'digest_mail' => Arr::get($globalPreferances, 'digest_email_status') ? 'yes' : 'no',
755 'mention_mail' => Arr::get($globalPreferances, 'mention_mail') ? 'yes' : 'no',
756 'reply_my_com_mail' => Arr::get($globalPreferances, 'reply_my_com_mail') ? 'yes' : 'no',
757 'com_my_post_mail' => Arr::get($globalPreferances, 'com_my_post_mail') ? 'yes' : 'no',
758 'message_email_frequency' => $isGlobalPerUser ? 'disabled' : 'default'
759 ];
760
761 $userGlobalPrefs = wp_parse_args($userGlobalPrefs, $userGlobalPrefsDefaults);
762
763 $profileUserId = $xProfile->user_id;
764 $spaceGroups = SpaceGroup::with(['spaces' => function ($query) use ($profileUserId) {
765 $query->whereHas('members', function ($q) use ($profileUserId) {
766 $q->where('user_id', $profileUserId)
767 ->where('status', 'active');
768 })
769 ->where('type', 'community');
770 }])
771 ->orderBy('serial', 'ASC')
772 ->get();
773
774 $formattedSpaceGroups = [];
775 foreach ($spaceGroups as $group) {
776 if ($group->spaces->isEmpty()) {
777 continue;
778 }
779 $formattedSpaces = [];
780 foreach ($group->spaces as $space) {
781
782 $pref = '';
783 if (isset($spaceWisePrefs[$space->id])) {
784 $perfs = (array)$spaceWisePrefs[$space->id];
785 if (!empty($perfs['np_by_member_mail'])) {
786 $pref = 'all_member_posts';
787 } else if (!empty($perfs['np_by_admin_mail'])) {
788 $pref = 'admin_only_posts';
789 }
790 }
791
792 $formattedSpaces[] = [
793 'id' => $space->id,
794 'title' => $space->title,
795 'icon' => $space->getIconMark(),
796 'pref' => $pref
797 ];
798 }
799 if ($formattedSpaces) {
800 $formattedSpaceGroups[] = [
801 'id' => $group->id,
802 'title' => $group->title,
803 'spaces' => $formattedSpaces
804 ];
805 }
806 }
807
808 // let's find the other spaces
809 $otherSpaces = Space::whereHas('members', function ($q) use ($xProfile) {
810 $q->where('user_id', $xProfile->user_id);
811 })
812 ->whereNull('parent_id')
813 ->orderBy('title', 'ASC')
814 ->get();
815
816 if (!$otherSpaces->isEmpty()) {
817 $formattedSpaces = [];
818 foreach ($otherSpaces as $space) {
819 $pref = '';
820 if (isset($spaceWisePrefs[$space->id])) {
821 $perfs = (array)$spaceWisePrefs[$space->id];
822 if (!empty($perfs['np_by_member_mail'])) {
823 $pref = 'all_member_posts';
824 } else if (!empty($perfs['np_by_admin_mail'])) {
825 $pref = 'admin_only_posts';
826 }
827 }
828
829 $formattedSpaces[] = [
830 'id' => $space->id,
831 'title' => $space->title,
832 'icon' => $space->getIconMark(),
833 'pref' => $pref
834 ];
835 }
836
837 $formattedSpaceGroups[] = [
838 'id' => 'other_space_group',
839 'title' => __('Other Spaces', 'fluent-community'),
840 'spaces' => $formattedSpaces
841 ];
842 }
843
844 $digestDay = (string)Arr::get($emailPref, 'digest_mail_day', 'tue');
845 if ($digestDay) {
846 $maps = [
847 'mon' => __('Monday', 'fluent-community'),
848 'tue' => __('Tuesday', 'fluent-community'),
849 'wed' => __('Wednesday', 'fluent-community'),
850 'thu' => __('Thursday', 'fluent-community'),
851 'fri' => __('Friday', 'fluent-community'),
852 'sat' => __('Saturday', 'fluent-community'),
853 'sun' => __('Sunday', 'fluent-community'),
854 ];
855 if (isset($maps[$digestDay])) {
856 $digestDay = $maps[$digestDay];
857 }
858 }
859
860 $data = [
861 'user_globals' => (object)$userGlobalPrefs,
862 'spaceGroups' => $formattedSpaceGroups,
863 'space_prefs' => $spaceWisePrefs,
864 'digestEmailDay' => $digestDay,
865 'default_messaging_email_frequency' => Arr::get($messagingConfig, 'messaging_email_status') !== 'yes' ? 'no' : Arr::get($messagingConfig, 'messaging_email_frequency'),
866 ];
867
868 return apply_filters('fluent_community/profile_notification_pref_api_response', $data, $request->all());
869 }
870
871 public function saveNotificationPreferance(Request $request, $userName)
872 {
873 $xProfile = $this->verfifyAndGetProfile($userName);
874
875 $userPrefs = $request->get('user_globals', []);
876 $sapcePrefs = $request->get('space_prefs', []);
877
878 $messagingPref = Arr::get($userPrefs, 'message_email_frequency');
879
880 $userPrefs = array_map(function ($item) {
881 return $item == 'yes' ? 1 : 0;
882 }, $userPrefs);
883
884 if ($messagingPref == 'hourly') {
885 $userPrefs['message_email_frequency'] = 1;
886 } else if ($messagingPref == 'daily') {
887 $userPrefs['message_email_frequency'] = 2;
888 } else if ($messagingPref == 'disabled') {
889 $userPrefs['message_email_frequency'] = 0;
890 } else if ($messagingPref == 'weekly') {
891 $userPrefs['message_email_frequency'] = 3;
892 } else {
893 unset($userPrefs['message_email_frequency']);
894 }
895
896 foreach ($sapcePrefs as $spaceId => $pref) {
897 $spaceId = (int)$spaceId;
898 if (!$pref || !$spaceId) {
899 continue;
900 }
901
902 if ($pref == 'all_member_posts') {
903 $userPrefs['np_by_member_mail_' . $spaceId] = 1;
904 $userPrefs['np_by_admin_mail_' . $spaceId] = 1;
905 } else if ($pref == 'admin_only_posts') {
906 $userPrefs['np_by_admin_mail_' . $spaceId] = 1;
907 }
908 }
909
910 NotificationPref::updateUserPrefs($xProfile->user_id, $userPrefs);
911
912 return [
913 'prefs' => $userPrefs,
914 'message' => __('Email Notification preferences have been updated', 'fluent-community')
915 ];
916 }
917
918 private function verfifyAndGetProfile($userName)
919 {
920 $xProfile = XProfile::where('username', $userName)->firstOrFail();
921
922 $currentUser = $this->getUser();
923 if ($xProfile->user_id != get_current_user_id() && (!$currentUser || !$currentUser->isCommunityModerator())) {
924 throw new \Exception('You are not allowed to update this profile');
925 }
926
927 return $xProfile;
928 }
929 }
930