You are here

function commerce_discount_shipping_service in Commerce Discount 7

Rules action: Apply shipping discount.

Parameters

EntityDrupalWrapper $order_wrapper: The wrapped order entity.

string $discount_name: The name of the discount.

2 string references to 'commerce_discount_shipping_service'
commerce_discount_commerce_discount_offer_type_info in ./commerce_discount.module
Implements hook_commerce_discount_offer_type_info().
commerce_discount_rules_action_info in ./commerce_discount.rules.inc
Implements hook_rules_action_info().

File

./commerce_discount.rules.inc, line 1029
Rules integration for the Commerce Discount module.

Code

function commerce_discount_shipping_service($order_wrapper, $discount_name) {
  $order = $order_wrapper
    ->value();

  // Shipping discount rules actions run on two occasions:
  // 1. When selecting the shipping service on checkout. This time
  //   $order->shipping_rates is already set. The cart refresh is triggered
  //   manually in commerce_discount_commerce_shipping_method_collect_rates()
  //   and we don't save the shipping line items, since we create them only to
  //   display the price.
  // 2. On any other cart refresh the $order->shipping_rates is initially
  //   empty so we set it to the shipping line item present on the order (if
  //   any). In this case we want to save the shipping line item.
  // What is common in the two cases is that we calculate the discount only
  // for the shipping services in $order->shipping_rates.
  // If the order hasn't had any shipping rates calculated for it, check if
  // there's already a shipping line item referenced by the order.
  if (empty($order->shipping_rates)) {
    foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
      if ($line_item_wrapper
        ->value() && $line_item_wrapper
        ->getBundle() === 'shipping') {
        $shipping_service = $line_item_wrapper->commerce_shipping_service
          ->value();
        $order->shipping_rates[$shipping_service] = $line_item_wrapper
          ->value();
      }
    }
    if (empty($order->shipping_rates)) {
      return;
    }
  }

  // Load the discount to find the free shipping service and strategy.
  $discount = entity_load_single('commerce_discount', $discount_name);
  $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount);
  $discount_offer_wrapper = $discount_wrapper->commerce_discount_offer;
  $shipping_discount_offer_type = $discount_offer_wrapper
    ->getBundle();
  switch ($shipping_discount_offer_type) {
    case 'percent_off_shipping':
      $shipping_service_to_discount = $discount_offer_wrapper->commerce_percent_off_ship_serv
        ->value();

      // Determine the discount multiplier based on submitted value.
      $discount_value = $discount_offer_wrapper->commerce_percent_off_shipping
        ->value();
      if ($discount_value > 1 && $discount_value <= 100) {
        $discount_multiplier = $discount_value / 100;
      }
      else {
        return;
      }
      foreach ($order->shipping_rates as $shipping_service => $shipping_object) {

        // Check if the order contains the shipping rate we want to apply a
        // discount against. Not all shipping rates apply to all orders.
        if (isset($shipping_service_to_discount) && $shipping_service_to_discount !== $shipping_service) {
          continue;
        }

        // Instantiate the line item wrapper.
        $shipping_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$shipping_service]);

        // Calculate the correct value to discount the line item.
        $discount_shipping_value = -$shipping_line_item_wrapper->commerce_unit_price->amount
          ->value() * $discount_multiplier;
        _commerce_discount_add_shipping_discount_price_component($shipping_line_item_wrapper, $order_wrapper, $discount_name, $discount_shipping_value);
      }
      break;
    case 'free_shipping':
      try {
        $free_service = $discount_wrapper->commerce_discount_offer->commerce_free_shipping
          ->raw();
        $free_shipping_strategy = commerce_discount_get_free_shipping_strategy($discount);
      } catch (Exception $e) {
        watchdog('commerce_discount', 'Free shipping configuration issue detected on discount %name.', array(
          '%name' => $discount_wrapper
            ->label(),
        ));
        return;
      }

      // If a shipping service is specified, ensure it exists.
      if (!empty($free_service)) {
        if (!empty($order->shipping_rates[$free_service])) {

          // Exit if the shipping service is already free.
          $free_service_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$free_service]);
          if ($free_service_wrapper->commerce_unit_price->amount
            ->value() <= 0) {
            return;
          }

          // Update the free shipping service line item's rate to be free using the
          // service's price.
          $discount_amount = $free_service_wrapper->commerce_unit_price->amount
            ->value();
          $order->shipping_rates[$free_service]->data['shipping_service']['description'] .= ' <span class="shipping-discount-text">' . check_plain($discount->component_title) . '</span>';
          _commerce_discount_add_shipping_discount_price_component($free_service_wrapper, $order_wrapper, $discount_name, -$discount_amount);
        }

        // The free shipping discount strategy requires us to discount all other
        // shipping services an equivalent amount.
        if ($free_shipping_strategy == 'discount_all') {
          foreach ($order->shipping_rates as $service_name => $line_item) {

            // Skip the free shipping service.
            if ($service_name == $free_service) {
              continue;
            }
            $free_shipping_service = commerce_shipping_service_load($free_service);
            $callback = commerce_shipping_service_callback($free_shipping_service, 'rate');
            $price = $callback($free_shipping_service, $order);
            $discount_amount = $price['amount'];

            // Apply the discount, ensuring the rate does not discount below 0,
            // but do not use the message again to reduce spam on the form.
            $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
            $service_discount_amount = min($discount_amount, $line_item_wrapper->commerce_unit_price->amount
              ->value());
            _commerce_discount_add_shipping_discount_price_component($line_item_wrapper, $order_wrapper, $discount_name, -$service_discount_amount);
          }
        }
      }
      break;
    case 'shipping_upgrade':
      try {
        $target_service_id = $discount_offer_wrapper->commerce_shipping_upgrade_target
          ->raw();
        $source_service_id = $discount_offer_wrapper->commerce_shipping_upgrade_source
          ->raw();
      } catch (Exception $e) {
        watchdog('commerce_discount', 'Shipping service upgrade configuration issue detected on discount %name.', array(
          '%name' => $discount_wrapper
            ->label(),
        ));
        return;
      }

      // Exit now if we either could not determine a target or source service or
      // they were not calculated for this order.
      if (empty($target_service_id) || empty($source_service_id) || empty($order->shipping_rates[$target_service_id])) {
        return;
      }
      $target_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $order->shipping_rates[$target_service_id]);

      // To calculate the source shipping rate with discounts, we need to
      // remove the target service from the order and apply the source instead.
      $cloned_order = clone $order;
      $cloned_order_wrapper = entity_metadata_wrapper('commerce_order', $cloned_order);
      if (empty($cloned_order->shipping_rates[$source_service_id])) {
        foreach ($cloned_order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {

          // If this line item is a shipping line item...
          if ($line_item_wrapper
            ->value() && $line_item_wrapper
            ->getBundle() === 'shipping') {
            $original_shipping_line_item = $line_item_wrapper
              ->value();
            $cloned_order_wrapper->commerce_line_items
              ->offsetUnset($delta);
            unset($cloned_order->shipping_rates[$target_service_id]);
          }
        }
        module_load_include('inc', 'commerce_shipping', 'commerce_shipping.rules');
        commerce_shipping_rate_apply($cloned_order, $source_service_id);

        // Get the new line item.
        $line_items = field_get_items('commerce_order', $cloned_order, 'commerce_line_items');
        $line_item_id = array_pop($line_items);
        $source_shipping_line_item = commerce_line_item_load($line_item_id['line_item_id']);
        $cloned_order->shipping_rates[$source_service_id] = $source_shipping_line_item;

        // Apply discounts on source line item. This won't create an endless
        // loop since we have unset the source service on the order above.
        commerce_discount_commerce_cart_order_refresh($cloned_order_wrapper);
      }

      // Get the discounted rate for the source service.
      $source_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $cloned_order->shipping_rates[$source_service_id]);

      // If the prices are already the same, exit now.
      if ($target_line_item_wrapper->commerce_unit_price->amount
        ->value() <= $source_line_item_wrapper->commerce_unit_price->amount
        ->value()) {
        return;
      }

      // Otherwise, calculate the difference between the source and the target.
      $difference = $source_line_item_wrapper->commerce_unit_price->amount
        ->value() - $target_line_item_wrapper->commerce_unit_price->amount
        ->value();
      _commerce_discount_add_shipping_discount_price_component($target_line_item_wrapper, $order_wrapper, $discount_name, $difference);
      break;
  }
}