PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
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 1.3.19 trunk All 48 releases
← All changes | api/Resource/CustomerResource.php +55 -34 1.3.25 → 1.6.5 View file →
@@ -12,9 +12,26 @@
12 12 use FluentCart\Framework\Support\Arr;
13 13
14 14 class CustomerResource extends BaseResourceApi
15 15 {
16 + /**
17 + * Per-request memo for getCurrentCustomer(). In production every HTTP
18 + * request runs in a fresh PHP process, so this lives exactly one
19 + * request. Long-running processes that simulate multiple requests
20 + * (test suites, CLI) must clear it between simulated requests via
21 + * resetCurrentCustomerRuntimeCache() — as a function-static it was
22 + * unreachable and leaked the first request's customer into every
23 + * subsequent one.
24 + *
25 + * @var object|null
26 + */
27 + private static $currentCustomerRuntimeCache = null;
16 28
29 + public static function resetCurrentCustomerRuntimeCache(): void
30 + {
31 + static::$currentCustomerRuntimeCache = null;
32 + }
33 +
17 34 public static function getQuery(): Builder
18 35 {
19 36 return Customer::query();
20 37 }
@@ -118,13 +135,17 @@
118 135 $email = Arr::get($data, 'email');
119 136 $data = static::resolveCustomerName($data);
120 137
121 138 $data['purchase_value'] = [];
122 - $customer = static::getQuery()->firstOrCreate(
123 - ['email' => $email],
124 - $data
125 - );
126 139
140 + // Preserve an established account link; otherwise reuse the email row
141 + // without linking it. Only the verification flow can claim guest history.
142 + $ownerId = (int) Arr::get($data, 'user_id');
143 + $customer = $ownerId ? static::getQuery()->where('user_id', $ownerId)->orderBy('id')->first() : null;
144 + if (!$customer) {
145 + $customer = static::getQuery()->firstOrCreate(['email' => $email], $data);
146 + }
147 +
127 148 if (empty($customer)) {
128 149 return static::makeErrorResponse([
129 150 ['code' => 400, 'message' => __('Customer creation failed.', 'fluent-cart')]
130 151 ]);
@@ -129,13 +150,22 @@
129 150 ['code' => 400, 'message' => __('Customer creation failed.', 'fluent-cart')]
130 151 ]);
131 152 }
132 153
133 - $isUserAttached = false;
134 - $user = get_user_by('email', $email);
135 - if ($user) {
136 - $customer->update(['user_id' => $user->ID]);
137 - $isUserAttached = true;
154 + // Linking happens only where identity is established. A row that
155 + // already existed is never claimed here: firstOrCreate() may have found
156 + // somebody else's record by its address. A fresh row is linked to the
157 + // account holding its email only for an actor with authority over that
158 + // account (an admin screen, the MCP tools) or when the caller supplied
159 + // the user_id it established itself (a signed-in checkout, the User
160 + // API). An anonymous caller — a guest at checkout — links nothing.
161 + $isUserAttached = (bool) $customer->user_id;
162 + if ($customer->wasRecentlyCreated && !$isUserAttached) {
163 + $user = get_user_by('email', $email);
164 + if ($user && get_current_user_id() && current_user_can('edit_user', $user->ID)) {
165 + $customer->update(['user_id' => $user->ID]);
166 + $isUserAttached = true;
167 + }
138 168 }
139 169
140 170 if (Arr::get($data, 'wp_user') === 'yes' && !$isUserAttached) {
141 171 $isUserCreated = \FluentCart\App\Services\AuthService::createUserFromCustomer($customer);
@@ -286,19 +316,19 @@
286 316 }
287 317
288 318 return static::makeErrorResponse([
289 319 ['code' => 400, 'message' => __('Customer update failed.', 'fluent-cart')]
290 - ]);
320 + ], 400);
291 321 }
292 322
293 323 return static::makeErrorResponse([
294 324 ['code' => 400, 'message' => __('Customer does not have any changes to update.', 'fluent-cart')]
295 - ]);
325 + ], 400);
296 326 }
297 327
298 328 return static::makeErrorResponse([
299 329 ['code' => 404, 'message' => __('Customer not found, please reload the page and try again!', 'fluent-cart')]
300 - ]);
330 + ], 404);
301 331 }
302 332
303 333 /**
304 334 * Update the status of multiple customers with the given parameters.
@@ -383,12 +413,10 @@
383 413 }
384 414
385 415 public static function getCurrentCustomer(bool $createIfNotExists = false): ?object
386 416 {
387 - static $cachedCustomer = null;
388 -
389 - if ($cachedCustomer !== null) {
390 - return $cachedCustomer;
417 + if (static::$currentCustomerRuntimeCache !== null) {
418 + return static::$currentCustomerRuntimeCache;
391 419 }
392 420
393 421 if (!is_user_logged_in()) {
394 422 return null;
@@ -395,29 +423,22 @@
395 423 }
396 424
397 425 $currentUser = get_user_by('ID', get_current_user_id());
398 426
399 - // Try to get the existing customer
400 - $query = Customer::query()->where('user_id', $currentUser->ID)
401 - ->orWhere('email', $currentUser->user_email)
402 - ->with(['billing_address', 'shipping_address']);
427 + // Reading the current customer must not claim a record by email.
428 + $existingCustomer = Customer::query()->where('user_id', $currentUser->ID)
429 + ->orderBy('id', 'ASC')
430 + ->with(['billing_address', 'shipping_address'])
431 + ->first();
403 432
404 -
405 - $existingCustomer = $query->first();
406 -
407 - // Return if found
408 433 if ($existingCustomer) {
409 - if ($existingCustomer->user_id != $currentUser->ID) {
410 - // Update the user_id if it doesn't match
411 - $existingCustomer->user_id = $currentUser->ID;
412 - $existingCustomer->save();
413 - }
414 -
415 - $cachedCustomer = $existingCustomer;
434 + static::$currentCustomerRuntimeCache = $existingCustomer;
416 435 return $existingCustomer;
417 436 }
418 437
419 - if (!$createIfNotExists) {
438 + if (!$createIfNotExists || Customer::query()->where('email', $currentUser->user_email)->exists()) {
439 + // Do not create a duplicate or expose an unclaimed customer to a getter.
440 + // Email confirmation links the existing row before dashboard access.
420 441 return null;
421 442 }
422 443
423 444 $userId = $currentUser->ID;
@@ -435,14 +456,14 @@
435 456 'postcode' => Arr::get($appRequestData, 'postcode', ''),
436 457 ]);
437 458
438 459 // get customer by id
439 - $cachedCustomer = static::getQuery()
460 + static::$currentCustomerRuntimeCache = static::getQuery()
440 461 ->where('id', $customer->id)
441 462 ->with(['billing_address', 'shipping_address'])
442 463 ->first();
443 464
444 - return $cachedCustomer;
465 + return static::$currentCustomerRuntimeCache;
445 466
446 467 }
447 468
448 469 private static function resolveCustomerName(array $data): array