You are here

class CombinationOffer in Commerce Core 8.2

Provides the 'combination_offer' offer plugin.

This provides support for combining/stacking multiple promotion offers.

Plugin annotation


@CommercePromotionOffer(
  id = "combination_offer",
  label = @Translation("Combination offer"),
  entity_type = "commerce_order",
)

Hierarchy

Expanded class hierarchy of CombinationOffer

1 file declares its use of CombinationOffer
PromotionTest.php in modules/promotion/tests/src/FunctionalJavascript/PromotionTest.php

File

modules/promotion/src/Plugin/Commerce/PromotionOffer/CombinationOffer.php, line 26

Namespace

Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer
View source
class CombinationOffer extends OrderPromotionOfferBase implements CombinationOfferInterface {

  /**
   * The inline form manager.
   *
   * @var \Drupal\commerce\InlineFormManager
   */
  protected $inlineFormManager;

  /**
   * The promotion offer manager.
   *
   * @var \Drupal\commerce_promotion\PromotionOfferManager
   */
  protected $offerManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->inlineFormManager = $container
      ->get('plugin.manager.commerce_inline_form');
    $instance->offerManager = $container
      ->get('plugin.manager.commerce_promotion_offer');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'offers' => [],
    ] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form += parent::buildConfigurationForm($form, $form_state);

    // Remove the main fieldset.
    $form['#type'] = 'container';
    $wrapper_id = Html::getUniqueId('combination-offer-ajax-wrapper');
    $form['#prefix'] = '<div id="' . $wrapper_id . '">';
    $form['#suffix'] = '</div>';

    // Remove the combination offer from the allowed plugins.
    $definitions = array_diff_key($this->offerManager
      ->getDefinitions(), [
      $this->pluginId => '',
    ]);
    $plugins = array_map(static function ($definition) {
      return $definition['label'];
    }, $definitions);
    asort($plugins);
    $user_input = (array) NestedArray::getValue($form_state
      ->getUserInput(), $form['#parents']);

    // Initialize the offers form.
    if (!$form_state
      ->get('offers_form_initialized')) {
      $offers = $this->configuration['offers'] ?: [
        NULL,
      ];

      // Initialize the offers with the user input if present.
      if (isset($user_input['offers'])) {
        $offers = $user_input['offers'];
      }
      $form_state
        ->set('offers', $offers);
      $form_state
        ->set('offers_form_initialized', TRUE);
    }
    $class = get_class($this);
    foreach ($form_state
      ->get('offers') as $index => $offer) {

      // Override the offer with the user input if present.
      if (!empty($user_input['offers'][$index])) {
        $offer = $user_input['offers'][$index];
      }
      $offer_form =& $form['offers'][$index];
      $offer_form['configuration'] = [
        '#type' => 'fieldset',
        '#title' => $this
          ->t('Offer #@index', [
          '@index' => $index + 1,
        ]),
        '#tree' => FALSE,
      ];
      $offer_form['configuration']['target_plugin_id'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Type'),
        '#options' => $plugins,
        '#default_value' => $offer['target_plugin_id'] ?? '',
        // Force the user to select a value.
        '#empty_value' => '',
        // Only require at least an offer.
        '#required' => $index === 0,
        '#ajax' => [
          'callback' => [
            $class,
            'ajaxCallback',
          ],
          'wrapper' => $wrapper_id,
        ],
        '#parents' => array_merge($form['#parents'], [
          'offers',
          $index,
          'target_plugin_id',
        ]),
      ];

      // When a target plugin ID is selected, embed the offer configuration
      // form.
      if (!empty($offer['target_plugin_id'])) {
        $inline_form = $this->inlineFormManager
          ->createInstance('plugin_configuration', [
          'plugin_type' => 'commerce_promotion_offer',
          'plugin_id' => $offer['target_plugin_id'],
          'plugin_configuration' => $offer['target_plugin_configuration'] ?? [],
        ]);
        $offer_form['configuration']['target_plugin_configuration'] = [
          '#inline_form' => $inline_form,
          '#parents' => array_merge($form['#parents'], [
            'offers',
            $index,
            'target_plugin_configuration',
          ]),
        ];
        $offer_form['configuration']['target_plugin_configuration'] = $inline_form
          ->buildInlineForm($offer_form['configuration']['target_plugin_configuration'], $form_state);
      }

      // Don't allow removing the first offer.
      if ($index > 0) {
        $offer_form['configuration']['remove'] = [
          '#type' => 'submit',
          '#name' => 'remove_offer' . $index,
          '#value' => $this
            ->t('Remove'),
          '#limit_validation_errors' => [],
          '#submit' => [
            [
              $class,
              'removeOfferSubmit',
            ],
          ],
          '#offer_index' => $index,
          '#parents' => array_merge($form['#parents'], [
            'offers',
            $index,
            'remove',
          ]),
          '#ajax' => [
            'callback' => [
              $class,
              'ajaxCallback',
            ],
            'wrapper' => $wrapper_id,
          ],
        ];
      }
    }
    $form['offers'][] = [
      'add_offer' => [
        '#type' => 'submit',
        '#value' => $this
          ->t('Add another offer'),
        '#submit' => [
          [
            $class,
            'addOfferSubmit',
          ],
        ],
        '#limit_validation_errors' => [
          array_merge($form['#parents'], [
            'offers',
          ]),
        ],
        '#ajax' => [
          'callback' => [
            $class,
            'ajaxCallback',
          ],
          'wrapper' => $wrapper_id,
        ],
      ],
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state
      ->getValue($form['#parents']);

    // Filter out the button rows.
    $values['offers'] = array_filter($values['offers'], function ($offer) {
      return !empty($offer['target_plugin_id']) && !empty($offer['target_plugin_configuration']) && !isset($offer['add_offer']);
    });
    $form_state
      ->setValue($form['#parents'], $values);
    if (empty($values['offers'])) {
      $form_state
        ->setError($form['offers'], $this
        ->t('Please configure at least one offer.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    parent::submitConfigurationForm($form, $form_state);
    if (!$form_state
      ->getErrors()) {
      $this->configuration['offers'] = [];
      $values = $form_state
        ->getValue($form['#parents']);
      foreach (array_filter($values['offers']) as $offer) {
        $this->configuration['offers'][] = $offer;
      }
    }
  }

  /**
   * Ajax callback.
   */
  public static function ajaxCallback(array $form, FormStateInterface $form_state) {
    $triggering_element = $form_state
      ->getTriggeringElement();
    $parents = $triggering_element['#array_parents'];
    $form_index = array_search('form', $parents, TRUE);
    $parents = array_splice($parents, 0, $form_index + 1);
    return NestedArray::getValue($form, $parents);
  }

  /**
   * Submit callback for adding a new offer.
   */
  public static function addOfferSubmit(array $form, FormStateInterface $form_state) {
    $offers = $form_state
      ->get('offers');
    $offers[] = [];
    $form_state
      ->set('offers', $offers);
    $form_state
      ->setRebuild();
  }

  /**
   * Submit callback for removing an offer.
   */
  public static function removeOfferSubmit(array $form, FormStateInterface $form_state) {
    $offers = $form_state
      ->get('offers');
    $index = $form_state
      ->getTriggeringElement()['#offer_index'];
    unset($offers[$index]);
    $form_state
      ->set('offers', $offers);
    $form_state
      ->setRebuild();
  }

  /**
   * {@inheritdoc}
   */
  public function apply(EntityInterface $entity, PromotionInterface $promotion) {
    assert($entity instanceof OrderInterface);

    // This is copied from Promotion::apply().
    foreach ($this
      ->getOffers() as $offer) {
      if ($offer instanceof OrderItemPromotionOfferInterface) {
        $offer_conditions = new ConditionGroup($offer
          ->getConditions(), $offer
          ->getConditionOperator());

        // Apply the offer to order items that pass the conditions.
        foreach ($entity
          ->getItems() as $order_item) {

          // Skip order items with a null unit price or with a quantity = 0.
          if (!$order_item
            ->getUnitPrice() || Calculator::compare($order_item
            ->getQuantity(), '0') === 0) {
            continue;
          }
          if ($offer_conditions
            ->evaluate($order_item)) {
            $offer
              ->apply($order_item, $promotion);
          }
        }
      }
      else {
        $offer
          ->apply($entity, $promotion);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getOffers() {
    $offers = [];
    foreach ($this->configuration['offers'] as $offer) {
      $offers[] = $this->offerManager
        ->createInstance($offer['target_plugin_id'], $offer['target_plugin_configuration']);
    }
    return $offers;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CombinationOffer::$inlineFormManager protected property The inline form manager.
CombinationOffer::$offerManager protected property The promotion offer manager.
CombinationOffer::addOfferSubmit public static function Submit callback for adding a new offer.
CombinationOffer::ajaxCallback public static function Ajax callback.
CombinationOffer::apply public function Applies the offer to the given entity. Overrides PromotionOfferInterface::apply
CombinationOffer::buildConfigurationForm public function Form constructor. Overrides PromotionOfferBase::buildConfigurationForm
CombinationOffer::create public static function Creates an instance of the plugin. Overrides OrderPromotionOfferBase::create
CombinationOffer::defaultConfiguration public function Gets default configuration for this plugin. Overrides PromotionOfferBase::defaultConfiguration
CombinationOffer::getOffers public function Gets the offers configured. Overrides CombinationOfferInterface::getOffers
CombinationOffer::removeOfferSubmit public static function Submit callback for removing an offer.
CombinationOffer::submitConfigurationForm public function Form submission handler. Overrides PromotionOfferBase::submitConfigurationForm
CombinationOffer::validateConfigurationForm public function Form validation handler. Overrides PromotionOfferBase::validateConfigurationForm
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
OrderPromotionOfferBase::$splitter protected property The price splitter.
OrderPromotionOfferBase::__construct public function Constructs a new OrderPromotionOfferBase object. Overrides PromotionOfferBase::__construct 1
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PromotionOfferBase::$rounder protected property The rounder.
PromotionOfferBase::assertEntity protected function Asserts that the given entity is of the expected type.
PromotionOfferBase::clear public function Allows an offer to clean up any modifications done to the given entity. Overrides PromotionOfferInterface::clear 1
PromotionOfferBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
PromotionOfferBase::getEntityTypeId public function Gets the offer entity type ID. Overrides PromotionOfferInterface::getEntityTypeId
PromotionOfferBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.