commerce_shipping_weight_tariff.module in Commerce Shipping Weight Tariff 7
Same filename and directory in other branches
commerce_shipping_weight_tariff.module
Allows the creation of complex matrices of shipping tariffs by order weight, across multiple shipping services. Each tariff is stored as a product entity with:
- a weight field for the maximum weight for the tariff
- a shipping service field to indicate the service this belongs to
- the usual Commerce price fields, to give the price of the tariff.
These products should not be referenced by nodes, and will not be added to the cart; rather, their price is returned to Commerce Shipping as the shipping service rate.
File
commerce_shipping_weight_tariff.moduleView source
<?php
/**
* @file commerce_shipping_weight_tariff.module
*
* Allows the creation of complex matrices of shipping tariffs by order weight,
* across multiple shipping services.
* Each tariff is stored as a product entity with:
* - a weight field for the maximum weight for the tariff
* - a shipping service field to indicate the service this belongs to
* - the usual Commerce price fields, to give the price of the tariff.
* These products should not be referenced by nodes, and will not be added to
* the cart; rather, their price is returned to Commerce Shipping as the
* shipping service rate.
*/
function commerce_shipping_weight_tariff_block_info() {
return array(
'shipping_matrix' => array(
'info' => t('Weight tariff shipping matrix'),
'cache' => DRUPAL_CACHE_GLOBAL,
),
);
}
function commerce_shipping_weight_tariff_block_view($delta = '') {
$block = array();
switch ($delta) {
case 'shipping_matrix':
$block['subject'] = t('Shipping table');
$block['content'] = array(
'#theme' => 'commerce_shipping_weight_tariff_shipping_table',
'#services' => commerce_shipping_services('weight_tariff_shipping'),
'#weight_unit' => variable_get('commerce_shipping_weight_tariff_block_weight_unit', 'kg'),
'#order_total_weight' => NULL,
);
break;
}
return $block;
}
function commerce_shipping_weight_tariff_block_configure($delta = '') {
$form = array();
switch ($delta) {
case 'shipping_matrix':
$units = array();
foreach (physical_weight_units() as $ukey => $unit) {
$units[$ukey] = "{$unit['name']} ({$unit['abbreviation']})";
}
asort($units);
$form['commerce_shipping_weight_tariff_block_weight_unit'] = array(
'#type' => 'select',
'#title' => t('Weight Unit'),
'#options' => $units,
'#default_value' => variable_get('commerce_shipping_weight_tariff_block_weight_unit', 'kg'),
);
break;
}
return $form;
}
function commerce_shipping_weight_tariff_block_save($delta = '', $edit = array()) {
switch ($delta) {
case 'shipping_matrix':
variable_set('commerce_shipping_weight_tariff_block_weight_unit', $edit['commerce_shipping_weight_tariff_block_weight_unit']);
break;
}
return;
}
function commerce_shipping_weight_tariff_theme($existing, $type, $theme, $path) {
return array(
'commerce_shipping_weight_tariff_shipping_table' => array(
'variables' => array(
'services' => array(),
'weight_unit' => 'kg',
'order_total_weight' => NULL,
),
),
);
}
function theme_commerce_shipping_weight_tariff_shipping_table($variables) {
$weights = array();
$prices = array();
$rows = array();
foreach ($variables['services'] as $skey => $service) {
$products = _commerce_shipping_weight_tariff_fetch_tariff_products($service['name']);
$rows[$skey] = array(
$service['display_title'],
);
foreach ($products as $product) {
$wrapper = entity_metadata_wrapper('commerce_product', $product);
$weight = $wrapper->commerce_product_tariff_weight->weight
->value();
$unit = $wrapper->commerce_product_tariff_weight->unit
->value();
$weight = physical_weight_convert(array(
'weight' => $weight,
'unit' => $unit,
), $variables['weight_unit']);
$weight = $weight['weight'];
$price = $wrapper->commerce_price
->value();
$prices[$weight][$skey] = commerce_currency_format($price['amount'], $price['currency_code']);
$service = $wrapper->commerce_product_tariff_service
->value();
$weights[$weight] = $weight;
}
}
asort($weights);
$table = array(
'rows' => $rows,
'header' => array(
'shipping service',
),
);
$order_weight = array(
'0',
'kg',
);
if (arg(0) === 'checkout' && arg(2) !== 'complete') {
global $user;
$order = commerce_cart_order_load($user->uid);
// Get the weight from the order.
$order_weight = commerce_physical_order_weight($order, 'kg');
}
$table['header'][] = 'Σ';
foreach ($table['rows'] as $skey => &$row) {
$row[] = implode(' ', $order_weight) . '.';
}
foreach ($weights as $weight) {
$table['header'][] = '< ' . number_format($weight, 3) . " {$variables['weight_unit']}";
foreach ($table['rows'] as $skey => &$row) {
if (isset($prices[$weight][$skey])) {
$row[] = $prices[$weight][$skey];
}
else {
$row[] = '';
}
}
}
return theme('table', $table);
}
/**
* Implements hook_menu().
*/
function commerce_shipping_weight_tariff_menu() {
// This is picked up magically by commerce_shipping_ui_overview().
$items['admin/commerce/config/shipping/methods/weight-tariff-shipping/matrix'] = array(
'title' => 'View tariff matrix',
'description' => 'View the overview matrix of shipping weight tariffs.',
'page callback' => 'commerce_shipping_weight_tariff_admin_matrix',
'access arguments' => array(
'administer shipping',
),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE,
'file' => 'commerce_shipping_weight_tariff.admin.inc',
'weight' => 8,
);
$items['admin/commerce/config/shipping/services/weight-tariff-shipping/add'] = array(
'title' => 'Add a weight tariff service',
'description' => 'Create a new weight tariff shipping service.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'commerce_shipping_weight_tariff_service_form',
commerce_shipping_weight_tariff_service_new(),
),
'access callback' => 'commerce_shipping_weight_tariff_service_access',
'access arguments' => array(
'create',
),
'type' => MENU_LOCAL_ACTION,
'context' => MENU_CONTEXT_PAGE,
'file' => 'commerce_shipping_weight_tariff.admin.inc',
);
foreach (commerce_shipping_services('weight_tariff_shipping') as $name => $shipping_service) {
// Convert underscores to hyphens for the menu item argument.
$service_name_arg = 'weight-tariff-shipping-' . strtr($name, '_', '-');
$items['admin/commerce/config/shipping/services/' . $service_name_arg . '/edit'] = array(
'title' => 'Edit',
'description' => 'Edit this weight tariff service.',
'page callback' => 'commerce_shipping_weight_tariff_service_edit_page',
'page arguments' => array(
$name,
),
'access callback' => 'commerce_shipping_weight_tariff_service_access',
'access arguments' => array(
'update',
),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE,
'weight' => 0,
'file' => 'commerce_shipping_weight_tariff.admin.inc',
);
$items['admin/commerce/config/shipping/services/' . $service_name_arg . '/delete'] = array(
'title' => 'Delete',
'description' => 'Delete this weight tariff service.',
'page callback' => 'commerce_shipping_weight_tariff_service_delete_page',
'page arguments' => array(
$name,
),
'access callback' => 'commerce_shipping_weight_tariff_service_access',
'access arguments' => array(
'delete',
),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE,
'weight' => 10,
'file' => 'commerce_shipping_weight_tariff.admin.inc',
);
}
return $items;
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function commerce_shipping_weight_tariff_menu_local_tasks_alter(&$data, $router_item, $root_path) {
if ($root_path == 'admin/commerce/config/shipping/methods/weight-tariff-shipping/matrix') {
if (user_access('create commerce_product entities of bundle shipping_tariff')) {
// Add an action linking to add a new tariff product.
// Get the return destination first, as that gives us a query array.
$query = drupal_get_destination();
$data['actions']['output'][] = array(
'#theme' => 'menu_local_task',
'#link' => array(
'title' => t('Add new shipping tariff'),
'href' => 'admin/commerce/products/add/shipping-tariff',
'localized_options' => array(
'query' => $query,
),
),
);
}
}
}
/**
* Rate callback for hook_commerce_shipping_service_info().
*
* Retrieves all the tariff products that point to the given shipping service,
* and finds the one whose maximum weight is nearest to the order weight. The
* price on this tariff product is returned as the shipping service rate.
*
* @param $shipping_service
* A shipping service definition.
* @param $order
* The current order object.
*/
function commerce_shipping_weight_tariff_service_rate($shipping_service, $order) {
// Get the weight from the order.
$order_weight = commerce_physical_order_weight($order, 'kg');
if (isset($order_weight['weight'])) {
$order_weight = $order_weight['weight'];
}
else {
$order_weight = 0;
}
$products = _commerce_shipping_weight_tariff_fetch_tariff_products($shipping_service['name']);
// Create an array of max weight => product id, for all the weights that are
// applicable. We then take the lowest one.
// (There is potential for clobbering here, but if you define two tariffs
// with the same max weight, then you have a problem anyway.)
$product_max_weights = array();
foreach ($products as $product) {
try {
$wrapper = entity_metadata_wrapper('commerce_product', $product);
$weight = $wrapper->commerce_product_tariff_weight->weight
->value();
$unit = $wrapper->commerce_product_tariff_weight->unit
->value();
if ($unit !== 'kg') {
$weight = physical_weight_convert(array(
'weight' => $weight,
'unit' => $unit,
), 'kg');
$weight = $weight['weight'];
}
} catch (EntityMetadataWrapperException $e) {
continue;
}
// Skip a tariff if the order weighs more than the tariff's max weight.
if ($weight < $order_weight) {
continue;
}
// Build the array of weights => product ids.
$product_max_weights["{$weight}"] = $product->product_id;
}
// If we found no tariffs (because the product is too heavy, perhaps), return nothing.
if (empty($product_max_weights)) {
return;
}
ksort($product_max_weights);
// Sort the weights array by weight, then take the lowest item.
$tariff_product_id = array_shift($product_max_weights);
// Get our desired tariff product.
$tariff_product = $products[$tariff_product_id];
//dsm($tariff_product);
// Return the price from the tariff product.
$wrapper = entity_metadata_wrapper('commerce_product', $tariff_product);
$price = $wrapper->commerce_price
->value();
// Hack: set the tariff product id in the price, which AFAICT is the only
// place to put it so that hook_commerce_shipping_method_collect_rates() can
// see it and put it in the line item.
// @see http://drupal.org/node/1804510
$price['tariff_product_id'] = $tariff_product_id;
if (isset($price['data']['components'][0]['price'])) {
$price['data']['components'] = array();
}
return $price;
}
/**
* Options list callback for the service method field on tariff products.
*/
function commerce_shipping_weight_tariff_methods_options_list() {
// Get the services for our method.
$services = commerce_shipping_services('weight_tariff_shipping');
$options = array();
foreach ($services as $service_name => $service_info) {
$options[$service_name] = $service_info['display_title'];
}
return $options;
}
/**
* Returns an initialized shipping service array for forms.
*/
function commerce_shipping_weight_tariff_service_new() {
return array(
'name' => '',
'title' => '',
'display_title' => '',
'description' => '',
'is_new' => TRUE,
);
}
/**
* Implements hook_permission().
*/
function commerce_shipping_weight_tariff_permission() {
return array(
'administer weight tariff services' => array(
'title' => t('Administer weight tariff shipping services.'),
'description' => t('Allows users to create, edit and delete weight tariff shipping services.'),
'restrict access' => TRUE,
),
);
}
/**
* Access callback: grants users access to weight tariff service operations if they
* have the specific permission or generic shipping permission.
*
* @param $op
* The operation string: of create, update, or delete.
*
* @return
* Boolean indicating the user's access.
*/
function commerce_shipping_weight_tariff_service_access($op) {
return user_access('administer shipping') || user_access('administer weight tariff services');
}
/**
* Saves a service to the database.
*
* @param $shipping_service
* The shipping service to save.
* @param $skip_reset
* Boolean indicating whether or not this save should result in shipping
* services being reset and the menu being rebuilt; defaults to FALSE. This is
* useful when you intend to perform many saves at once, as menu rebuilding is
* very costly in terms of performance.
*
* @return
* The return value of the call to drupal_write_record() to save the service;
* either FALSE on failure or SAVED_NEW or SAVED_UPDATED indicating
* the type of query performed to save the service.
*/
function commerce_shipping_weight_tariff_service_save($shipping_service, $skip_reset = FALSE) {
$op = drupal_write_record('commerce_shipping_weight_tariff_service', $shipping_service, empty($shipping_service['is_new']) ? 'name' : array());
// If this is a new service and the insert did not fail...
if (!empty($shipping_service['is_new']) && $op !== FALSE) {
// Notify other modules that a new service has been created.
module_invoke_all('commerce_shipping_weight_tariff_service_insert', $shipping_service, $skip_reset);
}
elseif ($op !== FALSE) {
// Notify other modules that an existing service has been updated.
module_invoke_all('commerce_shipping_weight_tariff_service_update', $shipping_service, $skip_reset);
}
// Clear the necessary caches and rebuild the menu items.
if (!$skip_reset) {
commerce_shipping_services_reset();
entity_defaults_rebuild();
rules_clear_cache(TRUE);
menu_rebuild();
}
return $op;
}
function _commerce_shipping_weight_tariff_fetch_tariff_products($name = false, $asClass = true) {
$products = array();
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'commerce_product')
->entityCondition('bundle', 'shipping_tariff');
if ($name) {
$query
->fieldCondition('commerce_product_tariff_service', 'value', $name, '=');
}
$result = $query
->execute();
if (isset($result['commerce_product'])) {
$products = commerce_product_load_multiple(array_keys($result['commerce_product']));
if (!$asClass) {
$products = array_keys($products);
}
}
return $products;
}
/**
* Deletes a service.
*
* @param $name
* The machine-name of the service.
* @param $skip_reset
* Boolean indicating whether or not this delete should result in shipping
* services being reset and the menu being rebuilt; defaults to FALSE. This is
* useful when you intend to perform many deletions at once, as menu
* rebuilding is very costly in terms of performance.
*/
function commerce_shipping_weight_tariff_service_delete($name, $skip_reset = FALSE) {
$shipping_service = commerce_shipping_service_load($name);
$products = _commerce_shipping_weight_tariff_fetch_tariff_products($name, false);
commerce_product_delete_multiple($products);
db_delete('commerce_shipping_weight_tariff_service')
->condition('name', $name)
->execute();
rules_config_delete(array(
'commerce_shipping_service_' . $name,
));
// Clear the necessary caches and rebuild the menu items.
if (!$skip_reset) {
commerce_shipping_services_reset();
entity_defaults_rebuild();
rules_clear_cache(TRUE);
menu_rebuild();
}
// Notify other modules that this flat rate service has been deleted.
module_invoke_all('commerce_shipping_weight_tariff_service_delete', $shipping_service, $skip_reset);
}