You are here

uc_usps.module in Ubercart 6.2

Shipping quote method module that receives quotes from the United States Postal Service via XML web service.

File

shipping/uc_usps/uc_usps.module
View source
<?php

/**
 * @file
 * Shipping quote method module that receives quotes from the United States
 * Postal Service via XML web service.
 */

/******************************************************************************
 * Drupal Hooks                                                               *
 ******************************************************************************/

/**
 * Implements hook_menu().
 */
function uc_usps_menu() {
  $items = array();
  $items['admin/store/settings/quotes/methods/usps'] = array(
    'title' => 'USPS',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'uc_usps_admin_settings',
    ),
    'access arguments' => array(
      'configure quotes',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'uc_usps.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function uc_usps_theme() {
  return array(
    'uc_usps_option_label' => array(
      'arguments' => array(
        'service' => NULL,
        'packages' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_form_alter().
 *
 * Add package type to products.
 *
 * @see uc_product_form()
 */
function uc_usps_form_alter(&$form, $form_state, $form_id) {
  if (uc_product_is_product_form($form)) {
    $node = $form['#node'];
    $enabled = variable_get('uc_quote_enabled', array());
    $weight = variable_get('uc_quote_method_weight', array(
      'usps' => 0,
      'usps_intl' => 1,
    ));
    $form['shipping']['usps'] = array(
      '#type' => 'fieldset',
      '#title' => t('USPS product description'),
      '#collapsible' => TRUE,
      '#collapsed' => $enabled['usps'] == FALSE || uc_product_get_shipping_type($node) != 'small_package',
      '#weight' => $weight['usps'],
      '#tree' => TRUE,
    );
    $form['shipping']['usps']['container'] = array(
      '#type' => 'select',
      '#title' => t('Package type'),
      '#options' => _uc_usps_pkg_types(),
      '#default_value' => isset($node->usps['container']) ? $node->usps['container'] : 'VARIABLE',
    );
  }
}

/**
 * Implements hook_nodeapi().
 */
function uc_usps_nodeapi(&$node, $op) {
  if (uc_product_is_product($node->type)) {
    switch ($op) {
      case 'insert':
      case 'update':
        if (isset($node->usps)) {
          $usps_values = $node->usps;
          if (!$node->revision) {
            db_query("DELETE FROM {uc_usps_products} WHERE vid = %d", $node->vid);
          }
          db_query("INSERT INTO {uc_usps_products} (vid, nid, container) VALUES (%d, %d, '%s')", $node->vid, $node->nid, $usps_values['container']);
        }
        break;
      case 'load':
        if (uc_product_get_shipping_type($node) == 'small_package') {
          return array(
            'usps' => db_fetch_array(db_query("SELECT * FROM {uc_usps_products} WHERE vid = %d", $node->vid)),
          );
        }
        break;
      case 'delete':
        db_query("DELETE FROM {uc_usps_products} WHERE nid = %d", $node->nid);
        break;
      case 'delete revision':
        db_query("DELETE FROM {uc_usps_products} WHERE vid = %d", $node->vid);
        break;
    }
  }
}

/******************************************************************************
 * Conditional Actions Hooks                                                  *
 ******************************************************************************/

/**
 * Implements hook_ca_predicate().
 *
 * Connect USPS quote action and event.
 */
function uc_usps_ca_predicate() {
  $enabled = variable_get('uc_quote_enabled', array());
  $quote_action = array(
    '#name' => 'uc_quote_action_get_quote',
    '#title' => t('Fetch a shipping quote'),
    '#argument_map' => array(
      'order' => 'order',
      'method' => 'method',
    ),
  );

  // Domestic areas include U.S., American Samoa, Guam, Puerto Rico, and the Virgin Islands
  $countries = array(
    16,
    316,
    630,
    840,
    850,
  );
  $predicates = array(
    'uc_usps_get_quote' => array(
      '#title' => t('Shipping quote from USPS'),
      '#trigger' => 'get_quote_from_usps',
      '#class' => 'uc_usps',
      '#status' => $enabled['usps'],
      '#conditions' => array(
        '#operator' => 'AND',
        '#conditions' => array(
          array(
            '#name' => 'uc_order_condition_delivery_country',
            '#title' => t('Is in domestic US areas (US, AS, GU, PR, VI)'),
            '#argument_map' => array(
              'order' => 'order',
            ),
            '#settings' => array(
              'countries' => $countries,
            ),
          ),
        ),
      ),
      '#actions' => array(
        $quote_action,
      ),
    ),
    'uc_usps_get_env_quote' => array(
      '#title' => t('Shipping quote from USPS'),
      '#trigger' => 'get_quote_from_usps_env',
      '#class' => 'uc_usps',
      '#status' => $enabled['usps_env'],
      '#conditions' => array(
        '#operator' => 'AND',
        '#conditions' => array(
          array(
            '#name' => 'uc_order_condition_delivery_country',
            '#title' => t('Is in domestic US areas (US, AS, GU, PR, VI)'),
            '#argument_map' => array(
              'order' => 'order',
            ),
            '#settings' => array(
              'countries' => $countries,
            ),
          ),
        ),
      ),
      '#actions' => array(
        $quote_action,
      ),
    ),
    'uc_usps_get_intl_quote' => array(
      '#title' => t('Shipping quote from USPS Intl.'),
      '#trigger' => 'get_quote_from_usps_intl',
      '#class' => 'uc_usps',
      '#status' => $enabled['usps_intl'],
      '#conditions' => array(
        '#operator' => 'AND',
        '#conditions' => array(
          array(
            '#name' => 'uc_order_condition_delivery_country',
            '#title' => t('Is not in domestic US areas (US, AS, GU, PR, VI)'),
            '#argument_map' => array(
              'order' => 'order',
            ),
            '#settings' => array(
              'negate' => TRUE,
              'countries' => $countries,
            ),
          ),
        ),
      ),
      '#actions' => array(
        $quote_action,
      ),
    ),
    'uc_usps_get_intl_env_quote' => array(
      '#title' => t('Shipping quote from USPS Intl.'),
      '#trigger' => 'get_quote_from_usps_intl_env',
      '#class' => 'uc_usps',
      '#status' => $enabled['usps_intl_env'],
      '#conditions' => array(
        '#operator' => 'AND',
        '#conditions' => array(
          array(
            '#name' => 'uc_order_condition_delivery_country',
            '#title' => t('Is not in domestic US areas (US, AS, GU, PR, VI)'),
            '#argument_map' => array(
              'order' => 'order',
            ),
            '#settings' => array(
              'negate' => TRUE,
              'countries' => $countries,
            ),
          ),
        ),
      ),
      '#actions' => array(
        $quote_action,
      ),
    ),
  );
  return $predicates;
}

/******************************************************************************
 * Ubercart Hooks                                                             *
 ******************************************************************************/

/**
 * Implements hook_shipping_type().
 */
function uc_usps_shipping_type() {
  $weight = variable_get('uc_quote_type_weight', array(
    'envelope' => -1,
    'small_package' => 0,
  ));
  $types = array(
    'envelope' => array(
      'id' => 'envelope',
      'title' => t('Envelope'),
      'weight' => $weight['envelope'],
    ),
    'small_package' => array(
      'id' => 'small_package',
      'title' => t('Small package'),
      'weight' => $weight['small_package'],
    ),
  );
  return $types;
}

/**
 * Implements hook_shipping_method().
 */
function uc_usps_shipping_method() {
  $enabled = variable_get('uc_quote_enabled', array());
  $weight = variable_get('uc_quote_method_weight', array(
    'usps_env' => 0,
    'usps' => 0,
    'usps_intl_env' => 1,
    'usps_intl' => 1,
  ));
  $methods = array(
    'usps_env' => array(
      'id' => 'usps_env',
      'module' => 'uc_usps',
      'title' => t('U.S. Postal Service (Envelope)'),
      'quote' => array(
        'type' => 'envelope',
        'callback' => 'uc_usps_quote',
        'accessorials' => _uc_usps_env_services(),
      ),
      'enabled' => $enabled['usps_env'],
      'weight' => $weight['usps_env'],
    ),
    'usps' => array(
      'id' => 'usps',
      'module' => 'uc_usps',
      'title' => t('U.S. Postal Service (Parcel)'),
      'quote' => array(
        'type' => 'small_package',
        'callback' => 'uc_usps_quote',
        'accessorials' => _uc_usps_services(),
      ),
      'enabled' => $enabled['usps'],
      'weight' => $weight['usps'],
    ),
    'usps_intl_env' => array(
      'id' => 'usps_intl_env',
      'module' => 'uc_usps',
      'title' => t('U.S. Postal Service (Intl., Envelope)'),
      'quote' => array(
        'type' => 'envelope',
        'callback' => 'uc_usps_quote',
        'accessorials' => _uc_usps_intl_env_services(),
      ),
      'enabled' => $enabled['usps_intl_env'],
      'weight' => $weight['usps_intl_env'],
    ),
    'usps_intl' => array(
      'id' => 'usps_intl',
      'module' => 'uc_usps',
      'title' => t('U.S. Postal Service (Intl., Parcel)'),
      'quote' => array(
        'type' => 'small_package',
        'callback' => 'uc_usps_quote',
        'accessorials' => _uc_usps_intl_services(),
      ),
      'enabled' => $enabled['usps_intl'],
      'weight' => $weight['usps_intl'],
    ),
  );
  return $methods;
}

/******************************************************************************
 * Module Functions                                                           *
 ******************************************************************************/

/**
 * Callback for retrieving USPS shipping quote.
 *
 * @param $products
 *   Array of cart contents.
 * @param $details
 *   Order details other than product information.
 * @param $method
 *   The shipping method to create the quote.
 *
 * @return
 *   JSON object containing rate, error, and debugging information.
 */
function uc_usps_quote($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;

  // Country code is always needed.
  if (empty($destination->country)) {

    // Skip this shipping method.
    return array();
  }

  // Shipments to the US also need zone and postal_code.
  if ($destination->country == 840 && (empty($destination->zone) || empty($destination->postal_code))) {

    // Skip this shipping method.
    return array();
  }

  // USPS Production server.
  $connection_url = 'http://production.shippingapis.com/ShippingAPI.dll';

  // Initialize $services to prevent PHP notices here and in uc_quote.
  $services['data'] = array(
    'debug' => NULL,
    'error' => '',
  );
  $addresses = array(
    (array) variable_get('uc_quote_store_default_address', new stdClass()),
  );
  $packages = _uc_usps_package_products($products, $addresses);
  if (!count($packages)) {
    return array();
  }
  foreach ($packages as $key => $ship_packages) {
    $orig = (object) $addresses[$key];
    $orig->email = uc_store_email();
    if (strpos($method['id'], 'intl') && $destination->country != 840) {

      // Build XML for international rate request
      $request = uc_usps_intl_rate_request($ship_packages, $orig, $destination);
    }
    elseif ($destination->country == 840) {

      // Build XML for domestic rate request
      $request = uc_usps_rate_request($ship_packages, $orig, $destination);
    }
    if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) {
      $services['data']['debug'] .= htmlentities(urldecode($request)) . "<br />\n";
    }
    $result = drupal_http_request($connection_url, array(), 'POST', $request);
    if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) {
      $services['data']['debug'] .= htmlentities($result->data) . "<br />\n";
    }
    $rate_type = variable_get('uc_usps_online_rates', FALSE);
    $response = new SimpleXMLElement($result->data);

    // Map double-encoded HTML markup in service names to Unicode characters
    $service_markup = array(
      '&lt;sup&gt;&amp;reg;&lt;/sup&gt;' => '®',
      '&lt;sup&gt;&amp;trade;&lt;/sup&gt;' => '™',
      '&lt;sup&gt;&#174;&lt;/sup&gt;' => '®',
      '&lt;sup&gt;&#8482;&lt;/sup&gt;' => '™',
      '**' => '',
    );

    // Use this map to fix USPS service names
    if (strpos($method['id'], 'intl')) {

      // Find and replace markup in International service names
      foreach ($response
        ->xpath('//Service') as $service) {
        $service->SvcDescription = str_replace(array_keys($service_markup), $service_markup, $service->SvcDescription);
      }
    }
    else {

      // Find and replace markup in Domestic service names
      foreach ($response
        ->xpath('//Postage') as $postage) {
        $postage->MailService = str_replace(array_keys($service_markup), $service_markup, $postage->MailService);
      }
    }
    if (isset($response->Package)) {
      foreach ($response->Package as $package) {
        if (isset($package->Error)) {
          $services['data']['error'] .= (string) $package->Error[0]->Description . '<br />';
        }
        else {
          if (strpos($method['id'], 'intl')) {
            foreach ($package->Service as $service) {
              $id = (string) $service['ID'];
              $services[$id]['label'] = t('U.S.P.S. @service', array(
                '@service' => (string) $service->SvcDescription,
              ));

              // Markup rate before customer sees it
              if (!isset($services[$id]['rate'])) {
                $services[$id]['rate'] = 0;
              }
              $services[$id]['rate'] += uc_usps_markup((string) $service->Postage);
            }
          }
          else {
            foreach ($package->Postage as $postage) {
              $classid = (string) $postage['CLASSID'];
              if ($classid === '0') {
                if ((string) $postage->MailService == "First-Class Mail® Parcel") {
                  $classid = 'zeroParcel';
                }
                elseif ((string) $postage->MailService == "First-Class Mail® Letter") {
                  $classid = 'zeroFlat';
                }
                else {
                  $classid = 'zero';
                }
              }
              if (!isset($services[$classid]['rate'])) {
                $services[$classid]['rate'] = 0;
              }
              $services[$classid]['label'] = t('U.S.P.S. @service', array(
                '@service' => (string) $postage->MailService,
              ));

              // Markup rate before customer sees it
              // Rates are stored differently if ONLINE $rate_type is requested.
              // First Class doesn't have online rates, so if CommercialRate
              // is missing use Rate instead
              if ($rate_type && !empty($postage->CommercialRate)) {
                $services[$classid]['rate'] += uc_usps_markup((string) $postage->CommercialRate);
              }
              else {
                $services[$classid]['rate'] += uc_usps_markup((string) $postage->Rate);
              }
            }
          }
        }
      }
    }
  }
  $method_services = 'uc_' . $method['id'] . '_services';
  $usps_services = array_filter(variable_get($method_services, array_keys(call_user_func('_' . $method_services))));
  foreach ($services as $service => $quote) {
    if ($service != 'data' && !in_array($service, $usps_services)) {
      unset($services[$service]);
    }
  }
  $context = array(
    'revision' => 'themed',
    'type' => 'amount',
  );
  foreach ($services as $key => $quote) {
    if (isset($quote['rate'])) {
      $context['subject']['quote'] = $quote;
      $context['revision'] = 'altered';
      $services[$key]['rate'] = uc_price($quote['rate'], $context);
      $context['revision'] = 'formatted';
      $services[$key]['format'] = uc_price($quote['rate'], $context);
      $services[$key]['option_label'] = theme('uc_usps_option_label', $quote['label'], $packages);
    }
  }

  // Separate debug data out of $services.  This is necessary because
  // $services['data'] is not sortable by a 'rate' key, so it has to be
  // kept separate from the reset of the $services array until after the sort.
  $debug_data = $services['data'];
  unset($services['data']);
  uasort($services, 'uc_quote_price_sort');
  if (isset($debug_data['debug']) || isset($debug_data['error']) && count($debug_data['error'])) {
    $services['data'] = $debug_data;
  }
  return $services;
}

/**
 * Theme function to format the USPS service name and rate amount line-item
 * shown to the customer.
 *
 * @param $service
 *   The USPS service name.
 * @param $packages
 *   Package information.
 *
 * @ingroup themeable
 */
function theme_uc_usps_option_label($service, $packages) {

  // Start with USPS logo
  $output = theme('image', drupal_get_path('module', 'uc_usps') . '/uc_usps_logo.gif', t('U.S.P.S.'), '', array(
    'class' => 'usps-logo',
  ));

  // Add USPS service name, removing any 'U.S.P.S.' prefix.
  $output .= preg_replace('/^U\\.S\\.P\\.S\\./', '', $service);

  // Add package information
  $output .= ' (' . format_plural(count($packages), '1 package', '@count packages') . ')';
  return $output;
}

/**
 * Constructs a quote request for domestic shipments.
 *
 * @param $packages
 *   Array of packages received from the cart.
 * @param $origin
 *   Delivery origin address information.
 * @param $destination
 *   Delivery destination address information.
 *
 * @return
 *   RateV4Request XML document to send to USPS
 */
function uc_usps_rate_request($packages, $origin, $destination) {
  $request = '<RateV4Request USERID="' . variable_get('uc_usps_user_id', '') . '">';
  $request .= '<Revision>2</Revision>';
  $rate_type = variable_get('uc_usps_online_rates', FALSE);
  $package_id = 0;
  foreach ($packages as $package) {
    $qty = $package->qty;
    for ($i = 0; $i < $qty; $i++) {
      $request .= '<Package ID="' . $package_id . '">' . '<Service>' . ($rate_type ? 'ONLINE' : 'ALL') . '</Service>' . '<ZipOrigination>' . substr(trim($origin->postal_code), 0, 5) . '</ZipOrigination>' . '<ZipDestination>' . substr(trim($destination->postal_code), 0, 5) . '</ZipDestination>' . '<Pounds>' . intval($package->pounds) . '</Pounds>' . '<Ounces>' . number_format($package->ounces, 1, '.', '') . '</Ounces>' . '<Container>' . $package->container . '</Container>' . '<Size>' . $package->size . '</Size>' . '<Width>' . $package->width . '</Width>' . '<Length>' . $package->length . '</Length>' . '<Height>' . $package->height . '</Height>' . '<Girth>' . $package->girth . '</Girth>' . '<Machinable>' . ($package->machinable ? 'True' : 'False') . '</Machinable>' . '</Package>';
      $package_id++;
    }
  }
  $request .= '</RateV4Request>';
  return 'API=RateV4&XML=' . drupal_urlencode($request);
}

/**
 * Constructs a quote request for international shipments.
 *
 * @param $packages
 *   Array of packages received from the cart.
 * @param $origin
 *   Delivery origin address information.
 * @param $destination
 *   Delivery destination address information.
 *
 * @return
 *   IntlRateRequest XML document to send to USPS
 */
function uc_usps_intl_rate_request($packages, $origin, $destination) {
  module_load_include('inc', 'uc_usps', 'uc_usps.countries');
  $request = '<IntlRateV2Request USERID="' . variable_get('uc_usps_user_id', '') . '">';
  $request .= '<Revision>2</Revision>';

  // USPS does not use ISO 3166 country name in some cases so we
  // need to transform ISO country name into one USPS understands.
  $shipto_country = uc_usps_country_map($destination->country);
  $package_id = 0;
  foreach ($packages as $package) {
    $qty = $package->qty;
    for ($i = 0; $i < $qty; $i++) {
      $request .= '<Package ID="' . $package_id . '">' . '<Pounds>' . intval($package->pounds) . '</Pounds>' . '<Ounces>' . ceil($package->ounces) . '</Ounces>' . '<MailType>All</MailType>' . '<ValueOfContents>' . $package->price . '</ValueOfContents>' . '<Country>' . $shipto_country . '</Country>' . '<Container>' . 'RECTANGULAR' . '</Container>' . '<Size>' . 'REGULAR' . '</Size>' . '<Width>' . '</Width>' . '<Length>' . '</Length>' . '<Height>' . '</Height>' . '<Girth>' . '</Girth>' . '<OriginZip>' . substr(trim($origin->postal_code), 0, 5) . '</OriginZip>';

      // Check if we need to add any special services to this package
      if (variable_get('uc_usps_insurance', FALSE)) {
        $request .= '<ExtraServices><ExtraService>1</ExtraService></ExtraServices>';
      }

      // Close off Package tag
      $request .= '</Package>';
      $package_id++;
    }
  }
  $request .= '</IntlRateV2Request>';
  $request = 'API=IntlRateV2&XML=' . drupal_urlencode($request);
  return $request;
}

/**
 * Modifies the rate received from USPS before displaying to the customer.
 *
 * @param $rate
 *   Shipping rate without any rate markup.
 *
 * @return
 *   Shipping rate after markup.
 */
function uc_usps_markup($rate) {
  $markup = variable_get('uc_usps_markup', '0');
  $type = variable_get('uc_usps_markup_type', 'percentage');
  if (is_numeric(trim($markup))) {
    switch ($type) {
      case 'percentage':
        return $rate + $rate * floatval(trim($markup)) / 100;
      case 'multiplier':
        return $rate * floatval(trim($markup));
      case 'currency':
        return $rate + floatval(trim($markup));
    }
  }
  else {
    return $rate;
  }
}

/**
 * Organizes products into packages for shipment.
 *
 * @param $products
 *   An array of product objects as they are represented in the cart or order.
 * @param &$addresses
 *   Reference to an array of addresses which are the pickup locations of each
 *   package. They are determined from the shipping addresses of their
 *   component products.
 *
 * @return
 *   Array of packaged products. Packages are separated by shipping address and
 *   weight or quantity limits imposed by the shipping method or the products.
 */
function _uc_usps_package_products($products, &$addresses) {
  $last_key = 0;
  $packages = array();
  if (variable_get('uc_usps_all_in_one', TRUE) && count($products) > 1) {

    // "All in one" packaging strategy
    // Only need to do this if more than one product line item in order
    foreach ($products as $product) {
      if ($product->nid) {
        $address = (array) uc_quote_get_default_shipping_address($product->nid);
        $key = array_search($address, $addresses);
        if ($key === FALSE) {
          $addresses[++$last_key] = $address;
          $key = $last_key;
          $packages[$key][0] = new stdClass();
        }
      }
      $packages[$key][0]->price += $product->price * $product->qty;
      $packages[$key][0]->weight += $product->weight * $product->qty * uc_weight_conversion($product->weight_units, 'lb');
    }
    foreach ($packages as $key => $package) {
      $packages[$key][0]->pounds = floor($package[0]->weight);
      $packages[$key][0]->ounces = LB_TO_OZ * ($package[0]->weight - $packages[$key][0]->pounds);
      $packages[$key][0]->container = 'VARIABLE';
      $packages[$key][0]->size = 'REGULAR';

      // Packages are "machinable" if heavier than 6oz. and less than 35lbs.
      $packages[$key][0]->machinable = ($packages[$key][0]->pounds == 0 ? $packages[$key][0]->ounces >= 6 : TRUE) && $packages[$key][0]->pounds <= 35 && ($packages[$key][0]->pounds == 35 ? $packages[$key][0]->ounces == 0 : TRUE);
      $packages[$key][0]->qty = 1;
    }
  }
  else {

    // !variable_get('uc_usps_all_in_one', TRUE) || count($products) = 1
    // "Each in own" packaging strategy, or only one product line item in order
    foreach ($products as $product) {
      if ($product->nid) {
        $address = (array) uc_quote_get_default_shipping_address($product->nid);
        $key = array_search($address, $addresses);
        if ($key === FALSE) {
          $addresses[++$last_key] = $address;
          $key = $last_key;
        }
      }
      if (!$product->pkg_qty) {
        $product->pkg_qty = 1;
      }
      $num_of_pkgs = (int) ($product->qty / $product->pkg_qty);
      if ($num_of_pkgs) {
        $package = drupal_clone($product);
        $package->description = $product->model;
        $weight = $product->weight * $product->pkg_qty;
        switch ($product->weight_units) {
          case 'g':

            // Convert to kg and fall through
            $weight = $weight * G_TO_KG;
          case 'kg':

            // Convert to lb and fall through
            $weight = $weight * KG_TO_LB;
          case 'lb':
            $package->pounds = floor($weight);
            $package->ounces = LB_TO_OZ * ($weight - $package->pounds);
            break;
          case 'oz':
            $package->pounds = floor($weight * OZ_TO_LB);
            $package->ounces = $weight - $package->pounds * LB_TO_OZ;
            break;
        }
        $package->container = $product->usps['container'];
        $length_conversion = uc_length_conversion($product->length_units, 'in');
        $package->length = max($product->length, $product->width) * $length_conversion;
        $package->width = min($product->length, $product->width) * $length_conversion;
        $package->height = $product->height * $length_conversion;
        if ($package->length < $package->height) {
          list($package->length, $package->height) = array(
            $package->height,
            $package->length,
          );
        }
        $package->girth = 2 * $package->width + 2 * $package->height;
        $package->size = $package->length <= 12 ? 'REGULAR' : 'LARGE';
        $package->machinable = $package->length >= 6 && $package->length <= 34 && $package->width >= 0.25 && $package->width <= 17 && $package->height >= 3.5 && $package->height <= 17 && ($package->pounds == 0 ? $package->ounces >= 6 : TRUE) && $package->pounds <= 35 && ($package->pounds == 35 ? $package->ounces == 0 : TRUE);
        $package->price = $product->price * $product->pkg_qty;
        $package->qty = $num_of_pkgs;
        $packages[$key][] = $package;
      }
      $remaining_qty = $product->qty % $product->pkg_qty;
      if ($remaining_qty) {
        $package = drupal_clone($product);
        $package->description = $product->model;
        $weight = $product->weight * $remaining_qty;
        switch ($product->weight_units) {
          case 'g':

            // Convert to kg and fall through
            $weight = $weight * G_TO_KG;
          case 'kg':

            // Convert to lb and fall through
            $weight = $weight * KG_TO_LB;
          case 'lb':
            $package->pounds = floor($weight);
            $package->ounces = LB_TO_OZ * ($weight - $package->pounds);
            break;
          case 'oz':
            $package->pounds = floor($weight * OZ_TO_LB);
            $package->ounces = $weight - $package->pounds * LB_TO_OZ;
            break;
        }
        $package->container = $product->usps['container'];
        $length_conversion = uc_length_conversion($product->length_units, 'in');
        $package->length = max($product->length, $product->width) * $length_conversion;
        $package->width = min($product->length, $product->width) * $length_conversion;
        $package->height = $product->height * $length_conversion;
        if ($package->length < $package->height) {
          list($package->length, $package->height) = array(
            $package->height,
            $package->length,
          );
        }
        $package->girth = 2 * $package->width + 2 * $package->height;
        $package->size = $package->length <= 12 ? 'REGULAR' : 'LARGE';
        $package->machinable = $package->length >= 6 && $package->length <= 34 && $package->width >= 0.25 && $package->width <= 17 && $package->height >= 3.5 && $package->height <= 17 && ($package->pounds == 0 ? $package->ounces >= 6 : TRUE) && $package->pounds <= 35 && ($package->pounds == 35 ? $package->ounces == 0 : TRUE);
        $package->price = $product->price * $remaining_qty;
        $package->qty = 1;
        $packages[$key][] = $package;
      }
    }
  }
  return $packages;
}

/**
 * Convenience function for select form elements.
 */
function _uc_usps_pkg_types() {
  return array(
    'VARIABLE' => t('Variable'),
    'FLAT RATE BOX' => t('Flat rate box'),
    'LG FLAT RATE BOX' => t('Large flat rate box'),
    'FLAT RATE ENVELOPE' => t('Flat rate envelope'),
    'RECTANGULAR' => t('Rectangular'),
    'NONRECTANGULAR' => t('Non-rectangular'),
  );
}

/**
 * Maps envelope shipment services to their IDs.
 */
function _uc_usps_env_services() {
  return array(
    'zero' => t('U.S.P.S. First-Class Mail Postcard'),
    'zeroFlat' => t('U.S.P.S. First-Class Mail Letter'),
    12 => t('U.S.P.S. First-Class Postcard Stamped'),
    1 => t('U.S.P.S. Priority Mail'),
    16 => t('U.S.P.S. Priority Mail Flat-Rate Envelope'),
    3 => t('U.S.P.S. Express Mail'),
    13 => t('U.S.P.S. Express Mail Flat-Rate Envelope'),
    23 => t('U.S.P.S. Express Mail Sunday/Holiday Guarantee'),
    25 => t('U.S.P.S. Express Mail Flat-Rate Envelope Sunday/Holiday Guarantee'),
  );
}

/**
 * Maps parcel shipment services to their IDs.
 */
function _uc_usps_services() {
  return array(
    'zeroFlat' => t('U.S.P.S. First-Class Mail Letter'),
    'zeroParcel' => t('U.S.P.S. First-Class Mail Parcel'),
    1 => t('U.S.P.S. Priority Mail'),
    28 => t('U.S.P.S. Priority Mail Small Flat-Rate Box'),
    17 => t('U.S.P.S. Priority Mail Regular/Medium Flat-Rate Box'),
    22 => t('U.S.P.S. Priority Mail Large Flat-Rate Box'),
    3 => t('U.S.P.S. Express Mail'),
    23 => t('U.S.P.S. Express Mail Sunday/Holiday Guarantee'),
    4 => t('U.S.P.S. Parcel Post'),
    5 => t('U.S.P.S. Bound Printed Matter'),
    6 => t('U.S.P.S. Media Mail'),
    7 => t('U.S.P.S. Library'),
  );
}

/**
 * Maps international envelope services to their IDs.
 */
function _uc_usps_intl_env_services() {
  return array(
    13 => t('First Class Mail International Letter'),
    14 => t('First Class Mail International Large Envelope'),
    2 => t('Priority Mail International'),
    8 => t('Priority Mail International Flat Rate Envelope'),
    4 => t('Global Express Guaranteed'),
    12 => t('GXG Envelopes'),
    1 => t('Express Mail International (EMS)'),
    10 => t('Express Mail International (EMS) Flat Rate Envelope'),
  );
}

/**
 * Maps international parcel services to their IDs.
 */
function _uc_usps_intl_services() {
  return array(
    15 => t('First Class Mail International Package'),
    2 => t('Priority Mail International'),
    16 => t('Priority Mail International Small Flat-Rate Box'),
    9 => t('Priority Mail International Regular/Medium Flat-Rate Box'),
    11 => t('Priority Mail International Large Flat-Rate Box'),
    4 => t('Global Express Guaranteed'),
    6 => t('Global Express Guaranteed Non-Document Rectangular'),
    7 => t('Global Express Guaranteed Non-Document Non-Rectangular'),
    1 => t('Express Mail International (EMS)'),
  );
}

Functions

Namesort descending Description
theme_uc_usps_option_label Theme function to format the USPS service name and rate amount line-item shown to the customer.
uc_usps_ca_predicate Implements hook_ca_predicate().
uc_usps_form_alter Implements hook_form_alter().
uc_usps_intl_rate_request Constructs a quote request for international shipments.
uc_usps_markup Modifies the rate received from USPS before displaying to the customer.
uc_usps_menu Implements hook_menu().
uc_usps_nodeapi Implements hook_nodeapi().
uc_usps_quote Callback for retrieving USPS shipping quote.
uc_usps_rate_request Constructs a quote request for domestic shipments.
uc_usps_shipping_method Implements hook_shipping_method().
uc_usps_shipping_type Implements hook_shipping_type().
uc_usps_theme Implements hook_theme().
_uc_usps_env_services Maps envelope shipment services to their IDs.
_uc_usps_intl_env_services Maps international envelope services to their IDs.
_uc_usps_intl_services Maps international parcel services to their IDs.
_uc_usps_package_products Organizes products into packages for shipment.
_uc_usps_pkg_types Convenience function for select form elements.
_uc_usps_services Maps parcel shipment services to their IDs.