webform_validation.module 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <?php
  2. /**
  3. * @file
  4. * Add validation rules to webforms
  5. */
  6. include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'webform_validation') . '/' . 'webform_validation.validators.inc';
  7. include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'webform_validation') . '/' . 'webform_validation.rules.inc';
  8. /**
  9. * Implements hook_menu().
  10. */
  11. function webform_validation_menu() {
  12. $items = array();
  13. $items['node/%webform_menu/webform/validation'] = array(
  14. 'title' => 'Form validation',
  15. 'page callback' => 'webform_validation_manage',
  16. 'page arguments' => array(1),
  17. 'access callback' => 'node_access',
  18. 'access arguments' => array('update', 1),
  19. 'file' => 'webform_validation.admin.inc',
  20. 'weight' => 3,
  21. 'type' => MENU_LOCAL_TASK,
  22. );
  23. $items['node/%webform_menu/webform/validation/add/%'] = array(
  24. 'title' => 'Add validation',
  25. 'page callback' => 'drupal_get_form',
  26. 'page arguments' => array('webform_validation_manage_rule', 1, 'add', 5),
  27. 'access callback' => 'node_access',
  28. 'access arguments' => array('update', 1),
  29. 'file' => 'webform_validation.admin.inc',
  30. 'type' => MENU_CALLBACK,
  31. );
  32. $items['node/%webform_menu/webform/validation/edit/%/%webform_validation_rule'] = array(
  33. 'title' => 'Edit rule',
  34. 'page callback' => 'drupal_get_form',
  35. 'page arguments' => array('webform_validation_manage_rule', 1, 'edit', 5, 6),
  36. 'access callback' => 'node_access',
  37. 'access arguments' => array('update', 1),
  38. 'file' => 'webform_validation.admin.inc',
  39. 'type' => MENU_CALLBACK,
  40. );
  41. $items['node/%webform_menu/webform/validation/delete/%webform_validation_rule'] = array(
  42. 'title' => 'Delete rule',
  43. 'page callback' => 'drupal_get_form',
  44. 'page arguments' => array('webform_validation_delete_rule', 5),
  45. 'access callback' => 'node_access',
  46. 'access arguments' => array('update', 1),
  47. 'file' => 'webform_validation.admin.inc',
  48. 'type' => MENU_CALLBACK,
  49. );
  50. return $items;
  51. }
  52. /**
  53. * Loads validation rule from menu parameter
  54. */
  55. function webform_validation_rule_load($ruleid) {
  56. return webform_validation_get_rule($ruleid);
  57. }
  58. /**
  59. * Implements hook_theme().
  60. */
  61. function webform_validation_theme() {
  62. return array(
  63. 'webform_validation_manage_add_rule' => array(
  64. 'variables' => array(
  65. 'nid' => NULL,
  66. ),
  67. ),
  68. 'webform_validation_manage_overview' => array(
  69. 'variables' => array(
  70. 'rules' => NULL,
  71. 'node' => NULL,
  72. ),
  73. ),
  74. );
  75. }
  76. /**
  77. * Implements hook_form_alter().
  78. */
  79. function webform_validation_form_alter(&$form, &$form_state, $form_id) {
  80. if (strpos($form_id, 'webform_client_form_') !== FALSE) {
  81. $form['#validate'][] = 'webform_validation_validate';
  82. }
  83. }
  84. /**
  85. * Webform validation handler to validate against the given rules
  86. */
  87. function webform_validation_validate($form, &$form_state) {
  88. $page_count = 1;
  89. $nid = $form_state['values']['details']['nid'];
  90. $node = node_load($nid);
  91. $values = isset($form_state['values']['submitted']) ? $form_state['values']['submitted'] : NULL;
  92. $flat_values = _webform_client_form_submit_flatten($node, $values);
  93. $rules = webform_validation_get_node_rules($nid);
  94. // Get number of pages for this webform
  95. if (isset($form_state['webform']['page_count'])) {
  96. $page_count = $form_state['webform']['page_count'];
  97. }
  98. else if (isset($form_state['storage']['page_count'])) {
  99. $page_count = $form_state['storage']['page_count'];
  100. }
  101. // Filter out rules that don't apply to this step in the multistep form
  102. if ($values && $page_count && $page_count > 1) {
  103. $current_page_components = webform_validation_get_field_keys($form_state['values']['submitted'], $node);
  104. if ($rules) {
  105. // filter out rules that don't belong in the current step
  106. foreach ($rules as $ruleid => $rule) {
  107. // get all the component formkeys for this specific validation rule
  108. $rule_formkeys = webform_validation_rule_get_formkeys($rule);
  109. $rule_applies_to_current_page = FALSE;
  110. if (!empty($rule_formkeys)) {
  111. foreach ($rule_formkeys as $formkey) {
  112. if (in_array($formkey, $current_page_components)) {
  113. // this rule applies to the current page,
  114. // because one of the rule components is on the page
  115. $rule_applies_to_current_page = TRUE;
  116. }
  117. }
  118. }
  119. if (!$rule_applies_to_current_page) {
  120. unset($rules[$ruleid]);
  121. }
  122. }
  123. }
  124. }
  125. if ($rules) {
  126. foreach ($rules as $rule) {
  127. // create a list of components that need validation against this rule (component id => user submitted value)
  128. $items = array();
  129. foreach ($rule['components'] as $cid => $component) {
  130. if (isset($flat_values[$cid])) {
  131. $items[$cid] = $flat_values[$cid];
  132. }
  133. }
  134. // prefix array keys to avoid reindexing by the module_invoke_all function call
  135. $items = webform_validation_prefix_keys($items);
  136. $component_definitions = webform_validation_prefix_keys($node->webform['components']);
  137. // have the submitted values validated
  138. $errors = module_invoke_all("webform_validation_validate", $rule['validator'], $items, $component_definitions, $rule);
  139. if ($errors) {
  140. $errors = webform_validation_unprefix_keys($errors);
  141. $components = webform_validation_unprefix_keys($component_definitions);
  142. foreach ($errors as $item_key => $error) {
  143. // build the proper form element error key, taking into account hierarchy
  144. $error_key = 'submitted][' . webform_validation_parent_tree($item_key, $components) . $components[$item_key]['form_key'];
  145. form_set_error($error_key, $error);
  146. }
  147. }
  148. }
  149. }
  150. }
  151. /**
  152. * Recursive helper function to get all field keys (including fields in fieldsets)
  153. */
  154. function webform_validation_get_field_keys($submitted, $node) {
  155. static $fields = array();
  156. foreach (element_children($submitted) as $child) {
  157. if (is_array($submitted[$child]) && element_children($submitted[$child])) {
  158. // only keep searching recursively if it's a fieldset
  159. $group_components = _webform_validation_get_group_types();
  160. if (in_array(_webform_validation_get_component_type($node, $child), $group_components)) {
  161. webform_validation_get_field_keys($submitted[$child], $node);
  162. }
  163. else {
  164. $fields[$child] = $child;
  165. }
  166. }
  167. else {
  168. $fields[$child] = $child;
  169. }
  170. }
  171. return $fields;
  172. }
  173. /**
  174. * Recursively add the parents for the element, to be used as first argument to form_set_error
  175. */
  176. function webform_validation_parent_tree($cid, $components) {
  177. $output = '';
  178. if ($pid = $components[$cid]['pid']) {
  179. $output .= webform_validation_parent_tree($pid, $components);
  180. $output .= $components[$pid]['form_key'] . '][';
  181. }
  182. return $output;
  183. }
  184. /**
  185. * Get an array of formkeys for all components that have been assigned to a rule
  186. */
  187. function webform_validation_rule_get_formkeys($rule) {
  188. $formkeys = array();
  189. if (isset($rule['components'])) {
  190. foreach ($rule['components'] as $cid => $component) {
  191. $formkeys[] = $component['form_key'];
  192. }
  193. }
  194. return $formkeys;
  195. }
  196. /**
  197. * Prefix numeric array keys to avoid them being reindexed by module_invoke_all
  198. */
  199. function webform_validation_prefix_keys($arr) {
  200. $ret = array();
  201. foreach ($arr as $k => $v) {
  202. $ret['item_' . $k] = $v;
  203. }
  204. return $ret;
  205. }
  206. /**
  207. * Undo prefixing numeric array keys to avoid them being reindexed by module_invoke_all
  208. */
  209. function webform_validation_unprefix_keys($arr) {
  210. $ret = array();
  211. foreach ($arr as $k => $v) {
  212. $new_key = str_replace('item_', '', $k);
  213. $ret[$new_key] = $v;
  214. }
  215. return $ret;
  216. }
  217. /**
  218. * Theme the 'add rule' list
  219. */
  220. function theme_webform_validation_manage_add_rule($variables) {
  221. $nid = $variables['nid'];
  222. $output = '';
  223. $validators = webform_validation_get_validators();
  224. if ($validators) {
  225. $output = '<h3>' . t('Add a validation rule') . '</h3>';
  226. $output .= '<dl>';
  227. foreach ($validators as $validator_key => $validator_info) {
  228. $item = '';
  229. $url = 'node/' . $nid . '/webform/validation/add/' . $validator_key;
  230. $components = ' (' . implode(', ', $validator_info['component_types']) . ')';
  231. $item = '<dt>' . l($validator_info['name'], $url, array("query" => drupal_get_destination())) . '</dt>';
  232. $item .= '<dd>';
  233. if ($validator_info['description']) {
  234. $item .= $validator_info['description'] . ' ';
  235. }
  236. $item .= $components . '</dd>';
  237. $output .= $item;
  238. }
  239. $output .= '</dl>';
  240. }
  241. return $output;
  242. }
  243. /**
  244. * Implements hook_webform_validation().
  245. */
  246. function webform_validation_webform_validation($type, $op, $data) {
  247. if ($type == 'rule' && in_array($op, array('add', 'edit'))) {
  248. if (module_exists('i18nstrings') && isset($data['error_message'])) {
  249. i18nstrings_update('webform_validation:error_message:' . $data['ruleid'] . ':message', $data['error_message']);
  250. }
  251. }
  252. }
  253. /**
  254. * Implements hook_node_insert().
  255. */
  256. function webform_validation_node_insert($node) {
  257. if (module_exists('clone')) {
  258. if (in_array($node->type, webform_variable_get('webform_node_types'))) {
  259. webform_validation_node_clone($node);
  260. }
  261. }
  262. }
  263. /**
  264. * Implements hook_node_delete().
  265. */
  266. function webform_validation_node_delete($node) {
  267. $rules = webform_validation_get_node_rules($node->nid);
  268. if ($rules) {
  269. foreach (array_keys($rules) as $ruleid) {
  270. webform_dynamic_delete_rule($ruleid);
  271. }
  272. }
  273. }
  274. /**
  275. * Adds support for node_clone module
  276. */
  277. function webform_validation_node_clone($node) {
  278. if (isset($node->clone_from_original_nid)) {
  279. $original_nid = $node->clone_from_original_nid;
  280. // Get existing rules for original node
  281. $rules = webform_validation_get_node_rules($original_nid);
  282. if ($rules) {
  283. foreach ($rules as $orig_ruleid => $rule) {
  284. unset($rule['ruleid']);
  285. $rule['action'] = 'add';
  286. $rule['nid'] = $node->nid; // attach existing rules to new node
  287. $rule['rule_components'] = $rule['components'];
  288. webform_validation_rule_save($rule);
  289. }
  290. }
  291. }
  292. }
  293. /**
  294. * Save a validation rule. Data comes from the admin form
  295. * or nodeapi function in case of node clone
  296. */
  297. function webform_validation_rule_save($values) {
  298. // save rules data
  299. if ($values['action'] == 'add') {
  300. drupal_write_record('webform_validation_rule', $values);
  301. $ruleid = $values['ruleid'];
  302. if ($ruleid && array_filter($values['rule_components'])) {
  303. webform_validation_save_rule_components($ruleid, array_filter($values['rule_components']));
  304. module_invoke_all('webform_validation', 'rule', 'add', $values);
  305. }
  306. }
  307. if ($values['action'] == 'edit') {
  308. drupal_write_record('webform_validation_rule', $values, 'ruleid');
  309. $ruleid = $values['ruleid'];
  310. // delete earlier component records for this rule id*/
  311. db_delete('webform_validation_rule_components')
  312. ->condition('ruleid', $ruleid)
  313. ->execute();
  314. if ($components = array_filter($values['rule_components'])) {
  315. webform_validation_save_rule_components($ruleid, $components);
  316. module_invoke_all('webform_validation', 'rule', 'edit', $values);
  317. }
  318. }
  319. }
  320. /**
  321. * Save components attached to a specific rule
  322. */
  323. function webform_validation_save_rule_components($ruleid, $components) {
  324. foreach ($components as $cid => $component) {
  325. $id = db_insert('webform_validation_rule_components')
  326. ->fields(array(
  327. 'ruleid' => $ruleid,
  328. 'cid' => $cid,
  329. ))
  330. ->execute();
  331. }
  332. }
  333. /**
  334. * Given a webform node, get the component type based on a given component key
  335. */
  336. function _webform_validation_get_component_type($node, $component_key) {
  337. if ($node->webform['components']) {
  338. foreach ($node->webform['components'] as $component) {
  339. if ($component['form_key'] == $component_key) {
  340. return $component['type'];
  341. }
  342. }
  343. }
  344. return FALSE;
  345. }
  346. /**
  347. * Get all webform components that are defined as a group
  348. */
  349. function _webform_validation_get_group_types(){
  350. $types = array();
  351. foreach(webform_components() as $name => $component){
  352. if(isset($component['features']['group']) && $component['features']['group']){
  353. $types[] = $name;
  354. }
  355. }
  356. return $types;
  357. }
  358. /**
  359. * Implementation of hook_webform_validator_alter().
  360. */
  361. function webform_validation_webform_validator_alter(&$validators) {
  362. // Add support for the Select (or Other) module
  363. if (module_exists('select_or_other')) {
  364. // if this module exists, all select components can now except user input.
  365. // Thus we provide those components the same rules as a textfield
  366. if ($validators) {
  367. foreach ($validators as $validator_name => $validator_info) {
  368. if (in_array('textfield', $validator_info['component_types'])) {
  369. $validators[$validator_name]['component_types'][] = 'select';
  370. }
  371. }
  372. }
  373. }
  374. }