You are here

field_ipaddress.module in IP address fields 7

File

field_ipaddress.module
View source
<?php

/**
 * Implements hook_migrate_api().
 */
function field_ipaddress_migrate_api() {
  $api = array(
    'api' => 2,
  );
  return $api;
}

/**
 * Implements hook_field_info().
 */
function field_ipaddress_field_info() {
  return array(
    'field_ipaddress' => array(
      'label' => t('IP Address'),
      'description' => t('Stores a single IP address, or range of IP addresses.'),
      'default_widget' => 'field_ipaddress_cidr',
      'default_formatter' => 'field_ipaddress_cidr',
    ),
  );
}

/**
 * Implements hook_field_is_empty().
 */
function field_ipaddress_field_is_empty($item, $field) {
  return empty($item['start']);
}

/**
 * Implements hook_field_formatter_info().
 */
function field_ipaddress_field_formatter_info() {
  return array(
    'field_ipaddress_shorthand' => array(
      'label' => t('List of IPs (shorthand)'),
      'field types' => array(
        'field_ipaddress',
      ),
    ),
    'field_ipaddress_cidr' => array(
      'label' => t('List of IPs (CIDR)'),
      'field types' => array(
        'field_ipaddress',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function field_ipaddress_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  switch ($display['type']) {
    case 'field_ipaddress_shorthand':
      $networks = array();
      foreach ($items as $delta => $item) {
        if (!empty($item)) {
          $networks[] = _field_ipaddress_long2shorthand($item['start'], $item['end']);
        }
      }
      if (!empty($networks)) {
        if (count($networks) === 1) {
          $element[0] = array(
            '#markup' => $networks[0],
          );
        }
        else {
          $element[0] = array(
            '#theme' => 'item_list',
            '#items' => $networks,
          );
        }
      }
      break;
    case 'field_ipaddress_cidr':
      $networks = array();
      foreach ($items as $delta => $item) {
        if (!empty($item)) {
          $networks[] = _field_ipaddress_long2cidr($item['start'], $item['end']);
        }
      }
      if (!empty($networks)) {
        if (count($networks) === 1) {
          $element[0] = array(
            '#markup' => $networks[0],
          );
        }
        else {
          $element[0] = array(
            '#theme' => 'item_list',
            '#items' => $networks,
          );
        }
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_field_widget_info().
 */
function field_ipaddress_field_widget_info() {
  return array(
    'field_ipaddress_shorthand' => array(
      'label' => t('Shorthand input'),
      'field types' => array(
        'field_ipaddress',
      ),
    ),
    'field_ipaddress_cidr' => array(
      'label' => t('CIDR input'),
      'field types' => array(
        'field_ipaddress',
      ),
    ),
    'field_ipaddress_long' => array(
      'label' => t('Long input'),
      'field types' => array(
        'field_ipaddress',
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function field_ipaddress_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  switch ($instance['widget']['type']) {
    case 'field_ipaddress_long':
      $element += array(
        '#type' => 'fieldset',
        '#element_validate' => array(
          '_field_ipaddress_validate_long',
        ),
      );
      $element['start'] = array(
        '#type' => 'textfield',
        '#title' => t('Start'),
        '#size' => 15,
        '#default_value' => isset($items[$delta]['start']) ? long2ip($items[$delta]['start']) : '',
      );
      $element['end'] = array(
        '#type' => 'textfield',
        '#title' => t('End'),
        '#size' => 15,
        '#default_value' => isset($items[$delta]['end']) ? long2ip($items[$delta]['end']) : '',
      );
      break;
    case 'field_ipaddress_shorthand':
      $element += array(
        '#element_validate' => array(
          '_field_ipaddress_validate_shorthand',
        ),
      );
      $value = isset($items[$delta]['start']) && isset($items[$delta]['end']) ? _field_ipaddress_long2shorthand($items[$delta]['start'], $items[$delta]['end']) : '';
      $element['start'] = array(
        '#type' => 'textfield',
        '#default_value' => $value,
        '#size' => 30,
        '#maxlength' => 100,
      );
      break;
    case 'field_ipaddress_cidr':
      $element += array(
        '#element_validate' => array(
          '_field_ipaddress_validate_cidr',
        ),
      );
      $value = isset($items[$delta]['start']) && isset($items[$delta]['end']) ? _field_ipaddress_long2cidr($items[$delta]['start'], $items[$delta]['end']) : '';
      $element['start'] = array(
        '#type' => 'textfield',
        '#default_value' => $value,
        '#size' => 30,
        '#maxlength' => 100,
      );
      break;
  }
  return $element;
}

/**
 * Implements hook_field_widget_error().
 */
function field_ipaddress_field_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'field_ipaddress_invalid':
      form_error($element, $error['message']);
      break;
  }
}

/**
 * Validate a long input.
 */
function _field_ipaddress_validate_long($element, &$form_state) {
  $delta = $element['#delta'];
  $lang_code = $element['#language'];
  $field = $form_state['field'][$element['#field_name']][$lang_code]['field'];
  $field_name = $field['field_name'];
  $startip = trim($form_state['values'][$field_name][$lang_code][$delta]['start']);
  $endip = trim($form_state['values'][$field_name][$lang_code][$delta]['end']);
  if (!empty($startip)) {
    $start = ip2long($startip);
    if (empty($endip)) {
      $end = $start;
    }
    else {
      $end = ip2long($endip);
    }
    $startip = long2ip($start);
    $endip = long2ip($end);
    if ($startip === '0.0.0.0' || $endip === '0.0.0.0') {
      form_error($element, t('Invalid start or end IP.'));
    }
    else {
      if ($start > $end) {
        form_error($element, t('Wrong order of start and end IP.'));
      }
      else {
        form_set_value($element['start'], $start, $form_state);
        form_set_value($element['end'], $end, $form_state);
      }
    }
  }
}

/**
 * Validate a CIDR input.
 *
 * The IP address (without CIDR prefix) is assumed to be the start of one well-defined block.
 */
function _field_ipaddress_validate_cidr($element, &$form_state) {
  $delta = $element['#delta'];
  $lang_code = $element['#language'];
  $field = $form_state['field'][$element['#field_name']][$lang_code]['field'];
  $field_name = $field['field_name'];
  $ipaddress = trim($form_state['values'][$field_name][$lang_code][$delta]['start']);
  if (!empty($ipaddress)) {
    list($ip, $bits) = explode('/', $ipaddress);
    $minimumprefix = _field_ipaddress_minimum_cidr_prefix(ip2long($ip), 32);
    if (!is_numeric($bits) || (int) $bits != $bits || $bits < 0 || $bits > 32) {
      form_error($element, t('The prefix must be an integer between 0 and 32.'));
    }
    else {
      if ($bits < $minimumprefix) {
        form_error($element, t('The prefix is too small for the given IP address. ' . 'The minimum prefix is @value.', array(
          '@value' => $minimumprefix,
        )));
      }
      else {
        $range = _field_ipaddress_cidr2long($ipaddress);
        if ($range) {
          form_set_value($element, $range, $form_state);
        }
        else {
          form_error($element, t('Invalid IP range or address.'));
        }
      }
    }
  }
}

/**
 * Validate a shorthand input.
 */
function _field_ipaddress_validate_shorthand($element, &$form_state) {
  $delta = $element['#delta'];
  $lang_code = $element['#language'];
  $field = $form_state['field'][$element['#field_name']][$lang_code]['field'];
  $field_name = $field['field_name'];
  $ipaddress = trim($form_state['values'][$field_name][$lang_code][$delta]['start']);
  if (!empty($ipaddress)) {
    $value = _field_ipaddress_shorthand2long($ipaddress);
    if ($value) {
      form_set_value($element, $value, $form_state);
    }
    else {
      form_error($element, t('Invalid IP range or address.'));
    }
  }
}

/**
 * Convert a start address and end address (as longs) into a smaller
 * human readable string.
 *
 * @param int $start The start address as a long
 * @param int $end The end address as a long
 * @return string A start and end range as the small formatted string
 * @example
 *   $s=1869573999, $e=1869574110 => 111.111.111.111-222
 */
function _field_ipaddress_long2shorthand($start, $end) {
  if ($start === $end) {
    $output = long2ip($start);
  }
  else {
    $s = explode('.', long2ip($start));
    $e = explode('.', long2ip($end));
    if ($s[0] === $e[0] && $s[1] === $e[1] && $s[2] === $e[2]) {
      if ($s[3] === '0' && $e[3] === '255') {
        $s[3] = '*';
        $output = implode('.', $s);
      }
      else {
        $s[3] = sprintf('%s-%s', $s[3], $e[3]);
        $output = implode('.', $s);
      }
    }
    else {
      $output = long2ip($start) . ' - ' . long2ip($end);
    }
  }
  return $output;
}

/**
 * Convert a start address and end address (as longs) into a CIDR.
 *
 * The start address is assumed to be the start of a well-defined block.
 */
function _field_ipaddress_long2cidr($start, $end) {
  $mask = (int) $start | ~(int) $end;
  $cidr = 32 - log(abs($mask), 2);
  $valid = (int) $cidr == $cidr;
  if (!$valid) {
    return _field_ipaddress_long2shorthand($start, $end);
  }
  return long2ip($start) . '/' . $cidr;
}

/**
 * Calculates the minimum CIDR prefix for a given IP address.
 * The IP address is assumed to be the start of the block.
 */
function _field_ipaddress_minimum_cidr_prefix($ibase, $tbit) {
  while ($tbit > 0) {
    $im = pow(2, 32) - pow(2, 32 - ($tbit - 1));
    $imand = $ibase & $im;
    if ($imand != $ibase) {
      break;
    }
    $tbit--;
  }
  return $tbit;
}

/**
 * Converts a CIDR into a start address and end address.
 */
function _field_ipaddress_cidr2long($cidr) {
  list($ip, $bits) = explode('/', $cidr);
  $start = long2ip(ip2long($ip));
  $end = long2ip(ip2long($ip) + pow(2, 32 - $bits) - 1);
  if ($start === '0.0.0.0' || $end === '0.0.0.0') {
    return FALSE;
  }
  else {
    return array(
      'start' => ip2long($start),
      'end' => ip2long($end),
    );
  }
}

/**
 *
 */
function _field_ipaddress_shorthand2long($shorthand) {
  $res = _field_ipaddress_shorthand2dottedquad($shorthand);
  if (!$res) {
    return FALSE;
  }
  list($start, $end) = $res;
  return array(
    'start' => ip2long($start),
    'end' => ip2long($end),
  );
}

/**
 * Take shorthand input and return the dotted quad start and end range.
 *
 * @param input $string An IP range.
 * @return array Start and end addresses in dotted quad format, else FALSE.
 * @example
 *   "111.111.111.*" => array("111.111.111.0", "111.111.111.255")
 *   "111.111.111.111-222" => array("111.111.111.111", "111.111.111.222")
 */
function _field_ipaddress_shorthand2dottedquad($network) {
  $star = strpos($network, '*');
  $dash = strpos($network, '-');
  if (ctype_alpha($network[strlen($network) - 1])) {

    // Hostname conversion
    $start = $end = gethostbyname($network);
    if ($start === $network) {
      return FALSE;
    }
  }
  else {

    // Simple validation
    if ($star === false && $dash === false) {
      $res = long2ip(ip2long($network));
      $start = $end = $res;
      if ($res === '0.0.0.0') {
        return FALSE;
      }
    }

    // Using a star
    if ($star !== false) {
      $start = long2ip(ip2long(str_replace('*', '0', $network)));
      $end = long2ip(ip2long(str_replace('*', '255', $network)));
      if ($start === '0.0.0.0' || $end === '0.0.0.0') {
        return FALSE;
      }
    }

    // Using a dash
    if ($dash !== FALSE) {
      list($start, $end) = explode('-', $network);

      // Check whether $end is a full IP or just the last quad
      if (strpos($end, '.') !== FALSE) {
        $end = long2ip(ip2long(trim($end)));
      }
      else {

        // Get the first 3 quads of the start address
        $classc = substr($start, 0, strrpos($start, '.'));
        $classc .= '.' . $end;
        $end = long2ip(ip2long(trim($classc)));
      }

      // Check for failure
      $start = long2ip(ip2long(trim($start)));
      if ($start === '0.0.0.0' || $end === '0.0.0.0') {
        return FALSE;
      }
    }
  }
  return array(
    $start,
    $end,
  );
}

/**
 * Convert IP address in decimal form to long.
 *
 * A wrapper around ip2long that ignores any leading zeros in each octet.
 * Normally a leading zero will cause ip2long (and many other tools) to
 * interpret the octet as an octal number. See for example
 * http://support.microsoft.com/kb/115388
 */
function _field_ipaddress_ip2long($ip) {
  $octets = explode(".", $ip);
  $ip = "";
  $dot = "";
  while (list($key, $val) = each($octets)) {
    $ip = sprintf("%s%s%d", $ip, $dot, $val);
    $dot = ".";
  }
  return ip2long($ip);
}

Functions

Namesort descending Description
field_ipaddress_field_formatter_info Implements hook_field_formatter_info().
field_ipaddress_field_formatter_view Implements hook_field_formatter_view().
field_ipaddress_field_info Implements hook_field_info().
field_ipaddress_field_is_empty Implements hook_field_is_empty().
field_ipaddress_field_widget_error Implements hook_field_widget_error().
field_ipaddress_field_widget_form Implements hook_field_widget_form().
field_ipaddress_field_widget_info Implements hook_field_widget_info().
field_ipaddress_migrate_api Implements hook_migrate_api().
_field_ipaddress_cidr2long Converts a CIDR into a start address and end address.
_field_ipaddress_ip2long Convert IP address in decimal form to long.
_field_ipaddress_long2cidr Convert a start address and end address (as longs) into a CIDR.
_field_ipaddress_long2shorthand Convert a start address and end address (as longs) into a smaller human readable string.
_field_ipaddress_minimum_cidr_prefix Calculates the minimum CIDR prefix for a given IP address. The IP address is assumed to be the start of the block.
_field_ipaddress_shorthand2dottedquad Take shorthand input and return the dotted quad start and end range.
_field_ipaddress_shorthand2long
_field_ipaddress_validate_cidr Validate a CIDR input.
_field_ipaddress_validate_long Validate a long input.
_field_ipaddress_validate_shorthand Validate a shorthand input.