Index: README.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uc_edi/README.txt,v
retrieving revision 1.1
diff -u -p -r1.1 README.txt
--- README.txt	15 Oct 2010 08:31:58 -0000	1.1
+++ README.txt	29 Oct 2010 23:09:10 -0000
@@ -30,6 +30,41 @@ To submit bug reports and feature sugges
 
 -- CONFIGURATION --
 
+* Export Templates
+  EDI templates use Drupal's theme system to allow complete control over
+  exported content. To customize exported content you can either override
+  existing templates or define new templates.
+
+  - Override existing templates
+    Templates can be overridden using normal Drupal themeing practices, i.e.
+    copy uc_edi-default.tpl.php into your theme directory. If multiple themes
+    are used, the template file must be copied into each themes directory.
+    *Due to the way the Drupal theme layer works, the dummy file
+    (uc_edi.tpl.php) must also be copied.*
+
+  - Create new templates
+    New templates can be defined by implementing hook_uc_edi_templates(). An
+    array of template names should be returned.
+
+  - Preprocess functions
+    Both methods above can take advantage of the hook_preprocess_uc_edi()
+    function which lets you add anything as a variable to the EDI template. The
+    default template_preprocess_uc_edi() function adds all order tokens as
+    variables including Ubercart and other order tokens correctly defined by
+    contributed modules. For a list of order token variables added by Ubercart
+    EDI, see the documentation block at the top of the default template file.
+
+  - Testing
+    To easily test new template files, an "export" tab has been added to the
+    order view page. This new tab allows you to select any defined template and
+    automatically updates the output to that of the newly selected template. A
+    save button allows you to save an export file to your computer for testing
+    the import process of your internal system.
+    *This order export form does not update the order status and should only be
+    used for testing template files or viewing and saving orders that have
+    already been processed. To routinely download export files manually, the
+    Manual import/export form should be used.
+
 * Configure user permissions in Administer >> User management >> Permissions >>
   uc_edi module:
 
Index: uc_edi.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uc_edi/uc_edi.info,v
retrieving revision 1.2
diff -u -p -r1.2 uc_edi.info
--- uc_edi.info	15 Oct 2010 08:28:04 -0000	1.2
+++ uc_edi.info	29 Oct 2010 23:09:10 -0000
@@ -1,6 +1,7 @@
 ; $Id: uc_edi.info,v 1.2 2010/10/15 08:28:04 antoinesolutions Exp $
 name = EDI Import/Export
 description = EDI import and export module; currently handles orders.
+dependencies[] = token
 dependencies[] = uc_order
 package = Ubercart - extra
 core = 6.x
Index: uc_edi.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uc_edi/uc_edi.install,v
retrieving revision 1.2
diff -u -p -r1.2 uc_edi.install
--- uc_edi.install	15 Oct 2010 08:28:04 -0000	1.2
+++ uc_edi.install	29 Oct 2010 23:09:10 -0000
@@ -43,11 +43,9 @@ function uc_edi_uninstall() {
   variable_del('uc_edi_order_export_mail_body');
   variable_del('uc_edi_order_export_freq');
   variable_del('uc_edi_order_export_status');
+  variable_del('uc_edi_order_export_template');
   variable_del('uc_edi_order_exported_status');
   variable_del('uc_edi_order_exported_message');
-  variable_del('uc_edi_order_export_pattern');
-  variable_del('uc_edi_order_export_product_pattern');
-  variable_del('uc_edi_order_export_delimiter');
   variable_del('uc_edi_order_export_last');
   variable_del('uc_edi_order_export_next_batch_id');
   variable_del('uc_edi_order_export_product_line_number');
@@ -89,3 +87,18 @@ function uc_edi_update_6000() {
 
   return array();
 }
+
+/**
+ * Implements hook_update_N().
+ */
+function uc_edi_update_6001() {
+  // Clear the menu cache.
+  menu_cache_clear_all();
+
+  // Remove deprecated vaiables
+  variable_del('uc_edi_order_export_pattern');
+  variable_del('uc_edi_order_export_product_pattern');
+  variable_del('uc_edi_order_export_delimiter');
+
+  return array();
+}
Index: uc_edi.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uc_edi/uc_edi.module,v
retrieving revision 1.2
diff -u -p -r1.2 uc_edi.module
--- uc_edi.module	15 Oct 2010 08:28:04 -0000	1.2
+++ uc_edi.module	29 Oct 2010 23:09:10 -0000
@@ -14,6 +14,30 @@
  ******************************************************************************/
 
 /**
+ * Implements hook_help().
+ */
+function uc_edi_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#uc_edi':
+      $output = '';
+      $output .= '<a name="export-templates"><h3>' . t('Export Templates') . '</h3></a>';
+      $output .= '<p>' . t("EDI templates use Drupal's theme system to allow complete control over exported content. To customize exported content you can either override existing templates or define new templates.") . '</p>';
+      $output .= '<h4>' . t('Override existing templates') . '</h4>';
+      $output .= '<p>' . t('Templates can be overridden using normal Drupal themeing practices, i.e. copy uc_edi-default.tpl.php into your theme directory. If multiple themes are used, the template file must be copied into each themes directory.') . '<br />';
+      $output .= '<strong>' . t('Due to the way the Drupal theme layer works, the dummy file (uc_edi.tpl.php) must also be copied.') . '</strong></p>';
+      $output .= '<h4>' . t('Create new templates') . '</h4>';
+      $output .= '<p>' . t('New templates can be defined by implementing hook_uc_edi_templates(). An array of template names should be returned.') . '</p>';
+      $output .= '<h4>' . t('Preprocess functions') . '</h4>';
+      $output .= '<p>' . t('Both methods above can take advantage of the hook_preprocess_uc_edi() function which lets you add anything as a variable to the EDI template. The default template_preprocess_uc_edi() function adds all order tokens as variables including Ubercart and other order tokens correctly defined by contributed modules. For a list of order token variables added by Ubercart EDI, see the documentation block at the top of the default template file.') . '</p>';
+      $output .= '<h4>' . t('Testing') . '</h4>';
+      $output .= '<p>' . t('To easily test new template files, an "export" tab has been added to the order view page. This new tab allows you to select any defined template and automatically updates the output to that of the newly selected template. A save button allows you to save an export file to your computer for testing the import process of your internal system.') . '<br />';
+      $output .= '<strong>' . t('This order export form does not update the order status and should only be used for testing template files or viewing and saving orders that have already been processed. To routinely download export files manually, the <a href="!url">Manual import/export form</a> should be used.', array('!url' => url('admin/store/orders/edi'))) . '</strong></p>';
+
+      return $output;
+  }
+}
+
+/**
  * Implements hook_menu().
  */
 function uc_edi_menu() {
@@ -61,6 +85,21 @@ function uc_edi_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  $items['admin/store/orders/%uc_order/export'] = array(
+    'title' => 'Export',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('uc_edi_order_export_form', 3),
+    'access arguments' => array('administer edi orders'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 9,
+  );
+  $items['admin/store/orders/%uc_order/export/template-js'] = array(
+    'page callback' => 'uc_edi_order_export_template_js',
+    'page arguments' => array(3),
+    'access arguments' => array('administer edi orders'),
+    'type' => MENU_CALLBACK,
+  );
+
   return $items;
 }
 
@@ -92,6 +131,152 @@ function uc_edi_cron() {
   }
 }
 
+/**
+ * Implements hook_theme().
+ */
+function uc_edi_theme() {
+  return array(
+    'uc_edi' => array(
+      'template' => 'uc_edi',
+      'path' => drupal_get_path('module', 'uc_edi') . '/templates',
+      'arguments' => array(
+        'order' => NULL,
+        'template' => 'default',
+        'uc_edi_header' => NULL,
+        'uc_edi_footer' => NULL,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_token_values().
+ */
+function uc_edi_token_values($type, $object = NULL) {
+  $values = array();
+  switch ($type) {
+    case 'order':
+      $order = $object;
+
+      $values['order-edi-batch-id'] = variable_get('uc_edi_order_next_batch_id', 1000);
+      $values['order-payment-method'] = check_plain($order->payment_method);
+
+      $shipping_charges = 0;
+      $tax_charges = 0;
+      if (is_array($order->line_items)) {
+        // Set line item uc_price context.
+        $context = array(
+          'revision' => 'altered',
+          'type' => 'line_item',
+        );
+
+        foreach ($order->line_items as $key => $value) {
+          $context['subject'] = array(
+            'order' => $order,
+            'line_item' => $order->line_items[$key],
+          );
+
+          // Calculate the total shipping charges.
+          if ($value['type'] == 'shipping') {
+            $shipping_charges += uc_price($order->line_items[$key]['amount'], $context);
+          }
+          // Calculate the total tax charges.
+          if ($value['type'] == 'tax') {
+            $tax_charges += uc_price($order->line_items[$key]['amount'], $context);
+          }
+        }
+      }
+
+      $context = array(
+        'revision' => 'altered',
+      );
+      $values['order-shipping-charges'] = uc_price($shipping_charges, $context);
+      $values['order-tax-charges'] = uc_price($tax_charges, $context);
+
+      $context['revision'] = 'formatted';
+      $values['order-shipping-charges-formatted'] = uc_price($shipping_charges, $context);
+      $values['order-tax-charges-formatted'] = uc_price($tax_charges, $context);
+
+      $values['order-shipping-first-name'] = check_plain($order->delivery_first_name);
+      $values['order-shipping-last-name'] = check_plain($order->delivery_last_name);
+      $values['order-shipping-company'] = check_plain($order->delivery_company);
+      $values['order-shipping-street1'] = check_plain($order->delivery_street1);
+      $values['order-shipping-street2'] = check_plain($order->delivery_street2);
+      $values['order-shipping-city'] = check_plain($order->delivery_city);
+      $values['order-shipping-zone'] = ($order->delivery_zone != 0 ? check_plain($order->delivery_zone) : '');
+      $values['order-shipping-zone-code'] = uc_get_zone_code(check_plain($order->delivery_zone));
+      $values['order-shipping-postal_code'] = check_plain($order->delivery_postal_code);
+      $values['order-shipping-country'] = check_plain($order->delivery_country);
+      $values['order-shipping-country-name'] = db_result(db_query("SELECT country_name FROM {uc_countries} WHERE country_id = %d", $order->delivery_country));
+      $values['order-shipping-country-code-2'] = db_result(db_query("SELECT country_iso_code_2 FROM {uc_countries} WHERE country_id = %d", $order->delivery_country));
+      $values['order-shipping-country-code-3'] = db_result(db_query("SELECT country_iso_code_3 FROM {uc_countries} WHERE country_id = %d", $order->delivery_country));
+      $values['order-billing-first-name'] = check_plain($order->billing_first_name);
+      $values['order-billing-last-name'] = check_plain($order->billing_last_name);
+      $values['order-billing-company'] = check_plain($order->billing_company);
+      $values['order-billing-street1'] = check_plain($order->billing_street1);
+      $values['order-billing-street2'] = check_plain($order->billing_street2);
+      $values['order-billing-city'] = check_plain($order->billing_city);
+      $values['order-billing-zone'] = check_plain($order->billing_zone);
+      $values['order-billing-zone-code'] = uc_get_zone_code(check_plain($order->billing_zone));
+      $values['order-billing-postal_code'] = check_plain($order->billing_postal_code);
+      $values['order-billing-country'] = check_plain($order->billing_country);
+      $values['order-billing-country-name'] = db_result(db_query("SELECT country_name FROM {uc_countries} WHERE country_id = %d", $order->billing_country));
+      $values['order-billing-country-code-2'] = db_result(db_query("SELECT country_iso_code_2 FROM {uc_countries} WHERE country_id = %d", $order->billing_country));
+      $values['order-billing-country-code-3'] = db_result(db_query("SELECT country_iso_code_3 FROM {uc_countries} WHERE country_id = %d", $order->billing_country));
+      $values['order-date-created-unix'] = $order->created;
+      $values['order-date-modified-unix'] = $order->modified;
+      break;
+  }
+
+  return $values;
+}
+
+/**
+ * Implements hook_token_list().
+ */
+function uc_edi_token_list($type = 'all') {
+  $tokens = array();
+
+  if ($type == 'order' || $type == 'ubercart' || $type == 'all') {
+    $tokens['uc_edi']['order-edi-batch-id'] = t('The export batch number.');
+    $tokens['uc_edi']['order-payment-method'] = t('The order payment method.');
+    $tokens['uc_edi']['order-shipping-charges'] = t('The "altered" order total shipping charges.');
+    $tokens['uc_edi']['order-shipping-charges-formatted'] = t('The "formatted" order total shipping charges.');
+    $tokens['uc_edi']['order-tax-charges'] = t('The "altered" order total tax charges.');
+    $tokens['uc_edi']['order-tax-charges-formatted'] = t('The "formatted" order total tax charges.');
+    $tokens['uc_edi']['order-shipping-first-name'] = t('The first name of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-last-name'] = t('The last name of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-company'] = t('The company of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-street1'] = t('The street1 of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-street2'] = t('The street2 of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-city'] = t('The city of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-zone'] = t('The zone/province/state of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-zone-code'] = t('The zone/province/state code of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-postal-code'] = t('The postal/zip code of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-country'] = t('The country ISO 3166-1 Numeric-3 code of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-country-name'] = t('The country ISO 3166-1 English short name of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-country-code-2'] = t('The country ISO 3166-1 Alpha-2 code of the order shipping address.');
+    $tokens['uc_edi']['order-shipping-country-code-3'] = t('The country ISO 3166-1 Alpha-3 code of the order shipping address.');
+    $tokens['uc_edi']['order-billing-first-name'] = t('The first name of the order billing address.');
+    $tokens['uc_edi']['order-billing-last-name'] = t('The last name of the order billing address.');
+    $tokens['uc_edi']['order-billing-company'] = t('The company of the order billing address.');
+    $tokens['uc_edi']['order-billing-street1'] = t('The street1 of the order billing address.');
+    $tokens['uc_edi']['order-billing-street2'] = t('The street2 of the order billing address.');
+    $tokens['uc_edi']['order-billing-city'] = t('The city of the order billing address.');
+    $tokens['uc_edi']['order-billing-zone'] = t('The zone/province/state of the order billing address.');
+    $tokens['uc_edi']['order-billing-zone-code'] = t('The zone/province/state code of the order billing address.');
+    $tokens['uc_edi']['order-billing-postal-code'] = t('The postal/zip code of the order billing address.');
+    $tokens['uc_edi']['order-billing-country'] = t('The country ISO 3166-1 Numeric-3 code of the order billing address.');
+    $tokens['uc_edi']['order-billing-country-name'] = t('The country ISO 3166-1 English short name of the order billing address.');
+    $tokens['uc_edi']['order-billing-country-code-2'] = t('The country ISO 3166-1 Alpha-2 code of the order billing address.');
+    $tokens['uc_edi']['order-billing-country-code-3'] = t('The country ISO 3166-1 Alpha-3 code of the order billing address.');
+    $tokens['uc_edi']['order-date-created-unix'] = t('The Unix/POSIX time the order was created.');
+    $tokens['uc_edi']['order-date-modified-unix'] = t('The Unix/POSIX time the order was last modified.');
+  }
+
+  return $tokens;
+}
+
 /*******************************************************************************
  * Hook Functions (Ubercart)
  ******************************************************************************/
@@ -110,6 +295,13 @@ function uc_edi_order_state() {
   return $states;
 }
 
+/**
+ * Implements hook_uc_edi_templates().
+ */
+function uc_edi_uc_edi_templates() {
+  return array('default');
+}
+
 /******************************************************************************
  * Callback Functions and Forms
  ******************************************************************************/
@@ -287,6 +479,13 @@ function uc_edi_settings_form() {
     '#options' => $options,
     '#default_value' => variable_get('uc_edi_order_export_status', 'edi_export'),
   );
+  $form['export']['orders']['uc_edi_order_export_template'] = array(
+    '#type' => 'select',
+    '#title' => t('Export template'),
+    '#description' => t('Select the export template to use.<br />This is separate from the template used when exporting on checkout completion which is configured through <a href="!url">Conditional actions</a>.', array('!url' => url(CA_UI_PATH))),
+    '#options' => uc_edi_template_options(),
+    '#default_value' => variable_get('uc_edi_order_export_template', 'default'),
+  );
   $form['export']['orders']['uc_edi_order_exported_status'] = array(
     '#type' => 'select',
     '#title' => t('Exported order status'),
@@ -300,24 +499,6 @@ function uc_edi_settings_form() {
     '#description' => t('Enter a message displayed on orders when an order gets exported.'),
     '#default_value' => variable_get('uc_edi_order_exported_message', '-'),
   );
-  $form['export']['orders']['uc_edi_order_export_pattern'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Order export pattern'),
-    '#description' => t('This is the pattern used for each order in the export file.<br />Separate values by commas and use any order field keyword, i.e. !delivery_first_name.<br />Use: !_ to add a line break, !products to add the products, !delivery_name, !billing_name.<br />Add (#) to any key where # is the maximum length you want the value to be.'),
-    '#default_value' => variable_get('uc_edi_order_export_pattern', uc_edi_default_order_export_pattern()),
-  );
-  $form['export']['orders']['uc_edi_order_export_product_pattern'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Order product export pattern'),
-    '#description' => t('This is the pattern used for each order product in the export file.<br />Separate values by commas and use any product field keyword, i.e. !qty.<br />Use: !_ to add a line break, !# for the incremented order line number.'),
-    '#default_value' => variable_get('uc_edi_order_export_product_pattern', uc_edi_default_order_product_export_pattern()),
-  );
-  $form['export']['orders']['uc_edi_order_export_delimiter'] = array(
-    '#type' => 'select',
-    '#title' => t('Export pattern delimiter'),
-    '#options' => drupal_map_assoc(array('comma', 'pipe', 'tab', 'nothing')),
-    '#default_value' => variable_get('uc_edi_order_export_delimiter', 'tab'),
-  );
   $form['export']['orders']['uc_edi_order_next_batch_id'] = array(
     '#type' => 'textfield',
     '#title' => t('Next order batch ID'),
@@ -470,9 +651,23 @@ function uc_edi_order_admin_form(&$form_
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
   );
-  foreach ($orders as $order_id) {
+  $count = count($orders);
+  $header = TRUE;
+  $footer = FALSE;
+  for ($i = 0; $order_id = $orders[$i]; ++$i) {
+    // Load the order object.
     $order = uc_order_load($order_id);
-    $preview .= uc_edi_generate_order_export($order);
+
+    if ($i + 1 == $count) {
+      $footer = TRUE;
+    }
+
+    // Create the order export string.
+    $preview .= theme('uc_edi', $order, variable_get('uc_edi_order_export_template', 'default'), $header, $footer);
+
+    if ($header) {
+      $header = FALSE;
+    }
   }
   if (empty($preview)) {
     $preview = t('No orders awaiting export.');
@@ -527,9 +722,211 @@ function uc_edi_order_admin_form_export_
   drupal_set_message(t('Order export timer reset.'));
 }
 
-/*******************************************************************************
+/**
+ * Generates a manual order export form for individual orders.
+ */
+function uc_edi_order_export_form($form_state, $order) {
+  drupal_set_message(t('This order export form does not update the order status and should only be used for testing template files or viewing and saving already exported orders. To manually download export files, the <a href="!url">Manual import/export form</a> should be used.', array('!url' => url('admin/store/orders/edi'))), 'warning');
+
+  $template = variable_get('uc_edi_order_export_template', 'default');
+  $form['template'] = array(
+    '#type' => 'select',
+    '#title' => t('Template'),
+    '#description' => t('Select the export template to use.'),
+    '#options' => uc_edi_template_options(),
+    '#default_value' => $template,
+    '#ahah' => array(
+      'method' => 'replace',
+      'path' => 'admin/store/orders/' . $order->order_id . '/export/template-js',
+      'wrapper' => 'template-sub-wrapper',
+    ),
+  );
+  $form['export'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Export'),
+    '#collapsible' => FALSE,
+    '#collapsed' => FALSE,
+  );
+  $form['export']['template_sub'] = uc_edi_order_export_template($order, $template);
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Return the unrendered order export form item.
+ *
+ * @param $order
+ *   A reference to an order object to export.
+ * @param $template
+ *   A reference to an order export template name.
+ *
+ *  @return
+ *    An array containing the unrendered form item.
+ */
+function uc_edi_order_export_template(&$order, &$template) {
+  // Create the order export string.
+  $form = array(
+    '#value' => theme('uc_edi', $order, $template, TRUE, TRUE),
+  );
+
+  // Add prefix and suffix elements.
+  $form += array(
+    '#prefix' => '<pre id="template-sub-wrapper">',
+    '#suffix' => '</pre>',
+  );
+
+  return $form;
+}
+
+/**
+ * Return the rendered order export form item.
+ *
+ * @param $order
+ *   A reference to an order object to export.
+ */
+function uc_edi_order_export_template_js(&$order) {
+  // Retrieve the template from $_POST.
+  $template = $_POST['template'];
+
+  // Retrieve the unrendered order export form item.
+  $form = uc_edi_order_export_template($order, $template);
+
+  // Remove #prefix and #suffix to avoid a duplicate wrapper.
+  unset($form['#prefix'], $form['#suffix']);
+
+  // Build and render the form item, then return it in JSON format.
+  $form_state = array();
+  $form = form_builder($form['#name'], $form, $form_state);
+  $output = drupal_render($form);
+  drupal_json(array('status' => TRUE, 'data' => $output));
+}
+
+/**
+ * Download an individual order export.
+ */
+function uc_edi_order_export_form_submit($form, &$form_state) {
+  $order = $form['#parameters'][2];
+
+  // Build the http headers necessary for a file download.
+  $prefix = variable_get('uc_edi_order_export_prefix', 'export-');
+  $ext = variable_get('uc_edi_order_export_extension', 'edi');
+  $headers = array(
+    'Content-Length:' . filesize($output),
+    'Content-Disposition: attachment; filename="' . $prefix . $order->order_id . '.' . $ext . '"',
+    'Content-Type: application/octet-stream'
+  );
+  foreach ($headers as $header) {
+    $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
+    drupal_set_header($header);
+  }
+
+  // Output the order export.
+  print theme('uc_edi', $order, $form_state['values']['template'], TRUE, TRUE);
+  exit();
+}
+
+/******************************************************************************
  * Module and Helper Functions
- ******************************************************************************/
+ *****************************************************************************/
+
+/**
+ * Preprocess a formatted export with an order's data.
+ */
+function template_preprocess_uc_edi(&$variables) {
+  $variables['products'] = $variables['order']->products;
+  if (!is_array($variables['products'])) {
+    $variables['products'] = array();
+  }
+
+  $variables['line_items'] = $variables['order']->line_items;
+  $items = _line_item_list();
+  foreach ($items as $item) {
+    if (isset($item['display_only']) && $item['display_only'] == TRUE) {
+      $result = $item['callback']('display', $variables['order']);
+      if (is_array($result)) {
+        foreach ($result as $line) {
+          $variables['line_items'][] = array(
+            'line_item_id' => $line['id'],
+            'title' => $line['title'],
+            'amount' => $line['amount'],
+            'weight' => $item['weight'],
+            'data' => $item['data'],
+          );
+        }
+      }
+    }
+  }
+  if (!is_array($variables['line_items'])) {
+    $variables['line_items'] = array();
+  }
+  usort($variables['line_items'], 'uc_weight_sort');
+
+  $types = array(
+    'order' => $variables['order'],
+  );
+
+  $full = new stdClass();
+  $full->tokens = $full->values = array();
+  foreach ($types as $type => $object) {
+    $temp = token_get_values($type, $object, FALSE, $options);
+    $full->tokens = array_merge($full->tokens, $temp->tokens);
+    $full->values = array_merge($full->values, $temp->values);
+  }
+
+  foreach ($full->tokens as $key => $token) {
+    $value = $full->values[$key];
+    $variables[str_replace('-', '_', $token)] = $value;
+  }
+
+  // Add template file suggestions.
+  $variables['template_files'] = array(
+    'uc_edi-default',
+    'uc_edi-'. $variables['template'],
+  );
+}
+
+/**
+ * Respond to EDI template definitions.
+ *
+ * This hook is invoked from uc_edi_template_list().
+ *
+ * @return
+ *   Array of EDI template names.
+ *
+ * @ingroup hooks
+ */
+function hook_uc_edi_templates() {
+  return array('default');
+}
+
+/**
+ * Return an array of EDI templates.
+ */
+function uc_edi_template_list() {
+  $templates = drupal_map_assoc(module_invoke_all('uc_edi_templates'));
+
+  // Sort the template names alphabetically.
+  sort($templates);
+
+  return $templates;
+}
+
+/**
+ * Return a list of options for a template select box.
+ */
+function uc_edi_template_options($custom = FALSE) {
+  $templates = drupal_map_assoc(uc_edi_template_list());
+
+  if ($custom) {
+    $templates[0] = t('Custom template');
+  }
+
+  return $templates;
+}
 
 /**
  * Create a table with links for downloading and archiving export files.
@@ -641,243 +1038,6 @@ function uc_edi_create_export($type, $co
 }
 
 /**
- * Generate an order export string from the order export pattern.
- *
- * @param $product
- *   A product object from an order.
- *
- * @return
- *   An order export string replacing placeholders in the order export pattern
- *   with values from the order.
- */
-function uc_edi_generate_order_export($order) {
-  $pattern = explode(',', variable_get('uc_edi_order_export_pattern', uc_edi_default_order_export_pattern()));
-  $delimiter = uc_edi_get_delimiter();
-
-  $values = uc_edi_order_values($order);
-
-  // Process the orders here, add the data to the current output string.
-  foreach ($pattern as $key) {
-    $key = trim($key);
-    if (substr($key, 0, 1) == '!' && strpos($key, '(') > 0) {
-      $match = substr($key, 0, strpos($key, '('));
-      $length = intval(trim(substr($key, strpos($key, '(')), '()'));
-    }
-    else {
-      $match = $key;
-      $length = FALSE;
-    }
-
-    if ($match == '!_') {
-      $output .= "\n";
-    }
-    elseif ($match == '!products') {
-      $output .= $values[$match];
-    }
-    elseif (!isset($values[$match])) {
-      $output .= $key . $delimiter;
-    }
-    else {
-      if ($length != FALSE) {
-        $temp = substr((string) $values[$match], 0, $length);
-        if (strlen($temp) < $length) {
-          $temp = $temp . str_repeat(' ', $length - strlen($temp));
-        }
-        $output .= $temp . $delimiter;
-      }
-      else {
-        $output .= $values[$match] . $delimiter;
-      }
-    }
-  }
-
-  if (substr($output, -1) !== "\n") {
-    $output .= "\n";
-  }
-
-  return $output;
-}
-
-/**
- * Generate a product export string from the product export pattern.
- *
- * @param $product
- *   A product object from an order.
- *
- * @return
- *   A product export string replacing placeholders in the product export
- *   pattern with values from the product.
- */
-function uc_edi_generate_order_product_export($product) {
-  $pattern = explode(',', variable_get('uc_edi_order_export_product_pattern', uc_edi_default_order_product_export_pattern()));
-  $delimiter = uc_edi_get_delimiter();
-
-  $values = uc_edi_order_product_values($product);
-
-  // Process the orders here, add the data to the current output string.
-  foreach ($pattern as $key) {
-    $key = trim($key);
-    if (substr($key, 0, 1) == '!' && strpos($key, '(') > 0) {
-      $match = substr($key, 0, strpos($key, '('));
-      $length = intval(trim(substr($key, strpos($key, '(')), '()'));
-    }
-    else {
-      $match = $key;
-      $length = FALSE;
-    }
-
-    if ($match == '!_') {
-      $output .= "\n";
-    }
-    elseif (!isset($values[$match])) {
-      $output .= $key . $delimiter;
-    }
-    else {
-      if ($length != FALSE) {
-        $temp = substr((string) $values[$match], 0, $length);
-        if (strlen($temp) < $length) {
-          $temp = $temp . str_repeat(' ', $length - strlen($temp));
-        }
-        $output .= $temp . $delimiter;
-      }
-      else {
-        $output .= $values[$match] . $delimiter;
-      }
-    }
-  }
-
-  if (substr($output, -1) !== "\n") {
-    $output .= "\n";
-  }
-
-  return $output;
-}
-
-/**
- * Returns a list of replacement values for order export patterns.
- *
- * @param $order
- *   Order object.
- *
- * @return
- *   Array of replacement values for order export patterns.
- */
-function uc_edi_order_values($order) {
-  static $data;
-  $order = (array) $order;
-  foreach (array_keys($order) as $key) {
-    if (!is_array($order[$key]) && !is_object($order[$key])) {
-      $values['!' . $key] = $order[$key];
-    }
-  }
-
-  // Build the products lines.
-  if (is_array($order['products'])) {
-    variable_set('uc_edi_order_export_product_line_number', 1);
-    foreach ($order['products'] as $product) {
-      $products .= uc_edi_generate_order_product_export($product);
-    }
-    $values['!products'] = $products;
-  }
-
-  // This is just an incremental value arbitrarily starting at 1000.
-  // Adjust this value to have a different starting batch ID.
-  $values['!batch_id'] = variable_get('uc_edi_order_next_batch_id', 1000);
-  variable_set('uc_edi_order_next_batch_id', $values['!batch_id'] + 1);
-
-  // This is a helper value combining the first and last names.
-  $values['!delivery_name'] = $order['delivery_first_name'] . ' ' . $order['delivery_last_name'];
-  $values['!billing_name'] = $order['billing_first_name'] . ' ' . $order['billing_last_name'];
-
-  // Add the zone code and country name values.
-  if (!isset($data['zone_code'][$order['delivery_zone']])) {
-    $values['!delivery_zone_code'] = uc_get_zone_code($order['delivery_zone']);
-    $data['zone_code'][$order['delivery_zone']] = $values['!delivery_zone_code'];
-  }
-  else {
-    $values['!delivery_zone_code'] = $data['zone_code'][$order['delivery_zone']];
-  }
-  if (!isset($data['zone_code'][$order['billing_zone']])) {
-    $values['!billing_zone_code'] = uc_get_zone_code($order['billing_zone']);
-    $data['zone_code'][$order['billing_zone']] = $values['!billing_zone_code'];
-  }
-  else {
-    $values['!billing_zone_code'] = $data['zone_code'][$order['delivery_zone']];
-  }
-
-  if (!isset($data['country_name'][$order['delivery_country']])) {
-    $values['!delivery_country_name'] = db_result(db_query("SELECT country_name FROM {uc_countries} WHERE country_id = %d", $order['delivery_country']));
-    $data['country_name'][$order['delivery_country']] = $values['!delivery_country_name'];
-  }
-  else {
-    $values['!delivery_country_name'] = $data['country_name'][$order['delivery_country']];
-  }
-
-  if (!isset($data['country_name'][$order['billing_country']])) {
-    $values['!billing_country_name'] = db_result(db_query("SELECT country_name FROM {uc_countries} WHERE country_id = %d", $order['billing_country']));
-    $data['country_name'][$order['billing_country']] = $values['!billing_country_name'];
-  }
-  else {
-    $values['!billing_country_name'] = $data['country_name'][$order['billing_country']];
-  }
-
-  if (!isset($data['country_code'][$order['delivery_country']])) {
-    $values['!delivery_country_code'] = db_result(db_query("SELECT country_iso_code_3 FROM {uc_countries} WHERE country_id = %d", $order['delivery_country']));
-    $data['country_code'][$order['delivery_country']] = $values['!delivery_country_code'];
-  }
-  else {
-    $values['!delivery_country_code'] = $data['country_code'][$order['delivery_country']];
-  }
-
-  if (!isset($data['country_code'][$order['billing_country']])) {
-    $values['!billing_country_code'] = db_result(db_query("SELECT country_iso_code_3 FROM {uc_countries} WHERE country_id = %d", $order['billing_country']));
-    $data['country_code'][$order['billing_country']] = $values['!billing_country_code'];
-  }
-  else {
-    $values['!billing_country_code'] = $data['country_code'][$order['billing_country']];
-  }
-
-  $values['!spacer'] = ' ';
-
-  $price = 0;
-  foreach ($order['line_items'] as $line) {
-    if ($line['type'] == 'shipping') {
-      $price += $line['amount'];
-    }
-  }
-  $values['!uc_shipping_charges'] = (string) round($price, 2);
-
-  return $values;
-}
-
-/**
- * Returns a list of replacement values for product export patterns.
- *
- * @param $product
- *   A product object from an order.
- *
- * @return
- *  Array or replacement values for product export patterns.
- */
-function uc_edi_order_product_values($product) {
-  $product = (array) $product;
-
-  foreach (array_keys($product) as $key) {
-    if (!is_array($product[$key]) && !is_object($product[$key])) {
-      $values['!' . $key] = $product[$key];
-    }
-  }
-
-  $values['!batch_id'] = variable_get('uc_edi_order_next_batch_id', 1000);
-  $values['!#'] = variable_get('uc_edi_order_export_product_line_number', 1);
-  variable_set('uc_edi_order_export_product_line_number', $values['!#'] + 1);
-
-  $values['!spacer'] = ' ';
-
-  return $values;
-}
-
-/**
  * Load and parse all available import files and archive them when done.
  */
 function uc_edi_import_orders() {
@@ -931,10 +1091,23 @@ function uc_edi_export_orders() {
   $result = db_query("SELECT order_id FROM {uc_orders} WHERE order_status = '%s' ORDER BY order_id", variable_get('uc_edi_order_export_status', 'edi_export'));
 
   $update_status = variable_get('uc_edi_order_exported_status', 'edi_pending');
-  while ($order = db_fetch_object($result)) {
-    // Load each valid order and create its export string.
+  $count = db_affected_rows();
+  $header = TRUE;
+  $footer = FALSE;
+  for ($i = 0; $order = db_fetch_object($result); ++$i) {
+    // Load the order object.
     $order = uc_order_load($order->order_id);
-    $output .= uc_edi_generate_order_export($order);
+
+    if ($i + 1 == $count) {
+      $footer = TRUE;
+    }
+
+    // Create the order export string.
+    $output .= theme('uc_edi', $order, variable_get('uc_edi_order_export_template', 'default'), $header, $footer);
+
+    if ($header) {
+      $header = FALSE;
+    }
 
     uc_order_update_status($order->order_id, $update_status);
     uc_order_comment_save($order->order_id, 0, variable_get('uc_edi_order_exported_message', '-'), 'order', $update_status);
@@ -1086,26 +1259,6 @@ function uc_edi_process_order_import_rul
 }
 
 /**
- * Returns the default order export pattern.
- */
-function uc_edi_default_order_export_pattern() {
-  // This is an example order export pattern.
-  $pattern = '01, !billing_name(29), !order_id(6), !uid(10), !_, 02, !delivery_name(30), !delivery_street1(20), !delivery_street2(20), !_, 03, !delivery_city(15), !delivery_zone_code(2), !delivery_country_name(10), !delivery_postal_code(13), !_, 04, !delivery_phone(15), !spacer(15), !primary_email(41), !_, !products, 10, !billing_name(30), !billing_street1(30), !_, 11, !billing_street2(30), !billing_city(15), !billing_zone_code(2), !billing_country_name(20), !billing_postal_code(13), !_, 12, !billing_phone(15), !billing_first_name(15), !primary_email(41)';
-
-  return $pattern;
-}
-
-/**
- * Returns the default order product export pattern.
- */
-function uc_edi_default_order_product_export_pattern() {
-  // This is an example product export pattern.
-  $pattern = '05, !#(3), !spacer(3), !qty(11), EA, !price(16), !spacer(4), !model(15)';
-
-  return $pattern;
-}
-
-/**
  * Returns a delimiter string.
  *
  * @param $var
Index: templates/uc_edi-default.tpl.php
===================================================================
RCS file: templates/uc_edi-default.tpl.php
diff -N templates/uc_edi-default.tpl.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ templates/uc_edi-default.tpl.php	29 Oct 2010 23:09:10 -0000
@@ -0,0 +1,116 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * This file is the default EDI export template.
+ *
+ * Available variables:
+ * - $uc_edi_header: Is TRUE if this is the first order in the export (header
+ *   should be printed if desired), otherwise FALSE.
+ * - $uc_edi_footer: Is TRUE if this is the last order in the export (footer
+ *   should be printed if desired), otherwise FALSE.
+ * - $order: The Ubercart order object.
+ * - $products: An array of product objects belonging to the order.
+ * - $line_items: An array of line items belonging to the order.
+ *
+ * Variables added by Ubercart EDI via the Token module.
+ * - $order_edi_batch_id: The export batch number.
+ * - $order_payment_method: The order payment method.
+ * - $order_shipping_charges: The "altered" order shipping charges.
+ * - $order_shipping_charges_formatted: The "formatted" order shipping charges.
+ * - $order-tax-charges: The "altered" order total tax charges.
+ * - $order-tax-charges-formatted: The "formatted" order total tax charges.
+ * - $order_shipping_first_name: The first name of the order shipping address.
+ * - $order_shipping_last_name: The last name of the order shipping address.
+ * - $order_shipping_company: The company of the order shipping address.
+ * - $order_shipping_street1: The street1 of the order shipping address.
+ * - $order_shipping_street2: The street2 of the order shipping address.
+ * - $order_shipping_city: The city of the order shipping address.
+ * - $order_shipping_zone: The zone/province/state of the order shipping address.
+ * - $order_shipping_zone_code: The zone/province/state code of the order shipping address.
+ * - $order_shipping_postal_code: The postal/zip code of the order shipping address.
+ * - $order_shipping_country: The country ISO 3166-1 Numeric-3 code of the order shipping address.
+ * - $order_shipping_country_name: The country ISO 3166-1 English short name of the order shipping address.
+ * - $order_shipping_country_code_2: The country ISO 3166-1 Alpha-2 code of the order shipping address.
+ * - $order_shipping_country_code_3: The country ISO 3166-1 Alpha-3 code of the order shipping address.
+ * - $order_billing_first_name: The first name of the order billing address.
+ * - $order_billing_last_name: The last name of the order billing address.
+ * - $order_billing_company: The company of the order billing address.
+ * - $order_billing_street1: The street1 of the order billing address.
+ * - $order_billing_street2: The street2 of the order billing address.
+ * - $order_billing_city: The city of the order billing address.
+ * - $order_billing_zone: The zone/province/state of the order billing address.
+ * - $order_billing_zone_code: The zone/province/state code of the order billing address.
+ * - $order_billing_postal_code: The postal/zip code of the order billing address.
+ * - $order_billing_country: The country ISO 3166-1 Numeric-3 code of the order billing address.
+ * - $order_billing_country_name: The country ISO 3166-1 English short name of the order billing address.
+ * - $order_billing_country_code_2: The country ISO 3166-1 Alpha-2 code of the order billing address.
+ * - $order_billing_country_code_3: The country ISO 3166-1 Alpha-3 code of the order billing address.
+ * - $order_date_created_unix: The Unix/POSIX time the order was created.
+ * - $order_date_modified_unix: The Unix/POSIX time the order was last modified.
+ *
+ * @see template_preprocess_uc_edi()
+ * @see uc_price()
+ *
+ * @ingroup themeable
+ */
+
+// Set the delimiter.
+$delimiter = "\t";
+
+// Set the headers.
+$headers = array(
+  'customer_account_number',
+  'customer_name',
+  'order_number',
+  'order_date',
+  'sku',
+  'quantity',
+  'price',
+);
+
+if ($uc_edi_header) {
+  /* -- Delete this line to print a list of available variables and their values.
+  echo check_plain(print_r($variables, 1));
+  // */
+
+  // Print the header.
+  echo implode($delimiter, $headers) . "\n";
+}
+
+// Set the default price context.
+$context = array(
+  'revision' => 'altered',
+  'type' => 'order_product',
+);
+
+// Loop through all products printing all order and product values.
+for ($line = 0; $product = $products[$line]; ++$line) {
+  // Set the product specific price context.
+  $price_info = array(
+    'price' => $product->price,
+    'qty' => $product->qty,
+  );
+  $context['subject'] = array(
+    'order_product' => $product,
+  );
+
+  // Print the values.
+  $values = array(
+    $order_uid,
+    $order_billing_first_name . ' ' . $order_billing_last_name,
+    $order_id,
+    $order_date_created_unix,
+    $product->model,
+    $product->qty,
+    uc_price($price_info, $context),
+  );
+  echo implode($delimiter, $values) . "\n";
+}
+
+// Re-print headers as footers.
+if ($uc_edi_footer) {
+  echo implode($delimiter, $headers) . "\n";
+}
+?>
Index: templates/uc_edi.tpl.php
===================================================================
RCS file: templates/uc_edi.tpl.php
diff -N templates/uc_edi.tpl.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ templates/uc_edi.tpl.php	29 Oct 2010 23:09:10 -0000
@@ -0,0 +1,11 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * This file is a dummy template.
+ *
+ * It serves as a reminder to theme authors that they must have a
+ * uc_edi.tpl.php file in their theme if they want to override the other EDI
+ * templates.
+ */
