| 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\Space; |
| 9 |
use FluentCommunity\App\Models\SpaceGroup; |
| 10 |
use FluentCommunity\App\Models\SpaceUserPivot; |
| 11 |
use FluentCommunity\App\Models\XProfile; |
| 12 |
use FluentCommunity\App\Services\CustomSanitizer; |
| 13 |
use FluentCommunity\App\Services\FeedsHelper; |
| 14 |
use FluentCommunity\App\Services\Helper; |
| 15 |
use FluentCommunity\App\Services\NotificationPref; |
| 16 |
use FluentCommunity\App\Services\ProfileHelper; |
| 17 |
use FluentCommunity\Framework\Http\Request\Request; |
| 18 |
use FluentCommunity\Framework\Support\Arr; |
| 19 |
use FluentCommunity\Modules\Course\Model\CourseLesson; |
| 20 |
use FluentCommunity\Modules\Course\Model\CourseTopic; |
| 21 |
use FluentCommunity\Modules\Course\Services\CourseHelper; |
| 22 |
use FluentCommunity\Modules\PushNotification\PushNotificationModule; |
| 23 |
use FluentCommunity\Framework\Foundation\Exceptions\HttpException; |
| 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((string) $xprofile->display_name)); |
| 80 |
$xprofileFirstName = array_shift($nameArray); |
| 81 |
$xprofileLastName = implode(' ', $nameArray); |
| 82 |
|
| 83 |
$profile['email'] = $user->user_email; |
| 84 |
$profile['first_name'] = $enableUserSync ? $user->first_name : $xprofileFirstName; |
| 85 |
$profile['last_name'] = $enableUserSync ? $user->last_name : $xprofileLastName; |
| 86 |
$profile['short_description'] = $xprofile->short_description; |
| 87 |
$profile['can_change_username'] = $isAdmin || Utility::getPrivacySetting('can_customize_username') === 'yes'; |
| 88 |
$profile['can_change_email'] = current_user_can('edit_users') || (Utility::getPrivacySetting('can_change_email') === 'yes' && $isOwn); |
| 89 |
$profile['can_change_password'] = $isOwn && Utility::getPrivacySetting('can_change_password') === 'yes'; |
| 90 |
} |
| 91 |
|
| 92 |
$profileBaseUrl = Helper::baseUrl('u/' . $xprofile->username . '/'); |
| 93 |
|
| 94 |
$profile['profile_navs'] = [ |
| 95 |
[ |
| 96 |
'slug' => 'user_profile', |
| 97 |
'title' => __('About', 'fluent-community'), |
| 98 |
'url' => $profileBaseUrl, |
| 99 |
'wrapper_class' => 'fcom_profile_about', |
| 100 |
'route' => [ |
| 101 |
'name' => 'user_profile' |
| 102 |
] |
| 103 |
], |
| 104 |
[ |
| 105 |
'slug' => 'user_profile_feeds', |
| 106 |
'title' => __('Posts', 'fluent-community'), |
| 107 |
'wrapper_class' => 'fcom_profile_posts', |
| 108 |
'url' => $profileBaseUrl . 'posts', |
| 109 |
'route' => [ |
| 110 |
'name' => 'user_profile_feeds' |
| 111 |
] |
| 112 |
] |
| 113 |
]; |
| 114 |
|
| 115 |
if ($profile['canViewUserSpaces']) { |
| 116 |
$profile['profile_navs'][] = [ |
| 117 |
'slug' => 'user_spaces', |
| 118 |
'wrapper_class' => 'fcom_profile_spaces', |
| 119 |
'title' => __('Spaces', 'fluent-community'), |
| 120 |
'url' => $profileBaseUrl . 'spaces', |
| 121 |
'route' => [ |
| 122 |
'name' => 'user_spaces' |
| 123 |
] |
| 124 |
]; |
| 125 |
|
| 126 |
if (Helper::isFeatureEnabled('course_module')) { |
| 127 |
$profile['profile_navs'][] = [ |
| 128 |
'slug' => 'user_courses', |
| 129 |
'wrapper_class' => 'fcom_profile_courses', |
| 130 |
'title' => __('Courses', 'fluent-community'), |
| 131 |
'url' => $profileBaseUrl . 'courses', |
| 132 |
'route' => [ |
| 133 |
'name' => 'user_courses' |
| 134 |
] |
| 135 |
]; |
| 136 |
} |
| 137 |
} |
| 138 |
|
| 139 |
$profile['profile_navs'][] = [ |
| 140 |
'slug' => 'user_comments', |
| 141 |
'wrapper_class' => 'fcom_profile_comments', |
| 142 |
'title' => __('Comments', 'fluent-community'), |
| 143 |
'url' => $profileBaseUrl . 'comments', |
| 144 |
'route' => [ |
| 145 |
'name' => 'user_comments' |
| 146 |
] |
| 147 |
]; |
| 148 |
|
| 149 |
$profile['profile_nav_actions'] = []; |
| 150 |
|
| 151 |
$profile = apply_filters('fluent_community/profile_view_data', $profile, $xprofile, $isAdmin); |
| 152 |
|
| 153 |
return [ |
| 154 |
'profile' => $profile |
| 155 |
]; |
| 156 |
} |
| 157 |
|
| 158 |
public function patchProfile(Request $request, $userName) |
| 159 |
{ |
| 160 |
$xprofile = $this->verifyAndGetProfile($userName); |
| 161 |
|
| 162 |
$updateData = $request->get('data', []); |
| 163 |
|
| 164 |
if (!empty($updateData['status']) && $updateData['status'] === 'deactivated' && $xprofile->status === 'active') { |
| 165 |
// handle deactivation |
| 166 |
$canDeactivate = Utility::getPrivacySetting('can_deactive_account') === 'yes' || Helper::isSiteAdmin(); |
| 167 |
if (!$canDeactivate) { |
| 168 |
return $this->sendError([ |
| 169 |
'message' => __('You are not allowed to deactivate this account.', 'fluent-community') |
| 170 |
]); |
| 171 |
} |
| 172 |
|
| 173 |
$xprofile->status = ''; |
| 174 |
$xprofile->save(); |
| 175 |
update_user_meta($xprofile->user_id, '_fcom_deactivated_at', current_time('mysql')); |
| 176 |
do_action('fluent_community/profile_deactivated', $xprofile); |
| 177 |
|
| 178 |
return [ |
| 179 |
'message' => __('Your profile has been deactivated successfully.', 'fluent-community') |
| 180 |
]; |
| 181 |
} |
| 182 |
|
| 183 |
$mediaTypes = ['cover_photo', 'avatar']; |
| 184 |
|
| 185 |
foreach ($mediaTypes as $type) { |
| 186 |
if (!empty($updateData[$type])) { |
| 187 |
$media = Helper::getMediaFromUrl($updateData[$type]); |
| 188 |
if (!$media || $media->is_active) { |
| 189 |
return $this->sendError([ |
| 190 |
'message' => __('Invalid media image. Please upload a new one.', 'fluent-community') |
| 191 |
]); |
| 192 |
} |
| 193 |
|
| 194 |
$updateData[$type] = $media->public_url; |
| 195 |
|
| 196 |
$media->update([ |
| 197 |
'is_active' => true, |
| 198 |
'user_id' => $xprofile->user_id, |
| 199 |
'object_source' => 'user_' . $type |
| 200 |
]); |
| 201 |
} |
| 202 |
} |
| 203 |
|
| 204 |
$deletedMedias = []; |
| 205 |
|
| 206 |
if (isset($updateData['avatar'])) { |
| 207 |
|
| 208 |
if ($xprofile->hasCustomAvatar()) { |
| 209 |
$deletedMedias[] = Arr::get($xprofile->getAttributes(), 'avatar'); |
| 210 |
} |
| 211 |
|
| 212 |
$xprofile->avatar = $updateData['avatar']; |
| 213 |
|
| 214 |
if (defined('FLUENTCRM')) { |
| 215 |
$contact = $xprofile->contact; |
| 216 |
|
| 217 |
if ($contact) { |
| 218 |
$contact->update([ |
| 219 |
'avatar' => $updateData['avatar'] ?: null |
| 220 |
]); |
| 221 |
} |
| 222 |
} |
| 223 |
|
| 224 |
if (empty($updateData['avatar'])) { |
| 225 |
Utility::forgetCache('user_avatar_' . $xprofile->user_id); |
| 226 |
} |
| 227 |
} |
| 228 |
|
| 229 |
if (isset($updateData['cover_photo'])) { |
| 230 |
$deletedMedias[] = Arr::get($xprofile->meta, 'cover_photo'); |
| 231 |
$xprofile->meta = wp_parse_args(['cover_photo' => $updateData['cover_photo']], $xprofile->meta); |
| 232 |
} |
| 233 |
|
| 234 |
$xprofile->save(); |
| 235 |
|
| 236 |
if ($deletedMedias = array_filter($deletedMedias)) { |
| 237 |
do_action('fluent_community/remove_medias_by_url', $deletedMedias, [ |
| 238 |
'user_id' => $xprofile->user_id, |
| 239 |
'object_sources' => ['user_avatar', 'user_cover_photo'] |
| 240 |
]); |
| 241 |
} |
| 242 |
|
| 243 |
return [ |
| 244 |
'message' => __('Profile updated', 'fluent-community') |
| 245 |
]; |
| 246 |
} |
| 247 |
|
| 248 |
public function updateProfile(Request $request, $userName) |
| 249 |
{ |
| 250 |
$currentUser = $this->getUser(true); |
| 251 |
$data = $request->get('data', []); |
| 252 |
|
| 253 |
/** @var XProfile $xProfile */ |
| 254 |
$xProfile = XProfile::where('username', $userName)->firstOrFail(); |
| 255 |
|
| 256 |
if ($xProfile->user_id != get_current_user_id()) { |
| 257 |
if(!$currentUser->isCommunityModerator()) { |
| 258 |
return $this->sendError([ |
| 259 |
'message' => __('You are not allowed to update this profile', 'fluent-community') |
| 260 |
]); |
| 261 |
} |
| 262 |
} |
| 263 |
|
| 264 |
$this->validate($data, [ |
| 265 |
'first_name' => 'required', |
| 266 |
], [ |
| 267 |
'first_name.required' => __('First name is required', 'fluent-community') |
| 268 |
]); |
| 269 |
|
| 270 |
$updateData = Arr::only($data, ['first_name', 'last_name', 'short_description', 'website']); |
| 271 |
|
| 272 |
$updateData = apply_filters('fluent_community/update_profile_data', $updateData, $data, $xProfile, $currentUser); |
| 273 |
|
| 274 |
$meta = $xProfile->meta; |
| 275 |
|
| 276 |
$userNameChanged = false; |
| 277 |
|
| 278 |
if ($currentUser->isCommunityModerator()) { |
| 279 |
$updateData['is_verified'] = Arr::get($data, 'is_verified') ? 1 : 0; |
| 280 |
$updateData['status'] = Arr::get($data, 'status', 'active'); |
| 281 |
$userName = Arr::get($data, 'username'); |
| 282 |
|
| 283 |
if (user_can($xProfile->user_id, 'list_users')) { |
| 284 |
$updateData['status'] = 'active'; |
| 285 |
} |
| 286 |
|
| 287 |
if ($userName) { |
| 288 |
// Check if username is exit or not |
| 289 |
$userName = CustomSanitizer::sanitizeUserName($userName); |
| 290 |
|
| 291 |
if (!$userName) { |
| 292 |
return $this->sendError([ |
| 293 |
'message' => __('Invalid username. Only Latin characters with _ & - are allowed.', 'fluent-community') |
| 294 |
]); |
| 295 |
} |
| 296 |
|
| 297 |
if (XProfile::where('username', $userName)->where('user_id', '!=', $xProfile->user_id)->exists()) { |
| 298 |
return $this->sendError([ |
| 299 |
'message' => __('Community Username already taken by someone else', 'fluent-community') |
| 300 |
]); |
| 301 |
} |
| 302 |
|
| 303 |
$userExist = get_user_by('user_login', $userName); |
| 304 |
|
| 305 |
if ($userExist && $userExist->ID != $xProfile->user_id) { |
| 306 |
return $this->sendError([ |
| 307 |
'message' => __('Username already taken by someone else. Please use a different username.', 'fluent-community') |
| 308 |
]); |
| 309 |
} |
| 310 |
|
| 311 |
$updateData['username'] = $userName; |
| 312 |
$userNameChanged = $userName != $xProfile->username; |
| 313 |
} |
| 314 |
|
| 315 |
if (Helper::isFeatureEnabled('user_badge')) { |
| 316 |
$badgeSlug = array_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))); |
| 321 |
} |
| 322 |
} else if (Utility::getPrivacySetting('can_customize_username')) { |
| 323 |
$userName = Arr::get($data, 'username'); |
| 324 |
|
| 325 |
if ($xProfile->username != $userName) { |
| 326 |
$userName = strtolower(CustomSanitizer::sanitizeUserName($userName)); |
| 327 |
if (!$userName) { |
| 328 |
return $this->sendError([ |
| 329 |
'message' => __('Invalid username. Only Latin characters with _ & - are allowed.', 'fluent-community') |
| 330 |
]); |
| 331 |
} |
| 332 |
|
| 333 |
if (XProfile::where('username', $userName)->where('user_id', '!=', $xProfile->user_id)->exists()) { |
| 334 |
return $this->sendError([ |
| 335 |
'message' => __('Community Username already taken by someone else', 'fluent-community') |
| 336 |
]); |
| 337 |
} |
| 338 |
|
| 339 |
if (strlen($userName) < 3) { |
| 340 |
return $this->sendError([ |
| 341 |
'message' => __('Username should be at least 3 characters long.', 'fluent-community') |
| 342 |
]); |
| 343 |
} |
| 344 |
|
| 345 |
$reservedUserNames = ProfileHelper::getReservedUserNames(); |
| 346 |
if (in_array($userName, $reservedUserNames)) { |
| 347 |
return $this->sendError([ |
| 348 |
'message' => __('Please use another username. This username is reserved', 'fluent-community') |
| 349 |
]); |
| 350 |
} |
| 351 |
|
| 352 |
$updateData['username'] = $userName; |
| 353 |
$userNameChanged = true; |
| 354 |
} |
| 355 |
} |
| 356 |
|
| 357 |
$updateData['display_name'] = trim(sanitize_text_field(Arr::get($data, 'first_name') . ' ' . Arr::get($data, 'last_name'))); |
| 358 |
|
| 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', ''))); |
| 362 |
$socialLinks = Arr::get($data, 'social_links', []); |
| 363 |
|
| 364 |
$maxDescriptionLength = apply_filters('fluent_community/max_profile_description_length', 5000); |
| 365 |
if ($updateData['short_description'] && strlen($updateData['short_description']) > $maxDescriptionLength) { |
| 366 |
return $this->sendError([ |
| 367 |
'message' => sprintf( |
| 368 |
/* translators: %d: Maximum number of characters allowed in the profile bio. */ |
| 369 |
__('Profile bio should not exceed %d characters.', 'fluent-community'), |
| 370 |
$maxDescriptionLength |
| 371 |
) |
| 372 |
]); |
| 373 |
} |
| 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 |
|
| 386 |
if ($socialLinks) { |
| 387 |
$socialLinks = array_filter($socialLinks); |
| 388 |
$formattedSocialLinkes = []; |
| 389 |
$socialLinkProviders = ProfileHelper::socialLinkProviders(true); |
| 390 |
foreach ($socialLinks as $linkName => $socialLink) { |
| 391 |
if (isset($socialLinkProviders[$linkName])) { |
| 392 |
$formattedSocialLinkes[$linkName] = sanitize_text_field(trim($socialLink)); |
| 393 |
} |
| 394 |
} |
| 395 |
$meta['social_links'] = $formattedSocialLinkes; |
| 396 |
} |
| 397 |
|
| 398 |
$meta['short_description_rendered'] = wp_kses_post(FeedsHelper::mdToHtml($updateData['short_description'])); |
| 399 |
|
| 400 |
$updateData['meta'] = $meta; |
| 401 |
|
| 402 |
$xProfile->fill($updateData); |
| 403 |
$xProfile->save(); |
| 404 |
|
| 405 |
// Let's update the user's details |
| 406 |
$xProfile->user->updateCustomData($updateData); |
| 407 |
$xProfile->compilation_score = $xProfile->getCompletionScore(); |
| 408 |
|
| 409 |
if ($userNameChanged) { |
| 410 |
return [ |
| 411 |
'message' => __('Profile has been updated', 'fluent-community'), |
| 412 |
'profile' => $xProfile, |
| 413 |
'redirect_url' => Helper::baseUrl('u/' . $xProfile->username . '/update') |
| 414 |
]; |
| 415 |
} |
| 416 |
|
| 417 |
$isOwn = $xProfile->user_id == get_current_user_id(); |
| 418 |
$canEditUsers = current_user_can('edit_users'); |
| 419 |
if ($canEditUsers || (Utility::getPrivacySetting('can_change_email') === 'yes' && $isOwn)) { |
| 420 |
$emailAddress = Arr::get($data, 'email'); |
| 421 |
|
| 422 |
if ($emailAddress && is_email($emailAddress) && $emailAddress != $xProfile->user->user_email) { |
| 423 |
$owner_id = email_exists($emailAddress); |
| 424 |
if ($owner_id && $owner_id != $xProfile->user_id) { |
| 425 |
return $this->sendError([ |
| 426 |
'message' => __('Email address already taken by someone else. Please use a different email address.', 'fluent-community') |
| 427 |
]); |
| 428 |
} |
| 429 |
|
| 430 |
// Let's check if it's their own |
| 431 |
$requireVerification = $isOwn && !$canEditUsers; |
| 432 |
if ($requireVerification) { |
| 433 |
$currentUser = get_user_by('ID', $xProfile->user_id); |
| 434 |
ProfileHelper::sendConfirmationOnProfileEmailChange($currentUser, $emailAddress); |
| 435 |
return [ |
| 436 |
'message' => __('Email address change is pending. Please check your inbox to verify the new email address.', 'fluent-community'), |
| 437 |
'profile' => $xProfile |
| 438 |
]; |
| 439 |
} |
| 440 |
|
| 441 |
wp_update_user([ |
| 442 |
'user_email' => $emailAddress, |
| 443 |
'ID' => $xProfile->user_id |
| 444 |
]); |
| 445 |
} |
| 446 |
} |
| 447 |
|
| 448 |
return [ |
| 449 |
'message' => __('Profile has been updated', 'fluent-community'), |
| 450 |
'profile' => $xProfile |
| 451 |
]; |
| 452 |
} |
| 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 |
|
| 543 |
public function getAllMemberships(Request $request, $userName) |
| 544 |
{ |
| 545 |
/** @var XProfile $xProfile */ |
| 546 |
$xProfile = XProfile::where('username', $userName)->firstOrFail(); |
| 547 |
|
| 548 |
$currentUser = $this->getUser(); |
| 549 |
|
| 550 |
if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) { |
| 551 |
return $this->sendError([ |
| 552 |
'message' => __('You are not allowed to view this profile\'s membership.', 'fluent-community'), |
| 553 |
'permission_failed' => true |
| 554 |
]); |
| 555 |
} |
| 556 |
|
| 557 |
$canSeeSecret = $xProfile->user_id == get_current_user_id() |
| 558 |
|| ($currentUser && $currentUser->isCommunityModerator()); |
| 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 |
|
| 568 |
return apply_filters('fluent_community/profile_all_memberships_api_response', [ |
| 569 |
'memberships' => $memberships |
| 570 |
], $request->all()); |
| 571 |
} |
| 572 |
|
| 573 |
public function getSpaces(Request $request, $userName) |
| 574 |
{ |
| 575 |
/** @var XProfile $xProfile */ |
| 576 |
$xProfile = XProfile::where('username', $userName)->firstOrFail(); |
| 577 |
$currentUser = $this->getUser(); |
| 578 |
|
| 579 |
if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) { |
| 580 |
return $this->sendError([ |
| 581 |
'message' => __('You are not allowed to view this profile\'s spaces.', 'fluent-community'), |
| 582 |
'permission_failed' => true |
| 583 |
]); |
| 584 |
} |
| 585 |
|
| 586 |
if ($xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator())) { |
| 587 |
$spaces = $xProfile->spaces() |
| 588 |
->wherePivot('status', 'active') |
| 589 |
->get(); |
| 590 |
} else { |
| 591 |
$spaces = $xProfile->spaces() |
| 592 |
->whereIn('privacy', ['public', 'private']) |
| 593 |
->wherePivot('status', 'active') |
| 594 |
->get(); |
| 595 |
} |
| 596 |
|
| 597 |
foreach ($spaces as $space) { |
| 598 |
$shouldHideMembersCount = Arr::get($space->settings, 'hide_members_count') == 'yes'; |
| 599 |
$canViewMembers = $currentUser && $space->verifyUserPermisson($currentUser, 'can_view_members', false); |
| 600 |
if ($shouldHideMembersCount && !$canViewMembers) { |
| 601 |
$space->members_count = 0; |
| 602 |
continue; |
| 603 |
} |
| 604 |
$space->members_count = $space->members()->count(); |
| 605 |
} |
| 606 |
|
| 607 |
$data = [ |
| 608 |
'spaces' => $spaces |
| 609 |
]; |
| 610 |
|
| 611 |
return apply_filters('fluent_community/profile_spaces_api_response', $data, $request->all()); |
| 612 |
} |
| 613 |
|
| 614 |
public function getCourses(Request $request, $userName) |
| 615 |
{ |
| 616 |
if (!Helper::isFeatureEnabled('course_module')) { |
| 617 |
return $this->sendError([ |
| 618 |
'message' => __('Course module is disabled.', 'fluent-community') |
| 619 |
]); |
| 620 |
} |
| 621 |
|
| 622 |
/** @var XProfile $xProfile */ |
| 623 |
$xProfile = XProfile::where('username', $userName)->firstOrFail(); |
| 624 |
$currentUser = $this->getUser(); |
| 625 |
|
| 626 |
if (!ProfileHelper::canViewUserSpaces($xProfile->user_id, $currentUser)) { |
| 627 |
return $this->sendError([ |
| 628 |
'message' => __('You are not allowed to view this profile\'s courses.', 'fluent-community'), |
| 629 |
'permission_failed' => true |
| 630 |
]); |
| 631 |
} |
| 632 |
|
| 633 |
$hasAllAccess = $xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator()); |
| 634 |
|
| 635 |
$courses = $xProfile->courses() |
| 636 |
->wherePivot('status', 'active') |
| 637 |
->where('fcom_spaces.status', 'published') |
| 638 |
->when(!$hasAllAccess, function ($q) { |
| 639 |
$q->whereIn('fcom_spaces.privacy', ['public', 'private']); |
| 640 |
}) |
| 641 |
->get(); |
| 642 |
|
| 643 |
foreach ($courses as $course) { |
| 644 |
$course->isEnrolled = CourseHelper::isEnrolled($course->id, $xProfile->user_id); |
| 645 |
if ($course->isEnrolled) { |
| 646 |
$course->progress = CourseHelper::getCourseProgress($course->id, $xProfile->user_id); |
| 647 |
} |
| 648 |
|
| 649 |
if (!$course->cover_photo) { |
| 650 |
$course->cover_photo = FLUENT_COMMUNITY_PLUGIN_URL . 'assets/images/course-placeholder.jpg'; |
| 651 |
} |
| 652 |
|
| 653 |
$course->sectionsCount = CourseTopic::where('space_id', $course->id)->count(); |
| 654 |
$course->lessonsCount = CourseLesson::where('space_id', $course->id)->count(); |
| 655 |
if (Arr::get($course->settings, 'hide_members_count') != 'yes') { |
| 656 |
$course->studentsCount = SpaceUserPivot::where('space_id', $course->id)->count(); |
| 657 |
} else { |
| 658 |
$course->studentsCount = 0; |
| 659 |
} |
| 660 |
|
| 661 |
do_action_ref_array('fluent_community/course', [&$course]); |
| 662 |
} |
| 663 |
|
| 664 |
$data = [ |
| 665 |
'courses' => $courses |
| 666 |
]; |
| 667 |
|
| 668 |
return apply_filters('fluent_community/profile_courses_api_response', $data, $request->all()); |
| 669 |
} |
| 670 |
|
| 671 |
public function getComments(Request $request, $userName) |
| 672 |
{ |
| 673 |
$xProfile = XProfile::where('username', $userName)->first(); |
| 674 |
|
| 675 |
if (!$xProfile) { |
| 676 |
return $this->sendError([ |
| 677 |
'message' => __('Profile not found', 'fluent-community') |
| 678 |
]); |
| 679 |
} |
| 680 |
|
| 681 |
$currentUser = $this->getUser(); |
| 682 |
$hasAllAccess = $xProfile->user_id == get_current_user_id() || ($currentUser && $currentUser->isCommunityModerator()); |
| 683 |
|
| 684 |
$comments = Comment::where('user_id', $xProfile->user_id) |
| 685 |
->where('status', 'published') |
| 686 |
->with([ |
| 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)); |
| 691 |
} |
| 692 |
]) |
| 693 |
->when(!$hasAllAccess, function ($q) { |
| 694 |
$q->whereHas('post', function ($query) { |
| 695 |
$query->byUserAccess(get_current_user_id()); |
| 696 |
$query->where('type', 'text'); |
| 697 |
}); |
| 698 |
}) |
| 699 |
->orderBy('id', 'desc') |
| 700 |
->paginate(); |
| 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 |
|
| 712 |
$data = [ |
| 713 |
'comments' => $comments, |
| 714 |
'xprofile' => $xProfile |
| 715 |
]; |
| 716 |
|
| 717 |
return apply_filters('fluent_community/profile_comments_api_response', $data, $request->all()); |
| 718 |
} |
| 719 |
|
| 720 |
public function getNotificationPreferance(Request $request, $userName) |
| 721 |
{ |
| 722 |
$emailPref = Utility::getEmailNotificationSettings(); |
| 723 |
|
| 724 |
$xProfile = $this->verifyAndGetProfile($userName); |
| 725 |
|
| 726 |
$globalPreferances = NotificationPref::getGlobalPrefs(); |
| 727 |
|
| 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); |
| 732 |
|
| 733 |
$frequencyMaps = [ |
| 734 |
0 => 'disabled', |
| 735 |
1 => 'hourly', |
| 736 |
2 => 'daily', |
| 737 |
3 => 'weekly' |
| 738 |
]; |
| 739 |
|
| 740 |
$userGlobalPrefs = []; |
| 741 |
$spaceWisePrefs = []; |
| 742 |
foreach ($userPrefs as $prefKey => $prefValue) { |
| 743 |
if ($prefKey === 'message_email_frequency') { |
| 744 |
$userGlobalPrefs[$prefKey] = isset($frequencyMaps[$prefValue]) ? $frequencyMaps[$prefValue] : 'default'; |
| 745 |
continue; |
| 746 |
} |
| 747 |
|
| 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] = []; |
| 753 |
} |
| 754 |
|
| 755 |
$spaceWisePrefs[$spaceId][$matches[1]] = $prefValue; |
| 756 |
continue; |
| 757 |
} |
| 758 |
|
| 759 |
$userGlobalPrefs[$prefKey] = $prefValue ? 'yes' : 'no'; |
| 760 |
} |
| 761 |
|
| 762 |
$messagingConfig = Utility::getOption('_messaging_settings', []); |
| 763 |
$isGlobalPerUser = Arr::get($messagingConfig, 'messaging_email_frequency') == 'disabled'; |
| 764 |
|
| 765 |
$pushAvailable = PushNotificationModule::isAvailable(); |
| 766 |
|
| 767 |
$userGlobalPrefsDefaults = [ |
| 768 |
'digest_mail' => Arr::get($globalPreferances, 'digest_email_status') ? 'yes' : 'no', |
| 769 |
'mention_mail' => Arr::get($globalPreferances, 'mention_mail') ? 'yes' : 'no', |
| 770 |
'reply_my_com_mail' => Arr::get($globalPreferances, 'reply_my_com_mail') ? 'yes' : 'no', |
| 771 |
'com_my_post_mail' => Arr::get($globalPreferances, 'com_my_post_mail') ? 'yes' : 'no', |
| 772 |
'message_email_frequency' => $isGlobalPerUser ? 'disabled' : 'default' |
| 773 |
]; |
| 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 |
|
| 784 |
$userGlobalPrefs = wp_parse_args($userGlobalPrefs, $userGlobalPrefsDefaults); |
| 785 |
|
| 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) |
| 790 |
->where('status', 'active'); |
| 791 |
}) |
| 792 |
->where('type', 'community'); |
| 793 |
}]) |
| 794 |
->orderBy('serial', 'ASC') |
| 795 |
->get(); |
| 796 |
|
| 797 |
$formattedSpaceGroups = []; |
| 798 |
foreach ($spaceGroups as $group) { |
| 799 |
if ($group->spaces->isEmpty()) { |
| 800 |
continue; |
| 801 |
} |
| 802 |
$formattedSpaces = []; |
| 803 |
foreach ($group->spaces as $space) { |
| 804 |
|
| 805 |
$pref = ''; |
| 806 |
if (isset($spaceWisePrefs[$space->id])) { |
| 807 |
$perfs = (array)$spaceWisePrefs[$space->id]; |
| 808 |
if (!empty($perfs['np_by_member_mail'])) { |
| 809 |
$pref = 'all_member_posts'; |
| 810 |
} else if (!empty($perfs['np_by_admin_mail'])) { |
| 811 |
$pref = 'admin_only_posts'; |
| 812 |
} |
| 813 |
} |
| 814 |
|
| 815 |
$formattedSpaces[] = [ |
| 816 |
'id' => $space->id, |
| 817 |
'title' => $space->title, |
| 818 |
'icon' => $space->getIconMark(), |
| 819 |
'pref' => $pref |
| 820 |
]; |
| 821 |
} |
| 822 |
if ($formattedSpaces) { |
| 823 |
$formattedSpaceGroups[] = [ |
| 824 |
'id' => $group->id, |
| 825 |
'title' => $group->title, |
| 826 |
'spaces' => $formattedSpaces |
| 827 |
]; |
| 828 |
} |
| 829 |
} |
| 830 |
|
| 831 |
// let's find the other spaces |
| 832 |
$otherSpaces = Space::whereHas('members', function ($q) use ($xProfile) { |
| 833 |
$q->where('user_id', $xProfile->user_id); |
| 834 |
}) |
| 835 |
->whereNull('parent_id') |
| 836 |
->orderBy('title', 'ASC') |
| 837 |
->get(); |
| 838 |
|
| 839 |
if (!$otherSpaces->isEmpty()) { |
| 840 |
$formattedSpaces = []; |
| 841 |
foreach ($otherSpaces as $space) { |
| 842 |
$pref = ''; |
| 843 |
if (isset($spaceWisePrefs[$space->id])) { |
| 844 |
$perfs = (array)$spaceWisePrefs[$space->id]; |
| 845 |
if (!empty($perfs['np_by_member_mail'])) { |
| 846 |
$pref = 'all_member_posts'; |
| 847 |
} else if (!empty($perfs['np_by_admin_mail'])) { |
| 848 |
$pref = 'admin_only_posts'; |
| 849 |
} |
| 850 |
} |
| 851 |
|
| 852 |
$formattedSpaces[] = [ |
| 853 |
'id' => $space->id, |
| 854 |
'title' => $space->title, |
| 855 |
'icon' => $space->getIconMark(), |
| 856 |
'pref' => $pref |
| 857 |
]; |
| 858 |
} |
| 859 |
|
| 860 |
$formattedSpaceGroups[] = [ |
| 861 |
'id' => 'other_space_group', |
| 862 |
'title' => __('Other Spaces', 'fluent-community'), |
| 863 |
'spaces' => $formattedSpaces |
| 864 |
]; |
| 865 |
} |
| 866 |
|
| 867 |
$digestDay = (string)Arr::get($emailPref, 'digest_mail_day', 'tue'); |
| 868 |
if ($digestDay) { |
| 869 |
$maps = [ |
| 870 |
'mon' => __('Monday', 'fluent-community'), |
| 871 |
'tue' => __('Tuesday', 'fluent-community'), |
| 872 |
'wed' => __('Wednesday', 'fluent-community'), |
| 873 |
'thu' => __('Thursday', 'fluent-community'), |
| 874 |
'fri' => __('Friday', 'fluent-community'), |
| 875 |
'sat' => __('Saturday', 'fluent-community'), |
| 876 |
'sun' => __('Sunday', 'fluent-community'), |
| 877 |
]; |
| 878 |
if (isset($maps[$digestDay])) { |
| 879 |
$digestDay = $maps[$digestDay]; |
| 880 |
} |
| 881 |
} |
| 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 |
|
| 891 |
$data = [ |
| 892 |
'user_globals' => (object)$userGlobalPrefs, |
| 893 |
'spaceGroups' => $formattedSpaceGroups, |
| 894 |
'space_prefs' => $spaceWisePrefs, |
| 895 |
'digestEmailDay' => $digestDay, |
| 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, |
| 899 |
]; |
| 900 |
|
| 901 |
return apply_filters('fluent_community/profile_notification_pref_api_response', $data, $request->all()); |
| 902 |
} |
| 903 |
|
| 904 |
public function saveNotificationPreferance(Request $request, $userName) |
| 905 |
{ |
| 906 |
$xProfile = $this->verifyAndGetOwnProfile($userName); |
| 907 |
|
| 908 |
$userPrefs = $request->get('user_globals', []); |
| 909 |
$sapcePrefs = $request->get('space_prefs', []); |
| 910 |
|
| 911 |
$messagingPref = Arr::get($userPrefs, 'message_email_frequency'); |
| 912 |
|
| 913 |
$userPrefs = array_map(function ($item) { |
| 914 |
return $item == 'yes' ? 1 : 0; |
| 915 |
}, $userPrefs); |
| 916 |
|
| 917 |
if ($messagingPref == 'hourly') { |
| 918 |
$userPrefs['message_email_frequency'] = 1; |
| 919 |
} else if ($messagingPref == 'daily') { |
| 920 |
$userPrefs['message_email_frequency'] = 2; |
| 921 |
} else if ($messagingPref == 'disabled') { |
| 922 |
$userPrefs['message_email_frequency'] = 0; |
| 923 |
} else if ($messagingPref == 'weekly') { |
| 924 |
$userPrefs['message_email_frequency'] = 3; |
| 925 |
} else { |
| 926 |
unset($userPrefs['message_email_frequency']); |
| 927 |
} |
| 928 |
|
| 929 |
foreach ($sapcePrefs as $spaceId => $pref) { |
| 930 |
$spaceId = (int)$spaceId; |
| 931 |
if (!$pref || !$spaceId) { |
| 932 |
continue; |
| 933 |
} |
| 934 |
|
| 935 |
if ($pref == 'all_member_posts') { |
| 936 |
$userPrefs['np_by_member_mail_' . $spaceId] = 1; |
| 937 |
$userPrefs['np_by_admin_mail_' . $spaceId] = 1; |
| 938 |
} else if ($pref == 'admin_only_posts') { |
| 939 |
$userPrefs['np_by_admin_mail_' . $spaceId] = 1; |
| 940 |
} |
| 941 |
} |
| 942 |
|
| 943 |
NotificationPref::updateUserPrefs($xProfile->user_id, $userPrefs); |
| 944 |
|
| 945 |
return [ |
| 946 |
'prefs' => $userPrefs, |
| 947 |
'message' => __('Email Notification preferences have been updated', 'fluent-community') |
| 948 |
]; |
| 949 |
} |
| 950 |
|
| 951 |
public function reconfirmEmail(Request $request, $userName) |
| 952 |
{ |
| 953 |
if (!defined('FLUENTCRM')) { |
| 954 |
return $this->sendError([ |
| 955 |
'message' => __('FluentCRM is not available on this site', 'fluent-community') |
| 956 |
]); |
| 957 |
} |
| 958 |
|
| 959 |
$xProfile = XProfile::where('username', $userName)->firstOrFail(); |
| 960 |
|
| 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)) { |
| 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')); |
| 1018 |
} |
| 1019 |
|
| 1020 |
return $xProfile; |
| 1021 |
} |
| 1022 |
} |
| 1023 |
|