# fluent-community/trunk/app/Http/Controllers/ProfileController.php

FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS &amp; Online Courses, version trunk. 1,023 lines.

- Page: https://pluginprobe.com/plugins/fluent-community/trunk/code/app/Http/Controllers/ProfileController.php
- Raw: https://pluginprobe.com/plugins/fluent-community/trunk/raw/app/Http/Controllers/ProfileController.php
- Modified: 2026-09-14T14:31:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-community/trunk/code/app/Http/Controllers/ProfileController.php#L10-L20`.

```php
<?php

namespace FluentCommunity\App\Http\Controllers;

use FluentCommunity\App\Functions\Utility;
use FluentCommunity\App\Models\Comment;
use FluentCommunity\App\Models\Feed;
use FluentCommunity\App\Models\Space;
use FluentCommunity\App\Models\SpaceGroup;
use FluentCommunity\App\Models\SpaceUserPivot;
use FluentCommunity\App\Models\XProfile;
use FluentCommunity\App\Services\CustomSanitizer;
use FluentCommunity\App\Services\FeedsHelper;
use FluentCommunity\App\Services\Helper;
use FluentCommunity\App\Services\NotificationPref;
use FluentCommunity\App\Services\ProfileHelper;
use FluentCommunity\Framework\Http\Request\Request;
use FluentCommunity\Framework\Support\Arr;
use FluentCommunity\Modules\Course\Model\CourseLesson;
use FluentCommunity\Modules\Course\Model\CourseTopic;
use FluentCommunity\Modules\Course\Services\CourseHelper;
use FluentCommunity\Modules\PushNotification\PushNotificationModule;
use FluentCommunity\Framework\Foundation\Exceptions\HttpException;

class ProfileController extends Controller
{
    public function getProfile(Request $request, $userName)
    {
        /** @var XProfile $xprofile */
        $xprofile = XProfile::where('username', $userName)
            ->firstOrFail();

        if ($xprofile->status != 'active' && !Helper::isModerator()) {
            return $this->sendError([
                'message' => __('This profile is not active', 'fluent-community')
            ], 403);
        }

        $canViewProfile = Utility::canViewUserProfile($xprofile->user_id);

        $user = get_user_by('ID', $xprofile->user_id);

        $profile = [
            'user_id'           => $xprofile->user_id,
            'is_verified'       => $xprofile->is_verified,
            'display_name'      => $xprofile->display_name,
            'username'          => $xprofile->username,
            'avatar'            => $xprofile->avatar,
            'has_custom_avatar' => $xprofile->hasCustomAvatar(),
            'cover_photo'       => Arr::get($xprofile->meta, 'cover_photo'),
            'headline'          => Arr::get($xprofile->meta, 'headline', ''),
            'total_points'      => $xprofile->total_points,
            'badge_slugs'       => (array)Arr::get($xprofile->meta, 'badge_slug', []),
            'status'            => $xprofile->status,
            'is_restricted'     => !$canViewProfile,
            'canViewUserSpaces' => ProfileHelper::canViewUserSpaces($xprofile->user_id, $this->getUser())
        ];

        if (Utility::showLastActivity()) {
            $profile['last_activity'] = $xprofile->last_activity;
        }

        if ($canViewProfile) {
            $profile['website'] = Arr::get($xprofile->meta, 'website');
            $profile['created_at'] = $xprofile->created_at->format('Y-m-d H:i:s');
            $profile['social_links'] = (object) Arr::get($xprofile->meta, 'social_links', []);
            $profile['compilation_score'] = $xprofile->getCompletionScore();
            $profile['short_description_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($xprofile->short_description));
        }

        $currentUserId = get_current_user_id();

        $isOwn = $xprofile->user_id == $currentUserId;

        $isAdmin = Helper::isSiteAdmin($currentUserId);

        if ($isOwn || $isAdmin) {
            $enableUserSync = Utility::getPrivacySetting('enable_user_sync') === 'yes';
            $nameArray = explode(' ', trim((string) $xprofile->display_name));
            $xprofileFirstName = array_shift($nameArray);
            $xprofileLastName = implode(' ', $nameArray);

            $profile['email'] = $user->user_email;
            $profile['first_name'] = $enableUserSync ? $user->first_name : $xprofileFirstName;
            $profile['last_name'] = $enableUserSync ? $user->last_name : $xprofileLastName;
            $profile['short_description'] = $xprofile->short_description;
            $profile['can_change_username'] = $isAdmin || Utility::getPrivacySetting('can_customize_username') === 'yes';
            $profile['can_change_email'] = current_user_can('edit_users') || (Utility::getPrivacySetting('can_change_email') === 'yes' && $isOwn);
            $profile['can_change_password'] = $isOwn && Utility::getPrivacySetting('can_change_password') === 'yes';
        }

        $profileBaseUrl = Helper::baseUrl('u/' . $xprofile->username . '/');

        $profile['profile_navs'] = [
            [
                'slug'          => 'user_profile',
                'title'         => __('About', 'fluent-community'),
                'url'           => $profileBaseUrl,
                'wrapper_class' => 'fcom_profile_about',
                'route'         => [
                    'name' => 'user_profile'
                ]
            ],
            [
                'slug'          => 'user_profile_feeds',
                'title'         => __('Posts', 'fluent-community'),
                'wrapper_class' => 'fcom_profile_posts',
                'url'           => $profileBaseUrl . 'posts',
                'route'         => [
                    'name' => 'user_profile_feeds'
                ]
            ]
        ];

        if ($profile['canViewUserSpaces']) {
            $profile['profile_navs'][] = [
                'slug'          => 'user_spaces',
                'wrapper_class' => 'fcom_profile_spaces',
                'title'         => __('Spaces', 'fluent-community'),
                'url'           => $profileBaseUrl . 'spaces',
                'route'         => [
                    'name' => 'user_spaces'
                ]
            ];

            if (Helper::isFeatureEnabled('course_module')) {
                $profile['profile_navs'][] = [
                    'slug'          => 'user_courses',
                    'wrapper_class' => 'fcom_profile_courses',
                    'title'         => __('Courses', 'fluent-community'),
                    'url'           => $profileBaseUrl . 'courses',
                    'route'         => [
                        'name' => 'user_courses'
                    ]
                ];
            }
        }

        $profile['profile_navs'][] = [
            'slug'          => 'user_comments',
            'wrapper_class' => 'fcom_profile_comments',
            'title'         => __('Comments', 'fluent-community'),
            'url'           => $profileBaseUrl . 'comments',
            'route'         => [
                'name' => 'user_comments'
            ]
        ];

        $profile['profile_nav_actions'] = [];

        $profile = apply_filters('fluent_community/profile_view_data', $profile, $xprofile, $isAdmin);

        return [
            'profile' => $profile
        ];
    }

    public function patchProfile(Request $request, $userName)
    {
        $xprofile = $this->verifyAndGetProfile($userName);

        $updateData = $request->get('data', []);

        if (!empty($updateData['status']) && $updateData['status'] === 'deactivated' && $xprofile->status === 'active') {
            // handle deactivation
            $canDeactivate = Utility::getPrivacySetting('can_deactive_account') === 'yes' || Helper::isSiteAdmin();
            if (!$canDeactivate) {
                return $this->sendError([
                    'message' => __('You are not allowed to deactivate this account.', 'fluent-community')
                ]);
            }

            $xprofile->status = '';
            $xprofile->save();
            update_user_meta($xprofile->user_id, '_fcom_deactivated_at', current_time('mysql'));
            do_action('fluent_community/profile_deactivated', $xprofile);

            return [
                'message' => __('Your profile has been deactivated successfully.', 'fluent-community')
            ];
        }

        $mediaTypes = ['cover_photo', 'avatar'];

        foreach ($mediaTypes as $type) {
            if (!empty($updateData[$type])) {
                $media = Helper::getMediaFromUrl($updateData[$type]);
                if (!$media || $media->is_active) {
                    return $this->sendError([
                        'message' => __('Invalid media image. Please upload a new one.', 'fluent-community')
                    ]);
                }

                $updateData[$type] = $media->public_url;

                $media->update([
                    'is_active'     => true,
                    'user_id'       => $xprofile->user_id,
                    'object_source' => 'user_' . $type
                ]);
            }
        }

        $deletedMedias = [];

        if (isset($updateData['avatar'])) {

            if ($xprofile->hasCustomAvatar()) {
                $deletedMedias[] = Arr::get($xprofile->getAttributes(), 'avatar');
            }

            $xprofile->avatar = $updateData['avatar'];

            if (defined('FLUENTCRM')) {
                $contact = $xprofile->contact;

                if ($contact) {
                    $contact->update([
                        'avatar' => $updateData['avatar'] ?: null
                    ]);
                }
            }

            if (empty($updateData['avatar'])) {
                Utility::forgetCache('user_avatar_' . $xprofile->user_id);
            }
        }

        if (isset($updateData['cover_photo'])) {
            $deletedMedias[] = Arr::get($xprofile->meta, 'cover_photo');
            $xprofile->meta = wp_parse_args(['cover_photo' => $updateData['cover_photo']], $xprofile->meta);
        }

        $xprofile->save();

        if ($deletedMedias = array_filter($deletedMedias)) {
            do_action('fluent_community/remove_medias_by_url', $deletedMedias, [
                'user_id'        => $xprofile->user_id,
                'object_sources' => ['user_avatar', 'user_cover_photo']
            ]);
        }

        return [
            'message' => __('Profile updated', 'fluent-community')
        ];
    }

    public function updateProfile(Request $request, $userName)
    {
        $currentUser = $this->getUser(true);
        $data = $request->get('data', []);

        /** @var XProfile $xProfile */
        $xProfile = XProfile::where('username', $userName)->firstOrFail();

        if ($xProfile->user_id != get_current_user_id()) {
            if(!$currentUser->isCommunityModerator()) {
                return $this->sendError([
                    'message' => __('You are not allowed to update this profile', 'fluent-community')
                ]);
            }
        }

        $this->validate($data, [
            'first_name' => 'required',
        ], [
            'first_name.required' => __('First name is required', 'fluent-community')
        ]);

        $updateData = Arr::only($data, ['first_name', 'last_name', 'short_description', 'website']);

        $updateData = apply_filters('fluent_community/update_profile_data', $updateData, $data, $xProfile, $currentUser);

        $meta = $xProfile->meta;

        $userNameChanged = false;

        if ($currentUser->isCommunityModerator()) {
            $updateData['is_verified'] = Arr::get($data, 'is_verified') ? 1 : 0;
            $updateData['status'] = Arr::get($data, 'status', 'active');
            $userName = Arr::get($data, 'username');

            if (user_can($xProfile->user_id, 'list_users')) {
                $updateData['status'] = 'active';
            }

            if ($userName) {
                // Check if username is exit or not
                $userName = CustomSanitizer::sanitizeUserName($userName);

                if (!$userName) {
                    return $this->sendError([
                        'message' => __('Invalid username. Only Latin characters with _ & - are allowed.', 'fluent-community')
                    ]);
                }

                if (XProfile::where('username', $userName)->where('user_id', '!=', $xProfile->user_id)->exists()) {
                    return $this->sendError([
                        'message' => __('Community Username already taken by someone else', 'fluent-community')
                    ]);
                }

                $userExist = get_user_by('user_login', $userName);

                if ($userExist && $userExist->ID != $xProfile->user_id) {
                    return $this->sendError([
                        'message' => __('Username already taken by someone else. Please use a different username.', 'fluent-community')
                    ]);
                }

                $updateData['username'] = $userName;
                $userNameChanged = $userName != $xProfile->username;
            }

            if (Helper::isFeatureEnabled('user_badge')) {
                $badgeSlug = array_filter((array) Arr::get($data, 'badge_slugs', []), 'is_scalar');
                $badgeSlug = array_map('sanitize_text_field', $badgeSlug);

                $definedBadges = (array) Utility::getOption('user_badges', []);
                $meta['badge_slug'] = array_values(array_intersect($badgeSlug, array_keys($definedBadges)));
            }
        } else if (Utility::getPrivacySetting('can_customize_username')) {
            $userName = Arr::get($data, 'username');

            if ($xProfile->username != $userName) {
                $userName = strtolower(CustomSanitizer::sanitizeUserName($userName));
                if (!$userName) {
                    return $this->sendError([
                        'message' => __('Invalid username. Only Latin characters with _ & - are allowed.', 'fluent-community')
                    ]);
                }

                if (XProfile::where('username', $userName)->where('user_id', '!=', $xProfile->user_id)->exists()) {
                    return $this->sendError([
                        'message' => __('Community Username already taken by someone else', 'fluent-community')
                    ]);
                }

                if (strlen($userName) < 3) {
                    return $this->sendError([
                        'message' => __('Username should be at least 3 characters long.', 'fluent-community')
                    ]);
                }

                $reservedUserNames = ProfileHelper::getReservedUserNames();
                if (in_array($userName, $reservedUserNames)) {
                    return $this->sendError([
                        'message' => __('Please use another username. This username is reserved', 'fluent-community')
                    ]);
                }

                $updateData['username'] = $userName;
                $userNameChanged = true;
            }
        }

        $updateData['display_name'] = trim(sanitize_text_field(Arr::get($data, 'first_name') . ' ' . Arr::get($data, 'last_name')));

        $updateData['short_description'] = CustomSanitizer::unslashMarkdown(sanitize_textarea_field(trim((string) Arr::get($data, 'short_description', ''))));
        $meta['website'] = sanitize_url((string) Arr::get($data, 'website', ''));
        $meta['headline'] = sanitize_text_field(trim(Arr::get($data, 'headline', '')));
        $socialLinks = Arr::get($data, 'social_links', []);

        $maxDescriptionLength = apply_filters('fluent_community/max_profile_description_length', 5000);
        if ($updateData['short_description'] && strlen($updateData['short_description']) > $maxDescriptionLength) {
            return $this->sendError([
                'message' => sprintf(
                    /* translators: %d: Maximum number of characters allowed in the profile bio. */
                    __('Profile bio should not exceed %d characters.', 'fluent-community'),
                    $maxDescriptionLength
                )
            ]);
        }

        $maxHeadlineLength = apply_filters('fluent_community/max_profile_headline_length', 60);
        if ($meta['headline'] && mb_strlen($meta['headline']) > $maxHeadlineLength) {
            return $this->sendError([
                'message' => sprintf(
                    /* translators: %d: Maximum number of characters allowed in the profile headline. */
                    __('Headline should not exceed %d characters.', 'fluent-community'),
                    $maxHeadlineLength
                )
            ]);
        }

        if ($socialLinks) {
            $socialLinks = array_filter($socialLinks);
            $formattedSocialLinkes = [];
            $socialLinkProviders = ProfileHelper::socialLinkProviders(true);
            foreach ($socialLinks as $linkName => $socialLink) {
                if (isset($socialLinkProviders[$linkName])) {
                    $formattedSocialLinkes[$linkName] = sanitize_text_field(trim($socialLink));
                }
            }
            $meta['social_links'] = $formattedSocialLinkes;
        }

        $meta['short_description_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($updateData['short_description']));

        $updateData['meta'] = $meta;

        $xProfile->fill($updateData);
        $xProfile->save();

        // Let's update the user's details
        $xProfile->user->updateCustomData($updateData);
        $xProfile->compilation_score = $xProfile->getCompletionScore();

        if ($userNameChanged) {
            return [
                'message'      => __('Profile has been updated', 'fluent-community'),
                'profile'      => $xProfile,
                'redirect_url' => Helper::baseUrl('u/' . $xProfile->username . '/update')
            ];
        }

        $isOwn = $xProfile->user_id == get_current_user_id();
        $canEditUsers = current_user_can('edit_users');
        if ($canEditUsers || (Utility::getPrivacySetting('can_change_email') === 'yes' && $isOwn)) {
            $emailAddress = Arr::get($data, 'email');

            if ($emailAddress && is_email($emailAddress) && $emailAddress != $xProfile->user->user_email) {
                $owner_id = email_exists($emailAddress);
                if ($owner_id && $owner_id != $xProfile->user_id) {
                    return $this->sendError([
                        'message' => __('Email address already taken by someone else. Please use a different email address.', 'fluent-community')
                    ]);
                }

                // Let's check if it's their own
                $requireVerification = $isOwn && !$canEditUsers;
                if ($requireVerification) {
                    $currentUser = get_user_by('ID', $xProfile->user_id);
                    ProfileHelper::sendConfirmationOnProfileEmailChange($currentUser, $emailAddress);
                    return [
                        'message' => __('Email address change is pending. Please check your inbox to verify the new email address.', 'fluent-community'),
                        'profile' => $xProfile
                    ];
                }

                wp_update_user([
                    'user_email' => $emailAddress,
                    'ID'         => $xProfile->user_id
                ]);
            }
        }

        return [
            'message' => __('Profile has been updated', 'fluent-community'),
            'profile' => $xProfile
        ];
    }

    public function changePassword(Request $request, $userName)
    {
        $xProfile = XProfile::where('username', $userName)->firstOrFail();

        // Password can only be changed by the account owner, never by moderators/admins here.
        if ($xProfile->user_id != get_current_user_id()) {
            return $this->sendError([
                'message' => __('You are not allowed to change this password', 'fluent-community')
            ]);
        }

        if (Utility::getPrivacySetting('can_change_password') !== 'yes') {
            return $this->sendError([
                'message' => __('Password change is disabled', 'fluent-community')
            ]);
        }

        $data = $request->get('data', []);

        $this->validate($data, [
            'current_password' => 'required',
            'new_password'     => 'required',
            'confirm_password' => 'required',
        ], [
            'current_password.required' => __('Current password is required', 'fluent-community'),
            'new_password.required'     => __('New password is required', 'fluent-community'),
            'confirm_password.required' => __('Please confirm your new password', 'fluent-community'),
        ]);

        // Passwords are used verbatim; sanitizing would corrupt valid characters.
        $currentPassword = (string) Arr::get($data, 'current_password');
        $newPassword     = (string) Arr::get($data, 'new_password');
        $confirmPassword = (string) Arr::get($data, 'confirm_password');

        if (strlen($newPassword) < 4) {
            return $this->sendError([
                'message' => __('New password must be at least 4 characters long', 'fluent-community')
            ]);
        }

        if ($newPassword !== $confirmPassword) {
            return $this->sendError([
                'message' => __('New password and confirmation do not match', 'fluent-community')
            ]);
        }

        if ($newPassword === $currentPassword) {
            return $this->sendError([
                'message' => __('New password must be different from your current password', 'fluent-community')
            ]);
        }

        $user = get_user_by('id', $xProfile->user_id);

        if (!$user || !wp_check_password($currentPassword, $user->user_pass, $user->ID)) {
            return $this->sendError([
                'message' => __('Your current password is incorrect', 'fluent-community')
            ]);
        }

        wp_set_password($newPassword, $user->ID);

        // wp_set_password destroys every session for the user, which also invalidates the
        // REST nonce the SPA holds. Re-issue the cookie to keep the session, capturing the
        // fresh logged-in cookie so the nonces we mint below bind to the new session token.
        $newLoggedInCookie = '';
        $captureLoggedInCookie = function ($loggedInCookie) use (&$newLoggedInCookie) {
            $newLoggedInCookie = $loggedInCookie;
        };
        add_action('set_logged_in_cookie', $captureLoggedInCookie);

        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID, true);

        remove_action('set_logged_in_cookie', $captureLoggedInCookie);

        if ($newLoggedInCookie) {
            $_COOKIE[LOGGED_IN_COOKIE] = $newLoggedInCookie;
        }

        do_action('fluent_community/user/password_changed', $user->ID);

        return [
            'message'    => __('Your password has been changed successfully', 'fluent-community'),
            'rest_nonce' => wp_create_nonce('wp_rest'),
            'ajax_nonce' => wp_create_nonce('fluent_community_ajax_nonce'),
        ];
    }

    public function getAllMemberships(Request $request, $userName)
    {
        /** @var XProfile $xProfile */
        $xProfile = XProfile::where('username', $userName)->firstOrFail();

        $currentUser = $this->getUser();

        if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
            return $this->sendError([
                'message'           => __('You are not allowed to view this profile\'s membership.', 'fluent-community'),
                'permission_failed' => true
            ]);
        }

        $canSeeSecret = $xProfile->user_id == get_current_user_id()
            || ($currentUser && $currentUser->isCommunityModerator());

        $memberships = $xProfile->spaces()
            ->wherePivot('status', 'active')
            ->when(!$canSeeSecret, function ($q) {
                $q->whereIn('privacy', ['public', 'private']);
            })
            ->get()
            ->pluck('id');

        return apply_filters('fluent_community/profile_all_memberships_api_response', [
            'memberships' => $memberships
        ], $request->all());
    }

    public function getSpaces(Request $request, $userName)
    {
        /** @var XProfile $xProfile */
        $xProfile = XProfile::where('username', $userName)->firstOrFail();
        $currentUser = $this->getUser();

        if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
            return $this->sendError([
                'message'           => __('You are not allowed to view this profile\'s spaces.', 'fluent-community'),
                'permission_failed' => true
            ]);
        }

        if ($xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator())) {
            $spaces = $xProfile->spaces()
                ->wherePivot('status', 'active')
                ->get();
        } else {
            $spaces = $xProfile->spaces()
                ->whereIn('privacy', ['public', 'private'])
                ->wherePivot('status', 'active')
                ->get();
        }

        foreach ($spaces as $space) {
            $shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes';
            $canViewMembers = $currentUser && $space->verifyUserPermisson($currentUser, 'can_view_members', false);
            if ($shouldHideMembersCount && !$canViewMembers) {
                $space->members_count = 0;
                continue;
            }
            $space->members_count = $space->members()->count();
        }

            $data = [
                'spaces' => $spaces
            ];

            return apply_filters('fluent_community/profile_spaces_api_response', $data, $request->all());
    }

    public function getCourses(Request $request, $userName)
    {
        if (!Helper::isFeatureEnabled('course_module')) {
            return $this->sendError([
                'message' => __('Course module is disabled.', 'fluent-community')
            ]);
        }

        /** @var XProfile $xProfile */
        $xProfile = XProfile::where('username', $userName)->firstOrFail();
        $currentUser = $this->getUser();

        if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) {
            return $this->sendError([
                'message'           => __('You are not allowed to view this profile\'s courses.', 'fluent-community'),
                'permission_failed' => true
            ]);
        }

        $hasAllAccess = $xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator());

        $courses = $xProfile->courses()
            ->wherePivot('status', 'active')
            ->where('fcom_spaces.status', 'published')
            ->when(!$hasAllAccess, function ($q) {
                $q->whereIn('fcom_spaces.privacy', ['public', 'private']);
            })
            ->get();

        foreach ($courses as $course) {
            $course->isEnrolled = CourseHelper::isEnrolled($course->id, $xProfile->user_id);
            if ($course->isEnrolled) {
                $course->progress = CourseHelper::getCourseProgress($course->id, $xProfile->user_id);
            }

            if (!$course->cover_photo) {
                $course->cover_photo = FLUENT_COMMUNITY_PLUGIN_URL . 'assets/images/course-placeholder.jpg';
            }

            $course->sectionsCount = CourseTopic::where('space_id', $course->id)->count();
            $course->lessonsCount = CourseLesson::where('space_id', $course->id)->count();
            if (Arr::get($course->settings, 'hide_members_count') != 'yes') {
                $course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count();
            } else {
                $course->studentsCount = 0;
            }

            do_action_ref_array('fluent_community/course', [&$course]);
        }

        $data = [
            'courses' => $courses
        ];

        return apply_filters('fluent_community/profile_courses_api_response', $data, $request->all());
    }

    public function getComments(Request $request, $userName)
    {
        $xProfile = XProfile::where('username', $userName)->first();

        if (!$xProfile) {
            return $this->sendError([
                'message' => __('Profile not found', 'fluent-community')
            ]);
        }

        $currentUser = $this->getUser();
        $hasAllAccess = $xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator());

        $comments = Comment::where('user_id', $xProfile->user_id)
            ->where('status', 'published')
            ->with([
                'post' => function ($q) use ($currentUser) {
                    // Eager load the full feed so the post opens in the modal without a per-click fetch.
                    $q->select(array_merge(Feed::$publicColumns, ['message']))
                        ->with(Feed::withPublicRelations($currentUser));
                }
            ])
            ->when(!$hasAllAccess, function ($q) {
                $q->whereHas('post', function ($query) {
                    $query->byUserAccess(get_current_user_id());
                    $query->where('type', 'text');
                });
            })
            ->orderBy('id', 'desc')
            ->paginate();

            $posts = $comments->getCollection()
                ->pluck('post')
                ->filter()
                ->unique('id')
                ->values();

            if ($posts->isNotEmpty()) {
                FeedsHelper::transformFeedsCollection($posts);
            }

            $data = [
                'comments' => $comments,
                'xprofile' => $xProfile
            ];

            return apply_filters('fluent_community/profile_comments_api_response', $data, $request->all());
    }

    public function getNotificationPreferance(Request $request, $userName)
    {
        $emailPref = Utility::getEmailNotificationSettings();

        $xProfile = $this->verifyAndGetProfile($userName);

        $globalPreferances = NotificationPref::getGlobalPrefs();

        // Read through the same service the save path writes through. These rows
        // live in fcom_notification_prefs, keyed by flat keys - space-scoped ones
        // carry an '_<space id>' suffix.
        $userPrefs = NotificationPref::getUserPrefs($xProfile->user_id);

        $frequencyMaps = [
            0 => 'disabled',
            1 => 'hourly',
            2 => 'daily',
            3 => 'weekly'
        ];

        $userGlobalPrefs = [];
        $spaceWisePrefs = [];
        foreach ($userPrefs as $prefKey => $prefValue) {
            if ($prefKey === 'message_email_frequency') {
                $userGlobalPrefs[$prefKey] = isset($frequencyMaps[$prefValue]) ? $frequencyMaps[$prefValue] : 'default';
                continue;
            }

            if (preg_match('/^(np_by_(?:member|admin)_mail)_(\d+)$/', $prefKey, $matches)) {
                $spaceId = (int)$matches[2];

                if (empty($spaceWisePrefs[$spaceId])) {
                    $spaceWisePrefs[$spaceId] = [];
                }

                $spaceWisePrefs[$spaceId][$matches[1]] = $prefValue;
                continue;
            }

            $userGlobalPrefs[$prefKey] = $prefValue ? 'yes' : 'no';
        }

        $messagingConfig = Utility::getOption('_messaging_settings', []);
        $isGlobalPerUser = Arr::get($messagingConfig, 'messaging_email_frequency') == 'disabled';

        $pushAvailable = PushNotificationModule::isAvailable();

        $userGlobalPrefsDefaults = [
            'digest_mail'             => Arr::get($globalPreferances, 'digest_email_status') ? 'yes' : 'no',
            'mention_mail'            => Arr::get($globalPreferances, 'mention_mail') ? 'yes' : 'no',
            'reply_my_com_mail'       => Arr::get($globalPreferances, 'reply_my_com_mail') ? 'yes' : 'no',
            'com_my_post_mail'        => Arr::get($globalPreferances, 'com_my_post_mail') ? 'yes' : 'no',
            'message_email_frequency' => $isGlobalPerUser ? 'disabled' : 'default'
        ];

        if ($pushAvailable) {
            $pushPreferances = NotificationPref::getGlobalPrefs('push');

            $userGlobalPrefsDefaults['com_my_post_push'] = Arr::get($pushPreferances, 'com_my_post_push') ? 'yes' : 'no';
            $userGlobalPrefsDefaults['reply_my_com_push'] = Arr::get($pushPreferances, 'reply_my_com_push') ? 'yes' : 'no';
            $userGlobalPrefsDefaults['mention_push'] = Arr::get($pushPreferances, 'mention_push') ? 'yes' : 'no';
            $userGlobalPrefsDefaults['co_com_push'] = Arr::get($pushPreferances, 'co_com_push') ? 'yes' : 'no';
        }

        $userGlobalPrefs = wp_parse_args($userGlobalPrefs, $userGlobalPrefsDefaults);

        $profileUserId = $xProfile->user_id;
        $spaceGroups = SpaceGroup::with(['spaces' => function ($query) use ($profileUserId) {
            $query->whereHas('members', function ($q) use ($profileUserId) {
                $q->where('user_id', $profileUserId)
                  ->where('status', 'active');
            })
                ->where('type', 'community');
        }])
            ->orderBy('serial', 'ASC')
            ->get();

        $formattedSpaceGroups = [];
        foreach ($spaceGroups as $group) {
            if ($group->spaces->isEmpty()) {
                continue;
            }
            $formattedSpaces = [];
            foreach ($group->spaces as $space) {

                $pref = '';
                if (isset($spaceWisePrefs[$space->id])) {
                    $perfs = (array)$spaceWisePrefs[$space->id];
                    if (!empty($perfs['np_by_member_mail'])) {
                        $pref = 'all_member_posts';
                    } else if (!empty($perfs['np_by_admin_mail'])) {
                        $pref = 'admin_only_posts';
                    }
                }

                $formattedSpaces[] = [
                    'id'    => $space->id,
                    'title' => $space->title,
                    'icon'  => $space->getIconMark(),
                    'pref'  => $pref
                ];
            }
            if ($formattedSpaces) {
                $formattedSpaceGroups[] = [
                    'id'     => $group->id,
                    'title'  => $group->title,
                    'spaces' => $formattedSpaces
                ];
            }
        }

        // let's find the other spaces
        $otherSpaces = Space::whereHas('members', function ($q) use ($xProfile) {
            $q->where('user_id', $xProfile->user_id);
        })
            ->whereNull('parent_id')
            ->orderBy('title', 'ASC')
            ->get();

        if (!$otherSpaces->isEmpty()) {
            $formattedSpaces = [];
            foreach ($otherSpaces as $space) {
                $pref = '';
                if (isset($spaceWisePrefs[$space->id])) {
                    $perfs = (array)$spaceWisePrefs[$space->id];
                    if (!empty($perfs['np_by_member_mail'])) {
                        $pref = 'all_member_posts';
                    } else if (!empty($perfs['np_by_admin_mail'])) {
                        $pref = 'admin_only_posts';
                    }
                }

                $formattedSpaces[] = [
                    'id'    => $space->id,
                    'title' => $space->title,
                    'icon'  => $space->getIconMark(),
                    'pref'  => $pref
                ];
            }

            $formattedSpaceGroups[] = [
                'id'     => 'other_space_group',
                'title'  => __('Other Spaces', 'fluent-community'),
                'spaces' => $formattedSpaces
            ];
        }

        $digestDay = (string)Arr::get($emailPref, 'digest_mail_day', 'tue');
        if ($digestDay) {
            $maps = [
                'mon' => __('Monday', 'fluent-community'),
                'tue' => __('Tuesday', 'fluent-community'),
                'wed' => __('Wednesday', 'fluent-community'),
                'thu' => __('Thursday', 'fluent-community'),
                'fri' => __('Friday', 'fluent-community'),
                'sat' => __('Saturday', 'fluent-community'),
                'sun' => __('Sunday', 'fluent-community'),
            ];
            if (isset($maps[$digestDay])) {
                $digestDay = $maps[$digestDay];
            }
        }

        $crmEmailStatus = '';
        if ($xProfile->user_id == get_current_user_id()) {
            $profileUser = get_user_by('ID', $xProfile->user_id);
            if ($profileUser && $profileUser->user_email) {
                $crmEmailStatus = Helper::getCrmUndeliverableStatus($profileUser->user_email);
            }
        }

        $data = [
            'user_globals'                      => (object)$userGlobalPrefs,
            'spaceGroups'                       => $formattedSpaceGroups,
            'space_prefs'                       => $spaceWisePrefs,
            'digestEmailDay'                    => $digestDay,
            'default_messaging_email_frequency' => Arr::get($messagingConfig, 'messaging_email_status') !== 'yes' ? 'no' : Arr::get($messagingConfig, 'messaging_email_frequency'),
            'crm_email_status'                  => $crmEmailStatus,
            'push_available'                    => $pushAvailable,
        ];

        return apply_filters('fluent_community/profile_notification_pref_api_response', $data, $request->all());
    }

    public function saveNotificationPreferance(Request $request, $userName)
    {
        $xProfile = $this->verifyAndGetOwnProfile($userName);

        $userPrefs = $request->get('user_globals', []);
        $sapcePrefs = $request->get('space_prefs', []);

        $messagingPref = Arr::get($userPrefs, 'message_email_frequency');

        $userPrefs = array_map(function ($item) {
            return $item == 'yes' ? 1 : 0;
        }, $userPrefs);

        if ($messagingPref == 'hourly') {
            $userPrefs['message_email_frequency'] = 1;
        } else if ($messagingPref == 'daily') {
            $userPrefs['message_email_frequency'] = 2;
        } else if ($messagingPref == 'disabled') {
            $userPrefs['message_email_frequency'] = 0;
        } else if ($messagingPref == 'weekly') {
            $userPrefs['message_email_frequency'] = 3;
        } else {
            unset($userPrefs['message_email_frequency']);
        }

        foreach ($sapcePrefs as $spaceId => $pref) {
            $spaceId = (int)$spaceId;
            if (!$pref || !$spaceId) {
                continue;
            }

            if ($pref == 'all_member_posts') {
                $userPrefs['np_by_member_mail_' . $spaceId] = 1;
                $userPrefs['np_by_admin_mail_' . $spaceId] = 1;
            } else if ($pref == 'admin_only_posts') {
                $userPrefs['np_by_admin_mail_' . $spaceId] = 1;
            }
        }

        NotificationPref::updateUserPrefs($xProfile->user_id, $userPrefs);

        return [
            'prefs'   => $userPrefs,
            'message' => __('Email Notification preferences have been updated', 'fluent-community')
        ];
    }

    public function reconfirmEmail(Request $request, $userName)
    {
        if (!defined('FLUENTCRM')) {
            return $this->sendError([
                'message' => __('FluentCRM is not available on this site', 'fluent-community')
            ]);
        }

        $xProfile = XProfile::where('username', $userName)->firstOrFail();

        if ($xProfile->user_id != get_current_user_id()) {
            return $this->sendError([
                'message' => __('You can only re-confirm your own email address', 'fluent-community')
            ]);
        }

        $profileUser = get_user_by('ID', $xProfile->user_id);
        $email = $profileUser ? $profileUser->user_email : '';

        if (!$email || !Helper::getCrmUndeliverableStatus($email)) {
            return $this->sendError([
                'message' => __('Your email address does not need re-confirmation', 'fluent-community')
            ]);
        }

        $subscriber = \FluentCrm\App\Models\Subscriber::where('email', $email)->first();

        if (!$subscriber) {
            return $this->sendError([
                'message' => __('Your email address does not need re-confirmation', 'fluent-community')
            ]);
        }

        // In-memory only, never saved: the opt-in sender is gated on status == 'pending'
        // and does not persist the subscriber, so the stored status stays untouched
        // and FluentCommunity keeps pausing emails until the confirmation link is clicked.
        $subscriber->status = 'pending';

        if (!$subscriber->sendDoubleOptinEmail()) {
            return $this->sendError([
                'message' => __('The confirmation email could not be sent right now. Please try again after a few minutes.', 'fluent-community')
            ]);
        }

        return [
            'message' => __('A confirmation email has been sent. Please check your inbox and click the confirmation link to resume email notifications.', 'fluent-community')
        ];
    }

    private function verifyAndGetProfile($userName)
    {
        $xProfile = XProfile::where('username', $userName)->firstOrFail();

        $currentUserId = get_current_user_id();
        if ($xProfile->user_id != $currentUserId && !Helper::isSuperAdmin($currentUserId)) {
            throw new \Exception('You are not allowed to update this profile');
        }

        return $xProfile;
    }

    private function verifyAndGetOwnProfile($userName)
    {
        $xProfile = XProfile::where('username', $userName)->firstOrFail();

        if (!get_current_user_id() || $xProfile->user_id != get_current_user_id()) {
            throw new HttpException(403, esc_html__('You are not allowed to access these notification preferences.', 'fluent-community'));
        }

        return $xProfile;
    }
}

```
