diff --git a/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php b/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php index 666a64cc..a1069b45 100644 --- a/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php +++ b/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php @@ -103,6 +103,7 @@ class BuyXGetY extends OrderPromotionOfferBase { 'buy_conditions' => [], 'get_quantity' => 1, 'get_conditions' => [], + 'get_auto_add' => FALSE, 'offer_type' => 'percentage', 'offer_percentage' => '0', 'offer_amount' => NULL, @@ -154,6 +155,15 @@ class BuyXGetY extends OrderPromotionOfferBase { '#entity_types' => ['commerce_order_item'], '#default_value' => $this->configuration['get_conditions'], ]; + $form['get']['auto_add'] = [ + '#type' => 'checkbox', + '#title' => $this->t("Automatically add the offer product to the cart if it isn't in it already"), + '#description' => $this->t('This behavior will only work when a single product variation (or a single product with only one variation) is specified.'), + '#default_value' => $this->configuration['get_auto_add'], + '#states' => [ + 'visible' => [$this->getAutoAddStatesVisibility()], + ], + ]; $parents = array_merge($form['#parents'], ['offer', 'type']); $selected_offer_type = NestedArray::getValue($form_state->getUserInput(), $parents); @@ -224,6 +234,27 @@ class BuyXGetY extends OrderPromotionOfferBase { if ($values['offer']['type'] == 'percentage' && empty($values['offer']['percentage'])) { $form_state->setError($form, $this->t('Percentage must be a positive number.')); } + + if ($values['get']['auto_add']) { + // Ensure that at least one compatible condition enabled. + $valid_condition_ids = array_keys(array_filter($this->conditionManager->getFilteredDefinitions('commerce_promotion', ['commerce_order_item']), function ($definition) { + return is_subclass_of($definition['class'], PurchasableEntityConditionInterface::class); + })); + $has_valid_condition = FALSE; + foreach ($values['get']['conditions']['products'] as $condition_id => $condition_values) { + if (isset($valid_condition_ids[$condition_id]) && $condition_values['enable'] == 1) { + $has_valid_condition = TRUE; + } + } + if (!$has_valid_condition) { + $form_state->setError($form['get'], $this->t('The "auto-add" offer can only be enabled when a compatible "get" condition is used.')); + } + + // Ensure that the offer is a 100% discount. + if ($values['offer']['type'] == 'fixed_amount' || ($values['offer']['type'] == 'percentage' && $values['offer']['percentage'] != '100')) { + $form_state->setError($form['offer'], $this->t('The "auto-add" offer can only be enabled for products that discounted 100%.')); + } + } } /** @@ -238,6 +269,7 @@ class BuyXGetY extends OrderPromotionOfferBase { $this->configuration['buy_conditions'] = $values['buy']['conditions']; $this->configuration['get_quantity'] = $values['get']['quantity']; $this->configuration['get_conditions'] = $values['get']['conditions']; + $this->configuration['get_auto_add'] = $values['get']['auto_add']; $this->configuration['offer_type'] = $values['offer']['type']; if ($this->configuration['offer_type'] == 'percentage') { $this->configuration['offer_percentage'] = Calculator::divide((string) $values['offer']['percentage'], '100'); @@ -269,22 +301,18 @@ class BuyXGetY extends OrderPromotionOfferBase { } $get_conditions = $this->buildConditionGroup($this->configuration['get_conditions']); - // @todo Make this behavior optional based on an additional promotion - // setting. - if ($get_purchasable_entity = $this->findSinglePurchasableEntity($get_conditions)) { + if ($this->configuration['get_auto_add'] && ($get_purchasable_entity = $this->findSinglePurchasableEntity($get_conditions))) { $order_item = $this->findOrCreateOrderItem($get_purchasable_entity, $order_items); $expected_get_quantity = $this->calculateExpectedGetQuantity($buy_quantities, $order_item); - // If the expected get quantity is non-zero, we need to either find an - // existing order item and update its quantity if needed, or create a new - // order item for $get_purchasable_entity. + // If the expected get quantity is non-zero, we need to update the + // quantity of the 'get' order item accordingly. if (Calculator::compare($expected_get_quantity, '0') !== 0) { if (Calculator::compare($order_item->getQuantity(), $expected_get_quantity) === -1) { $order_item->setQuantity($expected_get_quantity); } if ($order_item->isNew()) { - $order_item->set('order_id', $order->id()); $order_item->save(); $order->addItem($order_item); $order_items = $order->getItems(); @@ -423,12 +451,12 @@ class BuyXGetY extends OrderPromotionOfferBase { $purchasable_entity_ids = $condition->getPurchasableEntityIds(); if (count($purchasable_entity_ids) === 1) { $purchasable_entities = $condition->getPurchasableEntities(); - continue; + return reset($purchasable_entities); } } } - return !empty($purchasable_entities) ? reset($purchasable_entities) : NULL; + return NULL; } /** @@ -505,6 +533,40 @@ class BuyXGetY extends OrderPromotionOfferBase { return $expected_get_quantity; } + /** + * Gets the #states visibility array for the 'auto_add' form element. + * + * @return array + * An array of visibility options for a form element's #state property. + */ + protected function getAutoAddStatesVisibility() { + // The 'auto_add' form element has to be shown if _any_ condition that + // provides a purchasable entity is enabled for the 'get' conditions. This + // means we need to construct a list of OR statements for #states, which + // looks like this: + // '#states' => [ + // 'visible' => [ + // [':input[name="some_element"]' => ['checked' => TRUE]], + // 'or', + // [':input[name="another_element"]' => ['checked' => TRUE]], + // ... + // ], + // ], + $conditions = array_filter($this->conditionManager->getFilteredDefinitions('commerce_promotion', ['commerce_order_item']), function ($definition) { + return is_subclass_of($definition['class'], PurchasableEntityConditionInterface::class); + }); + + $states_visibility = array_map(function ($value) { + return [':input[name="offer[0][target_plugin_configuration][order_buy_x_get_y][get][conditions][products][' . $value . '][enable]"]' => ['checked' => TRUE]]; + }, array_keys($conditions)); + + for ($i = 0; $i < count($conditions) - 1; $i++) { + array_splice($states_visibility, $i + 1, 0, 'or'); + } + + return $states_visibility; + } + /** * Takes a slice from the given quantity list. *