| @@ -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 | } |
| @@ -140,11 +161,10 @@ | ||
| 140 | 161 | * |
| 141 | 162 | */ |
| 142 | 163 | public static function get(array $params = []) |
| 143 | 164 | { |
| 144 | - static $cart; | |
| 145 | - if (isset($cart)) { | |
| 146 | - return $cart; | |
| 165 | + if (static::$cartCache !== false && static::$cartCache !== null) { | |
| 166 | + return static::$cartCache; | |
| 147 | 167 | } |
| 148 | 168 | |
| 149 | 169 | $autoCreate = Arr::get($params, 'create', false); |
| 150 | 170 | |
| @@ -159,9 +179,9 @@ | ||
| 159 | 179 | ->where('cart_group', 'instant'); |
| 160 | 180 | |
| 161 | 181 | $tempCart = $cartQuery->first(); |
| 162 | 182 | |
| 163 | - $cart = $tempCart; | |
| 183 | + static::$cartCache = $tempCart; | |
| 164 | 184 | |
| 165 | 185 | if (!$autoCreate) { |
| 166 | 186 | return $tempCart; |
| 167 | 187 | } |
| @@ -166,11 +186,11 @@ | ||
| 166 | 186 | return $tempCart; |
| 167 | 187 | } |
| 168 | 188 | } |
| 169 | 189 | |
| 170 | - $cart = static::getOrSetCartForThisDevice($autoCreate); | |
| 190 | + static::$cartCache = static::getOrSetCartForThisDevice($autoCreate); | |
| 171 | 191 | |
| 172 | - return $cart; | |
| 192 | + return static::$cartCache; | |
| 173 | 193 | } |
| 174 | 194 | |
| 175 | 195 | public static function find($id, $params = []) |
| 176 | 196 | { |
| @@ -331,9 +351,21 @@ | ||
| 331 | 351 | ]); |
| 332 | 352 | } |
| 333 | 353 | |
| 334 | 354 | if (!$variation) { |
| 335 | - 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 | + ); | |
| 336 | 368 | } |
| 337 | 369 | |
| 338 | 370 | $soldIndividually = $isCustom |
| 339 | 371 | ? !empty($variation->sold_individually) |
| @@ -372,9 +404,13 @@ | ||
| 372 | 404 | } |
| 373 | 405 | |
| 374 | 406 | $utmData = static::prepareUtmData($data); |
| 375 | 407 | if ($utmData) { |
| 376 | - $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; | |
| 377 | 413 | $cart->save(); |
| 378 | 414 | } |
| 379 | 415 | |
| 380 | 416 | return $cart; |