PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.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
← All changes | app/Http/Controllers/ProfileController.php +269 -59 2.6.012.10.01 View file →
@@ -3,13 +3,12 @@
3 3 namespace FluentCommunity\App\Http\Controllers;
4 4
5 5 use FluentCommunity\App\Functions\Utility;
6 6 use FluentCommunity\App\Models\Comment;
7 -use FluentCommunity\App\Models\NotificationSubscription;
7 +use FluentCommunity\App\Models\Feed;
8 8 use FluentCommunity\App\Models\Space;
9 9 use FluentCommunity\App\Models\SpaceGroup;
10 10 use FluentCommunity\App\Models\SpaceUserPivot;
11 -use FluentCommunity\App\Models\User;
12 11 use FluentCommunity\App\Models\XProfile;
13 12 use FluentCommunity\App\Services\CustomSanitizer;
14 13 use FluentCommunity\App\Services\FeedsHelper;
15 14 use FluentCommunity\App\Services\Helper;
@@ -19,21 +18,23 @@
19 18 use FluentCommunity\Framework\Support\Arr;
20 19 use FluentCommunity\Modules\Course\Model\CourseLesson;
21 20 use FluentCommunity\Modules\Course\Model\CourseTopic;
22 21 use FluentCommunity\Modules\Course\Services\CourseHelper;
22 +use FluentCommunity\Modules\PushNotification\PushNotificationModule;
23 +use FluentCommunity\Framework\Foundation\Exceptions\HttpException;
23 24
24 25 class ProfileController extends Controller
25 26 {
26 27 public function getProfile(Request $request, $userName)
27 28 {
29 + /** @var XProfile $xprofile */
28 30 $xprofile = XProfile::where('username', $userName)
29 31 ->firstOrFail();
30 32
31 33 if ($xprofile->status != 'active' && !Helper::isModerator()) {
32 34 return $this->sendError([
33 - 'message' => __('This profile is not active', 'fluent-community'),
34 - 'status' => 403
35 - ]);
35 + 'message' => __('This profile is not active', 'fluent-community')
36 + ], 403);
36 37 }
37 38
38 39 $canViewProfile = Utility::canViewUserProfile($xprofile->user_id);
39 40
@@ -46,8 +47,9 @@
46 47 'username' => $xprofile->username,
47 48 'avatar' => $xprofile->avatar,
48 49 'has_custom_avatar' => $xprofile->hasCustomAvatar(),
49 50 'cover_photo' => Arr::get($xprofile->meta, 'cover_photo'),
51 + 'headline' => Arr::get($xprofile->meta, 'headline', ''),
50 52 'total_points' => $xprofile->total_points,
51 53 'badge_slugs' => (array)Arr::get($xprofile->meta, 'badge_slug', []),
52 54 'status' => $xprofile->status,
53 55 'is_restricted' => !$canViewProfile,
@@ -73,11 +75,11 @@
73 75 $isAdmin = Helper::isSiteAdmin($currentUserId);
74 76
75 77 if ($isOwn || $isAdmin) {
76 78 $enableUserSync = Utility::getPrivacySetting('enable_user_sync') === 'yes';
77 - $nameArray = explode(' ', trim($xprofile->display_name));
78 - $xprofileLastName = array_pop($nameArray);
79 - $xprofileFirstName = implode(' ', $nameArray);
79 + $nameArray = explode(' ', trim((string) $xprofile->display_name));
80 + $xprofileFirstName = array_shift($nameArray);
81 + $xprofileLastName = implode(' ', $nameArray);
80 82
81 83 $profile['email'] = $user->user_email;
82 84 $profile['first_name'] = $enableUserSync ? $user->first_name : $xprofileFirstName;
83 85 $profile['last_name'] = $enableUserSync ? $user->last_name : $xprofileLastName;
@@ -83,8 +85,9 @@
83 85 $profile['last_name'] = $enableUserSync ? $user->last_name : $xprofileLastName;
84 86 $profile['short_description'] = $xprofile->short_description;
85 87 $profile['can_change_username'] = $isAdmin || Utility::getPrivacySetting('can_customize_username') === 'yes';
86 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';
87 90 }
88 91
89 92 $profileBaseUrl = Helper::baseUrl('u/' . $xprofile->username . '/');
90 93
@@ -144,9 +147,9 @@
144 147 ];
145 148
146 149 $profile['profile_nav_actions'] = [];
147 150
148 - $profile = apply_filters('fluent_community/profile_view_data', $profile, $xprofile);
151 + $profile = apply_filters('fluent_community/profile_view_data', $profile, $xprofile, $isAdmin);
149 152
150 153 return [
151 154 'profile' => $profile
152 155 ];
@@ -153,9 +156,9 @@
153 156 }
154 157
155 158 public function patchProfile(Request $request, $userName)
156 159 {
157 - $xprofile = $this->verfifyAndGetProfile($userName);
160 + $xprofile = $this->verifyAndGetProfile($userName);
158 161
159 162 $updateData = $request->get('data', []);
160 163
161 164 if (!empty($updateData['status']) && $updateData['status'] === 'deactivated' && $xprofile->status === 'active') {
@@ -202,9 +205,9 @@
202 205
203 206 if (isset($updateData['avatar'])) {
204 207
205 208 if ($xprofile->hasCustomAvatar()) {
206 - $deletedMedias[] = $xprofile->attributes['avatar'];
209 + $deletedMedias[] = Arr::get($xprofile->getAttributes(), 'avatar');
207 210 }
208 211
209 212 $xprofile->avatar = $updateData['avatar'];
210 213
@@ -246,8 +249,9 @@
246 249 {
247 250 $currentUser = $this->getUser(true);
248 251 $data = $request->get('data', []);
249 252
253 + /** @var XProfile $xProfile */
250 254 $xProfile = XProfile::where('username', $userName)->firstOrFail();
251 255
252 256 if ($xProfile->user_id != get_current_user_id()) {
253 257 if(!$currentUser->isCommunityModerator()) {
@@ -264,11 +268,10 @@
264 268 ]);
265 269
266 270 $updateData = Arr::only($data, ['first_name', 'last_name', 'short_description', 'website']);
267 271
268 - $updateData = apply_filters('fluent_community/update_profile_data', $updateData, $data, $xProfile);
272 + $updateData = apply_filters('fluent_community/update_profile_data', $updateData, $data, $xProfile, $currentUser);
269 273
270 - $currentUser = User::findOrFail(get_current_user_id());
271 274 $meta = $xProfile->meta;
272 275
273 276 $userNameChanged = false;
274 277
@@ -309,10 +312,13 @@
309 312 $userNameChanged = $userName != $xProfile->username;
310 313 }
311 314
312 315 if (Helper::isFeatureEnabled('user_badge')) {
313 - $badgeSlug = (array)Arr::get($data, 'badge_slugs', []);
314 - $meta['badge_slug'] = $badgeSlug;
316 + $badgeSlug = array_filter((array) Arr::get($data, 'badge_slugs', []), 'is_scalar');
317 + $badgeSlug = array_map('sanitize_text_field', $badgeSlug);
318 +
319 + $definedBadges = (array) Utility::getOption('user_badges', []);
320 + $meta['badge_slug'] = array_values(array_intersect($badgeSlug, array_keys($definedBadges)));
315 321 }
316 322 } else if (Utility::getPrivacySetting('can_customize_username')) {
317 323 $userName = Arr::get($data, 'username');
318 324
@@ -349,10 +355,11 @@
349 355 }
350 356
351 357 $updateData['display_name'] = trim(sanitize_text_field(Arr::get($data, 'first_name') . ' ' . Arr::get($data, 'last_name')));
352 358
353 - $updateData['short_description'] = CustomSanitizer::unslashMarkdown(sanitize_textarea_field(trim(Arr::get($data, 'short_description'))));
354 - $meta['website'] = sanitize_url(Arr::get($data, 'website'));
359 + $updateData['short_description'] = CustomSanitizer::unslashMarkdown(sanitize_textarea_field(trim((string) Arr::get($data, 'short_description', ''))));
360 + $meta['website'] = sanitize_url((string) Arr::get($data, 'website', ''));
361 + $meta['headline'] = sanitize_text_field(trim(Arr::get($data, 'headline', '')));
355 362 $socialLinks = Arr::get($data, 'social_links', []);
356 363
357 364 $maxDescriptionLength = apply_filters('fluent_community/max_profile_description_length', 5000);
358 365 if ($updateData['short_description'] && strlen($updateData['short_description']) > $maxDescriptionLength) {
@@ -364,8 +371,19 @@
364 371 )
365 372 ]);
366 373 }
367 374
375 + $maxHeadlineLength = apply_filters('fluent_community/max_profile_headline_length', 60);
376 + if ($meta['headline'] && mb_strlen($meta['headline']) > $maxHeadlineLength) {
377 + return $this->sendError([
378 + 'message' => sprintf(
379 + /* translators: %d: Maximum number of characters allowed in the profile headline. */
380 + __('Headline should not exceed %d characters.', 'fluent-community'),
381 + $maxHeadlineLength
382 + )
383 + ]);
384 + }
385 +
368 386 if ($socialLinks) {
369 387 $socialLinks = array_filter($socialLinks);
370 388 $formattedSocialLinkes = [];
371 389 $socialLinkProviders = ProfileHelper::socialLinkProviders(true);
@@ -432,10 +450,100 @@
432 450 'profile' => $xProfile
433 451 ];
434 452 }
435 453
454 + public function changePassword(Request $request, $userName)
455 + {
456 + $xProfile = XProfile::where('username', $userName)->firstOrFail();
457 +
458 + // Password can only be changed by the account owner, never by moderators/admins here.
459 + if ($xProfile->user_id != get_current_user_id()) {
460 + return $this->sendError([
461 + 'message' => __('You are not allowed to change this password', 'fluent-community')
462 + ]);
463 + }
464 +
465 + if (Utility::getPrivacySetting('can_change_password') !== 'yes') {
466 + return $this->sendError([
467 + 'message' => __('Password change is disabled', 'fluent-community')
468 + ]);
469 + }
470 +
471 + $data = $request->get('data', []);
472 +
473 + $this->validate($data, [
474 + 'current_password' => 'required',
475 + 'new_password' => 'required',
476 + 'confirm_password' => 'required',
477 + ], [
478 + 'current_password.required' => __('Current password is required', 'fluent-community'),
479 + 'new_password.required' => __('New password is required', 'fluent-community'),
480 + 'confirm_password.required' => __('Please confirm your new password', 'fluent-community'),
481 + ]);
482 +
483 + // Passwords are used verbatim; sanitizing would corrupt valid characters.
484 + $currentPassword = (string) Arr::get($data, 'current_password');
485 + $newPassword = (string) Arr::get($data, 'new_password');
486 + $confirmPassword = (string) Arr::get($data, 'confirm_password');
487 +
488 + if (strlen($newPassword) < 4) {
489 + return $this->sendError([
490 + 'message' => __('New password must be at least 4 characters long', 'fluent-community')
491 + ]);
492 + }
493 +
494 + if ($newPassword !== $confirmPassword) {
495 + return $this->sendError([
496 + 'message' => __('New password and confirmation do not match', 'fluent-community')
497 + ]);
498 + }
499 +
500 + if ($newPassword === $currentPassword) {
501 + return $this->sendError([
502 + 'message' => __('New password must be different from your current password', 'fluent-community')
503 + ]);
504 + }
505 +
506 + $user = get_user_by('id', $xProfile->user_id);
507 +
508 + if (!$user || !wp_check_password($currentPassword, $user->user_pass, $user->ID)) {
509 + return $this->sendError([
510 + 'message' => __('Your current password is incorrect', 'fluent-community')
511 + ]);
512 + }
513 +
514 + wp_set_password($newPassword, $user->ID);
515 +
516 + // wp_set_password destroys every session for the user, which also invalidates the
517 + // REST nonce the SPA holds. Re-issue the cookie to keep the session, capturing the
518 + // fresh logged-in cookie so the nonces we mint below bind to the new session token.
519 + $newLoggedInCookie = '';
520 + $captureLoggedInCookie = function ($loggedInCookie) use (&$newLoggedInCookie) {
521 + $newLoggedInCookie = $loggedInCookie;
522 + };
523 + add_action('set_logged_in_cookie', $captureLoggedInCookie);
524 +
525 + wp_set_current_user($user->ID);
526 + wp_set_auth_cookie($user->ID, true);
527 +
528 + remove_action('set_logged_in_cookie', $captureLoggedInCookie);
529 +
530 + if ($newLoggedInCookie) {
531 + $_COOKIE[LOGGED_IN_COOKIE] = $newLoggedInCookie;
532 + }
533 +
534 + do_action('fluent_community/user/password_changed', $user->ID);
535 +
536 + return [
537 + 'message' => __('Your password has been changed successfully', 'fluent-community'),
538 + 'rest_nonce' => wp_create_nonce('wp_rest'),
539 + 'ajax_nonce' => wp_create_nonce('fluent_community_ajax_nonce'),
540 + ];
541 + }
542 +
436 543 public function getAllMemberships(Request $request, $userName)
437 544 {
545 + /** @var XProfile $xProfile */
438 546 $xProfile = XProfile::where('username', $userName)->firstOrFail();
439 547
440 548 $currentUser = $this->getUser();
441 549
@@ -445,12 +553,19 @@
445 553 'permission_failed' => true
446 554 ]);
447 555 }
448 556
449 - $memberships = $xProfile->space_pivot()
450 - ->where('status', 'active')
451 - ->pluck('space_id');
557 + $canSeeSecret = $xProfile->user_id == get_current_user_id()
558 + || ($currentUser && $currentUser->isCommunityModerator());
452 559
560 + $memberships = $xProfile->spaces()
561 + ->wherePivot('status', 'active')
562 + ->when(!$canSeeSecret, function ($q) {
563 + $q->whereIn('privacy', ['public', 'private']);
564 + })
565 + ->get()
566 + ->pluck('id');
567 +
453 568 return apply_filters('fluent_community/profile_all_memberships_api_response', [
454 569 'memberships' => $memberships
455 570 ], $request->all());
456 571 }
@@ -456,8 +571,9 @@
456 571 }
457 572
458 573 public function getSpaces(Request $request, $userName)
459 574 {
575 + /** @var XProfile $xProfile */
460 576 $xProfile = XProfile::where('username', $userName)->firstOrFail();
461 577 $currentUser = $this->getUser();
462 578
463 579 if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
@@ -502,8 +618,9 @@
502 618 'message' => __('Course module is disabled.', 'fluent-community')
503 619 ]);
504 620 }
505 621
622 + /** @var XProfile $xProfile */
506 623 $xProfile = XProfile::where('username', $userName)->firstOrFail();
507 624 $currentUser = $this->getUser();
508 625
509 626 if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
@@ -539,8 +656,10 @@
539 656 $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count();
540 657 } else {
541 658 $course->studentsCount = 0;
542 659 }
660 +
661 + do_action_ref_array('fluent_community/course', [&$course]);
543 662 }
544 663
545 664 $data = [
546 665 'courses' => $courses
@@ -564,15 +683,12 @@
564 683
565 684 $comments = Comment::where('user_id', $xProfile->user_id)
566 685 ->where('status', 'published')
567 686 ->with([
568 - 'post' => function ($q) {
569 - $q->select(['id', 'title', 'message', 'type', 'space_id', 'slug', 'created_at'])
570 - ->with([
571 - 'space' => function ($q) {
572 - $q->select(['id', 'title', 'slug', 'type']);
573 - }
574 - ]);
687 + 'post' => function ($q) use ($currentUser) {
688 + // Eager load the full feed so the post opens in the modal without a per-click fetch.
689 + $q->select(array_merge(Feed::$publicColumns, ['message']))
690 + ->with(Feed::withPublicRelations($currentUser));
575 691 }
576 692 ])
577 693 ->when(!$hasAllAccess, function ($q) {
578 694 $q->whereHas('post', function ($query) {
@@ -582,8 +698,18 @@
582 698 })
583 699 ->orderBy('id', 'desc')
584 700 ->paginate();
585 701
702 + $posts = $comments->getCollection()
703 + ->pluck('post')
704 + ->filter()
705 + ->unique('id')
706 + ->values();
707 +
708 + if ($posts->isNotEmpty()) {
709 + FeedsHelper::transformFeedsCollection($posts);
710 + }
711 +
586 712 $data = [
587 713 'comments' => $comments,
588 714 'xprofile' => $xProfile
589 715 ];
@@ -594,47 +720,51 @@
594 720 public function getNotificationPreferance(Request $request, $userName)
595 721 {
596 722 $emailPref = Utility::getEmailNotificationSettings();
597 723
598 - $xProfile = $this->verfifyAndGetProfile($userName);
724 + $xProfile = $this->verifyAndGetProfile($userName);
599 725
600 726 $globalPreferances = NotificationPref::getGlobalPrefs();
601 727
602 - $userPrefs = NotificationSubscription::where('user_id', $xProfile->user_id)
603 - ->select(['notification_type', 'is_read', 'object_id'])
604 - ->get();
728 + // Read through the same service the save path writes through. These rows
729 + // live in fcom_notification_prefs, keyed by flat keys - space-scoped ones
730 + // carry an '_<space id>' suffix.
731 + $userPrefs = NotificationPref::getUserPrefs($xProfile->user_id);
605 732
733 + $frequencyMaps = [
734 + 0 => 'disabled',
735 + 1 => 'hourly',
736 + 2 => 'daily',
737 + 3 => 'weekly'
738 + ];
739 +
606 740 $userGlobalPrefs = [];
607 741 $spaceWisePrefs = [];
608 - foreach ($userPrefs as $pref) {
609 - if (!$pref->object_id) {
610 - if ($pref->notification_type === 'message_email_frequency') {
611 - $maps = [
612 - 0 => 'disabled',
613 - 1 => 'hourly',
614 - 2 => 'daily',
615 - 3 => 'weekly'
616 - ];
742 + foreach ($userPrefs as $prefKey => $prefValue) {
743 + if ($prefKey === 'message_email_frequency') {
744 + $userGlobalPrefs[$prefKey] = isset($frequencyMaps[$prefValue]) ? $frequencyMaps[$prefValue] : 'default';
745 + continue;
746 + }
617 747
618 - if ($maps[$pref->is_read]) {
619 - $userGlobalPrefs[$pref->notification_type] = $maps[$pref->is_read];
620 - } else {
621 - $userGlobalPrefs[$pref->notification_type] = 'default';
622 - }
623 - continue;
748 + if (preg_match('/^(np_by_(?:member|admin)_mail)_(\d+)$/', $prefKey, $matches)) {
749 + $spaceId = (int)$matches[2];
750 +
751 + if (empty($spaceWisePrefs[$spaceId])) {
752 + $spaceWisePrefs[$spaceId] = [];
624 753 }
625 - $userGlobalPrefs[$pref->notification_type] = $pref->is_read ? 'yes' : 'no';
626 - } else {
627 - if (empty($spaceWisePrefs[$pref->object_id])) {
628 - $spaceWisePrefs[$pref->object_id] = [];
629 - }
630 - $spaceWisePrefs[$pref->object_id][$pref->notification_type] = $pref->is_read;
754 +
755 + $spaceWisePrefs[$spaceId][$matches[1]] = $prefValue;
756 + continue;
631 757 }
758 +
759 + $userGlobalPrefs[$prefKey] = $prefValue ? 'yes' : 'no';
632 760 }
633 761
634 762 $messagingConfig = Utility::getOption('_messaging_settings', []);
635 763 $isGlobalPerUser = Arr::get($messagingConfig, 'messaging_email_frequency') == 'disabled';
636 764
765 + $pushAvailable = PushNotificationModule::isAvailable();
766 +
637 767 $userGlobalPrefsDefaults = [
638 768 'digest_mail' => Arr::get($globalPreferances, 'digest_email_status') ? 'yes' : 'no',
639 769 'mention_mail' => Arr::get($globalPreferances, 'mention_mail') ? 'yes' : 'no',
640 770 'reply_my_com_mail' => Arr::get($globalPreferances, 'reply_my_com_mail') ? 'yes' : 'no',
@@ -641,13 +771,23 @@
641 771 'com_my_post_mail' => Arr::get($globalPreferances, 'com_my_post_mail') ? 'yes' : 'no',
642 772 'message_email_frequency' => $isGlobalPerUser ? 'disabled' : 'default'
643 773 ];
644 774
775 + if ($pushAvailable) {
776 + $pushPreferances = NotificationPref::getGlobalPrefs('push');
777 +
778 + $userGlobalPrefsDefaults['com_my_post_push'] = Arr::get($pushPreferances, 'com_my_post_push') ? 'yes' : 'no';
779 + $userGlobalPrefsDefaults['reply_my_com_push'] = Arr::get($pushPreferances, 'reply_my_com_push') ? 'yes' : 'no';
780 + $userGlobalPrefsDefaults['mention_push'] = Arr::get($pushPreferances, 'mention_push') ? 'yes' : 'no';
781 + $userGlobalPrefsDefaults['co_com_push'] = Arr::get($pushPreferances, 'co_com_push') ? 'yes' : 'no';
782 + }
783 +
645 784 $userGlobalPrefs = wp_parse_args($userGlobalPrefs, $userGlobalPrefsDefaults);
646 785
647 - $spaceGroups = SpaceGroup::with(['spaces' => function ($query) {
648 - $query->whereHas('members', function ($q) {
649 - $q->where('user_id', get_current_user_id())
786 + $profileUserId = $xProfile->user_id;
787 + $spaceGroups = SpaceGroup::with(['spaces' => function ($query) use ($profileUserId) {
788 + $query->whereHas('members', function ($q) use ($profileUserId) {
789 + $q->where('user_id', $profileUserId)
650 790 ->where('status', 'active');
651 791 })
652 792 ->where('type', 'community');
653 793 }])
@@ -739,8 +879,16 @@
739 879 $digestDay = $maps[$digestDay];
740 880 }
741 881 }
742 882
883 + $crmEmailStatus = '';
884 + if ($xProfile->user_id == get_current_user_id()) {
885 + $profileUser = get_user_by('ID', $xProfile->user_id);
886 + if ($profileUser && $profileUser->user_email) {
887 + $crmEmailStatus = Helper::getCrmUndeliverableStatus($profileUser->user_email);
888 + }
889 + }
890 +
743 891 $data = [
744 892 'user_globals' => (object)$userGlobalPrefs,
745 893 'spaceGroups' => $formattedSpaceGroups,
746 894 'space_prefs' => $spaceWisePrefs,
@@ -745,8 +893,10 @@
745 893 'spaceGroups' => $formattedSpaceGroups,
746 894 'space_prefs' => $spaceWisePrefs,
747 895 'digestEmailDay' => $digestDay,
748 896 'default_messaging_email_frequency' => Arr::get($messagingConfig, 'messaging_email_status') !== 'yes' ? 'no' : Arr::get($messagingConfig, 'messaging_email_frequency'),
897 + 'crm_email_status' => $crmEmailStatus,
898 + 'push_available' => $pushAvailable,
749 899 ];
750 900
751 901 return apply_filters('fluent_community/profile_notification_pref_api_response', $data, $request->all());
752 902 }
@@ -752,9 +902,9 @@
752 902 }
753 903
754 904 public function saveNotificationPreferance(Request $request, $userName)
755 905 {
756 - $xProfile = $this->verfifyAndGetProfile($userName);
906 + $xProfile = $this->verifyAndGetOwnProfile($userName);
757 907
758 908 $userPrefs = $request->get('user_globals', []);
759 909 $sapcePrefs = $request->get('space_prefs', []);
760 910
@@ -797,15 +947,75 @@
797 947 'message' => __('Email Notification preferences have been updated', 'fluent-community')
798 948 ];
799 949 }
800 950
801 - private function verfifyAndGetProfile($userName)
951 + public function reconfirmEmail(Request $request, $userName)
802 952 {
953 + if (!defined('FLUENTCRM')) {
954 + return $this->sendError([
955 + 'message' => __('FluentCRM is not available on this site', 'fluent-community')
956 + ]);
957 + }
958 +
803 959 $xProfile = XProfile::where('username', $userName)->firstOrFail();
804 960
805 - $currentUser = $this->getUser();
806 - if ($xProfile->user_id != get_current_user_id() && (!$currentUser || !$currentUser->isCommunityModerator())) {
961 + if ($xProfile->user_id != get_current_user_id()) {
962 + return $this->sendError([
963 + 'message' => __('You can only re-confirm your own email address', 'fluent-community')
964 + ]);
965 + }
966 +
967 + $profileUser = get_user_by('ID', $xProfile->user_id);
968 + $email = $profileUser ? $profileUser->user_email : '';
969 +
970 + if (!$email || !Helper::getCrmUndeliverableStatus($email)) {
971 + return $this->sendError([
972 + 'message' => __('Your email address does not need re-confirmation', 'fluent-community')
973 + ]);
974 + }
975 +
976 + $subscriber = \FluentCrm\App\Models\Subscriber::where('email', $email)->first();
977 +
978 + if (!$subscriber) {
979 + return $this->sendError([
980 + 'message' => __('Your email address does not need re-confirmation', 'fluent-community')
981 + ]);
982 + }
983 +
984 + // In-memory only, never saved: the opt-in sender is gated on status == 'pending'
985 + // and does not persist the subscriber, so the stored status stays untouched
986 + // and FluentCommunity keeps pausing emails until the confirmation link is clicked.
987 + $subscriber->status = 'pending';
988 +
989 + if (!$subscriber->sendDoubleOptinEmail()) {
990 + return $this->sendError([
991 + 'message' => __('The confirmation email could not be sent right now. Please try again after a few minutes.', 'fluent-community')
992 + ]);
993 + }
994 +
995 + return [
996 + 'message' => __('A confirmation email has been sent. Please check your inbox and click the confirmation link to resume email notifications.', 'fluent-community')
997 + ];
998 + }
999 +
1000 + private function verifyAndGetProfile($userName)
1001 + {
1002 + $xProfile = XProfile::where('username', $userName)->firstOrFail();
1003 +
1004 + $currentUserId = get_current_user_id();
1005 + if ($xProfile->user_id != $currentUserId && !Helper::isSuperAdmin($currentUserId)) {
807 1006 throw new \Exception('You are not allowed to update this profile');
1007 + }
1008 +
1009 + return $xProfile;
1010 + }
1011 +
1012 + private function verifyAndGetOwnProfile($userName)
1013 + {
1014 + $xProfile = XProfile::where('username', $userName)->firstOrFail();
1015 +
1016 + if (!get_current_user_id() || $xProfile->user_id != get_current_user_id()) {
1017 + throw new HttpException(403, esc_html__('You are not allowed to access these notification preferences.', 'fluent-community'));
808 1018 }
809 1019
810 1020 return $xProfile;
811 1021 }