PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.5
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.5
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.5, at app/Http/Controllers/ProfileController.php

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