You are here

webform_addressfield_extra.module in Webform Addressfield Extra 7

Main components.

File

webform_addressfield_extra.module
View source
<?php

/**
 * @file
 * Main components.
 */

/**
 * Implements hook_libraries_info().
 */
function webform_addressfield_extra_libraries_info() {
  $libraries['geocomplete'] = array(
    'name' => 'Geocomplete',
    'vendor url' => 'http://ubilabs.github.io/geocomplete/',
    'download url' => 'https://github.com/ubilabs/geocomplete/archive/master.zip',
    'version arguments' => array(
      'file' => 'jquery.geocomplete.js',
      'pattern' => '/V\\s+([0-9\\.\\ \\-]+)/',
      'lines' => 5,
    ),
    'files' => array(
      'js' => array(
        'jquery.geocomplete.js' => array(
          'type' => 'file',
          'weight' => 2,
        ),
      ),
    ),
    'variants' => array(
      'minified' => array(
        'files' => array(
          'js' => array(
            'jquery.geocomplete.min.js',
          ),
        ),
        'variant arguments' => array(
          'variant' => 'minified',
        ),
      ),
    ),
  );
  return $libraries;
}

/**
 * Implementation of hook_webform_component_defaults_alter().
 */
function webform_addressfield_extra_webform_component_defaults_alter(&$defaults, $type) {
  if ($type == 'addressfield') {
    $defaults['extra']['allow_no_postal_code'] = TRUE;
    $defaults['extra']['autocomplete'] = TRUE;
    $defaults['extra']['api_key'] = '';
    $defaults['extra']['placeholder'] = '';
  }
}

/**
 * Implementation of hook_form_FORM_ID_alter().
 */
function webform_addressfield_extra_form_webform_component_edit_form_alter(&$form, $form_state) {
  $component = $form_state['build_info']['args'][1];
  if ($component['type'] == 'addressfield') {
    $form['extra']['format_handlers']['allow_no_postal_code'] = array(
      '#type' => 'checkbox',
      '#title' => t('Make postal code optional'),
      '#default_value' => $component['extra']['allow_no_postal_code'],
      '#description' => t('Google places does not return a postal code for all addresses.  Check this to allow addresses without postal codes to validate'),
      '#weight' => 20,
      '#parents' => array(
        'extra',
        'allow_no_postal_code',
      ),
    );
    $form['extra']['autocomplete'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use autocompletion'),
      '#description' => t('Enable autocompletion functionnality on this address component.'),
      '#default_value' => $component['extra']['autocomplete'],
    );
    $form['extra']['autocomplete_container'] = array(
      '#type' => 'container',
      '#states' => array(
        "visible" => array(
          "input[name='extra[autocomplete]']" => array(
            "checked" => TRUE,
          ),
        ),
      ),
    );
    $form['extra']['autocomplete_container']['api_key'] = array(
      '#type' => 'textfield',
      '#title' => t('API KEY'),
      '#default_value' => $component['extra']['api_key'],
      '#description' => t('Optional:you can add here your Google Maps API Key.'),
      '#weight' => 1,
      '#parents' => array(
        'extra',
        'api_key',
      ),
    );
    $form['display']['placeholder'] = array(
      '#type' => 'textfield',
      '#title' => t('Placeholder'),
      '#default_value' => $component['extra']['placeholder'],
      '#description' => t('The placeholder will be shown in the field until the user starts entering a value.'),
      '#weight' => 1,
      '#parents' => array(
        'extra',
        'placeholder',
      ),
    );
  }
}

/**
 * Implementation of hook_webform_component_render_alter().
 */
function webform_addressfield_extra_webform_component_render_alter(&$element, &$component, $filter = TRUE) {

  // Let's override the addressfield with our custom autocompletion.
  if ($component['type'] == 'addressfield' && $component['extra']['autocomplete']) {

    // ---
    // 1. Creates a new element.
    $new_element = array(
      '#attributes' => $component['extra']['attributes'],
      '#theme_wrappers' => array(
        'webform_element',
      ),
      '#required' => $component['required'],
      '#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
      '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
      '#attributes' => $component['extra']['attributes'],
      '#description' => $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
      //Either one being true will could as required...because webform changed in 4.x-alpha8
      '#weight' => $component['weight'],
      '#webform_component' => $element['#webform_component'],
      '#type' => 'fieldset',
      '#element_validate' => array(),
    );
    if (isset($element['#wrapper_attributes'])) {
      $new_element['#wrapper_attributes'] = $element['#wrapper_attributes'];
    }

    // ---
    // 2. Update addressfield according to our custom requirements.
    // Add data-geo data to the address fields.
    $data_atributes = array(
      'organisation_block' => array(
        'organisation_name' => array(
          'geo' => 'organisation_name',
          'required' => $component['required'],
        ),
      ),
      'street_block' => array(
        'thoroughfare' => array(
          'geo' => 'subpremise street_number route',
          'required' => $component['required'],
        ),
        'premise' => array(
          'geo' => 'premise subpremise',
          'required' => FALSE,
        ),
      ),
      'locality_block' => array(
        'locality' => array(
          'geo' => 'administrative_area_level_3 postal_town locality',
          'required' => $component['required'],
        ),
        'dependent_locality' => array(
          'geo' => 'sub_locality',
          'required' => $component['required'],
        ),
        'administrative_area' => array(
          'geo' => 'administrative_area_level_1_short administrative_area_level_2',
          'required' => $component['required'],
        ),
        'postal_code' => array(
          'geo' => 'postal_code_prefix postal_code',
          'required' => $component['required'],
        ),
      ),
      'country' => array(
        'geo' => 'country_short',
        'required' => $component['required'],
      ),
      'coordinate_block' => array(
        'lat' => array(
          'geo' => 'lat',
          'required' => FALSE,
        ),
        'lng' => array(
          'geo' => 'lng',
          'required' => FALSE,
        ),
      ),
    );

    // Set up container for lat and long.
    $element['coordinate_block'] = array(
      '#type' => 'addressfield_container',
      '#title' => 'Coordinates',
    );
    $element['coordinate_block']['#attributes']['class'] = array(
      'element-invisible',
      'coordinate-block',
    );
    $element['coordinate_block']['lat'] = array(
      '#type' => 'textfield',
      '#title' => 'Latitude',
    );
    $element['coordinate_block']['lng'] = array(
      '#type' => 'textfield',
      '#title' => 'Longditude',
    );
    foreach ($data_atributes as $key => $data) {
      if (isset($data['geo']) && isset($element[$key])) {
        $element[$key]['#attributes']['data-geo'] = $data['geo'];
        $element[$key]['#attributes']['data-required'] = isset($element[$key]['#required']) ? $element[$key]['#required'] : FALSE;
        $element[$key]['#attributes']['placeholder'] = $element[$key]['#title'];
        $element[$key]['#title_display'] = 'invisible';
        $element[$key]['#needs_validation'] = FALSE;

        //$element[$key]['#required'] = $data['required'];
        continue;
      }
      foreach ($data as $field => $attributes) {
        if (isset($element[$key][$field])) {
          $element[$key][$field]['#attributes']['data-geo'] = $attributes['geo'];
          $element[$key][$field]['#attributes']['data-required'] = isset($element[$key][$field]['#required']) ? $element[$key][$field]['#required'] : FALSE;
          $element[$key][$field]['#attributes']['placeholder'] = $element[$key][$field]['#title'];
          $element[$key][$field]['#title_display'] = 'invisible';
          $element[$key][$field]['#needs_validation'] = FALSE;

          //$element[$key][$field]['#required'] = $attributes['required'];
        }
      }
    }
    unset($element['#title']);
    unset($element['#description']);
    unset($element['#translatable']);
    unset($element['#theme_wrappers']);
    unset($element['#webform_component']);

    // Move country after the city and coordinates after country.
    $element['country']['#weight'] = $element['locality_block']['#weight'] + 50;
    $element['coordinate_block']['#weight'] = $element['country']['#weight'] + 50;

    // ---
    // 3. Move address field container on the new element.
    $element['#prefix'] = '<div class="webform-addressfield-extra-wrapper" id="' . $element['#wrapper_id'] . '">';
    $new_element['addressfield'] = $element;

    // If the component has addressfield_extra address value format,
    // we need to regenerate and preprocess some parts of it.
    if (isset($new_element['addressfield']['#address']['addressfield'])) {
      unset($new_element['addressfield']['street_block']['#access']);
      unset($new_element['addressfield']['locality_block']['#access']);

      // Generate the address form.
      $context = array(
        'mode' => 'form',
        'delta' => 0,
        'instance' => array(),
      );
      if ($component['required']) {
        $context['instance']['required'] = $component['required'];
      }
      $handlers = !empty($component['extra']['format_handlers']) ? $component['extra']['format_handlers'] : array(
        'address',
      );
      $new_element['addressfield'] += addressfield_generate($new_element['addressfield']['#address']['addressfield'], $handlers, $context);
      $new_element['addressfield']['#process'] = array(
        '_webform_addressfield_extra_process_format_form',
      );
    }

    // ----
    // 4. Prepare for autocompletion.
    // Toggle postal code to not be mandatory as google places will sometimes return values without postal/zip if multiple codes apply to a given address.
    if (isset($component['extra']['allow_no_postal_code'])) {
      if ($component['extra']['allow_no_postal_code'] && isset($new_element['addressfield']['locality_block']['postal_code'])) {
        $new_element['addressfield']['locality_block']['postal_code']['#required'] = FALSE;
        $new_element['addressfield']['locality_block']['postal_code']['#attributes']['data-required'] = FALSE;
      }
    }

    // Determine if we need to restrict lookup to a single country.
    $restrict_country = count($component['extra']['available_countries']) == 1 ? TRUE : FALSE;

    // Add the autocompletion field.
    $new_element['autocompletion_block'] = array(
      '#type' => 'textfield',
      '#weight' => $element['#weight'] - 100,
      '#default_value' => isset($element['#address']['autocompletion_block']) ? $element['#address']['autocompletion_block'] : NULL,
      '#attributes' => array(
        'placeholder' => $component['extra']['placeholder'],
        'class' => array(
          'webform-addressfield-autocomplete-input',
        ),
        'autocomplete' => 'off',
        // Add country code data only if a single country is found in the available countries array.
        // This is done becasue our geocomplete call will only filter by a single country and if we only
        // have one country available we'll always want to filter by it.
        'data-restrictCountry' => $restrict_country ? implode(',', $component['extra']['available_countries']) : '',
      ),
      '#translatable' => array(
        'title',
        'description',
      ),
      '#attached' => array(
        'libraries_load' => array(
          array(
            'geocomplete',
          ),
        ),
        'js' => array(
          _webform_addressfield_extra_geocomplete_url($component['extra']['api_key']) => array(
            'type' => 'external',
            'weight' => 1,
          ),
          array(
            'data' => array(
              'webformAddressfieldExtra' => array(
                'restrictCountry' => $restrict_country,
              ),
            ),
            'type' => 'setting',
          ),
          drupal_get_path('module', 'webform_addressfield_extra') . '/webform_addressfield_extra.geocomplete.js' => array(
            'type' => 'file',
            'weight' => 3,
          ),
        ),
        'css' => array(
          drupal_get_path('module', 'webform_addressfield_extra') . '/webform_addressfield_extra.css' => array(
            'type' => 'file',
            'weight' => 3,
          ),
        ),
      ),
    );
    $link_variables = array(
      'external' => TRUE,
      'attributes' => array(
        'class' => array(
          'addressfield-autocomplete-reveal',
        ),
      ),
    );
    $new_element['link_container'] = array(
      '#type' => 'container',
      '#weight' => $element['#weight'] - 50,
      '#attributes' => array(
        'class' => array(
          'webform-addressfield-extra-wrapper--toogle',
        ),
      ),
      'link' => array(
        '#markup' => l(t("manual address entry"), 'javascript:void(0);', $link_variables),
      ),
    );

    // Respect the private setting.
    $new_element['#access'] = empty($component['extra']['private']);
    $element = $new_element;
  }
}

/**
 * Implementation of hook_webform_component_display_alter().
 */
function webform_addressfield_extra_webform_component_display_alter(&$element, &$component) {
  if ($component['type'] == 'addressfield' && $component['extra']['autocomplete']) {
    $element['#address'] = $element['#address']['addressfield'];
  }
}

/**
 * Alter a Webform submission's data when exported.
 */
function webform_addressfield_extra_webform_csv_data_alter(&$data, $component, $submission) {
  $cid = $component['cid'];
  $type = isset($component['type']) ? $component['type'] : '';
  $auto = isset($component['extra']['autocomplete']) ? $component['extra']['autocomplete'] : 0;
  if ($type == 'addressfield' && $auto) {
    $data = $submission->data[$cid];
    $data = _addressfield_tokens_expand_value($data);
    $data = $data['addressfield'];
    $separate = isset($component['extra']['csv_separate']) ? $component['extra']['csv_separate'] : 0;
    if ($separate) {
      $return = array();
      $properties = addressfield_tokens_property_names();
      drupal_alter('addressfield_tokens_download', $properties);
      foreach ($properties as $key => $name) {
        $return[] = isset($data[$key]) ? $data[$key] : '';
      }
      $data = $return;
    }
    else {
      if (!empty($data)) {
        $data = theme('addressfield_formatter__linear', array(
          'address' => $data,
        ));
      }
      else {
        $data = '';
      }
    }
  }
}

/**
 * Determine the site protocol (http or https).
 */
function _webform_addressfield_extra_url_protocol() {
  global $base_url;
  return parse_url($base_url, PHP_URL_SCHEME) == 'https' ? 'https' : 'http';
}
function _webform_addressfield_extra_geocomplete_url($api_key = FALSE) {
  $options = array(
    'query' => array(
      'libraries' => 'places',
    ),
    'external' => TRUE,
  );

  // Allow other modules to set or override API key.
  drupal_alter('webform_addressfield_extra_api_key', $api_key);
  if ($api_key) {
    $options['query']['key'] = $api_key;
  }
  return url(_webform_addressfield_extra_url_protocol() . '://maps.googleapis.com/maps/' . 'api/js', $options);
}

/**
 * Address autocomplete widget validation function.
 *
 * Validate to see if lat and lng have been added for addresses which
 * do not reveal the widget.
 */

/*function _webform_addressfield_extra_widget_validate(&$element, &$form_state, $form) {

  $elements = _find_all_children_elements($element['addressfield']);

  // Loop over all elements and find they are all empty or all valid.
  $all_empty = TRUE;
  $all_filled = TRUE;
  foreach ($elements as $elem) {
    // If element access if undefined or accessible and is required.
    if ((!isset($elem['#access']) || $elem['#access']) && $elem['#required']) {
      $all_empty &= empty($elem['#value']);
      $all_filled &= !empty($elem['#value']);
    }
  }

  if (($element['#required'] == TRUE && $all_empty)) {
    form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
  } else if ( $element['#required'] == TRUE && !$all_filled) {
    form_error($element, t('!name field is invalid.', array('!name' => $element['#title'])));
  }
}*/

/**
 * Generate a full-fledged form from a format snippet, as returned by addressfield_formats().
 */
function _webform_addressfield_extra_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']['addressfield']);
  return $format;
}
function _find_all_children_elements(&$element) {
  $results = array();
  $children = element_children($element);
  foreach ($children as $key) {
    $child =& $element[$key];
    if (is_array($child)) {
      if (!empty($child['#type']) && $child['#type'] != 'addressfield_container') {

        // OPTIONAL!
        $results[] = $child;
      }
      $results = array_merge($results, _find_all_children_elements($child));
    }
    unset($child);
  }
  return $results;
}