=== modified file 'docs/hooks.php'
--- docs/hooks.php	2009-03-20 20:03:26 +0000
+++ docs/hooks.php	2009-04-09 16:55:31 +0000
@@ -1140,6 +1140,31 @@
 }
 
 /**
+ * Use this hook to define price handlers for your module. You may define one
+ * price alterer and one price formatter. You may also define options that are
+ * merged into the options array in order of each price alterer's weight.
+ */
+function hook_uc_price_handler() {
+  return array(
+    'alter' => array(
+      'callback' => 'my_price_handler_alter',
+    ),
+    'format' => array(
+      'callback' => 'my_price_handler_format',
+    ),
+    'options' => array(
+      'sign' => variable_get('uc_currency_sign', '*'),
+      'sign_after' => TRUE,
+      'prec' => 4,
+      'dec' => ',',
+      'thou' => '.',
+      'label' => FALSE,
+      'my_option_that_my_formatter_recognizes' => 1337,
+    )
+  );
+}
+
+/**
  * Notify core of any SKUs your module adds to a given node.
  *
  * NOTE: DO NOT map the array keys, as the possibility for numeric SKUs exists, and

=== added file 'uc_store/includes/uc_price.inc'
--- uc_store/includes/uc_price.inc	1970-01-01 00:00:00 +0000
+++ uc_store/includes/uc_price.inc	2009-04-09 18:01:29 +0000
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * The heart of the price modification/displaying system.
+ *
+ * @param $price_info
+ *   An associative array containing the price per item as well
+ *   as the quantity, or just a price (which defaults the quantity
+ *   to 1).
+ * @param $context
+ *   An associative array containing information about where the
+ *   request came from.
+ * @param $options
+ *   An associative array containing options that will be passed through to
+ *   any alteration/formatting/theming functions implemented.
+ * @param $revision
+ *   A string describing the 'revision' of the price to return. Currently accepted
+ *   values are:
+ *     'original' => 'The original price',
+ *     'altered' => 'Price after passing through the alterer(s)',
+ *     'formatted' => 'Price after passing through the formatter',
+ *     (default) 'themed' => 'Price after passing through the theme layer'.
+ *
+ * This function handles price alteration, formatting, theming, and caching.
+ *
+ */
+function uc_price($price_info, $context, $options = array(), $revision = NULL) {
+  // If we're passed just a number for price, we'll set the quantity to 1.
+  if (is_numeric($price_info)) {
+    $price_info = array(
+      'value' => $price_info,
+      'qty' => 1,
+    );
+  }
+
+  // Initialize options.
+  $options += array(
+    'cache' => TRUE,
+  );
+  // Clamp to allowed revisions.
+  switch ($revision) {
+    case 'original':
+    case 'altered':
+    case 'formatted':
+    case 'themed':
+    break;
+
+    default:
+      $revision = 'themed';
+  }
+  // Get all the active handlers.
+  $handlers = _uc_price_get_handlers($options);
+  // Use the global user if none was passed in.
+  if (!isset($context['uid'])) {
+    global $user;
+    $context['uid'] = $user->uid;
+  }
+  // Merge any incoming options, giving them precedence.
+  $options += $handlers['options'];
+  // Build the cache key with the original state.
+  global $user;
+  $key = 'uc-price-'. $context['uid'] .'-'. md5(serialize($price_info + $context + $options));
+  // Is it cached?
+  if ($options['cache'] != FALSE && $cache = cache_get($key, 'cache_uc_price')) {
+    $cache = $cache->data;
+  }
+  // Otherwise, build it.
+  else {
+    $cache = array();
+    $cache['original'] = $price_info['value'] * $price_info['qty'];
+    // Alter the price, context, and options.
+    foreach ($handlers['alterers'] as $alterer) {
+      $alterer($price_info, $context, $options);
+    }
+    // A placeholder can override any formatting. For instance, trying to format
+    // something like "FREE for a limited time!" as a price wouldn't be easy.
+    if (isset($price_info['placeholder'])) {
+      $value = $price_info['placeholder'];
+      $cache['altered'] = $value;
+      $cache['formatted'] = $value;
+    }
+    else {
+      $value = $price_info['value'] * $price_info['qty'];
+      $cache['altered'] = $value;
+      // Format the price.
+      $value = $handlers['formatter']($value, $options);
+      $cache['formatted'] = $value;
+    }
+    // Theme the price.
+    $cache['themed'] = theme('uc_price', $value, $context, $options);;
+    // Cache this price's revisions?
+    if ($options['cache']) {
+      cache_set($key, $cache, 'cache_uc_price', CACHE_TEMPORARY);
+    }
+  }
+
+  // Return the requested revision.
+  return $cache[$revision];
+}
+
+function _uc_price_get_handlers($options = array()) {
+  // Set defaults.
+  $options += array(
+    'rebuild_handlers' => FALSE,
+    'all_handlers' => FALSE,
+  );
+  // Get handlers only if we haven't already, or if this is a rebuild.
+  static $handlers = array();
+  if (!$handlers || $options['rebuild_handlers']) {
+    // Get the handlers and sort them by weight.
+    $config = variable_get('uc_price_handler_config', array());
+    $modules = module_implements('uc_price_handler');
+    foreach ($modules as $module) {
+      // Sane defaults.
+      $hooks[$module] = module_invoke($module, 'uc_price_handler') + array(
+        'weight' => 0,
+        'enabled' => TRUE,
+      );
+      // Merge any configuration state in.
+      if (isset($config[$module])) {
+        $hooks[$module] = $config[$module] + $hooks[$module];
+      }
+      // Unset disabled ones, but only if we're not building the selection form.
+      if (!$options['all_handlers'] && $hooks[$module]['enabled'] == FALSE) {
+        unset($hooks[$module]);
+      }
+    }
+    uasort($hooks, 'uc_weight_sort');
+
+    // Store the raw data for selection form building.
+    $handlers['hook_data'] = $hooks;
+    // Store the selected formatter, defaulting to uc_store's implementation.
+    $format_func = variable_get('uc_store_price_format', 'uc_store_price_handler_format');
+    if (!function_exists($format_func)) {
+      $format_func = 'uc_store_price_handler_format';
+    }
+    $handlers['formatter'] = $format_func;
+    // Grab all the alter/format callbacks, as well as merging the options.
+    // This happens in order by weight, so we're kosher.
+    $handlers['alterers'] = array();
+    // We set some default options here. We could set them in the uc_store price handler,
+    // but that means if that handler is disabled, we won't get them merged in.
+    $handlers['options'] = array(
+      'sign' => variable_get('uc_currency_sign', '$'),
+      'sign_after' => variable_get('uc_sign_after_amount', FALSE),
+      'prec' => variable_get('uc_currency_prec', 2),
+      'dec' => variable_get('uc_currency_dec', '.'),
+      'thou' => variable_get('uc_currency_thou', ','),
+      'label' => TRUE,
+    );
+
+    foreach ($hooks as $hook) {
+      $handlers['alterers'][] = $hook['alter']['callback'];
+      $handlers['formatters'][] = $hook['format']['callback'];
+      if (is_array($hook['options'])) {
+        $handlers['options'] += $hook['options'];
+      }
+    }
+  }
+
+  return $handlers;
+}
+
+/**
+ * Implementation of hook_uc_price_handler().
+ */
+function uc_store_uc_price_handler() {
+  return array(
+    'alter' => array(
+      'callback' => 'uc_store_price_handler_alter',
+    ),
+    'format' => array(
+      'callback' => 'uc_store_price_handler_format',
+    ),
+  );
+}
+
+/**
+ * Price handler (alterer). Add the default prefixes to prices
+ * when viewing a product node.
+ */
+function uc_store_price_handler_alter(&$price, &$context, &$options) {
+  if (is_array($context['class'])) {
+    if (in_array('list', $context['class'])) {
+      $options['prefixes'][] = t('List Price: ');
+    }
+    if (in_array('cost', $context['class'])) {
+      $options['prefixes'][] = t('Cost: ');
+    }
+    if (in_array('sell', $context['class'])) {
+      $options['prefixes'][] = t('Price: ');
+    }
+  }
+}
+
+/**
+ * Price handler (formatter). Ubercart's uc_currency_format(), repurposed
+ * for a brighter tomorrow.
+ */
+function uc_store_price_handler_format($value, $options) {
+  $output = '';
+  // If the value is less than the minimum precision, zero it.
+  if ($options['prec'] > 0 && abs($value) < 1 / pow(10, $options['prec'])) {
+    $value = 0;
+  }
+  // Value is always positive, and we'll add the negative sign if necessary.
+  if ($value < 0) {
+    $value = abs($value);
+    $output .= '-';
+  }
+  // Add the currency sign first?
+  if ($options['sign'] && !$options['sign_after']) {
+    $output .= $options['sign'];
+  }
+  // Format the number, like 1234.567 => 1,234.57
+  $output .= number_format($value, $options['prec'], $options['dec'], $options['thou']);
+  // Add the currency sign last?
+  if ($options['sign'] && $options['sign_after']) {
+    $output .= $options['sign'];
+  }
+
+  return $output;
+}
+

=== modified file 'uc_store/uc_store.admin.inc'
--- uc_store/uc_store.admin.inc	2009-03-11 04:33:33 +0000
+++ uc_store/uc_store.admin.inc	2009-04-09 17:00:33 +0000
@@ -1079,3 +1079,100 @@
   drupal_goto('admin/store/settings/countries/edit/import');
 }
 
+/**
+ * Form for enabling/weighting price alterers and selecting a price formatter.
+ */
+function uc_price_settings_form($form_state) {
+
+  // Get all the handlers available.
+  $handlers = _uc_price_get_handlers(array('rebuild_handlers' => TRUE, 'all_handlers' => TRUE));
+  $options = array();
+  // Make our alterer table all slick with a tabledrag.
+  $form['uc_price_alterers']['#tree'] = TRUE;
+  foreach ($handlers['hook_data'] as $module => $hook_datum) {
+    if (isset($hook_datum['alter'])) {
+      $form['uc_price_alterers'][$module] = array(
+        'module' => array(
+          '#type' => 'markup',
+          '#value' => $module,
+        ),
+        'enabled' => array(
+          '#type' => 'checkbox',
+          '#default_value' => $hook_datum['enabled'],
+        ),
+        'weight' => array(
+          '#type' => 'weight',
+          '#default_value' => $hook_datum['weight'],
+          '#attributes' => array('class' => 'uc-price-handler-table-weight'),
+        ),
+      );
+    }
+    if (isset($hook_datum['format'])) {
+      $options[$module] = $module;
+    }
+  }
+
+  $form['invocation_message'] = array(
+    '#type' => 'markup',
+    '#value' => t('<div class="description">The price alterers are invoked in descending order.</div>'),
+  );
+
+  $form['uc_price_formatter'] = array(
+    '#type' => 'select',
+    '#title' => t('Price formatter'),
+    '#description' => t('Select which price formatter to use for your store.'),
+    '#options' => $options,
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  // Rebuild the handler cache!
+  cache_clear_all('*', 'cache_uc_price', TRUE);
+  _uc_price_get_handlers(array('rebuild_handlers' => TRUE));
+
+  return $form;
+}
+
+/**
+ * Render the price handler form, adding tabledrag.
+ *
+ * @ingroup themeable
+ */
+function theme_uc_price_settings_form($form) {
+  drupal_add_tabledrag('uc-price-handler-table', 'order', 'sibling', 'uc-price-handler-table-weight');
+
+  $header = array(t('Price alterers'), t('Enabled'), t('Weight'));
+  if (count(element_children($form['uc_price_alterers'])) > 0) {
+    foreach (element_children($form['uc_price_alterers']) as $module) {
+      $row = array();
+      foreach (element_children($form['uc_price_alterers'][$module]) as $field) {
+        $row[] = drupal_render($form['uc_price_alterers'][$module][$field]);
+      }
+
+      $rows[] = array(
+        'data' => $row,
+        'class' => 'draggable',
+      );
+    }
+  }
+
+  $output = theme('table', $header, $rows, array('id' => 'uc-price-handler-table'));
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+function uc_price_settings_form_submit($form, &$form_state) {
+  variable_set('uc_store_price_format', $form_state['values']['uc_price_formatter']);
+
+  $config = array();
+  foreach ($form_state['values']['uc_price_alterers'] as $module => $alterer) {
+    $config[$module] = $alterer;
+  }
+  variable_set('uc_price_handler_config', $config);
+
+  drupal_set_message(t('Price handler configuration saved.'));
+}

=== modified file 'uc_store/uc_store.install'
--- uc_store/uc_store.install	2008-12-03 19:11:21 +0000
+++ uc_store/uc_store.install	2009-04-09 17:00:32 +0000
@@ -99,6 +99,9 @@
     'primary key' => array('path_hash'),
   );
 
+  $schema['cache_uc_price'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_uc_price']['description'] = t('Cache table for Ubercart store prices.');
+
   return $schema;
 }
 
@@ -235,3 +238,13 @@
 
   return $ret;
 }
+
+function uc_store_update_6001() {
+  $ret = array();
+
+  $schema['cache_uc_price'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_uc_price']['description'] = t('Cache table for Ubercart store prices.');
+
+  db_create_table($ret, 'cache_uc_price', $schema['cache_uc_price']);
+  return $ret;
+}

=== modified file 'uc_store/uc_store.module'
--- uc_store/uc_store.module	2009-03-19 17:15:57 +0000
+++ uc_store/uc_store.module	2009-04-09 17:02:39 +0000
@@ -318,6 +318,16 @@
     'file' => 'uc_store.admin.inc',
   );
 
+  $items['admin/store/settings/prices'] = array(
+    'title' => 'Price handlers',
+    'description' => 'Select which price handlers to use for your store.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('uc_price_settings_form'),
+    'access arguments' => array('administer store'),
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'uc_store.admin.inc',
+  );
+
   return $items;
 }
 
@@ -329,6 +339,7 @@
 
   require_once($dir .'/includes/summaries.inc');
   require_once($dir .'/includes/tapir.inc');
+  require_once($dir .'/includes/uc_price.inc');
 
   drupal_add_css($dir .'/uc_store.css');
 }
@@ -351,6 +362,10 @@
   return $types;
 }
 
+function uc_store_flush_caches() {
+  return array('cache_uc_price');
+}
+
 function uc_store_theme() {
   return array(
     'uc_admin_dashboard' => array(
@@ -368,9 +383,15 @@
     'tapir_table' => array(
       'arguments' => array('form' => NULL),
     ),
+    'uc_price_settings_form' => array(
+      'arguments' => array('form' => NULL),
+    ),
     'summary_overview' => array(
       'arguments' => array(),
     ),
+    'uc_price' => array(
+      'arguments' => array('value' => 0, 'context' => array(), 'options' => array()),
+    ),
   );
 }
 
@@ -530,6 +551,32 @@
  * Callback Functions, Forms, and Tables
  ******************************************************************************/
 
+function theme_uc_price($value, $context, $options) {
+  // Fixup class names.
+  if (!is_array($context['class'])) {
+    $context['class'] = array();
+  }
+  foreach ($context['class'] as $key => $class) {
+    $context['class'][$key] = 'uc-price-'. $class;
+  }
+  $context['class'][] = 'uc-price';
+  // Class the element.
+  $output = '<div class="'. implode(' ', $context['class']) .'">';
+  // Prefix(es).
+  if ($options['label'] && isset($options['prefixes'])) {
+    $output .= implode('', $options['prefixes']);
+  }
+  // Value.
+  $output .= $value;
+  // Suffix(es).
+  if ($options['label'] && isset($options['suffixes'])) {
+    $output .= implode('', $options['suffixes']);
+  }
+  $output .= '</div>';
+
+  return $output;
+}
+
 // Themes the dashboard on the admin/store page.
 function theme_uc_admin_dashboard($type, $menus) {
   if ($type == 1) {

