PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 All 50 releases
fluent-cart / app / Services / CustomerIdentity / EmailVerificationService.php

EmailVerificationService.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.7.0, at app/Services/CustomerIdentity/EmailVerificationService.php

124 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\CustomerIdentity;
4
5 use FluentCart\Api\Resource\CustomerResource;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\Models\Customer;
8 use FluentCart\Framework\Support\Arr;
9
10 /** Tracks inbox proof independently from WordPress authentication. */
11 class EmailVerificationService
12 {
13 const META_KEY = '_fct_email_verification';
14
15 const PENDING_CLAIM_META_KEY = '_fct_email_claim';
16
17 /** Proof captured before WordPress consumes the reset key; never persisted. */
18 private static $passwordResetProof = [];
19
20 public static function capturePasswordResetProof($user): void
21 {
22 unset(self::$passwordResetProof[$user->ID]);
23 $cookie = Arr::get($_COOKIE, 'wp-resetpass-' . COOKIEHASH, '');
24 $postedKey = Arr::get($_POST, 'rp_key', '');
25 if (!is_string($cookie) || !is_string($postedKey) || !$postedKey) {
26 return;
27 }
28 $parts = explode(':', wp_unslash($cookie), 2);
29 if (count($parts) !== 2 || !hash_equals($parts[1], wp_unslash($postedKey))) {
30 return;
31 }
32 // Check the emailed key while it is still valid, not merely the reset hook.
33 $validated = check_password_reset_key($parts[1], $parts[0]);
34 if (is_wp_error($validated) || (int) $validated->ID !== (int) $user->ID) {
35 return;
36 }
37 self::$passwordResetProof[$user->ID] = [
38 'email' => $validated->user_email,
39 'password_hash' => $validated->user_pass,
40 ];
41 }
42
43 public static function verifyAfterPasswordReset($user): void
44 {
45 $proof = self::$passwordResetProof[$user->ID] ?? null;
46 unset(self::$passwordResetProof[$user->ID]);
47 if (!$proof) {
48 return;
49 }
50 clean_user_cache($user->ID);
51 $current = get_userdata($user->ID);
52 if (!$current || !static::isSame($proof['email'], $current->user_email)
53 || hash_equals($proof['password_hash'], $current->user_pass)) {
54 return;
55 }
56 EmailClaimService::confirmPasswordReset((int) $user->ID, $proof['email']);
57 }
58
59 public static function markPending(int $userId, string $email): void
60 {
61 update_user_meta($userId, static::META_KEY, [
62 'email' => static::normalize($email),
63 'verified' => false,
64 ]);
65 // Even changing away and back must invalidate the previous link.
66 delete_user_meta($userId, static::PENDING_CLAIM_META_KEY);
67 delete_user_meta($userId, CustomerRecoveryService::META_KEY);
68 CustomerResource::resetCurrentCustomerRuntimeCache();
69 }
70
71 public static function markVerified(int $userId, string $email): void
72 {
73 update_user_meta($userId, static::META_KEY, [
74 'email' => static::normalize($email),
75 'verified' => true,
76 ]);
77 CustomerResource::resetCurrentCustomerRuntimeCache();
78 }
79
80 public static function isEnabled(): bool
81 {
82 return (new StoreSettings())->get('require_customer_email_verification', 'no') !== 'no';
83 }
84
85 public static function isRequired(int $userId): bool
86 {
87 $user = $userId ? get_userdata($userId) : false;
88 if (!$user) {
89 return true;
90 }
91
92 if (!static::isEnabled()) {
93 return false;
94 }
95
96 // Registration and email changes always record state, so an account
97 // without it predates email verification and keeps its existing access.
98 if (!metadata_exists('user', $userId, static::META_KEY)) {
99 if (apply_filters('fluent_cart/customer/trust_legacy_accounts', true, ['user' => $user])) {
100 return false;
101 }
102 }
103
104 $state = get_user_meta($userId, static::META_KEY, true);
105 if (!is_array($state) || Arr::get($state, 'verified') !== true || !static::isSame(Arr::get($state, 'email', ''), $user->user_email)) {
106 return true;
107 }
108
109 // Check live customer data too: direct edits may bypass WordPress hooks.
110 $customer = Customer::query()->where('user_id', $userId)->orderBy('id', 'ASC')->first();
111 return $customer && !static::isSame($customer->email, $user->user_email);
112 }
113
114 public static function normalize($email): string
115 {
116 return strtolower(trim((string) $email));
117 }
118
119 public static function isSame($first, $second): bool
120 {
121 return static::normalize($first) === static::normalize($second);
122 }
123 }
124