'UPS', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_ups_admin_settings'), 'access arguments' => array('configure quotes'), 'type' => MENU_LOCAL_TASK, 'file' => 'uc_ups.admin.inc', ); $items['admin/store/orders/%uc_order/shipments/ups'] = array( 'title' => 'UPS shipment', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_ups_confirm_shipment', 3), 'access arguments' => array('fulfill orders'), 'file' => 'uc_ups.admin.inc', ); $items['admin/store/orders/%uc_order/shipments/labels/ups'] = array( 'page callback' => 'theme', 'page arguments' => array('uc_ups_label_image'), 'access arguments' => array('fulfill orders'), 'file' => 'uc_ups.admin.inc', ); return $items; } /** * Implements hook_init(). */ function uc_ups_init() { drupal_add_css(drupal_get_path('module', 'uc_ups') . '/uc_ups.css'); } /** * Implements hook_theme(). */ function uc_ups_theme() { return array( 'uc_ups_confirm_shipment' => array( 'render element' => 'form', 'file' => 'uc_ups.admin.inc', ), 'uc_ups_label_image' => array( 'variables' => array(), 'file' => 'uc_ups.admin.inc', ), 'uc_ups_option_label' => array( 'variables' => array('service' => NULL), ), ); } /** * Implements hook_form_alter(). * * Add package type to products. * * @see uc_product_form() * @see uc_ups_product_alter_validate() */ function uc_ups_form_alter(&$form, &$form_state, $form_id) { if (uc_product_is_product_form($form)) { $node = $form['#node']; $enabled = variable_get('uc_quote_enabled', array()) + array('ups' => FALSE); $weight = variable_get('uc_quote_method_weight', array()) + array('ups' => 0); $ups = array( '#type' => 'fieldset', '#title' => t('UPS product description'), '#collapsible' => TRUE, '#collapsed' => ($enabled['ups'] == FALSE || uc_product_get_shipping_type($node) != 'small_package'), '#weight' => $weight['ups'], '#tree' => TRUE, ); $ups['pkg_type'] = array( '#type' => 'select', '#title' => t('Package type'), '#options' => _uc_ups_pkg_types(), '#default_value' => isset($node->ups['pkg_type']) ? $node->ups['pkg_type'] : variable_get('uc_ups_pkg_type', '02'), ); $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, &$form_state) { if ($form_state['values']['shippable'] && ($form_state['values']['shipping_type'] == 'small_package' || (empty($form_state['values']['shipping_type']) && variable_get('uc_store_shipping_type', 'small_package') == 'small_package'))) { if ($form_state['values']['ups']['pkg_type'] == '02' && (empty($form_state['values']['dim_length']) || empty($form_state['values']['dim_width']) || empty($form_state['values']['dim_height']))) { form_set_error('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->type)) { if (isset($node->ups)) { $ups_values = $node->ups; if (!$node->revision) { db_delete('uc_ups_products') ->condition('vid', $node->vid) ->execute(); } db_insert('uc_ups_products') ->fields(array( 'vid' => $node->vid, 'nid' => $node->nid, 'pkg_type' => $ups_values['pkg_type'], )) ->execute(); } } } /** * Implements hook_node_load(). */ function uc_ups_node_load($nodes, $types) { $product_types = array_intersect(uc_product_types(), $types); if (empty($product_types)) { return; } $vids = array(); $shipping_type = variable_get('uc_store_shipping_type', 'small_package'); $shipping_types = db_query("SELECT id, shipping_type FROM {uc_quote_shipping_types} WHERE id_type = :type AND id IN (:ids)", array(':type' => 'product', ':ids' => array_keys($nodes)))->fetchAllKeyed(); foreach ($nodes as $nid => $node) { if (!in_array($node->type, $product_types)) { continue; } if (isset($shipping_types[$nid])) { $node->shipping_type = $shipping_types[$nid]; } else { $node->shipping_type = $shipping_type; } if ($node->shipping_type == 'small_package') { $vids[$nid] = $node->vid; } } if ($vids) { $result = db_query("SELECT * FROM {uc_ups_products} WHERE vid IN (:vids)", array(':vids' => $vids), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $ups) { $nodes[$ups['nid']]->ups = $ups; } } } /** * Implements hook_node_delete(). */ function uc_ups_node_delete($node) { db_delete('uc_ups_products') ->condition('nid', $node->nid) ->execute(); } /** * Implements hook_node_revision_delete(). */ function uc_ups_node_revision_delete($node) { db_delete('uc_ups_products') ->condition('vid', $node->vid) ->execute(); } /****************************************************************************** * Ubercart Hooks * ******************************************************************************/ /** * Implements hook_uc_shipping_type(). */ function uc_ups_uc_shipping_type() { $weight = variable_get('uc_quote_type_weight', array('small_package' => 0)); $types = array(); $types['small_package'] = array( '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 = array(); $enabled = variable_get('uc_quote_enabled', array()) + array('ups' => FALSE); $weight = variable_get('uc_quote_method_weight', array()) + array('ups' => 0); $methods['ups'] = array( 'id' => 'ups', 'module' => 'uc_ups', 'title' => t('UPS'), 'enabled' => $enabled['ups'], 'quote' => array( 'type' => 'small_package', 'callback' => 'uc_ups_quote', 'accessorials' => _uc_ups_service_list(), ), 'ship' => array( 'type' => 'small_package', 'callback' => 'uc_ups_fulfill_order', 'pkg_types' => _uc_ups_pkg_types(), ), 'cancel' => 'uc_ups_void_shipment', 'weight' => $weight['ups'], ); 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 = array(); $access = variable_get('uc_ups_access_license', '') != ''; $account = variable_get('uc_ups_shipper_number', '') != ''; $user = variable_get('uc_ups_user_id', '') != ''; $password = variable_get('uc_ups_password', '') != ''; if ($access && $account && $user && $password) { $messages[] = array('status' => 'ok', 'title' => t('UPS Online Tools'), 'desc' => t('Information needed to access UPS Online Tools has been entered.'), ); } else { $messages[] = array('status' => 'error', 'title' => t('UPS Online Tools'), 'desc' => t('More information is needed to access UPS Online Tools. Please enter it here.', array('!url' => url('admin/store/settings/quotes/methods/ups'))), ); } return $messages; } /****************************************************************************** * Module Functions * ******************************************************************************/ /** * Returns XML access request to be prepended to all requests to the * UPS webservice. */ function uc_ups_access_request() { $access = variable_get('uc_ups_access_license', ''); $user = variable_get('uc_ups_user_id', ''); $password = variable_get('uc_ups_password', ''); return " $access $user $password "; } /** * Constructs an XML quote request. * * @param $packages * Array of packages received from the cart. * @param $origin * Delivery origin address information. * @param $destination * Delivery destination address information. * @param $ups_service * UPS service code (refers to UPS Ground, Next-Day Air, etc.). * * @return * RatingServiceSelectionRequest XML document to send to UPS. */ function uc_ups_shipping_quote($packages, $origin, $destination, $ups_service) { $store['name'] = variable_get('uc_store_name', NULL); $store['owner'] = variable_get('uc_store_owner', NULL); $store['email'] = variable_get('uc_store_email', NULL); $store['email_from'] = variable_get('uc_store_email', NULL); $store['phone'] = variable_get('uc_store_phone', NULL); $store['fax'] = variable_get('uc_store_fax', NULL); $store['street1'] = variable_get('uc_store_street1', NULL); $store['street2'] = variable_get('uc_store_street2', NULL); $store['city'] = variable_get('uc_store_city', NULL); $store['zone'] = variable_get('uc_store_zone', NULL); $store['postal_code'] = variable_get('uc_store_postal_code', NULL); $store['country'] = variable_get('uc_store_country', 840); $account = variable_get('uc_ups_shipper_number', ''); $ua = explode(' ', $_SERVER['HTTP_USER_AGENT']); $user_agent = $ua[0]; $services = _uc_ups_service_list(); $service = array('code' => $ups_service, 'description' => $services[$ups_service]); $pkg_types = _uc_ups_pkg_types(); $shipper_zone = uc_get_zone_code($store['zone']); $shipper_country = uc_get_country_data(array('country_id' => $store['country'])); $shipper_country = $shipper_country[0]['country_iso_code_2']; $shipper_zip = $store['postal_code']; $shipto_zone = uc_get_zone_code($destination->zone); $shipto_country = uc_get_country_data(array('country_id' => $destination->country)); $shipto_country = $shipto_country[0]['country_iso_code_2']; $shipto_zip = $destination->postal_code; $shipfrom_zone = uc_get_zone_code($origin->zone); $shipfrom_country = uc_get_country_data(array('country_id' => $origin->country)); $shipfrom_country = $shipfrom_country[0]['country_iso_code_2']; $shipfrom_zip = $origin->postal_code; $ups_units = variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')); 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) { $qty = $package->qty; for ($i = 0; $i < $qty; $i++) { $package_type = array('code' => $package->pkg_type, 'description' => $pkg_types[$package->pkg_type]); $package_schema .= ""; $package_schema .= ""; $package_schema .= "" . $package_type['code'] . ""; $package_schema .= ""; if ($package->pkg_type == '02' && $package->length && $package->width && $package->height) { if ($package->length < $package->width) { list($package->length, $package->width) = array($package->width, $package->length); } $package_schema .= ""; $package_schema .= ""; $conversion = uc_length_conversion($package->length_units, variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))); $package_schema .= "" . strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))) . ""; $package_schema .= ""; $package_schema .= "" . number_format($package->length * $conversion, 2, '.', '') . ""; $package_schema .= "" . number_format($package->width * $conversion, 2, '.', '') . ""; $package_schema .= "" . number_format($package->height * $conversion, 2, '.', '') . ""; $package_schema .= ""; } $size = $package->length * $conversion + 2 * $conversion * ($package->width + $package->height); switch ($ups_units) { case 'in': $conversion = uc_weight_conversion($package->weight_units, 'lb'); break; case 'cm': $conversion = uc_weight_conversion($package->weight_units, 'kg'); break; } $weight = max(1, $package->weight * $conversion); $shipment_weight += $weight; $package_schema .= ""; $package_schema .= ""; $package_schema .= "$units"; $package_schema .= "$unit_name"; $package_schema .= ""; $package_schema .= "" . number_format($weight, 1, '.', '') . ""; $package_schema .= ""; if ($size > 130 && $size <= 165) { $package_schema .= ""; } if (variable_get('uc_ups_insurance', TRUE)) { $package_schema .= ""; $package_schema .= ""; $package_schema .= "" . variable_get('uc_currency_code', 'USD') . ""; $package_schema .= "" . $package->price . ""; $package_schema .= ""; $package_schema .= ""; } $package_schema .= ""; } } $schema = uc_ups_access_request() . " Complex Rate Request 1.0001 Rate rate " . variable_get('uc_ups_pickup_type', '01') . " " . variable_get('uc_ups_classification', '04') . " " . variable_get('uc_ups_shipper_number', '') . "
" . $store['city'] . " $shipper_zone $shipper_zip $shipper_country
$shipto_zone $shipto_zip $shipto_country "; if (variable_get('uc_ups_residential_quotes', 0)) { $schema .= " "; } $schema .= "
$shipfrom_zone $shipfrom_zip $shipfrom_country
$units $unit_name " . number_format($shipment_weight, 1, '.', '') . " {$service[code]} {$service[description]} "; $schema .= $package_schema; if (variable_get('uc_ups_negotiated_rates', FALSE)) { $schema .= " "; } $schema .= "
"; return $schema; } /** * Constructs an XML shippment request. * * @param $packages * Array of packages received from the cart. * @param $origin * Delivery origin address information. * @param $destination * Delivery destination address information. * @param $ups_service * UPS service code (refers to UPS Ground, Next-Day Air, etc.). * * @return * ShipConfirm XML document to send to UPS. */ function uc_ups_shipment_request($packages, $origin, $destination, $ups_service) { $store['name'] = variable_get('uc_store_name', NULL); $store['owner'] = variable_get('uc_store_owner', NULL); $store['email'] = variable_get('uc_store_email', NULL); $store['email_from'] = variable_get('uc_store_email', NULL); $store['phone'] = variable_get('uc_store_phone', NULL); $store['fax'] = variable_get('uc_store_fax', NULL); $store['street1'] = variable_get('uc_store_street1', NULL); $store['street2'] = variable_get('uc_store_street2', NULL); $store['city'] = variable_get('uc_store_city', NULL); $store['zone'] = variable_get('uc_store_zone', NULL); $store['postal_code'] = variable_get('uc_store_postal_code', NULL); $store['country'] = variable_get('uc_store_country', 840); $account = variable_get('uc_ups_shipper_number', ''); $ua = explode(' ', $_SERVER['HTTP_USER_AGENT']); $user_agent = $ua[0]; $services = _uc_ups_service_list(); $service = array('code' => $ups_service, 'description' => $services[$ups_service]); $pkg_types = _uc_ups_pkg_types(); $shipper_zone = uc_get_zone_code($store['zone']); $shipper_country = uc_get_country_data(array('country_id' => $store['country'])); $shipper_country = $shipper_country[0]['country_iso_code_2']; $shipper_zip = $store['postal_code']; $shipto_zone = uc_get_zone_code($destination->zone); $shipto_country = uc_get_country_data(array('country_id' => $destination->country)); $shipto_country = $shipto_country[0]['country_iso_code_2']; $shipto_zip = $destination->postal_code; $shipfrom_zone = uc_get_zone_code($origin->zone); $shipfrom_country = uc_get_country_data(array('country_id' => $origin->country)); $shipfrom_country = $shipfrom_country[0]['country_iso_code_2']; $shipfrom_zip = $origin->postal_code; $ups_units = variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')); $package_schema = ''; foreach ($packages as $package) { $qty = $package->qty; for ($i = 0; $i < $qty; $i++) { $package_type = array('code' => $package->pkg_type, 'description' => $pkg_types[$package->pkg_type]); $package_schema .= ""; $package_schema .= ""; $package_schema .= "" . $package_type['code'] . ""; $package_schema .= ""; if ($package->pkg_type == '02' && $package->length && $package->width && $package->height) { if ($package->length < $package->width) { list($package->length, $package->width) = array($package->width, $package->length); } $package_schema .= ""; $package_schema .= ""; $conversion = uc_length_conversion($package->length_units, variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))); $package_schema .= "" . strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))) . ""; $package_schema .= ""; $package_schema .= "" . (floor($package->length * $conversion) + 1) . ""; $package_schema .= "" . (floor($package->width * $conversion) + 1) . ""; $package_schema .= "" . (floor($package->height * $conversion) + 1) . ""; $package_schema .= ""; } $size = $package->length * $conversion + 2 * $conversion * ($package->width + $package->height); switch ($ups_units) { case 'in': $conversion = uc_weight_conversion($package->weight_units, 'lb'); break; case 'cm': $conversion = uc_weight_conversion($package->weight_units, 'kg'); break; } $weight = max(1, $package->weight * $conversion); $package_schema .= ""; $package_schema .= ""; $package_schema .= "$units"; $package_schema .= "$unit_name"; $package_schema .= ""; $package_schema .= "" . number_format($weight, 1, '.', '') . ""; $package_schema .= ""; if ($size > 130 && $size <= 165) { $package_schema .= ""; } $package_schema .= ""; $package_schema .= ""; $package_schema .= "" . variable_get('uc_currency_code', 'USD') . ""; $package_schema .= "" . number_format($package->value, 2, '.', '') . ""; $package_schema .= ""; $package_schema .= ""; $package_schema .= ""; } } $schema = uc_ups_access_request() . " Complex Rate Request 1.0001 ShipConfirm validate "; $schema .= ""; $schema .= "" . $store['name'] . ""; $schema .= "" . variable_get('uc_ups_shipper_number', '') . ""; if ($store['phone']) { $schema .= "" . $store['phone'] . ""; } if ($store['fax']) { $schema .= "" . $store['fax'] . ""; } if ($store['email']) { $schema .= "" . $store['email'] . ""; } $schema .= "
"; $schema .= "" . $store['street1'] . ""; if ($store['street2']) { $schema .= "" . $store['street2'] . ""; } $schema .= "" . $store['city'] . ""; $schema .= "$shipper_zone"; $schema .= "$shipper_zip"; $schema .= "$shipper_country"; $schema .= "
"; $schema .= "
"; $schema .= ""; $schema .= "" . $destination->company . ""; $schema .= "" . $destination->first_name . ' ' . $destination->last_name . ""; $schema .= "" . $destination->phone . ""; $schema .= "" . $destination->email . ""; $schema .= "
"; $schema .= "" . $destination->street1 . ""; if ($destination->street2) { $schema .= "" . $destination->street2 . ""; } $schema .= "" . $destination->city . ""; $schema .= "$shipto_zone"; $schema .= "$shipto_zip"; $schema .= "$shipto_country"; if ($destination->residence) { $schema .= ""; } $schema .= "
"; $schema .= "
"; $schema .= ""; $schema .= "" . $origin->company . ""; $schema .= "" . $origin->first_name . ' ' . $origin->last_name . ""; $schema .= "" . $origin->phone . ""; $schema .= "" . $origin->email . ""; $schema .= "
"; $schema .= "" . $origin->street1 . ""; if ($origin->street2) { $schema .= "" . $origin->street2 . ""; } $schema .= "" . $origin->city . ""; $schema .= "$shipfrom_zone"; $schema .= "$shipfrom_zip"; $schema .= "$shipfrom_country"; $schema .= "
"; $schema .= "
"; $schema .= ""; $schema .= ""; $schema .= ""; $schema .= "$account"; $schema .= ""; $schema .= ""; $schema .= ""; if (variable_get('uc_ups_negotiated_rates', FALSE)) { $schema .= " "; } $schema .= ""; $schema .= "{$service[code]}"; $schema .= "{$service[description]}"; $schema .= ""; $schema .= $package_schema; $schema .= "
"; $schema .= ""; $schema .= ""; $schema .= "GIF"; $schema .= ""; $schema .= ""; $schema .= "GIF"; $schema .= ""; $schema .= ""; $schema .= "
"; 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 $products * Array of cart contents. * @param $details * Order details other than product information. * * @return * JSON object containing rate, error, and debugging information. */ function uc_ups_quote($products, $details) { $quotes = array(); $method = uc_ups_uc_shipping_method(); $addresses = array(variable_get('uc_quote_store_default_address', new UcAddress())); $key = 0; $last_key = 0; $packages = array(); if (variable_get('uc_ups_all_in_one', TRUE) && 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. $address = uc_quote_get_default_shipping_address($product->nid); $key = array_search($address, $addresses); if ($key === FALSE) { // 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(); } $weight = $product->weight * $product->qty * uc_weight_conversion($product->weight_units, 'lb'); $package->weight += $weight; $package->price += $product->price * $product->qty; $conversion = uc_length_conversion($product->length_units, 'in'); $package->length = max($product->length * $conversion, $package->length); $package->width = max($product->width * $conversion, $package->width); $package->height = max($product->height * $conversion, $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 = (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 = 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 ? $product->ups['pkg_type'] : '02'; 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 ? $product->ups['pkg_type'] : '02'; if ($package->weight) { $packages[$key][] = $package; } } } } if (!count($packages)) { return array(); } $dest = (object)$details; foreach ($packages as $key => $ship_packages) { $orig = $addresses[$key]; $orig->email = variable_get('uc_store_email', ''); foreach (array_keys(array_filter(variable_get('uc_ups_services', array()))) as $ups_service) { $request = uc_ups_shipping_quote($ship_packages, $orig, $dest, $ups_service); $resp = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') . 'Rate', array( 'method' => 'POST', 'data' => $request, )); if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) { $quotes[$ups_service]['debug'] .= htmlentities($request) . '

' . htmlentities($resp->data); } $response = new SimpleXMLElement($resp->data); if (isset($response->Response->Error)) { foreach ($response->Response->Error as $error) { if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) { $quotes[$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 == variable_get('uc_currency_code', "USD")) { $rate = uc_ups_markup((string)$charge->MonetaryValue); $quotes[$ups_service]['rate'] += $rate; } } } } uasort($quotes, 'uc_quote_price_sort'); foreach ($quotes as $key => $quote) { if (isset($quote['rate'])) { $quotes[$key]['rate'] = $quote['rate']; $quotes[$key]['option_label'] = theme('uc_ups_option_label', array('service' => $method['ups']['quote']['accessorials'][$key])); } } /** * Ugly hack to work around PHP bug, details here: * http://bugs.php.net/bug.php?id=23220 * We strip out errors that look something like: * warning: fread() [function.fread]: SSL fatal protocol error in... * Copied from http://drupal.org/node/70915 and then improved by Lyle. */ $messages = drupal_set_message(); $errors = $messages['error']; $total = count($errors); for ($i = 0; $i <= $total; $i++) { if (strpos($errors[$i], 'SSL: fatal protocol error in')) { unset($_SESSION['messages']['error'][$i]); } } if (empty($_SESSION['messages']['error'])) { unset($_SESSION['messages']['error']); } db_delete('watchdog') ->condition('type', 'php') ->condition('variables', '%SSL: fatal protocol error%', 'LIKE') ->execute(); // End of ugly hack. return $quotes; } /** * Theme function to format the UPS service name and rate amount line-item * shown to the customer. * * @param $service * The UPS service name. * * @ingroup themeable */ function theme_uc_ups_option_label($variables) { $service = $variables['service']; // Start with logo as required by the UPS terms of service. $output = ' '; // Add the UPS service name. $output .= t('@service Rate', array('@service' => $service)); return $output; } /** * Shipment creation callback. * * Confirms shipment data before requesting a shipping label. * * @param $order_id * The order id for the shipment. * @param $package_ids * Array of package ids to shipped. * * @see uc_ups_fulfill_order_validate() * @see uc_ups_fulfill_order_submit() * @ingroup forms */ function uc_ups_fulfill_order($form, &$form_state, $order, $package_ids) { $pkg_types = _uc_ups_pkg_types(); $form['order_id'] = array('#type' => 'value', '#value' => $order->order_id); $packages = array(); $addresses = array(); $form['packages'] = array('#tree' => TRUE); foreach ($package_ids as $id) { $package = uc_shipping_package_load($id); if ($package) { foreach ($package->addresses as $address) { if (!in_array($address, $addresses)) { $addresses[] = $address; } } // Create list of products and get a representative product for default values $product_list = array(); $declared_value = 0; foreach ($package->products as $product) { $product_list[] = $product->qty . ' x ' . $product->model; $declared_value += $product->qty * $product->price; } $ups_data = db_query("SELECT pkg_type FROM {uc_ups_products} WHERE nid = :nid", array(':nid' => $product->nid))->fetchAssoc(); $product->ups = $ups_data; $pkg_form = array('#type' => 'fieldset', '#title' => t('Package !id', array('!id' => $id)), ); $pkg_form['products'] = array( '#theme' => 'item_list', '#items' => $product_list, ); $pkg_form['package_id'] = array( '#type' => 'hidden', '#value' => $id, ); $pkg_form['pkg_type'] = array( '#type' => 'select', '#title' => t('Package type'), '#options' => $pkg_types, '#default_value' => $product->ups['pkg_type'], '#required' => TRUE, ); $pkg_form['declared_value'] = array( '#type' => 'textfield', '#title' => t('Declared value'), '#default_value' => $declared_value, '#required' => TRUE, ); $pkg_type['dimensions'] = array( '#type' => 'fieldset', '#title' => t('Dimensions'), '#description' => t('Physical dimensions of the package.'), '#theme' => 'uc_shipping_package_dimensions', ); $pkg_form['dimensions']['units'] = array( '#type' => 'select', '#title' => t('Units of measurement'), '#options' => array( 'in' => t('Inches'), 'ft' => t('Feet'), 'cm' => t('Centimeters'), 'mm' => t('Millimeters'), ), '#default_value' => $product->length_units ? $product->length_units : variable_get('uc_length_unit', 'in'), ); $pkg_form['dimensions']['length'] = array('#type' => 'textfield', '#title' => t('Length'), '#default_value' => $product->length, ); $pkg_form['dimensions']['width'] = array('#type' => 'textfield', '#title' => t('Width'), '#default_value' => $product->width, ); $pkg_form['dimensions']['height'] = array('#type' => 'textfield', '#title' => t('Height'), '#default_value' => $product->height, ); $form['packages'][$id] = $pkg_form; } } $form = uc_shipping_address_form($form, $form_state, $addresses, $order); foreach (array('delivery_email', 'delivery_last_name', 'delivery_street1', 'delivery_city', 'delivery_zone', 'delivery_country', 'delivery_postal_code') as $field) { $form['destination'][$field]['#required'] = TRUE; } $services = _uc_ups_service_list(); $default_service = ''; $method = $order->quote['method']; if ($method == 'ups') { $default_service = $order->quote['accessorials']; $method = $services[$default_service]; } // Inform user of customer's shipping choice $form['shipping_choice'] = array( '#markup' => t('Customer selected @method as the shipping method and paid @rate', array('@method' => $method, '@rate' => uc_currency_format($order->quote['rate']))), ); $form['service'] = array( '#type' => 'select', '#title' => t('UPS service'), '#options' => $services, '#default_value' => $default_service, ); $today = getdate(); $form['ship_date'] = array( '#type' => 'date', '#title' => t('Ship date'), '#default_value' => array('year' => $today['year'], 'month' => $today['mon'], 'day' => $today['mday']), ); $form['expected_delivery'] = array( '#type' => 'date', '#title' => t('Expected delivery'), '#default_value' => array('year' => $today['year'], 'month' => $today['mon'], 'day' => $today['mday']), ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Review shipment')); return $form; } /** * Passes final information into shipment object. * * @see uc_ups_fulfill_order() * @see uc_ups_confirm_shipment() */ function uc_ups_fulfill_order_validate($form, &$form_state) { $origin = new stdClass(); $destination = new stdClass(); $packages = array(); foreach ($form_state['values'] as $key => $value) { if (substr($key, 0, 7) == 'pickup_') { $field = substr($key, 7); $origin->$field = $value; } elseif (substr($key, 0, 9) == 'delivery_') { $field = substr($key, 9); $destination->$field = $value; } } $_SESSION['ups'] = array(); $_SESSION['ups']['origin'] = $origin; if (empty($destination->company)) { $destination->company = $destination->first_name . ' ' . $destination->last_name; } $_SESSION['ups']['destination'] = $destination; foreach ($form_state['values']['packages'] as $id => $pkg_form) { $package = uc_shipping_package_load($id); $package->pkg_type = $pkg_form['pkg_type']; $package->value = $pkg_form['declared_value']; $package->length = $pkg_form['dimensions']['length']; $package->width = $pkg_form['dimensions']['width']; $package->height = $pkg_form['dimensions']['height']; $package->length_units = $pkg_form['dimensions']['units']; $package->qty = 1; $_SESSION['ups']['packages'][$id] = $package; } $_SESSION['ups']['service'] = $form_state['values']['service']; $_SESSION['ups']['ship_date'] = $form_state['values']['ship_date']; $_SESSION['ups']['expected_delivery'] = $form_state['values']['expected_delivery']; $_SESSION['ups']['order_id'] = $form_state['values']['order_id']; $request = uc_ups_shipment_request($_SESSION['ups']['packages'], $origin, $destination, $form_state['values']['service']); $response_obj = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') . 'ShipConfirm', array( 'method' => 'POST', 'data' => $request, )); $response = new SimpleXMLElement($response_obj->data); if (isset($response->Response->Error)) { $error = $response->Response->Error; $error_msg = (string)$error->ErrorSeverity . ' Error ' . (string)$error->ErrorCode . ': ' . (string)$error->ErrorDescription; if (strpos((string)$error->ErrorSeverity, 'Hard') !== FALSE) { form_set_error('', $error_msg); return FALSE; } else { drupal_set_message($error_msg, 'error'); } } $charge = new stdClass(); // if NegotiatedRates exist, quote based on those, otherwise, use TotalCharges if (isset($response->ShipmentCharges)) { $charge = $response->ShipmentCharges->TotalCharges; $_SESSION['ups']['rate']['type'] = t('Total Charges'); if (isset($response->NegotiatedRates)) { $charge = $response->NegotiatedRates->NetSummaryCharges->GrandTotal; $_SESSION['ups']['rate']['type'] = t('Negotiated Rates'); } } $_SESSION['ups']['rate']['currency'] = (string)$charge->CurrencyCode; $_SESSION['ups']['rate']['amount'] = (string)$charge->MonetaryValue; $_SESSION['ups']['digest'] = (string)$response->ShipmentDigest; } /** * Passes final information into shipment object. * * @see uc_ups_fulfill_order() * @see uc_ups_confirm_shipment() */ function uc_ups_fulfill_order_submit($form, &$form_state) { $form_state['redirect'] = 'admin/store/orders/' . $form_state['values']['order_id'] . '/shipments/ups'; } /** * Constructs a void shipment request. * * @param $shipment_number * The UPS shipment tracking number. * @param $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 * XML VoidShipmentRequest message. */ function uc_ups_void_shipment_request($shipment_number, $tracking_numbers = array()) { $schema = uc_ups_access_request(); $schema .= ''; $schema .= ''; $schema .= ''; $schema .= 'Void'; $schema .= ''; $schema .= ''; $schema .= t('Void shipment @ship_number and tracking numbers @track_list', array('@ship_number' => $shipment_number, '@track_list' => implode(', ', $tracking_numbers))); $schema .= ''; $schema .= '1.0'; $schema .= ''; $schema .= ''; $schema .= ''; $schema .= '' . $shipment_number . ''; foreach ($tracking_numbers as $number) { $schema .= '' . $number . ''; } $schema .= ''; $schema .= ''; return $schema; } /** * Instructs UPS to cancel (in whole or in part) a shipment. * * @param $shipment_number * The UPS shipment tracking number. * @param $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 * TRUE if the shipment or packages were successfully voided. */ function uc_ups_void_shipment($shipment_number, $tracking_numbers = array()) { $success = FALSE; $request = uc_ups_void_shipment_request($shipment_number, $tracking_numbers); $resp = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') . 'Void', array( 'method' => 'POST', 'data' => $request, )); $response = new SimpleXMLElement($resp->data); 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_set_message((string)$error->ErrorSeverity . ' ' . (string)$error->ErrorCode . ': ' . (string)$error->ErrorDescription, 'error'); } } } if (isset($response->Status)) { if (isset($response->Status->StatusType)) { $success = (string)$response->Status->StatusType->Code; } } return (bool)$success; } /** * Modifies the rate received from UPS before displaying to the customer. */ function uc_ups_markup($rate) { $markup = variable_get('uc_ups_markup', '0'); $type = variable_get('uc_ups_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; } } /** * Convenience function to get UPS codes for their services. */ function _uc_ups_service_list() { return array( // Domestic services '03' => t('UPS Ground'), '01' => t('UPS Next Day Air'), '13' => t('UPS Next Day Air Saver'), '14' => t('UPS Next Day Early A.M.'), '02' => t('UPS 2nd Day Air'), '59' => t('UPS 2nd Day Air A.M.'), '12' => t('UPS 3 Day Select'), // International services '11' => t('UPS Standard'), '07' => t('UPS Worldwide Express'), '08' => t('UPS Worldwide Expedited'), '54' => t('UPS Worldwide Express Plus'), '65' => t('UPS Worldwide Saver'), // Poland to Poland shipments only //'82' => t('UPS Today Standard'), //'83' => t('UPS Today Dedicated Courrier'), //'84' => t('UPS Today Intercity'), //'85' => t('UPS Today Express'), //'86' => t('UPS Today Express Saver'), ); } /** * Convenience function to get UPS codes for their package types. */ function _uc_ups_pkg_types() { return array( '01' => t('UPS Letter'), '02' => t('Customer Supplied Package'), '03' => t('Tube'), '04' => t('PAK'), '21' => t('UPS Express Box'), '24' => t('UPS 25KG Box'), '25' => t('UPS 10KG Box'), '30' => t('Pallet'), '2a' => t('Small Express Box'), '2b' => t('Medium Express Box'), '2c' => t('Large Express Box'), ); } /** * Pseudo-constructor to set default values of a package. */ function _uc_ups_new_package() { $package = new stdClass(); $package->weight = 0; $package->price = 0; $package->length = 0; $package->width = 0; $package->height = 0; $package->length_units = 'in'; $package->weight_units = 'lb'; $package->qty = 1; $package->pkg_type = '02'; return $package; }