You are here

function uc_ups_quote in Ubercart 8.4

Same name and namespace in other branches
  1. 5 shipping/uc_ups/uc_ups.module \uc_ups_quote()
  2. 6.2 shipping/uc_ups/uc_ups.module \uc_ups_quote()
  3. 7.3 shipping/uc_ups/uc_ups.module \uc_ups_quote()

Callback for retrieving a UPS shipping quote.

Requests a quote for each enabled UPS Service. Therefore, the quote will take longer to display to the user for each option the customer has available.

Parameters

array $products: Array of cart contents.

$details: Order details other than product information.

$method: The shipping method to create the quote.

Return value

array JSON object containing rate, error, and debugging information.

1 string reference to 'uc_ups_quote'
uc_ups_uc_shipping_method in shipping/uc_ups/uc_ups.module
Implements hook_uc_shipping_method().

File

shipping/uc_ups/uc_ups.module, line 541
UPS shipping quote module.

Code

function uc_ups_quote(array $products, $details, $method) {

  // The uc_quote AJAX query can fire before the customer has completely
  // filled out the destination address, so check to see whether the address
  // has all needed fields. If not, abort.
  $destination = (object) $details;
  if (empty($destination
    ->getZone()) || empty($destination
    ->getPostalCode()) || empty($destination
    ->getCountry())) {

    // Skip this shipping method.
    return [];
  }
  $quotes = [];
  $ups_config = \Drupal::config('uc_ups.settings');
  $quote_config = \Drupal::config('uc_quote.settings');
  $store_config = \Drupal::config('uc_store.settings');
  $addresses = [
    $quote_config
      ->get('store_default_address'),
  ];
  $key = 0;
  $last_key = 0;
  $packages = [];
  if ($ups_config
    ->get('all_in_one') && count($products) > 1) {
    foreach ($products as $product) {
      if ($product->nid) {

        // Packages are grouped by the address from which they will be
        // shipped. We will keep track of the different addresses in an array
        // and use their keys for the array of packages.
        $key = NULL;
        $address = uc_quote_get_default_shipping_address($product->nid);
        foreach ($addresses as $index => $value) {
          if ($address
            ->isSamePhysicalLocation($value)) {

            // This is an existing address.
            $key = $index;
            break;
          }
        }
        if (!isset($key)) {

          // This is a new address. Increment the address counter $last_key
          // instead of using [] so that it can be used in $packages and
          // $addresses.
          $addresses[++$last_key] = $address;
          $key = $last_key;
        }
      }

      // Add this product to the last package from the found address or start
      // a new package.
      if (isset($packages[$key]) && count($packages[$key])) {
        $package = array_pop($packages[$key]);
      }
      else {
        $package = _uc_ups_new_package();
      }

      // Grab some product properties directly from the (cached) product
      // data. They are not normally available here because the $product
      // object is being read out of the $order object rather than from
      // the database, and the $order object only carries around a limited
      // number of product properties.
      $temp = Node::load($product->nid);
      $product->length = $temp->length;
      $product->width = $temp->width;
      $product->height = $temp->height;
      $product->length_units = $temp->length_units;
      $product->ups['pkg_type'] = isset($temp->ups) ? $temp->ups['pkg_type'] : '02';
      $weight = $product->weight * $product->qty * uc_weight_conversion($product->weight_units, 'lb');
      $package->weight += $weight;
      $package->price += $product->price * $product->qty;
      $length_factor = uc_length_conversion($product->length_units, 'in');
      $package->length = max($product->length * $length_factor, $package->length);
      $package->width = max($product->width * $length_factor, $package->width);
      $package->height = max($product->height * $length_factor, $package->height);
      $packages[$key][] = $package;
    }
    foreach ($packages as $addr_key => $shipment) {
      foreach ($shipment as $key => $package) {
        if (!$package->weight) {
          unset($packages[$addr_key][$key]);
          continue;
        }
        elseif ($package->weight > 150) {

          // UPS has a weight limit on packages of 150 lbs. Pretend the
          // products can be divided into enough packages.
          $qty = floor($package->weight / 150) + 1;
          $package->qty = $qty;
          $package->weight /= $qty;
          $package->price /= $qty;
        }
      }
    }
  }
  else {
    foreach ($products as $product) {
      $key = 0;
      if ($product->nid) {
        $address = uc_quote_get_default_shipping_address($product->nid);
        if (in_array($address, $addresses)) {

          // This is an existing address.
          foreach ($addresses as $index => $value) {
            if ($address == $value) {
              $key = $index;
              break;
            }
          }
        }
        else {

          // This is a new address.
          $addresses[++$last_key] = $address;
          $key = $last_key;
        }
      }
      if (!isset($product->pkg_qty) || !$product->pkg_qty) {
        $product->pkg_qty = 1;
      }
      $num_of_pkgs = (int) ($product->qty / $product->pkg_qty);

      // Grab some product properties directly from the (cached) product
      // data. They are not normally available here because the $product
      // object is being read out of the $order object rather than from
      // the database, and the $order object only carries around a limited
      // number of product properties.
      $temp = Node::load($product->nid);
      $product->length = $temp->length;
      $product->width = $temp->width;
      $product->height = $temp->height;
      $product->length_units = $temp->length_units;
      $product->ups['pkg_type'] = isset($temp->ups) ? $temp->ups['pkg_type'] : '02';
      if ($num_of_pkgs) {
        $package = clone $product;
        $package->description = $product->model;
        $package->weight = $product->weight * $product->pkg_qty;
        $package->price = $product->price * $product->pkg_qty;
        $package->qty = $num_of_pkgs;
        $package->pkg_type = $product->ups['pkg_type'];
        if ($package->weight) {
          $packages[$key][] = $package;
        }
      }
      $remaining_qty = $product->qty % $product->pkg_qty;
      if ($remaining_qty) {
        $package = clone $product;
        $package->description = $product->model;
        $package->weight = $product->weight * $remaining_qty;
        $package->price = $product->price * $remaining_qty;
        $package->qty = 1;
        $package->pkg_type = $product->ups['pkg_type'];
        if ($package->weight) {
          $packages[$key][] = $package;
        }
      }
    }
  }
  if (!count($packages)) {
    return [];
  }
  $dest = (object) $details;
  foreach ($packages as $key => $ship_packages) {
    $orig = $addresses[$key];
    $orig->email = uc_store_email();
    foreach (array_keys(array_filter($ups_config
      ->get('services'))) as $ups_service) {
      $request = uc_ups_shipping_quote($ship_packages, $orig, $dest, $ups_service);
      $resp = \Drupal::httpClient()
        ->post($ups_config
        ->get('connection_address') . 'Rate', NULL, $request)
        ->send();
      $account = \Drupal::currentUser();
      if ($account
        ->hasPermission('configure quotes') && $ups_config
        ->get('uc_quote_display_debug')) {
        if (!isset($debug_data[$ups_service]['debug'])) {
          $debug_data[$ups_service]['debug'] = '';
        }
        $debug_data[$ups_service]['debug'] .= htmlentities($request) . ' <br /><br /> ' . htmlentities($resp
          ->getBody(TRUE));
      }
      $response = new \SimpleXMLElement($resp
        ->getBody(TRUE));
      if (isset($response->Response->Error)) {
        foreach ($response->Response->Error as $error) {
          if ($account
            ->hasPermission('configure quotes') && $ups_config
            ->get('uc_quote_display_debug')) {
            $debug_data[$ups_service]['error'][] = (string) $error->ErrorSeverity . ' ' . (string) $error->ErrorCode . ': ' . (string) $error->ErrorDescription;
          }
          if (strpos((string) $error->ErrorSeverity, 'Hard') !== FALSE) {

            // All or nothing quote. If some products can't be shipped by
            // a certain service, no quote is given for that service. If
            // that means no quotes are given at all, they'd better call in.
            unset($quotes[$ups_service]['rate']);
          }
        }
      }

      // If NegotiatedRates exist, quote based on those, otherwise, use
      // TotalCharges.
      if (isset($response->RatedShipment)) {
        $charge = $response->RatedShipment->TotalCharges;
        if (isset($response->RatedShipment->NegotiatedRates)) {
          $charge = $response->RatedShipment->NegotiatedRates->NetSummaryCharges->GrandTotal;
        }
        if (!isset($charge->CurrencyCode) || (string) $charge->CurrencyCode == $store_config
          ->get('currency.code')) {

          // Markup rate before customer sees it.
          if (!isset($quotes[$ups_service]['rate'])) {
            $quotes[$ups_service]['rate'] = 0;
          }
          $rate = $this
            ->rateMarkup((string) $charge->MonetaryValue);
          $quotes[$ups_service]['rate'] += $rate;
        }
      }
    }
  }

  // Sort quotes by price, lowest to highest.
  uasort($quotes, 'uc_quote_price_sort');
  foreach ($quotes as $key => $quote) {
    if (isset($quote['rate'])) {
      $quotes[$key]['rate'] = $quote['rate'];
      $quotes[$key]['label'] = $method['quote']['accessorials'][$key];
      $quotes[$key]['option_label'] = theme('uc_ups_option_label', [
        'service' => $method['quote']['accessorials'][$key],
        'packages' => $packages,
      ]);
    }
  }

  // Merge debug data into $quotes. This is necessary because
  // $debug_data is not sortable by a 'rate' key, so it has to be
  // kept separate from the $quotes data until this point.
  if (isset($debug_data)) {
    foreach ($debug_data as $key => $data) {
      if (isset($quotes[$key])) {

        // This is debug data for successful quotes.
        $quotes[$key]['debug'] = $debug_data[$key]['debug'];
        if (isset($debug_data[$key]['error'])) {
          $quotes[$key]['error'] = $debug_data[$key]['error'];
        }
      }
      else {

        // This is debug data for quotes that returned error responses from UPS.
        $quotes[$key] = $debug_data[$key];
      }
    }
  }
  return $quotes;
}