Mercurial > hg > cmmr2012-drupal-site
comparison vendor/symfony/validator/Constraints/IbanValidator.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of the Symfony package. | |
5 * | |
6 * (c) Fabien Potencier <fabien@symfony.com> | |
7 * | |
8 * For the full copyright and license information, please view the LICENSE | |
9 * file that was distributed with this source code. | |
10 */ | |
11 | |
12 namespace Symfony\Component\Validator\Constraints; | |
13 | |
14 use Symfony\Component\Validator\Constraint; | |
15 use Symfony\Component\Validator\ConstraintValidator; | |
16 use Symfony\Component\Validator\Exception\UnexpectedTypeException; | |
17 | |
18 /** | |
19 * @author Manuel Reinhard <manu@sprain.ch> | |
20 * @author Michael Schummel | |
21 * @author Bernhard Schussek <bschussek@gmail.com> | |
22 * | |
23 * @see http://www.michael-schummel.de/2007/10/05/iban-prufung-mit-php/ | |
24 */ | |
25 class IbanValidator extends ConstraintValidator | |
26 { | |
27 /** | |
28 * IBAN country specific formats. | |
29 * | |
30 * The first 2 characters from an IBAN format are the two-character ISO country code. | |
31 * The following 2 characters represent the check digits calculated from the rest of the IBAN characters. | |
32 * The rest are up to thirty alphanumeric characters for | |
33 * a BBAN (Basic Bank Account Number) which has a fixed length per country and, | |
34 * included within it, a bank identifier with a fixed position and a fixed length per country | |
35 * | |
36 * @see https://www.swift.com/sites/default/files/resources/iban_registry.pdf | |
37 */ | |
38 private static $formats = array( | |
39 'AD' => 'AD\d{2}\d{4}\d{4}[\dA-Z]{12}', // Andorra | |
40 'AE' => 'AE\d{2}\d{3}\d{16}', // United Arab Emirates | |
41 'AL' => 'AL\d{2}\d{8}[\dA-Z]{16}', // Albania | |
42 'AO' => 'AO\d{2}\d{21}', // Angola | |
43 'AT' => 'AT\d{2}\d{5}\d{11}', // Austria | |
44 'AX' => 'FI\d{2}\d{6}\d{7}\d{1}', // Aland Islands | |
45 'AZ' => 'AZ\d{2}[A-Z]{4}[\dA-Z]{20}', // Azerbaijan | |
46 'BA' => 'BA\d{2}\d{3}\d{3}\d{8}\d{2}', // Bosnia and Herzegovina | |
47 'BE' => 'BE\d{2}\d{3}\d{7}\d{2}', // Belgium | |
48 'BF' => 'BF\d{2}\d{23}', // Burkina Faso | |
49 'BG' => 'BG\d{2}[A-Z]{4}\d{4}\d{2}[\dA-Z]{8}', // Bulgaria | |
50 'BH' => 'BH\d{2}[A-Z]{4}[\dA-Z]{14}', // Bahrain | |
51 'BI' => 'BI\d{2}\d{12}', // Burundi | |
52 'BJ' => 'BJ\d{2}[A-Z]{1}\d{23}', // Benin | |
53 'BY' => 'BY\d{2}[\dA-Z]{4}\d{4}[\dA-Z]{16}', // Belarus - https://bank.codes/iban/structure/belarus/ | |
54 'BL' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Barthelemy | |
55 'BR' => 'BR\d{2}\d{8}\d{5}\d{10}[A-Z][\dA-Z]', // Brazil | |
56 'CG' => 'CG\d{2}\d{23}', // Congo | |
57 'CH' => 'CH\d{2}\d{5}[\dA-Z]{12}', // Switzerland | |
58 'CI' => 'CI\d{2}[A-Z]{1}\d{23}', // Ivory Coast | |
59 'CM' => 'CM\d{2}\d{23}', // Cameron | |
60 'CR' => 'CR\d{2}0\d{3}\d{14}', // Costa Rica | |
61 'CV' => 'CV\d{2}\d{21}', // Cape Verde | |
62 'CY' => 'CY\d{2}\d{3}\d{5}[\dA-Z]{16}', // Cyprus | |
63 'CZ' => 'CZ\d{2}\d{20}', // Czech Republic | |
64 'DE' => 'DE\d{2}\d{8}\d{10}', // Germany | |
65 'DO' => 'DO\d{2}[\dA-Z]{4}\d{20}', // Dominican Republic | |
66 'DK' => 'DK\d{2}\d{4}\d{10}', // Denmark | |
67 'DZ' => 'DZ\d{2}\d{20}', // Algeria | |
68 'EE' => 'EE\d{2}\d{2}\d{2}\d{11}\d{1}', // Estonia | |
69 'ES' => 'ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}', // Spain (also includes Canary Islands, Ceuta and Melilla) | |
70 'FI' => 'FI\d{2}\d{6}\d{7}\d{1}', // Finland | |
71 'FO' => 'FO\d{2}\d{4}\d{9}\d{1}', // Faroe Islands | |
72 'FR' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France | |
73 'GF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Guyana | |
74 'GB' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom of Great Britain and Northern Ireland | |
75 'GE' => 'GE\d{2}[A-Z]{2}\d{16}', // Georgia | |
76 'GI' => 'GI\d{2}[A-Z]{4}[\dA-Z]{15}', // Gibraltar | |
77 'GL' => 'GL\d{2}\d{4}\d{9}\d{1}', // Greenland | |
78 'GP' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Guadeloupe | |
79 'GR' => 'GR\d{2}\d{3}\d{4}[\dA-Z]{16}', // Greece | |
80 'GT' => 'GT\d{2}[\dA-Z]{4}[\dA-Z]{20}', // Guatemala | |
81 'HR' => 'HR\d{2}\d{7}\d{10}', // Croatia | |
82 'HU' => 'HU\d{2}\d{3}\d{4}\d{1}\d{15}\d{1}', // Hungary | |
83 'IE' => 'IE\d{2}[A-Z]{4}\d{6}\d{8}', // Ireland | |
84 'IL' => 'IL\d{2}\d{3}\d{3}\d{13}', // Israel | |
85 'IR' => 'IR\d{2}\d{22}', // Iran | |
86 'IS' => 'IS\d{2}\d{4}\d{2}\d{6}\d{10}', // Iceland | |
87 'IT' => 'IT\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // Italy | |
88 'JO' => 'JO\d{2}[A-Z]{4}\d{4}[\dA-Z]{18}', // Jordan | |
89 'KW' => 'KW\d{2}[A-Z]{4}\d{22}', // KUWAIT | |
90 'KZ' => 'KZ\d{2}\d{3}[\dA-Z]{13}', // Kazakhstan | |
91 'LB' => 'LB\d{2}\d{4}[\dA-Z]{20}', // LEBANON | |
92 'LI' => 'LI\d{2}\d{5}[\dA-Z]{12}', // Liechtenstein (Principality of) | |
93 'LT' => 'LT\d{2}\d{5}\d{11}', // Lithuania | |
94 'LU' => 'LU\d{2}\d{3}[\dA-Z]{13}', // Luxembourg | |
95 'LV' => 'LV\d{2}[A-Z]{4}[\dA-Z]{13}', // Latvia | |
96 'MC' => 'MC\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Monaco | |
97 'MD' => 'MD\d{2}[\dA-Z]{2}[\dA-Z]{18}', // Moldova | |
98 'ME' => 'ME\d{2}\d{3}\d{13}\d{2}', // Montenegro | |
99 'MF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Martin (French part) | |
100 'MG' => 'MG\d{2}\d{23}', // Madagascar | |
101 'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia, Former Yugoslav Republic of | |
102 'ML' => 'ML\d{2}[A-Z]{1}\d{23}', // Mali | |
103 'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Martinique | |
104 'MR' => 'MR13\d{5}\d{5}\d{11}\d{2}', // Mauritania | |
105 'MT' => 'MT\d{2}[A-Z]{4}\d{5}[\dA-Z]{18}', // Malta | |
106 'MU' => 'MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}', // Mauritius | |
107 'MZ' => 'MZ\d{2}\d{21}', // Mozambique | |
108 'NC' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // New Caledonia | |
109 'NL' => 'NL\d{2}[A-Z]{4}\d{10}', // The Netherlands | |
110 'NO' => 'NO\d{2}\d{4}\d{6}\d{1}', // Norway | |
111 'PF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Polynesia | |
112 'PK' => 'PK\d{2}[A-Z]{4}[\dA-Z]{16}', // Pakistan | |
113 'PL' => 'PL\d{2}\d{8}\d{16}', // Poland | |
114 'PM' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Pierre et Miquelon | |
115 'PS' => 'PS\d{2}[A-Z]{4}[\dA-Z]{21}', // Palestine, State of | |
116 'PT' => 'PT\d{2}\d{4}\d{4}\d{11}\d{2}', // Portugal (plus Azores and Madeira) | |
117 'QA' => 'QA\d{2}[A-Z]{4}[\dA-Z]{21}', // Qatar | |
118 'RE' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Reunion | |
119 'RO' => 'RO\d{2}[A-Z]{4}[\dA-Z]{16}', // Romania | |
120 'RS' => 'RS\d{2}\d{3}\d{13}\d{2}', // Serbia | |
121 'SA' => 'SA\d{2}\d{2}[\dA-Z]{18}', // Saudi Arabia | |
122 'SE' => 'SE\d{2}\d{3}\d{16}\d{1}', // Sweden | |
123 'SI' => 'SI\d{2}\d{5}\d{8}\d{2}', // Slovenia | |
124 'SK' => 'SK\d{2}\d{4}\d{6}\d{10}', // Slovak Republic | |
125 'SM' => 'SM\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // San Marino | |
126 'SN' => 'SN\d{2}[A-Z]{1}\d{23}', // Senegal | |
127 'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Southern Territories | |
128 'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste | |
129 'TN' => 'TN59\d{2}\d{3}\d{13}\d{2}', // Tunisia | |
130 'TR' => 'TR\d{2}\d{5}[\dA-Z]{1}[\dA-Z]{16}', // Turkey | |
131 'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine | |
132 'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands, British | |
133 'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Wallis and Futuna Islands | |
134 'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Republic of Kosovo | |
135 'YT' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Mayotte | |
136 ); | |
137 | |
138 /** | |
139 * {@inheritdoc} | |
140 */ | |
141 public function validate($value, Constraint $constraint) | |
142 { | |
143 if (!$constraint instanceof Iban) { | |
144 throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Iban'); | |
145 } | |
146 | |
147 if (null === $value || '' === $value) { | |
148 return; | |
149 } | |
150 | |
151 if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) { | |
152 throw new UnexpectedTypeException($value, 'string'); | |
153 } | |
154 | |
155 $value = (string) $value; | |
156 | |
157 // Remove spaces and convert to uppercase | |
158 $canonicalized = str_replace(' ', '', strtoupper($value)); | |
159 | |
160 // The IBAN must contain only digits and characters... | |
161 if (!ctype_alnum($canonicalized)) { | |
162 $this->context->buildViolation($constraint->message) | |
163 ->setParameter('{{ value }}', $this->formatValue($value)) | |
164 ->setCode(Iban::INVALID_CHARACTERS_ERROR) | |
165 ->addViolation(); | |
166 | |
167 return; | |
168 } | |
169 | |
170 // ...start with a two-letter country code | |
171 $countryCode = substr($canonicalized, 0, 2); | |
172 | |
173 if (!ctype_alpha($countryCode)) { | |
174 $this->context->buildViolation($constraint->message) | |
175 ->setParameter('{{ value }}', $this->formatValue($value)) | |
176 ->setCode(Iban::INVALID_COUNTRY_CODE_ERROR) | |
177 ->addViolation(); | |
178 | |
179 return; | |
180 } | |
181 | |
182 // ...have a format available | |
183 if (!array_key_exists($countryCode, self::$formats)) { | |
184 $this->context->buildViolation($constraint->message) | |
185 ->setParameter('{{ value }}', $this->formatValue($value)) | |
186 ->setCode(Iban::NOT_SUPPORTED_COUNTRY_CODE_ERROR) | |
187 ->addViolation(); | |
188 | |
189 return; | |
190 } | |
191 | |
192 // ...and have a valid format | |
193 if (!preg_match('/^'.self::$formats[$countryCode].'$/', $canonicalized) | |
194 ) { | |
195 $this->context->buildViolation($constraint->message) | |
196 ->setParameter('{{ value }}', $this->formatValue($value)) | |
197 ->setCode(Iban::INVALID_FORMAT_ERROR) | |
198 ->addViolation(); | |
199 | |
200 return; | |
201 } | |
202 | |
203 // Move the first four characters to the end | |
204 // e.g. CH93 0076 2011 6238 5295 7 | |
205 // -> 0076 2011 6238 5295 7 CH93 | |
206 $canonicalized = substr($canonicalized, 4).substr($canonicalized, 0, 4); | |
207 | |
208 // Convert all remaining letters to their ordinals | |
209 // The result is an integer, which is too large for PHP's int | |
210 // data type, so we store it in a string instead. | |
211 // e.g. 0076 2011 6238 5295 7 CH93 | |
212 // -> 0076 2011 6238 5295 7 121893 | |
213 $checkSum = self::toBigInt($canonicalized); | |
214 | |
215 // Do a modulo-97 operation on the large integer | |
216 // We cannot use PHP's modulo operator, so we calculate the | |
217 // modulo step-wisely instead | |
218 if (1 !== self::bigModulo97($checkSum)) { | |
219 $this->context->buildViolation($constraint->message) | |
220 ->setParameter('{{ value }}', $this->formatValue($value)) | |
221 ->setCode(Iban::CHECKSUM_FAILED_ERROR) | |
222 ->addViolation(); | |
223 } | |
224 } | |
225 | |
226 private static function toBigInt($string) | |
227 { | |
228 $chars = str_split($string); | |
229 $bigInt = ''; | |
230 | |
231 foreach ($chars as $char) { | |
232 // Convert uppercase characters to ordinals, starting with 10 for "A" | |
233 if (ctype_upper($char)) { | |
234 $bigInt .= (ord($char) - 55); | |
235 | |
236 continue; | |
237 } | |
238 | |
239 // Simply append digits | |
240 $bigInt .= $char; | |
241 } | |
242 | |
243 return $bigInt; | |
244 } | |
245 | |
246 private static function bigModulo97($bigInt) | |
247 { | |
248 $parts = str_split($bigInt, 7); | |
249 $rest = 0; | |
250 | |
251 foreach ($parts as $part) { | |
252 $rest = ($rest.$part) % 97; | |
253 } | |
254 | |
255 return $rest; | |
256 } | |
257 } |