Index: shipping/uc_quote/uc_quote.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/shipping/uc_quote/Attic/uc_quote.js,v
retrieving revision 1.5.2.9
diff -u -r1.5.2.9 uc_quote.js
--- shipping/uc_quote/uc_quote.js	20 Oct 2009 20:58:07 -0000	1.5.2.9
+++ shipping/uc_quote/uc_quote.js	16 Jun 2010 15:49:53 -0000
@@ -2,208 +2,191 @@
 
 /**
  * @file
- * Handle asynchronous calls on checkout page to retrieve shipping quotes.
+ * Extend the AHAH callbacks on checkout page to retrieve shipping quotes when a customer changes his or her address.
  */
 
-var page;
-var details;
-var methods;
+var activeRequest = null;
+var behaviorsBound = false;
 
 /**
- * Set event handlers on address fields.
+ * Register UC Quote JavaScript behaviors with Drupal.
  */
-function setQuoteCallbacks(products, context) {
-  triggerQuoteCallback = function() {
-    quoteCallback(products);
-  };
-  $("input[name*=delivery_postal_code]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').change(triggerQuoteCallback);
-  $("input[id*=quote-button]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').click(function() {
-    // returns false to prevent default actions and propogation
-    return quoteCallback(products);
-  });
-  $("input[name*=quote_method]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').change(function() {
-    // returns false to prevent default actions and propogation
-    return quoteCallback(products);
-  });
-  $("select[name*=delivery_address_select]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').change(function() {
-    $("input[name*=delivery_postal_code]").trigger('change');
-  });
-  $("input[name*=copy_address]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').click(function() {
-    if (copy_box_checked == true) {
-      $("input[name*=billing_postal_code]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').bind('change', triggerQuoteCallback);
-      $("select[name*=billing_address_select]:not(.getQuotes-processed)", context).addClass('getQuotes-processed').bind('change', triggerQuoteCallback);
-      triggerQuoteCallback();
+Drupal.behaviors.uc_quote_callbacks = function(context) {
+  // Behaviors for elements that are never dynamically added/removed should only happen once per page load.
+  if (!behaviorsBound) {
+    setQuoteCallbacks();
+    
+    behaviorsBound = true;
+  }
+  
+  setLineItemCallbacks();
+}
+
+/**
+ * Set-up quote request callback triggers on customer address fields.
+ * 
+ * This allows the checkout form to automatically generate shipping quotes in response to the customer providing
+ * his or her address.
+ */
+function setQuoteCallbacks() {
+  if (getQuoteButton() != null) {
+    registerShippingQuoteRequestListeners();
+    
+    // Delivery field names contain "[delivery_"
+    addQuoteChangeCallbacks($("input[name*='[delivery_']"));
+    addQuoteChangeCallbacks($("select[name*='[delivery_']"));
+    
+    // Billing field names contain "[billing_"
+    addQuoteChangeCallbacks($("input[name*='[billing_']"));
+    addQuoteChangeCallbacks($("select[name*='[billing_']"));
+    
+    /* If the billing address is the same as the shipping address, we don't need to request shipping quotes in response
+     * to billing field changes (since shipping quotes will already be requested when the user changes his or her
+     * shipping address).
+     * 
+     * On the other hand, if the addresses are not indicated as the same, we should re-request shipping quotes when the
+     * billing address changes, in case the site has an odd configuration that uses the billing address as the delivery
+     * address.
+     */
+    $("input[name*='[copy_address]']").bind('click', function() {
+      if (this.checked) {
+        triggerQuoteCallback();
+      }
+    });
+  }
+}
+
+/**
+ * Register callbacks with jQuery that will let us know when a shipping quote request is in progress.
+ * 
+ * This gives us enough information to cancel a pending request later if the user's shipping information changes.
+ */
+function registerShippingQuoteRequestListeners() {
+  var quotePathRegex = /^.*cart\/checkout\/shipping\/quote$/;
+  
+  $().ajaxSend(function (event, request, settings) {
+    if (settings.url.match(quotePathRegex)) {
+      activeRequest = new Object();
+      
+      activeRequest.request = request;
+      activeRequest.settings = settings;
     }
-    else {
-      $("input[name*=billing_postal_code].getQuotes-processed").removeClass('getQuotes-processed').unbind('change', triggerQuoteCallback);
-      $("select[name*=billing_address_select].getQuotes-processed").removeClass('getQuotes-processed').unbind('change', triggerQuoteCallback);
+  });
+  
+  $().ajaxComplete(function (event, request, settings) {
+    if (settings.url.match(quotePathRegex)) {
+      activeRequest = null;
     }
   });
 }
 
 /**
- * Refresh line item list when a shipping method is selected.
+ * Register a callback on shipping method radio buttons, so that line items are updated when the user picks a shipping
+ * method.
  */
-function setTaxCallbacks() {
+function setLineItemCallbacks() {
   // Choosing to use click because of IE's bloody stupid bug not to
   // trigger onChange until focus is lost. Click is better than doing
   // set_line_item() and getTax() twice, I believe.
   $("#quote").find("input:radio").click(function() {
-    var i = $(this).val();
     if (window.set_line_item) {
-      var label = $(this).parent().text();
-      set_line_item("shipping", label.substr(0, label.indexOf(":")), $(this).parent().prev().val(), 1, 1);
+      var shippingMethod = $(this).parent().find('span.shipping-method').text();
+      
+      /* The theme function for shipping options puts the unformatted rate in the title for us, so we don't have to
+       * deal with currency conversion issues.
+       */
+      var shippingRate   = $(this).parent().find('span.shipping-rate').attr('title');
+      
+      if (shippingMethod && shippingRate) {
+        set_line_item("shipping", shippingMethod, shippingRate, 1, 1);
+      }
     }
   });
 }
 
 /**
- * Retrieve a list of available shipping quotes.
- *
- * @param products
- *   Pipe- and carat-delimited values string representing the current contents
- *   of the shopping cart. Products are separated by | and product data by ^.
- */
-function quoteCallback(products) {
-  var updateCallback = function (progress, status, pb) {
-    if (progress == 100) {
-      pb.stopMonitoring();
-    }
-  };
-
-  page = $("input:hidden[name*=page]").val();
-  details = new Object();
-  details["uid"] = $("input[name*=uid]").val();
-  //details["details[zone]"] = $("select[name*=delivery_zone] option:selected").val();
-  //details["details[country]"] = $("select[name*=delivery_country] option:selected").val();
-
-  $("select[name*=delivery_]").each(function(i) {
-    details["details[delivery][" + $(this).attr("name").split("delivery_")[1].replace(/]/, "") + "]"] = $(this).val();
-  });
-  $("input[name*=delivery_]").each(function(i) {
-    details["details[delivery][" + $(this).attr("name").split("delivery_")[1].replace(/]/, "") + "]"] = $(this).val();
-  });
-  $("select[name*=billing_]").each(function(i) {
-    details["details[billing][" + $(this).attr("name").split("billing_")[1].replace(/]/, "") + "]"] = $(this).val();
-  });
-  $("input[name*=billing_]").each(function(i) {
-    details["details[billing][" + $(this).attr("name").split("billing_")[1].replace(/]/, "") + "]"] = $(this).val();
-  });
+ * The callback that is notified when address field text changes, so that a request for shipping quotes can be
+ * initiated, as if the user had clicked the "Calculate shipping" button.
+ */
+function triggerQuoteCallback() {
+  var quoteButton = getQuoteButton();
+  
+  if ((quoteButton != null)) {
+    cancelActiveRequest();
+    
+    // Act like the user clicked the "Calculate shipping" button.
+    quoteButton.triggerHandler('mousedown');
+  }
+}
 
-  if (!!products) {
-    details["products"] = products;
+/**
+ * Cancels the active request for shipping quotes, if any request is active.
+ * 
+ * If a request is canceled, its associated AHAH throbber is also removed.
+ */
+function cancelActiveRequest() {
+  if (activeRequest != null) {
+    // Abort the active request
+    activeRequest.request.abort();
+    
+    // Make the throbber disappear
+    activeRequest.settings.success(' ', 'success');
+    
+    activeRequest = null;
   }
-  else {
-    products = "";
-    var i = 0;
-    while ($("input[name^='products[" + i + "]']").length) {
-      products += "|" + $("input[name^='products[" + i + "]']").filter("[name$='[nid]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[title]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[model]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[manufacturer]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[qty]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[cost]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[price]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[weight]']").val();
-      products += "^" + $("input[name^='products[" + i + "]']").filter("[name$='[data]']").val();
-      i++;
-    }
-    details["products"] = products.substr(1);
+}
+
+/**
+ * Utility function for locating and returning the "Calculate shipping" button.
+ * 
+ * @return  The "Calculate shipping" submit button in a jQuery wrapper, or null, if the button could not be located.
+ */
+function getQuoteButton() {
+  var quoteButton = $('#edit-panes-quotes-get-quotes');
+    
+  if (quoteButton.length == 0) {
+    quoteButton = null;
   }
-  var progress = new Drupal.progressBar("quoteProgress");
-  progress.setProgress(-1, Drupal.settings.uc_quote.progress_msg);
-  $("#quote").empty().append(progress.element);
-  $("#quote").addClass("solid-border");
-  // progress.startMonitoring(Drupal.settings.basePath + "?q=shipping/quote", 0);
-  $.ajax({
-    type: "POST",
-    url: Drupal.settings.ucURL.shippingQuotes,
-    data: details,
-    dataType: "json",
-    success: displayQuote
-  });
+  
+  return quoteButton;
+}
 
-  return false;
+/**
+ * Binds callbacks to the specified form element, allowing shipping quotes to be calculated if the value of the
+ * form element changes while being in focus.
+ * 
+ * @param element
+ *        The form element for which the callbacks should be bound.
+ */
+function addQuoteChangeCallbacks(element) {
+  $(element).bind('focus', markElementForChange);
+  $(element).bind('blur', unmarkElementForChange);
 }
 
 /**
- * Parse and render the returned shipping quotes.
+ * Unbinds the callbacks from the specified form element that would allow shipping quotes to be calculated if the value
+ * of the form element changed while being in focus.
+ * 
+ * @param element
+ *        The form element for which the callbacks should be unbound.
  */
-function displayQuote(data) {
-  var quoteDiv = $("#quote").empty()/* .append("<input type=\"hidden\" name=\"method-quoted\" value=\"" + details["method"] + "\" />") */;
-  var numQuotes = 0;
-  var errorFlag = true;
-  var i;
-  for (i in data) {
-    if (data[i].rate != undefined || data[i].error || data[i].notes) {
-      numQuotes++;
-    }
-  }
-  for (i in data) {
-    var item = '';
-    var label = data[i].option_label;
-    if (data[i].rate != undefined || data[i].error || data[i].notes) {
-
-      if (data[i].rate != undefined) {
-        if (numQuotes > 1 && page != 'cart') {
-          item = "<input type=\"hidden\" name=\"rate[" + i + "]\" value=\"" + data[i].rate + "\" />"
-            + "<label class=\"option\">"
-            + "<input type=\"radio\" class=\"form-radio\" name=\"quote-option\" value=\"" + i + "\" />"
-            + label + ": " + data[i].format + "</label>";
-        }
-        else {
-          item = "<input type=\"hidden\" name=\"quote-option\" value=\"" + i + "\" />"
-            + "<input type=\"hidden\" name=\"rate[" + i + "]\" value=\"" + data[i].rate + "\" />"
-            + "<label class=\"option\">" + label + ": " + data[i].format + "</label>";
-          if (page == "checkout") {
-            if (label != "" && window.set_line_item) {
-              set_line_item("shipping", label, data[i].rate, 1);
-            }
-          }
-        }
-      }
-      if (data[i].error) {
-        item += '<div class="quote-error">' + data[i].error + "</div>";
-      }
-      if (data[i].notes) {
-        item += '<div class="quote-notes">' + data[i].notes + "</div>";
-      }
-      if (data[i].rate == undefined && item.length) {
-        item = label + ': ' + item;
-      }
-      quoteDiv.append('<div class="form-item">' + item + "</div>\n");
-      Drupal.attachBehaviors(quoteDiv);
-      if (page == "checkout") {
-        // Choosing to use click because of IE's bloody stupid bug not to
-        // trigger onChange until focus is lost. Click is better than doing
-        // set_line_item() and getTax() twice, I believe.
-        quoteDiv.find("input:radio[value=" + i +"]").click(function() {
-          var i = $(this).val();
-          if (window.set_line_item) {
-            set_line_item("shipping", data[i].option_label, data[i].rate, 1, 1);
-          }
-        });
-      }
-    }
-    if (data[i].debug != undefined) {
-      quoteDiv.append("<pre>" + data[i].debug + "</pre>");
-    }
-  }
-  if (quoteDiv.find("input").length == 0) {
-    quoteDiv.append(Drupal.settings.uc_quote.err_msg);
-  }
-  else {
-    quoteDiv.find("input:radio").eq(0).click().attr("checked", "checked");
-    var quoteForm = quoteDiv.html();
-    quoteDiv.append("<input type=\"hidden\" name=\"quote-form\" value=\"" + Drupal.encodeURIComponent(quoteForm) + "\" />");
-  }
+function removeQuoteChangeCallbacks(element) {
+  $(element).unbind('focus', markElementForChange);
+  $(element).unbind('blur', unmarkElementForChange);
+}
 
-  /* if (page == "checkout") {
-    if (window.getTax) {
-      getTax();
-    }
-    else if (window.render_line_items) {
-      render_line_items();
-    }
-  } */
+/**
+ * Callback that registers a change listener on the active form element, so that a shipping quote will be calculated if
+ * its value changes.
+ */
+function markElementForChange() {
+  $(this).bind('change', triggerQuoteCallback);
 }
+
+/**
+ * Callback that unregisters the change listener on the active form element, so that a shipping quote will be not be
+ * calculated if its value changes.
+ */
+function unmarkElementForChange() {
+  $(this).unbind('change', triggerQuoteCallback);
+}
\ No newline at end of file
Index: shipping/uc_quote/uc_quote.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/shipping/uc_quote/uc_quote.css,v
retrieving revision 1.4.2.1
diff -u -r1.4.2.1 uc_quote.css
--- shipping/uc_quote/uc_quote.css	10 Feb 2009 21:58:13 -0000	1.4.2.1
+++ shipping/uc_quote/uc_quote.css	16 Jun 2010 15:49:53 -0000
@@ -29,3 +29,15 @@
   display: inline;
   width: auto;
 }
+
+#edit-get-quotes,
+#edit-panes-quotes-get-quotes {
+  margin-top: 0.2em;
+  float: none;
+}
+
+#ahah-progress-edit-get-quotes,
+#ahah-progress-edit-panes-quotes-get-quotes {
+  float: none;
+  width: 100%;
+}
\ No newline at end of file
Index: shipping/uc_quote/uc_quote.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/shipping/uc_quote/uc_quote.module,v
retrieving revision 1.5.2.29
diff -u -r1.5.2.29 uc_quote.module
--- shipping/uc_quote/uc_quote.module	23 Feb 2010 18:36:53 -0000	1.5.2.29
+++ shipping/uc_quote/uc_quote.module	16 Jun 2010 15:49:54 -0000
@@ -65,8 +65,20 @@
     'weight' => -10,
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
+  $items['cart/shipping/quote'] = array(
+    'page callback' => 'uc_quote_request_cart_quotes',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'uc_quote.pages.inc',
+  );
   $items['cart/checkout/shipping/quote'] = array(
-    'page callback' => 'uc_quote_request_quotes',
+    'page callback' => 'uc_quote_request_checkout_quotes',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'uc_quote.pages.inc',
+  );
+  $items['admin/store/orders/shipping/quote'] = array(
+    'page callback' => 'uc_quote_request_admin_quotes',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
     'file' => 'uc_quote.pages.inc',
@@ -98,6 +110,12 @@
     'uc_cart_pane_quotes' => array(
       'arguments' => array('items' => NULL),
     ),
+    'uc_quote_pane_quotes_div' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'uc_quote_method_label' => array(
+      'arguments' => array('quote' => NULL),
+    ),
   );
 }
 
@@ -209,11 +227,6 @@
     $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);
   }
-  // Add quote selection form handlers to the checkout form.
-  if ($form_id == 'uc_cart_checkout_form' && isset($form['panes']['quotes'])) {
-    $form['#validate'][] = 'uc_quote_save_choice';
-    $form['#pre_render'][] = 'uc_quote_cache_quotes';
-  }
 }
 
 /******************************************************************************
@@ -414,6 +427,8 @@
       $details[$field] = $value;
     }
   }
+
+  // FIXME: Output buffering seems unnecessary. Modules / include files should never write output.
   ob_start();
   // Load include file containing quote callback, if there is one
   if (isset($method['quote']['file'])) {
@@ -468,6 +483,8 @@
 }
 
 /**
+ * Implementation of hook_checkout_pane().
+ *
  * Define the shipping quote checkout pane.
  */
 function uc_quote_checkout_pane() {
@@ -482,6 +499,8 @@
 }
 
 /**
+ * Implementation of hook_order_pane().
+ *
  * Defines the shipping quote order pane.
  */
 function uc_quote_order_pane() {
@@ -501,39 +520,22 @@
 }
 
 /**
- * Implementation of hook_add_to_cart().
- */
-function uc_quote_add_to_cart() {
-  unset($_SESSION['quote']);
-}
-
-/**
- * Implementation of hook_update_cart_item().
- */
-function uc_quote_update_cart_item() {
-  unset($_SESSION['quote']);
-}
-
-/**
  * Implementation of hook_order().
  */
-function uc_quote_order($op, &$arg1, $arg2) {
+function uc_quote_order($op, &$order, $arg2) {
   switch ($op) {
-    case 'submit':
-      unset($_SESSION['quote']);
-    break;
     case 'save':
-      db_query("DELETE FROM {uc_order_quotes} WHERE order_id = %d", $arg1->order_id);
+      db_query("DELETE FROM {uc_order_quotes} WHERE order_id = %d", $order->order_id);
       db_query("INSERT INTO {uc_order_quotes} (order_id, 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']);
+        $order->order_id, $order->quote['method'], $order->quote['accessorials'], $order->quote['rate'], $order->quote['quote_form']);
     break;
     case 'load':
-      $quote = db_fetch_array(db_query("SELECT method, accessorials, rate, quote_form FROM {uc_order_quotes} WHERE order_id = %d", $arg1->order_id));
-      $arg1->quote = $quote;
-      $arg1->quote['accessorials'] = strval($quote['accessorials']);
+      $quote = db_fetch_array(db_query("SELECT method, accessorials, rate, quote_form FROM {uc_order_quotes} WHERE order_id = %d", $order->order_id));
+      $order->quote = $quote;
+      $order->quote['accessorials'] = strval($quote['accessorials']);
     break;
     case 'delete':
-      db_query("DELETE FROM {uc_order_quotes} WHERE order_id = %d", $arg1->order_id);
+      db_query("DELETE FROM {uc_order_quotes} WHERE order_id = %d", $order->order_id);
     break;
   }
 }
@@ -650,94 +652,85 @@
 }
 
 /**
- * Cart pane callback.
+ * Form builder for the shipping quote cart pane.
  *
  * @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 = uc_quote_get_shipping_types();
-  $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'];
-    }
-  }
+function uc_cart_pane_quotes($form_state, $items) {
   $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['get_quotes'] = array(
+    '#type' => 'submit',
+    '#value' => t('Calculate shipping'),
+    '#submit' => array('uc_quote_cart_get_quotes_submit'),
+    '#ahah' => array(
+      'path' => 'cart/shipping/quote',
+      'wrapper' => 'quote',
+      'method' => 'replace',
+      'effect' => 'fade',
+      'progress' => array(
+        'type' => 'bar',
+        'message' => t('Receiving quotes...'),
+      ),
+    ),
   );
-  $form['quote'] = array('#type' => 'markup',
-    '#value' => '<div id="quote"></div>',
+
+  $form['quote'] = array(
+    /* NOTE: Because there are no quotes during the inital render, the theme function must provide us with HTML
+     *       output at all times for the #prefix and #suffix we have defined here to be rendered, unlike the Poll
+     *       module which has at least a single option to render in its AHAH-populated DIV at all times.
+     */
+    '#prefix'   => '<div id="quote">',
+    '#suffix'   => '</div>',
+    '#theme'    => 'uc_quote_pane_quotes_div',
   );
 
-  drupal_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),
-    ),
-    'ucURL' => array(
-      'shippingQuotes' => url('cart/checkout/shipping/quote'),
-    ),
-  ), 'setting');
-  drupal_add_js('misc/progress.js');
-  drupal_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.
-  drupal_add_js('$(function() {
-    setQuoteCallbacks("'. drupal_urlencode($prod_string) .'");
-    $("#uc-cart-pane-quotes").submit(function() {
-      quoteCallback("'. drupal_urlencode($prod_string) .'");
-      return false;
-    });
-  })', 'inline');
+  if (isset($form_state['storage']['quotes'])) {
+    uc_quote_add_quote_result_form($form['quote'], $form_state, FALSE);
+  }
 
   return $form;
 }
 
 /**
+ * FAPI submit callback for the "Calculate shipping" button.
+ *
+ * This calculates shipping estimates for the user, based on a hypothetical order being delivered to the specified city,
+ * state, country, and zip code, and consisting of shipments of all the products in the user's cart.
+ *
+ * @param $form
+ *        The form array.
+ *
+ * @param $form_state
+ *        A reference to the form state array.
+ *
+ * @see uc_cart_pane_quotes()
+ */
+function uc_quote_cart_get_quotes_submit($form, &$form_state) {
+  $cart = uc_cart_get_contents();
+
+  if (!empty($cart)) {
+    // We don't actually have an order yet, so we'll pretend, just like when we were kids.
+    $fake_order = new stdClass();
+
+    $fake_order->products = $cart;
+
+    $fake_order->delivery_country = $form_state['values']['delivery_country'];
+    $fake_order->delivery_zone = $form_state['values']['delivery_zone'];
+    $fake_order->delivery_postal_code = $form_state['values']['delivery_postal_code'];
+
+    _uc_quote_assemble_quotes_for_form($fake_order, $form_state);
+  }
+}
+
+/**
  * Display the formatted quote cart pane.
  *
  * @ingroup themeable
@@ -748,7 +741,7 @@
   $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['get_quotes']);
   $output .= drupal_render($form);
   $output .= '</div>';
 
@@ -759,173 +752,103 @@
  * 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
+ * of products in the cart. The "Calculate Shipping" 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) {
+function uc_checkout_pane_quotes($op, &$order, $unused, &$form = NULL, &$form_state = NULL) {
   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), FALSE);
+      $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),
+          FALSE);
 
-      // 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'),
+      $contents['get_quotes'] = array(
+        '#type' => 'submit',
+        '#value' => t('Calculate shipping'),
+        '#submit' => array('uc_quote_get_quotes_submit'),
         '#weight' => 0,
+        '#ahah' => array(
+          'path' => 'cart/checkout/shipping/quote',
+          'wrapper' => 'quote',
+          'method' => 'replace',
+          'effect' => 'fade',
+          'progress' => array(
+            'type' => 'bar',
+            'message' => t('Receiving quotes...'),
+          ),
+        ),
       );
 
-      drupal_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),
-        ),
-        'ucURL' => array(
-          'shippingQuotes' => url('cart/checkout/shipping/quote'),
-        ),
-      ), 'setting');
-      drupal_add_js('misc/progress.js');
-      drupal_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);
+      $contents['quote'] = array(
+        /* NOTE: Because there are no quotes during the inital render, the theme function must provide us with HTML
+         *       output at all times for the #prefix and #suffix we have defined here to be rendered, unlike the Poll
+         *       module which has at least a single option to render in its AHAH-populated DIV at all times.
+         */
+        '#prefix'   => '<div id="quote">',
+        '#suffix'   => '</div>',
+        '#theme'    => 'uc_quote_pane_quotes_div',
+        '#parents'  => array('panes', 'quotes'),
+      );
+
+      /* BUGBUG: This feels klunky, but it's the only good way I can think of to ensure we have shipping quotes
+       *         already prepared if the user goes back to checkout from the review order page.
+       */
+      if (empty($form_state['storage']['quotes']) && isset($_SESSION['cart_order'])) {
+        $order = uc_order_load($_SESSION['cart_order']);
+
+        $form_state['storage']['order'] = $order;
+
+        _uc_quote_assemble_quotes_for_form($order, $form_state);
       }
-      $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.
-      drupal_add_js('
-        Drupal.behaviors.getQuotes = function (context) {
-          setQuoteCallbacks("'. drupal_urlencode($prod_string) .'", context);
-        };
-        $(function() {
-        var quoteButton = $("input:radio[name=quote-option]").click(function() {
-          var quoteButton = $(this);
-          var label = quoteButton.parent("label").text().split(":", 2)[0];
-          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=\"" + Drupal.encodeURIComponent(quoteDiv.html()) + "\" />");
-        }
-      });', 'inline');
+
+      // Workaround for AHAH bashing the form action (#591696)
+      $form['#action'] = url('cart/checkout');
+
+      uc_quote_add_quote_result_form($contents['quote'], $form_state, TRUE);
 
       return array('description' => $description, 'contents' => $contents);
 
     case 'process':
-      if (!isset($_POST['quote-option'])) {
+      // Shortcut if not heading to the review step.
+      if ($form_state['clicked_button']['#value'] != $form['continue']['#value']) {
+        return TRUE;
+      }
+
+      if (empty($form_state['values']['panes']['quotes']['quote-option'])) {
         if (variable_get('uc_quote_require_quote', TRUE)) {
-          drupal_set_message(t('You must select a shipping option before continuing.'), 'error');
+          form_set_error('panes][quotes][quote-option', t('You must select a shipping option before continuing.'));
           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();
-
-      $arguments = array(
-        'order' => array(
-          '#entity' => 'uc_order',
-          '#title' => t('Order'),
-          '#data' => $arg1,
-        ),
-        'method' => array(
-          '#entity' => 'quote_method',
-          '#title' => t('Quote method'),
-          '#data' => $method,
-        ),
-        'account' => array(
-          '#entity' => 'user',
-          '#title' => t('User'),
-          '#data' => $user,
-        ),
-      );
-
-      $predicates = ca_load_trigger_predicates('get_quote_from_'. $method['id']);
-      $predicate = array_shift($predicates);
-      if ($predicate && ca_evaluate_conditions($predicate, $arguments)) {
-        $quote_data = uc_quote_action_get_quote($arg1, $method, $user);
-      }
-
-      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 _uc_quote_apply_quote_to_order($form_state['values']['panes']['quotes']['quote-option'], $order);
       }
-    return TRUE;
 
     case 'review':
       $context = array(
         'revision' => 'themed',
         'type' => 'line_item',
-        'subject' => array('order' => $arg1),
+        'subject' => array('order' => $order),
       );
-      $result = db_query("SELECT * FROM {uc_order_line_items} WHERE order_id = %d AND type = '%s'", $arg1->order_id, 'shipping');
+
+      $result = db_query("SELECT * FROM {uc_order_line_items} WHERE order_id = %d AND type = '%s'", $order->order_id, 'shipping');
+
       if ($line_item = db_fetch_array($result)) {
         $context['subject']['line_item'] = $line_item;
         $review[] = array('title' => $line_item['title'], 'data' => uc_price($line_item['amount'], $context));
       }
+
       return $review;
   }
 }
@@ -933,195 +856,544 @@
 /**
  * Shipping quote order pane callback.
  */
-function uc_order_pane_quotes($op, $arg1) {
+function uc_order_pane_quotes($op, $order, &$form_state = NULL) {
   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']['quote'] = array(
+        /* NOTE: Because there are no quotes during the inital render, the theme function must provide us with HTML
+         *       output at all times for the #prefix and #suffix we have defined here to be rendered, unlike the Poll
+         *       module which has at least a single option to render in its AHAH-populated DIV at all times.
+         */
+        '#prefix'   => '<div id="quote">',
+        '#suffix'   => '</div>',
+        '#theme'    => 'uc_quote_pane_quotes_div',
+        '#parents'  => array('panes', 'quotes'),
       );
-      $form['quotes']['add_quote'] = array(
+
+      $form['quotes']['get_quotes'] = array(
         '#type' => 'submit',
-        '#value' => t('Apply to order'),
-        '#attributes' => array('class' => 'save-button'),
-        '#disabled' => TRUE,
+        '#value' => t('Calculate shipping'),
+        '#submit' => array('uc_quote_get_quotes_submit'),
+        '#weight' => 0,
+        '#ahah' => array(
+          'path' => 'admin/store/orders/shipping/quote',
+          'wrapper' => 'quote',
+          'method' => 'replace',
+          'effect' => 'fade',
+          'progress' => array(
+            'type' => 'bar',
+            'message' => t('Receiving quotes...'),
+          ),
+        ),
       );
 
-      $form['quotes']['quote'] = array(
-        '#type' => 'markup',
-        '#value' => '<div id="quote"></div>',
-      );
+      $form_state['storage']['order'] = $order;
 
-      drupal_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),
-        ),
-        'ucURL' => array(
-          'shippingQuotes' => url('cart/checkout/shipping/quote'),
-        ),
-      ), 'setting');
+      uc_quote_add_quote_result_form($form['quotes']['quote'], $form_state, TRUE);
 
-      drupal_add_js('misc/progress.js');
-      drupal_add_js(drupal_get_path('module', 'uc_quote') .'/uc_quote.js');
+      if (!empty($form_state['storage']['quotes'])) {
+        $form['quotes']['quote']['add_quote'] = array(
+          '#type' => 'submit',
+          '#value' => t('Apply to order'),
+          '#attributes' => array('class' => 'save-button'),
+          '#validate' => array('uc_quote_order_apply_quote_validate'),
+          '#submit' => array('uc_quote_order_apply_quote_submit'),
+        );
+      }
 
-      $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.
-      drupal_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=\"" + Drupal.encodeURIComponent(quoteDiv.html()) + "\" />");
-        }
-      })', 'inline');
       return $form;
+
     case 'edit-theme':
-      return drupal_render($arg1['quotes']);
+      return drupal_render($order['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']);
+      if (!empty($form_state['storage'])) {
+        // Needed to prevent form rebuild. Curiously, $form_state['rebuild'] = FALSE isn't enough.
+        unset($form_state['storage']);
       }
-      $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));
-          $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();
-
-          $predicate = ca_load_trigger_predicates('get_quote_from_'. $method['id']);
-          $arguments = array(
-            'order' => array(
-              '#entity' => 'uc_order',
-              '#title' => t('Order'),
-              '#data' => $arg1,
-            ),
-            'method' => array(
-              '#entity' => 'quote_method',
-              '#title' => t('Quote method'),
-              '#data' => $method,
-            ),
-            'account' => array(
-              '#entity' => 'user',
-              '#title' => t('User'),
-              '#data' => $user,
-            ),
-          );
-          if (ca_evaluate_conditions($predicate, $arguments)) {
-            $quote_data = uc_quote_action_get_quote($order, $method, $user);
-          }
+/**
+ * FAPI validation callback for the "Apply to order" button on the order edit page.
+ *
+ * This will check to ensure that the user has picked a shipping option before attempting to apply the change to the
+ * order.
+ *
+ * @param $form
+ *        The form array.
+ *
+ * @param $form_state
+ *        A reference to the form state array.
+ *
+ * @see uc_order_pane_quotes()
+ */
+function uc_quote_order_apply_quote_validate($form, &$form_state) {
+  if (empty($form_state['values']['quote-option'])) {
+    form_set_error('quote-option', t('You must select a shipping option before continuing.'), 'error');
+  }
+}
+
+/**
+ * FAPI submit callback for the "Apply to order" button on the order edit page.
+ *
+ * This will apply the shipping change and display a success message to the user.
+ *
+ * @param $form
+ *        The form array.
+ *
+ * @param $form_state
+ *        A reference to the form state array.
+ *
+ * @see uc_order_pane_quotes()
+ */
+function uc_quote_order_apply_quote_submit($form, &$form_state) {
+  if (isset($form_state['storage']['order'])) {
+    $order = $form_state['storage']['order'];
+
+    if (_uc_quote_apply_quote_to_order($form_state['values']['quote-option'], $order)) {
+      uc_order_save($order);
+
+      // Needed to prevent form rebuild. Curiously, $form_state['rebuild'] = FALSE isn't enough.
+      unset($form_state['storage']);
+    }
+  }
+}
 
-          //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;
+/**
+ * FAPI submit callback for the "Calculate shipping" buttons on the check-out and order edit pages.
+ *
+ * This calculates shipping quotes based on details from the current order, which is expected to be stored in
+ * <code>$form_state['storage']['order']</code>.
+ *
+ * @param $form
+ *        The form array.
+ *
+ * @param $form_state
+ *        A reference to the form state array.
+ */
+function uc_quote_get_quotes_submit($form, &$form_state) {
+  if (!empty($form_state['storage']['order'])) {
+    $order = $form_state['storage']['order'];
+
+    _uc_quote_assemble_quotes_for_form($order, $form_state);
+  }
+}
+
+/**
+ * Builder for the form element containing shipping quotes.
+ *
+ * This function is called by other form builders to render the shipping quotes stored in
+ * $form_state['storage']['quotes'] (if any) as form elements under the provided parent
+ * form element. If <code>$edit_mode</code> is <code>TRUE</code>, the quotes will be
+ * rendered as radio buttons. If <code>$edit_mode</code> is <code>TRUE</code>, the quotes
+ * will be rendered as text.
+ *
+ * @param $form_element
+ *        A reference to the parent form element that should be populated with a child form element for each
+ *        shipping option.
+ *
+ * @param $form_state
+ *        The form state array.
+ *
+ * @param $edit_mode
+ *        <code>TRUE</code> if the form should be rendered as radio buttons, to allow the to pick a shipping option;
+ *        <code>FALSE</code> if the form should be rendered as read-only text, to display the available options to
+ *        the user, but not allow them to choose one.
+ */
+function uc_quote_add_quote_result_form(&$form_element, $form_state, $edit_mode) {
+  if (form_get_errors() == NULL) {
+    if (!empty($form_state['storage']['quotes'])) {
+      $options = array();
+
+      foreach ($form_state['storage']['quotes'] as $key => $quote) {
+        if (isset($quote['rate'])) {
+          $options[$key] = theme('uc_quote_method_label', $quote);
+        }
+
+        _uc_quote_add_quote_messages($key, $quote, $form_element);
+      }
+
+      if (count($options) > 1) {
+        $order = $form_state['storage']['order'];
+
+        // Edit mode allows the user to choose a shipping option
+        if ($edit_mode) {
+          // For checkout pane
+          if (!empty($form_state['values']['panes']['quotes']['quote-option'])) {
+            $default = $form_state['values']['panes']['quotes']['quote-option'];
           }
-          $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']
-            );
+          // For order management pane
+          elseif (!empty($form_state['values']['quote-option'])) {
+            $default = $form_state['values']['quote-option'];
+          }
+          elseif (!empty($order->quote['method'])) {
+            $default = $order->quote['method'] . '---' .
+              ($order->quote['accessorials'] ? $order->quote['accessorials'] : 0);
           }
           else {
-            uc_order_line_item_add($order->order_id, 'shipping',
-              $label,
-              $order->quote['rate']
+            $default = NULL;
+          }
+
+          $form_element['quote-option'] = array(
+            '#type'     => 'radios',
+            '#options'  => $options,
+            '#default_value' => $default,
+          );
+        }
+        // When not in edit mode, render options read-only.
+        else {
+          $form_element['quote-option'] = array();
+
+          foreach ($options as $key => $option) {
+            $form_element['quote-option'][$key] = array(
+              '#value'  => $option,
+              '#type'   => 'item',
             );
           }
         }
       }
-    break;
+      elseif (count($options) == 1) {
+        $form_element['quote-option'] = array(
+          '#type'   => 'hidden',
+          '#value'  => key($options),
+        );
+
+        // If there's only one shipping quote, there's no use in having the user choose it.
+        // We still have to display the option to the user, even though they aren't choosing it.
+        $form_element['quote-option']['markup'] = array(
+          '#type' => 'markup',
+          '#value' => theme('uc_quote_method_label', $quote),
+        );
+      }
+    }
+    // No options -- must be an error
+    elseif ($form_state['submitted']) {
+      drupal_set_message(
+        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),
+        'error');
+
+      unset($form_element['#theme']);
+    }
+  }
+}
+
+/**
+ * Internal function for rendering the debug, error, and notes messages associated with a quote as form
+ * elements.
+ *
+ * @param $quote_key
+ *        The key for referring to to the quote internally.
+ *
+ * @param $quote
+ *        The shipping quote for which messages are being rendered.
+ *
+ * @param $form_element
+ *        A reference to the form element that is the parent of all shipping option elements. The messages
+ *        will be added as children of this element.
+ */
+function _uc_quote_add_quote_messages($quote_key, $quote, &$form_element) {
+  // BUGBUG: "notes" isn't referenced anywhere in the docs or in other code, but the old code handled it...
+  //         Should we continue handling it?
+  foreach (array('debug', 'error', 'notes') as $messageType) {
+    if (isset($quote[$messageType])) {
+      if (is_array($quote[$messageType])) {
+        // BUGBUG: Is this the correct way to handle an array of messages?
+        $message = implode('<br />', $quote[$messageType]);
+      }
+      else {
+        $message = $quote[$messageType];
+      }
+
+      if ($messageType == 'debug') {
+        $message = '<pre>' . $message . '</pre>';
+      }
+
+      // Add messages.
+      $form_element[$quote_key][$messageType] = array(
+        '#type' => 'markup',
+        '#value' => $message,
+        '#prefix' => '<div class="messages ' . $messageType . ' quote-message">',
+        '#suffix' => '</div>',
+      );
+    }
   }
 }
 
 /**
- * Validate handler added to uc_cart_checkout_form().
+ * Theme function to render the returned shipping quotes.
+ *
+ * @param $form
+ *        The form element which contains the shipping quote information that is to be rendered.
+ *
+ * @return The form element rendered as an HTML string.
  *
- * Save the choice of shipping method in the customer's session.
+ * @ingroup themeable
  */
-function uc_quote_save_choice($form, &$form_state) {
-  $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 theme_uc_quote_pane_quotes_div($form) {
+  // Display each radio button next to associated data.
+  foreach (element_children($form) as $key) {
+    if (isset($form['quote-option'][$key])) {
+      $output .= drupal_render($form['quote-option'][$key]);
+    }
+
+    $output .= drupal_render($form[$key]);
+  }
+
+  $output .= drupal_render($form);
+
+  if ($output == '') {
+    // NOTE: We must have *some* output for the #prefix and #suffix to render.
+    $output = '&nbsp;';
+  }
+  else {
+    $output = '<div class="solid-border">' . $output . '</div>';
+  }
+
+  drupal_add_js(drupal_get_path('module', 'uc_quote') . '/uc_quote.js');
+
+  return $output;
+}
+
+/**
+ * Theme function to format a shipping quote into an option to display for the user to choose during check-out.
+ *
+ * NOTE: This theme function is not invoked for quotes that return errors (i.e. when $quote['error'] is set).
+ *
+ * @param $quote
+ *        An associative array containing the shipping quote information, including:
+ *        - 'rate': The shipping quote/rate, as a number.
+ *        - 'format': The shipping quote/rate, formatted as a currency string.
+ *        - 'option_label': The label text/mark-up that describes the quote. This will typically include an image
+ *          and the name of the accessorial.
+ *        - 'debug': Debug information about the shipping quote, if the logged-in user is an administrator and
+ *          debugging is enabled for shipping quotes.
+ *
+ * @return The shipping quote rendered as an HTML string.
+ *
+ * @see hook_shipping_method()
+ *
+ * @ingroup themeable
+ */
+function theme_uc_quote_method_label($quote) {
+  $output = '<span class="shipping-method-option">';
+
+  /* NOTE: The amount in the "title" attribute is referenced by uc_quote.js, to avoid having to parse the formatted
+   *       currency value.
+   */
+  // BUGBUG: Shouldn't we be using check_markup() or similar here on the mark-up from $quote?
+  $output .= sprintf(
+            '  <span class="shipping-method">%s</span>: <span class="shipping-rate" title="%f">%s</span>',
+            $quote['option_label'],
+            $quote['rate'],
+            $quote['format']);
+
+  $output .= '</span>';
+
+  return $output;
 }
 
 /**
- * Pre-render callback added to uc_cart_checkout_form().
+ * Internal utility function for applying a particular shipping option to an order, making it the method by
+ * which the order will be shipped.
+ *
+ * @param $quote_option
+ *        The value for the shipping option to apply to the order, in the format "[method]---[accessorial]" as
+ *        generated for the quote form in <code>uc_quote_add_quote_result_form()</code>.
  *
- * Render the shipping quotes without an asynchronous call to create them if a
- * choice had been cached in the session.
+ * @param $order
+ *        A reference to the order instance for which shipping will be set or updated.
  */
-function uc_quote_cache_quotes($form) {
-  if ($form['#id'] == 'uc-cart-checkout-form') {
-    if (isset($_SESSION['quote']) && isset($_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']];
+function _uc_quote_apply_quote_to_order($quote_option, &$order) {
+  $option_details = explode('---', $quote_option);
 
-        drupal_add_js('if (Drupal.jsEnabled) { $(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');
+  $methodName   = $option_details[0];
+  $accessorials = $option_details[1];
+
+  $order->quote['method'] = $methodName;
+  $order->quote['accessorials'] = $accessorials;
+
+  $quotes = _uc_quote_assemble_quotes($order, array($methodName));
+
+  if (empty($quotes) || !isset($quotes[$methodName][$accessorials]['rate'])) {
+    drupal_set_message(t('Invalid option selected. Recalculate shipping quotes to continue.'), 'error');
+    return FALSE;
+  }
+
+  $quote = $quotes[$methodName][$accessorials];
+
+  $label = trim(strip_tags($quote['option_label']));
+  $quote_rate = $quote['rate'];
+
+  $order->quote['rate'] = $quote_rate;
+
+  $result =
+    db_query(
+      "SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = %d AND type = 'shipping'",
+      $order->order_id);
+
+  if ($lid = db_result($result)) {
+    uc_order_update_line_item($lid, $label, $quote_rate);
+  }
+  else {
+    uc_order_line_item_add($order->order_id, 'shipping', $label, $quote_rate);
+  }
+
+  return TRUE;
+}
+
+/**
+ * Internal function for calling _uc_quote_assemble_quotes() and post-processing its output into a format
+ * suitable for use by a form builder function.
+ *
+ * The resulting quote information is then stored in <code>$form_state['storage']['quotes']</code>.
+ *
+ * @param $order
+ *        The order instance for which shipping quote information is to be generated.
+ *
+ * @param $form_state
+ *        A reference to the form state array, which will be populated with the shipping quote information.
+ */
+function _uc_quote_assemble_quotes_for_form($order, &$form_state) {
+  $quotes = _uc_quote_assemble_quotes($order);
+
+  $form_quotes = array();
+
+  foreach ($quotes as $method_id => $options) {
+    foreach ($options as $accessorial => $data) {
+      $form_quotes[$method_id . '---' . $accessorial] = $data;
     }
-    else {
-      $form['panes']['quotes']['quote'] = array('#type' => 'markup',
-        '#value' => '<div id="quote"></div>',
-        '#weight' => 1,
-      );
+  }
+
+  $form_state['storage']['quotes'] = $form_quotes;
+}
+
+/**
+ * Pull the get_quote_from_* triggers and assemble their returned data.
+ */
+function _uc_quote_assemble_quotes($order, $methodNames = array()) {
+  global $user;
+
+  $account = user_load($order->uid);
+
+  if (!$account) {
+    $account = $user;
+  }
+
+  $shipping_types = array();
+
+  foreach ($order->products as &$product) {
+    $shipping_types[] =  uc_product_get_shipping_type($product);
+
+    // Fully load the product info, so we can get the info needed for shipping,
+    // including package quantity and dimensions.
+    $product_node = node_load($product->nid);
+
+    foreach ((array)$product_node as $field => $value) {
+      if (!isset($product->$field)) {
+        $product->$field = $value;
+      }
     }
   }
-  return $form;
+
+  if (empty($methodNames)) {
+    $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 = 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]);
+      }
+    }
+  }
+  else {
+    $all_methods = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled');
+
+    $methods = array();
+
+    foreach ($methodNames as $methodName) {
+      if (isset($all_methods[$methodName])) {
+        $methods[$methodName] = $all_methods[$methodName];
+      }
+    }
+  }
+
+  $context = array(
+    'revision' => 'formatted',
+    'type' => 'line_item',
+    'subject' => array(
+      'order' => $order,
+    ),
+  );
+
+  //drupal_set_message('<pre>'. print_r($products, TRUE) .'</pre>');
+  $quote_data = array();
+  $arguments = array(
+    'order' => array(
+      '#entity' => 'uc_order',
+      '#title' => t('Order'),
+      '#data' => $order,
+    ),
+    'method' => array(
+      '#entity' => 'quote_method',
+      '#title' => t('Quote method'),
+      // #data => each $method in the following foreach() loop;
+    ),
+    'account' => array(
+      '#entity' => 'user',
+      '#title' => t('User'),
+      '#data' => $account,
+    ),
+  );
+
+  foreach ($methods as $method) {
+    $arguments['method']['#data'] = $method;
+    $predicates = ca_load_trigger_predicates('get_quote_from_'. $method['id']);
+    $predicate = array_shift($predicates);
+
+    if ($predicate && ca_evaluate_conditions($predicate, $arguments)) {
+      $data = uc_quote_action_get_quote($order, $method, $user);
+
+      foreach ($data as &$quote) {
+        if (isset($quote['rate'])) {
+          $context['subject']['line_item'] = array(
+            'type' => 'shipping',
+            'name' => $quote['option_label'],
+            'amount' => $quote['rate'],
+            'weight' => 1,
+          );
+
+          $quote['format'] = uc_price($quote['rate'], $context);
+        }
+      }
+
+      $quote_data[$method['id']] = $data;
+    }
+  }
+
+  return $quote_data;
 }
 
 /**
@@ -1228,4 +1500,4 @@
     }
   }
   return $types;
-}
+}
\ No newline at end of file
Index: shipping/uc_quote/uc_quote.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/shipping/uc_quote/uc_quote.pages.inc,v
retrieving revision 1.1.2.8
diff -u -r1.1.2.8 uc_quote.pages.inc
--- shipping/uc_quote/uc_quote.pages.inc	21 Sep 2009 14:34:47 -0000	1.1.2.8
+++ shipping/uc_quote/uc_quote.pages.inc	16 Jun 2010 15:49:54 -0000
@@ -3,160 +3,88 @@
 
 /**
  * @file
- * Menu callbacks for shipping quotes requested through AJAX.
+ * Menu callbacks for shipping quotes requested through AHAH.
  */
 
 /**
- * Callback to return the shipping quote(s) of the appropriate quoting method(s).
+ * AHAH callback for the shipping quote request from within the shopping cart page.
  */
-function uc_quote_request_quotes() {
+function uc_quote_request_cart_quotes() {
+  _uc_quote_request_quotes_ahah_init($form_id, $form, $form_state, $form_build_id);
+  _uc_quote_request_quotes_ahah_complete(array($form['quote'], $form['disclaimer']));
+}
 
-  /* 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_json($return_quotes);
+/**
+ * AHAH callback for the shipping quote request during check-out.
+ */
+function uc_quote_request_checkout_quotes() {
+  // FIXME: This unnecessarily couples this callback to the location of the form.
+  module_load_include('inc', 'uc_cart', 'uc_cart.pages');
+
+  _uc_quote_request_quotes_ahah_init($form_id, $form, $form_state);
+  _uc_quote_request_quotes_ahah_complete(array($form['panes']['quotes']['quote']));
 }
 
 /**
- * Pull the get_quote_from_* triggers and assemble their returned data.
+ * AHAH callback for the shipping quote request from within the shopping cart page.
  */
-function _uc_quote_assemble_quotes($order) {
-  global $user;
-  $account = user_load($order->uid);
-  if (!$account) {
-    $account = $user;
-  }
+function uc_quote_request_admin_quotes() {
+  // FIXME: This unnecessarily couples this callback to the location of the form.
+  module_load_include('inc', 'uc_order', 'uc_order.admin');
 
-  $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 = 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 = 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]);
-    }
-  }
+  _uc_quote_request_quotes_ahah_init($form_id, $form, $form_state);
+  _uc_quote_request_quotes_ahah_complete(array($form['quotes']['quote'], $form['quotes']['add_quote']));
+}
 
-  $context = array(
-    'revision' => 'formatted',
-    'type' => 'line_item',
-    'subject' => array(
-      'order' => $order,
-    ),
-  );
-
-  //drupal_set_message('<pre>'. print_r($products, TRUE) .'</pre>');
-  $quote_data = array();
-  $arguments = array(
-    'order' => array(
-      '#entity' => 'uc_order',
-      '#title' => t('Order'),
-      '#data' => $order,
-    ),
-    'method' => array(
-      '#entity' => 'quote_method',
-      '#title' => t('Quote method'),
-      // #data => each $method in the following foreach() loop;
-    ),
-    'account' => array(
-      '#entity' => 'user',
-      '#title' => t('User'),
-      '#data' => $account,
-    ),
-  );
-  foreach ($methods as $method) {
-    $arguments['method']['#data'] = $method;
-    $predicates = ca_load_trigger_predicates('get_quote_from_'. $method['id']);
-    $predicate = array_shift($predicates);
-    if ($predicate && ca_evaluate_conditions($predicate, $arguments)) {
-      $data = uc_quote_action_get_quote($order, $method, $user);
-
-      foreach ($data as &$quote) {
-        if (isset($quote['rate'])) {
-          $context['subject']['line_item'] = array(
-            'type' => 'shipping',
-            'name' => $quote['option_label'],
-            'amount' => $quote['rate'],
-            'weight' => 1,
-          );
-
-          $quote['format'] = uc_price($quote['rate'], $context);
-        }
-      }
-      $quote_data[$method['id']] = $data;
-    }
-  }
-  return $quote_data;
+/**
+ * Internal utility function for performing all of the form loading and processing tasks that are
+ * common to all AHAH callbacks.
+ *
+ * There's a rumor that this type of functionality will already be integrated into Drupal 7.
+ *
+ * @param $form_id
+ *        A reference to the string that will be populated with the internal identifier for the form that is processing
+ *        the AHAH callback.
+ *
+ * @param $form
+ *        A reference to the array that will be populated with the contents of the form that is processing
+ *        the AHAH callback.
+ *
+ * @param $form_state
+ *        A reference to the array that will be populated with the contents of the form state for the form that
+ *        is processing the AHAH callback.
+ */
+function _uc_quote_request_quotes_ahah_init(&$form_id, &$form, &$form_state) {
+  $form_state = array('storage' => NULL, 'submitted' => FALSE);
+  $form_build_id = $_POST['form_build_id'];
+
+  $form = form_get_cache($form_build_id, $form_state);
+
+  $args = $form['#parameters'];
+  $form_id = array_shift($args);
+
+  $form_state['post'] = $form['#post'] = $_POST;
+  $form['#programmed'] = $form['#redirect'] = FALSE;
+
+  drupal_process_form($form_id, $form, $form_state);
+
+  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
 }
 
+/**
+ * Internal utility function for completing an AHAH request by rendering the provided form elements out in a JSON
+ * response.
+ *
+ * @param $render_elements
+ *        A nested array of the form elements to be rendered out in a JSON response that is sent back to the client.
+ */
+function _uc_quote_request_quotes_ahah_complete(array $render_elements) {
+  $output = theme('status_messages');
+
+  foreach ($render_elements as $element) {
+    $output .= drupal_render($element);
+  }
+
+  // Final rendering callback.
+  drupal_json(array('status' => TRUE, 'data' => $output));
+}
\ No newline at end of file
Index: payment/uc_paypal/uc_paypal.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/payment/uc_paypal/uc_paypal.pages.inc,v
retrieving revision 1.1.2.11
diff -u -r1.1.2.11 uc_paypal.pages.inc
--- payment/uc_paypal/uc_paypal.pages.inc	18 Nov 2009 21:08:06 -0000	1.1.2.11
+++ payment/uc_paypal/uc_paypal.pages.inc	16 Jun 2010 15:49:53 -0000
@@ -232,21 +232,23 @@
 // Returns the form for the custom Review Payment screen for Express Checkout.
 function uc_paypal_ec_review_form($form_state, $order) {
   if (module_exists('uc_quote') && variable_get('uc_paypal_ec_review_shipping', TRUE) && uc_order_is_shippable($order)) {
-    $result = uc_checkout_pane_quotes('view', $order, NULL);
+    $unused = array();
+
+    $result = uc_checkout_pane_quotes('view', $order, NULL, $unused, $form_state);
     $form['panes']['quotes'] = array(
       '#type' => 'fieldset',
       '#title' => t('Calculate shipping'),
       '#collapsible' => FALSE,
     );
     $form['panes']['quotes'] += $result['contents'];
-    $form['panes']['quotes']['quote_button']['#value'] = t('Click to refresh shipping options');
+    $form['panes']['quotes']['get_quotes']['#value'] = t('Click to refresh shipping options');
 
     $form['panes']['quotes']['quote_div'] = array(
       '#value' => '<div id="quote"></div>',
     );
 
     // small hack; automatically looks for shipping quotes on page load
-    drupal_add_js("\$(document).ready(function() { \$(' #edit-quote-button').click(); });", 'inline');
+    drupal_add_js("\$(document).ready(function() { \$(' #edit-get-quote').click(); });", 'inline');
 
     // Fake the checkout form delivery information.
     $form['delivery_first_name'] = array('#type' => 'hidden', '#value' => $order->delivery_first_name);
Index: uc_cart/uc_cart.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_cart/uc_cart.pages.inc,v
retrieving revision 1.1.2.21
diff -u -r1.1.2.21 uc_cart.pages.inc
--- uc_cart/uc_cart.pages.inc	24 Feb 2010 04:42:09 -0000	1.1.2.21
+++ uc_cart/uc_cart.pages.inc	16 Jun 2010 15:49:54 -0000
@@ -122,7 +122,7 @@
  *   uc_cart_checkout_form_review()
  *   uc_cart_checkout_review()
  */
-function uc_cart_checkout_form() {
+function uc_cart_checkout_form(&$form_state) {
   global $user;
 
   // Cancel an order when a customer clicks the 'Cancel' button.
@@ -174,7 +174,11 @@
         }
       }
 
-      $return = $pane['callback']('view', $order, NULL);
+      /* FIXME: In a future version, it should be sufficient to just pass the operation, $order, $form, and $form_state
+       *        to the checkout pane callback, but for now we don't want to break the API for existing modules, so it's
+       *        better to pass superfluous data.
+       */
+      $return = $pane['callback']('view', $order, NULL, $form, $form_state);
 
       // Add the pane if any display data is returned from the callback.
       if (is_array($return) && (!empty($return['description']) || !empty($return['contents']))) {
@@ -317,7 +321,13 @@
   $_SESSION['checkout_valid'] = TRUE;
   foreach (element_children($form_state['values']['panes']) as $pane_id) {
     $func = _checkout_pane_data($pane_id, 'callback');
-    $isvalid = $func('process', $order, $form_state['values']['panes'][$pane_id]);
+
+    /* FIXME: In a future version, it should be sufficient to just pass the operation, $order, $form, and $form_state
+     *        to the checkout pane callback, but for now we don't want to break the API for existing modules, so it's
+     *        better to pass superfluous data.
+     */
+    $isvalid = $func('process', $order, $form_state['values']['panes'][$pane_id], $form, $form_state);
+
     if ($isvalid === FALSE) {
       $_SESSION['expanded_panes'][] = $pane_id;
       $_SESSION['checkout_valid'] = FALSE;
@@ -327,6 +337,8 @@
   $order->line_items = uc_order_load_line_items($order, TRUE);
 
   uc_order_save($order);
+
+  $form_state['storage']['order'] = $order;
 }
 
 /**
@@ -344,6 +356,9 @@
   unset($_SESSION['checkout_valid']);
 
   $form_state['redirect'] = $url;
+
+  // Needed to prevent form rebuild. Curiously, $form_state['rebuild'] = FALSE isn't enough.
+  unset($form_state['storage']);
 }
 
 /**
@@ -378,7 +393,12 @@
     if (variable_get('uc_pane_'. $pane['id'] .'_enabled', TRUE)) {
       $func = $pane['callback'];
       if (function_exists($func)) {
-        $return = $func('review', $order, NULL);
+        /* FIXME: In a future version, it should be sufficient to just pass the operation, $order, and $form to the
+         *        checkout pane callback, but for now we don't want to break the API for existing modules, so it's
+         *        better to pass superfluous data.
+         */
+        $return = $func('review', $order, NULL, $form);
+
         if (!is_null($return)) {
           $data[$pane['title']] = $return;
         }
Index: uc_order/uc_order.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ubercart/uc_order/uc_order.admin.inc,v
retrieving revision 1.1.2.32
diff -u -r1.1.2.32 uc_order.admin.inc
--- uc_order/uc_order.admin.inc	13 Apr 2010 03:44:36 -0000	1.1.2.32
+++ uc_order/uc_order.admin.inc	16 Jun 2010 15:49:55 -0000
@@ -1127,7 +1127,7 @@
     if (in_array('edit', $pane['show']) &&
         variable_get('uc_order_pane_'. $pane['id'] .'_show_edit', $pane['enabled'])) {
       $func = $pane['callback'];
-      if (function_exists($func) && ($contents = $func('edit-form', $order)) != NULL) {
+      if (function_exists($func) && ($contents = $func('edit-form', $order, $form_state)) != NULL) {
         $form = array_merge($form, $contents);
       }
     }
@@ -1147,6 +1147,7 @@
     );
   }
 
+
   return $form;
 }
 
@@ -1176,8 +1177,12 @@
     }
   }
 
+  /* FIXME: Can't this be re-written to to render the rest of the form with just one call to drupal_render(), instead of
+   *        having to call drupal_render() manually for each additional field we want to render?
+   */
   $output .= '<div class="order-pane abs-left">'. drupal_render($form['order_id']) . drupal_render($form['order_modified'])
            . drupal_render($form['form_id']) . drupal_render($form['form_token'])
+           . drupal_render($form['form_build_id'])
            . drupal_render($form['submit-changes']) . drupal_render($form['delete'])
             .'</div>';
 
@@ -1213,7 +1218,7 @@
         variable_get('uc_order_pane_'. $pane['id'] .'_show_edit', TRUE)) {
       $func = $pane['callback'];
       if (function_exists($func)) {
-        if (($changes = $func('edit-process', $form_state['values'])) != NULL) {
+        if (($changes = $func('edit-process', $form_state['values'], $form_state)) != NULL) {
           foreach ($changes as $key => $value) {
             if ($order->$key != $value) {
               if (!is_array($value)) {
@@ -1223,7 +1228,7 @@
             }
           }
         }
-        if (($ops = $func('edit-ops', NULL)) != NULL) {
+        if (($ops = $func('edit-ops', NULL, $form_state)) != NULL) {
           $perform[$func] = $ops;
         }
       }
@@ -1274,7 +1279,7 @@
   if (is_array($perform)) {
     foreach ($perform as $func => $ops) {
       if (in_array($form_state['values']['op'], $ops)) {
-        $func($form_state['values']['op'], $form_state['values']);
+        $func($form_state['values']['op'], $form_state['values'], $form_state);
       }
     }
   }
