diff --git a/addressfield.api.php b/addressfield.api.php index b52b950..629e522 100644 --- a/addressfield.api.php +++ b/addressfield.api.php @@ -6,6 +6,21 @@ */ /** + * Info hook defining available address formats. + */ +function hook_addressfield_format_info() { + return array( + 'address' => array( + 'title' => t('Address form (country-specific)'), + 'format callback' => 'addressfield_format_address_generate', + 'type' => 'address', + 'weight' => -100, + 'file' => 'formats/address.inc', + ), + ); +} + +/** * Format generation callback. * * @param $format @@ -30,6 +45,36 @@ function CALLBACK_addressfield_format_callback(&$format, $address, $context = ar } /** + * Allow other modules to alter at run time which handlers to use. + * Useful when you want to conditionally add/remove handlers based on data + * stored on an entity. + * + * @param &$handlers + * Array of handlers that can be used. Use a FALSE value to indicate it + * shouldn't be used and the name to indicate it should be used to generate + * the address. + * @param $address + * The address data used for the form + * @param $context + * An array of context arguments: + * - 'mode': can be either 'form' or 'render' + * - (optional) 'field': when generated for a field, the field + * - (optional) 'instance': when generated for a field, the field instance + * - (optional) 'langcode': when generated for a field, the langcode + * this field is being rendered in. + * - (optional) 'delta': when generated for a field, the delta of the + * currently handled address. + * - (optional) 'entity': The entity that the form/display is created for + * - (optional) 'entity_type': The entity_type of the entity provided + * - (optional) 'entity_types': If an entity_type can't be established, + * an array of types is passed instead. + * + */ +function hook_addressfield_handlers_alter(&$handlers, $address, $context) { + // No example. +} + +/** * Allows modules to alter the default values for an address field. * * @param $default_values diff --git a/addressfield.info b/addressfield.info index af5d068..5dd3993 100644 --- a/addressfield.info +++ b/addressfield.info @@ -7,5 +7,7 @@ dependencies[] = ctools files[] = addressfield.migrate.inc files[] = views/addressfield_views_handler_field_administrative_area.inc +files[] = views/addressfield_views_handler_argument_countryname.inc files[] = views/addressfield_views_handler_field_country.inc files[] = views/addressfield_views_handler_filter_country.inc +files[] = views/addressfield_views_handler_filter_country_admin.inc diff --git a/addressfield.module b/addressfield.module index 68b45d9..2e7c835 100644 --- a/addressfield.module +++ b/addressfield.module @@ -53,6 +53,45 @@ function addressfield_module_implements_alter(&$implementations, $hook) { } /** + * Return a list of address fields. + * + * @param string $entity_type + * The entity type to get the addressfields for. If empty, all + * addressfields are returned. + * + * @return array + * An array of addressfield fields. + */ +function addressfield_get_address_fields($entity_type = '') { + $fields = &drupal_static(__FUNCTION__ . '_' . $entity_type); + if (isset($fields)) { + return $fields; + } + + // Get all field definitions in advance to save individual function calls to field_info_field(). + $fields = field_info_fields(); + foreach (field_info_instances($entity_type) as $field_name => $instance) { + $field = $fields[$field_name]; + if (!isset($field['bundles'][$entity_type])) { + unset($fields[$field_name]); + } + } + + // Get all addressfield fields. + $fields = array_filter(field_info_field_map(), 'addressfield_field_map_filter'); + if (!empty($fields)) { + // Keep only the fields present on this entity_type to spare some + // iterations in the calling function. + 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(). @@ -62,7 +101,93 @@ function addressfield_field_map_filter($field) { } /** - * Get the list of format plugins. + * Implements hook_addressfield_format_info(). + */ +function addressfield_addressfield_format_info() { + return array( + 'address' => array( + 'title' => t('Show the address form (country-specific)'), + 'format callback' => 'addressfield_format_address_generate', + 'type' => 'address', + 'weight' => -100, + 'file' => 'formats/address.inc', + ), + 'address_hide_country' => array( + 'title' => t('Hide country field (when only one is available)'), + 'format callback' => 'addressfield_format_address_hide_country', + 'type' => 'address', + 'weight' => -90, + 'file' => 'formats/address-hide-country.inc', + ), + 'address_hide_postal_code' => array( + 'title' => t('Hide postal code field (Zip)'), + 'format callback' => 'addressfield_format_address_hide_postal_code', + 'type' => 'address', + 'weight' => -85, + 'file' => 'formats/address-hide-potal-code.inc', + ), + 'address_hide_administrative_area' => array( + 'title' => t('Hide administrative area field (State/Province)'), + 'format callback' => 'addressfield_format_address_hide_administrative_area', + 'type' => 'address', + 'weight' => -84, + 'file' => 'formats/address-hide-administrative-area.inc', + ), + 'address_hide_locality' => array( + 'title' => t('Hide locality field (City)'), + 'format callback' => 'addressfield_format_address_hide_locality', + 'type' => 'address', + 'weight' => -84, + 'file' => 'formats/address-hide-locality.inc', + ), + 'address_hide_street' => array( + 'title' => t('Hide 1st street address field (Address/locality)'), + 'format callback' => 'addressfield_format_address_hide_street', + 'type' => 'address', + 'weight' => -83, + 'file' => 'formats/address-hide-street.inc', + ), + 'address_hide_street_two' => array( + 'title' => t('Hide 2nd street address field (Address 2/premise)'), + 'format callback' => 'addressfield_format_address_hide_premise', + 'type' => 'address', + 'weight' => -82, + 'file' => 'formats/address-hide-premise.inc', + ), + + 'name_oneline' => array( + 'title' => t('Show a Full Name field'), + 'format callback' => 'addressfield_format_name_oneline_generate', + 'type' => 'name', + 'weight' => 0, + 'file' => 'formats/name-oneline.inc', + ), + 'name_full' => array( + 'title' => t('Show First & Last name fields)'), + 'format callback' => 'addressfield_format_name_full_generate', + 'type' => 'name', + 'weight' => 0, + 'file' => 'formats/name-full.inc', + ), + 'organisation' => array( + 'title' => t('Show Company/Organization field'), + 'format callback' => 'addressfield_format_organisation_generate', + 'type' => 'organisation', + 'weight' => -10, + 'file' => 'formats/organisation.inc', + ), + 'address_optional' => array( + 'title' => t('Make all fields optional (No validation - unsuitable for postal purposes)'), + 'format callback' => 'addressfield_format_address_optional', + 'type' => 'address', + 'weight' => 100, + 'file' => 'formats/address-optional.inc', + ), + ); +} + +/** + * Get the sorted list of format plugins. */ function addressfield_format_plugins() { ctools_include('plugins'); @@ -289,10 +414,81 @@ function addressfield_theme() { $hooks['addressfield_container'] = array( 'render element' => 'element', ); + $hooks['addressfield_address'] = array( + 'variables' => array('country' => NULL, + 'administrative_area' => NULL, + 'sub_administrative_area' => NULL, + 'locality' => NULL, + 'dependent_locality' => NULL, + 'postal_code' => NULL, + 'thoroughfare' => NULL, + 'premise' => NULL, + 'sub_premise' => NULL, + 'organisation_name' => NULL, + 'name_line' => NULL, + 'first_name' => NULL, + 'last_name' => NULL, + 'data' => NULL, + 'entity_type' => NULL, + 'entity' => NULL, + 'field' => NULL, + 'instance' => NULL, + 'langcode' => NULL, + 'display' => NULL, + ), + ); return $hooks; } /** + * Theme function for rendering an address. + * + * This is an alternative to Addressfield's plugin system for formatters. To + * use this theme function with fields, you must select the "Theme function" + * formatter for the field's "Manage Display" settings. + * + * @param $variables + * An array containing the addressfield sub-fields. + */ +function theme_addressfield_address($variables) { + // Make each sub-field available as its own variable. + extract($variables); + + $output = ''; + // Render street block + if ($thoroughfare || $premise) { + $output .= '
', + '#suffix' => '
', + '#markup' => t('Due to the way country abbreviations are mapped to country names, this filter assumes that the value passed in via the URL is written in the same language as the request that is being executed.'), + ); + } + + /** + * Add pre-render function so views doesn't explode. + * @todo find out why this is needed and what it should be doing. + */ + function pre_render() { + // Do nothing? + } + + /** + * {@inheritdoc} + */ + function query($group_by = FALSE) { + // Transform dashes in arguments. + $argument = $this->argument; + if (!empty($this->options['transform_dash'])) { + $argument = strtr($argument, '-', ' '); + } + + // Look up countries by name. + include_once DRUPAL_ROOT . '/includes/locale.inc'; + $countries = country_get_list(); + $country_by_name = array_flip($countries); + if (isset($country_by_name[$this->argument])) { + $this->argument = $country_by_name[$this->argument]; + } + + return parent::query(); + } +} diff --git a/views/addressfield_views_handler_filter_country.inc b/views/addressfield_views_handler_filter_country.inc index d1b11c1..f77a728 100644 --- a/views/addressfield_views_handler_filter_country.inc +++ b/views/addressfield_views_handler_filter_country.inc @@ -1,5 +1,7 @@ value_title = t('Country'); diff --git a/views/addressfield_views_handler_filter_country_admin.inc b/views/addressfield_views_handler_filter_country_admin.inc new file mode 100644 index 0000000..fdfdec1 --- /dev/null +++ b/views/addressfield_views_handler_filter_country_admin.inc @@ -0,0 +1,193 @@ +view = &$view; + + $this->country_field = $this->definition['additional fields'][0]; + $this->admin_field = $this->definition['additional fields'][1]; + } + + /** + * Override value options. + */ + function get_value_options() { + $this->value_title = t('Country'); + $field = field_info_field($this->definition['field_name']); + $this->value_options = _addressfield_country_options_list($field); + } + + /** + * Override value_form() to provide two selects: country & admin area. + * + * This should be overridden by all child classes and it must + * define $form['value'] + * + * @see options_form() + */ + function value_form(&$form, &$form_state) { + $path = drupal_get_path('module', 'addressfield'); + $form['value'] = array( + '#type' => 'container', + '#attached' => array( + 'css' => array($path . '/css/addressfield-views.css'), + 'js' => array($path . '/js/addressfield-views.js'), + ), + ); + + // Since multiple values is not allowed... + $filters = explode('-', $this->value[0]); + $default_country = (isset($filters[0])) ? $filters[0] : array(); + $default_admin_area = (isset($filters[1])) ? $filters[1] : array(); + + $id = $this->options['expose']['identifier']; + $field = field_info_field($this->definition['field_name']); + $countries = _addressfield_country_options_list($field); + $form['value'][$id . '-country'] = array( + '#type' => 'select', + '#title' => t('Country'), + '#options' => array_merge(array('all' => 'All'), $countries), + '#default_value' => $default_country, + '#attributes' => array('class' => array('addressfield-views-country')), + ); + + $admin_areas = array('' => t('Please select')); + module_load_include('inc', 'addressfield', 'addressfield.administrative_areas'); + foreach ($countries as $code => $name) { + if ($code != 'All') { + $areas = array('all' => t('- Any -')); + $all_areas = addressfield_get_administrative_areas($code); + // Limit options to values currently in use. + $result = db_query("SELECT DISTINCT($this->admin_field) AS administrative_area FROM {$this->definition['table']} WHERE $this->country_field = :code ORDER BY $this->admin_field", array(':code' => $code))->fetchAllKeyed(0,0); + if (!empty($result)) { + foreach ($result as $record) { + if ($record != '' && isset($all_areas[$record])) { + $areas[$record] = $all_areas[$record]; + } + } + } + if (is_array($areas)) { + $admin_areas[$code] = $areas; + } + } + } + $form['value'][$id . '-administrative_area'] = array( + '#type' => 'select', + '#title' => t('State / Province'), + '#options' => $admin_areas, + '#default_value' => $default_admin_area, + '#attributes' => array('class' => array('addressfield-views-admin-area')), + ); + } + + /** + * Available operators. + */ + function operators() { + return array( + 'in' => array( + 'title' => t('Is one of'), + 'short' => t('in'), + 'short_single' => t('='), + 'method' => 'op_simple', + 'values' => 1, + ), + 'not in' => array( + 'title' => t('Is not one of'), + 'short' => t('not in'), + 'short_single' => t('<>'), + 'method' => 'op_simple', + 'values' => 1, + ), + ); + } + + /** + * Modify the exposed form settings. + */ + function expose_form(&$form, &$form_state) { + parent::expose_form($form, $form_state); + unset($form['expose']['reduce']); + unset($form['expose']['multiple']); + } + + /** + * Sets value based on input. + */ + function accept_exposed_input($input) { + if (empty($this->options['exposed'])) { + return TRUE; + } + + if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) { + $this->operator = $input[$this->options['expose']['operator_id']]; + } + + if (!empty($this->options['expose']['identifier'])) { + $value = ''; + if (isset($input[$this->options['expose']['identifier'] . '-country'])) { + $value .= $input[$this->options['expose']['identifier'] . '-country']; + } + if (isset($input[$this->options['expose']['identifier'] . '-administrative_area'])) { + if (!is_array($input[$this->options['expose']['identifier'] . '-administrative_area'])) { + $value .= '-' . $input[$this->options['expose']['identifier'] . '-administrative_area']; + } + else{ + $value .= '-' . implode('-', $input[$this->options['expose']['identifier'] . '-administrative_area']); + } + } + + if (isset($value)) { + $this->value = $value; + } + else { + return FALSE; + } + } + + return TRUE; + } + + /** + * Perform any necessary changes to the form values prior to storage. + * There is no need for this function to actually store the data. + */ + function value_submit($form, &$form_state) { + $value = $form['value']['location-country']['#value']; + if (isset($form['value']['location-administrative_area']['#value']) && $form['value']['location-administrative_area']['#value'] != '') { + $value .= '-' . $form['value']['location-administrative_area']['#value']; + } + $form_state['values']['options']['value'] = array($value); + } + + /** + * Query the DB based on value. + */ + function query() { + // Since multiple values is not allowed... + $filters = explode('-', $this->value); + + $this->ensure_my_table(); + if (strtolower($filters[0]) != 'all') { + $this->query->add_where($this->options['group'], "$this->table_alias.$this->country_field", array($filters[0]), $this->operator); + } + + // @todo find out why this 'Array' hack is necessary. + if (isset($filters[1]) && !empty($filters[1]) && ($filters[1] != 'Array') && strtolower($filters[1]) != 'all') { + $this->query->add_where($this->options['group'], "$this->table_alias.$this->admin_field", array($filters[1]), $this->operator); + } + } +}