Passed
Push — 1.x ( 55d45f...e071a3 )
by Milwad
04:50 queued 01:48
created

ValidIban::isCountryValid()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 3
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 5
rs 10
1
<?php
2
3
namespace Milwad\LaravelValidate\Rules;
4
5
use Illuminate\Contracts\Validation\Rule;
6
7
class ValidIban implements Rule
8
{
9
    /**
10
     * Character map
11
     *
12
     * @var array|int[]
13
     */
14
    private array $characterMap = [
15
        'A' => 10,
16
        'B' => 11,
17
        'C' => 12,
18
        'D' => 13,
19
        'E' => 14,
20
        'F' => 15,
21
        'G' => 16,
22
        'H' => 17,
23
        'I' => 18,
24
        'J' => 19,
25
        'K' => 20,
26
        'L' => 21,
27
        'M' => 22,
28
        'N' => 23,
29
        'O' => 24,
30
        'P' => 25,
31
        'Q' => 26,
32
        'R' => 27,
33
        'S' => 28,
34
        'T' => 29,
35
        'U' => 30,
36
        'V' => 31,
37
        'W' => 32,
38
        'X' => 33,
39
        'Y' => 34,
40
        'Z' => 35,
41
    ];
42
43
    /**
44
     * Get country code with length.
45
     *
46
     * @var array|int[]
47
     */
48
    private array $ibanLengthByCountry = [
49
        Country::ALBANIA => 28,
50
        Country::ALGERIA => 26,
51
        Country::ANDORRA => 24,
52
        Country::ANGOLA => 25,
53
        Country::AUSTRIA => 20,
54
        Country::AZERBAIJAN => 28,
55
        Country::BAHRAIN => 22,
56
        Country::BELARUS => 28,
57
        Country::BELGIUM => 16,
58
        Country::BENIN => 28,
59
        Country::BOSNIA_HERZEGOVINA => 20,
60
        Country::BRAZIL => 29,
61
        Country::BULGARIA => 22,
62
        Country::BURKINA_FASO => 28,
63
        Country::BURUNDI => 28,
64
        Country::CAMEROON => 27,
65
        Country::CAPE_VERDE => 25,
66
        Country::CENTRAL_AFRICAN_REPUBLIC => 27,
67
        Country::CHAD => 27,
68
        Country::COMOROS => 27,
69
        Country::CONGO => 27,
70
        Country::COSTA_RICA => 22,
71
        Country::CROATIA => 21,
72
        Country::CYPRUS => 28,
73
        Country::CZECH_REPUBLIC => 24,
74
        Country::DENMARK => 18,
75
        Country::DJIBOUTI => 27,
76
        Country::DOMINICAN_REPUBLIC => 28,
77
        Country::EGYPT => 29,
78
        Country::EL_SALVADOR => 28,
79
        Country::EQUATORIAL_GUINEA => 27,
80
        Country::ESTONIA => 20,
81
        Country::FAROE_ISLANDS => 18,
82
        Country::FINLAND => 18,
83
        Country::FRANCE => 27,
84
        Country::GABON => 27,
85
        Country::GEORGIA => 22,
86
        Country::GERMANY => 22,
87
        Country::GIBRALTAR => 23,
88
        Country::GREECE => 27,
89
        Country::GREENLAND => 18,
90
        Country::GUATEMALA => 28,
91
        Country::GUINEA_BISSAU => 25,
92
        Country::HOLY_SEE => 22,
93
        Country::HONDURAS => 28,
94
        Country::HUNGARY => 28,
95
        Country::ICELAND => 26,
96
        Country::IRAN => 26,
97
        Country::IRAQ => 23,
98
        Country::IRELAND => 22,
99
        Country::ISRAEL => 23,
100
        Country::ITALY => 27,
101
        Country::IVORY_COAST => 28,
102
        Country::JORDAN => 30,
103
        Country::KAZAKHSTAN => 20,
104
        Country::KOSOVO => 20,
105
        Country::KUWAIT => 30,
106
        Country::LATVIA => 21,
107
        Country::LEBANON => 28,
108
        Country::LIBYA => 25,
109
        Country::LIECHTENSTEIN => 21,
110
        Country::LITHUANIA => 20,
111
        Country::LUXEMBOURG => 20,
112
        Country::MADAGASCAR => 27,
113
        Country::MALI => 28,
114
        Country::MALTA => 31,
115
        Country::MAURITANIA => 27,
116
        Country::MAURITIUS => 30,
117
        Country::MOLDOVA => 24,
118
        Country::MONACO => 27,
119
        Country::MONGOLIA => 20,
120
        Country::MONTENEGRO => 22,
121
        Country::MOROCCO => 28,
122
        Country::MOZAMBIQUE => 25,
123
        Country::NETHERLANDS => 18,
124
        Country::NICARAGUA => 32,
125
        Country::NIGER => 28,
126
        Country::NORTH_MACEDONIA => 19,
127
        Country::NORWAY => 15,
128
        Country::PAKISTAN => 24,
129
        Country::PALESTINE => 29,
130
        Country::POLAND => 28,
131
        Country::PORTUGAL => 25,
132
        Country::QATAR => 29,
133
        Country::ROMANIA => 24,
134
        Country::RUSSIA => 33,
135
        Country::SAINT_LUCIA => 32,
136
        Country::SAN_MARINO => 27,
137
        Country::SAO_TOME_PRINCIPE => 25,
138
        Country::SAUDI_ARABIA => 24,
139
        Country::SENEGAL => 28,
140
        Country::SERBIA => 22,
141
        Country::SEYCHELLES => 31,
142
        Country::SLOVAKIA => 24,
143
        Country::SLOVENIA => 19,
144
        Country::SOMALIA => 23,
145
        Country::SPAIN => 24,
146
        Country::SUDAN => 18,
147
        Country::SWEDEN => 24,
148
        Country::SWITZERLAND => 21,
149
        Country::TIMOR_LESTE => 23,
150
        Country::TOGO => 28,
151
        Country::TUNISIA => 24,
152
        Country::TURKEY => 26,
153
        Country::UKRAINE => 29,
154
        Country::UNITED_ARAB_EMIRATES => 23,
155
        Country::UNITED_KINGDOM => 22,
156
        Country::VIRGIN_ISLANDS_BRITISH => 24,
157
    ];
158
159
    /**
160
     * Set multiple country codes to validate IBAN (Optional).
161
     */
162
    private array $countries;
163
164
    public function __construct(array|string $countries = [])
165
    {
166
        $this->setCountries(func_get_args());
167
    }
168
169
    /**
170
     * Check IBAN.
171
     *
172
     * @param  string  $attribute
173
     * @param  mixed  $value
174
     * @return bool
175
     */
176
    public function passes($attribute, $value)
177
    {
178
        return $this->isIbanValid($value);
179
    }
180
181
    /**
182
     * Get the validation error message.
183
     *
184
     * @return string
185
     */
186
    public function message()
187
    {
188
        return __('validate.iban');
189
    }
190
191
    /**
192
     * Set value of $countries property.
193
     *
194
     * @return void
195
     */
196
    private function setCountries(array|null $countries)
197
    {
198
        if (empty($countries)) {
199
            $this->countries = [];
200
            return;
201
        }
202
203
        if (is_array($countries[0])) {
204
            $countries = $countries[0];
205
        }
206
207
        foreach ($countries as $country) {
208
            $this->countries[] = $country;
209
        }
210
    }
211
212
213
    /**
214
     * Check IBAN is valid.
215
     *
216
     * @return bool
217
     */
218
    private function isIbanValid(string $iban)
219
    {
220
        if (! $this->checkIbanFormat($iban)) {
221
            return false;
222
        }
223
224
        /*
225
         * Connect Iban title with value (code) ex: 8330001234567NO .
226
         */
227
        $parsedIban = substr($iban, 4).substr($iban, 0, 4);
228
229
        /*
230
         * Replace iban value with character map.
231
         */
232
        $parsedIban = strtr($parsedIban, $this->characterMap);
233
234
        return bcmod($parsedIban, '97') === '1';
235
    }
236
237
    /**
238
     * Check IBAN format is valid.
239
     *
240
     * @return bool
241
     */
242
    private function checkIbanFormat(string $iban)
243
    {
244
        if (empty($iban)) {
245
            return false;
246
        }
247
248
        $ibanCountryCode = $this->getIbanCountryCode($iban);
249
250
        if (
251
            empty($this->checkIfBcmodIsAvailable())
252
            || ! $this->twoFirstCharactersValid($ibanCountryCode)
253
            || ! $this->isCountriesValid($ibanCountryCode)
254
            || ! $this->isIbanLengthValid($iban, $ibanCountryCode)
255
        ) {
256
            return false;
257
        }
258
259
        return true;
260
    }
261
262
    /**
263
     * Get IBAN country code.
264
     *
265
     * @return string
266
     */
267
    private function getIbanCountryCode(string $iban)
268
    {
269
        return substr($iban, 0, 2);
270
    }
271
272
    /**
273
     * Check if bcmod function is available.
274
     *
275
     * @return string
276
     */
277
    private function checkIfBcmodIsAvailable()
278
    {
279
        return function_exists('bcmod');
0 ignored issues
show
Bug Best Practice introduced by
The expression return function_exists('bcmod') returns the type boolean which is incompatible with the documented return type string.
Loading history...
280
    }
281
282
    /**
283
     * Check two first characters validity.
284
     *
285
     * @return bool
286
     */
287
    private function twoFirstCharactersValid(string $countryCode)
288
    {
289
        return empty($countryCode) ? false : ctype_alpha($countryCode);
290
    }
291
292
    /**
293
     * Check countries of the IBAN.
294
     *
295
     * @return bool
296
     */
297
    private function isCountriesValid(string $ibanCountryCode)
298
    {
299
        if (empty($this->countries)) {
300
            return true;
301
        }
302
303
        foreach ($this->countries as $country) {
304
            if ($this->isCountryValid($country, $ibanCountryCode)) {
305
                return true;
306
            }
307
        }
308
309
        return false;
310
    }
311
312
    /**
313
     * Check country of the IBAN.
314
     *
315
     * @return bool
316
     */
317
    private function isCountryValid(string $country, string $ibanCountryCode)
318
    {
319
        return ! empty($country)
320
            && isset($this->ibanLengthByCountry[$country])
321
            && $ibanCountryCode === $country;
322
    }
323
324
    /**
325
     * Check country of the IBAN.
326
     *
327
     * @return bool
328
     */
329
    private function isIbanLengthValid(string $iban, string $ibanCountryCode)
330
    {
331
        return strlen($iban) === $this->ibanLengthByCountry[$ibanCountryCode];
332
    }
333
}
334
335
class Country
336
{
337
    const ALBANIA = 'AL';
338
    const ALGERIA = 'DZ';
339
    const ANDORRA = 'AD';
340
    const ANGOLA = 'AO';
341
    const AUSTRIA = 'AT';
342
    const AZERBAIJAN = 'AZ';
343
    const BAHRAIN = 'BH';
344
    const BELARUS = 'BY';
345
    const BELGIUM = 'BE';
346
    const BENIN = 'BJ';
347
    const BOSNIA_HERZEGOVINA = 'BA';
348
    const BRAZIL = 'BR';
349
    const BULGARIA = 'BG';
350
    const BURKINA_FASO = 'BF';
351
    const BURUNDI = 'BI';
352
    const CAMEROON = 'CM';
353
    const CAPE_VERDE = 'CV';
354
    const CENTRAL_AFRICAN_REPUBLIC = 'CF';
355
    const CHAD = 'TD';
356
    const COMOROS = 'KM';
357
    const CONGO = 'CG';
358
    const COSTA_RICA = 'CR';
359
    const CROATIA = 'HR';
360
    const CYPRUS = 'CY';
361
    const CZECH_REPUBLIC = 'CZ';
362
    const DENMARK = 'DK';
363
    const DJIBOUTI = 'DJ';
364
    const DOMINICAN_REPUBLIC = 'DO';
365
    const EGYPT = 'EG';
366
    const EL_SALVADOR = 'SV';
367
    const EQUATORIAL_GUINEA = 'GQ';
368
    const ESTONIA = 'EE';
369
    const FAROE_ISLANDS = 'FO';
370
    const FINLAND = 'FI';
371
    const FRANCE = 'FR';
372
    const GABON = 'GA';
373
    const GEORGIA = 'GE';
374
    const GERMANY = 'DE';
375
    const GIBRALTAR = 'GI';
376
    const GREECE = 'GR';
377
    const GREENLAND = 'GL';
378
    const GUATEMALA = 'GT';
379
    const GUINEA_BISSAU = 'GW';
380
    const HOLY_SEE = 'VA';
381
    const HONDURAS = 'HN';
382
    const HUNGARY = 'HU';
383
    const ICELAND = 'IS';
384
    const IRAN = 'IR';
385
    const IRAQ = 'IQ';
386
    const IRELAND = 'IE';
387
    const ISRAEL = 'IL';
388
    const ITALY = 'IT';
389
    const IVORY_COAST = 'CI';
390
    const JORDAN = 'JO';
391
    const KAZAKHSTAN = 'KZ';
392
    const KOSOVO = 'XK';
393
    const KUWAIT = 'KW';
394
    const LATVIA = 'LV';
395
    const LEBANON = 'LB';
396
    const LIBYA = 'LY';
397
    const LIECHTENSTEIN = 'LI';
398
    const LITHUANIA = 'LT';
399
    const LUXEMBOURG = 'LU';
400
    const MADAGASCAR = 'MG';
401
    const MALI = 'ML';
402
    const MALTA = 'MT';
403
    const MAURITANIA = 'MR';
404
    const MAURITIUS = 'MU';
405
    const MOLDOVA = 'MD';
406
    const MONACO = 'MC';
407
    const MONGOLIA = 'MN';
408
    const MONTENEGRO = 'ME';
409
    const MOROCCO = 'MA';
410
    const MOZAMBIQUE = 'MZ';
411
    const NETHERLANDS = 'NL';
412
    const NICARAGUA = 'NI';
413
    const NIGER = 'NE';
414
    const NORTH_MACEDONIA = 'MK';
415
    const NORWAY = 'NO';
416
    const PAKISTAN = 'PK';
417
    const PALESTINE = 'PS';
418
    const POLAND = 'PL';
419
    const PORTUGAL = 'PT';
420
    const QATAR = 'QA';
421
    const ROMANIA = 'RO';
422
    const RUSSIA = 'RU';
423
    const SAINT_LUCIA = 'LC';
424
    const SAN_MARINO = 'SM';
425
    const SAO_TOME_PRINCIPE = 'ST';
426
    const SAUDI_ARABIA = 'SA';
427
    const SENEGAL = 'SN';
428
    const SERBIA = 'RS';
429
    const SEYCHELLES = 'SC';
430
    const SLOVAKIA = 'SK';
431
    const SLOVENIA = 'SI';
432
    const SOMALIA = 'SO';
433
    const SPAIN = 'ES';
434
    const SUDAN = 'SD';
435
    const SWEDEN = 'SE';
436
    const SWITZERLAND = 'CH';
437
    const TIMOR_LESTE = 'TL';
438
    const TOGO = 'TG';
439
    const TUNISIA = 'TN';
440
    const TURKEY = 'TR';
441
    const UKRAINE = 'UA';
442
    const UNITED_ARAB_EMIRATES = 'AE';
443
    const UNITED_KINGDOM = 'GB';
444
    const VIRGIN_ISLANDS_BRITISH = 'VG';
445
}
446