Completed
Push — master ( 4e306e...d6d5dd )
by Magnar Ovedal
03:39
created

IntervalChange::test()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\Rule;
6
7
use DateInterval;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use StableSort\StableSort;
11
use Stadly\PasswordPolice\Constraint\Date;
12
use Carbon\CarbonInterval;
13
use Stadly\Date\Interval;
14
use Stadly\PasswordPolice\Password;
15
use Stadly\PasswordPolice\Policy;
16
use Stadly\PasswordPolice\Rule;
17
use Stadly\PasswordPolice\ValidationError;
18
19
final class IntervalChange implements Rule
20
{
21
    /**
22
     * @var Date[] Rule constraints.
23
     */
24
    private $constraints;
25
26
    /**
27
     * @param DateInterval $min Minimum time since last password change.
28
     * @param DateInterval|null $max Maximum time since last password change.
29
     * @param int $weight Constraint weight.
30
     */
31 7
    public function __construct(DateInterval $min, ?DateInterval $max = null, int $weight = 1)
32
    {
33 7
        $this->addConstraint($min, $max, $weight);
34 5
    }
35
36
    /**
37
     * @param DateInterval $min Minimum time since last password change.
38
     * @param DateInterval|null $max Maximum time since last password change.
39
     * @param int $weight Constraint weight.
40
     * @return $this
41
     */
42 2
    public function addConstraint(DateInterval $min, ?DateInterval $max = null, int $weight = 1): self
43
    {
44 2
        $this->constraints[] = new Date($min, $max, $weight);
45
46
        StableSort::usort($this->constraints, function (Date $a, Date $b): int {
47 2
            return $b->getWeight() <=> $a->getWeight();
48 2
        });
49
50 2
        return $this;
51
    }
52
53
    /**
54
     * Check whether a password is in compliance with the rule.
55
     *
56
     * @param Password|string $password Password to check.
57
     * @param int|null $weight Don't consider constraints with lower weights.
58
     * @return bool Whether the password is in compliance with the rule.
59
     */
60 6
    public function test($password, ?int $weight = 1): bool
61
    {
62 6
        $date = $this->getDate($password);
63 6
        $constraint = $this->getViolation($date, $weight);
64
65 6
        return $constraint === null;
66
    }
67
68
    /**
69
     * Validate that a password is in compliance with the rule.
70
     *
71
     * @param Password|string $password Password to validate.
72
     * @return ValidationError|null Validation error describing why the password is not in compliance with the rule.
73
     */
74 5
    public function validate($password): ?ValidationError
75
    {
76 5
        $date = $this->getDate($password);
77 5
        $constraint = $this->getViolation($date);
78
79 5
        if ($constraint !== null) {
80 4
            assert($date !== null);
81 4
            return new ValidationError(
82 4
                $this->getMessage($constraint, $date),
83 4
                $password,
84 4
                $this,
85 4
                $constraint->getWeight()
86
            );
87
        }
88
89 1
        return null;
90
    }
91
92
    /**
93
     * @param DateTimeInterface|null $date When the password was last changed.
94
     * @param int|null $weight Don't consider constraints with lower weights.
95
     * @return Date|null Constraint violated by the count.
96
     */
97 11
    private function getViolation(?DateTimeInterface $date, ?int $weight = null): ?Date
98
    {
99 11
        if ($date === null) {
100 1
            return null;
101
        }
102
103 10
        foreach ($this->constraints as $constraint) {
104 10
            if ($weight !== null && $constraint->getWeight() < $weight) {
105 1
                continue;
106
            }
107 9
            if (!$constraint->test($date)) {
108 9
                return $constraint;
109
            }
110
        }
111
112 4
        return null;
113
    }
114
115
    /**
116
     * @param Password|string $password Password to check when was last changed.
117
     * @return DateTimeInterface|null When the password was last changed.
118
     */
119 11
    private function getDate($password): ?DateTimeInterface
120
    {
121 11
        if ($password instanceof Password) {
122 10
            $formerPasswords = $password->getFormerPasswords();
123
124 10
            if ($formerPasswords !== []) {
125 10
                return reset($formerPasswords)->getDate();
126
            }
127
        }
128 1
        return null;
129
    }
130
131
    /**
132
     * @param Date $constraint Constraint that is violated.
133
     * @param DateTimeInterface $date Date that violates the constraint.
134
     * @return string Message explaining the violation.
135
     */
136 4
    private function getMessage(Date $constraint, DateTimeInterface $date): string
0 ignored issues
show
Unused Code introduced by
The parameter $date is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

136
    private function getMessage(Date $constraint, /** @scrutinizer ignore-unused */ DateTimeInterface $date): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137
    {
138 4
        $translator = Policy::getTranslator();
139 4
        $locale = $translator->getLocale();
140
141 4
        $min = CarbonInterval::instance($constraint->getMin());
142 4
        $min->locale($locale);
143 4
        $minString = $min->forHumans(['join' => true]);
144
145 4
        if ($constraint->getMax() === null) {
146 1
            return $translator->trans(
147 1
                'Must be at least %interval% between password changes.',
148 1
                ['%interval%' => $minString]
149
            );
150
        }
151
152 3
        $max = CarbonInterval::instance($constraint->getMax());
153 3
        $max->locale($locale);
154 3
        $maxString = $max->forHumans(['join' => true]);
155
156 3
        if (0 === Interval::compare(new DateInterval('PT0S'), $constraint->getMin())) {
157 1
            return $translator->trans(
158 1
                'Must be at most %interval% between password changes.',
159 1
                ['%interval%' => $maxString]
160
            );
161
        }
162
163 2
        if (Interval::compare($constraint->getMin(), $constraint->getMax()) === 0) {
164 1
            return $translator->trans(
165 1
                'Must be exactly %interval% between password changes.',
166 1
                ['%interval%' => $minString]
167
            );
168
        }
169
170 1
        return $translator->trans(
171 1
            'Must be between %min% and %max% between password changes.',
172 1
            ['%min%' => $minString, '%max%' => $maxString]
173
        );
174
    }
175
}
176