uc_ups.module in Ubercart 8.4
Same filename and directory in other branches
UPS shipping quote module.
File
shipping/uc_ups/uc_ups.moduleView source
<?php
/**
* @file
* UPS shipping quote module.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\uc_store\AddressInterface;
use Drupal\uc_ups\UPSUtilities;
/**
* Implements hook_menu().
*/
function uc_ups_menu() {
$items['admin/store/orders/%uc_order/shipments/ups'] = [
'title' => 'UPS shipment',
'page callback' => 'drupal_get_form',
'page arguments' => [
'uc_ups_confirm_shipment',
3,
],
'access arguments' => [
'fulfill orders',
],
'file' => 'src/Plugin/Ubercart/FulfillmentMethod/uc_ups.ship.inc',
];
$items['admin/store/orders/%uc_order/shipments/labels/ups'] = [
'page callback' => 'theme',
'page arguments' => [
'uc_ups_label_image',
],
'access arguments' => [
'fulfill orders',
],
'file' => 'src/Plugin/Ubercart/FulfillmentMethod/uc_ups.ship.inc',
];
return $items;
}
/**
* Implements hook_cron().
*
* Deletes UPS shipping labels from the file system automatically
* on a periodic basis. Cron must be enabled for automatic deletion.
* Default is never delete the labels, keep them forever.
*/
function uc_ups_cron() {
$ups_config = \Drupal::config('uc_ups.settings');
$cutoff = \Drupal::time()
->getRequestTime() - $ups_config
->get('label_lifetime');
if ($cutoff == \Drupal::time()
->getRequestTime()) {
// Label lifetime is set to 0, meaning never delete.
return;
}
// Loop over label files in public://ups_labels and test
// creation date against 'uc_ups_label_lifetime'.
$files = file_scan_directory('public://ups_labels', '/^label-/');
foreach ($files as $file) {
if ($cutoff > filectime($file->uri)) {
\Drupal::service('file_system')
->unlink($file->uri);
\Drupal::logger('uc_ups')
->notice('Removed uc_ups label file @file.', [
'@file' => $file->uri,
]);
}
}
}
/**
* Implements hook_theme().
*/
function uc_ups_theme() {
return [
'uc_ups_option_label' => [
'variables' => [
'service' => NULL,
'packages' => NULL,
],
'file' => 'uc_ups.theme.inc',
'function' => 'theme_uc_ups_option_label',
],
'uc_ups_confirm_shipment' => [
'render element' => 'form',
'file' => 'src/Plugin/Ubercart/FulfillmentMethod/uc_ups.ship.inc',
'function' => 'theme_uc_ups_confirm_shipment',
],
'uc_ups_label_image' => [
'variables' => [],
'file' => 'src/Plugin/Ubercart/FulfillmentMethod/uc_ups.ship.inc',
'function' => 'theme_uc_ups_label_image',
],
];
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for node_form().
*
* Adds package type to products.
*
* @see uc_product_form()
* @see uc_ups_product_alter_validate()
*/
function uc_ups_form_node_form_alter(&$form, FormStateInterface $form_state) {
$ups_config = \Drupal::config('uc_ups.settings');
$quote_config = \Drupal::config('uc_quote.settings');
$node = $form_state
->getFormObject()
->getEntity();
if (uc_product_is_product($node
->bundle())) {
$enabled = $quote_config
->get('enabled') + [
'ups' => FALSE,
];
$weight = $quote_config
->get('method_weight') + [
'ups' => 0,
];
$ups = [
'#type' => 'details',
'#title' => t('UPS product description'),
'#weight' => $weight['ups'],
'#tree' => TRUE,
];
$ups['pkg_type'] = [
'#type' => 'select',
'#title' => t('Package type'),
'#options' => UPSUtilities::packageTypes(),
'#default_value' => isset($node->ups['pkg_type']) ? $node->ups['pkg_type'] : $ups_config
->get('pkg_type'),
];
$form['shipping']['ups'] = $ups;
if ($enabled['ups']) {
$form['#validate'][] = 'uc_ups_product_alter_validate';
}
}
}
/**
* Validation handler for UPS product fields.
*
* @see uc_ups_form_alter()
*/
function uc_ups_product_alter_validate($form, FormStateInterface $form_state) {
$ups_config = \Drupal::config('uc_ups.settings');
if ($form_state
->hasValue('shippable') && ($form_state
->getValue('shipping_type') == 'small_package' || $form_state
->isValueEmpty('shipping_type') && $ups_config
->get('uc_store_shipping_type') == 'small_package')) {
if ($form_state
->getValue([
'ups',
'pkg_type',
]) == '02' && ($form_state
->isValueEmpty('dim_length') || $form_state
->isValueEmpty('dim_width')) || $form_state
->isValueEmpty('dim_height')) {
$form_state
->setErrorByName('base][dimensions', t('Dimensions are required for custom packaging.'));
}
}
}
/**
* Implements hook_node_insert().
*/
function uc_ups_node_insert($node) {
uc_ups_node_update($node);
}
/**
* Implements hook_node_update().
*/
function uc_ups_node_update($node) {
if (uc_product_is_product($node)) {
if (isset($node->ups)) {
$ups_values = $node->ups;
$connection = \Drupal::database();
if (!$node
->isNewRevision()) {
$connection
->delete('uc_ups_products')
->condition('vid', $node
->getRevisionId())
->execute();
}
$connection
->insert('uc_ups_products')
->fields([
'vid' => $node
->getRevisionId(),
'nid' => $node
->id(),
'pkg_type' => $ups_values['pkg_type'],
])
->execute();
}
}
}
/**
* Implements hook_node_load().
*/
function uc_ups_node_load($nodes) {
$nids = [];
foreach ($nodes as $node) {
if (uc_product_is_product($node)) {
$nids[] = $node
->id();
}
}
if (empty($nids)) {
return;
}
$vids = [];
$connection = \Drupal::database();
$ups_config = \Drupal::config('uc_ups.settings');
$shipping_type = $ups_config
->get('uc_store_shipping_type');
$shipping_types = $connection
->query("SELECT id, shipping_type FROM {uc_quote_shipping_types} WHERE id_type = :type AND id IN (:ids[])", [
':type' => 'product',
':ids[]' => $nids,
])
->fetchAllKeyed();
foreach ($nids as $nid) {
if (isset($shipping_types[$nid])) {
$nodes[$nid]->shipping_type = $shipping_types[$nid];
}
else {
$nodes[$nid]->shipping_type = $shipping_type;
}
if ($nodes[$nid]->shipping_type == 'small_package') {
$vids[$nid] = $nodes[$nid]
->getRevisionId();
}
}
if ($vids) {
$result = $connection
->query("SELECT * FROM {uc_ups_products} WHERE vid IN (:vids[])", [
':vids[]' => $vids,
], [
'fetch' => PDO::FETCH_ASSOC,
]);
foreach ($result as $ups) {
$nodes[$ups['nid']]->ups = $ups;
}
}
}
/**
* Implements hook_node_delete().
*/
function uc_ups_node_delete($node) {
$connection = \Drupal::database();
$connection
->delete('uc_ups_products')
->condition('nid', $node
->id())
->execute();
}
/**
* Implements hook_node_revision_delete().
*/
function uc_ups_node_revision_delete($node) {
$connection = \Drupal::database();
$connection
->delete('uc_ups_products')
->condition('vid', $node
->getRevisionId())
->execute();
}
/**
* Implements hook_uc_shipping_type().
*/
function uc_ups_uc_shipping_type() {
$quote_config = \Drupal::config('uc_quote.settings');
$weight = $quote_config
->get('type_weight');
$types = [];
$types['small_package'] = [
'id' => 'small_package',
'title' => t('Small packages'),
'weight' => $weight['small_package'],
];
return $types;
}
/**
* Implements hook_uc_shipping_method().
*/
function uc_ups_uc_shipping_method() {
$methods['ups'] = [
'id' => 'ups',
'module' => 'uc_ups',
'title' => t('UPS'),
'operations' => [
'configure' => [
'title' => t('configure'),
'url' => Url::fromRoute('uc_ups.settings')
->toString(),
],
],
'quote' => [
'type' => 'small_package',
'callback' => 'uc_ups_quote',
'accessorials' => UPSUtilities::services(),
],
'ship' => [
'type' => 'small_package',
'callback' => 'uc_ups_fulfill_order',
'file' => 'src/Plugin/Ubercart/FulfillmentMethod/uc_ups.ship.inc',
'pkg_types' => UPSUtilities::packageTypes(),
],
'cancel' => 'uc_ups_void_shipment',
];
return $methods;
}
/**
* Implements hook_uc_store_status().
*
* Lets the administrator know that the UPS account information has not been
* filled out.
*/
function uc_ups_uc_store_status() {
$messages = [];
$ups_config = \Drupal::config('uc_ups.settings');
$access = $ups_config
->get('access_license') != '';
$ups_account = $ups_config
->get('shipper_number') != '';
$user = $ups_config
->get('user_id') != '';
$password = $ups_config
->get('password') != '';
if ($access && $ups_account && $user && $password) {
$messages[] = [
'status' => 'ok',
'title' => t('UPS Online Tools'),
'desc' => t('Information needed to access UPS Online Tools has been entered.'),
];
}
else {
$messages[] = [
'status' => 'error',
'title' => t('UPS Online Tools'),
'desc' => t('More information is needed to access UPS Online Tools. Please enter it <a href=":url">here</a>.', [
':url' => Url::fromRoute('uc_ups.settings')
->toString(),
]),
];
}
return $messages;
}
/**
* Prepares XML access request string.
*
* @return string
* XML access request to be prepended to all requests to the UPS webservice.
*/
function uc_ups_access_request() {
$ups_config = \Drupal::config('uc_ups.settings');
$access = $ups_config
->get('access_license');
$user = $ups_config
->get('user_id');
$password = $ups_config
->get('password');
$xml = new \XMLWriter();
$xml
->openMemory();
$xml
->startDocument('1.0', 'UTF-8');
$xml
->startElement('AccessRequest');
$xml
->writeAttribute('xml:lang', 'en-US');
$xml
->writeElement('AccessLicenseNumber', $access);
$xml
->writeElement('UserId', $user);
$xml
->writeElement('Password', $password);
$xml
->endElement();
$xml
->endDocument();
return $xml
->outputMemory(TRUE);
}
/**
* Constructs an XML quote request.
*
* @param array $packages
* Array of packages received from the cart.
* @param \Drupal\uc_store\AddressInterface $origin
* Delivery origin address information.
* @param \Drupal\uc_store\AddressInterface $destination
* Delivery destination address information.
* @param string $ups_service
* UPS service code (refers to UPS Ground, Next-Day Air, etc.).
*
* @return string
* RatingServiceSelectionRequest XML document to send to UPS.
*/
function uc_ups_shipping_quote(array $packages, AddressInterface $origin, AddressInterface $destination, $ups_service) {
$ups_config = \Drupal::config('uc_ups.settings');
$ua = explode(' ', $_SERVER['HTTP_USER_AGENT']);
$user_agent = $ua[0];
$services = UPSUtilities::services();
$service = [
'code' => $ups_service,
'description' => $services[$ups_service],
];
$pkg_types = UPSUtilities::packageTypes();
$store_config = \Drupal::config('uc_store.settings');
$shipper_zone = $store_config
->get('address.zone');
$shipper_country = $store_config
->get('address.country');
$shipper_zip = $store_config
->get('address.postal_code');
$shipto_zone = $destination
->getZone();
$shipto_country = $destination
->getCountry();
$shipto_zip = $destination
->getPostalCode();
$shipfrom_zone = $origin
->getZone();
$shipfrom_country = $origin
->getCountry();
$shipfrom_zip = $origin
->getPostalCode();
$ups_units = $ups_config
->get('unit_system');
switch ($ups_units) {
case 'in':
$units = 'LBS';
$unit_name = 'Pounds';
break;
case 'cm':
$units = 'KGS';
$unit_name = 'Kilograms';
break;
}
$shipment_weight = 0;
$package_schema = '';
foreach ($packages as $package) {
// Determine length conversion factor and weight conversion factor
// for this shipment.
$length_factor = uc_length_conversion($package->length_units, $ups_config
->get('unit_system'));
switch ($ups_units) {
case 'in':
$weight_factor = uc_weight_conversion($package->weight_units, 'lb');
break;
case 'cm':
$weight_factor = uc_weight_conversion($package->weight_units, 'kg');
break;
}
// Loop over quantity of packages in this shipment.
$qty = $package->qty;
for ($i = 0; $i < $qty; $i++) {
// Build XML for this package.
$package_type = [
'code' => $package->pkg_type,
'description' => $pkg_types[$package->pkg_type],
];
$package_schema .= "<Package>";
$package_schema .= "<PackagingType>";
$package_schema .= "<Code>" . $package_type['code'] . "</Code>";
$package_schema .= "</PackagingType>";
if ($package->pkg_type == '02' && $package->length && $package->width && $package->height) {
if ($package->length < $package->width) {
list($package->length, $package->width) = [
$package->width,
$package->length,
];
}
$package_schema .= "<Dimensions>";
$package_schema .= "<UnitOfMeasurement>";
$package_schema .= "<Code>" . strtoupper($ups_config
->get('unit_system')) . "</Code>";
$package_schema .= "</UnitOfMeasurement>";
$package_schema .= "<Length>" . number_format($package->length * $length_factor, 2, '.', '') . "</Length>";
$package_schema .= "<Width>" . number_format($package->width * $length_factor, 2, '.', '') . "</Width>";
$package_schema .= "<Height>" . number_format($package->height * $length_factor, 2, '.', '') . "</Height>";
$package_schema .= "</Dimensions>";
}
$weight = max(1, $package->weight * $weight_factor);
$shipment_weight += $weight;
$package_schema .= "<PackageWeight>";
$package_schema .= "<UnitOfMeasurement>";
$package_schema .= "<Code>" . $units . "</Code>";
$package_schema .= "<Description>" . $unit_name . "</Description>";
$package_schema .= "</UnitOfMeasurement>";
$package_schema .= "<Weight>" . number_format($weight, 1, '.', '') . "</Weight>";
$package_schema .= "</PackageWeight>";
$size = $package->length * $length_factor + 2 * $length_factor * ($package->width + $package->height);
if ($size > 130 && $size <= 165) {
$package_schema .= "<LargePackageIndicator/>";
}
if ($ups_config
->get('insurance')) {
$package_schema .= "<PackageServiceOptions>";
$package_schema .= "<InsuredValue>";
$package_schema .= "<CurrencyCode>" . $store_config
->get('currency.code') . "</CurrencyCode>";
$package_schema .= "<MonetaryValue>" . $package->price . "</MonetaryValue>";
$package_schema .= "</InsuredValue>";
$package_schema .= "</PackageServiceOptions>";
}
$package_schema .= "</Package>";
}
}
$schema = uc_ups_access_request() . "\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<RatingServiceSelectionRequest xml:lang=\"en-US\">\n <Request>\n <TransactionReference>\n <CustomerContext>Complex Rate Request</CustomerContext>\n <XpciVersion>1.0001</XpciVersion>\n </TransactionReference>\n <RequestAction>Rate</RequestAction>\n <RequestOption>rate</RequestOption>\n </Request>\n <PickupType>\n <Code>" . $ups_config
->get('pickup_type') . "</Code>\n </PickupType>\n <CustomerClassification>\n <Code>" . $ups_config
->get('classification') . "</Code>\n </CustomerClassification>\n <Shipment>\n <Shipper>\n <ShipperNumber>" . $ups_config
->get('shipper_number') . "</ShipperNumber>\n <Address>\n <City>" . $store_config
->get('address.city') . "</City>\n <StateProvinceCode>{$shipper_zone}</StateProvinceCode>\n <PostalCode>{$shipper_zip}</PostalCode>\n <CountryCode>{$shipper_country}</CountryCode>\n </Address>\n </Shipper>\n <ShipTo>\n <Address>\n <StateProvinceCode>{$shipto_zone}</StateProvinceCode>\n <PostalCode>{$shipto_zip}</PostalCode>\n <CountryCode>{$shipto_country}</CountryCode>\n ";
if ($ups_config
->get('residential_quotes')) {
$schema .= "<ResidentialAddressIndicator/>\n ";
}
$schema .= "</Address>\n </ShipTo>\n <ShipFrom>\n <Address>\n <StateProvinceCode>{$shipfrom_zone}</StateProvinceCode>\n <PostalCode>{$shipfrom_zip}</PostalCode>\n <CountryCode>{$shipfrom_country}</CountryCode>\n </Address>\n </ShipFrom>\n <ShipmentWeight>\n <UnitOfMeasurement>\n <Code>{$units}</Code>\n <Description>{$unit_name}</Description>\n </UnitOfMeasurement>\n <Weight>" . number_format($shipment_weight, 1, '.', '') . "</Weight>\n </ShipmentWeight>\n <Service>\n <Code>{$service['code']}</Code>\n <Description>{$service['description']}</Description>\n </Service>\n ";
$schema .= $package_schema;
if ($ups_config
->get('negotiated_rates')) {
$schema .= "<RateInformation>\n <NegotiatedRatesIndicator/>\n </RateInformation>";
}
$schema .= "</Shipment>\n</RatingServiceSelectionRequest>";
return $schema;
}
/**
* 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.
*
* @param array $products
* Array of cart contents.
* @param $details
* Order details other than product information.
* @param $method
* The shipping method to create the quote.
*
* @return array
* JSON object containing rate, error, and debugging information.
*/
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;
}
/**
* Constructs a void shipment request.
*
* @param string $shipment_number
* The UPS shipment tracking number.
* @param array $tracking_numbers
* Array of tracking numbers for individual packages in the shipment.
* Optional for shipments of only one package, as they have the same tracking
* number.
*
* @return string
* XML VoidShipmentRequest message.
*/
function uc_ups_void_shipment_request($shipment_number, array $tracking_numbers = []) {
$customer_context = t('Void shipment @ship_number and tracking numbers @track_list', [
'@ship_number' => $shipment_number,
'@track_list' => implode(', ', $tracking_numbers),
]);
$xml = new \XMLWriter();
$xml
->openMemory();
$xml
->startDocument('1.0', 'UTF-8');
$xml
->startElement('VoidShipmentRequest');
$xml
->writeAttribute('xml:lang', 'en-US');
$xml
->startElement('Request');
$xml
->writeElement('RequestAction', 'Void');
$xml
->startElement('TransactionReference');
$xml
->writeElement('CustomerContext', $customer_context);
$xml
->writeElement('XpciVersion', '1.0');
$xml
->endElement();
// TransactionReference
$xml
->endElement();
// Request
$xml
->startElement('ExpandedVoidShipment');
$xml
->writeElement('ShipmentIdentificationNumber', $shipment_number);
foreach ($tracking_numbers as $number) {
$xml
->writeElement('TrackingNumber', $number);
}
$xml
->endElement();
// ExpandedVoidShipment
$xml
->endElement();
// VoidShipmentRequest
$xml
->endDocument();
return uc_ups_access_request() . $xml
->outputMemory(TRUE);
}
/**
* Instructs UPS to cancel (in whole or in part) a shipment.
*
* @param string $shipment_number
* The UPS shipment tracking number.
* @param array $tracking_numbers
* Array of tracking numbers for individual packages in the shipment.
* Optional for shipments of only one package, as they have the same tracking
* number.
*
* @return bool
* TRUE if the shipment or packages were successfully voided.
*/
function uc_ups_void_shipment($shipment_number, array $tracking_numbers = []) {
$success = FALSE;
$request = uc_ups_void_shipment_request($shipment_number, $tracking_numbers);
$ups_config = \Drupal::config('uc_ups.settings');
$resp = \Drupal::httpClient()
->post($ups_config
->get('connection_address') . 'Void', NULL, $request)
->send();
$response = new \SimpleXMLElement($resp
->getBody(TRUE));
if (isset($response->Response)) {
if (isset($response->Response->ResponseStatusCode)) {
$success = (string) $response->Response->ResponseStatusCode;
}
if (isset($response->Response->Error)) {
foreach ($response->Response->Error as $error) {
\Drupal::messenger()
->addError((string) $error->ErrorSeverity . ' ' . (string) $error->ErrorCode . ': ' . (string) $error->ErrorDescription);
}
}
}
if (isset($response->Status)) {
if (isset($response->Status->StatusType)) {
$success = (string) $response->Status->StatusType->Code;
}
}
return (bool) $success;
}
Functions
Name![]() |
Description |
---|---|
uc_ups_access_request | Prepares XML access request string. |
uc_ups_cron | Implements hook_cron(). |
uc_ups_form_node_form_alter | Implements hook_form_BASE_FORM_ID_alter() for node_form(). |
uc_ups_menu | Implements hook_menu(). |
uc_ups_node_delete | Implements hook_node_delete(). |
uc_ups_node_insert | Implements hook_node_insert(). |
uc_ups_node_load | Implements hook_node_load(). |
uc_ups_node_revision_delete | Implements hook_node_revision_delete(). |
uc_ups_node_update | Implements hook_node_update(). |
uc_ups_product_alter_validate | Validation handler for UPS product fields. |
uc_ups_quote | Callback for retrieving a UPS shipping quote. |
uc_ups_shipping_quote | Constructs an XML quote request. |
uc_ups_theme | Implements hook_theme(). |
uc_ups_uc_shipping_method | Implements hook_uc_shipping_method(). |
uc_ups_uc_shipping_type | Implements hook_uc_shipping_type(). |
uc_ups_uc_store_status | Implements hook_uc_store_status(). |
uc_ups_void_shipment | Instructs UPS to cancel (in whole or in part) a shipment. |
uc_ups_void_shipment_request | Constructs a void shipment request. |