| @@ -313,16 +313,24 @@ | ||
| 313 | 313 | * |
| 314 | 314 | * Deliberately distinct from isLocked(), which additionally requires order_id |
| 315 | 315 | * and is therefore false for renewal and early-installment carts, which never |
| 316 | 316 | * set that column. |
| 317 | + * | |
| 318 | + * Filterable so an integration that locks its cart only to pin its own item | |
| 319 | + * (e.g. a booking) can still take order bumps. The filter only decides the | |
| 320 | + * lock; a cart carrying an upgrade is refused after it either way. The upgrade | |
| 321 | + * swap in WebCheckoutHandler::handleOrderBumpRequest() ignores this filter and | |
| 322 | + * keeps refusing any `is_locked` cart. | |
| 317 | 323 | */ |
| 318 | 324 | public function acceptsAdditionalItems() |
| 319 | 325 | { |
| 320 | - if (Arr::get($this->checkout_data, 'is_locked') === 'yes') { | |
| 321 | - return false; | |
| 322 | - } | |
| 326 | + $accepts = (bool) apply_filters( | |
| 327 | + 'fluent_cart/cart/accepts_additional_items', | |
| 328 | + Arr::get($this->checkout_data, 'is_locked') !== 'yes', | |
| 329 | + ['cart' => $this] | |
| 330 | + ); | |
| 323 | 331 | |
| 324 | - return empty(Arr::get($this->checkout_data, 'upgrade_data')); | |
| 332 | + return $accepts && empty(Arr::get($this->checkout_data, 'upgrade_data')); | |
| 325 | 333 | } |
| 326 | 334 | |
| 327 | 335 | public function addItem($item = [], $replacingIndex = null) |
| 328 | 336 | { |
| @@ -409,8 +417,12 @@ | ||
| 409 | 417 | |
| 410 | 418 | if ($quantity == 0) { |
| 411 | 419 | // that means we have to remove it |
| 412 | 420 | return $this->removeItem($variation->id, Arr::get($config, 'remove_args', []), true); |
| 421 | + } | |
| 422 | + | |
| 423 | + if (!$variation->product) { | |
| 424 | + return new \WP_Error('product_not_found', __('This product is no longer available.', 'fluent-cart')); | |
| 413 | 425 | } |
| 414 | 426 | |
| 415 | 427 | $validate = Arr::get($config, 'will_validate', false); |
| 416 | 428 | |