Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform_validation / webform_validation.validators.inc @ 74f6bef0

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
        'number',
28
        'textfield',
29
        'hidden',
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
        'number',
43
        'textfield',
44
        'textarea',
45
        'email',
46
        'hidden',
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
        'number',
58
        'textfield',
59
        'textarea',
60
        'email',
61
        'hidden',
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
        'textfield',
73
        'textarea',
74
        'hidden',
75
        'html_textarea',
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
        'textfield',
87
        'textarea',
88
        'hidden',
89
        'html_textarea',
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
    'equal' => array(
98
      'name' => t('Equal values'),
99
      'component_types' => array(
100
        'number',
101
        'textfield',
102
        'email',
103
        'select',
104
        'hidden',
105
      ),
106
      'min_components' => 2,
107
      'description' => t('Verifies that all specified components contain equal values.'),
108
    ),
109
    'comparison' => array(
110
      'name' => t('Compare two values'),
111
      'component_types' => array(
112
        'date',
113
        'email',
114
        'hidden',
115
        'number',
116
        'textfield',
117
        'select',
118
        'time',
119
      ),
120
      'custom_error' => TRUE,
121
      'custom_data' => array(
122
        'label' => t('Comparison operator'),
123
        '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.'),
124
      ),
125
      'min_components' => 2,
126
      'max_components' => 2,
127
      'description' => t('Compare two values for greater than or less than, or equal to.'),
128
    ),
129
    'unique' => array(
130
      'name' => t('Unique values'),
131
      'component_types' => array(
132
        'number',
133
        'textfield',
134
        'email',
135
        'select',
136
        'hidden',
137
      ),
138
      'min_components' => 2,
139
      'description' => t('Verifies that all specified components contain unique values.'),
140
    ),
141
    'specific_value' => array(
142
      'name' => t('Specific value(s)'),
143
      'negatable' => TRUE,
144
      'component_types' => array(
145
        'number',
146
        'select',
147
        'textfield',
148
        'textarea',
149
        'email',
150
        'hidden',
151
      ),
152
      'custom_error' => TRUE,
153
      'custom_data' => array(
154
        'label' => t('(Key) value'),
155
        '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.'),
156
      ),
157
      'max_components' => 1,
158
      'description' => t('Verifies that the value of the specified component is from a list of allowed values.'),
159
    ),
160
    'default_value' => array(
161
      'name' => t('Default value'),
162
      'negatable' => TRUE,
163
      'component_types' => array(
164
        'select',
165
        'number',
166
        'textfield',
167
        'textarea',
168
        'email',
169
        'hidden',
170
      ),
171
      'custom_error' => TRUE,
172
      'description' => t('Verifies that the user-entered value is the default value for that component. Negate if you want not the default value.'),
173
    ),
174
    'oneoftwo' => array(
175
      'name' => t('Require at least one of two fields'),
176
      'component_types' => array(
177
        'number',
178
        'textfield',
179
        'textarea',
180
        'email',
181
        'select',
182
      ),
183
      'min_components' => 2,
184
      'max_components' => 2,
185
      'description' => t('Forces the user to specify / select at least one of two selected webform components'),
186
    ),
187
    'oneofseveral' => array(
188
      'name' => t('Require at least one of several fields'),
189
      'component_types' => array(
190
        'number',
191
        'textfield',
192
        'textarea',
193
        'email',
194
        'select',
195
      ),
196
      'min_components' => 2,
197
      'description' => t('Forces the user to specify / select at least one of several selected webform components.'),
198
    ),
199
    'select_min' => array(
200
      'name' => t('Minimum number of selections required'),
201
      'component_types' => array(
202
        'select',
203
      ),
204
      'custom_data' => array(
205
        'label' => t('Minimum number of selections'),
206
        'description' => t('Specify the minimum number of options a user should select.'),
207
      ),
208
      'description' => t('Forces the user to select at least a defined number of options from the specified webform components.'),
209
    ),
210
    'select_max' => array(
211
      'name' => t('Maximum number of selections allowed'),
212
      'component_types' => array(
213
        'select',
214
      ),
215
      'custom_data' => array(
216
        'label' => t('Maximum number of selections'),
217
        'description' => t('Specify the maximum number of options a user can select.'),
218
      ),
219
      'description' => t('Forces the user to select at most a defined number of options from the specified webform components.'),
220
    ),
221
    'select_exact' => array(
222
      'name' => t('Exact number of selections required'),
223
      'negatable' => TRUE,
224
      'component_types' => array(
225
        'select',
226
      ),
227
      'custom_data' => array(
228
        'label' => t('Number of selections'),
229
        'description' => t('Specify how many options a user can select.'),
230
      ),
231
      'description' => t('Forces the user to select exactly the defined number of options from the specified webform components.'),
232
    ),
233
    'plain_text' => array(
234
      'name' => t('Plain text (disallow tags)'),
235
      'negatable' => TRUE,
236
      'component_types' => array(
237
        'textfield',
238
        'textarea',
239
        'email',
240
        'hidden',
241
      ),
242
      'description' => t('Verifies that user-entered data doesn\'t contain HTML tags.'),
243
    ),
244
    'starts_with' => array(
245
      'name' => t('Starts with'),
246
      'negatable' => TRUE,
247
      'component_types' => array(
248
        'number',
249
        'textfield',
250
        'textarea',
251
        'email',
252
        'hidden',
253
      ),
254
      'custom_data' => array(
255
        'label' => t('Starts with'),
256
        'description' => t('Enter the text that this field must start with.'),
257
      ),
258
      'description' => t('Verifies that user-entered data starts with a given string.'),
259
    ),
260
    'ends_with' => array(
261
      'name' => t('Ends with'),
262
      'negatable' => TRUE,
263
      'component_types' => array(
264
        'number',
265
        'textfield',
266
        'textarea',
267
        'email',
268
        'hidden',
269
      ),
270
      'custom_data' => array(
271
        'label' => t('Ends with'),
272
        'description' => t('Enter the text that this field must end with.'),
273
      ),
274
      'description' => t('Verifies that user-entered data ends with a given string.'),
275
    ),
276
    'pattern' => array(
277
      'name' => 'Pattern',
278
      'component_types' => array(
279
        'textfield',
280
        'hidden',
281
      ),
282
      'custom_error' => TRUE,
283
      'custom_data' => array(
284
        'label' => t('Pattern'),
285
        'description' => t('Specify a pattern where:')
286
        . 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'))))
287
        . t('Examples')
288
        . theme('item_list', array('items' => array(t('North American phone number: (###) ###-####'), t('D-2500 series model numbers: D-25##'), t('UK Postal Code: @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA')))),
289
      ),
290
      'description' => t('Verifies that a user-entered value follows to a specified pattern.'),
291
    ),
292
    'regex' => array(
293
      'name' => t('Regular expression, case-sensitive'),
294
      'negatable' => TRUE,
295
      'component_types' => array(
296
        'number',
297
        'textfield',
298
        'textarea',
299
        'email',
300
        'hidden',
301
      ),
302
      'custom_error' => TRUE,
303
      'custom_data' => array(
304
        'label' => t('Regex code'),
305
        'description' => t('Specify regex code to validate the user input against.'),
306
      ),
307
      'description' => t('Validates user-entered text against a specified case-sensitive regular expression. Note: don\'t include delimiters such as /.'),
308
    ),
309
    'regexi' => array(
310
      'name' => t('Regular expression, case-insensitive'),
311
      'negatable' => TRUE,
312
      'component_types' => array(
313
        'number',
314
        'textfield',
315
        'textarea',
316
        'email',
317
        'hidden',
318
      ),
319
      'custom_error' => TRUE,
320
      'custom_data' => array(
321
        'label' => t('Regex code'),
322
        'description' => t('Specify regex code to validate the user input against.'),
323
      ),
324
      'description' => t('Validates user-entered text against a specified case-insensitive regular expression. Note: don\'t include delimiters such as /.'),
325
    ),
326
    'must_be_empty' => array(
327
      'name' => t('Must be empty'),
328
      'component_types' => array(
329
        'number',
330
        'textfield',
331
        'hidden',
332
      ),
333
      'description' => t('Verifies that a specified textfield remains empty. Recommended use case: used as an anti-spam measure by hiding the element with CSS.'),
334
    ),
335
    'blacklist' => array(
336
      'name' => t('Words blacklist'),
337
      'negatable' => TRUE,
338
      'component_types' => array(
339
        'textfield',
340
        'textarea',
341
        'email',
342
        'hidden',
343
      ),
344
      'custom_error' => TRUE,
345
      'custom_data' => array(
346
        'label' => t('Blacklisted words'),
347
        'description' => t('Specify illegal words, seperated by commas.'),
348
      ),
349
      'description' => t('Validates that user-entered data doesn\'t contain any of the specified illegal words.'),
350
    ),
351
   'username' => array(
352
      'name' => t('Must match a username'),
353
      'negatable' => TRUE,
354
      'component_types' => array(
355
        'textfield',
356
        'hidden',
357
      ),
358
      'description' => t('Validates that user-entered data matches a username.'),
359
    ),
360
    'valid_url' => array(
361
      'name' => 'Valid URL',
362
      'negatable' => TRUE,
363
      'component_types' => array(
364
        'textfield',
365
        'hidden',
366
      ),
367
      'description' => t('Validates that the user-entered data is a valid URL.'),
368
    ),
369
  );
370

    
371
  if (module_exists('email_verify')) {
372
    $validators['email_verify'] = array(
373
      'name' => 'Email Verify',
374
      'component_types' => array(
375
        'email',
376
      ),
377
      'description' => t('Verifies that an email address actually exists using the <a href="https://drupal.org/project/email_verify">Email Verification module</a>.'),
378
    );
379
  }
380

    
381
  return $validators;
382
}
383

    
384
/**
385
 * Implements hook_webform_validation_validate().
386
 */
387
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
388
  if ($items) {
389
    $errors = array();
390
    switch ($validator_name) {
391
      case 'numeric':
392
        $num_range = _webform_numeric_check_data($rule['data']);
393
        foreach ($items as $key => $val) {
394
          if ($val != '') {
395
            // first check if the value is numeric
396
            if (is_numeric($val)) {
397
              $val = (float) $val;
398
            }
399
            else {
400
              $errors[$key] = t('%item is not numeric.', array('%item' => $components[$key]['name']));
401
              continue;
402
            }
403

    
404
            // now validate the entered numeric value against the validator range settings, if appropriate
405
            // a. validate min & max
406
            if (isset($num_range['min']) && isset($num_range['max'])) {
407
              // validate the min - max range
408
              if ($val < $num_range['min'] || $val > $num_range['max']) {
409
                $errors[$key] = t('%item is not within the allowed range %range.', array('%item' => $components[$key]['name'], '%range' => str_replace('|' , ' - ', $rule['data'])));
410
              }
411
            }
412
            else {
413
              // b. validate min
414
              if (isset($num_range['min'])) {
415
                if ($val < $num_range['min']) {
416
                  $errors[$key] = t('%item should be greater than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['min']));
417
                }
418
              }
419
              // c. validate max
420
              if (isset($num_range['max'])) {
421
                if ($val > $num_range['max']) {
422
                  $errors[$key] = t('%item should be less than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['max']));
423
                }
424
              }
425
            }
426
          }
427
        }
428
        return $errors;
429
      case 'min_length':
430
        $min_length = $rule['data'];
431
        foreach ($items as $key => $val) {
432
          if ($val != '' && (drupal_strlen($val) < $min_length)) {
433
            $errors[$key] = t('%item should be at least %num characters long.', array('%item' => $components[$key]['name'], '%num' => $min_length));
434
          }
435
        }
436
        return $errors;
437
      case 'max_length':
438
        $max_length = $rule['data'];
439
        foreach ($items as $key => $val) {
440
          if ($val != '' && (drupal_strlen($val) > $max_length)) {
441
            $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
442
          }
443
        }
444
        return $errors;
445
      case 'min_words':
446
        $min_words = $rule['data'];
447
        foreach ($items as $key => $val) {
448
          if ($val != '' && (_webform_validation_count_words($val) < $min_words)) {
449
            $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']));
450
            $errors[$key] = $error;
451
          }
452
        }
453
        return $errors;
454
      case 'max_words':
455
        $max_words = $rule['data'];
456
        foreach ($items as $key => $val) {
457
          if ($val != '' && (_webform_validation_count_words($val) > $max_words)) {
458
            $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']));
459
            $errors[$key] = $error;
460
          }
461
        }
462
        return $errors;
463
      case 'equal':
464
        $first_entry_key = key($items);
465
        $first_entry = array_shift($items);
466
        $first_entry = _webform_validation_flatten_array($first_entry); // flatten in case of array
467
        // now check if following components equal the first one
468
        foreach ($items as $key => $val) {
469
          $val = _webform_validation_flatten_array($val); // flatten in case of array
470
          if ($val !== $first_entry) {
471
            $errors[$key] = t('%item_checked does not match %item_first.', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
472
          }
473
        }
474
        return $errors;
475
      case 'comparison':
476
        foreach (array(1, 2) as $count) {
477
          $entry[$count]['key'] = key($items);
478
          $value = array_shift($items);
479
          if ($components[$entry[$count]['key']]['type'] === 'date') {
480
            if (checkdate((int) $value['month'], (int) $value['day'], (int) $value['year'])) {
481
              $entry[$count]['value'] = date('Y-m-d', mktime(0, 0, 0, (int) $value['month'], (int) $value['day'], (int) $value['year']));
482
            }
483
            else {
484
              $entry[$count]['value'] = NULL;
485
            }
486
          }
487
          elseif ($components[$entry[$count]['key']]['type'] === 'time') {
488
            $time = $value['hour'] . ':' . $value['minute'];
489
            if (!empty($value['ampm'])) {
490
              $time .= ' ' . $value['ampm'];
491
            }
492
            $time = strtotime($time);
493
            if ($time) {
494
              $entry[$count]['value'] = date('H:i', $time);
495
            }
496
            else {
497
              $entry[$count]['value'] = NULL;
498
            }
499
          }
500
          else {
501
            $entry[$count]['value'] = _webform_validation_flatten_array($value);
502
          }
503
        }
504

    
505
        // Don't validate if either are empty.
506
        if (empty($entry[1]['value']) || empty($entry[2]['value'])) {
507
          return;
508
        }
509

    
510
        $error = FALSE;
511
        switch ($rule['data']) {
512
          case '>':
513
            if (!($entry[1]['value'] > $entry[2]['value'])) {
514
              $error = TRUE;
515
            }
516
            break;
517
          case '>=':
518
            if (!($entry[1]['value'] >= $entry[2]['value'])) {
519
              $error = TRUE;
520
            }
521
            break;
522
          case '<':
523
            if (!($entry[1]['value'] < $entry[2]['value'])) {
524
              $error = TRUE;
525
            }
526
            break;
527
          case '<=':
528
            if (!($entry[1]['value'] <= $entry[2]['value'])) {
529
              $error = TRUE;
530
            }
531
            break;
532
        }
533

    
534
        if ($error) {
535
          $errors[$entry[2]['key']] = _webform_validation_i18n_error_message($rule);
536
        }
537
        return $errors;
538
      case 'unique':
539
        foreach ($items as $key => $val) {
540
          if (is_array($val)) {
541
            // make sure to flatten arrays first
542
            $items[$key] = _webform_validation_flatten_array($val);
543
          }
544
          if (empty($items[$key])) {
545
            // items without a value selected shouldn't be validated
546
            unset($items[$key]);
547
          }
548
        }
549
        // now we count how many times each value appears, and find out which values appear more than once
550
        $items_count = array_count_values(array_map('drupal_strtolower', array_map('trim', $items)));
551
        $doubles = array_filter($items_count, create_function('$x', 'return $x > 1;'));
552
        foreach ($items as $key => $val) {
553
          if (in_array(drupal_strtolower($val), array_keys($doubles))) {
554
            $errors[$key] = t('The value of %item is not unique.', array('%item' => $components[$key]['name']));
555
          }
556
        }
557
        return $errors;
558
      case 'specific_value':
559
        $specific_values = explode(',', $rule['data']);
560
        $specific_values = array_map('trim', $specific_values);
561
        foreach ($items as $key => $val) {
562
          if (is_array($val)) {
563
            // Selects: Fail if at least one checked and at least one not in the allowed values.
564
            $val = array_filter($val);
565
            $test = count($val) && count(array_diff($val, $specific_values));
566
          }
567
          else {
568
            // Fail if not empty and not in the allowed values.
569
            $test = $val && !in_array($val, $specific_values);
570
          }
571
          _webform_validation_test($errors, $key, $rule, $test);
572
        }
573
        return $errors;
574
      case 'default_value':
575
        foreach ($items as $key => $val) {
576
          if (is_array($val)) {
577
            $val = _webform_validation_flatten_array($val);
578
          }
579
          $test = $val != $components[$key]['value'];
580
          _webform_validation_test($errors, $key, $rule, $test);
581
        }
582
        return $errors;
583
      case 'oneoftwo':
584
        // $components should have 2 items
585
        $keys = array_keys($items);
586
        $item1 = array_shift($keys);
587
        $item2 = array_shift($keys);
588
        $entry1 = _webform_validation_flatten_array($items[$item1]);
589
        $entry2 = _webform_validation_flatten_array($items[$item2]);
590
        if (empty($entry1) && empty($entry2)) {
591
          return array($item1 => t('You have to specify %item1 or %item2 (or both).', array('%item1' => $components[$item1]['name'], '%item2' => $components[$item2]['name'])));
592
        }
593
        return;
594
      case 'oneofseveral':
595
        foreach ($items as $key => $val) {
596
          if (is_array($val)) {
597
            // make sure to flatten arrays first
598
            $items[$key] = _webform_validation_flatten_array($val);
599
          }
600
        }
601
        // $components should have at least one of several items
602
        if (count(array_filter($items)) < 1) {
603
          $keys = array_keys($items);
604
          $names = array();
605
          foreach ($keys as $value) {
606
            $names[] = _webform_filter_xss($components[$value]['name']);
607
          }
608
          return array($keys[0] => t('You have to specify at least one of these items:') . theme('item_list', array('items' => $names)));
609
        }
610
        return;
611
      case 'select_min':
612
        $min_selections = $rule['data'];
613
        foreach ($items as $key => $val) {
614
          if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) < $min_selections)) {
615
            $errors[$key] = t('Please select at least %num options for %item.', array('%num' => $min_selections, '%item' => $components[$key]['name']));
616
          }
617
        }
618
        return $errors;
619
      case 'select_max':
620
        $max_selections = $rule['data'];
621
        foreach ($items as $key => $val) {
622
          if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) > $max_selections)) {
623
            $errors[$key] = t('Please select maximum %num options for %item.', array('%num' => $max_selections, '%item' => $components[$key]['name']));
624
          }
625
        }
626
        return $errors;
627
      case 'select_exact':
628
        $allowed_selections = $rule['data'];
629
        foreach ($items as $key => $val) {
630
          $error_strings = array(
631
            'regular' => 'Please select %num options for %item.',
632
            'negated' => 'Please do not select %num options for %item.',
633
          );
634
          $error_vars = array('%num' => $allowed_selections, '%item' => $components[$key]['name']);
635
          $test = is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections);
636
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
637
        }
638
        return $errors;
639
      case 'plain_text':
640
        foreach ($items as $key => $val) {
641
          $error_strings = array(
642
            'regular' => '%item only allows the use of plain text.',
643
            'negated' => '%item must contain HTML tags.',
644
          );
645
          $error_vars = array('%item' => $components[$key]['name']);
646
          $test = $val != '' && (strcmp($val, strip_tags($val)));
647
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
648
        }
649
        return $errors;
650
      case 'starts_with':
651
      case 'ends_with':
652
        $pattern = _webform_validation_preg_quote($rule['data'], '/');
653
        if ($validator_name === 'starts_with') {
654
          $pattern = '^' . $pattern;
655
          $verb = t('start');
656
        }
657
        else {
658
          $pattern .= '$';
659
          $verb = t('end');
660
        }
661
        $pattern = '/' . $pattern . '/';
662

    
663
        foreach ($items as $key => $val) {
664
          $val = (string) $val;
665
          $error_strings = array(
666
            'regular' => '%item must %verb with "%string".',
667
            'negated' => '%item must not %verb with "%string".',
668
          );
669
          $error_vars = array('%item' => $components[$key]['name'], '%verb' => $verb, '%string' => $rule['data']);
670
          $test = $val && !preg_match($pattern, $val);
671
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
672
        }
673
        return $errors;
674
      case 'pattern':
675
        $pattern = _webform_validation_preg_quote($rule['data'], '/');
676
        $pattern = str_replace('@', '[a-zA-Z]', $pattern);
677
        $pattern = str_replace('#', '[0-9]', $pattern);
678
        $pattern = '(' . $pattern . ')';
679
        // Un-escape "|" operator.
680
        $pattern = preg_replace('/(\\s*)(\\\\)(\\|)(\\s*)/', ')|(', $pattern);
681
        foreach ($items as $key => $val) {
682
          $val = (string) $val;
683
          $test = $val !== '' && !preg_match('/^(' . $pattern . ')$/', $val);
684
          _webform_validation_test($errors, $key, $rule, $test);
685
        }
686
        return $errors;
687
      case 'regex':
688
      case 'regexi':
689
        mb_regex_encoding('UTF-8');
690
        $regex = $rule['data'];
691
        foreach ($items as $key => $val) {
692
          $val = (string) $val;
693
          if ($val) {
694
            if ($validator_name === 'regexi') {
695
              $match = (bool) mb_eregi($regex, $val);
696
            }
697
            else {
698
              $match = (bool) mb_ereg($regex, $val);
699
            }
700
            $test = !$match;
701
            _webform_validation_test($errors, $key, $rule, $test);
702
          }
703
        }
704
        return $errors;
705
      case 'must_be_empty':
706
        foreach ($items as $key => $val) {
707
          if ($val) {
708
            $errors[$key] = t('%item does not contain the correct data.', array('%item' => $components[$key]['name']));
709
          }
710
        }
711
        return $errors;
712
      case 'blacklist':
713
        $blacklist = _webform_validation_preg_quote($rule['data'], '/');
714
        $blacklist = explode(',', $blacklist);
715
        $blacklist = array_map('trim', $blacklist);
716
        $blacklist = '/\b(' . implode('|', $blacklist) . ')\b/i';
717
        foreach ($items as $key => $val) {
718
          $test = $val != '' && preg_match($blacklist, $val);
719
          _webform_validation_test($errors, $key, $rule, $test);
720
        }
721
        return $errors;
722
      case 'username':
723
        foreach ($items as $key => $val) {
724
          $error_strings = array(
725
            'regular' => 'The %item field does not match an active username.',
726
            'negated' => 'The %item field matches an active username.',
727
          );
728
          $error_vars = array('%item' => $components[$key]['name']);
729
          $test = $val != '' && !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $val))->fetchField();
730
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
731
        }
732
        return $errors;
733
      case 'valid_url':
734
        foreach ($items as $key => $val) {
735
          $error_strings = array(
736
            'regular' => '%item does not appear to be a valid URL.',
737
            'negated' => '%item must not be a valid URL.',
738
          );
739
          $error_vars = array('%item' => $components[$key]['name']);
740
          $test = $val && !valid_url($val, TRUE);
741
          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
742
        }
743
        return $errors;
744
      case 'email_verify':
745
        if (module_exists('email_verify')) {
746
          foreach ($items as $key => $val) {
747
            $error = email_verify_check($val);
748
            if ($error) {
749
              $errors[$key] = $error;
750
            }
751
          }
752
        }
753
        return $errors;
754
    }
755
  }
756
}
757

    
758
/**
759
 * Helper function to negate validation rules as needed and create the correct error message.
760
 */
761
function _webform_validation_test(&$errors, $key, $rule, $test, array $error_strings = NULL, array $error_vars = NULL) {
762
  $rule['negate'] = !empty($rule['negate']);
763
  if ($rule['negate']) {
764
    $test = !$test;
765
  }
766
  if ($test) {
767
    if ($error_strings) {
768
      $error = t($error_strings[$rule['negate'] ? 'negated' : 'regular'], $error_vars);
769
    }
770
    else {
771
      $error = _webform_validation_i18n_error_message($rule);
772
    }
773
    $errors[$key] = $error;
774
  }
775
}
776

    
777
/**
778
 * Count the number of words in a value.
779
 */
780
function _webform_validation_count_words($val) {
781
  // Deal with values from HTML textareas.
782
  if (is_array($val) && isset($val['value'])) {
783
    $val = strip_tags($val['value']);
784
    // Replace entities representing spaces with actual spaces.
785
    $val = str_replace('&nbsp;', ' ', $val);
786
    $val = str_replace('&#160;', ' ', $val);
787
  }
788
  else {
789
    $val = _webform_validation_flatten_array($val);
790
  }
791
  return str_word_count($val);
792
}
793

    
794
/**
795
 * Fixed version of preg_quote() to work around PHP bug #47229. Remove when
796
 * PHP_VERSION < 5.3 is no longer supported.
797
 */
798
function _webform_validation_preg_quote($str, $delimiter = NULL) {
799
  $str = preg_quote($str, $delimiter);
800
  if (PHP_VERSION < 5.3) {
801
    $str = str_replace('-', '\-', $str);
802
  }
803
  return $str;
804
}
805

    
806
/**
807
 * Helper function to deal with submitted values that are arrays (e.g. multiple select component)
808
 * We flatten the array as a comma-separated list to do the comparison.
809
 */
810
function _webform_validation_flatten_array($val) {
811
  if (is_array($val)) {
812
    $arr = array_filter($val, '_webform_validation_check_false');
813
    return implode(',', $arr);
814
  }
815
  return $val;
816
}
817

    
818
/**
819
 * Get a list of validator definitions
820
 */
821
function webform_validation_get_validators() {
822
  $validators = module_invoke_all('webform_validation_validators');
823
  // let modules use hook_webform_validator_alter($validators) to change validator settings
824
  drupal_alter('webform_validator', $validators);
825
  return $validators;
826
}
827

    
828
/**
829
 * @todo Please document this function.
830
 * @see http://drupal.org/node/1354
831
 */
832
function webform_validation_get_validators_selection() {
833
  $selection = array();
834
  $validators = webform_validation_get_validators();
835
  if ($validators) {
836
    foreach ($validators as $validator_key => $validator_info) {
837
      $selection[$validator_key] = $validator_info['name'];
838
    }
839
  }
840
  return $selection;
841
}
842

    
843
/**
844
 * Get a list of valid component types per validator, as defined via hook_webform_validation_validators().
845
 * If 'all' is specified, all available component types will be returned.
846
 */
847
function webform_validation_valid_component_types($validator) {
848
  $validators = webform_validation_get_validators();
849
  if ($info = $validators[$validator]) {
850
    $allowed_types = $info['component_types'];
851
    if (_webform_validation_all_allowed($allowed_types)) {
852
      return array_keys(webform_components());
853
    }
854
    return $info['component_types'];
855
  }
856
}
857

    
858
/**
859
 * Helper function to check whether all components are allowed to be used for a certain validator
860
 */
861
function _webform_validation_all_allowed($allowed) {
862
  if ($allowed) {
863
    foreach ($allowed as $type) {
864
      if ($type == 'all') {
865
        return TRUE;
866
      }
867
    }
868
  }
869
  return FALSE;
870
}
871

    
872
/**
873
 * @todo Please document this function.
874
 * @see http://drupal.org/node/1354
875
 */
876
function webform_validation_get_validator_info($validator_key) {
877
  $validators = webform_validation_get_validators();
878
  return $validators[$validator_key];
879
}
880

    
881
/**
882
 * Handle translatable error messages, if available
883
 */
884
function _webform_validation_i18n_error_message($rule) {
885
  $rule['error_message'] = filter_xss($rule['error_message']);
886
  if (module_exists('i18nstrings')) {
887
    return i18nstrings('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
888
  }
889
  return $rule['error_message'];
890
}
891

    
892
/**
893
 * Helper function used by array_filter to determine if a value was selected or not
894
 */
895
function _webform_validation_check_false($var) {
896
  return $var !== FALSE && $var !== 0;
897
}
898

    
899
/**
900
 * Process the numeric value validation range that was provided in the numeric validator options
901
 */
902
function _webform_numeric_check_data($data) {
903
  $range = array('min' => NULL, 'max' => NULL);
904
  // if no value was specified, don't validate
905
  if ($data == '') {
906
    return $range;
907
  }
908

    
909
  // If only one numeric value was specified, this is the min value
910
  if (is_numeric($data)) {
911
    $range['min'] = (float) $data;
912
  }
913

    
914
  if (strpos($data, '|') !== FALSE) {
915
    list($min, $max) = explode('|', $data);
916
    if ($min != '' && is_numeric($min)) {
917
      $range['min'] = (float) $min;
918
    }
919
    if ($max != '' && is_numeric($max)) {
920
      $range['max'] = (float) $max;
921
    }
922
  }
923
  return $range;
924
}