cck_phone.module 30 KB

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