webform_phone.components.inc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php
  2. /**
  3. * @file
  4. * Webform Component information for a phone number field type
  5. */
  6. /**
  7. * Implements _webform_defaults_component().
  8. */
  9. function _webform_defaults_phone() {
  10. return array(
  11. 'name' => '',
  12. 'form_key' => NULL,
  13. 'required' => 0,
  14. 'mandatory' => 0,
  15. 'pid' => 0,
  16. 'weight' => 0,
  17. 'value' => '',
  18. 'extra' => array(
  19. 'title_display' => 0,
  20. 'width' => '',
  21. 'disabled' => FALSE,
  22. 'private' => FALSE,
  23. 'attributes' => array(),
  24. 'description' => '',
  25. 'placeholder' => '',
  26. 'country' => 'ca',
  27. 'phone_country_code' => 0,
  28. 'phone_default_country_code' => 1,
  29. 'phone_int_max_length' => 15,
  30. 'ca_phone_separator' => '-',
  31. 'ca_phone_parentheses' => 1,
  32. ),
  33. );
  34. }
  35. /**
  36. * Implements _webform_theme_component().
  37. */
  38. function _webform_theme_phone() {
  39. return array( 'webform_display_phonefield' => array( 'render element' => 'element' ) );
  40. }
  41. /**
  42. * Generate the form for editing a component.
  43. * Create a set of form elements to be displayed on the form for editing this
  44. * component. Use care naming the form items, as this correlates directly to the
  45. * database schema. The component "Name" and "Description" fields are added to
  46. * every component type and are not necessary to specify here (although they
  47. * may be overridden if desired).
  48. *
  49. * @param $component
  50. * A Webform component array.
  51. *
  52. * @return
  53. * An array of form items to be displayed on the edit component page
  54. */
  55. function _webform_edit_phone($component) {
  56. $form = array();
  57. // General Options
  58. $form['extra']['country'] = array(
  59. '#type' => 'select',
  60. '#title' => t('Country'),
  61. '#options' => phone_countries(),
  62. '#default_value' => $component['extra']['country'],
  63. '#description' => t('Which country-specific rules should this field be validated against and formatted according to.'),
  64. '#required' => TRUE,
  65. );
  66. $form['extra']['phone_country_code'] = array(
  67. '#type' => 'checkbox',
  68. '#title' => t('Add the country code if not filled by the user'),
  69. '#default_value' => $component['extra']['phone_country_code'],
  70. );
  71. // International Options
  72. $form['extra']['phone_int_help'] = array(
  73. '#type' => 'markup',
  74. '#value' => t('International phone numbers are in the form +XX YYYYYYY where XX is a country code and YYYYYYY is the local number. This field type is based off of the <a href="http://www.itu.int/rec/T-REC-E.123/en">E.123 specification</a>.'),
  75. '#states' => array(
  76. 'visible' => array(
  77. ':input[name="extra[country]"]' => array( 'value' => 'int' ),
  78. ),
  79. ),
  80. );
  81. $form['extra']['phone_default_country_code'] = array(
  82. '#type' => 'textfield',
  83. '#title' => t('Default country code to add to international numbers without one (omit + sign)'),
  84. '#default_value' => $component['extra']['phone_default_country_code'],
  85. '#states' => array(
  86. 'visible' => array(
  87. ':input[name="extra[country]"]' => array( 'value' => 'int' ),
  88. ),
  89. ),
  90. );
  91. $form['extra']['phone_int_max_length'] = array(
  92. '#type' => 'textfield',
  93. '#title' => t('Maximum length of international numbers, according to the ITU this is 15'),
  94. '#default_value' => $component['extra']['phone_int_max_length'],
  95. '#states' => array(
  96. 'visible' => array(
  97. ':input[name="extra[country]"]' => array( 'value' => 'int' ),
  98. ),
  99. ),
  100. );
  101. // US/Canada Options
  102. $form['extra']['ca_phone_separator'] = array(
  103. '#type' => 'textfield',
  104. '#title' => t('Separator'),
  105. '#default_value' => $component['extra']['ca_phone_separator'],
  106. '#size' => 2,
  107. '#states' => array(
  108. 'visible' => array(
  109. ':input[name="extra[country]"]' => array( 'value' => 'ca' ),
  110. ),
  111. ),
  112. );
  113. $form['extra']['ca_phone_parentheses'] = array(
  114. '#type' => 'checkbox',
  115. '#title' => t('Use parentheses around area code'),
  116. '#default_value' => $component['extra']['ca_phone_parentheses'],
  117. '#states' => array(
  118. 'visible' => array(
  119. ':input[name="extra[country]"]' => array( 'value' => 'ca' ),
  120. ),
  121. ),
  122. );
  123. $form['value'] = array(
  124. '#type' => 'textfield',
  125. '#title' => t('Default value'),
  126. '#default_value' => $component['value'],
  127. '#description' => t('The default value of the field.') . theme('webform_token_help'),
  128. '#size' => 60,
  129. '#maxlength' => 1024,
  130. '#weight' => 0,
  131. );
  132. $form['display']['width'] = array(
  133. '#type' => 'textfield',
  134. '#title' => t('Width'),
  135. '#default_value' => $component['extra']['width'],
  136. '#description' => t('Width of the textfield.') . ' ' . t('Leaving blank will use the default size.'),
  137. '#size' => 5,
  138. '#maxlength' => 10,
  139. '#weight' => 0,
  140. '#parents' => array( 'extra', 'width' ),
  141. );
  142. $form['display']['placeholder'] = array(
  143. '#type' => 'textfield',
  144. '#title' => t('Placeholder'),
  145. '#default_value' => $component['extra']['placeholder'],
  146. '#description' => t('The text will be shown in the field until the user starts entering a value.'),
  147. '#weight' => 1,
  148. '#parents' => array( 'extra', 'placeholder' ),
  149. );
  150. $form['display']['disabled'] = array(
  151. '#type' => 'checkbox',
  152. '#title' => t('Disabled'),
  153. '#return_value' => 1,
  154. '#description' => t('Make this field non-editable. Useful for setting an unchangeable default value.'),
  155. '#weight' => 11,
  156. '#default_value' => $component['extra']['disabled'],
  157. '#parents' => array( 'extra', 'disabled' ),
  158. );
  159. return $form;
  160. }
  161. /**
  162. * Render a Webform component to be part of a form.
  163. *
  164. * @param $component
  165. * A Webform component array.
  166. * @param $value
  167. * If editing an existing submission or resuming a draft, this will contain
  168. * an array of values to be shown instead of the default in the component
  169. * configuration. This value will always be an array, keyed numerically for
  170. * each value saved in this field.
  171. * @param $filter
  172. * Whether or not to filter the contents of descriptions and values when
  173. * rendering the component. Values need to be unfiltered to be editable by
  174. * Form Builder.
  175. *
  176. * @see _webform_client_form_add_component()
  177. */
  178. function _webform_render_phone($component, $value = NULL, $filter = TRUE) {
  179. //TODO: change these to use non-private functions (no _) if/when webform 3.x is entirely deprecated
  180. $form_item = array(
  181. '#type' => module_exists('elements') ? 'telfield' : 'textfield',
  182. // '#default_value' => $filter ? webform_replace_tokens($component['value'], NULL, NULL, NULL, TRUE) : $component['value'],
  183. '#default_value' => $filter ? _webform_filter_values($component['value']) : $component['value'],
  184. '#attributes' => $component['extra']['attributes'],
  185. '#theme_wrappers' => array( 'webform_element' ),
  186. // '#description' => $filter ? webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
  187. '#description' => $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
  188. '#element_validate' => array( 'webform_validate_phone' ),
  189. '#maxlength' => ( $component['extra']['country'] == 'int' ? ( isset( $component['extra']['phone_int_max_length'] ) ? $component['extra']['phone_int_max_length'] : NULL ) : NULL ),
  190. '#required' => $component['required'] || $component['mandatory'], //Either one being true will could as required...because webform changed in 4.x-alpha8
  191. '#size' => 17,
  192. // '#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
  193. '#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
  194. '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
  195. '#weight' => $component['weight'],
  196. '#translatable' => array( 'title', 'description' ),
  197. );
  198. if ($component['required']) {
  199. $form_item['#attributes']['required'] = 'required';
  200. }
  201. if (isset( $value )) {
  202. $form_item['#default_value'] = $value[0];
  203. }
  204. // Change the 'width' option to the correct 'size' option.
  205. if ($component['extra']['width'] > 0) {
  206. $form_item['#size'] = $component['extra']['width'];
  207. }
  208. // Show the placeholder text if used.
  209. if ($component['extra']['placeholder']) {
  210. $form_item['#attributes']['placeholder'] = $component['extra']['placeholder'];
  211. }
  212. if ($component['extra']['disabled']) {
  213. if ($filter) {
  214. $form_item['#attributes']['readonly'] = 'readonly';
  215. }
  216. else {
  217. $form_item['#disabled'] = TRUE;
  218. }
  219. }
  220. if (isset( $value[0] )) {
  221. $form_item['#default_value'] = $value[0];
  222. }
  223. return $form_item;
  224. }
  225. /**
  226. * Validation Callback for phone field
  227. */
  228. function webform_validate_phone($element, $form_state) {
  229. $value = $element['#value'];
  230. if (isset( $value ) && $value != '') {
  231. $ccode = $element['#webform_component']['extra']['country'];
  232. //run through 'phone' module's validation
  233. if (!valid_phone_number($ccode, $value)) {
  234. $country = phone_country_info($ccode);
  235. form_error($element, t($country['error'], array( '%value' => $value )));
  236. }
  237. }
  238. }
  239. /**
  240. * Display the result of a submission for a component.
  241. * The output of this function will be displayed under the "Results" tab then
  242. * "Submissions". This should output the saved data in some reasonable manner.
  243. *
  244. * @param $component
  245. * A Webform component array.
  246. * @param $value
  247. * An array of information containing the submission result, directly
  248. * correlating to the webform_submitted_data database table schema.
  249. * @param $format
  250. * Either 'html' or 'text'. Defines the format that the content should be
  251. * returned as. Make sure that returned content is run through check_plain()
  252. * or other filtering functions when returning HTML.
  253. *
  254. * @return
  255. * A renderable element containing at the very least these properties:
  256. * - #title
  257. * - #weight
  258. * - #component
  259. * - #format
  260. * - #value
  261. * Webform also uses #theme_wrappers to output the end result to the user,
  262. * which will properly format the label and content for use within an e-mail
  263. * (such as wrapping the text) or as HTML (ensuring consistent output).
  264. */
  265. function _webform_display_phone($component, $value, $format = 'html') {
  266. return array(
  267. '#title' => $component['name'],
  268. '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
  269. '#weight' => $component['weight'],
  270. '#theme' => 'webform_display_phonefield',
  271. '#theme_wrappers' => $format == 'html' ? array( 'webform_element' ) : array( 'webform_element_text' ),
  272. '#post_render' => array( 'webform_element_wrapper' ),
  273. '#component' => $component,
  274. '#format' => $format,
  275. '#value' => isset( $value[0] ) ? $value[0] : '',
  276. '#translatable' => array( 'title', 'description' ),
  277. );
  278. }
  279. /**
  280. * Format the output of data for this component.
  281. */
  282. function theme_webform_display_phonefield($variables) {
  283. $element = $variables['element'];
  284. $plain_value = check_plain($element['#value']);
  285. if ($element['#format'] == 'html') {
  286. //Use smarter detection if available for formatting the output
  287. $is_mobile_device = module_exists('mobile_tools') ? mobile_tools_is_mobile_device() : strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== FALSE || strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== FALSE;
  288. $value = ( $is_mobile_device ) ? '<a href="tel:' . $plain_value . '">' . $plain_value . '</a>' : $plain_value;
  289. }
  290. else {
  291. $value = $plain_value;
  292. }
  293. return $value;
  294. }
  295. /**
  296. * A hook for changing the input values before saving to the database.
  297. * Webform expects a component to consist of a single field, or a single array
  298. * of fields. If you have a component that requires a deeper form tree
  299. * you must flatten the data into a single array using this callback
  300. * or by setting #parents on each field to avoid data loss and/or unexpected
  301. * behavior.
  302. * Note that Webform will save the result of this function directly into the
  303. * database.
  304. *
  305. * @param $component
  306. * A Webform component array.
  307. * @param $value
  308. * The POST data associated with the user input.
  309. *
  310. * @return
  311. * An array of values to be saved into the database. Note that this should be
  312. * a numerically keyed array.
  313. */
  314. function _webform_submit_phone($component, $value) {
  315. $ccode = $component['extra']['country'];
  316. if (phone_countries($ccode) !== NULL) {
  317. if (isset( $value ) && !empty( $value )) {
  318. //Use 'phone' module to format the number
  319. return format_phone_number($ccode, $value, $component['extra']);
  320. }
  321. }
  322. return FALSE; //If we haven't returned already, something failed.
  323. }
  324. /**
  325. * Calculate and returns statistics about results for this component.
  326. * This takes into account all submissions to this webform. The output of this
  327. * function will be displayed under the "Results" tab then "Analysis".
  328. *
  329. * @param $component
  330. * An array of information describing the component, directly correlating to
  331. * the webform_component database schema.
  332. * @param $sids
  333. * An optional array of submission IDs (sid). If supplied, the analysis will
  334. * be limited to these sids.
  335. * @param $single
  336. * Boolean flag determining if the details about a single component are being
  337. * shown. May be used to provided detailed information about a single
  338. * component's analysis, such as showing "Other" options within a select list.
  339. *
  340. * @return
  341. * An array of data rows, each containing a statistic for this component's
  342. * submissions.
  343. */
  344. function _webform_analysis_phone($component, $sids = array(), $single = FALSE) {
  345. // Generate the list of options and questions.
  346. $query = db_select('webform_submitted_data', 'wsd', array( 'fetch' => PDO::FETCH_ASSOC ))->fields('wsd', array( 'data' ))->condition('nid', $component['nid'])->condition('cid', $component['cid']);
  347. if (count($sids)) {
  348. $query->condition('sid', $sids, 'IN');
  349. }
  350. $non_blanks = 0;
  351. $submissions = 0;
  352. $result = $query->execute();
  353. foreach ($result as $data) {
  354. if (drupal_strlen(trim($data['data'])) > 0) {
  355. $non_blanks++;
  356. }
  357. $submissions++;
  358. }
  359. $rows[0] = array(
  360. t('Left Blank'),
  361. ( $submissions - $non_blanks )
  362. );
  363. $rows[1] = array(
  364. t('User entered value'),
  365. $non_blanks
  366. );
  367. return $rows;
  368. }
  369. /**
  370. * Return the result of a component value for display in a table.
  371. * The output of this function will be displayed under the "Results" tab then
  372. * "Table".
  373. *
  374. * @param $component
  375. * A Webform component array.
  376. * @param $value
  377. * An array of information containing the submission result, directly
  378. * correlating to the webform_submitted_data database schema.
  379. *
  380. * @return
  381. * Textual output formatted for human reading.
  382. */
  383. function _webform_table_phone($component, $value) {
  384. return check_plain(empty( $value[0] ) ? '' : $value[0]);
  385. }
  386. /**
  387. * Return the header for this component to be displayed in a CSV file.
  388. * The output of this function will be displayed under the "Results" tab then
  389. * "Download".
  390. *
  391. * @param $component
  392. * A Webform component array.
  393. * @param $export_options
  394. * An array of options that may configure export of this field.
  395. *
  396. * @return
  397. * An array of data to be displayed in the first three rows of a CSV file, not
  398. * including either prefixed or trailing commas.
  399. */
  400. function _webform_csv_headers_phone($component, $export_options) {
  401. $header = array();
  402. $header[0] = '';
  403. $header[1] = '';
  404. $header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
  405. return $header;
  406. }
  407. /**
  408. * Format the submitted data of a component for CSV downloading.
  409. * The output of this function will be displayed under the "Results" tab then
  410. * "Download".
  411. *
  412. * @param $component
  413. * A Webform component array.
  414. * @param $export_options
  415. * An array of options that may configure export of this field.
  416. * @param $value
  417. * An array of information containing the submission result, directly
  418. * correlating to the webform_submitted_data database schema.
  419. *
  420. * @return
  421. * An array of items to be added to the CSV file. Each value within the array
  422. * will be another column within the file. This function is called once for
  423. * every row of data.
  424. */
  425. function _webform_csv_data_phone($component, $export_options, $value) {
  426. return !isset( $value[0] ) ? '' : $value[0];
  427. }
  428. /**
  429. * The first hook provides the name and position of the field in the Form Builder palette, as well as a default element to display when the field is pulled out of the palette.
  430. * The second hook maps the component properties and options to FormAPI properties that Form Builder can manipulate.
  431. * Form Builder then will manage pulling the form out of the normal Webform configuration form, loading configuration, and saving it.
  432. * There are plenty of examples in the form_builder_webform.components.inc file that other modules (such as Webform Phone Number) can use as templates.
  433. * I'm moving this request over to that module's queue, and changing to a feature request.
  434. */
  435. /**
  436. * @defgroup form-builder-webform-phone-callbacks Callbacks for the Phone component
  437. * @{
  438. */
  439. /**
  440. * Implements _form_builder_webform_form_builder_types_component().
  441. */
  442. function _form_builder_webform_form_builder_types_phone() {
  443. drupal_add_css(drupal_get_path('module', 'webform_phone') . '/webform_phone.css');
  444. $fields = array();
  445. $fields['phone'] = array(
  446. 'title' => t('Phone Number'),
  447. 'properties' => array(
  448. 'country',
  449. 'phone_country_code',
  450. 'phone_default_country_code',
  451. 'phone_int_max_length',
  452. 'ca_phone_separator',
  453. 'ca_phone_parentheses',
  454. ),
  455. 'weight' => -17,
  456. //Doesn't make sense that modules get to weight themselves, why wouldn't everyone want to be first?
  457. );
  458. $defaults = _webform_defaults_phone();
  459. $fields['phone']['default'] = _form_builder_webform_default('phone');
  460. $fields['phone']['default']['#title'] = t('New Phone Number Field');
  461. $fields['phone']['default']['#country'] = $defaults['extra']['country'];
  462. $fields['phone']['default']['#phone_country_code'] = $defaults['extra']['phone_country_code'];
  463. $fields['phone']['default']['#phone_default_country_code'] = $defaults['extra']['phone_default_country_code'];
  464. $fields['phone']['default']['#phone_int_max_length'] = $defaults['extra']['phone_int_max_length'];
  465. $fields['phone']['default']['#ca_phone_separator'] = $defaults['extra']['ca_phone_separator'];
  466. $fields['phone']['default']['#ca_phone_parentheses'] = $defaults['extra']['ca_phone_parentheses'];
  467. return $fields;
  468. }
  469. /**
  470. * Implements _form_builder_webform_form_builder_map_component().
  471. */
  472. function _form_builder_webform_form_builder_map_phone() {
  473. return array(
  474. 'form_builder_type' => 'phone',
  475. 'properties' => array(
  476. 'country' => array(
  477. 'form_parents' => array(
  478. 'extra',
  479. 'country'
  480. ),
  481. 'storage_parents' => array(
  482. 'extra',
  483. 'country'
  484. ),
  485. ),
  486. 'phone_country_code' => array(
  487. 'form_parents' => array(
  488. 'extra',
  489. 'phone_country_code'
  490. ),
  491. 'storage_parents' => array(
  492. 'extra',
  493. 'phone_country_code'
  494. ),
  495. ),
  496. 'phone_default_country_code' => array(
  497. 'form_parents' => array(
  498. 'extra',
  499. 'phone_default_country_code'
  500. ),
  501. 'storage_parents' => array(
  502. 'extra',
  503. 'phone_default_country_code'
  504. ),
  505. ),
  506. 'phone_int_max_length' => array(
  507. 'form_parents' => array(
  508. 'extra',
  509. 'phone_int_max_length'
  510. ),
  511. 'storage_parents' => array(
  512. 'extra',
  513. 'phone_int_max_length'
  514. ),
  515. ),
  516. 'ca_phone_separator' => array(
  517. 'form_parents' => array(
  518. 'extra',
  519. 'ca_phone_separator'
  520. ),
  521. 'storage_parents' => array(
  522. 'extra',
  523. 'ca_phone_separator'
  524. ),
  525. ),
  526. 'ca_phone_parentheses' => array(
  527. 'form_parents' => array(
  528. 'extra',
  529. 'ca_phone_parentheses'
  530. ),
  531. 'storage_parents' => array(
  532. 'extra',
  533. 'ca_phone_parentheses'
  534. ),
  535. ),
  536. ),
  537. );
  538. }
  539. /**
  540. * @} End of "defgroup form-builder-webform-phone-callbacks"
  541. */