PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.8.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.8.0
1.9.0 1.8.0 1.7.1 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 All 53 releases
fluent-cart / app / Hooks / Handlers / UserHandler.php

UserHandler.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.8.0, at app/Hooks/Handlers/UserHandler.php

157 lines 5.7 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\Hooks\Handlers;
4
5 use FluentCart\App\Helpers\Status;
6 use FluentCart\App\Services\CustomerIdentity\EmailClaimService;
7 use FluentCart\App\Services\CustomerIdentity\EmailVerificationService;
8 use FluentCart\App\Models\Customer;
9 use FluentCart\Framework\Support\Arr;
10
11 class UserHandler
12 {
13 public function register()
14 {
15 add_action('delete_user', [$this, 'userDeleteHandler'], 10, 1);
16 add_action('user_register', [$this, 'userRegistrationHandler'], 10, 1);
17 add_action('password_reset', [EmailVerificationService::class, 'capturePasswordResetProof'], 10, 1);
18 add_action('after_password_reset', [EmailVerificationService::class, 'verifyAfterPasswordReset'], 10, 1);
19
20 // Let's handle auto user registration!
21 add_action('fluent_cart/cart_completed', [$this, 'maybeCreateUser'], 10, 1);
22
23 add_action('profile_update', [$this, 'handleWpUserProfileUpdated'], 10, 3);
24
25 }
26
27 public function handleWpUserProfileUpdated($userId, $oldData, $newData = [])
28 {
29 $user = $userId ? get_userdata($userId) : false;
30 $oldEmail = is_object($oldData) && isset($oldData->user_email) ? wp_unslash($oldData->user_email) : '';
31 if (!$user || EmailVerificationService::isSame($oldEmail, $user->user_email)) {
32 return;
33 }
34
35 // Staff authority, or WordPress's own confirmation link, already proves the
36 // change, so the contact email follows the account without a second link.
37 if ($this->isStaffEmailChange((int) $userId) || $this->isWpConfirmedEmailChange((int) $userId, $user->user_email)) {
38 $status = EmailClaimService::confirmAdminChange((int) $userId, $user->user_email);
39 if (in_array($status, ['confirmed', 'recovering', 'incomplete'], true)) {
40 return;
41 }
42 // Fell through on a conflict or a storage failure: the gate still applies.
43 }
44
45 // State is recorded only while the store requires verification; accounts
46 // without it are treated as existing accounts if verification is enabled later.
47 if (!EmailVerificationService::isEnabled()) {
48 return;
49 }
50
51 // Account changes do not prove inbox ownership or change customer contact details.
52 EmailVerificationService::markPending((int) $userId, $user->user_email);
53 }
54
55 /**
56 * True when somebody who can edit this account, and is not the account holder,
57 * made the change. A self-service edit still has to prove the new inbox.
58 */
59 private function isStaffEmailChange(int $userId): bool
60 {
61 $actorId = get_current_user_id();
62 $isStaff = $actorId && $actorId !== $userId && current_user_can('edit_user', $userId);
63
64 /*
65 * Whether this email change is trusted enough to move the customer's
66 * contact email without a confirmation link. Lets a store extend the
67 * trust to its own tooling — WP-CLI and cron run with no current user.
68 *
69 * @param bool $isStaff
70 * @param array $context ['user_id' => int, 'actor_id' => int]
71 */
72 return (bool) apply_filters('fluent_cart/customer/trust_staff_email_change', $isStaff, [
73 'user_id' => $userId,
74 'actor_id' => (int) $actorId,
75 ]);
76 }
77
78 /**
79 * True while profile.php applies the account holder's clicked newuseremail
80 * link, which WordPress only sends to the new inbox.
81 */
82 private function isWpConfirmedEmailChange(int $userId, string $email): bool
83 {
84 $hash = isset($_GET['newuseremail']) ? sanitize_text_field(wp_unslash($_GET['newuseremail'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the hash is the proof
85 if (!$hash || get_current_user_id() !== $userId) {
86 return false;
87 }
88
89 $pending = get_user_meta($userId, '_new_email', true);
90
91 return is_array($pending)
92 && !empty($pending['hash'])
93 && hash_equals((string) $pending['hash'], $hash)
94 && EmailVerificationService::isSame((string) Arr::get($pending, 'newemail', ''), $email);
95 }
96
97 public function maybeCreateUser($data)
98 {
99 $cart = Arr::get($data, 'cart');
100 $order = Arr::get($data, 'order');
101 $customer = $order->customer;
102
103 if ($customer->getWpUserId()) {
104 return; // User already exists
105 }
106
107 $willCreateUser = $order->type === Status::ORDER_TYPE_SUBSCRIPTION;
108
109 if (!$willCreateUser) {
110 // check if cart has set it
111 $willCreateUser = $cart && Arr::get($cart, '_register_user', false);
112 }
113
114 if (!$willCreateUser) {
115 // get the global settings where we may have auto create user enabled
116 $willCreateUser = ''; // TODO: get the global settings
117 }
118
119 if (!$willCreateUser) {
120 return; // No need to create user
121 }
122
123 // create the user from the customer data
124
125
126 }
127
128 /**
129 * @return void
130 */
131 public function userDeleteHandler($userId)
132 {
133 if (!$userId) {
134 return;
135 }
136
137 // Unlink by identity. Matching on the account's email unlinked whichever
138 // customer happened to hold it — possibly another account's — and left
139 // this account's own customers pointing at a dead user id when their
140 // contact address had drifted from the account's.
141 Customer::query()->where('user_id', $userId)->update(['user_id' => null]);
142 }
143
144 public function userRegistrationHandler($userId)
145 {
146 if (!EmailVerificationService::isEnabled()) {
147 return;
148 }
149
150 $user = $userId ? get_userdata($userId) : false;
151 if ($user) {
152 EmailVerificationService::markPending((int) $userId, $user->user_email);
153 }
154 }
155
156 }
157