<?php
// $Id: uc_addresses.module,v 1.34 2010/03/17 16:05:28 freixas Exp $

/**
 * @file
 * Adds user profile address support to Ubercart.
 *
 * The uc_addresses module adds support for one or more addresses in
 * the user's profile. When users register, they must provide an
 * address. Users can then add more addresses and edit or delete
 * existing addresses. One address must be designated as the default
 * address and cannot be deleted (but it can be edited).
 *
 * The Ubercart order process is altered so that users select delivery
 * and billing addresses from their collection of addresses rather
 * than from previous orders. Any new addresses entered during the
 * order process are automatically added to the user's list.
 * <!--break-->
 *
 * @author Ben Thompson with inspiration from uc_order.module and uc_cart.module.
 * @author Rich from Freestyle Systems (enhancements).
 * @author Tony Freixas (maintainer) from Tiger Heron LLC (major revisions).
 *
 * @ingroup uc_addresses
 */

/* TODO Implement the hook_theme registry. Combine all theme registry entries
   into one hook_theme function in each corresponding module file.
*/

module_load_include('inc', 'uc_addresses', 'uc_addresses_address_pane');

/**
 * Give users the ability to view anyone's default address.
 */
define('UC_ADDRESSES_ACCESS_VIEW_DEFAULT', 'view default addresses');

/**
 * Give users the ability to view anyone's addresses.
 * Anyone with this permission also has UC_ADDRESSES_ACCESS_VIEW_DEFAULT.
 */
define('UC_ADDRESSES_ACCESS_VIEW_ALL', 'view all addresses');

/**
 * Give users the ability to add or edit anyone's addresses.
 * Anyone with this permission also has
 * UC_ADDRESSES_ACCESS_VIEW_DEFAULT and UC_ADDRESSES_ACCESS_VIEW_ALL.
 */
define('UC_ADDRESSES_ACCESS_ADD_EDIT', 'add/edit addresses');

/*******************************************************************************
 * Hook Functions
 ******************************************************************************/

/**
 * Implementation of hook_init()
 */
function uc_addresses_init() {
  drupal_add_css(drupal_get_path('module', 'uc_order') .'/uc_order.css');
}

/**
 * Given a wildcard of %uc_addresses_address in path, replace it with
 * the corresponding address object.
 *
 * @param $aid The value matched by %uc_addresses_address.
 * @param $uid The value of the user ID in the same path.
 * @return FALSE if the value is a valid address, else the address object.
 */
function uc_addresses_address_load($aid, $uid) {
  if (!isset($aid)) return FALSE;
  return _uc_addresses_db_get_address($uid, $aid);
}

/**
 * Implementation of hook_menu().
 *
 * @return A array of menu items.
 */
function uc_addresses_menu() {
  global $user;
  $items = array();

  $items['admin/store/settings/addresses'] = array(
    'title' => 'Address settings',
    'description' => 'Configure the address settings.',
    'page callback' => 'uc_addresses_settings_overview',
    'access arguments' => array('administer store'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'uc_addresses.admin.inc',
    );
  $items['admin/store/settings/addresses/overview'] = array(
    'title' => 'Overview',
    'access arguments' => array('administer store'),
    'description' => 'View the address settings.',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
    );
  $items['admin/store/settings/addresses/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_addresses_settings_form'),
    'access arguments' => array('administer store'),
    'description' => 'Edit the address settings.',
    'type' => MENU_LOCAL_TASK,
    'weight' => -5,
    'file' => 'uc_addresses.admin.inc',
    );

  $items['user/%user_uid_optional/addresses'] = array(
    'title' => 'Addresses',
    'description' => 'Manage your addresses',
    'page callback' => 'uc_addresses_list_addresses',
    'page arguments' => array(1, NULL),
    'access callback' => 'uc_addresses_can_view_addresses',
    'access arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
    );

  $items['user/%user_uid_optional/addresses/list'] = array(
    'title' => 'Address list',
    'description' => 'Manage your addresses',
    'access callback' => 'uc_addresses_can_view_addresses',
    'access arguments' => array(1),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0,
    );
  $items['user/%user_uid_optional/addresses/add'] = array(
    'title' => 'Add address',
    'description' => 'Add a new address.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_addresses_get_address_form', 1, NULL, 'add'),
    'access callback' => 'uc_addresses_can_add_edit_address',
    'access arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
    'weight' => 3,
    );

  $items['user/%user_uid_optional/addresses/%uc_addresses_address'] = array(
    'title' => 'View Address',
    'description' => 'View one saved address',
    'load arguments' => array(1),
    'page callback' => 'uc_addresses_list_addresses',
    'page arguments' => array(1, arg(3)),
    'access callback' => 'uc_addresses_can_view_addresses',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK,
    );
  $items['user/%user_uid_optional/addresses/%uc_addresses_address/edit'] = array(
    'title' => 'Edit address',
    'load arguments' => array(1),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('uc_addresses_get_address_form', 1, 3, 'edit'),
    'access callback' => 'uc_addresses_can_add_edit_address',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK,
    );
  $items['user/%user_uid_optional/addresses/%uc_addresses_address/delete'] = array(
    'title' => 'Delete address',
    'load arguments' => array(1),
    'page callback' => 'uc_addresses_delete_address_confirm',
    'page arguments' => array(1, 3),
    'access callback' => 'uc_addresses_can_add_edit_address',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK,
    );

  return $items;
}

/**
 * Implementation of hook_theme.
 */
function uc_addresses_theme() {
  return array(
    'uc_addresses_list_address' => array(
      'arguments' => array('address' => NULL, 'panes' => array()),
      ),
    'uc_addresses_get_address_form' => array(
      'arguments' => array('form' => NULL),
      ),
    'uc_addresses_address_delete_confirm' => array(
      'arguments' => array('help' => '', 'panes' => array(), 'form' => NULL),
      ),
    'uc_addresses_pane' => array(
      'arguments' => array('form' => NULL),
      'file' => 'uc_addresses_address_pane.inc',
      ),
  );
}

/**
 * Implementation of hook_perm().
 *
 * @return array An array of permission names.
 */
function uc_addresses_perm() {
  return array(UC_ADDRESSES_ACCESS_VIEW_DEFAULT,
	       UC_ADDRESSES_ACCESS_VIEW_ALL,
	       UC_ADDRESSES_ACCESS_ADD_EDIT);
}

function uc_addresses_can_view_addresses($address_user) {
  global $user;

  $access =
    user_access(UC_ADDRESSES_ACCESS_VIEW_DEFAULT) ||
    user_access(UC_ADDRESSES_ACCESS_VIEW_ALL) ||
    user_access(UC_ADDRESSES_ACCESS_ADD_EDIT) ||
    $user->uid == $address_user->uid;

  return $access;
}

function uc_addresses_can_add_edit_address($address_user) {
  global $user;

  $access =
    user_access(UC_ADDRESSES_ACCESS_ADD_EDIT) ||
    $user->uid == $address_user->uid;

  return $access;
}

/**
 * Implementation of hook_user().
 *
 * @param $op An integer representing the action being performed.
 * @param $edit An array of form values submitted by the user.
 * @param $account The user on which the operation is being performed.
 * @param $category The active category of user information being edited.
 */
function uc_addresses_user($op, &$edit, &$account, $category = NULL) {
  global $user;
  switch ($op) {
    case 'register':
      // For registration, we may want the user to enter his default
      // address

      if (variable_get('uc_addresses_require_address', TRUE)) {
	$form = uc_addresses_pane_address('new', NULL, $edit);
	$form = array($form['contents']); 	// Modify to what we need
	$form[0]['#title'] = t('Address'); 	// Rename the fieldset
	return $form;
      }
      return;

    case 'insert':
      // We're about to add the user to the database, so get the address
      // info and add it to the address table

      $address = (object)$edit;
      $address->is_default = 0;
      _uc_addresses_db_add_address($address);
      return;

    case 'delete':
      // We're deleting the user, so delete all his/her addresses as
      // well

      db_query("DELETE FROM {uc_addresses} WHERE uid = %d", $account->uid);
      db_query("DELETE FROM {uc_addresses_defaults} WHERE uid = %d", $account->uid);
      return;
  }
}

/**
 * Implementation of hook_form_alter().
 *
 * Here we're going to override the saved address options on the
 * checkout form.
 *
 * @param $form The form array.
 * @param $form_state A keyed array containing the current state of
 *	the form.
 */
function uc_addresses_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
  global $user;

  // This is a good place to clear any addresses we might have
  // leftover from a previous checkout

  if (isset($_SESSION['uc_addresses_saved_addresses'])) {
    $_SESSION['uc_addresses_saved_addresses'] = null;
    unset($_SESSION['uc_addresses_saved_addresses']);
  }

  // Create the list of addresses the user can select from
  // Each address is just line a full address, but on one line

  $options = NULL;
  if ($addresses = _uc_addresses_db_get_address($user->uid)) {
    $options = array('0' => t('Select one...'));
    foreach ($addresses as $address) {
      if ($address->is_default) {
        $default_address = $address;
      }
      $address = (array)$address;

      if ($address['is_default']) {
        $default = drupal_to_js($address);
      }
      if ($address['address_name']) {
        $options[drupal_to_js($address)] = $address['address_name'];
      }

      else {
        // Not happy about this--if uc_address_format() ever changes
        // it's output, we're hosed

        $options[drupal_to_js($address)] =
        preg_replace('/<.*?>/', ', ',
          uc_address_format(
            $address['first_name'],
            $address['last_name'],
            $address['company'],
            $address['street1'],
            $address['street2'],
            $address['city'],
            $address['zone'],
            $address['postal_code'],
            $address['country']
          )
        );
      }
    }
  }

  $address_book_icon =
    l(uc_store_get_icon('file:address_book', FALSE, 'address-book-icon'),
      'user/'. $user->uid . '/addresses', array('html' => TRUE));

  // If we have some addresses saved (almost always true), revise
  // the delivery/billing address selection

  if ($options) {
    //drupal_add_js(drupal_get_path('module', 'uc_addresses') .'/uc_addresses.js');

    // Might not have any shippable products so make sure the
    // delivery address exists before mucking with it

    $default_country = uc_store_default_country();
    $js_zone = FALSE;
    if ($form['panes']['delivery'] &&
    ((uc_cart_is_shippable() || !variable_get('uc_cart_delivery_not_shippable', TRUE)))) {
      if (variable_get('uc_pane_delivery_enabled', TRUE)) {
        $form['panes']['delivery']['#description'] =
        (variable_get('uc_addresses_default_delivery_address', TRUE) ?
        t('Edit the address below or select an address from the list. ') :
        t('Enter an address below or select an address from the list. ')) .
        t('Click !here to manage your saved addresses.',
        array('!here' => l(t('here'), 'user/'. $user->uid . '/addresses')));

        $form['panes']['delivery']['delivery_address_select'] = array(
          '#type' => 'select',
          '#title' => t('Saved addresses'),
          '#options' => $options,
          '#attributes' => array('onchange' => 'apply_address(\'delivery\', this.value);'),
          '#suffix' => $address_book_icon,
          '#weight' => -10,
        );

        // Copy the default address to the delivery address fields if
        // the option is enabled and if the fields are empty
        if (variable_get('uc_addresses_default_delivery_address', TRUE) && _uc_addresses_address_fields_empty($form['panes']['delivery'], 'delivery')) {
          $form['panes']['delivery']['delivery_address_select']['#default_value'] = $default;
          if (uc_address_field_enabled('first_name')) {
            $form['panes']['delivery']['delivery_first_name']['#default_value'] = $default_address->first_name;
          }
          if (uc_address_field_enabled('last_name')) {
            $form['panes']['delivery']['delivery_last_name']['#default_value'] = $default_address->last_name;
          }
          if (uc_address_field_enabled('phone')) {
            $form['panes']['delivery']['delivery_phone']['#default_value'] = $default_address->phone;
          }
          if (uc_address_field_enabled('company')) {
            $form['panes']['delivery']['delivery_company']['#default_value'] = $default_address->company;
          }
          if (uc_address_field_enabled('street1')) {
            $form['panes']['delivery']['delivery_street1']['#default_value'] = $default_address->street1;
          }
          if (uc_address_field_enabled('street2')) {
            $form['panes']['delivery']['delivery_street2']['#default_value'] = $default_address->street2;
          }
          if (uc_address_field_enabled('city')) {
            $form['panes']['delivery']['delivery_city']['#default_value'] = $default_address->city;
          }
          if (uc_address_field_enabled('country')) {
            $form['panes']['delivery']['delivery_country']['#default_value'] = $default_address->country;
          }
          if (uc_address_field_enabled('zone')) {
            if ($default_country == $default_address->country) {
              $form['panes']['delivery']['delivery_zone']['#default_value'] = $default_address->zone;
            }
            else {
              $js_zone = TRUE;
            }
          }
          if (uc_address_field_enabled('postal_code')) {
            $form['panes']['delivery']['delivery_postal_code']['#default_value'] = $default_address->postal_code;
          }
        }
      }
    }

    if (variable_get('uc_pane_billing_enabled', TRUE)) {
      $form['panes']['billing']['#description'] =
      (variable_get('uc_addresses_default_billing_address', TRUE) ?
      t('Edit the address below or select an address from the list. ') :
      t('Enter an address below or select an address from the list. ')) .
      t('Click !here to manage your saved addresses.',
      array('!here' => l(t('here'), 'user/'. $user->uid . '/addresses')));

      $form['panes']['billing']['billing_address_select'] = array(
        '#type' => 'select',
        '#title' => t('Saved addresses'),
        '#options' => $options,
        '#attributes' => array('onchange' => 'apply_address(\'billing\', this.value);'),
        '#suffix' => $address_book_icon,
        '#weight' => -10,
      );

      // Copy the default address to the billing address fields if the
      // option is enabled and if the fields are empty
      if (variable_get('uc_addresses_default_billing_address', TRUE) && _uc_addresses_address_fields_empty($form['panes']['billing'], 'billing')) {
        $form['panes']['billing']['billing_address_select']['#default_value'] = $default;
        if (uc_address_field_enabled('first_name')) {
          $form['panes']['billing']['billing_first_name']['#default_value'] = $default_address->first_name;
        }
        if (uc_address_field_enabled('last_name')) {
          $form['panes']['billing']['billing_last_name']['#default_value'] = $default_address->last_name;
        }
        if (uc_address_field_enabled('phone')) {
          $form['panes']['billing']['billing_phone']['#default_value'] = $default_address->phone;
        }
        if (uc_address_field_enabled('company')) {
          $form['panes']['billing']['billing_company']['#default_value'] = $default_address->company;
        }
        if (uc_address_field_enabled('street1')) {
          $form['panes']['billing']['billing_street1']['#default_value'] = $default_address->street1;
        }
        if (uc_address_field_enabled('street2')) {
          $form['panes']['billing']['billing_street2']['#default_value'] = $default_address->street2;
        }
        if (uc_address_field_enabled('city')) {
          $form['panes']['billing']['billing_city']['#default_value'] = $default_address->city;
        }
        if (uc_address_field_enabled('country')) {
          $form['panes']['billing']['billing_country']['#default_value'] = $default_address->country;
        }
        if (uc_address_field_enabled('zone')) {
          if ($default_country == $default_address->country) {
            $form['panes']['billing']['billing_zone']['#default_value'] = $default_address->zone;
          }
          else {
            $js_zone = TRUE;
          }
        }
        if (uc_address_field_enabled('postal_code')) {
          $form['panes']['billing']['billing_postal_code']['#default_value'] = $default_address->postal_code;
        }
      }
    }
    if ($js_zone) {
      drupal_add_js(drupal_get_path('module', 'uc_addresses') .'/uc_addresses.js');
      drupal_add_js(array('uc_address_default' => array('country' => $default_address->country, 'zone' => $default_address->zone)), 'setting');
    }
  }

  // If we have no addresses, remove the selection field. This
  // occurs only when this module is added to a system that already
  // has users

  else {
    unset($form['panes']['delivery']['delivery_address_select']);
    unset($form['panes']['billing']['billing_address_select']);
  }

  // Add the "Save address" checkbox
  // TODO: (Tony) I can add these, but don't know how to find out if
  // the checkbox was checked. For now, all order addresses are
  // automatically saved.

//     $form['panes']['billing']['billing_address_save_address'] = array(
//       '#title' => t('Save this address'),
//       '#type' => 'checkbox',
//       '#default_value' => 1,
//       '#weight' => 10
//       );
//     $form['panes']['delivery']['delivery_address_save_address'] = array(
//       '#title' => t('Save this address'),
//       '#type' => 'checkbox',
//       '#default_value' => 1,
//       '#weight' => 10
//       );

}

function _uc_addresses_address_fields_empty($fields, $type)
{
  if (uc_address_field_enabled('first_name') &&
      $fields[$type . '_first_name']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('last_name') &&
	   $fields[$type . '_last_name']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('phone') &&
	 $fields[$type . '_phone']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('company') &&
	 $fields[$type . '_company']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('street1') &&
	   $fields[$type . '_street1']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('street2') &&
	   $fields[$type . '_street2']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('city') &&
	   $fields[$type . '_city']['#default_value']) return FALSE;
  else if (uc_address_field_enabled('postal_code') &&
	   $fields[$type . '_postal_code']['#default_value']) return FALSE;
  return TRUE;
}

/*******************************************************************************
 * Hook Functions (Ubercart)
 ******************************************************************************/

/**
 * Implementation of hook_address_pane().
 *
 * @return array
 */
function uc_addresses_address_pane() {
  $panes[] = array(
    'id' => 'address',
    'callback' => 'uc_addresses_pane_address',
    'title' => t('Address'),
    'desc' => t("Manage the user's addresses and contact information."),
    'class' => 'pos-left',
    'weight' => 1,
    'show' => array('view', 'add', 'edit'),
    );

  return $panes;
}

/**
 * Use hook_uc_checkout_complete to catch any new addresses.
 *
 * @param $order The order.
 * @param $user The user.
 */
function uc_addresses_uc_checkout_complete($order, $user) {

  $address = new stdClass();

  // Add the billing address first. If the user has no addresses,
  // this is the one that will become the default address

  if (variable_get('uc_pane_billing_enabled', TRUE)) {
    $address->address_name = '';
    $address->uid = $order->uid;
    $address->first_name = $order->billing_first_name;
    $address->last_name = $order->billing_last_name;
    $address->company = $order->billing_company;
    $address->street1 = $order->billing_street1;
    $address->street2 = $order->billing_street2;
    $address->city = $order->billing_city;
    $address->zone = $order->billing_zone;
    $address->postal_code = $order->billing_postal_code;
    $address->country = $order->billing_country;
    $address->phone = $order->billing_phone;
    $address->is_default = 0;
    _uc_addresses_db_add_address($address, TRUE);
  }

  if (uc_order_is_shippable($order)) {
    $address->address_name = '';
    $address->uid = $order->uid;
    $address->first_name = $order->delivery_first_name;
    $address->last_name = $order->delivery_last_name;
    $address->company = $order->delivery_company;
    $address->street1 = $order->delivery_street1;
    $address->street2 = $order->delivery_street2;
    $address->city = $order->delivery_city;
    $address->zone = $order->delivery_zone;
    $address->postal_code = $order->delivery_postal_code;
    $address->country = $order->delivery_country;
    $address->phone = $order->delivery_phone;
    $address->is_default = 0;
    _uc_addresses_db_add_address($address, TRUE);
  }
}

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

/**
 * Generate a list of one or all addresses defined by one user and
 * then theme the list for display.
 *
 * If the current user can edit the addresses, then provide an edit
 * link for each address.
 *
 * @param $address_user The user whose address list we want to display.
 * @param $address The address to display or null to display all.
 * @return The themed list (as a string).
 */
function uc_addresses_list_addresses($address_user, $address = NULL) {
  global $user;

  $uid = $address_user->uid;

  // Save this for later use

  $saved_address = $address;

  // Determine if the user can view all addresses or just the default address

  $can_view_all_addresses =
    $user->uid == $uid ||
    user_access(UC_ADDRESSES_ACCESS_VIEW_ALL) ||
    user_access(UC_ADDRESSES_ACCESS_ADD_EDIT);

  // Flag to determine error message

  $allowed_to_view_address = true;

  // If they can view all addresses, fetch the addresses

  if ($can_view_all_addresses) {
    if ($address) {
      $addresses = $address;
    }
    else {
      $addresses = _uc_addresses_db_get_address($uid, NULL);
    }
  }

  // Otherwise, they can view only the default address

  else {
    if ($address) {
      if ($address->is_default === false) {
	$addresses = FALSE;
	$allowed_to_view_address = FALSE;
      }
    }
    else {
      $aid = _uc_addresses_get_default_address_id($uid);
      if ($aid === NULL) $aid = 0; // NULL would mean read all addresses

      $addresses = _uc_addresses_db_get_address($uid, $aid);
      if (is_object($addresses) && $addresses->is_default === false) {
	$addresses = FALSE;
	$allowed_to_view_address = FALSE;
      }
    }
  }

  $output = '';

  // We have multiple addresses

  if (is_array($addresses)) {
    foreach ($addresses as $address) {
      $output .= _uc_addresses_list_one_address($address);
    }
  }

  // We have one address

  elseif (is_object($addresses)) {
    $output .= _uc_addresses_list_one_address($addresses);
  }

  // We don't have any addresses. The message we report depends on the
  // user and what they asked for

  else {

    // If they asked for all addresses and got nothing, it's because
    // there is nothing

    if ($saved_address === NULL) {
      $output .= t('No addresses have been saved.<br />');
    }

    // If they asked for a specific address, it could be because it
    // doesn't exist or because they are only permitted to view the
    // default address and the one they picked is not the default

    elseif ($allowed_to_view_address) {
      $output .= t('This address does not exist.<br />');
    }

    else {
      $output .= t('You are not allowed to view this address.<br />');
    }
  }

  // Decide whether to include a link for adding a new address based
  // on whether the current user can edit the addresses

  if (user_access(UC_ADDRESSES_ACCESS_ADD_EDIT) || $user->uid == $uid) {
    $link = l(t('Add a new address'), 'user/'. $uid .'/addresses/add');
    $output .= $link;
  }

  return $output;
}

/**
 * List one address.
 *
 * @param $address The address object to list.
 * @return The HTML string for the address.
 */
function _uc_addresses_list_one_address($address) {

  $panes = _address_pane_list();
  foreach ($panes as $pane) {
    if (variable_get('uc_addresses_pane_'. $pane['id'] .'_enabled', TRUE)) {
      $func = $pane['callback'];
      if (function_exists($func)) {
	$return = $func('view', $address, NULL);
	if (!is_NULL($return)) {
	  $data[$pane['title']] = $return;
	}
      }
    }
  }
  $output = '<div class="list_address">';
  $output .= theme('uc_addresses_list_address', $address, $data);
  $output .= '</div>';
  return $output;
}

/**
 * Theme the address list view.
 *
 * @param $address The address object we are theming.
 * @param $panes An associative array for each address pane.
 *	The key is the pane's title and the value is either the data
 *	returned for that pane or an array of returned data.
 * @return The themed address.
 * @ingroup themeable
 */
function theme_uc_addresses_list_address($address, $panes) {
  global $user;

  $uid = $address->uid;
  $aid = $address->aid;

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

  $output = '';
  if ($address->is_default) {
    $output = '<table class="address-preview-table addresses-default-address">';
  }
  else {
    $output = '<table class="address-preview-table">';
  }

  foreach ($panes as $title => $data) {

    // We add an edit link only if the user is allowed to edit this address

    if (user_access(UC_ADDRESSES_ACCESS_ADD_EDIT) || $user->uid == $uid) {
      $output .= '<tr class="pane-title-row"><td colspan="2">'
	. l(t('Edit this address'), 'user/'. $uid .'/addresses/' . $aid . '/edit')
	. ($address->is_default ? '' :
	   ' | '
	   . l(t('Delete this address'), 'user/'. $uid .'/addresses/' . $aid . '/delete'))
	.'</td></tr>';
    }

    if ($address->is_default) {
      $output .= '<tr><td colspan="2" class="addresses-default-address-label"> '
	. t('Default address') . '</td></tr>';
    }

    if ($address->address_name) {
      $output .=
	'<tr class="pane-data-row"><td class="title-col" '
	.'>'.t('Name') .':</td><td class="data-col">'
	. $address->address_name .'</td></tr>';
    }

    if (is_array($data)) {
      foreach ($data as $row) {
        if (is_array($row)) {
          if (isset($row['border'])) {
            $border = ' class="row-border-'. $row['border'] .'"';
          }
          else {
            $border = '';
          }
          $output .= '<tr class="pane-data-row"'. $border .'><td class="title-col" '
	    .'>'. $row['title'] .':</td><td class="data-col">'
	    . $row['data'] .'</td></tr>';
        }
        else {
          $output .= '<tr class="pane-data-row"><td colspan="2">'. $row .'</td></tr>';
        }
      }
    }
    else {
      $output .= '<tr class="pane-data-row"><td colspan="2">'. $data .'</td></tr>';
    }
  }
  $output .= '</table><br />';

  return $output;
}

/**
 * Create a form used to add a new address or edit an existing address.
 *
 * @param $form_state The form state.
 * @param $address_user The user who "owns" this address.
 * @param $address The address to edit (NULL for new addresses)
 * @param $view The URL path for which form to display. 'add' or 'edit'.
 * @return An address form
 * @ingroup forms
 */
function uc_addresses_get_address_form(&$form_state, $address_user, $address, $view) {

  $uid = $address_user->uid;
  $aid = $address ? $address->aid : 0;

  $form['stored_values'] = array(
    '#type' => 'value',
    '#value' => array(
      'user' => $address_user,
      'address' => $address,
      'view' => $view),
    );

  // Get the panes to display

  $form['panes'] = array('#tree' => TRUE);
  $panes = _address_pane_list($view);
  foreach ($panes as $pane) {
    if (in_array($view, $pane['show']) &&
        variable_get('uc_addresses_pane_'. $pane['id'] .'_show_'. $view, TRUE)) {
      $return = $pane['callback']($view, $address, NULL);

      // Add the pane if any display data is returned from the
      // callback

      if (is_array($return) && (!empty($return['description']) || !empty($return['contents']))) {

        // Create the fieldset for the pane

        $form['panes'][$pane['id']] = array(
          '#type' => 'fieldset',
          '#title' => $pane['title'],
          '#description' => !empty($return['description']) ? $return['description'] : NULL,
          '#collapsible' => !empty($pane['collapsible']) ? $pane['collapsible'] : FALSE,
          '#collapsed' => FALSE,
          '#attributes' => array('id' => $pane['id'] .'-pane'),
          '#theme' => $return['theme'],
	  );

        // Add the contents of the fieldset if any were returned

        if (!empty($return['contents'])) {
          $form['panes'][$pane['id']] = array_merge($form['panes'][$pane['id']], $return['contents']);
        }
      }
    }
  }

  // Edit an existing address

  if ($view == 'edit') {
    $form['submit'] =
      array('#type' => 'submit',
	    '#value' => variable_get('uc_addresses_update_button', t('Update address')));
    $form['delete'] =
      array('#type' => 'submit',
	    '#value' => variable_get('uc_addresses_delete_button', t('Delete address')),
	    '#suffix' => l(t('Cancel'), 'user/'. $uid .'/addresses/'),
	    '#disabled' => $address->is_default);
  }

  // Add a new address

  else {
    $form['submit'] =
      array('#type' => 'submit',
	    '#value' => variable_get('uc_addresses_update_button', t('Add address')),
	    '#suffix' => l(t('Cancel'), 'user/'. $uid .'/addresses/'));
  }

  return $form;
}

/**
 * Theme the add or edit address form.
 *
 * @param $form The form array to theme.
 * @return The themed form (as a string).
 * @ingroup themeable
 */
function theme_uc_addresses_get_address_form($form) {
  drupal_add_css(drupal_get_path('module', 'uc_addresses') .'/uc_addresses.css');

  $output = '<p>';

  foreach (element_children($form['panes']) as $pane_id) {

    // TODO: (Tony) Not sure what's going on here.

    if (function_exists(($func = _address_pane_data($pane_id, 'callback')))) {
      $result = $func('theme', $form['panes'][$pane_id], NULL);
      if (!empty($result)) {
	$output .= $result;
	$form['panes'][$pane_id] = array();
      }
      else {
	$output .= drupal_render($form['panes'][$pane_id]);
      }
    }
    else {
      $output .= drupal_render($form['panes'][$pane_id]);
    }
  }

  $output .= '<div id="checkout-form-bottom">'. drupal_render($form) .'</div>';

  return $output;
}

/**
 * Handle the form submit for adding a new address or editing an
 * existing address.
 *
 * @param $form The form.
 * @param $form_state The form state.
 * @return The path where we should wind up.
 */
function uc_addresses_get_address_form_submit($form, &$form_state) {
  global $user;

  $address_user = $form['stored_values']['#value']['user'];
  $address = $form['stored_values']['#value']['address'];
  $view = $form['stored_values']['#value']['view'];

  if ($form_state['clicked_button']['#value'] == t('Delete address')) {
      cache_clear_all();
      $form_state['redirect'] =
	array('user/'. $address_user->uid .'/addresses/' . $address->aid . '/delete');
  }
  else {
    if (!$address) {
      $address = new stdClass();
      $address->uid = $address_user->uid;
    }

    $valid = TRUE;
    foreach (element_children($form_state['values']['panes']) as $pane_id) {
      $func = _address_pane_data($pane_id, 'callback');
      $isvalid = $func('process', $address, $form_state['values']['panes'][$pane_id]);
      if ($isvalid === FALSE) {
	$_SESSION['expanded_panes'][] = $key;
	$valid = FALSE;
      }
    }
    if ($view == 'edit') { // Update database
      _uc_addresses_db_update_address($address);
    }
    elseif ($view == 'new' || $view == 'add') { // Insert into datebase
      _uc_addresses_db_add_address($address);
    }
    $form_state['redirect'] = array('user/'. $address_user->uid .'/addresses');
  }
}

/**
 * Format an address the same as the rest of the Ubercart store.
 *
 * @param $items An object containing address information.
 * @return A formatted address.
 */
function uc_addresses_address($items) {
  $address = uc_address_format(
    $items->first_name,
    $items->last_name,
    $items->company,
    $items->street1,
    $items->street2,
    $items->city,
    $items->zone,
    $items->postal_code,
    $items->country
    );
  return $address;
}

/**
 * Display a confirmation page before deleting an address.
 *
 * @param $address_user The user who "owns" the address.
 * @param $address The address to delete.
 */
function uc_addresses_delete_address_confirm($address_user, $address) {
  $uid = $address_user->uid;
  $aid = $address->aid;

  $form = drupal_get_form('uc_addresses_delete_address_confirm_form', $address_user, $address);

  $help =
    variable_get('uc_addresses_delete_instructions',
		 t('Are you are sure you want to Delete this address? ' .
		   'Once deleted, the address cannot be recovered.',
		   array('!delete' => variable_get('uc_addresses_delete_button',
						   t('Delete address')))
		   )
      );

  $panes = _address_pane_list();
  foreach ($panes as $pane) {
    if (variable_get('uc_addresses_pane_'. $pane['id'] .'_enabled', TRUE)) {
      $func = $pane['callback'];
      if (function_exists($func)) {
        $return = $func('view', $address, NULL);
        if (!is_NULL($return)) {
          $data[$pane['title']] = $return;
        }
      }
    }
  }

  $output = theme('uc_addresses_address_delete_confirm', $help, $data, $form);

  return $output;
}

/**
 * Theme the address deletion confirmation form.
 *
 * @param $help The help message to display.
 * @param $panes An associative array for each address pane that
 *	has information to add to the delete page. The key is the
 *	pane's title and the value is either the data returned for
 *	that pane or an array of returned data.
 * @param $form The HTML version of the form that by default
 *	includes the 'Back' and 'Delete Address' buttons at the bottom
 *	of the confirmation page.
 * @return The themed confirmation form (as a string).
 * @ingroup themeable
 */
function theme_uc_addresses_address_delete_confirm($help, $panes, $form) {
  drupal_add_css(drupal_get_path('module', 'uc_addresses') .'/uc_addresses.css');

  $output = '<p>'. $help .'</p>';
  $output .= '<div class="list_address"><table class="address-preview-table">';
  foreach ($panes as $title => $data) {
    $output .= '<tr class="pane-title-row"><td colspan="2">'. t('Address')
      .'</td></tr>';
    if (is_array($data)) {
      foreach ($data as $row) {
        if (is_array($row)) {
          if (isset($row['border'])) {
            $border = ' class="row-border-'. $row['border'] .'"';
          }
          else {
            $border = '';
          }
          $output .= '<tr class="pane-data-row"'. $border .'><td class="title-col" '
	    .'>'. $row['title'] .':</td><td class="data-col">'
	    . $row['data'] .'</td></tr>';
        }
        else {
          $output .= '<tr class="pane-data-row"><td colspan="2">'. $row .'</td></tr>';
        }
      }
    }
    else {
      $output .= '<tr class="pane-data-row"><td colspan="2">'. $data .'</td></tr>';
    }
  }
  $output .= '<tr class="preview-button-row"><td colspan="2">'. $form
    .'</td></tr></table>';
  $output .= '</div>';
  return $output;
}

/**
 * Get the submit buttons to confirm deletion of a user's address.
 *
 * @param $form_state The state of the form.
 * @param $user The user who "owns" the address.
 * @param $address The address we are deleting.
 * @return The buttons for the form (as a string).
 * @ingroup forms
 */
function uc_addresses_delete_address_confirm_form(&$form_state, $address_user, $address) {

  $form['stored_values'] = array(
    '#type' => 'value',
    '#value' => array(
      'user' => $address_user,
      'address' => $address),
    );


  // The buttons

  $form['submit'] = array('#type' => 'submit',
			  '#value' => variable_get('uc_addresses_delete_button', t('Delete address')),
			  '#suffix' => l(t('Cancel'), 'user/'. $address_user->uid .'/addresses/'));
  return $form;
}

/**
 * Delete a user-confirmed address.
 *
 * @param $form The form.
 * @param $form_state The form state.
 */
function uc_addresses_delete_address_confirm_form_submit($form, &$form_state) {

  $address_user = $form['stored_values']['#value']['user'];
  $address = $form['stored_values']['#value']['address'];

  _uc_addresses_db_delete_address($address->aid);
  drupal_set_message(t('The address has been deleted.'));

  $form_state['redirect'] = array('user/'. $address_user->uid .'/addresses');
}

/*******************************************************************************
 * Database Functions
 ******************************************************************************/

/**
 * Add a new address to the database table. If the address is already
 * in the database (for this user), it is not added.
 *
 * @param $address The address to add (as an object).
 * @param The id of the new address or FALSE if there was an error.
 */
function _uc_addresses_db_add_address($address, $silent = FALSE) {

  // No user -- shouldn't happen
  if ($address->uid == 0) { echo "User 0\n"; exit(1); }

  // No address -- may happen
  if (!isset($address->last_name)) return TRUE;

  // From this point on, we have both a user and an address to add

  // We need to work with systems where this module is added when
  // users are already present. Find out how many addresses this user has

  $num_addresses =
    db_result(db_query("SELECT COUNT(*) FROM {uc_addresses} WHERE uid = %d", $address->uid));

  // If this is the first address the user has ever added, make it the
  // default address

  if ($num_addresses < 1) $address->is_default = 1;

  if ($num_addresses > 0) {
    // Check for problems

    $result = _uc_addresses_db_check_address($address, 'add', $silent);
    if (!$result) return FALSE;
  }
  else {
    _uc_addresses_db_normalize_address($address);
  }

  // Add the address

  db_query("INSERT INTO {uc_addresses} (uid, first_name, last_name, "
	   ."phone, company, street1, street2, city, zone, postal_code, country, "
	   ."address_name, "
	   ."created, modified) VALUES (%d, '%s', "
	   ."'%s', '%s', "
	   ."'%s', '%s', '%s', "
	   ."'%s', %d, '%s', %d, "
	   ."'%s', "
	   ."%d, %d)",
	   $address->uid,
     $address->first_name,
	   $address->last_name,
	   $address->phone,
     $address->company,
	   $address->street1,
	   $address->street2,
     $address->city,
	   $address->zone,
	   $address->postal_code,
     ((is_NULL($address->country) || $address->country == 0) ?
	     variable_get('uc_store_country', 840) :
	   $address->country),
	   $address->address_name,
     time(),
	   time());
  $aid = db_last_insert_id('uc_addresses', 'aid');

  // Update the default address, if necessary

  if ($address->is_default) {
    if ($num_addresses < 1) {
      db_query("INSERT INTO {uc_addresses_defaults} (aid, uid) VALUES (%d, %d)", $aid, $address->uid);
    }
    else {
      db_query("UPDATE {uc_addresses_defaults} SET aid = %d WHERE uid = %d", $aid, $address->uid);
    }
  }

  return $aid;
}

/**
 * Check to see if we saved any addresses for an anonymous user.
 *
 * @return True if there are any saved addresses.
 */

function _uc_addresses_db_have_saved_addresses() {
  return
    isset($_SESSION['uc_addresses_saved_addresses']) &&
    is_array($_SESSION['uc_addresses_saved_addresses']);
}

/**
 * Update an address in the database table.
 *
 * @param $address The address to add.
 * @param A boolean that is TRUE if the address was added, FALSE otherwise.
 */
function _uc_addresses_db_update_address($address) {

  // Check for problems

  $result = _uc_addresses_db_check_address($address, 'update', false);
  if (!$result) return FALSE;

  // We're set to go

  db_query("UPDATE {uc_addresses} SET "
	   ."first_name = '%s', "
	   ."last_name = '%s', "
	   ."phone = '%s', "
	   ."company = '%s', "
	   ."street1 = '%s', "
	   ."street2 = '%s', "
	   ."city = '%s', "
	   ."zone = %d, "
	   ."postal_code = '%s', "
	   ."country = %d, "
	   ."address_name = '%s', "
	   ."modified = %d "
	   ."WHERE aid = %d",
     $address->first_name,
	   $address->last_name,
	   $address->phone,
     $address->company,
	   $address->street1,
	   $address->street2,
     $address->city,
	   $address->zone,
	   $address->postal_code,
	   $address->country,
	   $address->address_name,
     time(),
	   $address->aid);

  // Update the default address, if necessary

  if ($address->is_default) {
      db_query("UPDATE {uc_addresses_defaults} SET aid = %d WHERE uid = %d", $address->aid, $address->uid);
  }
  drupal_set_message(t('The address was updated.'));

  return TRUE;
}

/**
 * Before adding or updating an address, check it for errors.
 *
 * @param $address The address we are about to add or update.
 * @param $op Either 'add' or 'update'.
 * @param A boolean which, if TRUE, tells us not to display warnings
 *	to the user.
 * @return TRUE if the address is valid, FALSE otherwise.
 */
function _uc_addresses_db_check_address($address, $op, $silent) {

  _uc_addresses_db_normalize_address($address);

  $result =
    db_query("SELECT aid FROM {uc_addresses} WHERE "
	     ."uid = %d AND "
	     ."first_name = '%s' AND "
	     ."last_name = '%s' AND "
	     ."phone = '%s' AND "
	     ."company = '%s' AND "
	     ."street1 = '%s' AND "
	     ."street2 = '%s' AND "
	     ."city = '%s' AND "
	     ."zone = %d AND "
	     ."postal_code = '%s' AND "
	     ."country = '%s'",
	     $address->uid,
	     $address->first_name,
	     $address->last_name,
	     $address->phone,
	     $address->company,
	     $address->street1,
	     $address->street2,
	     $address->city,
	     $address->zone,
	     $address->postal_code,
	     $address->country);

  $num_rows = 0;

  while ($db_address = db_fetch_object($result)) {
    $num_rows++;

    // If the address appears more than once, the database table is
    // corrupted. The fix, however, is simple: delete the extra
    // address

    if ($num_rows > 1) {
      drupal_set_message(t('This address appears more than once in your address book. '
			   .'Please delete the duplicates and file a bug report.'),
			 'error');
      return FALSE;
    }

    // If we get one address, it's OK only if it is the address we're
    // trying to add or update

    if (!isset($address->aid) || ($db_address->aid != $address->aid)) {
      if (!$silent) {
	if ($op == 'add') {
	  drupal_set_message(
	    t('This address already appears in your address book. '
	      .'A new address was not added.'),
	    'warning');
	}
	else {
	  drupal_set_message(
	    t('The revised address is already in your address book. '
	      .'Your change was not made.'),
	    'warning');
	}
      }
      return FALSE;
    }
  }

  // Now check to make sure the address_name is not already in use
  // Unless its for the address we are trying to update

  if ($address->address_name) {
    $result =
      db_query("SELECT aid FROM {uc_addresses} WHERE "
	       ."uid = %d AND "
	       ."address_name = '%s'",
	       $address->uid,
	       $address->address_name);
    while ($db_address = db_fetch_object($result)) {
      if ($db_address->aid != $address->aid && !$silent) {
	drupal_set_message(
	  t('The short name you selected for this address is already in your address book. '
	    .'Please select a different name.'),
	  'error');
	return FALSE;
      }
    }
  }

  return TRUE;
}

/**
 * SQL gets a little weird when comparing nulls. "value = NULL" is
 * always false, even if the value is NULL! You have to write "value
 * IS NULL" to get the "right" answer. Rather than changing the
 * comparisons all over the place, we will make sure no value is null.
 * Because address fields can be disabled, any field could be null.
 *
 * @param $address The address to normalize (altered in place).
 */
function _uc_addresses_db_normalize_address(&$address) {

  // Yes, the comparisons are checking for null, '', and 0. This is
  // deliberate--call me paranoid

  if (!$address->first_name) $address->first_name = '';
  if (!$address->last_name) $address->last_name = '';
  if (!$address->phone) $address->phone = '';
  if (!$address->company) $address->company = '';
  if (!$address->street1) $address->street1 = '';
  if (!$address->street2) $address->street2 = '';
  if (!$address->city) $address->city = '';
  if (!$address->zone) $address->zone = 0;
  if (!$address->postal_code) $address->postal_code = '';
  if (!$address->country) $address->country = variable_get('uc_store_country', 840);
  if (!isset($address->address_name) || !$address->address_name) $address->address_name = '';
}

/**
 * Get an address or list of addresses from the database.
 *
 * @param $uid The id of the user who "owns" the address.
 * @param $aid The id of the address to fetch. If NULL, fetch
 *	all addresses owned by the user.
 * @return FALSE on error. An object if $aid was not NULL. An
 *	array of objects if $aid was NULL.
 */
function _uc_addresses_db_get_address($uid, $aid = NULL) {

  if ($aid === 0) return FALSE;

  // If $aid is present, return just the one address

  if ($aid) {
    $result = db_query("SELECT * FROM {uc_addresses} WHERE aid = %d ",	$aid);

    // Check to make sure there is data

    if (($address = db_fetch_object($result)) == FALSE) return FALSE;

    // Check to make sure it's from the right user

    if ($address->uid != $uid) return FALSE;

    // Find out if this is the default address

    $address->is_default = 0;
    $def = db_query("SELECT aid FROM {uc_addresses_defaults} WHERE uid = %d", $uid);
    if ($default_aid = db_fetch_object($def)) {
      $address->is_default = $default_aid->aid == $address->aid;
    }

    // All OK

    return $address;
  }

  // If $aid is NULL, return all addresses for that user

  $result = db_query("SELECT * FROM {uc_addresses} WHERE uid = %d ORDER BY created", $uid);

  $default_aid = _uc_addresses_get_default_address_id($uid);

  // Gather up everything

  $num_rows = 0;
  while ($address = db_fetch_object($result)) {
    $num_rows++;
    $address->is_default = $default_aid == $address->aid;
    $addresses[] = $address;
  }
  if ($num_rows == 0) return FALSE;

  return $addresses;
}

/**
 * Get a user's default address id. Because the addresses module can
 * be added to an existing system, it's possible that some people will
 * not have a default address (or any addresses).
 *
 * @param $uid The id of the user who "owns" the address.
 * @return The $id of the default address or NULL if none.
 */
function _uc_addresses_get_default_address_id($uid) {
  $def =
    db_query("SELECT aid FROM {uc_addresses_defaults} WHERE uid = %d", $uid);

  $aid = null;
  while ($obj = db_fetch_object($def)) {
    $aid = $obj->aid;
  }
  return $aid;
}

/**
 * Delete an address from the database.
 *
 * @param $aid The id of the address to delete.
 * @return Returns TRUE if the address was deleted.
 */
function _uc_addresses_db_delete_address($aid) {
  if (_uc_addresses_db_check_if_default($aid)) {
    drupal_set_message(t('You cannot delete your default address'), 'warning');
    return FALSE;
  }
  else {
    db_query("DELETE FROM {uc_addresses} WHERE aid = %d", $aid);
    return TRUE;
  }
}

/**
 * Check to see if an address is the default address.
 *
 * @param $aid The id of the address to check.
 * @return Returns TRUE if the address is the default address.
 */
function _uc_addresses_db_check_if_default($aid) {
  $count =
    db_result(
      db_query("SELECT COUNT(*) FROM {uc_addresses_defaults} WHERE "
	       ."aid = %d", $aid));
  if ($count > 0) {
    return TRUE;
  }
  return FALSE;
}
