Project

General

Profile

Paste
Download (41.1 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / webform_validation / webform_validation.validators.inc @ d0ed0aa6

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
  if (module_exists('maxlength')) {
462
    $validators['max_length']['description'] .= ' ' . t('A JavaScript counter will show the number of characters remaining as the user types.');
463
  }
464

    
465
  return $validators;
466
}
467

    
468
/**
469
 * Implements hook_webform_validation_validate().
470
 */
471
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
472
  if (!$items) {
473
    return NULL;
474
  }
475

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

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

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

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

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

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

    
574
    case 'min_words':
575
      $min_words = $rule['data'];
576
      foreach ($items as $key => $val) {
577
        foreach ($val as $subval) {
578
          if (_webform_validation_count_words($subval) < $min_words) {
579
            $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']));
580
            $errors[$key] = $error;
581
          }
582
        }
583
      }
584
      return $errors;
585

    
586
    case 'max_words':
587
      $max_words = $rule['data'];
588
      foreach ($items as $key => $val) {
589
        foreach ($val as $subval) {
590
          if (_webform_validation_count_words($subval) > $max_words) {
591
            $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']));
592
            $errors[$key] = $error;
593
          }
594
        }
595
      }
596
      return $errors;
597

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

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

    
615
      // Parse the comparision operator and do comparison.
616
      module_load_include('inc', 'webform', 'components/number');
617
      $error = FALSE;
618
      if (substr($rule['data'], 0, 2) === '<=') {
619
        if (!(webform_compare_floats($sum, $compare_number) <= 0)) {
620
          $error = t('less than or equal to');
621
        }
622
      }
623
      elseif (substr($rule['data'], 0, 1) === '<') {
624
        if (!(webform_compare_floats($sum, $compare_number) < 0)) {
625
          $error = t('less than');
626
        }
627
      }
628
      elseif (substr($rule['data'], 0, 2) === '>=') {
629
        if (!(webform_compare_floats($sum, $compare_number) >= 0)) {
630
          $error = t('greater than or equal to');
631
        }
632
      }
633
      elseif (substr($rule['data'], 0, 1) === '>') {
634
        if (!(webform_compare_floats($sum, $compare_number) > 0)) {
635
          $error = t('greater than');
636
        }
637
      }
638
      else {
639
        if (!(webform_compare_floats($sum, $compare_number) === 0)) {
640
          $error = t('exactly');
641
        }
642
      }
643
      // Set error if needed.
644
      if ($error) {
645
        $keys = array_keys($items);
646
        $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)));
647
      }
648
      return $errors;
649

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

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

    
695
      // Don't validate if either are empty.
696
      if ((string) $entry[1]['value'] === '' || (string) $entry[2]['value'] === '') {
697
        return array();
698
      }
699

    
700
      $error = FALSE;
701
      switch ($rule['data']) {
702
        case '>':
703
          if (!($entry[1]['value'] > $entry[2]['value'])) {
704
            $error = TRUE;
705
          }
706
          break;
707

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

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

    
720
        case '<=':
721
          if (!($entry[1]['value'] <= $entry[2]['value'])) {
722
            $error = TRUE;
723
          }
724
          break;
725
      }
726

    
727
      if ($error) {
728
        $errors[$entry[2]['key']] = _webform_validation_i18n_error_message($rule);
729
      }
730
      return $errors;
731

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

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

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

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

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

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

    
804
      // Set error if needed.
805
      if ($error) {
806
        $keys = array_keys($items);
807
        $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)));
808
      }
809
      return $errors;
810

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1045
  return count(preg_split('/[[:space:]\xC2\xA0]+/', $val));
1046
}
1047

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

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

    
1074
  $validators = module_invoke_all('webform_validation_validators');
1075
  // Let modules use hook_webform_validator_alter($validators) to change
1076
  // validator settings.
1077
  drupal_alter('webform_validator', $validators);
1078

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

    
1086
  return $validators;
1087
}
1088

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

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

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

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

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

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

    
1181
  // If only one numeric value was specified, this is the min value.
1182
  if (is_numeric($data)) {
1183
    $range['min'] = (float) $data;
1184
  }
1185

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