cck_phone.module 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. <?php
  2. /**
  3. * @file
  4. * Defines phone number fields for CCK.
  5. * Provide some verifications on the phone numbers
  6. */
  7. define('CCK_PHONE_PHONE_MIN_LENGTH', 4); // Is there a phone number less than 4 digits?
  8. define('CCK_PHONE_PHONE_MAX_LENGTH', 15); // International standard 15 digits
  9. define('CCK_PHONE_EXTENSION_MAX_LENGTH', 6);
  10. define('CCK_PHONE_CC_MAX_LENGTH', 2);
  11. // load country codes
  12. require_once dirname(__FILE__) . '/cck_phone_countrycodes.inc';
  13. /**
  14. * Implements hook_init().
  15. * This hook is called on module initialization.
  16. */
  17. function cck_phone_init() {
  18. // Token module support.
  19. if (module_exists('token')) {
  20. module_load_include('inc', 'cck_phone', 'cck_phone.token');
  21. }
  22. }
  23. /**
  24. * Implements hook_theme().
  25. */
  26. function cck_phone_theme() {
  27. return array(
  28. 'cck_phone_phone_number' => array(
  29. 'render element' => 'element',
  30. ),
  31. 'phone_number_extension' => array(
  32. 'render element' => 'element',
  33. ),
  34. 'cck_phone_formatter_global_phone_number' => array(
  35. 'variables' => array('element' => NULL),
  36. ),
  37. 'cck_phone_formatter_local_phone_number' => array(
  38. 'variables' => array('element' => NULL),
  39. ),
  40. );
  41. }
  42. /**
  43. * Implements hook_field_info().
  44. */
  45. function cck_phone_field_info() {
  46. return array(
  47. 'phone_number' => array(
  48. 'label' => t('Phone number'),
  49. 'description' => t('Store a number and country code in the database to assemble a phone number.'),
  50. 'settings' => array('size' => CCK_PHONE_PHONE_MAX_LENGTH),
  51. 'instance_settings' => array(
  52. 'enable_default_country' => TRUE,
  53. 'default_country' => NULL,
  54. 'all_country_codes' => TRUE,
  55. 'country_codes' => array('hide_single_cc' => FALSE, 'country_selection' => array()),
  56. 'country_code_position' => 'after',
  57. 'enable_country_level_validation' => TRUE,
  58. 'enable_extension' => FALSE,
  59. ),
  60. 'default_widget' => 'phone_number',
  61. 'default_formatter' => 'global_phone_number',
  62. ),
  63. );
  64. }
  65. /**
  66. * Implements hook_field_instance_settings_form().
  67. */
  68. function cck_phone_field_instance_settings_form($field, $instance) {
  69. drupal_add_css(drupal_get_path('module', 'cck_phone') . '/cck_phone.css');
  70. drupal_add_js(drupal_get_path('module', 'cck_phone') . '/cck_phone.js');
  71. $defaults = field_info_instance_settings($field['type']);
  72. $settings = array_merge($defaults, $instance['settings']);
  73. $form = array();
  74. $form['enable_default_country'] = array(
  75. '#type' => 'checkbox',
  76. '#title' => t('Enable default country code'),
  77. '#default_value' => $settings['enable_default_country'],
  78. '#description' => t('Check this to enable default country code below.'),
  79. );
  80. $form['default_country'] = array(
  81. '#type' => 'select',
  82. '#title' => t('Default country code'),
  83. '#default_value' => $settings['default_country'],
  84. '#options' => _cck_phone_cc_options(TRUE),
  85. '#description' => t('Item marked with * comes with country level phone number validation.'),
  86. '#states' => array(
  87. 'invisible' => array(
  88. ':input[name="instance[settings][enable_default_country]"]' => array('checked' => FALSE),
  89. ),
  90. ),
  91. );
  92. $form['all_country_codes'] = array(
  93. '#type' => 'checkbox',
  94. '#title' => t('Show all country codes.'),
  95. '#default_value' => $settings['all_country_codes'],
  96. '#description' => t('Uncheck this to select the country to be displayed.'),
  97. );
  98. // Country codes settings
  99. $form['country_codes'] = array(
  100. '#title' => 'Country selection',
  101. '#type' => 'fieldset',
  102. '#collapsible' => TRUE,
  103. '#collapsed' => FALSE,
  104. '#attributes' => array('class' => array('cck-phone-settings')),
  105. '#states' => array(
  106. 'visible' => array(
  107. ':input[name="instance[settings][all_country_codes]"]' => array('checked' => FALSE),
  108. ),
  109. ),
  110. );
  111. $form['country_codes']['hide_single_cc'] = array(
  112. '#type' => 'checkbox',
  113. '#title' => t('Hide when only one country code'),
  114. '#default_value' => $settings['country_codes']['hide_single_cc'],
  115. '#description' => t('By default when there is only one country code, it will show as a display-only form element. Check this to hide the country code.'),
  116. );
  117. $form['country_codes']['country_selection'] = array(
  118. '#type' => 'checkboxes',
  119. '#title' => t('Select country codes to be included'),
  120. '#default_value' => isset($settings['country_codes']['country_selection']) && !empty($settings['country_codes']['country_selection']) ? $settings['country_codes']['country_selection'] : array($settings['default_country'] => $settings['default_country']),
  121. '#options' => _cck_phone_cc_options(TRUE),
  122. '#description' => t('Country marks with <em>*</em> has custom country code settings and/or validation.'),
  123. );
  124. if (isset($settings['country_codes']['country_selection']) && !empty($settings['country_codes']['country_selection'])) {
  125. $form['country_codes']['country_selection']['#default_value'] = $settings['country_codes']['country_selection'];
  126. }
  127. elseif ($settings['enable_default_country']) {
  128. $form['country_codes']['country_selection']['#default_value'] = array($settings['default_country'] => $settings['default_country']);
  129. }
  130. $form['country_code_position'] = array(
  131. '#type' => 'radios',
  132. '#title' => t('Country code position'),
  133. '#options' => array(
  134. 'before' => t('Before phone number'),
  135. 'after' => t('After phone number'),
  136. ),
  137. '#default_value' => $settings['country_code_position'],
  138. '#description' => t('Select the position of the country code selection field relative to the phone number text field.'),
  139. );
  140. $form['enable_country_level_validation'] = array(
  141. '#type' => 'checkbox',
  142. '#title' => t('Enable country level validation'),
  143. '#default_value' => $settings['enable_country_level_validation'],
  144. '#description' => t('Uncheck this to disable stringent country phone number validation.'),
  145. );
  146. $form['enable_extension'] = array(
  147. '#type' => 'checkbox',
  148. '#title' => t('Enable phone extension support'),
  149. '#default_value' => $settings['enable_extension'],
  150. '#description' => t('Check this to enable phone number extension field.'),
  151. );
  152. // Display country specific settings
  153. foreach (_cck_phone_custom_cc() as $cc) {
  154. $function = $cc . '_phone_field_settings';
  155. if (function_exists($function)) {
  156. $country_settings = $function($op, $field);
  157. if (isset($country_settings) && !empty($country_settings)) {
  158. $country_codes = cck_phone_countrycodes($cc);
  159. // Wrap with fieldset
  160. $wrapper = array(
  161. '#title' => t('%country specific settings', array('%country' => $country_codes['country'])),
  162. '#type' => 'fieldset',
  163. '#collapsible' => TRUE,
  164. '#collapsed' => TRUE,
  165. '#attributes' => array('class' => 'cck-phone-settings cck-phone-settings-' . $cc),
  166. );
  167. $wrapper[] = $country_settings;
  168. array_push($form, $wrapper);
  169. }
  170. }
  171. }
  172. return $form;
  173. }
  174. /**
  175. * Implements hook_field_validate().
  176. */
  177. function cck_phone_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  178. foreach ($items as $delta => $value) {
  179. _cck_phone_validate($items[$delta], $delta, $field, $instance, $langcode, $errors);
  180. }
  181. }
  182. /**
  183. * Implements hook_field_presave().
  184. */
  185. function cck_phone_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  186. foreach ($items as $delta => $value) {
  187. _cck_phone_sanitize($items[$delta], $delta, $field, $instance, $langcode);
  188. _cck_phone_process($items[$delta], $delta, $field, $instance, $langcode);
  189. }
  190. }
  191. /**
  192. * Implements hook_field_formatter_info().
  193. */
  194. function cck_phone_field_formatter_info() {
  195. return array(
  196. 'global_phone_number' => array(
  197. 'label' => t('Global phone number (default)'),
  198. 'field types' => array('phone_number'),
  199. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  200. ),
  201. 'local_phone_number' => array(
  202. 'label' => t('Local phone number'),
  203. 'field types' => array('phone_number'),
  204. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  205. ),
  206. );
  207. }
  208. /**
  209. * Implements hook_field_formatter_view().
  210. */
  211. function cck_phone_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  212. $element = array();
  213. $settings = $display['settings'];
  214. $formatter = $display['type'];
  215. foreach ($items as $delta => $item) {
  216. $element[$delta] = array(
  217. '#markup' => theme('cck_phone_formatter_' . $formatter, $item),
  218. );
  219. }
  220. return $element;
  221. }
  222. /**
  223. * Implements hook_field_is_empty().
  224. */
  225. function cck_phone_field_is_empty($item, $field) {
  226. return empty($item['number']);
  227. }
  228. /**
  229. * Theme function for phone extension.
  230. */
  231. function theme_phone_number_extension($element = '') {
  232. return t('<em> ext.</em> %extension', array('%extension' => $element['element']));
  233. }
  234. /**
  235. * Theme function for 'default' or global phone number field formatter.
  236. */
  237. function theme_cck_phone_formatter_global_phone_number($element) {
  238. $phone = '';
  239. // Display a global phone number with country code.
  240. if (!empty($element['number']) && !empty($element['country_codes'])) {
  241. // Call country default formatter if exist
  242. $custom_cc = _cck_phone_custom_cc();
  243. if (isset($custom_cc[$element['country_codes']])) {
  244. $function = $element['country_codes'] . '_formatter_default';
  245. if (function_exists($function)) {
  246. $phone = $function($element);
  247. }
  248. }
  249. // Output a raw value if no custom formatter or formatter return empty
  250. if (empty($phone)) {
  251. $cc = cck_phone_countrycodes($element['country_codes']);
  252. $phone = $cc['code'] . '-' . $element['number'];
  253. }
  254. // Extension
  255. if (!empty($element['extension'])) {
  256. $phone = $phone . theme('phone_number_extension', $element['extension']);
  257. }
  258. }
  259. return $phone;
  260. }
  261. /**
  262. * Theme function for 'local' phone number field formatter.
  263. */
  264. function theme_cck_phone_formatter_local_phone_number($element) {
  265. $phone = '';
  266. // Display a local phone number without country code.
  267. if (!empty($element['number'])) {
  268. // Call country local formatter if exist
  269. $custom_cc = _cck_phone_custom_cc();
  270. if (isset($custom_cc[$element['country_codes']])) {
  271. $function = $element['country_codes'] . '_formatter_local';
  272. if (function_exists($function)) {
  273. $phone = $function($element);
  274. }
  275. }
  276. // Output a raw value if no custom formatter or formatter return empty
  277. if (empty($phone)) {
  278. $phone = $element['number'];
  279. }
  280. // Extension
  281. if (!empty($element['extension'])) {
  282. $phone = $phone . theme('phone_number_extension', $element['extension']);
  283. }
  284. }
  285. return $phone;
  286. }
  287. /**
  288. * Generate an array of country codes, for use in select or checkboxes form.
  289. *
  290. * @param boolean $show_custom
  291. * Mark item with '*' to indicate the country code has include file.
  292. * @param array $country_selection
  293. * Limit the list to the countries listed in this array.
  294. * @return string
  295. */
  296. function _cck_phone_cc_options($show_custom = FALSE, $country_selection = array()) {
  297. $options = array();
  298. if ($show_custom) {
  299. $custom_cc = _cck_phone_custom_cc();
  300. }
  301. foreach (cck_phone_countrycodes() as $cc => $value) {
  302. $cc_name = $value['country'] . ' (' . $value['code'] . ')';
  303. // faster using array key instead of in_array
  304. if ($show_custom && isset($custom_cc[$cc])) {
  305. $cc_name .= ' *';
  306. }
  307. if (!empty($country_selection) && $country_selection[$cc] === 0) {
  308. continue;
  309. }
  310. $options[$cc] = check_plain($cc_name);
  311. }
  312. return $options;
  313. }
  314. /**
  315. * Get list of country codes that has custom includes.
  316. *
  317. * @return
  318. * Array of country codes abbreviation or empty array if none exist.
  319. */
  320. function _cck_phone_custom_cc() {
  321. static $countrycodes;
  322. if (!isset($countrycodes)) {
  323. // load custom country codes phone number includes
  324. $path = drupal_get_path('module', 'cck_phone') . '/includes';
  325. // scan include phone numbers directory
  326. $files = file_scan_directory($path, '/^phone\..*\.inc$/');
  327. $countrycodes = array();
  328. foreach ($files as $file) {
  329. module_load_include('inc', 'cck_phone', '/includes/' . $file->name);
  330. list ($dummy, $countrycode) = explode('.', $file->name);
  331. // faster using array key
  332. $countrycodes[$countrycode] = $countrycode;
  333. }
  334. }
  335. return $countrycodes;
  336. }
  337. function _cck_phone_valid_input($input) {
  338. // lenient checking, as long as don't have invalid phone number character
  339. $regex = '/^
  340. [\s.()-]* # optional separator
  341. (?: # }
  342. \d # } 4-15 digits number
  343. [\s.()-]* # } each followed by optional separator
  344. ){' . CCK_PHONE_PHONE_MIN_LENGTH . ',' . CCK_PHONE_PHONE_MAX_LENGTH . '} # }
  345. $/x';
  346. return preg_match($regex, $input);
  347. }
  348. function _cck_phone_valid_cc_input($list, $cc) {
  349. if (isset($list[$cc]) && $list[$cc] == $cc) {
  350. return TRUE;
  351. }
  352. return FALSE;
  353. }
  354. function _cck_phone_validate(&$item, $delta, $field, $instance, $langcode, &$errors) {
  355. if (isset($item['number'])) {
  356. $phone_input = trim($item['number']);
  357. }
  358. if (isset($item['country_codes'])) {
  359. $countrycode = trim($item['country_codes']);
  360. }
  361. $ext_input = '';
  362. $settings = $instance['settings'];
  363. if ($settings['enable_extension']) {
  364. $ext_input = trim($item['extension']);
  365. }
  366. if (isset($phone_input) && !empty($phone_input)) {
  367. $error_params = array(
  368. '%phone_input' => check_plain($phone_input), // original phone input
  369. '%countrycode' => check_plain($countrycode),
  370. '%min_length' => CCK_PHONE_PHONE_MIN_LENGTH,
  371. '%max_length' => CCK_PHONE_PHONE_MAX_LENGTH,
  372. '%ext_input' => check_plain($ext_input),
  373. '%ext_max_length' => CCK_PHONE_EXTENSION_MAX_LENGTH,
  374. );
  375. // Only allow digit, dash, space and bracket
  376. if (!_cck_phone_valid_input($phone_input, $ext_input)) {
  377. $error = t('The phone number must be between %min_length and %max_length digits in length.', $error_params);
  378. if ($settings['enable_extension'] && $ext_input != '') {
  379. $error .= '<br />' . t('The phone extension must be less than %ext_max_length digits in length.', $error_params);
  380. }
  381. form_set_error($field['field_name'], $error);
  382. }
  383. else {
  384. if (!$settings['all_country_codes']) {
  385. if (!_cck_phone_valid_cc_input($settings['country_codes']['country_selection'], $countrycode)) {
  386. $error = t('Invalid country code "%countrycode" submitted.', $error_params);
  387. form_set_error($field['field_name'], $error);
  388. }
  389. }
  390. // Generic number validation
  391. if (!cck_phone_validate_number($countrycode, $phone_input, $ext_input)) {
  392. $error = t('The phone number must be between %min_length and %max_length digits in length.', $error_params);
  393. if ($field['enable_extension'] && $ext_input != '') {
  394. $error .= '<br />' . t('The phone extension must be less than %ext_max_length digits in length.', $error_params);
  395. }
  396. form_set_error($field['field_name'], $error);
  397. }
  398. // Country level validation if enabled
  399. elseif ($settings['enable_country_level_validation']) {
  400. $custom_cc = _cck_phone_custom_cc();
  401. if (isset($custom_cc[$countrycode])) {
  402. $validate_function = $countrycode . '_validate_number';
  403. if (function_exists($validate_function)) {
  404. $error = '';
  405. if (!$validate_function($phone_input, $ext_input, $error)) {
  406. form_set_error($field['field_name'], t($error, $error_params));
  407. }
  408. }
  409. }
  410. }
  411. }
  412. }
  413. }
  414. function _cck_phone_process(&$item, $delta = 0, $field, $instance, $langcode) {
  415. $settings = $instance['settings'];
  416. // Clean up the phone number.
  417. $item['number'] = cck_phone_clean_number($item['number']);
  418. if (isset($item['extension'])) {
  419. $item['extension'] = cck_phone_clean_number($item['extension']);
  420. }
  421. // Don't save an invalid default value.
  422. if ((isset($instance['default_value']) && $item['number'] == $instance['default_value']) && (isset($settings['default_country']) && $item['country_codes'] == $settings['default_country'])) {
  423. if (!cck_phone_validate_number($item['country_codes'], $item['number'], $item['extension'])) {
  424. unset($item['number']);
  425. unset($item['country_codes']);
  426. unset($item['extension']);
  427. }
  428. }
  429. }
  430. /**
  431. * Cleanup user-entered values for a phone number field according to field settings.
  432. *
  433. * @param $item
  434. * A single phone number item, usually containing number and country code.
  435. * @param $delta
  436. * The delta value if this field is one of multiple fields.
  437. * @param $field
  438. * The CCK field definition.
  439. * @param $node
  440. * The node containing this phone number.
  441. */
  442. function _cck_phone_sanitize(&$item, $delta, $field, $instance, $langcode) {
  443. if (empty($item)) return;
  444. $settings = $instance['settings'];
  445. if (!empty($item['number'])) {
  446. $cc = $item['country_codes'];
  447. $item['number'] = cck_phone_clean_number($item['number']);
  448. $custom_cc = _cck_phone_custom_cc();
  449. if (isset($custom_cc[$cc])) {
  450. $function = $cc . '_sanitize_number';
  451. if (function_exists($function)) {
  452. $function($item['number']);
  453. }
  454. }
  455. }
  456. if ($settings['enable_extension']) {
  457. $item['extension'] = cck_phone_clean_number($item['extension']);
  458. }
  459. else {
  460. unset($item['extension']);
  461. }
  462. }
  463. /**
  464. * Implements hook_field_widget_info().
  465. */
  466. function cck_phone_field_widget_info() {
  467. return array(
  468. 'phone_number' => array(
  469. 'label' => t('Phone number'),
  470. 'field types' => array('phone_number'),
  471. 'behaviors' => array(
  472. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  473. 'default value' => FIELD_BEHAVIOR_NONE,
  474. ),
  475. 'settings' => array(
  476. 'size' => CCK_PHONE_PHONE_MAX_LENGTH,
  477. ),
  478. ),
  479. );
  480. }
  481. /**
  482. * Implements hook_field_settings_form().
  483. */
  484. function cck_phone_field_settings_form($field, $instance, $has_data) {
  485. $settings = $field['settings'];
  486. $form = array();
  487. $form['size'] = array(
  488. '#type' => 'textfield',
  489. '#title' => t('Size of phone number textfield'),
  490. '#default_value' => $settings['size'],
  491. '#element_validate' => array('_element_validate_integer_positive'),
  492. '#required' => TRUE,
  493. '#description' => t('International number is maximum 15 digits with additional country code, default is %length.', array('%length' => CCK_PHONE_PHONE_MAX_LENGTH)),
  494. );
  495. return $form;
  496. }
  497. /**
  498. * Implements hook_field_widget_form().
  499. */
  500. function cck_phone_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  501. // Retrieve any values set in $form_state, as will be the case during AJAX
  502. // rebuilds of this form.
  503. if (isset($form_state['values'][$field['field_name']][$langcode])) {
  504. $items = $form_state['values'][$field['field_name']][$langcode];
  505. unset($form_state['values'][$field['field_name']][$langcode]);
  506. }
  507. foreach ($items as $delta => $item) {
  508. // Remove any items from being displayed that are not needed.
  509. if (!isset($item['number']) || $item['number'] == '') {
  510. unset($items[$delta]);
  511. }
  512. }
  513. // Re-index deltas after removing empty items.
  514. $items = array_values($items);
  515. // Update order according to weight.
  516. $items = _field_sort_items($field, $items);
  517. // Essentially we use the phone_number type, extended with some enhancements.
  518. $element_info = element_info('phone_number');
  519. $element += array(
  520. '#type' => 'phone_number',
  521. '#default_value' => isset($items[$element['#delta']]) ? $items[$element['#delta']] : array(),
  522. '#process' => array_merge($element_info['#process'], array('cck_phone_field_widget_process')),
  523. );
  524. return $element;
  525. }
  526. /*
  527. * Implements hook_field_widget_error().
  528. */
  529. function cck_phone_field_widget_error($element, $error, $form, &$form_state) {
  530. form_error($element, $error['message'], $form, $form_state);
  531. }
  532. /**
  533. * An element #process callback for the phone_number field type.
  534. *
  535. * Expands the phone_number type to include the extension and country codes.
  536. */
  537. function cck_phone_field_widget_process($element, &$form_state, $form) {
  538. $item = $element['#value'];
  539. $field = field_widget_field($element, $form_state);
  540. $instance = field_widget_instance($element, $form_state);
  541. $settings = $instance['settings'];
  542. if ($settings['enable_extension']) {
  543. $element['extension'] = array(
  544. '#type' => 'textfield',
  545. '#maxlength' => CCK_PHONE_EXTENSION_MAX_LENGTH,
  546. '#size' => CCK_PHONE_EXTENSION_MAX_LENGTH,
  547. '#title' => t('ext'),
  548. '#required' => FALSE,
  549. '#default_value' => isset($item['extension']) ? $item['extension'] : NULL,
  550. '#weight' => 2,
  551. );
  552. }
  553. if ($settings['all_country_codes']) {
  554. $element['country_codes']['#options'] = _cck_phone_cc_options();
  555. }
  556. else {
  557. $element['country_codes']['#options'] = _cck_phone_cc_options(FALSE, $settings['country_codes']['country_selection']);
  558. }
  559. return $element;
  560. }
  561. /**
  562. * Implements hook_element_info().
  563. */
  564. function cck_phone_element_info() {
  565. $path = drupal_get_path('module', 'cck_phone');
  566. $types['phone_number'] = array(
  567. '#input' => TRUE,
  568. '#process' => array('cck_phone_phone_number_process'),
  569. '#element_validate' => array('cck_phone_phone_number_validate'),
  570. '#theme' => 'cck_phone_phone_number',
  571. '#theme_wrappers' => array('form_element'),
  572. '#attached' => array(
  573. 'css' => array($path . '/cck_phone.css'),
  574. // 'js' => array($path . '/cck_phone.js'),
  575. ),
  576. );
  577. return $types;
  578. }
  579. /**
  580. * Process an individual element.
  581. */
  582. function cck_phone_phone_number_process($element, &$form_state, $form) {
  583. $item = $element['#value'];
  584. $field = field_widget_field($element, $form_state);
  585. $instance = field_widget_instance($element, $form_state);
  586. $settings = $instance['settings'];
  587. $element['number'] = array(
  588. '#type' => 'textfield',
  589. '#maxlength' => $field['settings']['size'],
  590. '#size' => $field['settings']['size'],
  591. // '#title' => t('Number'),
  592. '#required' => ($element['#delta'] == 0 && $element['#required']) ? $element['#required'] : FALSE,
  593. '#default_value' => isset($item['number']) ? $item['number'] : NULL,
  594. '#attached' => array(
  595. 'css' => array(
  596. drupal_get_path('module', 'cck_phone') . '/cck_phone.css',
  597. )
  598. ),
  599. '#weight' => 0,
  600. );
  601. // If only one country code, make it as hidden form item
  602. $country_list = array_count_values($settings['country_codes']['country_selection']);
  603. if ($country_list['0'] == count($settings['country_codes']['country_selection']) - 1) {
  604. foreach (array_keys($country_list) as $k => $v) {
  605. if ($v != '0') { // only one value other than '0'
  606. $cc = cck_phone_countrycodes($v);
  607. $element['country_codes'] = array(
  608. '#type' => 'hidden',
  609. '#value' => $v,
  610. );
  611. if (!$settings['country_codes']['hide_single_cc']) {
  612. $element['country_codes_markup'] = array(
  613. '#type' => 'item',
  614. '#markup' => $value = $cc['country'] . ' (' . $cc['code'] . ')',
  615. '#weight' => ($settings['country_code_position'] == 'after' ? 1 : -1),
  616. );
  617. }
  618. }
  619. }
  620. }
  621. else {
  622. $element['country_codes'] = array(
  623. '#type' => 'select',
  624. // '#title' => 'Country code',
  625. '#options' => _cck_phone_cc_options(),
  626. '#weight' => ($settings['country_code_position'] == 'after' ? 1 : -1),
  627. );
  628. }
  629. if (!$element['#required']) {
  630. $element['country_codes']['#empty_option'] = t('- Select -');
  631. }
  632. if (isset($item['country_codes'])) {
  633. $element['country_codes']['#default_value'] = $item['country_codes'];
  634. }
  635. elseif ($settings['enable_default_country']) {
  636. $element['country_codes']['#default_value'] = $settings['default_country'];
  637. }
  638. return $element;
  639. }
  640. /**
  641. * An #element_validate callback for the phone_number element.
  642. */
  643. function cck_phone_phone_number_validate(&$element, &$form_state) {
  644. $item = $element['#value'];
  645. $field = field_widget_field($element, $form_state);
  646. $instance = field_widget_instance($element, $form_state);
  647. $settings = $instance['settings'];
  648. if (isset($item['number'])) {
  649. $phone_input = trim($item['number']);
  650. }
  651. if (isset($item['country_codes'])) {
  652. $countrycode = trim($item['country_codes']);
  653. }
  654. $ext_input = '';
  655. if ($settings['enable_extension'] && isset($item['extension'])) {
  656. $ext_input = trim($item['extension']);
  657. }
  658. if (isset($phone_input) && !empty($phone_input)) {
  659. if (empty($countrycode)) {
  660. form_set_error($field['field_name'], t('The phone number must be accompanied by a country code.'));
  661. }
  662. else {
  663. $error_params = array(
  664. '%phone_input' => check_plain($phone_input), // original phone input
  665. '%countrycode' => check_plain($countrycode),
  666. '%min_length' => CCK_PHONE_PHONE_MIN_LENGTH,
  667. '%max_length' => CCK_PHONE_PHONE_MAX_LENGTH,
  668. '%ext_input' => check_plain($ext_input),
  669. '%ext_max_length' => CCK_PHONE_EXTENSION_MAX_LENGTH,
  670. );
  671. // Only allow digit, dash, space and bracket
  672. if (!_cck_phone_valid_input($phone_input, $ext_input)) {
  673. $error = t('The phone number must be between %min_length and %max_length digits in length.', $error_params);
  674. if ($settings['enable_extension'] && $ext_input != '') {
  675. $error .= '<br />' . t('The phone extension must be less than %ext_max_length digits in length.', $error_params);
  676. }
  677. form_set_error($field['field_name'], $error);
  678. }
  679. else {
  680. if (!$settings['all_country_codes']) {
  681. if (!_cck_phone_valid_cc_input($settings['country_codes']['country_selection'], $countrycode)) {
  682. $error = t('Invalid country code "%countrycode" submitted.', $error_params);
  683. form_set_error($field['field_name'], $error);
  684. }
  685. }
  686. // Generic number validation
  687. if (!cck_phone_validate_number($countrycode, $phone_input, $ext_input)) {
  688. $error = t('The phone number must be between %min_length and %max_length digits in length.', $error_params);
  689. if ($settings['enable_extension'] && $ext_input != '') {
  690. $error .= '<br />' . t('The phone extension must be less than %ext_max_length digits in length.', $error_params);
  691. }
  692. form_set_error($field['field_name'], $error);
  693. }
  694. // Country level validation if enabled
  695. elseif ($settings['enable_country_level_validation']) {
  696. $custom_cc = _cck_phone_custom_cc();
  697. if (isset($custom_cc[$countrycode])) {
  698. $validate_function = $countrycode . '_validate_number';
  699. if (function_exists($validate_function)) {
  700. $error = '';
  701. if (!$validate_function($phone_input, $ext_input, $error)) {
  702. form_set_error($field['field_name'], t($error, $error_params));
  703. }
  704. }
  705. }
  706. }
  707. }
  708. }
  709. }
  710. }
  711. /**
  712. * Returns HTML for a phone number element.
  713. *
  714. * @param $variables
  715. * An associative array containing:
  716. * - element: A render element representing the file.
  717. *
  718. * @ingroup themeable
  719. */
  720. function theme_cck_phone_phone_number($variables) {
  721. $element = $variables['element'];
  722. // This wrapper is required to apply JS behaviors and CSS styling.
  723. $output = '';
  724. $output .= '<div class="form-phone-number">';
  725. $output .= drupal_render_children($element);
  726. $output .= '</div>';
  727. return $output;
  728. }
  729. /**
  730. * Strip number of space, hash, dash, bracket, etc leaving digit only.
  731. *
  732. * @param string $number
  733. * @return string Returns digit only phone number.
  734. */
  735. function cck_phone_clean_number($number) {
  736. // Remove none numeric characters
  737. $number = preg_replace('/[^0-9]/', '', $number);
  738. return $number;
  739. }
  740. /**
  741. * Generic validation for Phone Number.
  742. *
  743. * @param string $countrycode
  744. * @param string $number
  745. * @return boolean Returns boolean FALSE if the phone number is not valid.
  746. */
  747. function cck_phone_validate_number($countrycode, $number, $ext = '') {
  748. // We don't want to worry about separators
  749. $number = cck_phone_clean_number($number);
  750. if ($number !== '' && drupal_strlen($number) > CCK_PHONE_PHONE_MAX_LENGTH) {
  751. return FALSE;
  752. }
  753. $ext = cck_phone_clean_number($ext);
  754. if ($ext !== '' && drupal_strlen($ext) > CCK_PHONE_EXTENSION_MAX_LENGTH) {
  755. return FALSE;
  756. }
  757. return TRUE;
  758. }