=== modified file 'docs/hooks.php' --- docs/hooks.php 2009-03-20 20:03:26 +0000 +++ docs/hooks.php 2009-04-06 23:41:58 +0000 @@ -1249,5 +1249,30 @@ function hook_update_cart_item($nid, $da } /** + * 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, + ) + ); +} + +/** * @} End of "addtogroup hooks". */ === modified file 'uc_product/uc_product.css' --- uc_product/uc_product.css 2009-01-07 15:08:16 +0000 +++ uc_product/uc_product.css 2009-03-16 10:36:33 +0000 @@ -9,6 +9,17 @@ margin-left: 4px; } +.uc-price-display { + float: right; + clear: right; + width: 100px; + text-align: center; + font-size: 1.3em; + font-weight: bold; + padding-bottom: 4px; + padding-left: 4px; +} + .display-price { float: right; clear: right; === modified file 'uc_product/uc_product.module' --- uc_product/uc_product.module 2009-04-07 18:50:58 +0000 +++ uc_product/uc_product.module 2009-04-06 23:41:59 +0000 @@ -695,27 +695,45 @@ function uc_product_view($node, $teaser 'add_to_cart' => 10, )); + $context = array( + 'location' => 'product-view', + 'class' => array( + 'product', + ), + 'subject' => array( + 'nid' => $node->nid, + ), + ); + + if (module_exists('imagecache') && ($field = variable_get('uc_image_'. $node->type, '')) && isset($node->$field) && file_exists($node->{$field}[0]['filepath'])) { $node->content['image'] = array('#value' => theme('uc_product_image', $node->$field, $teaser, $page), '#access' => $enabled['image'], '#weight' => $weight['image'], ); } - $node->content['display_price'] = array('#value' => theme('uc_product_price', $node->sell_price, 'display-price', TRUE), + + $context['class'][1] = 'display'; + $node->content['display_price'] = array('#value' => uc_price($node->sell_price, $context), '#access' => $enabled['display_price'], '#weight' => $weight['display_price'], ); + if (!$teaser) { $node->content['model'] = array('#value' => theme('uc_product_model', $node->model, $teaser, $page), '#access' => $enabled['model'], '#weight' => $weight['model'], ); $node->content['body']['#weight'] = 1; - $node->content['list_price'] = array('#value' => theme('uc_product_price', $node->list_price, 'list-price'), + + $context['class'][1] = 'list'; + $node->content['list_price'] = array('#value' => uc_price($node->list_price, $context), '#access' => $enabled['list_price'], '#weight' => $weight['list_price'], ); - $node->content['cost'] = array('#value' => theme('uc_product_price', $node->cost, 'cost'), + + $context['class'][1] = 'cost'; + $node->content['cost'] = array('#value' => uc_price($node->cost, $context), '#access' => $enabled['cost'] && user_access('administer products'), '#weight' => $weight['cost'], ); @@ -724,7 +742,8 @@ function uc_product_view($node, $teaser $node->content['#attributes'] = array('style' => 'display: inline'); } - $node->content['sell_price'] = array('#value' => theme('uc_product_price', $node->sell_price, 'sell-price', $teaser), + $context['class'][1] = 'sell'; + $node->content['sell_price'] = array('#value' => uc_price($node->sell_price, $context, array('label' => !$teaser)), '#access' => $enabled['sell_price'], '#weight' => $weight['sell_price'], ); === 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-06 23:34:51 +0000 @@ -0,0 +1,222 @@ + '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 (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-03-17 14:16:52 +0000 @@ -1079,3 +1079,100 @@ function uc_country_update($country_id, 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('
The price alterers are invoked in descending order.
'), + ); + + $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-03-17 14:10:15 +0000 @@ -99,6 +99,9 @@ function uc_store_schema() { '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 @@ function uc_store_update_6000() { 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-06 23:41:59 +0000 @@ -318,6 +318,15 @@ function uc_store_menu() { '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; } @@ -330,6 +339,9 @@ function uc_store_init() { require_once($dir .'/includes/summaries.inc'); require_once($dir .'/includes/tapir.inc'); + // For uc_price + require_once($dir .'/includes/uc_price.inc'); + drupal_add_css($dir .'/uc_store.css'); } @@ -351,6 +363,10 @@ function uc_store_elements() { return $types; } +function uc_store_flush_caches() { + return array('cache_uc_price'); +} + function uc_store_theme() { return array( 'uc_admin_dashboard' => array( @@ -368,9 +384,15 @@ function uc_store_theme() { '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 +552,32 @@ function uc_store_exit() { * 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 = '
'; + // 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 .= '
'; + + return $output; +} + // Themes the dashboard on the admin/store page. function theme_uc_admin_dashboard($type, $menus) { if ($type == 1) {