NotSetInIntervalRule::getViolation()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

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

143
        /** @scrutinizer ignore-unused */ DateTimeInterface $date,

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...
144
        TranslatorInterface $translator
145
    ): string {
146 4
        $constraintMin = $constraint->getMin();
147 4
        $constraintMax = $constraint->getMax();
148
149 4
        $minString = $constraintMin === null ? '' : $constraintMin->format('Y-m-d H:i:s');
150 4
        $maxString = $constraintMax === null ? '' : $constraintMax->format('Y-m-d H:i:s');
151
152 4
        if ($constraintMax === null) {
153 1
            return $translator->trans(
154 1
                'The password must have been set before %date%.',
155 1
                ['%date%' => $minString]
156
            );
157
        }
158
159 3
        if ($constraintMin === null) {
160 1
            return $translator->trans(
161 1
                'The password must have been set after %date%.',
162 1
                ['%date%' => $maxString]
163
            );
164
        }
165
166 2
        if ($constraintMin->format(DateTime::RFC3339_EXTENDED) === $constraintMax->format(DateTime::RFC3339_EXTENDED)) {
167 1
            return $translator->trans(
168 1
                'The password must have been set before or after %date%.',
169 1
                ['%date%' => $minString]
170
            );
171
        }
172
173 1
        return $translator->trans(
174 1
            'The password must have been set before %min% or after %max%.',
175 1
            ['%min%' => $minString, '%max%' => $maxString]
176
        );
177
    }
178
}
179