Projet

Général

Profil

Paste
Télécharger (38 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform_validation / webform_validation.validators.inc @ 5c451ca3

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

    
395
  if (module_exists('email_verify')) {
396
    $validators['email_verify'] = array(
397
      'name' => t('Email Verify'),
398
      'component_types' => array(
399
        'email',
400
      ),
401
      'description' => t('Verifies that an email address actually exists using the <a href="https://drupal.org/project/email_verify">Email Verification module</a>.'),
402
    );
403
  }
404

    
405
  return $validators;
406
}
407

    
408
/**
409
 * Implements hook_webform_validation_validate().
410
 */
411
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
412
  if (!$items) {
413
    return NULL;
414
  }
415

    
416
  // For certain validators, if all components to be compared are email
417
  // components, make the values lower-case to avoid case-sensitive comparison.
418
  if (in_array($validator_name, array('equal', 'comparison', 'unique'))) {
419
    $all_email = TRUE;
420
    foreach ($rule['components'] as $component) {
421
      if ($component['type'] !== 'email') {
422
        $all_email = FALSE;
423
        break;
424
      }
425
    }
426
    if ($all_email) {
427
      $items = array_map('strtolower', $items);
428
    }
429
  }
430

    
431
  // Some components, such as multiple select, send their values as arrays, others don't.
432
  // Convert all to arrays and write the rules to handle them that way. Remove empty values.
433
  foreach ($items as $key => $val) {
434
    $new_values = array();
435
    foreach ((array) $val as $val_key => $val_val) {
436
      if ((string) $val_val !== '') {
437
        $new_values[$val_key] = $val_val;
438
      }
439
    }
440
    $items[$key] = $new_values;
441
  }
442

    
443
  $errors = array();
444
  switch ($validator_name) {
445
    case 'numeric':
446
      $num_range = _webform_numeric_check_data($rule['data']);
447
      foreach ($items as $key => $val) {
448
        foreach ($val as $subval) {
449
          // first check if the value is numeric
450
          if (is_numeric($subval)) {
451
            $subval = (float) $subval;
452
          }
453
          else {
454
            $errors[$key] = t('%item is not numeric.', array('%item' => $components[$key]['name']));
455
            continue;
456
          }
457

    
458
          // now validate the entered numeric value against the validator range settings, if appropriate
459
          // a. validate min & max
460
          if (isset($num_range['min']) && isset($num_range['max'])) {
461
            // validate the min - max range
462
            if ($subval < $num_range['min'] || $subval > $num_range['max']) {
463
              $errors[$key] = t('%item is not within the allowed range %range.', array('%item' => $components[$key]['name'], '%range' => str_replace('|' , ' - ', $rule['data'])));
464
            }
465
          }
466
          else {
467
            // b. validate min
468
            if (isset($num_range['min'])) {
469
              if ($subval < $num_range['min']) {
470
                $errors[$key] = t('%item should be greater than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['min']));
471
              }
472
            }
473
            // c. validate max
474
            if (isset($num_range['max'])) {
475
              if ($subval > $num_range['max']) {
476
                $errors[$key] = t('%item should be less than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['max']));
477
              }
478
            }
479
          }
480
        }
481
      }
482
      return $errors;
483
    case 'min_length':
484
      $min_length = $rule['data'];
485
      foreach ($items as $key => $val) {
486
        foreach ($val as $subval) {
487
          if (drupal_strlen($subval) < $min_length) {
488
            $errors[$key] = t('%item should be at least %num characters long.', array('%item' => $components[$key]['name'], '%num' => $min_length));
489
          }
490
        }
491
      }
492
      return $errors;
493
    case 'max_length':
494
      $max_length = $rule['data'];
495
      foreach ($items as $key => $val) {
496
        foreach ($val as $subval) {
497
          if (drupal_strlen($subval) > $max_length) {
498
            $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
499
          }
500
        }
501
      }
502
      return $errors;
503
    case 'min_words':
504
      $min_words = $rule['data'];
505
      foreach ($items as $key => $val) {
506
        foreach ($val as $subval) {
507
          if (_webform_validation_count_words($subval) < $min_words) {
508
            $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']));
509
            $errors[$key] = $error;
510
          }
511
        }
512
      }
513
      return $errors;
514
    case 'max_words':
515
      $max_words = $rule['data'];
516
      foreach ($items as $key => $val) {
517
        foreach ($val as $subval) {
518
          if (_webform_validation_count_words($subval) > $max_words) {
519
            $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']));
520
            $errors[$key] = $error;
521
          }
522
        }
523
      }
524
      return $errors;
525
    case 'sum':
526
      // Get the numbers to sum.
527
      $sum = array();
528
      foreach ($items as $item) {
529
        if (isset($item[0])) {
530
          $sum[] = $item[0];
531
        }
532
      }
533
      // If none of the components are completed, don't run this validator.
534
      if (!count($sum)) {
535
        return array();
536
      }
537
      $sum = array_sum($sum);
538

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

    
542
      // Parse the comparision operator and do comparison.
543
      module_load_include('inc', 'webform', 'includes/webform.conditionals');
544
      $error = FALSE;
545
      if (substr($rule['data'], 0, 2) === '<=') {
546
        if (!(webform_conditional_compare_floats($sum, $compare_number) <= 0)) {
547
          $error = t('less than or equal to');
548
        }
549
      }
550
      elseif (substr($rule['data'], 0, 1) === '<') {
551
        if (!(webform_conditional_compare_floats($sum, $compare_number) < 0)) {
552
          $error = t('less than');
553
        }
554
      }
555
      elseif (substr($rule['data'], 0, 2) === '>=') {
556
        if (!(webform_conditional_compare_floats($sum, $compare_number) >= 0)) {
557
          $error = t('greater than or equal to');
558
        }
559
      }
560
      elseif (substr($rule['data'], 0, 1) === '>') {
561
        if (!(webform_conditional_compare_floats($sum, $compare_number) > 0)) {
562
          $error = t('greater than');
563
        }
564
      }
565
      else {
566
        if (!(webform_conditional_compare_floats($sum, $compare_number) === 0)) {
567
          $error = t('exactly');
568
        }
569
      }
570
      // Set error if needed.
571
      if ($error) {
572
        $keys = array_keys($items);
573
        $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)));
574
      }
575
      return $errors;
576
    case 'equal':
577
      $first_entry_key = key($items);
578
      $first_entry = array_shift($items);
579
      foreach ($items as $key => $val) {
580
        if ($val !== $first_entry) {
581
          $errors[$key] = t('%item_checked does not match %item_first.', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
582
        }
583
      }
584
      return $errors;
585
    case 'comparison':
586
      foreach (array(1, 2) as $count) {
587
        $entry[$count]['key'] = key($items);
588
        $value = array_shift($items);
589
        if ($components[$entry[$count]['key']]['type'] === 'date') {
590
          if (checkdate((int) $value['month'], (int) $value['day'], (int) $value['year'])) {
591
            $entry[$count]['value'] = date('Y-m-d', mktime(0, 0, 0, (int) $value['month'], (int) $value['day'], (int) $value['year']));
592
          }
593
          else {
594
            $entry[$count]['value'] = NULL;
595
          }
596
        }
597
        elseif ($components[$entry[$count]['key']]['type'] === 'time') {
598
          $time = $value['hour'] . ':' . $value['minute'];
599
          if (!empty($value['ampm'])) {
600
            $time .= ' ' . $value['ampm'];
601
          }
602
          $time = strtotime($time);
603
          if ($time) {
604
            $entry[$count]['value'] = date('H:i', $time);
605
          }
606
          else {
607
            $entry[$count]['value'] = NULL;
608
          }
609
        }
610
        else {
611
          $entry[$count]['value'] = _webform_validation_flatten_array($value);
612
        }
613
      }
614

    
615
      // Don't validate if either are empty.
616
      if ((string) $entry[1]['value'] === '' || (string) $entry[2]['value'] === '') {
617
        return array();
618
      }
619

    
620
      $error = FALSE;
621
      switch ($rule['data']) {
622
        case '>':
623
          if (!($entry[1]['value'] > $entry[2]['value'])) {
624
            $error = TRUE;
625
          }
626
          break;
627
        case '>=':
628
          if (!($entry[1]['value'] >= $entry[2]['value'])) {
629
            $error = TRUE;
630
          }
631
          break;
632
        case '<':
633
          if (!($entry[1]['value'] < $entry[2]['value'])) {
634
            $error = TRUE;
635
          }
636
          break;
637
        case '<=':
638
          if (!($entry[1]['value'] <= $entry[2]['value'])) {
639
            $error = TRUE;
640
          }
641
          break;
642
      }
643

    
644
      if ($error) {
645
        $errors[$entry[2]['key']] = _webform_validation_i18n_error_message($rule);
646
      }
647
      return $errors;
648
    case 'unique':
649
      foreach ($items as $key => $val) {
650
        $items[$key] = _webform_validation_flatten_array($val);
651
      }
652
      // now we count how many times each value appears, and find out which values appear more than once
653
      $items_count = array_count_values(array_map('drupal_strtolower', array_map('trim', $items)));
654
      $doubles = array_filter($items_count, create_function('$x', 'return $x > 1;'));
655
      foreach ($items as $key => $val) {
656
        if (in_array(drupal_strtolower($val), array_keys($doubles))) {
657
          $errors[$key] = t('The value of %item is not unique.', array('%item' => $components[$key]['name']));
658
        }
659
      }
660
      return $errors;
661
    case 'specific_value':
662
      $specific_values = explode(',', $rule['data']);
663
      $specific_values = array_map('trim', $specific_values);
664
      foreach ($items as $key => $val) {
665
        // Selects: Fail if at least one checked and at least one not in the allowed values.
666
        $val = array_filter($val);
667
        $test = count($val) && count(array_diff($val, $specific_values));
668
        _webform_validation_test($errors, $key, $rule, $test);
669
      }
670
      return $errors;
671
    case 'default_value':
672
      foreach ($items as $key => $val) {
673
        $val = _webform_validation_flatten_array($val);
674
        $test = $val != $components[$key]['value'];
675
        _webform_validation_test($errors, $key, $rule, $test);
676
      }
677
      return $errors;
678
    case 'someofseveral':
679
      // Determine the number of components completed.
680
      foreach ($items as $key => $val) {
681
        $items[$key] = _webform_validation_flatten_array($val);
682
        $items[$key] = (bool) strlen($items[$key]);
683
      }
684
      $number_completed = count(array_filter($items));
685

    
686
      // Number being compared with.
687
      $compare_number = (int) preg_replace('/[^0-9]+/', '', $rule['data']);
688
      if ($compare_number < 1) {
689
        $compare_number = 1;
690
      }
691
      elseif ($compare_number > count($rule['components'])) {
692
        $compare_number = count($rule['components']);
693
      }
694

    
695
      // Parse the comparision operator and do comparison.
696
      $error = FALSE;
697
      if (substr($rule['data'], 0, 1) === '=') {
698
        if (!($number_completed === $compare_number)) {
699
          $error = t('exactly');
700
        }
701
      }
702
      elseif (substr($rule['data'], 0, 2) === '<=') {
703
        if (!($number_completed <= $compare_number)) {
704
          $error = t('at most');
705
        }
706
      }
707
      else {
708
        if (!($number_completed >= $compare_number)) {
709
          $error = t('at least');
710
        }
711
      }
712

    
713
      // Set error if needed.
714
      if ($error) {
715
        $keys = array_keys($items);
716
        $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)));
717
      }
718
      return $errors;
719
    case 'select_min':
720
      $min_selections = $rule['data'];
721
      foreach ($items as $key => $val) {
722
        if (count(array_filter($val, '_webform_validation_check_false')) < $min_selections) {
723
          $errors[$key] = t('Please select at least %num options for %item.', array('%num' => $min_selections, '%item' => $components[$key]['name']));
724
        }
725
      }
726
      return $errors;
727
    case 'select_max':
728
      $max_selections = $rule['data'];
729
      foreach ($items as $key => $val) {
730
        if (count(array_filter($val, '_webform_validation_check_false')) > $max_selections) {
731
          $errors[$key] = t('Please select maximum %num options for %item.', array('%num' => $max_selections, '%item' => $components[$key]['name']));
732
        }
733
      }
734
      return $errors;
735
    case 'select_exact':
736
      $allowed_selections = $rule['data'];
737
      foreach ($items as $key => $val) {
738
        $error_strings = array(
739
          'regular' => 'Please select %num options for %item.',
740
          'negated' => 'Please do not select %num options for %item.',
741
        );
742
        $error_vars = array('%num' => $allowed_selections, '%item' => $components[$key]['name']);
743
        $test = count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections;
744
        _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
745
      }
746
      return $errors;
747
    case 'plain_text':
748
      foreach ($items as $key => $val) {
749
        $error_strings = array(
750
          'regular' => '%item only allows the use of plain text.',
751
          'negated' => '%item must contain HTML tags.',
752
        );
753
        $error_vars = array('%item' => $components[$key]['name']);
754
        foreach ($val as $subval) {
755
          $test = strcmp($subval, strip_tags($subval));
756
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
757
        }
758
      }
759
      return $errors;
760
    case 'starts_with':
761
    case 'ends_with':
762
      $pattern = preg_quote($rule['data'], '/');
763
      if ($validator_name === 'starts_with') {
764
        $pattern = '^' . $pattern;
765
        $verb = t('start');
766
      }
767
      else {
768
        $pattern .= '$';
769
        $verb = t('end');
770
      }
771
      $pattern = '/' . $pattern . '/';
772

    
773
      foreach ($items as $key => $val) {
774
        $error_strings = array(
775
          'regular' => '%item must %verb with "%string".',
776
          'negated' => '%item must not %verb with "%string".',
777
        );
778
        $error_vars = array('%item' => $components[$key]['name'], '%verb' => $verb, '%string' => $rule['data']);
779
        foreach ($val as $subval) {
780
          $test = !preg_match($pattern, $subval);
781
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
782
        }
783
      }
784
      return $errors;
785
    case 'pattern':
786
      $pattern = preg_quote($rule['data'], '/');
787
      $pattern = str_replace('@', '[a-zA-Z]', $pattern);
788
      $pattern = str_replace('#', '[0-9]', $pattern);
789
      $pattern = '(' . $pattern . ')';
790
      // Un-escape "|" operator.
791
      $pattern = preg_replace('/(\\s*)(\\\\)(\\|)(\\s*)/', ')|(', $pattern);
792
      foreach ($items as $key => $val) {
793
        foreach ($val as $subval) {
794
          $test = !preg_match('/^(' . $pattern . ')$/', $subval);
795
          _webform_validation_test($errors, $key, $rule, $test);
796
        }
797
      }
798
      return $errors;
799
    case 'regex':
800
    case 'regexi':
801
      mb_regex_encoding('UTF-8');
802
      $regex = $rule['data'];
803
      foreach ($items as $key => $val) {
804
        foreach ($val as $subval) {
805
          if ($validator_name === 'regexi') {
806
            $match = (bool) mb_eregi($regex, $subval);
807
          }
808
          else {
809
            $match = (bool) mb_ereg($regex, $subval);
810
          }
811
          $test = !$match;
812
          _webform_validation_test($errors, $key, $rule, $test);
813
        }
814
      }
815
      return $errors;
816
    case 'must_be_empty':
817
      foreach ($items as $key => $val) {
818
        if (count($val) !== 0) {
819
          $errors[$key] = t('%item does not contain the correct data.', array('%item' => $components[$key]['name']));
820
        }
821
      }
822
      return $errors;
823
    case 'blacklist':
824
      $blacklist = preg_quote($rule['data'], '/');
825
      $blacklist = explode(',', $blacklist);
826
      $blacklist = array_map('trim', $blacklist);
827
      $blacklist = '/\b(' . implode('|', $blacklist) . ')\b/i';
828
      foreach ($items as $key => $val) {
829
        foreach ($val as $subval) {
830
          $test = preg_match($blacklist, $subval);
831
          _webform_validation_test($errors, $key, $rule, $test);
832
        }
833
      }
834
      return $errors;
835
    case 'username':
836
      foreach ($items as $key => $val) {
837
        $error_strings = array(
838
          'regular' => 'The %item field does not match an active username.',
839
          'negated' => 'The %item field matches an active username.',
840
        );
841
        $error_vars = array('%item' => $components[$key]['name']);
842
        foreach ($val as $subval) {
843
          $test = !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $subval))->fetchField();
844
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
845
        }
846
      }
847
      return $errors;
848
    case 'valid_url':
849
      foreach ($items as $key => $val) {
850
        $error_strings = array(
851
          'regular' => '%item does not appear to be a valid URL.',
852
          'negated' => '%item must not be a valid URL.',
853
        );
854
        $error_vars = array('%item' => $components[$key]['name']);
855
        foreach ($val as $subval) {
856
          $test = !valid_url($subval, TRUE);
857
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
858
        }
859
      }
860
      return $errors;
861
    case 'email_verify':
862
      if (module_exists('email_verify')) {
863
        foreach ($items as $key => $val) {
864
          foreach ($val as $subval) {
865
            $error = email_verify_check($subval);
866
            if ($error) {
867
              $errors[$key] = $error;
868
            }
869
          }
870
        }
871
      }
872
      return $errors;
873
  }
874
}
875

    
876
/**
877
 * Return an array of the names of the components in a validation rule.
878
 *
879
 * @param $rule array
880
 *   Array of information about a validation rule.
881
 *
882
 * @return array
883
 *   Array of the filtered names of the components in $rule.
884
 */
885
function _webform_validation_get_names_of_rule_components(array $rule) {
886
  $names = array();
887
  foreach ($rule['components'] as $component) {
888
    $names[] = _webform_filter_xss($component['name']);
889
  }
890
  return $names;
891
}
892

    
893
/**
894
 * Helper function to negate validation rules as needed and create the correct error message.
895
 */
896
function _webform_validation_test(&$errors, $key, $rule, $test, array $error_strings = NULL, array $error_vars = NULL) {
897
  $rule['negate'] = !empty($rule['negate']);
898
  if ($rule['negate']) {
899
    $test = !$test;
900
  }
901
  if ($test) {
902
    if ($error_strings) {
903
      $error = t($error_strings[$rule['negate'] ? 'negated' : 'regular'], $error_vars);
904
    }
905
    else {
906
      $error = _webform_validation_i18n_error_message($rule);
907
    }
908
    $errors[$key] = $error;
909
  }
910
}
911

    
912
/**
913
 * Count the number of words in a value.
914
 *
915
 * Strip HTML first.
916
 */
917
function _webform_validation_count_words($val) {
918
  $val = _webform_validation_flatten_array($val);
919

    
920
  $val = strip_tags($val);
921
  // Replace entities representing spaces with actual spaces.
922
  $val = str_replace('&nbsp;', ' ', $val);
923
  $val = str_replace('&#160;', ' ', $val);
924

    
925
  return str_word_count($val);
926
}
927

    
928
/**
929
 * Helper function to deal with submitted values that are arrays (for example, multiple select component).
930
 * We flatten the array as a comma-separated list to do the comparison.
931
 */
932
function _webform_validation_flatten_array($val) {
933
  if (is_array($val)) {
934
    $arr = array_filter($val, '_webform_validation_check_false');
935
    return implode(',', $arr);
936
  }
937
  return $val;
938
}
939

    
940
/**
941
 * Get a list of validator definitions.
942
 *
943
 * @return
944
 *   Array of information about validators.
945
 */
946
function webform_validation_get_validators() {
947
  $validators = module_invoke_all('webform_validation_validators');
948
  // Let modules use hook_webform_validator_alter($validators) to change validator settings.
949
  drupal_alter('webform_validator', $validators);
950

    
951
  // Remove entries for which only partial information exists.
952
  foreach ($validators as $validator_key => $validator_info) {
953
    if (!isset($validator_info['name']) || !isset($validator_info['component_types'])) {
954
      unset($validators[$validator_key]);
955
    }
956
  }
957

    
958
  return $validators;
959
}
960

    
961
/**
962
 * Return an array of the names of the validators.
963
 *
964
 * @return array
965
 *   Array of with keys being validator IDs and values validator names.
966
 */
967
function webform_validation_get_validators_selection() {
968
  $selection = array();
969
  $validators = webform_validation_get_validators();
970
  if ($validators) {
971
    foreach ($validators as $validator_key => $validator_info) {
972
      $selection[$validator_key] = $validator_info['name'];
973
    }
974
  }
975
  return $selection;
976
}
977

    
978
/**
979
 * Get a list of valid component types per validator, as defined via hook_webform_validation_validators().
980
 * If 'all' is specified, all available component types will be returned.
981
 */
982
function webform_validation_valid_component_types($validator) {
983
  $validators = webform_validation_get_validators();
984
  if ($info = $validators[$validator]) {
985
    $allowed_types = $info['component_types'];
986
    if (in_array('all', $allowed_types, TRUE)) {
987
      return array_keys(webform_components());
988
    }
989
    return $info['component_types'];
990
  }
991
}
992

    
993
/**
994
 * Return information about a specified validator.
995
 *
996
 * @param $validator_key string
997
 *   The key of the validator to return information about.
998
 *
999
 * @return array
1000
 *   Array of information about the validator.
1001
 */
1002
function webform_validation_get_validator_info($validator_key) {
1003
  $validators = webform_validation_get_validators();
1004
  return $validators[$validator_key];
1005
}
1006

    
1007
/**
1008
 * Handle translatable error messages, if available
1009
 */
1010
function _webform_validation_i18n_error_message($rule) {
1011
  $rule['error_message'] = filter_xss($rule['error_message']);
1012
  if (module_exists('i18n')) {
1013
    return i18n_string('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
1014
  }
1015
  return $rule['error_message'];
1016
}
1017

    
1018
/**
1019
 * Helper function used by array_filter to determine if a value was selected or not
1020
 */
1021
function _webform_validation_check_false($var) {
1022
  return $var !== FALSE && $var !== 0;
1023
}
1024

    
1025
/**
1026
 * Process the numeric value validation range that was provided in the numeric validator options
1027
 */
1028
function _webform_numeric_check_data($data) {
1029
  $range = array('min' => NULL, 'max' => NULL);
1030
  // if no value was specified, don't validate
1031
  if ($data == '') {
1032
    return $range;
1033
  }
1034

    
1035
  // If only one numeric value was specified, this is the min value
1036
  if (is_numeric($data)) {
1037
    $range['min'] = (float) $data;
1038
  }
1039

    
1040
  if (strpos($data, '|') !== FALSE) {
1041
    list($min, $max) = explode('|', $data);
1042
    if ($min != '' && is_numeric($min)) {
1043
      $range['min'] = (float) $min;
1044
    }
1045
    if ($max != '' && is_numeric($max)) {
1046
      $range['max'] = (float) $max;
1047
    }
1048
  }
1049
  return $range;
1050
}