You are here

telephone_validation.module in Telephone Validation 7

Same filename and directory in other branches
  1. 8.2 telephone_validation.module

Validate phone number.

Add element_validation method to telephone_default widget to make sure phone has valid format.

File

telephone_validation.module
View source
<?php

/**
 * @file
 * Validate phone number.
 *
 * Add element_validation method to telephone_default widget to make sure phone
 * has valid format.
 */

/**
 * Implements hook_form_FORM_ID_alter().
 */
function telephone_validation_form_field_ui_field_edit_form_alter(&$form, &$form_state) {

  // Get field and instance.
  $field = $form['#field'];
  $instance = $form['#instance'];
  if ($instance['widget']['type'] == 'telephone_default' && !$field['locked']) {
    if (!isset($instance['widget']['settings']['telephone_validation'])) {

      // If that's a brand new instance initialize settings array.
      $instance['widget']['settings']['telephone_validation'] = array();
    }
    $settings = $instance['widget']['settings']['telephone_validation'];

    // Ensure we have at least default values.
    $settings += array(
      'valid_format' => \libphonenumber\PhoneNumberFormat::E164,
      'valid_countries' => array(),
      'store_format' => \libphonenumber\PhoneNumberFormat::E164,
    );
    if (isset($form_state['values']['instance']['widget']['settings']['telephone_validation'])) {
      $form_settings = $form_state['values']['instance']['widget']['settings']['telephone_validation'];
    }
    if (!isset($form['instance']['widget']['settings'])) {

      // Initialize settings array.
      $form['instance']['widget']['settings'] = array();
    }

    // Add parent fieldset for module settings.
    $form['instance']['widget']['settings']['telephone_validation'] = array(
      '#type' => 'fieldset',
      '#title' => t('Validation'),
    );

    // Right now format field supports E164 format only (just to be sure that
    // phone validator will work correctly).
    $form['instance']['widget']['settings']['telephone_validation']['valid_format'] = array(
      '#type' => 'select',
      '#title' => t('Valid telephone format'),
      '#description' => t('It is recommended to use E164 validation format. If you want to limit field instance to one country only you can change it to National format and choose country in next field. Otherwise, users will be required to add their telephone country code as part of the telephone number entered.'),
      '#default_value' => $settings['valid_format'],
      '#options' => array(
        \libphonenumber\PhoneNumberFormat::E164 => t('E164'),
        \libphonenumber\PhoneNumberFormat::NATIONAL => t('National'),
      ),
      '#ajax' => array(
        'callback' => '_telephone_validation_valid_countries_ajax_callback',
        'wrapper' => 'telephone-validation-valid-countries',
        'method' => 'replace',
      ),
    );
    $default_value = isset($form_settings['valid_format']) ? $form_settings['valid_format'] : $settings['valid_format'];
    $form['instance']['widget']['settings']['telephone_validation']['valid_countries'] = array(
      '#type' => 'select',
      '#title' => t('List of valid countries'),
      '#description' => t('If no country selected all countries are valid.'),
      '#default_value' => $settings['valid_countries'],
      '#multiple' => $default_value == \libphonenumber\PhoneNumberFormat::NATIONAL ? FALSE : TRUE,
      '#options' => _telephone_validation_get_country_codes(),
      '#prefix' => '<div id="telephone-validation-valid-countries">',
      '#suffix' => '</div>',
    );
    $form['instance']['widget']['settings']['telephone_validation']['store_format'] = array(
      '#type' => 'select',
      '#title' => t('Store format'),
      '#description' => t('It is highly recommended to store data in E164 format. That is international format with no whitespaces preceded by plus and country code. Use other formats only if you know what you are doing.'),
      '#default_value' => $settings['store_format'],
      '#options' => array(
        \libphonenumber\PhoneNumberFormat::E164 => t('E164'),
        \libphonenumber\PhoneNumberFormat::NATIONAL => t('National'),
        \libphonenumber\PhoneNumberFormat::INTERNATIONAL => t('International'),
      ),
    );
  }
}

/**
 * Returns valid countries field on ajax request.
 */
function _telephone_validation_valid_countries_ajax_callback($form, $form_state) {
  return $form['instance']['widget']['settings']['telephone_validation']['valid_countries'];
}

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter().
 */
function telephone_validation_field_widget_telephone_default_form_alter(&$element, &$form_state, $context) {
  $element['value']['#element_validate'] = array(
    'telephone_validation_element',
  );
}

/**
 * Telephone element validation.
 */
function telephone_validation_element($element, &$form_state) {
  $value = $element['#value'];

  // Do validation only if value is not empty.
  if (!empty($value)) {
    $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
    $settings = _telephone_validation_get_instance_settings($instance);
    try {
      $number = _telephone_validation($value, $settings);
    } catch (Exception $e) {
      form_error($element, t('%name value is invalid.', array(
        '%name' => $instance['label'],
      )));
      return FALSE;
    }

    // If number looks ok, use google libphonenumber library to set correct
    // output format.
    $phone_util = \libphonenumber\PhoneNumberUtil::getInstance();

    // Change field value and store it in database in unified format.
    form_set_value($element, $phone_util
      ->format($number, $settings['store_format']), $form_state);
  }
}

/**
 * Get default values for telephone field.
 *
 * @return array
 *   Returns array of default field settings.
 */
function _telephone_validation_get_default_settings() {
  return array(
    'valid_format' => \libphonenumber\PhoneNumberFormat::E164,
    'valid_countries' => array(),
    'store_format' => \libphonenumber\PhoneNumberFormat::E164,
  );
}

/**
 * Returns settings array for given instance.
 *
 * @param array $instance
 *   Field instance.
 *
 * @return array
 *   Array of settings. Default ones if it is not set.
 */
function _telephone_validation_get_instance_settings($instance = array()) {
  $settings = isset($instance['widget']['settings']['telephone_validation']) ? $instance['widget']['settings']['telephone_validation'] : array();
  $settings += _telephone_validation_get_default_settings();
  return $settings;
}

/**
 * Get list of all available countries with codes.
 *
 * @return array
 *   Returns list of all countries from iso.inc file cross referenced with
 *   available country codes.
 */
function _telephone_validation_get_country_codes() {
  include_once DRUPAL_ROOT . '/includes/iso.inc';
  $phone_util = \libphonenumber\PhoneNumberUtil::getInstance();
  $regions = array();
  foreach (_country_get_predefined_list() as $region => $name) {
    $region_meta = $phone_util
      ->getMetadataForRegion($region);
    if (is_object($region_meta)) {
      $regions[$region] = $name . ' - +' . $region_meta
        ->getCountryCode() . ' ' . $region_meta
        ->getLeadingDigits();
    }
  }
  return $regions;
}

/**
 * Implements hook_field_info_alter().
 *
 * Defines default property types for telephone field type.
 */
function telephone_validation_field_info_alter(&$field_info) {
  $field_info['telephone']['property_callbacks'][] = 'telephone_validation_metadata_field_telephone_property_callback';
}

/**
 * Additional callback to adapt the property info for telephone fields.
 *
 * @see entity_metadata_field_entity_property_info()
 * @see entity_field_info_alter()
 * @see entity_property_text_formatted_info()
 */
function telephone_validation_metadata_field_telephone_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
  $property['validation callback'] = '_telephone_validation_metadata_field_property_validation';
  $property['setter callback'] = '_telephone_validation_metadata_field_property_set';
}

/**
 * Callback for setting field property values.
 *
 * @see entity_metadata_field_property_set().
 */
function _telephone_validation_metadata_field_property_set($entity, $name, $value, $langcode, $entity_type, $info) {
  $field = field_info_field($name);
  $columns = array_keys($field['columns']);
  $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
  $values = $field['cardinality'] == 1 ? array(
    $value,
  ) : (array) $value;
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  $instance = field_info_instance($entity_type, $name, $bundle);
  $settings = _telephone_validation_get_instance_settings($instance);

  // If number looks ok, use google libphonenumber library to set correct
  // output format.
  $phone_util = \libphonenumber\PhoneNumberUtil::getInstance();
  $items = array();
  foreach ($values as $delta => $value) {
    if (isset($value)) {
      $default_region = $settings['valid_format'] == \libphonenumber\PhoneNumberFormat::NATIONAL ? $settings['valid_countries'] : NULL;
      $number = $phone_util
        ->parse($value, $default_region);
      $items[$delta][$columns[0]] = $phone_util
        ->format($number, $settings['store_format']);
    }
  }
  $entity->{$name}[$langcode] = $items;

  // Empty the static field language cache, so the field system picks up any
  // possible new languages.
  drupal_static_reset('field_language');
}

/**
 * Validation callback wrapper to set only valid data values.
 */
function _telephone_validation_metadata_field_property_validation($items, $context) {

  // Normalize incoming data.
  $items = is_array($items) ? $items : array(
    $items,
  );

  // Get parent entity info.
  $entity_info = $context['parent']
    ->info();
  $entity_type = $entity_info['type'];

  // Get field instance.
  $instance = field_info_instance($entity_type, $context['name'], $context['parent']
    ->getBundle());

  // Load widget settings.
  $settings = _telephone_validation_get_instance_settings($instance);
  foreach ($items as $value) {

    // Perform telephone validation on every value.
    _telephone_validation($value, $settings);
  }

  // Return TRUE if everything seems to be ok.
  return TRUE;
}

/**
 * Validation callback.
 *
 * @param string $value
 *   Raw field value.
 * @param array $settings
 *   Array of field settings.
 *
 * @return \libphonenumber\PhoneNumber
 *   Returns phone number object if number is valid.
 *
 * @throws \Exception
 *   Throws exception if number is invalid.
 */
function _telephone_validation($value, $settings = array()) {

  // Be sure we have default settings if user didn't submit field settings
  // form yet.
  $settings += _telephone_validation_get_default_settings();

  // If number looks more or less ok, use google libphonenumber library to
  // parse  it.
  $phone_util = \libphonenumber\PhoneNumberUtil::getInstance();

  // If number will be incorrect (there will be no way to parse it) we will
  // get an exception here.
  $default_region = $settings['valid_format'] == \libphonenumber\PhoneNumberFormat::NATIONAL ? $settings['valid_countries'] : NULL;
  $number = $phone_util
    ->parse($value, $default_region);

  // If can be parsed do validation.
  if (!$phone_util
    ->isValidNumber($number)) {
    throw new Exception('Field value is invalid.');
  }

  // If valid_countries element is not empty and default region can be loaded
  // do region matching validation.
  // This condition is always TRUE for national phone number format.
  if (!empty($settings['valid_countries']) && ($default_region = $phone_util
    ->getRegionCodeForNumber($number))) {

    // If number should belong to one of selected countries.
    // This condition is always TRUE for national phone number format.
    if (!isset($settings['valid_countries'][$default_region]) && $settings['valid_countries'] != $default_region) {
      throw new Exception('Field does not support phone number from your country.');
    }
  }
  return $number;
}

Functions

Namesort descending Description
telephone_validation_element Telephone element validation.
telephone_validation_field_info_alter Implements hook_field_info_alter().
telephone_validation_field_widget_telephone_default_form_alter Implements hook_field_widget_WIDGET_TYPE_form_alter().
telephone_validation_form_field_ui_field_edit_form_alter Implements hook_form_FORM_ID_alter().
telephone_validation_metadata_field_telephone_property_callback Additional callback to adapt the property info for telephone fields.
_telephone_validation Validation callback.
_telephone_validation_get_country_codes Get list of all available countries with codes.
_telephone_validation_get_default_settings Get default values for telephone field.
_telephone_validation_get_instance_settings Returns settings array for given instance.
_telephone_validation_metadata_field_property_set Callback for setting field property values.
_telephone_validation_metadata_field_property_validation Validation callback wrapper to set only valid data values.
_telephone_validation_valid_countries_ajax_callback Returns valid countries field on ajax request.