webform.conditionals.inc 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778
  1. <?php
  2. /**
  3. * @file
  4. * Form elements and menu callbacks to provide conditional handling in Webform.
  5. */
  6. /**
  7. * Form builder; Provide the form for adding conditionals to a webform node.
  8. */
  9. function webform_conditionals_form($form, &$form_state, $node) {
  10. form_load_include($form_state, 'inc', 'webform', $name = 'includes/webform.components');
  11. form_load_include($form_state, 'inc', 'webform', $name = 'includes/webform.conditionals');
  12. // Add JavaScript settings to the page needed for conditional elements.
  13. _webform_conditional_expand_value_forms($node);
  14. if (isset($form_state['values']['conditionals'])) {
  15. // Remove the "new" conditional that always comes in.
  16. unset($form_state['values']['conditionals']['new']);
  17. $conditionals = $form_state['values']['conditionals'];
  18. }
  19. else {
  20. $conditionals = $node->webform['conditionals'];
  21. }
  22. // Empty out any conditionals that have no rules or actions.
  23. foreach ($conditionals as $rgid => &$conditional) {
  24. webform_delete_empty_subconditionals($conditional);
  25. if (empty($conditional['rules']) || empty($conditional['actions'])) {
  26. unset($conditionals[$rgid]);
  27. }
  28. }
  29. // Drop PHP reference.
  30. unset($conditional);
  31. // Check the current topological sort order for the conditionals and report
  32. // any errors, but only for actual form submissions and not for ajax-related
  33. // form builds, such as adding or removing a condition or conditional group.
  34. if (empty($form_state['triggering_element']['#ajax'])) {
  35. $node->webform['conditionals'] = $conditionals;
  36. webform_get_conditional_sorter($node)->reportErrors($conditionals);
  37. }
  38. $form['#tree'] = TRUE;
  39. $form['#node'] = $node;
  40. $form['#attached']['library'][] = array('webform', 'admin');
  41. $form['#attached']['css'][] = drupal_get_path('module', 'webform') . '/css/webform.css';
  42. // Wrappers used for AJAX addition/removal.
  43. $form['conditionals']['#theme'] = 'webform_conditional_groups';
  44. $form['conditionals']['#prefix'] = '<div id="webform-conditionals-ajax">';
  45. $form['conditionals']['#suffix'] = '</div>';
  46. // Keep track of the max conditional count to use as the range for weights.
  47. $form_state['conditional_count'] = isset($form_state['conditional_count']) ? $form_state['conditional_count'] : 1;
  48. $form_state['conditional_count'] = count($conditionals) > $form_state['conditional_count'] ? count($conditionals) : $form_state['conditional_count'];
  49. $source_list = webform_component_list($node, 'conditional', 'path', TRUE);
  50. $target_list = webform_component_list($node, TRUE, 'path', TRUE);
  51. $components = $node->webform['components'];
  52. $delta = $form_state['conditional_count'];
  53. $weight = -$delta - 1;
  54. $index = 0;
  55. foreach ($conditionals as $rgid => $conditional_group) {
  56. $weight = $conditional_group['weight'];
  57. $form['conditionals'][$rgid] = array(
  58. '#theme' => 'webform_conditional_group_row',
  59. '#even_odd' => ++$index % 2 ? 'odd' : 'even',
  60. '#weight' => $weight,
  61. 'rgid' => array(
  62. '#type' => 'value',
  63. '#value' => $rgid,
  64. ),
  65. 'conditional' => array(
  66. '#type' => 'webform_conditional',
  67. '#default_value' => $conditional_group,
  68. '#nid' => $node->nid,
  69. '#sources' => $source_list,
  70. '#actions' => array(
  71. 'show' => t('shown'),
  72. 'require' => t('required'),
  73. 'set' => t('set to'),
  74. ),
  75. '#targets' => $target_list,
  76. '#parents' => array('conditionals', $rgid),
  77. ),
  78. );
  79. foreach ($conditional_group['actions'] as $action) {
  80. $cid = $action['target'];
  81. if ($action['action'] == 'require' && !$components[$cid]['required']) {
  82. drupal_set_message(t('Component %title must be configured as Required for Webform to conditionally change its required status. <a href="!url">Configure %title.</a>',
  83. array(
  84. '%title' => $components[$cid]['name'],
  85. '!url' => url("node/{$node->nid}/webform/components/$cid", array('query' => array('destination' => "node/{$node->nid}/webform/conditionals"))),
  86. )
  87. ), 'error');
  88. }
  89. }
  90. $form['conditionals'][$rgid]['weight'] = array(
  91. '#type' => 'weight',
  92. '#title' => t('Weight for rule group !rgid', array('!rgid' => $rgid)),
  93. '#title_display' => 'invisible',
  94. '#default_value' => $weight,
  95. '#delta' => $delta,
  96. );
  97. }
  98. $form['conditionals']['new']['#weight'] = $weight + 1;
  99. $form['conditionals']['new']['weight'] = array(
  100. '#type' => 'weight',
  101. '#title' => t('Weight for new rule group'),
  102. '#title_display' => 'invisible',
  103. '#default_value' => $weight + 1,
  104. '#delta' => $delta,
  105. );
  106. $form['conditionals']['new']['new'] = array(
  107. '#type' => 'submit',
  108. '#value' => t('+'),
  109. '#submit' => array('webform_conditionals_form_add'),
  110. '#ajax' => array(
  111. 'progress' => 'none',
  112. 'effect' => 'fade',
  113. 'callback' => 'webform_conditionals_ajax',
  114. ),
  115. );
  116. // Create dummy remove button for form alignment only.
  117. $form['conditionals']['new']['remove'] = array(
  118. '#type' => 'submit',
  119. '#value' => t('-'),
  120. '#disabled' => TRUE,
  121. );
  122. $form['actions'] = array(
  123. '#type' => 'actions',
  124. '#tree' => FALSE,
  125. );
  126. $form['actions']['submit'] = array(
  127. '#type' => 'submit',
  128. '#value' => t('Save conditions'),
  129. '#validate' => array('webform_conditionals_form_validate'),
  130. '#submit' => array('webform_conditionals_form_submit'),
  131. );
  132. // Estimate if the form is too long for PHP max_input_vars and detect whether
  133. // a previous submission was truncated. The estimate will be accurate because
  134. // the form elements for this page are well known. Ajax use of this page will
  135. // not generate user-visible errors, so a preflight may be the only indication
  136. // to the user that the page is too long.
  137. webform_input_vars_check($form, $form_state, 'conditionals', '');
  138. return $form;
  139. }
  140. /**
  141. * Submit handler for webform_conditionals_form(). Add an additional choice.
  142. */
  143. function webform_conditionals_form_add($form, &$form_state) {
  144. // Build a default new conditional.
  145. unset($form_state['values']['conditionals']['new']);
  146. $weight = count($form_state['values']['conditionals']) > 10 ? -count($form_state['values']['conditionals']) : -10;
  147. foreach ($form_state['values']['conditionals'] as $key => $conditional) {
  148. $weight = max($weight, $conditional['weight']);
  149. }
  150. // Add the conditional to form state and rebuild the form.
  151. $form_state['values']['conditionals'][] = array(
  152. 'rules' => array(
  153. array(
  154. 'source_type' => 'component',
  155. 'source' => NULL,
  156. 'operator' => NULL,
  157. 'value' => NULL,
  158. ),
  159. ),
  160. 'andor' => 'and',
  161. 'actions' => array(
  162. array(
  163. 'target_type' => 'component',
  164. 'target' => NULL,
  165. 'invert' => NULL,
  166. 'action' => NULL,
  167. 'argument' => NULL,
  168. ),
  169. ),
  170. 'weight' => $weight + 1,
  171. );
  172. $form_state['rebuild'] = TRUE;
  173. }
  174. /**
  175. * Validate handler for webform_conditionals_form().
  176. *
  177. * Prohibit the source and target of a conditional rule from being the same.
  178. */
  179. function webform_conditionals_form_validate($form, &$form_state) {
  180. // Skip validation unless this is saving the form.
  181. $button_key = end($form_state['triggering_element']['#array_parents']);
  182. if ($button_key !== 'submit') {
  183. return;
  184. }
  185. $node = $form['#node'];
  186. $components = $node->webform['components'];
  187. $component_options = webform_component_options();
  188. foreach ($form_state['complete form']['conditionals'] as $conditional_key => $element) {
  189. if (substr($conditional_key, 0, 1) !== '#' && $conditional_key !== 'new') {
  190. $conditional = $element['conditional'];
  191. $targets = array();
  192. foreach ($conditional['actions'] as $action_key => $action) {
  193. if (is_numeric($action_key)) {
  194. $operation = $action['action']['#value'];
  195. $target_id = $action['target']['#value'];
  196. if (isset($targets[$target_id][$operation])) {
  197. form_set_error('conditionals][' . $conditional_key . '][actions][' . $action_key . '][target',
  198. t('A operation %op cannot be made for a component more than once. (%target).',
  199. array(
  200. '%op' => $action['action']['#options'][$operation],
  201. '%target' => $components[$action['target']['#value']]['name'],
  202. )));
  203. }
  204. $component_type = $node->webform['components'][$action['target']['#value']]['type'];
  205. if (!webform_conditional_action_able($component_type, $action['action']['#value'])) {
  206. form_set_error('conditionals][' . $conditional_key . '][actions][' . $action_key . '][action',
  207. t("A component of type %type can't be %action. (%target)",
  208. array(
  209. '%action' => $action['action']['#options'][$action['action']['#value']],
  210. '%type' => $component_options[$component_type],
  211. '%target' => $components[$action['target']['#value']]['name'],
  212. )));
  213. }
  214. $targets[$target_id][$operation] = $target_id;
  215. }
  216. }
  217. foreach ($conditional['rules'] as $rule_key => $rule) {
  218. if (!is_numeric($rule_key)) {
  219. continue;
  220. }
  221. $source_cid = isset($rule['source']['#value']) ? $rule['source']['#value'] : NULL;
  222. // Validate component rules, but not conditional_start/end rules.
  223. if ($source_cid && $rule['source_type']['#value'] == 'component' && isset($targets[$source_cid])) {
  224. form_set_error('conditionals][' . $conditional_key . '][rules][' . $rule_key . '][source',
  225. t('The subject of the conditional cannot be the same as the component that is changed (%target).',
  226. array('%target' => $components[$source_cid]['name'])));
  227. }
  228. if ($source_cid && $components[$source_cid]['type'] === 'date' && strtotime($rule['value']['#value']) === FALSE) {
  229. form_set_error('conditionals][' . $conditional_key . '][rules][' . $rule_key . '][value', t('The conditional comparison value must be a valid date.'));
  230. }
  231. }
  232. }
  233. }
  234. // Form validation will not rebuild the form, so we need to ensure
  235. // necessary JavaScript will still exist.
  236. _webform_conditional_expand_value_forms($node);
  237. }
  238. /**
  239. * Submit handler for webform_conditionals_form().
  240. */
  241. function webform_conditionals_form_submit($form, &$form_state) {
  242. $node = $form['#node'];
  243. // Remove the new conditional placeholder.
  244. unset($form_state['values']['conditionals']['new']);
  245. $node->webform['conditionals'] = $form_state['values']['conditionals'];
  246. node_save($node);
  247. drupal_set_message(t('Conditionals for %title saved.', array('%title' => $node->title)));
  248. }
  249. /**
  250. * AJAX callback to render out adding a new condition.
  251. */
  252. function webform_conditionals_ajax($form, $form_state) {
  253. $rgids = element_children($form['conditionals']);
  254. $new_rgid = max($rgids);
  255. $form['conditionals'][$new_rgid]['#ajax_added'] = TRUE;
  256. $commands = array('#type' => 'ajax');
  257. $commands['#commands'][] = ajax_command_before('.webform-conditional-new-row', drupal_render($form['conditionals'][$new_rgid]));
  258. $commands['#commands'][] = ajax_command_restripe('#webform-conditionals-table');
  259. return $commands;
  260. }
  261. /**
  262. * Theme the $form['conditionals'] of webform_conditionals_form().
  263. */
  264. function theme_webform_conditional_groups($variables) {
  265. $element = $variables['element'];
  266. drupal_add_tabledrag('webform-conditionals-table', 'order', 'sibling', 'webform-conditional-weight');
  267. drupal_add_js('Drupal.theme.prototype.tableDragChangedMarker = function() { return ""; }', 'inline');
  268. drupal_add_js('Drupal.theme.prototype.tableDragChangedWarning = function() { return "<span>&nbsp;</span>"; }', 'inline');
  269. $output = '<table id="webform-conditionals-table"><tbody>';
  270. $element_children = element_children($element, TRUE);
  271. $element_count = count($element_children);
  272. foreach ($element_children as $index => $key) {
  273. if ($key === 'new') {
  274. $even_odd = ($index + 1) % 2 ? 'odd' : 'even';
  275. $element[$key]['weight']['#attributes']['class'] = array('webform-conditional-weight');
  276. $data = '<div class="webform-conditional-new">';
  277. if ($element_count === 1) {
  278. $data .= t('There are no conditional actions on this form.') . ' ';
  279. }
  280. $data .= t('Add a new condition:') . ' ' . drupal_render($element[$key]['new']) . drupal_render($element[$key]['remove']);
  281. $data .= '</div>';
  282. $output .= '<tr class="webform-conditional-new-row ' . $even_odd . '">';
  283. $output .= '<td>' . $data . '</td>';
  284. $output .= '<td>' . drupal_render($element[$key]['weight']) . '</td>';
  285. $output .= '</tr>';
  286. }
  287. else {
  288. $output .= drupal_render($element[$key]);
  289. }
  290. }
  291. $output .= '</tbody></table>';
  292. $output .= drupal_render_children($element);
  293. return $output;
  294. }
  295. /**
  296. * Theme an individual conditional row of webform_conditionals_form().
  297. */
  298. function theme_webform_conditional_group_row($variables) {
  299. $element = $variables['element'];
  300. $element['weight']['#attributes']['class'] = array('webform-conditional-weight');
  301. $weight = drupal_render($element['weight']);
  302. $classes = array('draggable');
  303. if (!empty($element['#even_odd'])) {
  304. $classes[] = $element['#even_odd'];
  305. }
  306. if (!empty($element['#ajax_added'])) {
  307. $classes[] = 'ajax-new-content';
  308. }
  309. $output = '';
  310. $output .= '<tr class="' . implode(' ', $classes) . '">';
  311. $output .= '<td>' . drupal_render_children($element) . '</td>';
  312. $output .= '<td>' . $weight . '</td>';
  313. $output .= '</tr>';
  314. return $output;
  315. }
  316. /**
  317. * Form API #process function to expand a webform conditional element.
  318. */
  319. function _webform_conditional_expand($element) {
  320. $default_operator = 'and';
  321. $element['#tree'] = TRUE;
  322. $element['#default_value'] += array(
  323. 'andor' => $default_operator,
  324. );
  325. $wrapper_id = drupal_clean_css_identifier(implode('-', $element['#parents'])) . '-ajax';
  326. $element['#prefix'] = '<div id="' . $wrapper_id . '">';
  327. $element['#suffix'] = '</div>';
  328. $element['#wrapper_id'] = $wrapper_id;
  329. // Note: When rules or actions are added, the new rules are inserted into
  330. // $form_state['values']. So that FAPI can merge data from the post,
  331. // $form_state['input'] must be adjusted to. To make this easier, hidden
  332. // fields are added to the conditional_start and _end rules to ensure that
  333. // each rule is represented in the POST.
  334. $level = 0;
  335. $andor_stack[0] = array(
  336. 'value' => $element['#default_value']['andor'],
  337. 'parents' => array_merge($element['#parents'], array('andor')),
  338. 'rid' => 0,
  339. 'first' => TRUE,
  340. );
  341. $last_rid = -1;
  342. foreach ($element['#default_value']['rules'] as $rid => $conditional) {
  343. switch ($conditional['source_type']) {
  344. case 'conditional_start':
  345. $element['rules'][$rid] = array(
  346. '#level' => $level,
  347. 'source_type' => array(
  348. '#type' => 'hidden',
  349. '#value' => 'conditional_start',
  350. ),
  351. // The andor operator is located in the first child, which is
  352. // guaranteed to exist. Therefore, don't add a 'value' element here.
  353. 'add_subconditional' => _webform_conditional_add_expand($element, $rid, TRUE),
  354. 'add' => _webform_conditional_add_expand($element, $rid, FALSE),
  355. 'remove' => _webform_conditional_remove_expand($element, $rid),
  356. );
  357. $andor_stack[++$level] = array(
  358. 'value' => isset($conditional['operator']) ? $conditional['operator'] : $default_operator,
  359. 'parents' => array_merge($element['#parents'], array('rules', $rid, 'operator')),
  360. 'rid' => $rid,
  361. 'first' => TRUE,
  362. );
  363. break;
  364. case 'conditional_end':
  365. --$level;
  366. $element['rules'][$rid] = array(
  367. '#level' => $level,
  368. 'source_type' => array(
  369. '#type' => 'hidden',
  370. '#value' => 'conditional_end',
  371. ),
  372. 'add_subconditional' => _webform_conditional_add_expand($element, $rid, TRUE),
  373. 'add' => _webform_conditional_add_expand($element, $rid, FALSE),
  374. 'remove' => _webform_conditional_remove_expand($element, $rid),
  375. 'andor' => _webform_conditional_andor_expand($andor_stack[$level]),
  376. );
  377. // Remove the last nested and/or.
  378. unset($element['rules'][$last_rid]['andor']);
  379. break;
  380. case 'component':
  381. $element['rules'][$rid] = _webform_conditional_rule_expand($element, $rid, $conditional, $level, $andor_stack[$level]);
  382. break;
  383. default:
  384. drupal_set_message(t('Unexpected conditional rule source type found (rule id @rid). Contact the administrator.', array('@rid' => $rid)), 'error');
  385. }
  386. $last_rid = $rid;
  387. }
  388. // Remove the last and/or.
  389. unset($element['rules'][$rid]['andor']);
  390. foreach ($element['#default_value']['actions'] as $aid => $action) {
  391. $element['actions'][$aid] = _webform_conditional_action_expand($element, $aid, $action);
  392. }
  393. return $element;
  394. }
  395. /**
  396. * Helper. Generate the and/or select or static text.
  397. */
  398. function _webform_conditional_andor_expand(&$andor) {
  399. if ($andor['first']) {
  400. $andor['first'] = FALSE;
  401. return array(
  402. '#type' => 'select',
  403. '#title' => t('And/or'),
  404. '#options' => array(
  405. 'and' => t('and'),
  406. 'or' => t('or'),
  407. ),
  408. '#parents' => $andor['parents'],
  409. '#default_value' => $andor['value'],
  410. '#attributes' => array('data-rid' => $andor['rid']),
  411. );
  412. }
  413. else {
  414. return array(
  415. '#type' => 'container',
  416. '#attributes' => array('class' => array('webform-andor'), 'data-rid' => $andor['rid']),
  417. 'andor_text' => array(
  418. '#markup' => $andor['value'] == 'or' ? t('or') : t('and'),
  419. ),
  420. );
  421. }
  422. }
  423. /**
  424. * Helper. Generate the add_subconditional (+) or add + button.
  425. */
  426. function _webform_conditional_add_expand($element, $rid, $subconditional) {
  427. return array(
  428. '#type' => 'submit',
  429. '#value' => $subconditional ? t('(+)') : t('+'),
  430. '#submit' => array('webform_conditional_element_add'),
  431. '#subconditional' => $subconditional,
  432. '#name' => implode('_', $element['#parents']) . '_rules_' . $rid . ($subconditional ? '_add_subconditional' : '_add'),
  433. '#attributes' => array('class' => array('webform-conditional-rule-add')),
  434. '#ajax' => array(
  435. 'progress' => 'none',
  436. 'callback' => 'webform_conditional_element_ajax',
  437. 'wrapper' => $element['#wrapper_id'],
  438. 'event' => 'click',
  439. ),
  440. );
  441. }
  442. /**
  443. * Helper. Generate the add_subconditional (+), add + or remove - button.
  444. */
  445. function _webform_conditional_remove_expand($element, $rid) {
  446. return array(
  447. '#type' => 'submit',
  448. '#value' => t('-'),
  449. '#submit' => array('webform_conditional_element_remove'),
  450. '#name' => implode('_', $element['#parents']) . '_rules_' . $rid . '_remove',
  451. '#attributes' => array('class' => array('webform-conditional-rule-remove')),
  452. '#ajax' => array(
  453. 'progress' => 'none',
  454. 'callback' => 'webform_conditional_element_ajax',
  455. 'wrapper' => $element['#wrapper_id'],
  456. 'event' => 'click',
  457. ),
  458. );
  459. }
  460. /**
  461. * Helper. Generate form elements for one rule.
  462. */
  463. function _webform_conditional_rule_expand($element, $rid, $conditional, $level, &$andor) {
  464. return array(
  465. '#level' => $level,
  466. 'source_type' => array(
  467. '#type' => 'value',
  468. '#value' => $conditional['source_type'],
  469. ),
  470. 'source' => array(
  471. '#type' => 'select',
  472. '#title' => t('Source'),
  473. '#options' => $element['#sources'],
  474. '#default_value' => $conditional['source'],
  475. ),
  476. 'operator' => array(
  477. '#type' => 'select',
  478. '#title' => t('Operator'),
  479. '#options' => webform_conditional_operators_list(),
  480. '#default_value' => $conditional['operator'],
  481. ),
  482. 'value' => array(
  483. '#type' => 'textfield',
  484. '#title' => t('Value'),
  485. '#size' => 20,
  486. '#default_value' => $conditional['value'],
  487. ),
  488. 'add_subconditional' => _webform_conditional_add_expand($element, $rid, TRUE),
  489. 'add' => _webform_conditional_add_expand($element, $rid, FALSE),
  490. 'remove' => _webform_conditional_remove_expand($element, $rid),
  491. 'andor' => _webform_conditional_andor_expand($andor),
  492. );
  493. }
  494. /**
  495. * Helper. Generate form elements for one action.
  496. */
  497. function _webform_conditional_action_expand($element, $aid, $action) {
  498. return array(
  499. 'target_type' => array(
  500. '#type' => 'value',
  501. '#value' => $action['target_type'],
  502. ),
  503. 'target' => array(
  504. '#type' => 'select',
  505. '#title' => t('Target'),
  506. '#options' => $element['#targets'],
  507. '#default_value' => $action['target'],
  508. ),
  509. 'invert' => array(
  510. '#type' => 'select',
  511. '#title' => t("Is/Isn't"),
  512. '#options' => array(
  513. '0' => t('is'),
  514. '1' => t("isn't"),
  515. ),
  516. '#default_value' => $action['invert'],
  517. ),
  518. 'action' => array(
  519. '#type' => 'select',
  520. '#title' => t('Action'),
  521. '#options' => $element['#actions'],
  522. '#default_value' => $action['action'],
  523. ),
  524. 'argument' => array(
  525. '#type' => 'textfield',
  526. '#title' => t('Argument'),
  527. '#size' => 20,
  528. '#maxlength' => NULL,
  529. '#default_value' => $action['argument'],
  530. ),
  531. 'add' => array(
  532. '#type' => 'submit',
  533. '#value' => t('+'),
  534. '#submit' => array('webform_conditional_element_add'),
  535. '#name' => implode('_', $element['#parents']) . '_actions_' . $aid . '_add',
  536. '#attributes' => array('class' => array('webform-conditional-action-add')),
  537. '#ajax' => array(
  538. 'progress' => 'none',
  539. 'callback' => 'webform_conditional_element_ajax',
  540. 'wrapper' => $element['#wrapper_id'],
  541. 'event' => 'click',
  542. ),
  543. ),
  544. 'remove' => array(
  545. '#type' => 'submit',
  546. '#value' => t('-'),
  547. '#submit' => array('webform_conditional_element_remove'),
  548. '#name' => implode('_', $element['#parents']) . '_actions_' . $aid . '_remove',
  549. '#attributes' => array('class' => array('webform-conditional-action-remove')),
  550. '#ajax' => array(
  551. 'progress' => 'none',
  552. 'callback' => 'webform_conditional_element_ajax',
  553. 'wrapper' => $element['#wrapper_id'],
  554. 'event' => 'click',
  555. ),
  556. ),
  557. );
  558. }
  559. /**
  560. * Expand out all the value forms that could potentially be used.
  561. *
  562. * These forms are added to the page via JavaScript and swapped in only when
  563. * needed. Because the user may change the source and operator at any time,
  564. * all these forms need to be generated ahead of time and swapped in. This
  565. * could have been done via AJAX, but having all forms available makes for a
  566. * faster user experience.
  567. *
  568. * Added to the JavaScript settings is conditionalValues which contains
  569. * an array settings suitable for adding to the page via JavaScript. This
  570. * array contains the following keys:
  571. * - operators: An array containing a map of data types, operators, and form
  572. * keys. This array is structured as follows:
  573. * @code
  574. * - sources[$source_key] = array(
  575. * 'data_type' => $data_type,
  576. * );
  577. * $operators[$data_type][$operator] = array(
  578. * 'form' => $form_key,
  579. * );
  580. * @endcode
  581. * - forms[$form_key]: A string representing an HTML form for an operator.
  582. * - forms[$form_key][$source]: Or instead of a single form for all
  583. * components, if each component requires its own form, key each component
  584. * by its source value (currently always the component ID).
  585. *
  586. * @param object $node
  587. * The Webform node for which these forms are being generated.
  588. */
  589. function _webform_conditional_expand_value_forms($node) {
  590. $operators = webform_conditional_operators();
  591. $data = array();
  592. foreach ($operators as $data_type => $operator_info) {
  593. foreach ($operator_info as $operator => $data_operator_info) {
  594. $data['operators'][$data_type][$operator]['form'] = 'default';
  595. if (isset($data_operator_info['form callback'])) {
  596. $form_callback = $data_operator_info['form callback'];
  597. $data['operators'][$data_type][$operator]['form'] = $form_callback;
  598. if ($form_callback !== FALSE && !isset($data['forms'][$form_callback])) {
  599. $data['forms'][$form_callback] = $form_callback($node);
  600. }
  601. }
  602. }
  603. }
  604. foreach ($node->webform['components'] as $cid => $component) {
  605. if (webform_component_feature($component['type'], 'conditional')) {
  606. $data['sources'][$cid]['data_type'] = webform_component_property($component['type'], 'conditional_type');
  607. }
  608. }
  609. drupal_add_js(array('webform' => array('conditionalValues' => $data)), 'setting');
  610. }
  611. /**
  612. * Helper. Find the matching end of a given subconditional.
  613. *
  614. * @param array $rules
  615. * Array of conditional rules to be searched.
  616. * @param int $origin_rid
  617. * The starting rule id for the search.
  618. * @param int $target_delta_level
  619. * The level that is sought. 0 for current left. -1 for parent.
  620. *
  621. * @return int
  622. * The rid of the found rule, or -1 if none. Note that NULL is not used as a
  623. * semaphore for "not found" because it casts to 0, which is a valid rule id.
  624. */
  625. function _webform_conditional_find_end(array $rules, $origin_rid, $target_delta_level = 0) {
  626. $rids = array_keys($rules);
  627. $offset = array_search($origin_rid, $rids);
  628. $delta_level = 0;
  629. foreach (array_slice($rules, $offset, NULL, TRUE) as $rid => $conditional) {
  630. switch ($conditional['source_type']) {
  631. case 'conditional_start':
  632. $delta_level++;
  633. break;
  634. case 'conditional_end':
  635. $delta_level--;
  636. break;
  637. }
  638. if ($delta_level == $target_delta_level) {
  639. return $rid;
  640. }
  641. }
  642. // Mis-matched conditional_start / _end. Return -1.
  643. return -1;
  644. }
  645. /**
  646. * Helper. Find the matching start or end of a given subconditional.
  647. *
  648. * @see _webform_conditional_find_end()
  649. */
  650. function _webform_conditional_find_start($rules, $origin_rid, $target_delta_level = 0) {
  651. $rids = array_keys($rules);
  652. $offset = array_search($origin_rid, $rids);
  653. $delta_level = 0;
  654. foreach (array_reverse(array_slice($rules, 0, $offset + 1, TRUE), TRUE) as $rid => $conditional) {
  655. switch ($conditional['source_type']) {
  656. case 'conditional_end':
  657. $delta_level++;
  658. break;
  659. case 'conditional_start':
  660. $delta_level--;
  661. break;
  662. }
  663. if ($delta_level == $target_delta_level) {
  664. return $rid;
  665. }
  666. }
  667. // Mis-matched conditional_start / _end. Return -1.
  668. return -1;
  669. }
  670. /**
  671. * Submit handler for webform_conditional elements to add a new rule or action.
  672. */
  673. function webform_conditional_element_add($form, &$form_state) {
  674. $button = $form_state['clicked_button'];
  675. $parents = $button['#parents'];
  676. array_pop($parents);
  677. $rid = array_pop($parents);
  678. // Recurse through the form values until we find the Webform conditional rules
  679. // or actions. Save the conditional prior to descending to rules/actions.
  680. $parent_values = &$form_state['values'];
  681. $input_values = &$form_state['input'];
  682. foreach ($parents as $key) {
  683. if (array_key_exists($key, $parent_values)) {
  684. $conditional = $parent_values;
  685. $parent_values = &$parent_values[$key];
  686. }
  687. if (array_key_exists($key, $input_values)) {
  688. $input_values = &$input_values[$key];
  689. }
  690. }
  691. // Split the list of rules/actions in this conditional and inject into the
  692. // right spot.
  693. $rids = array_keys($parent_values);
  694. $offset = array_search($rid, $rids);
  695. $default_rule = isset($button['#subconditional'])
  696. ? array(
  697. 'source' => NULL,
  698. 'source_type' => 'component',
  699. 'operator' => NULL,
  700. 'value' => NULL,
  701. )
  702. : array(
  703. 'target_type' => 'component',
  704. 'target' => NULL,
  705. 'invert' => NULL,
  706. 'action' => NULL,
  707. 'argument' => NULL,
  708. );
  709. if (empty($button['#subconditional'])) {
  710. $new[0] = (isset($parent_values[$rid]['source_type']) && $parent_values[$rid]['source_type'] == 'component') ? $parent_values[$rid] : $default_rule;
  711. }
  712. else {
  713. // The default andor operator is opposite of current subconditional's
  714. // operatior.
  715. $parent_rid = _webform_conditional_find_start($parent_values, $rid, -1);
  716. $current_op = $parent_rid < 0 ? $conditional['andor'] : $parent_values[$parent_rid]['operator'];
  717. $current_op = $current_op == 'and' ? 'or' : 'and';
  718. $new = array(
  719. array('source_type' => 'conditional_start', 'operator' => $current_op) + $default_rule,
  720. $default_rule,
  721. $default_rule,
  722. array('source_type' => 'conditional_end') + $default_rule,
  723. );
  724. }
  725. // Update both $form_state['values'] and ['input] so that FAPI can merge
  726. // input values from the POST into the new form.
  727. $parent_values = array_merge(array_slice($parent_values, 0, $offset + 1), $new, array_slice($parent_values, $offset + 1));
  728. $input_values = array_merge(array_slice($input_values, 0, $offset + 1), $new, array_slice($input_values, $offset + 1));
  729. $form_state['rebuild'] = TRUE;
  730. }
  731. /**
  732. * Submit handler for webform_conditional elements to remove a rule or action.
  733. */
  734. function webform_conditional_element_remove($form, &$form_state) {
  735. $button = $form_state['clicked_button'];
  736. $parents = $button['#parents'];
  737. $action = array_pop($parents);
  738. $rid = array_pop($parents);
  739. // Recurse through the form values until we find the root Webform conditional.
  740. $parent_values = &$form_state['values'];
  741. foreach ($parents as $key) {
  742. if (array_key_exists($key, $parent_values)) {
  743. $parent_values = &$parent_values[$key];
  744. }
  745. }
  746. switch ($parent_values[$rid]['source_type']) {
  747. case 'conditional_start':
  748. unset($parent_values[_webform_conditional_find_end($parent_values, $rid)]);
  749. break;
  750. case 'conditional_end':
  751. unset($parent_values[_webform_conditional_find_start($parent_values, $rid)]);
  752. break;
  753. }
  754. // Remove this rule or action from the list of conditionals.
  755. unset($parent_values[$rid]);
  756. $form_state['rebuild'] = TRUE;
  757. }
  758. /**
  759. * Helper. Delete any subconditionals which contain no rules.
  760. *
  761. * @param array $conditional
  762. * Conditional array containing the rules.
  763. *
  764. * @return array
  765. * Array of deleted subconditionals. Empty array if none were deleted.
  766. */
  767. function webform_delete_empty_subconditionals(array &$conditional) {
  768. $deleted = array();
  769. do {
  770. $empty_deleted = FALSE;
  771. $open_rid = NULL;
  772. foreach ($conditional['rules'] as $rid => $rule) {
  773. switch ($rule['source_type']) {
  774. case 'conditional_start':
  775. $open_rid = $rid;
  776. break;
  777. case 'conditional_end':
  778. if ($open_rid) {
  779. // A conditional_start rule was immediately followed by a
  780. // conditional_end rule. Delete them both. Repeat the check in case
  781. // the parent is now empty.
  782. $deleted[$open_rid] = $open_rid;
  783. $deleted[$rid] = $rid;
  784. unset($conditional['rules'][$open_rid], $conditional['rules'][$rid]);
  785. $open_rid = NULL;
  786. $empty_deleted = TRUE;
  787. }
  788. break;
  789. default:
  790. $open_rid = NULL;
  791. }
  792. }
  793. } while ($empty_deleted);
  794. return $deleted;
  795. }
  796. /**
  797. * AJAX callback to render out adding a new condition.
  798. */
  799. function webform_conditional_element_ajax($form, $form_state) {
  800. $button = $form_state['clicked_button'];
  801. $parents = $button['#parents'];
  802. // Trim down the parents to go back up to the level of this elements wrapper.
  803. // The button name (add/remove).
  804. array_pop($parents);
  805. // The rule ID.
  806. array_pop($parents);
  807. // The "rules" grouping.
  808. array_pop($parents);
  809. $element = $form;
  810. foreach ($parents as $key) {
  811. if (!isset($element[$key])) {
  812. // The entire conditional has been removed.
  813. return '';
  814. }
  815. $element = $element[$key];
  816. }
  817. return drupal_render($element['conditional']);
  818. }
  819. /**
  820. * Theme the form for a conditional action.
  821. */
  822. function theme_webform_conditional($variables) {
  823. $element = $variables['element'];
  824. $output = '';
  825. $output .= '<div class="webform-conditional">';
  826. $output .= '<span class="webform-conditional-if">' . t('If') . '</span>';
  827. foreach (element_children($element['rules']) as $rid) {
  828. $rule = &$element['rules'][$rid];
  829. switch ($rule['source_type']['#value']) {
  830. case 'conditional_start':
  831. $source_phrase = '<div class="webform-subconditional">' . t('(') . '</div>';
  832. break;
  833. case 'conditional_end':
  834. $source_phrase = '<div class="webform-subconditional">' . t(')') . '</div>';
  835. break;
  836. default:
  837. // Hide labels.
  838. $rule['source']['#title_display'] = 'invisible';
  839. $rule['operator']['#title_display'] = 'invisible';
  840. $rule['value']['#title_display'] = 'invisible';
  841. $source = '<div class="webform-conditional-source">' . drupal_render($rule['source']) . '</div>';
  842. $operator = '<div class="webform-conditional-operator">' . drupal_render($rule['operator']) . '</div>';
  843. $value = '<div class="webform-conditional-value">' . drupal_render($rule['value']) . '</div>';
  844. $source_phrase = t('!source !operator !value', array(
  845. '!source' => $source,
  846. '!operator' => $operator,
  847. '!value' => $value,
  848. ));
  849. }
  850. $output .= '<div class="webform-conditional-rule">';
  851. // Can't use theme('indentation') here because it causes the draghandle to
  852. // be located after the last indentation div.
  853. $output .= str_repeat('<div class="webform-indentation">&nbsp;</div>', $rule['#level']);
  854. $output .= drupal_render($rule['source_type']);
  855. $output .= '<div class="webform-container-inline webform-conditional-condition">';
  856. $output .= $source_phrase;
  857. $output .= '</div>';
  858. if (isset($rule['andor'])) {
  859. $rule['andor']['#title_display'] = 'invisible';
  860. $output .= '<div class="webform-conditional-andor webform-container-inline">';
  861. $output .= drupal_render($rule['andor']);
  862. $output .= '</div>';
  863. }
  864. if (isset($rule['add']) || isset($rule['remove'])) {
  865. $output .= '<span class="webform-conditional-operations webform-container-inline">';
  866. $output .= drupal_render($rule['add_subconditional']);
  867. $output .= drupal_render($rule['add']);
  868. $output .= drupal_render($rule['remove']);
  869. $output .= '</span>';
  870. }
  871. $output .= '</div>';
  872. }
  873. // Hide labels.
  874. foreach (element_children($element['actions']) as $aid) {
  875. // Hide labels.
  876. $element['actions'][$aid]['target']['#title_display'] = 'invisible';
  877. $element['actions'][$aid]['invert']['#title_display'] = 'invisible';
  878. $element['actions'][$aid]['action']['#title_display'] = 'invisible';
  879. $element['actions'][$aid]['argument']['#title_display'] = 'invisible';
  880. $target = '<div class="webform-conditional-target">' . drupal_render($element['actions'][$aid]['target']) . '</div>';
  881. $invert = '<div class="webform-conditional-invert">' . drupal_render($element['actions'][$aid]['invert']) . '</div>';
  882. $action = '<div class="webform-conditional-action">' . drupal_render($element['actions'][$aid]['action']) . '</div>';
  883. $argument = '<div class="webform-conditional-argument">' . drupal_render($element['actions'][$aid]['argument']) . '</div>';
  884. $target_phrase = t('then !target !invert !action !argument', array(
  885. '!target' => $target,
  886. '!invert' => $invert,
  887. '!action' => $action,
  888. '!argument' => $argument,
  889. ));
  890. $output .= '<div class="webform-conditional-action">';
  891. $output .= '<div class="webform-container-inline webform-conditional-condition">';
  892. $output .= $target_phrase;
  893. $output .= '</div>';
  894. if (isset($element['actions'][$aid]['add']) || isset($element['actions'][$aid]['remove'])) {
  895. $output .= '<span class="webform-conditional-operations webform-container-inline">';
  896. $output .= drupal_render($element['actions'][$aid]['add']);
  897. $output .= drupal_render($element['actions'][$aid]['remove']);
  898. $output .= '</span>';
  899. }
  900. $output .= '</div>';
  901. }
  902. $output .= '</div>';
  903. return $output;
  904. }
  905. /**
  906. * Return a list of all Webform conditional operators.
  907. */
  908. function webform_conditional_operators() {
  909. static $operators;
  910. if (!isset($operators)) {
  911. $operators = module_invoke_all('webform_conditional_operator_info');
  912. drupal_alter('webform_conditional_operators', $operators);
  913. }
  914. return $operators;
  915. }
  916. /**
  917. * Return a nested list of all available operators, suitable for a select list.
  918. */
  919. function webform_conditional_operators_list() {
  920. $options = array();
  921. $operators = webform_conditional_operators();
  922. foreach ($operators as $data_type => $type_operators) {
  923. $options[$data_type] = array();
  924. foreach ($type_operators as $operator => $operator_info) {
  925. $options[$data_type][$operator] = $operator_info['label'];
  926. }
  927. }
  928. return $options;
  929. }
  930. /**
  931. * Implements hook_webform_conditional_operator_info().
  932. *
  933. * Called from webform.module's webform_webform_conditional_operator_info().
  934. */
  935. function _webform_conditional_operator_info() {
  936. // General operators:
  937. $operators['string']['equal'] = array(
  938. 'label' => t('is'),
  939. 'comparison callback' => 'webform_conditional_operator_string_equal',
  940. 'js comparison callback' => 'conditionalOperatorStringEqual',
  941. // A form callback is not needed here, since we can use the default,
  942. // non-JavaScript textfield for all text and numeric fields.
  943. // @code
  944. // 'form callback' => 'webform_conditional_operator_text',
  945. // @endcode
  946. );
  947. $operators['string']['not_equal'] = array(
  948. 'label' => t('is not'),
  949. 'comparison callback' => 'webform_conditional_operator_string_not_equal',
  950. 'js comparison callback' => 'conditionalOperatorStringNotEqual',
  951. );
  952. $operators['string']['contains'] = array(
  953. 'label' => t('contains'),
  954. 'comparison callback' => 'webform_conditional_operator_string_contains',
  955. 'js comparison callback' => 'conditionalOperatorStringContains',
  956. );
  957. $operators['string']['does_not_contain'] = array(
  958. 'label' => t('does not contain'),
  959. 'comparison callback' => 'webform_conditional_operator_string_does_not_contain',
  960. 'js comparison callback' => 'conditionalOperatorStringDoesNotContain',
  961. );
  962. $operators['string']['begins_with'] = array(
  963. 'label' => t('begins with'),
  964. 'comparison callback' => 'webform_conditional_operator_string_begins_with',
  965. 'js comparison callback' => 'conditionalOperatorStringBeginsWith',
  966. );
  967. $operators['string']['ends_with'] = array(
  968. 'label' => t('ends with'),
  969. 'comparison callback' => 'webform_conditional_operator_string_ends_with',
  970. 'js comparison callback' => 'conditionalOperatorStringEndsWith',
  971. );
  972. $operators['string']['empty'] = array(
  973. 'label' => t('is blank'),
  974. 'comparison callback' => 'webform_conditional_operator_string_empty',
  975. 'js comparison callback' => 'conditionalOperatorStringEmpty',
  976. // No value form at all.
  977. 'form callback' => FALSE,
  978. );
  979. $operators['string']['not_empty'] = array(
  980. 'label' => t('is not blank'),
  981. 'comparison callback' => 'webform_conditional_operator_string_not_empty',
  982. 'js comparison callback' => 'conditionalOperatorStringNotEmpty',
  983. // No value form at all.
  984. 'form callback' => FALSE,
  985. );
  986. // Numeric operators.
  987. $operators['numeric']['equal'] = array(
  988. 'label' => t('is equal to'),
  989. 'comparison callback' => 'webform_conditional_operator_numeric_equal',
  990. 'js comparison callback' => 'conditionalOperatorNumericEqual',
  991. );
  992. $operators['numeric']['not_equal'] = array(
  993. 'label' => t('is not equal to'),
  994. 'comparison callback' => 'webform_conditional_operator_numeric_not_equal',
  995. 'js comparison callback' => 'conditionalOperatorNumericNotEqual',
  996. );
  997. $operators['numeric']['less_than'] = array(
  998. 'label' => t('is less than'),
  999. 'comparison callback' => 'webform_conditional_operator_numeric_less_than',
  1000. 'js comparison callback' => 'conditionalOperatorNumericLessThan',
  1001. );
  1002. $operators['numeric']['less_than_equal'] = array(
  1003. 'label' => t('is less than or equal'),
  1004. 'comparison callback' => 'webform_conditional_operator_numeric_less_than_equal',
  1005. 'js comparison callback' => 'conditionalOperatorNumericLessThanEqual',
  1006. );
  1007. $operators['numeric']['greater_than'] = array(
  1008. 'label' => t('is greater than'),
  1009. 'comparison callback' => 'webform_conditional_operator_numeric_greater_than',
  1010. 'js comparison callback' => 'conditionalOperatorNumericGreaterThan',
  1011. );
  1012. $operators['numeric']['greater_than_equal'] = array(
  1013. 'label' => t('is greater than or equal'),
  1014. 'comparison callback' => 'webform_conditional_operator_numeric_greater_than_equal',
  1015. 'js comparison callback' => 'conditionalOperatorNumericGreaterThanEqual',
  1016. );
  1017. $operators['numeric']['empty'] = array(
  1018. 'label' => t('is blank'),
  1019. 'comparison callback' => 'webform_conditional_operator_string_empty',
  1020. 'js comparison callback' => 'conditionalOperatorStringEmpty',
  1021. // No value form at all.
  1022. 'form callback' => FALSE,
  1023. );
  1024. $operators['numeric']['not_empty'] = array(
  1025. 'label' => t('is not blank'),
  1026. 'comparison callback' => 'webform_conditional_operator_string_not_empty',
  1027. 'js comparison callback' => 'conditionalOperatorStringNotEmpty',
  1028. // No value form at all.
  1029. 'form callback' => FALSE,
  1030. );
  1031. // Select operators.
  1032. $operators['select']['equal'] = array(
  1033. 'label' => t('is'),
  1034. 'comparison callback' => 'webform_conditional_operator_string_equal',
  1035. 'js comparison callback' => 'conditionalOperatorStringEqual',
  1036. 'form callback' => 'webform_conditional_form_select',
  1037. );
  1038. $operators['select']['not_equal'] = array(
  1039. 'label' => t('is not'),
  1040. 'comparison callback' => 'webform_conditional_operator_string_not_equal',
  1041. 'js comparison callback' => 'conditionalOperatorStringNotEqual',
  1042. 'form callback' => 'webform_conditional_form_select',
  1043. );
  1044. $operators['select']['less_than'] = array(
  1045. 'label' => t('is before'),
  1046. 'comparison callback' => 'webform_conditional_operator_select_less_than',
  1047. 'js comparison callback' => 'conditionalOperatorSelectLessThan',
  1048. 'form callback' => 'webform_conditional_form_select',
  1049. );
  1050. $operators['select']['less_than_equal'] = array(
  1051. 'label' => t('is or is before'),
  1052. 'comparison callback' => 'webform_conditional_operator_select_less_than_equal',
  1053. 'js comparison callback' => 'conditionalOperatorSelectLessThanEqual',
  1054. 'form callback' => 'webform_conditional_form_select',
  1055. );
  1056. $operators['select']['greater_than'] = array(
  1057. 'label' => t('is after'),
  1058. 'comparison callback' => 'webform_conditional_operator_select_greater_than',
  1059. 'js comparison callback' => 'conditionalOperatorSelectGreaterThan',
  1060. 'form callback' => 'webform_conditional_form_select',
  1061. );
  1062. $operators['select']['greater_than_equal'] = array(
  1063. 'label' => t('is or is after'),
  1064. 'comparison callback' => 'webform_conditional_operator_select_greater_than_equal',
  1065. 'js comparison callback' => 'conditionalOperatorSelectGreaterThanEqual',
  1066. 'form callback' => 'webform_conditional_form_select',
  1067. );
  1068. $operators['select']['empty'] = array(
  1069. 'label' => t('is empty'),
  1070. 'comparison callback' => 'webform_conditional_operator_string_empty',
  1071. 'js comparison callback' => 'conditionalOperatorStringEmpty',
  1072. // No value form at all.
  1073. 'form callback' => FALSE,
  1074. );
  1075. $operators['select']['not_empty'] = array(
  1076. 'label' => t('is not empty'),
  1077. 'comparison callback' => 'webform_conditional_operator_string_not_empty',
  1078. 'js comparison callback' => 'conditionalOperatorStringNotEmpty',
  1079. // No value form at all.
  1080. 'form callback' => FALSE,
  1081. );
  1082. // Date operators:
  1083. $operators['date']['equal'] = array(
  1084. 'label' => t('is on'),
  1085. 'comparison callback' => 'webform_conditional_operator_datetime_equal',
  1086. 'comparison prepare js' => 'webform_conditional_prepare_date_js',
  1087. 'js comparison callback' => 'conditionalOperatorDateEqual',
  1088. 'form callback' => 'webform_conditional_form_date',
  1089. );
  1090. $operators['date']['not_equal'] = array(
  1091. 'label' => t('is not on'),
  1092. 'comparison callback' => 'webform_conditional_operator_datetime_not_equal',
  1093. 'comparison prepare js' => 'webform_conditional_prepare_date_js',
  1094. 'js comparison callback' => 'conditionalOperatorDateNotEqual',
  1095. 'form callback' => 'webform_conditional_form_date',
  1096. );
  1097. $operators['date']['before'] = array(
  1098. 'label' => t('is before'),
  1099. 'comparison callback' => 'webform_conditional_operator_datetime_before',
  1100. 'comparison prepare js' => 'webform_conditional_prepare_date_js',
  1101. 'js comparison callback' => 'conditionalOperatorDateBefore',
  1102. 'form callback' => 'webform_conditional_form_date',
  1103. );
  1104. $operators['date']['before_equal'] = array(
  1105. 'label' => t('is on or before'),
  1106. 'comparison callback' => 'webform_conditional_operator_datetime_before_equal',
  1107. 'comparison prepare js' => 'webform_conditional_prepare_date_js',
  1108. 'js comparison callback' => 'conditionalOperatorDateBeforeEqual',
  1109. 'form callback' => 'webform_conditional_form_date',
  1110. );
  1111. $operators['date']['after'] = array(
  1112. 'label' => t('is after'),
  1113. 'comparison callback' => 'webform_conditional_operator_datetime_after',
  1114. 'comparison prepare js' => 'webform_conditional_prepare_date_js',
  1115. 'js comparison callback' => 'conditionalOperatorDateAfter',
  1116. 'form callback' => 'webform_conditional_form_date',
  1117. );
  1118. $operators['date']['after_equal'] = array(
  1119. 'label' => t('is on or after'),
  1120. 'comparison callback' => 'webform_conditional_operator_datetime_after_equal',
  1121. 'comparison prepare js' => 'webform_conditional_prepare_date_js',
  1122. 'js comparison callback' => 'conditionalOperatorDateAfterEqual',
  1123. 'form callback' => 'webform_conditional_form_date',
  1124. );
  1125. // Time operators:
  1126. $operators['time']['equal'] = array(
  1127. 'label' => t('is at'),
  1128. 'comparison callback' => 'webform_conditional_operator_datetime_equal',
  1129. 'comparison prepare js' => 'webform_conditional_prepare_time_js',
  1130. 'js comparison callback' => 'conditionalOperatorTimeEqual',
  1131. 'form callback' => 'webform_conditional_form_time',
  1132. );
  1133. $operators['time']['not_equal'] = array(
  1134. 'label' => t('is not at'),
  1135. 'comparison callback' => 'webform_conditional_operator_datetime_not_equal',
  1136. 'comparison prepare js' => 'webform_conditional_prepare_time_js',
  1137. 'js comparison callback' => 'conditionalOperatorTimeNotEqual',
  1138. 'form callback' => 'webform_conditional_form_time',
  1139. );
  1140. $operators['time']['before'] = array(
  1141. 'label' => t('is before'),
  1142. 'comparison callback' => 'webform_conditional_operator_datetime_before',
  1143. 'comparison prepare js' => 'webform_conditional_prepare_time_js',
  1144. 'js comparison callback' => 'conditionalOperatorTimeBefore',
  1145. 'form callback' => 'webform_conditional_form_time',
  1146. );
  1147. $operators['time']['before_equal'] = array(
  1148. 'label' => t('is at or before'),
  1149. 'comparison callback' => 'webform_conditional_operator_datetime_before_equal',
  1150. 'comparison prepare js' => 'webform_conditional_prepare_time_js',
  1151. 'js comparison callback' => 'conditionalOperatorTimeBeforeEqual',
  1152. 'form callback' => 'webform_conditional_form_time',
  1153. );
  1154. $operators['time']['after'] = array(
  1155. 'label' => t('is after'),
  1156. 'comparison callback' => 'webform_conditional_operator_datetime_after',
  1157. 'comparison prepare js' => 'webform_conditional_prepare_time_js',
  1158. 'js comparison callback' => 'conditionalOperatorTimeAfter',
  1159. 'form callback' => 'webform_conditional_form_time',
  1160. );
  1161. $operators['time']['after_equal'] = array(
  1162. 'label' => t('is at or after'),
  1163. 'comparison callback' => 'webform_conditional_operator_datetime_after_equal',
  1164. 'comparison prepare js' => 'webform_conditional_prepare_time_js',
  1165. 'js comparison callback' => 'conditionalOperatorTimeAfterEqual',
  1166. 'form callback' => 'webform_conditional_form_time',
  1167. );
  1168. return $operators;
  1169. }
  1170. /**
  1171. * Form callback for select-type conditional fields.
  1172. *
  1173. * Unlike other built-in conditional value forms, the form callback for select
  1174. * types provides an array of forms, keyed by the $cid, which is the "source"
  1175. * for the condition.
  1176. */
  1177. function webform_conditional_form_select($node) {
  1178. static $count = 0;
  1179. $forms = array();
  1180. webform_component_include('select');
  1181. foreach ($node->webform['components'] as $cid => $component) {
  1182. if (webform_component_property($component['type'], 'conditional_type') == 'select') {
  1183. // @todo: Use a pluggable mechanism for retrieving select list values.
  1184. $options = _webform_select_options($component);
  1185. $element = array(
  1186. '#type' => 'select',
  1187. '#multiple' => FALSE,
  1188. '#size' => NULL,
  1189. '#attributes' => array(),
  1190. '#id' => NULL,
  1191. '#name' => 'webform-conditional-select-' . $cid . '-' . $count,
  1192. '#options' => $options,
  1193. '#parents' => array(),
  1194. );
  1195. $forms[$cid] = drupal_render($element);
  1196. }
  1197. }
  1198. $count++;
  1199. return $forms;
  1200. }
  1201. /**
  1202. * Form callback for date conditional fields.
  1203. */
  1204. function webform_conditional_form_date($node) {
  1205. static $count = 0;
  1206. $element = array(
  1207. '#title' => NULL,
  1208. '#title_display' => 'invisible',
  1209. '#size' => 24,
  1210. '#attributes' => array('placeholder' => t('@format or valid date', array('@format' => webform_date_format('short')))),
  1211. '#type' => 'textfield',
  1212. '#name' => 'webform-conditional-date-' . $count++,
  1213. );
  1214. return drupal_render($element);
  1215. }
  1216. /**
  1217. * Form callback for time conditional fields.
  1218. */
  1219. function webform_conditional_form_time($node) {
  1220. static $count = 0;
  1221. $element = array(
  1222. '#title' => NULL,
  1223. '#title_display' => 'invisible',
  1224. '#size' => 24,
  1225. '#attributes' => array('placeholder' => t('HH:MMam or valid time')),
  1226. '#type' => 'textfield',
  1227. '#name' => 'webform-conditional-time-' . $count++,
  1228. );
  1229. return drupal_render($element);
  1230. }
  1231. /**
  1232. * Load a conditional setting from the database.
  1233. */
  1234. function webform_conditional_load($rgid, $nid) {
  1235. $node = node_load($nid);
  1236. $conditional = isset($node->webform['conditionals'][$rgid]) ? $node->webform['conditionals'][$rgid] : FALSE;
  1237. return $conditional;
  1238. }
  1239. /**
  1240. * Insert a conditional rule group into the database.
  1241. */
  1242. function webform_conditional_insert($conditional) {
  1243. $transaction = db_transaction();
  1244. drupal_write_record('webform_conditional', $conditional);
  1245. foreach ($conditional['rules'] as $rid => $rule) {
  1246. $rule['nid'] = $conditional['nid'];
  1247. $rule['rgid'] = $conditional['rgid'];
  1248. $rule['rid'] = $rid;
  1249. drupal_write_record('webform_conditional_rules', $rule);
  1250. }
  1251. foreach ($conditional['actions'] as $aid => $action) {
  1252. $action['nid'] = $conditional['nid'];
  1253. $action['rgid'] = $conditional['rgid'];
  1254. $action['aid'] = $aid;
  1255. drupal_write_record('webform_conditional_actions', $action);
  1256. }
  1257. }
  1258. /**
  1259. * Update a conditional setting in the database.
  1260. */
  1261. function webform_conditional_update($node, $conditional) {
  1262. $transaction = db_transaction();
  1263. webform_conditional_delete($node, $conditional);
  1264. webform_conditional_insert($conditional);
  1265. }
  1266. /**
  1267. * Delete a conditional rule group.
  1268. */
  1269. function webform_conditional_delete($node, $conditional) {
  1270. $transaction = db_transaction();
  1271. db_delete('webform_conditional')
  1272. ->condition('nid', $node->nid)
  1273. ->condition('rgid', $conditional['rgid'])
  1274. ->execute();
  1275. db_delete('webform_conditional_rules')
  1276. ->condition('nid', $node->nid)
  1277. ->condition('rgid', $conditional['rgid'])
  1278. ->execute();
  1279. db_delete('webform_conditional_actions')
  1280. ->condition('nid', $node->nid)
  1281. ->condition('rgid', $conditional['rgid'])
  1282. ->execute();
  1283. }
  1284. /**
  1285. * Loop through all the conditional settings and add needed JavaScript settings.
  1286. *
  1287. * We do a bit of optimization for JavaScript before adding to the page as
  1288. * settings. We remove unnecessary data structures and provide a "source map"
  1289. * so that JavaScript can quickly determine if it needs to check rules when a
  1290. * field on the page has been modified.
  1291. *
  1292. * @param object $node
  1293. * The loaded node object, containing the webform.
  1294. * @param array $submission_data
  1295. * The cid-indexed array of existing submission values to be included for
  1296. * sources outside of the current page.
  1297. * @param int $page_num
  1298. * The number of the page for which javascript settings should be generated.
  1299. *
  1300. * @return array
  1301. * Array of settings to be send to the browser as javascript settings.
  1302. */
  1303. function webform_conditional_prepare_javascript($node, array $submission_data, $page_num) {
  1304. $settings = array(
  1305. 'ruleGroups' => array(),
  1306. 'sourceMap' => array(),
  1307. 'values' => array(),
  1308. );
  1309. $operators = webform_conditional_operators();
  1310. $conditionals = $node->webform['conditionals'];
  1311. $components = $node->webform['components'];
  1312. $topological_order = webform_get_conditional_sorter($node)->getOrder();
  1313. foreach ($topological_order[$page_num] as $conditional_spec) {
  1314. $conditional = $conditionals[$conditional_spec['rgid']];
  1315. $rgid_key = 'rgid_' . $conditional['rgid'];
  1316. // Assemble the main conditional group settings.
  1317. $settings['ruleGroups'][$rgid_key] = array(
  1318. 'andor' => $conditional['andor'],
  1319. );
  1320. foreach ($conditional['actions'] as $action) {
  1321. if ($action['target_type'] == 'component') {
  1322. $target_component = $components[$action['target']];
  1323. $target_parents = webform_component_parent_keys($node, $target_component);
  1324. $aid_key = 'aid_' . $action['aid'];
  1325. $action_settings = array(
  1326. 'target' => 'webform-component--' . str_replace('_', '-', implode('--', $target_parents)),
  1327. 'invert' => (int) $action['invert'],
  1328. 'action' => $action['action'],
  1329. 'argument' => $components[$action['target']]['type'] == 'markup' ? filter_xss_admin($action['argument']) : $action['argument'],
  1330. );
  1331. $settings['ruleGroups'][$rgid_key]['actions'][$aid_key] = $action_settings;
  1332. }
  1333. }
  1334. // Add on the list of rules to the conditional group.
  1335. foreach ($conditional['rules'] as $rule) {
  1336. $rid_key = 'rid_' . $rule['rid'];
  1337. switch ($rule['source_type']) {
  1338. case 'component':
  1339. $source_component = $components[$rule['source']];
  1340. $source_parents = webform_component_parent_keys($node, $source_component);
  1341. $source_id = 'webform-component--' . str_replace('_', '-', implode('--', $source_parents));
  1342. // If this source has a value set, add that as a setting. NULL or
  1343. // array(NULL) should be sent as an empty array to simplify the
  1344. // jQuery.
  1345. if (isset($submission_data[$source_component['cid']])) {
  1346. $source_value = $submission_data[$source_component['cid']];
  1347. $source_value = is_array($source_value) ? $source_value : array($source_value);
  1348. $settings['values'][$source_id] = $source_value === array(NULL) ? array() : $source_value;
  1349. }
  1350. $conditional_type = webform_component_property($source_component['type'], 'conditional_type');
  1351. $operator_info = $operators[$conditional_type][$rule['operator']];
  1352. $rule_settings = array(
  1353. 'source_type' => $rule['source_type'],
  1354. 'source' => $source_id,
  1355. 'value' => $rule['value'],
  1356. 'callback' => $operator_info['js comparison callback'],
  1357. );
  1358. if (isset($operator_info['comparison prepare js'])) {
  1359. $callback = $operator_info['comparison prepare js'];
  1360. $rule_settings['value'] = $callback($rule['value']);
  1361. }
  1362. $settings['ruleGroups'][$rgid_key]['rules'][$rid_key] = $rule_settings;
  1363. $settings['sourceMap'][$source_id][$rgid_key] = $rgid_key;
  1364. break;
  1365. case 'conditional_start':
  1366. $settings['ruleGroups'][$rgid_key]['rules'][$rid_key] = array(
  1367. 'source_type' => $rule['source_type'],
  1368. 'andor' => $rule['operator'],
  1369. );
  1370. break;
  1371. case 'conditional_end':
  1372. $settings['ruleGroups'][$rgid_key]['rules'][$rid_key] = array(
  1373. 'source_type' => $rule['source_type'],
  1374. );
  1375. break;
  1376. }
  1377. }
  1378. }
  1379. return $settings;
  1380. }
  1381. /**
  1382. * Determine whether a component type is capable of a given conditional action.
  1383. */
  1384. function webform_conditional_action_able($component_type, $action) {
  1385. switch ($action) {
  1386. case 'show':
  1387. return TRUE;
  1388. case 'require':
  1389. return webform_component_feature($component_type, 'required');
  1390. default:
  1391. return webform_component_feature($component_type, "conditional_action_$action");
  1392. }
  1393. }
  1394. /**
  1395. * Prepare a conditional value for adding as a JavaScript setting.
  1396. */
  1397. function webform_conditional_prepare_date_js($rule_value) {
  1398. // Convert the time/date string to a UTC timestamp for comparison. Note that
  1399. // this means comparisons against immediate times (such as "now") may be
  1400. // slightly stale by the time the comparison executes. Timestamps are in
  1401. // milliseconds, as to match JavaScript's Date.toString() method.
  1402. $date = webform_strtodate('c', $rule_value, 'UTC');
  1403. return webform_strtotime($date);
  1404. }
  1405. /**
  1406. * Prepare a conditional value for adding as a JavaScript setting.
  1407. */
  1408. function webform_conditional_prepare_time_js($rule_value) {
  1409. $date = webform_conditional_prepare_date_js($rule_value);
  1410. $today = webform_strtodate('c', 'today', 'UTC');
  1411. $today = webform_strtotime($today);
  1412. return $date - $today;
  1413. }
  1414. /**
  1415. * Conditional callback for string comparisons.
  1416. */
  1417. function webform_conditional_operator_string_equal($input_values, $rule_value) {
  1418. foreach ($input_values as $value) {
  1419. // Checkbox values come in as 0 integers for unchecked boxes.
  1420. $value = ($value === 0) ? '' : $value;
  1421. if (strcasecmp($value, $rule_value) === 0) {
  1422. return TRUE;
  1423. }
  1424. }
  1425. return FALSE;
  1426. }
  1427. /**
  1428. * Conditional callback for string comparisons.
  1429. */
  1430. function webform_conditional_operator_string_not_equal($input_values, $rule_value) {
  1431. return !webform_conditional_operator_string_equal($input_values, $rule_value);
  1432. }
  1433. /**
  1434. * Conditional callback for string comparisons.
  1435. */
  1436. function webform_conditional_operator_string_contains($input_values, $rule_value) {
  1437. foreach ($input_values as $value) {
  1438. if (stripos($value, $rule_value) !== FALSE) {
  1439. return TRUE;
  1440. }
  1441. }
  1442. return FALSE;
  1443. }
  1444. /**
  1445. * Conditional callback for string comparisons.
  1446. */
  1447. function webform_conditional_operator_string_does_not_contain($input_values, $rule_value) {
  1448. return !webform_conditional_operator_string_contains($input_values, $rule_value);
  1449. }
  1450. /**
  1451. * Conditional callback for string comparisons.
  1452. */
  1453. function webform_conditional_operator_string_begins_with($input_values, $rule_value) {
  1454. foreach ($input_values as $value) {
  1455. if (stripos($value, $rule_value) === 0) {
  1456. return TRUE;
  1457. }
  1458. }
  1459. return FALSE;
  1460. }
  1461. /**
  1462. * Conditional callback for string comparisons.
  1463. */
  1464. function webform_conditional_operator_string_ends_with($input_values, $rule_value) {
  1465. foreach ($input_values as $value) {
  1466. if (strripos($value, $rule_value) === strlen($value) - strlen($rule_value)) {
  1467. return TRUE;
  1468. }
  1469. }
  1470. return FALSE;
  1471. }
  1472. /**
  1473. * Conditional callback for checking for empty fields.
  1474. */
  1475. function webform_conditional_operator_string_empty($input_values, $rule_value) {
  1476. $empty = TRUE;
  1477. foreach ($input_values as $value) {
  1478. if ($value !== '' && $value !== NULL && $value !== 0) {
  1479. $empty = FALSE;
  1480. break;
  1481. }
  1482. }
  1483. return $empty;
  1484. }
  1485. /**
  1486. * Conditional callback for checking for empty fields.
  1487. */
  1488. function webform_conditional_operator_string_not_empty($input_values, $rule_value) {
  1489. return !webform_conditional_operator_string_empty($input_values, $rule_value);
  1490. }
  1491. /**
  1492. * Conditional callback for select comparisons.
  1493. */
  1494. function webform_conditional_operator_select_less_than($input_values, $rule_value, $component) {
  1495. return empty($input_values) ? FALSE : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)) < 0;
  1496. }
  1497. /**
  1498. * Conditional callback for select comparisons.
  1499. */
  1500. function webform_conditional_operator_select_less_than_equal($input_values, $rule_value, $component) {
  1501. $comparison = empty($input_values) ? NULL : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE));
  1502. return $comparison < 0 || $comparison === 0;
  1503. }
  1504. /**
  1505. * Conditional callback for select comparisons.
  1506. */
  1507. function webform_conditional_operator_select_greater_than($input_values, $rule_value, $component) {
  1508. return empty($input_values) ? FALSE : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)) > 0;
  1509. }
  1510. /**
  1511. * Conditional callback for select comparisons.
  1512. */
  1513. function webform_conditional_operator_select_greater_than_equal($input_values, $rule_value, $component) {
  1514. $comparison = empty($input_values) ? NULL : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE));
  1515. return $comparison > 0 || $comparison === 0;
  1516. }
  1517. /**
  1518. * Conditional callback for numeric comparisons.
  1519. */
  1520. function webform_conditional_operator_numeric_equal($input_values, $rule_value) {
  1521. return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) === 0;
  1522. }
  1523. /**
  1524. * Conditional callback for numeric comparisons.
  1525. */
  1526. function webform_conditional_operator_numeric_not_equal($input_values, $rule_value) {
  1527. return !webform_conditional_operator_numeric_equal($input_values, $rule_value);
  1528. }
  1529. /**
  1530. * Conditional callback for numeric comparisons.
  1531. */
  1532. function webform_conditional_operator_numeric_less_than($input_values, $rule_value) {
  1533. return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) < 0;
  1534. }
  1535. /**
  1536. * Conditional callback for numeric comparisons.
  1537. */
  1538. function webform_conditional_operator_numeric_less_than_equal($input_values, $rule_value) {
  1539. $comparison = empty($input_values) ? NULL : webform_compare_floats($input_values[0], $rule_value);
  1540. return $comparison < 0 || $comparison === 0;
  1541. }
  1542. /**
  1543. * Conditional callback for numeric comparisons.
  1544. */
  1545. function webform_conditional_operator_numeric_greater_than($input_values, $rule_value) {
  1546. return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) > 0;
  1547. }
  1548. /**
  1549. * Conditional callback for numeric comparisons.
  1550. */
  1551. function webform_conditional_operator_numeric_greater_than_equal($input_values, $rule_value) {
  1552. $comparison = empty($input_values) ? NULL : webform_compare_floats($input_values[0], $rule_value);
  1553. return $comparison > 0 || $comparison === 0;
  1554. }
  1555. /**
  1556. * Conditional callback for date and time comparisons.
  1557. */
  1558. function webform_conditional_operator_datetime_equal($input_values, $rule_value) {
  1559. $input_values = webform_conditional_value_datetime($input_values);
  1560. return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) === webform_strtotime($rule_value);
  1561. }
  1562. /**
  1563. * Conditional callback for date and time comparisons.
  1564. */
  1565. function webform_conditional_operator_datetime_not_equal($input_values, $rule_value) {
  1566. return !webform_conditional_operator_datetime_equal($input_values, $rule_value);
  1567. }
  1568. /**
  1569. * Conditional callback for date and time comparisons.
  1570. */
  1571. function webform_conditional_operator_datetime_after($input_values, $rule_value) {
  1572. $input_values = webform_conditional_value_datetime($input_values);
  1573. return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) > webform_strtotime($rule_value);
  1574. }
  1575. /**
  1576. * Conditional callback for date and time comparisons.
  1577. */
  1578. function webform_conditional_operator_datetime_after_equal($input_values, $rule_value) {
  1579. return webform_conditional_operator_datetime_after($input_values, $rule_value) ||
  1580. webform_conditional_operator_datetime_equal($input_values, $rule_value);
  1581. }
  1582. /**
  1583. * Conditional callback for date and time comparisons.
  1584. */
  1585. function webform_conditional_operator_datetime_before($input_values, $rule_value) {
  1586. $input_values = webform_conditional_value_datetime($input_values);
  1587. return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) < webform_strtotime($rule_value);
  1588. }
  1589. /**
  1590. * Conditional callback for date and time comparisons.
  1591. */
  1592. function webform_conditional_operator_datetime_before_equal($input_values, $rule_value) {
  1593. return webform_conditional_operator_datetime_before($input_values, $rule_value) ||
  1594. webform_conditional_operator_datetime_equal($input_values, $rule_value);
  1595. }
  1596. /**
  1597. * Utility function to convert incoming time and dates into strings.
  1598. */
  1599. function webform_conditional_value_datetime($input_values) {
  1600. // Convert times into a string.
  1601. $input_values = isset($input_values['hour']) ? array(webform_date_string(webform_time_convert($input_values, '24-hour'), 'time')) : $input_values;
  1602. // Convert dates into a string.
  1603. $input_values = isset($input_values['month']) ? array(webform_date_string($input_values, 'date')) : $input_values;
  1604. return $input_values;
  1605. }
  1606. /**
  1607. * Utility function to compare values of a select component.
  1608. *
  1609. * @param string $a
  1610. * First select option key to compare.
  1611. * @param string $b
  1612. * Second select option key to compare.
  1613. * @param array $options
  1614. * Associative array where the $a and $b are within the keys.
  1615. *
  1616. * @return int|null
  1617. * Based upon position of $a and $b in $options:
  1618. * -N if $a above (<) $b
  1619. * 0 if $a = $b
  1620. * +N if $a is below (>) $b
  1621. */
  1622. function webform_compare_select($a, $b, array $options) {
  1623. // Select keys that are integer-like strings are numeric indices in PHP.
  1624. // Convert the array keys to an array of strings.
  1625. $options_array = array_map(function ($i) {
  1626. return (string) $i;
  1627. }, array_keys($options));
  1628. $a_position = array_search($a, $options_array, TRUE);
  1629. $b_position = array_search($b, $options_array, TRUE);
  1630. return ($a_position === FALSE || $b_position === FALSE) ? NULL : $a_position - $b_position;
  1631. }