TRUE, ); return $plugins; } /** * Implements hook_views_api(). */ function addressfield_views_api() { return array( 'api' => 3, 'path' => drupal_get_path('module', 'addressfield') . '/views', ); } /** * 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(); $format['#handlers'] = $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'; $format['#required'] = FALSE; } 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'], $format['#required']); return $format; } function _addressfield_process_format_form(&$format, $address, $required) { foreach (element_children($format) as $key) { $child = &$format[$key]; // Automatically expand elements that matches one of the field 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'))) { // Set the type. if (isset($child['#widget_type'])) { $child['#type'] = $child['#widget_type']; } else { if (isset($child['#options'])) { $child['#type'] = 'select'; $child['#size'] = 0; } else { $child['#type'] = 'textfield'; } } if (!$required) { unset($child['#required']); } $child['#default_value'] = $address[$key]; } // Recurse through the child. _addressfield_process_format_form($child, $address, $required); } } /** * 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 matches one of the field 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($child['#options'][$address[$key]])) { $child['#children'] = check_plain($child['#options'][$address[$key]]); } else { $child['#children'] = check_plain($address[$key]); } // 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']); if (strlen($element['#children']) > 0) { $output = '<' . $element['#tag'] . drupal_attributes($element['#attributes']) . '>'; $output .= $element['#children']; $output .= '"; 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. */ function addressfield_default_values($available_countries = NULL) { if (!isset($available_countries)) { $available_countries = _addressfield_country_options_list(); } // Use the default country of the site if possible. $default_country = variable_get('site_default_country', NULL); // If the default country is undefined or not in the list of available countries, // just fallback to the first country in the list. if (!$default_country || !isset($available_countries[$default_country])) { $default_country = key($available_countries); } return 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' => '', ); } /** * 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_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(), '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['format_handlers'] = array( '#type' => 'checkboxes', '#title' => t('Format handlers'), '#options' => addressfield_format_plugins_options(), '#default_value' => $settings['format_handlers'], ); } return $form; } /** * Implements hook_field_widget_form() */ function addressfield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $settings = $instance['widget']['settings']; // Generate a specific key used to identify this element to restore a default // value upon AJAX submission regardless of where this element is in the // $form array. $element_key = implode('|', array($element['#entity_type'], $element['#bundle'], $element['#field_name'], $element['#language'], $element['#delta'])); // Store the key in the element array as a value so it can be easily retrieved // in context in the $form_state['values'] array in the element validator. $element['element_key'] = array( '#type' => 'value', '#value' => $element_key, ); // Get the default address used to build the widget form elements, looking // first in the form state, then in the stored value for the field, and then // in the default values of the instance. $address = array(); if (!empty($form_state['addressfield'][$element_key])) { // Use the value from the form_state if available. $address = $form_state['addressfield'][$element_key]; } elseif (!empty($items[$delta]['country'])) { // Else use the saved value for the field. $address = $items[$delta]; } else { // Otherwise use the instance default. $address = (array) $instance['default_value'][0]; } // 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 to provide a value for every expected array key. $address += addressfield_default_values($countries); // 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'; // Generate the address form. $context = array( 'mode' => 'form', 'field' => $field, 'instance' => $instance, 'langcode' => $langcode, 'delta' => $delta, ); $element += addressfield_generate($address, $settings['format_handlers'], $context); // Mark the form element as required if necessary. $element['#required'] = $delta == 0 && $instance['required']; } return $element; } /** * Element validate callback: rebuilds the form on country change and stores the * current address value in the $form_state for retrieval on rebuild. */ function addressfield_standard_country_validate($element, &$form_state) { // If the country was changed, rebuild the form. if ($element['#default_value'] != $element['#value']) { $form_state['rebuild'] = TRUE; } $parents = $element['#parents']; array_pop($parents); // Search through the form values to find the current address. $address = drupal_array_get_nested_value($form_state['values'], $parents); // Store the present address values in the form state for retrieval by the // widget form regardless of where the widget sits in the $form array. $form_state['addressfield'][$address['element_key']] = array_diff_key($address, array('element_key' => '')); } /** * 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'); // 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']; $summary = ''; 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'; $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() { // We can't call addressfield_default_values() directly, because it has an // optional array argument that will receive an invalid value when Entity API // tries to call it directly with its usual $property_name and $context // arguments. return addressfield_default_values(); } /** * 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)'), ), ); // 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; } /** * Wraps country_get_list() for use as an Entity API options list. */ function _addressfield_country_options_list($field = NULL, $instance = NULL) { // Necessary for country_get_list(). 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']); } else { // This instance allow all the countries. $countries = country_get_list(); break; } } } return $countries; }