webform_validation.validators.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. <?php
  2. /**
  3. * @file
  4. * Provides validation functionality and hooks
  5. */
  6. /**
  7. * Implements hook_webform_validation_validators().
  8. *
  9. * This function returns an array of validators, in the validator key => options array form.
  10. * Possible options:
  11. * - name (required): name of the validator
  12. * - component types (required): defines which component types can be validated by this validator. Specify 'all' to allow all types
  13. * - custom_error (optional): define whether a user can specify a custom error message upon creating the validation rule.
  14. * - custom_data (optional): define whether custom data can be added to the validation rule
  15. * - min_components (optional): define the minimum number of components to be selected for creating a validation rule
  16. * - max_components (optional): define the maximum number of components to be selected for creating a validation rule
  17. * - description (optional): provide a descriptive explanation about the validator
  18. */
  19. function webform_validation_webform_validation_validators() {
  20. return array(
  21. 'numeric' => array(
  22. 'name' => "Numeric values",
  23. 'component_types' => array(
  24. 'textfield',
  25. 'hidden',
  26. ),
  27. 'custom_data' => array(
  28. 'label' => t('Specify numeric validation range'),
  29. 'description' => t('Optionally specify the minimum-maximum range to validate the user-entered numeric value against.') . ' ' . t('Usage') . ':'
  30. . theme('item_list', array('items' => array(t('empty: no value validation'), t('"100": greater than or equal to 100'), t('"|100": less than or equal to 100 (including negative numbers)'), t('"0|100": greater than or equal to 0 &amp; less than or equal to 100'), t('"10|100": greater than or equal to 10 &amp; less than or equal to 100'), t('"-100|-10": greater than or equal to -100 &amp; less than or equal to -10')))),
  31. 'required' => FALSE,
  32. ),
  33. 'description' => t('Verifies that user-entered values are numeric, with the option to specify min and / or max values.'),
  34. ),
  35. 'min_length' => array(
  36. 'name' => "Minimum length",
  37. 'component_types' => array(
  38. 'textfield',
  39. 'textarea',
  40. 'email',
  41. 'hidden',
  42. ),
  43. 'custom_data' => array(
  44. 'label' => t('Minimum number of characters'),
  45. 'description' => t('Specify the minimum number of characters that have to be entered to pass validation.'),
  46. ),
  47. 'description' => t('Verifies that a user-entered value contains at least the specified number of characters'),
  48. ),
  49. 'max_length' => array(
  50. 'name' => "Maximum length",
  51. 'component_types' => array(
  52. 'textfield',
  53. 'textarea',
  54. 'email',
  55. 'hidden',
  56. ),
  57. 'custom_data' => array(
  58. 'label' => t('Maximum number of characters'),
  59. 'description' => t('Specify the maximum number of characters that can be entered to pass validation.'),
  60. ),
  61. 'description' => t('Verifies that a user-entered value contains at most the specified number of characters'),
  62. ),
  63. 'min_words' => array(
  64. 'name' => "Minimum number of words",
  65. 'component_types' => array(
  66. 'textfield',
  67. 'textarea',
  68. 'hidden',
  69. ),
  70. 'custom_data' => array(
  71. 'label' => t('Minimum number of words'),
  72. 'description' => t('Specify the minimum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.')
  73. ),
  74. 'description' => t('Verifies that a user-entered value contains at least the specified number of words'),
  75. ),
  76. 'max_words' => array(
  77. 'name' => "Maximum number of words",
  78. 'component_types' => array(
  79. 'textfield',
  80. 'textarea',
  81. 'hidden',
  82. ),
  83. 'custom_data' => array(
  84. 'label' => t('Maximum number of words'),
  85. 'description' => t('Specify the maximum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.')
  86. ),
  87. 'description' => t('Verifies that a user-entered value contains at most the specified number of words'),
  88. ),
  89. 'equal' => array(
  90. 'name' => "Equal values",
  91. 'component_types' => array(
  92. 'textfield',
  93. 'email',
  94. 'select',
  95. 'hidden',
  96. ),
  97. 'min_components' => 2,
  98. 'description' => t('Verifies that all specified components contain equal values'),
  99. ),
  100. 'unique' => array(
  101. 'name' => "Unique values",
  102. 'component_types' => array(
  103. 'textfield',
  104. 'email',
  105. 'select',
  106. 'hidden',
  107. ),
  108. 'min_components' => 2,
  109. 'description' => t('Verifies that all specified components contain unique values'),
  110. ),
  111. 'specific_value' => array(
  112. 'name' => "Specific value(s)",
  113. 'component_types' => array(
  114. 'select',
  115. 'textfield',
  116. 'textarea',
  117. 'email',
  118. 'hidden',
  119. ),
  120. 'custom_error' => TRUE,
  121. 'custom_data' => array(
  122. 'label' => t('(Key) value'),
  123. 'description' => t('Specify the specific value(s) you want the component to contain. Separate multiple options by a comma. For components that have keys, use the key value instead.'),
  124. ),
  125. 'max_components' => 1,
  126. 'description' => t('Verifies that the specified component contains a defined value'),
  127. ),
  128. 'oneoftwo' => array(
  129. 'name' => "Require at least one of two fields",
  130. 'component_types' => array(
  131. 'textfield',
  132. 'textarea',
  133. 'email',
  134. 'select',
  135. ),
  136. 'min_components' => 2,
  137. 'max_components' => 2,
  138. 'description' => t('Forces the user to specify / select at least one of two selected webform components'),
  139. ),
  140. 'oneofseveral' => array(
  141. 'name' => "Require at least one of several fields",
  142. 'component_types' => array(
  143. 'textfield',
  144. 'textarea',
  145. 'email',
  146. 'select',
  147. ),
  148. 'min_components' => 2,
  149. 'description' => t('Forces the user to specify / select at least one of several selected webform components'),
  150. ),
  151. 'select_min' => array(
  152. 'name' => "Minimum number of selections required",
  153. 'component_types' => array(
  154. 'select',
  155. ),
  156. 'custom_data' => array(
  157. 'label' => t('Minimum number of selections'),
  158. 'description' => t('Specify the minimum number of options a user should select.'),
  159. ),
  160. 'description' => t('Forces the user to select at least a defined number of options from the specified webform components'),
  161. ),
  162. 'select_max' => array(
  163. 'name' => "Maximum number of selections allowed",
  164. 'component_types' => array(
  165. 'select',
  166. ),
  167. 'custom_data' => array(
  168. 'label' => t('Maximum number of selections'),
  169. 'description' => t('Specify the maximum number of options a user can select.'),
  170. ),
  171. 'description' => t('Forces the user to select at most a defined number of options from the specified webform components'),
  172. ),
  173. 'select_exact' => array(
  174. 'name' => "Exact number of selections required",
  175. 'component_types' => array(
  176. 'select',
  177. ),
  178. 'custom_data' => array(
  179. 'label' => t('Number of selections'),
  180. 'description' => t('Specify how many options a user can select.'),
  181. ),
  182. 'description' => t('Forces the user to select exactly the defined number of options from the specified webform components'),
  183. ),
  184. 'plain_text' => array(
  185. 'name' => "Plain text (disallow tags)",
  186. 'component_types' => array(
  187. 'textfield',
  188. 'textarea',
  189. 'email',
  190. 'hidden',
  191. ),
  192. 'description' => t("Verifies that user-entered data doesn't contain HTML tags"),
  193. ),
  194. 'regex' => array(
  195. 'name' => "Regular expression",
  196. 'component_types' => array(
  197. 'textfield',
  198. 'textarea',
  199. 'email',
  200. 'hidden',
  201. ),
  202. 'custom_error' => TRUE,
  203. 'custom_data' => array(
  204. 'label' => t('Regex code'),
  205. 'description' => t('Specify regex code to validate the user input against.'),
  206. ),
  207. 'description' => t("Validates user-entered text against a specified regular expression. Note: don't include delimiters such as /."),
  208. ),
  209. 'must_be_empty' => array(
  210. 'name' => "Must be empty",
  211. 'component_types' => array(
  212. 'textfield',
  213. 'hidden',
  214. ),
  215. 'description' => t('Verifies that a specified textfield remains empty - Recommended use case: used as an anti-spam measure by hiding the element with CSS'),
  216. ),
  217. 'blacklist' => array(
  218. 'name' => "Words blacklist",
  219. 'component_types' => array(
  220. 'textfield',
  221. 'textarea',
  222. 'email',
  223. 'hidden',
  224. ),
  225. 'custom_error' => TRUE,
  226. 'custom_data' => array(
  227. 'label' => t('Blacklisted words'),
  228. 'description' => t('Specify illegal words, seperated by commas. Make sure to escape reserved regex characters with an escape (\) character.'),
  229. ),
  230. 'description' => t("Validates that user-entered data doesn't contain any of the specified illegal words"),
  231. ),
  232. 'username' => array(
  233. 'name' => "Must match a username",
  234. 'component_types' => array(
  235. 'textfield',
  236. 'hidden',
  237. ),
  238. 'description' => t("Validates that user-entered data matches a username"),
  239. ),
  240. );
  241. }
  242. /**
  243. * Implements hook_webform_validation_validate().
  244. */
  245. function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
  246. if ($items) {
  247. $errors = array();
  248. switch ($validator_name) {
  249. case 'numeric':
  250. $num_range = _webform_numeric_check_data($rule['data']);
  251. foreach ($items as $key => $val) {
  252. if ($val != '') {
  253. // first check if the value is numeric
  254. if (!is_numeric($val)) {
  255. $errors[$key] = t('%item is not numeric', array('%item' => $components[$key]['name']));
  256. }
  257. // now validate the entered numeric value against the validator range settings, if appropriate
  258. // a. validate min & max
  259. if (isset($num_range['min']) && isset($num_range['max'])) {
  260. // validate the min - max range
  261. if ($val < $num_range['min'] || $val > $num_range['max']) {
  262. $errors[$key] = t('%item is not within the allowed range %range', array('%item' => $components[$key]['name'], '%range' => str_replace('|' , ' - ', $rule['data'])));
  263. }
  264. }
  265. else {
  266. // b. validate min
  267. if (isset($num_range['min'])) {
  268. if ($val < $num_range['min']) {
  269. $errors[$key] = t('%item should be greater than or equal to %val', array('%item' => $components[$key]['name'], '%val' => $num_range['min']));
  270. }
  271. }
  272. // c. validate max
  273. if (isset($num_range['max'])) {
  274. if ($val > $num_range['max']) {
  275. $errors[$key] = t('%item should be less than or equal to %val', array('%item' => $components[$key]['name'], '%val' => $num_range['max']));
  276. }
  277. }
  278. }
  279. }
  280. }
  281. return $errors;
  282. break;
  283. case 'min_length':
  284. $min_length = $rule['data'];
  285. foreach ($items as $key => $val) {
  286. if ($val != '' && (drupal_strlen($val) < $min_length)) {
  287. $errors[$key] = t('%item needs to be at least %num characters long', array('%item' => $components[$key]['name'], '%num' => $min_length));
  288. }
  289. }
  290. return $errors;
  291. break;
  292. case 'max_length':
  293. $max_length = $rule['data'];
  294. foreach ($items as $key => $val) {
  295. if ($val != '' && (drupal_strlen($val) > $max_length)) {
  296. $errors[$key] = t('%item can be maximum %num characters long', array('%item' => $components[$key]['name'], '%num' => $max_length));
  297. }
  298. }
  299. return $errors;
  300. break;
  301. case 'min_words':
  302. $min_words = $rule['data'];
  303. foreach ($items as $key => $val) {
  304. if ($val != '' && (count(preg_split("/[\s]+/", trim($val))) < $min_words)) {
  305. $error = format_plural($min_words, '%item needs to be at least 1 word long', '%item needs to be at least @count words long', array('%item' => $components[$key]['name']));
  306. $errors[$key] = $error;
  307. }
  308. }
  309. return $errors;
  310. break;
  311. case 'max_words':
  312. $max_words = $rule['data'];
  313. foreach ($items as $key => $val) {
  314. if ($val != '' && (count(preg_split("/[\s]+/", trim($val))) > $max_words)) {
  315. $error = format_plural($max_words, '%item can be maximum 1 word long', '%item can be maximum @count words long', array('%item' => $components[$key]['name']));
  316. $errors[$key] = $error;
  317. }
  318. }
  319. return $errors;
  320. break;
  321. case "equal":
  322. $first_entry_key = key($items);
  323. $first_entry = array_shift($items);
  324. $first_entry = _webform_validation_flatten_array($first_entry); // flatten in case of array
  325. // now check if following components equal the first one
  326. foreach ($items as $key => $val) {
  327. $val = _webform_validation_flatten_array($val); // flatten in case of array
  328. if ($val !== $first_entry) {
  329. $errors[$key] = t('%item_checked does not match %item_first', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
  330. }
  331. }
  332. return $errors;
  333. break;
  334. case "unique":
  335. foreach ($items as $key => $val) {
  336. if (is_array($val)) {
  337. // make sure to flatten arrays first
  338. $items[$key] = _webform_validation_flatten_array($val);
  339. }
  340. if (empty($items[$key])) {
  341. // items without a value selected shouldn't be validated
  342. unset($items[$key]);
  343. }
  344. }
  345. // now we count how many times each value appears, and find out which values appear more than once
  346. $items_count = array_count_values(array_map('strtolower', array_map('trim', $items)));
  347. $doubles = array_filter($items_count, create_function('$x', 'return $x > 1;'));
  348. foreach ($items as $key => $val) {
  349. if (in_array(strtolower($val), array_keys($doubles))) {
  350. $errors[$key] = t('The value of %item is not unique', array('%item' => $components[$key]['name']));
  351. }
  352. }
  353. return $errors;
  354. break;
  355. case "specific_value":
  356. $specific_values = explode(',', $rule['data']);
  357. $specific_values = array_map('trim', $specific_values);
  358. foreach ($items as $key => $val) {
  359. if (is_array($val)) {
  360. $val = _webform_validation_flatten_array($val);
  361. }
  362. if (!in_array($val, $specific_values)) {
  363. $errors[$key] = _webform_validation_i18n_error_message($rule);
  364. }
  365. }
  366. return $errors;
  367. break;
  368. case "oneoftwo":
  369. // $components should have 2 items
  370. $keys = array_keys($items);
  371. $item1 = array_shift($keys);
  372. $item2 = array_shift($keys);
  373. $entry1 = _webform_validation_flatten_array($items[$item1]);
  374. $entry2 = _webform_validation_flatten_array($items[$item2]);
  375. if (empty($entry1) && empty($entry2)) {
  376. return array($item1 => t('You have to specify %item1 or %item2 (or both)', array('%item1' => $components[$item1]['name'], '%item2' => $components[$item2]['name'])));
  377. }
  378. break;
  379. case "oneofseveral":
  380. foreach ($items as $key => $val) {
  381. if (is_array($val)) {
  382. // make sure to flatten arrays first
  383. $items[$key] = _webform_validation_flatten_array($val);
  384. }
  385. }
  386. // $components should have at least one of several items
  387. if (count(array_filter($items)) < 1) {
  388. $keys = array_keys($items);
  389. $names = array();
  390. foreach ($keys as $value) {
  391. $names[] = $components[$value]['name'];
  392. }
  393. return array($keys[0] => t('You have to specify at least one of these items:') . theme('item_list', array('items' => $names)));
  394. }
  395. break;
  396. case "select_min":
  397. $min_selections = $rule['data'];
  398. foreach ($items as $key => $val) {
  399. if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) < $min_selections)) {
  400. $errors[$key] = t('Please select at least %num options for %item', array('%num' => $min_selections, '%item' => $components[$key]['name']));
  401. }
  402. }
  403. return $errors;
  404. break;
  405. case "select_max":
  406. $max_selections = $rule['data'];
  407. foreach ($items as $key => $val) {
  408. if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) > $max_selections)) {
  409. $errors[$key] = t('Please select maximum %num options for %item', array('%num' => $max_selections, '%item' => $components[$key]['name']));
  410. }
  411. }
  412. return $errors;
  413. break;
  414. case "select_exact":
  415. $allowed_selections = $rule['data'];
  416. foreach ($items as $key => $val) {
  417. if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections)) {
  418. $errors[$key] = t('Please select %num options for %item', array('%num' => $allowed_selections, '%item' => $components[$key]['name']));
  419. }
  420. }
  421. return $errors;
  422. break;
  423. case "plain_text":
  424. foreach ($items as $key => $val) {
  425. if ($val != '' && (strcmp($val, strip_tags($val)))) {
  426. $errors[$key] = t('%item only allows the use of plain text', array('%item' => $components[$key]['name']));
  427. }
  428. }
  429. return $errors;
  430. break;
  431. case "regex":
  432. mb_regex_encoding('UTF-8');
  433. $regex = $rule['data'];
  434. foreach ($items as $key => $val) {
  435. if ($val != '' && (!mb_ereg("$regex", $val))) {
  436. $errors[$key] = _webform_validation_i18n_error_message($rule);
  437. }
  438. }
  439. return $errors;
  440. break;
  441. case 'must_be_empty':
  442. foreach ($items as $key => $val) {
  443. if ($val) {
  444. $errors[$key] = t('%item does not contain the correct data', array('%item' => $components[$key]['name']));
  445. }
  446. }
  447. return $errors;
  448. break;
  449. case "blacklist":
  450. $blacklist = explode(',', $rule['data']);
  451. $blacklist = array_map('trim', $blacklist);
  452. $blacklist_regex = implode('|', $blacklist);
  453. foreach ($items as $key => $val) {
  454. if ($val != '' && (preg_match("/$blacklist_regex/i", $val))) {
  455. $errors[$key] = _webform_validation_i18n_error_message($rule);
  456. }
  457. }
  458. return $errors;
  459. break;
  460. case "username":
  461. foreach ($items as $key => $val) {
  462. // load user - if username does not match or status 0 throw error
  463. if ($val != '') {
  464. if (!db_query("SELECT COUNT(*) FROM {users} WHERE name = :username AND status = 1", array(':username' => $val))->fetchField()) {
  465. // Username doesn't exist
  466. $errors[$key] = t('The %item field does not match an active username.', array('%item' => $components[$key]['name']));
  467. }
  468. }
  469. }
  470. return $errors;
  471. break;
  472. }
  473. }
  474. }
  475. /**
  476. * Helper function to deal with submitted values that are arrays (e.g. multiple select component)
  477. * We flatten the array as a comma-separated list to do the comparison.
  478. */
  479. function _webform_validation_flatten_array($val) {
  480. if (is_array($val)) {
  481. $arr = array_filter($val, '_webform_validation_check_false');
  482. return implode(',', $arr);
  483. }
  484. return $val;
  485. }
  486. /**
  487. * Get a list of validator definitions
  488. */
  489. function webform_validation_get_validators() {
  490. $validators = module_invoke_all("webform_validation_validators");
  491. // let modules use hook_webform_validator_alter($validators) to change validator settings
  492. drupal_alter('webform_validator', $validators);
  493. return $validators;
  494. }
  495. /**
  496. * @todo Please document this function.
  497. * @see http://drupal.org/node/1354
  498. */
  499. function webform_validation_get_validators_selection() {
  500. $selection = array();
  501. $validators = webform_validation_get_validators();
  502. if ($validators) {
  503. foreach ($validators as $validator_key => $validator_info) {
  504. $selection[$validator_key] = $validator_info['name'];
  505. }
  506. }
  507. return $selection;
  508. }
  509. /**
  510. * Get a list of valid component types per validator, as defined via hook_webform_validation_validators().
  511. * If 'all' is specified, all available component types will be returned.
  512. */
  513. function webform_validation_valid_component_types($validator) {
  514. $validators = webform_validation_get_validators();
  515. if ($info = $validators[$validator]) {
  516. $allowed_types = $info['component_types'];
  517. if (_webform_validation_all_allowed($allowed_types)) {
  518. return array_keys(webform_components());
  519. }
  520. return $info['component_types'];
  521. }
  522. }
  523. /**
  524. * Helper function to check whether all components are allowed to be used for a certain validator
  525. */
  526. function _webform_validation_all_allowed($allowed) {
  527. if ($allowed) {
  528. foreach ($allowed as $type) {
  529. if ($type == "all") {
  530. return TRUE;
  531. }
  532. }
  533. }
  534. return FALSE;
  535. }
  536. /**
  537. * @todo Please document this function.
  538. * @see http://drupal.org/node/1354
  539. */
  540. function webform_validation_get_validator_info($validator_key) {
  541. $validators = webform_validation_get_validators();
  542. return $validators[$validator_key];
  543. }
  544. /**
  545. * Handle translatable error messages, if available
  546. */
  547. function _webform_validation_i18n_error_message($rule) {
  548. $rule['error_message'] = filter_xss($rule['error_message']);
  549. if (module_exists('i18nstrings')) {
  550. return i18nstrings('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
  551. }
  552. return $rule['error_message'];
  553. }
  554. /**
  555. * Helper function used by array_filter to determine if a value was selected or not
  556. */
  557. function _webform_validation_check_false($var) {
  558. return $var !== FALSE && $var !== 0;
  559. }
  560. /**
  561. * Process the numeric value validation range that was provided in the numeric validator options
  562. */
  563. function _webform_numeric_check_data($data) {
  564. $range = array('min' => NULL, 'max' => NULL);
  565. // if no value was specified, don't validate
  566. if ($data == '') {
  567. return $range;
  568. }
  569. // If only one numeric value was specified, this is the min value
  570. if (is_numeric($data)) {
  571. $range['min'] = (int) $data;
  572. }
  573. if (strpos($data, '|') !== FALSE) {
  574. list($min, $max) = explode('|', $data);
  575. if ($min != '' && is_numeric($min)) {
  576. $range['min'] = (int) $min;
  577. }
  578. if ($max != '' && is_numeric($max)) {
  579. $range['max'] = (int) $max;
  580. }
  581. }
  582. return $range;
  583. }