Projet

Général

Profil

Paste
Télécharger (39,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform_validation / webform_validation.validators.inc @ 81b16cc2

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

    
400
  // Only available in Webform 4.
401
  module_load_include('inc', 'webform', 'components/number');
402
  if (!function_exists('webform_compare_floats')) {
403
    unset($validators['sum']);
404
  }
405

    
406
  if (module_exists('email_verify')) {
407
    $validators['email_verify'] = array(
408
      'name' => t('Email Verify'),
409
      'component_types' => array(
410
        'email',
411
      ),
412
      'description' => t('Verifies that an email address actually exists using the <a href="https://drupal.org/project/email_verify">Email Verification module</a>.'),
413
    );
414
  }
415

    
416
  return $validators;
417
}
418

    
419
/**
420
 * Implements hook_webform_validation_validate().
421
 */
422
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
423
  if (!$items) {
424
    return NULL;
425
  }
426

    
427
  // For certain validators, if all components to be compared are email
428
  // components, make the values lower-case to avoid case-sensitive comparison.
429
  if (in_array($validator_name, array('equal', 'comparison', 'unique'))) {
430
    $all_email = TRUE;
431
    foreach ($rule['components'] as $component) {
432
      if ($component['type'] !== 'email') {
433
        $all_email = FALSE;
434
        break;
435
      }
436
    }
437
    if ($all_email) {
438
      $items = array_map('strtolower', $items);
439
    }
440
  }
441

    
442
  // Some components, such as multiple select, send their values as arrays, others don't.
443
  // Convert all to arrays and write the rules to handle them that way. Remove empty values.
444
  foreach ($items as $key => $val) {
445
    $new_values = array();
446
    foreach ((array) $val as $val_key => $val_val) {
447
      if ((string) $val_val !== '') {
448
        $new_values[$val_key] = $val_val;
449
      }
450
    }
451
    $items[$key] = $new_values;
452
  }
453
  // Ensure later calls to key() get the first item.
454
  reset($items);
455

    
456
  $errors = array();
457
  switch ($validator_name) {
458
    case 'numeric':
459
      $num_range = _webform_numeric_check_data($rule['data']);
460
      foreach ($items as $key => $val) {
461
        foreach ($val as $subval) {
462
          // First check if the value is numeric.
463
          if (is_numeric($subval)) {
464
            $subval = (float) $subval;
465
          }
466
          else {
467
            $errors[$key] = t('%item is not numeric.', array('%item' => $components[$key]['name']));
468
            continue;
469
          }
470

    
471
          // Now validate the entered numeric value against the validator range settings, if appropriate
472
          // a. validate min & max.
473
          if (isset($num_range['min']) && isset($num_range['max'])) {
474
            // Validate the min - max range.
475
            if ($subval < $num_range['min'] || $subval > $num_range['max']) {
476
              $errors[$key] = t('%item is not within the allowed range %range.', array('%item' => $components[$key]['name'], '%range' => str_replace('|', ' - ', $rule['data'])));
477
            }
478
          }
479
          else {
480
            // b. validate min.
481
            if (isset($num_range['min'])) {
482
              if ($subval < $num_range['min']) {
483
                $errors[$key] = t('%item should be greater than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['min']));
484
              }
485
            }
486
            // c. validate max.
487
            if (isset($num_range['max'])) {
488
              if ($subval > $num_range['max']) {
489
                $errors[$key] = t('%item should be less than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['max']));
490
              }
491
            }
492
          }
493
        }
494
      }
495
      return $errors;
496

    
497
    case 'min_length':
498
      $min_length = $rule['data'];
499
      foreach ($items as $key => $val) {
500
        foreach ($val as $subval) {
501
          if (drupal_strlen($subval) < $min_length) {
502
            $errors[$key] = t('%item should be at least %num characters long.', array('%item' => $components[$key]['name'], '%num' => $min_length));
503
          }
504
        }
505
      }
506
      return $errors;
507

    
508
    case 'max_length':
509
      $max_length = $rule['data'];
510
      foreach ($items as $key => $val) {
511
        foreach ($val as $subval) {
512
          if (drupal_strlen($subval) > $max_length) {
513
            $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
514
          }
515
        }
516
      }
517
      return $errors;
518

    
519
    case 'min_words':
520
      $min_words = $rule['data'];
521
      foreach ($items as $key => $val) {
522
        foreach ($val as $subval) {
523
          if (_webform_validation_count_words($subval) < $min_words) {
524
            $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']));
525
            $errors[$key] = $error;
526
          }
527
        }
528
      }
529
      return $errors;
530

    
531
    case 'max_words':
532
      $max_words = $rule['data'];
533
      foreach ($items as $key => $val) {
534
        foreach ($val as $subval) {
535
          if (_webform_validation_count_words($subval) > $max_words) {
536
            $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']));
537
            $errors[$key] = $error;
538
          }
539
        }
540
      }
541
      return $errors;
542

    
543
    case 'sum':
544
      // Get the numbers to sum.
545
      $sum = array();
546
      foreach ($items as $item) {
547
        if (isset($item[0])) {
548
          $sum[] = $item[0];
549
        }
550
      }
551
      // If none of the components are completed, don't run this validator.
552
      if (!count($sum)) {
553
        return array();
554
      }
555
      $sum = array_sum($sum);
556

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

    
560
      // Parse the comparision operator and do comparison.
561
      module_load_include('inc', 'webform', 'components/number');
562
      $error = FALSE;
563
      if (substr($rule['data'], 0, 2) === '<=') {
564
        if (!(webform_compare_floats($sum, $compare_number) <= 0)) {
565
          $error = t('less than or equal to');
566
        }
567
      }
568
      elseif (substr($rule['data'], 0, 1) === '<') {
569
        if (!(webform_compare_floats($sum, $compare_number) < 0)) {
570
          $error = t('less than');
571
        }
572
      }
573
      elseif (substr($rule['data'], 0, 2) === '>=') {
574
        if (!(webform_compare_floats($sum, $compare_number) >= 0)) {
575
          $error = t('greater than or equal to');
576
        }
577
      }
578
      elseif (substr($rule['data'], 0, 1) === '>') {
579
        if (!(webform_compare_floats($sum, $compare_number) > 0)) {
580
          $error = t('greater than');
581
        }
582
      }
583
      else {
584
        if (!(webform_compare_floats($sum, $compare_number) === 0)) {
585
          $error = t('exactly');
586
        }
587
      }
588
      // Set error if needed.
589
      if ($error) {
590
        $keys = array_keys($items);
591
        $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)));
592
      }
593
      return $errors;
594

    
595
    case 'equal':
596
      $first_entry_key = key($items);
597
      $first_entry = array_shift($items);
598
      foreach ($items as $key => $val) {
599
        if ($val !== $first_entry) {
600
          $errors[$key] = t('%item_checked does not match %item_first.', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
601
        }
602
      }
603
      return $errors;
604

    
605
    case 'comparison':
606
      foreach (array(1, 2) as $count) {
607
        $entry[$count]['key'] = key($items);
608
        $value = array_shift($items);
609
        if ($components[$entry[$count]['key']]['type'] === 'date') {
610
          if (!empty($value) && checkdate((int) $value['month'], (int) $value['day'], (int) $value['year'])) {
611
            $entry[$count]['value'] = date('Y-m-d', mktime(0, 0, 0, (int) $value['month'], (int) $value['day'], (int) $value['year']));
612
          }
613
          else {
614
            $entry[$count]['value'] = NULL;
615
          }
616
        }
617
        elseif ($components[$entry[$count]['key']]['type'] === 'time') {
618
          $time = $value['hour'] . ':' . $value['minute'];
619
          if (!empty($value['ampm'])) {
620
            $time .= ' ' . $value['ampm'];
621
          }
622
          $time = strtotime($time);
623
          if ($time) {
624
            $entry[$count]['value'] = date('H:i', $time);
625
          }
626
          else {
627
            $entry[$count]['value'] = NULL;
628
          }
629
        }
630
        else {
631
          $entry[$count]['value'] = _webform_validation_flatten_array($value);
632
        }
633
      }
634

    
635
      // Don't validate if either are empty.
636
      if ((string) $entry[1]['value'] === '' || (string) $entry[2]['value'] === '') {
637
        return array();
638
      }
639

    
640
      $error = FALSE;
641
      switch ($rule['data']) {
642
        case '>':
643
          if (!($entry[1]['value'] > $entry[2]['value'])) {
644
            $error = TRUE;
645
          }
646
          break;
647

    
648
        case '>=':
649
          if (!($entry[1]['value'] >= $entry[2]['value'])) {
650
            $error = TRUE;
651
          }
652
          break;
653

    
654
        case '<':
655
          if (!($entry[1]['value'] < $entry[2]['value'])) {
656
            $error = TRUE;
657
          }
658
          break;
659

    
660
        case '<=':
661
          if (!($entry[1]['value'] <= $entry[2]['value'])) {
662
            $error = TRUE;
663
          }
664
          break;
665
      }
666

    
667
      if ($error) {
668
        $errors[$entry[2]['key']] = _webform_validation_i18n_error_message($rule);
669
      }
670
      return $errors;
671

    
672
    case 'unique':
673
      foreach ($items as $key => $val) {
674
        $items[$key] = _webform_validation_flatten_array($val);
675
      }
676
      // Now we count how many times each value appears, and find out which values appear more than once.
677
      $items_count = array_count_values(array_map('drupal_strtolower', array_map('trim', $items)));
678
      $doubles = array_filter($items_count, create_function('$x', 'return $x > 1;'));
679
      foreach ($items as $key => $val) {
680
        if (in_array(drupal_strtolower($val), array_keys($doubles))) {
681
          $errors[$key] = t('The value of %item is not unique.', array('%item' => $components[$key]['name']));
682
        }
683
      }
684
      return $errors;
685

    
686
    case 'specific_value':
687
      $specific_values = explode(',', $rule['data']);
688
      $specific_values = array_map('trim', $specific_values);
689
      foreach ($items as $key => $val) {
690
        // Selects: Fail if at least one checked and at least one not in the allowed values.
691
        $val = array_filter($val);
692
        $test = count($val) && count(array_diff($val, $specific_values));
693
        _webform_validation_test($errors, $key, $rule, $test);
694
      }
695
      return $errors;
696

    
697
    case 'default_value':
698
      foreach ($items as $key => $val) {
699
        $val = _webform_validation_flatten_array($val);
700
        $test = $val != $components[$key]['value'];
701
        _webform_validation_test($errors, $key, $rule, $test);
702
      }
703
      return $errors;
704

    
705
    case 'someofseveral':
706
      // Determine the number of components completed.
707
      foreach ($items as $key => $val) {
708
        $items[$key] = _webform_validation_flatten_array($val);
709
        $items[$key] = (bool) strlen($items[$key]);
710
      }
711
      $number_completed = count(array_filter($items));
712

    
713
      // Number being compared with.
714
      $compare_number = (int) preg_replace('/[^0-9]+/', '', $rule['data']);
715
      if ($compare_number < 1) {
716
        $compare_number = 1;
717
      }
718
      elseif ($compare_number > count($rule['components'])) {
719
        $compare_number = count($rule['components']);
720
      }
721

    
722
      // Parse the comparision operator and do comparison.
723
      $error = FALSE;
724
      if (substr($rule['data'], 0, 1) === '=') {
725
        if (!($number_completed === $compare_number)) {
726
          $error = t('exactly');
727
        }
728
      }
729
      elseif (substr($rule['data'], 0, 2) === '<=') {
730
        if (!($number_completed <= $compare_number)) {
731
          $error = t('at most');
732
        }
733
      }
734
      else {
735
        if (!($number_completed >= $compare_number)) {
736
          $error = t('at least');
737
        }
738
      }
739

    
740
      // Set error if needed.
741
      if ($error) {
742
        $keys = array_keys($items);
743
        $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)));
744
      }
745
      return $errors;
746

    
747
    case 'select_min':
748
      $min_selections = $rule['data'];
749
      foreach ($items as $key => $val) {
750
        if (count(array_filter($val, '_webform_validation_check_false')) < $min_selections) {
751
          $errors[$key] = t('Please select at least %num options for %item.', array('%num' => $min_selections, '%item' => $components[$key]['name']));
752
        }
753
      }
754
      return $errors;
755

    
756
    case 'select_max':
757
      $max_selections = $rule['data'];
758
      foreach ($items as $key => $val) {
759
        if (count(array_filter($val, '_webform_validation_check_false')) > $max_selections) {
760
          $errors[$key] = t('Please select maximum %num options for %item.', array('%num' => $max_selections, '%item' => $components[$key]['name']));
761
        }
762
      }
763
      return $errors;
764

    
765
    case 'select_exact':
766
      $allowed_selections = $rule['data'];
767
      foreach ($items as $key => $val) {
768
        $error_strings = array(
769
          'regular' => 'Please select %num options for %item.',
770
          'negated' => 'Please do not select %num options for %item.',
771
        );
772
        $error_vars = array('%num' => $allowed_selections, '%item' => $components[$key]['name']);
773
        $test = count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections;
774
        _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
775
      }
776
      return $errors;
777

    
778
    case 'plain_text':
779
      foreach ($items as $key => $val) {
780
        $error_strings = array(
781
          'regular' => '%item only allows the use of plain text.',
782
          'negated' => '%item must contain HTML tags.',
783
        );
784
        $error_vars = array('%item' => $components[$key]['name']);
785
        foreach ($val as $subval) {
786
          $test = strcmp($subval, strip_tags($subval));
787
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
788
        }
789
      }
790
      return $errors;
791

    
792
    case 'starts_with':
793
    case 'ends_with':
794
      $pattern = preg_quote($rule['data'], '/');
795
      if ($validator_name === 'starts_with') {
796
        $pattern = '^' . $pattern;
797
        $verb = t('start');
798
      }
799
      else {
800
        $pattern .= '$';
801
        $verb = t('end');
802
      }
803
      $pattern = '/' . $pattern . '/';
804

    
805
      foreach ($items as $key => $val) {
806
        $error_strings = array(
807
          'regular' => '%item must %verb with "%string".',
808
          'negated' => '%item must not %verb with "%string".',
809
        );
810
        $error_vars = array('%item' => $components[$key]['name'], '%verb' => $verb, '%string' => $rule['data']);
811
        foreach ($val as $subval) {
812
          $test = !preg_match($pattern, $subval);
813
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
814
        }
815
      }
816
      return $errors;
817

    
818
    case 'pattern':
819
      $pattern = preg_quote($rule['data'], '/');
820
      $pattern = str_replace('@', '[a-zA-Z]', $pattern);
821
      $pattern = str_replace('#', '[0-9]', $pattern);
822
      $pattern = '(' . $pattern . ')';
823
      // Un-escape "|" operator.
824
      $pattern = preg_replace('/(\\s*)(\\\\)(\\|)(\\s*)/', ')|(', $pattern);
825
      foreach ($items as $key => $val) {
826
        foreach ($val as $subval) {
827
          $test = !preg_match('/^(' . $pattern . ')$/', $subval);
828
          _webform_validation_test($errors, $key, $rule, $test);
829
        }
830
      }
831
      return $errors;
832

    
833
    case 'regex':
834
    case 'regexi':
835
      mb_regex_encoding('UTF-8');
836
      $regex = $rule['data'];
837
      foreach ($items as $key => $val) {
838
        foreach ($val as $subval) {
839
          if ($validator_name === 'regexi') {
840
            $match = (bool) mb_eregi($regex, $subval);
841
          }
842
          else {
843
            $match = (bool) mb_ereg($regex, $subval);
844
          }
845
          $test = !$match;
846
          _webform_validation_test($errors, $key, $rule, $test);
847
        }
848
      }
849
      return $errors;
850

    
851
    case 'must_be_empty':
852
      foreach ($items as $key => $val) {
853
        if (count($val) !== 0) {
854
          $errors[$key] = t('%item does not contain the correct data.', array('%item' => $components[$key]['name']));
855
        }
856
      }
857
      return $errors;
858

    
859
    case 'blacklist':
860
      $blacklist = preg_quote($rule['data'], '/');
861
      $blacklist = explode(',', $blacklist);
862
      $blacklist = array_map('trim', $blacklist);
863
      $blacklist = '/\b(' . implode('|', $blacklist) . ')\b/i';
864
      foreach ($items as $key => $val) {
865
        foreach ($val as $subval) {
866
          $test = preg_match($blacklist, $subval);
867
          _webform_validation_test($errors, $key, $rule, $test);
868
        }
869
      }
870
      return $errors;
871

    
872
    case 'username':
873
      foreach ($items as $key => $val) {
874
        $error_strings = array(
875
          'regular' => 'The %item field does not match an active username.',
876
          'negated' => 'The %item field matches an active username.',
877
        );
878
        $error_vars = array('%item' => $components[$key]['name']);
879
        foreach ($val as $subval) {
880
          $test = !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $subval))->fetchField();
881
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
882
        }
883
      }
884
      return $errors;
885

    
886
    case 'valid_url':
887
      foreach ($items as $key => $val) {
888
        $error_strings = array(
889
          'regular' => '%item does not appear to be a valid URL.',
890
          'negated' => '%item must not be a valid URL.',
891
        );
892
        $error_vars = array('%item' => $components[$key]['name']);
893
        foreach ($val as $subval) {
894
          $test = !valid_url($subval, TRUE);
895
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
896
        }
897
      }
898
      return $errors;
899

    
900
    case 'email_verify':
901
      if (module_exists('email_verify')) {
902
        foreach ($items as $key => $val) {
903
          foreach ($val as $subval) {
904
            $error = email_verify_check($subval);
905
            if ($error) {
906
              $errors[$key] = $error;
907
            }
908
          }
909
        }
910
      }
911
      return $errors;
912
  }
913
}
914

    
915
/**
916
 * Return an array of the names of the components in a validation rule.
917
 *
918
 * @param array $rule
919
 *   Array of information about a validation rule.
920
 *
921
 * @return array
922
 *   Array of the filtered names of the components in $rule.
923
 */
924
function _webform_validation_get_names_of_rule_components(array $rule) {
925
  $names = array();
926
  foreach ($rule['components'] as $component) {
927
    $names[] = _webform_filter_xss($component['name']);
928
  }
929
  return $names;
930
}
931

    
932
/**
933
 * Helper function to negate validation rules as needed and create the correct error message.
934
 */
935
function _webform_validation_test(&$errors, $key, $rule, $test, array $error_strings = NULL, array $error_vars = NULL) {
936
  $rule['negate'] = !empty($rule['negate']);
937
  if ($rule['negate']) {
938
    $test = !$test;
939
  }
940
  if ($test) {
941
    if ($error_strings) {
942
      $error = t($error_strings[$rule['negate'] ? 'negated' : 'regular'], $error_vars);
943
    }
944
    else {
945
      $error = _webform_validation_i18n_error_message($rule);
946
    }
947
    $errors[$key] = $error;
948
  }
949
}
950

    
951
/**
952
 * Count the number of words in a value.
953
 *
954
 * Strip HTML first.
955
 *
956
 * @param string|array $val
957
 *   A string or array of strings in which to count words.
958
 *
959
 * @return int
960
 *   The number of words in the input.
961
 */
962
function _webform_validation_count_words($val) {
963
  $val = _webform_validation_flatten_array($val);
964

    
965
  // Strip any HTML tags so they're not counted in the word count.
966
  $val = strip_tags($val);
967
  // Convert character entities back to characters. Without this, entities for
968
  // whitespace characters would not be counted as word boundaries.
969
  $val = html_entity_decode($val);
970

    
971
  return count(preg_split('/[[:space:]\xC2\xA0]+/', $val));
972
}
973

    
974
/**
975
 * Helper function to deal with submitted values that are arrays.
976
 *
977
 * For example, multiple select component. We flatten the array as a
978
 * comma-separated list to do the comparison.
979
 */
980
function _webform_validation_flatten_array($val) {
981
  if (is_array($val)) {
982
    $arr = array_filter($val, '_webform_validation_check_false');
983
    return implode(',', $arr);
984
  }
985
  return $val;
986
}
987

    
988
/**
989
 * Get a array of validator definitions.
990
 *
991
 * @return array
992
 *   Information about validators.
993
 */
994
function webform_validation_get_validators() {
995
  static $validators;
996
  if ($validators) {
997
    return $validators;
998
  }
999

    
1000
  $validators = module_invoke_all('webform_validation_validators');
1001
  // Let modules use hook_webform_validator_alter($validators) to change validator settings.
1002
  drupal_alter('webform_validator', $validators);
1003

    
1004
  // Remove entries for which only partial information exists.
1005
  foreach ($validators as $validator_key => $validator_info) {
1006
    if (!isset($validator_info['name']) || !isset($validator_info['component_types'])) {
1007
      unset($validators[$validator_key]);
1008
    }
1009
  }
1010

    
1011
  return $validators;
1012
}
1013

    
1014
/**
1015
 * Return an array of the names of the validators.
1016
 *
1017
 * @return array
1018
 *   Array of with keys being validator IDs and values validator names.
1019
 */
1020
function webform_validation_get_validators_selection() {
1021
  $selection = array();
1022
  $validators = webform_validation_get_validators();
1023
  if ($validators) {
1024
    foreach ($validators as $validator_key => $validator_info) {
1025
      $selection[$validator_key] = $validator_info['name'];
1026
    }
1027
  }
1028
  return $selection;
1029
}
1030

    
1031
/**
1032
 * Get a list of valid component types per validator.
1033
 *
1034
 * These are defined via hook_webform_validation_validators(). If "all" is
1035
 * specified, all available component types will be returned.
1036
 */
1037
function webform_validation_valid_component_types($validator) {
1038
  $validators = webform_validation_get_validators();
1039
  if ($info = $validators[$validator]) {
1040
    $allowed_types = $info['component_types'];
1041
    if (in_array('all', $allowed_types, TRUE)) {
1042
      return array_keys(webform_components());
1043
    }
1044
    return $info['component_types'];
1045
  }
1046
}
1047

    
1048
/**
1049
 * Return information about a specified validator.
1050
 *
1051
 * @param string $validator_key
1052
 *   The key of the validator to return information about.
1053
 *
1054
 * @return array
1055
 *   Array of information about the validator.
1056
 */
1057
function webform_validation_get_validator_info($validator_key) {
1058
  $validators = webform_validation_get_validators();
1059
  return $validators[$validator_key];
1060
}
1061

    
1062
/**
1063
 * Handle translatable error messages, if available.
1064
 */
1065
function _webform_validation_i18n_error_message($rule) {
1066
  $rule['error_message'] = filter_xss($rule['error_message']);
1067
  if (module_exists('i18n')) {
1068
    return i18n_string('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
1069
  }
1070
  return $rule['error_message'];
1071
}
1072

    
1073
/**
1074
 * Helper function used by array_filter() to determine if a value is empty.
1075
 *
1076
 * This is used in place of empty() because string '0' needs to be considered
1077
 * not empty and return FALSE.
1078
 *
1079
 * @param mixed $var
1080
 *   The value to check.
1081
 *
1082
 * @return bool
1083
 *   TRUE if $var is not one of FALSE, int 0, or empty string; FALSE otherwise.
1084
 *
1085
 * @see https://www.drupal.org/node/886458
1086
 * @see https://www.drupal.org/node/2638172
1087
 */
1088
function _webform_validation_check_false($var) {
1089
  return $var !== FALSE && $var !== 0 && $var !== '';
1090
}
1091

    
1092
/**
1093
 * Process the numeric value validation range that was provided in the numeric validator options.
1094
 */
1095
function _webform_numeric_check_data($data) {
1096
  $range = array('min' => NULL, 'max' => NULL);
1097
  // If no value was specified, don't validate.
1098
  if ($data == '') {
1099
    return $range;
1100
  }
1101

    
1102
  // If only one numeric value was specified, this is the min value.
1103
  if (is_numeric($data)) {
1104
    $range['min'] = (float) $data;
1105
  }
1106

    
1107
  if (strpos($data, '|') !== FALSE) {
1108
    list($min, $max) = explode('|', $data);
1109
    if ($min != '' && is_numeric($min)) {
1110
      $range['min'] = (float) $min;
1111
    }
1112
    if ($max != '' && is_numeric($max)) {
1113
      $range['max'] = (float) $max;
1114
    }
1115
  }
1116
  return $range;
1117
}