Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform_validation / webform_validation.validators.inc @ 76bdcd04

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

    
407
  // Only available in Webform 4.
408
  module_load_include('inc', 'webform', 'components/number');
409
  if (!function_exists('webform_compare_floats')) {
410
    unset($validators['sum']);
411
  }
412

    
413
  if (module_exists('email_verify')) {
414
    $validators['email_verify'] = array(
415
      'name' => t('Email Verify'),
416
      'component_types' => array(
417
        'email',
418
      ),
419
      'description' => t('Verifies that an email address actually exists using the <a href="https://drupal.org/project/email_verify">Email Verification module</a>.'),
420
    );
421
  }
422

    
423
  return $validators;
424
}
425

    
426
/**
427
 * Implements hook_webform_validation_validate().
428
 */
429
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
430
  if (!$items) {
431
    return NULL;
432
  }
433

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

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

    
464
  $errors = array();
465
  switch ($validator_name) {
466
    case 'numeric':
467
      $num_range = _webform_numeric_check_data($rule['data']);
468
      foreach ($items as $key => $val) {
469
        foreach ($val as $subval) {
470
          // First check if the value is numeric.
471
          if (is_numeric($subval)) {
472
            $subval = (float) $subval;
473
          }
474
          else {
475
            $errors[$key] = t('%item is not numeric.', array('%item' => $components[$key]['name']));
476
            continue;
477
          }
478

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

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

    
517
    case 'max_length':
518
      $max_length = $rule['data'];
519
      foreach ($items as $key => $val) {
520
        foreach ($val as $subval) {
521
          if (drupal_strlen($subval) > $max_length) {
522
            $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
523
          }
524
        }
525
      }
526
      return $errors;
527

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

    
540
    case 'max_words':
541
      $max_words = $rule['data'];
542
      foreach ($items as $key => $val) {
543
        foreach ($val as $subval) {
544
          if (_webform_validation_count_words($subval) > $max_words) {
545
            $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']));
546
            $errors[$key] = $error;
547
          }
548
        }
549
      }
550
      return $errors;
551

    
552
    case 'sum':
553
      // Get the numbers to sum.
554
      $sum = array();
555
      foreach ($items as $item) {
556
        if (isset($item[0])) {
557
          $sum[] = $item[0];
558
        }
559
      }
560
      // If none of the components are completed, don't run this validator.
561
      if (!count($sum)) {
562
        return array();
563
      }
564
      $sum = array_sum($sum);
565

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

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

    
604
    case 'equal':
605
      $first_entry_key = key($items);
606
      $first_entry = array_shift($items);
607
      foreach ($items as $key => $val) {
608
        if ($val !== $first_entry) {
609
          $errors[$key] = t('%item_checked does not match %item_first.', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
610
        }
611
      }
612
      return $errors;
613

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

    
644
      // Don't validate if either are empty.
645
      if ((string) $entry[1]['value'] === '' || (string) $entry[2]['value'] === '') {
646
        return array();
647
      }
648

    
649
      $error = FALSE;
650
      switch ($rule['data']) {
651
        case '>':
652
          if (!($entry[1]['value'] > $entry[2]['value'])) {
653
            $error = TRUE;
654
          }
655
          break;
656

    
657
        case '>=':
658
          if (!($entry[1]['value'] >= $entry[2]['value'])) {
659
            $error = TRUE;
660
          }
661
          break;
662

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

    
669
        case '<=':
670
          if (!($entry[1]['value'] <= $entry[2]['value'])) {
671
            $error = TRUE;
672
          }
673
          break;
674
      }
675

    
676
      if ($error) {
677
        $errors[$entry[2]['key']] = _webform_validation_i18n_error_message($rule);
678
      }
679
      return $errors;
680

    
681
    case 'unique':
682
      foreach ($items as $key => $val) {
683
        $items[$key] = _webform_validation_flatten_array($val);
684
      }
685
      // Now we count how many times each value appears, and find out which
686
      // values appear more than once.
687
      $items_count = array_count_values(array_map('drupal_strtolower', array_map('trim', $items)));
688
      $doubles = array_filter($items_count, function ($x) {
689
        return $x > 1;
690
      });
691
      foreach ($items as $key => $val) {
692
        if (in_array(drupal_strtolower($val), array_keys($doubles))) {
693
          $errors[$key] = t('The value of %item is not unique.', array('%item' => $components[$key]['name']));
694
        }
695
      }
696
      return $errors;
697

    
698
    case 'specific_value':
699
      $specific_values = explode(',', $rule['data']);
700
      $specific_values = array_map('trim', $specific_values);
701
      foreach ($items as $key => $val) {
702
        // Selects: Fail if at least one checked and at least one not in the
703
        // allowed values.
704
        $val = array_filter($val);
705
        $test = count($val) && count(array_diff($val, $specific_values));
706
        _webform_validation_test($errors, $key, $rule, $test);
707
      }
708
      return $errors;
709

    
710
    case 'default_value':
711
      foreach ($items as $key => $val) {
712
        $val = _webform_validation_flatten_array($val);
713
        $test = $val != $components[$key]['value'];
714
        _webform_validation_test($errors, $key, $rule, $test);
715
      }
716
      return $errors;
717

    
718
    case 'someofseveral':
719
      // Determine the number of components completed.
720
      foreach ($items as $key => $val) {
721
        $items[$key] = _webform_validation_flatten_array($val);
722
        $items[$key] = (bool) strlen($items[$key]);
723
      }
724
      $number_completed = count(array_filter($items));
725

    
726
      // Number being compared with.
727
      $compare_number = (int) preg_replace('/[^0-9]+/', '', $rule['data']);
728
      if ($compare_number < 1) {
729
        $compare_number = 1;
730
      }
731
      elseif ($compare_number > count($rule['components'])) {
732
        $compare_number = count($rule['components']);
733
      }
734

    
735
      // Parse the comparision operator and do comparison.
736
      $error = FALSE;
737
      if (substr($rule['data'], 0, 1) === '=') {
738
        if (!($number_completed === $compare_number)) {
739
          $error = t('exactly');
740
        }
741
      }
742
      elseif (substr($rule['data'], 0, 2) === '<=') {
743
        if (!($number_completed <= $compare_number)) {
744
          $error = t('at most');
745
        }
746
      }
747
      else {
748
        if (!($number_completed >= $compare_number)) {
749
          $error = t('at least');
750
        }
751
      }
752

    
753
      // Set error if needed.
754
      if ($error) {
755
        $keys = array_keys($items);
756
        $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)));
757
      }
758
      return $errors;
759

    
760
    case 'select_min':
761
      $min_selections = $rule['data'];
762
      foreach ($items as $key => $val) {
763
        if (count(array_filter($val, '_webform_validation_check_false')) < $min_selections) {
764
          $errors[$key] = t('Please select at least %num options for %item.', array('%num' => $min_selections, '%item' => $components[$key]['name']));
765
        }
766
      }
767
      return $errors;
768

    
769
    case 'select_max':
770
      $max_selections = $rule['data'];
771
      foreach ($items as $key => $val) {
772
        if (count(array_filter($val, '_webform_validation_check_false')) > $max_selections) {
773
          $errors[$key] = t('Please select maximum %num options for %item.', array('%num' => $max_selections, '%item' => $components[$key]['name']));
774
        }
775
      }
776
      return $errors;
777

    
778
    case 'select_exact':
779
      $allowed_selections = $rule['data'];
780
      foreach ($items as $key => $val) {
781
        $error_strings = array(
782
          'regular' => 'Please select %num options for %item.',
783
          'negated' => 'Please do not select %num options for %item.',
784
        );
785
        $error_vars = array('%num' => $allowed_selections, '%item' => $components[$key]['name']);
786
        $test = count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections;
787
        _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
788
      }
789
      return $errors;
790

    
791
    case 'plain_text':
792
      foreach ($items as $key => $val) {
793
        $error_strings = array(
794
          'regular' => '%item only allows the use of plain text.',
795
          'negated' => '%item must contain HTML tags.',
796
        );
797
        $error_vars = array('%item' => $components[$key]['name']);
798
        foreach ($val as $subval) {
799
          $test = strcmp($subval, strip_tags($subval));
800
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
801
        }
802
      }
803
      return $errors;
804

    
805
    case 'starts_with':
806
    case 'ends_with':
807
      $pattern = preg_quote($rule['data'], '/');
808
      if ($validator_name === 'starts_with') {
809
        $pattern = '^' . $pattern;
810
        $verb = t('start');
811
      }
812
      else {
813
        $pattern .= '$';
814
        $verb = t('end');
815
      }
816
      $pattern = '/' . $pattern . '/';
817

    
818
      foreach ($items as $key => $val) {
819
        $error_strings = array(
820
          'regular' => '%item must %verb with "%string".',
821
          'negated' => '%item must not %verb with "%string".',
822
        );
823
        $error_vars = array('%item' => $components[$key]['name'], '%verb' => $verb, '%string' => $rule['data']);
824
        foreach ($val as $subval) {
825
          $test = !preg_match($pattern, $subval);
826
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
827
        }
828
      }
829
      return $errors;
830

    
831
    case 'pattern':
832
      $pattern = preg_quote($rule['data'], '/');
833
      $pattern = str_replace('@', '[a-zA-Z]', $pattern);
834
      $pattern = str_replace('#', '[0-9]', $pattern);
835
      $pattern = '(' . $pattern . ')';
836
      // Un-escape "|" operator.
837
      $pattern = preg_replace('/(\\s*)(\\\\)(\\|)(\\s*)/', ')|(', $pattern);
838
      foreach ($items as $key => $val) {
839
        foreach ($val as $subval) {
840
          $test = !preg_match('/^(' . $pattern . ')$/', $subval);
841
          _webform_validation_test($errors, $key, $rule, $test);
842
        }
843
      }
844
      return $errors;
845

    
846
    case 'regex':
847
    case 'regexi':
848
      mb_regex_encoding('UTF-8');
849
      $regex = $rule['data'];
850
      foreach ($items as $key => $val) {
851
        foreach ($val as $subval) {
852
          if ($validator_name === 'regexi') {
853
            $match = (bool) mb_eregi($regex, $subval);
854
          }
855
          else {
856
            $match = (bool) mb_ereg($regex, $subval);
857
          }
858
          $test = !$match;
859
          _webform_validation_test($errors, $key, $rule, $test);
860
        }
861
      }
862
      return $errors;
863

    
864
    case 'must_be_empty':
865
      foreach ($items as $key => $val) {
866
        if (count($val) !== 0) {
867
          $errors[$key] = t('%item does not contain the correct data.', array('%item' => $components[$key]['name']));
868
        }
869
      }
870
      return $errors;
871

    
872
    case 'blacklist':
873
      $blacklist = preg_quote($rule['data'], '/');
874
      $blacklist = explode(',', $blacklist);
875
      $blacklist = array_map('trim', $blacklist);
876
      $blacklist = '/\b(' . implode('|', $blacklist) . ')\b/i';
877
      foreach ($items as $key => $val) {
878
        foreach ($val as $subval) {
879
          $test = preg_match($blacklist, $subval);
880
          _webform_validation_test($errors, $key, $rule, $test);
881
        }
882
      }
883
      return $errors;
884

    
885
    case 'username':
886
      foreach ($items as $key => $val) {
887
        $error_strings = array(
888
          'regular' => 'The %item field does not match an active username.',
889
          'negated' => 'The %item field matches an active username.',
890
        );
891
        $error_vars = array('%item' => $components[$key]['name']);
892
        foreach ($val as $subval) {
893
          $test = !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $subval))->fetchField();
894
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
895
        }
896
      }
897
      return $errors;
898

    
899
    case 'valid_url':
900
      foreach ($items as $key => $val) {
901
        $error_strings = array(
902
          'regular' => '%item does not appear to be a valid URL.',
903
          'negated' => '%item must not be a valid URL.',
904
        );
905
        $error_vars = array('%item' => $components[$key]['name']);
906
        foreach ($val as $subval) {
907
          $test = !valid_url($subval, TRUE);
908
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
909
        }
910
      }
911
      return $errors;
912

    
913
    case 'email_verify':
914
      if (module_exists('email_verify')) {
915
        foreach ($items as $key => $val) {
916
          foreach ($val as $subval) {
917
            $error = email_verify_check($subval);
918
            if ($error) {
919
              $errors[$key] = $error;
920
            }
921
          }
922
        }
923
      }
924
      return $errors;
925
  }
926
}
927

    
928
/**
929
 * Return an array of the names of the components in a validation rule.
930
 *
931
 * @param array $rule
932
 *   Array of information about a validation rule.
933
 *
934
 * @return array
935
 *   Array of the filtered names of the components in $rule.
936
 */
937
function _webform_validation_get_names_of_rule_components(array $rule) {
938
  $names = array();
939
  foreach ($rule['components'] as $component) {
940
    $names[] = _webform_filter_xss($component['name']);
941
  }
942
  return $names;
943
}
944

    
945
/**
946
 * Helper function to negate validation rules as needed.
947
 *
948
 * Creates the correct error message.
949
 */
950
function _webform_validation_test(&$errors, $key, $rule, $test, array $error_strings = NULL, array $error_vars = NULL) {
951
  $rule['negate'] = !empty($rule['negate']);
952
  if ($rule['negate']) {
953
    $test = !$test;
954
  }
955
  if ($test) {
956
    if ($error_strings) {
957
      $error = t($error_strings[$rule['negate'] ? 'negated' : 'regular'], $error_vars);
958
    }
959
    else {
960
      $error = _webform_validation_i18n_error_message($rule);
961
    }
962
    $errors[$key] = $error;
963
  }
964
}
965

    
966
/**
967
 * Count the number of words in a value.
968
 *
969
 * Strip HTML first.
970
 *
971
 * @param string|array $val
972
 *   A string or array of strings in which to count words.
973
 *
974
 * @return int
975
 *   The number of words in the input.
976
 */
977
function _webform_validation_count_words($val) {
978
  $val = _webform_validation_flatten_array($val);
979

    
980
  // Strip any HTML tags so they're not counted in the word count.
981
  $val = strip_tags($val);
982
  // Convert character entities back to characters. Without this, entities for
983
  // whitespace characters would not be counted as word boundaries.
984
  $val = html_entity_decode($val);
985

    
986
  return count(preg_split('/[[:space:]\xC2\xA0]+/', $val));
987
}
988

    
989
/**
990
 * Helper function to deal with submitted values that are arrays.
991
 *
992
 * For example, multiple select component. We flatten the array as a
993
 * comma-separated list to do the comparison.
994
 */
995
function _webform_validation_flatten_array($val) {
996
  if (is_array($val)) {
997
    $arr = array_filter($val, '_webform_validation_check_false');
998
    return implode(',', $arr);
999
  }
1000
  return $val;
1001
}
1002

    
1003
/**
1004
 * Get a array of validator definitions.
1005
 *
1006
 * @return array
1007
 *   Information about validators.
1008
 */
1009
function webform_validation_get_validators() {
1010
  static $validators;
1011
  if ($validators) {
1012
    return $validators;
1013
  }
1014

    
1015
  $validators = module_invoke_all('webform_validation_validators');
1016
  // Let modules use hook_webform_validator_alter($validators) to change
1017
  // validator settings.
1018
  drupal_alter('webform_validator', $validators);
1019

    
1020
  // Remove entries for which only partial information exists.
1021
  foreach ($validators as $validator_key => $validator_info) {
1022
    if (!isset($validator_info['name']) || !isset($validator_info['component_types'])) {
1023
      unset($validators[$validator_key]);
1024
    }
1025
  }
1026

    
1027
  return $validators;
1028
}
1029

    
1030
/**
1031
 * Return an array of the names of the validators.
1032
 *
1033
 * @return array
1034
 *   Array of with keys being validator IDs and values validator names.
1035
 */
1036
function webform_validation_get_validators_selection() {
1037
  $selection = array();
1038
  $validators = webform_validation_get_validators();
1039
  if ($validators) {
1040
    foreach ($validators as $validator_key => $validator_info) {
1041
      $selection[$validator_key] = $validator_info['name'];
1042
    }
1043
  }
1044
  return $selection;
1045
}
1046

    
1047
/**
1048
 * Get a list of valid component types per validator.
1049
 *
1050
 * These are defined via hook_webform_validation_validators(). If "all" is
1051
 * specified, all available component types will be returned.
1052
 */
1053
function webform_validation_valid_component_types($validator) {
1054
  $validators = webform_validation_get_validators();
1055
  if ($info = $validators[$validator]) {
1056
    $allowed_types = $info['component_types'];
1057
    if (in_array('all', $allowed_types, TRUE)) {
1058
      return array_keys(webform_components());
1059
    }
1060
    return $info['component_types'];
1061
  }
1062
}
1063

    
1064
/**
1065
 * Return information about a specified validator.
1066
 *
1067
 * @param string $validator_key
1068
 *   The key of the validator to return information about.
1069
 *
1070
 * @return array
1071
 *   Array of information about the validator.
1072
 */
1073
function webform_validation_get_validator_info($validator_key) {
1074
  $validators = webform_validation_get_validators();
1075
  return $validators[$validator_key];
1076
}
1077

    
1078
/**
1079
 * Handle translatable error messages, if available.
1080
 */
1081
function _webform_validation_i18n_error_message($rule) {
1082
  $rule['error_message'] = filter_xss($rule['error_message']);
1083
  if (module_exists('i18n')) {
1084
    return i18n_string('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
1085
  }
1086
  return $rule['error_message'];
1087
}
1088

    
1089
/**
1090
 * Helper function used by array_filter() to determine if a value is empty.
1091
 *
1092
 * This is used in place of empty() because string '0' needs to be considered
1093
 * not empty and return FALSE.
1094
 *
1095
 * @param mixed $var
1096
 *   The value to check.
1097
 *
1098
 * @return bool
1099
 *   TRUE if $var is not one of FALSE, int 0, or empty string; FALSE otherwise.
1100
 *
1101
 * @see https://www.drupal.org/node/886458
1102
 * @see https://www.drupal.org/node/2638172
1103
 */
1104
function _webform_validation_check_false($var) {
1105
  return $var !== FALSE && $var !== 0 && $var !== '';
1106
}
1107

    
1108
/**
1109
 * Helper function to check numeric data.
1110
 *
1111
 * Process the numeric value validation range that was provided in the numeric
1112
 * validator options.
1113
 */
1114
function _webform_numeric_check_data($data) {
1115
  $range = array('min' => NULL, 'max' => NULL);
1116
  // If no value was specified, don't validate.
1117
  if ($data == '') {
1118
    return $range;
1119
  }
1120

    
1121
  // If only one numeric value was specified, this is the min value.
1122
  if (is_numeric($data)) {
1123
    $range['min'] = (float) $data;
1124
  }
1125

    
1126
  if (strpos($data, '|') !== FALSE) {
1127
    list($min, $max) = explode('|', $data);
1128
    if ($min != '' && is_numeric($min)) {
1129
      $range['min'] = (float) $min;
1130
    }
1131
    if ($max != '' && is_numeric($max)) {
1132
      $range['max'] = (float) $max;
1133
    }
1134
  }
1135
  return $range;
1136
}