<?php

/**
 * @file
 * Defines a field for attaching country-specific addresses to entities.
 */

/**
 * Implements hook_ctools_plugin_directory().
 */
function addressfield_ctools_plugin_directory($module, $plugin) {
  if ($module == 'addressfield') {
    return 'plugins/' . $plugin;
  }
}

/**
 * Implements hook_ctools_plugin_type().
 */
function addressfield_ctools_plugin_type() {
  $plugins['format'] = array(
    'load themes' => TRUE,
  );
  return $plugins;
}

/**
 * Implements hook_views_api().
 */
function addressfield_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'addressfield') . '/views',
  );
}

/**
 * Implements hook_module_implements_alter().
 *
 * Moves the hook_token_info_alter() implementation to the bottom so it is
 * invoked after all modules implementing the same hook.
 */
function addressfield_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'token_info_alter') {
    // Make sure that the $implementations list is populated before altering it,
    // to work around a crash experienced by some people (#2181001).
    if (isset($implementations['addressfield'])) {
      $group = $implementations['addressfield'];
      unset($implementations['addressfield']);
      $implementations['addressfield'] = $group;
    }
  }
}

/**
 * Returns a list of address fields optionally filtered by entity type.
 *
 * @param string $entity_type
 *   Optional machine-name of an entity type to filter the returned array by.
 *
 * @return array
 *   An array of address field mapping data.
 */
function addressfield_get_address_fields($entity_type = '') {
  $fields = &drupal_static(__FUNCTION__ . '_' . $entity_type);

  if (isset($fields)) {
    return $fields;
  }

  // Get mapping data for all address fields.
  $fields = array_filter(field_info_field_map(), 'addressfield_field_map_filter');

  // Filter the list of fields by entity type if specified.
  if (!empty($fields) && !empty($entity_type)) {
    foreach ($fields as $field_name => $field) {
      if (!isset($field['bundles'][$entity_type])) {
        unset($fields[$field_name]);
      }
    }
  }

  return $fields;
}

/**
 * Returns TRUE if a field map array value represents an addressfield.
 *
 * Provided for use as a callback by array_filter().
 */
function addressfield_field_map_filter($field) {
  return !empty($field['type']) && $field['type'] == 'addressfield';
}

/**
 * Get the list of format plugins.
 */
function addressfield_format_plugins() {
  ctools_include('plugins');
  $plugins = ctools_get_plugins('addressfield', 'format');
  uasort($plugins, 'ctools_plugin_sort');

  return $plugins;
}

/**
 * Get the list of format plugins in a format suitable for #options.
 */
function addressfield_format_plugins_options() {
  $options = array();
  foreach (addressfield_format_plugins() as $widget => $info) {
    $options[$widget] = check_plain($info['title']);
  }
  return $options;
}

/**
 * @defgroup addressfield_format Address format API
 * @{
 * API for generating address forms and display formats.
 *
 * Addresses forms and display formats are collaboratively generated by one or
 * more format handler plugins. An address with a name and a company, for example,
 * will be generated by three handlers:
 *   - 'address' that will generate the country, locality, street blocks
 *   - 'organisation' that will add the organisation block to the address
 *   - 'name-full' that will add a first name and last name block to the address
 *
 * A format handler is a CTools plugin of type 'addressfield' / 'format'. Each
 * handler is passed the format in turn, and can add to or modify the format.
 *
 * The format itself is a renderable array stub. This stub will be transformed
 * into either a Form API array suitable for use as part of a form or into a
 * renderable array suitable for use with drupal_render(). The following
 * modifications are done:
 *   - when rendering as a form, every element which name (its key in the array)
 *     is a valid addressfield column (see addressfield_field_schema()), will
 *     be transformed into a form element, either using a type explicitly
 *     defined in '#widget_type' or using 'select' if '#options' is set or
 *     'textfield' if it is not. In addition, the '#default_value' of every
 *     field will be populated from the address being edited.
 *   - when rendering as a formatter, every element which name (its key in the array)
 *     is a valid addressfield column (see addressfield_field_schema()), will
 *     be transformed into a renderable element, either using a type explicitly
 *     defined in '#render_type' or else using 'addressfield_container'. When
 *     the type is 'addressfield_container' the element will be rendered as
 *     an HTML element set by '#tag' (default: span).
 */

/**
 * Generate a format for a given address.
 *
 * @param $address
 *   The address format being generated.
 * @param $handlers
 *   The format handlers to use to generate the format.
 * @param $context
 *   An associative array of context information pertaining to how the address
 *   format should be generated. If no mode is given, it will initialize to the
 *   default value. The remaining context keys should only be present when the
 *   address format is being generated for a field:
 *   - mode: either 'form' or 'render'; defaults to 'render'.
 *   - field: the field info array.
 *   - instance: the field instance array.
 *   - langcode: the langcode of the language the field is being rendered in.
 *   - delta: the delta value of the given address.
 *
 * @return
 *   A renderable array suitable for use as part of a form (if 'mode' is 'form')
 *   or for formatted address output when passed to drupal_render().
 */
function addressfield_generate($address, array $handlers, array $context = array()) {
  // If no mode is given in the context array, default it to 'render'.
  if (empty($context['mode'])) {
    $context['mode'] = 'render';
  }

  ctools_include('plugins');
  $format = array();
  // Add the handlers, ordered by weight.
  $plugins = addressfield_format_plugins();
  $format['#handlers'] = array_intersect(array_keys($plugins), $handlers);

  foreach ($format['#handlers'] as $handler) {
    if ($callback = ctools_plugin_load_function('addressfield', 'format', $handler, 'format callback')) {
      $callback($format, $address, $context);
    }
  }

  // Store the address in the format, for processing.
  $format['#address'] = $address;

  // Post-process the format stub, depending on the rendering mode.
  if ($context['mode'] == 'form') {
    $format['#addressfield'] = TRUE;
    $format['#process'][] = 'addressfield_process_format_form';
  }
  elseif ($context['mode'] == 'render') {
    $format['#pre_render'][] = 'addressfield_render_address';
  }

  return $format;
}

/**
 * Generate a full-fledged form from a format snippet, as returned by addressfield_formats().
 */
function addressfield_process_format_form($format, &$form_state, $complete_form) {
  // Make sure to load all the plugins that participated in this format.
  ctools_include('plugins');
  foreach ($format['#handlers'] as $handler) {
    ctools_plugin_load_function('addressfield', 'format', $handler, 'format callback');
  }

  _addressfield_process_format_form($format, $format['#address']);
  return $format;
}

function _addressfield_process_format_form(&$format, $address) {
  foreach (element_children($format) as $key) {
    $child = &$format[$key];

    // Automatically convert any element in the format array to an appropriate
    // form element that matches one of the address component names.
    if (in_array($key, array('name_line', 'first_name', 'last_name', 'organisation_name', 'country', 'administrative_area', 'sub_administrative_area', 'locality', 'dependent_locality', 'postal_code', 'thoroughfare', 'premise', 'sub_premise'))) {
      // Set the form element type for the address component to whatever the
      // address format specified in its #widget_type property.
      if (isset($child['#widget_type'])) {
        $child['#type'] = $child['#widget_type'];
      }
      else {
        // If the element didn't specify a #widget_type and has options, turn it
        // into a select list and unset its #size value, which is typically used
        // to provide the width of a textfield.
        if (isset($child['#options'])) {
          $child['#type'] = 'select';
          unset($child['#size']);
        }
        else {
          // Otherwise go ahead and make it a textfield.
          $child['#type'] = 'textfield';
        }
      }

      if (isset($address[$key])) {
        $child['#default_value'] = $address[$key];
      }
    }

    // Recurse through the element's children if it has any.
    _addressfield_process_format_form($child, $address);
  }
}

/**
 * Render an address in a given format.
 */
function addressfield_render_address($format) {
  _addressfield_render_address($format, $format['#address']);
  return $format;
}

function _addressfield_render_address(&$format, $address) {
  foreach (element_children($format) as $key) {
    $child = &$format[$key];

    // Automatically expand elements that match one of the fields of the address
    // structure.
    if (in_array($key, array('name_line', 'first_name', 'last_name', 'organisation_name', 'country', 'administrative_area', 'sub_administrative_area', 'locality', 'dependent_locality', 'postal_code', 'thoroughfare', 'premise', 'sub_premise'), TRUE)) {
      if (isset($child['#render_type'])) {
        $child['#type'] = $child['#render_type'];
      }
      else {
        $child['#type'] = 'addressfield_container';
        if (!isset($child['#tag'])) {
          $child['#tag'] = 'span';
        }
      }

      // If the element instructs us to render the option value instead of the
      // raw address element value and its #options array has a matching key,
      // swap it out for the option value now.
      if (!empty($child['#render_option_value']) && isset($address[$key]) && isset($child['#options'][$address[$key]])) {
        $child['#children'] = check_plain($child['#options'][$address[$key]]);
      }
      elseif (isset($address[$key])) {
        $child['#children'] = check_plain($address[$key]);
      }
      else {
        $child['#children'] = '';
      }

      // Skip empty elements.
      if ((string) $child['#children'] === '') {
        $child['#access'] = FALSE;
      }

      // Add #field_prefix and #field_suffix to the prefixes and suffixes.
      if (isset($child['#field_prefix'])) {
        $child['#prefix'] = (isset($child['#prefix']) ? $child['#prefix'] : '') . $child['#field_prefix'];
      }
      if (isset($child['#field_suffix'])) {
        $child['#suffix'] = (isset($child['#suffix']) ? $child['#suffix'] : '') . $child['#field_suffix'];
      }
    }

    // Recurse through the child.
    _addressfield_render_address($child, $address);
  }
}

/**
 * @} End of "ingroup addressfield_format"
 */

/**
 * Implementation of hook_theme().
 */
function addressfield_theme() {
  $hooks['addressfield_container'] = array(
    'render element' => 'element',
  );
  return $hooks;
}

/**
 * Render a container for a set of address fields.
 */
function theme_addressfield_container($variables) {
  $element = $variables['element'];
  $element['#children'] = trim($element['#children']);
  // Remove the autocomplete attribute because the W3C validator complains.
  // It's only used on forms anyway.
  unset($element['#attributes']['autocomplete']);

  if (strlen($element['#children']) > 0) {
    $output = '<' . $element['#tag'] . drupal_attributes($element['#attributes']) . '>';
    $output .= $element['#children'];
    $output .= '</' . $element['#tag'] . '>';
    // Add a linebreak to the HTML after a div. This is invisible on the
    // rendered page but improves the appearance of address field output when
    // HTML tags are stripped, such as by Views Data Export.
    if ($element['#tag'] == 'div') {
      $output .= PHP_EOL;
    }

    return $output;
  }
  else {
    return '';
  }
}

/**
 * Implementation of hook_element_info().
 */
function addressfield_element_info() {
  $types['addressfield_container'] = array(
    '#theme_wrappers' => array('addressfield_container'),
    '#process' => array('addressfield_widget_process'),
    '#attributes' => array(),
    '#tag' => 'div',
  );
  return $types;
}

/**
 * Form API process function: set the #parents of the children of this element so they appear at the same level as the parent.
 */
function addressfield_widget_process($element) {
  foreach (element_children($element) as $key) {
    $element[$key]['#parents'] = $element['#parents'];
    $element[$key]['#parents'][count($element[$key]['#parents']) - 1] = $key;
  }

  return $element;
}

/**
 * Implements hook_field_info()
 */
function addressfield_field_info() {
  $fields = array();

  $fields['addressfield'] = array(
    'label' => t('Postal address'),
    'description' => t('A field type used for storing postal addresses according the xNAL standard.'),
    'settings' => array(),
    'instance_settings' => array(),
    'default_widget' => 'addressfield_standard',
    'default_formatter' => 'addressfield_default',
    'property_type' => 'addressfield',
    'property_callbacks' => array('addressfield_property_info_callback'),
  );

  return $fields;
}

/**
 * Returns an array of default values for the addressfield form elements.
 *
 * @param $field
 *   The field array.
 * @param $instance
 *   The instance array.
 * @param $address
 *   The current address values, if known. Allows for per-country defaults.
 *
 * @return
 *   An array of default values.
 */
function addressfield_default_values($field, $instance, array $address = array()) {
  $available_countries = _addressfield_country_options_list($field, $instance);
  $default_country = $instance['widget']['settings']['default_country'];
  // Resolve the special site_default option.
  if ($default_country == 'site_default') {
    $default_country = variable_get('site_default_country', '');
  }
  // Fallback to the first country in the list if the default country is not
  // available, or is empty even though the field is required.
  $not_available = $default_country && !isset($available_countries[$default_country]);
  $empty_but_required = empty($default_country) && !empty($instance['required']);
  if ($not_available || $empty_but_required) {
    $default_country = key($available_countries);
  }

  $default_values = array(
    'country' => $default_country,
    'name_line' => '',
    'first_name' => '',
    'last_name' => '',
    'organisation_name' => '',
    'administrative_area' => '',
    'sub_administrative_area' => '',
    'locality' => '',
    'dependent_locality' => '',
    'postal_code' => '',
    'thoroughfare' => '',
    'premise' => '',
    'sub_premise' => '',
    'data' => '',
  );

  // Allow other modules to alter the default values.
  $context = array(
    'field' => $field,
    'instance' => $instance,
    'address' => $address,
  );
  drupal_alter('addressfield_default_values', $default_values, $context);

  return $default_values;
}

/**
 * Implements hook_field_is_empty().
 */
function addressfield_field_is_empty($item, $field) {
  // Every address field must have at least a country value or it is considered
  // empty, even if it has name information.
  return empty($item['country']);
}

/**
 * Implements hook_field_presave().
 */
function addressfield_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($items as $delta => &$item) {
    // If the first name and last name are set but the name line isn't...
    if (isset($item['first_name']) && isset($item['last_name']) && !isset($item['name_line'])) {
      // Combine the first and last name to be the name line.
      $items[$delta]['name_line'] = $items[$delta]['first_name'] . ' ' . $items[$delta]['last_name'];
    }
    elseif (isset($item['name_line'])) {
      // Otherwise if the name line is set, separate it out into a best guess at
      // the first and last name.
      $names = explode(' ', $item['name_line']);

      $item['first_name'] = array_shift($names);
      $item['last_name'] = implode(' ', $names);
    }

    // Trim whitespace from all of the address components and convert any double
    // spaces to single spaces.
    foreach ($item as $key => &$value) {
      if (!in_array($key, array('data')) && is_string($value)) {
        $value = trim(preg_replace('/[[:blank:]]{2,}/u', ' ', $value));
      }
    }
  }
}

/**
 * Implements hook_field_widget_info()
 */
function addressfield_field_widget_info() {
  $widgets = array();

  $widgets['addressfield_standard'] = array(
    'label' => t('Dynamic address form'),
    'field types' => array('addressfield'),
    'settings' => array(
      'available_countries' => array(),
      // Can't use variable_get('site_default_country') here because it would
      // set the value in stone. Instead, the site_default option allows the
      // default country to always reflect the current site setting.
      'default_country' => 'site_default',
      'format_handlers' => array('address'),
    ),
  );

  return $widgets;
}

/**
 * Implements hook_field_widget_settings_form()
 */
function addressfield_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $defaults = field_info_widget_settings($widget['type']);
  $settings = array_merge($defaults, $widget['settings']);
  $form = array();

  if ($widget['type'] == 'addressfield_standard') {
    $form['available_countries'] = array(
      '#type' => 'select',
      '#multiple' => TRUE,
      '#title' => t('Available countries'),
      '#description' => t('If no countries are selected, all countries will be available.'),
      '#options' => _addressfield_country_options_list(),
      '#default_value' => $settings['available_countries'],
    );
    $form['default_country'] = array(
      '#type' => 'select',
      '#title' => t('Default country'),
      '#options' => array('site_default' => t('- Site default -')) + _addressfield_country_options_list(),
      '#default_value' => $settings['default_country'],
      '#empty_value' => '',
    );
    $form['format_handlers'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Format handlers'),
      '#options' => addressfield_format_plugins_options(),
      '#default_value' => $settings['format_handlers'],
    );
  }

  return $form;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 *
 * Removes the default values form from the field settings page.
 * Allows the module to implement its own, more predictable default value
 * handling, getting around #1253820 and other bugs.
 */
function addressfield_form_field_ui_field_edit_form_alter(&$form, $form_state) {
  if ($form['#field']['type'] == 'addressfield') {
    $form['instance']['default_value_widget']['#access'] = FALSE;
  }
}

/**
 * Implements hook_field_widget_form()
 */
function addressfield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $settings = $instance['widget']['settings'];

  $address = array();
  // If the form has been rebuilt via AJAX, use the form state values.
  // $form_state['values'] is empty because of #limit_validation_errors, so
  // $form_state['input'] needs to be used instead.
  $parents = array_merge($element['#field_parents'], array($element['#field_name'], $langcode, $delta));
  if (!empty($form_state['input'])) {
    $input_address = drupal_array_get_nested_value($form_state['input'], $parents);
  }
  if (!empty($input_address)) {
    $address = $input_address;
  }
  elseif (!empty($items[$delta]['country'])) {
    // Else use the saved value for the field.
    $address = $items[$delta];
  }

  // Determine the list of available countries, and if the currently selected
  // country is not in it, unset it so it can be reset to the default country.
  $countries = _addressfield_country_options_list($field, $instance);
  if (!empty($address['country']) && !isset($countries[$address['country']])) {
    unset($address['country']);
  }

  // Merge in default values.
  $address += addressfield_default_values($field, $instance, $address);

  // Add the form elements for the standard widget, which includes a country
  // select list at the top that reloads the available address elements when the
  // country is changed.
  if ($instance['widget']['type'] == 'addressfield_standard') {
    // Wrap everything in a fieldset. This is not the best looking element,
    // but it's the only wrapper available in Drupal we can properly use
    // in that context, and it is overridable if necessary.
    $element['#type'] = 'fieldset';

    if (!empty($instance['description'])) {
      // Checkout panes convert the fieldset into a container, causing
      // #description to not be rendered. Hence, a real element is added and
      // the old #description is removed.
      $element['#description'] = '';
      $element['element_description'] = array(
        '#markup' =>  $instance['description'],
        '#prefix' => '<div class="fieldset-description">',
        '#suffix' => '</div>',
        '#weight' => -999,
      );
    }

    // Generate the address form.
    $context = array(
      'mode' => 'form',
      'field' => $field,
      'instance' => $instance,
      'langcode' => $langcode,
      'delta' => $delta,
    );
    $element += addressfield_generate($address, $settings['format_handlers'], $context);

    // Remove any already saved default value.
    // See addressfield_form_field_ui_field_edit_form_alter() for the reasoning.
    if ($form_state['build_info']['form_id'] == 'field_ui_field_edit_form') {
      $element['#address'] = array('country' => '');
    }
  }

  return $element;
}

/**
 * Element validate callback: rebuilds the form on country change.
 */
function addressfield_standard_country_validate($element, &$form_state) {
  if ($element['#default_value'] != $element['#value']) {
    $parents = $element['#parents'];
    array_pop($parents);
    $address = drupal_array_get_nested_value($form_state['values'], $parents);

    // Clear the country-specific field values.
    $country_specific_data = array(
      'dependent_locality' => '',
      'locality' => '',
      'administrative_area' => '',
      'postal_code' => '',
    );
    $address = array_diff_key($address, $country_specific_data);

    drupal_array_set_nested_value($form_state['values'], $parents, $address);
    drupal_array_set_nested_value($form_state['input'], $parents, $address);

    $form_state['rebuild'] = TRUE;
  }
}

/**
 * Ajax callback in response to a change of country in an address field.
 *
 * The only thing we have to do is to find the proper element to render.
 */
function addressfield_standard_widget_refresh($form, $form_state) {
  // The target element is one element below the triggering country selector.
  $array_parents = $form_state['triggering_element']['#array_parents'];
  array_pop($array_parents);

  // Iterate over the form parents to find the element.
  $element = $form;
  foreach ($array_parents as $name) {
    $element = &$element[$name];
    if (!empty($element['#addressfield'])) {
      break;
    }
  }

  // Return the address block, but remove the '_weight' element inserted
  // by the field API.
  unset($element['_weight']);

  // Replace the address field widget with the updated widget and focus on the
  // new country select list.
  $commands[] = ajax_command_replace(NULL, render($element));
  $commands[] = ajax_command_invoke('#' . $element['country']['#id'], 'focus');
  // Add the status messages inside the new addressfield's wrapper element,
  // just like core does.
  $commands[] = ajax_command_prepend(NULL, theme('status_messages'));

  // Allow other modules to add arbitrary AJAX commands on the refresh.
  drupal_alter('addressfield_standard_widget_refresh', $commands, $form, $form_state);

  return array('#type' => 'ajax', '#commands' => $commands);
}

/**
 * Implements hook_field_formatter_info().
 */
function addressfield_field_formatter_info() {
  return array(
    'addressfield_default' => array(
      'label' => t('Default'),
      'field types' => array('addressfield'),
      'settings' => array(
        'use_widget_handlers' => 1,
        'format_handlers' => array('address'),
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function addressfield_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  $element['use_widget_handlers'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use the same configuration as the widget.'),
    '#default_value' => !empty($settings['use_widget_handlers']),
  );

  $element['format_handlers'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Format handlers'),
    '#options' => addressfield_format_plugins_options(),
    '#default_value' => $settings['format_handlers'],
    '#process' => array('form_process_checkboxes', '_addressfield_field_formatter_settings_form_process_add_state'),
    '#element_validate' => array('_addressfield_field_formatter_settings_form_validate')
  );

  return $element;
}

/**
 * Helper function: set the proper #states to the use widget handlers checkbox.
 */
function _addressfield_field_formatter_settings_form_process_add_state($element, $form_state) {
  // Build a #parents based on the current checkbox.
  $target_parents = array_slice($element['#parents'], 0, -1);
  $target_parents[] = 'use_widget_handlers';
  $target_parents = array_shift($target_parents) . ($target_parents ? '[' . implode('][', $target_parents) . ']' : '');

  $element['#states']['visible'] = array(
    ':input[name="' . $target_parents . '"]' => array('checked' => FALSE),
  );

  return $element;
}

/**
 * Helper function: filter the results of the checkboxes form element.
 */
function _addressfield_field_formatter_settings_form_validate($element, &$element_state) {
  form_set_value($element, array_filter($element['#value']), $element_state);
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function addressfield_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  if ($settings['use_widget_handlers']) {
    return t('Use widget configuration');
  }
  else {
    $summary = array();
    $plugins = addressfield_format_plugins();
    foreach ($settings['format_handlers'] as $handler) {
      $summary[] = $plugins[$handler]['title'];
    }
    return $summary ? implode(', ', $summary) : t('No handler');
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function addressfield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $settings = $display['settings'];
  $element = array();

  switch ($display['type']) {
    case 'addressfield_default':
      if (!empty($settings['use_widget_handlers'])) {
        $handlers = $instance['widget']['settings']['format_handlers'];
      }
      else {
        $handlers = $settings['format_handlers'];
      }
      foreach ($items as $delta => $address) {
        // Generate the address format.
        $context = array(
          'mode' => 'render',
          'field' => $field,
          'instance' => $instance,
          'langcode' => $langcode,
          'delta' => $delta,
        );
        $element[$delta] = addressfield_generate($address, $handlers, $context);
      }
      break;
  }

  return $element;
}

/**
 * Callback to alter the property info of address fields.
 *
 * @see addressfield_field_info().
 */
function addressfield_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $name = $field['field_name'];
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];

  $property['type'] = ($field['cardinality'] != 1) ? 'list<addressfield>' : 'addressfield';
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  $property['auto creation'] = 'addressfield_auto_creation';
  $property['property info'] = addressfield_data_property_info();

  unset($property['query callback']);
}

/**
 * Auto creation callback for an addressfield value array.
 *
 * @see addressfield_property_info_callback()
 */
function addressfield_auto_creation($property_name, $context) {
  return addressfield_default_values($context['field'], $context['instance']);
}

/**
 * Defines info for the properties of the address field data structure.
 */
function addressfield_data_property_info($name = NULL) {
  // Build an array of basic property information for the address field.
  $properties = array(
    'country' => array(
      'label' => t('Country'),
      'options list' => '_addressfield_country_options_list',
    ),
    'name_line' => array(
      'label' => t('Full name'),
    ),
    'first_name' => array(
      'label' => t('First name'),
    ),
    'last_name' => array(
      'label' => t('Last name'),
    ),
    'organisation_name' => array(
      'label' => t('Company'),
    ),
    'administrative_area' => array(
      'label' => t('Administrative area (i.e. State / Province)'),
    ),
    'sub_administrative_area' => array(
      'label' => t('Sub administrative area'),
    ),
    'locality' => array(
      'label' => t('Locality (i.e. City)'),
    ),
    'dependent_locality' => array(
      'label' => t('Dependent locality'),
    ),
    'postal_code' => array(
      'label' => t('Postal code'),
    ),
    'thoroughfare' => array(
      'label' => t('Thoroughfare (i.e. Street address)'),
    ),
    'premise' => array(
      'label' => t('Premise (i.e. Apartment / Suite number)'),
    ),
    'sub_premise' => array(
      'label' => t('Sub Premise (i.e. Suite, Apartment, Floor, Unknown.'),
    ),
  );

  // Add the default values for each of the address field properties.
  foreach ($properties as $key => &$value) {
    $value += array(
      'description' => !empty($name) ? t('!label of field %name', array('!label' => $value['label'], '%name' => $name)) : '',
      'type' => 'text',
      'getter callback' => 'entity_property_verbatim_get',
      'setter callback' => 'entity_property_verbatim_set',
    );
  }

  return $properties;
}

/**
 * Returns the country list in a format suitable for use as an options list.
 */
function _addressfield_country_options_list($field = NULL, $instance = NULL) {
  if (module_exists('countries')) {
    $countries = countries_get_countries('name', array('enabled' => COUNTRIES_ENABLED));
  }
  else {
    require_once DRUPAL_ROOT . '/includes/locale.inc';
    $countries = country_get_list();
  }

  if (isset($field)) {
    // If the instance is not specified, loop against all the instances of the field.
    if (!isset($instance)) {
      $instances = array();
      foreach ($field['bundles'] as $entity_type => $bundles) {
        foreach ($bundles as $bundle_name) {
          $instances[] = field_info_instance($entity_type, $field['field_name'], $bundle_name);
        }
      }
    }
    else {
      $instances = array($instance);
    }

    foreach ($instances as $instance) {
      if (!empty($instance['widget']['settings']['available_countries'])) {
        $countries = array_intersect_key($countries, $instance['widget']['settings']['available_countries']);
        break;
      }
    }
  }

  return $countries;
}
