You are here

function uc_discounts_get_discounts_for_order in Ubercart Discounts (Alternative) 7.2

Returns discounts for order.

Note: $order->uc_discounts_codes must be set

Return value

array Keyed array with 'discounts' array and 'messages' array.

10 calls to uc_discounts_get_discounts_for_order()
uc_discounts_apply in uc_discounts/uc_discounts.module
Applies the discounts to an order by creating the necessary line items.
uc_discounts_condition_discount_applied in uc_discounts/uc_discounts.ca.inc
Determines if a discounts has been applied.
uc_discounts_condition_discount_applied_condition in uc_discounts/uc_discounts.rules.inc
Checks if a particluar discount id has been applied on an order.
uc_discounts_condition_total in uc_discounts/uc_discounts.ca.inc
Check the current order balance minus any discounts.
uc_discounts_condition_total_after_discounts in uc_discounts/uc_discounts.rules.inc
Check the current order balance minus any discounts.

... See full list

File

uc_discounts/uc_discounts.module, line 1250

Code

function uc_discounts_get_discounts_for_order($order, $reset = FALSE) {

  // Store discounts in static variable.
  $order_discounts =& drupal_static(__FUNCTION__);
  $messages = array(
    'success' => array(),
    'warnings' => array(),
    'errors' => array(),
  );
  $discounts = array();
  $total_discount_amount = 0;
  if (!isset($order->order_id)) {
    $messages['errors'][] = t('Order ID does not exist.');
    return array(
      'discounts' => $discounts,
      'messages' => $messages,
      'total_discount_amount' => $total_discount_amount,
    );
  }

  // If reset then delete static variable for the order id. Also orders that
  // have order_id = 0 are not in the checkout process yet. These keep changing
  // so they are not saved.
  if (($reset || $order->order_id == 0) && isset($order_discounts[$order->order_id])) {
    unset($order_discounts[$order->order_id]);
  }

  // If static variable exists then return it.
  if (isset($order_discounts[$order->order_id])) {
    return $order_discounts[$order->order_id];
  }

  // If no static variable exists then do all the heavy lifting.
  // Product NIDS in cart => subtotal of individual item.
  $order_product_id_subtotal_map = array();

  // Product NIDS in cart => quantity of individual item.
  $order_product_id_quantity_map = array();

  // Product NIDS in cart.
  $order_product_ids = array();

  // Product NIDS in cart=> bool.
  $order_product_ids_set = array();

  // Product objects in cart.
  $order_product_id_product_array_map = array();
  $order_subtotal = 0;
  $kits = array();
  $where_clauses = array();

  // Create IN string of product node IDs in order.
  if (is_array($order->products) && !empty($order->products)) {
    foreach ($order->products as $product) {
      $nid = $product->nid;
      $order_product_ids_set[$nid] = TRUE;
      if (is_array($product->data) && !empty($product->data['kit_id'])) {
        $kit_id = $product->data['kit_id'];
        $order_product_ids_set[$kit_id] = TRUE;
        if (!isset($kits[$kit_id])) {
          $kits[$kit_id] = array(
            'product_qty' => $product->qty,
          );
        }
        elseif (!isset($kits[$kit_id]['product_qty'])) {
          $kits[$kit_id]['product_qty'] = $product->qty;
        }
        else {
          $kits[$kit_id]['product_qty'] += $product->qty;
        }
      }
      uc_discounts_add_to_existing_map_number_value($order_product_id_subtotal_map, $nid, $product->price * $product->qty);
      uc_discounts_add_to_existing_map_number_value($order_product_id_quantity_map, $nid, $product->qty);
      $a = array();
      if (isset($order_product_id_product_array_map[$nid]) && is_array($order_product_id_product_array_map[$nid])) {
        $a = $order_product_id_product_array_map[$nid];
      }
      $a[] = $product;
      $order_product_id_product_array_map[$nid] = $a;
      $order_subtotal += $product->price * $product->qty;
    }
    foreach ($kits as $kit_id => $value) {
      $kit_node = node_load($kit_id);
      foreach ($kit_node->products as $product_in_kit) {
        $pik_nid = $product_in_kit->nid;
        foreach ($order->products as $key => $product) {
          if ($product->nid == $pik_nid && isset($product->data['kit_id']) && $product->data['kit_id'] == $kit_id) {
            $kits[$kit_id]['kit_qty'] = $product->qty / $product_in_kit->qty;
            break;
          }
        }
      }
      uc_discounts_add_to_existing_map_number_value($order_product_id_quantity_map, $kit_id, $kits[$kit_id]['kit_qty']);
    }
    $order_product_ids = array_keys($order_product_ids_set);
  }

  // Populate product NID array with NIDs from the order.
  $where_product_ids = $order_product_ids;
  $where_product_ids[] = UC_DISCOUNTS_OPTION_ALL_PRODUCTS;
  $where_clauses['products'] = array(
    'sql' => 'd.filter_type <> :filter_type_products OR dp.product_id IN (:product_ids)',
    'args' => array(
      ':filter_type_products' => UC_DISCOUNTS_FILTER_TYPE_PRODUCTS,
      ':product_ids' => $where_product_ids,
    ),
  );

  // Search for product terms and add where clause to main query.
  $where_product_terms = array();
  $where_product_terms[] = UC_DISCOUNTS_OPTION_ALL_TERMS;
  if (!empty($order_product_ids)) {

    // Get terms for order's products.
    $where_product_terms_result = db_query("SELECT DISTINCT tid\n                       FROM {taxonomy_index}\n                       WHERE nid IN (:nids)", array(
      ':nids' => $order_product_ids,
    ));
    $where_product_terms += $where_product_terms_result
      ->fetchCol();
  }
  $where_clauses['terms'] = array(
    'sql' => 'd.filter_type <> :filter_type_terms OR dt.term_id IN (:product_terms)',
    'args' => array(
      ':filter_type_terms' => UC_DISCOUNTS_FILTER_TYPE_TERMS,
      ':product_terms' => $where_product_terms,
    ),
  );

  // Create IN string of SKUs in order.
  $where_product_skus = array();
  $where_product_skus[] = UC_DISCOUNTS_OPTION_ALL_SKUS;
  if (!empty($order_product_ids)) {

    // Get SKUs for order's products.
    $result = db_query("SELECT DISTINCT model\n                       FROM {uc_products}\n                       WHERE nid IN (:nids)", array(
      ':nids' => $order_product_ids,
    ));
    $where_product_skus += $result
      ->fetchCol();
  }
  $where_clauses['skus'] = array(
    'sql' => 'd.filter_type <> :filter_type_skus OR ds.sku IN (:product_skus)',
    'args' => array(
      ':filter_type_skus' => UC_DISCOUNTS_FILTER_TYPE_SKUS,
      ':product_skus' => $where_product_skus,
    ),
  );

  // Create IN string of classes in order.
  $where_product_classes = array();
  $where_product_classes[] = UC_DISCOUNTS_OPTION_ALL_CLASSES;
  if (!empty($order_product_ids)) {

    // Get classes for order's products.
    $where_product_classes_result = db_query("SELECT DISTINCT type\n                       FROM {node}\n                       WHERE nid IN (:nids)", array(
      ':nids' => $order_product_ids,
    ));
    $where_product_classes += $where_product_classes_result
      ->fetchCol();
  }
  $where_clauses['classes'] = array(
    'sql' => 'd.filter_type <> :filter_type_class OR dcl.class IN (:product_classes)',
    'args' => array(
      ':filter_type_class' => UC_DISCOUNTS_FILTER_TYPE_CLASS,
      ':product_classes' => $where_product_classes,
    ),
  );

  // Create IN string of authors in order.
  $where_authors = array();
  $where_authors[] = UC_DISCOUNTS_OPTION_ALL_AUTHORS;
  if (!empty($order_product_ids)) {

    // Get authors for order's products.
    $where_authors_result = db_query("SELECT DISTINCT uid\n                       FROM {node}\n                       WHERE nid IN (:nids)", array(
      ':nids' => $order_product_ids,
    ));
    $where_authors += $where_authors_result
      ->fetchCol();
  }
  $where_clauses['authors'] = array(
    'sql' => 'd.filter_type <> :filter_type_authors OR dau.author_id IN (:product_authors)',
    'args' => array(
      ':filter_type_authors' => UC_DISCOUNTS_FILTER_TYPE_AUTHORS,
      ':product_authors' => $where_authors,
    ),
  );

  // Create codes clause.
  $order_discount_codes = array();
  $where_clauses['codes'] = array(
    'sql' => 'd.requires_code = 0',
    'args' => array(),
  );
  if (!empty($order->uc_discounts_codes)) {
    $order_discount_codes = $order->uc_discounts_codes;
    $valid_codes_result = db_query('SELECT discount_id FROM {uc_discounts_codes} WHERE code IN (:order_discount_codes)', array(
      ':order_discount_codes' => $order_discount_codes,
    ));
    $valid_codes_ids = $valid_codes_result
      ->fetchCol();
    if (!empty($valid_codes_ids)) {
      $where_clauses['codes'] = array(
        'sql' => 'd.requires_code = 0 OR d.discount_id IN (:codes_ids)',
        'args' => array(
          ':codes_ids' => $valid_codes_ids,
        ),
      );
    }
  }

  // Create roles clause.
  $auth_rid = $order->uid != 0 ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
  $roles_discounts_sql = 'SELECT DISTINCT dr.discount_id FROM {uc_discounts_roles} dr' . ' LEFT JOIN {users_roles} ur ON (ur.rid = dr.role_id AND ur.uid = :uid)' . ' WHERE ur.uid IS NOT NULL' . '  OR dr.role_id = :all_roles' . '  OR dr.role_id = :auth_role';
  $roles_discounts_args = array(
    ':uid' => $order->uid,
    ':all_roles' => UC_DISCOUNTS_OPTION_ALL_ROLES,
    ':auth_role' => $auth_rid,
  );
  $roles_discounts_result = db_query($roles_discounts_sql, $roles_discounts_args);
  $roles_discounts_ids = $roles_discounts_result
    ->fetchCol();
  if (!empty($roles_discounts_ids)) {
    $where_clauses['roles'] = array(
      'sql' => 'd.has_role_filter = 0 OR d.discount_id IN (:role_discounts)',
      'args' => array(
        ':role_discounts' => $roles_discounts_ids,
      ),
    );
  }
  else {
    $where_clauses['roles'] = array(
      'sql' => 'd.has_role_filter = 0',
      'args' => array(),
    );
  }
  $grouping = UC_DISCOUNTS_GROUPING_APPLICATION;

  // Add warnings for expired discounts with codes if necessary.
  if (!empty($order->uc_discounts_codes)) {
    $expired_sql = "SELECT DISTINCT d.*, dc.code code FROM {uc_discounts} d\n      LEFT JOIN {uc_discounts_products} dp ON (dp.discount_id = d.discount_id AND dp.grouping = :grouping)\n      LEFT JOIN {uc_discounts_terms} dt ON (dt.discount_id = d.discount_id AND dt.grouping = :grouping)\n      LEFT JOIN {uc_discounts_skus} ds ON (ds.discount_id = d.discount_id AND ds.grouping = :grouping)\n      LEFT JOIN {uc_discounts_classes} dcl ON (dcl.discount_id = d.discount_id AND dcl.grouping = :grouping)\n      LEFT JOIN {uc_discounts_authors} dau ON (dau.discount_id = d.discount_id AND dau.grouping = :grouping)\n      LEFT JOIN {uc_discounts_roles} dr ON (dr.discount_id = d.discount_id)\n      LEFT JOIN {uc_discounts_codes} dc ON (dc.discount_id = d.discount_id)\n      WHERE dc.code IN (:order_discount_codes)\n      AND (d.has_expiration <> 0 AND d.expiration <= :request_time)\n      AND (d.is_active = :discount_active_constant)";
    $expired_args = array(
      ':grouping' => $grouping,
      ':order_discount_codes' => $order_discount_codes,
      ':request_time' => REQUEST_TIME,
      ':discount_active_constant' => UC_DISCOUNTS_DISCOUNT_ACTIVE,
    );
    foreach ($where_clauses as $key => $clause) {

      // @todo Why is the 'codes' clause ommited from the original sql?
      if ($key != 'codes') {
        $expired_sql .= ' AND (' . $clause['sql'] . ')';
        $expired_args += $clause['args'];
      }
    }
    $expired_sql .= " ORDER BY d.weight";
    $expired_result = db_query($expired_sql, $expired_args);
    foreach ($expired_result as $discount) {
      $messages['warnings'][] = t('The discount code %code has expired.', array(
        '%code' => $discount->code,
      ));
    }
  }
  $valid_discounts_sql = "SELECT DISTINCT d.* FROM {uc_discounts} d\n    LEFT JOIN {uc_discounts_products} dp ON (dp.discount_id = d.discount_id AND dp.grouping = :grouping)\n    LEFT JOIN {uc_discounts_terms} dt ON (dt.discount_id = d.discount_id AND dt.grouping = :grouping)\n    LEFT JOIN {uc_discounts_skus} ds ON (ds.discount_id = d.discount_id AND ds.grouping = :grouping)\n    LEFT JOIN {uc_discounts_classes} dcl ON (dcl.discount_id = d.discount_id AND dcl.grouping = :grouping)\n    LEFT JOIN {uc_discounts_authors} dau ON (dau.discount_id = d.discount_id AND dau.grouping = :grouping)\n    WHERE (d.has_activation = 0 AND d.activates_on < :request_time)\n    AND (d.has_expiration = 0 OR d.expiration > :request_time)\n    AND (d.is_active = :discount_active_constant)";
  $valid_discounts_args = array(
    ':grouping' => $grouping,
    ':request_time' => REQUEST_TIME,
    ':discount_active_constant' => UC_DISCOUNTS_DISCOUNT_ACTIVE,
  );
  foreach ($where_clauses as $clause) {
    $valid_discounts_sql .= ' AND (' . $clause['sql'] . ')';
    $valid_discounts_args += $clause['args'];
  }
  $valid_discounts_sql .= " ORDER BY d.weight";
  $valid_discounts_result = db_query($valid_discounts_sql, $valid_discounts_args);

  // Checks if order qualifies for each discount then applies discount.
  // @todo Functionality should be separated?
  foreach ($valid_discounts_result as $discount) {
    foreach (module_implements('uc_discount') as $module) {
      $function = $module . '_uc_discount';
      $function('load', $discount, $order);
    }

    // In case the hook modified the discount.
    if (!$discount->is_active) {
      continue;
    }

    // Get code for discount if one exists.
    // @todo Why is this overwritting the discount code? This validation has
    // already happened at least once in above code.
    $discount->code = NULL;
    if (!empty($order->uc_discounts_codes)) {
      $code = db_select('uc_discounts_codes', 'dc')
        ->fields('dc', array(
        'code',
      ))
        ->condition('discount_id', $discount->discount_id)
        ->condition('code', $order->uc_discounts_codes, 'IN')
        ->execute()
        ->fetchField();
      if (!empty($code)) {
        $discount->code = $code;
      }
    }

    // The query handled valid codes and expiration, this block must:
    // - Check max uses (if applicable).
    // - Check if discount is being combined and can be combined.
    // - Check if order qualifies (type, requires_single_product_to_qualify,
    //   required_products, can_be_combined_with_other_discounts).
    // - Determine number of times to apply discount.
    // If this discount has a max uses amount, check max uses.
    if ($discount->max_uses > 0) {
      $row = db_query("SELECT COUNT(*) as uses_count FROM {uc_discounts_uses} WHERE discount_id = :discount_id", array(
        ':discount_id' => $discount->discount_id,
      ))
        ->fetchArray();
      if ($row["uses_count"] >= $discount->max_uses) {

        // If this is a coded discount add error message.
        if (!empty($discount->code)) {
          $messages['warnings'][] = t('The discount for code "@code" has reached its maximum number of uses.', array(
            "@code" => $discount->code,
          ));
        }
        continue;
      }
      $discount->uses_count = $row["uses_count"];
    }

    // If this discount has a max uses per user amount, check max uses per user.
    if ($discount->max_uses_per_user > 0) {
      $row = db_query("SELECT COUNT(*) as user_uses_count FROM {uc_discounts_uses} WHERE discount_id = :discount_id AND user_id = :user_id", array(
        ':discount_id' => $discount->discount_id,
        ':user_id' => $order->uid,
      ))
        ->fetchArray();
      if ($row["user_uses_count"] >= $discount->max_uses_per_user) {

        // If this is a coded discount add warning message.
        if (!empty($discount->code)) {
          $messages['warnings'][] = t('The discount for code %code has reached its maximum number of uses.', array(
            "%code" => $discount->code,
          ));
        }
        continue;
      }
      $discount->user_uses_count = $row["user_uses_count"];
    }

    // If code exists and this discount has a max uses per code amount, check
    // max uses per code.
    if (!is_null($discount->code) && $discount->max_uses_per_code > 0) {
      $row = db_query("SELECT COUNT(*) as code_uses_count FROM {uc_discounts_uses} WHERE discount_id = :discount_id AND code = :code", array(
        ':discount_id' => $discount->discount_id,
        ':code' => $discount->code,
      ))
        ->fetchArray();
      if ($row['code_uses_count'] >= $discount->max_uses_per_code) {

        // Add warning message.
        $messages['warnings'][] = t('The discount code %code has reached its max number of uses.', array(
          '%code' => $discount->code,
        ));
        continue;
      }
      $discount->code_uses_count = $row["code_uses_count"];
    }

    // If there are applied discounts, check if discount is being combined and
    // can be combined.
    if (count($discounts) > 0) {
      if (!$discount->can_be_combined_with_other_discounts) {

        // If this is a coded discount add error message.
        if (!empty($discount->code)) {
          $messages['warnings'][] = t('The discount code %code cannot be combined with other discounts.', array(
            '%code' => $discount->code,
          ));
        }
        continue;
      }

      // Check if the first discount can't be combined.
      if (!$discounts[0]->can_be_combined_with_other_discounts) {

        // Add warning message only if both discounts have codes.
        if (!empty($discounts[0]->code) && !empty($discount->code)) {
          $messages['warnings'][] = t('The discount code %code cannot be combined with other discounts.', array(
            '%code' => $discounts[0]->code,
          ));
        }
        continue;
      }
    }

    // Check if order qualifies for this discount (check type,
    // requires_single_product_to_qualify, required_products).
    // Get product IDs for determining discount application.
    $discount_product_ids = uc_discounts_get_product_ids_for_discount($discount);
    if (in_array(UC_DISCOUNTS_OPTION_ALL_PRODUCTS, $discount_product_ids)) {
      $discount_product_ids = $order_product_ids;
    }

    // Get product IDs for determining discount qualification.
    $required_product_ids = uc_discounts_get_product_ids_for_discount($discount, UC_DISCOUNTS_GROUPING_QUALIFICATION);
    $required_ids_in_order = array_intersect($required_product_ids, $order_product_ids);
    if (!empty($discount->code) && !empty($required_product_ids) && empty($required_ids_in_order)) {
      $messages['warnings'][] = t('The discount code %code requires a product that is not in your cart.', array(
        '%code' => $discount->code,
      ));
      continue;
    }
    if ($discount->use_only_discounted_products_to_qualify) {
      $qualification_product_ids = $discount_product_ids;
    }
    elseif (!empty($required_product_ids)) {
      $qualification_product_ids = $required_product_ids;
    }
    else {
      $qualification_product_ids = $order_product_ids;
    }

    // Determine total qualifying amount of order and save in
    // order_qualifying_amount.
    $order_qualifying_amount = 0;
    switch ($discount->qualifying_type) {
      case UC_DISCOUNTS_QUALIFYING_TYPE_MINIMUM_PRICE:

        // Determine the total subtotal of discount's products.
        foreach ($qualification_product_ids as $product_id) {
          if (isset($order_product_id_subtotal_map[$product_id])) {
            if ($discount->requires_single_product_to_qualify) {
              if ($order_product_id_subtotal_map[$product_id] >= $discount->qualifying_amount) {

                // In this case, $order_qualifying amount should be the sum of
                // prices of products that both qualify and meet the minimum
                // qualification amount based on their individual price.
                $order_qualifying_amount += $order_product_id_subtotal_map[$product_id];
              }
            }
            else {
              $order_qualifying_amount += $order_product_id_subtotal_map[$product_id];
            }
          }
        }

        // Subtract already discounted amount.
        $order_qualifying_amount -= $total_discount_amount;
        break;
      case UC_DISCOUNTS_QUALIFYING_TYPE_MINIMUM_QUANTITY:

        // Determine the total quantity of discount's products.
        foreach ($qualification_product_ids as $product_id) {
          if (isset($order_product_id_quantity_map[$product_id])) {
            if ($discount->requires_single_product_to_qualify) {
              if ($order_product_id_quantity_map[$product_id] >= $discount->qualifying_amount) {

                // In this case, $order_qualifying amount should be the sum of
                // products that both qualify and meet the minimum qualification
                // amount based on their quantity.
                $order_qualifying_amount += $order_product_id_quantity_map[$product_id];
              }
            }
            else {
              $order_qualifying_amount += $order_product_id_quantity_map[$product_id];
            }
          }
        }
        break;
    }

    // If order does not qualify for this discount.
    if ($order_qualifying_amount < $discount->qualifying_amount) {

      // If this is a coded discount, add warning message.
      if (!empty($discount->code)) {
        switch ($discount->qualifying_type) {
          case UC_DISCOUNTS_QUALIFYING_TYPE_MINIMUM_PRICE:
            $qualifying_amount = uc_currency_format($discount->qualifying_amount);
            $messages['warnings'][] = t('The discount code %code requires a minimum amount of @qualifying_amount to qualify.', array(
              '%code' => $discount->code,
              '@qualifying_amount' => $qualifying_amount,
            ));
            break;
          case UC_DISCOUNTS_QUALIFYING_TYPE_MINIMUM_QUANTITY:
            $messages['warnings'][] = t('The discount for code %code requires a minimum quantity of @qualifying_amount to qualify.', array(
              '%code' => $discount->code,
              '@qualifying_amount' => $discount->qualifying_amount,
            ));
            break;
        }
      }
      continue;
    }

    // If this discount has a maximum qualifying amount and order exceeds it.
    if ($discount->has_qualifying_amount_max && $order_qualifying_amount > $discount->qualifying_amount_max) {

      // If this is a coded discount, add error message.
      if (!empty($discount->code)) {
        $qualifying_amount_max = uc_currency_format($discount->qualifying_amount_max);
        switch ($discount->qualifying_type) {
          case UC_DISCOUNTS_QUALIFYING_TYPE_MINIMUM_PRICE:
            $messages['warnings'][] = t('The discount for code %code cannot exceed the price of @qualifying_amount_max to qualify.', array(
              '%code' => $discount->code,
              '@qualifying_amount_max' => $qualifying_amount_max,
            ));
            break;
          case UC_DISCOUNTS_QUALIFYING_TYPE_MINIMUM_QUANTITY:
            $messages['warnings'][] = t('The discount for code %code cannot exceed the quantity of @qualifying_amount_max to qualify.', array(
              '%code' => $discount->code,
              '@qualifying_amount_max' => $discount->qualifying_amount_max,
            ));
            break;
        }
      }
      continue;
    }

    // Get product IDs in order that are in discount.
    $order_and_discount_product_ids = array_intersect($discount_product_ids, $order_product_ids);

    // Create array of product objects in cart to which this discount gets
    // applied.
    $order_and_discount_products = array();
    foreach ($order_and_discount_product_ids as $product_id) {
      if (array_key_exists($product_id, $order_product_id_product_array_map)) {
        $order_and_discount_products = array_merge($order_and_discount_products, $order_product_id_product_array_map[$product_id]);
      }
    }

    // Amount of products to which discounts get applied.
    $discount_products_amount = 0;

    // Quantity of products to which discounts get applied.
    $discount_products_qty = 0;
    foreach ($order_and_discount_products as $product) {
      $discount_products_qty += $product->qty;
      $discount_products_amount += $product->qty * $product->price;
    }

    // Determine number of times to apply discount. By default once for every
    // qualifying product.
    $discount->times_applied = $discount_products_qty;

    // See if it should be limited based on number of required products in cart.
    if ($discount->limit_max_times_applied && !empty($required_product_ids)) {
      $times = 0;
      foreach ($required_product_ids as $id) {
        if (isset($order_product_id_quantity_map[$id])) {
          $times += $order_product_id_quantity_map[$id];
        }
      }
      $discount->times_applied = min($discount->times_applied, $times);
    }

    // See if we need to limit the number of applications with a hard cap.
    if ($discount->max_times_applied != 0) {
      $discount->times_applied = min($discount->times_applied, $discount->max_times_applied);
    }
    switch ($discount->discount_type) {
      case UC_DISCOUNTS_DISCOUNT_TYPE_FREE_ITEMS:

        // The variable discount_amount is the monitary amount of discount.
        $discount_amount = 0;

        // The variable free_items_remaining is the [max] number of free items
        // for the order.
        $free_items_remaining = $discount->discount_amount * $discount->times_applied;

        // Loop until all free items have been applied or there are no more
        // products to discount (discount cheapest first).
        while ($free_items_remaining > 0) {

          // Determine cheapest remaining qualifying item.
          $cheapest_product = NULL;
          $cheapest_product_key = NULL;
          foreach ($order_and_discount_products as $key => $product) {

            // If this product has been fully discounted, continue.
            if ($product->uc_discounts_is_fully_discounted) {
              continue;
            }

            // If no current cheapest product exists, use this product.
            if (is_null($cheapest_product)) {
              $cheapest_product = $product;
              $cheapest_product_key = $key;
            }
            else {

              // If this product is cheaper than the current cheapest product,
              // use this product instead.
              if ($product->price < $cheapest_product->price) {
                $cheapest_product = $product;
                $cheapest_product_key = $key;
              }
            }
          }

          // If no cheapest product could be found, there are no more products
          // to discount so break.
          if (is_null($cheapest_product)) {
            break;
          }

          // Discount up to the lesser of cheapest product quantity and
          // free_items_remaining.
          $discount_count = min($cheapest_product->qty, $free_items_remaining);

          // Add current discount amount to running total.
          $discount_amount += $discount_count * $cheapest_product->price;

          // Mark item fully discounted.
          $order_and_discount_products[$cheapest_product_key]->uc_discounts_is_fully_discounted = TRUE;
          $free_items_remaining -= $discount_count;
        }
        $discount->amount = $discount_amount;
        break;
      case UC_DISCOUNTS_DISCOUNT_TYPE_PERCENTAGE_OFF_PER_QUALIFYING_ITEM:
        $discount->amount = $discount_products_amount / $discount_products_qty * $discount->discount_amount * $discount->times_applied;
        break;
      case UC_DISCOUNTS_DISCOUNT_TYPE_PERCENTAGE_OFF:

        // This is so complicated because we need to ensure only qualifying
        // products get discounted and no product is discounted more than 100%.
        // Always apply once since it applies to the whole order.
        $discount->times_applied = 1;

        // If this discount uses all products and previous discount is:
        // - same weight as this discount
        // - percentage off
        // - products of discounts must match
        // Then discount using same subtotal as last discount.
        if (count($discounts) > 0) {
          $last_discount = $discounts[count($discounts) - 1];
          if ($last_discount->weight == $discount->weight && $last_discount->discount_type == UC_DISCOUNTS_DISCOUNT_TYPE_PERCENTAGE_OFF) {

            // Last discount's and this discount's products must match exactly.
            $are_equal = TRUE;
            $last_discount_product_ids = uc_discounts_get_product_ids_for_discount($last_discount);
            $this_discount_product_ids = uc_discounts_get_product_ids_for_discount($discount);

            // If both contain "all products" they are equal.
            if (in_array(UC_DISCOUNTS_OPTION_ALL_PRODUCTS, $last_discount_product_ids) && in_array(UC_DISCOUNTS_OPTION_ALL_PRODUCTS, $this_discount_product_ids)) {
              $are_equal = TRUE;
            }
            else {
              foreach ($this_discount_product_ids as $product_id) {
                if (!in_array($product_id, $last_discount_product_ids)) {
                  $are_equal = FALSE;
                  break;
                }
              }
              if ($are_equal) {
                foreach ($last_discount_product_ids as $product_id) {
                  if (!in_array($product_id, $this_discount_product_ids)) {
                    $are_equal = FALSE;
                    break;
                  }
                }
              }
            }
            if ($are_equal) {
              $local_order_subtotal = $last_discount->amount / $last_discount->discount_amount;
              $discount->amount = $local_order_subtotal * $discount->discount_amount;
              break;
            }
          }
        }

        // Start patch from lutegrass.
        // This fixes the problem where a percent discount does not apply to all
        // products. It doesn't fix the problem where the products being
        // discounted have already been discounted in full, or the case where
        // the cart consists only of the products included in this discount.
        // Get qualifying products -- ignore "all products" selection.
        $discount_product_ids = uc_discounts_get_product_ids_for_discount($discount, UC_DISCOUNTS_GROUPING_APPLICATION, TRUE);

        // Do we have any products.
        if (count($discount_product_ids) > 0) {
          $discounted_products_amount = 0;
          foreach ($order_and_discount_products as $product) {
            $discounted_products_amount += $product->price * $product->qty;
          }
          $discount->amount = $discounted_products_amount * $discount->discount_amount;

          // Discount the subtotal so far.
        }
        else {
          $discount->amount = max($order_subtotal - $total_discount_amount, 0) * $discount->discount_amount;
        }

        // End patch from lutegrass.
        break;
      case UC_DISCOUNTS_DISCOUNT_TYPE_FIXED_AMOUNT_OFF:

        // Always apply once since it applies to the whole order.
        $discount->times_applied = 1;
        $discount->amount = $discount->discount_amount;
        break;
      case UC_DISCOUNTS_DISCOUNT_TYPE_FIXED_AMOUNT_OFF_PER_QUALIFYING_ITEM:
        $discount->amount = $discount->discount_amount * $discount->times_applied;
        break;
    }
    if (!is_null($messages)) {
      $options = array(
        '@short_description' => $discount->short_description,
        '@code' => $discount->code,
        '@times_applied' => $discount->times_applied,
        '@discount_amount' => uc_currency_format($discount->amount),
      );
      if (!is_null($discount->code)) {

        // @todo - Improve the following success messages.
        if (empty($discount->amount)) {
          $messages['success'][] = t("The discount, '@short_description', with code '@code' was applied.", $options);
        }
        elseif ($discount->times_applied == 1) {
          $messages['success'][] = t("The discount, '@short_description', with code '@code' was applied for a discount of @discount_amount.", $options);
        }
        else {
          $messages['success'][] = t("The discount, '@short_description', with code '@code' was applied @times_applied times for a discount of @discount_amount.", $options);
        }
      }
      else {
        if (empty($discount->amount)) {
          $messages['success'][] = t("The discount, '@short_description' was applied.", $options);
        }
        elseif ($discount->times_applied == 1) {
          $messages['success'][] = t("The discount, '@short_description', was applied for a discount of @discount_amount.", $options);
        }
        else {
          $messages['success'][] = t("The discount, '@short_description', was applied @times_applied times for a discount of @discount_amount.", $options);
        }
      }
    }

    // Round the discount to two places.
    $discount->amount = round($discount->amount, 2);

    // Add this discount's amount to running total.
    $total_discount_amount += $discount->amount;

    // Add this discount to list of discounts applied to order.
    $discounts[] = $discount;
  }

  // If no discount array was filled in, means that the discount was not found
  // in the database.
  if (empty($discounts) && !empty($order->uc_discounts_codes)) {

    // @todo - Fix text to using translation plural.
    $messages['warnings'][] = t('Coupon code %code does not exist or is not valid.', array(
      '%code' => implode(', ', $order->uc_discounts_codes),
    ));
  }

  // Save to static variable.
  $order_discounts[$order->order_id] = array(
    'discounts' => $discounts,
    'messages' => $messages,
    'total_discount_amount' => $total_discount_amount,
  );
  return $order_discounts[$order->order_id];
}