' . t('Address Field Lookup') . ''; $output .= '

' . t("The module provides an abstracted API for providing address field lookup services. Other modules can define their own services which are configurable from this module.") . '

'; return $output; case 'admin/config/regional/addressfield-lookup': $output = '

' . t("From this page you can see a list of all currently available address field lookup services. You can choose the default lookup service here and configure individual settings for each service. You can also test that your default service is working.") . '

'; return $output; } } /** * Implements hook_menu(). */ function addressfield_lookup_menu() { $items = array(); $items['admin/config/regional/addressfield-lookup'] = array( 'title' => 'Address Field Lookup', 'description' => 'Configure address field lookup services.', 'page callback' => 'drupal_get_form', 'page arguments' => array('addressfield_lookup_services_overview_form'), 'access arguments' => array('administer addressfield lookup services'), 'file' => 'includes/addressfield_lookup.admin.inc', 'weight' => -10, ); $items['admin/config/regional/addressfield-lookup/overview'] = array( 'title' => 'Services', 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['admin/config/regional/addressfield-lookup/settings'] = array( 'title' => 'Settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('addressfield_lookup_settings_form'), 'access arguments' => array('administer addressfield lookup services'), 'file' => 'includes/addressfield_lookup.admin.inc', 'weight' => 1, 'type' => MENU_LOCAL_TASK, ); return $items; } /** * Implements hook_permission(). */ function addressfield_lookup_permission() { return array( 'administer addressfield lookup services' => array( 'title' => t('Administer Address Field Lookup Services'), 'description' => t('Manage the settings for address field lookup services.'), ), ); } /** * Implements hook_theme(). */ function addressfield_lookup_theme() { return array( 'addressfield_lookup_services_overview_form' => array( 'render element' => 'form', ), ); } /** * Implements hook_flush_caches(). */ function addressfield_lookup_flush_caches() { return array( 'cache_addressfield_lookup_addresses', 'cache_addressfield_lookup_address_details', ); } /** * Implements hook_ctools_plugin_directory(). */ function addressfield_lookup_ctools_plugin_directory($module, $plugin) { if ($module == 'addressfield') { return 'plugins/' . $plugin; } } /** * Implements hook_field_widget_form_alter(). * * Use the right form elements identifiers needed for "#limit_validation_errors" * : "Postcode" & "House number or name" only, on "Find address" button element. * * @see https://drupal.org/node/2189327 */ function addressfield_lookup_field_widget_form_alter(&$element, &$form_state, $context) { // Only apply for address field lookup. if ($context['field']['type'] == 'addressfield' && !empty($element['addressfield_lookup_find_address'])) { // Parents array of an addressfield element in a entity form. $array_parents = array( $context['field']['field_name'], $context['langcode'], $context['delta'], ); // Parents array of an addressfield element for the entire built form. $array_parents = array_merge($context['form']['#parents'], $array_parents); // Get the house number and postal code fields. $addressfield_lookup_house_number = array_merge($array_parents, array('addressfield_lookup_house_number')); $addressfield_lookup_postal_code = array_merge($array_parents, array('addressfield_lookup_postal_code')); // Set the limit_validation_errors element. $element['addressfield_lookup_find_address']['#limit_validation_errors'] = array($addressfield_lookup_postal_code, $addressfield_lookup_house_number); } } /** * Returns an array of addressfield lookup services. * * @param bool $reset * Reset the addressfield lookup services static cache. * * @return array $addressfield_lookup_services * An associative array of addressfield lookup service arrays keyed by * the machine name. */ function addressfield_lookup_services($reset = FALSE) { $addressfield_lookup_services = &drupal_static(__FUNCTION__); // Check if we have statically cached services and no reset flag was passed. if (isset($addressfield_lookup_services) && !$reset) { return $addressfield_lookup_services; } // Otherwise we need to get the list of services. $addressfield_lookup_services = array(); // Get the services defined in other modules. foreach (module_implements('addressfield_lookup_service_info') as $module) { foreach (module_invoke($module, 'addressfield_lookup_service_info') as $service_name => $addressfield_lookup_service) { // Add the module name to the service info. $addressfield_lookup_service['module'] = $module; // Add the service machine name to the service info. $addressfield_lookup_service['machine_name'] = $service_name; // Add to the list. $addressfield_lookup_services[$service_name] = $addressfield_lookup_service; } } // Allow other modules to alter the list of services. drupal_alter('addressfield_lookup_service_info', $addressfield_lookup_services); // Loop through the defined services. foreach ($addressfield_lookup_services as $key => $addressfield_lookup_service) { // Ensure that the service class implements the correct interface. if (!in_array('AddressFieldLookupInterface', class_implements($addressfield_lookup_service['class']))) { // The wrong interface has been used so remove the service. unset($addressfield_lookup_services[$key]); watchdog('addressfield_lookup', 'Service %name has been disabled as the defined class does not implement the AddressFieldLookupInterface interface.', array('%name' => $addressfield_lookup_service['name']), WATCHDOG_ERROR); } } return $addressfield_lookup_services; } /** * Get the default address field lookup service. * * @return mixed $addressfield_lookup_default_service * Array containing the default address field service information or FALSE. */ function addressfield_lookup_get_default_service() { $addressfield_lookup_default_service = &drupal_static(__FUNCTION__); // Check if we have a statically cached default service. if (isset($addressfield_lookup_default_service)) { return $addressfield_lookup_default_service; } // If we have no statically cached default service, we need to get it. $addressfield_lookup_services = addressfield_lookup_services(); // Get the default service name. $default_service_name = variable_get('addressfield_lookup_default_service', NULL); // If there is no default set, assume the only service available is default. if (is_null($default_service_name)) { // Assume the 1st service. $default_service_name = key($addressfield_lookup_services); } try { // Check we have a default service, and it can be instantiated. if (isset($addressfield_lookup_services[$default_service_name])) { $addressfield_lookup_default_service = $addressfield_lookup_services[$default_service_name]; // Check for an object factory function. if (isset($addressfield_lookup_default_service['object factory']) && function_exists($addressfield_lookup_default_service['object factory'])) { $addressfield_lookup_default_service_object = call_user_func($addressfield_lookup_default_service['object factory'], $addressfield_lookup_default_service); } else { watchdog('addressfield_lookup', 'No factory function for default Address Lookup service.', array(), WATCHDOG_ERROR); $addressfield_lookup_default_service = FALSE; } } } catch (Exception $e) { $addressfield_lookup_default_service = FALSE; } return $addressfield_lookup_default_service; } /** * Get the instantiated default addressfield lookup service object. * * @param string $country * ISO2 code of the country to get addresses in. * * @return mixed * The instantiated default addressfield lookup service object or FALSE. */ function addressfield_lookup_get_default_service_object($country = NULL) { // Get the default service object. $addressfield_lookup_service = addressfield_lookup_get_default_service(); // Check we have a valid service. if (!is_array($addressfield_lookup_service)) { return FALSE; } // Try to instantiate the service object. if ($addressfield_lookup_service_object = call_user_func($addressfield_lookup_service['object factory'], $addressfield_lookup_service, $country)) { return $addressfield_lookup_service_object; } // There was no service that could be instantiated. return FALSE; } /** * Peform an address lookup using the current default address lookup service. * * @param string $search_term * Search term to lookup addresses for. * @param string $country * ISO2 code of the country to get addresses in. * @param bool $reset * Force a reset of the addresses cache for this search term. * * @return array $addresses * Array of search results in the format: * id - Address ID * street - Street (Address Line 1) * place - Remainder of address. */ function addressfield_lookup_get_addresses($search_term, $country = NULL, $reset = FALSE) { // Bail out early if we have no search term. if (empty($search_term)) { return FALSE; } $addresses = &drupal_static(__FUNCTION__); // Return the statically cached results if present. if (isset($addresses[$search_term]) && !$reset) { return $addresses[$search_term]; } // If there are no statically cached results, do the search. $addresses = array(); // Get the default service. $addressfield_lookup_default_service = addressfield_lookup_get_default_service(); // Bail out if there is no default service. if (!isset($addressfield_lookup_default_service)) { return FALSE; } // Build the cache ID we'll use for this search. Format is // service_name:hashed_search_term. $addresses_cache_id = $addressfield_lookup_default_service['machine_name'] . ':' . drupal_hash_base64($search_term); // Append the country code to the cache ID if present. if (!empty($country)) { $addresses_cache_id .= ':' . $country; } // Allow the default service module to alter the cache ID. if ($updated_cache_id = module_invoke($addressfield_lookup_default_service['module'], 'addressfield_lookup_get_addresses_cache_id_update', $addresses_cache_id, $country)) { $addresses_cache_id = $updated_cache_id; } // Check the cache bin for the address details. if (($cached_addresses = cache_get($addresses_cache_id, 'cache_addressfield_lookup_addresses')) && !$reset) { // There is cached data so return it. $addresses[$search_term] = $cached_addresses->data; return $addresses[$search_term]; } // There is no static or Drupal cache data. Do the lookup. try { // Get the default service object. if ($addressfield_lookup_service_object = addressfield_lookup_get_default_service_object($country)) { // Do the search. if ($lookup_results = $addressfield_lookup_service_object->lookup($search_term)) { $addresses[$search_term] = $lookup_results; // Cache the addresses. $cache_length = REQUEST_TIME + variable_get('addressfield_lookup_cache_length', ADDRESSFIELD_LOOKUP_CACHE_LENGTH); cache_set($addresses_cache_id, $addresses[$search_term], 'cache_addressfield_lookup_addresses', $cache_length); } else { $addresses[$search_term] = array(); } return $addresses[$search_term]; } else { // No service could be instantiated so bail out. return FALSE; } } catch (Exception $e) { // Failed to get addresses due to an exception, better log it. watchdog('addressfield_lookup', 'Address lookup failed. Reason: @reason', array('@reason' => $e->getMessage()), WATCHDOG_ERROR); return FALSE; } } /** * Get the full details for an address using the default address lookup service. * * @param mixed $address_id * ID of the address to get details for. * @param bool $reset * Force a reset of the address details cache for this address ID. * * @return mixed $address_details * Array of details for the given address in the format: * id - Address ID * sub_premise - The sub_premise of this address * premise - The premise of this address. (i.e. Apartment / Suite number). * thoroughfare - The thoroughfare of this address. (i.e. Street address). * dependent_locality - The dependent locality of this address. * locality - The locality of this address. (i.e. City). * postal_code - The postal code of this address. * administrative_area - The administrative area of this address. * (i.e. State/Province) * organisation_name - Contents of a primary OrganisationName element * in the xNL XML. * * Or FALSE. */ function addressfield_lookup_get_address_details($address_id, $reset = FALSE) { // Bail out early if we have no address ID. if (empty($address_id)) { return FALSE; } $address_details = &drupal_static(__FUNCTION__); // Return the statically cached details if present. if (isset($address_details[$address_id]) && !$reset) { return $address_details[$address_id]; } // If there are no statically details do the retrieval. // Get the default service. $addressfield_lookup_default_service = addressfield_lookup_get_default_service(); // Bail out if there is no default service. if (!isset($addressfield_lookup_default_service)) { return FALSE; } // Build the cache ID we'll use for this retrieval. Format is // service_name:address_id. $address_details_cache_id = $addressfield_lookup_default_service['machine_name'] . ':' . $address_id; // Check the cache bin for the address details. if (($cached_address_details = cache_get($address_details_cache_id, 'cache_addressfield_lookup_address_details')) && !$reset) { // There is cached data so return it. $address_details[$address_id] = $cached_address_details->data; return $address_details[$address_id]; } $address_details = array(); // There is no static or Drupal cache data. Do the address retrieval. try { // Get the default service object. if ($addressfield_lookup_service_object = addressfield_lookup_get_default_service_object()) { // Get the address details from the service. $address_details[$address_id] = $addressfield_lookup_service_object->getAddressDetails($address_id); // Cache the address details. $cache_length = REQUEST_TIME + variable_get('addressfield_lookup_cache_length', ADDRESSFIELD_LOOKUP_CACHE_LENGTH); cache_set($address_details_cache_id, $address_details[$address_id], 'cache_addressfield_lookup_address_details', $cache_length); return $address_details[$address_id]; } else { // No service could be instantiated so bail out. return FALSE; } } catch (Exception $e) { // Failed to get address details due to an exception, better log it. watchdog('addressfield_lookup', 'Address details retrieval failed. Reason: @reason', array('@reason' => $e->getMessage()), WATCHDOG_ERROR); return FALSE; } } /** * Allow the default address lookup service to make alterations to the format. * * @param array $format * The address format being generated. * @param array $address * The address this format is generated for. */ function addressfield_lookup_get_format_updates(array &$format, array $address) { // Get the default service. $addressfield_lookup_default_service = addressfield_lookup_get_default_service(); // Check there is a default service. if (is_array($addressfield_lookup_default_service)) { // Invoke the update hook (if it exists) in the default service module. if ($format_updates = module_invoke($addressfield_lookup_default_service['module'], 'addressfield_lookup_format_update', $format, $address)) { $format = $format_updates; } } } /** * A set of default address values. */ function _addressfield_lookup_default_values(&$address) { $address['thoroughfare'] = ''; $address['sub_premise'] = ''; $address['premise'] = ''; $address['dependent_locality'] = ''; $address['administrative_area'] = ''; $address['organisation_name'] = ''; $address['locality'] = ''; } /** * Prepare an array of address options for use in a Drupal select element. * * @param array $addresses * Array of addresses from an address lookup, in the format: * id - Address ID * street - Street (Address Line 1) * place - Remainder of address. * * @return array $address_options * Array of address options for use in a Drupal select element, keyed by id. */ function _addressfield_lookup_prepare_options(array $addresses) { $address_options = array(); // Prepare the options. if (!empty($addresses)) { foreach ($addresses as $address) { $address_options[$address['id']] = $address['street']; if (!empty($address['place'])) { $address_options[$address['id']] .= ', ' . $address['place']; } } } return $address_options; } /** * Populate addressfield with the selected postal code address data. */ function _addressfield_lookup_populate_addressfield($address_id, $element, &$form_state) { // Ensure we have a valid address ID. if (!empty($address_id)) { // Get the address element from the form. $array_parents = array_slice($element['#parents'], 0, -1); $address = drupal_array_get_nested_value($form_state['values'], $array_parents); // Get the full details for this address ID. $address_details = addressfield_lookup_get_address_details($address_id); // Check we have some details. if (!empty($address_details)) { // Reset the address to the default values. _addressfield_lookup_default_values($address); // Set the retrieved details on the address. $address = array_merge($address, $address_details); // Set the address ID value on the form. $address['addressfield_lookup_addresses_select'] = $address_id; // Set the mode based on presence of the house number. if (!empty($address['addressfield_lookup_house_number'])) { $address['addressfield_lookup_mode'] = 'address_selected'; } else { $address['addressfield_lookup_mode'] = 'address_selection'; } // Update the form state, where possible. if (isset($address['element_key']) && isset($form_state['addressfield'][$address['element_key']])) { $form_state['addressfield'][$address['element_key']] = array_diff_key($address, array('element_key' => '')); } // Set the address values on the form. foreach ($address as $key => $value) { // Update the form state for addressfield. // Note: form_state['input'] must be updated so that // addressfield_lookup_addressfield_format_generate() has correct // information during the rebuild. drupal_array_set_nested_value($form_state['values'], array_merge($array_parents, array($key)), $value, TRUE); drupal_array_set_nested_value($form_state['input'], array_merge($array_parents, array($key)), $value, TRUE); } } } } /** * Check if a country address format has a usable postcode field. * * @param array $format * The country address format being checked. * * @return bool * Does the country format have a usable postcode field. */ function _addressfield_lookup_country_format_has_postal_code(array $format) { return !empty($format['locality_block']['postal_code']) && $format['locality_block']['postal_code']['#access'] === TRUE; }