Passed
Push — master ( ae0a03...686af4 )
by Magnar Ovedal
03:17
created

Change::getMin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
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 InvalidArgumentException;
11
use Carbon\CarbonInterval;
12
use Stadly\Date\Interval;
13
use Stadly\PasswordPolice\Password;
14
use Stadly\PasswordPolice\Policy;
15
16
final class Change implements RuleInterface
17
{
18
    /**
19
     * @var DateInterval|null Minimum time between password changes.
20
     */
21
    private $min;
22
23
    /**
24
     * @var DateInterval|null Maximum time between password changes.
25
     */
26
    private $max;
27
28
    /**
29
     * @param DateInterval|null $min Minimum time between password changes.
30
     * @param DateInterval|null $max Maximum time between password changes.
31
     */
32 7
    public function __construct(?DateInterval $min, ?DateInterval $max = null)
33
    {
34 7
        if ($min === null) {
35 2
            if ($max === null) {
36 1
                throw new InvalidArgumentException('Both min and max cannot be unconstrained.');
37
            }
38 1
            if (0 < Interval::compare(new DateInterval('PT0S'), $max)) {
39 1
                throw new InvalidArgumentException('Max cannot be negative.');
40
            }
41
        } else {
42 5
            if (0 < Interval::compare(new DateInterval('PT0S'), $min)) {
43 1
                throw new InvalidArgumentException('Min cannot be negative.');
44
            }
45 4
            if ($max !== null && 0 < Interval::compare($min, $max)) {
46 1
                throw new InvalidArgumentException('Max cannot be smaller than min.');
47
            }
48
        }
49
50 4
        $this->min = $min;
51 4
        $this->max = $max;
52 4
    }
53
54
    /**
55
     * @return DateInterval|null Minimum time between password changes.
56
     */
57 1
    public function getMin(): ?DateInterval
58
    {
59 1
        return $this->min;
60
    }
61
62
    /**
63
     * @return DateInterval|null Maximum time between password changes.
64
     */
65 1
    public function getMax(): ?DateInterval
66
    {
67 1
        return $this->max;
68
    }
69
70
    /**
71
     * Check whether a password is in compliance with the rule.
72
     *
73
     * @param Password|string $password Password to check.
74
     * @return bool Whether the password is in compliance with the rule.
75
     */
76 5
    public function test($password): bool
77
    {
78 5
        $date = $this->getNoncompliantDate($password);
79
80 5
        return $date === null;
81
    }
82
83
    /**
84
     * Enforce that a password is in compliance with the rule.
85
     *
86
     * @param Password|string $password Password that must adhere to the rule.
87
     * @throws RuleException If the password does not adhrere to the rule.
88
     */
89 2
    public function enforce($password): void
90
    {
91 2
        $date = $this->getNoncompliantDate($password);
92
93 2
        if ($date !== null) {
94 1
            throw new RuleException($this, $this->getMessage());
95
        }
96 1
    }
97
98
    /**
99
     * @param Password|string $password Password to check when was last changed.
100
     * @return DateTimeInterface|null When the password was last changed if not in compliance with the rule.
101
     */
102 7
    private function getNoncompliantDate($password): ?DateTimeInterface
103
    {
104 7
        $date = $this->getDate($password);
105
106 7
        if ($date !== null) {
107 6
            $now = new DateTimeImmutable();
108 6
            if ($this->min !== null && $now->sub($this->min) < $date) {
109 2
                return $date;
110
            }
111
112 4
            if ($this->max !== null && $date < $now->sub($this->max)) {
113 1
                return $date;
114
            }
115
        }
116
117 4
        return null;
118
    }
119
120
    /**
121
     * @param Password|string $password Password to check when was last changed.
122
     * @return DateTimeInterface|null When the password was last changed.
123
     */
124 7
    private function getDate($password): ?DateTimeInterface
125
    {
126 7
        if ($password instanceof Password) {
127 6
            $formerPasswords = $password->getFormerPasswords();
128
129 6
            if ($formerPasswords !== []) {
130 6
                return reset($formerPasswords)->getDate();
131
            }
132
        }
133 1
        return null;
134
    }
135
136
    /**
137
     * {@inheritDoc}
138
     */
139 4
    public function getMessage(): string
140
    {
141 4
        $translator = Policy::getTranslator();
142 4
        $locale = $translator->getLocale();
143
144 4
        $min = $this->min === null ? null : CarbonInterval::instance($this->min)->locale($locale);
145 4
        $max = $this->max === null ? null : CarbonInterval::instance($this->max)->locale($locale);
146
147 4
        if ($this->max === null) {
148 1
            return $translator->trans(
149 1
                'Must be at least %interval% between password changes.',
150 1
                ['%interval%' => $min]
151
            );
152
        }
153
154 3
        if ($this->min === null) {
155 1
            return $translator->trans(
156 1
                'Must be at most %interval% between password changes.',
157 1
                ['%interval%' => $max]
158
            );
159
        }
160
161 2
        if (Interval::compare($this->min, $this->max) === 0) {
162 1
            return $translator->trans(
163 1
                'Must be exactly %interval% between password changes.',
164 1
                ['%interval%' => $min]
165
            );
166
        }
167
168 1
        return $translator->trans(
169 1
            'Must be between %min% and %max% between password changes.',
170 1
            ['%min%' => $min, '%max%' => $max]
171
        );
172
    }
173
}
174