Shipping quotes do not provide enough data for workflows

Project: 
Ubercart
Category: 
bug report
Version: 
Ubercart 1.3
Priority: 
normal
Assigned: 
Unassigned
Status: 
patch (needs review)

uc_quote_request_quotes (line 1349, quotes.module) called by /cart/checkout/shipping/quote, only provides _uc_quote_assemble_quotes (line 1415, quotes.module) with an $order with delivery address (as taken from the $_POST). However this $order is then used by workflow_ng and may contain logic which acts the billing address e.g. if you wanted to use a shipping method if the buyer was in a given country.

Re: Shipping quotes do not provide enough data for workflows

working towards a solution atm... (also in
#drupal-ubercart if you wanted details)

Re: Shipping quotes do not provide enough data for workflows

This fix needs changes in 2 locations.

First we need to send the billing address details to the function, so we need some extra javascript

uc_quote.js :line 65:

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();
  });
 
  if (!!products) {
    details["products"] = products;
  }
  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();
      i++;
    }
    details["products"] = products.substr(1);
  }
  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['base_path'] + "shipping/quote", 0);
  $.ajax({
    type: "POST",
    url: Drupal.settings['base_path'] + "cart/checkout/shipping/quote",
    data: details,
    dataType: "json",
    success: displayQuote
  });

  return false;
}

The new/modified part is

$("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();
  });

With the new data being submitted, we now need to make corresponding changes to uc_quote.module :line 1346:

<?php

/**
* 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 (
$_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) {
 
$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, $user);
   
$data = ob_get_contents();
    if (
$data = unserialize($data)) {
     
$quote_data[$method['id']] = $data;
    }
   
ob_clean();
  }
 
ob_end_clean();
  return
$quote_data;
}
?>

the new bit being, removing the old $details handling code at the top of the function and then modifying the loop around line 1390 :

<?php
 
foreach ($_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;
    }
  }
?>

Re: Re: Shipping quotes do not provide enough data for workflows

OK, looks good. I'll get that committed.

However, for next time when there's more than a simple change to the code, I'd appreciate a patch. It helps me figure out just what I'm replacing.

Re: Re: Re: Shipping quotes do not provide enough data for workf

yeah sorry about that, will do next time Smiling