Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 5f005dca

Přidáno uživatelem Jakub Juračka před téměř 5 roky(ů)

Issue #1107: Raiffeisen Bank parser for new file format (XML).

Zobrazit rozdíly:

application/controllers/import.php
*
*/
require_once APPPATH."libraries/importers/Raiffeisenbank/RB_Importer.php";
require_once APPPATH."libraries/importers/Raiffeisenbank/Parser_Ebanka.php";
require_once APPPATH."libraries/importers/Unicredit/UnicreditImport.php";
require_once APPPATH."libraries/importers/Unicredit/UnicreditSaver.php";
require_once APPPATH."libraries/importers/Raiffeisenbank/Parser_RB.php";
require_once APPPATH."libraries/importers/Raiffeisenbank/RB_Saver.php";
/**
* Handles importing of all types of bank listings into the database.
......
*/
private static $file_types = array
(
Bank_account_Model::TYPE_RAIFFEISENBANK => 'HTML Raiffeisenbank',
Bank_account_Model::TYPE_RAIFFEISENBANK => 'XML Raiffeisenbank',
Bank_account_Model::TYPE_FIO => 'Fio CSV',
Bank_account_Model::TYPE_UNICREDIT => 'Unicredit CSV'
);
......
switch ($bank_acc_model->type)
{
case Bank_account_Model::TYPE_RAIFFEISENBANK:
$this->import_ebank(
$this->import_raiffeisenbank(
$id, $form->listing->value,
Settings::get('email_enabled') &&
@$form_data['send_email_notice'] == 1,
......
}
}
/**
* Parse ebank account
/**
* Parse and imports Raiffeisen bank account statement
* This part of code was created for parsing RB XML account statemenets.
* This is only a modified code of function below
* "import_unicredit(@params)" which was created earlier.
*
* @author Jiri Svitak
* @param integer $back_account_id
* @author Jakub Juračka
*
* @param integer $bank_account_id
* @param string $file_url
* @param boolean $send_emails Send emails as payment accept notification?
* @param boolean $send_sms Send SMSs as payment accept notification?
* @param boolean $debtor_redir_react Reactivate debtor redirection?
* @param boolean $payment_notice_redir_react Reactivate payment notice redirection?
*/
private function import_ebank($bank_account_id, $url, $send_emails, $send_sms,
$debtor_redir_react, $payment_notice_redir_react)
private function import_raiffeisenbank($bank_account_id, $file_url,
$send_emails, $send_sms, $debtor_redir_react,
$payment_notice_redir_react)
{
try
{
$db = new Transfer_Model();
$db->transaction_start();
$parser = new Parser_RB;
// parse bank listing items
$parser->parse($file_url);
// get data
$data = $parser->get_data();
$parser = new Parser_Ebanka($send_emails, $send_sms);
if (isset($bank_account_id))
{
$ba = new Bank_account_Model($bank_account_id);
RB_Importer::$parsed_bank_acc = $ba;
}
else
// get header
$header = $parser->get_header();
// does match bank account in system with bank account of statement?
$ba = new Bank_account_Model($bank_account_id);
if ($ba->account_nr != $header['account_nr'] ||
$ba->bank_nr != $header['bank_nr'])
{
throw new RB_Exception("Nebyl nastaven bankovní účet k importu!");
$ba_nr = $ba->account_nr.'/'.$ba->bank_nr;
$listing_ba_nr = $header['account_nr'].'/'.$header['bank_nr'];
throw new RB_Exception(__(
'Bank account number in listing (%s) header does not match ' .
'bank account %s in database!', array($listing_ba_nr, $ba_nr)
));
}
// save bank statement
$statement = new Bank_statement_Model();
$statement->set_logger(FALSE);
$statement->bank_account_id = $bank_account_id;
$statement->user_id = $this->session->get('user_id');
$statement->user_id = $this->user_id;
$statement->type = self::$file_types[Bank_account_Model::TYPE_RAIFFEISENBANK];
$statement->from = $header['from'];
$statement->to = $header['to'];
$statement->opening_balance = $header['bal_start'];
$statement->closing_balance = $header['bal_end'];
$statement->save_throwable();
RB_Importer::$bank_statement_id = $statement->id;
RB_Importer::$user_id = $this->session->get('user_id');
RB_IMporter::$time_now = date('Y-m-d H:i:s');
// safe import is done by transaction processing started in method which called this one
$parser->parse($url);
// does imported bank account and bank account on the statement match?
if ($ba->account_nr != $parser->account_nr ||
$ba->bank_nr != $parser->bank_nr)
{
$ba_nr = $ba->account_nr."/".$ba->bank_nr;
$listing_ba_nr = $parser->account_nr."/".$parser->bank_nr;
throw new RB_Exception(__(
"Bank account number in listing (%s) header does not match " .
"bank account %s in database!", array($listing_ba_nr, $ba_nr)
));
}
// save bank listing items
$stats = RB_Saver::save(
$data, $bank_account_id, $statement->id,
$this->user_id, $send_emails, $send_sms
);
// save statement's from and to
$statement->from = $parser->from;
$statement->to = $parser->to;
// save statement number
$statement->statement_number = $parser->statement_number;
// save starting and ending balance
$statement->opening_balance = $parser->opening_balance;
$statement->closing_balance = $parser->closing_balance;
$statement->save_throwable();
$db->transaction_commit();
// redirection reactivation
......
catch (RB_Exception $e)
{
$db->transaction_rollback();
status::error(__('Import has failed.') . ' ' .
$e->getMessage(), NULL, FALSE
);
url::redirect('bank_accounts/show_all');
status::error(__('Import has failed.') . ' ' . $e->getMessage(), NULL, FALSE);
}
catch (Duplicity_Exception $e)
{
......
$db->transaction_rollback();
Log::add_exception($e);
status::error(
__('Import has failed') . '.<br>' . $e->getMessage(),
NULL, FALSE
__('Import has failed') . '.<br>' . $e->getMessage(), NULL, FALSE
);
}
}
}
/**
* Imports fio bank listing items from specified file.
*
application/i18n/cs_CZ/texts.php
'minimum membership interrupt period (months)' => 'Minimální doba přerušení členství (měsíce)',
'minute' => 'minuta',
'minutes' => 'minut',
'missing data (%s) on the line %d' => 'Chybějící data (%s) na řádku %d.',
'missing data in header - balane on start or end of statement period' => 'V hlavičce chybí data - stav účtu na začátku nebo konci období výpisu.',
'mod_rewrite is enabled' => 'Mod_rewrite je povolen',
'mod_rewrite is not enabled' => 'Mod_rewrite není povolen',
'mode' => 'Mód',
......
'php curl modul not installed' => 'PHP CURL modul není nainstalován',
'php fatal error' => 'PHP kritická chyba',
'platform' => 'Platforma',
'please, check the statement header' => 'Prosím, zkontrolujte hlavičku souboru.',
'please, check the file' => 'Prosím, zkontrolujte soubor.',
'please contact administrator' => 'Prosím kontaktujte administrátora.',
'please contact support' => 'Kontaktujte prosím podporu',
'please check your e-mail box' => 'Zkontrolujte prosím svou e-mailovou schránku',
......
'ports count' => 'Počet portů',
'possibility of canceling redirection to all ip addresses of member' => 'Možnost zrušení přesměrování všem IP adresám člena',
'possibility of canceling redirection to only current ip address' => 'Možnost zrušení přesměrování jen aktuální IP adrese',
'possible fault in iban, bank code or account number' => 'Možná chyba v položce IBAN, číslo účtu nebo kód banky.',
'postpaid' => 'Paušální',
'post title' => 'Tituly za jménem',
'pppoe' => 'PPPoE',
......
'statistics' => 'Statistiky',
'state' => 'Stav',
'statement' => 'Výpis',
'statement period (from - to) is not set' => 'Není nastaveno časové období (OD - DO).',
'statement number' => 'Číslo výpisu',
'statements' => 'Výpisy',
'stats' => 'Statistiky',
application/libraries/importers/Raiffeisenbank/Parser_Ebanka.php
<?php defined('SYSPATH') or die('No direct script access.');
/*
* This file is part of open source system FreenetIS
* and it is released under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
*/
require_once 'Parser_Html_Table.php';
require_once 'RB_Importer.php';
require_once 'RB_Exception.php';
/**
* Parser_Ebanka is a parser for getting data from bank account transaction listing
* in the HTML format used by the Czech bank called "Ebanka" (now Raiffeisen Bank).
*
* The parsing is a bit peculiar, because Ebanka uses different format for
* listings that are visible to general public (the "transparent" listing used
* by NGOV non-profit organizations) and different format for regular listing used in the
* ebanking application.
* This parser autodetects these two formats and parses the data according to it.
*
* Benchmarks:
* Machine: Notebook FSC Lifebook S7110, CPU: Pentium T2400 @ 1.8 GHz
* Win XP SP2, Apache 2.2.3, PHP 5.2.0
* Regular listing with 136 table rows (1 week listing): time=0.1 sec, memory=205 kB
* Regular listing with 2175 table rows (whole year listing): time=1.6 sec, memory=205 kB
* Transparent listing with 467 table rows: time=0.14 sec, memory=122 kB
*
*
* @author Tomas <Dulik at unart dot cz>
* @version 1.0
*/
class Parser_Ebanka extends Parser_Html_Table
{
/**
* Poslední řetězec před začátkem hlavní <TABLE>
*/
const START_STRING="Pohyby na";
/**
* U běžných (netransparentních) výpisů
*/
const YEAR_STRING="Bankovn";
/**
* String before account
*/
const ACCOUNT_STRING="IBAN:";
/**
* Rok výpisu ze záhlaví výpisu
*
* @var string|mixed
*/
protected $year = false;
/**
* Obsahuje jméno funkce, která se má zavolat poté, co naparsujeme
* 1 kompletní řádek HTML tabulky.
*
* Callback funkce, kterou můžeme volat např. pro uložení
* každého řádku výpisu do DB.
*
* @see set_callback
* @var string|mixed
*/
protected $callback;
/**
* $result je vkládán do 1. parametru $callback funkce,
* která přes něj předává jeden řádek výsledku.
*
* Pokud $result není nastaven funkcí set_callback,
* pak jde je nastaven na instanci std_class
*
* @var object
*/
protected $result;
/**
* Send SMS notification of received payments
*
* @since 1.1
* @var integer
*/
protected $send_sms = Notifications_Controller::KEEP;
/**
* Send e-mail notification of received payments
*
* @since 1.1
* @var integer
*/
protected $send_emails = Notifications_Controller::KEEP;
/**
* Construct
*
* @param boolean $send_emails Send e-mail notification of received payments?
* @param boolean $send_sms Send SMS notification of received payments?
*/
public function __construct($send_emails, $send_sms)
{
if ($send_emails)
{
$this->send_emails = Notifications_Controller::ACTIVATE;
}
if ($send_sms)
{
$this->send_sms = Notifications_Controller::ACTIVATE;
}
}
/**
*
* Function parses date from and to of the statement and statement number.
*
* @author Tomas Dulik, Jiri Svitak
*/
protected function get_statement_number_and_interval()
{
// code added by jsvitak, parsing date from and to
// hledej první "." v datumu
while (($pos = strpos($this->buffer, "<")) === false &&
$this->get_line());
if ($pos === false)
{
throw new RB_Exception("Nemůžu najít první znak '.' v řetězci ' za [období] ...'");
}
$toPos = substr($this->buffer, 0, $pos);
$parts = explode(' ', $toPos);
// parse statement number
$this->statement_number = intval($parts[3]);
// parse date from and to
$dates = explode('/', end($parts));
$from_arr = array_map('intval', explode('.', $dates[0]));
// save year for transfer creation purposes
$this->year = $from_arr[2];
$from_timestamp = mktime(0, 0, 0, $from_arr[1], $from_arr[0], $from_arr[2]);
// save date from
$this->from = date('Y-m-d', $from_timestamp);
// date to is set
if (isset($dates[1]))
{
$to_arr = array_map('intval', explode(".", $dates[1]));
$to_timestamp = mktime(0, 0, 0, $to_arr[1], $to_arr[0], $to_arr[2]);
// save date to
$this->to = date('Y-m-d', $to_timestamp);
}
else
// date to is not set, date from will be used
$this->to = $this->from;
}
/**
* Sets account number
*/
protected function get_cislo_uctu()
{
while (($czPos = stripos($this->buffer, "CZ")) === false &&
$this->get_line()); // hledej lomítko
if ($czPos === false)
{
throw new RB_Exception("Nemůžu najít 'CZ' v IBAN čísle účtu");
}
else
{
$this->bank_nr = substr($this->buffer, $czPos + 4, 4);
$account_nr = (int) substr($this->buffer, $czPos + 8, 16);
$this->account_nr = "$account_nr";
}
}
protected function get_balances()
{
$this->find_tags_and_trim(array(iconv("utf-8", "windows-1250", "Počáteční zůstatek"), self::START_STRING));
$this->find_tag_and_trim("\"RIGHT\">");
$raw_amount = substr($this->buffer, 0, strpos($this->buffer, "<"));
if ($raw_amount == "&nbsp;")
{
$this->find_tag_and_trim("\"RIGHT\">");
$raw_amount = substr($this->buffer, 0, strpos($this->buffer, "<"));
}
$this->opening_balance = str_replace(" ", "", $raw_amount);
//echo $this->opening_balance;
$this->find_tags_and_trim(array(iconv("utf-8", "windows-1250", "Konečný zůstatek"), self::START_STRING));
$this->find_tag_and_trim("\"RIGHT\">");
$raw_amount = substr($this->buffer, 0, strpos($this->buffer, "<"));
if ($raw_amount == "&nbsp;")
{
$this->find_tag_and_trim("\"RIGHT\">");
$raw_amount = substr($this->buffer, 0, strpos($this->buffer, "<"));
}
$this->closing_balance = str_replace(" ", "", $raw_amount);
//echo $this->closing_balance;
//echo $this->buffer;
//die();
}
/**
* Gets amount
*
* @param string $field
* @return string
*/
protected function get_amount($field)
{
$field = strip_tags($field);
$field = str_replace(array(" ", " "), "", $field);
return strtr($field, ",", ".");
}
/**
* V posledním sloupci HTML výpisu ebanka dává tyto poplatky: Poplatek, směna, zpráva.
* Poplatky jsou odděleny značkami <br>, u transp. výpisu <br/>
*
* @param $field
* @param $transparent
* @return ineteger součet všech poplatků generovaných pro daný řádek
*/
protected function get_fee($field, $transparent)
{
$field = str_replace(array(" ", " "), "", $field);
$field = strtr($field, ",", ".");
if ($transparent)
{
$br_tag = "<br/>";
}
else
{
$br_tag = "<br>";
}
$arr = preg_split("/<br>/si", $field);
$fee = 0;
foreach ($arr as $value)
{
$fee += $value;
}
return $fee;
}
/**
* Gets data from transparent
*/
protected function get_data_from_transparent()
{
$res = $this->result;
if ($res == NULL)
{
$res = new stdClass();
}
$first = true;
$line_nr = 0;
$rb_importer = new RB_Importer();
do
{
$status = $this->get_table_rows();
$nr = count($this->matches[1]);
$fields = str_replace(array("\r", "\n", "\t"), "", $this->matches[1]);
for ($i = 0; $i < $nr; $i++)
{
$field_nr = $i % 6;
$field = $fields[$i];
switch ($field_nr)
{
case 0: // příklad: 31.08.2008<br/>06:1
$arr = explode("<br/>", $field);
$arrDate = explode(".", $arr[0]);
$res->date_time = $arrDate[0];
break;
case 1: // Poznámky<br/>Název účtu plátce
$field = html_entity_decode($field, ENT_QUOTES, "UTF-8");
$arr = explode("<br/>", $field);
$res->comment = $arr[0];
$res->name = $arr[1];
break;
case 2: //2x za sebou datum odepsání<br/>typ platby
$arr = explode("<br/>", $field);
$res->type = html_entity_decode($arr[2], ENT_QUOTES, "UTF-8");
break;
case 3:
$arr = explode("<br/>", $field); //VS<br/>KS<br/>SS
$res->variable_symbol = (int) $arr[0];
$res->constant_symbol = $arr[1];
$res->specific_symbol = (int) $arr[2];
break;
case 4:
$res->amount = $this->get_amount($field); // částka
break;
case 5:
$res->fee = $this->get_fee($field, TRUE); // fee
$line_nr++;
$res->number = $line_nr;
//ted uz muzeme ulozit ziskane data do databaze:
//if (isset($this->callback))
// call_user_func($this->callback, $res);
$rb_importer->store_transfer_ebanka(
$res, $this->send_emails, $this->send_sms
);
break;
} // switch
} // for
} while ($status !== false);
}
/**
* Gets data from regular
*/
protected function get_data_from_regular()
{
$res = $this->result;
if ($res == NULL)
{
$res = new stdClass();
}
$first = true;
$rb_importer = new RB_Importer();
do
{
if (($status = $this->get_table_rows()) !== FALSE)
{
$nr = count($this->matches[1]);
$fields = str_replace(array("\r", "\n", "\t"), "", $this->matches[1]);
if ($first)
{
$i = 7;
$first = false;
}
else
{
$i = 0;
}
for (; $i < $nr; $i++)
{
$field_nr = $i % 7;
$field = $fields[$i];
// odstraneni &nbsp;
$field = html_entity_decode($field, ENT_QUOTES, "UTF-8");
$field = str_replace(" ", "", $field);
switch ($field_nr)
{
case 0: // číslo výpisu, každý měsíc od 1 do N
if (!is_numeric($field))
{
ob_flush();
flush();
throw new RB_Exception("Parser error: " .
"očekával jsem číslo výpisu, ale dostal jsem:<br/>\n" . $field .
"<br/> \nPoslední správně načtený řádek výpisu má číslo " . $res->number .
"<br/> \nCelý vstupní buffer je: <br/>\n" . htmlentities($this->buffer)
//,E_USER_ERROR
);
}
$res->number = $field;
break;
case 1: // datum a čas příklad: 08.08.<br>06:11
$arr = preg_split("/<br>/si", $field);
if (count($arr) < 2)
{
throw new RB_Exception("Parser error: " .
"očekávám datum/čas jako dd.mm.&lt;br&gt;hh:mm ale dostal jsem:<br/>\n" . $field .
"<br/> \nPoslední správně načtený řádek výpisu má číslo " . $res->number .
"<br/> \nCelý vstupní buffer je: <br/>\n" . htmlentities($this->buffer), E_USER_ERROR);
}
else
{
$arrDate = explode(".", $arr[0]);
if (count($arrDate) < 2)
throw new RB_Exception("Parser error: " .
"očekávám datum jako dd.mm. ale dostal jsem:<br/>\n" . $arr[0] .
"<br/> \nPoslední správně načtený řádek výpisu má číslo " . $res->number .
"<br/> \nCelý vstupní buffer je: <br/>\n" . htmlentities($this->buffer), E_USER_ERROR);
$res->date_time = $this->year . "-" . $arrDate[1] . "-" . $arrDate[0] . " " . $arr[1];
}
break;
case 2: // Poznámky<br>Název účtu a<br>číslo účtu plátce
$arr = preg_split("/<br>/si", $field); // dělelní dle <BR> nebo <br>
if (isset($arr[0]))
{
$res->comment = $arr[0];
}
if (isset($arr[1]))
{
$res->name = $arr[1];
}
if (isset($arr[2]))
{
$account_arr = explode("/", $arr[2]);
if (isset($account_arr[0]))
{
$res->account_nr = $account_arr[0];
}
if (isset($account_arr[1]))
{
$res->account_bank_nr = $account_arr[1];
}
}
break;
case 3: //datum odepsání<br><br>typ platby
$arr = preg_split("/<br>/si", $field);
$res->type = $arr[2];
break;
case 4: //SS<br>VS<br>KS
$arr = preg_split("/<br>/si", $field);
$res->variable_symbol = $arr[1];
$res->constant_symbol = $arr[2];
$res->specific_symbol = $arr[0];
break;
case 5: // částka
$res->amount = $this->get_amount($field);
break;
case 6: // fee
$res->fee = $this->get_fee($field, FALSE);
//if (isset($this->callback)) call_user_func($this->callback, $res);
$rb_importer->store_transfer_ebanka(
$res, $this->send_emails, $this->send_sms
);
/**
* ted uz muzeme ulozit ziskane data do databaze:
*/
break;
}
}
}
// dělej dokud nenajdeš konec souboru nebo tabulky
} while ($status !== false && $this->table_end == false);
}
/**
* Parses url
*
* @param string $url
* @param integer $account_id
*/
public function parse($url, $account_id=NULL)
{
$this->open($url);
$this->get_charset();
// Now: search for the begining of the table or the date
$found = $this->find_tags_and_trim(array
(
self::YEAR_STRING, self::ACCOUNT_STRING, self::START_STRING
));
switch ($found)
{
case 0: // období výpisu nalezeno = standardní (netransparentní) výpis
$transparent = false;
$this->get_statement_number_and_interval();
// zkus ještě najít číslo účtu
$found = $this->find_tags_and_trim(array(self::ACCOUNT_STRING, self::START_STRING));
$this->get_cislo_uctu();
// najdi počáteční a konečný zůstatek
$this->get_balances();
/*
// chyba (toto by nikdy nemělo nastat)
if ($found == 2)
{
throw new RB_Exception("Nemohu najít začátek tabulky: '" . self::START_STRING . "'");
}
// našel jsem START_STRING => číslo účtu nemám, konec switch-e
else if ($found == 1)
{
break;
}
*
*/
//else goto case 1: protože jsem našel číslo účtu
$found = $this->find_tag_and_trim(self::START_STRING);
break;
case 1: //nalezeno číslo účtu,
$this->get_cislo_uctu();
// najdi začátek výpisu
$found = $this->find_tag_and_trim(self::START_STRING);
// chyba (toto by nikdy nemělo nastat)
if ($found === false)
{
throw new RB_Exception("Nemohu najít začátek tabulky: '" . self::START_STRING . "'");
}
break;
case 2: //nalezen start tabulky s výpisy, což znamená, že
// období výpisu nenalezeno => transparentní výpis
$transparent = true;
// jako rok doplň aktuální rok
$this->year = date("Y");
break;
case 3:
throw new RB_Exception("Nemohu najít začátek tabulky nebo datum/rok");
break;
};
if ($transparent)
{
$this->get_data_from_transparent();
}
else
{
$this->get_data_from_regular();
}
fclose($this->file);
}
}
application/libraries/importers/Raiffeisenbank/Parser_Html_Table.php
<?php defined('SYSPATH') or die('No direct script access.');
/*
* This file is part of open source system FreenetIS
* and it is released under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
*/
/**
* Parser_Html_Table is ABSTRACT class containing methods useful
* for parsing HTML tables in generic HTML files.
*
* Motivation: we want to parse HTML tables to get interesting data from various web sites.
* The HTML code of the tables often does not conforms to XML/XHTML rules.
* It often does not conform even HTML4, e.g. - the table row is not closed by </tr>,
* table cell is not closed by </td> etc.
* Therefore, XML parsers can't be used for this.
* The Tidy extension is not available on all hostings.
* If you think about parsing a non-XHTML non-HTML4.0 table, look at this class.
* The methods have been optimized to give maximum possible performance
* and memory efficiency.
* For an example how to use this class, see the Parser_Ebanka class
*
* @author Tomas <Dulik at unart dot cz>
* @version 1.0
*/
abstract class Parser_Html_Table
{
const TIMEOUT = 3;
/**
* File descriptor
*
* @var resource
*/
protected $file;
/**
* Charset
*
* @var string
*/
protected $charset;
/**
* Buffer
*
* @var string
*/
protected $buffer;
/**
* The position of the last End Of Line in the buffer
*
* @var integer
*/
protected $eoln_pos;
/**
* Var for transfering matched items from preg_match
*
* @var array
*/
protected $matches;
/**
* Table end indicator
*
* @var bool
*/
protected $table_end = false;
/**
* Opens URL
*
* @param string $url
*/
public function open($url)
{
if ($url != "")
{
$old = ini_set('default_socket_timeout', self::TIMEOUT);
if (($this->file = fopen($url, "rb")) === false)
die("Can not open file! Check if $url exists!");
ini_set('default_socket_timeout', $old);
stream_set_timeout($this->file, self::TIMEOUT);
//stream_set_blocking($this->file, 0);
}
}
/**
* get_line appends **AT LEAST** one line from the $file into the $buffer.
*
*
* @return boolean
* @uses buffer, eoln_pos;
* In PHP4, this is MUCH faster than using fgets because of a PHP bug.
* In PHP5, this is usualy still faster than the following version based on fgets:
*
* protected function get_line_fgets() {
* if (!feof($this->file))
* $this->buffer .= fgets($this->file);
* else return false;
* $this->eoln_pos=strlen($this->buffer);
* return true;
* }
*
* Note for HTML files with super long lines (hundreds of kbytes without single
* EOLN) the fgets would be useless - it'd take a lot of memory to read a single line!
* For such files, you should modify the code of my function this way:
* Replace
* ...eoln_pos=strripos($this->buffer,"\n"))
* by something like
* ...eoln_pos=find_row_end()
*/
public function get_line()
{
while (!feof($this->file))
{
// read 8192 bytes from file or one packet
$new_part = fread($this->file, 8192);
$this->buffer .= $new_part;
// search eoln from end: found ?
if (($this->eoln_pos = strripos($this->buffer, "\n")) !== false)
{
// eoln found! done, OK...
return true;
}
}
// EOF happened ?
if (!isset($new_part))
{
// EOF right when the function begun? Return EOF!
return false;
}
// EOF happened but no EOLN
$this->eoln_pos = strlen($this->buffer); // set eoln_pos to EOF...
return true;
}
/**
* find_tag_and_trim($tag) tries to find the tag in the $this->buffer
* and trim the beginning of the buffer till (and including) the $tag
* returns false if string not found.
* returns true if string found, and the variable $this->buffer contains
* string trimmed from the first occurence of $tag
*/
protected function find_tag_and_trim($tag)
{
$found = false;
do
{
// can you find the tag ?
if (($pos = stripos($this->buffer, $tag)) !== false)
{
$found = true;
// set the cut $pos(ition) behind $tag
$pos += strlen($tag);
// now cut away everything from the beginning till the cut position
$this->buffer = substr($this->buffer, $pos);
// and update the counters
$this->eoln_pos -= $pos;
}
// tag not found and eoln found previously?
else if ($this->eoln_pos > 0)
{
// cut away all from beginning till eoln
$this->buffer = substr($this->buffer, $this->eoln_pos);
// so we don't have to deal with these lines again
$this->eoln_pos = 0;
}
}
while (!$found && $this->get_line());
return $found;
}
/**
* The same as previous function, but for multiple tags search.
* If tag is found, returns the tag index in the $tags array.
* If tag is not found, returns number of $tags+1
*
* @param array $tags
* @return integer
*/
protected function find_tags_and_trim($tags)
{
$found = false;
do
{
$i = 0;
// for all the tags do:
foreach ($tags as $tag)
{
// can you find the startag ?
if (($pos = stripos($this->buffer, $tag)) !== false)
{
$found = true;
// set the cut $pos(ition) behind $tag
$pos+=strlen($tag);
// now cut away everything from the beginning till the cut position
$this->buffer = substr($this->buffer, $pos);
// and update the counters
$this->eoln_pos -= $pos;
break;
// this tag not found - increment cntr and try another one
}
else
{
$i++;
}
}
// tags not found and eoln found previously?
if (!$found && $this->eoln_pos > 0)
{
// cut away all from beginning till eoln
$this->buffer = substr($this->buffer, $this->eoln_pos);
$this->eoln_pos = 0;
}
}
while (!$found && $this->get_line());
return $i;
}
/**
* this functions tries to find the end of table row.
* It can handle even rows terminated incorrectly by
* </table> instead of </tr>
*
* @return integer The position of the end row tag (</tr> or </table>)
* or false if the tag is not found.
*/
protected function find_row_end()
{
/**
* PHP5 version: in PHP5, strripos can search whole string,
* not only 1 char as in PHP4
*/
if (($res = stripos($this->buffer, "<table")) !== false ||
($res = stripos($this->buffer, "</table")) !== false)
{
$this->table_end = true;
return $res;
}
if (($res = strripos($this->buffer, "</tr")) !== false)
return $res;
return strripos($this->buffer, "<tr");
/**
* PHP4 version: we have to use perl regular expressions...
* This is only 0.03sec/100kB slower than PHP5 strripos version
*
$matchCnt=preg_match("/<[\/]?(?:tr|table)(?!.*<[\/]?(tr|table))/si",$this->buffer, $matches, PREG_OFFSET_CAPTURE);
if ($matchCnt==1) return $matches[0][1];
else return false;
*/
}
/**
* get_table_rows tries to fill the buffer with at least one table row (<tr>...<[/]tr>) string.
* It then parses the rows using a regular expression, which returns the content of the
* table cells in the $this->matches array
* Because fread reads whole blocks, it is possible this
*
* @return bool
*/
protected function get_table_rows()
{
// Try to find the starting <tr> tag:
if (!$this->find_tag_and_trim("<tr"))
return false;
// now try to find the last <[/]tr> or <[/]table> tag by searching these
// tags not followed by the same tags, if not successfull, read the
// next line of the file. Do it until EOF or table end
while (($lastTagPos = $this->find_row_end()) === false &&
$this->table_end == false &&
$this->get_line());
// if <tr> not found untill EOF, return EOF
if ($lastTagPos === false)
return false;
// $rows is string containing several <tr>...<tr>... ended by <tr>
$rows = substr($this->buffer, 0, $lastTagPos);
// if HTML charset is not UTF-8
if (strcasecmp($this->charset, "utf-8") != 0)
{
// convert it to UTF-8
$rows = iconv($this->charset, "UTF-8", $rows);
}
// Now: get the contents of all the table cells (the texts between
// <td > and <td > or </td> or <tr> or </tr> tags
preg_match_all("/<td[^>]*>(?:<[^>]*>)?(.*?)<(?:(?:\/)?td|tr|table)/si", $rows, $this->matches);
$this->buffer = substr($this->buffer, $lastTagPos);
if ($this->eoln_pos > $lastTagPos)
{
$this->eoln_pos -= $lastTagPos;
}
else
{
$this->eoln_pos = 0;
}
return true;
}
/**
* Sets charset
*/
protected function get_charset()
{
// if charset is missng set utf8
if (!$this->find_tag_and_trim("charset="))
{
$this->charset = "utf-8";
}
else
{
// try to find "
if (($quotesPos = strpos($this->buffer, '"')) === false)
{
if (($quotesPos = strpos($this->buffer, "'")) === false)
{
die("Can't find the quotes after 'charset=...'");
}
}
$this->charset = substr($this->buffer, 0, $quotesPos);
}
}
/**
* Parse method
*
* @param string $url
*/
abstract function parse($url);
}
application/libraries/importers/Raiffeisenbank/Parser_RB.php
<?php defined('SYSPATH') or die('No direct script access.');
/*
* This file is part of open source system FreenetIS
* and it is released under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
*/
require_once 'RB_Exception.php';
/**
* Parser_RB (Raiffeisen Bank) is a parser for getting data from a bank account
* statement in the new (2018) XML format.
*
* @author Jakub Juračka
* @version 1.0
*/
class Parser_RB
{
/**
* TYPE OF XML section which contains incoming payments.
*
* @var string
*/
private static $TYPE_INCOME = 'CRDT';
/**
* TYPE OF XML section which contains outgoing payments.
*
* @var string
*/
private static $TYPE_OUTG = 'DBIT';
/**
* TYPE OF XML section for saving BALANCE from the begining and end of
* statement period.
*
* @var string
*/
private static $TYPE_BAL = 'CLAV';
/**
* Store the simple_xml object with bank statement.
*
* @var simple_xml object
*/
protected $data;
/**
* Parsed header of bank statement.
*
* @var array()
*/
protected $header;
/**
* Contains parsed rows of bank statement with incoming/outgoing statements.
*
* @var string
*/
protected $rows;
/**
* Opening datafile and saving simple_xml object to variable $data
*
* @param string $url - File URL path
*
* @author Jakub Juračka
*/
private function open($url)
{
if ($this->data = simplexml_load_file($url))
{
$this->data = $this->data->BkToCstmrStmt->Stmt;
}
else
{
throw new Exception("Can not open file! Check if $url exists!");
}
}
/**
* Loading header and saving array
* with bank account info to the variable $header.
*
* @author Jakub Juračka
*/
private function loadHeader()
{
$datetime_info = $this->data->FrToDt;
$from = isset($datetime_info->FrDtTm) ? $datetime_info->FrDtTm : NULL;
$to = isset($datetime_info->ToDtTm) ? $datetime_info->ToDtTm : NULL;
if (!self::is_correct($from) || !self::is_correct($to))
{
throw new RB_Exception(__('Statement period (FROM - TO) is not set. Please, check the statement header.'));
}
//Proccessing datetime to sql format
$from = str_replace('T', ' ', $from);
$to = str_replace('T', ' ', $to);
////////////////////////////////////
$acc_info = $this->data->Acct;
foreach ($this->data->Bal as $Balance)
{
$type = $Balance->Tp->CdOrPrtry->Cd;
if ($type == self::$TYPE_BAL && !isset($balance_start_obj))
$balance_start_obj = $Balance;
if ($type == self::$TYPE_BAL && $Balance->Dt->Dt != $balance_start_obj->Dt->Dt)
$balance_end_obj = $Balance;
}
if (!isset($balance_start_obj) || !isset($balance_end_obj))
{
throw new RB_Exception(__('Please, check the file. ') . ' '
. __('Missing data in header - balance on START or END of statement period.'));
}
//GETTING BALANCE INFO
$money_status_start = $balance_start_obj->Amt;
$money_status_end = $balance_end_obj->Amt;
//GETTING INFO ABOUT BANK ACOUNT
$IBAN = isset($acc_info->Id->IBAN) ? $acc_info->Id->IBAN : NULL;
$currency = $acc_info->Ccy;
$bank_code = isset($acc_info->Svcr->FinInstnId->Othr->Id) ?
$acc_info->Svcr->FinInstnId->Othr->Id : NULL;
$BIC = isset($acc_info->Svcr->FinInstnId->BIC) ?
$acc_info->Svcr->FinInstnId->BIC : NULL;
$acc_num = substr($IBAN, -10);
if (!self::is_correct($IBAN) || !self::is_correct($bank_code) ||
!self::is_correct($acc_num))
{
throw new RB_Exception(__('Please, check the file.') . ' '
. __('Possible fault in IBAN, BANK CODE or ACCOUNT NUMBER.'));
}
$this->header = array
(
'from' => $from,
'to' => $to,
'IBAN' => $IBAN,
'currency' => $currency,
'bank_nr' => $bank_code,
'BIC' => $BIC,
'account_nr' => $acc_num,
'bal_start' => $money_status_start,
'bal_end' => $money_status_end,
);
}
/**
* Prasing $data variable to variable $rows
*
* @param string $url - File URL path
*
* @author Jakub Juračka
*/
public function parse($url)
{
//FILE LOADING
$this->open($url);
//Loading header (getting info about account assigned to the statement)
$this->loadHeader();
//Parsing data - if datetime, account number, bank_code or amount is not defined => throw an error
$this->rows = array();
//Couting proccessed rows
$iteration = 1;
//Processing payments
foreach ($this->data->Ntry as $row)
{
//Auxiliary variables $type, $detail
$type = $row->CdtDbtInd;
$detail = $row->NtryDtls->TxDtls;
//Testing if the payment is OUTGING/INCOMING
// and saving to the variable $rows
if ($type == self::$TYPE_OUTG)
{
if (!self::is_correct($row->ValDt->DtTm))
self::throw_error_row($iteration, 'Date and time');
if (!self::is_correct($detail->RltdPties->CdtrAcct->Id->Othr->Id))
self::throw_error_row($iteration, 'Account number');
if (!self::is_correct($detail->RltdAgts->CdtrAgt->FinInstnId->Othr->Id))
self::throw_error_row($iteration, 'Bank code');
if (!self::is_correct($row->Amt))
self::throw_error_row($iteration, 'Amount');
array_push($this->rows, array
(
'datetime' => $row->ValDt->DtTm,
'transaction_id' => isset($row->NtryRef) ? intval($row->NtryRef) : NULL,
'acc_num' => strval($detail->RltdPties->CdtrAcct->Id->Othr->Id),
'vs' => isset($detail->Refs->EndToEndId) ?
intval($detail->Refs->EndToEndId) : NULL,
'ks' => isset($detail->Refs->InstrId) ?
intval($detail->Refs->InstrId) : NULL,
'ss' => isset($detail->Refs->PmtInfId) ?
intval($detail->Refs->PmtInfId) : NULL,
'name' => isset($detail->RltdPties->DbtrAcct->Nm) ?
strval($detail->RltdPties->DbtrAcct->Nm) : NULL,
'bank_code' => strval($detail->RltdAgts->CdtrAgt->FinInstnId->Othr->Id),
'amount' => ($row->Amt) * (-1),
'text' => isset($detail->AddtlTxInf) ? strval($detail->AddtlTxInf) : ''
));
}
else if ($type == self::$TYPE_INCOME)
{
if (!self::is_correct($row->ValDt->DtTm))
self::throw_error_row($iteration, 'Date and time');
if (!self::is_correct($detail->RltdPties->DbtrAcct->Id->Othr->Id))
self::throw_error_row($iteration, 'Account number');
if (!self::is_correct($detail->RltdAgts->DbtrAgt->FinInstnId->Othr->Id))
self::throw_error_row($iteration, 'Bank code');
if (!self::is_correct($row->Amt))
self::throw_error_row($iteration, 'Amount');
array_push($this->rows, array
(
'datetime' => $row->ValDt->DtTm,
'transaction_id' => isset($row->NtryRef) ? intval($row->NtryRef) : NULL,
'acc_num' => strval($detail->RltdPties->DbtrAcct->Id->Othr->Id),
'vs' => isset($detail->Refs->EndToEndId) ?
intval($detail->Refs->EndToEndId) : NULL,
'ks' => isset($detail->Refs->InstrId) ?
intval($detail->Refs->InstrId) : NULL,
'ss' => isset($detail->Refs->PmtInfId) ?
intval($detail->Refs->PmtInfId) : NULL,
'name' => isset($detail->RltdPties->DbtrAcct->Nm) ?
strval($detail->RltdPties->DbtrAcct->Nm) : NULL,
'bank_code' => strval($detail->RltdAgts->DbtrAgt->FinInstnId->Othr->Id),
'amount' => $row->Amt,
'text' => isset($detail->AddtlTxInf) ? strval($detail->AddtlTxInf) : ''
));
}
$iteration++;
}
}
/**
* Function which only returns rows with incoming/outgoing payments
*
* @author Jakub Juračka
*/
public function get_data()
{
return $this->rows;
}
/**
* Function which only returns info about account assigned with bank
* statement.
*
* @author Jakub Juračka
*/
public function get_header()
{
return $this->header;
}
/**
* Function for checking data correctness
*
* @param string $data
*
* @author Jakub Juračka
*/
private function is_correct($data)
{
return isset($data) && $data != NULL && $data != '';
}
/**
* Shortcut for error throwing
*
* @param int $row_num
* @param string $datatype
*
* @author Jakub Juračka
*/
private function throw_error_row($row_num, $datatype)
{
throw new RB_Exception(__('Please, check the file.') . ' '
. __('Missing data (%s) on the line %d.', array($datatype, $row_num)));
}
}
application/libraries/importers/Raiffeisenbank/RB_Exception.php
* @author Jiri Svitak
*/
class RB_Exception extends Exception {}
?>
application/libraries/importers/Raiffeisenbank/RB_Importer.php
<?php defined('SYSPATH') or die('No direct script access.');
/*
* This file is part of open source system FreenetIS
* and it is released under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
... Rozdílový soubor je zkrácen, protože jeho délka přesahuje max. limit.

Také k dispozici: Unified diff