? modules/ecommerce/shippingtest.module
Index: modules/ecommerce/product/product.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/product/product.module,v
retrieving revision 1.109
diff -u -r1.109 product.module
--- modules/ecommerce/product/product.module	12 May 2006 07:35:11 -0000	1.109
+++ modules/ecommerce/product/product.module	13 May 2006 21:37:10 -0000
@@ -224,21 +224,6 @@
       }
     }
 
-    // Handle shipping.
-    // Don't test for if it *is* 'per_product', test for if it *supports* per_product
-    if (function_exists(variable_get('shipping_method', 'none').'_shippingapi')) {
-    // we can't use module_invoke, because one module could define multiple types
-      $f = variable_get('shipping_method', 'none').'_shippingapi';
-      $per_product = $f($edit, 'per_product');
-    }
-    if ($per_product && in_array('is_shippable', (array)module_invoke($product->ptype, 'productapi', $product, 'attributes', 'is_shippable'))) {
-      if ($shipping = module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $product, 'per_product_load')) {
-        foreach ($shipping as $key => $value) {
-          $product->$key = $value;
-        }
-      }
-    }
-
     $products[$node->vid] = $product;
   }
 
@@ -296,6 +281,16 @@
     );
   }
   else {
+
+    foreach(product_get_ptypes() as $type => $description) {
+      $items[] = array(
+        'path' => 'admin/settings/content-types/product/' . $type,
+        'title' => $description,
+        'callback' => 'product_types_configure',
+        'access' => user_access('administer products'),
+        'type' => MENU_CALLBACK
+      );
+    }
     if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('administer products')) {
       // Only add the product-tab for non-product pages:
       if (db_result(db_query(db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n WHERE n.nid = %d AND n.type != 'product'"), arg(1))) > 0) {
@@ -339,14 +334,56 @@
  * Implementation of hook_form_alter()
  */
 function product_form_alter($form_id, &$form) {
-  if (isset($form['type']) && $form_id == $form['type']['#value'] .'_node_form') {
-    foreach (array('ptype') as $key) {
-      $form[$key] = array('#type' => 'value', '#value' => $form['#node']->$key);
+  if (isset($form['type'])) {
+    if ($form_id == $form['type']['#value'] .'_node_form') {
+      foreach (array('ptype') as $key) {
+        $form[$key] = array('#type' => 'value', '#value' => $form['#node']->$key);
+      }
+    }
+    // Add a fieldset with links for configuring individual product types.
+    else if ($form_id == $form['type']['#value'] .'_node_settings') {
+      $form['product_types'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Configure product types'),
+        '#collapsible' => TRUE,
+        '#weight' => -10,
+        '#children' => product_types_configure()
+      );
     }
   }
 }
 
 /**
+ * Menu callback; presents each product type configuration page.
+ */
+function product_types_configure() {
+  if (arg(4)) {
+    $form = array('#ptype' => arg(4));
+    return system_settings_form('product_type_settings', $form);
+  }
+  else {
+    $header = array(t('Type'), t('Operations'));
+
+    $rows = array();
+    foreach (product_get_ptypes() as $type => $name) {
+      $form = array('#ptype' => $type);
+      // Only show this link if at least one module has added settings.
+      // We test for > 1 because we are sending one element, the ptype.
+      // We don't use module_invoke because we need to pass by reference.
+      foreach (module_implements('form_alter') as $module) {
+        $function = $module . '_form_alter';
+        $function('product_type_settings', $form);
+      }
+      if (count($form) > 1) {
+        $rows[] = array($name, l(t('configure'), 'admin/settings/content-types/product/'. $type));
+      }
+    }
+
+    return theme('table', $header, $rows);
+  }
+}
+
+/**
  * Implementation of hook_nodeapi().
  *
  * Provide product functionality for nodes that are not products
@@ -741,10 +778,6 @@
     }
   }
 
-  if ($edit->per_product_shipping) {
-    module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $edit, 'per_product_validate');
-  }
-
   if (!$errors['ptype']) {
     $f = $edit->ptype. '_productapi';
     if (function_exists($f)) {
@@ -769,10 +802,6 @@
     $node->is_recurring = ($node->price_interval) ? 1 : 0;
     $fields = product_fields();
 
-    /*
-    TODO: ADD SHIPPING API CALLBACKS
-    */
-
     /* Be very sure that we have a product entry to update! */
     if (db_result(db_query('SELECT COUNT(vid) FROM {ec_product} WHERE vid = %d', $node->vid))) {
       foreach ($node as $key => $value) {
@@ -784,9 +813,6 @@
       $v[] = $node->vid;
       db_query('UPDATE {ec_product} SET '. implode(', ', $q) ." WHERE vid = %d", $v);
       module_invoke($node->ptype, 'productapi', $node, 'update');
-      if ($node->per_product_shipping) {
-        module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $node, 'per_product_update');
-      }
     }
     else {
       foreach ($node as $key => $value) {
@@ -799,9 +825,6 @@
 
       db_query('INSERT INTO {ec_product} ('. implode(', ', $k) .') VALUES('. implode(', ', $s) .')', $v);
       module_invoke($node->ptype, 'productapi', $node, 'insert');
-      if ($node->per_product_shipping) {
-        module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $node, 'per_product_insert');
-      }
     }
   }
 }
@@ -928,17 +951,30 @@
   return $output;
 }
 
-function product_is_shippable($vid) {
+/**
+ * Determine if a product or a product type is shippable.
+ *
+ * If a vid is given, the test is for a particular product,
+ * whereas if a ptype is given it's for the ptype.
+ *
+ * @param $vid
+ *   Version id of product.
+ * @param $ptype
+ *   Product type.
+ * @return
+ *    Boolean.
+ */
+function product_is_shippable($vid = NULL, $ptype = NULL) {
 
-  $product = db_fetch_object(db_query('SELECT * FROM {ec_product} WHERE vid = %d', $vid));
+  $product = $vid ? db_fetch_object(db_query('SELECT ptype FROM {ec_product} WHERE vid = %d', $vid)) : (object) array('ptype' => $ptype);
 
-  $attributes = (array) module_invoke($product->ptype, 'productapi', $product, 'attributes', 'is_shippable');
+  $attributes = (array) module_invoke($product->ptype, 'productapi', $product, 'attributes');
 
   if (in_array('is_shippable', $attributes)) {
-    return true;
+    return TRUE;
   }
 
-  return false;
+  return FALSE;
 }
 
 /**
Index: modules/ecommerce/shipping/shipping.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/shipping/shipping.install,v
retrieving revision 1.3
diff -u -r1.3 shipping.install
--- modules/ecommerce/shipping/shipping.install	9 May 2006 12:23:37 -0000	1.3
+++ modules/ecommerce/shipping/shipping.install	14 May 2006 14:09:48 -0000
@@ -8,65 +8,28 @@
   switch ($GLOBALS['db_type']) {
     case 'mysql':
     case 'mysqli':
-      db_query("CREATE TABLE {ec_shipping_shipping} (
-        item_slot varchar(25) NOT NULL default '0',
-        price_slot varchar(25) NOT NULL default '0',
-        KEY item_slot (item_slot),
-        KEY price_slot (price_slot)
-      ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('1', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('2-4', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('5', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('6-10', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('10-1000', '0.00');");
-
-      db_query("CREATE TABLE {ec_subtotal_shipping} (
-        item_slot varchar(25) NOT NULL default '0',
-        price_slot varchar(25) NOT NULL default '0',
-        KEY item_slot (item_slot),
-        KEY price_slot (price_slot)
-      ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('0-0.99', '0.00');");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('1.00-10.99', '0.00');");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('11.00-50.00', '0.00');");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('50.00-10000', '0.00');");
-
-      db_query("CREATE TABLE {ec_per_product_shipping} (
+      db_query("CREATE TABLE {ec_shipping_product} (
+        vid int(10) unsigned NOT NULL default '0',
         nid int(10) unsigned NOT NULL default '0',
-        shipping_fee decimal(10,2) unsigned NOT NULL default '0.00',
-        PRIMARY KEY  (nid)
+        type varchar(40) NOT NULL default '0',
+        method varchar(40) NOT NULL default '0',
+        PRIMARY KEY (vid),
+        KEY nid (nid)
       ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
       break;
     case 'pgsql':
       db_query("CREATE TABLE {ec_shipping_shipping} (
-        item_slot varchar(25) NOT NULL default '0',
-        price_slot varchar(25) NOT NULL default '0'
-      )");
-      db_query("CREATE INDEX {ec_shipping_shipping}_item_slot ON {ec_shipping_shipping} (item_slot)");
-      db_query("CREATE INDEX {ec_shipping_shipping}_price_slot ON {ec_shipping_shipping} (price_slot)");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('1', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('2-4', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('5', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('6-10', '0.00');");
-      db_query("INSERT INTO {ec_shipping_shipping} VALUES ('10-1000', '0.00');");
-
-      db_query("CREATE TABLE {ec_subtotal_shipping} (
-        item_slot varchar(25) NOT NULL default '0',
-        price_slot varchar(25) NOT NULL default '0'
-      )");
-      db_query("CREATE INDEX {ec_subtotal_shipping}_item_slot ON {ec_subtotal_shipping} (item_slot)");
-      db_query("CREATE INDEX {ec_subtotal_shipping}_price_slot ON {ec_subtotal_shipping} (price_slot)");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('0-0.99', '0.00');");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('1.00-10.99', '0.00');");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('11.00-50.00', '0.00');");
-      db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('50.00-10000', '0.00');");
-
-      db_query("CREATE TABLE {ec_per_product_shipping} (
-        nid integer NOT NULL default '0',
-        shipping_fee decimal(10,2) NOT NULL default '0.00',
-        PRIMARY KEY  (nid)
+        vid integer(10) NOT NULL default '0',
+        nid integer(10) NOT NULL default '0',
+        type varchar(40) NOT NULL default '0',
+        method varchar(40) NOT NULL default '0',
+        PRIMARY KEY (vid),
+        KEY nid (nid)
       )");
       break;
   }
 }
 
+function shipping_update_1() {
+  shipping_install();
+}
Index: modules/ecommerce/shipping/shipping.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/shipping/shipping.module,v
retrieving revision 1.41
diff -u -r1.41 shipping.module
--- modules/ecommerce/shipping/shipping.module	12 May 2006 07:35:11 -0000	1.41
+++ modules/ecommerce/shipping/shipping.module	15 May 2006 00:34:41 -0000
@@ -11,11 +11,9 @@
 function shipping_help($section = 'admin/help#shipping') {
   switch ($section) {
     case 'admin/modules#description':
-      return t('Enable a shipping module API for ecommerce.');
-    case 'admin/store/shipping':
-      return t("First, choose a method to calculate shipping costs, then configure the shipping details for the method selected.");
+      return t('Enable a shipping module API for ecommerce. You must also install and enable at least one shipping module to use the API.');
     case 'admin/settings/shipping':
-      return t('Shipping calculation methods are <a href="%shipping_calc_url">here</a>.', array('%shipping_calc_url' => url('admin/store/shipping')));
+      return t('Set shipping options for each product type at the <a href="%product_settings">product settings page</a>.', array('%product_settings' => url('admin/settings/content-types/product')));
     case t('Shipping email'):
       return t("Hello %first_name,\n\nWe have shipped the following item(s) from Order #%txnid, received  %order_date.\n\nItems(s) Shipped:\n%items\n%shipping_to\nQuestions about your order? Please contact us at %email.\n\nThanks for shopping at %site.  We hope to hear from you again real soon!\n\n%uri");
   }
@@ -24,22 +22,6 @@
 }
 
 /**
- * Implementation of hook_menu()
- */
-function shipping_menu($may_cache) {
-  $items = array();
-  if ($may_cache) {
-    $items[] = array('path' => 'admin/store/settings/shipping', 
-                     'title' => t('shipping'), 'callback' => 'shipping_admin', 
-                     'access' => user_access('administer store'));
-    $items[] = array('path' => 'admin/store/shipping', 
-                     'title' => t('shipping'), 'callback' => 'shipping_admin', 
-                     'access' => user_access('administer store'));
-  }
-  return $items;
-}
-
-/**
  * Implementation of hook_settings()
  */
 function shipping_settings() {
@@ -61,37 +43,232 @@
   return $form;
 }
 
+/**
+ * Implementation of hook_form_alter()
+ */
+function shipping_form_alter($form_id, &$form) {
+  // On each product type's settings page, e.g., admin/settings/content-types/product/tangible,
+  // offer shipping methods for the type.
+  if (($form_id == 'product_type_settings') && product_is_shippable(NULL, $form['#ptype'])) {
+    $ptype = $form['#ptype'];
+    $form[$ptype . '_shipping_methods'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Shipping methods'),
+      '#description' => t('Select the shipping methods you wish to make available for this product type.'),
+      '#weight' => -10,
+      '#tree' => TRUE
+    );
+    $methods = shipping_get_methods();
+    $settings = variable_get($ptype . '_shipping_methods', array());
+    foreach ($methods as $type => $method) {
+      $options = array();
+      foreach (element_children($method) as $key) {
+        $options[$key] = $method[$key]['#title'];
+      }
+      // Read title from method.
+      $form[$ptype . '_shipping_methods'][$type] = array(
+        '#title' => $methods[$type]['#title'],
+        '#type' => 'checkboxes',
+        '#default_value' => $settings[$type] ? array_filter($settings[$type]) : array(),
+        '#options' => $options
+      );
+      // Read in the optional method description.
+      if ($methods[$type]['#description']) {
+        $form[$ptype . 'shipping_methods'][$type]['#description'] = $methods[$type]['#description'];
+      };
+    }
+  }
+  // On the product add/edit form, add shipping method selection.
+  else if (isset($form['product']) && isset($form['product']['ptype'])) {
+    $ptype = $form['product']['ptype']['#value'];
+    $methods = shipping_get_methods(NULL, $ptype);
+    if (count($methods)) {
+      $form['shipping_data'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Shipping methods'),
+        '#description' => t('Select the shipping methods you wish to make available for this product.'),
+        '#collapsible' => TRUE
+      );
+      $form['shipping_data']['shipping_methods'] = array(
+        '#tree' => TRUE
+      );
+      foreach ($methods as $type => $method) {
+        $options = array();
+        foreach (element_children($method) as $key) {
+          $options[$key] = $method[$key]['#title'];
+        }
+        // Read title from method.
+        // The value of the checkboxes comes from the shipping_methods array set at nodeapi load.
+        $form['shipping_data']['shipping_methods'][$type] = array(
+          '#title' => $methods[$type]['#title'],
+          '#type' => 'checkboxes',
+          '#default_value' => $form['#node']->shipping_methods[$type],
+          '#options' => $options
+        );
+        // Read in the optional method description.
+        if ($methods[$type]['#description']) {
+          $form['shipping_data']['shipping_methods'][$type]['#description'] = $methods[$type]['#description'];
+        };
+      }
+      // Allow shipping modules to modify the form, adding elements for collecting data.
+      // We need to call a hook rather than relying on form_alter because
+      // other modules need to add to the shipping_methods fieldset, which won't be available
+      // until shipping.module has been called.
+      foreach (module_implements('shipping_product_form') as $module) {
+        // Determine if any of the module's methods are registered to this product type.
+        $module_methods = shipping_methods_filter(module_invoke($module, 'shipping_methods'), $methods);
+        if (count($module_methods)) {
+          // Need to pass by reference so can't use module_invoke.
+          $function = $module .'_shipping_product_form';
+          $function($form, $module_methods);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * On load, we add an array of shipping_methods to the node object.
+ */
+function shipping_nodeapi(&$node, $op, $teaser) {
+  switch ($op) {
+
+    case 'load':
+      $result = db_query("SELECT type, method FROM {ec_shipping_product} WHERE  vid = %d", $node->vid);
+      if (db_num_rows($result)) {
+        $node->shipping_methods = array();
+        while ($method = db_fetch_object($result)) {
+          $node->shipping_methods[$method->type][] = $method->method;
+        }
+      }
+      break;
+
+    case 'update':
+      if ($node->shipping_methods && !$node->revision) {
+        db_query('DELETE FROM {ec_shipping_product} WHERE vid = %d', $node->vid);
+      }
+      // Fall through.
+    case 'insert':
+      if ($node->shipping_methods) {
+        foreach ($node->shipping_methods as $type => $methods) {
+          foreach ($methods as $method) {
+            if ($method) {
+              db_query("INSERT INTO {ec_shipping_product} (vid, nid, type, method) VALUES (%d, %d, '%s', '%s')", $node->vid, $node->nid, $type, $method);
+            }
+          }
+        }
+      }
+      break;
+
+    case 'delete':
+      db_query('DELETE FROM {ec_shipping_product} WHERE nid = %d', $node->nid);
+      break;
+
+    case 'delete revision':
+      db_query('DELETE FROM {ec_shipping_product} WHERE vid = %d', $node->vid);
+      break;
+  }
+}
+
 /********************************************************************
  * Module Functions
  ********************************************************************/
 
 /**
- * Controller. This callback is invoked via menu() when "admin/store/shipping*" is hit.
+ * Return shipping methods.
  */
-function shipping_admin($action = '') {
-
-  $edit = $_POST['edit'];
-  $op = $_POST["op"];
+function shipping_get_methods($module = NULL, $ptype = NULL) {
+  // If a module is set, we want only its methods. Otherwise, we want all.
+  $methods = $module ? module_invoke($module, 'shipping_methods') : module_invoke_all('shipping_methods');
+  // If a ptype is set, we want only its registered methods.
+  if ($ptype) {
+    // Load the product type settings.
+    $ptype_methods = variable_get($ptype . '_shipping_methods', array());
+    // We filter the values because checkboxes saves unneeded zeros.
+    foreach ($ptype_methods as $type => $method) {
+      $ptype_methods[$type] = array_filter($method);
+    }
+    // Filter to unset any methods not applicable for this product type.
+    $methods = shipping_methods_filter($methods, $ptype_methods);
+  }
+  return $methods;
+}
 
-  switch ($op ? $op : $action) {
-    case t('Save configuration'):
-      variable_set("shipping_method", $edit["shipping_method"]);
-      drupal_set_message(t('shipping options have been saved'));
-      $output = shipping_admin_overview();
-      break;
+/**
+ * Filter a set of shipping methods.
+ *
+ * Arrays of methods are generally in one of two different formats.
+ * The first is that returned by the _shipping_methods hook, e.g,
+ * 
+ * <code>
+ * $methods['ups'] = array(
+ *    '#title' => t('UPS'),
+ *    '#description' => t('United Parcel Service of America, Inc.')
+ *  );
+ *  $methods['ups']['1DM'] = array(
+ *    '#title' => t('Next Day Air Early AM'),
+ *  );
+ * </code>
+ *
+ * The second is that used to pass product type and product shipping
+ * options, e.g.,
+ *
+ * <code>
+ * array('ups' => array('1DM', '1DA'));
+ * </code>
+ *
+ * This function is used to filter method arrays of the first type (the
+ * first argument), returning only items with corresponding values in the
+ * method of the second type (the second argument).
+ */
+function shipping_methods_filter($methods, $used_methods) {
 
-    case t('Save shipping details'):
-    
-      if (shipping_details_validate($edit)) {
-       
-        shipping_details_save($edit);
-        drupal_goto('admin/store/shipping');
+  foreach ($methods as $type => $method) {
+    // First filter out base methods, e.g, 'ups'.
+    if (!in_array($type, array_keys($used_methods))) {
+      unset($methods[$type]);
+    }
+    // For applicable methods, filter out any sub-methods not used, e.g., for 'ups', 'firstday'.
+    else {
+      foreach (element_children($method) as $key) {
+        if (!in_array($key, $used_methods[$type])) {
+          unset($methods[$type][$key]);
+        }
       }
-      else {
-        $output = shipping_admin_overview($edit);
+    }
+  }
+
+  return $methods;
+}
+
+/**
+ * Return modules implementing one or more of given methods.
+ */
+function shipping_get_method_modules($methods) {
+  $modules = array();
+  foreach (module_implements('shipping_methods') as $module) {
+    $module_methods = module_invoke($module, 'shipping_methods');
+    $shared_methods = array_intersect(array_keys($methods), array_keys($module_methods));
+    if (count($shared_methods)) {
+      foreach ($shared_methods as $method) {
+        if (count(array_intersect(element_children($methods[$method]), element_children($module_methods[$method])))) {
+          $modules[] = $module;
+          break;
+        }
       }
-      break;
+    }
+  }
+  return $modules;
+}
 
+/**
+ * Callback to send email.
+ */
+function shipping_email($action) {
+
+  switch ($action) {
     case 'send':
       drupal_set_title(t('Shipping notification'));
       $output = shipping_preview_email(arg(4), 1);
@@ -108,80 +285,11 @@
         drupal_set_message(t('Unable to send email.'), 'error');
       }
       break;
-
-    default:
-      $output = shipping_admin_overview();
   }
 
   return $output;
 }
 
-/**
- * Displays the default configuration screen for managing shipping information.
- *
- * @return string HTML
- */
-function shipping_admin_overview($edit = null) {
-  /* Grab shipping methods from other modules */
-  $method = shipping_invoke_shippingapi($edit, 'method select');
-  $radio['per_product'] = t('Shipping costs are calculated on a <strong>per product</strong> basis.');
-  foreach ($method as $key => $desc) {
-    $radio[$key] = $desc;
-  }
-  $radio['subtotal'] = t('Shipping costs are calculated based on the <strong>total cost of items</strong> in the cart.');
-  $radio["none"] = t("Do not calculate shipping costs.");
-  
-  $form['methods'] = array(
-    '#type' => 'fieldset',
-    '#title' => t("Shipping calculation methods"),
-  );
-
-  $form['methods']["shipping_method"] = array(
-    '#type' => 'radios',
-    '#title' => t('Choose one'),
-    '#default_value' => variable_get("shipping_method", "none"),
-    '#options' => $radio,
-  );
-  $form[] = array(
-    '#type' => 'submit',
-    '#value' => t("Save configuration"),
-  );
-  
-
-  $output = drupal_get_form('shipping_method', $form);
-
-  unset ($form);
-  /* goodie goodie, now let's display the form specifc to the shipping method chosen. */
-  $shipping_instance_settings = '';
-  
-  if (variable_get("shipping_method", "none") != "none") {
-    $form['details'] = array(
-      //'#type' => 'fieldset',  // This fieldset is no rendering properly. It gets shown after the table. to fix this we would need to use the #theme property
-      '#title' => t("Shipping details"),
-      '#tree' => TRUE
-      //'#theme' => 'shipping_details' // This needs to be used in order to render the table within the fieldset, however it's not working properly and needs some more work
-    );
-    $fields = module_invoke(variable_get("shipping_method", ""), "shippingapi", $edit, 'form');
-    if ($fields) {
-      if (variable_get("shipping_method", "") == "subtotal") { 
-        $output .= "<p>". t("Enter the subtotals a customer should have for a given shipping cost to be applied.  This can be a single price (e.g. 1.00, 2.99, 5.34) or a range (e.g. 1.00-3.99, 4.00-10.26). Then, enter the cost of shipping for that subtotal (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."</p>";
-      }
-      else {
-        $output .= "<p>". t("Enter the number of items a customer should have for a given shipping cost to be applied.  This can be a single digit (e.g. 1, 2, 5) or a range (e.g. 1-3, 4-10, 5-100). Then, enter the cost of shipping for that quantity (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."</p>";
-      }
-      $form['details'] = array_merge($form['details'], $fields);
-       
-      $form[] = array(
-        '#type' => 'submit',
-        '#value' => t("Save shipping details"),
-      );
-      $output .= drupal_get_form('shipping_details', $form);
-      //$output .= form_render($form); // This is used instead of previous line in order to use the #theme property
-    }
-  }
-  return $output;
-}
-
 function shipping_preview_email($txnid, $preview = null) {
 
   $t = store_transaction_load($txnid);
@@ -294,7 +402,7 @@
   );
 
 
-  $output .= drupal_get_form('your_form_id', $form);
+  $output .= drupal_get_form('shipping_preview_email', $form);
   return $output;
 }
 
@@ -310,352 +418,5 @@
   return $mail_sent;
 }
 
-/**
- * Run the validation routines
- */
-function shipping_details_validate(&$edit) {
-  /* We use this instead of module_invoke to avoid passing $edit by reference. */
-  $f = variable_get("shipping_method", ""). "_shippingapi";
-  if (function_exists($f)) {
-    $f($edit, 'validate');
-  }
-
-  return ((form_get_errors()) ? false : true);
-}
-
-/**
- * Save the shipping information.
- *
- * This function doesn't save any information itself. Instead it calls other
- * method save functions.
- *
- * @return string shipping_admin_overview()
- */
-function shipping_details_save($edit) {
-  module_invoke(variable_get("shipping_method", ""), "shippingapi", $edit, "save");
-  drupal_set_message('Shipping information updated.');
-}
-
-/**
- * Calculcate the shipping cost
- *
- * Invoke the methods pf the current shipping scheme to calculate shipping cost.
- *
- * @param array $item A sequential array of all items in a user's cart
- *
- * @return float The actual cost of shipping
- */
-function shipping_calculate_cost($item) {
-  return module_invoke(variable_get("shipping_method", ""), "shippingapi", $item, "calculate");
-}
-
-/**
- * The controller for the plugin API hooks. It iterates through the enabled
- * plugins and calls their *_shippingapi hook if one exists, passing it the
- * action (via $op) to execute.
- *
- * @param object $items
- * @param string $op The action to execute (e.g., validate, insert, update, delete)
- * @param string $arg Optional argument to pass along. Currently being used to
- *                    pass an array of errors during form display
- *
- * @return array Collection of output from the all *_shippingapi calls.
- */
-function shipping_invoke_shippingapi(&$items, $op, $arg = 0) {
-
-  $return = array();
-  foreach (module_list() as $name) {
-    $function = $name ."_shippingapi";
-    if (function_exists($function)) {
-      $result = $function($items, $op, $arg);
-      if (isset($result)) {
-        $return = array_merge($return, $result);
-      }
-    }
-  }
-  return $return;
-}
-
-/********************************************************************
- * Default Shipping Method Functions
- *
- *  - I wanted to have a default shipping calculation method, and this
- *    seemed like the best place to put it.
- ********************************************************************/
-
-/**
- * The Controller to create a new shipping interface.
- */
-function shipping_shippingapi(&$items, $op) {
-
-  switch ($op) {
-    case 'method select': /*      module name => desc    */
-      return array('shipping' => t('Shipping costs are calculated based on the <strong>total quantity of items</strong> in the cart.'));
-
-    case 'calculate':
-      return shipping_calculate($items);
-
-    case 'form':
-      return shipping_form($items);
-
-    case 'validate':
-      shipping_validate($items);
-    break;
-
-    case 'save':
-    
-      return shipping_save($items);
-  }
-}
-
-function shipping_calculate($items) {
-
-  $type = variable_get("shipping_method", "");
-  $pivot_element = 0;
-  $cost = 0;
-
-  if ($type == "subtotal") {
-    foreach ($items as $item) {
-      if (product_is_shippable($item->nid)) {
-        $pivot_element += $item->qty * $item->price;
-      }
-    }
-  }
-  else {
-    foreach ($items as $item) {
-      if (product_is_shippable($item->nid)) {
-        $pivot_element += $item->qty;
-      }
-    }
-  }
-
-  $function = $type. "_load";
-  $ship = $function();
-  $numset = count($ship['ship_item_slot']);
-  for ($i = 0; $i < $numset; $i++) {
-    $item       = $ship['ship_item_slot'][$i];
-    $price      = $ship['ship_price_slot'][$i];
-
-    /* Check for range match */
-    if (strstr($item, '-')) {
-      list($low, $high) = explode('-', $item);
-      if ($pivot_element >= $low and $pivot_element <= $high) {
-        $cost = $price;
-      }
-    }
-    /* Check for exact item quantity match */
-    elseif ($item == $pivot_element) {
-      $cost = $price;
-    }
-  }
-
-  return $cost;
-}
-
-function shipping_validate(&$edit) {
-  $errors = array();
-
-  if (variable_get("shipping_method", "") == "subtotal") {
-    $row_1_error = t("One of the subtotal columns contain an invalid format.");
-  }
-  else {
-    $row_1_error = t("One of the item quantities columns contain an invalid format.");
-  }
-
-  $numset = count($edit['ship_item_slot']);
-  for ($i = 0; $i < $numset; $i++) {
-
-    $item   = $edit['ship_item_slot'][$i];
-    $price  = $edit['ship_price_slot'][$i];
-
-    if ($item != '' && $price != '') {
-      if (!preg_match("/^[0-9.]+([-]?[0-9.]+)*$/", $item)) {
-        $errors['ship_item_slot'] = $row_1_error;
-      }
-
-      /* Remove the currency symbol at the beginning of the price if it exists */
-      if (substr($price, 0, 1) == variable_get('payment_symbol', '$')) {
-        $price = substr($price, count(variable_get('payment_symbol', '$')));
-        $edit['ship_price_slot'][$i] = $price;
-      }
-      $price = str_replace(',', '', $price);
-      if (!is_numeric($price)) {
-        $errors['ship_price_slot'] = t("Please enter a numeric value for the shipping cost.");
-      }
-    }
-    elseif (($item == '' && $price != '') || ($item != '' && $price == '')) {
-      $errors['ship_blank_field'] = t("You need to enter a value for both columns of a row and not just one of the fields.");
-    }
-  }
-
-  foreach ($errors as $name => $message) {
-    form_set_error($name, $message);
-  }
-  return count($errors) == 0;
-}
 
-function shipping_form($edit = null) {
 
-  if (!$edit) {
-    $edit = shipping_load();
-  }
-
-  $type = variable_get("shipping_method", "");
-  if ($type == "subtotal") { 
-    $sentance_pre = t("A subtotal of ");
-    //$help = "<p>". t("Enter the subtotals a customer should have for a given shipping cost to be applied.  This can be a single price (e.g. 1.00, 2.99, 5.34) or a range (e.g. 1.00-3.99, 4.00-10.26). Then, enter the cost of shipping for that subtotal (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."</p>";
-  }
-  else {
-    $column_name = t("Item quantity");
-    $sentance_pre = t("A quantity of ");
-    $sentance_post = t("items");
-    //$help = "<p>". t("Enter the number of items a customer should have for a given shipping cost to be applied.  This can be a single digit (e.g. 1, 2, 5) or a range (e.g. 1-3, 4-10, 5-100). Then, enter the cost of shipping for that quantity (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."</p>";
-  }
-
-  $header = array($column_name, t("Shipping cost"));
-  $fields = 10;
-
-  for ($i = 0; $i < $fields; $i++) {
-   
-   $form['ship_item_slot'][$i] = array(
-    '#type' => 'textfield',
-    '#value' => $edit['ship_item_slot'][$i],
-    '#size' => 35,
-    '#maxlength' => 128,
-    '#required' => false,
-    '#prefix' => $sentance_pre,
-    '#suffix' => $sentance_post
-   );
-   $form['ship_price_slot'][$i] = array(
-    '#type' => 'textfield',
-    '#value' => $edit['ship_price_slot'][$i],
-    '#size' => 35,
-    '#maxlength' => 128,
-    '#required' => false,
-    '#prefix' => t('cost'),
-    '#suffix' => t("in shipping.")
-   );
-  }
-  return $form;
-}
-
-function theme_shipping_details(&$form) {
-  for ($i = 0; $i <= 9; $i++) {
-      $row = array();
-      $row[] = form_render($form['details']['ship_item_slot'][$i]);
-      $row[] = form_render($form['details']['ship_price_slot'][$i]);
-      $rows[] = $row;
-  }
-  
-  $type = variable_get("shipping_method", "");
-  if ($type == "subtotal") {
-    $column_name = t("Subtotal");
-  }
-  else {
-    $column_name = t("Item quantity");
-  }
-  $header = array($column_name, t("Shipping cost"));
-  $output = theme('table', $header, $rows);
-  $output .= form_render($form);
-  return $output;
-}
-
-function shipping_save($edit) {
-
-  $type = variable_get("shipping_method", ""). "_shipping";
-  $numset = count($edit['details']['ship_item_slot']);
-  /* As a safeguard, don't delete the old shipping values unless we're sure we
-     got at least one update. */
-  if ($numset) {
-    db_query("DELETE FROM {ec_$type}");
-  }
-
-  for ($i = 0; $i < $numset; $i++) {
-    $item       = $edit['details']['ship_item_slot'][$i];
-    $price      = $edit['details']['ship_price_slot'][$i];
-    if ($item && $price) {
-      db_query("INSERT INTO {ec_$type} (item_slot, price_slot) VALUES ('%s', '%s')", $item, $price);
-    }
-  }
-
-  drupal_set_message(t("the shipping options have been saved"));
-}
-
-function shipping_load() {
-
-  $type = variable_get("shipping_method", ""). "_shipping";
-  $result = db_query("SELECT * FROM {ec_$type}");
-
-  while ($data = db_fetch_array($result)) {
-    $output['ship_item_slot'][] = $data['item_slot'];
-    $output['ship_price_slot'][] = $data['price_slot'];
-  }
-
-  return $output;
-}
-
-/**
- * Some young whipper snapper voodoo. I have extended the shipping module with
- * two plugins in this shipping API module.  They are both so very similar that
- * i didn't overlap code.  Kids, don't try this at home. Technically only one
- * shipping api should be in each module
- */
-function subtotal_shippingapi(&$items, $op, $arg = '') {
-  return shipping_shippingapi($items, $op, $arg);
-}
-
-function subtotal_load() {
-  return shipping_load();
-}
-
-function per_product_shippingapi(&$items, $op) {
-
-  switch ($op) {
-    case 'calculate':
-      return per_product_calculate($items);
-    case 'per_product' :
-      return TRUE;
-    case 'per_product_load':
-      return db_fetch_object(db_query('SELECT * FROM {ec_per_product_shipping} WHERE nid = %d', $items->nid));
-
-    case 'per_product_form':
-      $form['shipping_fee'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Shipping charge'),
-        '#default_value' => $items->shipping_fee,
-        '#size' => 25,
-        '#maxlength' => 50,
-        '#description' => t('Enter any shipping or handling charges for this product here.'),
-      );
-      return $form;
-
-    case 'per_product_validate':
-      if ($items->shipping_fee != NULL && !is_numeric($items->shipping_fee)) {
-        form_set_error('shipping_fee', t('You must enter a numeric value for the shipping fee.'));
-      }
-      return form_get_errors();
-
-    case 'per_product_update':
-    case 'per_product_insert':
-      return per_product_save($items);
-  }
-}
-
-function per_product_save($node) {
-  if ($node->per_product_shipping) {
-    db_query('DELETE FROM {ec_per_product_shipping} WHERE nid = %d', $node->nid);
-    db_query('INSERT INTO {ec_per_product_shipping} (nid, shipping_fee) VALUES (%d, %f)', $node->nid, $node->shipping_fee);
-  }
-}
-
-/**
- * An array of items is passed in.
- */
-function per_product_calculate($items) {
-  $shipping = 0;
-  foreach ($items as $nid => $i) {
-    $shipping += $i->qty * $i->shipping_fee;
-  }
-  return $shipping;
-}
-?>