ITAlgorithm::isFollowPattern()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 9
rs 10
1
<?php
2
3
namespace LeKoala\Tin\Algo;
4
5
use LeKoala\Tin\Exception\TINException;
6
use LeKoala\Tin\Util\DateUtil;
7
use LeKoala\Tin\Util\StringUtil;
8
9
/**
10
 * Italy
11
 */
12
class ITAlgorithm extends TINAlgorithm
13
{
14
    const LENGTH = 16;
15
    const PATTERN = "[a-zA-Z]{6}[LMNPQRSTUVlmnpqrstuv0-9]{2}[ABCDEHLMPRSTabcdehlmprst]([0Ll][1-9]|[1Mm2Nn4Qq5Rr6Ss][0-9]|[3Pp7Tt][0-1])[a-zA-Z][LMNPQRSTUVlmnpqrstuv0-9]{3}[a-zA-Z]";
16
    const IT_ID_LIST_PATH = "/resources/IT_ID_list";
17
    private static $listSet = [];
18
19
    public function __construct()
20
    {
21
        $filename = dirname(dirname(__DIR__)) . self::IT_ID_LIST_PATH;
22
        if (!is_file($filename) || !is_readable($filename)) {
23
            throw new TINException("Could not read resource file");
24
        }
25
        $arr = file($filename, FILE_IGNORE_NEW_LINES);
26
        self::$listSet = $arr;
27
    }
28
29
    public static function setResultDateValidation($set)
30
    {
31
        self::$listSet = $set;
32
    }
33
34
    public function validate(string $tin)
35
    {
36
        if (!$this->isFollowLength($tin)) {
37
            return StatusCode::INVALID_LENGTH;
38
        }
39
        if (!$this->isFollowPattern($tin) || !$this->isValidDate($tin)) {
40
            return StatusCode::INVALID_PATTERN;
41
        }
42
        if (!$this->isFollowRules($tin)) {
43
            return StatusCode::INVALID_SYNTAX;
44
        }
45
        return StatusCode::VALID;
46
    }
47
48
    public function isFollowLength(string $tin)
49
    {
50
        return StringUtil::isFollowLength($tin, self::LENGTH);
51
    }
52
53
    public function isFollowPattern(string $tin)
54
    {
55
        $code = StringUtil::substring($tin, 11, 12) . $this->convertCharToNumber(StringUtil::substring($tin, 12, 15));
56
57
        $containsUpper = in_array(strtoupper($code), self::$listSet);
58
        $containsLower = in_array(strtolower($code), self::$listSet);
59
        $matchPattern = StringUtil::isFollowPattern($tin, self::PATTERN);
60
61
        return ($containsUpper || $containsLower) && $matchPattern;
62
    }
63
64
    public function isFollowRules(string $tin)
65
    {
66
        return $this->isFollowRuleItalia($tin);
67
    }
68
69
    public function isFollowRuleItalia(string $tin)
70
    {
71
        $sum = 0;
72
        for ($i = 0; $i < 15; $i++) {
73
            if ($i % 2 == 0) {
74
                $sum += $this->convertOddCharacter($tin[$i]);
75
            } else {
76
                $sum += $this->convertEvenCharacter($tin[$i]);
77
            }
78
        }
79
        $remainderBy26 = $sum % 26;
80
        $c16 = $tin[15];
81
        $check = StringUtil::getAlphabeticalPosition($c16) - 1;
82
83
        return $remainderBy26 == $check;
84
    }
85
86
    protected function convertEvenCharacter(string $c)
87
    {
88
        if (is_numeric($c)) {
89
            return intval($c);
90
        }
91
        return StringUtil::getAlphabeticalPosition($c) - 1;
92
    }
93
94
    protected function convertOddCharacter(string $c)
95
    {
96
        $normalizedChar = $c;
97
        if (!is_numeric($normalizedChar)) {
98
            $normalizedChar = strtoupper($normalizedChar);
99
        }
100
        switch ($normalizedChar) {
101
            case '0':
102
            case 'A':
103
                return 1;
104
            case '1':
105
            case 'B':
106
                return 0;
107
            case '2':
108
            case 'C':
109
                return 5;
110
            case '3':
111
            case 'D':
112
                return 7;
113
            case '4':
114
            case 'E':
115
                return 9;
116
            case '5':
117
            case 'F':
118
                return 13;
119
            case '6':
120
            case 'G':
121
                return 15;
122
            case '7':
123
            case 'H':
124
                return 17;
125
            case '8':
126
            case 'I':
127
                return 19;
128
            case '9':
129
            case 'J':
130
                return 21;
131
            case 'K':
132
                return 2;
133
            case 'L':
134
                return 4;
135
            case 'M':
136
                return 18;
137
            case 'N':
138
                return 20;
139
            case 'O':
140
                return 11;
141
            case 'P':
142
                return 3;
143
            case 'Q':
144
                return 6;
145
            case 'R':
146
                return 8;
147
            case 'S':
148
                return 12;
149
            case 'T':
150
                return 14;
151
            case 'U':
152
                return 16;
153
            case 'V':
154
                return 10;
155
            case 'W':
156
                return 22;
157
            case 'X':
158
                return 25;
159
            case 'Y':
160
                return 24;
161
            case 'Z':
162
                return 23;
163
            default:
164
                return -1;
165
        }
166
    }
167
168
    protected function getMonthNumber(string $m)
169
    {
170
        switch (strtoupper($m)) {
171
            case 'A':
172
                return 1;
173
            case 'B':
174
                return 2;
175
            case 'C':
176
                return 3;
177
            case 'D':
178
                return 4;
179
            case 'E':
180
                return 5;
181
            case 'H':
182
                return 6;
183
            case 'L':
184
                return 7;
185
            case 'M':
186
                return 8;
187
            case 'P':
188
                return 9;
189
            case 'R':
190
                return 10;
191
            case 'S':
192
                return 11;
193
            case 'T':
194
                return 12;
195
            default:
196
                return -1;
197
        }
198
    }
199
200
    private function isValidDate(string $tin)
201
    {
202
        $day = intval($this->convertCharToNumber(StringUtil::substring($tin, 9, 11)));
203
        $c9 = $tin[8];
204
        $month = $this->getMonthNumber($c9);
205
        $year = intval($this->convertCharToNumber(StringUtil::substring($tin, 6, 8)));
206
        if ($day >= 1 && $day <= 31) {
207
            return DateUtil::validate(1900 + $year, $month, $day) || DateUtil::validate(2000 + $year, $month, $day);
208
        }
209
        return $day >= 41 && $day <= 71 && (DateUtil::validate(1900 + $year, $month, $day - 40) || DateUtil::validate(2000 + $year, $month, $day - 40));
210
    }
211
212
    private function convertCharToNumber($oldStr)
213
    {
214
        $newStr = '';
215
        for ($i = 0; $i < strlen($oldStr); $i++) {
216
            if (!is_numeric($oldStr[$i])) {
217
                $newStr .= $this->getNumberFromChar($oldStr[$i]);
218
            } else {
219
                $newStr .= $oldStr[$i];
220
            }
221
        }
222
        return $newStr;
223
    }
224
225
    protected function getNumberFromChar($m)
226
    {
227
        switch (strtoupper($m)) {
228
            case 'L':
229
                return 0;
230
            case 'M':
231
                return 1;
232
            case 'N':
233
                return 2;
234
            case 'P':
235
                return 3;
236
            case 'Q':
237
                return 4;
238
            case 'R':
239
                return 5;
240
            case 'S':
241
                return 6;
242
            case 'T':
243
                return 7;
244
            case 'U':
245
                return 8;
246
            case 'V':
247
                return 9;
248
            default:
249
                return -1;
250
        }
251
    }
252
}
253