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/FrontendResource/CartResource.php +71 -11 1.3.27 → 1.6.5 View file →
@@ -19,8 +19,29 @@
19 19
20 20 class CartResource extends BaseResourceApi
21 21 {
22 22
23 + /**
24 + * Per-request memo for get(). In production every HTTP request runs in a
25 + * fresh PHP process, so this lives exactly one request. Long-running
26 + * processes that simulate multiple requests (test suites, CLI) must clear
27 + * it between simulated requests via resetCartCache() — as a
28 + * function-static it was unreachable and leaked the first request's cart
29 + * into every subsequent one.
30 + *
31 + * Only a resolved Cart is memoized; a null ("no cart") result is
32 + * deliberately re-queried on the next call — matching the original
33 + * function-static behavior, where isset(null) === false.
34 + *
35 + * @var Cart|null|false false = not resolved yet
36 + */
37 + private static $cartCache = false;
38 +
39 + public static function resetCartCache(): void
40 + {
41 + static::$cartCache = false;
42 + }
43 +
23 44 public static function getQuery(): Builder
24 45 {
25 46 return Cart::query();
26 47 }
@@ -35,11 +56,15 @@
35 56 $variation = Arr::get($params, 'variation');
36 57 $variation = CartHelper::normalizeCustomFields($variation);
37 58 }
38 59 else {
60 + // product_detail must be eager-loaded — generateCartItemFromVariation
61 + // reads $variation['product_detail']['variation_type'] to stamp
62 + // the cart item's variation_type. Without it the field is null on
63 + // instant-checkout carts and CartRenderer can't tell whether to
64 + // hide the variant-title line for simple products.
39 65 $variation = ProductVariation::query()
40 - ->with(['product'])
41 - ->with(['media', 'shippingClass'])
66 + ->with(['product', 'product_detail', 'media', 'shippingClass'])
42 67 ->where('id', $variationId)->first();
43 68
44 69 $variation = apply_filters('fluent_cart/cart/item_modify', $variation, [
45 70 'item_id' => $variationId,
@@ -86,11 +111,28 @@
86 111 if(!$isCustom) {
87 112 $cart = CartHelper::generateCartFromVariation($variation, $quantity);
88 113 }
89 114 else {
90 - // TODO: Legacy object-to-array conversion. Kept for backward compatibility.
115 + // Legacy object-to-array conversion. Kept for backward compatibility.
91 116 $cart = CartHelper::generateCartFromCustomVariation(json_decode(json_encode($variation), true), $quantity);
92 117 }
118 + } else {
119 + // Refresh cart_data from the current variation on every instant hit.
120 + // The inputs (variationId + quantity) are deterministic URL params,
121 + // so regenerating is idempotent — and it picks up any fields that
122 + // were missing on a previously-created draft (variation_type for
123 + // advanced-variation rows, refreshed pricing, updated featured
124 + // media). Without this, a stale draft from before a code change
125 + // keeps rendering with its old shape.
126 + if (!$isCustom) {
127 + $cart->cart_data = [
128 + CartHelper::generateCartItemFromVariation($variation, $quantity)
129 + ];
130 + } else {
131 + $cart->cart_data = [
132 + CartHelper::generateCartItemCustomItem(json_decode(json_encode($variation), true), $quantity)
133 + ];
134 + }
93 135 }
94 136
95 137 if (is_user_logged_in()) {
96 138 $cart->user_id = get_current_user_id();
@@ -119,11 +161,10 @@
119 161 *
120 162 */
121 163 public static function get(array $params = [])
122 164 {
123 - static $cart;
124 - if (isset($cart)) {
125 - return $cart;
165 + if (static::$cartCache !== false && static::$cartCache !== null) {
166 + return static::$cartCache;
126 167 }
127 168
128 169 $autoCreate = Arr::get($params, 'create', false);
129 170
@@ -138,9 +179,9 @@
138 179 ->where('cart_group', 'instant');
139 180
140 181 $tempCart = $cartQuery->first();
141 182
142 - $cart = $tempCart;
183 + static::$cartCache = $tempCart;
143 184
144 185 if (!$autoCreate) {
145 186 return $tempCart;
146 187 }
@@ -145,11 +186,11 @@
145 186 return $tempCart;
146 187 }
147 188 }
148 189
149 - $cart = static::getOrSetCartForThisDevice($autoCreate);
190 + static::$cartCache = static::getOrSetCartForThisDevice($autoCreate);
150 191
151 - return $cart;
192 + return static::$cartCache;
152 193 }
153 194
154 195 public static function find($id, $params = [])
155 196 {
@@ -310,9 +351,21 @@
310 351 ]);
311 352 }
312 353
313 354 if (!$variation) {
314 - return $cart->removeItem($itemId);
355 + // An item already in the cart whose variation row has since
356 + // disappeared (product/variation deleted) is dropped gracefully.
357 + // An id that was never in the cart and resolves to nothing is a
358 + // client error — silently answering "Cart updated successfully"
359 + // hid typos and probing as a 200 no-op.
360 + if ($existingItem !== null) {
361 + return $cart->removeItem($itemId);
362 + }
363 +
364 + return new WP_Error(
365 + 'invalid_item',
366 + __('Invalid item.', 'fluent-cart')
367 + );
315 368 }
316 369
317 370 $soldIndividually = $isCustom
318 371 ? !empty($variation->sold_individually)
@@ -351,9 +404,13 @@
351 404 }
352 405
353 406 $utmData = static::prepareUtmData($data);
354 407 if ($utmData) {
355 - $cart->utm_data = array_merge(is_array($cart->utm_data) ? $cart->utm_data : [], $utmData);
408 + // Replaced, not merged. A cart row is reused across visits, so merging
409 + // key by key accumulated a union of every touch that ever reached it and
410 + // the column stopped describing any single one. The browser has already
411 + // resolved which touch this is, so its block is the answer.
412 + $cart->utm_data = $utmData;
356 413 $cart->save();
357 414 }
358 415
359 416 return $cart;
@@ -721,12 +778,15 @@
721 778 }
722 779
723 780 $userId = get_current_user_id();
724 781 if ($userId) {
782 + // Latest cart first — without an order, first() picks by primary key
783 + // (cart_hash), which resurrects an arbitrary old cart for the user.
725 784 $cart = static::getQuery()
726 785 ->where('user_id', $userId)
727 786 ->where('stage', '!=', 'completed')
728 787 ->where('cart_group', 'global')
788 + ->orderBy('updated_at', 'DESC')
729 789 ->first();
730 790
731 791 if ($cart) {
732 792 return $cart;