1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4: */
3/**
4 * Financial functions for validation and calculation
5 *
6 * PHP Versions 4 and 5
7 *
8 * This source file is subject to version 3.01 of the PHP license,
9 * that is bundled with this package in the file LICENSE, and is
10 * available through the world-wide-web at the following URI:
11 * http://www.php.net/license/3_01.txt
12 * If you did not receive a copy of the PHP license and are unable to
13 * obtain it through the world-wide-web, please send a note to
14 * license@php.net so we can mail you a copy immediately.
15 *
16 * @category  Validate
17 * @package   Validate_Finance
18 * @author    Stefan Neufeind <pear.neufeind@speedpartner.de>
19 * @copyright 1997-2009 The PHP Group
20 * @license   http://www.php.net/license/3_01.txt  PHP License 3.01
21 * @version   SVN: $Id$
22 * @link      http://pear.php.net/package/Validate_Finance
23 */
24
25// needed for call to isError()
26require_once 'PEAR.php';
27
28/*
29 * Error codes for the IBAN interface, which will be mapped to textual messages
30 * in the IBAN::errorMessage() function.  If you are to add a new error code, be
31 * sure to add the textual messages to the IBAN::errorMessage() function as well
32 */
33
34define('VALIDATE_FINANCE_IBAN_OK', 1);
35define('VALIDATE_FINANCE_IBAN_ERROR', -1);
36define('VALIDATE_FINANCE_IBAN_GENERAL_INVALID', -2);
37define('VALIDATE_FINANCE_IBAN_TOO_SHORT', -4);
38define('VALIDATE_FINANCE_IBAN_TOO_LONG', -5);
39define('VALIDATE_FINANCE_IBAN_COUNTRY_INVALID', -6);
40// tested via regex; e.g. if un-allowed characters in IBAN
41define('VALIDATE_FINANCE_IBAN_INVALID_FORMAT', -7);
42define('VALIDATE_FINANCE_IBAN_CHECKSUM_INVALID', -8);
43
44/**
45 * Validate and process IBAN (international bank account numbers)
46 *
47 * @category  File_Formats
48 * @package   Validate_Finance
49 * @author    Stefan Neufeind <pear.neufeind@speedpartner.de>
50 * @copyright 1997-2009 The PHP Group
51 * @license   http://www.php.net/license/3_01.txt  PHP License 3.01
52 * @link      http://pear.php.net/package/Validate_Finance
53 */
54class Validate_Finance_IBAN
55{
56    /**
57     * String containing the IBAN to be processed
58     * @var     string
59     * @access  private
60     */
61    var $_iban = '';
62
63    /**
64     * Integer containing errorcode of last validation
65     * @var     integer
66     * @access  private
67     */
68    var $_errorcode = 0;
69
70    /**
71     * List of all IBAN countrycodes; also gives corresponding countrynames (in
72     * long form)
73     *
74     * @return  array
75     * @access  private
76     */
77    function _getCountrycodeCountryname()
78    {
79        static $_iban_countrycode_countryname;
80        if (!isset($_iban_countrycode_countryname)) {
81            $_iban_countrycode_countryname = array(
82                'AD' => 'Andorra',
83                'AE' => 'United Arab Emirates',
84                'AL' => 'Albania',
85                'AT' => 'Austria',
86                'BA' => 'Bosnia and Herzegovina',
87                'BE' => 'Belgium',
88                'BG' => 'Bulgaria',
89                'CH' => 'Switzerland',
90                'CY' => 'Cyprus',
91                'CZ' => 'Czech Republic',
92                'DE' => 'Germany',
93                'DK' => 'Denmark',
94                'EE' => 'Estonia',
95                'ES' => 'Spain',
96                'FR' => 'France',
97                'FI' => 'Finland',
98                'GB' => 'United Kingdom',
99                'GE' => 'Georgia',
100                'GI' => 'Gibraltar',
101                'GR' => 'Greece',
102                'HR' => 'Croatia',
103                'HU' => 'Hungary',
104                'IE' => 'Ireland',
105                'IL' => 'Israel',
106                'IS' => 'Iceland',
107                'IT' => 'Italy',
108                'KW' => 'Kuwait',
109                'LB' => 'Lebanon',
110                'LI' => 'Liechtenstein',
111                'LT' => 'Lithuania',
112                'LU' => 'Luxembourg',
113                'LV' => 'Latvia',
114                'MC' => 'Monaco',
115                'ME' => 'Montenegro',
116                'MK' => 'Macedonia',
117                'MR' => 'Mauritania',
118                'MT' => 'Malta',
119                'MU' => 'Mauritius',
120                'NL' => 'The Netherlands',
121                'NO' => 'Norwegian',
122                'PL' => 'Poland',
123                'PT' => 'Portugal',
124                'RO' => 'Romania',
125                'RS' => 'Serbia',
126                'SA' => 'Saudi Arabia',
127                'SE' => 'Sweden',
128                'SI' => 'Slovenia',
129                'SK' => 'Slovak Republic',
130                'SM' => 'San Marino',
131                'TN' => 'Tunisia',
132                'TR' => 'Turkey',
133            );
134        }
135        return $_iban_countrycode_countryname;
136    }
137
138    /**
139     * List of IBAN length; can be used for a quick check
140     *
141     * @return  array
142     * @access  private
143     */
144    function _getCountrycodeIBANLength()
145    {
146        static $_iban_countrycode_length;
147        if (!isset($_iban_countrycode_length)) {
148            $_iban_countrycode_length = array(
149                'AD' => 24,
150                'AE' => 23,
151                'AL' => 28,
152                'AT' => 20,
153                'BA' => 20,
154                'BE' => 16,
155                'BG' => 22,
156                'CH' => 21,
157                'CY' => 28,
158                'CZ' => 24,
159                'DE' => 22,
160                'DK' => 18,
161                'EE' => 20,
162                'ES' => 24,
163                'FR' => 27,
164                'FI' => 18,
165                'GB' => 22,
166                'GE' => 22,
167                'GI' => 23,
168                'GR' => 27,
169                'HR' => 21,
170                'HU' => 28,
171                'IE' => 22,
172                'IL' => 23,
173                'IS' => 26,
174                'IT' => 27,
175                'KW' => 30,
176                'LB' => 28,
177                'LI' => 21,
178                'LT' => 20,
179                'LU' => 20,
180                'LV' => 21,
181                'MC' => 27,
182                'ME' => 22,
183                'MK' => 19,
184                'MR' => 27,
185                'MT' => 31,
186                'MU' => 30,
187                'NL' => 18,
188                'NO' => 15,
189                'PL' => 28,
190                'PT' => 25,
191                'RO' => 24,
192                'RS' => 22,
193                'SA' => 24,
194                'SE' => 24,
195                'SI' => 19,
196                'SK' => 24,
197                'SM' => 27,
198                'TN' => 24,
199                'TR' => 26,
200            );
201        }
202        return $_iban_countrycode_length;
203    }
204
205    /**
206     * List of where the bankcode inside an IBAN starts (starting from 0) and
207     * its length
208     *
209     * @return  array
210     * @access  private
211     */
212    function _getCountrycodeBankcode()
213    {
214        static $_iban_countrycode_bankcode;
215        if (!isset($_iban_countrycode_bankcode)) {
216            $_iban_countrycode_bankcode = array(
217                //AD: first 4 chars bankcode, last 4 chars branch
218                'AD' => array('start' =>  4, 'length' =>  8),
219                'AE' => array('start' =>  4, 'length' =>  3),
220                //AL: first 3 chars bankcode, next 4 chars branch, one char checksum
221                'AL' => array('start' =>  4, 'length' =>  8),
222                'AT' => array('start' =>  4, 'length' =>  5),
223                //BA: first 3 chars bankcode, last 3 chars branch
224                'BA' => array('start' =>  4, 'length' =>  6),
225                'BE' => array('start' =>  4, 'length' =>  3),
226                'BG' => array('start' =>  4, 'length' =>  8),
227                'CH' => array('start' =>  4, 'length' =>  5),
228                //CY: first 3 chars bankcode, last 5 chars branch
229                'CY' => array('start' =>  4, 'length' =>  8),
230                'CZ' => array('start' =>  4, 'length' =>  4),
231                'DE' => array('start' =>  4, 'length' =>  8),
232                'DK' => array('start' =>  4, 'length' =>  4),
233                //EE: first 2 chars bankidentifier, last 2 chars bankcode
234                'EE' => array('start' =>  4, 'length' =>  4),
235                // ES: followed by 2 chars (checksum)
236                'ES' => array('start' =>  4, 'length' =>  8),
237                'FR' => array('start' =>  4, 'length' => 10),
238                'FI' => array('start' =>  4, 'length' =>  6),
239                //GB: first 4 chars bankidentifier, last 6 chars bank-branchcode
240                'GB' => array('start' =>  4, 'length' => 10),
241                'GE' => array('start' =>  4, 'length' =>  2),
242                'GI' => array('start' =>  4, 'length' =>  4),
243                //GR: first 3 chars bankcode, last 4 chars branch
244                'GR' => array('start' =>  4, 'length' =>  7),
245                'HR' => array('start' =>  4, 'length' =>  7),
246                //HU: first 3 chars bankcode, last 4 chars branch,
247                //followed by 1 char (checksum)
248                'HU' => array('start' =>  4, 'length' =>  7),
249                //IE: first 4 chars bankcode, last 6 chars branch
250                'IE' => array('start' =>  4, 'length' => 10),
251                //IL: first 3 chars bankcode, last 3 chars branch
252                'IL' => array('start' =>  4, 'length' =>  6),
253                'IS' => array('start' =>  4, 'length' =>  4),
254                'IT' => array('start' =>  4, 'length' => 11),
255                'KW' => array('start' =>  4, 'length' =>  4),
256                'LB' => array('start' =>  4, 'length' =>  4),
257                //LI: bankcode and branch
258                'LI' => array('start' =>  4, 'length' =>  5),
259                'LT' => array('start' =>  4, 'length' =>  5),
260                'LU' => array('start' =>  4, 'length' =>  3),
261                'LV' => array('start' =>  4, 'length' =>  4),
262                //MC: first 5 chars bankcode, last 5 chars branch
263                'MC' => array('start' =>  4, 'length' =>  10),
264                'ME' => array('start' =>  4, 'length' =>  3),
265                'MK' => array('start' =>  4, 'length' =>  3),
266                //MR: first 5 chars bankcode, last 5 chars branch
267                'M$' => array('start' =>  4, 'length' => 10),
268                //MT: first 4 chars bankcode, last 5 chars bank sort code
269                'MT' => array('start' =>  4, 'length' =>  9),
270                //MU: first 6 chars bankcode, last 2 chars branch
271                'MU' => array('start' =>  4, 'length' =>  8),
272                'NL' => array('start' =>  4, 'length' =>  4),
273                'NO' => array('start' =>  4, 'length' =>  4),
274                'PL' => array('start' =>  4, 'length' =>  8),
275                'PT' => array('start' =>  4, 'length' =>  8),
276                'RO' => array('start' =>  4, 'length' =>  4),
277                'RS' => array('start' =>  4, 'length' =>  3),
278                'SA' => array('start' =>  4, 'length' =>  2),
279                //SE: bankcode and branch
280                'SE' => array('start' =>  4, 'length' =>  3),
281                'SI' => array('start' =>  4, 'length' =>  5),
282                'SK' => array('start' =>  4, 'length' =>  4),
283                //SM: starts with one char, followed by 5 chars bankcode, last 5 chars branch
284                'SM' => array('start' =>  5, 'length' =>  10),
285                //TN: first 2 chars bankcode, last 3 chars branch
286                'TN' => array('start' =>  4, 'length' =>  5),
287                //TR: followed by 1 char (reserved field)
288                'TR' => array('start' =>  4, 'length' =>  5),
289            );
290        }
291        return $_iban_countrycode_bankcode;
292    }
293
294    /**
295     * List of where the bankaccount-number inside an IBAN starts (starting from 0)
296     * and its length
297     *
298     * @return  array
299     * @access  private
300     */
301    function _getCountrycodeBankaccount()
302    {
303        static $_iban_countrycode_bankaccount;
304        if (!isset($_iban_countrycode_bankaccount)) {
305            $_iban_countrycode_bankaccount = array(
306                'AD' => array('start' => 12, 'length' => 12),
307                'AE' => array('start' =>  7, 'length' => 23),
308                'AL' => array('start' => 12, 'length' => 16),
309                'AT' => array('start' =>  9, 'length' => 11),
310                //BA: followed by 2 chars (checksum)
311                'BA' => array('start' => 10, 'length' =>  8),
312                //BE: followed by 2 chars (checksum)
313                'BE' => array('start' =>  7, 'length' =>  7),
314                'BG' => array('start' => 12, 'length' => 10),
315                'CH' => array('start' =>  9, 'length' => 12),
316                'CY' => array('start' => 12, 'length' => 16),
317                'CZ' => array('start' =>  8, 'length' => 16),
318                'DE' => array('start' => 12, 'length' => 10),
319                //DK: followed by 1 char (checksum)
320                'DK' => array('start' =>  8, 'length' =>  9),
321                //EE: followed by 1 char (checksum)
322                'EE' => array('start' =>  8, 'length' => 11),
323                'ES' => array('start' => 14, 'length' => 10),
324                //FR: followed by 2 chars (checksum)
325                'FR' => array('start' => 14, 'length' => 11),
326                //FI: followed by 1 char (checksum)
327                'FI' => array('start' => 10, 'length' =>  7),
328                'GB' => array('start' => 14, 'length' =>  8),
329                'GE' => array('start' =>  6, 'length' => 16),
330                'GI' => array('start' =>  8, 'length' => 15),
331                'GR' => array('start' => 11, 'length' => 16),
332                'HR' => array('start' => 11, 'length' => 10),
333                //HU: followed by 1 char (checksum)
334                'HU' => array('start' => 12, 'length' => 15),
335                'IE' => array('start' => 14, 'length' =>  8),
336                'IL' => array('start' => 10, 'length' => 13),
337                //IS: 2 accounttype, 6 account number, 10 identification number
338                'IS' => array('start' =>  8, 'length' => 18),
339                'IT' => array('start' => 15, 'length' => 12),
340                'KW' => array('start' =>  8, 'length' => 22),
341                'LB' => array('start' =>  8, 'length' => 20),
342                'LI' => array('start' =>  9, 'length' => 12),
343                'LT' => array('start' =>  9, 'length' => 11),
344                'LU' => array('start' =>  7, 'length' => 13),
345                'LV' => array('start' =>  8, 'length' => 13),
346                'MC' => array('start' => 14, 'length' => 13),
347                'ME' => array('start' =>  7, 'length' => 15),
348                //MK: followed by 2 chars (checksum)
349                'MK' => array('start' =>  7, 'length' => 10),
350                //MR: followed by 2 chars (checksum?)
351                'MR' => array('start' => 14, 'length' => 13),
352                'MT' => array('start' => 13, 'length' => 18),
353                'MU' => array('start' => 12, 'length' => 18),
354                'NL' => array('start' =>  8, 'length' => 10),
355                //NO: followed by 1 char (checksum)
356                'NO' => array('start' =>  8, 'length' =>  6),
357                'PL' => array('start' => 12, 'length' => 16),
358                //PT: followed by 2 chars (checksum)
359                'PT' => array('start' => 12, 'length' => 11),
360                // branch and client account identifier
361                'RO' => array('start' =>  8, 'length' => 16),
362                'RS' => array('start' =>  7, 'length' => 15),
363                'SA' => array('start' =>  6, 'length' => 18),
364                //SE: followed by 1 char (checksum)
365                'SE' => array('start' =>  7, 'length' => 16),
366                //SI: followed by 2 chars (checksum)
367                'SI' => array('start' =>  9, 'length' =>  8),
368                'SK' => array('start' =>  8, 'length' => 16),
369                'SM' => array('start' => 15, 'length' => 12),
370                //TN: followed by 2 chars (checksum)
371                'TN' => array('start' =>  9, 'length' => 13),
372                'TR' => array('start' => 10, 'length' => 16),
373            );
374        }
375        return $_iban_countrycode_bankaccount;
376    }
377
378    /**
379     * List of regex for validating an IBAN according to standards for each country
380     *
381     * @return  array
382     * @access  private
383     */
384    function _getCountrycodeRegex()
385    {
386        static $_iban_countrycode_regex;
387        if (!isset($_iban_countrycode_regex)) {
388            $_iban_countrycode_regex = array(
389                'AD' => '/^AD[0-9]{2}[0-9]{8}[A-Z0-9]{12}$/',
390                'AE' => '/^AE[0-9]{2}[0-9]{3}[0-9]{16}$/',
391                'AL' => '/^AL[0-9]{2}[0-9]{8}[A-Z0-9]{16}$/',
392                'AT' => '/^AT[0-9]{2}[0-9]{5}[0-9]{11}$/',
393                'BA' => '/^BA[0-9]{2}[0-9]{6}[0-9]{10}$/',
394                'BE' => '/^BE[0-9]{2}[0-9]{3}[0-9]{9}$/',
395                'BG' => '/^BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}$/',
396                'CH' => '/^CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}$/',
397                'CY' => '/^CY[0-9]{2}[0-9]{8}[A-Z0-9]{16}$/',
398                'CZ' => '/^CZ[0-9]{2}[0-9]{4}[0-9]{16}$/',
399                'DE' => '/^DE[0-9]{2}[0-9]{8}[0-9]{10}$/',
400                'DK' => '/^DK[0-9]{2}[0-9]{4}[0-9]{10}$/',
401                'EE' => '/^EE[0-9]{2}[0-9]{4}[0-9]{12}$/',
402                'ES' => '/^ES[0-9]{2}[0-9]{8}[0-9]{12}$/',
403                'FR' => '/^FR[0-9]{2}[0-9]{10}[A-Z0-9]{13}$/',
404                'FI' => '/^FI[0-9]{2}[0-9]{6}[0-9]{8}$/',
405                'GB' => '/^GB[0-9]{2}[A-Z]{4}[0-9]{14}$/',
406                'GE' => '/^GE[0-9]{2}[A-Z]{2}[0-9]{16}$/',
407                'GI' => '/^GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}$/',
408                'GR' => '/^GR[0-9]{2}[0-9]{7}[A-Z0-9]{16}$/',
409                'HR' => '/^HR[0-9]{2}[0-9]{7}[0-9]{10}$/',
410                'HU' => '/^HU[0-9]{2}[0-9]{7}[0-9]{1}[0-9]{15}[0-9]{1}$/',
411                'IE' => '/^IE[0-9]{2}[A-Z0-9]{4}[0-9]{6}[0-9]{8}$/',
412                'IL' => '/^IL[0-9]{2}[0-9]{6}[0-9]{13}$/',
413                'IS' => '/^IS[0-9]{2}[0-9]{4}[0-9]{18}$/',
414                'IT' => '/^IT[0-9]{2}[A-Z]{1}[0-9]{10}[A-Z0-9]{12}$/',
415                'KW' => '/^KW[0-9]{2}[A-Z]{4}[A-Z0-9]{22}$/',
416                'LB' => '/^LB[0-9]{2}[0-9]{4}[A-Z0-9]{20}$/',
417                'LI' => '/^LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}$/',
418                'LU' => '/^LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}$/',
419                'LT' => '/^LT[0-9]{2}[0-9]{5}[0-9]{11}$/',
420                'LV' => '/^LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}$/',
421                'MC' => '/^MC[0-9]{2}[0-9]{10}[A-Z0-9]{11}[0-9]{2}$/',
422                'ME' => '/^ME[0-9]{2}[0-9]{3}[0-9]{15}$/',
423                'MK' => '/^MK[0-9]{2}[0-9]{3}[A-Z0-9]{10}[0-9]{2}$/',
424                'MR' => '/^MR[0-9]{2}[0-9]{10}[0-9]{13}$/',
425                'MT' => '/^MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}$/',
426                'MU' => '/^MU[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{15}[A-Z]{3}$/',
427                'NL' => '/^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/',
428                'NO' => '/^NO[0-9]{2}[0-9]{4}[0-9]{7}$/',
429                'PL' => '/^PL[0-9]{2}[0-9]{8}[0-9]{16}$/',
430                'PT' => '/^PT[0-9]{2}[0-9]{8}[0-9]{13}$/',
431                'RO' => '/^RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}$/',
432                'RS' => '/^RS[0-9]{2}[0-9]{3}[0-9]{15}$/',
433                'SA' => '/^SA[0-9]{2}[0-9]{2}[A-Z0-9]{18}$/',
434                'SE' => '/^SE[0-9]{2}[0-9]{3}[0-9]{17}$/',
435                'SI' => '/^SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}$/',
436                'SK' => '/^SK[0-9]{2}[0-9]{4}[0-9]{16}$/',
437                'SM' => '/^SM[0-9]{2}[A-Z]{1}[0-9]{10}[A-Z0-9]{12}$/',
438                'TN' => '/^TN[0-9]{2}[0-9]{5}[0-9]{15}$/',
439                'TR' => '/^TR[0-9]{2}[0-9]{5}[A-Z0-9]{17}$/',
440             );
441        }
442        return $_iban_countrycode_regex;
443    }
444
445    /**
446     * Class constructor
447     *
448     * @param string $iban IBAN to be validated / processed
449     *
450     * @access   public
451     */
452    function Validate_Finance_IBAN($iban = '')
453    {
454        $iban        = strtoupper($iban);
455        $this->_iban = $iban;
456    }
457
458    /**
459     * Returns the current IBAN
460     *
461     * @access    public
462     * @return    string
463     */
464    function getIBAN()
465    {
466        return $this->_iban;
467    } // end func getIBAN
468
469    /**
470     * Sets the current IBAN to a new value
471     *
472     * @param string $iban IBAN to be validated / processed
473     *
474     * @access    public
475     * @return    void
476     */
477    function setIBAN($iban = '')
478    {
479        $iban        = strtoupper($iban);
480        $this->_iban = $iban;
481    }
482
483    /**
484     * Performs validation of the IBAN
485     *
486     * @param string $arg optional parameter for calling validate as static function
487     *
488     * @access    public
489     * @return    boolean   true if no error found
490     */
491    function validate($arg = null)
492    {
493        if (isset($this) && is_a($this, 'Validate_Finance_IBAN')) {
494            $iban = $this->_iban;
495        } else {
496            $iban = $arg;
497        }
498        $iban = strtoupper($iban);
499
500        $errorcode = VALIDATE_FINANCE_IBAN_OK;
501
502        static $_iban_countrycode_countryname;
503        if (!isset($_iban_countrycode_countryname)) {
504            $_iban_countrycode_countryname = Validate_Finance_IBAN::_getCountrycodeCountryname();
505        }
506        static $_iban_countrycode_length;
507        if (!isset($_iban_countrycode_length)) {
508            $_iban_countrycode_ibanlength = Validate_Finance_IBAN::_getCountrycodeIBANLength();
509        }
510        static $_iban_countrycode_regex;
511        if (!isset($_iban_countrycode_regex)) {
512            $_iban_countrycode_regex = Validate_Finance_IBAN::_getCountrycodeRegex();
513        }
514
515        // 2 character abbreviation at start of IBAN - the country code
516        $abbrev = substr($iban, 0, 2);
517
518        if (strlen($iban) <= 4) {
519            $errorcode = VALIDATE_FINANCE_IBAN_TOO_SHORT;
520        } elseif (!isset( $_iban_countrycode_countryname[$abbrev])) {
521            $errorcode = VALIDATE_FINANCE_IBAN_COUNTRY_INVALID;
522        } elseif (strlen($iban) < $_iban_countrycode_ibanlength[$abbrev]) {
523            $errorcode = VALIDATE_FINANCE_IBAN_TOO_SHORT;
524        } elseif (strlen($iban) > $_iban_countrycode_ibanlength[$abbrev]) {
525            $errorcode = VALIDATE_FINANCE_IBAN_TOO_LONG;
526        } elseif (!preg_match($_iban_countrycode_regex[$abbrev], $iban)) {
527            $errorcode = VALIDATE_FINANCE_IBAN_INVALID_FORMAT;
528        } else {
529            // todo: maybe implement direct checks for bankcodes of certain countries
530
531            // let's see if checksum is also correct
532            $iban_replace_chars = range('A', 'Z');
533            foreach (range(10, 35) as $tempvalue) {
534                $iban_replace_values[] = strval($tempvalue);
535            }
536
537            // move first 4 chars (countrycode and checksum) to the end of the string
538            $tempiban = substr($iban, 4) . substr($iban, 0, 4);
539            $tempiban = str_replace(
540                $iban_replace_chars,
541                $iban_replace_values,
542                $tempiban
543            );
544
545            $tempcheckvalue = intval(substr($tempiban, 0, 1));
546            for ($strcounter = 1; $strcounter < strlen($tempiban); $strcounter++) {
547                $tempcheckvalue *= 10;
548                $tempcheckvalue += intval(substr($tempiban, $strcounter, 1));
549                $tempcheckvalue %= 97;
550            }
551
552            // checkvalue of 1 indicates correct IBAN checksum
553            if ($tempcheckvalue != 1) {
554                $errorcode = VALIDATE_FINANCE_IBAN_CHECKSUM_INVALID;
555            } else {
556                $errorcode = VALIDATE_FINANCE_IBAN_OK;
557            }
558        }
559
560        if (isset($this)) {
561            $this->_errorcode = $errorcode;
562        }
563        return ($errorcode == VALIDATE_FINANCE_IBAN_OK);
564    } // end func validate
565
566    /**
567     * Returns errorcode corresponding to last validation
568     *
569     * @access    public
570     * @return    integer    errorcode
571     */
572    function getErrorcode()
573    {
574        return $this->_errorcode;
575    } // end func getErrorcode
576
577    /**
578     * Returns the countrycode of the IBAN
579     *
580     * @access    public
581     * @return    string
582     */
583    function getCountrycode()
584    {
585        if (strlen($this->_iban)>4) {
586            // return first two characters
587            return substr($this->_iban, 0, 2);
588        } else {
589            $this->_errorcode = VALIDATE_FINANCE_IBAN_TOO_SHORT;
590            $msg              = $this->errorMessage($this->_errorcode);
591            return PEAR::raiseError(
592                $msg,
593                $this->_errorcode,
594                PEAR_ERROR_TRIGGER,
595                E_USER_WARNING,
596                "$msg in VALIDATE_FINANCE_IBAN::getCountrycode()"
597            );
598        }
599    } // end func getCountrycode
600
601    /**
602     * Returns the countryname of the IBAN
603     * If the name can not be determined then return the country code.
604     *
605     * @access    public
606     * @return    string
607     */
608    function getCountryname()
609    {
610        $countrycode = $this->getCountrycode();
611        if (is_string($countrycode)) {
612            $countryname = Validate_Finance_IBAN::_getCountrycodeCountryname();
613            return $countryname[$countrycode];
614        } else { // e.g. if it's an error
615            return $countrycode;
616        }
617    } // end func getCountryname
618
619    /**
620     * Returns the bankcode of the IBAN
621     *
622     * @access    public
623     * @return    string
624     */
625    function getBankcode()
626    {
627        if (!$this->validate()) {
628            $this->_errorcode = VALIDATE_FINANCE_IBAN_GENERAL_INVALID;
629            $msg              = $this->errorMessage($this->_errorcode);
630            return PEAR::raiseError(
631                $msg,
632                $this->_errorcode,
633                PEAR_ERROR_TRIGGER,
634                E_USER_WARNING,
635                "$msg in VALIDATE_FINANCE_IBAN::getBankcode()"
636            );
637        } else {
638            $bankcode = Validate_Finance_IBAN::_getCountrycodeBankcode();
639            $position = substr($this->_iban, 0, 2);
640            // find out where the bankcode is inside the iban
641            $currCountrycodeBankcode = $bankcode[$position];
642            // extract and return that code
643            return substr(
644                $this->_iban,
645                $currCountrycodeBankcode['start'],
646                $currCountrycodeBankcode['length']
647            );
648        }
649    } // end func getBankcode
650
651    /**
652     * Returns the bankaccount of the IBAN
653     *
654     * @access    public
655     * @return    string
656     */
657    function getBankaccount()
658    {
659        if (!$this->validate()) {
660            $this->_errorcode = VALIDATE_FINANCE_IBAN_GENERAL_INVALID;
661            //get error message
662            $msg = $this->errorMessage($this->_errorcode);
663            return PEAR::raiseError(
664                $msg,
665                $this->_errorcode,
666                PEAR_ERROR_TRIGGER,
667                E_USER_WARNING,
668                "$msg in VALIDATE_FINANCE_IBAN::getBankaccount()"
669            );
670        } else {
671            $bankaccount = Validate_Finance_IBAN::_getCountrycodeBankaccount();
672            //extract details
673            $currCountrycodeBankaccount = $bankaccount[substr($this->_iban, 0, 2)];
674            return substr(
675                $this->_iban,
676                $currCountrycodeBankaccount['start'],
677                $currCountrycodeBankaccount['length']
678            );
679        }
680    } // end func getAccount
681
682    /**
683     * Return a textual error message for an IBAN error code
684     *
685     * @param int $value error code
686     *
687     * @access  public
688     * @return  string  error message
689     */
690    function errorMessage($value)
691    {
692        // make the variable static so that it only has to do the defining on the first call
693        static $errorMessages;
694
695        // define the varies error messages
696        if (!isset($errorMessages)) {
697            $errorMessages = array(
698                VALIDATE_FINANCE_IBAN_OK                => 'no error',
699                VALIDATE_FINANCE_IBAN_ERROR             => 'unknown error',
700                VALIDATE_FINANCE_IBAN_GENERAL_INVALID   => 'IBAN generally invalid',
701                VALIDATE_FINANCE_IBAN_TOO_SHORT         => 'IBAN is too short',
702                VALIDATE_FINANCE_IBAN_TOO_LONG          => 'IBAN is too long',
703                VALIDATE_FINANCE_IBAN_COUNTRY_INVALID   => 'IBAN countrycode is invalid',
704                VALIDATE_FINANCE_IBAN_INVALID_FORMAT    => 'IBAN has invalid format',
705                VALIDATE_FINANCE_IBAN_CHECKSUM_INVALID  => 'IBAN checksum is invalid'
706            );
707        }
708
709        // If this is an error object, then grab the corresponding error code
710        if (PEAR::isError($value)) {
711            $value = $value->getCode();
712        }
713
714        // return the textual error message corresponding to the code
715        return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[VALIDATE_FINANCE_IBAN_ERROR];
716    } // end func errorMessage
717} // end class VALIDATE_FINANCE_IBAN
718