FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

@@ -0,0 +1,140 @@
<?php
/**
* @file
* Flat rate shipping method administration menu items.
*/
/**
* Configures the store default product shipping rates.
*
* @see uc_flatrate_admin_method_edit_form_submit()
* @see uc_flatrate_admin_method_edit_form_delete()
* @ingroup forms
*/
function uc_flatrate_admin_method_edit_form($form, &$form_state, $mid = 0) {
if ($mid && ($method = db_query("SELECT * FROM {uc_flatrate_methods} WHERE mid = :mid", array(':mid' => $mid))->fetchObject())) {
$form['mid'] = array('#type' => 'value', '#value' => $mid);
}
else {
$method = (object) array(
'title' => '',
'label' => '',
'base_rate' => '',
'product_rate' => '',
);
}
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Shipping method title'),
'#description' => t('The name shown to administrators distinguish this method from other flatrate methods.'),
'#default_value' => $method->title,
'#required' => TRUE,
);
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Line item label'),
'#description' => t('The name shown to the customer when they choose a shipping method at checkout.'),
'#default_value' => $method->label,
'#required' => TRUE,
);
$form['base_rate'] = array(
'#type' => 'uc_price',
'#title' => t('Base price'),
'#description' => t('The starting price for shipping costs.'),
'#default_value' => $method->base_rate,
'#required' => TRUE,
);
$form['product_rate'] = array(
'#type' => 'uc_price',
'#title' => t('Default product shipping rate'),
'#description' => t('Additional shipping cost per product in cart.'),
'#default_value' => $method->product_rate,
'#required' => TRUE,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
if (isset($form['mid'])) {
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#validate' => array(),
'#submit' => array('uc_flatrate_admin_method_edit_form_delete'),
);
}
return $form;
}
/**
* Helper function to delete a flatrate method.
*
* @see uc_flatrate_admin_method_edit_form()
*/
function uc_flatrate_admin_method_edit_form_delete($form, &$form_state) {
drupal_goto('admin/store/settings/quotes/flatrate/' . $form_state['values']['mid'] . '/delete');
}
/**
* Form submission handler for uc_flatrate_admin_method_edit_form().
*
* @see uc_flatrate_admin_method_edit_form()
*/
function uc_flatrate_admin_method_edit_form_submit($form, &$form_state) {
if (isset($form_state['values']['mid'])) {
drupal_write_record('uc_flatrate_methods', $form_state['values'], 'mid');
drupal_set_message(t('Flat rate shipping method was updated.'));
$form_state['redirect'] = 'admin/store/settings/quotes/methods';
}
else {
drupal_write_record('uc_flatrate_methods', $form_state['values']);
// Ensure Rules picks up the new condition.
entity_flush_caches();
drupal_set_message(t('Created and enabled new flat rate shipping method.'));
$form_state['redirect'] = 'admin/store/settings/quotes/manage/get_quote_from_flatrate_' . $form_state['values']['mid'];
}
}
/**
* Confirms deletion of a flat rate shipping method.
*
* @see uc_flatrate_admin_method_confirm_delete_submit()
* @ingroup forms
*/
function uc_flatrate_admin_method_confirm_delete($form, &$form_state, $mid) {
$form['mid'] = array('#type' => 'value', '#value' => $mid);
return confirm_form($form, t('Do you want to delete this shipping method?'),
'admin/store/settings/quotes/methods',
t('This will remove the shipping method and the product-specific overrides (if applicable). This action can not be undone.'),
t('Delete'));
}
/**
* Form submission handler for uc_flatrate_admin_method_confirm_delete().
*
* @see uc_flatrate_admin_method_confirm_delete()
*/
function uc_flatrate_admin_method_confirm_delete_submit($form, &$form_state) {
$mid = $form_state['values']['mid'];
db_delete('uc_flatrate_methods')
->condition('mid', $mid)
->execute();
db_delete('uc_flatrate_products')
->condition('mid', $mid)
->execute();
rules_config_delete(array('get_quote_from_flatrate_' . $mid));
drupal_set_message(t('Flat rate shipping method deleted.'));
$form_state['redirect'] = 'admin/store/settings/quotes/methods';
}

View File

@@ -0,0 +1,14 @@
name = Flat rate
description = Assigns a flat shipping rate to an order, plus an optional amount per product.
dependencies[] = uc_quote
package = Ubercart - fulfillment
core = 7.x
configure = admin/store/settings/quotes/methods
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,113 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_flatrate module.
*/
/**
* Implements hook_schema().
*/
function uc_flatrate_schema() {
$schema = array();
$schema['uc_flatrate_products'] = array(
'description' => 'Stores product information for quantity-based shipping quotes methods.',
'fields' => array(
'vid' => array(
'description' => 'The {uc_products}.vid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
'description' => 'The {uc_products}.nid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'mid' => array(
'description' => 'The {uc_flatrate_methods}.mid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'rate' => array(
'description' => 'The rate multiplier, in the store default currency, per each of this product in the shipment.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => FALSE,
),
),
'primary key' => array('vid', 'mid'),
'foreign keys' => array(
'uc_products' => array(
'table' => 'uc_products',
'columns' => array(
'nid' => 'nid',
'vid' => 'vid',
),
),
'uc_flatrate_methods' => array(
'table' => 'uc_flatrate_methods',
'columns' => array('mid' => 'mid'),
),
),
);
$schema['uc_flatrate_methods'] = array(
'description' => 'Stores quantity-based shipping quotes method information.',
'fields' => array(
'mid' => array(
'description' => 'Primary key: The shipping quote method ID.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'title' => array(
'description' => 'The method title, displayed on administration pages.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'label' => array(
'description' => 'The user-facing label of the shipping method.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'base_rate' => array(
'description' => 'The amount of shipping cost before product quantity is applied.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0.0,
),
'product_rate' => array(
'description' => 'The default rate multiplier, in the store default currency, per product in the shipment.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0.0,
),
),
'primary key' => array('mid'),
);
return $schema;
}
/**
* Implements hook_update_last_removed().
*/
function uc_flatrate_update_last_removed() {
return 6003;
}

View File

@@ -0,0 +1,234 @@
<?php
/**
* @file
* Shipping quote module that defines a flat shipping rate for each product.
*/
/**
* Implements hook_menu().
*/
function uc_flatrate_menu() {
$items = array();
$items['admin/store/settings/quotes/methods/flatrate/add'] = array(
'title' => 'Add flat rate quote',
'description' => 'Create a new flat rate shipping quote.',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_flatrate_admin_method_edit_form'),
'access arguments' => array('configure quotes'),
'type' => MENU_LOCAL_ACTION,
'file' => 'uc_flatrate.admin.inc',
);
$items['admin/store/settings/quotes/methods/flatrate/%'] = array(
'title' => 'Edit flat rate method',
'description' => 'Edit an existing flat rate shipping quote.',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_flatrate_admin_method_edit_form', 6),
'access arguments' => array('configure quotes'),
'file' => 'uc_flatrate.admin.inc',
);
$items['admin/store/settings/quotes/flatrate/%/delete'] = array(
'title' => 'Delete flat rate method',
'description' => 'Delete a flat rate shipping quote.',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_flatrate_admin_method_confirm_delete', 5),
'access arguments' => array('configure quotes'),
'file' => 'uc_flatrate.admin.inc',
);
return $items;
}
/**
* Implements hook_form_alter().
*
* Adds a form element for the shipping rate of a product.
*/
function uc_flatrate_form_alter(&$form, &$form_state, $form_id) {
if (uc_product_is_product_form($form)) {
$weight = variable_get('uc_quote_method_weight', array());
$result = db_query("SELECT mid, title, product_rate FROM {uc_flatrate_methods}");
foreach ($result as $method) {
// Ensure default weight is set.
$weight += array('flatrate_' . $method->mid => 0);
if (!isset($form['shipping']['flatrate'])) {
$form['shipping']['flatrate'] = array(
'#type' => 'fieldset',
'#title' => t('Flat shipping rates'),
'#description' => t("Overrides the default shipping rate per product for each flat rate shipping method. Leave field empty to use the method's default value."),
'#tree' => TRUE,
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => 0,
);
}
$form['shipping']['flatrate'][$method->mid] = array(
'#type' => 'uc_price',
'#title' => check_plain($method->title),
'#default_value' => isset($form['#node']->flatrate[$method->mid]) ? $form['#node']->flatrate[$method->mid] : '',
'#description' => t('Default rate: %price', array('%price' => uc_currency_format($method->product_rate))),
'#weight' => $weight['flatrate_' . $method->mid],
'#empty_zero' => FALSE,
);
}
}
}
/**
* Implements hook_node_insert().
*/
function uc_flatrate_node_insert($node) {
uc_flatrate_node_update($node);
}
/**
* Implements hook_node_update().
*/
function uc_flatrate_node_update($node) {
if (uc_product_is_product($node->type)) {
if (isset($node->flatrate) && is_array($node->flatrate)) {
if (empty($node->revision)) {
db_delete('uc_flatrate_products')
->condition('vid', $node->vid)
->execute();
}
$query = db_insert('uc_flatrate_products')
->fields(array('vid', 'nid', 'mid', 'rate'));
foreach ($node->flatrate as $mid => $rate) {
if (is_numeric($rate) && $rate >= 0) {
$query->values(array(
'vid' => $node->vid,
'nid' => $node->nid,
'mid' => $mid,
'rate' => $rate,
));
}
}
$query->execute();
}
}
}
/**
* Implements hook_node_load().
*/
function uc_flatrate_node_load($nodes, $types) {
$vids = array();
$product_types = uc_product_types();
foreach ($nodes as &$node) {
if (in_array($node->type, $product_types)) {
$vids[$node->nid] = $node->vid;
}
}
if ($vids) {
$result = db_query("SELECT nid, mid, rate FROM {uc_flatrate_products} WHERE vid IN (:vids)", array(':vids' => $vids));
foreach ($result as $method) {
$nodes[$method->nid]->flatrate[$method->mid] = $method->rate;
}
}
}
/**
* Implements hook_node_delete().
*/
function uc_flatrate_node_delete($node) {
db_delete('uc_flatrate_products')
->condition('nid', $node->nid)
->execute();
}
/**
* Implements hook_node_revision_delete().
*/
function uc_flatrate_node_revision_delete($node) {
db_delete('uc_flatrate_products')
->condition('vid', $node->vid)
->execute();
}
/**
* Implements hook_uc_shipping_method().
*/
function uc_flatrate_uc_shipping_method() {
$methods = array();
$result = db_query("SELECT mid, title, label, base_rate, product_rate FROM {uc_flatrate_methods}");
foreach ($result as $method) {
$methods['flatrate_' . $method->mid] = array(
'id' => 'flatrate_' . $method->mid,
'module' => 'uc_flatrate',
'title' => $method->title,
'description' => t('!base_rate + !product_rate per item', array('!base_rate' => uc_currency_format($method->base_rate), '!product_rate' => uc_currency_format($method->product_rate))),
'operations' => array(
'edit' => array(
'title' => t('edit'),
'href' => 'admin/store/settings/quotes/methods/flatrate/' . $method->mid,
),
'delete' => array(
'title' => t('delete'),
'href' => 'admin/store/settings/quotes/flatrate/' . $method->mid . '/delete',
),
),
'quote' => array(
'type' => 'order',
'callback' => 'uc_flatrate_quote',
'accessorials' => array(
$method->label,
),
),
'enabled' => TRUE,
);
}
return $methods;
}
/**
* Standard callback to return a shipping rate via the flat rate method.
*
* @param $products
* The order's products.
* @param $details
* Other order details including a shipping address.
* @param $method
* The shipping method to use to create the quote.
*
* @return
* An array containing the shipping quote for the order.
*/
function uc_flatrate_quote($products, $details, $method) {
$method = explode('_', $method['id']);
$mid = $method[1];
if ($method = db_query("SELECT * FROM {uc_flatrate_methods} WHERE mid = :mid", array(':mid' => $mid))->fetchObject()) {
// Start at the base rate.
$rate = $method->base_rate;
foreach ($products as $product) {
if (!isset($product->flatrate[$mid])) {
// Add the method's default product rate.
$rate += $method->product_rate * $product->qty;
}
else {
// Add the product-specific rate.
$rate += $product->flatrate[$mid] * $product->qty;
}
}
$quotes[] = array(
'rate' => $rate,
'label' => check_plain($method->label),
'option_label' => check_plain($method->label),
);
}
return $quotes;
}

View File

@@ -0,0 +1,232 @@
<?php
/**
* @file
* Ubercart Shipping Quote Tests.
*/
/**
* SimpleTests for Ubercart Shipping Quotes.
*/
class UbercartQuoteTestCase extends UbercartTestHelper {
public static function getInfo() {
return array(
'name' => 'Shipping Quotes',
'description' => 'Test shipping quotes.',
'group' => 'Ubercart',
);
}
/**
* Overrides DrupalWebTestCase::setUp().
*/
function setUp() {
$modules = array('rules_admin', 'uc_payment', 'uc_payment_pack', 'uc_quote', 'uc_flatrate');
$permissions = array('administer rules', 'bypass rules access');
parent::setUp($modules, $permissions);
module_load_include('inc', 'uc_flatrate', 'uc_flatrate.admin');
$this->drupalLogin($this->adminUser);
}
/**
* Creates a flat rate shipping quote with optional conditions.
*
* @param $edit
* Data to use to create shipping quote, same format as the values
* submitted from the add flatrate method form.
* @param $condition
* If specified, a RulesAnd component defining the conditions to apply
* for this method.
*/
function createQuote($edit = array(), $condition = FALSE) {
$edit += array(
'title' => $this->randomName(8),
'label' => $this->randomName(8),
'base_rate' => mt_rand(1, 10),
'product_rate' => mt_rand(1, 10),
);
$form_state = array('values' => $edit);
drupal_form_submit('uc_flatrate_admin_method_edit_form', $form_state);
$method = db_query("SELECT * FROM {uc_flatrate_methods} WHERE mid = :mid", array(':mid' => $form_state['values']['mid']))->fetchObject();
if ($condition) {
$name = 'get_quote_from_flatrate_' . $method->mid;
$condition['LABEL'] = $edit['label'] . ' conditions';
$oldconfig = rules_config_load($name);
$newconfig = rules_import(array($name => $condition));
$newconfig->id = $oldconfig->id;
unset($newconfig->is_new);
$newconfig->status = ENTITY_CUSTOM;
$newconfig->save();
entity_flush_caches();
}
$this->assertTrue($method->base_rate == $edit['base_rate'], 'Flatrate quote was created successfully');
return $method;
}
/**
* Simulates selection of a delivery country on the checkout page.
*
* @param $country
* The text version of the country name to select, e.g. "Canada" or
* "United States".
*/
protected function selectCountry($country = "Canada") {
$dom = new DOMDocument();
$dom->loadHTML($this->content);
$parent = $dom->getElementById('edit-panes-delivery-delivery-country');
$options = $parent->getElementsByTagName('option');
for ($i = 0; $i < $options->length; $i++) {
if ($options->item($i)->textContent == $country) {
$options->item($i)->setAttribute('selected', 'selected');
}
else {
$options->item($i)->removeAttribute('selected');
}
}
$this->drupalSetContent($dom->saveHTML());
return $this->drupalPostAjax(NULL, array(), 'panes[delivery][delivery_country]');
}
/**
* Simulates selection of a quote on the checkout page.
*
* @param $n
* The index of the quote to select.
*/
protected function selectQuote($n) {
// Get the list of available quotes.
$xpath = '//*[@name="panes[quotes][quotes][quote_option]"]';
$elements = $this->xpath($xpath);
$vals = array();
foreach ($elements as $element) {
$vals[(string) $element['id']] = (string) $element['value'];
}
// Set the checked attribute of the chosen quote.
$dom = new DOMDocument();
$dom->loadHTML($this->content);
$i = 0;
$selected = '';
foreach ($vals as $id => $value) {
if ($i == $n) {
$dom->getElementById($id)->setAttribute('checked', 'checked');
$selected = $value;
}
else {
$dom->getElementById($id)->removeAttribute('checked');
}
$i++;
}
$this->drupalSetContent($dom->saveHTML());
// Post the selection via Ajax.
$option = array('panes[quotes][quotes][quote_option]' => $selected);
return $this->drupalPostAjax(NULL, array(), $option);
}
/**
* Verifies shipping pane is hidden when there are no shippable items.
*/
public function testNoQuote() {
$product = $this->createProduct(array('shippable' => FALSE));
$quote = $this->createQuote();
$this->drupalPost('node/' . $product->nid, array(), t('Add to cart'));
$this->drupalPost('cart', array('items[0][qty]' => 1), t('Checkout'));
$this->assertNoText('Calculate shipping cost', 'Shipping pane is not present with no shippable item.');
}
/**
* Tests basic flatrate shipping quote functionality.
*/
public function testQuote() {
// Create product and quotes.
$product = $this->createProduct();
$quote1 = $this->createQuote();
$quote2 = $this->createQuote(array(), array(
'LABEL' => 'quote_conditions',
'PLUGIN' => 'and',
'REQUIRES' => array('rules'),
'USES VARIABLES' => array(
'order' => array(
'type' => 'uc_order',
'label' => 'Order'
),
),
'AND' => array( array(
'data_is' => array(
'data' => array('order:delivery-address:country'),
'value' => '840',
),
)),
));
// Define strings to test for.
$qty = mt_rand(2, 100);
foreach (array($quote1, $quote2) as $quote) {
$quote->amount = uc_currency_format($quote->base_rate + $quote->product_rate * $qty);
$quote->option_text = $quote->label . ': ' . $quote->amount;
$quote->total = uc_currency_format($product->sell_price * $qty + $quote->base_rate + $quote->product_rate * $qty);
}
// Add product to cart, update qty, and go to checkout page.
$this->drupalPost('node/' . $product->nid, array(), t('Add to cart'));
$this->drupalPost('cart', array('items[0][qty]' => $qty), t('Checkout'));
$this->assertText($quote1->option_text, 'The default quote option is available');
$this->assertText($quote2->option_text, 'The second quote option is available');
$this->assertText($quote1->total, 'Order total includes the default quote.');
// Select a different quote and ensure the total updates correctly. Currently, we have to do this
// by examining the ajax return value directly (rather than the page contents) because drupalPostAjax() can
// only handle replacements via the 'wrapper' property, and the ajax callback may use a command with a selector.
$edit = array('panes[quotes][quotes][quote_option]' => 'flatrate_2---0');
$result = $this->ucPostAjax(NULL, $edit, $edit);
$this->assertText($quote2->total, 'The order total includes the selected quote.');
// Switch to a different country and ensure the ajax updates the page correctly.
$edit['panes[delivery][delivery_country]'] = 124;
$result = $this->ucPostAjax(NULL, $edit, 'panes[delivery][delivery_country]');
$this->assertText($quote1->option_text, 'The default quote is still available after changing the country.');
$this->assertNoText($quote2->option_text, 'The second quote is no longer available after changing the country.');
$this->assertText($quote1->total, 'The total includes the default quote.');
// Proceed to review page and ensure the correct quote is present.
$edit['panes[quotes][quotes][quote_option]'] = 'flatrate_1---0';
$edit = $this->populateCheckoutForm($edit);
$this->drupalPost(NULL, $edit, t('Review order'));
$this->assertRaw(t('Your order is almost complete.'));
$this->assertText($quote1->total, 'The total is correct on the order review page.');
// Submit the review.
$this->drupalPost(NULL, array(), t('Submit order'));
$order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField();
if ($order_id) {
$order = uc_order_load($order_id);
foreach ($order->line_items as $line) {
if ($line['type'] == 'shipping') {
break;
}
}
// Verify line item is correct.
$this->assertEqual($line['type'], 'shipping', t('The shipping line item was saved to the order.'));
$this->assertEqual($quote1->amount, uc_currency_format($line['amount']), t('Stored shipping line item has the correct amount.'));
// Verify order total is correct on order-view form.
$this->drupalGet('admin/store/orders/' . $order_id);
$this->assertText($quote1->total, 'The total is correct on the order admin view page.');
// Verify shipping line item is correct on order edit form.
$this->drupalGet('admin/store/orders/' . $order_id . '/edit');
$this->assertFieldByName('line_items[' . $line['line_item_id'] . '][title]', $quote1->label, t('Found the correct shipping line item title.'));
$this->assertFieldByName('line_items[' . $line['line_item_id'] . '][amount]', substr($quote1->amount, 1), t('Found the correct shipping line item title.'));
// Verify that the "get quotes" button works as expected.
$result = $this->ucPostAjax('admin/store/orders/' . $order_id . '/edit', array(), array('op' => t('Get shipping quotes')));
$this->assertText($quote1->option_text, 'The default quote is available on the order-edit page.');
$this->assertNoText($quote2->option_text, 'The second quote is not available on the order-edit page.');
}
else {
$this->fail('No order was created.');
}
}
}

View File

@@ -0,0 +1,286 @@
<?php
/**
* @file
* Shipping quotes administration menu items.
*/
/**
* Default shipping settings.
*
* Sets the default shipping location of the store. Allows the user to
* determine which quotin methods are enabled and which take precedence over
* the others. Also sets the default quote and shipping types of all products
* in the store. Individual products may be configured differently.
*
* @see uc_quote_admin_settings_submit()
* @ingroup forms
*/
function uc_quote_admin_settings($form, &$form_state) {
$address = variable_get('uc_quote_store_default_address', new UcAddress());
$form['uc_quote_log_errors'] = array(
'#type' => 'checkbox',
'#title' => t('Log errors during checkout to watchdog'),
'#default_value' => variable_get('uc_quote_log_errors', FALSE),
);
$form['uc_quote_display_debug'] = array(
'#type' => 'checkbox',
'#title' => t('Display debug information to administrators.'),
'#default_value' => variable_get('uc_quote_display_debug', FALSE),
);
$form['uc_quote_require_quote'] = array(
'#type' => 'checkbox',
'#title' => t('Prevent the customer from completing an order if a shipping quote is not selected.'),
'#default_value' => variable_get('uc_quote_require_quote', TRUE),
);
$form['uc_quote_pane_description'] = array(
'#type' => 'fieldset',
'#title' => t('Shipping quote pane description'),
'#tree' => TRUE,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['uc_quote_pane_description']['text'] = array(
'#type' => 'textarea',
'#title' => t('Message text'),
'#default_value' => variable_get('uc_quote_pane_description', t('Shipping quotes are generated automatically when you enter your address and may be updated manually with the button below.')),
);
$form['uc_quote_err_msg'] = array(
'#type' => 'fieldset',
'#title' => t('Shipping quote error message'),
'#tree' => TRUE,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['uc_quote_err_msg']['text'] = array(
'#type' => 'textarea',
'#title' => t('Message text'),
'#default_value' => variable_get('uc_quote_err_msg', t("There were problems getting a shipping quote. Please verify the delivery and product information and try again.\nIf this does not resolve the issue, please call in to complete your order.")),
);
$form['default_address'] = array(
'#type' => 'fieldset',
'#title' => t('Default pickup address'),
'#description' => t("When delivering products to customers, the original location of the product must be known in order to accurately quote the shipping cost and set up a delivery. This form provides the default location for all products in the store. If a product's individual pickup address is blank, Ubercart uses the store's default pickup address specified here."),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['default_address']['address'] = array(
'#type' => 'uc_address',
'#default_value' => isset($form_state['values']) ? $form_state['values'] : $address,
'#required' => FALSE,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration') );
return $form;
}
/**
* Form submission handler for uc_quote_admin_settings().
*
* @see uc_quote_admin_settings()
*/
function uc_quote_admin_settings_submit($form, &$form_state) {
$address = new UcAddress();
$address->first_name = $form_state['values']['first_name'];
$address->last_name = $form_state['values']['last_name'];
$address->company = $form_state['values']['company'];
$address->phone = $form_state['values']['phone'];
$address->street1 = $form_state['values']['street1'];
$address->street2 = $form_state['values']['street2'];
$address->city = $form_state['values']['city'];
$address->zone = $form_state['values']['zone'];
$address->postal_code = $form_state['values']['postal_code'];
$address->country = $form_state['values']['country'];
variable_set('uc_quote_store_default_address', $address);
variable_set('uc_quote_log_errors', $form_state['values']['uc_quote_log_errors']);
variable_set('uc_quote_display_debug', $form_state['values']['uc_quote_display_debug']);
variable_set('uc_quote_require_quote', $form_state['values']['uc_quote_require_quote']);
variable_set('uc_quote_pane_description', $form_state['values']['uc_quote_pane_description']['text']);
variable_set('uc_quote_err_msg', $form_state['values']['uc_quote_err_msg']['text']);
drupal_set_message(t('The configuration options have been saved.'));
}
/**
* Settings for the shipping quote methods.
*
* Enables and reorders shipping quote methods. Sets the default shipping type.
*
* @see uc_quote_method_settings_validate()
* @see uc_quote_method_settings_submit()
* @see theme_uc_quote_method_settings()
* @ingroup forms
*/
function uc_quote_method_settings($form, &$form_state) {
$form['methods'] = array(
'#tree' => TRUE,
);
foreach (uc_quote_methods(TRUE) as $method) {
if (isset($method['quote'])) {
$id = $method['id'];
// Build a list of operations links.
$operations = isset($method['operations']) ? $method['operations'] : array();
$operations += array('conditions' => array(
'title' => t('conditions'),
'href' => 'admin/store/settings/quotes/manage/get_quote_from_' . $id,
'weight' => 5,
));
// Ensure "delete" comes towards the end of the list.
if (isset($operations['delete'])) {
$operations['delete']['weight'] = 10;
}
uasort($operations, 'drupal_sort_weight');
$form['methods'][$id]['uc_quote_enabled'] = array(
'#type' => 'checkbox',
'#title' => check_plain($method['title']),
'#default_value' => $method['enabled'],
);
$form['methods'][$id]['description'] = array(
'#markup' => isset($method['description']) ? $method['description'] : '',
);
$form['methods'][$id]['uc_quote_method_weight'] = array(
'#type' => 'weight',
'#default_value' => $method['weight'],
'#attributes' => array('class' => array('uc-quote-method-weight')),
);
$form['methods'][$id]['operations'] = array(
'#theme' => 'links',
'#links' => $operations,
'#attributes' => array('class' => array('links', 'inline')),
);
}
}
$shipping_types = uc_quote_shipping_type_options();
if (is_array($shipping_types)) {
$form['uc_quote_type_weight'] = array(
'#type' => 'fieldset',
'#title' => t('List position'),
'#description' => t('Determines which shipping methods are quoted at checkout when products of different shipping types are ordered. Larger values take precedence.'),
'#collapsible' => TRUE,
'#tree' => TRUE,
);
$weight = variable_get('uc_quote_type_weight', array());
$shipping_methods = module_invoke_all('uc_shipping_method');
$method_types = array();
foreach ($shipping_methods as $method) {
// Get shipping method types from shipping methods that provide quotes
if (isset($method['quote'])) {
$method_types[$method['quote']['type']][] = $method['title'];
}
}
if (isset($method_types['order']) && is_array($method_types['order'])) {
$count = count($method_types['order']);
$form['uc_quote_type_weight']['#description'] .= format_plural($count, '<br />The %list method is compatible with any shipping type.', '<br />The %list methods are compatible with any shipping type.', array('%list' => implode(', ', $method_types['order'])));
}
foreach ($shipping_types as $id => $title) {
$form['uc_quote_type_weight'][$id] = array(
'#type' => 'weight',
'#title' => $title . (isset($method_types[$id]) && is_array($method_types[$id]) ? ' (' . implode(', ', $method_types[$id]) . ')' : ''),
'#delta' => 5,
'#default_value' => isset($weight[$id]) ? $weight[$id] : 0,
);
}
}
$form['uc_store_shipping_type'] = array(
'#type' => 'select',
'#title' => t('Default order fulfillment type for products'),
'#options' => $shipping_types,
'#default_value' => variable_get('uc_store_shipping_type', 'small_package'),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration') );
return $form;
}
/**
* Displays a formatted list of shipping quote methods and form elements.
*
* @see uc_quote_method_settings()
* @ingroup themeable
*/
function theme_uc_quote_method_settings($variables) {
$form = $variables['form'];
drupal_add_tabledrag('uc-quote-methods', 'order', 'sibling', 'uc-quote-method-weight');
$header = array(t('Shipping method'), t('Details'), array('data' => t('List position'), 'sort' => 'asc'), t('Operations'));
$rows = array();
foreach (element_children($form['methods']) as $method) {
$row = array(
drupal_render($form['methods'][$method]['uc_quote_enabled']),
drupal_render($form['methods'][$method]['description']),
drupal_render($form['methods'][$method]['uc_quote_method_weight']),
drupal_render($form['methods'][$method]['operations']),
);
$rows[] = array(
'data' => $row,
'class' => array('draggable'),
);
}
$output = theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => 'uc-quote-methods'),
'empty' => t('No shipping quotes have been configured yet.'),
));
$output .= drupal_render_children($form);
return $output;
}
/**
* Form validation for uc_quote_method_settings().
*
* Requires at least one enabled shipping method.
*
* @see uc_quote_method_settings()
* @see uc_quote_method_settings_submit()
*/
function uc_quote_method_settings_validate($form, &$form_state) {
$none_enabled = TRUE;
if (is_array($form_state['values']['methods'])) {
foreach ($form_state['values']['methods'] as $method) {
if ($method['uc_quote_enabled']) {
$none_enabled = FALSE;
}
}
}
if ($none_enabled) {
form_set_error('uc_quote_enabled', t('At least one shipping quote method must be enabled.'));
}
}
/**
* Form submission handler for uc_quote_method_settings().
*
* @see uc_quote_method_settings()
* @see uc_quote_method_settings_validate()
*/
function uc_quote_method_settings_submit($form, &$form_state) {
$enabled = array();
$method_weight = array();
foreach ($form_state['values']['methods'] as $id => $method) {
$enabled[$id] = $method['uc_quote_enabled'];
$method_weight[$id] = $method['uc_quote_method_weight'];
}
variable_set('uc_quote_enabled', $enabled);
variable_set('uc_quote_method_weight', $method_weight);
variable_set('uc_quote_type_weight', $form_state['values']['uc_quote_type_weight']);
variable_set('uc_store_shipping_type', $form_state['values']['uc_store_shipping_type']);
drupal_set_message(t('The configuration options have been saved.'));
}

View File

@@ -0,0 +1,128 @@
<?php
/**
* @file
* Hooks provided by the Shipping Quotes module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Defines callbacks and service options for shipping methods.
*
* @return
* An array of shipping methods, keyed by the unique method ID, and with the
* following members:
* - id: The unique method ID, the same as the array key for this method.
* - module: The name of the implementing module.
* - title: The shipping method title.
* - description: (optional) A short description of the shipping method.
* - operations: (optional) A set of links that can be used to edit or
* configure the shipping method, suitable for passing to theme_links().
* - quote: (optional) An associative array, with the following members:
* - type: The quote and shipping types are ids of the product shipping type
* that these methods apply to. type may also be 'order' which indicates
* that the quote applies to the entire order, regardless of the shipping
* types of its products. This is used by quote methods that are based on
* the location of the customer rather than their purchase.
* - callback: The function that is called by uc_quote when a shipping quote
* is requested. Its arguments are the array of products and an array of
* order details (the shipping address). The return value is an array
* representing the rates quoted and errors returned (if any) for each
* option in the accessorials array.
* - file: (optional) The name of the file that contains the callback
* function.
* - accessorials: This array represents the different options the customer
* may choose for their shipment. The callback function should generate a
* quote for each option in accessorials and return them via an array.
* @code
* return array(
* '03' => array('rate' => 15.75, 'option_label' => t('UPS Ground'),
* 'error' => 'Additional handling charge applied.'),
* '14' => array('error' => 'Invalid package type.'),
* '59' => array('rate' => 26.03, 'option_label' => t('UPS 2nd Day'))
* );
* @endcode
* - pkg_types: The list of package types that the shipping method can
* handle. This should be an associative array that can be used as the
* #options of a select form element. It is recommended that a function
* be written to output this array so the method doesn't need to be found
* just for the package types.
* - ship: (optional) An associative array, in the same format as 'quote'.
* - enabled: (optional) Whether the method should be enabled by default.
* Defaults to FALSE.
* - weight: (optional) The default position of the method in the list of
* shipping quotes. Defaults to 0.
*/
function hook_uc_shipping_method() {
$methods = array();
$methods['ups'] = array(
'id' => 'ups',
'title' => t('UPS'),
'quote' => array(
'type' => 'small package',
'callback' => 'uc_ups_quote',
'accessorials' => array(
'03' => t('UPS Ground'),
'11' => t('UPS Standard'),
'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'),
),
),
'ship' => array(
'type' => 'small package',
'callback' => 'uc_ups_fulfill_order',
'pkg_types' => 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'),
),
),
);
return $methods;
}
/**
* Defines shipping types for shipping methods.
*
* This hook defines a shipping type that this module is designed to handle.
* These types are specified by a machine- and human-readable name called 'id',
* and 'title' respectively. Shipping types may be set for individual products,
* manufacturers, and for the entire store catalog. Shipping modules should be
* careful to use the same shipping type ids as other similar shipping modules
* (i.e., FedEx and UPS both operate on "small package" shipments). Modules that
* do not fulfill orders may not need to implement this hook.
*
* @return
* An array of shipping types keyed by a machine-readable name.
*/
function hook_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 package'),
'weight' => $weight['small_package'],
);
return $types;
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,47 @@
/**
* @file
* Styles for shipping quote cart and checkout panes.
*/
#uc-cart-pane-quotes {
border: solid 1px #bbb;
padding: 5px;
}
#uc-cart-pane-quotes .quote-title {
font-weight: bold;
}
#uc-cart-pane-quotes .uc-store-address-field .form-item label {
padding: 5px 6px 6px;
}
#uc-cart-pane-quotes .form-submit {
margin-left: 16em;
}
#quote {
border: solid 1px #bbb;
margin-top: 1em;
padding: 5px;
}
#uc-paypal-ec-review-form #quote {
border: 0;
padding: 0.5em 0 0;
}
.quote-error {
display: inline;
}
.quote-notes {
margin-left: 25px;
}
/**
* Styles for uc_ups.
*/
.ups-logo {
vertical-align: middle;
}

View File

@@ -0,0 +1,17 @@
name = Shipping quotes
description = Enables shipping quotes to be calculated and displayed at checkout.
dependencies[] = uc_cart
package = Ubercart - core (optional)
core = 7.x
configure = admin/store/settings/quotes
; Test cases
files[] = tests/uc_quote.test
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,17 @@
<?php
/**
* @file
* Entity metadata hooks for uc_quote.module.
*/
/**
* Implements hook_entity_property_info_alter().
*/
function uc_quote_entity_property_info_alter(&$info) {
$info['uc_order_product']['properties']['shipping_type'] = array(
'type' => 'text',
'label' => t('Shipping type'),
'getter callback' => 'uc_product_get_shipping_type', // not a typical callback.
);
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_quote module.
*/
/**
* Implements hook_schema().
*/
function uc_quote_schema() {
$schema = array();
$schema['uc_quote_shipping_types'] = array(
'description' => 'Stores shipping information of products.',
'fields' => array(
'id_type' => array(
'description' => 'Determines the table that id references. "product" => {uc_products}.nid.',
'type' => 'varchar',
'length' => 127,
'not null' => TRUE,
'default' => '',
),
'id' => array(
'description' => 'The entity ID.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'shipping_type' => array(
'description' => 'The basic type of shipment, e.g.: small package, freight.',
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('id_type', 'id'),
);
$schema['uc_quote_product_locations'] = array(
'description' => 'Stores default product origin addresses.',
'fields' => array(
'nid' => array(
'description' => 'The {uc_products}.nid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'first_name' => array(
'description' => 'The address first name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'last_name' => array(
'description' => 'The address last name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'company' => array(
'description' => 'The address company.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'street1' => array(
'description' => 'The address street line 1.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'street2' => array(
'description' => 'The address street line 2.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'city' => array(
'description' => 'The address city.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'zone' => array(
'description' => 'The address state/province, from {uc_zones}.zone_id.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'postal_code' => array(
'description' => 'The address postal code.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'country' => array(
'description' => 'The address country, from {uc_countries}.country_id.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'phone' => array(
'description' => 'The address phone number.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('nid'),
'foreign keys' => array(
'uc_products' => array(
'table' => 'uc_products',
'columns' => array('nid' => 'nid'),
),
),
);
$schema['uc_order_quotes'] = array(
'description' => 'Stores shipping quotes.',
'fields' => array(
'order_id' => array(
'description' => 'The {uc_orders}.order_id.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'method' => array(
'description' => 'The quoted shipping method.',
'type' => 'varchar',
'length' => 25,
'not null' => TRUE,
'default' => '',
),
'accessorials' => array(
'description' => 'Additional services or special instructions.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'rate' => array(
'description' => 'The quoted shipping rate.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0,
),
),
'unique keys' => array(
'order_id_quote_method' => array('order_id', 'method'),
),
'foreign keys' => array(
'uc_orders' => array(
'table' => 'uc_orders',
'columns' => array('order_id' => 'order_id'),
),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function uc_quote_uninstall() {
db_delete('variable')
->condition('name', 'uc_quote_%', 'LIKE')
->execute();
variable_del('uc_store_shipping_type');
}
/**
* Implements hook_update_last_removed().
*/
function uc_quote_update_last_removed() {
return 6004;
}
/**
* Drops {uc_order_quotes}.quote_form.
*/
function uc_quote_update_7001() {
db_drop_field('uc_order_quotes', 'quote_form');
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
<?php
/**
* @file
* Menu callbacks for shipping quotes requested through AJAX.
*/
/**
* Pulls the get_quote_from_* triggers and assembles their returned data.
*/
function uc_quote_assemble_quotes($order) {
$products = $order->products;
foreach ($products as $id => $product) {
$node = (array) node_load($product->nid);
foreach ($node as $key => $value) {
if (!isset($product->$key)) {
$product->$key = $value;
}
}
$order->products[$id] = $product;
}
$shipping_types = array();
foreach ($products as $product) {
$shipping_types[] = uc_product_get_shipping_type($product);
}
$shipping_types = array_unique($shipping_types);
$all_types = uc_quote_get_shipping_types();
$shipping_type = '';
// Use the most prominent shipping type (highest weight).
// In theory, you can ship lighter products with heavier by the same
// method, but not vice versa.
$type_weight = -1000; // arbitrary low number
foreach ($shipping_types as $type) {
if ($all_types[$type]['weight'] > $type_weight) {
$shipping_type = $all_types[$type]['id'];
$type_weight = $all_types[$type]['weight'];
}
}
$methods = uc_quote_methods();
foreach ($methods as $id => $method) {
if (!isset($method['quote']) || ($method['quote']['type'] != 'order' && $method['quote']['type'] != $shipping_type)) {
unset($methods[$id]);
}
}
$quote_data = array();
foreach ($methods as $method) {
$set = rules_config_load('get_quote_from_' . $method['id']);
if (!$set || $set->execute($order)) {
$data = uc_quote_action_get_quote($order, $method);
foreach ($data as &$quote) {
if (isset($quote['rate'])) {
$quote['format'] = uc_currency_format($quote['rate']);
}
}
$quote_data[$method['id']] = $data;
}
}
return $quote_data;
}
/**
* Retrieves shipping quote.
*
* @param $order
* The order the quote is for.
* @param $method
* The shipping method to generate the quote.
*
* @return
* Array of shipping quotes.
*/
function uc_quote_action_get_quote($order, $method) {
$details = array();
foreach ($order as $key => $value) {
if (substr($key, 0, 9) == 'delivery_') {
$field = substr($key, 9);
$details[$field] = $value;
}
}
ob_start();
// Load include file containing quote callback, if there is one
if (isset($method['quote']['file'])) {
$inc_file = drupal_get_path('module', $method['module']) . '/' . $method['quote']['file'];
if (is_file($inc_file)) {
require_once($inc_file);
}
}
if (function_exists($method['quote']['callback'])) {
// This feels wrong, but it's the only way I can ensure that shipping
// methods won't mess up the products in their methods.
$products = array();
foreach ($order->products as $key => $item) {
if (uc_order_product_is_shippable($item)) {
$products[$key] = clone $item;
}
}
$quote_data = call_user_func($method['quote']['callback'], $products, $details, $method);
}
$messages = ob_get_contents();
ob_end_clean();
if ($messages && variable_get('uc_quote_log_errors', FALSE)) {
watchdog('quote', '!messages', array('!messages' => $messages), WATCHDOG_WARNING);
watchdog('quote', '<pre>@data</pre>', array('@data' => print_r($quote_data, TRUE)), WATCHDOG_WARNING);
}
return $quote_data;
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* @file
* Rules hooks for uc_quote.module.
*/
/**
* Implements hook_rules_condition_info().
*/
function uc_quote_rules_condition_info() {
return array(
'uc_quote_condition_order_shipping_method' => array(
'label' => t("Order has a shipping quote from a particular method"),
'group' => t('Order: Shipping Quote'),
'base' => 'uc_quote_condition_order_shipping_method',
'parameter' => array(
'order' => array('type' => 'uc_order', 'label' => t('Order')),
'method' => array('type' => 'text', 'label' => t('Shipping method'), 'options list' => 'uc_quote_condition_order_shipping_method_options'),
),
),
);
}
/**
* Checks an order's shipping method.
*/
function uc_quote_condition_order_shipping_method($order, $method) {
// Check the easy way first.
if (!empty($order->quote)) {
return $order->quote['method'] == $method;
}
// Otherwise, look harder.
if (!empty($order->line_items)) {
$methods = module_invoke_all('uc_shipping_method');
$accessorials = $methods[$method]['quote']['accessorials'];
foreach ($order->line_items as $line_item) {
if ($line_item['type'] == 'shipping' && in_array($line_item['title'], $accessorials)) {
return TRUE;
}
}
}
return FALSE;
}
/**
* Options callback.
*
* @see uc_quote_condition_order_shipping_method()
*/
function uc_quote_condition_order_shipping_method_options() {
$methods = module_invoke_all('uc_shipping_method');
$enabled = variable_get('uc_quote_enabled', array());
$options = array();
foreach ($methods as $id => $method) {
$options[$id] = $method['title'];
if (!isset($enabled[$id]) || !$enabled[$id]) {
$options[$id] .= ' ' . t('(disabled)');
}
}
return $options;
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @file
* Rules configurations for shipping quotes modules.
*/
/**
* Implements hook_default_rules_configuration().
*/
function uc_quote_default_rules_configuration() {
$configs = array();
$methods = module_invoke_all('uc_shipping_method');
foreach ($methods as $method) {
$set = rules_and(array(
'order' => array('type' => 'uc_order', 'label' => t('Order')),
));
$set->label = t('@method conditions', array('@method' => $method['title']));
$configs['get_quote_from_' . $method['id']] = $set;
}
return $configs;
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* @file
* Theme functions for the uc_quote module.
*/
/**
* Displays the formatted quote cart pane.
*
* @param $variables
* An associative array containing:
* - form: A render element representing the form.
*
* @ingroup themeable
*/
function theme_uc_cart_pane_quotes($variables) {
$form = $variables['form'];
$output = '<p class="quote-title">' . t('Estimated shipping cost:') . '</p>';
$output .= drupal_render_children($form);
return $output;
}
/**
* Displays the returned shipping rates.
*
* @param $variables
* An associative array containing:
* - form: A render element representing the form.
*
* @ingroup themeable
*/
function theme_uc_quote_returned_rates($variables) {
$form = $variables['form'];
$output = '';
$keys = element_children($form);
// Render notes and error messages after each radio button.
if (count($keys) > 1) {
foreach ($keys as $key) {
if ($key == 'quote_option') {
continue;
}
if (isset($form['quote_option'][$key])) {
$output .= drupal_render($form['quote_option'][$key]);
}
$output .= drupal_render($form[$key]);
}
}
$output .= drupal_render_children($form);
return $output;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -0,0 +1,25 @@
<?php
/**
* @file
* Default theme implementation to display a printable Ubercart packing slip.
*
* @see template_preprocess_uc_order_invoice_page()
*/
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
<head>
<title><?php print t('Packing slip'); ?></title>
<style>
.page-break {
page-break-before: always;
}
</style>
</head>
<body>
<?php print $content; ?>
</body>
</html>

View File

@@ -0,0 +1,159 @@
<?php
/**
* @file
* The shipment packing slip template.
*/
?>
<table width="95%" border="0" cellspacing="0" cellpadding="1" align="center" bgcolor="#006699" style="font-family: verdana, arial, helvetica; font-size: small;">
<tr>
<td>
<table width="100%" border="0" cellspacing="0" cellpadding="5" align="center" bgcolor="#FFFFFF" style="font-family: verdana, arial, helvetica; font-size: small;">
<tr valign="top">
<td>
<table width="100%" style="font-family: verdana, arial, helvetica; font-size: small;">
<tr>
<td>
<?php print $site_logo; ?>
</td>
<td width="20%" nowrap="nowrap">
<?php print $store_address; ?>
<br />
<?php print $store_phone; ?>
</td>
</tr>
</table>
</td>
</tr>
<tr valign="top">
<td>
<table cellpadding="4" cellspacing="0" border="0" width="100%" style="font-family: verdana, arial, helvetica; font-size: small;">
<tr>
<td colspan="2" bgcolor="#006699" style="color: white;">
<b><?php echo t('Purchasing Information:'); ?></b>
</td>
</tr>
<tr>
<td nowrap="nowrap">
<b><?php echo t('E-mail Address:'); ?></b>
</td>
<td width="98%">
<?php print $order_email; ?>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellspacing="0" cellpadding="0" style="font-family: verdana, arial, helvetica; font-size: small;">
<tr>
<td valign="top" width="50%">
<b><?php echo t('Billing Address:'); ?></b><br />
<?php print $billing_address; ?><br />
<br />
<b><?php echo t('Billing Phone:'); ?></b><br />
<?php print $billing_phone; ?><br />
</td>
<td valign="top" width="50%">
<b><?php echo t('Shipping Address:'); ?></b><br />
<?php print $shipping_address; ?><br />
<br />
<b><?php echo t('Shipping Phone:'); ?></b><br />
<?php print $shipping_phone; ?><br />
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td nowrap="nowrap">
<b><?php echo t('Payment Method:'); ?></b>
</td>
<td width="98%">
<?php print $payment_method; ?>
</td>
</tr>
<tr>
<td colspan="2" bgcolor="#006699" style="color: white;">
<b><?php echo t('Order Summary:'); ?></b>
</td>
</tr>
<tr>
<td colspan="2" bgcolor="#EEEEEE">
<font color="#CC6600"><b><?php echo t('Shipping Details:'); ?></b></font>
</td>
</tr>
<tr>
<td colspan="2">
<table border="0" cellpadding="1" cellspacing="0" width="100%" style="font-family: verdana, arial, helvetica; font-size: small;">
<tr>
<td nowrap="nowrap">
<b><?php echo t('Order #:'); ?></b>
<?php print $order_link; ?>
</td>
</tr>
<tr>
<td nowrap="nowrap">
<b><?php echo t('Carrier:'); ?></b>
<?php print $carrier; ?>
</td>
</tr>
<tr>
<td nowrap="nowrap">
<b><?php echo t('Tracking #:'); ?></b>
<?php print $tracking_number; ?>
</td>
</tr>
<tr>
<td colspan="2">
<br /><br /><b><?php echo t('Products on order:'); ?>&nbsp;</b>
<table width="100%" style="font-family: verdana, arial, helvetica; font-size: small;">
<?php if (is_array($packages)) {
foreach ($packages as $package) {
foreach ($package->products as $product) { ?>
<tr>
<td valign="top" nowrap="nowrap">
<b><?php print $product->qty; ?> x </b>
</td>
<td width="98%">
<b><?php print $product->title; ?></b>
<br />
<?php echo t('SKU: ') . $product->model; ?><br />
<?php if (isset($product->data['attributes']) && is_array($product->data['attributes']) && count($product->data['attributes']) > 0) {
foreach ($product->data['attributes'] as $attribute => $option) {
echo '<li>' . t('@attribute: @options', array('@attribute' => $attribute, '@options' => implode(', ', (array)$option))) . '</li>';
}
} ?>
<br />
</td>
</tr>
<?php }
}
} ?>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1,109 @@
<?php
/**
* @file
* Hooks provided by the Shipping module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Handles additional data and activity for shipments.
*
* Adds additional activity after shipment objects are loaded from,
* saved to, or deleted from the database. This is useful for shipment
* modules that store method-specific shipment data in separate tables that
* need to be kept in sync with the uc_shipments table.
*
* The members of the shipment object are the fields in the corresponding
* record of the uc_shipments table, plus $shipment->packages, an array
* of package objects as returned by uc_shipping_package_load().
*
* @param $op
* The action being taken on the shipment. One of the following values:
* - load: The shipment and its packages are loaded from the database.
* - save: Changes to the shipment have been written.
* - delete: The shipment has been deleted and the packages are available
* for reshipment.
* @param $shipment
* The shipment object.
*
* @return
* Only given when $op is "load". An associative array of extra data to
* be added to the shipment object. Each key/value element of the array
* becomes a separate member of the shipment object. Elements of the array
* with the same key as members of the shipment object replace those members
* of the shipment object.
*/
function hook_uc_shipment($op, $shipment) {
switch ($op) {
case 'save':
$google_order_number = uc_google_checkout_get_google_number($shipment->order_id);
if ($google_order_number && $shipment->is_new) {
$xml_data = '';
foreach ($shipment->packages as $package) {
if ($package->tracking_number) {
$tracking_number = $package->tracking_number;
}
elseif ($shipment->tracking_number) {
$tracking_number = $shipment->tracking_number;
}
if ($tracking_number) {
foreach ($package->products as $product) {
$xml_data .= '<item-shipping-information>';
$xml_data .= '<item-id>';
$xml_data .= '<merchant-item-id>' . check_plain($product->nid . '|' . $product->model) . '</merchant-item-id>';
$xml_data .= '</item-id>';
$xml_data .= '<tracking-data-list>';
$xml_data .= '<tracking-data>';
$xml_data .= '<carrier>' . check_plain($shipment->carrier) . '</carrier>';
$xml_data .= '<tracking-number>' . check_plain($tracking_number) . '</tracking-number>';
$xml_data .= '</tracking-data>';
$xml_data .= '</tracking-data-list>';
$xml_data .= '</item-shipping-information>';
}
}
}
if ($xml_data) {
$request = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$request .= '<ship-items xmlns="http://checkout.google.com/schema/2" google-order-number="' . $google_order_number . '">';
$request .= '<item-shipping-information-list>';
$request .= $xml_data;
$request .= '</item-shipping-information-list>';
$request .= '<send-email>true</send-email>';
$request .= '</ship-items>';
$response = uc_google_checkout_send_request('request', $request);
}
}
break;
case 'delete':
$google_order_number = uc_google_checkout_get_google_number($shipment->order_id);
if ($google_order_number) {
foreach ($shipment->packages as $package) {
foreach ($package->products as $product) {
$reset_ids[] = check_plain($product->nid . '|' . $product->model);
}
}
$request = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$request .= '<reset-items-shipping-information xmlns="http://checkout.google.com/schema/2" google-order-number="' . $google_order_number . '">';
$request .= '<item-ids>';
foreach (array_unique($reset_ids) as $item_id) {
$request .= '<item-id>';
$request .= '<merchant-item-id>' . $item_id . '</merchant-item-id>';
$request .= '</item-id>';
}
$request .= '</item-ids>';
$request .= '<send-email>false</send-email>';
$request .= '</reset-items-shipping-information>';
}
$response = uc_google_checkout_send_request('request', $request);
break;
}
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,16 @@
name = Shipping
description = Gets products ready for physical shipment.
dependencies[] = uc_quote
package = Ubercart - core (optional)
core = 7.x
; Views handlers
files[] = views/uc_shipping_handler_field_shipment_id.inc
files[] = views/uc_shipping_handler_field_package_weight.inc
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,520 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_shipping module.
*/
/**
* Implements hook_schema().
*/
function uc_shipping_schema() {
$schema = array();
$schema['uc_shipments'] = array(
'description' => 'Stores shipment information.',
'fields' => array(
'sid' => array(
'description' => 'Primary key: the shipment ID.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'order_id' => array(
'description' => 'The {uc_orders}.order_id of the order associated with the shipment.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'o_first_name' => array(
'description' => 'Origin address: First name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_last_name' => array(
'description' => 'Origin address: Last name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_company' => array(
'description' => 'Origin address: Company name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_street1' => array(
'description' => 'Origin address: Street line 1.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_street2' => array(
'description' => 'Origin address: Street line 2.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_city' => array(
'description' => 'Origin address: City.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_zone' => array(
'description' => 'Origin address: State/province, from {uc_zones}.zone_id.',
'type' => 'int',
'size' => 'medium',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'o_postal_code' => array(
'description' => 'Origin address: Postal code.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'o_country' => array(
'description' => 'Origin address: Country, from {uc_countries}.country_id.',
'type' => 'int',
'size' => 'medium',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'd_first_name' => array(
'description' => 'Destination address: First name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_last_name' => array(
'description' => 'Destination address: Last name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_company' => array(
'description' => 'Destination address: Company name.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_street1' => array(
'description' => 'Destination address: Street line 1.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_street2' => array(
'description' => 'Destination address: Street line 2.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_city' => array(
'description' => 'Destination address: City.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_zone' => array(
'description' => 'Destination address: State/province, from {uc_zones}.zone_id.',
'type' => 'int',
'size' => 'medium',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'd_postal_code' => array(
'description' => 'Destination address: Postal code.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'd_country' => array(
'description' => 'Destination address: Country, from {uc_countries}.country_id.',
'type' => 'int',
'size' => 'medium',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'shipping_method' => array(
'description' => 'The shipping method.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'accessorials' => array(
'description' => 'Shipping options and special instructions.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'carrier' => array(
'description' => 'The company making the delivery.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'transaction_id' => array(
'description' => "The carrier's shipment identifier.",
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'tracking_number' => array(
'description' => 'The number used by the carrier to locate the shipment while it is in transit.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'ship_date' => array(
'description' => 'The Unix timestamp indicating when the shipment left the origin address.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'expected_delivery' => array(
'description' => 'The Unix timestamp indicating the expected date of delivery.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'cost' => array(
'description' => 'The cost of the shipment.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0.0,
),
'changed' => array(
'description' => 'The Unix timestamp indicating the last time the shipment was modified.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('sid'),
'foreign keys' => array(
'uc_orders' => array(
'table' => 'uc_orders',
'columns' => array('order_id' => 'order_id'),
),
),
'indexes' => array(
'order_id' => array('order_id'),
),
);
$schema['uc_packages'] = array(
'description' => 'Stores shipment package information.',
'fields' => array(
'package_id' => array(
'description' => 'Primary key: the package ID.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'order_id' => array(
'description' => 'The {uc_orders}.order_id.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'shipping_type' => array(
'description' => 'The basic type of shipment, e.g.: small package, freight, etc.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'pkg_type' => array(
'description' => 'The type of packaging.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'length' => array(
'description' => 'The package length.',
'type' => 'float',
'not null' => FALSE,
),
'width' => array(
'description' => 'The package width.',
'type' => 'float',
'not null' => FALSE,
),
'height' => array(
'description' => 'The package height.',
'type' => 'float',
'not null' => FALSE,
),
'length_units' => array(
'description' => 'The physical units of the length, width, and height.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'value' => array(
'description' => 'The monetary value of the package contents.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => FALSE,
'default' => 0.0,
),
'sid' => array(
'description' => 'The {uc_shipments}.sid, if the package has been shipped.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
'tracking_number' => array(
'description' => 'The package-specific tracking number, if available.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'label_image' => array(
'description' => 'The {file}.fid that refers to an image of the shipping label of the package.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
),
'primary key' => array('package_id'),
'foreign keys' => array(
'uc_orders' => array(
'table' => 'uc_orders',
'columns' => array('order_id' => 'order_id'),
),
'uc_quote_shipping_types' => array(
'table' => 'uc_quote_shipping_types',
'columns' => array('shipping_type' => 'shipping_type'),
),
'uc_shipments' => array(
'table' => 'uc_shipments',
'columns' => array('sid' => 'sid'),
),
'file' => array(
'table' => 'file',
'columns' => array('label_image' => 'fid'),
),
),
'indexes' => array(
'order_id' => array('order_id'),
'sid' => array('sid'),
),
);
$schema['uc_packaged_products'] = array(
'description' => 'Stores packaged product information.',
'fields' => array(
'package_id' => array(
'description' => 'The {uc_packages}.package_id in which the product is shipped.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'order_product_id' => array(
'description' => 'The {uc_order_products}.order_product_id of the ordered product.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'qty' => array(
'description' => 'The number of this product in this package.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('package_id', 'order_product_id'),
'foreign keys' => array(
'package_id' => array('uc_packages' => 'package_id'),
'order_product_id' => array('uc_order_products' => 'order_product_id'),
),
'indexes' => array(
'order_product_id' => array('order_product_id'),
),
);
return $schema;
}
/**
* Implements hook_update_last_removed().
*/
function uc_shipping_update_last_removed() {
return 6005;
}
/**
* Changes {uc_packages}.label_image to an integer file id.
*/
function uc_shipping_update_7000(&$sandbox) {
$sandbox['#finished'] = 0;
$schema = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
);
if (!isset($sandbox['total'])) {
db_add_field('uc_packages', 'label_image_id', $schema);
$sandbox['last'] = 0;
$sandbox['count'] = 0;
$sandbox['total'] = db_query("SELECT COUNT(*) FROM {uc_packages} WHERE label_image IS NOT NULL")->fetchField();
}
else {
$found = FALSE;
$scheme = variable_get('file_default_scheme', 'public');
if ($sandbox['total']) {
$limit = 200;
$images = array();
$packages = db_query_range("SELECT package_id, label_image FROM {uc_packages} WHERE package_id > :package_id AND label_image IS NOT NULL", 0, $limit, array(':package_id' => $sandbox['last']));
foreach ($packages as $package) {
$found = TRUE;
$uri = file_stream_wrapper_uri_normalize($scheme . '://' . $package->label_image);
$fid = db_query("SELECT fid FROM {file_managed} WHERE uri = :uri", array(':uri' => $uri))->fetchField();
if (!$fid) {
$stat = stat($package->label_image);
$info = getimagesize($package->label_image);
$file = array(
'uid' => 1,
'filename' => basename($package->label_image),
'uri' => $uri,
'filemime' => $info['mime'],
'filesize' => $stat['size'],
'status' => FILE_STATUS_PERMANENT,
'timestamp' => $stat['ctime'],
);
$fid = db_insert('file_managed')->fields($file)->execute();
}
db_update('uc_packages')
->fields(array('label_image_id' => $fid))
->condition('package_id', $package->package_id)
->execute();
$sandbox['last'] = $package->package_id;
$sandbox['count']++;
$sandbox['message'] = check_plain($package->label_image);
}
$sandbox['#finished'] = min(0.99, $sandbox['count'] / $sandbox['total']);
}
if (!$found) {
db_drop_field('uc_packages', 'label_image');
db_change_field('uc_packages', 'label_image_id', 'label_image', $schema, array(
'foreign keys' => array(
'label_image' => array('file_managed' => 'fid'),
),
));
$sandbox['#finished'] = 1;
return t('!number label images moved to the file table.', array('!number' => $sandbox['total']));
}
}
}
/**
* Add 'changed' column to shipments.
*/
function uc_shipping_update_7001() {
if (!db_field_exists('uc_shipments', 'changed')) {
db_add_field('uc_shipments', 'changed', array(
'description' => 'The Unix timestamp indicating the last time the shipment was modified.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
}
}
/**
* Add indexes to package and shipment tables.
*/
function uc_shipping_update_7300() {
// Alter {uc_shipments} table.
if (!db_index_exists('uc_shipments', 'order_id')) {
db_add_index('uc_shipments', 'order_id', array('order_id'));
}
// Alter {uc_packages} table.
if (!db_index_exists('uc_packages', 'order_id')) {
db_add_index('uc_packages', 'order_id', array('order_id'));
}
if (!db_index_exists('uc_packages', 'sid')) {
db_add_index('uc_packages', 'sid', array('sid'));
}
// Alter {uc_packaged_products} table.
if (!db_index_exists('uc_packaged_products', 'order_product_id')) {
db_add_index('uc_packaged_products', 'order_product_id', array('order_product_id'));
}
}
/**
* Remove orphaned package and shipment records.
*/
function uc_shipping_update_7301() {
$subquery = db_select('uc_orders', 'o')
->fields('o', array('order_id'));
db_delete('uc_shipments')
->condition('order_id', $subquery, 'NOT IN')
->execute();
db_delete('uc_packages')
->condition('order_id', $subquery, 'NOT IN')
->execute();
$subquery = db_select('uc_packages', 'p')
->fields('p', array('package_id'));
db_delete('uc_packaged_products')
->condition('package_id', $subquery, 'NOT IN')
->execute();
}

View File

@@ -0,0 +1,32 @@
/**
* @file
* Adds autofill address functionality to shipment forms.
*/
/**
* Autofills shipment address form from user selection.
*
* @param type
* Field prefix used to identify the address.
* @param json_address
* JSON object of address data.
*/
function apply_address(type, json_address) {
//if (json_address != "0") {
eval("var address = " + json_address +";");
jQuery('#edit-' + type + '-first-name').val(address.first_name);
jQuery('#edit-' + type + '-last-name').val(address.last_name);
jQuery('#edit-' + type + '-phone').val(address.phone);
jQuery('#edit-' + type + '-company').val(address.company);
jQuery('#edit-' + type + '-street1').val(address.street1);
jQuery('#edit-' + type + '-street2').val(address.street2);
jQuery('#edit-' + type + '-city').val(address.city);
jQuery('#edit-' + type + '-postal-code').val(address.postal_code);
if (jQuery('#edit-' + type + '-country').val() != address.country) {
jQuery('#edit-' + type + '-country').val(address.country);
}
jQuery('#edit-' + type + '-zone').val(address.zone);
//}
}

View File

@@ -0,0 +1,882 @@
<?php
/**
* @file
* Organizes ordered products into packages and sets them up for shipment.
* Shipping method modules may add functionality to generate shipping labels
* and tracking numbers.
*/
/**
* Implements hook_help().
*/
function uc_shipping_help($path, $arg) {
switch ($path) {
case 'admin/store/orders/%/packages/new':
return '<p>' . t('Organize products into packages. Package numbers in multiple shipping types are of the first shipping type they appear in. All packages are given a unique ID when they are saved. Choose the default package "Sep." to automatically create a package for each of the selected quantity of products in that row.') . '</p>';
break;
}
}
/**
* Implements hook_menu().
*/
function uc_shipping_menu() {
$items = array();
$items['admin/store/orders/%uc_order/packages'] = array(
'title' => 'Packages',
'page callback' => 'uc_shipping_order_packages',
'page arguments' => array(3),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'weight' => 6,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/packages/new'] = array(
'title' => 'Create packages',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_new_package', 3),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'type' => MENU_LOCAL_ACTION,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/packages/%uc_shipping_package/edit'] = array(
'title' => 'Edit package',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_package_edit', 3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/packages/%uc_shipping_package/cancel'] = array(
'title' => 'Cancel package shipment',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_package_cancel_confirm', 3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/packages/%uc_shipping_package/delete'] = array(
'title' => 'Delete package',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_package_delete_confirm', 3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments'] = array(
'title' => 'Shipments',
'page callback' => 'uc_shipping_order_shipments',
'page arguments' => array(3),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'weight' => 7,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments/new'] = array(
'title' => 'New shipment',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_new_shipment', 3),
'access callback' => 'uc_shipping_new_shipment_access',
'access arguments' => array(3),
'type' => MENU_LOCAL_ACTION,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment'] = array(
'title callback' => 'uc_shipping_shipment_page_title',
'title arguments' => array(5),
'page callback' => 'uc_shipping_shipment_view',
'page arguments' => array(3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/view'] = array(
'title' => 'View',
'weight' => -5,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/edit'] = array(
'title' => 'Edit',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_shipment_edit', 3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'weight' => -1,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/print'] = array(
'title' => 'Print',
'page callback' => 'uc_shipping_shipment_print',
'page arguments' => array(3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'type' => MENU_LOCAL_TASK,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/packing_slip'] = array(
'title' => 'Packing slip',
'page callback' => 'uc_shipping_shipment_print',
'page arguments' => array(3, 5, FALSE),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'type' => MENU_LOCAL_TASK,
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/delete'] = array(
'title' => 'Delete shipment',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_shipping_shipment_delete_confirm', 3, 5),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'file' => 'uc_shipping.admin.inc',
);
$items['admin/store/orders/%uc_order/ship'] = array(
'title' => 'Ship packages',
'page callback' => 'uc_shipping_make_shipment',
'page arguments' => array(3),
'access callback' => 'uc_shipping_order_access',
'access arguments' => array(3),
'file' => 'uc_shipping.admin.inc',
);
return $items;
}
/**
* Title callback for admin/store/orders/%/shipments/%.
*/
function uc_shipping_shipment_page_title($shipment) {
return t('Shipment !id', array('!id' => $shipment->sid));
}
/**
* Ensures access to the Shipments tab.
*/
function uc_shipping_order_access($order) {
return user_access('fulfill orders') && uc_order_is_shippable($order);
}
/**
* Access callback for the new shipment page.
*/
function uc_shipping_new_shipment_access($order) {
return uc_shipping_order_access($order) && db_query("SELECT COUNT(*) FROM {uc_packages} WHERE order_id = :id AND sid IS NULL", array(':id' => $order->order_id))->fetchField();
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function uc_shipping_menu_local_tasks_alter(&$data, $router_item, $root_path) {
if ($root_path == 'admin/store/orders/%/shipments') {
$order = $router_item['page_arguments'][0];
$item = menu_get_item('admin/store/orders/' . $order->order_id . '/packages/new');
if ($item['access']) {
$data['actions']['output'][] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Implements hook_admin_paths().
*/
function uc_shipping_admin_paths() {
return array(
// Don't show packing slips with the admin theme, overlay, etc.
'admin/store/orders/*/shipments/*/print' => FALSE,
'admin/store/orders/*/shipments/*/packing_slip' => FALSE,
);
}
/**
* Implements hook_permission().
*/
function uc_shipping_permission() {
return array(
'fulfill orders' => array(
'title' => t('Fulfill orders'),
),
);
}
/**
* Implements hook_theme().
*/
function uc_shipping_theme() {
return array(
'uc_shipping_new_package_fieldset' => array(
'render element' => 'fieldset',
'file' => 'uc_shipping.admin.inc',
),
'uc_shipping_edit_package_fieldset' => array(
'render element' => 'fieldset',
'file' => 'uc_shipping.admin.inc',
),
'uc_shipping_new_shipment' => array(
'render element' => 'form',
'file' => 'uc_shipping.admin.inc',
),
'uc_shipping_shipment_print' => array(
'variables' => array('order' => NULL, 'shipment' => NULL, 'labels' => TRUE),
'file' => 'uc_shipping.admin.inc',
),
'uc_packing_slip' => array(
'variables' => array('order' => NULL, 'shipment' => NULL),
'template' => 'uc-packing-slip',
),
'uc_packing_slip_page' => array(
'variables' => array('content' => NULL),
'template' => 'uc-packing-slip-page',
),
);
}
/**
* Preprocess function to make tokens available in the packing slip template.
*
* @see uc-packing-slip.tpl.php
*/
function template_preprocess_uc_packing_slip(&$variables) {
$tokens = token_generate('site', drupal_map_assoc(array('logo')));
$variables['site_logo'] = isset($tokens['logo']) ? $tokens['logo'] : '';
$tokens = token_generate('store', drupal_map_assoc(array('name', 'address', 'phone')));
$variables['store_name'] = $tokens['name'];
$variables['store_address'] = $tokens['address'];
$variables['store_phone'] = $tokens['phone'];
$order = $variables['order'];
$variables['order_link'] = l($order->order_id, url('user/' . $order->uid . '/orders/' . $order->order_id, array('absolute' => TRUE)));
$variables['order_email'] = check_plain($order->primary_email);
$variables['billing_address'] = uc_order_address($order, 'billing');
$variables['billing_phone'] = check_plain($order->billing_phone);
$variables['shipping_address'] = uc_order_address($order, 'delivery');
$variables['shipping_phone'] = check_plain($order->delivery_phone);
if (module_exists('uc_payment')) {
$payment_method = _uc_payment_method_data($order->payment_method, 'review');
if (empty($payment_method)) {
$payment_method = _uc_payment_method_data($order->payment_method, 'name');
}
$variables['payment_method'] = $payment_method;
}
else {
$variables['payment_method'] = '';
}
$shipment = $variables['shipment'];
$variables['carrier'] = check_plain($shipment->carrier);
$variables['tracking_number'] = check_plain($shipment->tracking_number);
$variables['packages'] = $shipment->packages;
}
/**
* Preprocesses a printable packing slip page.
*
* @see uc-packing-slip-page.tpl.php
*/
function template_preprocess_uc_packing_slip_page(&$variables) {
$language = isset($GLOBALS['language']) ? $GLOBALS['language'] : language_default();
$variables['language'] = $language;
$variables['language']->dir = $language->direction ? 'rtl' : 'ltr';
}
/**
* Implements hook_uc_order_pane().
*/
function uc_shipping_uc_order_pane() {
$panes['packages'] = array(
'callback' => 'uc_shipping_order_pane_packages',
'title' => t('Tracking numbers'),
'desc' => t('Display tracking numbers of shipped packages.'),
'class' => 'pos-left',
'weight' => 7,
'show' => array('view', 'invoice', 'customer'),
);
return $panes;
}
/**
* Implements hook_uc_order_actions().
*/
function uc_shipping_uc_order_actions($order) {
$actions = array();
if (user_access('fulfill orders')) {
$result = db_query("SELECT COUNT(nid) FROM {uc_order_products} WHERE order_id = :id AND data LIKE :data", array(':id' => $order->order_id, ':data' => '%s:9:\"shippable\";s:1:\"1\";%'));
if ($result->fetchField()) {
$title = t('Package order !order_id products.', array('!order_id' => $order->order_id));
$actions[] = array(
'name' => t('Package'),
'url' => 'admin/store/orders/' . $order->order_id . '/packages',
'icon' => theme('image', array('path' => drupal_get_path('module', 'uc_shipping') . '/images/package.gif', 'alt' => $title)),
'title' => $title,
);
$result = db_query("SELECT COUNT(package_id) FROM {uc_packages} WHERE order_id = :id", array(':id' => $order->order_id));
if ($result->fetchField()) {
$title = t('Ship order !order_id packages.', array('!order_id' => $order->order_id));
$actions[] = array(
'name' => t('Ship'),
'url' => 'admin/store/orders/' . $order->order_id . '/shipments',
'icon' => theme('image', array('path' => drupal_get_path('module', 'uc_shipping') . '/images/ship.gif', 'alt' => $title)),
'title' => $title,
);
}
}
}
return $actions;
}
/**
* Displays the details of a package.
*/
function uc_shipping_package_view($package) {
$shipment = uc_shipping_shipment_load($package->sid);
$build = array(
'#prefix' => '<div class="order-pane pos-left">',
'#suffix' => '</div>',
);
$rows = array();
$build['title'] = array(
'#markup' => t('Package %id:', array('%id' => $package->package_id)),
'#prefix' => '<div class="order-pane-title">',
'#suffix' => '</div>',
);
$rows[] = array(t('Contents:'), filter_xss_admin($package->description));
if ($shipment) {
$methods = module_invoke_all('uc_shipping_method');
if (isset($methods[$shipment->shipping_method])) {
$pkg_type = $methods[$shipment->shipping_method]['ship']['pkg_types'][$package->pkg_type];
}
}
$rows[] = array(t('Package type:'), isset($pkg_type) ? $pkg_type : check_plain($package->pkg_type));
if ($package->length && $package->width && $package->height) {
$rows[] = array(t('Dimensions:'), t('!l x !w x !h', array('!l' => uc_length_format($package->length), '!w' => uc_length_format($package->width), '!h' => uc_length_format($package->height))));
}
$rows[] = array(t('Insured value:'), array('data' => array('#theme' => 'uc_price', '#price' => $package->value)));
if ($package->tracking_number) {
$rows[] = array(t('Tracking number:'), check_plain($package->tracking_number));
}
if ($shipment && isset($package->label_image) &&
file_exists($package->label_image->uri)) {
$rows[] = array(t('Label:'), l(t('Click to view.'), 'admin/store/orders/' . $package->order_id . '/shipments/labels/' . $shipment->shipping_method . '/' . $package->label_image->uri));
}
else {
$rows[] = array(t('Label:'), t('n/a'));
}
$build['package'] = array(
'#theme' => 'table',
'#rows' => $rows,
'attributes' => array('style' => 'width:auto;'),
);
return $build;
}
/**
* Loads a package and its products.
*/
function uc_shipping_package_load($package_id) {
static $packages = array();
if (!isset($packages[$package_id])) {
$result = db_query("SELECT * FROM {uc_packages} WHERE package_id = :id", array(':id' => $package_id));
if ($package = $result->fetchObject()) {
$products = array();
$description = '';
$weight = 0;
$units = variable_get('uc_weight_unit', 'lb');
$addresses = array();
$result = db_query("SELECT op.order_product_id, pp.qty, pp.qty * op.weight AS weight, op.weight_units, op.nid, op.title, op.model, op.price, op.data FROM {uc_packaged_products} pp LEFT JOIN {uc_order_products} op ON op.order_product_id = pp.order_product_id WHERE pp.package_id = :id ORDER BY op.order_product_id", array(':id' => $package_id));
foreach ($result as $product) {
$address = uc_quote_get_default_shipping_address($product->nid);
// TODO: Lodge complaint that array_unique() compares as strings.
if (!in_array($address, $addresses)) {
$addresses[] = $address;
}
$description .= ', ' . $product->qty . ' x ' . $product->model;
// Normalize all weights to default units.
$weight += $product->weight * uc_weight_conversion($product->weight_units, $units);
$product->data = unserialize($product->data);
$products[$product->order_product_id] = $product;
}
$package->addresses = $addresses;
$package->description = substr($description, 2);
$package->weight = $weight;
$package->weight_units = $units;
$package->products = $products;
if ($package->label_image && $image = file_load($package->label_image)) {
$package->label_image = $image;
}
else {
unset($package->label_image);
}
$packages[$package_id] = $package;
}
else {
return FALSE;
}
}
return $packages[$package_id];
}
/**
* Saves a package.
*/
function uc_shipping_package_save($package) {
$package = (object)$package;
if (!isset($package->package_id)) {
$package->package_id = db_insert('uc_packages')
->fields(array('order_id' => $package->order_id))
->execute();
}
if (isset($package->products) && $package->products) {
$insert = db_insert('uc_packaged_products')
->fields(array('package_id', 'order_product_id', 'qty'));
foreach ($package->products as $id => $product) {
$insert->values(array(
'package_id' => $package->package_id,
'order_product_id' => $id,
'qty' => $product->qty,
));
$result = db_query("SELECT data FROM {uc_order_products} WHERE order_product_id = :id", array(':id' => $id));
if ($order_product = $result->fetchObject()) {
$order_product->data = unserialize($order_product->data);
$order_product->data['package_id'] = intval($package->package_id);
db_update('uc_order_products')
->fields(array('data' => serialize($order_product->data)))
->condition('order_product_id', $id)
->execute();
}
}
db_delete('uc_packaged_products')
->condition('package_id', $package->package_id)
->execute();
$insert->execute();
}
$fields = array(
'order_id' => $package->order_id,
'shipping_type' => $package->shipping_type,
);
if (isset($package->pkg_type)) {
$fields['pkg_type'] = $package->pkg_type;
}
if (isset($package->length) && isset($package->width) && isset($package->height) && isset($package->length_units)) {
$fields['length'] = $package->length;
$fields['width'] = $package->width;
$fields['height'] = $package->height;
$fields['length_units'] = $package->length_units;
}
if (isset($package->value)) {
$fields['value'] = $package->value;
}
if (isset($package->sid)) {
$fields['sid'] = $package->sid;
}
if (isset($package->tracking_number)) {
$fields['tracking_number'] = $package->tracking_number;
}
if (isset($package->label_image) && is_object($package->label_image)) {
$fields['label_image'] = $package->label_image->fid;
}
db_update('uc_packages')
->fields($fields)
->condition('package_id', $package->package_id)
->execute();
}
/**
* Deletes a package.
*/
function uc_shipping_package_delete($package_id) {
// @todo: Make these delete functions take the actual object.
$package = uc_shipping_package_load($package_id);
db_delete('uc_packages')
->condition('package_id', $package_id)
->execute();
db_delete('uc_packaged_products')
->condition('package_id', $package_id)
->execute();
if (isset($package->label_image)) {
file_usage_delete($package->label_image, 'uc_shipping', 'package', $package_id);
file_delete($package->label_image);
}
drupal_set_message(t('Package @id has been deleted.', array('@id' => $package_id)));
}
/**
* Loads a shipment and its packages.
*/
function uc_shipping_shipment_load($shipment_id) {
$shipment = db_query("SELECT * FROM {uc_shipments} WHERE sid = :sid", array(':sid' => $shipment_id))->fetchObject();
if ($shipment) {
$result = db_query("SELECT package_id FROM {uc_packages} WHERE sid = :sid", array(':sid' => $shipment_id));
$packages = array();
foreach ($result as $package) {
$packages[$package->package_id] = uc_shipping_package_load($package->package_id);
}
$shipment->packages = $packages;
$extra = module_invoke_all('uc_shipment', 'load', $shipment);
if (is_array($extra)) {
foreach ($extra as $key => $value) {
$shipment->$key = $value;
}
}
}
return $shipment;
}
/**
* Saves a shipment.
*/
function uc_shipping_shipment_save($shipment) {
if (isset($shipment->origin)) {
foreach ($shipment->origin as $field => $value) {
$field = 'o_' . $field;
$shipment->$field = $value;
$fields[$field] = $value;
}
}
if (isset($shipment->destination)) {
foreach ($shipment->destination as $field => $value) {
$field = 'd_' . $field;
$shipment->$field = $value;
$fields[$field] = $value;
}
}
$shipment->changed = time();
if (!isset($shipment->sid)) {
drupal_write_record('uc_shipments', $shipment);
$shipment->is_new = TRUE;
}
else {
drupal_write_record('uc_shipments', $shipment, 'sid');
$shipment->is_new = FALSE;
}
if (is_array($shipment->packages)) {
foreach ($shipment->packages as $package) {
$package->sid = $shipment->sid;
// Since the products haven't changed, we take them out of the object so
// that they are not deleted and re-inserted.
$products = $package->products;
unset($package->products);
uc_shipping_package_save($package);
// But they're still necessary for hook_uc_shipment(), so they're added
// back in.
$package->products = $products;
}
}
module_invoke_all('uc_shipment', 'save', $shipment);
$order = uc_order_load($shipment->order_id);
rules_invoke_event('uc_shipment_save', $order, $shipment);
}
/**
* Deletes a shipment.
*/
function uc_shipping_shipment_delete($shipment_id) {
$shipment = uc_shipping_shipment_load($shipment_id);
db_update('uc_packages')
->fields(array(
'sid' => NULL,
'tracking_number' => NULL,
'label_image' => NULL,
))
->condition('sid', $shipment_id)
->execute();
db_delete('uc_shipments')
->condition('sid', $shipment_id)
->execute();
foreach ($shipment->packages as $package) {
if (isset($package->label_image)) {
file_delete($package->label_image);
unset($package->label_image);
}
}
module_invoke_all('uc_shipment', 'delete', $shipment);
}
/**
* Shipping order pane callback.
*
* @see uc_shipping_uc_order_pane()
*/
function uc_shipping_order_pane_packages($op, $order) {
switch ($op) {
case 'view':
case 'customer':
$tracking = array();
$result = db_query("SELECT sid FROM {uc_shipments} WHERE order_id = :id", array(':id' => $order->order_id));
foreach ($result as $shipment) {
$shipment = uc_shipping_shipment_load($shipment->sid);
if ($shipment->tracking_number) {
$tracking[$shipment->carrier]['data'] = $shipment->carrier;
$tracking[$shipment->carrier]['children'][] = check_plain($shipment->tracking_number);
}
else {
foreach ($shipment->packages as $package) {
if ($package->tracking_number) {
$tracking[$shipment->carrier]['data'] = $shipment->carrier;
$tracking[$shipment->carrier]['children'][] = check_plain($package->tracking_number);
}
}
}
}
// Do not show an empty pane to customers.
if ($op == 'view' || !empty($tracking)) {
$build['tracking'] = array(
'#theme' => 'item_list',
'#items' => $tracking,
);
return $build;
}
break;
}
}
/**
* Chooses an address to fill out a form.
*/
function uc_shipping_select_address($addresses, $onchange = '', $title = NULL) {
if (!is_array($addresses) || count($addresses) == 0) {
$addresses = array();
}
$store_address = variable_get('uc_quote_store_default_address', new UcAddress());
if (!in_array($store_address, $addresses)) {
$addresses[] = $store_address;
}
$blank = array(
'first_name' => '',
'last_name' => '',
'phone' => '',
'company' => '',
'street1' => '',
'street2' => '',
'city' => '',
'postal_code' => '',
'country' => 0,
'zone' => 0,
);
$options = array(drupal_json_encode($blank) => t('- Reset fields -'));
foreach ($addresses as $address) {
$options[drupal_json_encode($address)] = $address->company . ' ' . $address->street1 . ' ' . $address->city;
}
$select = array(
'#type' => 'select',
'#title' => is_null($title) ? t('Address book') : $title,
'#options' => $options,
'#default_value' => drupal_json_encode($addresses[0]),
'#attributes' => array('onchange' => $onchange),
);
return $select;
}
/**
* Helper function for addresses in forms.
*
* @ingroup forms
*/
function uc_shipping_address_form($form, &$form_state, $addresses, $order) {
drupal_add_js(drupal_get_path('module', 'uc_shipping') . '/uc_shipping.js');
$form['origin'] = array(
'#type' => 'fieldset',
'#title' => t('Origin address'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => -2,
);
$form['origin']['pickup_address_select'] = uc_shipping_select_address($addresses, 'apply_address(\'pickup\', this.value);', t('Saved Addresses'));
$form['origin']['pickup_address_select']['#weight'] = -2;
$form['origin']['pickup_email'] = uc_textfield(t('E-mail'), uc_store_email(), FALSE, NULL, 255);
$form['origin']['pickup_email']['#weight'] = -1;
$form['origin']['pickup_address']['#tree'] = TRUE;
$form['origin']['pickup_address']['pickup_address'] = array(
'#type' => 'uc_address',
'#default_value' => reset($addresses),
'#required' => FALSE,
);
$form['destination'] = array(
'#type' => 'fieldset',
'#title' => t('Destination address'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => -1,
);
if (isset($form_state['values']['delivery_country'])) {
$order->delivery_country = $form_state['values']['delivery_country'];
}
$form['destination']['delivery_email'] = uc_textfield(t('E-mail'), $order->primary_email, FALSE, NULL, 255);
$form['destination']['delivery_email']['#weight'] = -1;
$form['destination']['delivery_address'] = array(
'#type' => 'uc_address',
'#default_value' => $order,
'#required' => FALSE,
'#key_prefix' => 'delivery',
);
return $form;
}
/**
* Implements hook_views_api().
*/
function uc_shipping_views_api() {
return array(
'api' => '2.0',
'path' => drupal_get_path('module', 'uc_shipping') . '/views',
);
}
/**
* Implements hook_date_views_tables().
*/
function uc_shipping_date_views_tables() {
return array('uc_shipments');
}
/**
* Implements hook_date_views_fields().
*
* All modules that create custom fields that use the
* 'views_handler_field_date' handler can provide
* additional information here about the type of
* date they create so the date can be used by
* the Date API views date argument and date filter.
*/
function uc_shipping_date_views_fields($field) {
$values = array(
// The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
'sql_type' => DATE_UNIX,
// Timezone handling options: 'none', 'site', 'date', 'utc' .
'tz_handling' => 'site',
// Needed only for dates that use 'date' tz_handling.
'timezone_field' => '',
// Needed only for dates that use 'date' tz_handling.
'offset_field' => '',
// Array of "table.field" values for related fields that should be
// loaded automatically in the Views SQL.
'related_fields' => array(),
// Granularity of this date field's db data.
'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
);
switch ($field) {
case 'uc_shipments.ship_date':
case 'uc_shipments.expected_delivery':
case 'uc_shipments.changed':
return $values;
}
}
/**
* Implements hook_uc_order().
*
* Prevent users from deleting orders with a shipment or package that has
* a tracking number, unless the user has administrative privileges or the
* "Unconditionally delete orders" permission.
*
* Delete packages and shipments attached to orders being deleted.
*/
function uc_shipping_uc_order($op, $order, $arg2) {
switch ($op) {
case 'can_delete':
// Find and check the shipments for tracking numbers.
// {uc_shipments}.tracking_number is NOT NULL.
$shipment_count = db_select('uc_shipments')
->condition('order_id', $order->order_id)
->condition('tracking_number', '', '<>')
->countQuery()
->execute()
->fetchField();
if ($shipment_count > 0) {
return FALSE;
}
// Find and check the packages.
$package_count = db_select('uc_packages')
->condition('order_id', $order->order_id)
->isNotNull('tracking_number')
->condition('tracking_number', '', '<>')
->countQuery()
->execute()
->fetchField();
if ($package_count > 0) {
return FALSE;
}
return TRUE;
break;
case 'delete':
// Find and delete the shipments.
$shipment_ids = db_select('uc_shipments')
->fields(NULL, array('sid'))
->condition('order_id', $order->order_id)
->execute()
->fetchCol();
foreach ($shipment_ids as $sid) {
uc_shipping_shipment_delete($sid);
}
// Find and delete the packages.
$package_ids = db_select('uc_packages')
->fields(NULL, array('package_id'))
->condition('order_id', $order->order_id)
->execute()
->fetchCol();
foreach ($package_ids as $pid) {
uc_shipping_package_delete($pid);
}
break;
}
}

View File

@@ -0,0 +1,225 @@
<?php
/**
* @file
* Rules hooks for uc_shipping.module.
*/
/**
* Implements hook_rules_data_info().
*/
function uc_shipping_rules_data_info() {
$address_info = uc_address_property_info();
$entities['uc_shipment'] = array(
'label' => t('Ubercart shipment object'),
'group' => t('Ubercart'),
'wrap' => TRUE,
'property info' => array(
'sid' => array(
'type' => 'integer',
'label' => t('Shipment ID'),
),
'order-id' => array(
'type' => 'integer',
'label' => t('Order ID'),
),
'origin' => array(
'type' => 'struct',
'label' => t('Origin address'),
'description' => t('The origin location for the shipment.'),
'getter callback' => 'uc_shipping_address_property_get',
'setter callback' => 'uc_shipping_address_property_set',
'setter permission' => 'fulfill orders',
'property info' => $address_info,
),
'destination' => array(
'type' => 'struct',
'label' => t('Destination address'),
'description' => t('The destination location for the shipment.'),
'getter callback' => 'uc_shipping_address_property_get',
'setter callback' => 'uc_shipping_address_property_set',
'setter permission' => 'fulfill orders',
'property info' => $address_info,
),
'shipping-method' => array(
'type' => 'text',
'label' => t('Shipping method'),
'description' => t('The transportation method used to ship.'),
),
'accessorials' => array(
'type' => 'text',
'label' => t('Accessorials'),
'description' => t('Shipping options and special instructions.'),
),
'carrier' => array(
'type' => 'text',
'label' => t('Carrier'),
'description' > t('The company making the delivery.'),
),
'transaction-id' => array(
'type' => 'text',
'label' => t('Transaction ID'),
'description' => t("The carrier's shipment identifier."),
),
'tracking-number' => array(
'type' => 'text',
'label' => t('Tracking number'),
'description' => t('The number used by the carrier to locate the shipment while it is in transit.'),
),
'ship-date' => array(
'type' => 'date',
'label' => t('Ship date'),
'description' => t('The time the shipment was sent out.'),
),
'expected-delivery' => array(
'type' => 'date',
'label' => t('Expected delivery'),
'description' => t('The time the shipment is expected to be delivered'),
),
'cost' => array(
'type' => 'decimal',
'label' => t('Cost'),
'description' => t('The cost of the shipment.'),
),
'packages' => array(
'type' => 'list<struct>',
'label' => t('Packages'),
'description' => t('The physical items being shipped.'),
'property info' => array(
'package-id' => array(
'type' => 'integer',
'label' => t('Package ID'),
),
'shipping-type' => array(
'type' => 'text',
'label' => t('Shipping type'),
'description' => t('The basic type of shipment, e.g.: small package, freight, etc.'),
),
'pkg-type' => array(
'type' => 'text',
'label' => t('Package type'),
'description' => t('The type of packaging.'),
),
'length' => array(
'type' => 'decimal',
'label' => t('Length'),
'description' => t('The package length.'),
),
'width' => array(
'type' => 'decimal',
'label' => t('Width'),
'description' => t('The package width.'),
),
'height' => array(
'type' => 'decimal',
'label' => t('Height'),
'description' => t('The package height.'),
),
'value' => array(
'type' => 'decimal',
'label' => t('Value'),
'description' => t('The monetary value of the package contents.'),
),
'tracking-number' => array(
'type' => 'text',
'label' => t('Tracking number'),
'description' => t('The number used by the carrier to locate the shipment while it is in transit.'),
),
'description' => array(
'type' => 'text',
'label' => t('Description'),
'description' => t('The package description'),
),
'weight' => array(
'type' => 'decimal',
'label' => t('Weight'),
'description' => t('The physical weight of the package.'),
),
'products' => array(
'type' => 'list<uc_order_product>',
'label' => t('Products'),
'description' => t('The package contents.'),
),
'label-image' => array(
'type' => 'file',
'label' => t('Label image'),
'description' => t('An image of the shipping label.'),
),
),
),
),
);
return $entities;
}
/**
* Entity metadata callback to get origin or destination address of an shipment.
*/
function uc_shipping_address_property_get($shipment, array $options, $name, $entity_type) {
switch ($name) {
case 'origin':
$type = 'o_';
break;
case 'destination':
$type = 'd_';
break;
default:
return NULL;
}
$address = new UcAddress();
foreach ($address as $field => $value) {
$address->{$field} = $shipment->{$type . $field};
}
return $address;
}
/**
* Entity metadata callback to set origin or destination address of an order.
*/
function uc_shipping_address_property_set($shipment, $name, $address) {
switch ($name) {
case 'origin':
$type = 'o_';
break;
case 'destination':
$type = 'd_';
break;
default:
return;
}
foreach ($address as $field => $value) {
$shipment->{$type . $field} = $value;
}
}
/**
* Implements hook_rules_event_info().
*/
function uc_shipping_rules_event_info() {
$events['uc_shipment_save'] = array(
'label' => t('A shipment is saved'),
'group' => t('Fulfillment'),
'variables' => array(
'order' => array(
'type' => 'uc_order',
'label' => t('Order'),
),
'shipment' => array(
'type' => 'uc_shipment',
'label' => t('Shipment'),
),
),
);
return $events;
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* @file
* Token hooks for the uc_shipping module.
*/
/**
* Implements hook_token_info().
*/
function uc_shipping_token_info() {
$tokens = array();
$tokens['tracking-number'] = array(
'name' => t('Shipment tracking number(s)'),
'description' => t('Tracking number(s) (if applicable) for product shipments.'),
);
return array(
'tokens' => array('uc_order' => $tokens),
);
}
/**
* Implements hook_tokens().
*/
function uc_shipping_tokens($type, $tokens, $data = array(), $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']);
if ($type == 'uc_order' && !empty($data['uc_order'])) {
$order = $data['uc_order'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'tracking-number':
$result = db_query('SELECT tracking_number FROM {uc_shipments} WHERE order_id = :order_id', array(':order_id' => $order->order_id));
$tracking_numbers = array();
foreach ($result as $record) {
if ((isset($record->tracking_number)) && (!empty($record->tracking_number))) {
$tracking_numbers[] = $record->tracking_number;
}
}
$tracking = implode(', ', $tracking_numbers);
$replacements[$original] = $sanitize ? check_plain($tracking) : $tracking;
break;
}
}
}
return $replacements;
}

View File

@@ -0,0 +1,675 @@
<?php
/**
* @file
* Views hooks for Ubercart shipping.
*/
/**
* Implements hook_views_data().
*/
function uc_shipping_views_data() {
$data['uc_shipments']['table']['group'] = t('Shipment');
// Allow base tables of shipments.
$data['uc_shipments']['table']['base'] = array(
'field' => 'sid', // This is the identifier field for the view.
'title' => t('Ubercart shipments'),
'help' => t('Ubercart shipments contain shipping information for orders and can be related to orders and packages.'),
);
// Shipment relationship for orders.
$data['uc_orders']['shipments'] = array(
'relationship' => array(
'title' => t('Shipments'),
'help' => t('Relate shipments to an order. This relationship will create one record for each shipment.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_shipments',
'base field' => 'order_id',
'relationship field' => 'order_id',
'label' => t('shipment'),
),
);
// Order relationship for shipments.
$data['uc_shipments']['order'] = array(
'relationship' => array(
'title' => t('Order'),
'help' => t('Relate an order to a shipment. Use this relationship to get order information for a shipment.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_orders',
'base field' => 'order_id',
'relationship field' => 'order_id',
'label' => t('order'),
),
);
// Expose packages to their shipments as a relationship.
$data['uc_shipments']['packages'] = array(
'relationship' => array(
'title' => t('Packages'),
'help' => t('Relate packages to a shipment. This relationship will create one record for each shipped package.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_packages',
'base field' => 'sid',
'relationship field' => 'sid',
'label' => t('package'),
),
);
// Shipment ID field.
$data['uc_shipments']['sid'] = array(
'title' => t('Shipment ID'),
'help' => t('The shipment ID.'),
'field' => array(
'handler' => 'uc_shipping_handler_field_shipment_id',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'sid',
),
);
// Order ID field.
$data['uc_shipments']['order_id'] = array(
'title' => t('Order ID'),
'help' => t('The order ID.'),
'field' => array(
'handler' => 'uc_order_handler_field_order_id',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'order_id',
),
);
// Carrier field.
$data['uc_shipments']['carrier'] = array(
'title' => t('Carrier'),
'help' => t('The company making the delivery.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
// Shipment transaction ID field.
$data['uc_shipments']['transaction_id'] = array(
'title' => t('Transaction ID'),
'help' => t("The carrier's shipment identifier."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Shipment tracking number field.
$data['uc_shipments']['tracking_number'] = array(
'title' => t('Tracking number'),
'help' => t('The number used by the carrier to locate the shipment while it is in transit.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['uc_shipments']['ship_date'] = array(
'title' => t('Ship date'),
'help' => t('The date when the shipment left the origin address.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
$data['uc_shipments']['expected_delivery'] = array(
'title' => t('Expected delivery date'),
'help' => t('The expected date of delivery.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
$data['uc_shipments']['changed'] = array(
'title' => t('Last modified'),
'help' => t('The time the shipment was last modified.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
$data['uc_shipments']['cost'] = array(
'title' => t('Cost'),
'help' => t('The cost of the shipment.'),
'field' => array(
'handler' => 'uc_order_handler_field_money_amount',
'click sortable' => TRUE,
'float' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
$addresses = array(
'o' => t('Origin address'),
'd' => t('Delivery address'),
);
$fields = array(
'first_name' => t('First name'),
'last_name' => t('Last name'),
'company' => t('Company'),
'street1' => t('Street address 1'),
'street2' => t('Street address 2'),
'city' => t('City'),
'postal_code' => t('Postal code'),
);
foreach ($addresses as $prefix => $address) {
$group = t('Shipment') . ': ' . $address;
foreach ($fields as $field => $label) {
$data['uc_shipments'][$prefix . '_' . $field] = array(
'group' => $group,
'title' => $label,
'help' => t('The !field of the !address of the shipment.', array('!field' => drupal_strtolower($label), '!address' => drupal_strtolower($address))),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
}
// uc_order is required by shipping module so is safe to use uc_order handler.
$data['uc_shipments'][$prefix . '_full_name'] = array(
'group' => $group,
'title' => t('Full name'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('full name'), '!address' => drupal_strtolower($address))),
'field' => array(
'additional fields' => array(
$prefix . '_first_name',
$prefix . '_last_name'
),
'handler' => 'uc_order_handler_field_order_fullname',
'prefix' => $prefix,
),
);
$data['uc_shipments_' . $prefix . '_countries']['table']['group'] = $group;
$data['uc_shipments_' . $prefix . '_countries']['table']['join']['uc_shipments'] = array(
'table' => 'uc_countries',
'left_field' => $prefix . '_country',
'field' => 'country_id',
);
$data['uc_shipments_' . $prefix . '_countries']['country_id'] = array(
'title' => t('ISO country code (numeric)'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('numeric ISO country code'), '!address' => drupal_strtolower($address))),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'country_iso_code_2',
'numeric' => TRUE,
'validate type' => 'country_id',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
$data['uc_shipments_' . $prefix . '_countries']['country_name'] = array(
'title' => t('Country'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('country name'), '!address' => drupal_strtolower($address))),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
$data['uc_shipments_' . $prefix . '_countries']['country_iso_code_2'] = array(
'title' => t('ISO country code (2 characters)'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('ISO country code'), '!address' => drupal_strtolower($address))),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
$data['uc_shipments_' . $prefix . '_countries']['country_iso_code_3'] = array(
'title' => t('ISO country code (3 characters)'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('ISO country code'), '!address' => drupal_strtolower($address))),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
$data['uc_shipments_' . $prefix . '_zones']['table']['group'] = $group;
$data['uc_shipments_' . $prefix . '_zones']['table']['join']['uc_shipments'] = array(
'table' => 'uc_zones',
'left_field' => $prefix . '_zone',
'field' => 'zone_id',
);
$data['uc_shipments_' . $prefix . '_zones']['zone_name'] = array(
'title' => t('State/Province'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('state or province'), '!address' => drupal_strtolower($address))),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
$data['uc_shipments_' . $prefix . '_zones']['zone_code'] = array(
'title' => t('State/Province code'),
'help' => t('The !field of the !address of the shipment.', array('!field' => t('state or province code'), '!address' => drupal_strtolower($address))),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
}
// Expose packages.
$data['uc_packages']['table']['group'] = t('Package');
// Allow base tables of packages.
$data['uc_packages']['table']['base'] = array(
'field' => 'sid', // This is the identifier field for the view.
'title' => t('Ubercart packages'),
'help' => t('Ubercart packages contain physical characteristics, tracking, and label information. They can be related to their orders, shipments, and contents.'),
);
// Expose packages to their order as a relationship.
// Packages can exists without a shipment so this relationship may be useful.
$data['uc_orders']['packages'] = array(
'relationship' => array(
'title' => t('Packages'),
'help' => t('Relate packages to an order. This relationship will create one record for each package of an order.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_packages',
'base field' => 'order_id',
'relationship field' => 'order_id',
'label' => t('package'),
),
);
// Order relationship for packages.
$data['uc_packages']['order'] = array(
'relationship' => array(
'title' => t('Order'),
'help' => t('Relate an order to a package. Use this relationship to get order information for a package.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_orders',
'base field' => 'order_id',
'relationship field' => 'order_id',
'label' => t('order'),
),
);
// Expose packaged products to their package as a relationship.
$data['uc_packages']['packaged_products'] = array(
'relationship' => array(
'title' => t('Products'),
'help' => t('Relate packaged products to a package. This relationship will create one record for each packaged product.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_packaged_products',
'base field' => 'package_id',
'relationship field' => 'package_id',
'label' => t('product'),
),
);
// Expose shipments to their packages as a relationship.
$data['uc_packages']['shipment'] = array(
'relationship' => array(
'title' => t('Shipment'),
'help' => t('Relate shipment to package. Use this relationship to get shipping information for the package. Note that this relationship might not exist.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_shipments',
'base field' => 'sid',
'relationship field' => 'sid',
'label' => t('shipment'),
),
);
// Package ID field.
// We don't redirect to the shipment page because a package can exist without a shipment.
$data['uc_packages']['package_id'] = array(
'title' => t('Package ID'),
'help' => t('The package ID.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'sid',
),
);
// Shipment ID field.
$data['uc_packages']['sid'] = array(
'title' => t('Shipment ID'),
'help' => t('The shipment ID.'),
'field' => array(
'handler' => 'uc_shipping_handler_field_shipment_id',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'sid',
),
);
// Order ID field.
$data['uc_packages']['order_id'] = array(
'title' => t('Order ID'),
'help' => t('The order ID.'),
'field' => array(
'handler' => 'uc_order_handler_field_order_id',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'order_id',
),
);
// Shipment type field.
$data['uc_packages']['shipping_type'] = array(
'title' => t('Shipment type'),
'help' => t('The basic type of shipment, e.g.: small package, freight, etc.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
// Package type.
$data['uc_packages']['pkg_type'] = array(
'title' => t('Package type'),
'help' => t('The type of packaging.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
// Package value.
$data['uc_packages']['value'] = array(
'title' => t('Value'),
'help' => t('The monetary value of the package contents.'),
'field' => array(
'handler' => 'uc_order_handler_field_money_amount',
'click sortable' => TRUE,
'float' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
// Package tracking number field.
$data['uc_packages']['tracking_number'] = array(
'title' => t('Tracking number'),
'help' => t('The number used by the carrier to locate the package while it is in transit.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Package length field.
$data['uc_packages']['length'] = array(
'title' => t('Length'),
'help' => t('The physical length.'),
'field' => array(
'additional fields' => array(
'field' => 'length_units',
),
'handler' => 'uc_product_handler_field_length',
'click sortable' => TRUE,
'float' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_float',
),
);
// Package width field.
$data['uc_packages']['width'] = array(
'title' => t('Width'),
'help' => t('The physical width.'),
'field' => array(
'additional fields' => array(
'field' => 'length_units',
),
'handler' => 'uc_product_handler_field_length',
'click sortable' => TRUE,
'float' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_float',
),
);
// Package height field.
$data['uc_packages']['height'] = array(
'title' => t('Height'),
'help' => t('The physical height.'),
'field' => array(
'additional fields' => array(
'field' => 'length_units',
),
'handler' => 'uc_product_handler_field_length',
'click sortable' => TRUE,
'float' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_float',
),
);
// Package weight field.
$data['uc_packages']['weight'] = array(
'title' => t('Weight'),
'help' => t('The physical weight of package.'),
'field' => array(
'additional fields' => array(
'package_id',
),
'handler' => 'uc_shipping_handler_field_package_weight',
'click sortable' => FALSE,
'float' => TRUE,
),
);
// Expose packaged products.
$data['uc_packaged_products']['table']['group'] = t('Package: Product');
// Expose packaged products to the ordered product as a relationship.
// By using a relation and not expose fields directly we make sure that
// when ordered products will be fieldable entities all their custom fields,
// the one not stored in the schema, will get loaded.
$data['uc_packaged_products']['uc_order_products'] = array(
'relationship' => array(
'title' => t('Ordered product'),
'help' => t('Relate packaged product to the ordered product.'),
'handler' => 'views_handler_relationship',
'base' => 'uc_order_products',
'base field' => 'order_product_id',
'relationship field' => 'order_product_id',
'label' => t('ordered product'),
),
);
// Packaged quantity field.
$data['uc_packaged_products']['qty'] = array(
'title' => t('Quantity'),
'help' => t('The quantity packaged.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
return $data;
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* @file
* Total package weight field handler.
*/
/**
* Field handler: displays the weight of the package.
*
* We cannot use a subquery because there is no way to make sure that all products
* in packages have the same weight unit.
*/
class uc_shipping_handler_field_package_weight extends uc_product_handler_field_weight {
/**
* Overrides views_handler::use_group_by().
*
* Disables aggregation for this field.
*/
function use_group_by() {
return FALSE;
}
/**
* Overrides uc_product_handler_field_weight::query().
*/
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
/**
* Overrides uc_product_handler_field_weight::render().
*/
function render($values) {
$package = uc_shipping_package_load($values->{$this->aliases['package_id']});
if ($this->options['format'] == 'numeric') {
return $package->weight;
}
if ($this->options['format'] == 'uc_weight') {
return uc_weight_format($package->weight, $package->weight_units);
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* @file
* Shipment ID field handler.
*/
/**
* Field handler: simple renderer that links to the shipment page.
*/
class uc_shipping_handler_field_shipment_id extends views_handler_field {
/**
* Override init function to provide generic option to link to shipment.
*/
function init(&$view, &$data) {
parent::init($view, $data);
if (!empty($this->options['link_to_shipment'])) {
$this->additional_fields['order_id'] = array('table' => $this->table_alias, 'field' => 'order_id');
}
}
/**
* Overrides views_handler::option_definition().
*/
function option_definition() {
$options = parent::option_definition();
$options['link_to_shipment'] = array('default' => FALSE);
return $options;
}
/**
* Overrides views_handler::options_form().
*
* Provides link to shipment administration page.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['link_to_shipment'] = array(
'#title' => t('Link this field to the shipment page'),
'#description' => t('This will override any other link you have set.'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_shipment']),
);
}
/**
* Renders whatever the data is as a link to the order.
*
* Data should be made XSS safe prior to calling this function.
*/
function render_link($data, $values) {
if (!empty($this->options['link_to_shipment'])) {
$this->options['alter']['make_link'] = FALSE;
if (user_access('fulfill orders')) {
$path = 'admin/store/orders/' . $this->get_value($values, 'order_id') . '/shipments/' . $values->{$this->field_alias};
}
else {
$path = FALSE;
}
if ($path && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = $path;
}
}
return $data;
}
/**
* Overrides views_handler_field::render().
*/
function render($values) {
return $this->render_link(check_plain($values->{$this->field_alias}), $values);
}
}

View File

@@ -0,0 +1,350 @@
<?php
/**
* @file
* UPS administration menu items.
*/
/**
* UPS Online Tool settings.
*
* Records UPS account information necessary to use the service. Allows testing
* or production mode. Configures which UPS services are quoted to customers.
*
* @see uc_ups_admin_settings_validate()
* @see uc_ups_admin_settings_submit()
* @ingroup forms
*/
function uc_ups_admin_settings($form, &$form_state) {
// Put fieldsets into vertical tabs
$form['ups-settings'] = array(
'#type' => 'vertical_tabs',
'#attached' => array(
'js' => array(
'vertical-tabs' => drupal_get_path('module', 'uc_ups') . '/uc_ups.admin.js',
),
),
);
// Container for credential forms
$form['uc_ups_credentials'] = array(
'#type' => 'fieldset',
'#title' => t('Credentials'),
'#description' => t('Account number and authorization information.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'ups-settings',
);
$form['uc_ups_credentials']['uc_ups_access_license'] = array(
'#type' => 'textfield',
'#title' => t('UPS OnLine Tools XML Access Key'),
'#default_value' => variable_get('uc_ups_access_license', ''),
'#required' => TRUE,
);
$form['uc_ups_credentials']['uc_ups_shipper_number'] = array(
'#type' => 'textfield',
'#title' => t('UPS Shipper #'),
'#description' => t('The 6-character string identifying your UPS account as a shipper.'),
'#default_value' => variable_get('uc_ups_shipper_number', ''),
'#required' => TRUE,
);
$form['uc_ups_credentials']['uc_ups_user_id'] = array(
'#type' => 'textfield',
'#title' => t('UPS.com user ID'),
'#default_value' => variable_get('uc_ups_user_id', ''),
'#required' => TRUE,
);
$form['uc_ups_credentials']['uc_ups_password'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#default_value' => variable_get('uc_ups_password', ''),
);
$form['uc_ups_credentials']['uc_ups_connection_address'] = array(
'#type' => 'select',
'#title' => t('Server mode'),
'#description' => t('Use the Testing server while developing and configuring your site. Switch to the Production server only after you have demonstrated that transactions on the Testing server are working and you are ready to go live.'),
'#options' => array('https://wwwcie.ups.com/ups.app/xml/' => t('Testing'),
'https://onlinetools.ups.com/ups.app/xml/' => t('Production'),
),
'#default_value' => variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/'),
);
$form['services'] = array(
'#type' => 'fieldset',
'#title' => t('Service options'),
'#description' => t('Set the conditions that will return a UPS quote.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'ups-settings',
);
$form['services']['uc_ups_services'] = array(
'#type' => 'checkboxes',
'#title' => t('UPS services'),
'#default_value' => variable_get('uc_ups_services', _uc_ups_service_list()),
'#options' => _uc_ups_service_list(),
'#description' => t('Select the UPS services that are available to customers.'),
);
// Container for quote options
$form['uc_ups_quote_options'] = array(
'#type' => 'fieldset',
'#title' => t('Quote options'),
'#description' => t('Preferences that affect computation of quote.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'ups-settings',
);
$form['uc_ups_quote_options']['uc_ups_all_in_one'] = array(
'#type' => 'radios',
'#title' => t('Product packages'),
'#default_value' => variable_get('uc_ups_all_in_one', 1),
'#options' => array(
0 => t('Each product in its own package'),
1 => t('All products in one package'),
),
'#description' => t('Indicate whether each product is quoted as shipping separately or all in one package. Orders with one kind of product will still use the package quantity to determine the number of packages needed, however.'),
);
// Form to select package types
$form['uc_ups_quote_options']['uc_ups_pkg_type'] = array(
'#type' => 'select',
'#title' => t('Default Package Type'),
'#default_value' => variable_get('uc_ups_pkg_type', _uc_ups_pkg_types()),
'#options' => _uc_ups_pkg_types(),
'#description' => t('Type of packaging to be used. May be overridden on a per-product basis via the product node edit form.'),
);
$form['uc_ups_quote_options']['uc_ups_classification'] = array(
'#type' => 'select',
'#title' => t('UPS Customer classification'),
'#options' => array(
'01' => t('Wholesale'),
'03' => t('Occasional'),
'04' => t('Retail'),
),
'#default_value' => variable_get('uc_ups_classification', '04'),
'#description' => t('The kind of customer you are to UPS. For daily pickups the default is wholesale; for customer counter pickups the default is retail; for other pickups the default is occasional.'),
);
$form['uc_ups_quote_options']['uc_ups_negotiated_rates'] = array(
'#type' => 'radios',
'#title' => t('Negotiated rates'),
'#default_value' => variable_get('uc_ups_negotiated_rates', 0),
'#options' => array(1 => t('Yes'), 0 => t('No')),
'#description' => t('Is your UPS account receiving negotiated rates on shipments?'),
);
// Form to select pickup type
$form['uc_ups_quote_options']['uc_ups_pickup_type'] = array(
'#type' => 'select',
'#title' => t('Pickup type'),
'#options' => array(
'01' => 'Daily Pickup',
'03' => 'Customer Counter',
'06' => 'One Time Pickup',
'07' => 'On Call Air',
'11' => 'Suggested Retail Rates',
'19' => 'Letter Center',
'20' => 'Air Service Center',
),
'#default_value' => variable_get('uc_ups_pickup_type', '01'),
);
$form['uc_ups_quote_options']['uc_ups_residential_quotes'] = array(
'#type' => 'radios',
'#title' => t('Assume UPS shipping quotes will be delivered to'),
'#default_value' => variable_get('uc_ups_residential_quotes', 0),
'#options' => array(
0 => t('Business locations'),
1 => t('Residential locations (extra fees)'),
),
);
$form['uc_ups_quote_options']['uc_ups_unit_system'] = array(
'#type' => 'select',
'#title' => t('System of measurement'),
'#default_value' => variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')),
'#options' => array(
'in' => t('British'),
'cm' => t('Metric'),
),
'#description' => t('Choose the standard system of measurement for your country.'),
);
$form['uc_ups_quote_options']['uc_ups_insurance'] = array(
'#type' => 'checkbox',
'#title' => t('Package insurance'),
'#default_value' => variable_get('uc_ups_insurance', TRUE),
'#description' => t('When enabled, the quotes presented to the customer will include the cost of insurance for the full sales price of all products in the order.'),
);
// Container for markup forms
$form['uc_ups_markups'] = array(
'#type' => 'fieldset',
'#title' => t('Markups'),
'#description' => t('Modifiers to the shipping weight and quoted rate.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'ups-settings',
);
// Form to select type of rate markup
$form['uc_ups_markups']['uc_ups_rate_markup_type'] = array(
'#type' => 'select',
'#title' => t('Rate markup type'),
'#default_value' => variable_get('uc_ups_rate_markup_type', 'percentage'),
'#options' => array(
'percentage' => t('Percentage (%)'),
'multiplier' => t('Multiplier (×)'),
'currency' => t('Addition (!currency)', array('!currency' => variable_get('uc_currency_sign', '$'))),
),
);
// Form to select rate markup amount
$form['uc_ups_markups']['uc_ups_rate_markup'] = array(
'#type' => 'textfield',
'#title' => t('Shipping rate markup'),
'#default_value' => variable_get('uc_ups_rate_markup', '0'),
'#description' => t('Markup shipping rate quote by currency amount, percentage, or multiplier.'),
);
// Form to select type of weight markup
$form['uc_ups_markups']['uc_ups_weight_markup_type'] = array(
'#type' => 'select',
'#title' => t('Weight markup type'),
'#default_value' => variable_get('uc_ups_weight_markup_type', 'percentage'),
'#options' => array(
'percentage' => t('Percentage (%)'),
'multiplier' => t('Multiplier (×)'),
'mass' => t('Addition (!mass)', array('!mass' => '#')),
),
'#disabled' => TRUE,
);
// Form to select weight markup amount
$form['uc_ups_markups']['uc_ups_weight_markup'] = array(
'#type' => 'textfield',
'#title' => t('Shipping weight markup'),
'#default_value' => variable_get('uc_ups_weight_markup', '0'),
'#description' => t('Markup UPS shipping weight on a per-package basis before quote, by weight amount, percentage, or multiplier.'),
'#disabled' => TRUE,
);
// Container for label printing
$form['uc_ups_labels'] = array(
'#type' => 'fieldset',
'#title' => t('Label Printing'),
'#description' => t('Preferences for UPS Shipping Label Printing. Additional permissions from UPS are required to use this feature.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'ups-settings',
);
$period = drupal_map_assoc(array(86400, 302400, 604800, 1209600, 2419200, 0), 'format_interval');
$period[0] = t('Forever');
// Form to select how long labels stay on server
$form['uc_ups_labels']['uc_ups_label_lifetime'] = array(
'#type' => 'select',
'#title' => t('Label lifetime'),
'#default_value' => variable_get('uc_ups_label_lifetime', 0),
'#options' => $period,
'#description' => t('Controls how long labels are stored on the server before being automatically deleted. Cron must be enabled for automatic deletion. Default is never delete the labels, keep them forever.'),
);
// Taken from system_settings_form(). Only, don't use its submit handler.
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
);
$form['actions']['cancel'] = array(
'#markup' => l(t('Cancel'), 'admin/store/settings/quotes'),
);
if (!empty($_POST) && form_get_errors()) {
drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
}
if (!isset($form['#theme'])) {
$form['#theme'] = 'system_settings_form';
}
return $form;
}
/**
* Validation handler for uc_ups_admin_settings.
*
* Requires password only if it hasn't been set.
*
* @see uc_ups_admin_settings()
* @see uc_ups_admin_settings_submit()
*/
function uc_ups_admin_settings_validate($form, &$form_state) {
$old_password = variable_get('uc_ups_password', '');
if (!$form_state['values']['uc_ups_password']) {
if ($old_password) {
form_set_value($form['uc_ups_credentials']['uc_ups_password'], $old_password, $form_state);
}
else {
form_set_error('uc_ups_password', t('Password field is required.'));
}
}
if (!is_numeric($form_state['values']['uc_ups_rate_markup'])) {
form_set_error('uc_ups_rate_markup', t('Rate markup must be a numeric value.'));
}
if (!is_numeric($form_state['values']['uc_ups_weight_markup'])) {
form_set_error('uc_ups_weight_markup', t('Weight markup must be a numeric value.'));
}
}
/**
* Submit handler for uc_ups_admin_settings().
*
* Emulates system_settings_form_submit(), but only on a subset of the
* form values.
*
* @see uc_ups_admin_settings()
* @see uc_ups_admin_settings_validate()
*/
function uc_ups_admin_settings_submit($form, &$form_state) {
$fields = array(
'uc_ups_access_license',
'uc_ups_shipper_number',
'uc_ups_user_id',
'uc_ups_password',
'uc_ups_connection_address',
'uc_ups_services',
'uc_ups_pickup_type',
'uc_ups_pkg_type',
'uc_ups_classification',
'uc_ups_negotiated_rates',
'uc_ups_residential_quotes',
'uc_ups_rate_markup_type',
'uc_ups_rate_markup',
'uc_ups_weight_markup_type',
'uc_ups_weight_markup',
'uc_ups_label_lifetime',
'uc_ups_all_in_one',
'uc_ups_unit_system',
'uc_ups_insurance',
);
foreach ($fields as $key) {
$value = $form_state['values'][$key];
if (is_array($value) && isset($form_state['values']['array_filter'])) {
$value = array_keys(array_filter($value));
}
variable_set($key, $value);
}
drupal_set_message(t('The configuration options have been saved.'));
cache_clear_all();
drupal_theme_rebuild();
}

View File

@@ -0,0 +1,36 @@
/**
* @file
* Utility functions to display settings summaries on vertical tabs.
*/
(function ($) {
Drupal.behaviors.upsAdminFieldsetSummaries = {
attach: function (context) {
$('fieldset#edit-uc-ups-credentials', context).drupalSetSummary(function(context) {
var server = $('#edit-uc-ups-connection-address :selected', context).text().toLowerCase();
return Drupal.t('Using UPS @role server', { '@role': server });
});
$('fieldset#edit-uc-ups-markups', context).drupalSetSummary(function(context) {
return Drupal.t('Rate markup') + ': '
+ $('#edit-uc-ups-rate-markup', context).val() + ' '
+ $('#edit-uc-ups-rate-markup-type', context).val() + '<br />'
+ Drupal.t('Weight markup') + ': '
+ $('#edit-uc-ups-weight-markup', context).val() + ' '
+ $('#edit-uc-ups-weight-markup-type', context).val();
});
$('fieldset#edit-uc-ups-quote-options', context).drupalSetSummary(function(context) {
if ($('#edit-uc-ups-insurance').is(':checked')) {
return Drupal.t('Packages are insured');
}
else {
return Drupal.t('Packages are not insured');
}
});
}
};
})(jQuery);

View File

@@ -0,0 +1,14 @@
name = UPS
description = Integrates UPS Rates and Services Selection and Shipping Online Tools.
dependencies[] = uc_quote
package = Ubercart - fulfillment
core = 7.x
configure = admin/store/settings/quotes/settings/ups
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,101 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_ups module.
*/
/**
* Implements hook_schema().
*/
function uc_ups_schema() {
$schema = array();
$schema['uc_ups_products'] = array(
'description' => 'Stores product information for UPS shipping quotes.',
'fields' => array(
'vid' => array(
'description' => 'The {uc_products}.vid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
'description' => 'The {uc_products}.nid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'pkg_type' => array(
'description' => 'The type of package in which the product will be shipped.',
'type' => 'varchar',
'length' => 2,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('vid'),
'foreign keys' => array(
'uc_products' => array(
'table' => 'uc_products',
'columns' => array(
'nid' => 'nid',
'vid' => 'vid',
),
),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function uc_ups_uninstall() {
variable_del('uc_ups_access_license');
variable_del('uc_ups_shipper_number');
variable_del('uc_ups_user_id');
variable_del('uc_ups_password');
variable_del('uc_ups_connection_address');
variable_del('uc_ups_services');
variable_del('uc_ups_pickup_type');
variable_del('uc_ups_pkg_type');
variable_del('uc_ups_classification');
variable_del('uc_ups_negotiated_rates');
variable_del('uc_ups_all_in_one');
variable_del('uc_ups_unit_system');
variable_del('uc_ups_insurance');
variable_del('uc_ups_rate_markup');
variable_del('uc_ups_rate_markup_type');
variable_del('uc_ups_weight_markup');
variable_del('uc_ups_weight_markup_type');
}
/**
* Separates markup variables into rate_markup and weight_markup.
*/
function uc_ups_update_7300() {
// Rename variables while preserving previous setting values
variable_set('uc_ups_rate_markup', variable_get('uc_ups_markup', ''));
variable_set('uc_ups_rate_markup_type', variable_get('uc_ups_markup_type', ''));
// Remove old variables
variable_del('uc_ups_markup');
variable_del('uc_ups_markup_type');
}
/**
* Updates UPS Production URL.
*/
function uc_ups_update_7301() {
$current = variable_get('uc_ups_connection_address', '');
// If currently using production URL, update variable to new value.
if ($current == 'https://www.ups.com/ups.app/xml/') {
variable_set('uc_ups_connection_address',
'https://onlinetools.ups.com/ups.app/xml/');
}
}

View File

@@ -0,0 +1,991 @@
<?php
/**
* @file
* UPS shipping quote module.
*/
/******************************************************************************
* Drupal Hooks *
******************************************************************************/
/**
* Implements hook_menu().
*/
function uc_ups_menu() {
$items = array();
$items['admin/store/settings/quotes/settings/ups'] = array(
'title' => '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.ship.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.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() {
$cutoff = REQUEST_TIME - variable_get('uc_ups_label_lifetime', 0);
if ($cutoff == REQUEST_TIME) {
// 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_unlink($file->uri);
watchdog('uc_ups', 'Removed uc_ups label file @file.', array('@file' => $file->uri), WATCHDOG_NOTICE);
}
}
}
/**
* Implements hook_theme().
*/
function uc_ups_theme() {
return array(
'uc_ups_option_label' => array(
'variables' => array(
'service' => NULL,
'packages' => NULL,
),
'file' => 'uc_ups.theme.inc',
),
'uc_ups_confirm_shipment' => array(
'render element' => 'form',
'file' => 'uc_ups.ship.inc',
),
'uc_ups_label_image' => array(
'variables' => array(),
'file' => 'uc_ups.ship.inc',
),
);
}
/**
* Implements hook_form_alter().
*
* Adds 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 (isset($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 (empty($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['ups'] = array(
'id' => 'ups',
'module' => 'uc_ups',
'title' => t('UPS'),
'operations' => array(
'configure' => array(
'title' => t('configure'),
'href' => 'admin/store/settings/quotes/settings/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',
'file' => 'uc_ups.ship.inc',
'pkg_types' => _uc_ups_pkg_types(),
),
'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 = 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 <a href="!url">here</a>.', array('!url' => url('admin/store/settings/quotes/settings/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 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<AccessRequest xml:lang=\"en-US\">
<AccessLicenseNumber>$access</AccessLicenseNumber>
<UserId>$user</UserId>
<Password>$password</Password>
</AccessRequest>
";
}
/**
* 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'] = uc_store_name();
$store['owner'] = variable_get('uc_store_owner', NULL);
$store['email'] = uc_store_email();
$store['email_from'] = uc_store_email();
$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) {
// Determine length conversion factor and weight conversion factor
// for this shipment.
$length_factor = uc_length_conversion($package->length_units, variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')));
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 = array('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) = array($package->width, $package->length);
}
$package_schema .= "<Dimensions>";
$package_schema .= "<UnitOfMeasurement>";
$package_schema .= "<Code>" . strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))) . "</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 (variable_get('uc_ups_insurance', TRUE)) {
$package_schema .= "<PackageServiceOptions>";
$package_schema .= "<InsuredValue>";
$package_schema .= "<CurrencyCode>" . variable_get('uc_currency_code', 'USD') . "</CurrencyCode>";
$package_schema .= "<MonetaryValue>" . $package->price . "</MonetaryValue>";
$package_schema .= "</InsuredValue>";
$package_schema .= "</PackageServiceOptions>";
}
$package_schema .= "</Package>";
}
}
$schema = uc_ups_access_request() . "
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<RatingServiceSelectionRequest xml:lang=\"en-US\">
<Request>
<TransactionReference>
<CustomerContext>Complex Rate Request</CustomerContext>
<XpciVersion>1.0001</XpciVersion>
</TransactionReference>
<RequestAction>Rate</RequestAction>
<RequestOption>rate</RequestOption>
</Request>
<PickupType>
<Code>" . variable_get('uc_ups_pickup_type', '01') . "</Code>
</PickupType>
<CustomerClassification>
<Code>" . variable_get('uc_ups_classification', '04') . "</Code>
</CustomerClassification>
<Shipment>
<Shipper>
<ShipperNumber>" . variable_get('uc_ups_shipper_number', '') . "</ShipperNumber>
<Address>
<City>" . $store['city'] . "</City>
<StateProvinceCode>$shipper_zone</StateProvinceCode>
<PostalCode>$shipper_zip</PostalCode>
<CountryCode>$shipper_country</CountryCode>
</Address>
</Shipper>
<ShipTo>
<Address>
<StateProvinceCode>$shipto_zone</StateProvinceCode>
<PostalCode>$shipto_zip</PostalCode>
<CountryCode>$shipto_country</CountryCode>
";
if (variable_get('uc_ups_residential_quotes', 0)) {
$schema .= "<ResidentialAddressIndicator/>
";
}
$schema .= "</Address>
</ShipTo>
<ShipFrom>
<Address>
<StateProvinceCode>$shipfrom_zone</StateProvinceCode>
<PostalCode>$shipfrom_zip</PostalCode>
<CountryCode>$shipfrom_country</CountryCode>
</Address>
</ShipFrom>
<ShipmentWeight>
<UnitOfMeasurement>
<Code>$units</Code>
<Description>$unit_name</Description>
</UnitOfMeasurement>
<Weight>" . number_format($shipment_weight, 1, '.', '') . "</Weight>
</ShipmentWeight>
<Service>
<Code>{$service['code']}</Code>
<Description>{$service['description']}</Description>
</Service>
";
$schema .= $package_schema;
if (variable_get('uc_ups_negotiated_rates', FALSE)) {
$schema .= "<RateInformation>
<NegotiatedRatesIndicator/>
</RateInformation>";
}
$schema .= "</Shipment>
</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 $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_ups_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;
if (empty($destination->zone) ||
empty($destination->postal_code) ||
empty($destination->country) ) {
// Skip this shipping method.
return array();
}
$quotes = array();
$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.
$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 array();
}
$dest = (object)$details;
foreach ($packages as $key => $ship_packages) {
$orig = $addresses[$key];
$orig->email = 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)) {
if (!isset($debug_data[$ups_service]['debug'])) {
$debug_data[$ups_service]['debug'] = '';
}
$debug_data[$ups_service]['debug'] .= htmlentities($request) . ' <br /><br /> ' . 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)) {
$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 == variable_get('uc_currency_code', "USD")) {
// Markup rate before customer sees it.
if (!isset($quotes[$ups_service]['rate'])) {
$quotes[$ups_service]['rate'] = 0;
}
$rate = uc_ups_rate_markup((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', array('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 $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 .= '<?xml version="1.0"?>';
$schema .= '<VoidShipmentRequest>';
$schema .= '<Request>';
$schema .= '<RequestAction>Void</RequestAction>';
$schema .= '<TransactionReference>';
$schema .= '<CustomerContext>';
$schema .= t('Void shipment @ship_number and tracking numbers @track_list', array('@ship_number' => $shipment_number, '@track_list' => implode(', ', $tracking_numbers)));
$schema .= '</CustomerContext>';
$schema .= '<XpciVersion>1.0</XpciVersion>';
$schema .= '</TransactionReference>';
$schema .= '</Request>';
$schema .= '<ExpandedVoidShipment>';
$schema .= '<ShipmentIdentificationNumber>' . $shipment_number . '</ShipmentIdentificationNumber>';
foreach ($tracking_numbers as $number) {
$schema .= '<TrackingNumber>' . $number . '</TrackingNumber>';
}
$schema .= '</ExpandedVoidShipment>';
$schema .= '</VoidShipmentRequest>';
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.
*
* @param $rate
* Shipping rate without any rate markup.
*
* @return
* Shipping rate after markup.
*/
function uc_ups_rate_markup($rate) {
$markup = trim(variable_get('uc_ups_rate_markup', '0'));
$type = variable_get('uc_ups_rate_markup_type', 'percentage');
if (is_numeric($markup)) {
switch ($type) {
case 'percentage':
return $rate + $rate * floatval($markup) / 100;
case 'multiplier':
return $rate * floatval($markup);
case 'currency':
return $rate + floatval($markup);
}
}
else {
return $rate;
}
}
/**
* Modifies the weight of shipment before sending to UPS for a quote.
*
* @param $weight
* Shipping weight without any weight markup.
*
* @return
* Shipping weight after markup.
*/
function uc_ups_weight_markup($weight) {
$markup = trim(variable_get('uc_ups_weight_markup', '0'));
$type = variable_get('uc_ups_weight_markup_type', 'percentage');
if (is_numeric($markup)) {
switch ($type) {
case 'percentage':
return $weight + $weight * floatval($markup) / 100;
case 'multiplier':
return $weight * floatval($markup);
case 'mass':
return $weight + floatval($markup);
}
}
else {
return $weight;
}
}
/**
* 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(
// Customer Supplied Page is first so it will be the default.
'02' => t('Customer Supplied Package'),
'01' => t('UPS Letter'),
'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;
}

View File

@@ -0,0 +1,816 @@
<?php
/**
* @file
* UPS functions for label generation.
*/
/**
* 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();
// Container for package data
$form['packages'] = array(
'#type' => 'fieldset',
'#title' => t('Packages'),
'#collapsible' => TRUE,
'#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 (last one in
// the loop) to use for some 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;
}
// Use last product in package to determine package type
$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_form['weight'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('uc-inline-form', 'clearfix')),
'#description' => t('Weight of the package. Default value is sum of product weights in the package.'),
'#weight' => 15,
);
$pkg_form['weight']['weight'] = array(
'#type' => 'textfield',
'#title' => t('Weight'),
'#default_value' => isset($package->weight) ? $package->weight : 0,
'#size' => 10,
'#maxlength' => 15,
);
$pkg_form['weight']['units'] = array(
'#type' => 'select',
'#title' => t('Units'),
'#options' => array(
'lb' => t('Pounds'),
'kg' => t('Kilograms'),
'oz' => t('Ounces'),
'g' => t('Grams'),
),
'#default_value' => isset($package->weight_units) ?
$package->weight_units :
variable_get('uc_weight_unit', 'lb'),
);
$pkg_form['dimensions'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('uc-inline-form', 'clearfix')),
'#description' => t('Physical dimensions of the package.'),
'#weight' => 20,
);
$pkg_form['dimensions']['length'] = array(
'#type' => 'textfield',
'#title' => t('Length'),
'#default_value' => isset($package->length) ? $package->length : 1,
'#size' => 10,
);
$pkg_form['dimensions']['width'] = array(
'#type' => 'textfield',
'#title' => t('Width'),
'#default_value' => isset($package->width) ? $package->width : 1,
'#size' => 10,
);
$pkg_form['dimensions']['height'] = array(
'#type' => 'textfield',
'#title' => t('Height'),
'#default_value' => isset($package->height) ? $package->height : 1,
'#size' => 10,
);
$pkg_form['dimensions']['units'] = array(
'#type' => 'select',
'#title' => t('Units'),
'#options' => array(
'in' => t('Inches'),
'ft' => t('Feet'),
'cm' => t('Centimeters'),
'mm' => t('Millimeters'),
),
'#default_value' => isset($package->length_units) ?
$package->length_units :
variable_get('uc_length_unit', 'in'),
);
$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_country', 'delivery_postal_code') as $field) {
$form['destination'][$field]['#required'] = TRUE;
}
// Determine shipping option chosen by the customer
$method = $order->quote['method'];
$methods = module_invoke_all('uc_shipping_method');
if (isset($methods[$method])) {
$services = $methods[$method]['quote']['accessorials'];
$method = $services[$order->quote['accessorials']];
}
// Container for shipment data
$form['shipment'] = array(
'#type' => 'fieldset',
'#title' => t('Shipment data'),
'#collapsible' => TRUE,
);
// Inform user of customer's shipping choice
$form['shipment']['shipping_choice'] = array(
'#type' => 'markup',
'#prefix' => '<div>',
'#markup' => t('Customer selected "@method" as the shipping method and paid @rate', array('@method' => $method, '@rate' => uc_currency_format($order->quote['rate']))),
'#suffix' => '</div>',
);
// Pass shipping charge paid information on to validation function so it
// can be displayed alongside actual costs
$form['shipment']['paid'] = array(
'#type' => 'value',
'#value' => uc_currency_format($order->quote['rate']),
);
$services = _uc_ups_service_list();
$default_service = '';
if ($method == 'ups') {
$default_service = $order->quote['accessorials'];
}
$form['shipment']['service'] = array(
'#type' => 'select',
'#title' => t('UPS service'),
'#options' => $services,
'#default_value' => $default_service,
);
$today = getdate();
$form['shipment']['ship_date'] = array(
'#type' => 'date',
'#title' => t('Ship date'),
'#default_value' => array(
'year' => $today['year'],
'month' => $today['mon'],
'day' => $today['mday']
),
);
$form['shipment']['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) {
$errors = form_get_errors();
if (isset($errors)) {
// Some required elements are missing - don't bother with making
// a UPS API call until that gets fixed.
return;
}
$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;
}
}
// This is a total hack to work around changes made in the return value
// from uc_shipping_address_form(). That function needs to be fixed, but
// until then this should do the trick.
$origin = $form_state['values']['pickup_address'];
$origin->phone = $form_state['values']['phone'];
$origin->first_name = $form_state['values']['first_name'];
$origin->last_name = $form_state['values']['last_name'];
$origin->company = $form_state['values']['company'];
$origin->street1 = $form_state['values']['street1'];
$origin->street2 = $form_state['values']['street2'];
$origin->city = $form_state['values']['city'];
$origin->zone = $form_state['values']['zone'];
$origin->country = $form_state['values']['country'];
$origin->postal_code = $form_state['values']['postal_code'];
$origin->email = $form_state['values']['pickup_email'];
$_SESSION['ups'] = array();
$_SESSION['ups']['origin'] = $origin;
if (empty($destination->company)) {
$destination->company = $destination->first_name . ' ' . $destination->last_name;
}
// Determine if address is Residential or Commercial
$destination->residential = variable_get('uc_ups_residential_quotes', FALSE);
$_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->weight = $pkg_form['weight']['weight'];
$package->weight_units = $pkg_form['weight']['units'];
$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']['paid'] = $form_state['values']['paid'];
$_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 an XML shipment 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'] = uc_store_name();
$store['owner'] = variable_get('uc_store_owner', NULL);
$store['email'] = uc_store_email();
$store['email_from'] = uc_store_email();
$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) {
// Determine length conversion factor and weight conversion factor
// for this shipment
$length_factor = uc_length_conversion($package->length_units, variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')));
switch ($ups_units) {
case 'in':
$weight_factor = uc_weight_conversion($package->weight_units, 'lb');
$units = 'LBS';
$unit_name = 'Pounds';
break;
case 'cm':
$weight_factor = uc_weight_conversion($package->weight_units, 'kg');
$units = 'KGS';
$unit_name = 'Kilograms';
break;
}
$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>";
$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) = array($package->width, $package->length);
}
$package_schema .= "<Dimensions>";
$package_schema .= "<UnitOfMeasurement>";
$package_schema .= "<Code>" . strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))) . "</Code>";
$package_schema .= "</UnitOfMeasurement>";
$package_schema .= "<Length>" . (floor($package->length * $length_factor) + 1) . "</Length>";
$package_schema .= "<Width>" . (floor($package->width * $length_factor) + 1) . "</Width>";
$package_schema .= "<Height>" . (floor($package->height * $length_factor) + 1) . "</Height>";
$package_schema .= "</Dimensions>";
}
$size = $package->length * $length_factor + 2 * $length_factor * ($package->width + $package->height);
$weight = max(1, $package->weight * $weight_factor);
$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>";
if ($size > 130 && $size <= 165) {
$package_schema .= "<LargePackageIndicator/>";
}
$package_schema .= "<PackageServiceOptions>";
$package_schema .= "<InsuredValue>";
$package_schema .= "<CurrencyCode>" . variable_get('uc_currency_code', 'USD') . "</CurrencyCode>";
$package_schema .= "<MonetaryValue>" . number_format($package->value, 2, '.', '') . "</MonetaryValue>";
$package_schema .= "</InsuredValue>";
$package_schema .= "</PackageServiceOptions>";
$package_schema .= "</Package>";
}
}
$schema = uc_ups_access_request() . "
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<ShipmentConfirmRequest xml:lang=\"en-US\">
<Request>
<TransactionReference>
<CustomerContext>Complex Rate Request</CustomerContext>
<XpciVersion>1.0001</XpciVersion>
</TransactionReference>
<RequestAction>ShipConfirm</RequestAction>
<RequestOption>validate</RequestOption>
</Request>
<Shipment>";
$schema .= "<Shipper>";
$schema .= "<Name>" . $store['name'] . "</Name>";
$schema .= "<ShipperNumber>" . variable_get('uc_ups_shipper_number', '') . "</ShipperNumber>";
if ($store['phone']) {
$schema .= "<PhoneNumber>" . $store['phone'] . "</PhoneNumber>";
}
if ($store['fax']) {
$schema .= "<FaxNumber>" . $store['fax'] . "</FaxNumber>";
}
if ($store['email']) {
$schema .= "<EMailAddress>" . $store['email'] . "</EMailAddress>";
}
$schema .= "<Address>";
$schema .= "<AddressLine1>" . $store['street1'] . "</AddressLine1>";
if ($store['street2']) {
$schema .= "<AddressLine2>" . $store['street2'] . "</AddressLine2>";
}
$schema .= "<City>" . $store['city'] . "</City>";
$schema .= "<StateProvinceCode>$shipper_zone</StateProvinceCode>";
$schema .= "<PostalCode>$shipper_zip</PostalCode>";
$schema .= "<CountryCode>$shipper_country</CountryCode>";
$schema .= "</Address>";
$schema .= "</Shipper>";
$schema .= "<ShipTo>";
$schema .= "<CompanyName>" . $destination->company . "</CompanyName>";
$schema .= "<AttentionName>" . $destination->first_name . ' ' . $destination->last_name . "</AttentionName>";
$schema .= "<PhoneNumber>" . $destination->phone . "</PhoneNumber>";
$schema .= "<EMailAddress>" . $destination->email . "</EMailAddress>";
$schema .= "<Address>";
$schema .= "<AddressLine1>" . $destination->street1 . "</AddressLine1>";
if ($destination->street2) {
$schema .= "<AddressLine2>" . $destination->street2 . "</AddressLine2>";
}
$schema .= "<City>" . $destination->city . "</City>";
$schema .= "<StateProvinceCode>$shipto_zone</StateProvinceCode>";
$schema .= "<PostalCode>$shipto_zip</PostalCode>";
$schema .= "<CountryCode>$shipto_country</CountryCode>";
if ($destination->residential) {
$schema .= "<ResidentialAddressIndicator/>";
}
$schema .= "</Address>";
$schema .= "</ShipTo>";
$schema .= "<ShipFrom>";
$schema .= "<CompanyName>" . $origin->company . "</CompanyName>";
$schema .= "<AttentionName>" . $origin->first_name . ' ' . $origin->last_name . "</AttentionName>";
$schema .= "<PhoneNumber>" . $origin->phone . "</PhoneNumber>";
$schema .= "<EMailAddress>" . $origin->email . "</EMailAddress>";
$schema .= "<Address>";
$schema .= "<AddressLine1>" . $origin->street1 . "</AddressLine1>";
if ($origin->street2) {
$schema .= "<AddressLine2>" . $origin->street2 . "</AddressLine2>";
}
$schema .= "<City>" . $origin->city . "</City>";
$schema .= "<StateProvinceCode>$shipfrom_zone</StateProvinceCode>";
$schema .= "<PostalCode>$shipfrom_zip</PostalCode>";
$schema .= "<CountryCode>$shipfrom_country</CountryCode>";
$schema .= "</Address>";
$schema .= "</ShipFrom>";
$schema .= "<PaymentInformation>";
$schema .= "<Prepaid>";
$schema .= "<BillShipper>";
$schema .= "<AccountNumber>$account</AccountNumber>";
$schema .= "</BillShipper>";
$schema .= "</Prepaid>";
$schema .= "</PaymentInformation>";
if (variable_get('uc_ups_negotiated_rates', FALSE)) {
$schema .= "<RateInformation>
<NegotiatedRatesIndicator/>
</RateInformation>";
}
$schema .= "<Service>";
$schema .= "<Code>{$service['code']}</Code>";
$schema .= "<Description>{$service['description']}</Description>";
$schema .= "</Service>";
$schema .= $package_schema;
$schema .= "</Shipment>";
$schema .= "<LabelSpecification>";
$schema .= "<LabelPrintMethod>";
$schema .= "<Code>GIF</Code>";
$schema .= "</LabelPrintMethod>";
$schema .= "<LabelImageFormat>";
$schema .= "<Code>GIF</Code>";
$schema .= "</LabelImageFormat>";
$schema .= "</LabelSpecification>";
$schema .= "</ShipmentConfirmRequest>";
return $schema;
}
/**
* Last chance for user to review shipment.
*
* @see uc_ups_confirm_shipment_submit()
* @see theme_uc_ups_confirm_shipment()
* @ingroup forms
*/
function uc_ups_confirm_shipment($form, &$form_state, $order) {
$form['digest'] = array(
'#type' => 'hidden',
'#value' => $_SESSION['ups']['digest']
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Request Pickup')
);
return $form;
}
/**
* Displays final shipment information for review.
*
* @see uc_ups_confirm_shipment()
* @ingroup themeable
*/
function theme_uc_ups_confirm_shipment($variables) {
$form = $variables['form'];
$output = '<div class="shipping-address"><b>' . t('Ship from:') . '</b><br />';
$output .= uc_address_format(
check_plain($_SESSION['ups']['origin']->first_name),
check_plain($_SESSION['ups']['origin']->last_name),
check_plain($_SESSION['ups']['origin']->company),
check_plain($_SESSION['ups']['origin']->street1),
check_plain($_SESSION['ups']['origin']->street2),
check_plain($_SESSION['ups']['origin']->city),
check_plain($_SESSION['ups']['origin']->zone),
check_plain($_SESSION['ups']['origin']->postal_code),
check_plain($_SESSION['ups']['origin']->country)
);
$output .= '<br />' . check_plain($_SESSION['ups']['origin']->email);
$output .= '</div>';
$output .= '<div class="shipping-address"><b>' . t('Ship to:') . '</b><br />';
$output .= uc_address_format(
check_plain($_SESSION['ups']['destination']->first_name),
check_plain($_SESSION['ups']['destination']->last_name),
check_plain($_SESSION['ups']['destination']->company),
check_plain($_SESSION['ups']['destination']->street1),
check_plain($_SESSION['ups']['destination']->street2),
check_plain($_SESSION['ups']['destination']->city),
check_plain($_SESSION['ups']['destination']->zone),
check_plain($_SESSION['ups']['destination']->postal_code),
check_plain($_SESSION['ups']['destination']->country)
);
$output .= '<br />' . check_plain($_SESSION['ups']['destination']->email);
$output .= '</div>';
$output .= '<div class="shipment-data">';
$method = uc_ups_uc_shipping_method();
$output .= '<b>' . $method['ups']['quote']['accessorials'][$_SESSION['ups']['service']] . '</b><br />';
$output .= '<i>' . check_plain($_SESSION['ups']['rate']['type']) . '</i>: ' . theme('uc_price', array('price' => $_SESSION['ups']['rate']['amount'])) . ' (' . check_plain($_SESSION['ups']['rate']['currency']) . ') -- ';
$output .= '<i>' . t('Paid') . '</i>: ' . $_SESSION['ups']['paid'] . '<br />';
$ship_date = $_SESSION['ups']['ship_date'];
$output .= 'Ship date: ' . format_date(gmmktime(12, 0, 0, $ship_date['month'], $ship_date['day'], $ship_date['year']), 'uc_store');
$exp_delivery = $_SESSION['ups']['expected_delivery'];
$output .= '<br />Expected delivery: ' . format_date(gmmktime(12, 0, 0, $exp_delivery['month'], $exp_delivery['day'], $exp_delivery['year']), 'uc_store');
$output .= "</div>\n<br style=\"clear: both;\" />";
$output .= drupal_render_children($form);
return $output;
}
/**
* Generates label and schedules pickup of the shipment.
*
* @see uc_ups_confirm_shipment()
*/
function uc_ups_confirm_shipment_submit($form, &$form_state) {
// Request pickup using parameters in form.
$order_id = $_SESSION['ups']['order_id'];
$packages = array_keys($_SESSION['ups']['packages']);
$request = uc_ups_request_pickup($form_state['values']['digest'], $order_id, $packages);
$result = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') . 'ShipAccept', array(
'method' => 'POST',
'data' => $request,
));
$response = new SimpleXMLElement($result->data);
$code = (string) $response->Response->ResponseStatusCode;
if ($code == 0) { // failed request
$error = $response->Response->Error;
$error_severity = (string) $error->ErrorSeverity;
$error_code = (string) $error->ErrorCode;
$error_description = (string) $error->ErrorDescription;
drupal_set_message(t('(@severity error @code) @description', array('@severity' => $error_severity, '@code' => $error_code, '@description' => $error_description)), 'error');
if ($error_severity == 'HardError') {
$form_state['redirect'] = 'admin/store/orders/' . $order_id . '/shipments/ups/' . implode('/', $packages);
return;
}
}
$shipment = new stdClass();
$shipment->order_id = $order_id;
$shipment->origin = clone $_SESSION['ups']['origin'];
$shipment->destination = clone $_SESSION['ups']['destination'];
$shipment->packages = $_SESSION['ups']['packages'];
$shipment->shipping_method = 'ups';
$shipment->accessorials = $_SESSION['ups']['service'];
$shipment->carrier = t('UPS');
// if NegotiatedRates exist, quote based on those, otherwise, use TotalCharges
if (isset($response->ShipmentResults->ShipmentCharges)) {
$charge = $response->ShipmentResults->ShipmentCharges->TotalCharges;
if (isset($response->ShipmentResults->NegotiatedRates)) {
$charge = $response->ShipmentResults->NegotiatedRates->NetSummaryCharges->GrandTotal;
}
}
$cost = (string) $charge->MonetaryValue;
$shipment->cost = $cost;
$shipment->tracking_number = (string) $response->ShipmentResults->ShipmentIdentificationNumber;
$ship_date = $_SESSION['ups']['ship_date'];
$shipment->ship_date = gmmktime(12, 0, 0, $ship_date['month'], $ship_date['day'], $ship_date['year']);
$exp_delivery = $_SESSION['ups']['expected_delivery'];
$shipment->expected_delivery = gmmktime(12, 0, 0, $exp_delivery['month'], $exp_delivery['day'], $exp_delivery['year']);
foreach ($response->ShipmentResults->PackageResults as $package_results) {
$package =& current($shipment->packages);
$package->tracking_number = (string) $package_results->TrackingNumber;
$label_image = (string) $package_results->LabelImage->GraphicImage;
// Save the label
$directory = 'public://ups_labels';
if (file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
$label_path = $directory . '/label-' . $package->tracking_number . '.gif';
if ($label_file = file_save_data(base64_decode($label_image), $label_path, FILE_EXISTS_REPLACE)) {
file_usage_add($label_file, 'uc_shipping', 'package', $package->package_id);
$package->label_image = $label_file;
}
else {
drupal_set_message(t('Could not open a file to save the label image.'), 'error');
}
}
else {
drupal_set_message(t('Could not find or create the directory %directory in the file system path.', array('%directory' => $directory)), 'error');
}
unset($package);
next($shipment->packages);
}
uc_shipping_shipment_save($shipment);
unset($_SESSION['ups']);
$form_state['redirect'] = 'admin/store/orders/' . $order_id . '/shipments';
}
/**
* Constructs an XML label and pickup request.
*
* @param $digest
* Base-64 encoded shipment request.
* @param $order_id
* The order id of the shipment.
* @param $packages
* An array of package ids to be shipped.
*
* @return
* ShipmentAcceptRequest XML document to send to UPS.
*/
function uc_ups_request_pickup($digest, $order_id = 0, $packages = array()) {
$packages = (array)$packages;
$schema = uc_ups_access_request();
$schema .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<ShipmentAcceptRequest>
<Request>
<RequestAction>ShipAccept</RequestAction>";
if ($order_id || count($packages)) {
$schema .= "\n<TransactionReference>
<CustomerContext>";
if ($order_id) {
$schema .= "<OrderId>" . $order_id . "</OrderId>\n";
}
foreach ($packages as $pkg_id) {
$schema .= "<PackageId>" . $pkg_id . "</PackageId>\n";
}
$schema .= "</CustomerContext>\n</TransactionReference>\n";
}
$schema .= " </Request>
<ShipmentDigest>" . $digest . "</ShipmentDigest>
</ShipmentAcceptRequest>";
return $schema;
}
/**
* Displays the shipping label for printing.
*
* Each argument is a component of the file path to the image.
*
* @ingroup themeable
*/
function theme_uc_ups_label_image() {
$args = explode('/', $_GET['q'], 8);
if (count($args) != 8) {
return MENU_NOT_FOUND;
}
$image_path = file_create_url(file_stream_wrapper_uri_normalize($args[7]));
$output = <<<EOLABEL
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.2//EN">
<html>
<head>
<title>View/Print Label</title>
<style>
.small-text {font-size: 80%;}
.large-text {font-size: 115%;}
</style>
</head>
<body bgcolor="#FFFFFF">
<table border="0" cellpadding="0" cellspacing="0" width="600"><tr>
<td height="410" align="left" valign="top">
<b class="large-text">View/Print Label</b>
&nbsp;<br />
<ol class="small-text"> <li><b>Print the label:</b> &nbsp;
Select Print from the File menu in this browser window to print the label below.<br /><br /><li><b>
Fold the printed label at the dotted line.</b> &nbsp;
Place the label in a UPS Shipping Pouch. If you do not have a pouch, affix the folded label using clear plastic shipping tape over the entire label.<br /><br /><li><b>GETTING YOUR SHIPMENT TO UPS<br />
Customers without a Daily Pickup</b><ul><li>Ground, 3 Day Select, and Standard to Canada shipments must be dropped off at an authorized UPS location, or handed to a UPS driver. Pickup service is not available for these services. To find the nearest drop-off location, select the Drop-off icon from the UPS tool bar.<li>
Air shipments (including Worldwide Express and Expedited) can be picked up or dropped off. To schedule a pickup, or to find a drop-off location, select the Pickup or Drop-off icon from the UPS tool bar. </ul> <br />
<b>Customers with a Daily Pickup</b><ul><li>
Your driver will pickup your shipment(s) as usual. </ul>
</ol></td></tr></table><table border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td class="small-text" align="left" valign="top">
&nbsp;&nbsp;&nbsp;
FOLD HERE</td>
</tr>
<tr>
<td align="left" valign="top"><hr />
</td>
</tr>
</table>
<table>
<tr>
<td height="10">&nbsp;
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="650" ><tr>
<td align="left" valign="top">
<img src="$image_path" height="392" width="672">
</td>
</tr></table>
</body>
</html>
EOLABEL;
print $output;
exit();
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* @file
* Theme functions for the uc_ups module.
*/
/**
* Theme function to format the UPS service name and rate amount line-item
* shown to the customer.
*
* @param $variables
* Associative array containing information needed to theme a quote.
* Contains two keys:
* - service: The UPS service name.
* - packages: Package information.
*
* @ingroup themeable
*/
function theme_uc_ups_option_label($variables) {
$service = $variables['service'];
$packages = $variables['packages'];
// Start with logo as required by the UPS terms of service.
$output = theme('image', array(
'path' => drupal_get_path('module', 'uc_ups') . '/uc_ups_logo.gif',
'alt' => t('UPS logo'),
'attributes' => array('class' => 'ups-logo')
));
// Add the UPS service name.
$output .= t('@service Rate', array('@service' => $service));
// Add package information
$output .= ' (' . format_plural(count($packages), '1 package', '@count packages') . ')';
return $output;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,270 @@
<?php
/**
* @file
* USPS administration menu items.
*/
/**
* Configures USPS settings.
*
* @see uc_usps_admin_settings_validate()
* @see uc_usps_admin_settings_submit()
* @ingroup forms
*/
function uc_usps_admin_settings($form, &$form_state) {
// Put fieldsets into vertical tabs
$form['usps-settings'] = array(
'#type' => 'vertical_tabs',
'#attached' => array(
'js' => array(
'vertical-tabs' => drupal_get_path('module', 'uc_usps') . '/uc_usps.admin.js',
),
),
);
// Container for credential forms
$form['uc_usps_credentials'] = array(
'#type' => 'fieldset',
'#title' => t('Credentials'),
'#description' => t('Account number and authorization information.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'usps-settings',
);
$form['uc_usps_credentials']['uc_usps_user_id'] = array(
'#type' => 'textfield',
'#title' => t('USPS user ID'),
'#description' => t('To acquire or locate your user ID, refer to the <a href="!url">USPS documentation</a>.', array('!url' => 'http://drupal.org/node/1308256')),
'#default_value' => variable_get('uc_usps_user_id', ''),
);
$form['domestic'] = array(
'#type' => 'fieldset',
'#title' => t('USPS Domestic'),
'#description' => t('Set the conditions that will return a USPS quote.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'usps-settings',
);
$form['domestic']['uc_usps_online_rates'] = array(
'#type' => 'checkbox',
'#title' => t('Display USPS "online" rates'),
'#default_value' => variable_get('uc_usps_online_rates', FALSE),
'#description' => t('Show your customer standard USPS rates (default) or discounted "online" rates. Online rates apply only if you, the merchant, pay for and print out postage from the USPS <a href="https://sss-web.usps.com/cns/landing.do">Click-N-Ship</a> web site.'),
);
$form['domestic']['uc_usps_env_services'] = array(
'#type' => 'checkboxes',
'#title' => t('USPS envelope services'),
'#default_value' => variable_get('uc_usps_env_services', array_keys(_uc_usps_env_services())),
'#options' => _uc_usps_env_services(),
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
$form['domestic']['uc_usps_services'] = array(
'#type' => 'checkboxes',
'#title' => t('USPS parcel services'),
'#default_value' => variable_get('uc_usps_services', array_keys(_uc_usps_services())),
'#options' => _uc_usps_services(),
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
$form['international'] = array(
'#type' => 'fieldset',
'#title' => t('USPS International'),
'#description' => t('Set the conditions that will return a USPS International quote.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'usps-settings',
);
$form['international']['uc_usps_intl_env_services'] = array(
'#type' => 'checkboxes',
'#title' => t('USPS international envelope services'),
'#default_value' => variable_get('uc_usps_intl_env_services', array_keys(_uc_usps_intl_env_services())),
'#options' => _uc_usps_intl_env_services(),
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
$form['international']['uc_usps_intl_services'] = array(
'#type' => 'checkboxes',
'#title' => t('USPS international parcel services'),
'#default_value' => variable_get('uc_usps_intl_services', array_keys(_uc_usps_intl_services())),
'#options' => _uc_usps_intl_services(),
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
// Container for quote options
$form['uc_usps_quote_options'] = array(
'#type' => 'fieldset',
'#title' => t('Quote options'),
'#description' => t('Preferences that affect computation of quote.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'usps-settings',
);
$form['uc_usps_quote_options']['uc_usps_all_in_one'] = array(
'#type' => 'radios',
'#title' => t('Product packages'),
'#default_value' => variable_get('uc_usps_all_in_one', 1),
'#options' => array(
0 => t('Each product in its own package'),
1 => t('All products in one package'),
),
'#description' => t('Indicate whether each product is quoted as shipping separately or all in one package. Orders with one kind of product will still use the package quantity to determine the number of packages needed, however.'),
);
// Insurance
$form['uc_usps_quote_options']['uc_usps_insurance'] = array(
'#type' => 'checkbox',
'#title' => t('Package insurance'),
'#default_value' => variable_get('uc_usps_insurance', FALSE),
'#description' => t('When enabled, the quotes presented to the customer will include the cost of insurance for the full sales price of all products in the order.'),
'#disabled' => TRUE,
);
// Delivery Confirmation
$form['uc_usps_quote_options']['uc_usps_delivery_confirmation'] = array(
'#type' => 'checkbox',
'#title' => t('Delivery confirmation'),
'#default_value' => variable_get('uc_usps_delivery_confirmation', FALSE),
'#description' => t('When enabled, the quotes presented to the customer will include the cost of delivery confirmation for all packages in the order.'),
'#disabled' => TRUE,
);
// Signature Confirmation
$form['uc_usps_quote_options']['uc_usps_signature_confirmation'] = array(
'#type' => 'checkbox',
'#title' => t('Signature confirmation'),
'#default_value' => variable_get('uc_usps_signature_confirmation', FALSE),
'#description' => t('When enabled, the quotes presented to the customer will include the cost of signature confirmation for all packages in the order.'),
'#disabled' => TRUE,
);
// Container for markup forms
$form['uc_usps_markups'] = array(
'#type' => 'fieldset',
'#title' => t('Markups'),
'#description' => t('Modifiers to the shipping weight and quoted rate.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'usps-settings',
);
$form['uc_usps_markups']['uc_usps_rate_markup_type'] = array(
'#type' => 'select',
'#title' => t('Rate markup type'),
'#default_value' => variable_get('uc_usps_rate_markup_type', 'percentage'),
'#options' => array(
'percentage' => t('Percentage (%)'),
'multiplier' => t('Multiplier (×)'),
'currency' => t('Addition (!currency)', array('!currency' => variable_get('uc_currency_sign', '$'))),
),
);
$form['uc_usps_markups']['uc_usps_rate_markup'] = array(
'#type' => 'textfield',
'#title' => t('Shipping rate markup'),
'#default_value' => variable_get('uc_usps_rate_markup', '0'),
'#description' => t('Markup shipping rate quote by dollar amount, percentage, or multiplier.'),
);
// Form to select type of weight markup
$form['uc_usps_markups']['uc_usps_weight_markup_type'] = array(
'#type' => 'select',
'#title' => t('Weight markup type'),
'#default_value' => variable_get('uc_usps_weight_markup_type', 'percentage'),
'#options' => array(
'percentage' => t('Percentage (%)'),
'multiplier' => t('Multiplier (×)'),
'mass' => t('Addition (!mass)', array('!mass' => '#')),
),
'#disabled' => TRUE,
);
// Form to select weight markup amount
$form['uc_usps_markups']['uc_usps_weight_markup'] = array(
'#type' => 'textfield',
'#title' => t('Shipping weight markup'),
'#default_value' => variable_get('uc_usps_weight_markup', '0'),
'#description' => t('Markup shipping weight on a per-package basis before quote, by weight amount, percentage, or multiplier.'),
'#disabled' => TRUE,
);
// Taken from system_settings_form(). Only, don't use its submit handler.
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
);
$form['actions']['cancel'] = array(
'#markup' => l(t('Cancel'), 'admin/store/settings/quotes'),
);
if (!empty($_POST) && form_get_errors()) {
drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
}
if (!isset($form['#theme'])) {
$form['#theme'] = 'system_settings_form';
}
return $form;
}
/**
* Validation handler for uc_usps_admin_settings form.
*
* @see uc_usps_admin_settings()
* @see uc_usps_admin_settings_submit()
*/
function uc_usps_admin_settings_validate($form, &$form_state) {
if (!is_numeric($form_state['values']['uc_usps_rate_markup'])) {
form_set_error('uc_usps_rate_markup', t('Rate markup must be a numeric value.'));
}
if (!is_numeric($form_state['values']['uc_usps_weight_markup'])) {
form_set_error('uc_usps_weight_markup', t('Weight markup must be a numeric value.'));
}
}
/**
* Submit handler for uc_usps_admin_settings form.
*
* @see uc_usps_admin_settings()
* @see uc_usps_admin_settings_validate()
*/
function uc_usps_admin_settings_submit($form, &$form_state) {
$fields = array(
'uc_usps_user_id',
'uc_usps_online_rates',
'uc_usps_env_services',
'uc_usps_services',
'uc_usps_intl_env_services',
'uc_usps_intl_services',
'uc_usps_rate_markup_type',
'uc_usps_rate_markup',
'uc_usps_weight_markup_type',
'uc_usps_weight_markup',
'uc_usps_all_in_one',
'uc_usps_insurance',
'uc_usps_delivery_confirmation',
'uc_usps_signature_confirmation',
);
foreach ($fields as $key) {
$value = $form_state['values'][$key];
if (is_array($value) && isset($form_state['values']['array_filter'])) {
$value = array_keys(array_filter($value));
}
variable_set($key, $value);
}
drupal_set_message(t('The configuration options have been saved.'));
cache_clear_all();
drupal_theme_rebuild();
}

View File

@@ -0,0 +1,30 @@
/**
* @file
* Utility functions to display settings summaries on vertical tabs.
*/
(function ($) {
Drupal.behaviors.uspsAdminFieldsetSummaries = {
attach: function (context) {
$('fieldset#edit-domestic', context).drupalSetSummary(function(context) {
if ($('#edit-uc-usps-online-rates').is(':checked')) {
return Drupal.t('Using "online" rates');
}
else {
return Drupal.t('Using standard rates');
}
});
$('fieldset#edit-uc-usps-markups', context).drupalSetSummary(function(context) {
return Drupal.t('Rate markup') + ': '
+ $('#edit-uc-usps-rate-markup', context).val() + ' '
+ $('#edit-uc-usps-rate-markup-type', context).val() + '<br />'
+ Drupal.t('Weight markup') + ': '
+ $('#edit-uc-usps-weight-markup', context).val() + ' '
+ $('#edit-uc-usps-weight-markup-type', context).val();
});
}
};
})(jQuery);

View File

@@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains the map of ISO country codes to USPS Individual Country Listings.
*/
/**
* Returns the country name formatted according to the USPS requirements.
*
* USPS uses the ISO 3166 English short names in most cases. This function
* handles the exceptions.
*
* @param $code
* ISO 3166-1 3-digit numerical country code.
*
* @return
* Country name string for use by the USPS International Rate API.
*
* @see http://pe.usps.gov/text/imm/immctry.htm
*/
function uc_usps_country_map($code = NULL) {
$countries = array(
248 => 'Aland Island (Finland)',
334 => 'Australia', // Heard Island and McDonald Islands
68 => 'Bolivia',
535 => 'Bonaire (Netherlands Antilles)',
92 => 'British Virgin Islands',
166 => 'Cocos Island (Australia)',
180 => 'Congo, Democratic Republic of the',
178 => 'Congo, Republic of the',
531 => 'Curacao (Netherlands Antilles)',
384 => "Cote d'Ivoire",
626 => 'East Timor (Indonesia)',
238 => 'Falkland Islands',
260 => 'France', // French Southern Territories
268 => 'Georgia, Republic of',
826 => 'Great Britain and Northern Ireland',
86 => 'Great Britain and Northern Ireland', // British Indian Ocean Territory
239 => 'Great Britain and Northern Ireland', // South Georgia and the
// South Sandwich Islands
364 => 'Iran',
275 => 'Israel', // Palestinian Territory, Occupied
833 => 'Isle of Man (Great Britain and Northern Ireland)',
498 => 'Moldova',
732 => 'Morocco', // Western Sahara
408 => "Korea, Democratic People's Republic of (North Korea)",
74 => 'Norway', // Bouvet Island
744 => 'Norway', // Svalbard and Jan Mayen
410 => 'Korea, Republic of (South Korea)',
418 => 'Laos',
492 => 'Monaco (France)',
104 => 'Myanmar (Burma)',
612 => 'Pitcairn Island',
638 => 'Reunion',
643 => 'Russia',
688 => 'Serbia, Republic of',
652 => 'Saint Barthelemy (Guadeloupe)',
654 => 'Saint Helena',
534 => 'Saint Maarten (Dutch) (Netherlands Antilles)',
663 => 'Saint Martin (French) (Guadeloupe)',
688 => 'Serbia-Montenegro',
703 => 'Slovak Republic',
158 => 'Taiwan',
834 => 'Tanzania',
792 => 'Turkey',
804 => 'Ukraine',
336 => 'Vatican City',
862 => 'Venezuela',
876 => 'Wallis and Futuna Islands',
);
if ($code) {
if (isset($countries[$code])) {
return $countries[$code];
}
else {
return uc_country_get_by_id($code);
}
}
return $countries;
}

View File

@@ -0,0 +1,14 @@
name = U.S. Postal Service
description = Integrates USPS Rate Calculator and Mail Service Standards Web Tools.
dependencies[] = uc_quote
package = Ubercart - fulfillment
core = 7.x
configure = admin/store/settings/quotes/settings/usps
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,85 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_usps module.
*/
/**
* Implements hook_schema().
*/
function uc_usps_schema() {
$schema = array();
$schema['uc_usps_products'] = array(
'description' => 'Stores product information for USPS shipping quotes.',
'fields' => array(
'vid' => array(
'description' => 'The {uc_products}.vid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
'description' => 'The {uc_products}.nid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'container' => array(
'description' => 'The package type in which the product will be shipped.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('vid'),
'foreign keys' => array(
'uc_products' => array(
'table' => 'uc_products',
'columns' => array(
'nid' => 'nid',
'vid' => 'vid',
),
),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function uc_usps_uninstall() {
variable_del('uc_usps_user_id');
variable_del('uc_usps_services');
variable_del('uc_usps_env_services');
variable_del('uc_usps_intl_services');
variable_del('uc_usps_intl_env_services');
variable_del('uc_usps_rate_markup');
variable_del('uc_usps_rate_markup_type');
variable_del('uc_usps_weight_markup');
variable_del('uc_usps_weight_markup_type');
variable_del('uc_usps_all_in_one');
variable_del('uc_usps_insurance');
variable_del('uc_usps_delivery_confirmation');
variable_del('uc_usps_signature_confirmation');
variable_del('uc_usps_online_rates');
}
/**
* Separates markup variables into rate_markup and weight_markup.
*/
function uc_usps_update_7300() {
// Rename variables while preserving previous setting values
variable_set('uc_usps_rate_markup', variable_get('uc_usps_markup', ''));
variable_set('uc_usps_rate_markup_type', variable_get('uc_usps_markup_type', ''));
// Remove old variables
variable_del('uc_usps_markup');
variable_del('uc_usps_markup_type');
}

View File

@@ -0,0 +1,937 @@
<?php
/**
* @file
* United States Postal Service (USPS) shipping quote module.
*/
/******************************************************************************
* Drupal Hooks *
******************************************************************************/
/**
* Implements hook_menu().
*/
function uc_usps_menu() {
$items = array();
$items['admin/store/settings/quotes/settings/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(
'variables' => array(
'service' => NULL,
'packages' => NULL,
),
'file' => 'uc_usps.theme.inc',
),
);
}
/**
* Implements hook_form_alter().
*
* Adds 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()) + array('usps' => FALSE, 'usps_intl' => FALSE);
$weight = variable_get('uc_quote_method_weight', array()) + 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_node_insert().
*/
function uc_usps_node_insert($node) {
uc_usps_node_update($node);
}
/**
* Implements hook_node_update().
*/
function uc_usps_node_update($node) {
if (uc_product_is_product($node->type)) {
if (isset($node->usps)) {
$usps_values = $node->usps;
if (empty($node->revision)) {
db_delete('uc_usps_products')
->condition('vid', $node->vid)
->execute();
}
db_insert('uc_usps_products')
->fields(array(
'vid' => $node->vid,
'nid' => $node->nid,
'container' => $usps_values['container'],
))
->execute();
}
}
}
/**
* Implements hook_node_load().
*/
function uc_usps_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_usps_products} WHERE vid IN (:vids)", array(':vids' => $vids), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $usps) {
$nodes[$usps['nid']]->usps = $usps;
}
}
}
/**
* Implements hook_node_delete().
*/
function uc_usps_node_delete($node) {
db_delete('uc_usps_products')
->condition('nid', $node->nid)
->execute();
}
/**
* Implements hook_node_revision_delete().
*/
function uc_usps_node_revision_delete($node) {
db_delete('uc_usps_products')
->condition('vid', $node->vid)
->execute();
}
/******************************************************************************
* Ubercart Hooks *
******************************************************************************/
/**
* Implements hook_uc_shipping_type().
*/
function uc_usps_uc_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' => isset($weight['envelope']) ? $weight['envelope'] : -1,
),
'small_package' => array(
'id' => 'small_package',
'title' => t('Small package'),
'weight' => isset($weight['small_package']) ? $weight['small_package'] : 0,
),
);
return $types;
}
/**
* Implements hook_uc_shipping_method().
*/
function uc_usps_uc_shipping_method() {
$operations = array(
'configure' => array(
'title' => t('configure'),
'href' => 'admin/store/settings/quotes/settings/usps',
),
);
$methods = array(
'usps_env' => array(
'id' => 'usps_env',
'module' => 'uc_usps',
'title' => t('U.S. Postal Service (Envelope)'),
'operations' => $operations,
'quote' => array(
'type' => 'envelope',
'callback' => 'uc_usps_quote',
'accessorials' => _uc_usps_env_services(),
),
),
'usps' => array(
'id' => 'usps',
'module' => 'uc_usps',
'title' => t('U.S. Postal Service (Parcel)'),
'operations' => $operations,
'quote' => array(
'type' => 'small_package',
'callback' => 'uc_usps_quote',
'accessorials' => _uc_usps_services(),
),
),
'usps_intl_env' => array(
'id' => 'usps_intl_env',
'module' => 'uc_usps',
'title' => t('U.S. Postal Service (Intl., Envelope)'),
'operations' => $operations,
'quote' => array(
'type' => 'envelope',
'callback' => 'uc_usps_quote',
'accessorials' => _uc_usps_intl_env_services(),
),
'weight' => 1,
),
'usps_intl' => array(
'id' => 'usps_intl',
'module' => 'uc_usps',
'title' => t('U.S. Postal Service (Intl., Parcel)'),
'operations' => $operations,
'quote' => array(
'type' => 'small_package',
'callback' => 'uc_usps_quote',
'accessorials' => _uc_usps_intl_services(),
),
'weight' => 1,
),
);
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 $debug_data to prevent PHP notices here and in uc_quote.
$debug_data = array('debug' => NULL, 'error' => array());
$services = array();
$addresses = array(variable_get('uc_quote_store_default_address', new UcAddress()));
$packages = _uc_usps_package_products($products, $addresses);
if (!count($packages)) {
return array();
}
foreach ($packages as $key => $ship_packages) {
$orig = $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)) {
$debug_data['debug'] .= htmlentities(urldecode($request)) . "<br />\n";
}
$result = drupal_http_request($connection_url, array(
'method' => 'POST',
'data' => $request,
));
if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) {
$debug_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)) {
$debug_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_rate_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_rate_markup((string)$postage->CommercialRate);
}
else {
$services[$classid]['rate'] += uc_usps_rate_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 (!in_array($service, $usps_services)) {
unset($services[$service]);
}
}
foreach ($services as $key => $quote) {
if (isset($quote['rate'])) {
$services[$key]['rate'] = $quote['rate'];
$services[$key]['option_label'] = theme('uc_usps_option_label', array('service' => $quote['label'], 'packages' => $packages));
}
}
uasort($services, 'uc_quote_price_sort');
// Merge debug data into $services. This is necessary because
// $debug_data is not sortable by a 'rate' key, so it has to be
// kept separate from the $services data until this point.
if (isset($debug_data['debug']) ||
(isset($debug_data['error']) && count($debug_data['error']))) {
$services['data'] = $debug_data;
}
return $services;
}
/**
* 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>' .
'<Value>' . $package->price . '</Value>' .
'<Machinable>' . ($package->machinable ? 'TRUE' : 'FALSE') . '</Machinable>' .
'<ReturnLocations>TRUE</ReturnLocations>' .
'<ShipDate Option="EMSH">' . format_date(time(), 'custom', 'd-M-Y', 'America/New_York', 'en') . '</ShipDate>';
// Check if we need to add any special services to this package.
if (variable_get('uc_usps_insurance', FALSE) ||
variable_get('uc_usps_delivery_confirmation', FALSE) ||
variable_get('uc_usps_signature_confirmation', FALSE) ) {
$request .= '<SpecialServices>';
if (variable_get('uc_usps_insurance', FALSE)) {
$request .= '<SpecialService>1</SpecialService>';
}
if (variable_get('uc_usps_delivery_confirmation', FALSE)) {
$request .= '<SpecialService>13</SpecialService>';
}
if (variable_get('uc_usps_signature_confirmation', FALSE)) {
$request .= '<SpecialService>15</SpecialService>';
}
$request .= '</SpecialServices>';
}
// Close off Package tag.
$request .= '</Package>';
$package_id++;
}
}
$request .= '</RateV4Request>';
return 'API=RateV4&XML=' . drupal_encode_path($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_encode_path($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_rate_markup($rate) {
$markup = trim(variable_get('uc_usps_rate_markup', '0'));
$type = variable_get('uc_usps_rate_markup_type', 'percentage');
if (is_numeric($markup)) {
switch ($type) {
case 'percentage':
return $rate + $rate * floatval($markup) / 100;
case 'multiplier':
return $rate * floatval($markup);
case 'currency':
return $rate + floatval($markup);
}
}
else {
return $rate;
}
}
/**
* Modifies the weight of shipment before sending to USPS for a quote.
*
* @param $weight
* Shipping weight without any weight markup.
*
* @return
* Shipping weight after markup.
*/
function uc_usps_weight_markup($weight) {
$markup = trim(variable_get('uc_usps_weight_markup', '0'));
$type = variable_get('uc_usps_weight_markup_type', 'percentage');
if (is_numeric($markup)) {
switch ($type) {
case 'percentage':
return $weight + $weight * floatval($markup) / 100;
case 'multiplier':
return $weight * floatval($markup);
case 'mass':
return $weight + floatval($markup);
}
}
else {
return $weight;
}
}
/**
* 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.
$packages[$last_key] = array(0 => _uc_usps_new_package());
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;
$packages[$key] = array(0 => _uc_usps_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->usps['container'] = isset($temp->usps['container']) ? $temp->usps['container'] : 'VARIABLE';
$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 = 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);
if ($num_of_pkgs) {
$package = 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;
}
// 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->usps['container'] = isset($temp->usps['container']) ? $temp->usps['container'] : 'VARIABLE';
$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 = 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 ENVELOPE' => t('Flat rate envelope'),
'PADDED FLAT RATE ENVELOPE' => t('Padded flat rate envelope'),
'LEGAL FLAT RATE ENVELOPE' => t('Legal flat rate envelope'),
'SMALL FLAT RATE ENVELOPE' => t('Small flat rate envelope'),
'WINDOW FLAT RATE ENVELOPE' => t('Window flat rate envelope'),
'GIFT CARD FLAT RATE BOX' => t('Gift card flat rate box'),
'FLAT RATE BOX' => t('Flat rate box'),
'SM FLAT RATE BOX' => t('Small flat rate box'),
'MD FLAT RATE BOX' => t('Medium flat rate box'),
'LG FLAT RATE BOX' => t('Large flat rate box'),
'REGIONALRATEBOXA' => t('Regional rate box A'),
'REGIONALRATEBOXB' => t('Regional rate box B'),
'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)'),
);
}
/**
* Pseudo-constructor to set default values of a package.
*/
function _uc_usps_new_package() {
$package = new stdClass();
$package->price = 0;
$package->qty = 1;
$package->pounds = 0;
$package->ounces = 0;
$package->container = 0;
$package->size = 0;
$package->machinable = TRUE;
$package->length = 0;
$package->width = 0;
$package->height = 0;
$package->girth = 0;
// $package->length_units = 'in';
// $package->weight_units = 'lb';
return $package;
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* @file
* Rules configurations for uc_usps module.
*/
/**
* Implements hook_default_rules_configuration_alter().
*/
function uc_usps_default_rules_configuration_alter(&$configs) {
if (!isset($configs['get_quote_from_usps'])) {
return;
}
// Domestic areas include U.S., American Samoa, Guam, Puerto Rico, and the Virgin Islands
$countries = array(
16 => t('American Samoa'),
316 => t('Guam'),
630 => t('Puerto Rico'),
840 => t('United States'),
850 => t('Virgin Islands (US)'),
);
$domestic = rules_or();
$domestic->label = t('Order delivers to one of');
$domestic_env = clone $domestic;
$foreign = rules_or();
$foreign->negate();
$foreign->label = t('NOT Order delivers to one of');
$foreign_env = clone $foreign;
foreach ($countries as $country => $name) {
$condition = rules_condition('data_is', array('data:select' => 'order:delivery-address:country', 'value' => $country));
$condition->label = $name;
$domestic->condition(clone $condition);
$domestic_env->condition(clone $condition);
$foreign->condition(clone $condition);
$foreign_env->condition($condition);
}
$configs['get_quote_from_usps']->condition($domestic);
$configs['get_quote_from_usps_env']->condition($domestic_env);
$configs['get_quote_from_usps_intl']->condition($foreign);
$configs['get_quote_from_usps_intl_env']->condition($foreign_env);
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* @file
* Theme functions for the uc_usps module.
*/
/**
* Theme function to format the USPS service name and rate amount line-item
* shown to the customer.
*
* @param $variables
* Associative array containing information needed to theme a quote.
* Contains two keys:
* - service: The USPS service name.
* - packages: Package information.
*
* @ingroup themeable
*/
function theme_uc_usps_option_label($variables) {
$service = $variables['service'];
$packages = $variables['packages'];
// Start with USPS logo.
$output = theme('image', array(
'path' => drupal_get_path('module', 'uc_usps') . '/uc_usps_logo.gif',
'alt' => t('U.S.P.S.'),
'attributes' => 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

View File

@@ -0,0 +1,141 @@
<?php
/**
* @file
* Weight quote shipping method administration menu items.
*/
/**
* Configures the store default product shipping rates.
*
* @see uc_weightquote_admin_method_edit_form_submit()
* @see uc_weightquote_admin_method_edit_form_delete()
* @ingroup forms
*/
function uc_weightquote_admin_method_edit_form($form, &$form_state, $mid = 0) {
if ($mid && ($method = db_query("SELECT * FROM {uc_weightquote_methods} WHERE mid = :mid", array(':mid' => $mid))->fetchObject())) {
$form['mid'] = array('#type' => 'value', '#value' => $mid);
}
else {
$method = (object) array(
'title' => '',
'label' => '',
'base_rate' => '',
'product_rate' => '',
);
}
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Shipping method title'),
'#description' => t('The name shown to administrators distinguish this method from other weightquote methods.'),
'#default_value' => $method->title,
'#required' => TRUE,
);
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Line item label'),
'#description' => t('The name shown to the customer when they choose a shipping method at checkout.'),
'#default_value' => $method->label,
'#required' => TRUE,
);
$form['base_rate'] = array(
'#type' => 'uc_price',
'#title' => t('Base price'),
'#description' => t('The starting price for weight-based shipping costs.'),
'#default_value' => $method->base_rate,
'#required' => TRUE,
);
$form['product_rate'] = array(
'#type' => 'uc_price',
'#title' => t('Default cost adjustment per !unit', array('!unit' => variable_get('uc_weight_unit', 'lb'))),
'#description' => t('The amount per weight unit to add to the shipping cost for an item.'),
'#default_value' => $method->product_rate,
'#required' => TRUE,
'#field_suffix' => t('per @unit', array('@unit' => variable_get('uc_weight_unit', 'lb'))),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
if (isset($form['mid'])) {
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#validate' => array(),
'#submit' => array('uc_weightquote_admin_method_edit_form_delete'),
);
}
return $form;
}
/**
* Helper function to delete a weightquote method.
*
* @see uc_weightquote_admin_method_edit_form()
*/
function uc_weightquote_admin_method_edit_form_delete($form, &$form_state) {
drupal_goto('admin/store/settings/quotes/weightquote/' . $form_state['values']['mid'] . '/delete');
}
/**
* Form submission handler for uc_weightquote_admin_method_edit_form().
*
* @see uc_weightquote_admin_method_edit_form()
*/
function uc_weightquote_admin_method_edit_form_submit($form, &$form_state) {
if (isset($form_state['values']['mid'])) {
drupal_write_record('uc_weightquote_methods', $form_state['values'], 'mid');
drupal_set_message(t('Weight quote shipping method was updated.'));
$form_state['redirect'] = 'admin/store/settings/quotes/methods';
}
else {
drupal_write_record('uc_weightquote_methods', $form_state['values']);
// Ensure Rules picks up the new condition.
entity_flush_caches();
drupal_set_message(t('Created and enabled new weight quote shipping method.'));
$form_state['redirect'] = 'admin/store/settings/quotes/manage/get_quote_from_weightquote_' . $form_state['values']['mid'];
}
}
/**
* Confirms deletion of a weight-based shipping method.
*
* @see uc_weightquote_admin_method_confirm_delete_submit()
* @ingroup forms
*/
function uc_weightquote_admin_method_confirm_delete($form, &$form_state, $mid) {
$form['mid'] = array('#type' => 'value', '#value' => $mid);
return confirm_form($form, t('Do you want to delete this shipping method?'),
'admin/store/settings/quotes/methods',
t('This will remove the shipping method and the product-specific overrides (if applicable). This action can not be undone.'),
t('Delete'));
}
/**
* Form submission handler for uc_weightquote_admin_method_confirm_delete().
*
* @see uc_weightquote_admin_method_confirm_delete()
*/
function uc_weightquote_admin_method_confirm_delete_submit($form, &$form_state) {
$mid = $form_state['values']['mid'];
db_delete('uc_weightquote_methods')
->condition('mid', $mid)
->execute();
db_delete('uc_weightquote_products')
->condition('mid', $mid)
->execute();
rules_config_delete(array('get_quote_from_weightquote_' . $mid));
drupal_set_message(t('Weight quote shipping method deleted.'));
$form_state['redirect'] = 'admin/store/settings/quotes/methods';
}

View File

@@ -0,0 +1,14 @@
name = Weight quote
description = Assigns a shipping rate to products based on weight.
dependencies[] = uc_quote
package = Ubercart - fulfillment
core = 7.x
configure = admin/store/settings/quotes/methods
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,106 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_weightquote module.
*/
/**
* Implements hook_schema().
*/
function uc_weightquote_schema() {
$schema = array();
$schema['uc_weightquote_products'] = array(
'description' => 'Stores product information about weight-based shipping quotes.',
'fields' => array(
'vid' => array(
'description' => 'The {uc_products}.vid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
'description' => 'The {uc_products}.nid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'mid' => array(
'description' => 'The {uc_weightquote_methods}.mid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'rate' => array(
'description' => 'The rate multiplier for this product, in the store default currency, per the product weight in store default weight units.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => FALSE,
),
),
'primary key' => array('vid', 'mid'),
'foreign keys' => array(
'uc_products' => array(
'table' => 'uc_products',
'columns' => array(
'nid' => 'nid',
'vid' => 'vid',
),
),
'uc_weightquote_methods' => array(
'table' => 'uc_weightquote_methods',
'columns' => array('mid' => 'mid'),
),
),
);
$schema['uc_weightquote_methods'] = array(
'description' => 'Stores weight-based shipping quote methods information.',
'fields' => array(
'mid' => array(
'description' => 'Primary key: The shipping quote method ID.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'title' => array(
'description' => 'The method title, displayed on administration pages.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'label' => array(
'description' => 'The user-facing label of the shipping method.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'base_rate' => array(
'description' => 'The amount of shipping cost before product weight is applied.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0.0,
),
'product_rate' => array(
'description' => 'The default rate multiplier, in the store default currency, per the product weight in store default weight units.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0.0,
),
),
'primary key' => array('mid'),
);
return $schema;
}

View File

@@ -0,0 +1,237 @@
<?php
/**
* @file
* Shipping quote module that defines a weight-based shipping rate for each
* product.
*/
/**
* Implements hook_menu().
*/
function uc_weightquote_menu() {
$items = array();
$items['admin/store/settings/quotes/methods/weightquote/add'] = array(
'title' => 'Add weight quote',
'description' => 'Create a new weight based shipping quote.',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_weightquote_admin_method_edit_form'),
'access arguments' => array('configure quotes'),
'type' => MENU_LOCAL_ACTION,
'file' => 'uc_weightquote.admin.inc',
);
$items['admin/store/settings/quotes/methods/weightquote/%'] = array(
'title' => 'Edit weight quote method',
'description' => 'Edit an existing weight based shipping quote.',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_weightquote_admin_method_edit_form', 6),
'access arguments' => array('configure quotes'),
'file' => 'uc_weightquote.admin.inc',
);
$items['admin/store/settings/quotes/weightquote/%/delete'] = array(
'title' => 'Delete weight quote method',
'description' => 'Delete a weight based shipping quote.',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_weightquote_admin_method_confirm_delete', 5),
'access arguments' => array('configure quotes'),
'file' => 'uc_weightquote.admin.inc',
);
return $items;
}
/**
* Implements hook_form_alter().
*
* Adds a form element for the shipping rate of a product.
*/
function uc_weightquote_form_alter(&$form, &$form_state, $form_id) {
if (uc_product_is_product_form($form)) {
$weight = variable_get('uc_quote_method_weight', array());
$result = db_query("SELECT mid, title, product_rate FROM {uc_weightquote_methods}");
foreach ($result as $method) {
// Ensure the default weight is set.
$weight += array('weightquote_' . $method->mid => 0);
if (!isset($form['shipping']['weightquote'])) {
$form['shipping']['weightquote'] = array(
'#type' => 'fieldset',
'#title' => t('Shipping rates by weight'),
'#description' => t("Overrides the default shipping rate per product for each flat rate shipping method. Leave field empty to use the method's default value."),
'#tree' => TRUE,
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => 0,
);
}
$form['shipping']['weightquote'][$method->mid] = array(
'#type' => 'uc_price',
'#title' => check_plain($method->title),
'#default_value' => isset($form['#node']->weightquote[$method->mid]) ? $form['#node']->weightquote[$method->mid] : '',
'#description' => t('Default rate: %price per @unit', array('%price' => uc_currency_format($method->product_rate), '@unit' => variable_get('uc_weight_unit', 'lb'))),
'#field_suffix' => t('per @unit', array('@unit' => variable_get('uc_weight_unit', 'lb'))),
'#weight' => $weight['weightquote_' . $method->mid],
'#empty_zero' => FALSE,
);
}
}
}
/**
* Implements hook_node_insert().
*/
function uc_weightquote_node_insert($node) {
uc_weightquote_node_update($node);
}
/**
* Implements hook_node_update().
*/
function uc_weightquote_node_update($node) {
if (uc_product_is_product($node->type)) {
if (isset($node->weightquote) && is_array($node->weightquote)) {
if (empty($node->revision)) {
db_delete('uc_weightquote_products')
->condition('vid', $node->vid)
->execute();
}
$query = db_insert('uc_weightquote_products')
->fields(array('vid', 'nid', 'mid', 'rate'));
foreach ($node->weightquote as $mid => $rate) {
if (is_numeric($rate) && $rate >= 0) {
$query->values(array(
'vid' => $node->vid,
'nid' => $node->nid,
'mid' => $mid,
'rate' => $rate,
));
}
}
$query->execute();
}
}
}
/**
* Implements hook_node_load().
*/
function uc_weightquote_node_load($nodes, $types) {
$vids = array();
$product_types = uc_product_types();
foreach ($nodes as &$node) {
if (in_array($node->type, $product_types)) {
$vids[$node->nid] = $node->vid;
}
}
if ($vids) {
$result = db_query("SELECT nid, mid, rate FROM {uc_weightquote_products} WHERE vid IN (:vids)", array(':vids' => $vids));
foreach ($result as $method) {
$nodes[$method->nid]->weightquote[$method->mid] = $method->rate;
}
}
}
/**
* Implements hook_node_delete().
*/
function uc_weightquote_node_delete($node) {
db_delete('uc_weightquote_products')
->condition('nid', $node->nid)
->execute();
}
/**
* Implements hook_node_revision_delete().
*/
function uc_weightquote_node_revision_delete($node) {
db_delete('uc_weightquote_products')
->condition('vid', $node->vid)
->execute();
}
/**
* Implements hook_uc_shipping_method().
*/
function uc_weightquote_uc_shipping_method() {
$methods = array();
$result = db_query("SELECT mid, title, label, base_rate, product_rate FROM {uc_weightquote_methods}");
foreach ($result as $method) {
$methods['weightquote_' . $method->mid] = array(
'id' => 'weightquote_' . $method->mid,
'module' => 'uc_weightquote',
'title' => $method->title,
'description' => t('!base_rate + !product_rate per !unit', array('!base_rate' => uc_currency_format($method->base_rate), '!product_rate' => uc_currency_format($method->product_rate), '!unit' => variable_get('uc_weight_unit', 'lb'))),
'operations' => array(
'edit' => array(
'title' => t('edit'),
'href' => 'admin/store/settings/quotes/methods/weightquote/' . $method->mid,
),
'delete' => array(
'title' => t('delete'),
'href' => 'admin/store/settings/quotes/weightquote/' . $method->mid . '/delete',
),
),
'quote' => array(
'type' => 'order',
'callback' => 'uc_weightquote_quote',
'accessorials' => array(
$method->label,
),
),
'enabled' => TRUE,
);
}
return $methods;
}
/**
* Standard callback to return a shipping rate via the weight quote method.
*
* @param $products
* The order's products.
* @param $details
* Other order details including a shipping address.
* @param $method
* The shipping method to use to create the quote.
*
* @return
* An array containing the shipping quote for the order.
*/
function uc_weightquote_quote($products, $details, $method) {
$method = explode('_', $method['id']);
$mid = $method[1];
if ($method = db_query("SELECT * FROM {uc_weightquote_methods} WHERE mid = :mid", array(':mid' => $mid))->fetchObject()) {
// Start at the base rate.
$rate = $method->base_rate;
foreach ($products as $product) {
if (empty($product->weightquote) || is_null($product->weightquote[$mid])) {
// Add the method's default product rate.
$product_rate = $method->product_rate * $product->qty;
}
else {
// Add the product-specific rate.
$product_rate = $product->weightquote[$mid] * $product->qty;
}
$rate += $product_rate * $product->weight * uc_weight_conversion($product->weight_units, variable_get('uc_weight_unit', 'lb'));
}
$quotes[] = array(
'rate' => $rate,
'label' => check_plain($method->label),
'option_label' => check_plain($method->label),
);
}
return $quotes;
}