From b1e2936f77f75498993cb528eade097595f4a4f7 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 23 Nov 2011 15:20:03 -0500
Subject: [PATCH 01/15] Lonwave's initial patch from #22

---
 uc_attribute/uc_attribute.module     |   69 +++++++++++++++----------------
 uc_cart/uc_cart.module               |   35 ++--------------
 uc_product/uc_product.info.inc       |    8 +++-
 uc_product/uc_product.module         |   27 +++++++++++-
 uc_product/uc_product.rules.inc      |   19 +++++++++
 uc_product_kit/uc_product_kit.module |   74 ++++++++++++++++++---------------
 uc_taxes/uc_taxes.module             |   47 +++-------------------
 7 files changed, 136 insertions(+), 143 deletions(-)

diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index d1f01c6..72da9ac 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -504,40 +504,34 @@ function uc_attribute_uc_product_class($type, $op) {
 }
 
 /**
- * Implements hook_uc_cart_item().
+ * Implements hook_uc_product_alter().
  */
-function uc_attribute_uc_cart_item($op, $item) {
-  switch ($op) {
-    case 'load':
-      if (!isset($item->data['attributes'])) {
-        return;
-      }
-
-      $options = _uc_cart_product_get_options($item);
-      foreach ($options as $option) {
-        $item->cost += $option['cost'];
-        $item->price += $option['price'];
-        $item->display_price += $option['price'];
-        $item->weight += $option['weight'];
-      }
+function uc_attribute_uc_product_alter(&$node) {
+  if (isset($node->data['attributes'])) {
+    $options = _uc_cart_product_get_options($node);
+    foreach ($options as $option) {
+      $node->cost += $option['cost'];
+      $node->price += $option['price'];
+      $node->display_price += $option['price'];
+      $node->weight += $option['weight'];
+    }
 
-      $combination = array();
-      foreach ((array)$item->data['attributes'] as $aid => $value) {
-        if (is_numeric($value)) {
-          $attribute = uc_attribute_load($aid, $item->nid, 'product');
-          if ($attribute && ($attribute->display == 1 || $attribute->display == 2)) {
-            $combination[$aid] = $value;
-          }
+    $combination = array();
+    foreach ($node->data['attributes'] as $aid => $value) {
+      if (is_numeric($value)) {
+        $attribute = uc_attribute_load($aid, $node->nid, 'product');
+        if ($attribute && ($attribute->display == 1 || $attribute->display == 2)) {
+          $combination[$aid] = $value;
         }
       }
-      ksort($combination);
+    }
+    ksort($combination);
 
-      $model = db_query("SELECT model FROM {uc_product_adjustments} WHERE nid = :nid AND combination LIKE :combo", array(':nid' => $item->nid, ':combo' => serialize($combination)))->fetchField();
+    $model = db_query("SELECT model FROM {uc_product_adjustments} WHERE nid = :nid AND combination LIKE :combo", array(':nid' => $node->nid, ':combo' => serialize($combination)))->fetchField();
 
-      if (!empty($model)) {
-        $item->model = $model;
-      }
-      break;
+    if (!empty($model)) {
+      $node->model = $model;
+    }
   }
 }
 
@@ -1263,18 +1257,21 @@ function _uc_attribute_alter_form($product) {
     // Build the attribute's options array.
     $options = array();
     foreach ($attribute->options as $option) {
+      $display_price = '';
+      $variant = uc_product_load_variant($product->nid, array('attributes' => array($attribute->aid => $option->oid)));
       switch (variable_get('uc_attribute_option_price_format', 'adjustment')) {
         case 'total':
-          $display_price = in_array($attribute->aid, $priced_attributes) ? ', ' . uc_currency_format($product->sell_price + $option->price) : '';
+          if (in_array($attribute->aid, $priced_attributes)) {
+            $display_price = uc_currency_format($variant->display_price);
+          }
           if (count($priced_attributes) == 1 && $attribute->display != 3) {
             break;
           }
         case 'adjustment':
-          $display_price = ($option->price != 0 ? ', ' . ($option->price > 0 ? '+' : '') . uc_currency_format($option->price) : '');
-          break;
-        case 'none':
-        default:
-          $display_price = '';
+          $adjustment = $variant->display_price - $product->display_price;
+          if ($adjustment) {
+            $display_price = ($adjustment > 0 ? '+' : '') . uc_currency_format($adjustment);
+          }
           break;
       }
       $options[$option->oid] = $option->name;
@@ -1282,7 +1279,9 @@ function _uc_attribute_alter_form($product) {
         // Select options are check_plain()ed, but radio button labels are not.
         $options[$option->oid] = check_plain($options[$option->oid]);
       }
-      $options[$option->oid] .= $display_price;
+      if ($display_price) {
+        $options[$option->oid] .= ', ' . $display_price;
+      }
     }
 
     if (count($attribute->options) && $attribute->display > 0) {
diff --git a/uc_cart/uc_cart.module b/uc_cart/uc_cart.module
index d42eb2c..a6b3535 100644
--- a/uc_cart/uc_cart.module
+++ b/uc_cart/uc_cart.module
@@ -1353,37 +1353,12 @@ function uc_cart_get_item($item) {
     $item = db_query("SELECT * FROM {uc_cart_products} WHERE cart_item_id = :id", array(':id' => $item))->fetchObject();
   }
 
-  if (empty($item)) {
-    return;
-  }
-
-  $product = node_load($item->nid);
-  if (!$product) {
-    return;
+  if ($item && $product = uc_product_load_variant($item->nid, unserialize($item->data))) {
+    $product->cart_item_id = $item->cart_item_id;
+    $product->qty = $item->qty;
+    $product->module = $product->data['module'];
+    return $product;
   }
-
-  $item->vid = $product->vid;
-  $item->title = $product->title;
-  $item->cost = $product->cost;
-  $item->price = $product->sell_price;
-  $item->weight = $product->weight;
-  $item->weight_units = $product->weight_units;
-  $item->data = unserialize($item->data);
-  $item->module = $item->data['module'];
-  $item->model = $product->model;
-  $item->display_price = $product->sell_price;
-  $item->display_price_suffix = '';
-
-  // Invoke hook_uc_cart_item() with $op = 'load' in enabled modules.
-  foreach (module_implements('uc_cart_item') as $module) {
-    $func = $module . '_uc_cart_item';
-    if (function_exists($func)) {
-      // $item must be passed by reference.
-      $func('load', $item);
-    }
-  }
-
-  return $item;
 }
 
 /**
diff --git a/uc_product/uc_product.info.inc b/uc_product/uc_product.info.inc
index 76758cd..9d0b8d5 100644
--- a/uc_product/uc_product.info.inc
+++ b/uc_product/uc_product.info.inc
@@ -29,9 +29,15 @@ function uc_product_entity_property_info_alter(&$info) {
     );
     $properties['sell_price'] = array(
       'label' => t('Sell price'),
-      'description' => t('The amount the customer pays for the product.'),
+      'description' => t('The base amount the customer pays for the product.'),
       'type' => 'decimal',
     );
+    $properties['price'] = array(
+      'label' => t('Price'),
+      'description' => t('The amount the customer pays for this specific product.'),
+      'type' => 'decimal',
+      'setter callback' => 'entity_property_verbatim_set',
+    );
     $properties['weight'] = array(
       'label' => t('Weight'),
       'description' => t('The physical weight of the product.'),
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 0489a72..cd822e7 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -639,12 +639,35 @@ function uc_product_load($nodes) {
     foreach ($node as $field => $value) {
       $nodes[$node->nid]->$field = $value;
     }
-    $nodes[$node->nid]->display_price = $node->sell_price;
-    $nodes[$node->nid]->display_price_suffix = '';
+    _uc_product_calculate($nodes[$node->nid]);
   }
 }
 
 /**
+ * Load a specific altered variant of a product.
+ */
+function uc_product_load_variant($nid, $data) {
+  $node = clone node_load($nid);
+  $node->data = $data;
+  _uc_product_calculate($node);
+  return $node;
+}
+
+/**
+ * Calculate the sale and display prices for a product.
+ */
+function _uc_product_calculate(&$node) {
+  $node->price = $node->sell_price;
+
+  rules_invoke_event('uc_product_load', $node);
+
+  $node->display_price = $node->price;
+  $node->display_price_suffix = '';
+
+  drupal_alter('uc_product', $node);
+}
+
+/**
  * Implements hook_delete().
  */
 function uc_product_delete(&$node) {
diff --git a/uc_product/uc_product.rules.inc b/uc_product/uc_product.rules.inc
index 20f35d7..c3d0c2e 100644
--- a/uc_product/uc_product.rules.inc
+++ b/uc_product/uc_product.rules.inc
@@ -6,6 +6,25 @@
  */
 
 /**
+ * Implements hook_rules_event_info().
+ */
+function uc_product_rules_event_info() {
+  $events['uc_product_load'] = array(
+    'label' => t('A product is being loaded'),
+    'group' => t('Node'),
+    'variables' => array(
+      'node' => array(
+        'type' => 'node',
+        'label' => t('Node'),
+        'skip save' => TRUE,
+      ),
+    ),
+  );
+
+  return $events;
+}
+
+/**
  * Implements hook_rules_condition_info().
  */
 function uc_product_rules_condition_info() {
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index 845c264..6b64452 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -365,33 +365,34 @@ function uc_product_kit_load($nodes) {
     $nodes[$prod->nid]->mutable = $prod->mutable;
     $nodes[$prod->nid]->synchronized = $prod->synchronized;
 
-    // Save products in an associative array to know exactly which products
-    // belong to which kit.
-    $all_products[$prod->nid][$prod->product_id] = $prod;
-  }
-
-  foreach ($all_products as $kit_products) {
-    $products = node_load_multiple(array_keys($kit_products));
-    foreach ($products as $p) {
-      // Get our component product.
-      $prod = $kit_products[$p->nid];
-
-      // We need to clone the object to avoid changes by reference
-      // http://www.php.net/manual/en/language.references.php#93812
-      $product = clone $p;
-
-      // Add the component information.
-      $product->qty = $prod->qty;
-      $product->discount = $prod->discount;
-      $product->ordering = $prod->ordering;
-
-      // Add product to the kit.
-      $nodes[$prod->nid]->products[$product->nid] = $product;
+    // Add the component information.
+    $data = array();
+    if ($prod->mutable != UC_PRODUCT_KIT_MUTABLE) {
+      $data = array('kit_id' => $prod->nid, 'kit_discount' => $prod->discount);
     }
+    $product = uc_product_load_variant($prod->product_id, $data);
+    $product->qty = $prod->qty;
+    $product->discount = $prod->discount;
+    $product->ordering = $prod->ordering;
+
+    // Add product to the kit.
+    $nodes[$prod->nid]->products[$product->nid] = $product;
   }
 
   // Add product data to kits.
   uc_product_load($nodes);
+
+  // Recalculate display price.
+  foreach ($nodes as &$node) {
+    if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
+      $node->display_price = 0;
+      $node->display_price_suffix = '';
+      foreach ($node->products as $pid => &$product) {
+        $node->display_price += $product->display_price * $product->qty;
+        $node->display_price_suffix .= $product->display_price_suffix;
+      }
+    }
+  }
 }
 
 /**
@@ -1010,22 +1011,27 @@ function uc_product_kit_uc_add_to_cart($nid, $qty, $kit_data) {
 }
 
 /**
- * Implements hook_uc_cart_item().
+ * Implements hook_uc_product_alter().
  */
-function uc_product_kit_uc_cart_item($op, $item) {
-  switch ($op) {
-    case 'load':
-      if (isset($item->data['kit_id']) && ($kit = node_load($item->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
-        $kit_discount = $kit->products[$item->nid]->discount;
-        if ($kit_discount !== '') {
-          $item->price += $kit_discount;
-          $item->display_price += $kit_discount;
-        }
-      }
-    break;
+function uc_product_kit_uc_product_alter(&$node) {
+  if (isset($node->data['kit_id'])) {
+    // If this is a kit component load, we would cause infinite recursion trying
+    // to node_load() the parent, but we already have the discount available.
+    if (isset($node->data['kit_discount'])) {
+      $discount = isset($node->data['kit_discount']);
+    }
+    elseif (($kit = node_load($node->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
+      $discount = $kit->products[$node->nid]->discount;
+    }
+    else {
+      $discount = 0;
+    }
+    $node->price += $discount;
+    $node->display_price += $discount;
   }
 }
 
+
 /**
  * Implements hook_uc_order_product_alter().
  *
diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index b0b2e8a..4192fc9 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -105,51 +105,16 @@ function uc_taxes_form_uc_order_edit_form_alter(&$form, &$form_state) {
 }
 
 /**
- * Implements hook_uc_cart_item().
+ * Implements hook_uc_product_alter().
  *
  * Adds included taxes (VAT) to display price of applicable products.
  */
-function uc_taxes_uc_cart_item($op, $item) {
-  if ($op == 'load') {
-    $product = node_load($item->nid);
+function uc_taxes_uc_product_alter(&$node) {
+  list($amount, $suffixes) = uc_taxes_get_included_tax($node, $node->price);
 
-    list($amount, $suffixes) = uc_taxes_get_included_tax($product, $item->price);
-
-    $item->display_price += $amount;
-    if (!empty($suffixes)) {
-      $item->display_price_suffix .= implode(' ', $suffixes);
-    }
-  }
-}
-
-/**
- * Implements hook_node_load().
- *
- * Adds included taxes (VAT) to display price of applicable products.
- */
-function uc_taxes_node_load($nodes, $types) {
-  foreach ($nodes as &$node) {
-    if (uc_product_is_product($node)) {
-      $amount = 0;
-      $suffixes = array();
-      if ($node->type == 'product_kit') {
-        // For product kits, the total tax is the sum of the tax on the discounted
-        // prices of component products.
-        foreach ($node->products as $pid => &$product) {
-          $discount =  ($node->mutable != UC_PRODUCT_KIT_MUTABLE) ? $product->discount : 0;
-          list($this_amount, $these_suffixes) = uc_taxes_get_included_tax($product, $product->sell_price + $discount);
-          $amount += $this_amount * $product->qty;
-          $suffixes += $these_suffixes;
-        }
-      }
-      else {
-        list($amount, $suffixes) = uc_taxes_get_included_tax($node);
-      }
-      $node->display_price += $amount;
-      if (!empty($suffixes)) {
-        $node->display_price_suffix .= implode(' ', $suffixes);
-      }
-    }
+  $node->display_price += $amount;
+  if (!empty($suffixes)) {
+    $node->display_price_suffix .= implode(' ', $suffixes);
   }
 }
 
-- 
1.7.3.4


From 5867a76deffb0a9cbaad43352b95043642e62273 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 23 Nov 2011 15:22:49 -0500
Subject: [PATCH 02/15] Fix minor bug in uc_product_kit_uc_product_alter()

---
 uc_product_kit/uc_product_kit.module |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index 6b64452..fe03904 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -1018,7 +1018,7 @@ function uc_product_kit_uc_product_alter(&$node) {
     // If this is a kit component load, we would cause infinite recursion trying
     // to node_load() the parent, but we already have the discount available.
     if (isset($node->data['kit_discount'])) {
-      $discount = isset($node->data['kit_discount']);
+      $discount = $node->data['kit_discount'];
     }
     elseif (($kit = node_load($node->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
       $discount = $kit->products[$node->nid]->discount;
-- 
1.7.3.4


From 72c725e39406738c84cd0d271542c904ef408f37 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 23 Nov 2011 15:23:58 -0500
Subject: [PATCH 03/15] Make display price suffixes into an array

---
 uc_product/uc_product.module         |    6 +++---
 uc_product_kit/uc_product_kit.module |    8 ++++----
 uc_taxes/uc_taxes.module             |    2 +-
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index cd822e7..2d23382 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -662,7 +662,7 @@ function _uc_product_calculate(&$node) {
   rules_invoke_event('uc_product_load', $node);
 
   $node->display_price = $node->price;
-  $node->display_price_suffix = '';
+  $node->display_price_suffixes = array();
 
   drupal_alter('uc_product', $node);
 }
@@ -688,7 +688,7 @@ function uc_product_view($node, $view_mode) {
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
     '#value' => $node->display_price,
-    'suffix' => array('#markup' => $node->display_price_suffix),
+    'suffix' => array('#markup' => implode(' ', $node->display_price_suffixes)),
     '#attributes' => array(
       'class' => array(
         'display-price',
@@ -1048,7 +1048,7 @@ function uc_product_uc_cart_display($item) {
   );
 
   $element['#total'] = $item->display_price * $item->qty;
-  $element['#suffix'] = $item->display_price_suffix;
+  $element['#suffix'] = implode(' ', $item->display_price_suffixes);
   $element['data'] = array('#type' => 'hidden', '#value' => serialize($item->data));
   $element['qty'] = array(
     '#type' => 'uc_quantity',
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index fe03904..0721814 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -386,10 +386,10 @@ function uc_product_kit_load($nodes) {
   foreach ($nodes as &$node) {
     if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
       $node->display_price = 0;
-      $node->display_price_suffix = '';
+      $node->display_price_suffixes = array();
       foreach ($node->products as $pid => &$product) {
         $node->display_price += $product->display_price * $product->qty;
-        $node->display_price_suffix .= $product->display_price_suffix;
+        $node->display_price_suffixes += $product->display_price_suffixes;
       }
     }
   }
@@ -664,7 +664,7 @@ function uc_product_kit_view($node, $view_mode) {
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
     '#value' => $node->display_price,
-    'suffix' => array('#markup' => $node->display_price_suffix),
+    'suffix' => array('#markup' => implode(' ', $node->display_price_suffixes)),
     '#attributes' => array(
       'class' => array(
         'product-kit',
@@ -1119,7 +1119,7 @@ function uc_product_kit_uc_cart_display($item) {
       );
       $element['description'] = array('#markup' => '');
       $element['#total'] = 0;
-      $element['#suffix'] = $kit->display_price_suffix;
+      $element['#suffix'] = implode(' ', $kit->display_price_suffixes);
       $element['#extra'] = array();
 
       $elements[$unique_id] = $element;
diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index 4192fc9..9a7d47d 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -114,7 +114,7 @@ function uc_taxes_uc_product_alter(&$node) {
 
   $node->display_price += $amount;
   if (!empty($suffixes)) {
-    $node->display_price_suffix .= implode(' ', $suffixes);
+    $node->display_price_suffixes += $suffixes;
   }
 }
 
-- 
1.7.3.4


From 8cadf6fcbf12e37a439556f1a7456b35f7e679b7 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 23 Nov 2011 15:25:33 -0500
Subject: [PATCH 04/15] Fix rules-based price alteration for product kits.

---
 uc_attribute/uc_attribute.module                   |    5 +--
 uc_product/uc_product.module                       |   19 +++++++++-----
 .../uc_product_handler_field_display_price.inc     |    2 +-
 uc_product_kit/uc_product_kit.module               |   25 ++++++++++++++++----
 uc_taxes/uc_taxes.module                           |    4 +-
 5 files changed, 37 insertions(+), 18 deletions(-)

diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index 72da9ac..d91a8a4 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -512,7 +512,6 @@ function uc_attribute_uc_product_alter(&$node) {
     foreach ($options as $option) {
       $node->cost += $option['cost'];
       $node->price += $option['price'];
-      $node->display_price += $option['price'];
       $node->weight += $option['weight'];
     }
 
@@ -1262,13 +1261,13 @@ function _uc_attribute_alter_form($product) {
       switch (variable_get('uc_attribute_option_price_format', 'adjustment')) {
         case 'total':
           if (in_array($attribute->aid, $priced_attributes)) {
-            $display_price = uc_currency_format($variant->display_price);
+            $display_price = uc_currency_format($variant->price + $variant->display_price_adjustment);
           }
           if (count($priced_attributes) == 1 && $attribute->display != 3) {
             break;
           }
         case 'adjustment':
-          $adjustment = $variant->display_price - $product->display_price;
+          $adjustment = ($variant->price + $variant->display_price_adjustment) - ($product->price + $product->display_price_adjustment);
           if ($adjustment) {
             $display_price = ($adjustment > 0 ? '+' : '') . uc_currency_format($adjustment);
           }
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 2d23382..c4930f1 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -658,16 +658,21 @@ function uc_product_load_variant($nid, $data) {
  */
 function _uc_product_calculate(&$node) {
   $node->price = $node->sell_price;
-
-  rules_invoke_event('uc_product_load', $node);
-
-  $node->display_price = $node->price;
+  $node->display_price_adjustment = 0;
   $node->display_price_suffixes = array();
-
   drupal_alter('uc_product', $node);
 }
 
 /**
+ * Implements hook_uc_product_alter.
+ * 
+ * Invokes rules event to allow product modifications.
+ */
+function uc_product_uc_product_alter(&$node) {
+  rules_invoke_event('uc_product_load', $node);
+}
+
+/**
  * Implements hook_delete().
  */
 function uc_product_delete(&$node) {
@@ -687,7 +692,7 @@ function uc_product_delete(&$node) {
 function uc_product_view($node, $view_mode) {
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
-    '#value' => $node->display_price,
+    '#value' => $node->price + $node->display_price_adjustment,
     'suffix' => array('#markup' => implode(' ', $node->display_price_suffixes)),
     '#attributes' => array(
       'class' => array(
@@ -1047,7 +1052,7 @@ function uc_product_uc_cart_display($item) {
     '#markup' => node_access('view', $node) ? l($item->title, 'node/' . $node->nid) : check_plain($item->title),
   );
 
-  $element['#total'] = $item->display_price * $item->qty;
+  $element['#total'] = ($item->price + $item->display_price_adjustment) * $item->qty;
   $element['#suffix'] = implode(' ', $item->display_price_suffixes);
   $element['data'] = array('#type' => 'hidden', '#value' => serialize($item->data));
   $element['qty'] = array(
diff --git a/uc_product/views/uc_product_handler_field_display_price.inc b/uc_product/views/uc_product_handler_field_display_price.inc
index bf205f4..b7de82b 100644
--- a/uc_product/views/uc_product_handler_field_display_price.inc
+++ b/uc_product/views/uc_product_handler_field_display_price.inc
@@ -21,6 +21,6 @@ class uc_product_handler_field_display_price extends uc_product_handler_field_pr
   function get_value($values, $field = NULL) {
     $nid = parent::get_value($values, $field);
     $node = node_load($nid);
-    return $node->display_price;
+    return $node->price + $node->display_price_adjustment;
   }
 }
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index 0721814..76dd10a 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -385,10 +385,10 @@ function uc_product_kit_load($nodes) {
   // Recalculate display price.
   foreach ($nodes as &$node) {
     if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
-      $node->display_price = 0;
+      $node->display_price_adjustment = 0;
       $node->display_price_suffixes = array();
       foreach ($node->products as $pid => &$product) {
-        $node->display_price += $product->display_price * $product->qty;
+        $node->display_price_adjustment += $product->display_price_adjustment * $product->qty;
         $node->display_price_suffixes += $product->display_price_suffixes;
       }
     }
@@ -396,6 +396,22 @@ function uc_product_kit_load($nodes) {
 }
 
 /**
+ * Implements hook_module_implements_alter().
+ *
+ * Ensure that our component products have their discounts applied before any other
+ * product alterations are made.
+ */
+function uc_product_kit_module_implements_alter(&$implementations, $hook) {
+  if ($hook == 'uc_product_alter') {
+    $group = $implementations['uc_product_kit'];
+    unset($implementations['uc_product_kit']);
+    $implementations =  array('uc_product_kit' => $group) + $implementations;
+  }
+}
+
+
+
+/**
  * Implements hook_theme().
  */
 function uc_product_kit_theme() {
@@ -663,7 +679,7 @@ function theme_uc_product_kit_items_form($variables) {
 function uc_product_kit_view($node, $view_mode) {
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
-    '#value' => $node->display_price,
+    '#value' => $node->price + $node->display_price_adjustment,
     'suffix' => array('#markup' => implode(' ', $node->display_price_suffixes)),
     '#attributes' => array(
       'class' => array(
@@ -1027,7 +1043,6 @@ function uc_product_kit_uc_product_alter(&$node) {
       $discount = 0;
     }
     $node->price += $discount;
-    $node->display_price += $discount;
   }
 }
 
@@ -1134,7 +1149,7 @@ function uc_product_kit_uc_cart_display($item) {
       );
     }
 
-    $elements[$unique_id]['#total'] += $item->display_price * $item->qty;
+    $elements[$unique_id]['#total'] += ($item->price + $item->display_price_adjustment) * $item->qty;
     $elements[$unique_id]['data'][$item->nid] = $item;
     $products[$unique_id][] = $item->nid;
 
diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index 9a7d47d..f26841d 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -76,7 +76,7 @@ function uc_taxes_menu() {
  * calculations are made.
  */
 function uc_taxes_module_implements_alter(&$implementations, $hook) {
-  if ($hook == 'uc_order') {
+  if (in_array($hook, array('uc_order', 'uc_product_alter'))) {
     $group = $implementations['uc_taxes'];
     unset($implementations['uc_taxes']);
     $implementations['uc_taxes'] = $group;
@@ -112,7 +112,7 @@ function uc_taxes_form_uc_order_edit_form_alter(&$form, &$form_state) {
 function uc_taxes_uc_product_alter(&$node) {
   list($amount, $suffixes) = uc_taxes_get_included_tax($node, $node->price);
 
-  $node->display_price += $amount;
+  $node->display_price_adjustment += $amount;
   if (!empty($suffixes)) {
     $node->display_price_suffixes += $suffixes;
   }
-- 
1.7.3.4


From c79e69d17cef9ec6e94c3ecf44cbf86bfdd69be2 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 23 Nov 2011 15:27:21 -0500
Subject: [PATCH 05/15] Only run uc_product_alter sequence when uc_product_load_variant() is called explicitly (i.e. not at node_load)

---
 uc_attribute/uc_attribute.module     |    7 ++--
 uc_product/uc_product.module         |   56 +++++++++++++++++-----------------
 uc_product_kit/uc_product_kit.module |   19 ++++++-----
 3 files changed, 42 insertions(+), 40 deletions(-)

diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index d91a8a4..7eb435e 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -507,7 +507,7 @@ function uc_attribute_uc_product_class($type, $op) {
  * Implements hook_uc_product_alter().
  */
 function uc_attribute_uc_product_alter(&$node) {
-  if (isset($node->data['attributes'])) {
+  if (isset($node->data['attributes']) && is_array($node->data['attributes'])) {
     $options = _uc_cart_product_get_options($node);
     foreach ($options as $option) {
       $node->cost += $option['cost'];
@@ -1257,7 +1257,7 @@ function _uc_attribute_alter_form($product) {
     $options = array();
     foreach ($attribute->options as $option) {
       $display_price = '';
-      $variant = uc_product_load_variant($product->nid, array('attributes' => array($attribute->aid => $option->oid)));
+      $variant = uc_product_load_variant($product, array('attributes' => array($attribute->aid => $option->oid)));
       switch (variable_get('uc_attribute_option_price_format', 'adjustment')) {
         case 'total':
           if (in_array($attribute->aid, $priced_attributes)) {
@@ -1267,7 +1267,8 @@ function _uc_attribute_alter_form($product) {
             break;
           }
         case 'adjustment':
-          $adjustment = ($variant->price + $variant->display_price_adjustment) - ($product->price + $product->display_price_adjustment);
+          $base = uc_product_load_variant($product);
+          $adjustment = ($variant->price + $variant->display_price_adjustment) - ($base->price + $base->display_price_adjustment);
           if ($adjustment) {
             $display_price = ($adjustment > 0 ? '+' : '') . uc_currency_format($adjustment);
           }
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index c4930f1..1bdd8b7 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -639,28 +639,27 @@ function uc_product_load($nodes) {
     foreach ($node as $field => $value) {
       $nodes[$node->nid]->$field = $value;
     }
-    _uc_product_calculate($nodes[$node->nid]);
+    $nodes[$node->nid]->price = $nodes[$node->nid]->sell_price;
+    $nodes[$node->nid]->display_price_adjustment = 0;
+    $nodes[$node->nid]->display_price_suffixes = array();
   }
 }
 
 /**
- * Load a specific altered variant of a product.
- */
-function uc_product_load_variant($nid, $data) {
-  $node = clone node_load($nid);
-  $node->data = $data;
-  _uc_product_calculate($node);
-  return $node;
-}
-
-/**
- * Calculate the sale and display prices for a product.
+ * Gets a specific altered variant of a product.
+ * 
+ * @param $nid
+ *   The nid of the product.  May also supply a complete, previously loaded product object.
+ * @param $data
+ *   Optional data to add to the product before invoking the alter hooks.
  */
-function _uc_product_calculate(&$node) {
-  $node->price = $node->sell_price;
-  $node->display_price_adjustment = 0;
-  $node->display_price_suffixes = array();
+function uc_product_load_variant($nid, $data = FALSE) {
+  $node = clone (is_object($nid) ? $nid : node_load($nid));
+  if (!empty($data)) {
+    $node->data = $data;
+  }
   drupal_alter('uc_product', $node);
+  return $node;
 }
 
 /**
@@ -690,10 +689,11 @@ function uc_product_delete(&$node) {
  * Implements hook_view().
  */
 function uc_product_view($node, $view_mode) {
+  $variant = uc_product_load_variant($node);
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
-    '#value' => $node->price + $node->display_price_adjustment,
-    'suffix' => array('#markup' => implode(' ', $node->display_price_suffixes)),
+    '#value' => $variant->price + $variant->display_price_adjustment,
+    'suffix' => array('#markup' => implode(' ', $variant->display_price_suffixes)),
     '#attributes' => array(
       'class' => array(
         'display-price',
@@ -702,13 +702,13 @@ function uc_product_view($node, $view_mode) {
   );
   $node->content['model'] = array(
     '#theme' => 'uc_product_model',
-    '#model' => $node->model,
+    '#model' => $variant->model,
     '#view_mode' => $view_mode,
   );
   $node->content['list_price'] = array(
     '#theme' => 'uc_product_price',
     '#title' => t('List price:'),
-    '#value' => $node->list_price,
+    '#value' => $variant->list_price,
     '#attributes' => array(
       'class' => array(
         'list-price',
@@ -718,7 +718,7 @@ function uc_product_view($node, $view_mode) {
   $node->content['cost'] = array(
     '#theme' => 'uc_product_price',
     '#title' => t('Cost:'),
-    '#value' => $node->cost,
+    '#value' => $variant->cost,
     '#attributes' => array(
       'class' => array(
         'cost',
@@ -729,7 +729,7 @@ function uc_product_view($node, $view_mode) {
   $node->content['sell_price'] = array(
     '#theme' => 'uc_product_price',
     '#title' => t('Price:'),
-    '#value' => $node->sell_price,
+    '#value' => $variant->sell_price,
     '#attributes' => array(
       'class' => array(
         'sell-price',
@@ -738,16 +738,16 @@ function uc_product_view($node, $view_mode) {
   );
   $node->content['weight'] = array(
     '#theme' => 'uc_product_weight',
-    '#amount' => $node->weight,
-    '#units' => $node->weight_units,
+    '#amount' => $variant->weight,
+    '#units' => $variant->weight_units,
     '#view_mode' => $view_mode,
   );
   $node->content['dimensions'] = array(
     '#theme' => 'uc_product_dimensions',
-    '#length' => $node->length,
-    '#width' => $node->width,
-    '#height' => $node->height,
-    '#units' => $node->length_units,
+    '#length' => $variant->length,
+    '#width' => $variant->width,
+    '#height' => $variant->height,
+    '#units' => $variant->length_units,
     '#view_mode' => $view_mode,
   );
   if (module_exists('uc_cart') && isset($node->nid)) {
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index 76dd10a..f5a58c9 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -677,10 +677,11 @@ function theme_uc_product_kit_items_form($variables) {
  * Implements hook_view().
  */
 function uc_product_kit_view($node, $view_mode) {
+  $variant = uc_product_load_variant($node);
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
-    '#value' => $node->price + $node->display_price_adjustment,
-    'suffix' => array('#markup' => implode(' ', $node->display_price_suffixes)),
+    '#value' => $variant->price + $variant->display_price_adjustment,
+    'suffix' => array('#markup' => implode(' ', $variant->display_price_suffixes)),
     '#attributes' => array(
       'class' => array(
         'product-kit',
@@ -690,13 +691,13 @@ function uc_product_kit_view($node, $view_mode) {
   );
   $node->content['model'] = array(
     '#theme' => 'uc_product_model',
-    '#model' => $node->model,
+    '#model' => $variant->model,
     '#view_mode' => $view_mode,
   );
   $node->content['list_price'] = array(
     '#theme' => 'uc_product_price',
     '#title' => t('List price:'),
-    '#value' => $node->list_price,
+    '#value' => $variant->list_price,
     '#attributes' => array(
       'class' => array(
         'product-kit',
@@ -707,7 +708,7 @@ function uc_product_kit_view($node, $view_mode) {
   $node->content['cost'] = array(
     '#theme' => 'uc_product_price',
     '#title' => t('Cost:'),
-    '#value' => $node->cost,
+    '#value' => $variant->cost,
     '#attributes' => array(
       'class' => array(
         'product-kit',
@@ -719,7 +720,7 @@ function uc_product_kit_view($node, $view_mode) {
   $node->content['sell_price'] = array(
     '#theme' => 'uc_product_price',
     '#title' => t('Price:'),
-    '#value' => $node->sell_price,
+    '#value' => $variant->sell_price,
     '#attributes' => array(
       'class' => array(
         'product-kit',
@@ -729,8 +730,8 @@ function uc_product_kit_view($node, $view_mode) {
   );
   $node->content['weight'] = array(
     '#theme' => 'uc_product_weight',
-    '#amount' => $node->weight,
-    '#units' => $node->weight_units,
+    '#amount' => $variant->weight,
+    '#units' => $variant->weight_units,
     '#view_mode' => $view_mode,
   );
 
@@ -738,7 +739,7 @@ function uc_product_kit_view($node, $view_mode) {
     $node->content['products'] = array('#weight' => 6);
 
     $i = 0;
-    foreach ($node->products as $product) {
+    foreach ($variant->products as $product) {
       $node->content['products'][$product->nid]['qty'] = array(
         '#markup' => '<div class="product-qty">' . theme('uc_product_kit_list_item', array('product' => $product)) . '</div>',
       );
-- 
1.7.3.4


From 6adee76a4d188580ea76bf5bb3c661f8bc27f0c4 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 7 Dec 2011 09:30:19 -0500
Subject: [PATCH 06/15] Split product alteration into load_variant and view stages. Add 'uc_cart_item' entity.

---
 uc_attribute/uc_attribute.module                   |    8 +-
 uc_cart/uc_cart.module                             |  123 ++++++++++++++------
 uc_cart/uc_cart_checkout_pane.inc                  |    7 +-
 uc_order/uc_order.module                           |    1 +
 uc_order/uc_order.order_pane.inc                   |   74 +-----------
 uc_product/uc_product.module                       |   46 +++++---
 .../uc_product_handler_field_display_price.inc     |    5 +-
 uc_product_kit/uc_product_kit.module               |   70 +++++++-----
 uc_store/uc_store.module                           |    9 +-
 uc_taxes/uc_taxes.module                           |   48 ++++++--
 10 files changed, 215 insertions(+), 176 deletions(-)

diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index 7eb435e..eb427b7 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -1257,18 +1257,18 @@ function _uc_attribute_alter_form($product) {
     $options = array();
     foreach ($attribute->options as $option) {
       $display_price = '';
-      $variant = uc_product_load_variant($product, array('attributes' => array($attribute->aid => $option->oid)));
+      $variant = node_view(uc_product_load_variant($product->nid, array('attributes' => array($attribute->aid => $option->oid))));
       switch (variable_get('uc_attribute_option_price_format', 'adjustment')) {
         case 'total':
           if (in_array($attribute->aid, $priced_attributes)) {
-            $display_price = uc_currency_format($variant->price + $variant->display_price_adjustment);
+            $display_price = uc_currency_format($variant['display_price']['#value']);
           }
           if (count($priced_attributes) == 1 && $attribute->display != 3) {
             break;
           }
         case 'adjustment':
-          $base = uc_product_load_variant($product);
-          $adjustment = ($variant->price + $variant->display_price_adjustment) - ($base->price + $base->display_price_adjustment);
+          $base = node_view(uc_product_load_variant($product->nid));
+          $adjustment = $variant['display_price']['#value'] - $base['display_price']['#value'];
           if ($adjustment) {
             $display_price = ($adjustment > 0 ? '+' : '') . uc_currency_format($adjustment);
           }
diff --git a/uc_cart/uc_cart.module b/uc_cart/uc_cart.module
index a6b3535..76918cc 100644
--- a/uc_cart/uc_cart.module
+++ b/uc_cart/uc_cart.module
@@ -303,10 +303,11 @@ function uc_cart_block_view($delta = '') {
       $total = 0;
 
       if ($product_count) {
-        foreach (uc_cart_get_contents() as $item) {
-          $display_item = module_invoke($item->module, 'uc_cart_display', $item);
+        $display_items = entity_view('uc_cart_item', uc_cart_get_contents());
+        foreach (element_children($display_items['uc_cart_item']) as $key) {
+          $display_item = $display_items['uc_cart_item'][$key];
 
-          if (!empty($display_item)) {
+          if (count(element_children($display_item))) {
             $items[] = array(
               'nid' => $display_item['nid']['#value'],
               'qty' => t('@qty&times;', array('@qty' => $display_item['qty']['#default_value'])),
@@ -315,9 +316,9 @@ function uc_cart_block_view($delta = '') {
               'desc' => isset($display_item['description']['#markup']) ? $display_item['description']['#markup'] : FALSE,
             );
             $total += $display_item['#total'];
+            $item_count += $display_item['qty']['#default_value'];
           }
 
-          $item_count += $item->qty;
         }
       }
 
@@ -780,6 +781,7 @@ function theme_uc_empty_cart() {
   return '<p class="uc-cart-empty">' . t('There are no products in your shopping cart.') . '</p>';
 }
 
+
 /**
  * Displays a page allowing the customer to view the contents of his or her cart.
  *
@@ -803,20 +805,16 @@ function uc_cart_view_form($form, &$form_state, $items = NULL) {
   );
 
   $i = 0;
-  foreach ($items as $item) {
-    $display_item = module_invoke($item->module, 'uc_cart_display', $item);
-    if (!empty($display_item)) {
+  $display_items = entity_view('uc_cart_item', $items);
+  foreach (element_children($display_items['uc_cart_item']) as $key) {
+    $display_item = $display_items['uc_cart_item'][$key];
+    if (count(element_children($display_item))) {
       $form['items'][$i] = $display_item;
       $form['items'][$i]['image'] = uc_product_get_picture($display_item['nid']['#value'], 'uc_cart');
 
       $description = $display_item['title']['#markup'] . $display_item['description']['#markup'];
       $form['items'][$i]['desc']['#markup'] = $description;
 
-      $form['items'][$i]['cart_item_id'] = array(
-        '#type' => 'hidden',
-        '#value' => $item->cart_item_id,
-      );
-
       if (isset($form['items'][$i]['remove'])) {
         // Backward compatibility with old checkbox method.
         if ($form['items'][$i]['remove']['#type'] == 'checkbox') {
@@ -840,12 +838,11 @@ function uc_cart_view_form($form, &$form_state, $items = NULL) {
         '#theme' => 'uc_price',
         '#price' => $display_item['#total'],
       );
-      if (!empty($display_item['#suffix'])) {
-        $form['items'][$i]['total']['#suffix'] = $display_item['#suffix'];
-        unset($form['items'][$i]['#suffix']);
+      if (!empty($display_item['#suffixes'])) {
+        $form['items'][$i]['total']['#suffixes'] = $display_item['#suffixes'];
       }
-      $i++;
     }
+    $i++;
   }
 
   $form['items'] = tapir_get_table('uc_cart_view_table', $form['items']);
@@ -1282,14 +1279,30 @@ function uc_cart_get_contents($cid = NULL, $action = NULL) {
   }
 
   if (!isset($items[$cid])) {
-    $items[$cid] = array();
-    $result = db_query("SELECT * FROM {uc_cart_products} WHERE cart_id = :id ORDER BY cart_item_id ASC", array(':id' => $cid));
-
-    foreach ($result as $item) {
-      if ($item = uc_cart_get_item($item)) {
-        $items[$cid][] = $item;
+    // Find all cart items associated with this cart.
+    $efq = new EntityFieldQuery();
+    $result = $efq->entityCondition('entity_type', 'uc_cart_item')
+      ->propertyCondition('cart_id', $cid)
+      ->propertyOrderBy('cart_item_id', 'ASC')
+      ->execute();
+    if (!empty($result['uc_cart_item'])) {
+      $items[$cid] = entity_load('uc_cart_item', array_keys($result['uc_cart_item']), NULL, TRUE);
+      // Create a bare order and attach it to each item as context.
+      $order = new UcOrder();
+      $order->uid = $cid;
+      if ($account = user_load($cid)) {
+        $order->primary_email = $account->mail;
+      }
+      $order->order_status = uc_order_state_default('in_checkout');
+      $order->created = REQUEST_TIME;
+      $order->modified = REQUEST_TIME;
+      foreach ($items[$cid] as $item) {
+        $item->order = $order;
       }
     }
+    else {
+      $items[$cid] = array();
+    }
 
     // Allow other modules a chance to alter the fully loaded cart object.
     drupal_alter('uc_cart', $items[$cid]);
@@ -1340,24 +1353,32 @@ function uc_cart_get_total_qty($cid = NULL) {
 }
 
 /**
- * Allows us to get one single line item in a cart.
- *
- * @param $item
- *   Either an item row from the database, or the cart item ID to fetch.
- *
- * @return
- *   Fully loaded cart item or NULL if item not found.
+ * Entity controller for cart items.
  */
-function uc_cart_get_item($item) {
-  if (!is_object($item)) {
-    $item = db_query("SELECT * FROM {uc_cart_products} WHERE cart_item_id = :id", array(':id' => $item))->fetchObject();
+class UcCartItemController extends EntityAPIController {
+
+  public function buildContent($item, $view_mode = 'full', $langcode = NULL, $content = array()) {
+    $content = array();
+    $content += module_invoke($item->module, 'uc_cart_display', $item);
+    if (!empty($content)) {
+      $content['cart_item_id'] = array(
+				'#type' => 'hidden',
+				'#value' => $item->cart_item_id,
+      );
+    }
+    return parent::buildContent($item, $view_mode, $langcode, $content);
   }
 
-  if ($item && $product = uc_product_load_variant($item->nid, unserialize($item->data))) {
-    $product->cart_item_id = $item->cart_item_id;
-    $product->qty = $item->qty;
-    $product->module = $product->data['module'];
-    return $product;
+  public function attachLoad(&$items, $revision_id = FALSE) {
+    foreach ($items as &$item) {
+      $product = uc_product_load_variant($item->nid, $item->data);
+      // Merge in fields from the product.
+      foreach ($product as $key => $value) {
+        $item->$key = $value;
+      }
+      $item->module = $item->data['module'];
+    }
+    parent::attachLoad($items, $revision_id);
   }
 }
 
@@ -1664,3 +1685,31 @@ function uc_cart_filter_checkout_panes($panes, $remove) {
 
   return $panes;
 }
+
+
+
+/**
+ * Implements hook_entity_info().
+ */
+function uc_cart_entity_info() {
+  return array(
+    'uc_cart_item' => array(
+      'label' => t('Cart item'),
+      'base table' => 'uc_cart_products',
+      'controller class' => 'UcCartItemController',
+      'entity keys' => array(
+        'id' => 'cart_item_id',
+      ),
+      'bundles' => array(
+        'uc_cart_item' => array(
+          'label' => t('Cart item'),
+        ),
+      ),
+      // Entity API callbacks
+      // !TODO Implement these callbacks.
+      //'access callback' => 'uc_cart_item_access',
+      //'save callback'   => 'uc_cart_item_save',
+      //'delete callback' => 'uc_order_product_delete',
+    ),
+  );
+}
diff --git a/uc_cart/uc_cart_checkout_pane.inc b/uc_cart/uc_cart_checkout_pane.inc
index 2cea671..f273f97 100644
--- a/uc_cart/uc_cart_checkout_pane.inc
+++ b/uc_cart/uc_cart_checkout_pane.inc
@@ -489,9 +489,10 @@ function theme_uc_cart_review_table($variables) {
   );
 
   // Set up table rows.
-  foreach ($items as $item) {
-    $display_item = module_invoke($item->module, 'uc_cart_display', $item);
-    if (!empty($display_item)) {
+  $display_items = entity_view('uc_cart_item', $items);
+  foreach (element_children($display_items['uc_cart_item']) as $key) {
+    $display_item = $display_items['uc_cart_item'][$key];
+    if (count(element_children($display_item))) {
       $total = $display_item['#total'];
       $subtotal += $total;
       $description = $display_item['title']['#markup'];
diff --git a/uc_order/uc_order.module b/uc_order/uc_order.module
index 3244055..4d9c32b 100644
--- a/uc_order/uc_order.module
+++ b/uc_order/uc_order.module
@@ -1229,6 +1229,7 @@ class UcOrderController extends DrupalDefaultEntityController {
     foreach ($orders as &$order) {
       $order->data = unserialize($order->data);
 
+      // !FIXME Refactor to use uc_order_product_load().
       $result = db_query("SELECT * FROM {uc_order_products} WHERE order_id = :id ORDER BY order_product_id", array(':id' => $order->order_id));
       $order->products = array();
       foreach ($result as $product) {
diff --git a/uc_order/uc_order.order_pane.inc b/uc_order/uc_order.order_pane.inc
index 77774c7..e7c8191 100644
--- a/uc_order/uc_order.order_pane.inc
+++ b/uc_order/uc_order.order_pane.inc
@@ -1065,6 +1065,7 @@ function uc_order_view_update_form_submit($form, &$form_state) {
 
 /**
  * Builds the order view products table.
+ * !FIXME Refactor to use uc_order_product_view.
  */
 function uc_op_products_view_table($order) {
   $table = array(
@@ -1120,40 +1121,6 @@ function uc_op_products_view_table($order) {
 
   if (is_array($order->products)) {
     foreach ($order->products as $product) {
-      $data = array();
-      $data['qty'] = array(
-        '#markup' => $product->qty . '&times;',
-        '#cell_attributes' => array('align' => 'right'),
-      );
-      $node = node_load($product->nid);
-      $title = node_access('view', $node) ? l($product->title, 'node/'. $node->nid) : check_plain($product->title);
-      $data['product'] = array(
-        '#markup' => $title . uc_product_get_description($product),
-      );
-      $data['model'] = array(
-        '#markup' => check_plain($product->model),
-        '#cell_attributes' => array('align' => 'center'),
-      );
-
-      if (user_access('administer products')) {
-        $data['cost'] = array(
-          '#theme' => 'uc_price',
-          '#price' => $product->cost,
-          '#cell_attributes' => array('align' => 'right'),
-        );
-      }
-
-      $data['price'] = array(
-        '#theme' => 'uc_price',
-        '#price' => $product->price,
-        '#cell_attributes' => array('align' => 'right'),
-      );
-      $data['total'] = array(
-        '#theme' => 'uc_price',
-        '#price' => $product->price * $product->qty,
-        '#cell_attributes' => array('align' => 'right'),
-      );
-      $data['#attributes'] = array('valign' => 'top');
       $table['#rows'][] = $data;
     }
   }
@@ -1169,6 +1136,7 @@ function uc_op_products_view_table($order) {
 
 /**
  * Builds the order customer's view products table.
+ * !FIXME Refactor to use uc_order_product_view.
  */
 function uc_op_products_customer_table($order) {
   $table = array(
@@ -1223,43 +1191,7 @@ function uc_op_products_customer_table($order) {
   );
 
   if (is_array($order->products)) {
-    foreach ($order->products as $product) {
-      $data = array();
-      $data['qty'] = array(
-        '#markup' => $product->qty . '&times;',
-        '#cell_attributes' => array('align' => 'right'),
-      );
-      $node = node_load($product->nid);
-      $title = node_access('view', $node) ? l($product->title, 'node/'. $node->nid) : check_plain($product->title);
-      $data['product'] = array(
-        '#markup' => $title . uc_product_get_description($product),
-      );
-      $data['model'] = array(
-        '#markup' => check_plain($product->model),
-        '#cell_attributes' => array('align' => 'center'),
-      );
-
-      if (user_access('administer products')) {
-        $data['cost'] = array(
-          '#theme' => 'uc_price',
-          '#price' => $product->cost,
-          '#cell_attributes' => array('align' => 'right'),
-        );
-      }
-
-      $data['price'] = array(
-        '#theme' => 'uc_price',
-        '#price' => $product->price,
-        '#cell_attributes' => array('align' => 'right'),
-      );
-      $data['total'] = array(
-        '#theme' => 'uc_price',
-        '#price' => $product->price * $product->qty,
-        '#cell_attributes' => array('align' => 'right'),
-      );
-      $data['#attributes'] = array('valign' => 'top');
-      $table['#rows'][] = $data;
-    }
+    $table['#rows'] = entity_view('uc_order_product', $order->products);
   }
   else {
     $table['#rows'][]['product'] = array(
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 1bdd8b7..5df140c 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -216,7 +216,7 @@ function uc_product_theme() {
       'variables' => array('body' => '', 'view_mode' => 'full'),
     ),
     'uc_product_add_to_cart' => array(
-      'variables' => array('form' => NULL, 'view_mode' => 'full'),
+      'variables' => array('node' => NULL, 'view_mode' => 'full'),
     ),
     'uc_product_price' => array(
       'render element' => 'element',
@@ -640,25 +640,27 @@ function uc_product_load($nodes) {
       $nodes[$node->nid]->$field = $value;
     }
     $nodes[$node->nid]->price = $nodes[$node->nid]->sell_price;
-    $nodes[$node->nid]->display_price_adjustment = 0;
-    $nodes[$node->nid]->display_price_suffixes = array();
   }
 }
 
 /**
- * Gets a specific altered variant of a product.
- * 
+ * Gets a specific altered variant of a product.  The (possibly cached) base product remains unaltered.
+ *
  * @param $nid
- *   The nid of the product.  May also supply a complete, previously loaded product object.
+ *   The nid of the product to load.
  * @param $data
  *   Optional data to add to the product before invoking the alter hooks.
+ *
+ * @return
+ *   An variant of the product, altered based on the provided data.
  */
 function uc_product_load_variant($nid, $data = FALSE) {
-  $node = clone (is_object($nid) ? $nid : node_load($nid));
+  $node = clone node_load($nid);
   if (!empty($data)) {
     $node->data = $data;
   }
   drupal_alter('uc_product', $node);
+  $node->variant = TRUE;
   return $node;
 }
 
@@ -689,11 +691,15 @@ function uc_product_delete(&$node) {
  * Implements hook_view().
  */
 function uc_product_view($node, $view_mode) {
-  $variant = uc_product_load_variant($node);
+  // Give modules a chance to alter this product.  If it is a variant, this will have been
+  // done already by uc_product_load_variant(), so we check a flag to be sure not to
+  // alter twice -- cf. entity_prepare_view().
+  $variant = empty($node->variant) ? uc_product_load_variant($node->nid) : $node;
+
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
-    '#value' => $variant->price + $variant->display_price_adjustment,
-    'suffix' => array('#markup' => implode(' ', $variant->display_price_suffixes)),
+    '#value' => $variant->price,
+    '#suffixes' => array(),
     '#attributes' => array(
       'class' => array(
         'display-price',
@@ -750,13 +756,15 @@ function uc_product_view($node, $view_mode) {
     '#units' => $variant->length_units,
     '#view_mode' => $view_mode,
   );
-  if (module_exists('uc_cart') && isset($node->nid)) {
+
+  if (module_exists('uc_cart') && isset($variant->nid)) {
     $node->content['add_to_cart'] = array(
       '#theme' => 'uc_product_add_to_cart',
-      '#form' => drupal_get_form('uc_product_add_to_cart_form_' . $node->nid, $node),
       '#view_mode' => $view_mode,
+      '#node' => $variant,
     );
   }
+  $node->content['#node'] = $variant;
 
   return $node;
 }
@@ -1052,8 +1060,8 @@ function uc_product_uc_cart_display($item) {
     '#markup' => node_access('view', $node) ? l($item->title, 'node/' . $node->nid) : check_plain($item->title),
   );
 
-  $element['#total'] = ($item->price + $item->display_price_adjustment) * $item->qty;
-  $element['#suffix'] = implode(' ', $item->display_price_suffixes);
+  $element['#total'] = $item->price * $item->qty;
+  $element['#suffixes'] = array();
   $element['data'] = array('#type' => 'hidden', '#value' => serialize($item->data));
   $element['qty'] = array(
     '#type' => 'uc_quantity',
@@ -1233,8 +1241,10 @@ function theme_uc_product_body($variables) {
  * @ingroup themeable
  */
 function theme_uc_product_add_to_cart($variables) {
+  $node = $variables['node'];
+
   $output = '<div class="add-to-cart">';
-  $output .= drupal_render($variables['form']);
+  $output .= drupal_render(drupal_get_form('uc_product_add_to_cart_form_' . $node->nid, $node));
   $output .= '</div>';
 
   return $output;
@@ -1322,7 +1332,11 @@ function theme_uc_product_price($variables) {
   if ($label) {
     $output .= '<span class="uc-price-label">' . $label . '</span> ';
   }
-  $output .= theme('uc_price', array('price' => $price));
+  $vars = array('price' => $price);
+  if (!empty($element['#suffixes'])) {
+    $vars['suffixes'] = $element['#suffixes'];
+  }
+  $output .= theme('uc_price', $vars);
   $output .= drupal_render_children($element);
   $output .= '</div>';
 
diff --git a/uc_product/views/uc_product_handler_field_display_price.inc b/uc_product/views/uc_product_handler_field_display_price.inc
index b7de82b..7517c5a 100644
--- a/uc_product/views/uc_product_handler_field_display_price.inc
+++ b/uc_product/views/uc_product_handler_field_display_price.inc
@@ -20,7 +20,8 @@ class uc_product_handler_field_display_price extends uc_product_handler_field_pr
 
   function get_value($values, $field = NULL) {
     $nid = parent::get_value($values, $field);
-    $node = node_load($nid);
-    return $node->price + $node->display_price_adjustment;
+    // !TODO Refactor so that all variants are loaded at once in the pre_render hook.
+    $node = node_view(node_load($nid));
+    return $node['display_price']['#value'];
   }
 }
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index f5a58c9..d7f7ede 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -381,18 +381,6 @@ function uc_product_kit_load($nodes) {
 
   // Add product data to kits.
   uc_product_load($nodes);
-
-  // Recalculate display price.
-  foreach ($nodes as &$node) {
-    if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
-      $node->display_price_adjustment = 0;
-      $node->display_price_suffixes = array();
-      foreach ($node->products as $pid => &$product) {
-        $node->display_price_adjustment += $product->display_price_adjustment * $product->qty;
-        $node->display_price_suffixes += $product->display_price_suffixes;
-      }
-    }
-  }
 }
 
 /**
@@ -677,11 +665,31 @@ function theme_uc_product_kit_items_form($variables) {
  * Implements hook_view().
  */
 function uc_product_kit_view($node, $view_mode) {
-  $variant = uc_product_load_variant($node);
+  // Give modules a chance to alter this product.  If it is a variant, this will have been done
+  // already by uc_product_load_variant(), so we check a flag to be sure not to alter twice.
+  $variant = empty($node->variant) ? uc_product_load_variant($node->nid) : $node;
+
+  // Calculate the display price.
+  $display_price = 0;
+  $suffixes = array();
+  if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
+    // If this is a non-mutable kit, then sum the display price of each of the component products.
+    foreach ($variant->products as $product) {
+      $build = node_view($product);
+      $display_price += $build['display_price']['#value'] * $product->qty;
+      $suffixes += $build['display_price']['#suffixes'];
+    }
+  }
+  else {
+    // For mutable, just use the price.
+    $display_price = $variant->price;
+    $suffixes = array();
+  }
+
   $node->content['display_price'] = array(
     '#theme' => 'uc_product_price',
-    '#value' => $variant->price + $variant->display_price_adjustment,
-    'suffix' => array('#markup' => implode(' ', $variant->display_price_suffixes)),
+    '#value' => $display_price,
+    '#suffixes' => $suffixes,
     '#attributes' => array(
       'class' => array(
         'product-kit',
@@ -739,7 +747,7 @@ function uc_product_kit_view($node, $view_mode) {
     $node->content['products'] = array('#weight' => 6);
 
     $i = 0;
-    foreach ($variant->products as $product) {
+    foreach ($node->products as $product) {
       $node->content['products'][$product->nid]['qty'] = array(
         '#markup' => '<div class="product-qty">' . theme('uc_product_kit_list_item', array('product' => $product)) . '</div>',
       );
@@ -749,12 +757,12 @@ function uc_product_kit_view($node, $view_mode) {
 
   if (module_exists('uc_cart')) {
     $node->content['add_to_cart'] = array(
-      '#theme' => 'uc_product_kit_add_to_cart',
-      '#node' => $node,
-      '#view_mode' => $view_mode,
+			'#theme' => 'uc_product_kit_add_to_cart',
+			'#node' => $variant,
+			'#view_mode' => $view_mode,
     );
   }
-
+  $node->content['#node'] = $variant;
   return $node;
 }
 
@@ -1030,20 +1038,20 @@ function uc_product_kit_uc_add_to_cart($nid, $qty, $kit_data) {
 /**
  * Implements hook_uc_product_alter().
  */
-function uc_product_kit_uc_product_alter(&$node) {
-  if (isset($node->data['kit_id'])) {
+function uc_product_kit_uc_product_alter(&$variant) {
+  if (isset($variant->data['kit_id'])) {
     // If this is a kit component load, we would cause infinite recursion trying
     // to node_load() the parent, but we already have the discount available.
-    if (isset($node->data['kit_discount'])) {
-      $discount = $node->data['kit_discount'];
+    if (isset($variant->data['kit_discount'])) {
+      $discount = $variant->data['kit_discount'];
     }
-    elseif (($kit = node_load($node->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
-      $discount = $kit->products[$node->nid]->discount;
+    elseif (($kit = node_load($variant->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
+      $discount = $kit->products[$variant->nid]->discount;
     }
     else {
       $discount = 0;
     }
-    $node->price += $discount;
+    $variant->price += $discount;
   }
 }
 
@@ -1092,6 +1100,7 @@ function uc_product_kit_uc_order_product_alter(&$product, $order) {
   // Don't save the base kit node, though.
   $product->skip_save = TRUE;
 }
+
 /**
  * Implements hook_uc_cart_display().
  *
@@ -1135,8 +1144,9 @@ function uc_product_kit_uc_cart_display($item) {
       );
       $element['description'] = array('#markup' => '');
       $element['#total'] = 0;
-      $element['#suffix'] = implode(' ', $kit->display_price_suffixes);
+      $element['#suffixes'] = array();
       $element['#extra'] = array();
+      $element['#entity'] = $kit; // Override the entity associated with this render-array to be the kit itself.
 
       $elements[$unique_id] = $element;
     }
@@ -1150,7 +1160,9 @@ function uc_product_kit_uc_cart_display($item) {
       );
     }
 
-    $elements[$unique_id]['#total'] += ($item->price + $item->display_price_adjustment) * $item->qty;
+    $build = node_view($item);
+    $elements[$unique_id]['#total'] += $build['display_price']['#value'] * $item->qty;
+    $elements[$unique_id]['#suffixes'] += $build['display_price']['#suffixes'];
     $elements[$unique_id]['data'][$item->nid] = $item;
     $products[$unique_id][] = $item->nid;
 
diff --git a/uc_store/uc_store.module b/uc_store/uc_store.module
index 158ada3..d95cfd9 100644
--- a/uc_store/uc_store.module
+++ b/uc_store/uc_store.module
@@ -516,7 +516,7 @@ function uc_store_theme() {
       'render element' => 'element',
     ),
     'uc_price' => array(
-      'variables' => array('price' => 0),
+      'variables' => array('price' => 0, 'suffixes' => array()),
     ),
     'uc_qty_label' => array(
       'variables' => array(),
@@ -699,11 +699,16 @@ function uc_store_reviews() {
  * @param $variables
  *   An associative array containing:
  *   - price: A numerical price value.
+ *   - suffixes: An array of suffixes to be attached to this price.
  *
  * @ingroup themeable
  */
 function theme_uc_price($variables) {
-  return '<span class="uc-price">' . uc_currency_format($variables['price']) . '</span>';
+  $output = '<span class="uc-price">' . uc_currency_format($variables['price']) . '</span>';
+  if (!empty($variables['suffixes'])) {
+    $output .= '<span class="price-suffixes">'. implode(' ', $variables['suffixes']) .'</span>';
+  }
+  return $output;
 }
 
 /**
diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index f26841d..1bba68a 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -76,7 +76,7 @@ function uc_taxes_menu() {
  * calculations are made.
  */
 function uc_taxes_module_implements_alter(&$implementations, $hook) {
-  if (in_array($hook, array('uc_order', 'uc_product_alter'))) {
+  if (in_array($hook, array('uc_order', 'entity_view_alter'))) {
     $group = $implementations['uc_taxes'];
     unset($implementations['uc_taxes']);
     $implementations['uc_taxes'] = $group;
@@ -105,16 +105,43 @@ function uc_taxes_form_uc_order_edit_form_alter(&$form, &$form_state) {
 }
 
 /**
- * Implements hook_uc_product_alter().
+ * Implements hook_entity_view().
  *
  * Adds included taxes (VAT) to display price of applicable products.
  */
-function uc_taxes_uc_product_alter(&$node) {
-  list($amount, $suffixes) = uc_taxes_get_included_tax($node, $node->price);
+function uc_taxes_entity_view_alter(&$build, $entity_type) {
+  switch ($entity_type) {
+    case 'node':
+      if (uc_product_is_product($build['#node'])) {
+        list($amount, $suffixes) = uc_taxes_get_included_tax($build['#node']);
+        $build['display_price']['#value'] += $amount;
+        if (!empty($suffixes)) {
+          $build['display_price']['#suffixes'] += $suffixes;
+        }
+      }
+      break;
+
+    case 'uc_cart_item':
+      if (!empty($build['#total'])) {
+        list($amount, $suffixes) = uc_taxes_get_included_tax($build['#entity']);
+        if (!empty($amount)) {
+          $build['#total'] += $amount * $build['qty']['#default_value'];
+        }
+        if (!empty($suffixes)) {
+          $build['#suffixes'] += $suffixes;
+        }
+      }
+      break;
 
-  $node->display_price_adjustment += $amount;
-  if (!empty($suffixes)) {
-    $node->display_price_suffixes += $suffixes;
+    case 'uc_order_product':
+      list($amount, $suffixes) = uc_taxes_get_included_tax($build['#entity']);
+      $build['price']['#price'] += $amount;
+      $build['total']['#total'] += $amount * $build['#entity']->qty;
+      if (!empty($suffixes)) {
+        $build['price']['#suffixes'] += $suffixes;
+        $build['total']['#suffixes'] += $suffixes;
+      }
+      break;
   }
 }
 
@@ -598,16 +625,13 @@ function uc_taxes_apply_tax($order, $tax) {
  * Calculates the taxes that should be included in the display price of a product.
  * @param $product
  *   The product whose included taxes are to be calculated.
- * @param $price
- *   If specified, use this value as the tax-base.  Otherwise use the product sell price.
  */
-function uc_taxes_get_included_tax($product, $price = FALSE) {
+function uc_taxes_get_included_tax($product) {
   $amount = 0;
   $suffixes = array();
-  $price = ($price === FALSE) ? $product->sell_price : $price;
   foreach (uc_taxes_rate_load() as $tax) {
     if ($tax->display_include && in_array($product->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $product->shippable == 1)) {
-      $amount += $price * $tax->rate;
+      $amount += $product->price * $tax->rate;
       $suffixes[$tax->inclusion_text] = $tax->inclusion_text;
     }
   }
-- 
1.7.3.4


From 932ee641e39cc7e71ced7491853b7aa072f1a894 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 7 Dec 2011 14:22:05 -0500
Subject: [PATCH 07/15] Add uc_qty theme

---
 uc_cart/uc_cart.module            |    2 +-
 uc_cart/uc_cart_checkout_pane.inc |    4 ++--
 uc_store/uc_store.module          |   15 +++++++++++++++
 3 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/uc_cart/uc_cart.module b/uc_cart/uc_cart.module
index 76918cc..8db8355 100644
--- a/uc_cart/uc_cart.module
+++ b/uc_cart/uc_cart.module
@@ -310,7 +310,7 @@ function uc_cart_block_view($delta = '') {
           if (count(element_children($display_item))) {
             $items[] = array(
               'nid' => $display_item['nid']['#value'],
-              'qty' => t('@qty&times;', array('@qty' => $display_item['qty']['#default_value'])),
+              'qty' => theme('uc_qty', array('qty' => $display_item['qty']['#default_value'])),
               'title' => $display_item['title']['#markup'],
               'price' => $display_item['#total'],
               'desc' => isset($display_item['description']['#markup']) ? $display_item['description']['#markup'] : FALSE,
diff --git a/uc_cart/uc_cart_checkout_pane.inc b/uc_cart/uc_cart_checkout_pane.inc
index f273f97..4c22d8f 100644
--- a/uc_cart/uc_cart_checkout_pane.inc
+++ b/uc_cart/uc_cart_checkout_pane.inc
@@ -499,8 +499,8 @@ function theme_uc_cart_review_table($variables) {
       if (!empty($display_item['description']['#markup'])) {
         $description .= $display_item['description']['#markup'];
       }
-      $qty = t('@qty&times;', array('@qty' => $display_item['qty']['#default_value']));
-      $suffix = !empty($display_item['#suffix']) ? $display_item['#suffix'] : '';
+      $qty = theme('uc_qty', array('qty' => $display_item['qty']['#default_value']));
+      $suffix = !empty($display_item['#suffixes']) ? implode(' ', $display_item['#suffixes']) : '';
 
       $rows[] = array(
         array('data' => $qty, 'class' => array('qty')),
diff --git a/uc_store/uc_store.module b/uc_store/uc_store.module
index d95cfd9..98b9d69 100644
--- a/uc_store/uc_store.module
+++ b/uc_store/uc_store.module
@@ -521,6 +521,9 @@ function uc_store_theme() {
     'uc_qty_label' => array(
       'variables' => array(),
     ),
+    'uc_qty' => array(
+      'variables' => array('qty' => 1),
+    ),
     'uc_uid' => array(
       'variables' => array('uid' => 0),
     ),
@@ -721,6 +724,18 @@ function theme_uc_qty_label() {
 }
 
 /**
+ * Displays a quantity.
+ *
+ * @param $variables
+ *   -qty = The quantity to display.
+ *
+ * @ingroup themeable
+ */
+function theme_uc_qty($variables) {
+  return t('@qty &times;', array('@qty' => $variables['qty']));
+}
+
+/**
  * Displays a username in the standard format and with consistent markup.
  *
  * @param $variables
-- 
1.7.3.4


From 779f0c2bd8644b52ddfb51aa9b2ba24e53969ce9 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 7 Dec 2011 14:22:37 -0500
Subject: [PATCH 08/15] Implement product_alter mechanism for order products.

---
 uc_cart/uc_cart_checkout_pane.inc    |   14 ++++--
 uc_order/uc_order.info.inc           |   87 +++++++++++----------------------
 uc_order/uc_order.module             |   69 ++++++++++++++++++++++----
 uc_order/uc_order.order_pane.inc     |   36 +++++++-------
 uc_product/uc_product.module         |   10 +++-
 uc_product_kit/uc_product_kit.module |    1 -
 uc_taxes/uc_taxes.module             |   56 ++++++++++++++++------
 7 files changed, 163 insertions(+), 110 deletions(-)

diff --git a/uc_cart/uc_cart_checkout_pane.inc b/uc_cart/uc_cart_checkout_pane.inc
index 4c22d8f..0af60f2 100644
--- a/uc_cart/uc_cart_checkout_pane.inc
+++ b/uc_cart/uc_cart_checkout_pane.inc
@@ -552,11 +552,15 @@ function theme_uc_cart_review_table($variables) {
  */
 function theme_uc_checkout_pane_cart_review($variables) {
   $output = '<table>';
-  foreach ($variables['items'] as $item) {
-    $desc = check_plain($item->title) . uc_product_get_description($item);
-
-    $output .= '<tr valign="top"><td>' . $item->qty . '&times;</td><td width="100%">' . $desc
-              . '</td><td nowrap="nowrap">' . theme('uc_price', array('price' => $item->price * $item->qty)) . '</td></tr>';
+  $display_items = entity_view('uc_order_product', $variables['items']);
+  foreach (element_children($display_items['uc_order_product']) as $key) {
+    $display_item = $display_items['uc_order_product'][$key];
+    $output .= '<tr valign="top"><td nowrap="nowrap">'
+              . render($display_item['qty'])
+              .'</td><td width="100%">'
+              . render($display_item['product'])
+              . '</td><td nowrap="nowrap">'
+              . render($display_item['total']) . '</td></tr>';
   }
   $output .= '</table>';
 
diff --git a/uc_order/uc_order.info.inc b/uc_order/uc_order.info.inc
index 2e45576..214fcd2 100644
--- a/uc_order/uc_order.info.inc
+++ b/uc_order/uc_order.info.inc
@@ -12,64 +12,6 @@ function uc_order_entity_property_info() {
   $address_info = uc_address_property_info();
 
   return array(
-    'uc_order_product' => array(
-      'properties' => array(
-        'order_product_id' => array(
-          'type' => 'integer',
-          'label' => t('Order product ID'),
-          'description' => t('The unique ID for the purchased product.'),
-        ),
-        'order_id' => array(
-          'type' => 'integer',
-          'label' => t('Order ID'),
-          'description' => t('The order ID that owns the product.'),
-          'required' => TRUE,
-        ),
-        'nid' => array(
-          'type' => 'integer',
-          'label' => t('Node ID'),
-          'description' => t('The unique ID of the node representing the product.'),
-          'clear' => array('node'),
-        ),
-        'node' => array(
-          'type' => 'node',
-          'label' => t('Node'),
-          'description' => t('The node representing the product.'),
-          'getter callback' => 'uc_order_product_node_property_get',
-          'setter callback' => 'uc_order_product_node_property_set',
-        ),
-        'title' => array(
-          'type' => 'text',
-          'label' => t('Title'),
-          'description' => t('The product title.'),
-        ),
-        'model' => array(
-          'type' => 'text',
-          'label' => t('Model/SKU'),
-          'description' => t('The model number of the product.'),
-        ),
-        'qty' => array(
-          'type' => 'integer',
-          'label' => t('Quantity'),
-          'description' => t('The number of the same product ordered.'),
-        ),
-        'cost' => array(
-          'type' => 'decimal',
-          'label' => t('Cost'),
-          'description' => t('The cost to the store for the product.'),
-        ),
-        'price' => array(
-          'type' => 'decimal',
-          'label' => t('Price'),
-          'description' => t('The price paid for the ordered product.'),
-        ),
-        'weight' => array(
-          'type' => 'decimal',
-          'label' => t('Weight'),
-          'description' => t('The physical weight of the product.'),
-        ),
-      ),
-    ),
     'uc_order' => array(
       'properties' => array(
         'order_id' => array(
@@ -171,3 +113,32 @@ function uc_order_entity_property_info() {
     ),
   );
 }
+
+/**
+ * Metadata controller class for uc_order_product entities.
+ */
+class UcOrderProductMetadataController extends EntityDefaultMetadataController {
+  public function entityPropertyInfo() {
+    $props = parent::entityPropertyInfo();
+    // Copy the descriptions from the schema. Drupal discards this information, so we have to
+    // call uc_order_schema() directly.
+    module_load_include('install', 'uc_order', 'uc_order');
+    $schema = uc_order_schema();
+    foreach ($schema['uc_order_products']['fields'] as $name => $info) {
+      if (is_array($props['uc_order_product']['properties'][$name]) && !empty($info['description'])) {
+        $props['uc_order_product']['properties'][$name]['description'] = $info['description'];
+      }
+    }
+
+    // Add the 'node' property.
+    $props['uc_order_product']['properties']['node'] = array(
+      'type' => 'node',
+      'label' => t('Node'),
+      'description' => t('The node representing the product.'),
+      'getter callback' => 'uc_order_product_node_property_get',
+      'setter callback' => 'uc_order_product_node_property_set',
+    );
+
+    return $props;
+  }
+}
diff --git a/uc_order/uc_order.module b/uc_order/uc_order.module
index 4d9c32b..931c397 100644
--- a/uc_order/uc_order.module
+++ b/uc_order/uc_order.module
@@ -550,6 +550,8 @@ function uc_order_entity_info() {
     'uc_order_product' => array(
       'label' => t('Order product'),
       'base table' => 'uc_order_products',
+      'controller class' => 'UcOrderProductController',
+      'metadata controller class' => 'UcOrderProductMetadataController',
       'entity keys' => array(
         'id' => 'order_product_id',
       ),
@@ -1217,25 +1219,70 @@ function uc_order_view_invoice($order, $print = FALSE) {
   return $build;
 }
 
+
+
+class UcOrderProductController extends EntityAPIController {
+  public function buildContent($product, $view_mode = 'full', $langcode = NULL, $content = array()) {
+    $content['qty'] = array(
+      '#theme' => 'uc_qty',
+      '#qty' => $product->qty,
+    );
+    $node = node_load($product->nid);
+    $title = node_access('view', $node) ? l($product->title, 'node/'. $node->nid) : check_plain($product->title);
+    $content['product'] = array(
+      '#markup' => $title . uc_product_get_description($product),
+    );
+    $content['model'] = array(
+      '#markup' => check_plain($product->model),
+      '#cell_attributes' => array('align' => 'center'),
+    );
+
+    if (user_access('administer products')) {
+      $content['cost'] = array(
+        '#theme' => 'uc_price',
+        '#price' => $product->cost,
+        '#cell_attributes' => array('align' => 'right'),
+      );
+    }
+    $content['price'] = array(
+      '#theme' => 'uc_price',
+      '#price' => $product->price,
+      '#suffixes' => array(),
+      '#cell_attributes' => array('align' => 'right'),
+    );
+    $content['total'] = array(
+      '#theme' => 'uc_price',
+      '#price' => $product->price * $product->qty,
+      '#suffixes' => array(),
+      '#cell_attributes' => array('align' => 'right'),
+    );
+    $content['#attributes'] = array('valign' => 'top');
+    return parent::buildContent($product, $view_mode, $langcode, $content);
+  }
+}
+
 /**
  * Controller class for uc_order entity.
  */
 class UcOrderController extends DrupalDefaultEntityController {
 
-  /**
-   *
-   */
   function attachLoad(&$orders, $revision_id = FALSE) {
     foreach ($orders as &$order) {
       $order->data = unserialize($order->data);
-
-      // !FIXME Refactor to use uc_order_product_load().
-      $result = db_query("SELECT * FROM {uc_order_products} WHERE order_id = :id ORDER BY order_product_id", array(':id' => $order->order_id));
-      $order->products = array();
-      foreach ($result as $product) {
-        $product->data = unserialize($product->data);
-        $product->order_uid = $order->uid;
-        $order->products[] = $product;
+      $efq = new EntityFieldQuery();
+      $result = $efq->entityCondition('entity_type', 'uc_order_product')
+        ->propertyCondition('order_id', $order->order_id)
+        ->propertyOrderBy('order_product_id', 'ASC')
+        ->execute();
+      if (!empty($result['uc_order_product'])) {
+        $order->products = entity_load('uc_order_product', array_keys($result['uc_order_product']), NULL, TRUE);
+        foreach ($order->products as $product) {
+          $product->order = $order;
+          $product->order_uid = $order->uid;
+        }
+      }
+      else {
+        $order->products = array();
       }
 
       uc_order_module_invoke('load', $order, NULL);
diff --git a/uc_order/uc_order.order_pane.inc b/uc_order/uc_order.order_pane.inc
index e7c8191..5634d4b 100644
--- a/uc_order/uc_order.order_pane.inc
+++ b/uc_order/uc_order.order_pane.inc
@@ -376,7 +376,7 @@ function uc_order_add_product_form($form, &$form_state, $order, $node) {
 function uc_order_edit_products_form($form, &$form_state, $products) {
   if (($product_count = count($products)) > 0) {
     $form['products'] = tapir_get_table('uc_op_products_edit_table');
-    for ($i = 0; $i < $product_count; $i++) {
+    for ($i=0, $product = reset($products); $i < $product_count; $i++, $product = next($products)) {
       $form['products'][$i]['remove'] = array(
         '#type' => 'image_button',
         '#title' => t('Remove this product.'),
@@ -385,27 +385,27 @@ function uc_order_edit_products_form($form, &$form_state, $products) {
           'style' => 'padding-top: 1px; padding-left: .8em; padding-right: 2px; float: left; cursor: pointer;',
         ),
         '#submit' => array('uc_order_edit_products_remove', 'uc_order_edit_form_submit'),
-        '#return_value' => $products[$i]->order_product_id,
+        '#return_value' => $product->order_product_id,
       );
       $form['products'][$i]['order_product_id'] = array(
         '#type' => 'hidden',
-        '#value' => $products[$i]->order_product_id,
+        '#value' => $product->order_product_id,
       );
       $form['products'][$i]['nid'] = array(
         '#type' => 'hidden',
-        '#value' => $products[$i]->nid,
+        '#value' => $product->nid,
       );
       $form['products'][$i]['qty'] = array(
         '#type' => 'uc_quantity',
         '#title' => theme('uc_qty_label'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->qty,
+        '#default_value' => $product->qty,
       );
       $form['products'][$i]['title'] = array(
         '#type' => 'textfield',
         '#title' => t('Title'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->title,
+        '#default_value' => $product->title,
         '#size' => 30,
         '#maxlength' => 255,
       );
@@ -413,14 +413,14 @@ function uc_order_edit_products_form($form, &$form_state, $products) {
         '#type' => 'textfield',
         '#title' => t('SKU'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->model,
+        '#default_value' => $product->model,
         '#size' => 6,
       );
       $form['products'][$i]['weight'] = array(
         '#type' => 'textfield',
         '#title' => t('Weight'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->weight,
+        '#default_value' => $product->weight,
         '#size' => 3,
       );
       $units = array(
@@ -433,26 +433,26 @@ function uc_order_edit_products_form($form, &$form_state, $products) {
         '#type' => 'select',
         '#title' => t('Units'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->weight_units,
+        '#default_value' => $product->weight_units,
         '#options' => $units,
       );
       $form['products'][$i]['cost'] = array(
         '#type' => 'uc_price',
         '#title' => t('Cost'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->cost,
+        '#default_value' => $product->cost,
         '#size' => 5,
       );
       $form['products'][$i]['price'] = array(
         '#type' => 'uc_price',
         '#title' => t('Price'),
         '#title_display' => 'invisible',
-        '#default_value' => $products[$i]->price,
+        '#default_value' => $product->price,
         '#size' => 5,
       );
       $form['products'][$i]['data'] = array(
         '#type' => 'hidden',
-        '#value' => serialize($products[$i]->data),
+        '#value' => serialize($product->data),
       );
     }
   }
@@ -1119,10 +1119,9 @@ function uc_op_products_view_table($order) {
     'weight' => 5,
   );
 
-  if (is_array($order->products)) {
-    foreach ($order->products as $product) {
-      $table['#rows'][] = $data;
-    }
+  if (!empty($order->products)) {
+    $build = entity_view('uc_order_product', $order->products);
+    $table['#rows'] = $build['uc_order_product'];
   }
   else {
     $table['#rows'][]['product'] = array(
@@ -1190,8 +1189,9 @@ function uc_op_products_customer_table($order) {
     'weight' => 5,
   );
 
-  if (is_array($order->products)) {
-    $table['#rows'] = entity_view('uc_order_product', $order->products);
+  if (!empty($order->products)) {
+    $build = entity_view('uc_order_product', $order->products);
+    $table['#rows'] = $build['uc_order_product'];
   }
   else {
     $table['#rows'][]['product'] = array(
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 5df140c..26b0ad9 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -1107,10 +1107,16 @@ function uc_product_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL
 function uc_product_uc_add_to_cart_data($form_values) {
   if (isset($form_values['nid'])) {
     $node = node_load($form_values['nid']);
-    return array('shippable' => isset($node->shippable) ? $node->shippable : variable_get('uc_product_shippable_' . $node->type, 1));
+    return array(
+      'shippable' => isset($node->shippable) ? $node->shippable : variable_get('uc_product_shippable_' . $node->type, 1),
+      'type' => $node->type
+    );
   }
   else {
-    return array('shippable' => variable_get('uc_product_shippable_product', 1));
+    return array(
+      'shippable' => variable_get('uc_product_shippable_product', 1),
+      'type' => 'product',
+    );
   }
 }
 
diff --git a/uc_product_kit/uc_product_kit.module b/uc_product_kit/uc_product_kit.module
index d7f7ede..bd64e16 100644
--- a/uc_product_kit/uc_product_kit.module
+++ b/uc_product_kit/uc_product_kit.module
@@ -1070,7 +1070,6 @@ function uc_product_kit_uc_order_product_alter(&$product, $order) {
   if (empty($product->type) || $product->type !== 'product_kit') {
     return;
   }
-
   // Have to save each individual product if this is a kit.
   foreach ($product->products as $kit_product) {
     $kit_product->price = $kit_product->sell_price;
diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index 1bba68a..3325567 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -105,7 +105,7 @@ function uc_taxes_form_uc_order_edit_form_alter(&$form, &$form_state) {
 }
 
 /**
- * Implements hook_entity_view().
+ * Implements hook_entity_view_alter().
  *
  * Adds included taxes (VAT) to display price of applicable products.
  */
@@ -136,7 +136,7 @@ function uc_taxes_entity_view_alter(&$build, $entity_type) {
     case 'uc_order_product':
       list($amount, $suffixes) = uc_taxes_get_included_tax($build['#entity']);
       $build['price']['#price'] += $amount;
-      $build['total']['#total'] += $amount * $build['#entity']->qty;
+      $build['total']['#price'] += $amount * $build['#entity']->qty;
       if (!empty($suffixes)) {
         $build['price']['#suffixes'] += $suffixes;
         $build['total']['#suffixes'] += $suffixes;
@@ -540,22 +540,44 @@ function uc_taxes_uc_calculate_tax($order) {
 }
 
 /**
- * Calculates tax for a single product.
+ * Calculates taxable amount for a single product.
  */
 function uc_taxes_apply_item_tax($item, $tax) {
-  $node = node_load($item->nid);
+  // Determine the product type.
+  if (isset($item->data['type'])) { // Saved in the order product data array.
+    $type = $item->data['type'];
+  }
+  elseif (empty($item->nid)) { // "Blank-line" product.
+    $type = 'blank-line';
+  }
+  elseif ($node = node_load($item->nid)) { // Use type of current node, if it exists.
+    $type = $node->type;
+  }
+  else { // Default to generic product.
+    $type = 'product';
+  }
 
-  // Special handling for manually added "Blank line" products.
-  if (!$node) {
-    $node = new stdClass();
-    $node->type = 'blank-line';
-    $node->shippable = $item->weight > 0;
+  // Determine whether this is a shippable product.
+  if (isset($item->data['shippable'])) { // Saved in the order product data array.
+    $shippable = $item->data['shippable'];
+  }
+  elseif (empty($item->nid)) { // "Blank line" product.
+    $shippable = $item->weight > 0;
+  }
+  elseif ($node = node_load($item->nid)) { // Use current node.
+    $shippable = $node->shippable;
+  }
+  else {
+    $shippable = variable_get('uc_product_shippable_' . $type); // Use default for this node type.
   }
 
   // Tax products if they are of a taxed type and if it is shippable if
   // the tax only applies to shippable products.
-  if (in_array($node->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $node->shippable == 1)) {
-    return $item->price * $item->qty;
+  if (in_array($type, $tax->taxed_product_types) && ($tax->shippable == 0 || $shippable == 1)) {
+    return $item->price;
+  }
+  else {
+    return FALSE;
   }
 }
 
@@ -575,7 +597,7 @@ function uc_taxes_apply_tax($order, $tax) {
   $taxable_amount = 0;
   if (is_array($order->products)) {
     foreach ($order->products as $item) {
-      $taxable_amount += uc_taxes_apply_item_tax($item, $tax);
+      $taxable_amount += $item->qty * uc_taxes_apply_item_tax($item, $tax);
     }
   }
   $taxed_line_items = $tax->taxed_line_items;
@@ -630,10 +652,14 @@ function uc_taxes_get_included_tax($product) {
   $amount = 0;
   $suffixes = array();
   foreach (uc_taxes_rate_load() as $tax) {
-    if ($tax->display_include && in_array($product->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $product->shippable == 1)) {
-      $amount += $product->price * $tax->rate;
-      $suffixes[$tax->inclusion_text] = $tax->inclusion_text;
+    if ($tax->display_include) {
+      $taxable = uc_taxes_apply_item_tax($product, $tax);
+      if (!empty($taxable)) {
+        $amount += $taxable * $tax->rate;
+        $suffixes[$tax->inclusion_text] = $tax->inclusion_text;
+      }
     }
   }
+
   return array($amount, $suffixes);
 }
-- 
1.7.3.4


From 149d01cd3c65a796062b1e50ae014e71da919177 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 7 Dec 2011 15:31:09 -0500
Subject: [PATCH 09/15] Add display-adjusted prices to order invoice.

---
 uc_order/uc_order.module |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/uc_order/uc_order.module b/uc_order/uc_order.module
index 931c397..5787e52 100644
--- a/uc_order/uc_order.module
+++ b/uc_order/uc_order.module
@@ -1796,10 +1796,11 @@ function template_preprocess_uc_order(&$variables) {
   $variables['shippable'] = uc_order_is_shippable($order);
 
   $variables['products'] = $order->products;
+  $display = entity_view('uc_order_product', $order->products);
   foreach ($variables['products'] as &$product) {
-    $product->total_price = uc_currency_format($product->price * $product->qty);
+    $product->total_price = render($display['uc_order_product'][$product->order_product_id]['total']);
     if ($product->qty > 1) {
-      $product->individual_price = t('(!price each)', array('!price' => uc_currency_format($product->price)));
+      $product->individual_price = t('(!price each)', array('!price' => uc_currency_format($display['uc_order_product'][$product->order_product_id]['price']['#price'])));
     }
     else {
       $product->individual_price = '';
-- 
1.7.3.4


From 289f398f5159c58443d43a8dab0953ef4a532ff1 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 7 Dec 2011 16:48:48 -0500
Subject: [PATCH 10/15] Check to be sure ->data is an array before testing values, bc user module sometimes sets it to a serialized string.

---
 uc_taxes/uc_taxes.module |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index 3325567..8c2d8a1 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -544,7 +544,7 @@ function uc_taxes_uc_calculate_tax($order) {
  */
 function uc_taxes_apply_item_tax($item, $tax) {
   // Determine the product type.
-  if (isset($item->data['type'])) { // Saved in the order product data array.
+  if (is_array($item->data) && isset($item->data['type'])) { // Saved in the order product data array.
     $type = $item->data['type'];
   }
   elseif (empty($item->nid)) { // "Blank-line" product.
@@ -558,7 +558,7 @@ function uc_taxes_apply_item_tax($item, $tax) {
   }
 
   // Determine whether this is a shippable product.
-  if (isset($item->data['shippable'])) { // Saved in the order product data array.
+  if (is_array($item->data) && isset($item->data['shippable'])) { // Saved in the order product data array.
     $shippable = $item->data['shippable'];
   }
   elseif (empty($item->nid)) { // "Blank line" product.
-- 
1.7.3.4


From d8e46dc6848ba46ebc09e6fd11e14f1d400c9589 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Wed, 7 Dec 2011 18:23:23 -0500
Subject: [PATCH 11/15] Integrate stored tax data into inclusive tax calculations.

---
 uc_taxes/uc_taxes.module |   12 ++++++++----
 1 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/uc_taxes/uc_taxes.module b/uc_taxes/uc_taxes.module
index 8c2d8a1..79d7fc5 100644
--- a/uc_taxes/uc_taxes.module
+++ b/uc_taxes/uc_taxes.module
@@ -134,7 +134,7 @@ function uc_taxes_entity_view_alter(&$build, $entity_type) {
       break;
 
     case 'uc_order_product':
-      list($amount, $suffixes) = uc_taxes_get_included_tax($build['#entity']);
+      list($amount, $suffixes) = uc_taxes_get_included_tax($build['#entity'], $build['#entity']->order);
       $build['price']['#price'] += $amount;
       $build['total']['#price'] += $amount * $build['#entity']->qty;
       if (!empty($suffixes)) {
@@ -394,8 +394,12 @@ function uc_taxes_rate_save($rate) {
 function uc_taxes_filter_rates($order) {
   $taxes = array();
 
+  // If no order, then just return all rates.
+  if (empty($order)) {
+    $taxes = uc_taxes_rate_load();
+  }
   // For orders no longer in checkout, only the saved tax rates can apply.
-  if (isset($order->order_status) && uc_order_status_data($order->order_status, 'state') != 'in_checkout') {
+  elseif (isset($order->order_status) && uc_order_status_data($order->order_status, 'state') != 'in_checkout') {
     if (isset($order->line_items)) {
       foreach($order->line_items as $item) {
         if ($item['type'] == 'tax') {
@@ -648,10 +652,10 @@ function uc_taxes_apply_tax($order, $tax) {
  * @param $product
  *   The product whose included taxes are to be calculated.
  */
-function uc_taxes_get_included_tax($product) {
+function uc_taxes_get_included_tax($product, $order = NULL) {
   $amount = 0;
   $suffixes = array();
-  foreach (uc_taxes_rate_load() as $tax) {
+  foreach (uc_taxes_filter_rates($order) as $tax) {
     if ($tax->display_include) {
       $taxable = uc_taxes_apply_item_tax($product, $tax);
       if (!empty($taxable)) {
-- 
1.7.3.4


From c1e29c1d797df53affa804337a133598a64894c8 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Mon, 12 Dec 2011 11:08:11 -0500
Subject: [PATCH 12/15] Prevent infinite recursion when uc_attribute alters the add_to_cart_form so as to emove call to form builder inside theme function.

---
 uc_attribute/uc_attribute.module |   13 +++++++++++--
 uc_product/uc_product.module     |    8 +++-----
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index eb427b7..1de30e1 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -1235,6 +1235,12 @@ function _uc_cart_product_get_options($item) {
  * @see theme_uc_attribute_add_to_cart()
  */
 function _uc_attribute_alter_form($product) {
+  // Prevent infinite recursion because we call node_view() below to get the display prices.
+  if (!empty($product->data['uc_attribute_form_skip'])) {
+    return NULL;
+  }
+  $skip_flag = array('uc_attribute_form_skip' => TRUE);
+
   // If the product doesn't have attributes, return the form as it is.
   if (!isset($product->attributes)) {
     $product->attributes = uc_product_get_attributes($product->nid);
@@ -1250,6 +1256,7 @@ function _uc_attribute_alter_form($product) {
   $attributes = $product->attributes;
 
   $form_attributes = array();
+  $base = FALSE;
 
   // Loop through each product attribute and generate its form element.
   foreach ($attributes as $attribute) {
@@ -1257,7 +1264,7 @@ function _uc_attribute_alter_form($product) {
     $options = array();
     foreach ($attribute->options as $option) {
       $display_price = '';
-      $variant = node_view(uc_product_load_variant($product->nid, array('attributes' => array($attribute->aid => $option->oid))));
+      $variant = node_view(uc_product_load_variant($product->nid, $skip_flag + array('attributes' => array($attribute->aid => $option->oid))));
       switch (variable_get('uc_attribute_option_price_format', 'adjustment')) {
         case 'total':
           if (in_array($attribute->aid, $priced_attributes)) {
@@ -1267,7 +1274,9 @@ function _uc_attribute_alter_form($product) {
             break;
           }
         case 'adjustment':
-          $base = node_view(uc_product_load_variant($product->nid));
+          if (!$base) {
+            $base = node_view(uc_product_load_variant($product->nid, $skip_flag));
+          }
           $adjustment = $variant['display_price']['#value'] - $base['display_price']['#value'];
           if ($adjustment) {
             $display_price = ($adjustment > 0 ? '+' : '') . uc_currency_format($adjustment);
diff --git a/uc_product/uc_product.module b/uc_product/uc_product.module
index 26b0ad9..d25710a 100644
--- a/uc_product/uc_product.module
+++ b/uc_product/uc_product.module
@@ -216,7 +216,7 @@ function uc_product_theme() {
       'variables' => array('body' => '', 'view_mode' => 'full'),
     ),
     'uc_product_add_to_cart' => array(
-      'variables' => array('node' => NULL, 'view_mode' => 'full'),
+      'variables' => array('form' => NULL, 'view_mode' => 'full'),
     ),
     'uc_product_price' => array(
       'render element' => 'element',
@@ -761,7 +761,7 @@ function uc_product_view($node, $view_mode) {
     $node->content['add_to_cart'] = array(
       '#theme' => 'uc_product_add_to_cart',
       '#view_mode' => $view_mode,
-      '#node' => $variant,
+      '#form' => drupal_get_form('uc_product_add_to_cart_form_' . $variant->nid, $variant),
     );
   }
   $node->content['#node'] = $variant;
@@ -1247,10 +1247,8 @@ function theme_uc_product_body($variables) {
  * @ingroup themeable
  */
 function theme_uc_product_add_to_cart($variables) {
-  $node = $variables['node'];
-
   $output = '<div class="add-to-cart">';
-  $output .= drupal_render(drupal_get_form('uc_product_add_to_cart_form_' . $node->nid, $node));
+  $output .= drupal_render($variables['form']);
   $output .= '</div>';
 
   return $output;
-- 
1.7.3.4


From 3c3a3f328585aa3be853ee56cbfd4519046ce8cb Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Mon, 12 Dec 2011 11:27:18 -0500
Subject: [PATCH 13/15] Move entity controller classes into separate include files.

---
 uc_cart/uc_cart.controller.inc   |   32 ++++++++++++
 uc_cart/uc_cart.info             |    3 +
 uc_cart/uc_cart.module           |   30 -----------
 uc_order/uc_order.controller.inc |  104 ++++++++++++++++++++++++++++++++++++++
 uc_order/uc_order.info           |    3 +
 uc_order/uc_order.module         |   95 ----------------------------------
 6 files changed, 142 insertions(+), 125 deletions(-)
 create mode 100644 uc_cart/uc_cart.controller.inc
 create mode 100644 uc_order/uc_order.controller.inc

diff --git a/uc_cart/uc_cart.controller.inc b/uc_cart/uc_cart.controller.inc
new file mode 100644
index 0000000..1ebbda0
--- /dev/null
+++ b/uc_cart/uc_cart.controller.inc
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @file 
+ * 
+ * Contains the controller for uc_cart_item entities.
+ */
+class UcCartItemController extends EntityAPIController {
+
+  public function buildContent($item, $view_mode = 'full', $langcode = NULL, $content = array()) {
+    $content = array();
+    $content += module_invoke($item->module, 'uc_cart_display', $item);
+    if (!empty($content)) {
+      $content['cart_item_id'] = array(
+				'#type' => 'hidden',
+				'#value' => $item->cart_item_id,
+      );
+    }
+    return parent::buildContent($item, $view_mode, $langcode, $content);
+  }
+
+  public function attachLoad(&$items, $revision_id = FALSE) {
+    foreach ($items as &$item) {
+      $product = uc_product_load_variant($item->nid, $item->data);
+      // Merge in fields from the product.
+      foreach ($product as $key => $value) {
+        $item->$key = $value;
+      }
+      $item->module = $item->data['module'];
+    }
+    parent::attachLoad($items, $revision_id);
+  }
+}
diff --git a/uc_cart/uc_cart.info b/uc_cart/uc_cart.info
index ca5531f..d3021d5 100644
--- a/uc_cart/uc_cart.info
+++ b/uc_cart/uc_cart.info
@@ -11,4 +11,7 @@ files[] = tests/uc_cart.test
 ; Views handlers
 files[] = views/uc_cart_handler_field_cart_user.inc
 
+; Classes
+files[] = uc_cart.controller.inc
+
 configure = admin/store/settings/cart
diff --git a/uc_cart/uc_cart.module b/uc_cart/uc_cart.module
index 8db8355..afa5912 100644
--- a/uc_cart/uc_cart.module
+++ b/uc_cart/uc_cart.module
@@ -1353,36 +1353,6 @@ function uc_cart_get_total_qty($cid = NULL) {
 }
 
 /**
- * Entity controller for cart items.
- */
-class UcCartItemController extends EntityAPIController {
-
-  public function buildContent($item, $view_mode = 'full', $langcode = NULL, $content = array()) {
-    $content = array();
-    $content += module_invoke($item->module, 'uc_cart_display', $item);
-    if (!empty($content)) {
-      $content['cart_item_id'] = array(
-				'#type' => 'hidden',
-				'#value' => $item->cart_item_id,
-      );
-    }
-    return parent::buildContent($item, $view_mode, $langcode, $content);
-  }
-
-  public function attachLoad(&$items, $revision_id = FALSE) {
-    foreach ($items as &$item) {
-      $product = uc_product_load_variant($item->nid, $item->data);
-      // Merge in fields from the product.
-      foreach ($product as $key => $value) {
-        $item->$key = $value;
-      }
-      $item->module = $item->data['module'];
-    }
-    parent::attachLoad($items, $revision_id);
-  }
-}
-
-/**
  * Updates a cart item.
  *
  * @param $item
diff --git a/uc_order/uc_order.controller.inc b/uc_order/uc_order.controller.inc
new file mode 100644
index 0000000..1733742
--- /dev/null
+++ b/uc_order/uc_order.controller.inc
@@ -0,0 +1,104 @@
+<?php
+/**
+ * @file
+ * Contains controller classes for uc_order and uc_order_product entities.
+ */
+
+/**
+ * Controller class for uc_order entity.
+ */
+class UcOrderController extends DrupalDefaultEntityController {
+
+  function attachLoad(&$orders, $revision_id = FALSE) {
+    foreach ($orders as &$order) {
+      $order->data = unserialize($order->data);
+      $efq = new EntityFieldQuery();
+      $result = $efq->entityCondition('entity_type', 'uc_order_product')
+        ->propertyCondition('order_id', $order->order_id)
+        ->propertyOrderBy('order_product_id', 'ASC')
+        ->execute();
+      if (!empty($result['uc_order_product'])) {
+        $order->products = entity_load('uc_order_product', array_keys($result['uc_order_product']), NULL, TRUE);
+        foreach ($order->products as $product) {
+          $product->order = $order;
+          $product->order_uid = $order->uid;
+        }
+      }
+      else {
+        $order->products = array();
+      }
+
+      uc_order_module_invoke('load', $order, NULL);
+
+      // Load line items... has to be last after everything has been loaded.
+      $order->line_items = uc_order_load_line_items($order);
+
+      $fields = array();
+
+      // Make sure the total still matches up...
+      if (($total = uc_order_get_total($order)) !== $order->order_total) {
+        $fields['order_total'] = $total;
+        $order->order_total = $total;
+      }
+
+      if (($count = uc_order_get_product_count($order)) !== $order->product_count) {
+        $fields['product_count'] = $count;
+        $order->product_count = $count;
+      }
+
+      if (count($fields)) {
+        $query = db_update('uc_orders')
+          ->fields($fields)
+          ->condition('order_id', $order->order_id)
+          ->execute();
+      }
+    }
+
+    parent::attachLoad($orders, $revision_id);
+  }
+
+}
+
+/**
+ * Controller class for the uc_order_product entity.
+ */
+class UcOrderProductController extends EntityAPIController {
+  public function buildContent($product, $view_mode = 'full', $langcode = NULL, $content = array()) {
+    $content['qty'] = array(
+      '#theme' => 'uc_qty',
+      '#qty' => $product->qty,
+    );
+    $node = node_load($product->nid);
+    $title = node_access('view', $node) ? l($product->title, 'node/'. $node->nid) : check_plain($product->title);
+    $content['product'] = array(
+      '#markup' => $title . uc_product_get_description($product),
+    );
+    $content['model'] = array(
+      '#markup' => check_plain($product->model),
+      '#cell_attributes' => array('align' => 'center'),
+    );
+
+    if (user_access('administer products')) {
+      $content['cost'] = array(
+        '#theme' => 'uc_price',
+        '#price' => $product->cost,
+        '#cell_attributes' => array('align' => 'right'),
+      );
+    }
+    $content['price'] = array(
+      '#theme' => 'uc_price',
+      '#price' => $product->price,
+      '#suffixes' => array(),
+      '#cell_attributes' => array('align' => 'right'),
+    );
+    $content['total'] = array(
+      '#theme' => 'uc_price',
+      '#price' => $product->price * $product->qty,
+      '#suffixes' => array(),
+      '#cell_attributes' => array('align' => 'right'),
+    );
+    $content['#attributes'] = array('valign' => 'top');
+    return parent::buildContent($product, $view_mode, $langcode, $content);
+  }
+}
+
diff --git a/uc_order/uc_order.info b/uc_order/uc_order.info
index d6b2aeb..b290a4c 100644
--- a/uc_order/uc_order.info
+++ b/uc_order/uc_order.info
@@ -25,5 +25,8 @@ files[] = views/uc_order_handler_filter_payment_method.inc
 files[] = views/uc_order_handler_filter_zone.inc
 files[] = views/uc_order_plugin_row_invoice_view.inc
 
+; Classes
+files[] = uc_order.controller.inc
+
 configure = admin/store/settings/orders
 stylesheets[all][] = uc_order.css
diff --git a/uc_order/uc_order.module b/uc_order/uc_order.module
index 5787e52..a47fe28 100644
--- a/uc_order/uc_order.module
+++ b/uc_order/uc_order.module
@@ -1221,101 +1221,6 @@ function uc_order_view_invoice($order, $print = FALSE) {
 
 
 
-class UcOrderProductController extends EntityAPIController {
-  public function buildContent($product, $view_mode = 'full', $langcode = NULL, $content = array()) {
-    $content['qty'] = array(
-      '#theme' => 'uc_qty',
-      '#qty' => $product->qty,
-    );
-    $node = node_load($product->nid);
-    $title = node_access('view', $node) ? l($product->title, 'node/'. $node->nid) : check_plain($product->title);
-    $content['product'] = array(
-      '#markup' => $title . uc_product_get_description($product),
-    );
-    $content['model'] = array(
-      '#markup' => check_plain($product->model),
-      '#cell_attributes' => array('align' => 'center'),
-    );
-
-    if (user_access('administer products')) {
-      $content['cost'] = array(
-        '#theme' => 'uc_price',
-        '#price' => $product->cost,
-        '#cell_attributes' => array('align' => 'right'),
-      );
-    }
-    $content['price'] = array(
-      '#theme' => 'uc_price',
-      '#price' => $product->price,
-      '#suffixes' => array(),
-      '#cell_attributes' => array('align' => 'right'),
-    );
-    $content['total'] = array(
-      '#theme' => 'uc_price',
-      '#price' => $product->price * $product->qty,
-      '#suffixes' => array(),
-      '#cell_attributes' => array('align' => 'right'),
-    );
-    $content['#attributes'] = array('valign' => 'top');
-    return parent::buildContent($product, $view_mode, $langcode, $content);
-  }
-}
-
-/**
- * Controller class for uc_order entity.
- */
-class UcOrderController extends DrupalDefaultEntityController {
-
-  function attachLoad(&$orders, $revision_id = FALSE) {
-    foreach ($orders as &$order) {
-      $order->data = unserialize($order->data);
-      $efq = new EntityFieldQuery();
-      $result = $efq->entityCondition('entity_type', 'uc_order_product')
-        ->propertyCondition('order_id', $order->order_id)
-        ->propertyOrderBy('order_product_id', 'ASC')
-        ->execute();
-      if (!empty($result['uc_order_product'])) {
-        $order->products = entity_load('uc_order_product', array_keys($result['uc_order_product']), NULL, TRUE);
-        foreach ($order->products as $product) {
-          $product->order = $order;
-          $product->order_uid = $order->uid;
-        }
-      }
-      else {
-        $order->products = array();
-      }
-
-      uc_order_module_invoke('load', $order, NULL);
-
-      // Load line items... has to be last after everything has been loaded.
-      $order->line_items = uc_order_load_line_items($order);
-
-      $fields = array();
-
-      // Make sure the total still matches up...
-      if (($total = uc_order_get_total($order)) !== $order->order_total) {
-        $fields['order_total'] = $total;
-        $order->order_total = $total;
-      }
-
-      if (($count = uc_order_get_product_count($order)) !== $order->product_count) {
-        $fields['product_count'] = $count;
-        $order->product_count = $count;
-      }
-
-      if (count($fields)) {
-        $query = db_update('uc_orders')
-          ->fields($fields)
-          ->condition('order_id', $order->order_id)
-          ->execute();
-      }
-    }
-
-    parent::attachLoad($orders, $revision_id);
-  }
-
-}
-
 /**
  * Loads one order entity from the database.
  */
-- 
1.7.3.4


From 3407a1f72ea8d547bea04f936e0a90679d20f7d0 Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Mon, 12 Dec 2011 18:29:36 -0500
Subject: [PATCH 14/15] Add tests for tax inclusive prices.

---
 uc_taxes/tests/uc_taxes.test |   58 +++++++++++++++++++++++++++++++++++++++---
 1 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/uc_taxes/tests/uc_taxes.test b/uc_taxes/tests/uc_taxes.test
index 5205710..1dda2df 100644
--- a/uc_taxes/tests/uc_taxes.test
+++ b/uc_taxes/tests/uc_taxes.test
@@ -19,8 +19,8 @@ class UbercartInclusiveTaxesTestCase extends UbercartTestHelper {
   }
 
   function setUp() {
-    $modules = array('uc_product_kit', 'uc_attribute', 'uc_taxes');
-    $permissions = array();
+    $modules = array('uc_product_kit', 'uc_attribute', 'uc_cart', 'uc_payment', 'uc_payment_pack', 'uc_taxes');
+    $permissions = array('administer rules', 'configure taxes');
     parent::setUp($modules, $permissions);
   }
 
@@ -37,10 +37,13 @@ class UbercartInclusiveTaxesTestCase extends UbercartTestHelper {
       'weight' => 0,
       'shippable' => 0,
       'display_include' => 1,
-      'inclusion_text' => '',
+      'inclusion_text' => $this->randomName(6),
     );
     uc_taxes_rate_save($rate);
 
+    // Ensure Rules picks up the new condition.
+    entity_flush_caches();
+
     // Create a $10 product.
     $product = $this->createProduct(array(
       'sell_price' => 10,
@@ -87,13 +90,60 @@ class UbercartInclusiveTaxesTestCase extends UbercartTestHelper {
     $kit = node_load($kit->nid);
     $this->assertEqual($kit->products[$product->nid]->discount, -1, 'Product kit component has correct discount applied.');
 
+    // Ensure the price is displayed tax-inclusively on the add-to-cart form.
+    $this->drupalGet('node/' . $kit->nid);
+    $this->assertText('$10.80' . $rate->inclusion_text, 'Tax inclusive price on node-view form is accurate.'); // $10.80 = $9.00 + 20%
+    $this->assertRaw($option->name . ', +$6.00</option>', 'Tax inclusive option price on node view form is accurate.'); // $6.00 = $5.00 + 20%
+
     // Add the product kit to the cart, selecting the option.
     $attribute_key = 'products[' . $product->nid . '][attributes][' . $attribute->aid . ']';
     $this->drupalPost('node/' . $kit->nid, array($attribute_key => $option->oid), t('Add to cart'));
 
     // Check that the subtotal is $16.80 ($10 base + $5 option - $1 discount, with 20% tax)
     $this->drupalGet('cart');
-    $this->assertText('Subtotal: $16.80', 'Order subtotal is correct.');
+    $this->assertText('Subtotal: $16.80', 'Order subtotal is correct on cart page.');
+
+    // Make sure that the subtotal is also correct on the checkout page.
+    $this->drupalPost('cart', array(), 'Checkout');
+    $this->assertText('Subtotal: $16.80', 'Order subtotal is correct on checkout page.');
+
+    // Manually proceed to checkout review.
+    $zone_id = db_query('SELECT zone_id FROM {uc_zones} WHERE zone_country_id = :country ORDER BY rand() LIMIT 1', array('country' => variable_get('uc_store_country', 840)))->fetchField();
+    $edit = array(
+      'panes[delivery][delivery_first_name]' => $this->randomName(10),
+      'panes[delivery][delivery_last_name]' => $this->randomName(10),
+      'panes[delivery][delivery_street1]' => $this->randomName(10),
+      'panes[delivery][delivery_city]' => $this->randomName(10),
+      'panes[delivery][delivery_zone]' => $zone_id,
+      'panes[delivery][delivery_postal_code]' => mt_rand(10000, 99999),
+
+      'panes[billing][billing_first_name]' => $this->randomName(10),
+      'panes[billing][billing_last_name]' => $this->randomName(10),
+      'panes[billing][billing_street1]' => $this->randomName(10),
+      'panes[billing][billing_city]' => $this->randomName(10),
+      'panes[billing][billing_zone]' => $zone_id,
+      'panes[billing][billing_postal_code]' => mt_rand(10000, 99999),
+    );
+    $this->drupalPost('cart/checkout', $edit, t('Review order'));
+    $this->assertRaw(t('Your order is almost complete.'));
+
+    // Make sure the price is still listed tax-inclusively.
+    // !TODO This could be handled more specifically with a regex.
+    $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears in cart pane on checkout review page');
+
+    // Ensure the tax-inclusive price is listed on the order admin page.
+    $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField();
+    $this->assertTrue($order_id, 'Order was created successfully');
+    $this->drupalGet('admin/store/orders/' . $order_id);
+    $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the order view page.');
+
+    // And on the invoice.
+    $this->drupalGet('admin/store/orders/' . $order_id . '/invoice');
+    $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the invoice.');
+
+    // And on the printable invoice.
+    $this->drupalGet('admin/store/orders/' . $order_id . '/invoice');
+    $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the printable invoice.');
   }
 }
 
-- 
1.7.3.4


From c0e12bfc19b08d77705e5d577bd222b78f91db6d Mon Sep 17 00:00:00 2001
From: Chris Oden <wodenx@gmail.com>
Date: Tue, 13 Dec 2011 10:47:30 -0500
Subject: [PATCH 15/15] Add more tests for stored tax data.

---
 uc_taxes/tests/uc_taxes.test |   76 +++++++++++++++++++++---------------------
 1 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/uc_taxes/tests/uc_taxes.test b/uc_taxes/tests/uc_taxes.test
index 1dda2df..940c738 100644
--- a/uc_taxes/tests/uc_taxes.test
+++ b/uc_taxes/tests/uc_taxes.test
@@ -166,6 +166,24 @@ class UbercartStoredTaxesTestCase extends UbercartTestHelper {
     parent::setUp($modules, $permissions);
   }
 
+  function loadTaxLine($order_id) {
+    $order = uc_order_load($order_id, TRUE);
+    foreach ($order->line_items as $line) {
+      if ($line['type'] == 'tax') {
+        return $line;
+      }
+    }
+    return FALSE;
+  }
+
+  function assertTaxLineCorrect($line, $rate, $when) {
+    $this->assertTrue($line, t('The tax line item was saved to the order ' . $when));
+    $this->assertTrue(number_format($rate * $this->product->sell_price, 2) == number_format($line['amount'], 2), t('Stored tax line item has the correct amount ' . $when));
+    $this->assertFieldByName('line_items['. $line['line_item_id'] .'][li_id]', $line['line_item_id'], t('Found the tax line item ID ' . $when));
+    $this->assertText($line['title'], t('Found the tax title ' . $when));
+    $this->assertText(uc_currency_format($line['amount']), t('Tax display has the correct amount ' . $when));
+  }
+
   function testTaxDisplay() {
     $this->drupalLogin($this->adminUser);
 
@@ -240,64 +258,46 @@ class UbercartStoredTaxesTestCase extends UbercartTestHelper {
         t('Order %order_id has been created', array('%order_id' => $order_id))
       );
 
-      $order = uc_order_load($order_id);
-      foreach ($order->line_items as $line) {
-        if ($line['type'] == 'tax') {
-          break;
-        }
-      }
-      $this->assertEqual($line['type'], 'tax', t('The tax line item was saved to the order.'));
-
       $this->drupalGet('admin/store/orders/' . $order_id . '/edit');
-      $this->assertFieldByName('line_items['. $line['line_item_id'] .'][li_id]', $line['line_item_id'], t('Found the tax line item ID.'));
-      $this->assertText($line['title'], t('Found the tax title.'));
-      $this->assertText(uc_currency_format($line['amount']), t('Tax has the correct amount.'));
+      $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'on initial order load');
 
       $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
       $this->assertText(t('Order changes saved.'));
-      $this->assertFieldByName('line_items['. $line['line_item_id'] .'][li_id]', $line['line_item_id'], t('Found the same tax line item ID.'));
-      $this->assertText($line['title'], t('Found the tax title.'));
-      $this->assertText(uc_currency_format($line['amount']), t('Tax has the same amount as before.'));
+      $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'after saving order');
 
       // Change tax rate and ensure order doesn't change.
       $oldrate = $rate->rate;
       $rate->rate = 0.1;
       $rate = uc_taxes_rate_save($rate);
-
       // Ensure Rules picks up the new condition.
       entity_flush_caches();
-
+      // Save order because tax changes are only updated on save.
       $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
       $this->assertText(t('Order changes saved.'));
-      $this->assertFieldByName('line_items['. $line['line_item_id'] .'][li_id]', $line['line_item_id'], t('Found the same tax line item ID.'));
-      $this->assertText($line['title'], t('Found the tax title.'));
-      $this->assertText(uc_currency_format($line['amount']), t('Tax has the same amount as before.'));
-
-      uc_order_update_status($order_id, 'completed');
+      $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $oldrate, 'after rate change');
 
-      $order = uc_order_load($order_id);
-      foreach ($order->line_items as $line) {
-        if ($line['type'] == 'tax') {
-          break;
-        }
-      }
-      $this->assertEqual($line['type'], 'tax', t('The tax line item was saved to the order.'));
-
-      $this->assertTrue(number_format($oldrate * $this->product->sell_price, 2) == number_format($line['amount'], 2), t('Stored tax line item has the correct amount.'));
+      // Change taxable products and ensure order doesn't change.
+      $class = $this->createProductClass();
+      $rate->taxed_product_types = array($class->name);
+      uc_taxes_rate_save($rate);
+      entity_flush_caches();
+      $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
+      $this->assertText(t('Order changes saved.'));
+      $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $oldrate, 'after applicable product change');
 
-      // Change order Status back to in_checkout and ensure tax-rate changes update the order.
+      // Change order Status back to in_checkout and ensure tax-rate changes now update the order.
       uc_order_update_status($order_id, 'in_checkout');
-      $rate->rate = 0.3;
-      $rate = uc_taxes_rate_save($rate);
+      $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
+      $this->assertText(t('Order changes saved.'));
+      $this->assertFalse($this->loadTaxLine($order_id), t('The tax line was removed from the order when order status changed back to in_checkout.'));
 
-      // Ensure Rules picks up the new condition.
+      // Restore taxable product and ensure new tax is added.
+      $rate->taxed_product_types = array('product');
+      uc_taxes_rate_save($rate);
       entity_flush_caches();
-
       $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
       $this->assertText(t('Order changes saved.'));
-      $this->assertFieldByName('line_items['. $line['line_item_id'] .'][li_id]', $line['line_item_id'], t('Found the same tax line item ID.'));
-      $this->assertText($line['title'], t('Found the tax title.'));
-      $this->assertText(uc_currency_format($rate->rate * $this->product->sell_price), t('Tax has new amount.'));
+      $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'when order status changed back to in_checkout');
     }
     else {
       $this->fail(t('No order was created.'));
-- 
1.7.3.4

