Passed
Push — master ( 178b8b...50d73a )
by Magnar Ovedal
03:03
created

ChangeWithIntervalRule::getDate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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

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