? phone-1.patch
? phone-2.patch
Index: README.int.txt
===================================================================
RCS file: README.int.txt
diff -N README.int.txt
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ README.int.txt	21 Jan 2008 22:56:12 -0000
@@ -0,0 +1,56 @@
+About the International Phone Number format
+===========================================
+
+These rules have been formulated off of the E.123 (for display) and
+E.164 (for input) specifications published by the ITU. In order to
+prevent ambiguity, we have chosen to restrict some of the stipulations
+these specifications give.
+
+Reference materials can be found here:
+- http://www.itu.int/rec/T-REC-E.123/en
+- http://www.itu.int/rec/T-REC-E.164/en
+
+Modifications to E.123
+----------------------
+
+7.1: The international prefix symbol "+" MUST prefix international
+phone numbers. All numbers missing this symbol will be assumed to be in
+the default country code.
+
+When reformatting numbers to a standard format, the following conventions
+will be taken:
+
+7.2: Parentheses will be normalized to spaces.
+
+We do not support the multiple phone numbers as described by (7.4); users
+can always specify that multiple values are allowed if this is desired.
+The functionality specified by 7.5, 7.6 and 8 IS NOT implemented.
+
+9.2 specifies that spacing SHALL OCCUR between the country code, the trunk
+code and the subscriber number. As trunk codes are omitted by convention,
+this means the only guaranteed separation will be between the country code
+and subscriber number. Our implementation MAY treat hyphens, spaces and
+parentheses as advisory indicators as to where spaces should be placed.
+However, +1 7329060489 will stay as it was specified, while +1 (732) 906-0489
+will be normalized to +1 732 906 0489. As a future feature, rules may
+be implemented for country codes specifying these conventions, however,
+I have deemed such functionality out of scope for now.
+
+The Drupal task specifies that we should validate country codes, however,
+due to the highly volatile nature of these codes, the author does not
+believe that it is a good idea to maintain a list of valid country codes.
+Thus, we only validate that the country code is three or less digits.
+
+Modifications to E.164
+----------------------
+
+Our processing for NDD's will be similarly constrained. As per
+7.3.2, we will treat 0 as a valid trunk code for all countries.
+Other digits may be specified if the fall in the form of (X), where X is
+a single digit that is 7 or 8.
+
+Postscript
+----------
+
+Modifications to our implementation will occur as necessary by user bug
+reports.
\ No newline at end of file
Index: README.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/phone/README.txt,v
retrieving revision 1.1.1.3.2.3
diff -u -r1.1.1.3.2.3 README.txt
--- README.txt	23 Dec 2007 15:31:07 -0000	1.1.1.3.2.3
+++ README.txt	21 Jan 2008 22:14:35 -0000
@@ -9,7 +9,7 @@
 
 Features:
 ---------
-* Validation of phone numbers : support of French, British, Italian, Russian, US and Canadian phone numbers 
+* Validation of phone numbers : support of French, British, Italian, Russian, US, Canadian and generic international phone numbers 
 * Formating of phone numbers 
 * Option for internationalization phone numbers  
 * IPhone support 
Index: phone.int.inc
===================================================================
RCS file: phone.int.inc
diff -N phone.int.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ phone.int.inc	21 Jan 2008 23:29:39 -0000
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Verifies that $number is a valid international phone number or,
+ * if a default country code is specified, a valid subscriber number.
+ * 
+ * @param $number
+ *   International phone number to validate
+ * @return
+ *   TRUE if valid, FALSE if otherwise.
+ */
+function valid_int_phone_number($number, $field = array()) {
+  $number = trim($number);
+  if ($number === '') {
+    return FALSE;
+  }
+  $number = _normalize_country_code($number, $field);
+  $base_number = str_replace(array('.', '(', ')', '[', ']', '-', '+', ' '), '', $number);
+  if (strlen($base_number) > 15) {
+    return FALSE;
+  }
+  // Extract country code and see if it's correct:
+  preg_match('/^\+(\d+)/', $number, $matches);
+  $cc = $matches[1];
+  if (strlen($cc) > 3) {
+    return FALSE;
+  }
+  // TODO: Check if parentheses/brackets add up.
+  return TRUE;
+}
+
+/**
+ * Formats $number into the standard representation of international
+ * numbers as per E.123.
+ * 
+ * @param $number
+ *   International phone number to format
+ * @return
+ *   Formatted international phone number
+ */
+function format_int_phone_number($number, $field = array()) {
+  $number = trim($number);
+  if ($number === '') {
+    return '';
+  }
+  $number = _normalize_country_code($number, $field);
+  $bits = preg_split('/[.()\[\]\- ]/', $number, -1, PREG_SPLIT_NO_EMPTY);
+  // $bits[0] is the country code WITH a plus sign.
+  if (isset($bits[1])) {
+    // This is the first non-CC segment, so it could have the NDN.
+    switch ($bits[1][0]) {
+      case 0:
+        $bits[1] = substr($bits[1], 1);
+        break;
+    }
+    switch ($bits[1]) {
+      case 0:
+      case 7:
+      case 8:
+        array_splice($bits, 1, 1);
+        break;
+    }
+  }
+  return implode(' ', $bits);
+}
+
+/**
+ * Adds a country code to a phone number if necessary.
+ * 
+ * @param $number
+ *   International or local phone number to format
+ * @return
+ *   International phone number with country code
+ */
+function _normalize_country_code($number, $field = array()) {
+  if ($number[0] !== '+') {
+    $cc = isset($field['phone_default_country_code']) ? $field['phone_default_country_code'] : '1';
+    return "+$cc $number";
+  }
+  return $number;
+}
Index: phone.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/phone/phone.module,v
retrieving revision 1.1.1.5.2.8
diff -u -r1.1.1.5.2.8 phone.module
--- phone.module	23 Dec 2007 15:31:07 -0000	1.1.1.5.2.8
+++ phone.module	21 Jan 2008 22:50:40 -0000
@@ -20,6 +20,7 @@
     'ca_phone' => array('label' => t('US & Canadian Phone Numbers')),
     'uk_phone' => array('label' => t('British (UK) Phone Numbers')),
     'ru_phone' => array('label' => t('Russian Phone Numbers')),
+    'int_phone'=> array('label' => t('International Phone Numbers')),
    );
 }
 
@@ -34,18 +35,26 @@
         '#type' => 'checkbox',
         '#title' => t('Add the country code if not filled by the user'),
         '#default_value' => isset($field['phone_country_code']) ? $field['phone_country_code'] : '',
-      );  
+      );
+      if ($field['type'] == 'int_phone') {
+        $form['phone_default_country_code'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Default country code to add to international numbers without one (omit + sign)'),
+          '#default_value' => isset($field['phone_default_country_code']) ? $field['phone_default_country_code'] : '1',
+        );
+      }
       return $form;
 
     case 'save':
-      return array('phone_country_code');
+      return array('phone_country_code', 'phone_default_country_code');
 
     case 'database columns':
       if ($field['type'] == 'fr_phone'
        || $field['type'] == 'it_phone'
        || $field['type'] == 'ca_phone'
        || $field['type'] == 'uk_phone'
-       || $field['type'] == 'ru_phone'){ 
+       || $field['type'] == 'ru_phone'
+       || $field['type'] == 'int_phone'){ 
       	$columns = array(
         	'value' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
       	);
@@ -85,7 +94,7 @@
   return array(
     'default' => array(
       'label' => 'Default',
-      'field types' => array('fr_phone', 'it_phone', 'ca_phone', 'uk_phone', 'ru_phone'),
+      'field types' => array('fr_phone', 'it_phone', 'ca_phone', 'uk_phone', 'ru_phone', 'int_phone'),
     ),
   );
  } 
@@ -117,7 +126,7 @@
   return array(
     'phone' => array(
       'label' => t('Textfield'),
-      'field types' => array('fr_phone', 'it_phone', 'ca_phone', 'uk_phone', 'ru_phone'),
+      'field types' => array('fr_phone', 'it_phone', 'ca_phone', 'uk_phone', 'ru_phone', 'int_phone'),
     ),
   );
 }
@@ -179,55 +188,44 @@
       return $form;
     
     case 'process form values':
-          if (is_array($node_field)) {
-            foreach ($node_field as $delta => $item) {
-    	    //format the phone number
-    	      if ($item['value'] != '') 
-	      { 
-    	        if ($field['type'] == 'fr_phone') { 
-	  	  $node_field[0]['value'] = format_phone_number('fr', $node_field[0]['value'], $field);
-	        }
-    	        if ($field['type'] == 'it_phone') { 
-	  	  $node_field[0]['value'] = format_phone_number('it', $node_field[0]['value'], $field);
-	        }	        
-    	        if ($field['type'] == 'ca_phone') { 
-	          $node_field[0]['value'] = format_phone_number('ca', $node_field[0]['value'], $field);
-	        }
-    	        if ($field['type'] == 'uk_phone') { 
-	          $node_field[0]['value'] = format_phone_number('uk', $node_field[0]['value'], $field);
-	        }
-    	        if ($field['type'] == 'ru_phone') { 
-	          $node_field[0]['value'] = format_phone_number('ru', $node_field[0]['value'], $field);
-	        }	      }
-	    }
-	  }
-    break;
+      if (is_array($node_field)) {
+        foreach ($node_field as $delta => $item) {
+         //format the phone number
+          if ($item['value'] != '') {
+            list($lang) = explode('_', $field['type']);
+            $node_field[0]['value'] = format_phone_number($lang, $node_field[0]['value'], $field);
+          }
+        }
+      }
+      break;
     
     case 'validate':
       if (is_array($node_field)) {
-          foreach ($node_field as $delta => $item) {
-
-	    if ($item['value'] != '') 
-	    { 
-              if ($field['type'] == 'fr_phone' && !valid_phone_number('fr', $item['value'])) {
-                form_set_error($field['field_name'],t('"%value" is not a valid French phone number<br>French phone numbers should only contain numbers and spaces and be like 99 99 99 99 99', array('%value' => $item['value'])));
-              }
-              if ($field['type'] == 'it_phone' && !valid_phone_number('it', $item['value'])) {
-                form_set_error($field['field_name'],t('"%value" is not a valid Italian phone number<br>Italian phone numbers should only ...', array('%value' => $item['value'])));
-              }              
-              if ($field['type'] == 'ca_phone' && !valid_phone_number('ca', $item['value'])) {
-                form_set_error($field['field_name'],t('"%value" is not a valid North American phone number<br>North American Phone numbers should only contain numbers and + and - and ( and ) and spaces and be like 999-999-9999. Please enter a valid ten-digit phone number with optional extension.', array('%value' => $item['value'])));
-              }
-              if ($field['type'] == 'uk_phone' && !valid_phone_number('uk', $item['value'])) {
-                form_set_error($field['field_name'],t('"%value" is not a valid British phone number<br>British Phone numbers should .... ', array('%value' => $item['value'])));
-              }    
-              if ($field['type'] == 'ru_phone' && !valid_phone_number('ru', $item['value'])) {
-                form_set_error($field['field_name'],t('"%value" is not a valid Russian phone number<br>British Phone numbers should .... ', array('%value' => $item['value'])));
-              }                 }
+        foreach ($node_field as $delta => $item) {
+          if ($item['value'] != '')  { 
+            if ($field['type'] == 'fr_phone' && !valid_phone_number('fr', $item['value'])) {
+              form_set_error($field['field_name'],t('"%value" is not a valid French phone number<br>French phone numbers should only contain numbers and spaces and be like 99 99 99 99 99', array('%value' => $item['value'])));
+            }
+            elseif ($field['type'] == 'it_phone' && !valid_phone_number('it', $item['value'])) {
+              form_set_error($field['field_name'],t('"%value" is not a valid Italian phone number<br>Italian phone numbers should only ...', array('%value' => $item['value'])));
+            }
+            elseif ($field['type'] == 'ca_phone' && !valid_phone_number('ca', $item['value'])) {
+              form_set_error($field['field_name'],t('"%value" is not a valid North American phone number<br>North American Phone numbers should only contain numbers and + and - and ( and ) and spaces and be like 999-999-9999. Please enter a valid ten-digit phone number with optional extension.', array('%value' => $item['value'])));
+            }
+            elseif ($field['type'] == 'uk_phone' && !valid_phone_number('uk', $item['value'])) {
+              form_set_error($field['field_name'],t('"%value" is not a valid British phone number', array('%value' => $item['value'])));
+            }
+            elseif ($field['type'] == 'ru_phone' && !valid_phone_number('ru', $item['value'])) {
+              form_set_error($field['field_name'],t('"%value" is not a valid Russian phone number', array('%value' => $item['value'])));
+            }
+            elseif ($field['type'] == 'int_phone' && !valid_phone_number('int', $item['value'])) {
+              form_set_error($field['field_name'],t('"%value" is not a valid international phone number<br>International phone numbers should contain a country code prefixed by a plus sign followed by the local number.', array('%value' => $item['value'])));
+            }
           }
-      }      
+        }
+      }
       break;
-  }                   
+  }
 }
 
 /**
@@ -246,7 +244,8 @@
   	|| $countrycode == 'it'  
   	|| $countrycode == 'ca'
   	|| $countrycode == 'uk'
-  	|| $countrycode == 'ru') { 
+  	|| $countrycode == 'ru'
+    || $countrycode == 'int') { 
 	
         //drupal_set_message('langue = ' . $countrycode, 'error');
 
@@ -282,7 +281,8 @@
   	|| $countrycode == 'it'
   	|| $countrycode == 'ca'
   	|| $countrycode == 'uk'
-  	|| $countrycode == 'ru') { 
+  	|| $countrycode == 'ru'
+    || $countrycode == 'int') { 
 	
         //drupal_set_message('langue = ' . $countrycode, 'error');       
 
Index: tests/phone.int.test
===================================================================
RCS file: tests/phone.int.test
diff -N tests/phone.int.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/phone.int.test	21 Jan 2008 23:30:52 -0000
@@ -0,0 +1,69 @@
+<?php
+
+require_once drupal_get_path('module', 'phone') . '/phone.int.inc';
+
+class PhoneIntTest extends DrupalTestCase
+{
+  
+  function get_info() {
+    return array(
+      'name'  => t('International phone number test'),
+      'desc'  => t('Tests international phone number validation.'),
+      'group' => t('Phone module'),
+    );
+  }
+  
+  function assertConversion($input, $expect = TRUE, $field = array()) {
+    if ($expect === FALSE) {
+      $this->assertFALSE(valid_int_phone_number($input, $field));
+      return;
+    }
+    elseif ($expect === TRUE) {
+      $expect = $input;
+    }
+    $this->assertTRUE(valid_int_phone_number($input, $field));
+    $result = format_int_phone_number($input, $field);
+    $this->assertIdentical($result, $expect);
+  }
+  
+  function testBasic() {
+    $this->assertConversion('+1 7329018493');
+  }
+  
+  function testBasicWithThreeCountryCode() {
+    $this->assertConversion('+672 565434');
+  }
+  
+  function testBasicWithFourCountryCode() {
+    $this->assertConversion('+6724 565434', FALSE);
+  }
+  
+  function testBasicWithSpaces() {
+    $this->assertConversion('+1 732 901 8493');
+  }
+  
+  function testBasicNormalizeOtherCharacters() {
+    $this->assertConversion('+1 (732) 901-8493', '+1 732 901 8493');
+  }
+  
+  function testRemoveNDD() {
+    $this->assertConversion('+54 0435344', '+54 435344');
+  }
+  
+  function testRemoveNonStandardNDD() {
+    $this->assertConversion('+374 (8) 435344', '+374 435344');
+  }
+  
+  function testAddCountryCode() {
+    $this->assertConversion('732 343 2333', '+1 732 343 2333', array('phone_default_country_code' => '1'));
+  }
+  
+  function testOverlongNumber() {
+    $this->assertConversion('+123 456 789 012 3456', FALSE);
+  }
+  
+  function testOverlongNumberWithoutCountryCode() {
+    $this->assertConversion('456 789 012 3456', FALSE, array('phone_default_country_code' => '123'));
+  }
+  
+}
