Projet

Général

Profil

Paste
Télécharger (40,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform_validation / webform_validation.validators.inc @ 8916d7ec

1
<?php
2

    
3
/**
4
 * @file
5
 * Provides validation functionality and hooks.
6
 */
7

    
8
/**
9
 * Implements hook_webform_validation_validators().
10
 *
11
 * This function returns an array of validators, in the validator key => options
12
 * array form. Possible options:
13
 * - name (required): name of the validator.
14
 * - component_types (required): defines which component types can be validated
15
 *   by this validator. Specify 'all' to allow all types.
16
 * - negatable (optional): define whether the rule can be negated, meaning it
17
 *   will validate the inverse of the rule.
18
 * - custom_error (optional): define whether a user can specify a custom error
19
 *   message upon creating the validation rule.
20
 * - custom_data (optional): define whether custom data can be added to the
21
 *   validation rule.
22
 * - min_components (optional): define the minimum number of components to be
23
 *   selected for creating a validation rule.
24
 * - max_components (optional): define the maximum number of components to be
25
 *   selected for creating a validation rule.
26
 * - description (optional): provide a descriptive explanation about the
27
 *   validator.
28
 */
29
function webform_validation_webform_validation_validators() {
30
  $validators = array(
31
    'numeric' => array(
32
      'name' => t('Numeric values'),
33
      'component_types' => array(
34
        'hidden',
35
        'number',
36
        'textfield',
37
      ),
38
      'custom_data' => array(
39
        'label' => t('Numeric validation range'),
40
        'description' => t('Optionally specify the minimum-maximum range to validate the user-entered numeric value against. Usage:') . theme('table', array(
41
          'header' => array(t('Value'), t('Meaning')),
42
          'rows' => array(
43
            array(t('(empty)'), t('No value validation')),
44
            array('100', t('Greater than or equal to 100')),
45
            array('|100', t('Less than or equal to 100 (including negative numbers)')),
46
            array('0|100', t('Greater than or equal to 0 &amp; less than or equal to 100')),
47
            array('10|100', t('Greater than or equal to 10 &amp; less than or equal to 100')),
48
            array('-100|-10', t('Greater than or equal to -100 &amp; less than or equal to -10')),
49
          ),
50
        )),
51
        'validate_regex' => '/^((-?[\d.]+)?\|)?(-?[\d.]+)$/',
52
        'required' => FALSE,
53
      ),
54
      'description' => t('Verifies that user-entered values are numeric, with the option to specify min and / or max values.'),
55
    ),
56
    'min_length' => array(
57
      'name' => t('Minimum length'),
58
      'component_types' => array(
59
        'email',
60
        'hidden',
61
        'number',
62
        'textarea',
63
        'textfield',
64
      ),
65
      'custom_data' => array(
66
        'label' => t('Minimum number of characters'),
67
        'description' => t('Specify the minimum number of characters that have to be entered to pass validation.'),
68
        'validate_regex' => '/^\d+$/',
69
      ),
70
      'description' => t('Verifies that a user-entered value contains at least the specified number of characters.'),
71
    ),
72
    'max_length' => array(
73
      'name' => t('Maximum length'),
74
      'component_types' => array(
75
        'email',
76
        'hidden',
77
        'number',
78
        'textarea',
79
        'textfield',
80
      ),
81
      'custom_data' => array(
82
        'label' => t('Maximum number of characters'),
83
        'description' => t('Specify the maximum number of characters that can be entered to pass validation.'),
84
        'validate_regex' => '/^\d+$/',
85
      ),
86
      'description' => t('Verifies that a user-entered value contains at most the specified number of characters.'),
87
    ),
88
    'min_words' => array(
89
      'name' => t('Minimum number of words'),
90
      'component_types' => array(
91
        'hidden',
92
        'html_textarea',
93
        'textarea',
94
        'textfield',
95
      ),
96
      'custom_data' => array(
97
        'label' => t('Minimum number of words'),
98
        'description' => t('Specify the minimum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.'),
99
        'validate_regex' => '/^\d+$/',
100
      ),
101
      'description' => t('Verifies that a user-entered value contains at least the specified number of words.'),
102
    ),
103
    'max_words' => array(
104
      'name' => t('Maximum number of words'),
105
      'component_types' => array(
106
        'hidden',
107
        'html_textarea',
108
        'textarea',
109
        'textfield',
110
      ),
111
      'custom_data' => array(
112
        'label' => t('Maximum number of words'),
113
        'description' => t('Specify the maximum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.'),
114
        'validate_regex' => '/^\d+$/',
115
      ),
116
      'description' => t('Verifies that a user-entered value contains at most the specified number of words.'),
117
    ),
118
    // Only available in Webform 4; removed below if not.
119
    'sum' => array(
120
      'name' => t('Adds up to'),
121
      'component_types' => array(
122
        'number',
123
      ),
124
      'custom_data' => array(
125
        'label' => t('Number the fields add up to'),
126
        'description' => t('Specify the number and the type of comparison. For example:') . theme('table', array(
127
          'header' => array(t('Value'), t('Meaning')),
128
          'rows' => array(
129
            array('=3', t('The components must add up to exactly 3.')),
130
            array('>10', t('The components must add up to greater than 10.')),
131
            array('>=10', t('The components must add up to greater than or equal to 10.')),
132
            array('<20', t('The components must add up to less than 20.')),
133
            array('<=20', t('The components must add up to less than or equal to 20.')),
134
          ),
135
        )),
136
        'validate_regex' => '/^(=|>|>=|<|<=)(-?[\d.]+)$/',
137
      ),
138
      'description' => t('Require the values of the selected fields to add up to exactly, greater than or equal to, or less than or equal to a specified number.'),
139
    ),
140
    'equal' => array(
141
      'name' => t('Equal values'),
142
      'component_types' => array(
143
        'date',
144
        'email',
145
        'hidden',
146
        'number',
147
        'select',
148
        'textarea',
149
        'textfield',
150
        'time',
151
        'boolean',
152
      ),
153
      'min_components' => 2,
154
      'description' => t('Verifies that all specified components contain equal values. If all components are of type email, they will get case-insensitive comparison.'),
155
    ),
156
    'comparison' => array(
157
      'name' => t('Compare two values'),
158
      'component_types' => array(
159
        'date',
160
        'email',
161
        'hidden',
162
        'number',
163
        'select',
164
        'textarea',
165
        'textfield',
166
        'time',
167
      ),
168
      'custom_error' => TRUE,
169
      'custom_data' => array(
170
        'label' => t('Comparison operator'),
171
        'description' => t('Specify the comparison operator you want to use. Must be one of: >, >=, <, <=. The validator will compare the first component with the second using this operator. If the components are of type email, they will get case-insensitive comparison.'),
172
        'validate_regex' => '/^(>|>=|<|<=)$/',
173
      ),
174
      'min_components' => 2,
175
      'max_components' => 2,
176
      'description' => t('Compare two values for greater than (>), less than (<), greater than or equal to (>=), or less than or equal to (<=).'),
177
    ),
178
    'unique' => array(
179
      'name' => t('Unique values'),
180
      'component_types' => array(
181
        'date',
182
        'email',
183
        'hidden',
184
        'number',
185
        'select',
186
        'textarea',
187
        'textfield',
188
        'time',
189
        'boolean',
190
      ),
191
      'min_components' => 2,
192
      'description' => t('Verifies that none of the specified components contain the same value as another selected component in this submission. (To check that values are unique between submissions, use the unique validation option on the "Edit component" page for that component.) If all components are of type email, they will get case-insensitive comparison.'),
193
    ),
194
    'specific_value' => array(
195
      'name' => t('Specific value(s)'),
196
      'negatable' => TRUE,
197
      'component_types' => array(
198
        'email',
199
        'hidden',
200
        'number',
201
        'select',
202
        'textarea',
203
        'textfield',
204
        'boolean',
205
      ),
206
      'custom_error' => TRUE,
207
      'custom_data' => array(
208
        'label' => t('(Key) value'),
209
        'description' => t('Specify the specific value(s) you want the component to contain. Separate multiple options by a comma. For components that have keys, use the key value instead.'),
210
      ),
211
      'description' => t('Verifies that the value of the specified component is from a list of allowed values.'),
212
    ),
213
    'default_value' => array(
214
      'name' => t('Default value'),
215
      'negatable' => TRUE,
216
      'component_types' => array(
217
        'email',
218
        'hidden',
219
        'number',
220
        'select',
221
        'textarea',
222
        'textfield',
223
        'boolean',
224
      ),
225
      'custom_error' => TRUE,
226
      'description' => t('Verifies that the user-entered value is the default value for that component. Negate if you want not the default value.'),
227
    ),
228
    'someofseveral' => array(
229
      'name' => t('Some of several'),
230
      'component_types' => array(
231
        'date',
232
        'email',
233
        'file',
234
        'number',
235
        'select',
236
        'textarea',
237
        'textfield',
238
        'time',
239
        'boolean',
240
      ),
241
      'custom_data' => array(
242
        'label' => t('Number to be completed'),
243
        'description' => t('Specify the number that must be completed and the type of comparison. For example:') . theme('table', array(
244
          'header' => array(t('Value'), t('Meaning')),
245
          'rows' => array(
246
            array('>=1', t('The user must complete <b>at least</b> 1 of the selected components.')),
247
            array('=3', t('The user must complete <b>exactly</b> 3 of the selected components.')),
248
            array('<=2', t('The user must complete <b>at most</b> 2 of the selected components.')),
249
          ),
250
        )),
251
        'validate_regex' => '/^[><]?=\d+$/',
252
      ),
253
      'min_components' => 2,
254
      'description' => t('Requires the user to complete some number of components out of a group of components. For example, complete at least 2 out of 3, complete at most 4 out of 6, or complete exactly 3 our of 4.'),
255
    ),
256
    'select_min' => array(
257
      'name' => t('Minimum number of selections required'),
258
      'component_types' => array(
259
        'select',
260
      ),
261
      'custom_data' => array(
262
        'label' => t('Minimum number of selections'),
263
        'description' => t('Specify the minimum number of options a user should select.'),
264
        'validate_regex' => '/^\d+$/',
265
      ),
266
      'description' => t('Forces the user to select at least a defined number of options from the specified webform components.'),
267
    ),
268
    'select_max' => array(
269
      'name' => t('Maximum number of selections allowed'),
270
      'component_types' => array(
271
        'select',
272
      ),
273
      'custom_data' => array(
274
        'label' => t('Maximum number of selections'),
275
        'description' => t('Specify the maximum number of options a user can select.'),
276
        'validate_regex' => '/^\d+$/',
277
      ),
278
      'description' => t('Forces the user to select at most a defined number of options from the specified webform components.'),
279
    ),
280
    'select_exact' => array(
281
      'name' => t('Exact number of selections required'),
282
      'negatable' => TRUE,
283
      'component_types' => array(
284
        'select',
285
      ),
286
      'custom_data' => array(
287
        'label' => t('Number of selections'),
288
        'description' => t('Specify how many options a user can select.'),
289
        'validate_regex' => '/^\d+$/',
290
      ),
291
      'description' => t('Forces the user to select exactly the defined number of options from the specified webform components.'),
292
    ),
293
    'plain_text' => array(
294
      'name' => t('Plain text (disallow tags)'),
295
      'negatable' => TRUE,
296
      'component_types' => array(
297
        'email',
298
        'hidden',
299
        'textarea',
300
        'textfield',
301
      ),
302
      'description' => t("Verifies that user-entered data doesn't contain HTML tags."),
303
    ),
304
    'starts_with' => array(
305
      'name' => t('Starts with'),
306
      'negatable' => TRUE,
307
      'component_types' => array(
308
        'email',
309
        'hidden',
310
        'number',
311
        'textarea',
312
        'textfield',
313
      ),
314
      'custom_data' => array(
315
        'label' => t('Starts with'),
316
        'description' => t('Enter the text that this field must start with.'),
317
      ),
318
      'description' => t('Verifies that user-entered data starts with a given string.'),
319
    ),
320
    'ends_with' => array(
321
      'name' => t('Ends with'),
322
      'negatable' => TRUE,
323
      'component_types' => array(
324
        'email',
325
        'hidden',
326
        'number',
327
        'textarea',
328
        'textfield',
329
      ),
330
      'custom_data' => array(
331
        'label' => t('Ends with'),
332
        'description' => t('Enter the text that this field must end with.'),
333
      ),
334
      'description' => t('Verifies that user-entered data ends with a given string.'),
335
    ),
336
    'pattern' => array(
337
      'name' => t('Pattern'),
338
      'component_types' => array(
339
        'hidden',
340
        'textarea',
341
        'textfield',
342
      ),
343
      'custom_error' => TRUE,
344
      'custom_data' => array(
345
        'label' => t('Pattern'),
346
        'description' => t('Specify a pattern where:') . theme('table', array(
347
          'header' => array(t('Value'), t('Meaning')),
348
          'rows' => array(
349
            array('@', t('Any letter A-Z.')),
350
            array('#', t('Any numeral 0-9.')),
351
            array('|', t('Separates two or more acceptable patterns.')),
352
            array('', t('Any other character must appear in its exact position.')),
353
          ),
354
        )) . t('Examples:') . theme('table', array(
355
          'header' => array(t('Value'), t('Meaning')),
356
          'rows' => array(
357
            array('(###) ###-####', t('North American phone number.')),
358
            array('D-25##', t('D-2500 series model numbers.')),
359
            array('@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA', t('UK Postal Code.')),
360
          ),
361
        )),
362
      ),
363
      'description' => t('Verifies that a user-entered value follows to a specified pattern.'),
364
    ),
365
    'regex' => array(
366
      'name' => t('Regular expression, case-sensitive'),
367
      'negatable' => TRUE,
368
      'component_types' => array(
369
        'email',
370
        'hidden',
371
        'number',
372
        'textarea',
373
        'textfield',
374
      ),
375
      'custom_error' => TRUE,
376
      'custom_data' => array(
377
        'label' => t('Regex code (case-sensitive)'),
378
        'description' => t('Specify regex code to validate the user input against. Do not include delimiters such as /.'),
379
      ),
380
      'description' => t('Validates user-entered text against a case-sensitive regular expression.'),
381
    ),
382
    'regexi' => array(
383
      'name' => t('Regular expression, case-insensitive'),
384
      'negatable' => TRUE,
385
      'component_types' => array(
386
        'email',
387
        'hidden',
388
        'number',
389
        'textarea',
390
        'textfield',
391
      ),
392
      'custom_error' => TRUE,
393
      'custom_data' => array(
394
        'label' => t('Regex code (case-insensitive)'),
395
        'description' => t('Specify regex code to validate the user input against. Do not include delimiters such as /.'),
396
      ),
397
      'description' => t('Validates user-entered text against a case-insensitive regular expression.'),
398
    ),
399
    'must_be_empty' => array(
400
      'name' => t('Must be empty'),
401
      'component_types' => array(
402
        'hidden',
403
        'number',
404
        'textarea',
405
        'textfield',
406
      ),
407
      'description' => t('Verifies that a specified textfield remains empty. Recommended use case: used as an anti-spam measure by hiding the element with CSS.'),
408
    ),
409
    'blacklist' => array(
410
      'name' => t('Words blacklist'),
411
      'negatable' => TRUE,
412
      'component_types' => array(
413
        'email',
414
        'hidden',
415
        'textarea',
416
        'textfield',
417
      ),
418
      'custom_error' => TRUE,
419
      'custom_data' => array(
420
        'label' => t('Blacklisted words'),
421
        'description' => t('Specify illegal words, seperated by commas.'),
422
      ),
423
      'description' => t("Validates that user-entered data doesn't contain any of the specified illegal words."),
424
    ),
425
    'username' => array(
426
      'name' => t('Must match a username'),
427
      'negatable' => TRUE,
428
      'component_types' => array(
429
        'hidden',
430
        'textfield',
431
      ),
432
      'description' => t('Validates that user-entered data matches a username.'),
433
    ),
434
    'valid_url' => array(
435
      'name' => t('Valid URL'),
436
      'negatable' => TRUE,
437
      'component_types' => array(
438
        'hidden',
439
        'textfield',
440
      ),
441
      'description' => t('Validates that the user-entered data is a valid URL.'),
442
    ),
443
  );
444

    
445
  // Only available in Webform 4.
446
  module_load_include('inc', 'webform', 'components/number');
447
  if (!function_exists('webform_compare_floats')) {
448
    unset($validators['sum']);
449
  }
450

    
451
  if (module_exists('email_verify')) {
452
    $validators['email_verify'] = array(
453
      'name' => t('Email Verify'),
454
      'component_types' => array(
455
        'email',
456
      ),
457
      'description' => t('Verifies that an email address actually exists using the <a href="https://drupal.org/project/email_verify">Email Verification module</a>.'),
458
    );
459
  }
460

    
461
  return $validators;
462
}
463

    
464
/**
465
 * Implements hook_webform_validation_validate().
466
 */
467
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
468
  if (!$items) {
469
    return NULL;
470
  }
471

    
472
  // For certain validators, if all components to be compared are email
473
  // components, make the values lower-case to avoid case-sensitive comparison.
474
  if (in_array($validator_name, array('equal', 'comparison', 'unique'))) {
475
    $all_email = TRUE;
476
    foreach ($rule['components'] as $component) {
477
      if ($component['type'] !== 'email') {
478
        $all_email = FALSE;
479
        break;
480
      }
481
    }
482
    if ($all_email) {
483
      $items = array_map('strtolower', $items);
484
    }
485
  }
486

    
487
  // Some components, such as multiple select, send their values as arrays,
488
  // others don't. Convert all to arrays and write the rules to handle them that
489
  // way. Remove empty values.
490
  foreach ($items as $key => $val) {
491
    $new_values = array();
492
    foreach ((array) $val as $val_key => $val_val) {
493
      if ((string) $val_val !== '') {
494
        $new_values[$val_key] = $val_val;
495
      }
496
    }
497
    $items[$key] = $new_values;
498
  }
499
  // Ensure later calls to key() get the first item.
500
  reset($items);
501

    
502
  $errors = array();
503
  switch ($validator_name) {
504
    case 'numeric':
505
      $num_range = _webform_numeric_check_data($rule['data']);
506
      foreach ($items as $key => $val) {
507
        foreach ($val as $subval) {
508
          // First check if the value is numeric.
509
          if (is_numeric($subval)) {
510
            $subval = (float) $subval;
511
          }
512
          else {
513
            $errors[$key] = t('%item is not numeric.', array('%item' => $components[$key]['name']));
514
            continue;
515
          }
516

    
517
          // Now validate the entered numeric value against the validator range
518
          // settings, if appropriate.
519
          // a. validate min & max.
520
          if (isset($num_range['min']) && isset($num_range['max'])) {
521
            // Validate the min - max range.
522
            if ($subval < $num_range['min'] || $subval > $num_range['max']) {
523
              $options = array(
524
                '%item' => $components[$key]['name'],
525
                '%range' => str_replace('|', ' - ', $rule['data']),
526
              );
527
              $errors[$key] = t('%item is not within the allowed range %range.', $options);
528
            }
529
          }
530
          else {
531
            // b. validate min.
532
            if (isset($num_range['min'])) {
533
              if ($subval < $num_range['min']) {
534
                $errors[$key] = t('%item should be greater than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['min']));
535
              }
536
            }
537
            // c. validate max.
538
            if (isset($num_range['max'])) {
539
              if ($subval > $num_range['max']) {
540
                $errors[$key] = t('%item should be less than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['max']));
541
              }
542
            }
543
          }
544
        }
545
      }
546
      return $errors;
547

    
548
    case 'min_length':
549
      $min_length = $rule['data'];
550
      foreach ($items as $key => $val) {
551
        foreach ($val as $subval) {
552
          if (drupal_strlen($subval) < $min_length) {
553
            $errors[$key] = t('%item should be at least %num characters long.', array('%item' => $components[$key]['name'], '%num' => $min_length));
554
          }
555
        }
556
      }
557
      return $errors;
558

    
559
    case 'max_length':
560
      $max_length = $rule['data'];
561
      foreach ($items as $key => $val) {
562
        foreach ($val as $subval) {
563
          if (drupal_strlen($subval) > $max_length) {
564
            $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
565
          }
566
        }
567
      }
568
      return $errors;
569

    
570
    case 'min_words':
571
      $min_words = $rule['data'];
572
      foreach ($items as $key => $val) {
573
        foreach ($val as $subval) {
574
          if (_webform_validation_count_words($subval) < $min_words) {
575
            $error = format_plural($min_words, '%item should be at least 1 word long.', '%item should be at least @count words long.', array('%item' => $components[$key]['name']));
576
            $errors[$key] = $error;
577
          }
578
        }
579
      }
580
      return $errors;
581

    
582
    case 'max_words':
583
      $max_words = $rule['data'];
584
      foreach ($items as $key => $val) {
585
        foreach ($val as $subval) {
586
          if (_webform_validation_count_words($subval) > $max_words) {
587
            $error = format_plural($max_words, '%item should be at most 1 word long.', '%item should be at most @count words long.', array('%item' => $components[$key]['name']));
588
            $errors[$key] = $error;
589
          }
590
        }
591
      }
592
      return $errors;
593

    
594
    case 'sum':
595
      // Get the numbers to sum.
596
      $sum = array();
597
      foreach ($items as $item) {
598
        if (isset($item[0])) {
599
          $sum[] = $item[0];
600
        }
601
      }
602
      // If none of the components are completed, don't run this validator.
603
      if (!count($sum)) {
604
        return array();
605
      }
606
      $sum = array_sum($sum);
607

    
608
      // Number being compared with.
609
      $compare_number = (float) preg_replace('/^[^0-9]+/', '', $rule['data']);
610

    
611
      // Parse the comparision operator and do comparison.
612
      module_load_include('inc', 'webform', 'components/number');
613
      $error = FALSE;
614
      if (substr($rule['data'], 0, 2) === '<=') {
615
        if (!(webform_compare_floats($sum, $compare_number) <= 0)) {
616
          $error = t('less than or equal to');
617
        }
618
      }
619
      elseif (substr($rule['data'], 0, 1) === '<') {
620
        if (!(webform_compare_floats($sum, $compare_number) < 0)) {
621
          $error = t('less than');
622
        }
623
      }
624
      elseif (substr($rule['data'], 0, 2) === '>=') {
625
        if (!(webform_compare_floats($sum, $compare_number) >= 0)) {
626
          $error = t('greater than or equal to');
627
        }
628
      }
629
      elseif (substr($rule['data'], 0, 1) === '>') {
630
        if (!(webform_compare_floats($sum, $compare_number) > 0)) {
631
          $error = t('greater than');
632
        }
633
      }
634
      else {
635
        if (!(webform_compare_floats($sum, $compare_number) === 0)) {
636
          $error = t('exactly');
637
        }
638
      }
639
      // Set error if needed.
640
      if ($error) {
641
        $keys = array_keys($items);
642
        $errors[$keys[0]] = t('These items must add up to %verb %compare_number:', array('%verb' => $error, '%compare_number' => $compare_number)) . theme('item_list', array('items' => _webform_validation_get_names_of_rule_components($rule)));
643
      }
644
      return $errors;
645

    
646
    case 'equal':
647
      $first_entry_key = key($items);
648
      $first_entry = array_shift($items);
649
      foreach ($items as $key => $val) {
650
        if ($val !== $first_entry) {
651
          $errors[$key] = t('%item_checked does not match %item_first.', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
652
        }
653
      }
654
      return $errors;
655

    
656
    case 'comparison':
657
      foreach (array(1, 2) as $count) {
658
        $entry[$count]['key'] = key($items);
659
        $value = array_shift($items);
660
        if ($components[$entry[$count]['key']]['type'] === 'date') {
661
          if (!empty($value) && checkdate((int) $value['month'], (int) $value['day'], (int) $value['year'])) {
662
            $entry[$count]['value'] = date('Y-m-d', mktime(0, 0, 0, (int) $value['month'], (int) $value['day'], (int) $value['year']));
663
          }
664
          else {
665
            $entry[$count]['value'] = NULL;
666
          }
667
        }
668
        elseif ($components[$entry[$count]['key']]['type'] === 'time') {
669
          if (isset($value['hour']) && isset($value['minute'])) {
670
            $time = $value['hour'] . ':' . str_pad($value['minute'], 2, '0', STR_PAD_LEFT);
671
            if (!empty($value['ampm'])) {
672
              $time .= ' ' . $value['ampm'];
673
            }
674
            $time = strtotime($time);
675
          }
676
          else {
677
            $time = NULL;
678
          }
679
          if ($time) {
680
            $entry[$count]['value'] = date('H:i', $time);
681
          }
682
          else {
683
            $entry[$count]['value'] = NULL;
684
          }
685
        }
686
        else {
687
          $entry[$count]['value'] = _webform_validation_flatten_array($value);
688
        }
689
      }
690

    
691
      // Don't validate if either are empty.
692
      if ((string) $entry[1]['value'] === '' || (string) $entry[2]['value'] === '') {
693
        return array();
694
      }
695

    
696
      $error = FALSE;
697
      switch ($rule['data']) {
698
        case '>':
699
          if (!($entry[1]['value'] > $entry[2]['value'])) {
700
            $error = TRUE;
701
          }
702
          break;
703

    
704
        case '>=':
705
          if (!($entry[1]['value'] >= $entry[2]['value'])) {
706
            $error = TRUE;
707
          }
708
          break;
709

    
710
        case '<':
711
          if (!($entry[1]['value'] < $entry[2]['value'])) {
712
            $error = TRUE;
713
          }
714
          break;
715

    
716
        case '<=':
717
          if (!($entry[1]['value'] <= $entry[2]['value'])) {
718
            $error = TRUE;
719
          }
720
          break;
721
      }
722

    
723
      if ($error) {
724
        $errors[$entry[2]['key']] = _webform_validation_i18n_error_message($rule);
725
      }
726
      return $errors;
727

    
728
    case 'unique':
729
      foreach ($items as $key => $val) {
730
        $items[$key] = _webform_validation_flatten_array($val);
731
      }
732
      // Now we count how many times each value appears, and find out which
733
      // values appear more than once.
734
      $items_count = array_count_values(array_map('drupal_strtolower', array_map('trim', $items)));
735
      $doubles = array_filter($items_count, function ($x) {
736
        return $x > 1;
737
      });
738
      foreach ($items as $key => $val) {
739
        if (in_array(drupal_strtolower($val), array_keys($doubles))) {
740
          $errors[$key] = t('The value of %item is not unique.', array('%item' => $components[$key]['name']));
741
        }
742
      }
743
      return $errors;
744

    
745
    case 'specific_value':
746
      $specific_values = explode(',', $rule['data']);
747
      $specific_values = array_map('trim', $specific_values);
748
      foreach ($items as $key => $val) {
749
        // Selects: Fail if at least one checked and at least one not in the
750
        // allowed values.
751
        $val = array_filter($val);
752
        $test = count($val) && count(array_diff($val, $specific_values));
753
        _webform_validation_test($errors, $key, $rule, $test);
754
      }
755
      return $errors;
756

    
757
    case 'default_value':
758
      foreach ($items as $key => $val) {
759
        $val = _webform_validation_flatten_array($val);
760
        $test = $val != $components[$key]['value'];
761
        _webform_validation_test($errors, $key, $rule, $test);
762
      }
763
      return $errors;
764

    
765
    case 'someofseveral':
766
      // Determine the number of components completed.
767
      foreach ($items as $key => $val) {
768
        $items[$key] = _webform_validation_flatten_array($val);
769
        $items[$key] = (bool) strlen($items[$key]);
770
      }
771
      $number_completed = count(array_filter($items));
772

    
773
      // Number being compared with.
774
      $compare_number = (int) preg_replace('/[^0-9]+/', '', $rule['data']);
775
      if ($compare_number < 1) {
776
        $compare_number = 1;
777
      }
778
      elseif ($compare_number > count($rule['components'])) {
779
        $compare_number = count($rule['components']);
780
      }
781

    
782
      // Parse the comparision operator and do comparison.
783
      $error = FALSE;
784
      if (substr($rule['data'], 0, 1) === '=') {
785
        if (!($number_completed === $compare_number)) {
786
          $error = t('exactly');
787
        }
788
      }
789
      elseif (substr($rule['data'], 0, 2) === '<=') {
790
        if (!($number_completed <= $compare_number)) {
791
          $error = t('at most');
792
        }
793
      }
794
      else {
795
        if (!($number_completed >= $compare_number)) {
796
          $error = t('at least');
797
        }
798
      }
799

    
800
      // Set error if needed.
801
      if ($error) {
802
        $keys = array_keys($items);
803
        $errors[$keys[0]] = t('You must complete %verb %compare_number of these items:', array('%verb' => $error, '%compare_number' => $compare_number)) . theme('item_list', array('items' => _webform_validation_get_names_of_rule_components($rule)));
804
      }
805
      return $errors;
806

    
807
    case 'select_min':
808
      $min_selections = $rule['data'];
809
      foreach ($items as $key => $val) {
810
        if (count(array_filter($val, '_webform_validation_check_false')) < $min_selections) {
811
          $errors[$key] = t('Please select at least %num options for %item.', array('%num' => $min_selections, '%item' => $components[$key]['name']));
812
        }
813
      }
814
      return $errors;
815

    
816
    case 'select_max':
817
      $max_selections = $rule['data'];
818
      foreach ($items as $key => $val) {
819
        if (count(array_filter($val, '_webform_validation_check_false')) > $max_selections) {
820
          $errors[$key] = t('Please select maximum %num options for %item.', array('%num' => $max_selections, '%item' => $components[$key]['name']));
821
        }
822
      }
823
      return $errors;
824

    
825
    case 'select_exact':
826
      $allowed_selections = $rule['data'];
827
      foreach ($items as $key => $val) {
828
        $error_strings = array(
829
          'regular' => 'Please select %num options for %item.',
830
          'negated' => 'Please do not select %num options for %item.',
831
        );
832
        $error_vars = array('%num' => $allowed_selections, '%item' => $components[$key]['name']);
833
        $test = count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections;
834
        _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
835
      }
836
      return $errors;
837

    
838
    case 'plain_text':
839
      foreach ($items as $key => $val) {
840
        $error_strings = array(
841
          'regular' => '%item only allows the use of plain text.',
842
          'negated' => '%item must contain HTML tags.',
843
        );
844
        $error_vars = array('%item' => $components[$key]['name']);
845
        foreach ($val as $subval) {
846
          $test = strcmp($subval, strip_tags($subval));
847
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
848
        }
849
      }
850
      return $errors;
851

    
852
    case 'starts_with':
853
    case 'ends_with':
854
      $pattern = preg_quote($rule['data'], '/');
855
      if ($validator_name === 'starts_with') {
856
        $pattern = '^' . $pattern;
857
        $verb = t('start');
858
      }
859
      else {
860
        $pattern .= '$';
861
        $verb = t('end');
862
      }
863
      $pattern = '/' . $pattern . '/';
864

    
865
      foreach ($items as $key => $val) {
866
        $error_strings = array(
867
          'regular' => '%item must %verb with "%string".',
868
          'negated' => '%item must not %verb with "%string".',
869
        );
870
        $error_vars = array(
871
          '%item' => $components[$key]['name'],
872
          '%verb' => $verb,
873
          '%string' => $rule['data'],
874
        );
875
        foreach ($val as $subval) {
876
          $test = !preg_match($pattern, $subval);
877
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
878
        }
879
      }
880
      return $errors;
881

    
882
    case 'pattern':
883
      $pattern = preg_quote($rule['data'], '/');
884
      $pattern = str_replace('@', '[a-zA-Z]', $pattern);
885
      // Since PHP 7.3, the # character is quoted by preg_quote().
886
      $quoted_hash = version_compare(PHP_VERSION, '7.3.0', '>=') ? '\\#' : '#';
887
      $pattern = str_replace($quoted_hash, '[0-9]', $pattern);
888
      $pattern = '(' . $pattern . ')';
889
      // Un-escape "|" operator.
890
      $pattern = preg_replace('/(\\s*)(\\\\)(\\|)(\\s*)/', ')|(', $pattern);
891
      foreach ($items as $key => $val) {
892
        foreach ($val as $subval) {
893
          $test = !preg_match('/^(' . $pattern . ')$/', $subval);
894
          _webform_validation_test($errors, $key, $rule, $test);
895
        }
896
      }
897
      return $errors;
898

    
899
    case 'regex':
900
    case 'regexi':
901
      mb_regex_encoding('UTF-8');
902
      $regex = $rule['data'];
903
      foreach ($items as $key => $val) {
904
        foreach ($val as $subval) {
905
          if ($validator_name === 'regexi') {
906
            $match = (bool) mb_eregi($regex, $subval);
907
          }
908
          else {
909
            $match = (bool) mb_ereg($regex, $subval);
910
          }
911
          $test = !$match;
912
          _webform_validation_test($errors, $key, $rule, $test);
913
        }
914
      }
915
      return $errors;
916

    
917
    case 'must_be_empty':
918
      foreach ($items as $key => $val) {
919
        if (count($val) !== 0) {
920
          $errors[$key] = t('%item does not contain the correct data.', array('%item' => $components[$key]['name']));
921
        }
922
      }
923
      return $errors;
924

    
925
    case 'blacklist':
926
      $blacklist = $rule['data'];
927
      $blacklist = token_replace($blacklist);
928
      $blacklist = preg_quote($blacklist, '/');
929
      $blacklist = explode(',', $blacklist);
930
      $blacklist = array_map('trim', $blacklist);
931
      $blacklist = '/\b(' . implode('|', $blacklist) . ')\b/i';
932
      foreach ($items as $key => $val) {
933
        foreach ($val as $subval) {
934
          $test = preg_match($blacklist, $subval);
935
          _webform_validation_test($errors, $key, $rule, $test);
936
        }
937
      }
938
      return $errors;
939

    
940
    case 'username':
941
      foreach ($items as $key => $val) {
942
        $error_strings = array(
943
          'regular' => 'The %item field does not match an active username.',
944
          'negated' => 'The %item field matches an active username.',
945
        );
946
        $error_vars = array('%item' => $components[$key]['name']);
947
        foreach ($val as $subval) {
948
          $test = !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $subval))->fetchField();
949
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
950
        }
951
      }
952
      return $errors;
953

    
954
    case 'valid_url':
955
      foreach ($items as $key => $val) {
956
        $error_strings = array(
957
          'regular' => '%item does not appear to be a valid URL.',
958
          'negated' => '%item must not be a valid URL.',
959
        );
960
        $error_vars = array('%item' => $components[$key]['name']);
961
        foreach ($val as $subval) {
962
          $test = !valid_url($subval, TRUE);
963
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
964
        }
965
      }
966
      return $errors;
967

    
968
    case 'email_verify':
969
      if (module_exists('email_verify')) {
970
        foreach ($items as $key => $val) {
971
          foreach ($val as $subval) {
972
            $error = email_verify_check($subval);
973
            if ($error) {
974
              $errors[$key] = $error;
975
            }
976
          }
977
        }
978
      }
979
      return $errors;
980
  }
981
}
982

    
983
/**
984
 * Return an array of the names of the components in a validation rule.
985
 *
986
 * @param array $rule
987
 *   Array of information about a validation rule.
988
 *
989
 * @return array
990
 *   Array of the filtered names of the components in $rule.
991
 */
992
function _webform_validation_get_names_of_rule_components(array $rule) {
993
  $names = array();
994
  foreach ($rule['components'] as $component) {
995
    $names[] = _webform_filter_xss($component['name']);
996
  }
997
  return $names;
998
}
999

    
1000
/**
1001
 * Helper function to negate validation rules as needed.
1002
 *
1003
 * Creates the correct error message.
1004
 */
1005
function _webform_validation_test(&$errors, $key, $rule, $test, array $error_strings = NULL, array $error_vars = NULL) {
1006
  $rule['negate'] = !empty($rule['negate']);
1007
  if ($rule['negate']) {
1008
    $test = !$test;
1009
  }
1010
  if ($test) {
1011
    if ($error_strings) {
1012
      $error = t($error_strings[$rule['negate'] ? 'negated' : 'regular'], $error_vars);
1013
    }
1014
    else {
1015
      $error = _webform_validation_i18n_error_message($rule);
1016
    }
1017
    $errors[$key] = $error;
1018
  }
1019
}
1020

    
1021
/**
1022
 * Count the number of words in a value.
1023
 *
1024
 * Strip HTML first.
1025
 *
1026
 * @param string|array $val
1027
 *   A string or array of strings in which to count words.
1028
 *
1029
 * @return int
1030
 *   The number of words in the input.
1031
 */
1032
function _webform_validation_count_words($val) {
1033
  $val = _webform_validation_flatten_array($val);
1034

    
1035
  // Strip any HTML tags so they're not counted in the word count.
1036
  $val = strip_tags($val);
1037
  // Convert character entities back to characters. Without this, entities for
1038
  // whitespace characters would not be counted as word boundaries.
1039
  $val = html_entity_decode($val);
1040

    
1041
  return count(preg_split('/[[:space:]\xC2\xA0]+/', $val));
1042
}
1043

    
1044
/**
1045
 * Helper function to deal with submitted values that are arrays.
1046
 *
1047
 * For example, multiple select component. We flatten the array as a
1048
 * comma-separated list to do the comparison.
1049
 */
1050
function _webform_validation_flatten_array($val) {
1051
  if (is_array($val)) {
1052
    $arr = array_filter($val, '_webform_validation_check_false');
1053
    return implode(',', $arr);
1054
  }
1055
  return $val;
1056
}
1057

    
1058
/**
1059
 * Get a array of validator definitions.
1060
 *
1061
 * @return array
1062
 *   Information about validators.
1063
 */
1064
function webform_validation_get_validators() {
1065
  static $validators;
1066
  if ($validators) {
1067
    return $validators;
1068
  }
1069

    
1070
  $validators = module_invoke_all('webform_validation_validators');
1071
  // Let modules use hook_webform_validator_alter($validators) to change
1072
  // validator settings.
1073
  drupal_alter('webform_validator', $validators);
1074

    
1075
  // Remove entries for which only partial information exists.
1076
  foreach ($validators as $validator_key => $validator_info) {
1077
    if (!isset($validator_info['name']) || !isset($validator_info['component_types'])) {
1078
      unset($validators[$validator_key]);
1079
    }
1080
  }
1081

    
1082
  return $validators;
1083
}
1084

    
1085
/**
1086
 * Return an array of the names of the validators.
1087
 *
1088
 * @return array
1089
 *   Array of with keys being validator IDs and values validator names.
1090
 */
1091
function webform_validation_get_validators_selection() {
1092
  $selection = array();
1093
  $validators = webform_validation_get_validators();
1094
  if ($validators) {
1095
    foreach ($validators as $validator_key => $validator_info) {
1096
      $selection[$validator_key] = $validator_info['name'];
1097
    }
1098
  }
1099
  return $selection;
1100
}
1101

    
1102
/**
1103
 * Get a list of valid component types per validator.
1104
 *
1105
 * These are defined via hook_webform_validation_validators(). If "all" is
1106
 * specified, all available component types will be returned.
1107
 */
1108
function webform_validation_valid_component_types($validator) {
1109
  $validators = webform_validation_get_validators();
1110
  if ($info = $validators[$validator]) {
1111
    $allowed_types = $info['component_types'];
1112
    if (in_array('all', $allowed_types, TRUE)) {
1113
      return array_keys(webform_components());
1114
    }
1115
    return $info['component_types'];
1116
  }
1117
}
1118

    
1119
/**
1120
 * Return information about a specified validator.
1121
 *
1122
 * @param string $validator_key
1123
 *   The key of the validator to return information about.
1124
 *
1125
 * @return array
1126
 *   Array of information about the validator.
1127
 */
1128
function webform_validation_get_validator_info($validator_key) {
1129
  $validators = webform_validation_get_validators();
1130
  return $validators[$validator_key];
1131
}
1132

    
1133
/**
1134
 * Handle translatable error messages, if available.
1135
 */
1136
function _webform_validation_i18n_error_message($rule) {
1137
  $rule['error_message'] = filter_xss($rule['error_message']);
1138
  if (module_exists('i18n')) {
1139
    $rule['ruleid'] = isset($rule['ruleid']) ? $rule['ruleid'] : NULL;
1140
    return i18n_string('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
1141
  }
1142
  return $rule['error_message'];
1143
}
1144

    
1145
/**
1146
 * Helper function used by array_filter() to determine if a value is empty.
1147
 *
1148
 * This is used in place of empty() because string '0' needs to be considered
1149
 * not empty and return FALSE.
1150
 *
1151
 * @param mixed $var
1152
 *   The value to check.
1153
 *
1154
 * @return bool
1155
 *   TRUE if $var is not one of FALSE, int 0, or empty string; FALSE otherwise.
1156
 *
1157
 * @see https://www.drupal.org/node/886458
1158
 * @see https://www.drupal.org/node/2638172
1159
 */
1160
function _webform_validation_check_false($var) {
1161
  return $var !== FALSE && $var !== 0 && $var !== '';
1162
}
1163

    
1164
/**
1165
 * Helper function to check numeric data.
1166
 *
1167
 * Process the numeric value validation range that was provided in the numeric
1168
 * validator options.
1169
 */
1170
function _webform_numeric_check_data($data) {
1171
  $range = array('min' => NULL, 'max' => NULL);
1172
  // If no value was specified, don't validate.
1173
  if ($data == '') {
1174
    return $range;
1175
  }
1176

    
1177
  // If only one numeric value was specified, this is the min value.
1178
  if (is_numeric($data)) {
1179
    $range['min'] = (float) $data;
1180
  }
1181

    
1182
  if (strpos($data, '|') !== FALSE) {
1183
    list($min, $max) = explode('|', $data);
1184
    if ($min != '' && is_numeric($min)) {
1185
      $range['min'] = (float) $min;
1186
    }
1187
    if ($max != '' && is_numeric($max)) {
1188
      $range['max'] = (float) $max;
1189
    }
1190
  }
1191
  return $range;
1192
}