<?php
// $Id: uc_store.module,v 1.14.2.40 2010/07/12 03:47:36 tr Exp $

/**
 * @file
 * Contains global Ubercart functions and store administration functionality.
 *
 * The store module is a container of sorts for various helper functions used
 * in different parts of the Ubercart core.  It also provides screens and
 * settings pages for use in store administration.
 */

// Ubercart price API may be required before hook_init() is invoked.
require_once dirname(__FILE__) .'/includes/uc_price.inc';

/**
 * Unit conversion ratios.
 *
 * Used by the unit conversion functions uc_weight_convesion() and
 * uc_length_conversion().
 */
define('KG_TO_KG', 1);
define('KG_TO_G',  1000);
define('KG_TO_LB', 2.204622621849);
define('KG_TO_OZ', 35.27396194958);

define('G_TO_G',   1);
define('G_TO_KG',  0.001);
define('G_TO_LB',  0.002204622622);
define('G_TO_OZ',  0.03527396195);

define('LB_TO_LB', 1);
define('LB_TO_OZ', 16);
define('LB_TO_KG', 0.45359237);
define('LB_TO_G',  453.59237);

define('OZ_TO_OZ', 1);
define('OZ_TO_LB', 0.0625);
define('OZ_TO_G',  28.349523125);
define('OZ_TO_KG', 0.028349523);

define('IN_TO_IN', 1);
define('IN_TO_FT', 0.083333333333);
define('IN_TO_CM', 2.54);
define('IN_TO_MM', 25.4);

define('FT_TO_FT', 1);
define('FT_TO_IN', 12);
define('FT_TO_CM', 30.48);
define('FT_TO_MM', 304.8);

define('CM_TO_CM', 1);
define('CM_TO_IN', 0.393700787402);
define('CM_TO_FT', 0.03280839895);
define('CM_TO_MM', 10);

define('MM_TO_MM', 1);
define('MM_TO_IN', 0.03937007874);
define('MM_TO_FT', 0.003280839895);
define('MM_TO_CM', 0.1);

/*******************************************************************************
 * Hook Functions (Drupal)
 ******************************************************************************/

/**
 * Implementation of hook_menu().
 */
function uc_store_menu() {
  $items = array();

  $items['admin/store'] = array(
    'title' => 'Store administration',
    'description' => 'Administer store settings, products, orders, and more.',
    'page callback' => 'uc_store_admin',
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/customers'] = array(
    'title' => 'Customers',
    'description' => 'View and modify customer information and orders.',
    'page callback' => 'uc_store_customers',
    'access arguments' => array('view customers'),
    'weight' => -6,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/customers/view'] = array(
    'title' => 'View customers',
    'description' => 'View and modify customer information and orders.',
    'page arguments' => array(NULL, NULL, NULL, '25'),
    'access arguments' => array('view customers'),
    'weight' => -10,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/customers/search'] = array(
    'title' => 'Search customers',
    'description' => 'Search through your customer list.',
    'page callback' => 'uc_store_customer_search',
    'access arguments' => array('view customers'),
    'weight' => -5,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/reports'] = array(
    'title' => 'Reports',
    'description' => 'Browse various store reports.',
    'page callback' => 'uc_store_reports',
    'access arguments' => array('view store reports'),
    'weight' => 2,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings'] = array(
    'title' => 'Configuration',
    'description' => 'Adjust configuration settings for Ubercart.',
    'page callback' => 'uc_store_configuration_page',
    'access arguments' => array('administer store'),
    'weight' => 6,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/help'] = array(
    'title' => 'Help',
    'description' => 'Links to get help!',
    'page callback' => 'uc_store_ubercart_help',
    'access arguments' => array('administer store'),
    'weight' => 10,
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/help/tokens'] = array(
    'title' => 'Using tokens',
    'description' => 'Understand what tokens are and how to use them.',
    'page callback' => 'uc_store_ubercart_help_tokens',
    'access arguments' => array('administer store'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries'] = array(
    'title' => 'Country settings',
    'description' => 'Configure country specific settings.',
    'page callback' => 'uc_country_settings_overview',
    'access arguments' => array('administer store'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/overview'] = array(
    'title' => 'Overview',
    'description' => 'View the country settings.',
    'access arguments' => array('administer store'),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/edit'] = array(
    'title' => 'Edit',
    'description' => 'Edit the country settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_country_import_form'),
    'access arguments' => array('administer store'),
    'weight' => -5,
    'type' => MENU_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/edit/import'] = array(
    'title' => 'Import countries',
    'description' => 'Import and manage countries.',
    'access arguments' => array('administer store'),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/edit/formats'] = array(
    'title' => 'Country formats',
    'description' => 'Edit the country specific format settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_country_formats_form'),
    'access arguments' => array('administer store'),
    'weight' => -5,
    'type' => MENU_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );

  $items['admin/store/settings/store'] = array(
    'title' => 'Store settings',
    'description' => 'Configure the main store settings.',
    'page callback' => 'uc_store_store_settings_overview',
    'access arguments' => array('administer store'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/store/overview'] = array(
    'title' => 'Overview',
    'description' => 'View the store settings.',
    'access arguments' => array('administer store'),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/store/edit'] = array(
    'title' => 'Edit',
    'description' => 'Edit the store settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_store_store_settings_form'),
    'access arguments' => array('administer store'),
    'weight' => -5,
    'type' => MENU_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/store/edit/contact'] = array(
    'title' => 'Contact settings',
    'description' => 'Edit the contact settings.',
    'access arguments' => array('administer store'),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/store/edit/display'] = array(
    'title' => 'Display settings',
    'description' => 'Edit the display settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_store_display_settings_form'),
    'access arguments' => array('administer store'),
    'weight' => -5,
    'type' => MENU_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/store/edit/format'] = array(
    'title' => 'Format settings',
    'description' => 'Edit the format settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_store_format_settings_form'),
    'access arguments' => array('administer store'),
    'weight' => 0,
    'type' => MENU_LOCAL_TASK,
    'file' => 'uc_store.admin.inc',
  );

  $items['admin/store/settings/store/initials'] = array(
    'title' => 'User initials',
    'description' => 'Assign initials to user accounts.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_store_initials'),
    'access arguments' => array('administer store'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_store.admin.inc',
  );
  /*$items['admin/store/settings/tables'] = array(
    'title' => 'Table display settings',
    'description' => 'Configure the display of tables in your store.',
    'page callback' => 'uc_store_tables',
    'access arguments' => array('administer store'),
    'type' => MENU_NORMAL_ITEM,
  );*/

  $items['uc_js_util/%'] = array(
    'title' => 'JS utilities',
    'page callback' => 'uc_store_js_util',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/store/customers/orders/%'] = array(
    'title' => 'Customer orders',
    'description' => 'View a list of orders placed by this customer.',
    'page callback' => 'uc_store_customer_orders',
    'page arguments' => array(4),
    'access arguments' => array('view all orders'),
    'weight' => -5,
    'type' => MENU_CALLBACK,
    'file' => 'uc_store.admin.inc',
  );

  $items['admin/store/settings/countries/%/disable'] = array(
    'title' => 'Disable a country',
    'description' => 'Disable a country from use.',
    'page callback' => 'uc_country_disable',
    'page arguments' => array(4),
    'access arguments' => array('administer store'),
    'type' => MENU_CALLBACK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/%/enable'] = array(
    'title' => 'Enable a country',
    'description' => 'Enable a disabled country.',
    'page callback' => 'uc_country_enable',
    'page arguments' => array(4),
    'access arguments' => array('administer store'),
    'type' => MENU_CALLBACK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/%/remove'] = array(
    'title' => 'Remove a country',
    'description' => 'Remove an installed country.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_country_remove_form', 4),
    'access arguments' => array('administer store'),
    'type' => MENU_CALLBACK,
    'file' => 'uc_store.admin.inc',
  );
  $items['admin/store/settings/countries/%/update/%'] = array(
    'title' => 'Update a country',
    'description' => 'Update an installed country.',
    'page callback' => 'uc_country_update',
    'page arguments' => array(4, 6),
    'access arguments' => array('administer store'),
    'type' => MENU_CALLBACK,
    'file' => 'uc_store.admin.inc',
  );

  $items['admin/store/settings/price-handlers'] = array(
    'title' => 'Price handler settings',
    '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;
}

/**
 * Implementation of hook_init().
 */
function uc_store_init() {
  module_load_include('inc', 'uc_store', 'includes/summaries');
  module_load_include('inc', 'uc_store', 'includes/tapir');

  drupal_add_css(drupal_get_path('module', 'uc_store') .'/uc_store.css');

  global $conf;
  $conf['i18n_variables'][] = 'uc_store_name';
  $conf['i18n_variables'][] = 'uc_field_first_name';
  $conf['i18n_variables'][] = 'uc_field_last_name';
  $conf['i18n_variables'][] = 'uc_field_email';
  $conf['i18n_variables'][] = 'uc_field_phone';
  $conf['i18n_variables'][] = 'uc_field_company';
  $conf['i18n_variables'][] = 'uc_field_address';
  $conf['i18n_variables'][] = 'uc_field_street';
  $conf['i18n_variables'][] = 'uc_field_street1';
  $conf['i18n_variables'][] = 'uc_field_street2';
  $conf['i18n_variables'][] = 'uc_field_city';
  $conf['i18n_variables'][] = 'uc_field_zone';
  $conf['i18n_variables'][] = 'uc_field_postal_code';
  $conf['i18n_variables'][] = 'uc_field_country';
}

/**
 * Implementation of hook_elements().
 */
function uc_store_elements() {
  $types = array();

  $types['tapir_table'] = array(
    '#columns' => array(),
    '#rows' => array(),
    '#tree' => TRUE,
    '#value' => NULL,
    '#pre_render' => array('tapir_gather_rows'),
    '#process' => 'form_expand_ahah',
  );

  return $types;
}

/**
 * Implementation of hook_flush_caches().
 */
function uc_store_flush_caches() {
  return array('cache_uc_price');
}

/**
 * Implementation of hook_theme().
 */
function uc_store_theme() {
  return array(
    'uc_admin_dashboard' => array(
      'arguments' => array('type' => NULL, 'menus' => NULL),
    ),
    'uc_store_footer' => array(
      'arguments' => array('message' => ''),
    ),
    'uc_store_address_fields_form' => array(
      'arguments' => array('form' => NULL),
    ),
    'uc_pane_sort_table' => array(
      'arguments' => array('form' => NULL),
    ),
    '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()),
    ),
  );
}

/**
 * Implementation of hook_token_values(). (token.module)
 */
function uc_store_token_values($type, $object = NULL) {
  global $base_url;

  switch ($type) {
    case 'global':
      $login_link = url('user', array('absolute' => TRUE));
      $values['site-login'] = l($login_link, $login_link);

      $theme_key = variable_get('theme_default', 'garland');
      $settings = theme_get_settings($theme_key);
      $themes = list_themes();
      $theme_object = $themes[$theme_key];
      if ($settings['toggle_logo']) {
        if ($settings['default_logo']) {
          $settings['logo'] = dirname($theme_object->filename) .'/logo.png';
        }
        elseif ($settings['logo_path']) {
          $settings['logo'] = $settings['logo_path'];
        }
      }

      // Use a logo; but only if we have one to use.
      if (isset($settings['logo']) && $settings['logo']) {
        $values['site-logo'] = '<img src="'. url($base_url .'/'. $settings['logo'], array('absolute' => TRUE)) .'" />';
      }
      else {
        $values['site-logo'] = '';
      }

      $values['store-name'] = variable_get('uc_store_name', t('Our store'));
      $values['store-url'] = $base_url;
      $values['store-link'] = l(variable_get('uc_store_name', t('Our store')), $base_url);
      $values['store-owner'] = variable_get('uc_store_owner', '');
      $values['store-email'] = variable_get('uc_store_email', '');
      $values['store-phone'] = variable_get('uc_store_phone', '');
      $values['store-fax'] = variable_get('uc_store_fax', '');
      $values['store-address'] = uc_store_address();
      $values['store-help-url'] = url(variable_get('uc_store_help_page', ''), array('absolute' => TRUE));
      break;
  }

  return $values;
}

/**
 * Implementation of hook_token_list(). (token.module)
 */
function uc_store_token_list($type = 'all') {
  $tokens['global']['site-login'] = t('A link to the site login page.');
  $tokens['global']['site-logo'] = t('The URL for the site logo.');
  $tokens['global']['store-name'] = t('The Ubercart store name.');
  $tokens['global']['store-url'] = t('The Ubercart store URL.');
  $tokens['global']['store-link'] = t('A link to the Ubercart store using the store name.');
  $tokens['global']['store-owner'] = t('The Ubercart store owner.');
  $tokens['global']['store-email'] = t('The Ubercart store e-mail address.');
  $tokens['global']['store-phone'] = t('The Ubercart store phone number.');
  $tokens['global']['store-fax'] = t('The Ubercart store fax number.');
  $tokens['global']['store-address'] = t('The Ubercart store mailing address.');
  $tokens['global']['store-help-url'] = t('The URL to the store help page.');

  return $tokens;
}

/**
 * Implementation of hook_help().
 */
function uc_store_help($path, $arg) {
  switch ($path) {
    case 'admin/store/settings/store/edit/report':
      return t("Reporting to Ubercart.org happens once a week during a normal cron run and is logged in your site's watchdog. Reporting lets Ubercart.org calculate the total number of Ubercart installations and aggregate version information for use in development. Your site's data is never shared with anyone, and aggregate statistics displayed through Ubercart.org will be in the form of anonymous group statistics. Reporting will also allow Ubercart.org to report security notices back to your site if needed.");
    case 'admin/store/customers':
      return t('This table lists out all users on your site who have placed orders.');
    case 'admin/store/customers/search':
      return t('Use this page to search through users on your site who have placed orders.');
  }
}

/**
 * Implementation of hook_perm().
 */
function uc_store_perm() {
  return array('administer store', 'view customers', 'view store reports');
}

/**
 * Implementation of hook_footer().
 */
function uc_store_footer() {
  // Exit if the store footer is turned off.
  if (variable_get('uc_footer_message', 0) === 'none') {
    return;
  }

  // Figure out what page is being viewed.
  $path = drupal_get_normal_path($_GET['q']);
  $parts = explode('/', $path);

  // Break out of the switch if the page isn't governed by Ubercart.
  switch ($parts[0]) {
    case 'admin':
      // No footer on /admin or /admin/*.
      // But add a footer on /admin/store and /admin/store/*
      if (!isset($parts[1]) || $parts[1] != 'store') {
        break;
      }
    case 'node':
      // No footer on /node or /node/[type]/add.
      // Only add a footer on /node/[nid] if that node is a product.
      if (count($parts) != 2 || intval($parts[1]) == 0) {
        break;
      }
      else {
        $node = node_load($parts[1]);
        if ($node == FALSE || !function_exists('uc_product_node_info') || !uc_product_is_product($node->type)) {
          break;
        }
      }
    // Add a footer to the following pages.
    case 'catalog':
    case 'cart':
    case 'manufacturer':
      $messages = _store_footer_options();
      if (($message = variable_get('uc_footer_message', 0)) > 0) {
        $message = $messages[$message];
      }
      else {
        $message = db_result(db_query("SELECT message FROM {uc_store_footers} WHERE path_hash = '%s'", md5($path)));
        if (!$message) {
          unset($messages['none']);
          $message = $messages[array_rand($messages)];
          db_query("INSERT INTO {uc_store_footers} (path_hash, message) VALUES ('%s', '%s')", md5($path), $message);
        }
      }

      return theme('uc_store_footer', $message);
  }
}

/**
 * Implementation of hook_exit().
 */
function uc_store_exit() {
  // Save the current request for tracking paths on subsequent page requests.
  // When HTTP_REFERER is set, the session version is not; and vice versa.
  if (referer_uri() == '') {
    $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') ? 'https' : 'http';
    $_SESSION['uc_referer_uri'] = $protocol .'://'. $_SERVER['SERVER_NAME'] . $GLOBALS['base_path'] . $_GET['q'];
  }
  else {
    if (isset($_SESSION['uc_referer_uri'])) {
      unset($_SESSION['uc_referer_uri']);
    }
  }

  // Save the timestamp of the last access.
  // $_SESSION['uc_last_access'] = time();
}

/**
 * Implementation of hook_form_alter().
 */
function uc_store_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'uc_product_table') {
    unset($form['table']['#value']['columns']['list_price']);
  }
}

/******************************************************************************
 * Callback Functions, Forms, and Tables
 ******************************************************************************/

/**
 * Theme a formatted price for display.
 *
 * @ingroup themeable
 */
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 = '<span class="'. implode(' ', $context['class']) .'">';
  // Prefix(es).
  if ($options['label'] && isset($options['prefixes'])) {
    $output .= '<span class="price-prefixes">'. implode('', $options['prefixes']) .'</span>';
  }
  // Value.
  $output .= $value;
  // Suffix(es).
  if ($options['label'] && isset($options['suffixes'])) {
    $output .= '<span class="price-suffixes">'. implode('', $options['suffixes']) .'</span>';
  }
  $output .= '</span>';

  return $output;
}

/**
 * Theme the dashboard on the admin/store page.
 *
 * @ingroup themeable
 */
function theme_uc_admin_dashboard($type, $menus) {
  if ($type == 1) {
    drupal_add_js(drupal_get_path('module', 'uc_store') .'/uc_store.js', 'module');
    drupal_add_js(array('ucTextShow' => t('- Show links -'), 'ucTextHide' => t('- Hide links -')), 'setting');
  }

  $output = '<table class="uc-store-admin-table" align="center"><tr valign="top">';

  $panel = 0;
  if (is_array($menus)) {
    foreach ($menus as $menu) {
      $panel++;
      if ($panel % 4 == 0) {
        $output .= '</tr><tr valign="top">';
      }
      $panel_title = $menu['title'];
      if ($type == 3) {
        $panel_links = '';
      }
      else {
        $panel_links = theme('admin_block_content', system_admin_menu_block(menu_get_item($menu['href'])));
      }
      $panel_table = '<table width="100%"><tr>'
                    .'<td>'. l(uc_store_get_icon($menu['href']), $menu['href'],
                               array('html' => TRUE)) .'</td>'
                    .'<td class="panel-title">'. l($menu['title'], $menu['href'])
                    .'</td></tr>';
      if (strlen($panel_links) > 0) {
        if ($type == 1) {
          $disp = 'display: none;';
        }
        $panel_table .= '<tr><td nowrap colspan="2" class="panel-links" '
                       .'style="'. $disp .'">'. $panel_links .'</td></tr>';
        if ($type == 1) {
          $panel_table .= '<tr><td align="center" colspan="2" '
                         .'class="panel-show-link" id="show-links-'. $panel
                         .'"><a>'. t('- Show links -') .'</a></td></tr>';
        }
      }
      $panel_table .= '</table>';
      $output .= '<td class="uc-store-admin-panel" id="panel-'. $panel .'">'
               . $panel_table .'</td>';
    }
  }

  $output .= '</tr></table>';

  return $output;
}

/**
 * Return the default store footer options.
 */
function _store_footer_options() {
  $url = array('!url' => 'http://www.ubercart.org/');
  return array(
    1 => t('<a href="!url">Powered by Ubercart</a>', $url),
    2 => t('<a href="!url">Drupal e-commerce</a> provided by Ubercart.', $url),
    3 => t('Supported by Ubercart, an <a href="!url">open source e-commerce suite</a>.', $url),
    4 => t('Powered by Ubercart, the <a href="!url">free shopping cart software</a>.', $url),
    'none' => t('(Do not display a message in the footer.)'),
  );
}

/**
 * Wrap the footer in a div so it can be re-styled.
 *
 * @ingroup themeable
 */
function theme_uc_store_footer($message) {
  return '<div id="store-footer">'. $message .'</div>';
}

/**
 * Form to configure address fields.
 *
 * @ingroup forms
 * @see
 *   theme_uc_store_address_fields_form()
 *   uc_store_address_fields_form_submit()
 *   uc_store_address_fields_form_reset()
 */
function uc_store_address_fields_form() {
  $form['fields'] = array(
    '#tree' => TRUE,
    '#summary callback' => 'summarize_form',
  );

  $fields = array(
    'first_name' => array(t('First name'), TRUE),
    'last_name' => array(t('Last name'), TRUE),
    'phone' => array(t('Phone number'), TRUE),
    'company' => array(t('Company'), TRUE),
    'street1' => array(t('Street address 1'), TRUE),
    'street2' => array(t('Street address 2'), TRUE),
    'city' => array(t('City'), TRUE),
    'zone' => array(t('State/Province'), TRUE),
    'country' => array(t('Country'), TRUE),
    'postal_code' => array(t('Postal code'), TRUE),
    'address' => array(t('Address'), FALSE),
    'street' => array(t('Street address'), FALSE),
  );
  $current = variable_get('uc_address_fields', drupal_map_assoc(array('first_name', 'last_name', 'phone', 'company', 'street1', 'street2', 'city', 'zone', 'postal_code', 'country')));
  $required = variable_get('uc_address_fields_required', drupal_map_assoc(array('first_name', 'last_name', 'street1', 'city', 'zone', 'postal_code', 'country')));
  foreach ($fields as $field => $data) {
    if ($data[1]) {
      $form['fields'][$field]['#summary callback'] = 'summarize_form';
      $form['fields'][$field]['enabled'] = array(
        '#type' => 'checkbox',
        '#summary callback' => 'summarize_checkbox',
        '#summary arguments' => array(
          t('@field is enabled.', array('@field' => uc_get_field_name($field))),
          t('@field is disabled.', array('@field' => uc_get_field_name($field))),
        ),
        '#default_value' => isset($current[$field]) ? TRUE : FALSE,
      );
      $form['fields'][$field]['required'] = array(
        '#type' => 'checkbox',
        '#default_value' => isset($required[$field]) ? TRUE : FALSE,
      );
    }
    else {
      $form['fields'][$field]['enabled'] = array(
        '#value' => '-',
      );
    }
    $form['fields'][$field]['default'] = array(
      '#value' => $data[0],
    );
    $form['fields'][$field]['title'] = array(
      '#type' => 'textfield',
      '#summary callback' => 'summarize_null',
      '#default_value' => uc_get_field_name($field),
      '#size' => 32,
    );
  }

  $form['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset to defaults'),
    '#submit' => array('uc_store_address_fields_form_reset'),
  );

  return $form;
}

/**
 * @ingroup themeable
 * @see uc_store_address_fields_form()
 */
function theme_uc_store_address_fields_form($form) {
  $header = array(t('Enabled'), t('Field'), t('Title'), t('Required'));

  foreach (element_children($form['fields']) as $field) {
    $rows[] = array(
      array('data' => drupal_render($form['fields'][$field]['enabled']), 'align' => 'center'),
      drupal_render($form['fields'][$field]['default']),
      drupal_render($form['fields'][$field]['title']),
      drupal_render($form['fields'][$field]['required']),
    );
  }

  $output = theme('table', $header, $rows) .'<br />'. drupal_render($form);

  return $output;
}

/**
 * Save the address fields settings.
 *
 * @see uc_store_address_fields_form()
 */
function uc_store_address_fields_form_submit($form, &$form_state) {
  $fields = array();
  $required = array();
  foreach ($form_state['values']['fields'] as $field => $data) {
    variable_set('uc_field_'. $field, $data['title']);
    if ($data['enabled'] == TRUE) {
      $fields[] = $field;
    }
    if ($data['required'] == TRUE) {
      $required[] = $field;
    }
  }
  variable_set('uc_address_fields', drupal_map_assoc($fields));
  variable_set('uc_address_fields_required', drupal_map_assoc($required));
  drupal_set_message(t('The configuration options have been saved.'));
}

/**
 * Reset the address fields settings to their default values.
 *
 * @see uc_store_address_fields_form()
 */
function uc_store_address_fields_form_reset($form_id, &$form_state) {
  foreach ($form_state['values']['fields'] as $field => $data) {
    variable_del('uc_field_'. $field);
  }
  variable_del('uc_address_fields');
  drupal_set_message(t('The configuration options have been reset to their default values.'));
}

/**
 * A handler for Javascript helper functions...
 */
function uc_store_js_util($func) {
  switch ($func) {
    case 'currency_format':
      $context = array(
        'revision' => 'formatted-original',
      );
      $amount = is_numeric($_POST['amount']) ? $_POST['amount'] : 0;
      $output = uc_price($amount, $context);
      break;
    case 'zone_select':
      $country_id = intval($_POST['country_id']) > 0 ? intval($_POST['country_id']) : uc_store_default_country();
      $title = isset($_POST['title']) ? check_plain($_POST['title']) : NULL;
      $display = isset($_POST['display']) ? check_plain($_POST['display']) : 'name';
      $select = uc_zone_select($title, NULL, NULL, $country_id, $display);
      $select['#parents'] = array();
      $match = array('`<[/]*div[^>]*>`', '`<[/]*select[^>]*>`', '`\n|\r`');
      $replace = array('', '', '');
      $output = preg_replace($match, $replace, theme('select', $select));
  }

  print $output;
  exit();
}

/*******************************************************************************
 * Module and Helper Functions
 ******************************************************************************/

/**
 * Return the IMG tag for a store icon.
 *
 * @param $path
 *   The Drupal path of the menu item. Atlernately may specify a filename by
 *   passing this string as file:filename.png.
 * @param $small
 *   Pass TRUE to get a link to the small version of the icon. If specifying a
 *   filename, you should let this be FALSE.
 * @return
 *   HTML output for the image.
 */
function uc_store_get_icon($path, $small = FALSE, $class = 'uc-store-icon', $alt = NULL) {
  $file = FALSE;

  switch ($path) {
    case 'admin/store':
      $file = 'store_monitor';
      break;
    case 'admin/store/orders':
      $file = 'menu_orders';
      break;
    case 'admin/store/customers':
      $file = 'menu_customers';
      break;
    case 'admin/store/products':
      $file = 'menu_products';
      break;
    case 'admin/store/reports':
      $file = 'menu_reports';
      break;
    case 'admin/store/settings':
      $file = 'menu_store_settings';
      break;
    case 'admin/store/help':
      $file = 'menu_help';
      break;
  }

  if (substr($path, 0, 5) == 'file:') {
    $file = substr($path, 5);
  }

  if (!$file) {
    // See if it's hooked in anywhere else...
    return '';
  }

  if ($small) {
    $file .= '_small';
  }

  $alt = ' alt="'. (string) $alt .'"';

  $output = '<img src="'. base_path() . drupal_get_path('module', 'uc_store')
           .'/images/'. $file .'.gif" class="'. $class .'"'. $alt .' />';

  return $output;
}

/**
 * Format an amount for display with the store's currency settings.
 */
function uc_currency_format($value, $sign = TRUE, $thou = TRUE, $dec = NULL) {
  if ($value === NULL) {
    return NULL;
  }

  $context = array(
    'revision' => 'formatted',
    'type' => 'amount',
  );

  $options = array();

  if (!$sign) {
    $options['sign'] = '';
  }
  if (!$thou) {
    $options['thou'] = '';
  }
  if (!is_null($dec)) {
    $options['dec'] = $dec;
  }

  return uc_price($value, $context, $options);
}

/**
 * Format a weight value for display.
 */
function uc_weight_format($value, $unit = NULL) {
  $vars = array('!value' => $value);

  if (is_null($unit)) {
    $unit = variable_get('uc_weight_unit', 'lb');
  }

  $defaults = array(
    'lb' => '!value lb.',
    'oz' => '!value oz.',
    'kg' => '!valuekg',
    'g' => '!valueg',
  );

  $pattern = variable_get('uc_weight_format_'. $unit, $defaults[$unit]);
  if (strpos($pattern, '!value') === FALSE) {
    $pattern = $defaults[$unit];
  }

  $format = strtr($pattern, $vars);

  return $format;
}

/**
 * Get the conversion ratio from one unit of weight to another.
 */
function uc_weight_conversion($from_units, $to_units = NULL) {
  if (is_null($to_units)) {
    $to_units = variable_get('uc_weight_unit', 'lb');
  }
  $constant = strtoupper($from_units) .'_TO_'. strtoupper($to_units);
  if (defined($constant) && ($conversion = constant($constant)) > 0) {
    return $conversion;
  }
  else {
    return 1;
  }
}

/**
 * Format a length value for display.
 */
function uc_length_format($value, $unit = NULL) {
  $vars = array('!value' => $value);

  if (is_null($unit)) {
    $unit = variable_get('uc_length_unit', 'in');
  }

  $defaults = array(
    'in' => '!valuein.',
    'ft' => '!valueft.',
    'cm' => '!valuecm',
    'mm' => '!valuemm',
  );

  $pattern = variable_get('uc_length_format_'. $unit, $defaults[$unit]);
  if (strpos($pattern, '!value') === FALSE) {
    $pattern = $defaults[$unit];
  }

  $format = strtr($pattern, $vars);

  return $format;
}

/**
 * Get the conversion ratio from one unit of length to another.
 */
function uc_length_conversion($from_units, $to_units = NULL) {
  if (is_null($to_units)) {
    $to_units = variable_get('uc_length_unit', 'in');
  }
  $constant = strtoupper($from_units) .'_TO_'. strtoupper($to_units);
  if (defined($constant) && ($conversion = constant($constant)) > 0) {
    return $conversion;
  }
  else {
    return 1;
  }
}

/**
 * Format a date value for display.
 */
function uc_date_format($month, $day, $year, $class = 'default') {
  $time = strtotime($month .'/'. $day .'/'. $year);

  $pattern = variable_get('uc_date_format_'. $class, 'm/d/Y');
  if (strlen($pattern) < 3) {
    $pattern = 'm/d/Y';
  }

  return format_date($time, 'custom', $pattern);
}

/**
 * Save the address format for a country.
 */
function uc_set_address_format($country_id, $format) {
  variable_set('uc_address_format_'. intval($country_id), $format);
}

/**
 * Format an address for display based on a country's address format.
 */
function uc_address_format($first_name, $last_name, $company, $street1, $street2, $city, $zone, $postal_code, $country) {
  $result = db_query("SELECT * FROM {uc_zones} WHERE zone_id = %d", $zone);
  if (!($zone_data = db_fetch_array($result))) {
    $zone_data = array('zone_code' => t('N/A'), 'zone_name' => t('Unknown'));
  }
  $result = db_query("SELECT * FROM {uc_countries} WHERE country_id = %d", $country);
  if (!($country_data = db_fetch_array($result))) {
    $country_data = array(
      'country_name' => t('Unknown'),
      'country_iso_code_2' => t('N/A'),
      'country_iso_code_3' => t('N/A'),
    );
  }

  $variables = array(
    "\r\n" => '<br />',
    '!company' => check_plain($company),
    '!first_name' => check_plain($first_name),
    '!last_name' => check_plain($last_name),
    '!street1' => check_plain($street1),
    '!street2' => check_plain($street2),
    '!city' => check_plain($city),
    '!zone_code' => $zone_data['zone_code'],
    '!zone_name' => $zone_data['zone_name'],
    '!postal_code' => check_plain($postal_code),
    '!country_name' => $country_data['country_name'],
    '!country_code2' => $country_data['country_iso_code_2'],
    '!country_code3' => $country_data['country_iso_code_3'],
  );

  if (uc_store_default_country() != $country) {
    $variables['!country_name_if'] = $country_data['country_name'];
    $variables['!country_code2_if'] = $country_data['country_iso_code_2'];
    $variables['!country_code3_if'] = $country_data['country_iso_code_3'];
  }
  else {
    $variables['!country_name_if'] = '';
    $variables['!country_code2_if'] = '';
    $variables['!country_code3_if'] = '';
  }

  $format = variable_get('uc_address_format_'. $country, '');
  if (empty($format)) {
    $format = "!company\r\n!first_name !last_name\r\n!street1\r\n!street2\r\n!city, !zone_code !postal_code\r\n!country_name_if";
  }
  $address = strtr($format, $variables);
  $address = strtr($address, array("\n" => '<br />'));

  $match = array('`^<br( /)?>`', '`<br( /)?>$`', '`<br( /)?>(\s*|[\s*<br( /)?>\s*]+)<br( /)?>`', '`<br( /)?><br( /)?>`', '`<br( /)?>, N/A`');
  $replace = array('', '', '<br />', '<br />', '', '');
  $address = preg_replace($match, $replace, $address);

  return $address;
}

/**
 * Return the code abbreviation for a zone based on the zone ID or name.
 */
function uc_get_zone_code($zone = NULL) {
  if (empty($zone)) {
    return FALSE;
  }

  if (is_numeric($zone)) {
    $result = db_query("SELECT zone_code FROM {uc_zones} WHERE zone_id = %d", $zone);
  }
  else {
    $result = db_query("SELECT zone_code FROM {uc_zones} WHERE zone_name = '%s'", $zone);
  }

  if ($row = db_fetch_object($result)) {
    return $row->zone_code;
  }

  return FALSE;
}

/**
 * Returns the rows of countries whose data matches the fields specified in the
 * $fields array.
 */
function uc_get_country_data($match = array(), $sort = 'country_name') {
  $valid_fields = array('country_id', 'country_name', 'country_iso_code_2', 'country_iso_code_3', 'version');

  if (!is_array($match)) {
    $match = array();
  }
  if (!in_array($sort, $valid_fields)) {
    $sort = 'country_name';
  }

  $query = 'SELECT * FROM {uc_countries}';
  if (count($match) > 0) {
    $where = '';
    foreach ($match as $key => $value) {
      if (!in_array($key, $valid_fields)) {
        continue;
      }
      if (strlen($where) == 0) {
        $where = ' WHERE ';
      }
      if (strlen($where) > 7) {
        $where .= ' AND ';
      }
      $where .= $key ." = '". check_plain($value) ."'";
    }
  }
  $query .= $where .' ORDER BY '. check_plain($sort);

  $result = db_query($query);

  while ($row = db_fetch_array($result)) {
    $countries[] = $row;
  }

  return empty($countries) ? FALSE : $countries;
}

/**
 * Return the name of an address field.
 */
function uc_get_field_name($field) {
  $fields = array(
    'first_name' => t('First name'),
    'last_name' => t('Last name'),
    'email' => t('E-mail'),
    'phone' => t('Phone number'),
    'company' => t('Company'),
    'address' => t('Address'),
    'street' => t('Street address'),
    'street1' => t('Street address 1'),
    'street2' => t('Street address 2'),
    'city' => t('City'),
    'zone' => t('State/Province'),
    'postal_code' => t('Postal code'),
    'country' => t('Country'),
  );

  $default = $fields[$field];
  if (empty($default)) {
    drupal_set_message(t('The field title %field is being accessed incorrectly.', array('%field' => $field)), 'error');
    return '';
  }

  return variable_get('uc_field_'. $field, $default);
}

/**
 * Return TRUE if the address field is enabled.
 */
function uc_address_field_enabled($field) {
  $fields = variable_get('uc_address_fields', drupal_map_assoc(array('first_name', 'last_name', 'phone', 'company', 'street1', 'street2', 'city', 'zone', 'postal_code', 'country')));

  if (!isset($fields[$field])) {
    return FALSE;
  }
  else {
    return TRUE;
  }
}

/**
 * Return TRUE if the address field field is required.
 */
function uc_address_field_required($field) {
  $fields = variable_get('uc_address_fields_required', drupal_map_assoc(array('first_name', 'last_name', 'street1', 'city', 'zone', 'postal_code', 'country')));

  if (!isset($fields[$field])) {
    return FALSE;
  }
  else {
    return TRUE;
  }
}

/**
 * A simple Forms API textfield generator...
 */
function uc_textfield($title, $default = NULL, $required = TRUE, $description = NULL, $maxlength = 32, $size = 32) {
  if (is_null($title) || empty($title))
    return NULL;

  $textfield = array(
    '#type' => 'textfield',
    '#title' => $title,
    '#description' => $description,
    '#size' => $size,
    '#maxlength' => $maxlength,
    '#required' => $required,
    '#default_value' => $default,
    '#summary' => $default ? t('@title is %default.', array('@title' => $title, '%default' => $default))
                           : t('@title is not set.', array('@title' => $title)),
  );

  return $textfield;
}

/**
 * Retrieve a zone's name from the database, using its ID.
 *
 * @param $id
 *   The zone's ID.
 */
function uc_zone_get_by_id($id) {
  return db_result(db_query("SELECT zone_name FROM {uc_zones} WHERE zone_id = %d", $id));
}

/**
 * Create a zone select box for a form.
 *
 * @param $display
 *   Can be 'code' or 'name'.
 */
function uc_zone_select($title, $default = NULL, $description = NULL, $country_id = NULL, $display = 'name', $required = FALSE) {
  if (empty($country_id)) {
    $country_id = uc_store_default_country();
  }

  $result = db_query("SELECT * FROM {uc_zones} WHERE zone_country_id = %d ORDER BY %s", $country_id, ($display == 'code') ? 'zone_code' : 'zone_name');

  $options[''] = t('Please select');
  while ($zone = db_fetch_object($result)) {
    $options[$zone->zone_id] = ($display == 'code') ? $zone->zone_code : $zone->zone_name;
  }

  if (count($options) == 1) {
    $options = array(-1 => t('Not applicable'));
  }

  $select = array(
    '#type' => 'select',
    '#title' => $title,
    '#description' => $description,
    '#options' => $options,
    '#default_value' => $default,
    '#required' => $required,
    '#disabled' => isset($options[-1]) ? TRUE : FALSE,
    '#suffix' => '<span class="zone-throbber"></span>',
  );

  return $select;
}

/**
 * Retrieve a country's name from the database, using its ID.
 *
 * @param $id
 *   The country's ID.
 */
function uc_country_get_by_id($id) {
  return db_result(db_query("SELECT country_name FROM {uc_countries} WHERE country_id = %d", $id));
}

/**
 * Create a country select box for a form.
 * $display can be 'name', 'code2' for the 2-digit code, or 'code3' for the 3-digit code.
 */
function uc_country_select($title, $default = NULL, $description = NULL, $display = 'name', $required = FALSE) {
  if ($display == 'code2') {
    $order_by = 'country_iso_code_2';
  }
  elseif ($display == 'code3') {
    $order_by = 'country_iso_code_3';
  }
  else {
    $order_by = 'country_name';
  }

  $result = db_query("SELECT * FROM {uc_countries} WHERE version > 0 ORDER BY %s", $order_by);

  $options = array();
  while ($country = db_fetch_array($result)) {
    $options[$country['country_id']] = $country[$order_by];
  }
  if (count($options) == 0) {
    $options[] = t('No countries found.');
  }

  $default = db_result(db_query("SELECT country_id FROM {uc_countries} WHERE country_id = %d AND version > 0", empty($default) ? 0 : intval($default)));

  $select = array(
    '#type' => 'select',
    '#title' => $title,
    '#description' => $description,
    '#options' => $options,
    '#default_value' => empty($default) ? uc_store_default_country() : $default,
    '#required' => $required,
  );

  drupal_add_js(drupal_get_path('module', 'uc_store') .'/uc_country_select.js');

  return $select;
}

/**
 * Create a day select box for a form.
 */
function uc_select_day($title = NULL, $default = NULL, $allow_empty = FALSE) {
  $options = $allow_empty ? array('' => '') : array();

  $select = array(
    '#type' => 'select',
    '#title' => (is_null($title) ? t('Day') : $title),
    '#options' => $options + drupal_map_assoc(range(1, 31)),
    '#default_value' => (is_null($default) ? 0 : $default),
  );

  return $select;
}

/**
 * Create a month select box for a form.
 */
function uc_select_month($title = NULL, $default = NULL, $allow_empty = FALSE) {
  $options = $allow_empty ? array('' => '') : array();

  $select = array(
    '#type' => 'select',
    '#title' => (is_null($title) ? t('Month') : $title),
    '#options' => $options +
                  array(1 => t('01 - January'), 2 => t('02 - February'),
                        3 => t('03 - March'), 4 => t('04 - April'),
                        5 => t('05 - May'), 6 => t('06 - June'),
                        7 => t('07 - July'), 8 => t('08 - August'),
                        9 => t('09 - September'), 10 => t('10 - October'),
                        11 => t('11 - November'), 12 => t('12 - December')
                  ),
    '#default_value' => (is_null($default) ? 0 : $default),
  );

  return $select;
}

/**
 * Create a year select box for a form.
 */
function uc_select_year($title = NULL, $default = NULL, $min = NULL, $max = NULL, $allow_empty = FALSE) {
  $min = is_null($min) ? intval(date('Y')) : $min;
  $max = is_null($max) ? intval(date('Y')) + 20 : $max;
  $options = $allow_empty ? array('' => '') : array();

  $select = array(
    '#type' => 'select',
    '#title' => (is_null($title) ? t('Year') : $title),
    '#options' => $options + drupal_map_assoc(range($min, $max)),
    '#default_value' => (is_null($default) ? 0 : $default),
  );

  return $select;
}

/**
 * Create an address select box based on a user's previous orders.
 *
 * @param $uid
 *   The user's ID to search for in the orders table.
 * @param $type
 *   Choose either 'shipping' or 'billing'.
 */
function uc_select_address($uid, $type = 'billing', $onchange = '', $title = NULL, $icon_suffix = FALSE) {
  $addresses = uc_get_addresses($uid, $type);

  if (!is_array($addresses) || count($addresses) == 0) {
    return NULL;
  }

  $options = array('0' => t('Select one...'));
  foreach ($addresses as $key => $address) {
    $option = $address['street1'];
    // Check if the address is a duplicate (i.e. same address, but sent to different person)
    if ((isset($addresses[$key - 1]) && $option == $addresses[$key - 1]['street1']) ||
        (isset($addresses[$key + 1]) && $option == $addresses[$key + 1]['street1'])) {
      $option .= ' - ' . $address['first_name'] . ' ' . $address['last_name'];
    }
    $options[drupal_to_js($address)] = check_plain($option);
  }

  $select = array(
    '#type' => 'select',
    '#title' => is_null($title) ? t('Address book') : $title,
    '#options' => $options,
    '#attributes' => array('onchange' => $onchange),
    '#suffix' => $icon_suffix ? uc_store_get_icon('file:address_book', FALSE, 'address-book-icon') : NULL,
  );

  return $select;
}

/**
 * Load a customer's previously given addresses.
 */
function uc_get_addresses($uid, $type = 'billing') {
  if ($uid == 0) {
    return NULL;
  }

  if ($type == 'delivery') {
    $type = 'delivery';
  }
  else {
    $type = 'billing';
  }

  switch ($GLOBALS['db_type']) {
    case 'mysqli':
    case 'mysql':
      $result = db_query("SELECT DISTINCT ". $type ."_first_name AS first_name, "
                   . $type ."_last_name AS last_name, ". $type ."_phone AS phone, "
                   . $type ."_company AS company, ". $type ."_street1 AS street1, "
                   . $type ."_street2 AS street2, ". $type ."_city AS city, "
                   . $type ."_zone AS zone, ". $type ."_postal_code AS postal_code, "
                   . $type ."_country AS country FROM {uc_orders} WHERE uid = %d "
                   ."AND order_status IN ". uc_order_status_list('general', TRUE)
                   ." ORDER BY created DESC", $uid);
      break;
    case 'pgsql':
      // In pgsql, ORDER BY requires the field being sorted by to be in the SELECT list.
      // But if we have the 'created' column in the SELECT list, the DISTINCT is
      // rather useless. So for pgsql we will just sort addresses alphabetically.
      $result = db_query("SELECT DISTINCT ". $type ."_first_name AS first_name, "
                   . $type ."_last_name AS last_name, ". $type ."_phone AS phone, "
                   . $type ."_company AS company, ". $type ."_street1 AS street1, "
                   . $type ."_street2 AS street2, ". $type ."_city AS city, "
                   . $type ."_zone AS zone, ". $type ."_postal_code AS postal_code, "
                   . $type ."_country AS country FROM {uc_orders} WHERE uid = %d "
                   ."AND order_status IN ". uc_order_status_list('general', TRUE)
                   ." ORDER BY ". $type ."_street1 DESC", $uid);
      break;
  }

  $addresses = array();
  while ($address = db_fetch_array($result)) {
    if (!empty($address['street1']) || !empty($address['postal_code'])) {
      $addresses[] = $address;
    }
  }

  return $addresses;
}

/**
 * Strip <form> tags and form_token and form_id hidden fields from form HTML for
 * use in an AJAX populated div. (Enables these values to be access via $_POST.)
 */
function uc_strip_form($html) {
  $html = preg_replace('`</?form.*>`', '', $html);
  $html = preg_replace('`<input.*name="form_(token|id)".*>`', '', $html);

  return $html;
}

/**
 * Return the initials for a user account.
 */
function uc_get_initials($uid) {
  if ($uid == 0 || $uid == NULL) {
    return '-';
  }
  return check_plain(variable_get('user_initials_'. $uid, $uid));
}

/**
 * Return an array of country files in ubercart/uc_store/countries that can
 * be installed or updated.
 */
function _country_import_list() {
  $dir = drupal_get_path('module', 'uc_store') .'/countries/';

  $countries = array();
  if (is_dir($dir)) {
    if ($dh = opendir($dir)) {
      while (($file = readdir($dh)) !== FALSE) {
        switch (filetype($dir . $file)) {
          case 'file':
            if (substr($file, -4, 4) == '.cif') {
              $pieces = explode('_', substr($file, 0, strlen($file) - 4));
              $country_id = intval($pieces[count($pieces) - 2]);
              $version = $pieces[count($pieces) - 1];

              if (!isset($countries[$country_id])) {
                $countries[$country_id]['version'] = $version;
                $countries[$country_id]['file'] = $file;
              }
              else {
                if ($version > $countries[$country_id]['version']) {
                  $countries[$country_id]['version'] = $version;
                  $countries[$country_id]['file'] = $file;
                }
              }
            }
            break;
        }
      }
      closedir($dh);
    }
  }

  return $countries;
}

/**
 * Import an Ubercart country file by filename.
 *
 * @param $file
 *   The filename of the country to import.
 * @return
 *   TRUE or FALSE indicating whether or not the country was imported.
 */
function uc_country_import($file) {
  require_once(drupal_get_path('module', 'uc_store') .'/countries/'. $file);

  $pieces = explode('_', substr($file, 0, strlen($file) - 4));

  $country_id = $pieces[count($pieces) - 2];
  $version = $pieces[count($pieces) - 1];
  $country = substr($file, 0, strlen($file) - strlen($country_id) - strlen($version) - 6);

  $func = $country .'_install';

  if (function_exists($func)) {
    $func();
    return TRUE;
  }

  return FALSE;
}

/**
 * Include the appropriate country file and return the base for hooks.
 */
function _country_import_include($country_id, $version) {
  $dir = drupal_get_path('module', 'uc_store') .'/countries/';
  $match = '_'. $country_id .'_'. $version .'.cif';
  $matchlen = strlen($match);

  $countries = array();
  if (is_dir($dir)) {
    if ($dh = opendir($dir)) {
      while (($file = readdir($dh)) !== FALSE) {
        switch (filetype($dir . $file)) {
          case 'file':
            if (substr($file, -$matchlen) == $match) {
              require_once($dir . $file);
              return substr($file, 0, strlen($file) - $matchlen);
            }
            break;
        }
      }
      closedir($dh);
    }
  }

  return FALSE;
}

/**
 * Sort an array of arrays having a weight key to determine their order.
 */
function uc_weight_sort($a, $b) {
  if ($a['weight'] == $b['weight']) {
    return 0;
  }

  return ($a['weight'] > $b['weight']) ? 1 : -1;
}

/**
 * Return the default message for a configurable message.
 */
function uc_get_message($message_id) {
  static $messages;

  if (empty($messages)) {
    $messages = module_invoke_all('uc_message');
  }

  return $messages[$message_id];
}

/**
 * Theme a pane sorting form into a table.
 *
 * @ingroup themeable
 */
function theme_uc_pane_sort_table($form) {
  $prefix = $form['#pane_prefix'];
  if (isset($form['#table_attributes']) && is_array($form['#table_attributes'])) {
    $attributes = $form['#table_attributes'];
  }
  else {
    $attributes = array();
  }

  $header = array(t('Enabled'), t('Title'), t('List position'));

  foreach (element_children($form) as $pane_id) {
    $rows[] = array(
      array(
        'data' => drupal_render($form[$pane_id][$prefix .'_'. $pane_id .'_enabled']),
        'align' => 'center'
      ),
      drupal_render($form[$pane_id]['title']),
      drupal_render($form[$pane_id][$prefix .'_'. $pane_id .'_weight']),
    );
  }

  return theme('table', $header, $rows, $attributes) .'<br />';
}

/**
 * Return an array of values like PHP 5's range function.
 */
function uc_range($low, $high, $step = 1) {
  if (!is_numeric($low) || !is_numeric($high)) {
    return array(0);
  }

  if ($low == $high || !is_numeric($step) || $step == 0) {
    return array($low);
  }

  if ($step < 0) {
    $step = abs($step);
  }

  $arr = array();
  for ($i = $low; ($low < $high) ? $i <= $high : $i >= $high; ($low < $high) ? $i += $step : $i -= $step) {
    $arr[] = $i;
  }

  return $arr;
}

/**
 * Return the user-defined store address.
 */
function uc_store_address() {
  $store_address = uc_address_format(NULL, NULL, variable_get('uc_store_name', NULL),
    variable_get('uc_store_street1', NULL), variable_get('uc_store_street2', NULL),
    variable_get('uc_store_city', NULL), variable_get('uc_store_zone', NULL),
    variable_get('uc_store_postal_code', NULL), uc_store_default_country());

  return $store_address;
}

/**
 * Return the e-mail from string in the format of Store Name <store@email.com>.
 */
function uc_store_email_from() {
  $email_from = variable_get('uc_store_email', '');
  if (empty($email_from)) {
    $email_from = variable_get('site_mail', ini_get('sendmail_from'));
  }

  // Add the store name to the e-mail from line.  Must be optional to prevent
  // server conflicts.
  if (variable_get('uc_store_email_include_name', TRUE)) {
    $store = variable_get('uc_store_name', '');
    if (!empty($store)) {
      $email_from = '"'. $store .'" <'. $email_from .'>';
    }
  }

  return $email_from;
}

/**
 * Derive a valid username from an e-mail address.
 *
 * @param $email
 *   An e-mail address.
 * @return
 *   A username derived from the e-mail address, using the part of the address
 *     up to the @ with integers appended to the end if needed to avoid a
 *     duplicate username.
 */
function uc_store_email_to_username($email) {
  // Default to the first part of the e-mail address.
  $name = substr($email, 0, strpos($email, '@'));

  // Remove possible illegal characters.
  $name = preg_replace('/[^A-Za-z0-9_.-]/', '', $name);

  // Trim that value for spaces and length.
  $name = trim(substr($name, 0, USERNAME_MAX_LENGTH - 4));

  // Make sure we don't hand out a duplicate username.
  while (db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE LOWER(name) = LOWER('%s')", $name)) > 0) {
    // If the username got too long, trim it back down.
    if (strlen($name) == USERNAME_MAX_LENGTH) {
      $name = substr($name, 0, USERNAME_MAX_LENGTH - 4);
    }

    // Append a random integer to the name.
    $name .= rand(0, 9);
  }

  return $name;
}

/**
 * Handle credit-card encryption.
 *
 * Trimmed down version of GPL class by Tony Marston.  Details available at
 * http://www.tonymarston.co.uk/php-mysql/encryption.html
 *
 * Usage:
 * 1) Create an encryption object.
 *    ex: $crypt = new uc_encryption_class;
 * 2) To encrypt string data, use the encrypt method with the key.
 *    ex: $encrypted = $crypt->encrypt($key, $string);
 * 3) To decrypt string data, use the decrypt method with the original key.
 *    ex: $decrypted = $crypt->decrypt($key, $string);
 * 4) To check for errors, use the errors method to return an array of errors.
 *    ex: $errors = $crypt->errors();
 */
class uc_encryption_class {
  var $scramble1;
  var $scramble2;
  var $errors;
  var $adj;
  var $mod;

  function uc_encryption_class() {
    $this->errors = array();
    $this->scramble1 = '! #$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`"abcdefghijklmnopqrstuvwxyz{|}~';
    $this->scramble2 = 'f^jAE]okIOzU[2&q1{3`h5w_794p@6s8?BgP>dFV=m" D<TcS%Ze|r:lGK/uCy.Jx)HiQ!#$~(;Lt-R}Ma,NvW+Ynb*0X';
    $this->adj = 1.75;
    $this->mod = 3;
  }

  function decrypt($key, $source) {
    $this->errors = array();

    $fudgefactor = $this->_convertKey($key);
    if ($this->errors) {
      return;
    }

    if (empty($source)) {
      // Commented out to prevent errors getting logged for use cases that may
      // have variable encryption/decryption requirements. -RS
      // $this->errors[] = t('No value has been supplied for decryption');
      return;
    }

    $target = NULL;
    $factor2 = 0;

    for ($i = 0; $i < strlen($source); $i++) {
      $char2 = substr($source, $i, 1);

      $num2 = strpos($this->scramble2, $char2);
      if ($num2 === FALSE) {
        $this->errors[] = t('Source string contains an invalid character (!char)', array('!char' => $char2));
        return;
      }

      $adj = $this->_applyFudgeFactor($fudgefactor);
      $factor1 = $factor2 + $adj;
      $num1 = $num2 - round($factor1);
      $num1 = $this->_checkRange($num1);
      $factor2 = $factor1 + $num2;
      $char1 = substr($this->scramble1, $num1, 1);
      $target .= $char1;
    }

    return rtrim($target);
  }

  function encrypt($key, $source, $sourcelen = 0) {
    $this->errors = array();
    $fudgefactor = $this->_convertKey($key);

    if ($this->errors) {
      return;
    }

    if (empty($source)) {
      // Commented out to prevent errors getting logged for use cases that may
      // have variable encryption/decryption requirements. -RS
      // $this->errors[] = t('No value has been supplied for encryption');
      return;
    }

    while (strlen($source) < $sourcelen) {
      $source .= ' ';
    }

    $target = NULL;
    $factor2 = 0;

    for ($i = 0; $i < strlen($source); $i++) {
      $char1 = substr($source, $i, 1);

      $num1 = strpos($this->scramble1, $char1);
      if ($num1 === FALSE) {
        $this->errors[] = t('Source string contains an invalid character (!char)', array('!char' => $char1));
        return;
      }

      $adj = $this->_applyFudgeFactor($fudgefactor);
      $factor1 = $factor2 + $adj;
      $num2 = round($factor1) + $num1;
      $num2 = $this->_checkRange($num2);
      $factor2 = $factor1 + $num2;
      $char2 = substr($this->scramble2, $num2, 1);
      $target .= $char2;
    }

    return $target;
  }

  function getAdjustment() {
    return $this->adj;
  }

  function getModulus() {
    return $this->mod;
  }

  function setAdjustment($adj) {
    $this->adj = (float) $adj;
  }

  function setModulus($mod) {
    $this->mod = (int)abs($mod);
  }

  function _applyFudgeFactor(&$fudgefactor) {
    static $alerted = FALSE;

    if (!is_array($fudgefactor)) {
      if (!$alerted) {
        // Throw an error that makes sense so this stops getting reported.
        $this->errors[] = t('No encryption key was found.');
        drupal_set_message(t('Ubercart cannot find a necessary encryption key.  Refer to the store admin <a href="!url">dashboard</a> to isolate which one.', array('!url' => url('admin/store'))), 'error');

        $alerted = TRUE;
      }
    }
    else {
      $fudge = array_shift($fudgefactor);
    }

    $fudge = $fudge + $this->adj;
    $fudgefactor[] = $fudge;

    if (!empty($this->mod)) {
      if ($fudge % $this->mod == 0) {
        $fudge = $fudge * -1;
      }
    }

    return $fudge;
  }

  function _checkRange($num) {
    $num = round($num);
    $limit = strlen($this->scramble1);

    while ($num >= $limit) {
      $num = $num - $limit;
    }
    while ($num < 0) {
      $num = $num + $limit;
    }

    return $num;
  }

  function _convertKey($key) {
    if (empty($key)) {
      // Commented out to prevent errors getting logged for use cases that may
      // have variable encryption/decryption requirements. -RS
      // $this->errors[] = 'No value has been supplied for the encryption key';
      return;
    }

    $array[] = strlen($key);

    $tot = 0;
    for ($i = 0; $i < strlen($key); $i++) {
      $char = substr($key, $i, 1);

      $num = strpos($this->scramble1, $char);
      if ($num === FALSE) {
        $this->errors[] = "Key contains an invalid character ($char)";
        return;
      }

      $array[] = $num;
      $tot = $tot + $num;
    }

    $array[] = $tot;

    return $array;
  }
}

/**
 * Log encryption errors to watchdog.
 *
 * @param $crypt
 *   The object used to perform your encryption/decryption.
 * @param $module
 *   The module name to specify in the watchdog notices.
 */
function uc_store_encryption_errors(&$crypt, $module) {
  if (!empty($crypt->errors)) {
    foreach ($crypt->errors as $message) {
      $items[] = $message;
    }
    watchdog('encryption', 'Encryption failed. !messages', array('!messages' => theme('item_list', $items)), WATCHDOG_ERROR);
  }
}

/**
 * Return a default store country value.
 */
function uc_store_default_country() {
  static $default;

  if (!empty($default)) {
    return $default;
  }

  $default = variable_get('uc_store_country', 840);

  $result = db_result(db_query("SELECT COUNT(*) FROM {uc_countries} WHERE country_id = %d AND version > 0", $default));
  if ($result == 0) {
    $default = db_result(db_query_range("SELECT country_id FROM {uc_countries} WHERE version > 0 ORDER BY country_name", 0, 1));
  }

  return $default;
}

/**
 * Wrapper for drupal_add_js() to cache .js files based on their timestamp.
 *
 * Deprecated! Use drupal_add_js() instead.
 */
function uc_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE) {
  watchdog('uc_store', 'uc_add_js() has been deprecated. Use drupal_add_js() in your code.', array(), WATCHDOG_ERROR);
  drupal_add_js($data, $type, $scope, $defer, $cache);
}

/**
 * Check referers to see if they are in the allowed list.
 */
function uc_referer_check($urls) {
  $http_referer = uc_referer_uri();

  // Always return true if we have no referer; covers the case of page refreshes
  // and switching from HTTP to HTTPS. This bypasses the two-time check below...
  // is it safe?
  if (empty($http_referer)) {
    return TRUE;
  }

  // Check the user didn't shamelessly two-time us with another site.
  $referer = parse_url($http_referer);
  if ($referer['host'] != $_SERVER['SERVER_NAME']) {
    return FALSE;
  }

  // The check itself.
  foreach ((array) $urls as $url) {
    if (substr($http_referer, -strlen($url)) == $url) {
      return TRUE;
    }
  }

  return FALSE;
}

/**
 * Provide a more reliable referrer for Ubercart.
 */
function uc_referer_uri() {
  if (referer_uri() == '') {
    return $_SESSION['uc_referer_uri'];
  }
  else {
    return referer_uri();
  }
}

/**
 * Returns the current version of Ubercart.
 */
function uc_version() {
  return '6.x-2.1';
}

/**
 * Get image widgets defined by various modules.
 */
function uc_store_get_image_widgets() {
  return module_invoke_all('uc_image_widget');
}

/**
 * Implementation of hook_uc_image_widget().
 *
 * Built-in support for Colorbox, Thickbox and Lightbox2.
 */
function uc_store_uc_image_widget() {
  $widgets = array();

  if (module_exists('colorbox')) {
    $widgets['colorbox'] = array(
      'name' => t('Colorbox'),
      'callback' => 'uc_store_image_widget_colorbox',
    );
  }
  if (module_exists('thickbox')) {
    $widgets['thickbox'] = array(
      'name' => t('Thickbox'),
      'callback' => 'uc_store_image_widget_thickbox',
    );
  }
  if (module_exists('lightbox2')) {
    $widgets['lightbox2'] = array(
      'name' => t('Lightbox2'),
      'callback' => 'uc_store_image_widget_lightbox2',
    );
  }

  return $widgets;
}

/**
 * This function generates the Colorbox-specific HTML attributes.
 */
function uc_store_image_widget_colorbox($rel_count) {
  if (!is_null($rel_count)) {
    $img_index = 'uc_image_'. $rel_count;
  }
  else {
    $img_index = 'uc_image';
  }
  return ' class="colorbox" rel="'. $img_index .'"';
}

/**
 * This function generates the Thickbox-specific HTML attributes.
 */
function uc_store_image_widget_thickbox($rel_count) {
  if (!is_null($rel_count)) {
    $img_index = 'uc_image_'. $rel_count;
  }
  else {
    $img_index = 'uc_image';
  }
  return ' class="thickbox" rel="'. $img_index .'"';
}

/**
 * This function generates the Lightbox2-specific HTML attributes.
 */
function uc_store_image_widget_lightbox2($rel_count) {
  if (!is_null($rel_count)) {
    $img_index = 'lightbox['. $rel_count .']';
  }
  else {
    $img_index = 'lightbox';
  }
  return ' rel="'. $img_index .'"';
}

/**
 * Get the preferred language for a user's email address.
 *
 * @param $address
 *   The email address to check.
 * @return
 *   The language object to be used in translation, localization, etc. If a
 *   user account can not be found for $address, language_default() is
 *   returned.
 *
 * @see
 *   user_preferred_language()
 *   language_default()
 */
function uc_store_mail_recipient_language($address) {
  // See if any user exists for this address.
  $account = user_load(array('mail' => trim($address)));
  if ($account) {
    $lang_object = user_preferred_language($account);
  }
  // If not, site-wide default.
  else {
    $lang_object = language_default();
  }
  return $lang_object;
}

/**
 * Display prices in forms with a minimum number of decimal places.
 *
 * @param $price
 *   The price to display as the #default_value in a form field.
 */
function uc_store_format_price_field_value($price) {
  $exact = rtrim(number_format($price, 6, '.', ''), '0');
  $round = number_format($price, variable_get('uc_currency_prec', 2), '.', '');

  if ($exact == rtrim($round, '0')) {
    return $round;
  }
  else {
    return $exact;
  }
}

/**
 * Executes hook_uc_form_alter() implementations.
 *
 * API function to invoke hook_uc_form_alter() implementations allowing those
 * modules to alter the form before the Drupal layer hook_form_alter() is
 * invoked.
 *
 * @see hook_uc_form_alter().
 */
function uc_form_alter(&$form, &$form_state, $form_id) {
  $form['__drupal_alter_by_ref'] = array(&$form_state);
  drupal_alter('uc_form', $form, $form_id);
}
