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"
."ofx date | timestamp | formatted output |
\n";
foreach($data as $s) {
$d = strToDate($s);
$s2 = date("Y-m-d H:i:s.u", $d);
echo "$s | $d | $s2 [GMT] |
\n";
}
echo "
";
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$key>";
}
$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 "
";
}
?>