=== modified file 'docs/hooks.php' --- docs/hooks.php 2009-03-20 20:03:26 +0000 +++ docs/hooks.php 2009-04-11 19:15:24 +0000 @@ -1140,6 +1140,35 @@ } /** + * 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( + 'title' => t('My price handler'), + 'description' => t('Handles my price alteration needs.'), + 'callback' => 'my_price_handler_alter', + ), + 'format' => array( + 'title' => t('My price handler'), + 'description' => t('Handles my price formatting needs.'), + '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-11 22:18:09 +0000 @@ -0,0 +1,280 @@ + the price per item + * - 'qty' => a quantity + * - 'placeholder' => a placeholder string to use as the formatted price + * value in lieu of actually formatting the altered price value + * @param $context + * An associative array containing information about where the function call + * came from. This function will at least add an 'account' key to the array + * if it is not already set with the value being the current user object. + * @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', + * - 'themed' => 'Price after passing through the theme layer'. (default) + * + * This function handles price alteration, formatting, theming, and caching. + * + */ +function uc_price($price_info, $context, $options = array(), $revision = NULL) { + global $user; + $values = array(); + + // If we're passed just a number for price, we'll set the quantity to 1. + if (is_numeric($price_info)) { + $price_info = array( + 'price' => $price_info, + 'qty' => 1, + ); + } + + // Initialize the options array. + $options += array( + 'cache' => variable_get('uc_price_caching', 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['account'])) { + $context['account'] = $user; + } + + // Merge any incoming options, giving them precedence. + $options += $handlers['options']; + + // Build the cache key with the original state, trimming down the account to + // an array with a limited set of keys that don't change every pageload; the + // uid, name, mail, and roles of the user account. + $context_clone = $context; + $context_clone['account'] = array_intersect_key((array) $context['account'], drupal_map_assoc(array('uid', 'name', 'mail', 'roles'))); + + $key = md5(serialize($price_info + $context_clone + $options)); + + // If this price has already been calculated and cached... + if ($options['cache'] && $cache = cache_get($key, 'cache_uc_price')) { + // Use the cached price data. + $values = $cache->data; + } + else { + // Otherwise, build it from scratch. + $values['original'] = $price_info['value'] * $price_info['qty']; + + // Alter the price, context, and options. + foreach ($handlers['alterers'] as $alterer) { + $alterer($price_info, $context, $options); + } + + // If a price placeholder was specified in the price info array... + if (isset($price_info['placeholder'])) { + // Force the formatted value to the placeholder. + $values['formatted'] = $price_info['placeholder']; + } + else { + // Otherwise use the actual numeric and formatted numeric values. + $values['altered'] = $price_info['price'] * $price_info['qty']; + + // Format the price using the formatter callback. + $values['formatted'] = $handlers['formatter']($values['altered'], $options); + } + + // Theme the price. + $values['themed'] = theme('uc_price', $values['formatted'], $context, $options);; + + // Cache this price values. + // TODO: Determine if we can safely cache these things... + if ($options['cache']) { + cache_set($key, $values, 'cache_uc_price', CACHE_TEMPORARY); + } + } + + // Return the requested revision. + return $values[$revision]; +} + +/** + * Return an array of price handler data. + * + * @param $options + * An associative array of options used when building the array with keys: + * - 'rebuild_handlers' => TRUE or FALSE indicating whether we should nuke + * the cache and rebuild the handler data. + * - 'all_handlers' => TRUE or FALSE indicating whether or not to return all + * defined price handlers' alterers or just enabled ones. + * @return + * A structured array of price handler data. + */ +function _uc_price_get_handlers($options = array()) { + static $handlers = array(); + + // Set default options. + $options += array( + 'rebuild_handlers' => FALSE, + 'all_handlers' => FALSE, + ); + + // Get handlers only if we haven't already, or if this is a rebuild. + if (empty($handlers) || $options['rebuild_handlers']) { + // Get the handlers and sort them by weight. + $config = variable_get('uc_price_handler_config', array()); + + foreach (module_implements('uc_price_handler') as $module) { + // Create a price handler hook data array and merge in sensible 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 hooks if we're not building the selection form. + if (!$options['all_handlers'] && !$hooks[$module]['enabled']) { + unset($hooks[$module]); + } + } + + // Sort the hook data by weight. + 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. + $formatter = variable_get('uc_price_format_callback', 'uc_store_price_handler_format'); + + if (function_exists($formatter)) { + $handlers['formatter'] = $formatter; + } + else { + $handlers['formatter'] = 'uc_store_price_handler_format'; + } + + // 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( + 'title' => t('Default price handler'), + 'description' => t('The default handler alterer is simply responsible for prefixing various product prices for display.'), + 'callback' => 'uc_store_price_handler_alter', + ), + 'format' => array( + 'title' => t('Default price handler'), + 'description' => t('The default handler formatter passes prices through a single currency formatter based on the store currency display settings.'), + 'callback' => 'uc_store_price_handler_format', + ), + ); +} + +/** + * Default price handler alterer; adds the default prefixes to the various + * product prices when viewing a product node. + */ +function uc_store_price_handler_alter(&$price, &$context, &$options) { + // If a class was specified in the price's context array... + if (is_array($context['class'])) { + // Look for a product price type in the class array and adjust the price + // prefix accordingly. + if (in_array('list', $context['class'])) { + $options['prefixes'][] = t('List Price: '); + } + elseif (in_array('cost', $context['class'])) { + $options['prefixes'][] = t('Cost: '); + } + elseif (in_array('sell', $context['class'])) { + $options['prefixes'][] = t('Price: '); + } + } +} + +/** + * Default price handler formatter; formats the price using the store currency + * display settings. + */ +function uc_store_price_handler_format($price, $options) { + $output = ''; + + // If the value is less than the minimum precision, zero it. + if ($options['prec'] > 0 && abs($price) < 1 / pow(10, $options['prec'])) { + $price = 0; + } + + // Force the price to a positive value and add a negative sign if necessary. + if ($price < 0) { + $price = abs($price); + $output .= '-'; + } + + // Add the currency sign first if specified. + if ($options['sign'] && !$options['sign_after']) { + $output .= $options['sign']; + } + + // Format the number, like 1234.567 => 1,234.57 + $output .= number_format($price, $options['prec'], $options['dec'], $options['thou']); + + // Add the currency sign last if specified. + 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-11 22:17:54 +0000 @@ -1079,3 +1079,132 @@ drupal_goto('admin/store/settings/countries/edit/import'); } +// Form for enabling/weighting price alterers and selecting a price formatter. +function uc_price_settings_form() { + $options = array(); + $form = array(); + + // Get all the handlers available. + $handlers = _uc_price_get_handlers(array('rebuild_handlers' => TRUE, 'all_handlers' => TRUE)); + + // Build a draggable price alterer table. + $form['uc_price_alterers']['#tree'] = TRUE; + + // Loop through the handlers. + foreach ($handlers['hook_data'] as $module => $data) { + // If the handler defines an alterer, add it to the form. + if (isset($data['alter'])) { + $description = $data['alter']['title']; + + if (!empty($data['alter']['description'])) { + $description .= '