diff --git a/ofxtest.php b/ofxtest.php index c2996d6..6f0b4f6 100644 --- a/ofxtest.php +++ b/ofxtest.php @@ -1,517 +1,518 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ /* * ofx test web service for kmymoney login test * * based on http://www.ofx.net/downloads/OFX%202.2.pdf */ # show errors $debug = isset($_REQUEST['debug']); if ($debug) { error_reporting(E_ALL); ini_set('display_errors', '1'); echo ''; } $brokenPHP = version_compare(phpversion(), '5.6.3', '<'); /** * convert an ofx date string into a timestamp */ function strToDate($s) { global $config; $i = strpos($s, "["); if ($i !== false) { $dt = substr($s, 0, $i); $tzs = substr($s, $i+1, strlen($s)-$i-2); try { if (is_numeric($tzs[0])) $tz = new DateTimeZone('+'.$tzs); else $tz = new DateTimeZone($tzs); } catch (Exception $e) { echo '"; - $tz = new DateTimeZone('GMT'); + error_log("'$s' could not create time zone: ".$e->getMessage()); + return 0; } } else { $dt = $s; $tz = new DateTimeZone("GMT"); } if (strlen($dt) == 8) $d = DateTime::createFromFormat("YmdHis", $dt."000000", $tz); elseif (strlen($dt) == 14) $d = DateTime::createFromFormat("YmdHis", $dt, $tz); elseif (strlen($dt) == 18) $d = DateTime::createFromFormat("YmdHis#u", $dt, $tz); else $d = 0; if (!$d) { error_log("'$s' invalid date format"); return 0; } $t = $d->getTimestamp(); if ($config['debug']) error_log("$dt '".$tz->getName()."' -> $t"); return $t; } if (isset($_REQUEST['debug']) && $_REQUEST['debug'] == 'date') { $data = array( "20181201", "20181201000000", "20181201000000.000", "20181201000000[GMT]", "20181201000000[12]", "20181201000000[-11]", "20181201000000[+12]", "20181201000000[12.00]", "20181201000000[-11.50]", "20181201000000[+10.50]", "20181201000000[+12:MAGT]", "20181201000000[12:MAGT]", "20181201000000[-11:SST]", # from spec section 3.2.8.2 Date and Datetime "19961005132200.124[-5:EST]", ); echo "\n" ."\n"; foreach($data as $s) { $d = strToDate($s); $s2 = date("Y-m-d H:i:s.u", $d); echo "\n"; } echo "
ofx datetimestampformatted output
$s$d$s2 [GMT]
"; exit(1); } /** * check that datetime string $a is in range between $start and $end */ function isInRange($a, $start, $end) { global $config; global $brokenPHP; if (!$brokenPHP) { $t = strToDate($a); $r = (empty($start) || $t >= strToDate($start)) && (empty($end) || $t <= strToDate($end)); } else { $r = (empty($start) || strcmp($t, $start) >= 0) && (empty($end) || strcmp($t, $end) <= 0); } if ($config['debug']) error_log("isInRange($a, $start, $end) -> $r"); return $r; } $today = date('Ymd'); $config = array( 'debug' => false, 'OFXHEADER' => '200', 'VERSION' => '220', 'USERID' => 'test', 'USERPASS' => 'test1', 'ORG' => 'test', 'FID' => 'test', 'DESC' => 'Power Checking', 'PHONE' => '0088224482', 'BANKID' => '123456789', 'accounts' => array( '00001234' => array( 'ACCTID' => '00001234', 'ACCTTYPE' => 'CHECKING', # non standard field 'MEMO' => 'account with check and atm statements', 'BANKTRANLIST' => array( '20171001' => array( 'TRNTYPE' => 'CHECK', 'DTPOSTED' => $today, 'TRNAMT' => -200.00, 'FITID' => '00002', 'NAME' => 'Test1', 'CHECKNUM' => '1000' ), '20171002' => array( 'TRNTYPE' => 'ATM', 'DTPOSTED' => $today, 'DTUSER' => '20051020', 'TRNAMT' => -300.00, 'FITID' => '00003', 'NAME' => 'Test2', ), ), 'LEDGERBAL' => array( 'BALAMT' => '200.29', 'DTASOF' => $today, ), 'AVAILBAL' => array( 'BALAMT' => '200.29', 'DTASOF' => $today, ), ), '00001235' => array( 'ACCTID' => '00001235', 'ACCTTYPE' => 'CHECKING', # non standard field 'MEMO' => 'another account with check statement', 'BANKTRANLIST' => array( '20171001' => array( 'TRNTYPE' => 'CHECK', 'DTPOSTED' => $today, 'TRNAMT' => 200.00, 'FITID' => '00001', 'NAME' => 'Test3', ), ), 'LEDGERBAL' => array( 'BALAMT' => '100.29', 'DTASOF' => $today, ), 'AVAILBAL' => array( 'BALAMT' => '100.29', 'DTASOF' => $today, ), ), '00001236' => array( 'ACCTID' => '00001236', 'ACCTTYPE' => 'CHECKING', # non standard field 'MEMO' => 'account for testing dates with different time zones', 'BANKTRANLIST' => array( '1' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201', 'TRNAMT' => -140.00, 'FITID' => '0001', 'NAME' => 'Landlord-1', 'MEMO' => 'short date - 12:00 AM (the start of the day), GMT', ), '2' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000', 'TRNAMT' => -140.00, 'FITID' => '0002', 'NAME' => 'Landlord-2', 'MEMO' => 'date with time without tz', ), '3' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000.000', 'TRNAMT' => -140.00, 'FITID' => '0003', 'NAME' => 'Landlord-3', 'MEMO' => 'date with time and msecs without tz', ), '4' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[GMT]', 'TRNAMT' => -140.00, 'FITID' => '0004', 'NAME' => 'Landlord-4', 'MEMO' => 'date with time and default tz', ), '5' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000.000[GMT]', 'TRNAMT' => -140.00, 'FITID' => '0005', 'NAME' => 'Landlord-5', 'MEMO' => 'date with time, msecs and default tz', ), '6a' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[-11]', 'TRNAMT' => -140.00, 'FITID' => '0006a', 'NAME' => 'Landlord-6a', 'MEMO' => 'date with time, tz offset without tz identifier (-11)', ), '6b' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[-11:SST]', 'TRNAMT' => -140.00, 'FITID' => '0006b', 'NAME' => 'Landlord-6b', 'MEMO' => 'date with time, tz offset and identifier (-11:SST)', ), '7a' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[12]', 'TRNAMT' => -140.00, 'FITID' => '0007a', 'NAME' => 'Landlord-7a', 'MEMO' => 'date with time, tz offset without tz identifier (12)', ), '7b' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[12:MAGT]', 'TRNAMT' => -140.00, 'FITID' => '0007b', 'NAME' => 'Landlord-7b', 'MEMO' => 'date with time, tz offset and identifier (12:MAGT)', ), '8a' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[+12]', 'TRNAMT' => -140.00, 'FITID' => '0008a', 'NAME' => 'Landlord-8a', 'MEMO' => 'date with time, tz offset without tz identifier (+12)', ), '8b' => array( 'TRNTYPE' => 'XFER', 'DTPOSTED' => '20181201000000[+12:MAGT]', 'TRNAMT' => -140.00, 'FITID' => '0008b', 'NAME' => 'Landlord-8b', 'MEMO' => 'date with time, tz offset and identifier (+12:MAGT)', ), ), 'LEDGERBAL' => array( 'BALAMT' => '-980', 'DTASOF' => $today, ), 'AVAILBAL' => array( 'BALAMT' => '-980', 'DTASOF' => $today, ), ), ), 'CURDEF' => 'USD' ); function getStatusCode(&$data) { global $config; $code = 0; if ($data['USERID'] != $config['USERID'] || $data['USERPASS'] != $config['USERPASS']) $code = 15500; return $code; } function getStatusResponse($code) { if ($code != 0) return '' .''.$code.'' .'ERROR' .'' ; return '' .'0' .'INFO' .'' ; } function accountInformationRequest(&$data) { global $config; $code = getStatusCode($data); $s = '' .''.$data['TRNUID'].'' .getStatusResponse($code) ; if (!$code) { $s .= '' .''.$data['DTACCTUP'].'' ; foreach ($config['accounts'] as $account) { $s .= '' .''.$config['DESC'].'' .''.$config['PHONE'].'' .'' .'' .''.$config['BANKID'].' ' .''.$account['ACCTID'].'' .''.$account['ACCTTYPE'].'' .'' .'Y' .'Y' .'Y' .'ACTIVE' .'' .'' ; } $s .= '' ; } $s .= '' ; return $s; } function statementDownloadRequest(&$data) { global $config, $today; $code = getStatusCode($data); $s = '' .'' .getStatusResponse($code) .'20051029101003' .'ENG' .'19991029101003' //.''.$data['DTACCTUP'].'' .'' .''.$data['ORG'].'' .''.$data['FID'].'' .'' .'' .'' ; if ($code) return $s; $s .= '' .'' .''.$data['TRNUID'].'' ; if (!isset($config['accounts'][$data['ACCTID']])) { $code = 2003; // account not found } $s .= getStatusResponse($code); // account has been found if (!$code) { $account = &$config['accounts'][$data['ACCTID']]; $dtstart = $data['DTSTART']; $dtend = isset($data['DTEND']) ? $data['DTEND'] : $today; $s .= '' .''.$config['CURDEF'].'' .'' .''.$config['BANKID'].' ' .''.$account['ACCTID'].'' .''.$account['ACCTTYPE'].'' .'' .'' .'' .''.$dtstart.'' .''.$dtend.'' ; // add transactions foreach ($account['BANKTRANLIST'] as $t) { if (!isInRange($t['DTPOSTED'], $dtstart, $dtend)) continue; $s .= ''; foreach ($t as $key => $value) { $s .= "<$key>$value"; } $s .= '' ; } $s .= '' .'' .''.$account['LEDGERBAL']['BALAMT'].'' .''.$account['LEDGERBAL']['DTASOF'].'' .'' .'' .'' .''.$account['AVAILBAL']['BALAMT'].'' .''.$account['AVAILBAL']['DTASOF'].'' .'' .''; } $s .= '' .'' ; return $s; } $validOFXRecord = false; $rawPostData = file_get_contents("php://input"); $postData = explode("\r\n", $rawPostData); if ($config['debug']) error_log(print_r($postData,true)); // parse input foreach($postData as $line) { $l = trim($line); if ($config['debug']) error_log($l); if (strcmp($l, "") === 0) { $validOFXRecord = true; } if (0 === strpos($l, '<')) { $a = explode(">", $l); $b = explode("<", $a[0]); $key = $b[1]; $value = $a[1]; $data[$key] = $value; if ($config['debug']) error_log("$key $value"); } } if ($validOFXRecord) { header("Content-Type: application/x-ofx"); $code = getStatusCode($data); $s = '' .'' .''; if ($code) { $s .= getStatusResponse($code); } else if (isset($data['ACCTINFOTRNRQ'])) { $s .= accountInformationRequest($data); } else if (isset($data['BANKMSGSRQV1'])) { $s .= statementDownloadRequest($data); } else $s .= getStatusResponse(2028); $s .= ''; echo $s; if ($config['debug']) error_log($s); } else { header("Content-Type: text/html"); echo "
"
 	."This service is intended for access from KMyMoney only\n\n"
 	."It supports assigning an ofx online account to a KMyMoney account\n"
 	."and updating the account from the online account.\n\n"
 	."---------------------- access data ----------------------\n"
 	."Choose adding manual account with the following settings:\n\n"
 	."Org: ".$config['ORG']."\n"
 	."Fid: ".$config['FID']."\n"
 	."Url: "."http".(($_SERVER['SERVER_PORT'] == 443) ? "s://" : "://").$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\n"
 	."User: ".$config['USERID']."\n"
 	."Password: ".$config['USERPASS']."\n"
 	."\n"
 	."---------------------- bank data ----------------------\n"
 	."bank identification:".$config['BANKID']."\n"
 	."number of accounts: ".sizeof($config['accounts'])."\n"
 	."\n"
 	."---------------------- accounts ----------------------\n"
 	." account  \t type   \t notes\n";
 	foreach($config['accounts'] as $account) {
 		echo $account['ACCTID']."\t". $account['ACCTTYPE']."\t". $account['MEMO']."\n";
 	}
 	echo "
"; } ?>