You are here

uc_quote.module in Ubercart 5

The controller module for fulfillment modules that process physical goods.

This module collects information that is necessary to transport products from one place to another. Its hook system is used by fulfillment modules to get their specific information so that a shipment may be quoted and requested.

Coded by Lyle Mantooth

File

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

/**
 * @file
 * The controller module for fulfillment modules that process physical goods.
 *
 * This module collects information that is necessary to transport products from
 * one place to another. Its hook system is used by fulfillment modules to get
 * their specific information so that a shipment may be quoted and requested.
 *
 * Coded by Lyle Mantooth
 */

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

/**
 * Implementation of hook_perm().
 */
function uc_quote_perm() {
  return array(
    'configure quotes',
  );
}

/**
 * Implementation of hook_menu().
 */
function uc_quote_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/store/settings/quotes',
      'access' => user_access('configure quotes'),
      'title' => t('Shipping quote settings'),
      'description' => t('Configure the shipping quote settings.'),
      'callback' => 'uc_quote_overview',
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/store/settings/quotes/overview',
      'access' => user_access('configure quotes'),
      'title' => t('Overview'),
      'description' => t('View general shipping quote settings.'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/store/settings/quotes/edit',
      'access' => user_access('configure quotes'),
      'title' => t('Edit'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'uc_quote_admin_settings',
      'type' => MENU_LOCAL_TASK,
      'weight' => -8,
    );
    $items[] = array(
      'path' => 'admin/store/settings/quotes/methods',
      'access' => user_access('configure quotes'),
      'title' => t('Quote methods'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'uc_quote_method_settings',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => -5,
    );
    $items[] = array(
      'path' => 'admin/store/settings/quotes/methods/general',
      'title' => t('General settings'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'cart/checkout/shipping/quote',
      'access' => user_access('access content'),
      'callback' => 'uc_quote_request_quotes',
      'type' => MENU_CALLBACK,
    );
  }
  else {
    drupal_add_css(drupal_get_path('module', 'uc_quote') . '/uc_quote.css', 'module');
  }
  return $items;
}

/**
 * Implementation of hook_taxonomy().
 *
 * Save and delete manufacturer shipping locations.
 */
function uc_quote_taxonomy($op, $type, $object = null) {
  switch ($type) {
    case 'term':
      switch ($op) {
        case 'insert':
        case 'update':
          db_query("DELETE FROM {uc_quote_manufacturer_locations} WHERE tid = %d", $object['tid']);
          if ($object['street1']) {
            db_query("INSERT INTO {uc_quote_manufacturer_locations} (tid, first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d, '%s')", $object['tid'], $object['first_name'], $object['last_name'], $object['company'], $object['street1'], $object['street2'], $object['city'], $object['zone'], $object['postal_code'], $object['country'], $object['phone']);
          }
          uc_quote_set_shipping_type('manufacturer', $object['tid'], $object['shipping_type']);
          break;
        case 'delete':
          db_query("DELETE FROM {uc_quote_shipping_types} WHERE id_type = 'manufacturer' AND id = %d", $object['tid']);
          db_query("DELETE FROM {uc_quote_manufacturer_locations} WHERE tid = %d", $object['tid']);
          break;
      }
      break;
  }
}

/**
 * Implementation of hook_nodeapi().
 *
 * Load, save, and delete the shipping type and default shipping address of products.
 */
function uc_quote_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
  if (in_array($node->type, module_invoke_all('product_types'))) {
    switch ($op) {
      case 'insert':
      case 'update':
        if (isset($node->shipping_type)) {
          uc_quote_set_shipping_type('product', $node->nid, $node->shipping_type);
        }
        db_query("DELETE FROM {uc_quote_product_locations} WHERE nid = %d", $node->nid);
        if ($node->street1) {
          db_query("INSERT INTO {uc_quote_product_locations} (nid, first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d, '%s')", $node->nid, $node->first_name, $node->last_name, $node->company, $node->street1, $node->street2, $node->city, $node->zone, $node->postal_code, $node->country, $node->phone);
        }
        break;
      case 'load':

        // Can't just use uc_product_get_shipping_type() because $node->manufacturer doesn't necessarily exist yet.
        if (module_exists('uc_manufacturer')) {
          $manufacturer = uc_product_get_manufacturer($node->nid);
        }
        $shipping_type = db_result(db_query("SELECT shipping_type FROM {uc_quote_shipping_types} WHERE id_type = 'product' AND id = %d", $node->nid));
        if (!$shipping_type) {
          if (isset($manufacturer)) {
            $shipping_type = db_result(db_query("SELECT shipping_type FROM {uc_quote_shipping_types} WHERE id_type = 'manufacturer' AND id = %d", $manufacturer->tid));
          }
          if (!$shipping_type) {
            $shipping_type = variable_get('uc_store_shipping_type', 'small_package');
          }
        }
        $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_product_locations} WHERE nid = %d", $node->nid));
        if ($address === false) {
          if (module_exists('uc_manufacturer')) {
            $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_manufacturer_locations} WHERE tid = %d", $manufacturer->tid));
          }
          if ($address === false) {
            $address = variable_get('uc_quote_store_default_address', new stdClass());
          }
        }
        return array(
          'shipping_type' => $shipping_type,
          'shipping_address' => $address,
        );
        break;
      case 'delete':
        db_query("DELETE FROM {uc_quote_shipping_types} WHERE id_type = 'product' AND id = %d", $node->nid);
        db_query("DELETE FROM {uc_quote_product_locations} WHERE nid = %d", $node->nid);
        break;
    }
  }
}

/**
 * Implementation of hook_form_alter().
 *
 * Add a default shipping address for products and manufacturers. If it is left
 * blank, products default to their manufacturers', which default to the store's.
 */
function uc_quote_form_alter($form_id, &$form) {
  $node = $form['#node'];
  $product_types = uc_product_node_info();
  if ($form_id == 'taxonomy_form_term' && $form['vid']['#value'] == variable_get('uc_manufacturer_vid', 0) || $form_id == 'uc_manufacturer_form' || $form_id == $node->type . '_node_form' && isset($form['base']['dimensions']) && in_array($node->type, module_invoke_all('product_types'))) {
    if (strpos($form_id, 'node_form')) {
      $type = 'product';
      $id = $form['nid']['#value'];
      $weight = 1;
      $address = $node->shipping_address;
    }
    else {
      $type = 'manufacturer';
      $id = $form['tid']['#value'];
      $weight = 0;
      if (module_exists('uc_manufacturer')) {
        $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country FROM {uc_quote_manufacturer_locations} WHERE tid = %d", $id));
      }
    }
    if (empty($address)) {
      $address = variable_get('uc_quote_store_default_address', new stdClass());
    }
    if (!isset($form['shipping'])) {
      $form['shipping'] = array();
    }
    $form['shipping'] += array(
      '#type' => 'fieldset',
      '#title' => t('Shipping settings'),
      '#collapsible' => true,
      '#weight' => $weight,
      '#attributes' => array(
        'class' => 'product-shipping',
      ),
    );
    $form['shipping']['default_address'] = array(
      '#type' => 'fieldset',
      '#title' => t('Default product pickup address'),
      '#description' => t("When delivering products to customers, the original\n location of the product must be known in order to accurately quote the shipping\n cost and set up a delivery. This form provides the default location for products,\n either individually or an entire manufacturer's line. If a product's individual\n pickup address is blank, &Uuml;bercart looks for the manufacturer's. If that is\n also blank, it uses the store's default pickup address, which can be set <a href=\"!here\">here</a>.", array(
        '!here' => url('admin/store/settings/quotes'),
      )),
      '#collapsible' => true,
      '#collapsed' => true,
      '#weight' => -6,
    );
    $form['shipping']['default_address']['first_name'] = uc_textfield(uc_get_field_name('first_name'), $address->first_name, FALSE);
    $form['shipping']['default_address']['last_name'] = uc_textfield(uc_get_field_name('last_name'), $address->last_name, FALSE);
    $form['shipping']['default_address']['company'] = uc_textfield(uc_get_field_name('company'), $address->company, FALSE);
    $form['shipping']['default_address']['street1'] = uc_textfield(uc_get_field_name('street1'), $address->street1, FALSE, NULL, 64);
    $form['shipping']['default_address']['street2'] = uc_textfield(uc_get_field_name('street2'), $address->street2, FALSE, NULL, 64);
    $form['shipping']['default_address']['city'] = uc_textfield(uc_get_field_name('city'), $address->city, FALSE);
    if (isset($_POST['country'])) {
      $country = $_POST['country'];
    }
    else {
      $country = $address->country;
    }
    $form['shipping']['default_address']['zone'] = uc_zone_select(uc_get_field_name('zone'), $address->zone, null, $country);
    $form['shipping']['default_address']['postal_code'] = uc_textfield(uc_get_field_name('postal_code'), $address->postal_code, FALSE, NULL, 10, 10);
    $form['shipping']['default_address']['country'] = uc_country_select(uc_get_field_name('country'), $address->country);
    $types = uc_quote_shipping_type_options();
    if (is_array($types)) {
      $types = array_merge(array(
        '' => t('<Store default>'),
      ), $types);
    }
    else {
      $types = array(
        '' => t('<Store default>'),
      );
    }
    $form['shipping']['shipping_type'] = array(
      '#type' => 'select',
      '#title' => t('Default product shipping type'),
      '#default_value' => uc_quote_get_shipping_type($type, $id),
      '#options' => $types,
      '#weight' => -7,
    );
  }
  else {
    if ($form_id == 'uc_cart_checkout_form' && isset($form['panes']['quotes'])) {
      $form['#validate']['uc_quote_save_choice'] = array();
      $form['#pre_render'][] = 'uc_quote_cache_quotes';
    }
  }
}

/******************************************************************************
 * Workflow-ng Hooks                                                          *
 ******************************************************************************/

/**
 * Implementation of hook_event_info().
 *
 * Register an event for each shipping method. Enabled methods have active
 * configurations.
 */
function uc_quote_event_info() {
  $methods = module_invoke_all('shipping_method');
  $events = array();
  foreach ($methods as $id => $method) {
    $events['get_quote_from_' . $id] = array(
      '#label' => t('Getting shipping quote via !method', array(
        '!method' => $method['title'],
      )),
      '#module' => t('Quote'),
      '#arguments' => array(
        'order' => array(
          '#entity' => 'order',
          '#label' => t('Order'),
        ),
        'method' => array(
          '#entity' => 'quote_method',
          '#label' => t('Quote method'),
        ),
        'account' => array(
          '#entity' => 'user',
          '#label' => t('User account'),
        ),
      ),
    );
  }
  return $events;
}

/**
 * Implementation of hook_condition_info().
 */
function uc_quote_condition_info() {
  return array(
    'uc_quote_condition_product_shipping_type' => array(
      '#label' => t("Order has a product of a particular shipping type"),
      '#arguments' => array(
        'order' => array(
          '#entity' => 'order',
          '#label' => t('Order'),
        ),
      ),
      '#module' => t('Order: Product'),
    ),
    'uc_quote_condition_order_shipping_method' => array(
      '#label' => t("Order has a shipping quote from a particular method"),
      '#arguments' => array(
        'order' => array(
          '#entity' => 'order',
          '#label' => t('Order'),
        ),
      ),
      '#module' => t('Order: Shipping Quote'),
    ),
  );
}

/**
 * Returns true if the order has a product of the chosen shipping type.
 *
 * @see uc_quote_condition_product_shipping_type_form
 */
function uc_quote_condition_product_shipping_type($order, $settings) {
  $result = false;
  foreach ($order->products as $product) {
    if ($product->nid && uc_product_get_shipping_type($product) == $settings['type']) {
      $result = true;
      break;
    }
  }
  return $result;
}

/**
 * Settings form for uc_quote_condition_product_shipping_type().
 *
 * @ingroup forms
 * @see uc_quote_condition_product_shipping_type
 * @see uc_quooe_condition_product_shipping_type_submit
 */
function uc_quote_condition_product_shipping_type_form($settings = array()) {
  $form = array();
  $options = array();
  $types = module_invoke_all('shipping_type');
  foreach ($types as $id => $type) {
    $options[$id] = $type['title'];
  }
  $form['type'] = array(
    '#type' => 'select',
    '#title' => t('Shipping type'),
    '#options' => $options,
    '#default_value' => $settings['type'],
  );
  return $form;
}

/**
 * Submit handler for uc_quote_condition_product_shipping_type_form().
 */
function uc_quote_condition_product_shipping_type_submit($form_id, $form_values) {
  return array(
    'type' => $form_values['type'],
  );
}
function uc_quote_condition_order_shipping_method($order, $settings) {

  // Check the easy way first.
  if (is_array($order->quote)) {
    return $order->quote['method'] == $settings['method'];
  }

  // Otherwise, look harder.
  if (is_array($order->line_items)) {
    foreach ($order->line_items as $line_item) {
      if ($line_item['type'] == 'shipping' && in_array($line_item['title'], $settings['accessorials'])) {
        return TRUE;
      }
    }
  }
  return FALSE;
}
function uc_quote_condition_order_shipping_method_form($settings = array()) {
  $form = array();
  $methods = module_invoke_all('shipping_method');
  $enabled = variable_get('uc_quote_enabled', array());
  $options = array();
  foreach ($methods as $id => $method) {
    $options[$id] = $method['title'];
    if (!$enabled[$id]) {
      $options[$id] .= ' ' . t('(disabled)');
    }
  }
  $form['method'] = array(
    '#type' => 'select',
    '#title' => t('Shipping quote method'),
    '#default_value' => $settings['method'],
    '#options' => $options,
  );
  return $form;
}
function uc_quote_condition_order_shipping_method_submit($form_id, $form_values) {
  $methods = module_invoke_all('shipping_method');
  return array(
    'method' => $form_values['method'],
    'accessorials' => $methods[$form_values['method']]['quote']['accessorials'],
  );
}

/**
 * Implementation of hook_action_info().
 */
function uc_quote_action_info() {
  return array(
    'uc_quote_action_get_quote' => array(
      '#label' => t('Fetch a shipping quote'),
      '#arguments' => array(
        'order' => array(
          '#entity' => 'order',
          '#label' => t('Order'),
        ),
        'method' => array(
          '#entity' => 'quote_method',
          '#label' => t('Quote method'),
        ),
      ),
      '#module' => t('Quote'),
    ),
  );
}

/**
 * Retrive shipping quote and print JSON data.
 *
 * @param $order
 *   The order the quote is for.
 * @param $method
 *   The shipping method to generate the quote.
 */
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();
  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_cart_product_is_shippable($item)) {
        $products[$key] = drupal_clone($item);
      }
    }
    $quote_data = call_user_func($method['quote']['callback'], $products, $details, $method);
  }
  $messages = ob_get_contents();
  ob_end_clean();

  //drupal_set_message('<pre>'. print_r($quote_data, true) .'</pre>');
  if ($messages && variable_get('uc_quote_log_errors', false)) {
    watchdog('quote', $messages, WATCHDOG_WARNING);
    watchdog('quote', '<pre>' . print_r($quote_data, true) . '</pre>', WATCHDOG_WARNING);
  }
  print serialize($quote_data);
}

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

/**
 * Implementation of Übercart's hook_cart_pane.
 */
function uc_quote_cart_pane($items) {
  if (!variable_get('uc_cap_quotes_enabled', FALSE) || variable_get('uc_cart_delivery_not_shippable', TRUE) && !uc_cart_is_shippable()) {
    return array();
  }
  $panes[] = array(
    'id' => 'quotes',
    'title' => t('Shipping quotes'),
    'enabled' => FALSE,
    'weight' => 5,
    'body' => drupal_get_form('uc_cart_pane_quotes', $items),
  );
  return $panes;
}

/**
 * Defines the shipping quote checkout pane.
 */
function uc_quote_checkout_pane() {
  $panes[] = array(
    'id' => 'quotes',
    'callback' => 'uc_checkout_pane_quotes',
    'title' => t('Calculate shipping cost'),
    'desc' => t('Extra information necessary to ship.'),
    'weight' => 5,
    'shippable' => TRUE,
  );
  return $panes;
}

/**
 * Defines the shipping quote order pane.
 */
function uc_quote_order_pane() {
  $panes = array();
  $panes[] = array(
    'id' => 'quotes',
    'callback' => 'uc_order_pane_quotes',
    'title' => t('Shipping quote'),
    'desc' => t('Get a shipping quote for the order from a quoting module.'),
    'class' => 'abs-left',
    'weight' => 7,
    'show' => array(
      'edit',
    ),
  );
  return $panes;
}
function uc_quote_add_to_cart() {
  unset($_SESSION['quote']);
}
function uc_quote_update_cart_item() {
  unset($_SESSION['quote']);
}

/**
 * Implementation of hook_order().
 */
function uc_quote_order($op, &$arg1, $arg2) {
  switch ($op) {
    case 'submit':
      unset($_SESSION['quote']);
      break;
    case 'save':
      db_query("DELETE FROM {uc_order_quotes} WHERE oid = %d", $arg1->order_id);
      db_query("INSERT INTO {uc_order_quotes} (oid, method, accessorials, rate, quote_form) VALUES (%d, '%s', '%s', %f, '%s')", $arg1->order_id, $arg1->quote['method'], $arg1->quote['accessorials'], $arg1->quote['rate'], $arg1->quote['quote_form']);
      break;
    case 'load':
      $quote = db_fetch_array(db_query("SELECT method, accessorials, rate, quote_form FROM {uc_order_quotes} WHERE oid = %d", $arg1->order_id));
      $arg1->quote = $quote;
      $arg1->quote['accessorials'] = strval($quote['accessorials']);
      break;
    case 'delete':
      db_query("DELETE FROM {uc_order_quotes} WHERE oid = %d", $arg1->order_id);
      break;
  }
}

/**
 * Defines the shipping quote line item.
 */
function uc_quote_line_item() {
  $items[] = array(
    'id' => 'shipping',
    'title' => t('Shipping'),
    'weight' => 1,
    'default' => false,
    'stored' => true,
    'calculated' => true,
    'display_only' => false,
    'add_list' => true,
  );
  return $items;
}

/**
 * Implementation of Übercart's hook_shipping_type().
 */
function uc_quote_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;
}

/******************************************************************************
 * Menu Callbacks                                                             *
 ******************************************************************************/

/**
 * Display a summary of the shipping quote settings.
 */
function uc_quote_overview() {
  $sections = array();
  $address = variable_get('uc_quote_store_default_address', new stdClass());
  $sections[] = array(
    'edit' => 'admin/store/settings/quotes/edit',
    'title' => t('Quote settings'),
    'items' => array(
      variable_get('uc_quote_log_errors', false) ? t('Quote errors are submitted to watchdog.') : t('Quote errors are not submitted to watchdog.'),
      variable_get('uc_quote_display_debug', false) ? t('Debugging information is displayed to administrators when quotes are generated.') : t('Debugging information is not displayed to administrators when quotes are generated.'),
      t('The default store shipping address is:<br />!address', array(
        '!address' => uc_address_format($address->first_name, $address->last_name, $address->company, $address->street1, $address->street2, $address->city, $address->zone, $address->postal_code, $address->country),
      )),
    ),
  );
  $items = array();
  $methods = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled');
  uasort($methods, '_uc_quote_type_sort');
  foreach ($methods as $method) {
    $items[] = l(t('@method is enabled.', array(
      '@method' => $method['title'],
    )), 'admin/store/settings/quotes/methods/' . $method['id']);
  }
  $sections[] = array(
    'edit' => 'admin/store/settings/quotes/methods',
    'title' => t('Quote methods'),
    'items' => $items,
  );
  $types = uc_quote_shipping_type_options();
  $default = variable_get('uc_store_shipping_type', 'small_package');
  $types[$default] .= theme('item_list', array(
    t('Default'),
  ));
  $sections[] = array(
    'edit' => 'admin/store/settings/quotes/methods',
    'title' => t('Shipping types'),
    'items' => $types,
  );
  return theme('uc_settings_overview', $sections);
}

/**
 * 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.
 *
 * @ingroup forms
 * @see uc_quote_admin_settings_submit
 */
function uc_quote_admin_settings() {
  $address = variable_get('uc_quote_store_default_address', new stdClass());
  $form = array();
  $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'),
    '#summary callback ' => 'summarize_form',
    '#tree' => TRUE,
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $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_pane_description']['format'] = filter_form(variable_get('uc_quote_desc_format', FILTER_FORMAT_DEFAULT), NULL, array(
    'uc_quote_pane_description',
    'format',
  ));
  $form['uc_quote_pane_description']['format']['#summary callback'] = 'summarize_null';
  $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['uc_quote_err_msg']['format'] = filter_form(variable_get('uc_quote_msg_format', FILTER_FORMAT_DEFAULT), NULL, array(
    'uc_quote_err_msg',
    'format',
  ));
  $form['default_address'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default pickup address'),
    '#description' => t("When delivering products to customers, the original\n location of the product must be known in order to accurately quote the shipping\n cost and set up a delivery. This form provides the default location for products\n across the entire store. If a product's individual pickup address is blank,\n &Uuml;bercart looks for the manufacturer's. If that is also blank, it uses the\n store's default pickup address."),
    '#collapsible' => true,
    '#collapsed' => false,
  );
  $form['default_address']['first_name'] = uc_textfield(uc_get_field_name('first_name'), $address->first_name, FALSE);
  $form['default_address']['last_name'] = uc_textfield(uc_get_field_name('last_name'), $address->last_name, FALSE);
  $form['default_address']['company'] = uc_textfield(uc_get_field_name('company'), $address->company, FALSE);
  $form['default_address']['phone'] = uc_textfield(uc_get_field_name('phone'), $address->phone, FALSE, NULL, 32, 16);
  $form['default_address']['street1'] = uc_textfield(uc_get_field_name('street1'), $address->street1, FALSE, NULL, 64);
  $form['default_address']['street2'] = uc_textfield(uc_get_field_name('street2'), $address->street2, FALSE, NULL, 64);
  $form['default_address']['city'] = uc_textfield(uc_get_field_name('city'), $address->city, FALSE);
  if (isset($_POST['country'])) {
    $country = $_POST['country'];
  }
  else {
    $country = $address->country;
  }
  $form['default_address']['country'] = uc_country_select(uc_get_field_name('country'), $address->country);
  $form['default_address']['zone'] = uc_zone_select(uc_get_field_name('zone'), $address->zone, null, $country);
  $form['default_address']['postal_code'] = uc_textfield(uc_get_field_name('postal_code'), $address->postal_code, FALSE, NULL, 10, 10);
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

/**
 * Submit function for uc_quote_admin_settings() in lieu of system_settings_form().
 */
function uc_quote_admin_settings_submit($form_id, $form_values) {
  $address = new stdClass();
  $address->first_name = $form_values['first_name'];
  $address->last_name = $form_values['last_name'];
  $address->company = $form_values['company'];
  $address->phone = $form_values['phone'];
  $address->street1 = $form_values['street1'];
  $address->street2 = $form_values['street2'];
  $address->city = $form_values['city'];
  $address->zone = $form_values['zone'];
  $address->postal_code = $form_values['postal_code'];
  $address->country = $form_values['country'];
  variable_set('uc_quote_store_default_address', $address);
  variable_set('uc_quote_log_errors', $form_values['uc_quote_log_errors']);
  variable_set('uc_quote_display_debug', $form_values['uc_quote_display_debug']);
  variable_set('uc_quote_require_quote', $form_values['uc_quote_require_quote']);
  variable_set('uc_quote_pane_description', $form_values['uc_quote_pane_description']['text']);
  variable_set('uc_quote_desc_format', $form_values['uc_quote_pane_description']['format']);
  variable_set('uc_quote_err_msg', $form_values['uc_quote_err_msg']['text']);
  variable_set('uc_quote_msg_format', $form_values['uc_quote_err_msg']['format']);
  drupal_set_message(t('The configuration options have been saved.'));
}

/**
 * Settings for the shipping quote methods.
 *
 * Enable and reorder shipping quote methods. Set the default shipping type.
 *
 * @ingroup forms
 * @see theme_uc_quote_method_settings
 * @see uc_quote_method_settings_validate
 * @see uc_quote_method_settings_submit
 */
function uc_quote_method_settings() {
  $form = array();
  $form['methods'] = array(
    '#tree' => true,
  );
  $enabled = variable_get('uc_quote_enabled', array());
  $weight = variable_get('uc_quote_method_weight', array());
  $methods = uc_quote_shipping_method_options();
  if (is_array($methods)) {
    foreach ($methods as $id => $title) {
      $form['methods'][$id]['uc_quote_enabled'] = array(
        '#type' => 'checkbox',
        '#default_value' => $enabled[$id],
      );
      $form['methods'][$id]['uc_quote_method_weight'] = array(
        '#type' => 'weight',
        '#delta' => 5,
        '#default_value' => $weight[$id],
      );
    }
  }
  $shipping_types = uc_quote_shipping_type_options();
  if (is_array($shipping_types)) {
    $form['uc_quote_type_weight'] = array(
      '#type' => 'fieldset',
      '#title' => t('Type ordering'),
      '#description' => t('Determines which shipping methods are quoted at checkout when products of different shipping types are ordered. Lighter weights are chosen first.'),
      '#collapsible' => true,
      '#tree' => true,
    );
    $weight = variable_get('uc_quote_type_weight', array());
    $shipping_methods = module_invoke_all('shipping_method');
    $method_types = array();
    foreach ($shipping_methods as $method) {
      $method_types[$method['quote']['type']][] = $method['title'];
    }
    if (is_array($method_types['order'])) {
      $form['uc_quote_type_weight']['#description'] .= t('<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 . (is_array($method_types[$id]) ? ' (' . implode(', ', $method_types[$id]) . ')' : ''),
        '#delta' => 5,
        '#default_value' => $weight[$id],
      );
    }
  }
  $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['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

/**
 * Display a formatted list of shipping quote methods and form elements.
 *
 * @ingroup themeable
 * @see uc_quote_method_settings
 */
function theme_uc_quote_method_settings($form) {
  $methods = uc_quote_shipping_method_options();
  $output = '';
  $header = array(
    t('Enable'),
    t('Shipping method'),
    t('Weight'),
  );
  $rows = array();
  foreach (element_children($form['methods']) as $method) {
    $row = array();
    $row[] = drupal_render($form['methods'][$method]['uc_quote_enabled']);
    $row[] = $methods[$method];
    $row[] = drupal_render($form['methods'][$method]['uc_quote_method_weight']);
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

/**
 * Validation handler for uc_quote_method_settings().
 *
 * Must enable at least one shipping method.
 */
function uc_quote_method_settings_validate($form_id, $form_values) {
  $none_enabled = true;
  if (is_array($form_values['methods'])) {
    foreach ($form_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.'));
  }
}

/**
 * Submit handler for uc_quote_method_settings().
 */
function uc_quote_method_settings_submit($form_id, $form_values) {
  $enabled = array();
  $method_weight = array();
  foreach ($form_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_values['uc_quote_type_weight']);
  variable_set('uc_store_shipping_type', $form_values['uc_store_shipping_type']);
  workflow_ng_clear_cache();
  drupal_set_message(t('The configuration options have been saved.'));
}

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

/**
 * Store the shipping type of products and manufacturers.
 *
 * Fulfillment modules are invoked for products that match their shipping type.
 * This function stores the shipping type of a product or a manufacturer.
 *
 * @param $id_type
 *   product | manufacturer
 * @param $id
 *   Either the node id or term id of the object receiving the shipping type.
 * @param $shipping_type
 *   The type of product that is fulfilled by various fulfillment modules.
 */
function uc_quote_set_shipping_type($id_type, $id, $shipping_type) {
  db_query("DELETE FROM {uc_quote_shipping_types} WHERE id_type = '%s' AND id = %d", $id_type, $id);
  if ($shipping_type !== '') {
    db_query("INSERT INTO {uc_quote_shipping_types} (id_type, id, shipping_type) VALUES ('%s', %d, '%s')", $id_type, $id, $shipping_type);
  }
}

/**
 * Retrieve a product's or manufacturer's shipping type from the database.
 *
 * @param $id_type
 *   product | manufacturer
 * @param $id
 *   Either the node id or term id of the object that was assigned the shipping type.
 * @return The shipping type.
 */
function uc_quote_get_shipping_type($id_type, $id) {
  static $types = array();
  if (!isset($types[$id_type][$id])) {
    $types[$id_type][$id] = db_result(db_query("SELECT shipping_type FROM {uc_quote_shipping_types} WHERE id_type = '%s' AND id = %d", $id_type, $id));
  }
  return $types[$id_type][$id];
}

/**
 * Get a product's shipping type, defaulting to its manufacturer's or the store's if it doesn't exist.
 *
 * @param $product
 *   The product object.
 * @return The shipping type.
 */
function uc_product_get_shipping_type($product) {
  $shipping_type = variable_get('uc_store_shipping_type', 'small_package');
  if (module_exists('uc_manufacturer') && $product->manufacturer) {
    $m = taxonomy_get_term_by_name($product->manufacturer);
    if ($type = uc_quote_get_shipping_type('manufacturer', $m->tid)) {
      $shipping_type = $type;
    }
  }
  if ($type = uc_quote_get_shipping_type('product', $product->nid)) {
    $shipping_type = $type;
  }
  return $shipping_type;
}

/**
 * Get a product's default shipping address.
 *
 * Load the default shipping address of a product, it's manufacturer's, or the
 * store's, whichever is available.
 *
 * @param $nid
 *   A product node id.
 * @return
 *   An address object.
 */
function uc_quote_get_default_shipping_address($nid) {
  $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_product_locations} WHERE nid = %d", $nid));
  if (empty($address)) {
    if (module_exists('uc_manufacturer')) {
      $manufacturer = uc_product_get_manufacturer($nid);
      $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_manufacturer_locations} WHERE tid = %d", $manufacturer->tid));
    }
    if (empty($address)) {
      $address = variable_get('uc_quote_store_default_address', new stdClass());
    }
  }
  return $address;
}

/**
 * Cart pane callback.
 *
 * @ingroup forms
 * @see theme_uc_cart_pane_quotes
 */
function uc_cart_pane_quotes($items) {
  global $user;

  // Get all quote types neccessary to fulfill order.
  $shipping_types = array();
  foreach ($items as $product) {
    $shipping_types[] = uc_product_get_shipping_type($product);
  }
  $shipping_types = array_unique($shipping_types);
  $all_types = module_invoke_all('shipping_type');
  $shipping_type = '';
  $type_weight = 1000;

  // arbitrary large 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 = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled');
  uasort($methods, '_uc_quote_type_sort');
  $method_choices = array();
  foreach ($methods as $method) {
    if ($method['quote']['type'] == 'order' || $method['quote']['type'] == $shipping_type) {
      $method_choices[$method['id']] = $method['title'];
    }
  }
  $form['delivery_country'] = uc_country_select(uc_get_field_name('country'), uc_store_default_country(), NULL, 'name', TRUE);
  $country_id = isset($_POST['delivery_country']) ? intval($_POST['delivery_country']) : uc_store_default_country();
  $form['delivery_zone'] = uc_zone_select(uc_get_field_name('zone'), null, null, $country_id, 'name', true);
  $form['delivery_postal_code'] = uc_textfield(uc_get_field_name('postal_code'), '', TRUE, NULL, 10, 10);
  $form['quote_method'] = array(
    '#type' => 'hidden',
    '#value' => key($method_choices),
  );
  $form['get_quote'] = array(
    '#type' => 'button',
    '#value' => t('Calculate'),
  );
  $form['page'] = array(
    '#type' => 'hidden',
    '#value' => 'cart',
  );
  $form['uid'] = array(
    '#type' => 'hidden',
    '#value' => $user->uid,
  );
  $form['quote'] = array(
    '#type' => 'markup',
    '#value' => '<div id="quote"></div>',
  );
  uc_add_js(array(
    'uc_quote' => array(
      'progress_msg' => t('Receiving quotes:'),
      'err_msg' => check_markup(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.")), variable_get('uc_quote_msg_format', FILTER_FORMAT_DEFAULT), false),
    ),
  ), 'setting');
  uc_add_js('misc/progress.js');
  uc_add_js(drupal_get_path('module', 'uc_quote') . '/uc_quote.js');
  $prod_string = '';
  foreach (uc_cart_get_contents() as $item) {
    $prod_string .= '|' . $item->nid;
    $prod_string .= '^' . $item->title;
    $prod_string .= '^' . $item->model;
    $prod_string .= '^' . $item->manufacturer;
    $prod_string .= '^' . $item->qty;
    $prod_string .= '^' . $item->cost;
    $prod_string .= '^' . $item->price;
    $prod_string .= '^' . $item->weight;
    $prod_string .= '^' . serialize($item->data);
  }
  $prod_string = substr($prod_string, 1);

  // If a previous quote gets loaded, make sure it gets saved again.
  // Also, make sure the previously checked option is checked by default.
  uc_add_js('$(function() {
    setQuoteCallbacks("' . urlencode($prod_string) . '");
    $("#uc-cart-pane-quotes").submit(function() {
      quoteCallback("' . urlencode($prod_string) . '");
      return false;
    });
  })', 'inline');
  return $form;
}

/**
 * Display the formatted quote cart pane.
 */
function theme_uc_cart_pane_quotes($form) {
  $output = '<div class="solid-border">';
  $output .= '<strong>' . t('Estimated shipping cost:') . '</strong>';
  $output .= drupal_render($form['delivery_country']);
  $output .= drupal_render($form['delivery_zone']);
  $output .= drupal_render($form['delivery_postal_code']);
  $output .= drupal_render($form['get_quote']);
  $output .= drupal_render($form);
  $output .= '</div>';
  return $output;
}

/**
 * Shipping quote checkout pane callback.
 *
 * Selects a quoting method based on the enabled methods' weight and the types
 * of products in the cart. The "Get Quotes" button fires a callback that returns
 * a form for the customer to select a rate based on their needs and preferences.
 *
 * Adds a line item to the order that records the chosen shipping quote.
 */
function uc_checkout_pane_quotes($op, &$arg1, $arg2) {
  global $user;
  switch ($op) {
    case 'view':
      $description = check_markup(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.')), variable_get('uc_quote_desc_format', FILTER_FORMAT_DEFAULT));

      // Let Javascript know where we are.
      $contents['page'] = array(
        '#type' => 'hidden',
        '#value' => 'checkout',
      );
      $contents['uid'] = array(
        '#type' => 'hidden',
        '#value' => $user->uid,
      );
      $contents['quote_button'] = array(
        '#type' => 'button',
        '#value' => t('Click to calculate shipping'),
        '#weight' => 0,
      );
      uc_add_js(array(
        'uc_quote' => array(
          'progress_msg' => t('Receiving quotes:'),
          'err_msg' => check_markup(variable_get('uc_quote_err_msg', t("There were problems getting a shipping quote. Please verify the delivery address and product information and try again.\nIf this does not resolve the issue, please call @phone to complete your order.", array(
            '@phone' => variable_get('uc_store_phone', null),
          ))), variable_get('uc_quote_msg_format', FILTER_FORMAT_DEFAULT), false),
        ),
      ), 'setting');
      uc_add_js('misc/progress.js');
      uc_add_js(drupal_get_path('module', 'uc_quote') . '/uc_quote.js');
      $default = $arg1->quote['method'] . '---' . ($arg1->quote['accessorials'] ? $arg1->quote['accessorials'] : 0);
      $prod_string = '';
      foreach (uc_cart_get_contents() as $item) {
        $prod_string .= '|' . $item->nid;
        $prod_string .= '^' . $item->title;
        $prod_string .= '^' . $item->model;
        $prod_string .= '^' . $item->manufacturer;
        $prod_string .= '^' . $item->qty;
        $prod_string .= '^' . $item->cost;
        $prod_string .= '^' . $item->price;
        $prod_string .= '^' . $item->weight;
        $prod_string .= '^' . serialize($item->data);
      }
      $prod_string = substr($prod_string, 1);

      // If a previous quote gets loaded, make sure it gets saved again.
      // Also, make sure the previously checked option is checked by default.
      uc_add_js('$(function() {
        setQuoteCallbacks("' . urlencode($prod_string) . '");
        var quoteButton = $("input:radio[@name=quote-option]").click(function() {
          var quoteButton = $(this);
          var label = quoteButton.parent("label").text().split(":", 2)[0];
          quoteButton.end();
          var rate = $("input:hidden[@name=\'rate[" + quoteButton.val() + "]\']").val();
          set_line_item("shipping", label, rate, 1, 1, false);
          if (window.getTax) {
            getTax();
          }
          else if (window.render_line_items) {
            render_line_items();
          }
        }).filter("[@value=' . $default . ']").click();
        var quoteDiv = $("#quote");
        if (quoteDiv.length && $("#quote input[@name=quote-form]").length == 0) {
          quoteDiv.append("<input type=\\"hidden\\" name=\\"quote-form\\" value=\\"" + encodeURIComponent(quoteDiv.html()) + "\\" />");
        }
      });', 'inline');
      return array(
        'description' => $description,
        'contents' => $contents,
      );
    case 'process':
      if (!isset($_POST['quote-option'])) {
        if (variable_get('uc_quote_require_quote', true)) {
          drupal_set_message(t('You must select a shipping option before continuing.'), 'error');
          return false;
        }
        else {
          return true;
        }
      }
      $details = array();
      foreach ($arg1 as $key => $value) {
        if (strpos($key, 'delivery_') !== false) {
          $details[substr($key, 9)] = $value;
        }
      }
      $products = array();
      foreach ($arg1->products as $id => $product) {
        $node = (array) node_load($product->nid);
        foreach ($node as $key => $value) {
          if (!isset($product->{$key})) {
            $product->{$key} = $value;
          }
        }
        $arg1->products[$id] = $product;
      }
      $quote_option = explode('---', $_POST['quote-option']);
      $arg1->quote['method'] = $quote_option[0];
      $arg1->quote['accessorials'] = $quote_option[1];
      $_SESSION['quote']['quote_form'] = rawurldecode($_POST['quote-form']);
      $methods = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled');
      $method = $methods[$quote_option[0]];
      $quote_data = array();
      ob_start();
      workflow_ng_invoke_event('get_quote_from_' . $method['id'], $arg1, $method, $user);
      $quote_string = ob_get_contents();
      ob_end_clean();
      $quote_data = unserialize($quote_string);
      if (!isset($quote_data[$quote_option[1]])) {
        drupal_set_message(t('Invalid option selected. Recalculate shipping quotes to continue.'), 'error');
        return false;
      }
      $label = $method['quote']['accessorials'][$quote_option[1]];
      $arg1->quote['rate'] = $quote_data[$quote_option[1]]['rate'];
      $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = %d AND type = 'shipping'", $arg1->order_id);
      if ($lid = db_result($result)) {
        uc_order_update_line_item($lid, $label, $arg1->quote['rate']);
      }
      else {
        uc_order_line_item_add($arg1->order_id, 'shipping', $label, $arg1->quote['rate']);
      }
      return true;
    case 'review':
      $result = db_query("SELECT * FROM {uc_order_line_items} WHERE order_id = %d AND type = '%s'", $arg1->order_id, 'shipping');
      if ($line_item = db_fetch_object($result)) {
        $review[] = array(
          'title' => $line_item->title,
          'data' => uc_currency_format($line_item->amount),
        );
      }
      return $review;
  }
}

/**
 * Shipping quote order pane callback.
 */
function uc_order_pane_quotes($op, $arg1) {
  switch ($op) {
    case 'edit-form':

      // Let Javascript know where we are.
      $form['quotes']['page'] = array(
        '#type' => 'hidden',
        '#value' => 'order-edit',
      );
      $form['quotes']['quote_button'] = array(
        '#type' => 'submit',
        '#value' => t('Get shipping quotes'),
      );
      $form['quotes']['add_quote'] = array(
        '#type' => 'submit',
        '#value' => t('Apply to order'),
        '#attributes' => array(
          'class' => 'save-button',
        ),
        '#disabled' => TRUE,
      );
      $form['quotes']['quote'] = array(
        '#type' => 'markup',
        '#value' => '<div id="quote"></div>',
      );
      uc_add_js(array(
        'uc_quote' => array(
          'progress_msg' => t('Receiving quotes:'),
          'err_msg' => check_markup(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.")), variable_get('uc_quote_msg_format', FILTER_FORMAT_DEFAULT), false),
        ),
      ), 'setting');
      uc_add_js('misc/progress.js');
      uc_add_js(drupal_get_path('module', 'uc_quote') . '/uc_quote.js');
      $default = $arg1->quote['accessorials'] ? $arg1->quote['accessorials'] : 0;

      // If a previous quote gets loaded, make sure it gets saved again.
      // Also, make sure the previously checked option is checked by default.
      uc_add_js('$(function() {
        setQuoteCallbacks();
        $("input:radio[@name=quote-option]").filter("[@value=' . $default . ']").attr("checked", "checked");
        var quoteDiv = $("#quote");
        if (quoteDiv.length && $("#quote input[@name=quote-form]").length == 0) {
          quoteDiv.append("<input type=\\"hidden\\" name=\\"quote-form\\" value=\\"" + encodeURIComponent(quoteDiv.html()) + "\\" />");
        }
      })', 'inline');
      return $form;
    case 'edit-theme':
      return drupal_render($arg1['quotes']);
    case 'edit-process':

      //drupal_set_message('<pre>'. print_r($_POST, true) .'</pre>');
      if (isset($_POST['quote-option'])) {
        list($changes['quote']['method'], $changes['quote']['accessorials']) = explode('---', $_POST['quote-option']);
      }
      $changes['quote']['rate'] = $_POST['rate'][$_POST['quote-option']];
      $changes['quote']['quote_form'] = rawurldecode($_POST['quote-form']);
      return $changes;
    case 'edit-ops':
      return array(
        t('Apply to order'),
      );
    case t('Apply to order'):
      if (isset($_POST['quote-option'])) {
        if ($order = uc_order_load($arg1['order_id'])) {
          $user = user_load(array(
            'uid' => $order->uid,
          ));
          $details = array();
          foreach ($order as $key => $value) {
            if (strpos($key, 'delivery_') !== false) {
              $details[substr($key, 9)] = $value;
            }
          }
          $products = array();
          foreach ($order->products as $product) {
            if ($product->nid) {
              $node = (array) node_load($product->nid);
              foreach ($node as $key => $value) {
                if (!isset($product->{$key})) {
                  $product->{$key} = $value;
                }
              }
            }
            $products[] = $product;
          }

          //drupal_set_message('<pre>'. print_r($order, true) .'</pre>');
          $quote_option = explode('---', $_POST['quote-option']);
          $order->quote['method'] = $quote_option[0];
          $order->quote['accessorials'] = $quote_option[1];
          $order->quote['quote_form'] = rawurldecode($_POST['quote-form']);
          $methods = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled');
          $method = $methods[$quote_option[0]];
          $quote_data = array();
          ob_start();
          workflow_ng_invoke_event('get_quote_from_' . $method['id'], $order, $method, $user);
          $quote_string = ob_get_contents();
          ob_end_clean();
          $quote_data = unserialize($quote_string);

          //drupal_set_message('Chosen quote method:<pre>'. print_r($method, true) .'</pre>');

          //drupal_set_message('Chosen quote data:<pre>'. print_r($quote_data, true) .'</pre>');
          if (!isset($quote_data[$quote_option[1]])) {
            drupal_set_message(t('Invalid option selected. Recalculate shipping quotes to continue.'), 'error');
            break;
          }
          $label = $method['quote']['accessorials'][$quote_option[1]];
          $order->quote['rate'] = $quote_data[$quote_option[1]]['rate'];
          $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = %d AND type = 'shipping'", $arg1['order_id']);
          if ($lid = db_result($result)) {
            uc_order_update_line_item($lid, $label, $order->quote['rate']);
          }
          else {
            uc_order_line_item_add($order->order_id, 'shipping', $label, $order->quote['rate']);
          }
        }
      }
      break;
  }
}
function uc_quote_save_choice($form_values) {
  $quote_option = explode('---', $_POST['quote-option']);
  $_SESSION['quote'] = array(
    'method' => $quote_option[0],
    'accessorials' => $quote_option[1],
    'rate' => $_POST['rate'][$_POST['quote-option']],
    'quote_form' => $_POST['quote-form'],
  );
}
function uc_quote_cache_quotes($form_id, &$form) {
  if ($form_id == 'uc_cart_checkout_form') {
    if (isset($_SESSION['quote']) && $_SESSION['quote']['rate']) {
      $quote = $_SESSION['quote'];
      $form['panes']['quotes']['quote'] = array(
        '#type' => 'markup',
        '#value' => '<div id="quote" class="solid-border">' . rawurldecode($_SESSION['quote']['quote_form']) . '</div>',
        '#weight' => 1,
      );
      $methods = module_invoke_all('shipping_method');
      $method = $methods[$quote['method']];
      uc_add_js('$(document).ready(function() {
          $("#quote").find("input:radio[@value=' . $quote['method'] . '---' . $quote['accessorials'] . ']").eq(0).change().attr("checked", "checked");
          if (window.set_line_item) {
            set_line_item("shipping", "' . $method['quote']['accessorials'][$quote['accessorials']] . '", ' . $quote['rate'] . ', 1, 1, false);
          }
          if (window.getTax) {
            getTax();
            setTaxCallbacks();
          }
          else if (window.render_line_items) {
            render_line_items();
          }
        });', 'inline');
    }
    else {
      $form['panes']['quotes']['quote'] = array(
        '#type' => 'markup',
        '#value' => '<div id="quote"></div>',
        '#weight' => 1,
      );
    }
  }
}

/**
 * Callback to return the shipping quote(s) of the appropriate quoting method(s).
 */
function uc_quote_request_quotes() {

  /* print '<pre>';
    print_r($_POST);
    print '</pre>'; */
  $products = array();
  foreach (explode('|', urldecode($_POST['products'])) as $item) {
    $props = explode('^', $item);
    $product = new stdClass();
    $product->nid = $props[0];
    $product->title = $props[1];
    $product->model = $props[2];
    $product->manufacturer = $props[3];
    $product->qty = $props[4];
    $product->cost = $props[5];
    $product->price = $props[6];
    $product->weight = $props[7];
    if ($data = unserialize($props[8])) {
      $product->data = $data;
    }
    else {
      $product->data = $props[8];
    }
    if ($product->nid) {
      $node = (array) node_load($product->nid);
      foreach ($node as $key => $value) {
        if (!isset($product->{$key})) {
          $product->{$key} = $value;
        }
      }
    }
    $products[] = $product;
  }
  $fake_order = new stdClass();
  $fake_order->uid = $_POST['uid'];
  $fake_order->products = $products;
  foreach ((array) $_POST['details'] as $type => $address) {
    foreach ($address as $key => $value) {
      if ($key == 'country' and $value == '') {
        $value = variable_get('uc_store_country', 840);
      }
      $field = $type . '_' . $key;
      $fake_order->{$field} = $value;
    }
  }

  // Consider the total to be from products only, because line items are
  // mostly non-existent at this point.
  $fake_order->order_total = uc_order_get_total($fake_order, true);

  // Get all quote types neccessary to fulfill order.
  $quote_data = _uc_quote_assemble_quotes($fake_order);

  //drupal_set_message('<pre>'. print_r($methods, true) .'</pre>');

  //drupal_set_message('<pre>'. print_r($quote_data, true) .'</pre>');
  $return_quotes = array();
  foreach ($quote_data as $method_id => $options) {
    foreach ($options as $accsrl => $data) {
      $return_quotes[$method_id . '---' . $accsrl] = $data;
    }
  }
  drupal_set_header("Content-Type: text/javascript; charset=utf-8");
  print drupal_to_js($return_quotes);
  exit;
}
function _uc_quote_assemble_quotes($order) {
  global $user;
  $account = user_load(array(
    'uid' => $order->uid,
  ));
  if (!$account) {
    $account = $user;
  }
  $products = $order->products;
  $shipping_types = array();
  foreach ($products as $product) {
    $shipping_types[] = uc_product_get_shipping_type($product);
  }
  $shipping_types = array_unique($shipping_types);
  $all_types = module_invoke_all('shipping_type');
  $shipping_type = '';

  // Use the most prominent shipping type (lightest weight).
  $type_weight = 1000;

  // arbitrary large 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 = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled');
  uasort($methods, '_uc_quote_type_sort');
  foreach ($methods as $id => $method) {
    if ($method['quote']['type'] != 'order' && $method['quote']['type'] != $shipping_type) {
      unset($methods[$id]);
    }
  }
  ob_start();

  //drupal_set_message('<pre>'. print_r($products, true) .'</pre>');
  $quote_data = array();
  foreach ($methods as $method) {
    workflow_ng_invoke_event('get_quote_from_' . $method['id'], $order, $method, $account);
    $data = ob_get_contents();
    if ($data = unserialize($data)) {
      $quote_data[$method['id']] = $data;
    }
    ob_clean();
  }
  ob_end_clean();
  return $quote_data;
}

/**
 * Callback for array_filter().
 */
function _uc_quote_method_enabled($method) {
  return $method['enabled'];
}

/**
 * Callback for uasort().
 */
function _uc_quote_type_sort($a, $b) {
  $aw = $a['weight'];
  $bw = $b['weight'];
  if ($aw == $bw) {
    return strcasecmp($a['id'], $b['id']);
  }
  else {
    return $aw < $bw ? -1 : 1;
  }
}

/**
 * Callback for uasort().
 *
 * Sort service rates by increasing price.
 */
function uc_quote_price_sort($a, $b) {
  $ar = $a['rate'];
  $br = $b['rate'];
  if ($ar == $br) {
    return 0;
  }
  else {
    return $ar < $br ? -1 : 1;
  }
}

/**
 * Returns an array of quote types to be selected in a form.
 */
function uc_quote_type_options() {
  $methods = module_invoke_all('shipping_method');
  foreach ($methods as $method) {
    if (isset($method['quote'])) {
      $types[$method['id']] = $method['title'];
    }
  }
  return $types;
}

/**
 * Returns an array of shipping types to be selected in a form.
 */
function uc_quote_shipping_type_options() {
  $ship_types = module_invoke_all('shipping_type');
  uasort($ship_types, '_uc_quote_type_sort');
  $types = array();
  foreach ($ship_types as $ship_type) {
    $types[$ship_type['id']] = $ship_type['title'];
  }
  if (!count($types)) {
    $types['small_package'] = t('Small package');
  }
  return $types;
}

/**
 * Returns an array of shipping methods to be selected in a form.
 */
function uc_quote_shipping_method_options() {
  $methods = module_invoke_all('shipping_method');
  uasort($methods, '_uc_quote_type_sort');
  $types = array();
  foreach ($methods as $method) {
    $types[$method['id']] = $method['title'];
  }
  return $types;
}

Functions

Namesort descending Description
theme_uc_cart_pane_quotes Display the formatted quote cart pane.
theme_uc_quote_method_settings Display a formatted list of shipping quote methods and form elements.
uc_cart_pane_quotes Cart pane callback.
uc_checkout_pane_quotes Shipping quote checkout pane callback.
uc_order_pane_quotes Shipping quote order pane callback.
uc_product_get_shipping_type Get a product's shipping type, defaulting to its manufacturer's or the store's if it doesn't exist.
uc_quote_action_get_quote Retrive shipping quote and print JSON data.
uc_quote_action_info Implementation of hook_action_info().
uc_quote_add_to_cart
uc_quote_admin_settings Default shipping settings.
uc_quote_admin_settings_submit Submit function for uc_quote_admin_settings() in lieu of system_settings_form().
uc_quote_cache_quotes
uc_quote_cart_pane Implementation of Übercart's hook_cart_pane.
uc_quote_checkout_pane Defines the shipping quote checkout pane.
uc_quote_condition_info Implementation of hook_condition_info().
uc_quote_condition_order_shipping_method
uc_quote_condition_order_shipping_method_form
uc_quote_condition_order_shipping_method_submit
uc_quote_condition_product_shipping_type Returns true if the order has a product of the chosen shipping type.
uc_quote_condition_product_shipping_type_form Settings form for uc_quote_condition_product_shipping_type().
uc_quote_condition_product_shipping_type_submit Submit handler for uc_quote_condition_product_shipping_type_form().
uc_quote_event_info Implementation of hook_event_info().
uc_quote_form_alter Implementation of hook_form_alter().
uc_quote_get_default_shipping_address Get a product's default shipping address.
uc_quote_get_shipping_type Retrieve a product's or manufacturer's shipping type from the database.
uc_quote_line_item Defines the shipping quote line item.
uc_quote_menu Implementation of hook_menu().
uc_quote_method_settings Settings for the shipping quote methods.
uc_quote_method_settings_submit Submit handler for uc_quote_method_settings().
uc_quote_method_settings_validate Validation handler for uc_quote_method_settings().
uc_quote_nodeapi Implementation of hook_nodeapi().
uc_quote_order Implementation of hook_order().
uc_quote_order_pane Defines the shipping quote order pane.
uc_quote_overview Display a summary of the shipping quote settings.
uc_quote_perm Implementation of hook_perm().
uc_quote_price_sort Callback for uasort().
uc_quote_request_quotes Callback to return the shipping quote(s) of the appropriate quoting method(s).
uc_quote_save_choice
uc_quote_set_shipping_type Store the shipping type of products and manufacturers.
uc_quote_shipping_method_options Returns an array of shipping methods to be selected in a form.
uc_quote_shipping_type Implementation of Übercart's hook_shipping_type().
uc_quote_shipping_type_options Returns an array of shipping types to be selected in a form.
uc_quote_taxonomy Implementation of hook_taxonomy().
uc_quote_type_options Returns an array of quote types to be selected in a form.
uc_quote_update_cart_item
_uc_quote_assemble_quotes
_uc_quote_method_enabled Callback for array_filter().
_uc_quote_type_sort Callback for uasort().