Controller_Validator_Basic   D
last analyzed

Complexity

Total Complexity 143

Size/Duplication

Total Lines 1122
Duplicated Lines 5.17 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
dl 58
loc 1122
rs 4.4102
c 0
b 0
f 0
wmc 143
lcom 2
cbo 1

67 Methods

Rating   Name   Duplication   Size   Complexity  
A to_strip_regex() 0 6 1
A rule_to_trim() 0 4 1
A rule_to_ltrim() 0 4 1
A rule_to_rtrim() 0 4 1
A rule_to_alpha_num() 0 4 1
A rule_to_lower() 0 4 1
A rule_to_upper_words() 0 4 1
A rule_to_add_slashes() 0 4 1
A init() 0 12 1
A rule_required() 0 6 4
A rule_len() 0 10 2
A rule_in() 0 7 2
A rule_not_in() 0 7 2
A rule_between() 0 13 4
A rule_gt() 9 9 2
A rule_lt() 9 9 2
A rule_gte() 9 9 2
A rule_lte() 9 9 2
A rule_eq() 0 9 2
A rule_eqf() 8 8 2
A rule_number() 0 6 2
A rule_decimal() 0 6 2
A rule_phone() 0 6 2
A rule_decimal_places() 0 8 2
A rule_int() 0 6 2
A rule_alpha() 0 7 2
A rule_alphanum() 0 7 2
A rule_type() 0 6 1
A rule_class() 0 9 2
A rule_bool() 0 13 2
A rule_true() 0 11 2
A rule_false() 0 11 2
B validate_iso_date() 0 18 6
B rule_iso_time() 0 21 6
A rule_iso_datetime() 0 20 4
A rule_before() 7 7 2
A rule_after() 7 7 2
A rule_email() 0 6 2
A rule_zip() 0 6 2
B rule_credit_card() 0 41 6
A rule_card_to_date() 0 7 2
A rule_card_from_date() 0 7 2
A rule_to_int() 0 4 1
A rule_to_number() 0 4 1
A rule_to_float() 0 4 1
A rule_to_digits_and_single_spaces() 0 6 1
A rule_to_strip_space() 0 4 1
A rule_to_strip_extra_space() 0 4 1
A rule_to_alpha() 0 4 1
A rule_to_alpha_unicode() 0 4 1
A rule_to_upper() 0 4 1
A rule_to_alpha_num_unicode() 0 4 1
A rule_to_alpha_num_dash() 0 4 1
A rule_to_truncate() 0 6 1
A rule_to_truncate_custom() 0 7 1
A rule_to_alpha_num_dash_unicode() 0 4 1
A rule_to_iso_date() 0 6 1
A rule_to_strip_tags() 0 4 1
A rule_to_quote_meta() 0 4 1
A rule_to_strip_slashes() 0 4 1
A rule_to_strip_nasties() 0 4 1
A rule_to_zip() 0 6 1
D rule_to_name() 0 283 16
A strip_excess_whitespace() 0 4 1
A prep_in_vals() 0 13 2
A rule_regex() 0 13 3
C card_date_parser() 0 51 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Controller_Validator_Basic often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Controller_Validator_Basic, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
class Controller_Validator_Basic extends Controller_Validator_Abstract
4
{
5
    public function init()
6
    {
7
        parent::init();
8
9
        $this->alias = array_merge(
10
            $this->alias,
11
            array(
12
                'same' => 'eq',
13
                'different' => 'ne',
14
            )
15
        );
16
    }
17
18
    /* \section Value Test Rules: General */
19
20
    public function rule_required($a)
21
    {
22
        if ($a === '' || $a === false || $a === null) {
23
            return $this->fail('must not be empty');
24
        }
25
    }
26
27
    public function rule_len($a)
28
    {
29
        if (!is_string($a)) {
30
            return $this->fail('must be a string');
31
        }
32
33
        $this->prefix = 'length of ';
34
35
        return strlen($a);
36
    }
37
38
    /**
39
     * Requires a regex pattern as the
40
     * next rule in the chain.
41
     *
42
     * Please give your rule a custom error message.
43
     */
44
    public function rule_regex($a)
45
    {
46
        $opt = array();
47
        $rule = $this->pullRule();
48
        if ($rule[0] != '/') {
49
            $rule = '/^'.$rule.'*$/';
50
        }
51
        $opt['regexp'] = $rule;
52
53
        if (!filter_var($a, FILTER_VALIDATE_REGEXP, ['options' => $opt])) {
54
            return $this->fail('does not match the pattern');
55
        }
56
    }
57
58
    /* \section Value Test Rules: Value Lists */
59
60
    /**
61
     * Checks that value is in the list provided.
62
     * Syntax: |in|foo,bar,foobar.
63
     */
64
    public function rule_in($a)
65
    {
66
        $vals = $this->prep_in_vals($a);
67
        if (!in_array($a, $vals)) {
68
            return $this->fail('has an invalid value');
69
        }
70
    }
71
72
    /**
73
     * Checks that value is not in the list provided.
74
     * Syntax: |not_in|foo,bar,foobar.
75
     */
76
    public function rule_not_in($a)
77
    {
78
        $vals = $this->prep_in_vals($a);
79
        if (in_array($a, $vals)) {
80
            return $this->fail('has an invalid value');
81
        }
82
    }
83
84
    /* \section Value Comparison Rules: absolute value */
85
86
    /**
87
     * Inclusive range check.
88
     *
89
     * Overloaded: checks value for numbers,
90
     * string-length for other values.
91
     *
92
     * Next 2 rules must specify the min and max
93
     */
94
    public function rule_between($a)
95
    {
96
        $min = $this->pullRule(true);
97
        $max = $this->pullRule(true);
98
99
        if (is_numeric($a)) {
100
            if ($a < $min || $a > $max) {
101
                return $this->fail('must be between {{arg1}} and {{arg2}}', $min, $max);
102
            }
103
        } else {
104
            return $this->fail('Must be a numeric value');
105
        }
106
    }
107
108 View Code Duplication
    public function rule_gt($a)
109
    {
110
        $target = $this->pullRule(true);
111
        if ($a <= $target) {
112
            return $this->fail('must be greater than {{arg1}}', $target);
113
        }
114
115
        return $a;
116
    }
117
118 View Code Duplication
    public function rule_lt($a)
119
    {
120
        $target = $this->pullRule(true);
121
        if ($a >= $target) {
122
            return $this->fail('must be less than {{arg1}}', $target);
123
        }
124
125
        return $a;
126
    }
127
128 View Code Duplication
    public function rule_gte($a)
129
    {
130
        $target = $this->pullRule(true);
131
        if ($a < $target) {
132
            return $this->fail('must be at least {{arg1}}', $target);
133
        }
134
135
        return $a;
136
    }
137
138 View Code Duplication
    public function rule_lte($a)
139
    {
140
        $target = $this->pullRule(true);
141
        if ($a > $target) {
142
            return $this->fail('must be not greater than {{arg1}}', $target);
143
        }
144
145
        return $a;
146
    }
147
148
    public function rule_eq($a)
149
    {
150
        $target = $this->pullRule();
151
        if ($a !== $target) {
152
            return $this->fail('must be exactly {{arg1}}', $target);
153
        }
154
155
        return $a;
156
    }
157
158 View Code Duplication
    public function rule_eqf($a)
159
    {
160
        $target=$this->pullRule(true);
161
        if ($a !== $this->get($target)) {
162
            return $this->fail('must be same as {{arg1}}', $target);
163
        }
164
        return $a;
165
    }
166
167
    /* \section Value Test Rules: Numeric */
168
169
    /**
170
     * Checks for int or float.
171
     */
172
    public function rule_number($a)
173
    {
174
        if (!is_numeric($a)) {
175
            return $this->fail('must be a number');
176
        }
177
    }
178
179
    public function rule_decimal($a)
180
    {
181
        if (!preg_match('/^[0-9]+\.[0-9]+$/', $a)) {
182
            return $this->fail('must be a decimal number: eg 12.34');
183
        }
184
    }
185
186
    public function rule_phone($a)
187
    {
188
        if (strlen(preg_replace('/[^0-9]/', '', $a)) < 6) {
189
            return $this->fail('must contain valid phone number');
190
        }
191
    }
192
193
    /**
194
     * Checks for a specific number of
195
     * decimal places.
196
     *
197
     * Arg: int- number of places
198
     */
199
    public function rule_decimal_places($a)
200
    {
201
        $places = $this->pullRule();
202
        $pattern = sprintf('/^[0-9]+\.[0-9]{%s}$/', $places);
203
        if (!preg_match($pattern, $a)) {
204
            return $this->fail('Must have {{arg1}} decimal places', $places);
205
        }
206
    }
207
208
    public function rule_int($a)
209
    {
210
        if (!preg_match('/^[0-9]*$/', $a)) {
211
            return $this->fail('Must be an integer: eg 1234');
212
        }
213
    }
214
215
    /* \section Value Test Rules: String */
216
217
    /**
218
     * Test for A-Za-z.
219
     */
220
    public function rule_alpha($a)
221
    {
222
        $msg = 'must contain only letters';
223
        if (!preg_match('/^([A-Za-z])*$/', $a)) {
224
            return $this->fail($msg);
225
        }
226
    }
227
228
    /**
229
     * Test for A-Za-z0-9.
230
     */
231
    public function rule_alphanum($a)
232
    {
233
        $msg = 'must contain only digits and letters';
234
        if (!preg_match('/^([a-zA-Z0-9])*$/', $a)) {
235
            return $this->fail($msg);
236
        }
237
    }
238
239
    public function rule_type($a)
240
    {
241
        $this->prefix = 'type of ';
242
243
        return gettype($a);
244
    }
245
246
    public function rule_class($a)
247
    {
248
        if (!is_object($a)) {
249
            return $this->fail('is not an object');
250
        }
251
        $this->prefix = 'class of ';
252
253
        return get_class($a);
254
    }
255
256
    /* \section Value Test Rules: Boolean */
257
258
    /**
259
     * Validate for true|false|t|f|1|0|yes|no|y|n.
260
     *
261
     * Normalizes to lower case
262
     */
263
    public function rule_bool($a)
264
    {
265
        // We don't use PHP inbuilt test - a bit restrictive
266
267
        // Changes PHP true/false to 1, 0
268
        $a = strtolower($a);
269
270
        $vals = array('true', 'false', 't', 'f', 1, 0, 'yes', 'no', 'y', 'n');
271
272
        if (!in_array($a, $vals)) {
273
            return $this->fail('Must be a boolean value');
274
        }
275
    }
276
277
    /**
278
     * Validate for true|t|1|yes|y.
279
     *
280
     * Normalizes to lower case
281
     *
282
     * Useful for 'if' rules
283
     */
284
    public function rule_true($a)
285
    {
286
        // Changes PHP true to 1
287
        $a = strtolower($a);
288
289
        $vals = array('true', 't', 1, 'yes', 'y');
290
291
        if (!in_array($a, $vals)) {
292
            return $this->fail('Must be true');
293
        }
294
    }
295
296
    /**
297
     * Validate for false|f|0|no|n.
298
     *
299
     * Normalizes to lower case
300
     *
301
     * Useful for 'if' rules
302
     */
303
    public function rule_false($a)
304
    {
305
        // Changes PHP false to 0
306
        $a = strtolower($a);
307
308
        $vals = array('false', 'f', 0, 'no', 'n');
309
310
        if (!in_array($a, $vals)) {
311
            return $this->fail('Must be false');
312
        }
313
    }
314
315
    /* \section Value Test Rules: Date & Time */
316
317
    /**
318
     * Validate for ISO date in format YYYY-MM-DD.
319
     *
320
     * Also checks for valid month and day values
321
     */
322
    public function validate_iso_date($a)
323
    {
324
        $date = explode('-', $a);
325
326
        $msg = 'Must be date in format: YYYY-MMM-DD';
327
328
        if (count($date) != 3) {
329
            return $this->fail($msg);
330
        }
331
332
        if (strlen($date[0]) !== 4 || strlen($date[1]) !== 2 || strlen($date[2]) !== 2) {
333
            return $this->fail($msg);
334
        }
335
336
        if (!@checkdate($date[1], $date[2], $date[0])) {
337
            return $this->fail($msg);
338
        }
339
    }
340
341
    /**
342
     * Validate for ISO time.
343
     *
344
     * Requires a complete hour:minute:second time with
345
     * the optional ':' separators.
346
     *
347
     * Checks for hh:mm[[:ss][.**..] where * = microseconds
348
     * Also checks for valid # of hours, mins, secs
349
     */
350
    public function rule_iso_time($a)
351
    {
352
        $pattern = "/^([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:(?:\.[0-9]{1,}))?)?$/";
353
        $msg = 'Must be a valid ISO time';
354
355
        if (preg_match($pattern, $a, $matches)) {
356
            if ($matches[1] > 24) {
357
                return $this->fail($msg);
358
            }
359
360
            if ($matches[2] > 59) {
361
                return $this->fail($msg);
362
            }
363
364
            if (isset($matches[3]) && $matches[3] > 59) {
365
                return $this->fail($msg);
366
            }
367
        } else {
368
            return $this->fail($msg);
369
        }
370
    }
371
372
    /**
373
     * Validate ISO datetime in the format:.
374
     *
375
     * YYYY-MM-DD hh:mm:ss with optional microseconds
376
     */
377
    public function rule_iso_datetime($a)
378
    {
379
        $parts = explode(' ', $a);
380
        $msg = 'Must be a valid ISO datetime';
381
382
        if (count($parts) != 2) {
383
            return $this->fail($msg);
384
        }
385
386
        try {
387
            $this->rule_iso_date($parts[0]);
0 ignored issues
show
Bug introduced by
The method rule_iso_date() does not exist on Controller_Validator_Basic. Did you maybe mean rule_iso_datetime()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
388
        } catch (Exception $e) {
389
            return $this->fail($msg);
390
        }
391
        try {
392
            $this->rule_iso_time($parts[1]);
393
        } catch (Exception $e) {
394
            return $this->fail($msg);
395
        }
396
    }
397
398
    /**
399
     * Checks any PHP datetime format:
400
     * http://www.php.net/manual/en/datetime.formats.date.php.
401
     */
402 View Code Duplication
    public function rule_before($a)
403
    {
404
        $time = $this->pullRule();
405
        if (strtotime($a) >= strtotime($time)) {
406
            return $this->fail('Must be before {{arg1}}', $time);
407
        }
408
    }
409
410
    /**
411
     * Checks any PHP datetime format:
412
     * http://www.php.net/manual/en/datetime.formats.date.php.
413
     */
414 View Code Duplication
    public function rule_after($a)
415
    {
416
        $time = $this->pullRule();
417
        if (strtotime($a) <= strtotime($time)) {
418
            return $this->fail('Must be after {{arg1}}', $time);
419
        }
420
    }
421
422
    /* \section Value Test Rules: Postal, Email & Credit Card */
423
424
    public function rule_email($a)
425
    {
426
        if (!filter_var($a, FILTER_VALIDATE_EMAIL)) {
427
            return $this->fail('Must be a valid email address');
428
        }
429
    }
430
431
    /**
432
     * Checks for a 5 digit or extended US zip.
433
     */
434
    public function rule_zip($a)
435
    {
436
        if (!preg_match('/^\d{5}(-\d{4})?$/', $a)) {
437
            return $this->fail('Must be a valid ZIP code');
438
        }
439
    }
440
441
    /**
442
     * Validate for credit card number.
443
     *
444
     * Uses the Luhn Mod 10 check
445
     */
446
    public function rule_credit_card($a)
447
    {
448
        // Card formats keep changing and there is too high a risk
449
        // of false negatives if we get clever. So we just check it
450
        // with the Luhn Mod 10 formula
451
452
        // Calculate the Luhn check number
453
454
        $msg = 'Not a valid card number';
455
456
        $sum = 0;
457
        $alt = false;
458
459
        for ($i = strlen($a) - 1; $i >= 0; --$i) {
460
            $n = substr($a, $i, 1);
461
            if ($alt) {
462
                //square n
463
                $n *= 2;
464
                if ($n > 9) {
465
                    //calculate remainder
466
                    $n = ($n % 10) + 1;
467
                }
468
            }
469
            $sum += $n;
470
            $alt = !$alt;
471
        }
472
473
        // If $sum divides exactly by 10 it's valid
474
475
        if (!($sum % 10 == 0)) {
476
            return $this->fail($msg);
477
        } else {
478
            // Luhn check seems to return true for any string of 0s
479
480
            $stripped = str_replace('0', '', $a);
481
482
            if (strlen($stripped) == 0) {
483
                return $this->fail($msg);
484
            }
485
        }
486
    }
487
488
    /**
489
     * Validate a card "expires end" date.
490
     */
491
    public function rule_card_to_date($a)
492
    {
493
        $msg = 'Not a valid date';
494
        if (!$this->card_date_parser($a, 'to')) {
495
            return $this->fail($msg);
496
        }
497
    }
498
499
    /**
500
     * Validate a card "valid from" date.
501
     */
502
    public function rule_card_from_date($a)
503
    {
504
        $msg = 'Not a valid date';
505
        if (!$this->card_date_parser($a, 'from')) {
506
            return $this->fail($msg);
507
        }
508
    }
509
510
    /* \section Value Conversion Rules: General */
511
512
    /**
513
     * Strips out the characters matched by the
514
     * pattern in the argument.
515
     *
516
     * The pattern should include the pattern delimiter, eg:
517
     *
518
     * /my_pattern/
519
     */
520
    public function to_strip_regex($a)
521
    {
522
        $pattern = $this->pullRule();
523
524
        return preg_replace($pattern, '', $a);
525
    }
526
527
    /* \section Value Conversion Rules: Numeric */
528
529
    public function rule_to_int($a)
530
    {
531
        return (int) $a = preg_replace('/[^0-9]/', '', $a);
532
    }
533
534
    public function rule_to_number($a)
535
    {
536
        return (int) $a = preg_replace('/[^0-9\.]/', '', $a);
537
    }
538
539
    public function rule_to_float($a)
540
    {
541
        return (int) $a = preg_replace('/[^0-9\.]/', '', $a);
542
    }
543
544
    public function rule_to_digits_and_single_spaces($a)
545
    {
546
        $a = preg_replace("/[^\d ]/", '', $a);
547
548
        return $this->rule_strip_extra_space($a);
0 ignored issues
show
Bug introduced by
The method rule_strip_extra_space() does not exist on Controller_Validator_Basic. Did you maybe mean rule_to_strip_extra_space()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
549
    }
550
551
    /* \section Value Conversion Rules: String */
552
553
    public function rule_to_trim($a)
554
    {
555
        return trim($a);
556
    }
557
558
    public function rule_to_ltrim($a)
559
    {
560
        return ltrim($a);
561
    }
562
563
    public function rule_to_rtrim($a)
564
    {
565
        return rtrim($a);
566
    }
567
568
    /**
569
     * Strip out all white space.
570
     */
571
    public function rule_to_strip_space($a)
572
    {
573
        return preg_replace("/\s/", '', $a);
574
    }
575
576
    /**
577
     * Reduce sequential whitespaces to a single space.
578
     */
579
    public function rule_to_strip_extra_space($a)
580
    {
581
        return $this->strip_excess_whitespace($a);
582
    }
583
584
    /**
585
     * Strip to A-Za-z0-9.
586
     */
587
    public function rule_to_alpha($a)
588
    {
589
        return preg_replace('/[^a-zA-Z]/', '', $a);
590
    }
591
592
    /**
593
     * Test for unicode letter characters.
594
     *
595
     * Should work even is PCRE compiled
596
     * without "--enable-unicode-properties".
597
     */
598
    public function rule_to_alpha_unicode($a)
599
    {
600
        return preg_replace('/(*UTF8)[^\p{L}]/u', '', $a);
601
    }
602
603
    public function rule_to_alpha_num($a)
604
    {
605
        return preg_replace('/[^a-zA-Z0-9]/', '', $a);
606
    }
607
608
    public function rule_to_lower($a)
609
    {
610
        return  strtolower($a);
611
    }
612
613
    public function rule_to_upper($a)
614
    {
615
        return $this->mb_str_to_upper($a);
0 ignored issues
show
Documentation Bug introduced by
The method mb_str_to_upper does not exist on object<Controller_Validator_Basic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
616
    }
617
618
    public function rule_to_upper_words($a)
619
    {
620
        return $this->mb_str_to_upper_words($a);
0 ignored issues
show
Documentation Bug introduced by
The method mb_str_to_upper_words does not exist on object<Controller_Validator_Basic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
621
    }
622
623
    /* \section Value Conversion Rules: Alphanumeric */
624
625
    /**
626
     * Strip to unicode letter characters and 0-9.
627
     *
628
     * Should work even is PCRE compiled
629
     * without "--enable-unicode-properties".
630
     */
631
    public function rule_to_alpha_num_unicode($a)
632
    {
633
        return preg_replace('/(*UTF8)[^\p{L}0-9]/u', '', $a);
634
    }
635
636
    public function rule_to_alpha_num_dash($a)
637
    {
638
        return preg_replace('/[^a-zA-Z0-9_-]/', '', $a);
639
    }
640
641
    /**
642
     * Truncates, and adds '...' to
643
     * end of string.
644
     *
645
     * Requires parameter 'length'
646
     */
647
    public function rule_to_truncate($a)
648
    {
649
        $len = $this->pullRule();
650
651
        return $this->mb_truncate($a, $len);
0 ignored issues
show
Documentation Bug introduced by
The method mb_truncate does not exist on object<Controller_Validator_Basic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
652
    }
653
654
    /**
655
     * Requires parameters: length,
656
     * custom string to end of string.
657
     */
658
    public function rule_to_truncate_custom($a)
659
    {
660
        $len = $this->pullRule();
661
        $append = $this->pullRule();
662
663
        return $this->mb_truncate($a, $len, $append);
0 ignored issues
show
Documentation Bug introduced by
The method mb_truncate does not exist on object<Controller_Validator_Basic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
664
    }
665
666
    /**
667
     * Strip to unicode letter characters and 0-9, -, _.
668
     *
669
     * Requires PCRE compiled with  "--enable-unicode-properties".
670
     * Most distros these days will offer this
671
     */
672
    public function rule_to_alpha_num_dash_unicode($a)
673
    {
674
        return preg_replace('/(*UTF8)[^\p{L}0-9_-]/u', '', $a);
675
    }
676
677
    /* \section Value Conversion Rules: Date & Time */
678
679
    public function rule_to_iso_date($a)
680
    {
681
        $a = preg_replace("/[^T0-9\/\-\(\): ]/", '', $a);
682
683
        return $this->rule_iso_date($a);
0 ignored issues
show
Bug introduced by
The method rule_iso_date() does not exist on Controller_Validator_Basic. Did you maybe mean rule_iso_datetime()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
684
    }
685
686
    /* \section Value Conversion Rules: Data Sanitization */
687
688
    public function rule_to_strip_tags($a)
689
    {
690
        return strip_tags($a);
691
    }
692
693
    public function rule_to_quote_meta($a)
694
    {
695
        return quotemeta($a);
696
    }
697
698
    public function rule_to_add_slashes($a)
699
    {
700
        return addslashes($a);
701
    }
702
703
    public function rule_to_strip_slashes($a)
704
    {
705
        return stripslashes($a);
706
    }
707
708
    /**
709
     * Strip out attack characters from names & addresses
710
     * and other strings where they have no place.
711
     *
712
     * Strips: * ^ <> ? ! () | / \ [] + = % ; ~ `
713
     */
714
    public function rule_to_strip_nasties($a)
715
    {
716
        return preg_replace("|[\*\^<?>!\"\(\)\|\\\\/\[\]\+=#%;~`]|", '', $a);
717
    }
718
719
    /* \section Value Conversion Rules: Phone, Name, Address */
720
721
    /**
722
     * Normalizes, then validates.
723
     */
724
    public function rule_to_zip($a)
725
    {
726
        // Change 12345 1234 to 12345-1234
727
        $a = preg_replace('/[^0-9-]/', '', $a);
728
        $this->rule_zip($a);
729
    }
730
731
    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
732
    // PERSONAL NAMES (European style)
733
    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
734
735
    /**
736
     * Useful for cleaning up input where you don't want to present
737
     * an error to the user - eg a checkout where ease of use is
738
     * more important than accuracy. Can save a lot of re-keying.
739
     *
740
     * Some libraries don't clean up names if already in mixed case.
741
     * Experience shows this isn't very useful, as many users
742
     * will type mAry, JOSepH etc.
743
     *
744
     * Can only be a best guess - but much better than nothing:
745
     * has been in production for years without any negative
746
     * customer feedback.
747
     *
748
     * Set $is_capitalise_prefix if you want prefixes in upper:
749
     * Von Trapp vs von Trapp. You would do this to format
750
     * a last name for use in salutations:
751
     *
752
     * Dear Mr Von Trapp
753
     *
754
     * Can handle full names, 1st only, middle only, last only.
755
     *
756
     * Cleans up extra whitespace.
757
     */
758
    public function rule_to_name($name, $is_capitalise_prefix = false)
759
    {
760
        /*
761
        A name can have up to 5 components, space delimited:
762
763
        Worst case:
764
765
            salutation  |  forenames 	| prefix(es)	|	main name 	|	suffix
766
            Ms          |  Jo-Sue Ellen	| de la 		| 	Mer-Savarin | 	III
767
768
        Rules for forenames
769
770
        1) Capitalise 1st char and after a hyphen.
771
772
        Rules for special case prefixes: von, de etc
773
774
        1) Set capitalisation at runtime.
775
776
            There seem to be no fixed rules, but
777
            lower case is commonly used as part of a
778
            whole name:
779
780
            John von Trapp
781
782
            While it is normally capitalised as part of
783
            a salutation:
784
785
            Dear Mr Von Trapp
786
787
            By default we store in the lower case form.
788
            Set the param $is_capitalise_prefix to true
789
            to capitalise.
790
791
        2) In default mode, St is capitalised,
792
            other prefixes lower cased. We retain user's
793
            choice of punctuation with St./St
794
795
        Rules for main name:
796
797
        1) Capitalise after a hyphen in the main name:
798
799
            Smythington-Fenwick
800
801
        2) Capitalise after Mc at start of name -
802
            this is pretty much a universal rule
803
804
            MCDONALD => McDonald
805
806
        3) Unless user has capitalised after the M,
807
            do NOT capitalise after Mac at start of name:
808
809
            - Many Scottish Mac names
810
                are not capitalised: Macaulay, Macdonald
811
812
            - Many non-Scottish names start with Mac:
813
                eg Macon - we want to avoid "MacOn";
814
815
            - The Cpan name modules and some style manuals
816
                force MacDonald, but this seems to
817
                create more problems than it solves.
818
819
            macrae => Macrae
820
821
        4) Capitalise after O'
822
823
            o'grady => O'Grady
824
        */
825
826
        // If name string is empty, bail out
827
828
        if (empty($name)) {
829
            return '';
830
        }
831
832
        // Setup special case prefix lookup list.
833
        // These are prefixes that are not capitalised.
834
835
        // Prefixes which are capitalised such as "St"
836
        // can be omitted.
837
838
        // We omit prefixes that are also common names,
839
        // such as "Della", "Di" and "Ben"
840
841
        $prefixes = array(
842
            'ap' => array('upper' => 'Ap', 'lower' => 'ap'),
843
            'da' => array('upper' => 'Da', 'lower' => 'da'),
844
            'de' => array('upper' => 'De', 'lower' => 'de'),
845
            'del' => array('upper' => 'Del', 'lower' => 'del'),
846
            'der' => array('upper' => 'Der', 'lower' => 'der'),
847
            'du' => array('upper' => 'Du', 'lower' => 'du'),
848
            'la' => array('upper' => 'La', 'lower' => 'la'),
849
            'le' => array('upper' => 'Le', 'lower' => 'le'),
850
            'lo' => array('upper' => 'Lo', 'lower' => 'lo'),
851
            'van' => array('upper' => 'Van', 'lower' => 'van'),
852
            'von' => array('upper' => 'Von', 'lower' => 'von'),
853
           );
854
855
        // Set up suffix lookup list
856
857
        // We preserve user's preferred punctuation: Sr./Sr
858
859
        $suffixes = array(
860
                        'i' => 'I',
861
                        'ii' => 'II',
862
                        'iii' => 'III',
863
                        'iv' => 'IV',
864
                        'v' => 'V',
865
                        'vi' => 'VI',
866
                        'vii' => 'VII',
867
                        'viii' => 'VIII',
868
                        'ix' => 'IX',
869
                        'x' => 'X',
870
                        'jr.' => 'Jr.',
871
                        'jr' => 'Jr',
872
                        'jnr.' => 'Jnr.',
873
                        'jnr' => 'Jnr',
874
                        'sr.' => 'Sr.',
875
                        'sr' => 'Sr',
876
                        'snr.' => 'Snr.',
877
                        'snr' => 'Snr',
878
                        '1st' => '1st',
879
                        '2nd' => '2nd',
880
                        '3rd' => '3rd',
881
                        '4th' => '4th',
882
                        '5th' => '5th',
883
                        '6th' => '6th',
884
                        '7th' => '7th',
885
                        '8th' => '8th',
886
                        '9th' => '9th',
887
                        '10th' => '10th',
888
                        '1st.' => '1st.',
889
                        '2nd.' => '2nd.',
890
                        '3rd.' => '3rd.',
891
                        '4th.' => '4th.',
892
                        '5th.' => '5th.',
893
                        '6th.' => '6th.',
894
                        '7th.' => '7th.',
895
                        '8th.' => '8th.',
896
                        '9th.' => '9th.',
897
                        '10th.' => '10th.',
898
                       );
899
900
        // Clean out extra whitespace
901
902
        $name = $this->strip_excess_whitespace(trim($name));
903
904
        // Try to parse into forenames, main name, suffix
905
906
        $parts = explode(' ', $name);
907
908
        if (count($parts) == 1) {
909
            // Must be the main name
910
911
            $name_main = array_pop($parts);
912
            $name_fname = false;
913
            $name_suffix = false;
914
        } else {
915
            // We have more than one part to parse
916
917
            // Is the last part a suffix?
918
            // We assume name can have only one suffix
919
920
            $part = array_pop($parts);
921
            $normalised_part = strtolower($part);
922
923
            if (array_key_exists($normalised_part, $suffixes)) {
924
                // Last part is a suffix
925
926
                $name_main = array_pop($parts);
927
                $name_suffix = $suffixes[$normalised_part];
928
            } else {
929
                // Last part is the main name
930
931
                $name_main = $part;
932
                $name_suffix = false;
933
            }
934
        }
935
936
        // Anything left is a salutation, initial or forname
937
938
        if (count($parts) > 0) {
939
            $name_fnames = $parts;
940
        } else {
941
            $name_fnames = false;
942
        }
943
944
        // We build the name from first to last:
945
946
        $new_name = array();
947
948
        // Set case for the forenames
949
950
        if ($name_fnames) {
951
            foreach ($name_fnames as $fname) {
952
                $parts = array();
953
                $fname = strtolower($fname);
954
955
                // Do hypenated parts separately
956
957
                $exploded_fname = explode('-', $fname);
958
959
                foreach ($exploded_fname as $part) {
960
                    // If it is one of our special case prefixes
961
                    // we use the appropriate value
962
                    // Else, we capitalise
963
964
                    if (array_key_exists($part, $prefixes)) {
965
                        if ($is_capitalise_prefix !== false) {
966
                            $parts[] = $prefixes[$part]['upper'];
967
                        } else {
968
                            $parts[] = $prefixes[$part]['lower'];
969
                        }
970
                    } else {
971
                        // It is a normal forename, salutation or initial
972
                        // We capitalise it.
973
974
                        $parts[] = ucfirst($part);
975
                    }
976
                }
977
978
                $new_name[] = implode('-', $parts);
979
            }
980
        }
981
982
        // Set case for the main name
983
984
        $name_main_original = $name_main;
985
        $name_main = strtolower($name_main);
986
987
        // Do hypenated parts separately
988
989
        $exploded_main_original = explode('-', $name_main_original);
990
        $exploded_main = explode('-', $name_main);
991
992
        $parts = array();
993
994
        foreach ($exploded_main as $key => $part) {
995
            $part_original = $exploded_main_original[$key];
996
997
            if (substr($part, 0, 2) == 'mc') {
998
                // Do "Mc"
999
1000
                // Uppercase the 3rd character
1001
1002
                $a = substr($part, 2);
1003
                $parts[] = 'Mc'.ucfirst($a);
1004
            } elseif (substr($part, 0, 3) == 'mac') {
1005
                // Do "Mac"
1006
1007
                // Lowercase the 3rd character
1008
                // unless user has submitted
1009
                // a correct looking name
1010
1011
                if (preg_match('|^Mac[A-Z][a-z]*$|', $part_original)) {
1012
                    $parts[] = $part_original;
1013
                } else {
1014
                    $parts[] = ucfirst($part);
1015
                }
1016
            } elseif (substr($part, 0, 2) == "o'") {
1017
                // Do O'
1018
                // Uppercase the 3rd character
1019
1020
                $a = substr($part, 2);
1021
                $parts[] = "O'".ucwords($a);
1022
            } else {
1023
                // It is a plain-jane name
1024
1025
                $parts[] = ucfirst($part);
1026
            }
1027
        }
1028
1029
        $new_name[] = implode('-', $parts);
1030
1031
        if ($name_suffix !== false) {
1032
            $new_name[] = $name_suffix;
1033
        }
1034
1035
        // Assemble the new name
1036
1037
        $output = implode(' ', $new_name);
1038
1039
        return $output;
1040
    }
1041
1042
    /* \section Helper Functions */
1043
1044
    /**
1045
     * Reduce sequential whitespaces to a single space.
1046
     */
1047
    protected function strip_excess_whitespace($a)
1048
    {
1049
        return preg_replace('/\s\s+/', ' ', $a);
1050
    }
1051
1052
    /**
1053
     * Helper for validating card to and from dates.
1054
     */
1055
    protected function card_date_parser($a, $type)
1056
    {
1057
        // Strip out any slash
1058
1059
        $date = str_replace('/', '', $a);
1060
1061
        // Check that we have 4 digits
1062
1063
        if (!preg_match('|^[0-9]{4}$|', $date)) {
1064
            return false;
1065
        }
1066
1067
        $month = substr($date, 0, 2);
1068
        $year = substr($date, 2, 2);
1069
1070
        // Check month is logical
1071
1072
        if ($month > 12) {
1073
            return false;
1074
        }
1075
1076
        $parts = array(date('Y'), date('m'), 1);
1077
        $now_datetime = new DateTime(implode('-', $parts));
1078
1079
        $parts = array('20'.$year, $month, '1');
1080
        $card_datetime = new DateTime(implode('-', $parts));
1081
1082
        $interval = $now_datetime->diff($card_datetime);
1083
        $days = $interval->format('%R%a days');
1084
1085
        if ($type == 'from') {
1086
            // Check from date is older or equal to current month
1087
1088
            if ($days <= 0 && $days > -3650) {
1089
                return true;
1090
            } else {
1091
                return false;
1092
            }
1093
        } elseif ($type == 'to') {
1094
            // Check to date is newer or equal to current month
1095
1096
            if ($days >= 0 && $days < 3650) {
1097
                return true;
1098
            } else {
1099
                return false;
1100
            }
1101
        } else {
1102
            $msg = "Bad date type '$type' in card-date validation";
1103
            throw new Error($msg);
1104
        }
1105
    }
1106
1107
    /**
1108
     * Explode and trim a comma-delmited list
1109
     * for the in and not_in rules.
1110
     */
1111
    protected function prep_in_vals($a)
1112
    {
1113
        $vals = $this->pullRule();
1114
        if (is_array($vals)) {
1115
            return $vals;
1116
        }
1117
        $vals = explode(',', $vals);
1118
        array_walk($vals, function ($val) {
1119
            return trim($val);
1120
        });
1121
       // create_function('&$val', '$val = trim($val);'));
1122
        return $vals;
1123
    }
1124
}
1125