mirror of
https://github.com/YunoHost-Apps/dolibarr_ynh.git
synced 2024-09-03 18:35:53 +02:00
486 lines
16 KiB
PHP
486 lines
16 KiB
PHP
<?php
|
|
|
|
# PHP IBAN - http://code.google.com/p/php-iban - LGPLv3
|
|
|
|
# Global flag by request
|
|
$__disable_iiban_gmp_extension=false;
|
|
|
|
# Verify an IBAN number. Returns true or false.
|
|
# NOTE: Input can be printed 'IIBAN xx xx xx...' or 'IBAN xx xx xx...' or machine 'xxxxx' format.
|
|
function verify_iban($iban) {
|
|
|
|
# First convert to machine format.
|
|
$iban = iban_to_machine_format($iban);
|
|
|
|
# Get country of IBAN
|
|
$country = iban_get_country_part($iban);
|
|
|
|
# Test length of IBAN
|
|
if(strlen($iban)!=iban_country_get_iban_length($country)) { return false; }
|
|
|
|
# Get checksum of IBAN
|
|
$checksum = iban_get_checksum_part($iban);
|
|
|
|
# Get country-specific IBAN format regex
|
|
$regex = '/'.iban_country_get_iban_format_regex($country).'/';
|
|
|
|
# Check regex
|
|
if(preg_match($regex,$iban)) {
|
|
# Regex passed, check checksum
|
|
if(!iban_verify_checksum($iban)) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
|
|
# Otherwise it 'could' exist
|
|
return true;
|
|
}
|
|
|
|
# Convert an IBAN to machine format. To do this, we
|
|
# remove IBAN from the start, if present, and remove
|
|
# non basic roman letter / digit characters
|
|
function iban_to_machine_format($iban) {
|
|
# Uppercase and trim spaces from left
|
|
$iban = ltrim(strtoupper($iban));
|
|
# Remove IIBAN or IBAN from start of string, if present
|
|
$iban = preg_replace('/^I?IBAN/','',$iban);
|
|
# Remove all non basic roman letter / digit characters
|
|
$iban = preg_replace('/[^a-zA-Z0-9]/','',$iban);
|
|
return $iban;
|
|
}
|
|
|
|
# Convert an IBAN to human format. To do this, we
|
|
# simply insert spaces right now, as per the ECBS
|
|
# (European Committee for Banking Standards)
|
|
# recommendations available at:
|
|
# http://www.europeanpaymentscouncil.eu/knowledge_bank_download.cfm?file=ECBS%20standard%20implementation%20guidelines%20SIG203V3.2.pdf
|
|
function iban_to_human_format($iban) {
|
|
# First verify validity, or return
|
|
if(!verify_iban($iban)) { return false; }
|
|
# Add spaces every four characters
|
|
$human_iban = '';
|
|
for($i=0;$i<strlen($iban);$i++) {
|
|
$human_iban .= substr($iban,$i,1);
|
|
if(($i>0) && (($i+1)%4==0)) { $human_iban .= ' '; }
|
|
}
|
|
return $human_iban;
|
|
}
|
|
|
|
# Get the country part from an IBAN
|
|
function iban_get_country_part($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
return substr($iban,0,2);
|
|
}
|
|
|
|
# Get the checksum part from an IBAN
|
|
function iban_get_checksum_part($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
return substr($iban,2,2);
|
|
}
|
|
|
|
# Get the BBAN part from an IBAN
|
|
function iban_get_bban_part($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
return substr($iban,4);
|
|
}
|
|
|
|
# Check the checksum of an IBAN - code modified from Validate_Finance PEAR class
|
|
function iban_verify_checksum($iban) {
|
|
# convert to machine format
|
|
$iban = iban_to_machine_format($iban);
|
|
# move first 4 chars (countrycode and checksum) to the end of the string
|
|
$tempiban = substr($iban, 4).substr($iban, 0, 4);
|
|
# subsitutute chars
|
|
$tempiban = iban_checksum_string_replace($tempiban);
|
|
# mod97-10
|
|
$result = iban_mod97_10($tempiban);
|
|
# checkvalue of 1 indicates correct IBAN checksum
|
|
if ($result != 1) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
# Find the correct checksum for an IBAN
|
|
# $iban The IBAN whose checksum should be calculated
|
|
function iban_find_checksum($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
# move first 4 chars to right
|
|
$left = substr($iban,0,2) . '00'; # but set right-most 2 (checksum) to '00'
|
|
$right = substr($iban,4);
|
|
# glue back together
|
|
$tmp = $right . $left;
|
|
# convert letters using conversion table
|
|
$tmp = iban_checksum_string_replace($tmp);
|
|
# get mod97-10 output
|
|
$checksum = iban_mod97_10_checksum($tmp);
|
|
# return 98 minus the mod97-10 output, left zero padded to two digits
|
|
return str_pad((98-$checksum),2,'0',STR_PAD_LEFT);
|
|
}
|
|
|
|
# Set the correct checksum for an IBAN
|
|
# $iban IBAN whose checksum should be set
|
|
function iban_set_checksum($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
return substr($iban,0,2) . iban_find_checksum($iban) . substr($iban,4);
|
|
}
|
|
|
|
# Character substitution required for IBAN MOD97-10 checksum validation/generation
|
|
# $s Input string (IBAN)
|
|
function iban_checksum_string_replace($s) {
|
|
$iban_replace_chars = range('A','Z');
|
|
foreach (range(10,35) as $tempvalue) { $iban_replace_values[]=strval($tempvalue); }
|
|
return str_replace($iban_replace_chars,$iban_replace_values,$s);
|
|
}
|
|
|
|
# Same as below but actually returns resulting checksum
|
|
function iban_mod97_10_checksum($numeric_representation) {
|
|
$checksum = intval(substr($numeric_representation, 0, 1));
|
|
for ($position = 1; $position < strlen($numeric_representation); $position++) {
|
|
$checksum *= 10;
|
|
$checksum += intval(substr($numeric_representation,$position,1));
|
|
$checksum %= 97;
|
|
}
|
|
return $checksum;
|
|
}
|
|
|
|
# Perform MOD97-10 checksum calculation ('Germanic-level effiency' version - thanks Chris!)
|
|
function iban_mod97_10($numeric_representation) {
|
|
global $__disable_iiban_gmp_extension;
|
|
# prefer php5 gmp extension if available
|
|
if(!($__disable_iiban_gmp_extension) && function_exists('gmp_intval')) { return gmp_intval(gmp_mod(gmp_init($numeric_representation, 10),'97')) === 1; }
|
|
|
|
/*
|
|
# old manual processing (~16x slower)
|
|
$checksum = intval(substr($numeric_representation, 0, 1));
|
|
for ($position = 1; $position < strlen($numeric_representation); $position++) {
|
|
$checksum *= 10;
|
|
$checksum += intval(substr($numeric_representation,$position,1));
|
|
$checksum %= 97;
|
|
}
|
|
return $checksum;
|
|
*/
|
|
|
|
# new manual processing (~3x slower)
|
|
$length = strlen($numeric_representation);
|
|
$rest = "";
|
|
$position = 0;
|
|
while ($position < $length) {
|
|
$value = 9-strlen($rest);
|
|
$n = $rest . substr($numeric_representation,$position,$value);
|
|
$rest = $n % 97;
|
|
$position = $position + $value;
|
|
}
|
|
return ($rest === 1);
|
|
}
|
|
|
|
# Get an array of all the parts from an IBAN
|
|
function iban_get_parts($iban) {
|
|
return array(
|
|
'country' => iban_get_country_part($iban),
|
|
'checksum' => iban_get_checksum_part($iban),
|
|
'bban' => iban_get_bban_part($iban),
|
|
'bank' => iban_get_bank_part($iban),
|
|
'country' => iban_get_country_part($iban),
|
|
'branch' => iban_get_branch_part($iban),
|
|
'account' => iban_get_account_part($iban)
|
|
);
|
|
}
|
|
|
|
# Get the Bank ID (institution code) from an IBAN
|
|
function iban_get_bank_part($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
$country = iban_get_country_part($iban);
|
|
$start = iban_country_get_bankid_start_offset($country);
|
|
$stop = iban_country_get_bankid_stop_offset($country);
|
|
if($start!=''&&$stop!='') {
|
|
$bban = iban_get_bban_part($iban);
|
|
return substr($bban,$start,($stop-$start+1));
|
|
}
|
|
return '';
|
|
}
|
|
|
|
# Get the Branch ID (sort code) from an IBAN
|
|
function iban_get_branch_part($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
$country = iban_get_country_part($iban);
|
|
$start = iban_country_get_branchid_start_offset($country);
|
|
$stop = iban_country_get_branchid_stop_offset($country);
|
|
if($start!=''&&$stop!='') {
|
|
$bban = iban_get_bban_part($iban);
|
|
return substr($bban,$start,($stop-$start+1));
|
|
}
|
|
return '';
|
|
}
|
|
|
|
# Get the (branch-local) account ID from an IBAN
|
|
function iban_get_account_part($iban) {
|
|
$iban = iban_to_machine_format($iban);
|
|
$country = iban_get_country_part($iban);
|
|
$start = iban_country_get_branchid_stop_offset($country);
|
|
if($start=='') {
|
|
$start = iban_country_get_bankid_stop_offset($country);
|
|
}
|
|
if($start!='') {
|
|
$bban = iban_get_bban_part($iban);
|
|
return substr($bban,$start+1);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
# Get the name of an IBAN country
|
|
function iban_country_get_country_name($iban_country) {
|
|
return _iban_country_get_info($iban_country,'country_name');
|
|
}
|
|
|
|
# Get the domestic example for an IBAN country
|
|
function iban_country_get_domestic_example($iban_country) {
|
|
return _iban_country_get_info($iban_country,'domestic_example');
|
|
}
|
|
|
|
# Get the BBAN example for an IBAN country
|
|
function iban_country_get_bban_example($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_example');
|
|
}
|
|
|
|
# Get the BBAN format (in SWIFT format) for an IBAN country
|
|
function iban_country_get_bban_format_swift($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_format_swift');
|
|
}
|
|
|
|
# Get the BBAN format (as a regular expression) for an IBAN country
|
|
function iban_country_get_bban_format_regex($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_format_regex');
|
|
}
|
|
|
|
# Get the BBAN length for an IBAN country
|
|
function iban_country_get_bban_length($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_length');
|
|
}
|
|
|
|
# Get the IBAN example for an IBAN country
|
|
function iban_country_get_iban_example($iban_country) {
|
|
return _iban_country_get_info($iban_country,'iban_example');
|
|
}
|
|
|
|
# Get the IBAN format (in SWIFT format) for an IBAN country
|
|
function iban_country_get_iban_format_swift($iban_country) {
|
|
return _iban_country_get_info($iban_country,'iban_format_swift');
|
|
}
|
|
|
|
# Get the IBAN format (as a regular expression) for an IBAN country
|
|
function iban_country_get_iban_format_regex($iban_country) {
|
|
return _iban_country_get_info($iban_country,'iban_format_regex');
|
|
}
|
|
|
|
# Get the IBAN length for an IBAN country
|
|
function iban_country_get_iban_length($iban_country) {
|
|
return _iban_country_get_info($iban_country,'iban_length');
|
|
}
|
|
|
|
# Get the BBAN Bank ID start offset for an IBAN country
|
|
function iban_country_get_bankid_start_offset($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_bankid_start_offset');
|
|
}
|
|
|
|
# Get the BBAN Bank ID stop offset for an IBAN country
|
|
function iban_country_get_bankid_stop_offset($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_bankid_stop_offset');
|
|
}
|
|
|
|
# Get the BBAN Branch ID start offset for an IBAN country
|
|
function iban_country_get_branchid_start_offset($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_branchid_start_offset');
|
|
}
|
|
|
|
# Get the BBAN Branch ID stop offset for an IBAN country
|
|
function iban_country_get_branchid_stop_offset($iban_country) {
|
|
return _iban_country_get_info($iban_country,'bban_branchid_stop_offset');
|
|
}
|
|
|
|
# Get the registry edition for an IBAN country
|
|
function iban_country_get_registry_edition($iban_country) {
|
|
return _iban_country_get_info($iban_country,'registry_edition');
|
|
}
|
|
|
|
# Is the IBAN country a SEPA member?
|
|
function iban_country_is_sepa($iban_country) {
|
|
return _iban_country_get_info($iban_country,'country_sepa');
|
|
}
|
|
|
|
# Get the list of all IBAN countries
|
|
function iban_countries() {
|
|
global $_iban_registry;
|
|
return array_keys($_iban_registry);
|
|
}
|
|
|
|
# Given an incorrect IBAN, return an array of zero or more checksum-valid
|
|
# suggestions for what the user might have meant, based upon common
|
|
# mistranscriptions.
|
|
function iban_mistranscription_suggestions($incorrect_iban) {
|
|
|
|
# abort on ridiculous length input (but be liberal)
|
|
$length = strlen($incorrect_iban);
|
|
if($length<5 || $length>34) { return array('(length bad)'); }
|
|
|
|
# abort if mistranscriptions data is unable to load
|
|
if(!_iban_load_mistranscriptions()) { return array('(failed to load)'); }
|
|
|
|
# init
|
|
global $_iban_mistranscriptions;
|
|
$suggestions = array();
|
|
|
|
# we have a string of approximately IBAN-like length.
|
|
# ... now let's make suggestions.
|
|
$numbers = array('0','1','2','3','4','5','6','7','8','9');
|
|
for($i=0;$i<$length;$i++) {
|
|
# get the character at this position
|
|
$character = substr($incorrect_iban,$i,1);
|
|
# for each known transcription error resulting in this character
|
|
foreach($_iban_mistranscriptions[$character] as $possible_origin) {
|
|
# if we're:
|
|
# - in the first 2 characters (country) and the possible replacement
|
|
# is a letter
|
|
# - in the 3rd or 4th characters (checksum) and the possible
|
|
# replacement is a number
|
|
# - later in the string
|
|
if(($i<2 && !in_array($possible_origin,$numbers)) ||
|
|
($i>=2 && $i<=3 && in_array($possible_origin,$numbers)) ||
|
|
$i>3) {
|
|
# construct a possible IBAN using this possible origin for the
|
|
# mistranscribed character, replaced at this position only
|
|
$possible_iban = substr($incorrect_iban,0,$i) . $possible_origin . substr($incorrect_iban,$i+1);
|
|
# if the checksum passes, return it as a possibility
|
|
if(verify_iban($possible_iban)) {
|
|
array_push($suggestions,$possible_iban);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# now we check for the type of mistransposition case where all of
|
|
# the characters of a certain type within a string were mistransposed.
|
|
# - first generate a character frequency table
|
|
$char_freqs = array();
|
|
for($i=0;$i<strlen($incorrect_iban);$i++) {
|
|
if(!isset($char_freqs[substr($incorrect_iban,$i,1)])) {
|
|
$char_freqs[substr($incorrect_iban,$i,1)] = 1;
|
|
}
|
|
else {
|
|
$char_freqs[substr($incorrect_iban,$i,1)]++;
|
|
}
|
|
}
|
|
# - now, for each of the characters in the string...
|
|
foreach($char_freqs as $char=>$freq) {
|
|
# if the character occurs more than once
|
|
if($freq>1) {
|
|
# check the 'all occurrences of <char> were mistranscribed' case
|
|
foreach($_iban_mistranscriptions[$char] as $possible_origin) {
|
|
$possible_iban = str_replace($char,$possible_origin,$incorrect_iban);
|
|
if(verify_iban($possible_iban)) {
|
|
array_push($suggestions,$possible_iban);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $suggestions;
|
|
}
|
|
|
|
|
|
##### internal use functions - safe to ignore ######
|
|
|
|
# Load the IBAN registry from disk.
|
|
global $_iban_registry;
|
|
$_iban_registry = array();
|
|
_iban_load_registry();
|
|
function _iban_load_registry() {
|
|
global $_iban_registry;
|
|
# if the registry is not yet loaded, or has been corrupted, reload
|
|
if(!is_array($_iban_registry) || count($_iban_registry)<1) {
|
|
$data = file_get_contents(dirname(__FILE__) . '/registry.txt');
|
|
$lines = explode("\n",$data);
|
|
array_shift($lines); # drop leading description line
|
|
# loop through lines
|
|
foreach($lines as $line) {
|
|
if($line!='') {
|
|
# split to fields
|
|
$old_display_errors_value = ini_get('display_errors');
|
|
ini_set('display_errors',false);
|
|
$old_error_reporting_value = ini_get('error_reporting');
|
|
ini_set('error_reporting',false);
|
|
list($country,$country_name,$domestic_example,$bban_example,$bban_format_swift,$bban_format_regex,$bban_length,$iban_example,$iban_format_swift,$iban_format_regex,$iban_length,$bban_bankid_start_offset,$bban_bankid_stop_offset,$bban_branchid_start_offset,$bban_branchid_stop_offset,$registry_edition,$country_sepa) = explode('|',$line);
|
|
ini_set('display_errors',$old_display_errors_value);
|
|
ini_set('error_reporting',$old_error_reporting_value);
|
|
# assign to registry
|
|
$_iban_registry[$country] = array(
|
|
'country' => $country,
|
|
'country_name' => $country_name,
|
|
'country_sepa' => $country_sepa,
|
|
'domestic_example' => $domestic_example,
|
|
'bban_example' => $bban_example,
|
|
'bban_format_swift' => $bban_format_swift,
|
|
'bban_format_regex' => $bban_format_regex,
|
|
'bban_length' => $bban_length,
|
|
'iban_example' => $iban_example,
|
|
'iban_format_swift' => $iban_format_swift,
|
|
'iban_format_regex' => $iban_format_regex,
|
|
'iban_length' => $iban_length,
|
|
'bban_bankid_start_offset' => $bban_bankid_start_offset,
|
|
'bban_bankid_stop_offset' => $bban_bankid_stop_offset,
|
|
'bban_branchid_start_offset' => $bban_branchid_start_offset,
|
|
'bban_branchid_stop_offset' => $bban_branchid_stop_offset,
|
|
'registry_edition' => $registry_edition
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Get information from the IBAN registry by example IBAN / code combination
|
|
function _iban_get_info($iban,$code) {
|
|
$country = iban_get_country_part($iban);
|
|
return _iban_country_get_info($country,$code);
|
|
}
|
|
|
|
# Get information from the IBAN registry by country / code combination
|
|
function _iban_country_get_info($country,$code) {
|
|
global $_iban_registry;
|
|
$country = strtoupper($country);
|
|
$code = strtolower($code);
|
|
if(array_key_exists($country,$_iban_registry)) {
|
|
if(array_key_exists($code,$_iban_registry[$country])) {
|
|
return $_iban_registry[$country][$code];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
# Load common mistranscriptions from disk.
|
|
function _iban_load_mistranscriptions() {
|
|
global $_iban_mistranscriptions;
|
|
# do not reload if already present
|
|
if(is_array($_iban_mistranscriptions) && count($_iban_mistranscriptions) == 36) { return true; }
|
|
$_iban_mistranscriptions = array();
|
|
$file = dirname(__FILE__) . '/mistranscriptions.txt';
|
|
if(!file_exists($file) || !is_readable($file)) { return false; }
|
|
$data = file_get_contents($file);
|
|
$lines = explode("\n",$data);
|
|
foreach($lines as $line) {
|
|
# match lines with ' c-<x> = <something>' where x is a word-like character
|
|
if(preg_match('/^ *c-(\w) = (.*?)$/',$line,$matches)) {
|
|
# normalize the character to upper case
|
|
$character = strtoupper($matches[1]);
|
|
# break the possible origins list at '/', strip quotes & spaces
|
|
$chars = explode(' ',str_replace('"','',preg_replace('/ *?\/ *?/','',$matches[2])));
|
|
# assign as possible mistranscriptions for that character
|
|
$_iban_mistranscriptions[$character] = $chars;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
?>
|