Projet

Général

Profil

Paste
Télécharger (54,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform / includes / webform.conditionals.inc @ fc2c1c7a

1
<?php
2

    
3
/**
4
 * @file
5
 * Form elements and menu callbacks to provide conditional handling in Webform.
6
 */
7

    
8
/**
9
 * Form builder; Provide the form for adding conditionals to a webform node.
10
 */
11
function webform_conditionals_form($form, &$form_state, $node) {
12
  form_load_include($form_state, 'inc', 'webform', $name = 'includes/webform.components');
13
  form_load_include($form_state, 'inc', 'webform', $name = 'includes/webform.conditionals');
14

    
15
  // Add JavaScript settings to the page needed for conditional elements.
16
  _webform_conditional_expand_value_forms($node);
17

    
18
  if (isset($form_state['values']['conditionals'])) {
19
    // Remove the "new" conditional that always comes in.
20
    unset($form_state['values']['conditionals']['new']);
21

    
22
    $conditionals = $form_state['values']['conditionals'];
23
  }
24
  else {
25
    $conditionals = $node->webform['conditionals'];
26
  }
27
  // Empty out any conditionals that have no rules or actions.
28
  foreach ($conditionals as $rgid => $conditional) {
29
    if (empty($conditional['rules']) || empty($conditional['actions'])) {
30
      unset($conditionals[$rgid]);
31
    }
32
  }
33

    
34
  // Check the current topological sort order for the conditionals and report any errors,
35
  // but only for actual form submissions and not for ajax-related form builds, such as
36
  // adding or removing a condtion or conditional group.
37
  if (empty($form_state['triggering_element']['#ajax'])) {
38
    $node->webform['conditionals'] = $conditionals;
39
    webform_get_conditional_sorter($node)->reportErrors($conditionals);
40
  }
41

    
42
  $form['#tree'] = TRUE;
43
  $form['#node'] = $node;
44

    
45
  $form['#attached']['library'][] = array('webform', 'admin');
46
  $form['#attached']['css'][] = drupal_get_path('module', 'webform') . '/css/webform.css';
47

    
48
  // Wrappers used for AJAX addition/removal.
49
  $form['conditionals']['#theme'] = 'webform_conditional_groups';
50
  $form['conditionals']['#prefix'] = '<div id="webform-conditionals-ajax">';
51
  $form['conditionals']['#suffix'] = '</div>';
52

    
53
  // Keep track of the max conditional count to use as the range for weights.
54
  $form_state['conditional_count'] = isset($form_state['conditional_count']) ? $form_state['conditional_count'] : 1;
55
  $form_state['conditional_count'] = count($conditionals) > $form_state['conditional_count'] ? count($conditionals) : $form_state['conditional_count'];
56

    
57
  $source_list = webform_component_list($node, 'conditional', 'path', TRUE);
58
  $target_list = webform_component_list($node, TRUE, 'path', TRUE);
59
  $components = $node->webform['components'];
60
  $delta = $form_state['conditional_count'];
61
  $weight = -$delta - 1;
62
  $index = 0;
63
  foreach ($conditionals as $rgid => $conditional_group) {
64
    $weight = $conditional_group['weight'];
65
    $form['conditionals'][$rgid] = array(
66
      '#theme' => 'webform_conditional_group_row',
67
      '#even_odd' => ++$index % 2 ? 'odd' : 'even',
68
      '#weight' => $weight,
69
    );
70
    $form['conditionals'][$rgid]['conditional'] = array(
71
      '#type' => 'webform_conditional',
72
      '#default_value' => $conditional_group,
73
      '#nid' => $node->nid,
74
      '#sources' => $source_list,
75
      '#actions' => array(
76
        'show' => t('shown'),
77
        'require' => t('required'),
78
        'set' => t('set to'),
79
      ),
80
      '#targets' => $target_list,
81
      '#parents' => array('conditionals', $rgid),
82
    );
83
    foreach ($conditional_group['actions'] as $action) {
84
      $cid = $action['target'];
85
      if ($action['action'] == 'require' && !$components[$cid]['required']) {
86
        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>',
87
                             array('%title' => $components[$cid]['name'],
88
                                   '!url' => url("node/{$node->nid}/webform/components/$cid", array('query' => array('destination' => "node/{$node->nid}/webform/conditionals"))))
89
                             ), 'error');
90
      }
91
    }
92
    $form['conditionals'][$rgid]['weight'] = array(
93
      '#type' => 'weight',
94
      '#title' => t('Weight for rule group !rgid', array('!rgid' => $rgid)),
95
      '#title_display' => 'invisible',
96
      '#default_value' => $weight,
97
      '#delta' => $delta,
98
    );
99
  }
100

    
101
  $form['conditionals']['new']['#weight'] = $weight + 1;
102
  $form['conditionals']['new']['weight'] = array(
103
    '#type' => 'weight',
104
    '#title' => t('Weight for new rule group'),
105
    '#title_display' => 'invisible',
106
    '#default_value' => $weight + 1,
107
    '#delta' => $delta,
108
  );
109
  $form['conditionals']['new']['new'] = array(
110
    '#type' => 'submit',
111
    '#value' => t('+'),
112
    '#submit' => array('webform_conditionals_form_add'),
113
    '#ajax' => array(
114
      'progress' => 'none',
115
      'effect' => 'fade',
116
      'callback' => 'webform_conditionals_ajax',
117
    ),
118
  );
119

    
120
  $form['actions'] = array(
121
    '#type' => 'actions',
122
    '#tree' => FALSE,
123
  );
124
  $form['actions']['submit'] = array(
125
    '#type' => 'submit',
126
    '#value' => t('Save conditions'),
127
    '#validate' => array('webform_conditionals_form_validate'),
128
    '#submit' => array('webform_conditionals_form_submit'),
129
  );
130

    
131
  // Estimate if the form is too long for PHP max_input_vars and detect whether a previous submission was truncated.
132
  // The estimate will be accurate because the form elements for this page are well known. Ajax use of this
133
  // page will not generate user-visible errors, so a preflight may be the only indication to the user that
134
  // the page is too long.
135
  webform_input_vars_check($form, $form_state, 'conditionals', '');
136
  return $form;
137
}
138

    
139
/**
140
 * Submit handler for webform_conditionals_form(). Add an additional choice.
141
 */
142
function webform_conditionals_form_add($form, &$form_state) {
143
  // Build a default new conditional.
144
  unset($form_state['values']['conditionals']['new']);
145
  $weight = count($form_state['values']['conditionals']) > 10 ? -count($form_state['values']['conditionals']) : -10;
146
  foreach ($form_state['values']['conditionals'] as $key => $conditional) {
147
    $weight = max($weight, $conditional['weight']);
148
  }
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' => NULL,
155
        'operator' => NULL,
156
        'value' => NULL,
157
      ),
158
    ),
159
    'andor' => 'and',
160
    'actions' => array(
161
      array(
162
        'target' => NULL,
163
        'invert' => NULL,
164
        'action' => NULL,
165
        'argument' => NULL,
166
      ),
167
    ),
168
    'weight' => $weight + 1,
169
  );
170
  $form_state['rebuild'] = TRUE;
171
}
172

    
173
/**
174
 * Validate handler for webform_conditionals_form().
175
 *
176
 * Prohibit the source and target of a conditional rule from being the same.
177
 */
178
function webform_conditionals_form_validate($form, &$form_state) {
179
  // Skip validation unless this is saving the form.
180
  $button_key = end($form_state['triggering_element']['#array_parents']);
181
  if ($button_key !== 'submit') {
182
    return;
183
  }
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('%op' => $action['action']['#options'][$operation],
200
                                   '%target' => $components[$action['target']['#value']]['name'])));
201
          }
202
          $component_type = $node->webform['components'][$action['target']['#value']]['type'];
203
          if (!webform_conditional_action_able($component_type, $action['action']['#value'])) {
204
            form_set_error('conditionals][' . $conditional_key . '][actions][' . $action_key . '][action',
205
                           t('A component of type %type can\'t be %action. (%target)',
206
                             array(
207
                                '%action' => $action['action']['#options'][$action['action']['#value']],
208
                                '%type' => $component_options[$component_type],
209
                                '%target' => $components[$action['target']['#value']]['name'])));
210
          }
211
          $targets[$target_id][$operation] = $target_id;
212
        }
213
      }
214
      foreach ($conditional['rules'] as $rule_key => $rule) {
215
        if (is_numeric($rule_key) && isset($targets[$rule['source']['#value']])) {
216
          form_set_error('conditionals][' . $conditional_key . '][rules][' . $rule_key . '][source',
217
                         t('The subject of the conditional cannot be the same as the component that is changed (%target).',
218
                         array('%target' => $components[$rule['source']['#value']]['name'])));
219
        }
220
      }
221
    }
222
  }
223

    
224
  // Form validation will not rebuild the form, so we need to ensure
225
  // necessary JavaScript will still exist.
226
  _webform_conditional_expand_value_forms($node);
227
}
228

    
229
/**
230
 * Submit handler for webform_conditionals_form().
231
 */
232
function webform_conditionals_form_submit($form, &$form_state) {
233
  $node = $form['#node'];
234

    
235
  // Remove the new conditional placeholder.
236
  unset($form_state['values']['conditionals']['new']);
237

    
238
  $conditionals = array();
239

    
240
  // Fill in missing properties for each value so that it can save properly.
241
  // TODO: Remove hard-coded source and target type.
242
  foreach ($form_state['values']['conditionals'] as $rgid => $conditional) {
243
    $conditional['rgid'] = $rgid;
244
    foreach ($conditional['rules'] as $rid => $rule) {
245
      $conditional['rules'][$rid]['source_type'] = 'component';
246
    }
247
    foreach ($conditional['actions'] as $aid => $action) {
248
      $conditional['actions'][$aid]['target_type'] = 'component';
249
    }
250
    $conditionals[$rgid] = $conditional;
251
  }
252

    
253
  $node->webform['conditionals'] = $conditionals;
254
  node_save($node);
255
  drupal_set_message(t('Conditionals for %title saved.', array('%title' => $node->title)));
256
}
257

    
258
/**
259
 * AJAX callback to render out adding a new condition.
260
 */
261
function webform_conditionals_ajax($form, $form_state) {
262
  $rgids = element_children($form['conditionals']);
263
  $new_rgid = max($rgids);
264
  $form['conditionals'][$new_rgid]['#ajax_added'] = TRUE;
265

    
266
  $commands = array('#type' => 'ajax');
267
  $commands['#commands'][] = ajax_command_before('.webform-conditional-new-row', drupal_render($form['conditionals'][$new_rgid]));
268
  $commands['#commands'][] = ajax_command_restripe('#webform-conditionals-table');
269
  return $commands;
270
}
271

    
272
/**
273
 * Theme the $form['conditionals'] of webform_conditionals_form().
274
 */
275
function theme_webform_conditional_groups($variables) {
276
  $element = $variables['element'];
277
  drupal_add_tabledrag('webform-conditionals-table', 'order', 'sibling', 'webform-conditional-weight');
278
  drupal_add_js('Drupal.theme.prototype.tableDragChangedMarker = function() { return ""; }', 'inline');
279
  drupal_add_js('Drupal.theme.prototype.tableDragChangedWarning = function() { return "<span>&nbsp;</span>"; }', 'inline');
280

    
281
  $output = '<table id="webform-conditionals-table"><tbody>';
282
  $element_children = element_children($element, TRUE);
283
  $element_count = count($element_children);
284
  foreach ($element_children as $index => $key) {
285
    if ($key === 'new') {
286
      $data = '';
287
      if ($element_count === 1) {
288
        $data = t('There are no conditional actions on this form.') . ' ';
289
      }
290
      $even_odd = ($index + 1) % 2 ? 'odd' : 'even';
291
      $element[$key]['weight']['#attributes']['class'] = array('webform-conditional-weight');
292
      $data = '<div class="webform-conditional-new">' . $data . t('Add a new condition:') . ' ' . drupal_render($element[$key]['new']) . '</div>';
293
      $output .= '<tr class="webform-conditional-new-row ' . $even_odd . '">';
294
      $output .= '<td>' . $data . '</td>';
295
      $output .= '<td>' . drupal_render($element[$key]['weight']) . '</td>';
296
      $output .= '</tr>';
297
    }
298
    else {
299
      $output .= drupal_render($element[$key]);
300
    }
301
  }
302
  $output .= '</tbody></table>';
303
  $output .= drupal_render_children($element);
304

    
305
  return $output;
306
}
307

    
308
/**
309
 * Theme an individual conditional row of webform_conditionals_form().
310
 */
311
function theme_webform_conditional_group_row($variables) {
312
  $element = $variables['element'];
313

    
314
  $element['weight']['#attributes']['class'] = array('webform-conditional-weight');
315
  $weight = drupal_render($element['weight']);
316
  $classes = array('draggable');
317
  if (!empty($element['#even_odd'])) {
318
    $classes[] = $element['#even_odd'];
319
  }
320
  if (!empty($element['#ajax_added'])) {
321
    $classes[] = 'ajax-new-content';
322
  }
323

    
324
  $output = '';
325
  $output .= '<tr class="' . implode(' ', $classes) . '">';
326
  $output .= '<td>' . drupal_render_children($element) . '</td>';
327
  $output .= '<td>' . $weight . '</td>';
328
  $output .= '</tr>';
329

    
330
  return $output;
331
}
332

    
333
/**
334
 * Form API #process function to expand a webform conditional element.
335
 */
336
function _webform_conditional_expand($element) {
337
  $element['#tree'] = TRUE;
338
  $element['#default_value'] += array(
339
    'andor' => 'and',
340
  );
341

    
342
  $wrapper_id = drupal_clean_css_identifier(implode('-', $element['#parents'])) . '-ajax';
343
  $element['#prefix'] = '<div id="' . $wrapper_id . '">';
344
  $element['#suffix'] = '</div>';
345

    
346
  foreach ($element['#default_value']['rules'] as $rid => $conditional) {
347
    $element['rules'][$rid]['source'] = array(
348
      '#type' => 'select',
349
      '#title' => t('Source'),
350
      '#options' => $element['#sources'],
351
      '#default_value' => $element['#default_value']['rules'][$rid]['source'],
352
    );
353
    $element['rules'][$rid]['operator'] = array(
354
      '#type' => 'select',
355
      '#title' => t('Operator'),
356
      '#options' => webform_conditional_operators_list(),
357
      '#default_value' => $element['#default_value']['rules'][$rid]['operator'],
358
    );
359
    $element['rules'][$rid]['value'] = array(
360
      '#type' => 'textfield',
361
      '#title' => t('Value'),
362
      '#size' => 20,
363
      '#default_value' => $element['#default_value']['rules'][$rid]['value'],
364
    );
365
    $element['rules'][$rid]['remove'] = array(
366
      '#type' => 'submit',
367
      '#value' => t('-'),
368
      '#submit' => array('webform_conditional_element_remove'),
369
      '#name' => implode('_', $element['#parents']) . '_rules_' . $rid . '_remove',
370
      '#attributes' => array('class' => array('webform-conditional-rule-remove')),
371
      '#ajax' => array(
372
        'progress' => 'none',
373
        'callback' => 'webform_conditional_element_ajax',
374
        'wrapper' => $wrapper_id,
375
        'event' => 'click',
376
      ),
377
    );
378
    $element['rules'][$rid]['add'] = array(
379
      '#type' => 'submit',
380
      '#value' => t('+'),
381
      '#submit' => array('webform_conditional_element_add'),
382
      '#name' => implode('_', $element['#parents']) . '_rules_' . $rid . '_add',
383
      '#attributes' => array('class' => array('webform-conditional-rule-add')),
384
      '#ajax' => array(
385
        'progress' => 'none',
386
        'callback' => 'webform_conditional_element_ajax',
387
        'wrapper' => $wrapper_id,
388
        'event' => 'click',
389
      ),
390
    );
391

    
392
    // The and/or selector is shown for every rule, even though the whole
393
    // conditional group shares a single and/or property. They are made to match
394
    // via JavaScript (though they all share the same "name" attribute so only
395
    // a single value is ever submitted via POST).
396
    $element['rules'][$rid]['andor'] = array(
397
      '#type' => 'select',
398
      '#title' => t('And/or'),
399
      '#options' => array(
400
        'and' => t('and'),
401
        'or' => t('or'),
402
      ),
403
      '#parents' => array_merge($element['#parents'], array('andor')),
404
      '#default_value' => $element['#default_value']['andor'],
405
    );
406
  }
407

    
408
  // Remove the last and/or.
409
  unset($element['rules'][$rid]['andor']);
410

    
411
  foreach ($element['#default_value']['actions'] as $aid => $action) {
412
    $element['actions'][$aid]['target'] = array(
413
      '#type' => 'select',
414
      '#title' => t('Target'),
415
      '#options' => $element['#targets'],
416
      '#default_value' => $element['#default_value']['actions'][$aid]['target'],
417
    );
418
    $element['actions'][$aid]['invert'] = array(
419
      '#type' => 'select',
420
      '#title' => t('Is/Isn\'t'),
421
      '#options' => array(
422
        '0' => t('is'),
423
        '1' => t('isn\'t'),
424
      ),
425
      '#default_value' => $element['#default_value']['actions'][$aid]['invert'],
426
    );
427
    $element['actions'][$aid]['action'] = array(
428
      '#type' => 'select',
429
      '#title' => t('Action'),
430
      '#options' => $element['#actions'],
431
      '#default_value' => $element['#default_value']['actions'][$aid]['action'],
432
    );
433
    $element['actions'][$aid]['argument'] = array(
434
      '#type' => 'textfield',
435
      '#title' => t('Argument'),
436
      '#size' => 20,
437
      '#maxlength' => NULL,
438
      '#default_value' => $element['#default_value']['actions'][$aid]['argument'],
439
    );
440
    $element['actions'][$aid]['remove'] = array(
441
      '#type' => 'submit',
442
      '#value' => t('-'),
443
      '#submit' => array('webform_conditional_element_remove'),
444
      '#name' => implode('_', $element['#parents']) . '_actions_' . $aid . '_remove',
445
      '#attributes' => array('class' => array('webform-conditional-action-remove')),
446
      '#ajax' => array(
447
        'progress' => 'none',
448
        'callback' => 'webform_conditional_element_ajax',
449
        'wrapper' => $wrapper_id,
450
        'event' => 'click',
451
      ),
452
    );
453
    $element['actions'][$aid]['add'] = array(
454
      '#type' => 'submit',
455
      '#value' => t('+'),
456
      '#submit' => array('webform_conditional_element_add'),
457
      '#name' => implode('_', $element['#parents']) . '_actions_' . $aid . '_add',
458
      '#attributes' => array('class' => array('webform-conditional-action-add')),
459
      '#ajax' => array(
460
        'progress' => 'none',
461
        'callback' => 'webform_conditional_element_ajax',
462
        'wrapper' => $wrapper_id,
463
        'event' => 'click',
464
      ),
465
    );
466
  }
467

    
468
  return $element;
469
}
470

    
471
/**
472
 * Expand out all the value forms that could potentially be used.
473
 *
474
 * These forms are added to the page via JavaScript and swapped in only when
475
 * needed. Because the user may change the source and operator at any time,
476
 * all these forms need to be generated ahead of time and swapped in. This
477
 * could have been done via AJAX, but having all forms available makes for a
478
 * faster user experience.
479
 *
480
 * Added to the JavaScript settings is conditionalValues which contains
481
 * an array settings suitable for adding to the page via JavaScript. This
482
 * array contains the following keys:
483
 *   - operators: An array containing a map of data types, operators, and form
484
 *     keys. This array is structured as follows:
485
 *     @code
486
 *   - sources[$source_key] = array(
487
 *       'data_type' => $data_type,
488
 *     );
489
 *     $operators[$data_type][$operator] = array(
490
 *       'form' => $form_key,
491
 *     );
492
 *     @endcode
493
 *   - forms[$form_key]: A string representing an HTML form for an operator.
494
 *   - forms[$form_key][$source]: Or instead of a single form for all components,
495
 *     if each component requires its own form, key each component by its source
496
 *     value (currently always the component ID).
497
 *
498
 * @param $node
499
 *   The Webform node for which these forms are being generated.
500
 */
501
function _webform_conditional_expand_value_forms($node) {
502
  $operators = webform_conditional_operators();
503
  $data = array();
504
  foreach ($operators as $data_type => $operator_info) {
505
    foreach ($operator_info as $operator => $data_operator_info) {
506
      $data['operators'][$data_type][$operator]['form'] = 'default';
507
      if (isset($data_operator_info['form callback'])) {
508
        $form_callback = $data_operator_info['form callback'];
509
        $data['operators'][$data_type][$operator]['form'] = $form_callback;
510
        if ($form_callback !== FALSE && !isset($data['forms'][$form_callback])) {
511
          $data['forms'][$form_callback] = $form_callback($node);
512
        }
513
      }
514
    }
515
  }
516

    
517
  foreach ($node->webform['components'] as $cid => $component) {
518
    if (webform_component_feature($component['type'], 'conditional')) {
519
      $data['sources'][$cid]['data_type'] = webform_component_property($component['type'], 'conditional_type');
520
    }
521
  }
522

    
523
  drupal_add_js(array('webform' => array('conditionalValues' => $data)), 'setting');
524
}
525

    
526
/**
527
 * Submit handler for webform_conditional elements to add a new rule.
528
 */
529
function webform_conditional_element_add($form, &$form_state) {
530
  $button = $form_state['clicked_button'];
531
  $parents = $button['#parents'];
532
  $action = array_pop($parents);
533
  $rid = array_pop($parents);
534

    
535
  // Recurse through the form values until we find the root Webform conditional.
536
  $parent_values = &$form_state['values'];
537
  foreach ($parents as $key) {
538
    if (array_key_exists($key, $parent_values)) {
539
      $parent_values = &$parent_values[$key];
540
    }
541
  }
542

    
543
  // Split the list of rules in this conditional and inject into the right spot.
544
  $rids = array_keys($parent_values);
545
  $offset = array_search($rid, $rids);
546
  $first = array_slice($parent_values, 0, $offset + 1);
547
  $second = array_slice($parent_values, $offset + 1);
548
  $new[0] = $parent_values[$rid];
549

    
550
  $parent_values = array_merge($first, $new, $second);
551

    
552
  $form_state['rebuild'] = TRUE;
553
}
554

    
555
/**
556
 * Submit handler for webform_conditional elements to remove a rule or action.
557
 */
558
function webform_conditional_element_remove($form, &$form_state) {
559
  $button = $form_state['clicked_button'];
560
  $parents = $button['#parents'];
561
  $action = array_pop($parents);
562
  $current_id = array_pop($parents);
563

    
564
  // Recurse through the form values until we find the root Webform conditional.
565
  $parent_values = &$form_state['values'];
566
  foreach ($parents as $key) {
567
    if (array_key_exists($key, $parent_values)) {
568
      $parent_values = &$parent_values[$key];
569
    }
570
  }
571

    
572
  // Remove this rule or action from the list of conditionals.
573
  unset($parent_values[$current_id]);
574

    
575
  $form_state['rebuild'] = TRUE;
576
}
577

    
578

    
579
/**
580
 * AJAX callback to render out adding a new condition.
581
 */
582
function webform_conditional_element_ajax($form, $form_state) {
583
  $button = $form_state['clicked_button'];
584
  $parents = $button['#parents'];
585

    
586
  // Trim down the parents to go back up to the level of this elements wrapper.
587
  array_pop($parents); // The button name (add/remove).
588
  array_pop($parents); // The rule ID.
589
  array_pop($parents); // The "rules" grouping.
590

    
591
  $element = $form;
592
  foreach ($parents as $key) {
593
    if (!isset($element[$key])) {
594
      // The entire conditional has been removed
595
      return '';
596
    }
597
    $element = $element[$key];
598
  }
599

    
600
  return drupal_render($element['conditional']);
601
}
602

    
603
/**
604
 * Theme the form for a conditional action.
605
 */
606
function theme_webform_conditional($variables) {
607
  $element = $variables['element'];
608

    
609
  $output = '';
610
  $output .= '<div class="webform-conditional">';
611
  $output .= '<span class="webform-conditional-if">' . t('If') . '</span>';
612

    
613
  foreach (element_children($element['rules']) as $rid) {
614
    // Hide labels.
615
    $element['rules'][$rid]['source']['#title_display'] = 'none';
616
    $element['rules'][$rid]['operator']['#title_display'] = 'none';
617
    $element['rules'][$rid]['value']['#title_display'] = 'none';
618
    $element['rules'][$rid]['andor']['#title_display'] = 'none';
619

    
620
    $source = '<div class="webform-conditional-source">' . drupal_render($element['rules'][$rid]['source']) . '</div>';
621
    $operator = '<div class="webform-conditional-operator">' . drupal_render($element['rules'][$rid]['operator']) . '</div>';
622
    $value = '<div class="webform-conditional-value">' . drupal_render($element['rules'][$rid]['value']) . '</div>';
623

    
624
    $source_phrase = t('!source !operator !value', array(
625
      '!source' => $source,
626
      '!operator' => $operator,
627
      '!value' => $value,
628
    ));
629

    
630
    $output .= '<div class="webform-conditional-rule">';
631
    $output .= '<div class="webform-container-inline webform-conditional-condition">';
632
    $output .= $source_phrase;
633
    $output .= '</div>';
634

    
635
    if (isset($element['rules'][$rid]['andor'])) {
636
      $output .= '<div class="webform-conditional-andor webform-container-inline">';
637
      $output .= drupal_render($element['rules'][$rid]['andor']);
638
      $output .= '</div>';
639
    }
640

    
641
    if (isset($element['rules'][$rid]['add']) || isset($element['rules'][$rid]['remove'])) {
642
      $output .= '<span class="webform-conditional-operations webform-container-inline">';
643
      $output .= drupal_render($element['rules'][$rid]['remove']);
644
      $output .= drupal_render($element['rules'][$rid]['add']);
645
      $output .= '</span>';
646
    }
647

    
648
    $output .= '</div>';
649
  }
650

    
651
  // Hide labels.
652
  foreach (element_children($element['actions']) as $aid) {
653
    // Hide labels.
654
    $element['actions'][$aid]['target']['#title_display'] = 'none';
655
    $element['actions'][$aid]['invert']['#title_display'] = 'none';
656
    $element['actions'][$aid]['action']['#title_display'] = 'none';
657
    $element['actions'][$aid]['argument']['#title_display'] = 'none';
658

    
659
    $target = '<div class="webform-conditional-target">' . drupal_render($element['actions'][$aid]['target']) . '</div>';
660
    $invert = '<div class="webform-conditional-invert">' . drupal_render($element['actions'][$aid]['invert']) . '</div>';
661
    $action = '<div class="webform-conditional-action">' . drupal_render($element['actions'][$aid]['action']) . '</div>';
662
    $argument = '<div class="webform-conditional-argument">' . drupal_render($element['actions'][$aid]['argument']) . '</div>';
663

    
664
    $target_phrase = t('then !target !invert !action !argument', array(
665
      '!target' => $target,
666
      '!invert' => $invert,
667
      '!action' => $action,
668
      '!argument' => $argument,
669
    ));
670

    
671
    $output .= '<div class="webform-conditional-action">';
672
    $output .= '<div class="webform-container-inline webform-conditional-condition">';
673
    $output .= $target_phrase;
674
    $output .= '</div>';
675

    
676
    if (isset($element['actions'][$aid]['add']) || isset($element['actions'][$aid]['remove'])) {
677
      $output .= '<span class="webform-conditional-operations webform-container-inline">';
678
      $output .= drupal_render($element['actions'][$aid]['remove']);
679
      $output .= drupal_render($element['actions'][$aid]['add']);
680
      $output .= '</span>';
681
    }
682

    
683
    $output .= '</div>';
684
  }
685

    
686
  $output .= '</div>';
687

    
688
  return $output;
689
}
690

    
691
/**
692
 * Return a list of all Webform conditional operators.
693
 */
694
function webform_conditional_operators() {
695
  static $operators;
696

    
697
  if (!isset($operators)) {
698
    $operators = module_invoke_all('webform_conditional_operator_info');
699
    drupal_alter('webform_conditional_operators', $operators);
700
  }
701

    
702
  return $operators;
703
}
704

    
705
/**
706
 * Return a nested list of all available operators, suitable for a select list.
707
 */
708
function webform_conditional_operators_list() {
709
  $options = array();
710
  $operators = webform_conditional_operators();
711

    
712
  foreach ($operators as $data_type => $type_operators) {
713
    $options[$data_type] = array();
714
    foreach ($type_operators as $operator => $operator_info) {
715
      $options[$data_type][$operator] = $operator_info['label'];
716
    }
717
  }
718

    
719
  return $options;
720
}
721

    
722
/**
723
 * Internal implementation of hook_webform_conditional_operator_info().
724
 *
725
 * Called from webform.module's webform_webform_conditional_operator_info().
726
 */
727
function _webform_conditional_operator_info() {
728
  // General operators:
729
  $operators['string']['equal'] = array(
730
    'label' => t('is'),
731
    'comparison callback' => 'webform_conditional_operator_string_equal',
732
    'js comparison callback' => 'conditionalOperatorStringEqual',
733
    // A form callback is not needed here, since we can use the default,
734
    // non-JavaScript textfield for all text and numeric fields.
735
    // 'form callback' => 'webform_conditional_operator_text',
736
  );
737
  $operators['string']['not_equal'] = array(
738
    'label' => t('is not'),
739
    'comparison callback' => 'webform_conditional_operator_string_not_equal',
740
    'js comparison callback' => 'conditionalOperatorStringNotEqual',
741
  );
742
  $operators['string']['contains'] = array(
743
    'label' => t('contains'),
744
    'comparison callback' => 'webform_conditional_operator_string_contains',
745
    'js comparison callback' => 'conditionalOperatorStringContains',
746
  );
747
  $operators['string']['does_not_contain'] = array(
748
    'label' => t('does not contain'),
749
    'comparison callback' => 'webform_conditional_operator_string_does_not_contain',
750
    'js comparison callback' => 'conditionalOperatorStringDoesNotContain',
751
  );
752
  $operators['string']['begins_with'] = array(
753
    'label' => t('begins with'),
754
    'comparison callback' => 'webform_conditional_operator_string_begins_with',
755
    'js comparison callback' => 'conditionalOperatorStringBeginsWith',
756
  );
757
  $operators['string']['ends_with'] = array(
758
    'label' => t('ends with'),
759
    'comparison callback' => 'webform_conditional_operator_string_ends_with',
760
    'js comparison callback' => 'conditionalOperatorStringEndsWith',
761
  );
762
  $operators['string']['empty'] = array(
763
    'label' => t('is blank'),
764
    'comparison callback' => 'webform_conditional_operator_string_empty',
765
    'js comparison callback' => 'conditionalOperatorStringEmpty',
766
    'form callback' => FALSE, // No value form at all.
767
  );
768
  $operators['string']['not_empty'] = array(
769
    'label' => t('is not blank'),
770
    'comparison callback' => 'webform_conditional_operator_string_not_empty',
771
    'js comparison callback' => 'conditionalOperatorStringNotEmpty',
772
    'form callback' => FALSE, // No value form at all.
773
  );
774

    
775
  // Numeric operators.
776
  $operators['numeric']['equal'] = array(
777
    'label' => t('is equal to'),
778
    'comparison callback' => 'webform_conditional_operator_numeric_equal',
779
    'js comparison callback' => 'conditionalOperatorNumericEqual',
780
  );
781
  $operators['numeric']['not_equal'] = array(
782
    'label' => t('is not equal to'),
783
    'comparison callback' => 'webform_conditional_operator_numeric_not_equal',
784
    'js comparison callback' => 'conditionalOperatorNumericNotEqual',
785
  );
786
  $operators['numeric']['less_than'] = array(
787
    'label' => t('is less than'),
788
    'comparison callback' => 'webform_conditional_operator_numeric_less_than',
789
    'js comparison callback' => 'conditionalOperatorNumericLessThan',
790
  );
791
  $operators['numeric']['less_than_equal'] = array(
792
    'label' => t('is less than or equal'),
793
    'comparison callback' => 'webform_conditional_operator_numeric_less_than_equal',
794
    'js comparison callback' => 'conditionalOperatorNumericLessThanEqual',
795
  );
796
  $operators['numeric']['greater_than'] = array(
797
    'label' => t('is greater than'),
798
    'comparison callback' => 'webform_conditional_operator_numeric_greater_than',
799
    'js comparison callback' => 'conditionalOperatorNumericGreaterThan',
800
  );
801
  $operators['numeric']['greater_than_equal'] = array(
802
    'label' => t('is greater than or equal'),
803
    'comparison callback' => 'webform_conditional_operator_numeric_greater_than_equal',
804
    'js comparison callback' => 'conditionalOperatorNumericGreaterThanEqual',
805
  );
806
  $operators['numeric']['empty'] = array(
807
    'label' => t('is blank'),
808
    'comparison callback' => 'webform_conditional_operator_string_empty',
809
    'js comparison callback' => 'conditionalOperatorStringEmpty',
810
    'form callback' => FALSE, // No value form at all.
811
  );
812
  $operators['numeric']['not_empty'] = array(
813
    'label' => t('is not blank'),
814
    'comparison callback' => 'webform_conditional_operator_string_not_empty',
815
    'js comparison callback' => 'conditionalOperatorStringNotEmpty',
816
    'form callback' => FALSE, // No value form at all.
817
  );
818

    
819
  // Select operators.
820
  $operators['select']['equal'] = array(
821
    'label' => t('is'),
822
    'comparison callback' => 'webform_conditional_operator_string_equal',
823
    'js comparison callback' => 'conditionalOperatorStringEqual',
824
    'form callback' => 'webform_conditional_form_select',
825
  );
826
  $operators['select']['not_equal'] = array(
827
    'label' => t('is not'),
828
    'comparison callback' => 'webform_conditional_operator_string_not_equal',
829
    'js comparison callback' => 'conditionalOperatorStringNotEqual',
830
    'form callback' => 'webform_conditional_form_select',
831
  );
832
  $operators['select']['less_than'] = array(
833
    'label' => t('is before'),
834
    'comparison callback' => 'webform_conditional_operator_select_less_than',
835
    'js comparison callback' => 'conditionalOperatorSelectLessThan',
836
    'form callback' => 'webform_conditional_form_select',
837
  );
838
  $operators['select']['less_than_equal'] = array(
839
    'label' => t('is or is before'),
840
    'comparison callback' => 'webform_conditional_operator_select_less_than_equal',
841
    'js comparison callback' => 'conditionalOperatorSelectLessThanEqual',
842
    'form callback' => 'webform_conditional_form_select',
843
  );
844
  $operators['select']['greater_than'] = array(
845
    'label' => t('is after'),
846
    'comparison callback' => 'webform_conditional_operator_select_greater_than',
847
    'js comparison callback' => 'conditionalOperatorSelectGreaterThan',
848
    'form callback' => 'webform_conditional_form_select',
849
  );
850
  $operators['select']['greater_than_equal'] = array(
851
    'label' => t('is or is after'),
852
    'comparison callback' => 'webform_conditional_operator_select_greater_than_equal',
853
    'js comparison callback' => 'conditionalOperatorSelectGreaterThanEqual',
854
    'form callback' => 'webform_conditional_form_select',
855
  );
856
  $operators['select']['empty'] = array(
857
    'label' => t('is empty'),
858
    'comparison callback' => 'webform_conditional_operator_string_empty',
859
    'js comparison callback' => 'conditionalOperatorStringEmpty',
860
    'form callback' => FALSE, // No value form at all.
861
  );
862
  $operators['select']['not_empty'] = array(
863
    'label' => t('is not empty'),
864
    'comparison callback' => 'webform_conditional_operator_string_not_empty',
865
    'js comparison callback' => 'conditionalOperatorStringNotEmpty',
866
    'form callback' => FALSE, // No value form at all.
867
  );
868

    
869
  // Date operators:
870
  $operators['date']['equal'] = array(
871
    'label' => t('is on'),
872
    'comparison callback' => 'webform_conditional_operator_datetime_equal',
873
    'comparison prepare js' => 'webform_conditional_prepare_date_js',
874
    'js comparison callback' => 'conditionalOperatorDateEqual',
875
    'form callback' => 'webform_conditional_form_date',
876
  );
877
  $operators['date']['not_equal'] = array(
878
    'label' => t('is not on'),
879
    'comparison callback' => 'webform_conditional_operator_datetime_not_equal',
880
    'comparison prepare js' => 'webform_conditional_prepare_date_js',
881
    'js comparison callback' => 'conditionalOperatorDateNotEqual',
882
    'form callback' => 'webform_conditional_form_date',
883
  );
884
  $operators['date']['before'] = array(
885
    'label' => t('is before'),
886
    'comparison callback' => 'webform_conditional_operator_datetime_before',
887
    'comparison prepare js' => 'webform_conditional_prepare_date_js',
888
    'js comparison callback' => 'conditionalOperatorDateBefore',
889
    'form callback' => 'webform_conditional_form_date',
890
  );
891
  $operators['date']['before_equal'] = array(
892
    'label' => t('is on or before'),
893
    'comparison callback' => 'webform_conditional_operator_datetime_before_equal',
894
    'comparison prepare js' => 'webform_conditional_prepare_date_js',
895
    'js comparison callback' => 'conditionalOperatorDateBeforeEqual',
896
    'form callback' => 'webform_conditional_form_date',
897
  );
898
  $operators['date']['after'] = array(
899
    'label' => t('is after'),
900
    'comparison callback' => 'webform_conditional_operator_datetime_after',
901
    'comparison prepare js' => 'webform_conditional_prepare_date_js',
902
    'js comparison callback' => 'conditionalOperatorDateAfter',
903
    'form callback' => 'webform_conditional_form_date',
904
  );
905
  $operators['date']['after_equal'] = array(
906
    'label' => t('is on or after'),
907
    'comparison callback' => 'webform_conditional_operator_datetime_after_equal',
908
    'comparison prepare js' => 'webform_conditional_prepare_date_js',
909
    'js comparison callback' => 'conditionalOperatorDateAfterEqual',
910
    'form callback' => 'webform_conditional_form_date',
911
  );
912

    
913
  // Time operators:
914
  $operators['time']['equal'] = array(
915
    'label' => t('is at'),
916
    'comparison callback' => 'webform_conditional_operator_datetime_equal',
917
    'comparison prepare js' => 'webform_conditional_prepare_time_js',
918
    'js comparison callback' => 'conditionalOperatorTimeEqual',
919
    'form callback' => 'webform_conditional_form_time',
920
  );
921
  $operators['time']['not_equal'] = array(
922
    'label' => t('is not at'),
923
    'comparison callback' => 'webform_conditional_operator_datetime_not_equal',
924
    'comparison prepare js' => 'webform_conditional_prepare_time_js',
925
    'js comparison callback' => 'conditionalOperatorTimeNotEqual',
926
    'form callback' => 'webform_conditional_form_time',
927
  );
928
  $operators['time']['before'] = array(
929
    'label' => t('is before'),
930
    'comparison callback' => 'webform_conditional_operator_datetime_before',
931
    'comparison prepare js' => 'webform_conditional_prepare_time_js',
932
    'js comparison callback' => 'conditionalOperatorTimeBefore',
933
    'form callback' => 'webform_conditional_form_time',
934
  );
935
  $operators['time']['before_equal'] = array(
936
    'label' => t('is at or before'),
937
    'comparison callback' => 'webform_conditional_operator_datetime_before_equal',
938
    'comparison prepare js' => 'webform_conditional_prepare_time_js',
939
    'js comparison callback' => 'conditionalOperatorTimeBeforeEqual',
940
    'form callback' => 'webform_conditional_form_time',
941
  );
942
  $operators['time']['after'] = array(
943
    'label' => t('is after'),
944
    'comparison callback' => 'webform_conditional_operator_datetime_after',
945
    'comparison prepare js' => 'webform_conditional_prepare_time_js',
946
    'js comparison callback' => 'conditionalOperatorTimeAfter',
947
    'form callback' => 'webform_conditional_form_time',
948
  );
949
  $operators['time']['after_equal'] = array(
950
    'label' => t('is at or after'),
951
    'comparison callback' => 'webform_conditional_operator_datetime_after_equal',
952
    'comparison prepare js' => 'webform_conditional_prepare_time_js',
953
    'js comparison callback' => 'conditionalOperatorTimeAfterEqual',
954
    'form callback' => 'webform_conditional_form_time',
955
  );
956

    
957
  return $operators;
958
}
959

    
960
/**
961
 * Form callback for select-type conditional fields.
962
 *
963
 * Unlike other built-in conditional value forms, the form callback for select
964
 * types provides an array of forms, keyed by the $cid, which is the "source"
965
 * for the condition.
966
 */
967
function webform_conditional_form_select($node) {
968
  static $count = 0;
969
  $forms = array();
970
  webform_component_include('select');
971
  foreach ($node->webform['components'] as $cid => $component) {
972
    if (webform_component_property($component['type'], 'conditional_type') == 'select') {
973
      // TODO: Use a pluggable mechanism for retrieving select list values.
974
      $options = _webform_select_options($component);
975
      $element = array(
976
        '#type' => 'select',
977
        '#multiple' => FALSE,
978
        '#size' => NULL,
979
        '#attributes' => array(),
980
        '#id' => NULL,
981
        '#name' => 'webform-conditional-select-' . $cid . '-' . $count,
982
        '#options' => $options,
983
        '#parents' => array(),
984
      );
985
      $forms[$cid] = drupal_render($element);
986
    }
987
  }
988
  $count++;
989
  return $forms;
990
}
991

    
992
/**
993
 * Form callback for date conditional fields.
994
 */
995
function webform_conditional_form_date($node) {
996
  static $count = 0;
997
  $element = array(
998
    '#title' => NULL,
999
    '#title_display' => 'none',
1000
    '#size' => 24,
1001
    '#attributes' => array('placeholder' => t('@format or valid date', array('@format' => webform_date_format('short')))),
1002
    '#type' => 'textfield',
1003
    '#name' => 'webform-conditional-date-' . $count++,
1004
  );
1005
  return drupal_render($element);
1006
}
1007

    
1008
/**
1009
 * Form callback for time conditional fields.
1010
 */
1011
function webform_conditional_form_time($node) {
1012
  static $count = 0;
1013
  $element = array(
1014
    '#title' => NULL,
1015
    '#title_display' => 'none',
1016
    '#size' => 24,
1017
    '#attributes' => array('placeholder' => t('HH:MMam or valid time')),
1018
    '#type' => 'textfield',
1019
    '#name' => 'webform-conditional-time-' . $count++,
1020
  );
1021
  return drupal_render($element);
1022
}
1023

    
1024
/**
1025
 * Load a conditional setting from the database.
1026
 */
1027
function webform_conditional_load($rgid, $nid) {
1028
  $node = node_load($nid);
1029

    
1030
  $conditional = isset($node->webform['conditionals'][$rgid]) ? $node->webform['conditionals'][$rgid] : FALSE;
1031

    
1032
  return $conditional;
1033
}
1034

    
1035
/**
1036
 * Insert a conditional rule group into the database.
1037
 */
1038
function webform_conditional_insert($conditional) {
1039
  drupal_write_record('webform_conditional', $conditional);
1040
  foreach ($conditional['rules'] as $rid => $rule) {
1041
    $rule['nid'] = $conditional['nid'];
1042
    $rule['rgid'] = $conditional['rgid'];
1043
    $rule['rid'] = $rid;
1044
    drupal_write_record('webform_conditional_rules', $rule);
1045
  }
1046
  foreach ($conditional['actions'] as $aid => $action) {
1047
    $action['nid'] = $conditional['nid'];
1048
    $action['rgid'] = $conditional['rgid'];
1049
    $action['aid'] = $aid;
1050
    drupal_write_record('webform_conditional_actions', $action);
1051
  }
1052
}
1053

    
1054
/**
1055
 * Update a conditional setting in the database.
1056
 */
1057
function webform_conditional_update($node, $conditional) {
1058
  webform_conditional_delete($node, $conditional);
1059
  webform_conditional_insert($conditional);
1060
}
1061

    
1062
/**
1063
 * Delete a conditional rule group.
1064
 */
1065
function webform_conditional_delete($node, $conditional) {
1066
  db_delete('webform_conditional')
1067
    ->condition('nid', $node->nid)
1068
    ->condition('rgid', $conditional['rgid'])
1069
    ->execute();
1070
  db_delete('webform_conditional_rules')
1071
    ->condition('nid', $node->nid)
1072
    ->condition('rgid', $conditional['rgid'])
1073
    ->execute();
1074
  db_delete('webform_conditional_actions')
1075
    ->condition('nid', $node->nid)
1076
    ->condition('rgid', $conditional['rgid'])
1077
    ->execute();
1078
}
1079

    
1080
/**
1081
 * Loop through all the conditional settings and add needed JavaScript settings.
1082
 *
1083
 * We do a bit of optimization for JavaScript before adding to the page as
1084
 * settings. We remove unnecessary data structures and provide a "source map"
1085
 * so that JavaScript can quickly determine if it needs to check rules when a
1086
 * field on the page has been modified.
1087
 *
1088
 * @param object $node
1089
 *   The loaded node object, containing the webform.
1090
 * @param array $submission_data
1091
 *   The cid-indexed array of existing submission values to be included for
1092
 *   sources outside of the current page.
1093
 * @param integer $page_num
1094
 *   The number of the page for which javascript settings should be generated.
1095
 * @return array
1096
 *   Array of settings to be send to the browser as javascript settings.
1097
 */
1098
function webform_conditional_prepare_javascript($node, $submission_data, $page_num) {
1099
  $settings = array(
1100
    'ruleGroups' => array(),
1101
    'sourceMap' => array(),
1102
    'values' => array(),
1103
  );
1104
  $operators = webform_conditional_operators();
1105
  $conditionals = $node->webform['conditionals'];
1106
  $components = $node->webform['components'];
1107
  $topological_order = webform_get_conditional_sorter($node)->getOrder();
1108
  foreach ($topological_order[$page_num] as $conditional_spec) {
1109
    $conditional = $conditionals[$conditional_spec['rgid']];
1110
    $rgid_key = 'rgid_' . $conditional['rgid'];
1111
    // Assemble the main conditional group settings.
1112

    
1113
    $settings['ruleGroups'][$rgid_key] = array(
1114
      'andor' => $conditional['andor'],
1115
    );
1116
    foreach ($conditional['actions'] as $action) {
1117
      if ($action['target_type'] == 'component') {
1118
        $target_component = $components[$action['target']];
1119
        $target_parents = webform_component_parent_keys($node, $target_component);
1120
        $aid_key = 'aid_' . $action['aid'];
1121
        $action_settings = array(
1122
          'target' => 'webform-component--' . str_replace('_', '-', implode('--', $target_parents)),
1123
          'invert' => (int)$action['invert'],
1124
          'action' => $action['action'],
1125
          'argument' => $components[$action['target']]['type'] == 'markup' ? filter_xss_admin($action['argument']) : $action['argument'],
1126
        );
1127
        $settings['ruleGroups'][$rgid_key]['actions'][$aid_key] = $action_settings;
1128
      }
1129
    }
1130
    // Add on the list of rules to the conditional group.
1131
    foreach ($conditional['rules'] as $rule) {
1132
      if ($rule['source_type'] == 'component') {
1133
        $source_component = $components[$rule['source']];
1134
        $source_parents = webform_component_parent_keys($node, $source_component);
1135
        $source_id = 'webform-component--' . str_replace('_', '-', implode('--', $source_parents));
1136
        $rid_key = 'rid_' . $rule['rid'];
1137

    
1138
        // If this source has a value set, add that as a setting.
1139
        // NULL or array(NULL) should be sent as an empty array to simplify the jQuery.
1140
        if (isset($submission_data[$source_component['cid']])) {
1141
          $source_value = $submission_data[$source_component['cid']];
1142
          $source_value = is_array($source_value) ? $source_value : array($source_value);
1143
          $settings['values'][$source_id] = $source_value === array(NULL) ? array() : $source_value;
1144
        }
1145

    
1146
        $conditional_type = webform_component_property($source_component['type'], 'conditional_type');
1147
        $operator_info = $operators[$conditional_type][$rule['operator']];
1148
        $rule_settings = array(
1149
          'source' => $source_id,
1150
          'value' => $rule['value'],
1151
          'callback' => $operator_info['js comparison callback'],
1152
        );
1153
        if (isset($operator_info['comparison prepare js'])) {
1154
          $callback = $operator_info['comparison prepare js'];
1155
          $rule_settings['value'] = $callback($rule['value']);
1156
        }
1157
        $settings['ruleGroups'][$rgid_key]['rules'][$rid_key] = $rule_settings;
1158
        $settings['sourceMap'][$source_id][$rgid_key] = $rgid_key;
1159
      }
1160
    }
1161
  }
1162

    
1163
  return $settings;
1164
}
1165

    
1166
/**
1167
 * Determine whether a component type is capable of a given conditional action.
1168
 */
1169
function webform_conditional_action_able($component_type, $action) {
1170
  switch ($action) {
1171
    case 'show':
1172
      return TRUE;
1173
      // break;
1174
    case 'require':
1175
      return webform_component_feature($component_type, 'required');
1176
      // break;
1177
    default:
1178
      return webform_component_feature($component_type, "conditional_action_$action");
1179
      // break;
1180
  }
1181
}
1182

    
1183
/**
1184
 * Prepare a conditional value for adding as a JavaScript setting.
1185
 */
1186
function webform_conditional_prepare_date_js($rule_value) {
1187
  // Convert the time/date string to a UTC timestamp for comparison. Note that
1188
  // this means comparisons against immediate times (such as "now") may be
1189
  // slightly stale by the time the comparison executes. Timestamps are in
1190
  // milliseconds, as to match JavaScript's Date.toString() method.
1191
  $date = webform_strtodate('c', $rule_value, 'UTC');
1192
  return webform_strtotime($date);
1193
}
1194

    
1195
/**
1196
 * Prepare a conditional value for adding as a JavaScript setting.
1197
 */
1198
function webform_conditional_prepare_time_js($rule_value) {
1199
  $date = webform_conditional_prepare_date_js($rule_value);
1200
  $today = webform_strtodate('c', 'today', 'UTC');
1201
  $today = webform_strtotime($today);
1202
  return $date - $today;
1203
}
1204

    
1205
/**
1206
 * Conditional callback for string comparisons.
1207
 */
1208
function webform_conditional_operator_string_equal($input_values, $rule_value) {
1209
  foreach ($input_values as $value) {
1210
    // Checkbox values come in as 0 integers for unchecked boxes.
1211
    $value = ($value === 0) ? '' : $value;
1212
    if (strcasecmp($value, $rule_value) === 0) {
1213
      return TRUE;
1214
    }
1215
  }
1216
  return FALSE;
1217
}
1218

    
1219
/**
1220
 * Conditional callback for string comparisons.
1221
 */
1222
function webform_conditional_operator_string_not_equal($input_values, $rule_value) {
1223
  return !webform_conditional_operator_string_equal($input_values, $rule_value);
1224
}
1225

    
1226
/**
1227
 * Conditional callback for string comparisons.
1228
 */
1229
function webform_conditional_operator_string_contains($input_values, $rule_value) {
1230
  foreach ($input_values as $value) {
1231
    if (stripos($value, $rule_value) !== FALSE) {
1232
      return TRUE;
1233
    }
1234
  }
1235
  return FALSE;
1236
}
1237

    
1238
/**
1239
 * Conditional callback for string comparisons.
1240
 */
1241
function webform_conditional_operator_string_does_not_contain($input_values, $rule_value) {
1242
  return !webform_conditional_operator_string_contains($input_values, $rule_value);
1243
}
1244

    
1245
/**
1246
 * Conditional callback for string comparisons.
1247
 */
1248
function webform_conditional_operator_string_begins_with($input_values, $rule_value) {
1249
  foreach ($input_values as $value) {
1250
    if (stripos($value, $rule_value) === 0) {
1251
      return TRUE;
1252
    }
1253
  }
1254
  return FALSE;
1255
}
1256

    
1257
/**
1258
 * Conditional callback for string comparisons.
1259
 */
1260
function webform_conditional_operator_string_ends_with($input_values, $rule_value) {
1261
  foreach ($input_values as $value) {
1262
    if (strripos($value, $rule_value) === strlen($value) - strlen($rule_value)) {
1263
      return TRUE;
1264
    }
1265
  }
1266
  return FALSE;
1267
}
1268

    
1269
/**
1270
 * Conditional callback for checking for empty fields.
1271
 */
1272
function webform_conditional_operator_string_empty($input_values, $rule_value) {
1273
  $empty = TRUE;
1274
  foreach ($input_values as $value) {
1275
    if ($value !== '' && $value !== NULL && $value !== 0) {
1276
      $empty = FALSE;
1277
      break;
1278
    }
1279
  }
1280
  return $empty;
1281
}
1282

    
1283
/**
1284
 * Conditional callback for checking for empty fields.
1285
 */
1286
function webform_conditional_operator_string_not_empty($input_values, $rule_value) {
1287
  return !webform_conditional_operator_string_empty($input_values, $rule_value);
1288
}
1289

    
1290
/**
1291
 * Conditional callback for select comparisons.
1292
 */
1293
function webform_conditional_operator_select_less_than($input_values, $rule_value, $component) {
1294
  return empty($input_values) ? FALSE : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)) < 0;
1295
}
1296

    
1297
/**
1298
 * Conditional callback for select comparisons.
1299
 */
1300
function webform_conditional_operator_select_less_than_equal($input_values, $rule_value, $component) {
1301
  $comparison = empty($input_values) ? NULL : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE));
1302
  return $comparison < 0 || $comparison === 0;
1303
}
1304

    
1305
/**
1306
 * Conditional callback for select comparisons.
1307
 */
1308
function webform_conditional_operator_select_greater_than($input_values, $rule_value, $component) {
1309
  return empty($input_values) ? FALSE : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE)) > 0;
1310
}
1311

    
1312
/**
1313
 * Conditional callback for select comparisons.
1314
 */
1315
function webform_conditional_operator_select_greater_than_equal($input_values, $rule_value, $component) {
1316
  $comparison = empty($input_values) ? NULL : webform_compare_select($input_values[0], $rule_value, _webform_select_options($component, TRUE));
1317
  return $comparison > 0 || $comparison === 0;
1318
}
1319

    
1320
/**
1321
 * Conditional callback for numeric comparisons.
1322
 */
1323
function webform_conditional_operator_numeric_equal($input_values, $rule_value) {
1324
  return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) === 0;
1325
}
1326

    
1327
/**
1328
 * Conditional callback for numeric comparisons.
1329
 */
1330
function webform_conditional_operator_numeric_not_equal($input_values, $rule_value) {
1331
  return !webform_conditional_operator_numeric_equal($input_values, $rule_value);
1332
}
1333

    
1334
/**
1335
 * Conditional callback for numeric comparisons.
1336
 */
1337
function webform_conditional_operator_numeric_less_than($input_values, $rule_value) {
1338
  return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) < 0;
1339
}
1340

    
1341
/**
1342
 * Conditional callback for numeric comparisons.
1343
 */
1344
function webform_conditional_operator_numeric_less_than_equal($input_values, $rule_value) {
1345
  $comparison = empty($input_values) ? NULL : webform_compare_floats($input_values[0], $rule_value);
1346
  return $comparison < 0 || $comparison === 0;
1347
}
1348

    
1349
/**
1350
 * Conditional callback for numeric comparisons.
1351
 */
1352
function webform_conditional_operator_numeric_greater_than($input_values, $rule_value) {
1353
  return empty($input_values) ? FALSE : webform_compare_floats($input_values[0], $rule_value) > 0;
1354
}
1355

    
1356
/**
1357
 * Conditional callback for numeric comparisons.
1358
 */
1359
function webform_conditional_operator_numeric_greater_than_equal($input_values, $rule_value) {
1360
  $comparison = empty($input_values) ? NULL : webform_compare_floats($input_values[0], $rule_value);
1361
  return $comparison > 0 || $comparison === 0;
1362
}
1363

    
1364
/**
1365
 * Conditional callback for date and time comparisons.
1366
 */
1367
function webform_conditional_operator_datetime_equal($input_values, $rule_value) {
1368
  $input_values = webform_conditional_value_datetime($input_values);
1369
  return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) === webform_strtotime($rule_value);
1370
}
1371

    
1372
/**
1373
 * Conditional callback for date and time comparisons.
1374
 */
1375
function webform_conditional_operator_datetime_not_equal($input_values, $rule_value) {
1376
  return !webform_conditional_operator_datetime_equal($input_values, $rule_value);
1377
}
1378

    
1379
/**
1380
 * Conditional callback for date and time comparisons.
1381
 */
1382
function webform_conditional_operator_datetime_after($input_values, $rule_value) {
1383
  $input_values = webform_conditional_value_datetime($input_values);
1384
  return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) > webform_strtotime($rule_value);
1385
}
1386

    
1387
/**
1388
 * Conditional callback for date and time comparisons.
1389
 */
1390
function webform_conditional_operator_datetime_after_equal($input_values, $rule_value) {
1391
  return webform_conditional_operator_datetime_after($input_values, $rule_value) ||
1392
         webform_conditional_operator_datetime_equal($input_values, $rule_value);
1393
}
1394

    
1395
/**
1396
 * Conditional callback for date and time comparisons.
1397
 */
1398
function webform_conditional_operator_datetime_before($input_values, $rule_value) {
1399
  $input_values = webform_conditional_value_datetime($input_values);
1400
  return empty($input_values) ? FALSE : webform_strtotime($input_values[0]) < webform_strtotime($rule_value);
1401
}
1402

    
1403
/**
1404
 * Conditional callback for date and time comparisons.
1405
 */
1406
function webform_conditional_operator_datetime_before_equal($input_values, $rule_value) {
1407
  return webform_conditional_operator_datetime_before($input_values, $rule_value) ||
1408
         webform_conditional_operator_datetime_equal($input_values, $rule_value);
1409
}
1410

    
1411
/**
1412
 * Utility function to convert incoming time and dates into strings.
1413
 */
1414
function webform_conditional_value_datetime($input_values) {
1415
  // Convert times into a string.
1416
  $input_values = isset($input_values['hour']) ? array(webform_date_string(webform_time_convert($input_values, '24-hour'), 'time')) : $input_values;
1417
  // Convert dates into a string.
1418
  $input_values = isset($input_values['month']) ? array(webform_date_string($input_values, 'date')) : $input_values;
1419
  return $input_values;
1420
}
1421

    
1422
/**
1423
 * Utility function to compare values of a select component.
1424
 * @param string $a
1425
 *   First select option key to compare
1426
 * @param string $b
1427
 *   Second select option key to compare
1428
 * @param array $options
1429
 *   Associative array where the $a and $b are within the keys
1430
 * @return integer based upon position of $a and $b in $options
1431
 *   -N if $a above (<) $b
1432
 *   0 if $a = $b
1433
 *   +N if $a is below (>) $b
1434
 */
1435
function webform_compare_select($a, $b, $options) {
1436
  // Select keys that are integer-like strings are numberic indices in PHP.
1437
  // Convert the array keys to an array of strings.
1438
  $options_array = array_map(function($i) {return (string) $i; }, array_keys($options));
1439
  $a_position = array_search($a, $options_array, TRUE);
1440
  $b_position = array_search($b, $options_array, TRUE);
1441
  return ($a_position === FALSE || $a_position === FALSE) ? NULL : $a_position - $b_position;
1442

    
1443
}