From 61630c17ff075825cca0e3f8ed4d833c69b6625f Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 20 May 2013 18:49:48 -0500 Subject: [PATCH 01/64] Better handling of null returns in get_list --- api_post.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index 16a3c66..704fd8f 100755 --- a/api_post.php +++ b/api_post.php @@ -261,7 +261,10 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ $xml = api_util::phpToXml('content',array($func)); $res = api_post::post($xml, $session,$dtdVersion, true); $ret = api_post::processListResults($res, api_returnFormat::PHPOBJ, $count); - $toReturn = $ret[$object]; + $toReturn = null; + if (array_key_exists($object,$ret)) { + $toReturn = $ret[$object]; + } if (is_array($toReturn)) { $keys = array_keys($toReturn); if (!is_numeric($keys[0])) { From a972239a111155402969f2410fe2517139f28d79 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Sat, 8 Jun 2013 16:18:48 -0500 Subject: [PATCH 02/64] fixed bug where an array of values wasn't getting the correct element --- api_util.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api_util.php b/api_util.php index 97b898b..82f8188 100755 --- a/api_util.php +++ b/api_util.php @@ -144,7 +144,12 @@ public static function phpToXml($key, $values) { $xml .= $_xml; } else { - $xml .= "<" . $node . $attrString . ">" . htmlspecialchars($value) . ""; + if (is_numeric($node)) { + $xml .= "<" . $key. $attrString . ">" . htmlspecialchars($value) . ""; + } + else { + $xml .= "<" . $node . $attrString . ">" . htmlspecialchars($value) . ""; + } } } if (!is_numeric(array_shift(array_keys($values)))) { From ca8409e4e86ada8d94ed2503903f9ab355bb7a1c Mon Sep 17 00:00:00 2001 From: John Campbell Date: Tue, 18 Jun 2013 10:02:11 -0500 Subject: [PATCH 03/64] adding full php opener tags --- api_objDef.php | 2 +- api_post.php | 2 +- api_returnFormat.php | 4 ++-- api_session.php | 2 +- api_util.php | 2 +- api_viewFilter.php | 4 ++-- api_viewFilters.php | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api_objDef.php b/api_objDef.php index 7eeb7f0..aded6d5 100644 --- a/api_objDef.php +++ b/api_objDef.php @@ -32,4 +32,4 @@ public function __construct(simpleXmlElement $simpleXml) { } } -} \ No newline at end of file +} diff --git a/api_post.php b/api_post.php index 704fd8f..f08288d 100755 --- a/api_post.php +++ b/api_post.php @@ -1,4 +1,4 @@ -operator = $operator; $this->value = HTMLSpecialChars($value); } -} \ No newline at end of file +} diff --git a/api_viewFilters.php b/api_viewFilters.php index df66178..3950e3c 100755 --- a/api_viewFilters.php +++ b/api_viewFilters.php @@ -1,4 +1,4 @@ -filters = $filters; $this->operator = $operator; } -} \ No newline at end of file +} From 3c3a8dbb8e4e3158685c3052be102b83a224a10b Mon Sep 17 00:00:00 2001 From: John Campbell Date: Sun, 30 Jun 2013 15:08:54 -0500 Subject: [PATCH 04/64] Adding key field deleteByQuery --- api_post.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api_post.php b/api_post.php index f08288d..8e45aeb 100755 --- a/api_post.php +++ b/api_post.php @@ -557,10 +557,10 @@ public static function deleteAll($object, api_session $session, $max=10000) { * @param Integer $max [optional] Maximum number of records to delete. Default is 10000 * @return Integer count of records deleted */ - public static function deleteByQuery($object, $query, api_session $session, $max=10000) { + public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=10000) { // read all the record ids for the given object - $ids = api_post::readByQuery($object, "id > 0 and $query", "id", $session, $max); + $ids = api_post::readByQuery($object, "$key_field > 0 and $query", $key_field, $session, $max); if ((!is_array($ids) && trim($ids) == '') || !count($ids) > 0) { return 0; @@ -569,7 +569,7 @@ public static function deleteByQuery($object, $query, api_session $session, $max $count = 0; $delIds = array(); foreach($ids as $rec) { - $delIds[] = $rec['id']; + $delIds[] = $rec[$key_field]; if (count($delIds) == 100) { api_post::delete($object, implode(",", $delIds), $session); $count += 100; From 1820c2759e6f43c5dc6e0eeb0b9011848d0367e9 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Sat, 27 Jul 2013 00:15:44 -0500 Subject: [PATCH 05/64] New readDocumentByQuery function --- api_post.php | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/api_post.php b/api_post.php index 8e45aeb..4b58dbc 100755 --- a/api_post.php +++ b/api_post.php @@ -449,6 +449,88 @@ public static function readByQuery($object, $query, $fields, api_session $sessio return $$returnFormat; } + /** + * Read records using a query. Specify the object you want to query and something like a "where" clause" + * @param String $object the object upon which to run the query + * @param String $query the query string to execute. Use SQL operators + * @param String $fields A comma separated list of fields to return + * @param api_session $session An instance of the api_session object with a valid connection + * @param int $maxRecords number of records to return. Defaults to 100000 + * @param string $returnFormat defaults to php object. Pass one of the valid constants from api_returnFormat class + * @return mixed either string or array of objects depending on returnFormat argument + */ + public static function readDocumentByQuery($object, $docparid, $query, $fields, api_session $session, $maxRecords=self::DEFAULT_MAXRETURN, $returnFormat=api_returnFormat::PHPOBJ) { + + $pageSize = ($maxRecords <= self::DEFAULT_PAGESIZE) ? $maxRecords : self::DEFAULT_PAGESIZE; + + if ($returnFormat == api_returnFormat::PHPOBJ) { + $returnFormatArg = api_returnFormat::CSV; + } + else { + $returnFormatArg = $returnFormat; + } + + // TODO: Implement returnFormat. Today we only support PHPOBJ + $query = HTMLSpecialChars($query); + + $readXml = "$object$query$fields$returnFormatArg"; + $readXml .= "$docparid"; + $readXml .= "$pageSize"; + $readXml .= ""; + + $response = api_post::post($readXml,$session); + if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { + // csv with no records will have no response, so avoid the error from validate and just return + return ''; + } + api_post::validateReadResults($response); + + + $phpobj = array(); $csv = ''; $json = ''; $xml = ''; $count = 0; + $$returnFormat = self::processReadResults($response, $returnFormat, $thiscount); + + $totalcount = $thiscount; + + // we have no idea if there are more if CSV is returned, so just check + // if the last count returned was $pageSize + while($thiscount == $pageSize && $totalcount <= $maxRecords) { + $readXml = "$object"; + try { + $response = api_post::post($readXml, $session); + api_post::validateReadResults($response); + $page = self::processReadResults($response, $returnFormat, $pageCount); + $totalcount += $pageCount; + $thiscount = $pageCount; + + switch($returnFormat) { + case api_returnFormat::PHPOBJ: + foreach($page as $objRec) { + $phpobj[] = $objRec; + } + break; + case api_returnFormat::CSV: + $page = explode("\n", $page); + array_shift($page); + $csv .= implode($page, "\n"); + break; + case api_returnFormat::XML: + $xml .= $page; + break; + default: + throw new Exception("Invalid return format: " . $returnFormat); + break; + } + + } + catch (Exception $ex) { + // we've probably exceeded the limit + break; + } + } + return $$returnFormat; + } + + /** * Inspect an object to get a list of its fields * From cb024016e940e5459d3003c67f3392db622f46db Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 30 Aug 2013 11:17:24 -0500 Subject: [PATCH 06/64] Added some max record management code to deleteByQuery and extended the timeout --- api_post.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/api_post.php b/api_post.php index 4b58dbc..6b891b3 100755 --- a/api_post.php +++ b/api_post.php @@ -641,6 +641,7 @@ public static function deleteAll($object, api_session $session, $max=10000) { */ public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=10000) { + $num_per_func= 100; // read all the record ids for the given object $ids = api_post::readByQuery($object, "$key_field > 0 and $query", $key_field, $session, $max); @@ -650,11 +651,18 @@ public static function deleteByQuery($object, $query, $key_field, api_session $s $count = 0; $delIds = array(); + foreach($ids as $rec) { $delIds[] = $rec[$key_field]; - if (count($delIds) == 100) { - api_post::delete($object, implode(",", $delIds), $session); - $count += 100; + if (count($delIds) == $num_per_func) { + try { + api_post::delete($object, implode(",", $delIds), $session); + } + catch (Exception $ex) { + $delIds = array(); + continue; + } + $count += $num_per_func; $delIds = array(); } } @@ -793,14 +801,16 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult public static function execute($body, $endPoint) { self::$lastRequest = $body; + self::$lastResponse = null; $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); curl_setopt( $ch, CURLOPT_HEADER, 0 ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 3000 ); //Seconds until timeout + curl_setopt( $ch, CURLOPT_TIMEOUT, 600 ); //Seconds until timeout curl_setopt( $ch, CURLOPT_POST, 1 ); + curl_setopt( $ch, CURLOPT_VERBOSE, false ); // TODO: Research and correct the problem with CURLOPT_SSL_VERIFYPEER curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false); // yahoo doesn't like the api.intacct.com CA @@ -810,6 +820,7 @@ public static function execute($body, $endPoint) { $response = curl_exec( $ch ); $error = curl_error($ch); if ($error != "") { + dbg("CURL ERROR: curl_errno returned: " . curl_errno($ch)); throw new exception($error); } curl_close( $ch ); From 5a076762ec3e8ef0b9aa1757a9463a0faa0661ba Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 30 Sep 2013 07:59:40 -0500 Subject: [PATCH 07/64] adding better get_list functionality --- api_post.php | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/api_post.php b/api_post.php index 8e45aeb..358df7a 100755 --- a/api_post.php +++ b/api_post.php @@ -243,6 +243,9 @@ public static function sendFunctions($phpObj, api_session $session, $dtdVersion= public static function get_list($object, $filter, $sorts, $fields, api_session $session, $dtdVersion="2.1") { $get_list = array(); $get_list['@object'] = $object; + $get_list['@start'] = 0; + $get_list['@maxitems'] = 100; + if ($filter != null) { $get_list['filter'] = $filter; } @@ -271,6 +274,44 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ $toReturn = array ($toReturn); } } + + // now get more if there are any + $xml = simplexml_load_string($res); + $total = $xml->operation->result->listtype; + $attrs = $total->attributes(); + $total = $attrs['total']; + + if (count($toReturn) < $total) { + + do { + // we need to fetch more + $get_list['@start'] = count($toReturn); + $func['function'] = array(); + + $func['function'][] = array ( + '@controlid' => 'control1', + 'get_list' => $get_list + ); + + $xml = api_util::phpToXml('content',array($func)); + $res = api_post::post($xml, $session,$dtdVersion, true); + $ret = api_post::processListResults($res, api_returnFormat::PHPOBJ, $count); + + $nextBatch = null; + if (array_key_exists($object,$ret)) { + $nextBatch = $ret[$object]; + } + if (is_array($nextBatch)) { + $keys = array_keys($nextBatch); + if (!is_numeric($keys[0])) { + $nextBatch = array ($nextBatch); + } + } + $toReturn = array_merge($toReturn,$nextBatch); + + } while ( count($toReturn) < $total) ; + } + return $toReturn; } From 7726daccf8658a09218486dd1c29a3079f98a8b9 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 11 Oct 2013 13:54:19 -0500 Subject: [PATCH 08/64] adding some list handler stuff --- api_post.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index 3804156..0d4942e 100755 --- a/api_post.php +++ b/api_post.php @@ -803,6 +803,7 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult if (self::$dryRun == true) { self::$lastRequest = $xml; + self::$lastResponse= null; return; } @@ -861,7 +862,6 @@ public static function execute($body, $endPoint) { $response = curl_exec( $ch ); $error = curl_error($ch); if ($error != "") { - dbg("CURL ERROR: curl_errno returned: " . curl_errno($ch)); throw new exception($error); } curl_close( $ch ); @@ -1052,8 +1052,27 @@ public static function processListResults($response, $returnFormat = api_returnF return; } - $json = json_encode($xml->operation->result->data); + $json = json_encode($xml->operation->result->data,JSON_FORCE_OBJECT); $array = json_decode($json,TRUE); + + $obj = key($array); + + if (!is_numeric(key($array[$obj]))) { + $array[$obj] = array ( $array[$obj] ); + } + + // check for known line item issues + // lame, but not sure how else to fix this. the json_decode removes the level from a single line item and it needs to be restored + // make this generic if it works + if (isset($array['sotransaction'])) { + foreach ($array['sotransaction'] as $key => $txn) { + if (isset($txn['sotransitems']['sotransitem'])) { + if (!is_numeric(key($txn['sotransitems']['sotransitem']))) { + $array['sotransaction'][$key]['sotransitems']['sotransitem'] = array ($txn['sotransitems']['sotransitem']); + } + } + } + } return $array; } From b4d461501891ed18c69e99a92e602eedd9bda031 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Tue, 10 Dec 2013 15:53:42 -0600 Subject: [PATCH 09/64] added a dry run fix --- api_post.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api_post.php b/api_post.php index 0d4942e..f0c96f7 100755 --- a/api_post.php +++ b/api_post.php @@ -263,6 +263,9 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ $xml = api_util::phpToXml('content',array($func)); $res = api_post::post($xml, $session,$dtdVersion, true); + if (self::$dryRun == true) { + return; + } $ret = api_post::processListResults($res, api_returnFormat::PHPOBJ, $count); $toReturn = null; if (array_key_exists($object,$ret)) { From 6373a1519a412efb44c0f2439a004858ad2b093e Mon Sep 17 00:00:00 2001 From: John Campbell Date: Tue, 10 Dec 2013 15:54:03 -0600 Subject: [PATCH 10/64] fixed a warning in php2xml --- api_util.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api_util.php b/api_util.php index 326c9a4..c23cca1 100755 --- a/api_util.php +++ b/api_util.php @@ -129,11 +129,14 @@ public static function phpToXml($key, $values) { } $firstKey = array_shift(array_keys($value)); - if (is_array($value[$firstKey]) || count($value) > 1 ) { + if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { $_xml = self::phpToXml($node,$value) ; } else { - $v = $value[$firstKey]; + $v = ""; + if (isset($value[$firstKey])) { + $v = $value[$firstKey]; + } $_xml .= "<$node>" . htmlspecialchars($v) . ""; } From 0980b651a71dc44601579a71912e372725e8cc23 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 24 Jan 2014 14:13:11 -0600 Subject: [PATCH 11/64] updating timeout to 10 min and doing some line item conversion fixes --- api_post.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index f0c96f7..af9edf9 100755 --- a/api_post.php +++ b/api_post.php @@ -853,7 +853,7 @@ public static function execute($body, $endPoint) { curl_setopt( $ch, CURLOPT_HEADER, 0 ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 600 ); //Seconds until timeout + curl_setopt( $ch, CURLOPT_TIMEOUT, 6000 ); //Seconds until timeout curl_setopt( $ch, CURLOPT_POST, 1 ); curl_setopt( $ch, CURLOPT_VERBOSE, false ); // TODO: Research and correct the problem with CURLOPT_SSL_VERIFYPEER @@ -1076,6 +1076,15 @@ public static function processListResults($response, $returnFormat = api_returnF } } } + if (isset($array['arpayment'])) { + foreach ($array['arpayment'] as $key => $txn) { + if (isset($txn['lineitems']['lineitem'])) { + if (!is_numeric(key($txn['lineitems']['lineitem']))) { + $array['arpayment'][$key]['lineitems']['lineitem'] = array ($txn['lineitems']['lineitem']); + } + } + } + } return $array; } From 2a9ceb8f7b1e0b88baa646374586698968e41ed3 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Tue, 8 Apr 2014 09:54:22 -0500 Subject: [PATCH 12/64] making sendFunctions dtd default to 3.0 --- api_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index af9edf9..a1bf7c9 100755 --- a/api_post.php +++ b/api_post.php @@ -226,7 +226,7 @@ public static function call21Method($function, $phpObj, api_session $session) { * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function sendFunctions($phpObj, api_session $session, $dtdVersion="2.1") { + public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0") { $xml = api_util::phpToXml('content',array($phpObj)); return api_post::post($xml, $session,$dtdVersion, true); } From 5b885a58cfe69449c786a563ab4d64547115f4ec Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 20 Aug 2014 12:38:50 -0500 Subject: [PATCH 13/64] adding code to parse a multi-function result --- api_post.php | 10 +++++----- api_util.php | 27 +++++++++++++++++---------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/api_post.php b/api_post.php index a1bf7c9..3ce9e37 100755 --- a/api_post.php +++ b/api_post.php @@ -683,7 +683,7 @@ public static function deleteAll($object, api_session $session, $max=10000) { * @param Integer $max [optional] Maximum number of records to delete. Default is 10000 * @return Integer count of records deleted */ - public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=10000) { + public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=100000) { $num_per_func= 100; // read all the record ids for the given object @@ -879,7 +879,7 @@ public static function execute($body, $endPoint) { * @param String $response The XML response document * @throws Exception */ - public static function findResponseErrors($response) { + public static function findResponseErrors($response,$multi=false) { $errorArray = array(); @@ -896,7 +896,7 @@ public static function findResponseErrors($response) { // look for a failure in the operation, but not the result if (isset($simpleXml->operation->errormessage)) { $error = $simpleXml->operation->errormessage->error[0]; - $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->operation->errormessage)); + $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->operation->errormessage,$multi)); } // if we didn't get an operation, the request failed and we should raise an exception @@ -904,14 +904,14 @@ public static function findResponseErrors($response) { // did the method invocation fail? if (!isset($simpleXml->operation)) { if (isset($simpleXml->errormessage)) { - $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->errormessage)); + $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->errormessage,$multi)); } } else { $results = $simpleXml->xpath('/response/operation/result'); foreach ($results as $result) { if ((string)$result->status == "failure") { - $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage)); + $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage,$multi)); } } } diff --git a/api_util.php b/api_util.php index c23cca1..0a10e7a 100755 --- a/api_util.php +++ b/api_util.php @@ -196,22 +196,29 @@ public static function csvToPhp($csv) { * @param Object $error simpleXmlObject * @return string formatted error message */ - public static function xmlErrorToString($error) { + public static function xmlErrorToString($error,$multi=false) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); } - $error = $error->error[0]; - if (!is_object($error)) { - return "Malformed error: " . var_export($error, true); - } + // show just the first error + //$error = $error->error[0]; + foreach ($error->error as $error) { + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); + } - $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; - $description = is_object($error->description) ? (string)$error->description : ' '; - $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; - $correction = is_object($error->correction) ? (string)$error->correction : ' '; - return "$errorno: $description: $description2: $correction"; + $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; + $description = is_object($error->description) ? (string)$error->description : ' '; + $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; + $correction = is_object($error->correction) ? (string)$error->correction : ' '; + $error_string .= "$errorno: $description: $description2: $correction\n"; + if ($multi === false) { + break; + } + } + return $error_string; } From b2d9129fdd11d27a8f876262030af398165e4c6e Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 20 Aug 2014 12:39:40 -0500 Subject: [PATCH 14/64] some object def additions --- api_objDef.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api_objDef.php b/api_objDef.php index aded6d5..6408aad 100644 --- a/api_objDef.php +++ b/api_objDef.php @@ -24,12 +24,21 @@ public function __construct(simpleXmlElement $simpleXml) { $this->Description = (string)$simpleXml->Attributes->Description; $fields = $simpleXml->Fields; - $this->Fields = array(); + foreach($fields->Field as $field) { $fieldDef = new api_fieldDef($field); $this->Fields[$fieldDef->Name] = $fieldDef; } } + public function get_field_array() + { + $array = array(); + foreach ($this->Fields as $field) { + $array[] = (string)$field->Name; + } + return $array; + } + } From 997c453de054fc988fcb99fb5c1722e9060388bf Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 3 Sep 2014 10:40:17 -0500 Subject: [PATCH 15/64] Various updates --- api_post.php | 10 ++++++++++ api_util.php | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index af9edf9..48d0322 100755 --- a/api_post.php +++ b/api_post.php @@ -1085,6 +1085,16 @@ public static function processListResults($response, $returnFormat = api_returnF } } } + + if (isset($array['recursotransaction'])) { + foreach ($array['recursotransaction'] as $key => $txn) { + if (isset($txn['recursotransitems']['recursotransitem'])) { + if (!is_numeric(key($txn['recursotransitems']['recursotransitem']))) { + $array['recursotransaction'][$key]['recursotransitems']['recursotransitem'] = array ($txn['recursotransitems']['recursotransitem']); + } + } + } + } return $array; } diff --git a/api_util.php b/api_util.php index c23cca1..e4b8f1d 100755 --- a/api_util.php +++ b/api_util.php @@ -103,7 +103,9 @@ public static function phpToXml($key, $values) { return "<$key>$values"; } - if (!is_numeric(array_shift(array_keys($values)))) { + $temp1 = array_keys($values); + $temp2 = array_shift($temp1); + if (!is_numeric($temp2)) { $xml = "<" . $key . ">"; } foreach($values as $node => $value) { @@ -155,7 +157,9 @@ public static function phpToXml($key, $values) { } } } - if (!is_numeric(array_shift(array_keys($values)))) { + $temp1 = array_keys($values); + $temp2 = array_shift($temp1); + if (!is_numeric($temp2)) { $xml .= ""; } return $xml; From 2ca17d31393600ebd3371055bd691f69b835e171 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Tue, 3 Mar 2015 09:10:17 -0600 Subject: [PATCH 16/64] Returning all errors now --- api_util.php | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/api_util.php b/api_util.php index c23cca1..5191889 100755 --- a/api_util.php +++ b/api_util.php @@ -196,22 +196,33 @@ public static function csvToPhp($csv) { * @param Object $error simpleXmlObject * @return string formatted error message */ - public static function xmlErrorToString($error) { + public static function xmlErrorToString($errors) { - if (!is_object($error)) { - return "Malformed error: " . var_export($error, true); + if (!is_object($errors)) { + return "Malformed error: " . var_export($errors, true); } - $error = $error->error[0]; - if (!is_object($error)) { - return "Malformed error: " . var_export($error, true); + $error = $errors->error[0]; + if (!is_object($errors)) { + return "Malformed error: " . var_export($errors, true); } - $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; - $description = is_object($error->description) ? (string)$error->description : ' '; - $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; - $correction = is_object($error->correction) ? (string)$error->correction : ' '; - return "$errorno: $description: $description2: $correction"; + $all_errors = ""; + foreach ($errors->error as $err) { + //$errorno = is_object($err->errorno) ? (string)$err->errorno : ' '; + //$description = is_object($err->description) ? (string)$err->description : ' '; + $description2 = is_object($err->description2) ? (string)$err->description2 : ' '; + //$correction = is_object($err->correction) ? (string)$err->correction : ' '; + $all_errors .= "$description2.\n"; + + } + + //$errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; + //$description = is_object($error->description) ? (string)$error->description : ' '; + //$description2 = is_object($error->description2) ? (string)$error->description2 : ' '; + //$correction = is_object($error->correction) ? (string)$error->correction : ' '; + //return "$errorno: $description: $description2: $correction"; + return $all_errors; } From 6963e00cffbb4286b72a2eb0c96bd3cc78a34790 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 5 Mar 2015 15:18:22 -0600 Subject: [PATCH 17/64] error handling --- api_post.php | 2 +- api_util.php | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/api_post.php b/api_post.php index 3ce9e37..b435340 100755 --- a/api_post.php +++ b/api_post.php @@ -951,7 +951,7 @@ private static function validateResponse($response) { else { $results = $simpleXml->operation->result; foreach ($results as $res) { - if ($res->status == "failure") { + if ($res->status == "failure" || $res->status == "aborted") { throw new Exception("[Error] " . api_util::xmlErrorToString($res->errormessage)); } } diff --git a/api_util.php b/api_util.php index 0a10e7a..e89ef68 100755 --- a/api_util.php +++ b/api_util.php @@ -128,15 +128,25 @@ public static function phpToXml($key, $values) { } } + //$firstKey = array_shift(array_keys($value)); + //if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { + // $_xml = self::phpToXml($node,$value) ; + //} + //else { + // $v = ""; + // if (isset($value[$firstKey])) { + // $v = $value[$firstKey]; + // } + // $_xml .= "<$node>" . htmlspecialchars($v) . ""; + //} + // $firstKey = array_shift(array_keys($value)); - if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { + if (is_array($value[$firstKey]) || count($value) > 0 ) { $_xml = self::phpToXml($node,$value) ; } else { - $v = ""; - if (isset($value[$firstKey])) { - $v = $value[$firstKey]; - } + $_xml = self::phpToXml($node,$value) ; + $v = $value[$firstKey]; $_xml .= "<$node>" . htmlspecialchars($v) . ""; } From 64be4d397479dd5d1c5de3756789694bb6a66329 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 9 Mar 2016 10:15:38 -0600 Subject: [PATCH 18/64] Fixed typo --- api_post.php | 2 ++ api_util.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index d60a796..f908052 100755 --- a/api_post.php +++ b/api_post.php @@ -440,8 +440,10 @@ public static function readByQuery($object, $query, $fields, api_session $sessio $readXml = "$object$query$fields$returnFormatArg"; $readXml .= "$pageSize"; $readXml .= ""; + dbg($readXml); $response = api_post::post($readXml,$session); + dbg($response); if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { // csv with no records will have no response, so avoid the error from validate and just return return ''; diff --git a/api_util.php b/api_util.php index 014d18e..6dae3a9 100755 --- a/api_util.php +++ b/api_util.php @@ -211,8 +211,8 @@ public static function csvToPhp($csv) { * @return string formatted error message */ public static function xmlErrorToString($error,$multi=false) { - if (!is_object($errors)) { - return "Malformed error: " . var_export($errors, true); + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); } // show just the first error //$error = $error->error[0]; From 1c4c5e499c364f20b2ae78a27eecfcc59ca1e30c Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 18 May 2016 15:43:56 -0500 Subject: [PATCH 19/64] some mods --- api_post.php | 4 +++- api_util.php | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/api_post.php b/api_post.php index d60a796..8af7f5a 100755 --- a/api_post.php +++ b/api_post.php @@ -945,6 +945,7 @@ private static function validateResponse($response) { if (!isset($simpleXml->operation)) { if (isset($simpleXml->errormessage)) { + dbg("HEY1: " . $simpleXml->errormessage); throw new Exception("[Error] " . api_util::xmlErrorToString($simpleXml->errormessage)); } } @@ -952,7 +953,8 @@ private static function validateResponse($response) { $results = $simpleXml->operation->result; foreach ($results as $res) { if ($res->status == "failure" || $res->status == "aborted") { - throw new Exception("[Error] " . api_util::xmlErrorToString($res->errormessage)); + $msg = api_util::xmlErrorToString($res->errormessage); + throw new Exception("[Error] " . $msg); } } } diff --git a/api_util.php b/api_util.php index 014d18e..d9f63d2 100755 --- a/api_util.php +++ b/api_util.php @@ -211,11 +211,12 @@ public static function csvToPhp($csv) { * @return string formatted error message */ public static function xmlErrorToString($error,$multi=false) { - if (!is_object($errors)) { - return "Malformed error: " . var_export($errors, true); + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); } // show just the first error //$error = $error->error[0]; + $error_string = ""; foreach ($error->error as $error) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); From 7952ab42d86b8d339f9ae700e5232046838ae8c9 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 18 May 2016 15:58:43 -0500 Subject: [PATCH 20/64] new changes --- api_post.php | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ api_util.php | 7 ++-- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/api_post.php b/api_post.php index d60a796..6496842 100755 --- a/api_post.php +++ b/api_post.php @@ -37,6 +37,30 @@ public static function read($object, $id, $fields, api_session $session) { } } + /** + * ReadDocument one or more records by their key. For platform objects, the key is the 'id' field. + * For standard objects, the key is the 'recordno' field. Results are returned as a php structured array + * @param String $object the integration name for the object + * @param String $docparid the transaction type + * @param String $id a comma separated list of keys for each record you wish to read + * @param String $fields a comma separated list of fields to return + * @param \api_session|Object $session an instance of the php_session object + * @return Array of records + */ + public static function readDocument($object, $docparid, $id, $fields, api_session $session) { + + $readXml = "$object$id$fieldscsv$docparid"; + $objCsv = api_post::post($readXml, $session); + api_post::validateReadResults($objCsv); + $objAry = api_util::csvToPhp($objCsv); + if (count(explode(",",$id)) > 1) { + return $objAry; + } + else { + return $objAry[0]; + } + } + /** * Create one or more records. Object types can be mixed and can be either standard or custom. * Check the developer documentation to see which standard objects are supported in this method @@ -638,6 +662,86 @@ public static function readRelated($object, $keys, $relation, $fields, api_sessi return $objAry; } + /** + * Reads all the records related to a source record through a named relationship. + * @param String $object the integration name of the object + * @param String $keys a comma separated list of 'id' values of the source records from which you want to read related records + * @param String $relation the name of the relationship. This will determine the type of object you are reading + * @param String $fields a comma separated list of fields to return + * @param api_session $session + * @return Array of objects + */ + public static function readReport($report, api_session $session, $arguments=null, $waitTime=0, $pageSize=100) { + $max_try = 100; + $try = 0; + $returnFormat = "csv"; + + if (is_array($arguments) ) { + $argxml= ""; + foreach ($arguments as $key => $arg) { + $argxml.= "<$key>$arg"; + } + $argxml .= ""; + } + $readXml = "$reportcsv$waitTime$argxml$pagesize"; + dbg($readXml); + $objCsv = api_post::post($readXml, $session); + api_post::validateReadResults($objCsv); + $objAry = api_util::csvToPhp($objCsv); + + if (is_array($objAry) && count($objAry) == 1) { + $id = $objAry[0]['REPORTID']; + do { + $readXml = "$id"; + try { + $response = api_post::post($readXml, $session); + dbg("READMORE:"); + dbg($response); + dbg("TRIMMED:"); + dbg(trim($response)); + if (trim($response) == "") { + return array(); + } + api_post::validateReadResults($response); + $_obj = api_util::csvToPhp($response); + dbg($_obj); + if (isset($_obj[0]['STATUS']) && $_obj[0]['STATUS'] == 'PENDING') { + dbg("Sleeping 10, try = $try"); + sleep("10"); + $try++; + continue; + } + + $page = self::processReadResults($response, $returnFormat, $pageCount); + $count += $pageCount; + if ($returnFormat == api_returnFormat::PHPOBJ) { + foreach($page as $objRec) { + $phpobj[] = $objRec; + } + } + elseif ($returnFormat == api_returnFormat::CSV) { + // append all but the first row to the CSV file + $page = explode("\n", $page); + array_shift($page); + $csv .= implode($page, "\n"); + } + elseif ($returnFormat == api_returnFormat::XML) { + // just add the xml string + $xml .= $page; + } + } + catch (Exception $ex) { + // for now, pass the exception on + Throw new Exception($ex); + } + if ($pageCount < $pageSize || $count >= $maxRecords) break; + } while ($try < $max_try); + + dbg("FINISHED LOOP"); + } + return $objAry; + } + /** * WARNING: This method will attempt to delete all records of a given object type * Deletes first 10000 by default diff --git a/api_util.php b/api_util.php index 014d18e..d4b5425 100755 --- a/api_util.php +++ b/api_util.php @@ -175,7 +175,7 @@ public static function phpToXml($key, $values) { return $xml; } - /** + /** * Convert a CSV string result into a php array. * This work for Intacct API results. Not a generic method */ @@ -211,11 +211,12 @@ public static function csvToPhp($csv) { * @return string formatted error message */ public static function xmlErrorToString($error,$multi=false) { - if (!is_object($errors)) { - return "Malformed error: " . var_export($errors, true); + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); } // show just the first error //$error = $error->error[0]; + $error_string = ""; foreach ($error->error as $error) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); From f6745a2477f9f11e3a6e1f41839bb6e01ade849e Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 18 May 2016 16:25:49 -0500 Subject: [PATCH 21/64] new readReport --- api_post.php | 158 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 8 deletions(-) diff --git a/api_post.php b/api_post.php index af9edf9..2d06a33 100755 --- a/api_post.php +++ b/api_post.php @@ -12,7 +12,7 @@ class api_post { private static $dryRun; const DEFAULT_PAGESIZE = 1000; - const DEFAULT_MAXRETURN = 100000; + const DEFAULT_MAXRETURN = 200000; /** * Read one or more records by their key. For platform objects, the key is the 'id' field. @@ -37,6 +37,30 @@ public static function read($object, $id, $fields, api_session $session) { } } + /** + * ReadDocument one or more records by their key. For platform objects, the key is the 'id' field. + * For standard objects, the key is the 'recordno' field. Results are returned as a php structured array + * @param String $object the integration name for the object + * @param String $docparid the transaction type + * @param String $id a comma separated list of keys for each record you wish to read + * @param String $fields a comma separated list of fields to return + * @param \api_session|Object $session an instance of the php_session object + * @return Array of records + */ + public static function readDocument($object, $docparid, $id, $fields, api_session $session) { + + $readXml = "$object$id$fieldscsv$docparid"; + $objCsv = api_post::post($readXml, $session); + api_post::validateReadResults($objCsv); + $objAry = api_util::csvToPhp($objCsv); + if (count(explode(",",$id)) > 1) { + return $objAry; + } + else { + return $objAry[0]; + } + } + /** * Create one or more records. Object types can be mixed and can be either standard or custom. * Check the developer documentation to see which standard objects are supported in this method @@ -226,7 +250,7 @@ public static function call21Method($function, $phpObj, api_session $session) { * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function sendFunctions($phpObj, api_session $session, $dtdVersion="2.1") { + public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0") { $xml = api_util::phpToXml('content',array($phpObj)); return api_post::post($xml, $session,$dtdVersion, true); } @@ -638,6 +662,114 @@ public static function readRelated($object, $keys, $relation, $fields, api_sessi return $objAry; } + /** + * Reads all the records related to a source record through a named relationship. + * @param String $object the integration name of the object + * @param String $keys a comma separated list of 'id' values of the source records from which you want to read related records + * @param String $relation the name of the relationship. This will determine the type of object you are reading + * @param String $fields a comma separated list of fields to return + * @param api_session $session + * @return Array of objects + */ + public static function readReport($report, api_session $session, $arguments=null, $waitTime=0, $pageSize=100) { + $maxRecords = self::DEFAULT_MAXRETURN; + $max_try = 1000; + $try = 0; + + if (is_array($arguments) ) { + $argxml= ""; + foreach ($arguments as $key => $arg) { + $argxml.= "<$key>$arg"; + } + $argxml .= ""; + } + $readXml = "$reportcsv$waitTime$argxml$pagesize"; +// dbg($readXml); + $objCsv = api_post::post($readXml, $session); + api_post::validateReadResults($objCsv); + $objAry = api_util::csvToPhp($objCsv); + + if (is_array($objAry) && count($objAry) == 1) { + $id = $objAry[0]['REPORTID']; + while(true) { + $readXml = "$id"; + try { + $response = api_post::post($readXml, $session); + //dbg(trim($response)); + if (trim($response) == "") { + return array(); + } + api_post::validateReadResults($response); + $_obj = api_util::csvToPhp($response); + if (isset($_obj[0]['STATUS']) && $_obj[0]['STATUS'] == 'PENDING') { + //dbg("Sleeping 10, try = $try"); + sleep("10"); + $try++; + if ($try > $max_try) { + Throw new Exception("Timeout waiting for report to return."); + } + continue; + } else { + break; + } + } catch (Exception $ex) { + // for now, pass the exception on + Throw new Exception($ex); + } + } + + $totalcount = $thiscount; + + //dbg("GOT THE RESULT, now read them in!"); + $phpobj = array(); $csv = ''; $json = ''; $xml = ''; $count = 0; + $returnFormat = api_returnFormat::PHPOBJ; + $$returnFormat = self::processReadResults($response, $returnFormat, $thiscount); + $totalcount = $thiscount; + + //dbg("while($thiscount == $pageSize && $totalcount <= $maxRecords)"); + while($thiscount == $pageSize && $totalcount <= $maxRecords) { + //dbg("DOING A READ MORE because: $thiscount == $pageSize && $totalcount <= $maxRecords "); + $readXml = "$id"; + try { + $response = api_post::post($readXml, $session); + //dbg("READMORE: $response"); + api_post::validateReadResults($response); + $page = self::processReadResults($response, $returnFormat, $pageCount); + $totalcount += $pageCount; + $thiscount = $pageCount; + + switch($returnFormat) { + case api_returnFormat::PHPOBJ: + foreach($page as $objRec) { + $phpobj[] = $objRec; + } + break; + case api_returnFormat::CSV: + $page = explode("\n", $page); + array_shift($page); + $csv .= implode($page, "\n"); + break; + case api_returnFormat::XML: + $xml .= $page; + break; + default: + throw new Exception("Invalid return format: " . $returnFormat); + break; + } + + } + catch (Exception $ex) { + // we've probably exceeded the limit + break; + } + //dbg("while($thiscount == $pageSize && $totalcount <= $maxRecords)"); + } + //dbg("RETURNING"); + //dbg($$returnFormat); + return $$returnFormat; + } + } + /** * WARNING: This method will attempt to delete all records of a given object type * Deletes first 10000 by default @@ -683,7 +815,7 @@ public static function deleteAll($object, api_session $session, $max=10000) { * @param Integer $max [optional] Maximum number of records to delete. Default is 10000 * @return Integer count of records deleted */ - public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=10000) { + public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=100000) { $num_per_func= 100; // read all the record ids for the given object @@ -879,7 +1011,7 @@ public static function execute($body, $endPoint) { * @param String $response The XML response document * @throws Exception */ - public static function findResponseErrors($response) { + public static function findResponseErrors($response,$multi=false) { $errorArray = array(); @@ -896,7 +1028,7 @@ public static function findResponseErrors($response) { // look for a failure in the operation, but not the result if (isset($simpleXml->operation->errormessage)) { $error = $simpleXml->operation->errormessage->error[0]; - $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->operation->errormessage)); + $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->operation->errormessage,$multi)); } // if we didn't get an operation, the request failed and we should raise an exception @@ -904,14 +1036,14 @@ public static function findResponseErrors($response) { // did the method invocation fail? if (!isset($simpleXml->operation)) { if (isset($simpleXml->errormessage)) { - $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->errormessage)); + $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->errormessage,$multi)); } } else { $results = $simpleXml->xpath('/response/operation/result'); foreach ($results as $result) { if ((string)$result->status == "failure") { - $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage)); + $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage,$multi)); } } } @@ -951,7 +1083,7 @@ private static function validateResponse($response) { else { $results = $simpleXml->operation->result; foreach ($results as $res) { - if ($res->status == "failure") { + if ($res->status == "failure" || $res->status == "aborted") { throw new Exception("[Error] " . api_util::xmlErrorToString($res->errormessage)); } } @@ -1085,6 +1217,16 @@ public static function processListResults($response, $returnFormat = api_returnF } } } + + if (isset($array['recursotransaction'])) { + foreach ($array['recursotransaction'] as $key => $txn) { + if (isset($txn['recursotransitems']['recursotransitem'])) { + if (!is_numeric(key($txn['recursotransitems']['recursotransitem']))) { + $array['recursotransaction'][$key]['recursotransitems']['recursotransitem'] = array ($txn['recursotransitems']['recursotransitem']); + } + } + } + } return $array; } From ef63f011825b2aa8547f2164224b131097f3e8f3 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 23 May 2016 12:54:46 -0500 Subject: [PATCH 22/64] new mods --- api_util.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/api_util.php b/api_util.php index c23cca1..50f5959 100755 --- a/api_util.php +++ b/api_util.php @@ -189,30 +189,36 @@ public static function csvToPhp($csv) { } return $table; - } - + } + /** * Convert a error object into nice text * @param Object $error simpleXmlObject * @return string formatted error message */ - public static function xmlErrorToString($error) { - + public static function xmlErrorToString($error,$multi=false) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); } + // show just the first error + //$error = $error->error[0]; + foreach ($error->error as $error) { + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); + } - $error = $error->error[0]; - if (!is_object($error)) { - return "Malformed error: " . var_export($error, true); + $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; + $description = is_object($error->description) ? (string)$error->description : ' '; + $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; + $correction = is_object($error->correction) ? (string)$error->correction : ' '; + $error_string .= "$errorno: $description: $description2: $correction\n"; + if ($multi === false) { + break; + } } - - $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; - $description = is_object($error->description) ? (string)$error->description : ' '; - $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; - $correction = is_object($error->correction) ? (string)$error->correction : ' '; - return "$errorno: $description: $description2: $correction"; + return $error_string; } + } From 8db57e1668a2f08e7b4b59bef7e0805a78382fb1 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 22 Sep 2016 14:14:24 -0500 Subject: [PATCH 23/64] removal of dbg statement --- api_post.php | 1 - 1 file changed, 1 deletion(-) diff --git a/api_post.php b/api_post.php index 66f4f89..c81d572 100755 --- a/api_post.php +++ b/api_post.php @@ -1050,7 +1050,6 @@ private static function validateResponse($response) { if (!isset($simpleXml->operation)) { if (isset($simpleXml->errormessage)) { - dbg("HEY1: " . $simpleXml->errormessage); throw new Exception("[Error] " . api_util::xmlErrorToString($simpleXml->errormessage)); } } From 78430a9c1d251946c703dc7bfc432168f79282da Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 4 Nov 2016 16:42:02 -0500 Subject: [PATCH 24/64] some minor fixes --- api_post.php | 25 +++++++++++++++++++++++++ api_util.php | 5 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index d60a796..c7714ae 100755 --- a/api_post.php +++ b/api_post.php @@ -37,6 +37,30 @@ public static function read($object, $id, $fields, api_session $session) { } } + /** + * ReadDocument one or more records by their key. For platform objects, the key is the 'id' field. + * For standard objects, the key is the 'recordno' field. Results are returned as a php structured array + * @param String $object the integration name for the object + * @param String $docparid the transaction type + * @param String $id a comma separated list of keys for each record you wish to read + * @param String $fields a comma separated list of fields to return + * @param \api_session|Object $session an instance of the php_session object + * @return Array of records + */ + public static function readDocument($object, $docparid, $id, $fields, api_session $session) { + + $readXml = "$object$id$fieldscsv$docparid"; + $objCsv = api_post::post($readXml, $session); + api_post::validateReadResults($objCsv); + $objAry = api_util::csvToPhp($objCsv); + if (count(explode(",",$id)) > 1) { + return $objAry; + } + else { + return $objAry[0]; + } + } + /** * Create one or more records. Object types can be mixed and can be either standard or custom. * Check the developer documentation to see which standard objects are supported in this method @@ -94,6 +118,7 @@ public static function update($records, api_session $session) { $updateXml = $updateXml . $objXml; } $updateXml = $updateXml . ""; + dbg($updateXml); $res = api_post::post($updateXml, $session); return api_post::processUpdateResults($res, $node); diff --git a/api_util.php b/api_util.php index 014d18e..d9f63d2 100755 --- a/api_util.php +++ b/api_util.php @@ -211,11 +211,12 @@ public static function csvToPhp($csv) { * @return string formatted error message */ public static function xmlErrorToString($error,$multi=false) { - if (!is_object($errors)) { - return "Malformed error: " . var_export($errors, true); + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); } // show just the first error //$error = $error->error[0]; + $error_string = ""; foreach ($error->error as $error) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); From b50ad1511d1dec041bc4fa47d7559573ea54a738 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 12 Dec 2016 12:21:16 -0600 Subject: [PATCH 25/64] new clientid stuff --- api_session.php | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/api_session.php b/api_session.php index 4daec0e..eaba59e 100755 --- a/api_session.php +++ b/api_session.php @@ -41,6 +41,18 @@ class api_session { const DEFAULT_LOGIN_URL = "https://api.intacct.com/ia/xml/xmlgw.phtml"; + public function __toString() { + $temp = array ( + 'sessionId' => $this->sessionId, + 'endPoint' => $this->endPoint, + 'companyId' => $this->companyId, + 'userId' => $this->userId, + 'senderId' => $this->senderId, + 'senderPassword' => 'REDACTED', + 'transaction' => $this->transaction + ); + return json_encode($temp); + } /** * Connect to the Intacct Web Service using a set of user credntials for a subentity @@ -53,7 +65,7 @@ class api_session { * @param String $entityId The sub entity id * @throws Exception this method returns no value, but will raise any connection exceptions */ - private function buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType = null, $entityId = null ) + private function buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType = null, $entityId = null ) { $xml = self::XML_HEADER . self::XML_LOGIN . self::XML_FOOTER; @@ -64,13 +76,14 @@ private function buildHeaderXML($companyId, $userId, $password, $senderId, $send $xml = str_replace("{4%}", $senderId, $xml); $xml = str_replace("{5%}", $senderPassword, $xml); + // hack for backward compat if ($entityType == 'location') { $xml = str_replace("{%entityid%}", "$entityId", $xml); - } - else if ($entityType == 'client') { + } else if ($entityType == 'client') { $xml = str_replace("{%entityid%}", "$entityId", $xml); - } - else { + } else if (!empty($entityType) || !empty($entityId)) { + $xml = str_replace("{%entityid%}", "$entityType$entityId", $xml); + } else { $xml = str_replace("{%entityid%}", "", $xml); } @@ -88,7 +101,7 @@ private function buildHeaderXML($companyId, $userId, $password, $senderId, $send */ public function connectCredentials($companyId, $userId, $password, $senderId, $senderPassword, $entityType=null, $entityId=null) { - $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType, $entityId); + $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType, $entityId); $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); @@ -118,7 +131,7 @@ public function connectCredentials($companyId, $userId, $password, $senderId, $s */ public function connectCredentialsEntity($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId) { - $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId); + $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId); $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); From ac436cf297bd0c19e46f7ce613f74ff2dcabf13a Mon Sep 17 00:00:00 2001 From: Vladimir Gorea Date: Wed, 14 Dec 2016 16:36:51 +0200 Subject: [PATCH 26/64] Refactor api_post::read Refactor method to use xml or csv as response format --- api_post.php | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/api_post.php b/api_post.php index 321a814..3df3fa4 100755 --- a/api_post.php +++ b/api_post.php @@ -17,24 +17,37 @@ class api_post { /** * Read one or more records by their key. For platform objects, the key is the 'id' field. * For standard objects, the key is the 'recordno' field. Results are returned as a php structured array - * @param String $object the integration name for the object - * @param String $id a comma separated list of keys for each record you wish to read - * @param String $fields a comma separated list of fields to return - * @param \api_session|Object $session an instance of the php_session object + * @param String $object the integration name for the object + * @param String $id a comma separated list of keys for each record you wish to read + * @param String $fields a comma separated list of fields to return + * @param \api_session|Object $session an instance of the php_session object + * @param String $response_type csv (default), xml * @return Array of records */ - public static function read($object, $id, $fields, api_session $session) { + public static function read($object, $id, $fields, api_session $session, $response_type = 'csv') { - $readXml = "$object$id$fieldscsv"; - $objCsv = api_post::post($readXml, $session); - api_post::validateReadResults($objCsv); - $objAry = api_util::csvToPhp($objCsv); - if (count(explode(",",$id)) > 1) { - return $objAry; - } - else { - return $objAry[0]; + $readXml = "$object$id$fields$response_type"; + $response = api_post::post($readXml, $session); + api_post::validateReadResults($response); + switch ($response_type) { + case 'xml': + $resultRecallsArr = new SimpleXMLElement($response); + $result = $resultRecallsArr->operation->result->data; + break; + case 'csv': + $objAry = api_util::csvToPhp($response); + if (count(explode(",",$id)) > 1) { + $result = $objAry; + } + else { + $result = $objAry[0]; + } + break; + default: + $result = false; + break; } + return $result; } /** From c62db9d480a06ff1373a518f7d90d6fcacb433ef Mon Sep 17 00:00:00 2001 From: Vladimir Gorea Date: Thu, 15 Dec 2016 13:33:36 +0200 Subject: [PATCH 27/64] Bugfix api_util::phpToXml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix “Strict Standards: Only variables should be passed by reference” error at line 145 --- api_util.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api_util.php b/api_util.php index a245009..7cdc1a9 100755 --- a/api_util.php +++ b/api_util.php @@ -142,7 +142,8 @@ public static function phpToXml($key, $values) { // $_xml .= "<$node>" . htmlspecialchars($v) . ""; //} // - $firstKey = array_shift(array_keys($value)); + $valuekeys = array_keys($value); + $firstKey = array_shift($valuekeys); if (is_array($value[$firstKey]) || count($value) > 0 ) { $_xml = self::phpToXml($node,$value) ; } From bd923c14bb5bfd8980bbb158e727f62ca28bf0ed Mon Sep 17 00:00:00 2001 From: Vladimir Gorea Date: Thu, 29 Dec 2016 20:20:07 +0200 Subject: [PATCH 28/64] Bugfix - phpToXml Escape specials chars in attr values --- api_util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_util.php b/api_util.php index a245009..8ba7390 100755 --- a/api_util.php +++ b/api_util.php @@ -121,7 +121,7 @@ public static function phpToXml($key, $values) { if (substr($_k,0,1) == '@') { $pad = ($attrString == "") ? " " : ""; $aname = substr($_k,1); - $aval = $v; + $aval = htmlspecialchars($v); //$attrs = explode(':', substr($v,1)); //$attrString .= $pad . $attrs[0].'="'.$attrs[1].'" '; $attrString .= $pad . $aname.'="'.$aval.'" '; From ebbca5b0ad0c17868c13c04b8adcecf28b46c970 Mon Sep 17 00:00:00 2001 From: alexalbulescu Date: Wed, 31 May 2017 17:10:02 +0300 Subject: [PATCH 29/64] Encode password on building header Xml --- api_session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_session.php b/api_session.php index eaba59e..abac086 100755 --- a/api_session.php +++ b/api_session.php @@ -74,7 +74,7 @@ private function buildHeaderXML($companyId, $userId, $password, $senderId, $send $xml = str_replace("{2%}", $companyId, $xml); $xml = str_replace("{3%}", $password, $xml); $xml = str_replace("{4%}", $senderId, $xml); - $xml = str_replace("{5%}", $senderPassword, $xml); + $xml = str_replace("{5%}", htmlspecialchars($senderPassword), $xml); // hack for backward compat if ($entityType == 'location') { From 5f7ee32bdde9665328707099cec1794e1038e629 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 29 Jun 2017 09:44:46 -0500 Subject: [PATCH 30/64] local changes to fix readReort and other stuff --- api_post.php | 99 ++++++++++++++++++++++++++++++++-------------------- api_util.php | 13 +++++-- 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/api_post.php b/api_post.php index 66f4f89..e57b801 100755 --- a/api_post.php +++ b/api_post.php @@ -12,7 +12,7 @@ class api_post { private static $dryRun; const DEFAULT_PAGESIZE = 1000; - const DEFAULT_MAXRETURN = 200000; + const DEFAULT_MAXRETURN = 100000; /** * Read one or more records by their key. For platform objects, the key is the 'id' field. @@ -464,10 +464,8 @@ public static function readByQuery($object, $query, $fields, api_session $sessio $readXml = "$object$query$fields$returnFormatArg"; $readXml .= "$pageSize"; $readXml .= ""; - dbg($readXml); $response = api_post::post($readXml,$session); - dbg($response); if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { // csv with no records will have no response, so avoid the error from validate and just return return ''; @@ -675,7 +673,7 @@ public static function readRelated($object, $keys, $relation, $fields, api_sessi */ public static function readReport($report, api_session $session, $arguments=null, $waitTime=0, $pageSize=100) { $maxRecords = self::DEFAULT_MAXRETURN; - $max_try = 1000; + $max_try = 100; $try = 0; if (is_array($arguments) ) { @@ -685,62 +683,91 @@ public static function readReport($report, api_session $session, $arguments=null } $argxml .= ""; } - $readXml = "$reportcsv$waitTime$argxml$pagesize"; + $readXml = "$reportcsv$waitTime$argxml$pageSize"; +// dbg($readXml); $objCsv = api_post::post($readXml, $session); api_post::validateReadResults($objCsv); $objAry = api_util::csvToPhp($objCsv); if (is_array($objAry) && count($objAry) == 1) { $id = $objAry[0]['REPORTID']; - do { + while(true) { $readXml = "$id"; try { $response = api_post::post($readXml, $session); - dbg("READMORE:"); - dbg($response); - dbg("TRIMMED:"); - dbg(trim($response)); + //dbg(trim($response)); if (trim($response) == "") { return array(); } api_post::validateReadResults($response); $_obj = api_util::csvToPhp($response); - dbg($_obj); if (isset($_obj[0]['STATUS']) && $_obj[0]['STATUS'] == 'PENDING') { - dbg("Sleeping 10, try = $try"); + //dbg("Sleeping 10, try = $try"); sleep("10"); $try++; + if ($try > $max_try) { + Throw new Exception("Timeout waiting for report to return."); + } continue; + } else { + break; } + } catch (Exception $ex) { + // for now, pass the exception on + Throw new Exception($ex); + } + } + $totalcount = $thiscount; + + //dbg("GOT THE RESULT, now read them in!"); + $phpobj = array(); $csv = ''; $json = ''; $xml = ''; $count = 0; + $returnFormat = api_returnFormat::PHPOBJ; + $$returnFormat = self::processReadResults($response, $returnFormat, $thiscount); + $totalcount = $thiscount; + + //dbg("while($thiscount == $pageSize && $totalcount <= $maxRecords)"); + while($thiscount == $pageSize && $totalcount <= $maxRecords) { + //dbg("DOING A READ MORE because: $thiscount == $pageSize && $totalcount <= $maxRecords "); + $readXml = "$id"; + try { + $response = api_post::post($readXml, $session); + //dbg("READMORE: $response"); + api_post::validateReadResults($response); $page = self::processReadResults($response, $returnFormat, $pageCount); - $count += $pageCount; - if ($returnFormat == api_returnFormat::PHPOBJ) { - foreach($page as $objRec) { - $phpobj[] = $objRec; - } - } - elseif ($returnFormat == api_returnFormat::CSV) { - // append all but the first row to the CSV file - $page = explode("\n", $page); - array_shift($page); - $csv .= implode($page, "\n"); - } - elseif ($returnFormat == api_returnFormat::XML) { - // just add the xml string - $xml .= $page; + $totalcount += $pageCount; + $thiscount = $pageCount; + + switch($returnFormat) { + case api_returnFormat::PHPOBJ: + foreach($page as $objRec) { + $phpobj[] = $objRec; + } + break; + case api_returnFormat::CSV: + $page = explode("\n", $page); + array_shift($page); + $csv .= implode($page, "\n"); + break; + case api_returnFormat::XML: + $xml .= $page; + break; + default: + throw new Exception("Invalid return format: " . $returnFormat); + break; } + } catch (Exception $ex) { - // for now, pass the exception on - Throw new Exception($ex); + // we've probably exceeded the limit + break; } - if ($pageCount < $pageSize || $count >= $maxRecords) break; - } while ($try < $max_try); - - dbg("FINISHED LOOP"); + //dbg("while($thiscount == $pageSize && $totalcount <= $maxRecords)"); + } + //dbg("RETURNING"); + //dbg($$returnFormat); + return $$returnFormat; } - return $objAry; } /** @@ -1050,7 +1077,6 @@ private static function validateResponse($response) { if (!isset($simpleXml->operation)) { if (isset($simpleXml->errormessage)) { - dbg("HEY1: " . $simpleXml->errormessage); throw new Exception("[Error] " . api_util::xmlErrorToString($simpleXml->errormessage)); } } @@ -1058,8 +1084,7 @@ private static function validateResponse($response) { $results = $simpleXml->operation->result; foreach ($results as $res) { if ($res->status == "failure" || $res->status == "aborted") { - $msg = api_util::xmlErrorToString($res->errormessage); - throw new Exception("[Error] " . $msg); + throw new Exception("[Error] " . api_util::xmlErrorToString($res->errormessage)); } } } diff --git a/api_util.php b/api_util.php index d4b5425..26e428c 100755 --- a/api_util.php +++ b/api_util.php @@ -130,6 +130,14 @@ public static function phpToXml($key, $values) { } } + if (is_array($value) && count($value) == 0) { + // this means we are a function like with + // no elements inside. We are done. + $xml .= "<$node $attrString>"; + + break; + } + //$firstKey = array_shift(array_keys($value)); //if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { // $_xml = self::phpToXml($node,$value) ; @@ -203,8 +211,8 @@ public static function csvToPhp($csv) { } return $table; - } - + } + /** * Convert a error object into nice text * @param Object $error simpleXmlObject @@ -235,4 +243,5 @@ public static function xmlErrorToString($error,$multi=false) { } + } From b2639ac79edabbb9c3e09013a2635e843e236352 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 6 Jul 2017 14:31:24 -0500 Subject: [PATCH 31/64] added abort to the reponse error finder --- api_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index c5a82f3..ff1ed43 100755 --- a/api_post.php +++ b/api_post.php @@ -1056,7 +1056,7 @@ public static function findResponseErrors($response,$multi=false) { else { $results = $simpleXml->xpath('/response/operation/result'); foreach ($results as $result) { - if ((string)$result->status == "failure") { + if ((string)$result->status == "failure" || (string)$result->status == "aborted") { $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage,$multi)); } } From 7c0cfc80ec350a4e0ff739fc65f404fdd897fa70 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Sun, 13 Aug 2017 10:50:17 -0500 Subject: [PATCH 32/64] local mods --- api_post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index 66f4f89..a75a2dc 100755 --- a/api_post.php +++ b/api_post.php @@ -191,7 +191,7 @@ public static function upsert($object, $records, $nameField, $keyField, api_sess unset($toCreate[$key][$object][$nameField]); } } - api_post::create($toCreate, $session); + return api_post::create($toCreate, $session); } if (count($toUpdate) > 0) { foreach ($toUpdate as $updateKey => $updateRec) { @@ -200,7 +200,7 @@ public static function upsert($object, $records, $nameField, $keyField, api_sess unset($toUpdate[$updateKey][$object][$nameField]); } } - api_post::update($toUpdate, $session); + return api_post::update($toUpdate, $session); } } } From 1864a556d66c655220dfdbb6a7566d48417f73bf Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 29 Mar 2018 11:08:36 -0500 Subject: [PATCH 33/64] making exception be Exception --- api_util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_util.php b/api_util.php index d5819ba..4a5ee0c 100755 --- a/api_util.php +++ b/api_util.php @@ -199,7 +199,7 @@ public static function csvToPhp($csv) { // get the header row $header = fgetcsv($fp, 10000, ',','"'); if (is_null($header) || is_null($header[0])) { - throw new exception ("Unable to determine header. Is there garbage in the file?"); + throw new \Exception ("Unable to determine header. Is there garbage in the file?"); } // get the rows From ef25b6ab8c7369f80aeadef68b574ae340bc5a20 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 26 Apr 2018 17:38:20 +0300 Subject: [PATCH 34/64] Fix error 'Unable to determine header. Is there garbage in the file?' for read related --- api_post.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api_post.php b/api_post.php index 3df3fa4..fabb94b 100755 --- a/api_post.php +++ b/api_post.php @@ -673,6 +673,10 @@ public static function readByName($object, $name, $fields, api_session $session) public static function readRelated($object, $keys, $relation, $fields, api_session $session) { $readXml = "$object$keys$relation$fieldscsv"; $objCsv = api_post::post($readXml, $session); + //if we receive an empty response we return it + if (trim($objCsv) == "") { + return ''; + } api_post::validateReadResults($objCsv); $objAry = api_util::csvToPhp($objCsv); return $objAry; From 7d72c4e904f5c5c07786daa627082ff6bc840f52 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 29 Oct 2018 14:06:40 -0500 Subject: [PATCH 35/64] initializing $thiscount per request from dev team --- api_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index 0698e37..a551ef1 100755 --- a/api_post.php +++ b/api_post.php @@ -487,7 +487,7 @@ public static function readByQuery($object, $query, $fields, api_session $sessio api_post::validateReadResults($response); - $phpobj = array(); $csv = ''; $json = ''; $xml = ''; $count = 0; + $phpobj = array(); $csv = ''; $json = ''; $xml = ''; $count = 0; $thiscount = 0; $$returnFormat = self::processReadResults($response, $returnFormat, $thiscount); $totalcount = $thiscount; From ef49c61ac0c910c04a17f0d2e245b14578ec4694 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 19 Nov 2018 13:38:45 -0600 Subject: [PATCH 36/64] snapshot --- api_objDef.php | 11 ++- api_post.php | 177 ++++++++++++++++++++++++++++++++++++++++++------ api_session.php | 27 ++++++-- api_util.php | 87 +++++++++++++++--------- 4 files changed, 243 insertions(+), 59 deletions(-) diff --git a/api_objDef.php b/api_objDef.php index aded6d5..6408aad 100644 --- a/api_objDef.php +++ b/api_objDef.php @@ -24,12 +24,21 @@ public function __construct(simpleXmlElement $simpleXml) { $this->Description = (string)$simpleXml->Attributes->Description; $fields = $simpleXml->Fields; - $this->Fields = array(); + foreach($fields->Field as $field) { $fieldDef = new api_fieldDef($field); $this->Fields[$fieldDef->Name] = $fieldDef; } } + public function get_field_array() + { + $array = array(); + foreach ($this->Fields as $field) { + $array[] = (string)$field->Name; + } + return $array; + } + } diff --git a/api_post.php b/api_post.php index af9edf9..812be45 100755 --- a/api_post.php +++ b/api_post.php @@ -12,20 +12,57 @@ class api_post { private static $dryRun; const DEFAULT_PAGESIZE = 1000; - const DEFAULT_MAXRETURN = 100000; + const DEFAULT_MAXRETURN = 200000; /** * Read one or more records by their key. For platform objects, the key is the 'id' field. * For standard objects, the key is the 'recordno' field. Results are returned as a php structured array + * @param String $object the integration name for the object + * @param String $id a comma separated list of keys for each record you wish to read + * @param String $fields a comma separated list of fields to return + * @param \api_session|Object $session an instance of the php_session object + * @param String $response_type csv (default), xml + * @return Array of records + */ + public static function read($object, $id, $fields, api_session $session, $response_type = 'csv') { + + $readXml = "$object$id$fields$response_type"; + $response = api_post::post($readXml, $session); + api_post::validateReadResults($response); + switch ($response_type) { + case 'xml': + $resultRecallsArr = new SimpleXMLElement($response); + $result = $resultRecallsArr->operation->result->data; + break; + case 'csv': + $objAry = api_util::csvToPhp($response); + if (count(explode(",",$id)) > 1) { + $result = $objAry; + } + else { + $result = $objAry[0]; + } + break; + default: + $result = false; + break; + } + return $result; + } + + /** + * ReadDocument one or more records by their key. For platform objects, the key is the 'id' field. + * For standard objects, the key is the 'recordno' field. Results are returned as a php structured array * @param String $object the integration name for the object + * @param String $docparid the transaction type * @param String $id a comma separated list of keys for each record you wish to read * @param String $fields a comma separated list of fields to return * @param \api_session|Object $session an instance of the php_session object * @return Array of records */ - public static function read($object, $id, $fields, api_session $session) { + public static function readDocument($object, $docparid, $id, $fields, api_session $session) { - $readXml = "$object$id$fieldscsv"; + $readXml = "$object$id$fieldscsv$docparid"; $objCsv = api_post::post($readXml, $session); api_post::validateReadResults($objCsv); $objAry = api_util::csvToPhp($objCsv); @@ -94,6 +131,7 @@ public static function update($records, api_session $session) { $updateXml = $updateXml . $objXml; } $updateXml = $updateXml . ""; + //dbg($updateXml); $res = api_post::post($updateXml, $session); return api_post::processUpdateResults($res, $node); @@ -221,12 +259,12 @@ public static function call21Method($function, $phpObj, api_session $session) { /** * Run any Intacct API method not directly implemented in this class. You must pass * valid XML for the method you wish to invoke. - * @param Array $phpObj an array for all the functions . + * @param Array $phpObj an array for all the functions . * @param api_session $session an api_session instance with a valid connection * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function sendFunctions($phpObj, api_session $session, $dtdVersion="2.1") { + public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0") { $xml = api_util::phpToXml('content',array($phpObj)); return api_post::post($xml, $session,$dtdVersion, true); } @@ -262,7 +300,7 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ ); $xml = api_util::phpToXml('content',array($func)); - $res = api_post::post($xml, $session,$dtdVersion, true); + $res = api_post::post($xml, $session,$dtdVersion, true); if (self::$dryRun == true) { return; } @@ -297,7 +335,7 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ ); $xml = api_util::phpToXml('content',array($func)); - $res = api_post::post($xml, $session,$dtdVersion, true); + $res = api_post::post($xml, $session,$dtdVersion, true); $ret = api_post::processListResults($res, api_returnFormat::PHPOBJ, $count); $nextBatch = null; @@ -440,8 +478,10 @@ public static function readByQuery($object, $query, $fields, api_session $sessio $readXml = "$object$query$fields$returnFormatArg"; $readXml .= "$pageSize"; $readXml .= ""; + //dbg($readXml); $response = api_post::post($readXml,$session); + //dbg($response); if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { // csv with no records will have no response, so avoid the error from validate and just return return ''; @@ -453,13 +493,17 @@ public static function readByQuery($object, $query, $fields, api_session $sessio $$returnFormat = self::processReadResults($response, $returnFormat, $thiscount); $totalcount = $thiscount; + //dbg("$thiscount == $pageSize && $totalcount <= $maxRecords"); // we have no idea if there are more if CSV is returned, so just check // if the last count returned was $pageSize while($thiscount == $pageSize && $totalcount <= $maxRecords) { + dbg("READMORE: " . ++$count); $readXml = "$object"; try { $response = api_post::post($readXml, $session); + //dbg($response); + //dbg(api_post::getLastRequest()); api_post::validateReadResults($response); $page = self::processReadResults($response, $returnFormat, $pageCount); $totalcount += $pageCount; @@ -638,6 +682,85 @@ public static function readRelated($object, $keys, $relation, $fields, api_sessi return $objAry; } + /** + * Reads all the records related to a source record through a named relationship. + * @param String $object the integration name of the object + * @param String $keys a comma separated list of 'id' values of the source records from which you want to read related records + * @param String $relation the name of the relationship. This will determine the type of object you are reading + * @param String $fields a comma separated list of fields to return + * @param api_session $session + * @return Array of objects + */ + public static function readReport($report, api_session $session, $arguments=null, $waitTime=0, $pageSize=100) { + $maxRecords = self::DEFAULT_MAXRETURN; + $max_try = 1000; + $try = 0; + + if (is_array($arguments) ) { + $argxml= ""; + foreach ($arguments as $key => $arg) { + $argxml.= "<$key>$arg"; + } + $argxml .= ""; + } + $readXml = "$reportcsv$waitTime$argxml$pagesize"; + $objCsv = api_post::post($readXml, $session); + api_post::validateReadResults($objCsv); + $objAry = api_util::csvToPhp($objCsv); + + if (is_array($objAry) && count($objAry) == 1) { + $id = $objAry[0]['REPORTID']; + do { + $readXml = "$id"; + try { + $response = api_post::post($readXml, $session); + //dbg("READMORE:"); + //dbg($response); + //dbg("TRIMMED:"); + //dbg(trim($response)); + if (trim($response) == "") { + return array(); + } + api_post::validateReadResults($response); + $_obj = api_util::csvToPhp($response); + //dbg($_obj); + if (isset($_obj[0]['STATUS']) && $_obj[0]['STATUS'] == 'PENDING') { + //dbg("Sleeping 10, try = $try"); + sleep("10"); + $try++; + continue; + } + + $page = self::processReadResults($response, $returnFormat, $pageCount); + $count += $pageCount; + if ($returnFormat == api_returnFormat::PHPOBJ) { + foreach($page as $objRec) { + $phpobj[] = $objRec; + } + } + elseif ($returnFormat == api_returnFormat::CSV) { + // append all but the first row to the CSV file + $page = explode("\n", $page); + array_shift($page); + $csv .= implode($page, "\n"); + } + elseif ($returnFormat == api_returnFormat::XML) { + // just add the xml string + $xml .= $page; + } + } + catch (Exception $ex) { + // for now, pass the exception on + Throw new Exception($ex); + } + if ($pageCount < $pageSize || $count >= $maxRecords) break; + } while ($try < $max_try); + + //dbg("FINISHED LOOP"); + } + return $objAry; + } + /** * WARNING: This method will attempt to delete all records of a given object type * Deletes first 10000 by default @@ -683,11 +806,12 @@ public static function deleteAll($object, api_session $session, $max=10000) { * @param Integer $max [optional] Maximum number of records to delete. Default is 10000 * @return Integer count of records deleted */ - public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=10000) { + public static function deleteByQuery($object, $query, $key_field, api_session $session, $max=100000) { $num_per_func= 100; // read all the record ids for the given object $ids = api_post::readByQuery($object, "$key_field > 0 and $query", $key_field, $session, $max); + dbg("COUNT of things to delete: " . count($ids)); if ((!is_array($ids) && trim($ids) == '') || !count($ids) > 0) { return 0; @@ -704,6 +828,7 @@ public static function deleteByQuery($object, $query, $key_field, api_session $s } catch (Exception $ex) { $delIds = array(); + print_r($ex); continue; } $count += $num_per_func; @@ -736,7 +861,7 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult $transaction = ( $session->transaction ) ? 'true' : 'false' ; - /* + /* $templateHead = " @@ -788,7 +913,7 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult " "; - $contentFoot = + $contentFoot = " "; @@ -879,7 +1004,7 @@ public static function execute($body, $endPoint) { * @param String $response The XML response document * @throws Exception */ - public static function findResponseErrors($response) { + public static function findResponseErrors($response,$multi=false) { $errorArray = array(); @@ -896,7 +1021,7 @@ public static function findResponseErrors($response) { // look for a failure in the operation, but not the result if (isset($simpleXml->operation->errormessage)) { $error = $simpleXml->operation->errormessage->error[0]; - $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->operation->errormessage)); + $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->operation->errormessage,$multi)); } // if we didn't get an operation, the request failed and we should raise an exception @@ -904,14 +1029,14 @@ public static function findResponseErrors($response) { // did the method invocation fail? if (!isset($simpleXml->operation)) { if (isset($simpleXml->errormessage)) { - $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->errormessage)); + $errorArray[] = array ( 'desc' => api_util::xmlErrorToString($simpleXml->errormessage,$multi)); } } else { $results = $simpleXml->xpath('/response/operation/result'); foreach ($results as $result) { - if ((string)$result->status == "failure") { - $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage)); + if ((string)$result->status == "failure" || (string)$result->status == "aborted") { + $errorArray[] = array ( 'controlid' => (string)$result->controlid, 'desc' => api_util::xmlErrorToString($result->errormessage,$multi)); } } } @@ -948,11 +1073,12 @@ private static function validateResponse($response) { throw new Exception("[Error] " . api_util::xmlErrorToString($simpleXml->errormessage)); } } - else { + else { $results = $simpleXml->operation->result; foreach ($results as $res) { - if ($res->status == "failure") { - throw new Exception("[Error] " . api_util::xmlErrorToString($res->errormessage)); + if ($res->status == "failure" || $res->status == "aborted") { + $msg = api_util::xmlErrorToString($res->errormessage); + throw new Exception("[Error] " . $msg); } } } @@ -1041,6 +1167,7 @@ private static function validateReadResults($response) { * @return Mixed string or object depending on return format */ public static function processListResults($response, $returnFormat = api_returnFormat::PHPOBJ, &$count) { + //dbg($response); $xml = simplexml_load_string($response); @@ -1049,7 +1176,7 @@ public static function processListResults($response, $returnFormat = api_returnF throw new Exception("Get List failed"); return; } - + if ($returnFormat != api_returnFormat::PHPOBJ) { throw new Exception("Only PHPOBJ is supported for returnFormat currently."); return; @@ -1085,6 +1212,16 @@ public static function processListResults($response, $returnFormat = api_returnF } } } + + if (isset($array['recursotransaction'])) { + foreach ($array['recursotransaction'] as $key => $txn) { + if (isset($txn['recursotransitems']['recursotransitem'])) { + if (!is_numeric(key($txn['recursotransitems']['recursotransitem']))) { + $array['recursotransaction'][$key]['recursotransitems']['recursotransitem'] = array ($txn['recursotransitems']['recursotransitem']); + } + } + } + } return $array; } @@ -1152,5 +1289,5 @@ public static function setDryRun($tf=true) { self::$dryRun = $tf; } - + } diff --git a/api_session.php b/api_session.php index 4daec0e..eaba59e 100755 --- a/api_session.php +++ b/api_session.php @@ -41,6 +41,18 @@ class api_session { const DEFAULT_LOGIN_URL = "https://api.intacct.com/ia/xml/xmlgw.phtml"; + public function __toString() { + $temp = array ( + 'sessionId' => $this->sessionId, + 'endPoint' => $this->endPoint, + 'companyId' => $this->companyId, + 'userId' => $this->userId, + 'senderId' => $this->senderId, + 'senderPassword' => 'REDACTED', + 'transaction' => $this->transaction + ); + return json_encode($temp); + } /** * Connect to the Intacct Web Service using a set of user credntials for a subentity @@ -53,7 +65,7 @@ class api_session { * @param String $entityId The sub entity id * @throws Exception this method returns no value, but will raise any connection exceptions */ - private function buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType = null, $entityId = null ) + private function buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType = null, $entityId = null ) { $xml = self::XML_HEADER . self::XML_LOGIN . self::XML_FOOTER; @@ -64,13 +76,14 @@ private function buildHeaderXML($companyId, $userId, $password, $senderId, $send $xml = str_replace("{4%}", $senderId, $xml); $xml = str_replace("{5%}", $senderPassword, $xml); + // hack for backward compat if ($entityType == 'location') { $xml = str_replace("{%entityid%}", "$entityId", $xml); - } - else if ($entityType == 'client') { + } else if ($entityType == 'client') { $xml = str_replace("{%entityid%}", "$entityId", $xml); - } - else { + } else if (!empty($entityType) || !empty($entityId)) { + $xml = str_replace("{%entityid%}", "$entityType$entityId", $xml); + } else { $xml = str_replace("{%entityid%}", "", $xml); } @@ -88,7 +101,7 @@ private function buildHeaderXML($companyId, $userId, $password, $senderId, $send */ public function connectCredentials($companyId, $userId, $password, $senderId, $senderPassword, $entityType=null, $entityId=null) { - $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType, $entityId); + $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType, $entityId); $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); @@ -118,7 +131,7 @@ public function connectCredentials($companyId, $userId, $password, $senderId, $s */ public function connectCredentialsEntity($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId) { - $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId); + $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId); $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); diff --git a/api_util.php b/api_util.php index c23cca1..f3517a0 100755 --- a/api_util.php +++ b/api_util.php @@ -70,7 +70,7 @@ public static function dateToOldDate($date) { * @return array */ public static function getRangeOfDates($date, $count) { - // the first date is the first of the following month + // the first date is the first of the following month $month = date("m", strToTime($date)) + 1; $year = date("Y", strToTime($date)); if ($month == 13) { @@ -81,7 +81,7 @@ public static function getRangeOfDates($date, $count) { $dateTime->modify("-1 day"); $dates = array($dateTime->format("Y-m-d")); - // now, iterate $count - 1 times adding one month to each + // now, iterate $count - 1 times adding one month to each for ($x=1; $x < $count; $x++) { $dateTime->modify("+1 day"); $dateTime->modify("+1 month"); @@ -91,11 +91,11 @@ public static function getRangeOfDates($date, $count) { return $dates; } - /** - * Convert a php structure to an XML element + /** + * Convert a php structure to an XML element * @param String $key element name * @param Array $values element values - * @return string xml + * @return string xml */ public static function phpToXml($key, $values) { $xml = ""; @@ -103,7 +103,9 @@ public static function phpToXml($key, $values) { return "<$key>$values"; } - if (!is_numeric(array_shift(array_keys($values)))) { + $temp1 = array_keys($values); + $temp2 = array_shift($temp1); + if (!is_numeric($temp2)) { $xml = "<" . $key . ">"; } foreach($values as $node => $value) { @@ -119,7 +121,7 @@ public static function phpToXml($key, $values) { if (substr($_k,0,1) == '@') { $pad = ($attrString == "") ? " " : ""; $aname = substr($_k,1); - $aval = $v; + $aval = htmlspecialchars($v); //$attrs = explode(':', substr($v,1)); //$attrString .= $pad . $attrs[0].'="'.$attrs[1].'" '; $attrString .= $pad . $aname.'="'.$aval.'" '; @@ -128,16 +130,29 @@ public static function phpToXml($key, $values) { } } - $firstKey = array_shift(array_keys($value)); - if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { - $_xml = self::phpToXml($node,$value) ; + //$firstKey = array_shift(array_keys($value)); + //if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { + // $_xml = self::phpToXml($node,$value) ; + //} + //else { + // $v = ""; + // if (isset($value[$firstKey])) { + // $v = $value[$firstKey]; + // } + // $_xml .= "<$node>" . htmlspecialchars($v) . ""; + //} + // + $valuekeys = array_keys($value); + $firstKey = array_shift($valuekeys); + if (is_array($value[$firstKey]) || count($value) > 0 ) { + $_xml = self::phpToXml($node,$value) ; } else { - $v = ""; - if (isset($value[$firstKey])) { - $v = $value[$firstKey]; + $_xml = self::phpToXml($node,$value) ; + $v = $value[$firstKey]; + if (!empty($v)) { + $_xml .= "<$node>" . htmlspecialchars($v) . ""; } - $_xml .= "<$node>" . htmlspecialchars($v) . ""; } if ($attrString != "") { @@ -145,6 +160,7 @@ public static function phpToXml($key, $values) { } $xml .= $_xml; +// dbg("XML now is $xml"); } else { if (is_numeric($node)) { @@ -155,15 +171,17 @@ public static function phpToXml($key, $values) { } } } - if (!is_numeric(array_shift(array_keys($values)))) { + $temp1 = array_keys($values); + $temp2 = array_shift($temp1); + if (!is_numeric($temp2)) { $xml .= ""; } return $xml; } - /** - * Convert a CSV string result into a php array. - * This work for Intacct API results. Not a generic method + /** + * Convert a CSV string result into a php array. + * This work for Intacct API results. Not a generic method */ public static function csvToPhp($csv) { @@ -173,13 +191,13 @@ public static function csvToPhp($csv) { rewind($fp); $table = array(); - // get the header row + // get the header row $header = fgetcsv($fp, 10000, ',','"'); if (is_null($header) || is_null($header[0])) { throw new exception ("Unable to determine header. Is there garbage in the file?"); } - // get the rows + // get the rows while (($data = fgetcsv($fp, 10000, ',','"')) !== false) { $row = array(); foreach($header as $key => $value) { @@ -196,23 +214,30 @@ public static function csvToPhp($csv) { * @param Object $error simpleXmlObject * @return string formatted error message */ - public static function xmlErrorToString($error) { - + public static function xmlErrorToString($error,$multi=false) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); } + // show just the first error + //$error = $error->error[0]; + $error_string = ""; + foreach ($error->error as $error) { + if (!is_object($error)) { + return "Malformed error: " . var_export($error, true); + } - $error = $error->error[0]; - if (!is_object($error)) { - return "Malformed error: " . var_export($error, true); + $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; + $description = is_object($error->description) ? (string)$error->description : ' '; + $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; + $correction = is_object($error->correction) ? (string)$error->correction : ' '; + $error_string .= "$errorno: $description: $description2: $correction\n"; + if ($multi === false) { + break; + } } - - $errorno = is_object($error->errorno) ? (string)$error->errorno : ' '; - $description = is_object($error->description) ? (string)$error->description : ' '; - $description2 = is_object($error->description2) ? (string)$error->description2 : ' '; - $correction = is_object($error->correction) ? (string)$error->correction : ' '; - return "$errorno: $description: $description2: $correction"; + return $error_string; } + } From 3d834e478f5ba114233b702c2c8fb2b728d0c151 Mon Sep 17 00:00:00 2001 From: Alexadru Nagacevschi Date: Mon, 10 Dec 2018 13:43:58 +0200 Subject: [PATCH 37/64] -Trim spaces from xml response before validating it. --- api_post.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api_post.php b/api_post.php index d9ca0bd..ea895fb 100755 --- a/api_post.php +++ b/api_post.php @@ -1095,6 +1095,8 @@ private static function validateResponse($response) { * @throws Exception */ private static function processUpdateResults($response, $objectName) { + //Fix Intacct bug, by trim spaces from the returned xml response string + $response = trim($response); $simpleXml = simplexml_load_string($response); if ($simpleXml === false) { throw new Exception("Invalid XML response: \n " . var_export($response, true)); From 4d7f31a222c503415feb973cbfa4f83dd439f548 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 11 Feb 2019 12:16:31 -0600 Subject: [PATCH 38/64] allowing more than 100 updates --- api_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index d9ca0bd..dd04998 100755 --- a/api_post.php +++ b/api_post.php @@ -119,7 +119,7 @@ public static function create($records, api_session $session) { * @return array An array of 'ids' updated in the method invocation */ public static function update($records, api_session $session) { - if (count($records) > 100) throw new Exception("Attempting to update more than 100 records."); + if (count($records) > 10000) throw new Exception("Attempting to update more than 10000 records."); // convert the $records array into an xml structure $updateXml = ""; From d0d05ce6e3e58296876ef58d9cefe56479ca83cd Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 27 Feb 2019 17:37:07 -0600 Subject: [PATCH 39/64] fixed bug in max records returned from readByQuery --- api_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index 6113c7f..8ad7670 100755 --- a/api_post.php +++ b/api_post.php @@ -496,7 +496,7 @@ public static function readByQuery($object, $query, $fields, api_session $sessio // we have no idea if there are more if CSV is returned, so just check // if the last count returned was $pageSize - while($thiscount == $pageSize && $totalcount <= $maxRecords) { + while($thiscount == $pageSize && $totalcount < $maxRecords) { dbg("READMORE: " . ++$count); $readXml = "$object"; try { From 6df7e330b1077fade957852c3f60db40bb0fb8d5 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 8 Apr 2019 17:37:46 +0300 Subject: [PATCH 40/64] added entityId --- api_session.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api_session.php b/api_session.php index abac086..427dc7e 100755 --- a/api_session.php +++ b/api_session.php @@ -48,6 +48,7 @@ public function __toString() { 'companyId' => $this->companyId, 'userId' => $this->userId, 'senderId' => $this->senderId, + 'entityId' => $this->entityId, 'senderPassword' => 'REDACTED', 'transaction' => $this->transaction ); @@ -111,6 +112,7 @@ public function connectCredentials($companyId, $userId, $password, $senderId, $s $this->sessionId = (string)$responseObj->operation->result->data->api->sessionid; $this->endPoint = (string)$responseObj->operation->result->data->api->endpoint; + $this->entityId = (string)$responseObj->operation->authentication->locationid; $this->companyId = $companyId; $this->userId = $userId; $this->senderId = $senderId; @@ -141,6 +143,7 @@ public function connectCredentialsEntity($companyId, $userId, $password, $sender $this->sessionId = (string)$responseObj->operation->result->data->api->sessionid; $this->endPoint = (string)$responseObj->operation->result->data->api->endpoint; + $this->entityId = (string)$responseObj->operation->authentication->locationid; $this->companyId = $companyId; $this->userId = $userId; $this->senderId = $senderId; @@ -172,6 +175,7 @@ public function connectSessionId($sessionId, $senderId, $senderPassword) { $this->companyId = (string)$responseObj->operation->authentication->companyid; $this->userId = (string)$responseObj->operation->authentication->userid; $this->endPoint = (string)$responseObj->operation->result->data->api->endpoint; + $this->entityId = (string)$responseObj->operation->authentication->locationid; $this->senderId = $senderId; $this->senderPassword = $senderPassword; } From bc9bb5ae31dfa07f68fb654664f7dbd67a6a8024 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 15 Apr 2019 17:43:43 -0500 Subject: [PATCH 41/64] fixing session thingy --- api_session.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api_session.php b/api_session.php index 427dc7e..1209eeb 100755 --- a/api_session.php +++ b/api_session.php @@ -6,6 +6,7 @@ class api_session { public $sessionId; public $endPoint; public $companyId; + public $entityId; public $userId; public $senderId; public $senderPassword; @@ -46,6 +47,7 @@ public function __toString() { 'sessionId' => $this->sessionId, 'endPoint' => $this->endPoint, 'companyId' => $this->companyId, + 'entityId' => $this->entityId, 'userId' => $this->userId, 'senderId' => $this->senderId, 'entityId' => $this->entityId, From 657bcba39139274ca1cc3957725a9a88f9bb7679 Mon Sep 17 00:00:00 2001 From: Gabriel Rotar Date: Thu, 23 May 2019 14:59:57 +0300 Subject: [PATCH 42/64] Fix re-throwing caught API exceptions using the default Expeception::toString() as message. Use exception linking when re-throwing caught API exceptions --- api_post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index 8ad7670..2c792c5 100755 --- a/api_post.php +++ b/api_post.php @@ -952,10 +952,10 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult if (strpos($ex->getMessage(), "too many operations") !== false) { $count++; if ($count >= 5) { - throw new Exception($ex); + throw new Exception($ex->getMessage(),$ex->getCode(),$ex); } } else { - throw new Exception($ex); + throw new Exception($ex->getMessage(),$ex->getCode(),$ex); } } } From 10e8a52d6dfd19fe7e57bcdeeeca6ff712cea26c Mon Sep 17 00:00:00 2001 From: Gabriel Rotar <203163+neolode@users.noreply.github.com> Date: Wed, 10 Jul 2019 14:03:47 +0300 Subject: [PATCH 43/64] Add support for switching entity based on existing sessions --- api_session.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api_session.php b/api_session.php index 1209eeb..8d79be7 100755 --- a/api_session.php +++ b/api_session.php @@ -30,6 +30,12 @@ class api_session { "; + const XML_FOOTER_2 = " + + {6%} + + +"; const XML_LOGIN = " {1%} @@ -161,12 +167,13 @@ public function connectCredentialsEntity($companyId, $userId, $password, $sender * @param String $senderPassword Your Intacct partner password * @throws Exception This method returns no values, but will raise an exception if there's a connection error */ - public function connectSessionId($sessionId, $senderId, $senderPassword) { + public function connectSessionId($sessionId, $senderId, $senderPassword, $entityId = '') { - $xml = self::XML_HEADER . self::XML_SESSIONID . self::XML_FOOTER; + $xml = self::XML_HEADER . self::XML_SESSIONID . self::XML_FOOTER_2; $xml = str_replace("{1%}", $sessionId, $xml); $xml = str_replace("{4%}", $senderId, $xml); $xml = str_replace("{5%}", $senderPassword, $xml); + $xml = str_replace("{6%}", $entityId, $xml); $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); From a099bc4cd03c2d4dc0d2560b6e2ebfbf1e40e2e6 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 10 Apr 2020 17:21:58 -0500 Subject: [PATCH 44/64] added query method --- api_post.php | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index ea895fb..c1050c6 100755 --- a/api_post.php +++ b/api_post.php @@ -263,11 +263,78 @@ public static function call21Method($function, $phpObj, api_session $session) { * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0") { + public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0", $returnFormat = api_returnFormat::XML) { $xml = api_util::phpToXml('content',array($phpObj)); - return api_post::post($xml, $session,$dtdVersion, true); + $res = api_post::post($xml, $session,$dtdVersion, true); + if ($returnFormat == api_returnFormat::PHPOBJ) { + $res_xml = simplexml_load_string($res); + $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); + dbg($json); + $array = json_decode($json,TRUE); + dbg($array); + } else { + return $res; + } + } + + public static function prune_empty_element($a) { + foreach ($a as $k => $v) { + if (is_array($v) && empty($v)) { + $a[$k] = ""; + } + } + return $a; } + /** + * Run any Intacct API method not directly implemented in this class. You must pass + * valid XML for the method you wish to invoke. + * @param Array $phpObj an array for all the functions . + * @param api_session $session an api_session instance with a valid connection + * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" + * @return String the XML response from Intacct + */ + public static function query(api_session $session, $call, $returnFormat = api_returnFormat::PHPOBJ) { + $obj = $call['object']; + $call['offset'] = $call['offset'] ?? '0'; + $call['pagesize'] = $call['pagesize'] ?? '100'; + $phpObj = array ( + 'function' => array ( + '@controlid' => uniqid(), + 'query' => $call, + ) + ); + $rows = array(); + do { + $xml = api_util::phpToXml('content',array($phpObj)); + $res = api_post::post($xml, $session,'3.0', true); + $res_xml = simplexml_load_string($res); +// $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); + $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); + $array = json_decode($json,TRUE); + if (!isset($array[$obj])) { + $array[$obj] = array(); + } + + $row = array_map(array('api_post','prune_empty_element'),$array[$obj]); + $rows = array_merge($rows,$row); + $pagesize = $call['pagesize'] ?? 100; + $num_remaining = $array['@attributes']['numremaining']; + $phpObj['function']['query']['offset'] += $phpObj['function']['query']['pagesize'] ; + //dbg("READ: " . count($row)); + //dbg("NR: " . $num_remaining); + //dbg("NEW OFFSET: " . $phpObj['function']['query']['offset']); + //dbg("ROW SIZE NOW: " . count($rows)); + //sleep(2); + + } while ($num_remaining > 0); + + if ($returnFormat == api_returnFormat::PHPOBJ) { + return $rows; + } else { + die("only php return format supported"); + } + } /** * Run any Intacct API method not directly implemented in this class. You must pass * valid XML for the method you wish to invoke. @@ -536,6 +603,114 @@ public static function readByQuery($object, $query, $fields, api_session $sessio return $$returnFormat; } + /** + * Read records using a query. Specify the object you want to query and something like a "where" clause" + * @param api_session $session An instance of the api_session object with a valid connection + * @param String $object the object upon which to run the query + * @param String $fields A comma separated list of fields to return + * @param String $query the query string to execute. Use SQL operators + * @param int $maxRecords number of records to return. Defaults to 100000 + * @param string $returnFormat defaults to php object. Pass one of the valid constants from api_returnFormat class + * @return mixed either string or array of objects depending on returnFormat argument + */ + public static function query_bad(api_session $session, $object, $fields=null, $query=null, $maxRecords=self::DEFAULT_MAXRETURN, $returnFormat=api_returnFormat::PHPOBJ) { + + $pageSize = ($maxRecords <= self::DEFAULT_PAGESIZE) ? $maxRecords : self::DEFAULT_PAGESIZE; + + if ($returnFormat == api_returnFormat::PHPOBJ) { + $returnFormatArg = api_returnFormat::CSV; + } + else { + $returnFormatArg = $returnFormat; + } + + $field_xml = ""; + if ($fields !== null) { + $field_xml = "" . str_replace(",","",$fields) . ""; + } + if ($query !== NULL) { + $query_xml = api_util::phpToXml($query); + + } + + // TODO: Implement returnFormat. Today we only support PHPOBJ +// $query = HTMLSpecialChars($query); + + $readXml = "$object$query_xml"; + $readXml .= "$pageSize"; + $readXml .= ""; + dbg($readXml); + + $response = api_post::post($readXml,$session); + dbg($response); + die(); + if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { + // csv with no records will have no response, so avoid the error from validate and just return + return ''; + } + api_post::validateReadResults($response); + + + $phpobj = array(); $xml = ''; $count = 0; $thiscount = 0; + + $simpleXml = simplexml_load_string($response); + $data = $simpleXml->xpath("/response/operation/result/data"); + $thiscount = $data[0]['count']; + $rows = $simpleXml->xpath("/response/operation/result/data/$object"); + foreach ($rows as $row) { + $phpobj[] = (array)$row; + } + + if ($data[0]['numremaining']) + +// $thiscount = $data-> + + //$$returnFormat = self::processReadResults($response, $returnFormat, $thiscount); + + $totalcount = $thiscount; + //dbg("$thiscount == $pageSize && $totalcount <= $maxRecords"); + + // we have no idea if there are more if CSV is returned, so just check + // if the last count returned was $pageSize + while($thiscount == $pageSize && $totalcount <= $maxRecords) { + dbg("READMORE: " . ++$count); + $readXml = "$object"; + try { + $response = api_post::post($readXml, $session); + //dbg($response); + //dbg(api_post::getLastRequest()); + api_post::validateReadResults($response); + $page = self::processReadResults($response, $returnFormat, $pageCount); + $totalcount += $pageCount; + $thiscount = $pageCount; + + switch($returnFormat) { + case api_returnFormat::PHPOBJ: + foreach($page as $objRec) { + $phpobj[] = $objRec; + } + break; + case api_returnFormat::CSV: + $page = explode("\n", $page); + array_shift($page); + $csv .= implode($page, "\n"); + break; + case api_returnFormat::XML: + $xml .= $page; + break; + default: + throw new Exception("Invalid return format: " . $returnFormat); + break; + } + + } + catch (Exception $ex) { + // we've probably exceeded the limit + break; + } + } + return $$returnFormat; + } /** * Read records using a query. Specify the object you want to query and something like a "where" clause" * @param String $object the object upon which to run the query @@ -813,6 +988,9 @@ public static function deleteByQuery($object, $query, $key_field, api_session $s $num_per_func= 100; // read all the record ids for the given object $ids = api_post::readByQuery($object, "$key_field > 0 and $query", $key_field, $session, $max); + if (!is_array($ids)) { + $ids = array(); + } dbg("COUNT of things to delete: " . count($ids)); if ((!is_array($ids) && trim($ids) == '') || !count($ids) > 0) { @@ -821,11 +999,14 @@ public static function deleteByQuery($object, $query, $key_field, api_session $s $count = 0; $delIds = array(); + $_count = count($ids); foreach($ids as $rec) { $delIds[] = $rec[$key_field]; if (count($delIds) == $num_per_func) { try { + $_count -= $num_per_func; + dbg($_count); api_post::delete($object, implode(",", $delIds), $session); } catch (Exception $ex) { From 50852adcb990b1ea9b5a76165fb8a2b5ff09c032 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 16 Apr 2020 15:39:24 -0500 Subject: [PATCH 45/64] udpates for us --- api_post.php | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/api_post.php b/api_post.php index 8ad7670..22d485c 100755 --- a/api_post.php +++ b/api_post.php @@ -277,11 +277,11 @@ public static function sendFunctions($phpObj, api_session $session, $dtdVersion= * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function get_list($object, $filter, $sorts, $fields, api_session $session, $dtdVersion="2.1") { + public static function get_list($object, $filter, $sorts, $fields, api_session $session, $dtdVersion="2.1", $max_desired = null) { $get_list = array(); $get_list['@object'] = $object; $get_list['@start'] = 0; - $get_list['@maxitems'] = 100; + $get_list['@maxitems'] = min(1000,$max_desired); if ($filter != null) { $get_list['filter'] = $filter; @@ -307,6 +307,8 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ $toReturn = null; if (array_key_exists($object,$ret)) { $toReturn = $ret[$object]; + } else { + return array(); } if (is_array($toReturn)) { $keys = array_keys($toReturn); @@ -320,10 +322,12 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ $total = $xml->operation->result->listtype; $attrs = $total->attributes(); $total = $attrs['total']; + $c = count($toReturn); - if (count($toReturn) < $total) { + if ($c < $total && ($max_desired == null || $c < $max_desired )) { do { + dbg("FETCH MORE " . count($toReturn) . " of $total ($max_desired)"); // we need to fetch more $get_list['@start'] = count($toReturn); $func['function'] = array(); @@ -337,6 +341,10 @@ public static function get_list($object, $filter, $sorts, $fields, api_session $ $res = api_post::post($xml, $session,$dtdVersion, true); $ret = api_post::processListResults($res, api_returnFormat::PHPOBJ, $count); + if (!is_array($ret) || empty($ret)) { + break; + } + $nextBatch = null; if (array_key_exists($object,$ret)) { $nextBatch = $ret[$object]; @@ -480,7 +488,6 @@ public static function readByQuery($object, $query, $fields, api_session $sessio //dbg($readXml); $response = api_post::post($readXml,$session); - //dbg($response); if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { // csv with no records will have no response, so avoid the error from validate and just return return ''; @@ -497,7 +504,6 @@ public static function readByQuery($object, $query, $fields, api_session $sessio // we have no idea if there are more if CSV is returned, so just check // if the last count returned was $pageSize while($thiscount == $pageSize && $totalcount < $maxRecords) { - dbg("READMORE: " . ++$count); $readXml = "$object"; try { $response = api_post::post($readXml, $session); @@ -526,6 +532,7 @@ public static function readByQuery($object, $query, $fields, api_session $sessio throw new Exception("Invalid return format: " . $returnFormat); break; } + dbg("READMORE GOT: $thiscount"); } catch (Exception $ex) { @@ -535,7 +542,6 @@ public static function readByQuery($object, $query, $fields, api_session $sessio } return $$returnFormat; } - /** * Read records using a query. Specify the object you want to query and something like a "where" clause" * @param String $object the object upon which to run the query @@ -813,12 +819,16 @@ public static function deleteByQuery($object, $query, $key_field, api_session $s $num_per_func= 100; // read all the record ids for the given object $ids = api_post::readByQuery($object, "$key_field > 0 and $query", $key_field, $session, $max); - dbg("COUNT of things to delete: " . count($ids)); + if (!is_array($ids)) { + $ids = array(); + } if ((!is_array($ids) && trim($ids) == '') || !count($ids) > 0) { return 0; } + dbg("COUNT of things to delete: " . count($ids)); + $count = 0; $delIds = array(); @@ -1187,6 +1197,10 @@ public static function processListResults($response, $returnFormat = api_returnF } $json = json_encode($xml->operation->result->data,JSON_FORCE_OBJECT); + if ($json == "{}") { + return array(); + } + $array = json_decode($json,TRUE); $obj = key($array); From 1fddad14379e92bb08e3a9c9d0f526d19e73f368 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 16 Apr 2020 15:39:46 -0500 Subject: [PATCH 46/64] fixes for odd scenarios --- api_session.php | 2 +- api_util.php | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/api_session.php b/api_session.php index abac086..aa2cdcf 100755 --- a/api_session.php +++ b/api_session.php @@ -77,7 +77,7 @@ private function buildHeaderXML($companyId, $userId, $password, $senderId, $send $xml = str_replace("{5%}", htmlspecialchars($senderPassword), $xml); // hack for backward compat - if ($entityType == 'location') { + if ($entityType == 'location' || $entityType === null) { $xml = str_replace("{%entityid%}", "$entityId", $xml); } else if ($entityType == 'client') { $xml = str_replace("{%entityid%}", "$entityId", $xml); diff --git a/api_util.php b/api_util.php index e3d4e94..deaa580 100755 --- a/api_util.php +++ b/api_util.php @@ -111,7 +111,7 @@ public static function phpToXml($key, $values) { foreach($values as $node => $value) { $attrString = ""; $_xml = ""; - if (is_array($value)) { + if (is_array($value) && count($value) > 0) { if (is_numeric($node)) { $node = $key; } @@ -130,27 +130,17 @@ public static function phpToXml($key, $values) { } } - //$firstKey = array_shift(array_keys($value)); - //if ((isset($value[$firstKey]) && is_array($value[$firstKey]) || count($value) > 1 )) { - // $_xml = self::phpToXml($node,$value) ; - //} - //else { - // $v = ""; - // if (isset($value[$firstKey])) { - // $v = $value[$firstKey]; - // } - // $_xml .= "<$node>" . htmlspecialchars($v) . ""; - //} - // $valuekeys = array_keys($value); $firstKey = array_shift($valuekeys); - if (is_array($value[$firstKey]) || count($value) > 0 ) { + if (is_array($value) && (( isset($value[$firstKey]) && is_array($value[$firstKey])) || count($value) > 0)) { $_xml = self::phpToXml($node,$value) ; } else { $_xml = self::phpToXml($node,$value) ; - $v = $value[$firstKey]; - $_xml .= "<$node>" . htmlspecialchars($v) . ""; + $v = $value[$firstKey] ?? ''; + if (!empty($v)) { + $_xml .= "<$node>" . htmlspecialchars($v) . ""; + } } if ($attrString != "") { @@ -165,7 +155,8 @@ public static function phpToXml($key, $values) { $xml .= "<" . $key. $attrString . ">" . htmlspecialchars($value) . ""; } else { - $xml .= "<" . $node . $attrString . ">" . htmlspecialchars($value) . ""; + $_v = ($value != "0" && empty($value)) ? '' : htmlspecialchars($value); + $xml .= "<" . $node . $attrString . ">" .$_v . ""; } } } @@ -184,8 +175,11 @@ public static function phpToXml($key, $values) { public static function csvToPhp($csv) { $fp = fopen('php://temp', 'r+'); + $csv = str_replace("\,",",",$csv); + $csv = str_replace('\",','",',$csv); fwrite($fp, trim($csv)); + rewind($fp); $table = array(); From d90c2cbef5b82499f67f6d15ce1b67b2519718b1 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Sun, 26 Apr 2020 10:32:54 -0500 Subject: [PATCH 47/64] fix to query and new method to just send xml over --- api_post.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index 56b976a..617a7f6 100755 --- a/api_post.php +++ b/api_post.php @@ -242,6 +242,10 @@ public static function otherMethod($xml, api_session $session, $dtdVersion="3.0" return api_post::post($xml, $session,$dtdVersion); } + public static function send_xml($xml, api_session $session) { + return api_post::post("$xml", $session,"3.0",true); + } + /** * Run any Intacct API method not directly implemented in this class. You must pass * valid XML for the method you wish to invoke. @@ -305,6 +309,7 @@ public static function query(api_session $session, $call, $returnFormat = api_re ) ); $rows = array(); + dbg("**RBQ**: (QUERY) " . $obj ); do { $xml = api_util::phpToXml('content',array($phpObj)); $res = api_post::post($xml, $session,'3.0', true); @@ -314,7 +319,11 @@ public static function query(api_session $session, $call, $returnFormat = api_re $array = json_decode($json,TRUE); if (!isset($array[$obj])) { $array[$obj] = array(); - } + } else if (!is_numeric(key($array[$obj]))) { + $array[$obj] = array( + $array[$obj] + ); + } $row = array_map(array('api_post','prune_empty_element'),$array[$obj]); $rows = array_merge($rows,$row); @@ -528,6 +537,7 @@ public static function readView($viewName, api_session $session, api_viewFilters * @return mixed either string or array of objects depending on returnFormat argument */ public static function readByQuery($object, $query, $fields, api_session $session, $maxRecords=self::DEFAULT_MAXRETURN, $returnFormat=api_returnFormat::PHPOBJ) { + dbg("RBQ: $object -> $query with $fields"); $pageSize = ($maxRecords <= self::DEFAULT_PAGESIZE) ? $maxRecords : self::DEFAULT_PAGESIZE; From 5ccd142e9397b8eeefc23ef4af18ed162c3b59b5 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 30 Apr 2020 17:35:29 -0500 Subject: [PATCH 48/64] fixing weird error case --- api_util.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api_util.php b/api_util.php index deaa580..c133ab5 100755 --- a/api_util.php +++ b/api_util.php @@ -213,6 +213,9 @@ public static function xmlErrorToString($error,$multi=false) { // show just the first error //$error = $error->error[0]; $error_string = ""; + if (!isset($error->error)) { + return "Malformed error: " . var_export($error, true); + } foreach ($error->error as $error) { if (!is_object($error)) { return "Malformed error: " . var_export($error, true); From 7842fcefa47cb32d1b3af4236a0bf0a145703a19 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 15 May 2020 15:09:40 -0500 Subject: [PATCH 49/64] added limit to query --- api_post.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/api_post.php b/api_post.php index a61a360..ef9da63 100755 --- a/api_post.php +++ b/api_post.php @@ -298,10 +298,14 @@ public static function prune_empty_element($a) { * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function query(api_session $session, $call, $returnFormat = api_returnFormat::PHPOBJ) { + public static function query(api_session $session, $call, int $limit = null, $returnFormat = api_returnFormat::PHPOBJ) { $obj = $call['object']; $call['offset'] = $call['offset'] ?? '0'; $call['pagesize'] = $call['pagesize'] ?? '100'; + if ($limit !== null) { + $call['pagesize'] = min($limit,$call['pagesize']); + } + $phpObj = array ( 'function' => array ( '@controlid' => uniqid(), @@ -310,11 +314,11 @@ public static function query(api_session $session, $call, $returnFormat = api_re ); $rows = array(); dbg("**RBQ**: (QUERY) " . $obj ); + do { $xml = api_util::phpToXml('content',array($phpObj)); $res = api_post::post($xml, $session,'3.0', true); $res_xml = simplexml_load_string($res); -// $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); $array = json_decode($json,TRUE); if (!isset($array[$obj])) { @@ -330,11 +334,10 @@ public static function query(api_session $session, $call, $returnFormat = api_re $pagesize = $call['pagesize'] ?? 100; $num_remaining = $array['@attributes']['numremaining']; $phpObj['function']['query']['offset'] += $phpObj['function']['query']['pagesize'] ; - //dbg("READ: " . count($row)); - //dbg("NR: " . $num_remaining); - //dbg("NEW OFFSET: " . $phpObj['function']['query']['offset']); - //dbg("ROW SIZE NOW: " . count($rows)); - //sleep(2); + + if ($limit !== null && $phpObj['function']['query']['offset'] >= $limit) { + $num_remaining = 0; + } } while ($num_remaining > 0); From afad98e1f0c55eba2ae62bfbf72795c92264c2bb Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 1 Jul 2020 18:32:44 -0500 Subject: [PATCH 50/64] fixed bug in sendFunctions where it didn't return array in php --- api_post.php | 1 + 1 file changed, 1 insertion(+) diff --git a/api_post.php b/api_post.php index a61a360..55a4196 100755 --- a/api_post.php +++ b/api_post.php @@ -276,6 +276,7 @@ public static function sendFunctions($phpObj, api_session $session, $dtdVersion= dbg($json); $array = json_decode($json,TRUE); dbg($array); + return $array; } else { return $res; } From 72517189298c183327ea18c910fe7021fb4b1a24 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Thu, 3 Sep 2020 21:12:32 -0500 Subject: [PATCH 51/64] debug --- api_post.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/api_post.php b/api_post.php index 56742f6..06405cc 100755 --- a/api_post.php +++ b/api_post.php @@ -273,9 +273,7 @@ public static function sendFunctions($phpObj, api_session $session, $dtdVersion= if ($returnFormat == api_returnFormat::PHPOBJ) { $res_xml = simplexml_load_string($res); $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); - dbg($json); $array = json_decode($json,TRUE); - dbg($array); return $array; } else { return $res; From b909c37128bbec4be318a208bd748e803b2b4a08 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 17 May 2021 14:30:36 +0000 Subject: [PATCH 52/64] adding preview url to api_session --- api_session.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api_session.php b/api_session.php index 8b096a1..eafb771 100755 --- a/api_session.php +++ b/api_session.php @@ -47,6 +47,7 @@ class api_session { const XML_SESSIONID = "{1%}"; const DEFAULT_LOGIN_URL = "https://api.intacct.com/ia/xml/xmlgw.phtml"; + const PRV_LOGIN_URL = "https://preview.intacct.com/ia/xml/xmlgw.phtml"; public function __toString() { $temp = array ( @@ -112,7 +113,8 @@ public function connectCredentials($companyId, $userId, $password, $senderId, $s $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword, $entityType, $entityId); - $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); + $endpoint = strpos($companyId,"-prv") === FALSE ? self::DEFAULT_LOGIN_URL : self::PRV_LOGIN_URL; + $response = api_post::execute($xml, $endpoint); self::validateConnection($response); From 427db547005acdbeccac57b4343961c448c32b13 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Tue, 18 May 2021 17:24:39 +0000 Subject: [PATCH 53/64] fixing for preview companies --- api_session.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api_session.php b/api_session.php index eafb771..d438692 100755 --- a/api_session.php +++ b/api_session.php @@ -145,7 +145,8 @@ public function connectCredentialsEntity($companyId, $userId, $password, $sender $xml = $this->buildHeaderXML($companyId, $userId, $password, $senderId, $senderPassword,$entityType, $entityId); - $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); + $endpoint = strpos($companyId,"-prv") === FALSE ? self::DEFAULT_LOGIN_URL : self::PRV_LOGIN_URL; + $response = api_post::execute($xml, $endpoint); self::validateConnection($response); @@ -177,7 +178,8 @@ public function connectSessionId($sessionId, $senderId, $senderPassword, $entity $xml = str_replace("{5%}", $senderPassword, $xml); $xml = str_replace("{6%}", $entityId, $xml); - $response = api_post::execute($xml, self::DEFAULT_LOGIN_URL); + $endpoint = ($this->companyId === null || strpos($this->companyId,"-prv") === FALSE) ? self::DEFAULT_LOGIN_URL : self::PRV_LOGIN_URL; + $response = api_post::execute($xml, $endpoint); self::validateConnection($response); From ae1178a958420b6a7acfab26caa88f10137ec0f6 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 28 Jan 2022 13:33:51 -0600 Subject: [PATCH 54/64] adding code to capture response header --- api_post.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/api_post.php b/api_post.php index 06405cc..008657e 100755 --- a/api_post.php +++ b/api_post.php @@ -1177,27 +1177,40 @@ public static function execute($body, $endPoint) { $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); - curl_setopt( $ch, CURLOPT_HEADER, 0 ); + curl_setopt( $ch, CURLOPT_HEADER, true ); + curl_setopt( $ch, CURLINFO_HEADER_OUT, true ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_TIMEOUT, 6000 ); //Seconds until timeout curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_VERBOSE, false ); + curl_setopt( $ch, CURLOPT_VERBOSE, 0); // TODO: Research and correct the problem with CURLOPT_SSL_VERIFYPEER curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false); // yahoo doesn't like the api.intacct.com CA $body = "xmlrequest=" . urlencode( $body ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, $body ); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $body ); $response = curl_exec( $ch ); $error = curl_error($ch); + + //$info = curl_getinfo( $ch, CURLINFO_HEADER_OUT ); + //$info = curl_getinfo( $ch ); + + //dbg($response); + // + // Then, after your curl_exec call: + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $response_header = substr($response, 0, $header_size); + $response_body = substr($response, $header_size); + if ($error != "") { throw new exception($error); } curl_close( $ch ); - self::$lastResponse = $response; - return $response; + self::$lastResponse = $response_header . "--" . $response_body; +dbg($response_header); + return $response_body; } From e2c2de64eeabcbf0921d6e170bd634c7b620bd6e Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 28 Jan 2022 13:35:06 -0600 Subject: [PATCH 55/64] saving headers for debug purposes --- api_post.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/api_post.php b/api_post.php index 06405cc..008657e 100755 --- a/api_post.php +++ b/api_post.php @@ -1177,27 +1177,40 @@ public static function execute($body, $endPoint) { $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); - curl_setopt( $ch, CURLOPT_HEADER, 0 ); + curl_setopt( $ch, CURLOPT_HEADER, true ); + curl_setopt( $ch, CURLINFO_HEADER_OUT, true ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_TIMEOUT, 6000 ); //Seconds until timeout curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_VERBOSE, false ); + curl_setopt( $ch, CURLOPT_VERBOSE, 0); // TODO: Research and correct the problem with CURLOPT_SSL_VERIFYPEER curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false); // yahoo doesn't like the api.intacct.com CA $body = "xmlrequest=" . urlencode( $body ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, $body ); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $body ); $response = curl_exec( $ch ); $error = curl_error($ch); + + //$info = curl_getinfo( $ch, CURLINFO_HEADER_OUT ); + //$info = curl_getinfo( $ch ); + + //dbg($response); + // + // Then, after your curl_exec call: + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $response_header = substr($response, 0, $header_size); + $response_body = substr($response, $header_size); + if ($error != "") { throw new exception($error); } curl_close( $ch ); - self::$lastResponse = $response; - return $response; + self::$lastResponse = $response_header . "--" . $response_body; +dbg($response_header); + return $response_body; } From 04e3e68a0e458813711a5ac899846f1a216dfb83 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 31 Jan 2022 09:33:26 -0600 Subject: [PATCH 56/64] adding header debug info --- api_post.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index 008657e..a29afd3 100755 --- a/api_post.php +++ b/api_post.php @@ -9,6 +9,7 @@ class api_post { private static $lastRequest; private static $lastResponse; + private static $lastResponseHeader; private static $dryRun; const DEFAULT_PAGESIZE = 1000; @@ -1208,8 +1209,8 @@ public static function execute($body, $endPoint) { } curl_close( $ch ); - self::$lastResponse = $response_header . "--" . $response_body; -dbg($response_header); + self::$lastResponse = $response_body; + self::$lastResponseHeader = $response_header; return $response_body; } @@ -1506,6 +1507,10 @@ public static function getLastResponse() { return self::$lastResponse; } + public static function getLastResponseHeader() { + return self::$lastResponseHeader; + } + public static function setDryRun($tf=true) { self::$dryRun = $tf; From 9ec370c0da68acaf78837137483aaae75e2c86bb Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 22 Jun 2022 12:48:38 -0500 Subject: [PATCH 57/64] adding policy ID to create method --- api_post.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/api_post.php b/api_post.php index 3df3fa4..599803c 100755 --- a/api_post.php +++ b/api_post.php @@ -86,7 +86,7 @@ public static function readDocument($object, $docparid, $id, $fields, api_sessio * @throws Exception * @return Array array of keys to the objects created */ - public static function create($records, api_session $session) { + public static function create($records, api_session $session,$policy=null) { if (count($records) > 100) throw new Exception("Attempting to create more than 100 records. (" . count($records) . ") "); @@ -100,9 +100,11 @@ public static function create($records, api_session $session) { $createXml = $createXml . $objXml; } $createXml = $createXml . ""; - $res = api_post::post($createXml, $session); + $res = api_post::post($createXml, $session, "3.0", false, $policy); + if ($policy !== null) { + return $res; + } $records = api_post::processUpdateResults($res, $node); - return $records; } @@ -496,7 +498,7 @@ public static function readByQuery($object, $query, $fields, api_session $sessio // we have no idea if there are more if CSV is returned, so just check // if the last count returned was $pageSize - while($thiscount == $pageSize && $totalcount <= $maxRecords) { + while($thiscount == $pageSize && $totalcount < $maxRecords) { $readXml = "$object"; try { $response = api_post::post($readXml, $session); @@ -577,7 +579,7 @@ public static function readDocumentByQuery($object, $docparid, $query, $fields, // we have no idea if there are more if CSV is returned, so just check // if the last count returned was $pageSize - while($thiscount == $pageSize && $totalcount <= $maxRecords) { + while($thiscount == $pageSize && $totalcount < $maxRecords) { $readXml = "$object"; try { $response = api_post::post($readXml, $session); @@ -846,7 +848,7 @@ public static function deleteByQuery($object, $query, $key_field, api_session $s * @throws Exception * @return String the XML response document */ - private static function post($xml, api_session $session, $dtdVersion="3.0",$multiFunc=false) { + private static function post($xml, api_session $session, $dtdVersion="3.0",$multiFunc=false, $policy=null) { $sessionId = $session->sessionId; $endPoint = $session->endPoint; @@ -894,10 +896,13 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult {$senderId} {$senderPassword} - foobar + ".uniqid()." false - {$dtdVersion} - + {$dtdVersion}"; + if ($policy !== null) { + $templateHead .= "$policy"; + } + $templateHead .= " {$sessionId} From 55cd95091e639699d021ef6bd69d3ef3ddef9697 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Sun, 26 Jun 2022 20:14:52 -0500 Subject: [PATCH 58/64] snapshot --- api_post.php | 18 +++++++++++------- api_session.php | 11 +++++++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/api_post.php b/api_post.php index a29afd3..bf4fef1 100755 --- a/api_post.php +++ b/api_post.php @@ -331,13 +331,14 @@ public static function query(api_session $session, $call, int $limit = null, $re $row = array_map(array('api_post','prune_empty_element'),$array[$obj]); $rows = array_merge($rows,$row); - $pagesize = $call['pagesize'] ?? 100; +// $pagesize = $call['pagesize'] ?? 100; $num_remaining = $array['@attributes']['numremaining']; $phpObj['function']['query']['offset'] += $phpObj['function']['query']['pagesize'] ; if ($limit !== null && $phpObj['function']['query']['offset'] >= $limit) { $num_remaining = 0; } + dbg(" **NUM REMAINING**: " . $num_remaining); } while ($num_remaining > 0); @@ -612,7 +613,7 @@ public static function readByQuery($object, $query, $fields, api_session $sessio throw new Exception("Invalid return format: " . $returnFormat); break; } - dbg("READMORE GOT: $thiscount"); + dbg("READMORE GOT: $thiscount, Total now: $totalcount"); } catch (Exception $ex) { @@ -1148,13 +1149,16 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult try { api_post::validateResponse($res, $xml); break; - } - catch (Exception $ex) { + } catch (Exception $ex) { + if ($count >= 5) { + throw new Exception($ex->getMessage(),$ex->getCode(),$ex); + } if (strpos($ex->getMessage(), "too many operations") !== false) { $count++; - if ($count >= 5) { - throw new Exception($ex->getMessage(),$ex->getCode(),$ex); - } + } else if (strpos($ex->getMessage(), "UJPP0007") !== false) { + $count++; + dbg("Got UJPP007. Sleeping one minute and trying again."); + sleep(60); } else { throw new Exception($ex->getMessage(),$ex->getCode(),$ex); } diff --git a/api_session.php b/api_session.php index d438692..91ecf74 100755 --- a/api_session.php +++ b/api_session.php @@ -170,9 +170,16 @@ public function connectCredentialsEntity($companyId, $userId, $password, $sender * @param String $senderPassword Your Intacct partner password * @throws Exception This method returns no values, but will raise an exception if there's a connection error */ - public function connectSessionId($sessionId, $senderId, $senderPassword, $entityId = '') { + public function connectSessionId($sessionId, $senderId, $senderPassword, $entityId = null) { - $xml = self::XML_HEADER . self::XML_SESSIONID . self::XML_FOOTER_2; + if ($entityId === null) { + // we are passing NO entity/location. do not add locationid to the XML + $xml = self::XML_HEADER . self::XML_SESSIONID . self::XML_FOOTER; + } else { + // we are passing entity/location ('' for top-level flip from entity). add locationid to the XML + $xml = self::XML_HEADER . self::XML_SESSIONID . self::XML_FOOTER_2; + } + $xml = str_replace("{1%}", $sessionId, $xml); $xml = str_replace("{4%}", $senderId, $xml); $xml = str_replace("{5%}", $senderPassword, $xml); From c2ce3b3618e42f12457a7bcb72ae3f43b3bfa855 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 4 Jul 2022 20:45:02 +0000 Subject: [PATCH 59/64] adding debug for UJPP errors in Intacct due to major API issues in 2022 --- api_post.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api_post.php b/api_post.php index b9affe8..0864933 100755 --- a/api_post.php +++ b/api_post.php @@ -1154,14 +1154,20 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult api_post::validateResponse($res, $xml); break; } catch (Exception $ex) { + dbg("JPC EXCEPTION"); if ($count >= 5) { throw new Exception($ex->getMessage(),$ex->getCode(),$ex); } + dbg("EX getMessage"); + dbg($ex->getMessage()); if (strpos($ex->getMessage(), "too many operations") !== false) { $count++; } else if (strpos($ex->getMessage(), "UJPP0007") !== false) { $count++; dbg("Got UJPP007. Sleeping one minute and trying again."); + dbg("RESPONSE HEADER:"); + dbg(self::$lastResponseHeader); + dbg("END RESPONSE HEADER:"); sleep(60); } else { throw new Exception($ex->getMessage(),$ex->getCode(),$ex); @@ -1212,13 +1218,18 @@ public static function execute($body, $endPoint) { $response_header = substr($response, 0, $header_size); $response_body = substr($response, $header_size); + self::$lastResponse = $response_body; + self::$lastResponseHeader = $response_header; + if (strpos($response,"UJPP00") !== FALSE) { + dbg("FULL RESPONSE with header"); + dbg($response); + } + if ($error != "") { throw new exception($error); } curl_close( $ch ); - self::$lastResponse = $response_body; - self::$lastResponseHeader = $response_header; return $response_body; } From 465fa875caf54e6ba3e1f9b1869a4163c1489957 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 28 Sep 2022 14:12:04 -0500 Subject: [PATCH 60/64] fixed issues --- api_post.php | 25 ++++++++++++++++++------- api_session.php | 4 +++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/api_post.php b/api_post.php index 06405cc..060d699 100755 --- a/api_post.php +++ b/api_post.php @@ -317,6 +317,7 @@ public static function query(api_session $session, $call, int $limit = null, $re do { $xml = api_util::phpToXml('content',array($phpObj)); $res = api_post::post($xml, $session,'3.0', true); + //dbg($res); $res_xml = simplexml_load_string($res); $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); $array = json_decode($json,TRUE); @@ -329,9 +330,12 @@ public static function query(api_session $session, $call, int $limit = null, $re } $row = array_map(array('api_post','prune_empty_element'),$array[$obj]); + //dbg("READ this many rows" . count($row)); $rows = array_merge($rows,$row); + //dbg("Total read is " . count($rows)); $pagesize = $call['pagesize'] ?? 100; $num_remaining = $array['@attributes']['numremaining']; + dbg("REMAINING: $num_remaining. OFFSET is now : " . $phpObj['function']['query']['pagesize']); $phpObj['function']['query']['offset'] += $phpObj['function']['query']['pagesize'] ; if ($limit !== null && $phpObj['function']['query']['offset'] >= $limit) { @@ -571,6 +575,10 @@ public static function readByQuery($object, $query, $fields, api_session $sessio // csv with no records will have no response, so avoid the error from validate and just return return ''; } + if ($object == 'PROJECT') { + dbg(api_post::getLastRequest()); + dbg($response); + } api_post::validateReadResults($response); @@ -586,8 +594,11 @@ public static function readByQuery($object, $query, $fields, api_session $sessio $readXml = "$object"; try { $response = api_post::post($readXml, $session); - //dbg($response); - //dbg(api_post::getLastRequest()); + if ($object == 'PROJECT') { + dbg("READMORE PROJECT"); + dbg(api_post::getLastRequest()); + dbg($response); + } api_post::validateReadResults($response); $page = self::processReadResults($response, $returnFormat, $pageCount); $totalcount += $pageCount; @@ -602,7 +613,7 @@ public static function readByQuery($object, $query, $fields, api_session $sessio case api_returnFormat::CSV: $page = explode("\n", $page); array_shift($page); - $csv .= implode($page, "\n"); + $csv .= implode("\n",$page); break; case api_returnFormat::XML: $xml .= $page; @@ -657,10 +668,10 @@ public static function query_bad(api_session $session, $object, $fields=null, $q $readXml = "$object$query_xml"; $readXml .= "$pageSize"; $readXml .= ""; - dbg($readXml); + //dbg($readXml); $response = api_post::post($readXml,$session); - dbg($response); + //dbg($response); die(); if ($returnFormatArg == api_returnFormat::CSV && trim($response) == "") { // csv with no records will have no response, so avoid the error from validate and just return @@ -1370,7 +1381,7 @@ private static function validateReadResults($response) { * @throws Exception * @return Mixed string or object depending on return format */ - public static function processListResults($response, $returnFormat = api_returnFormat::PHPOBJ, &$count) { + public static function processListResults($response, $returnFormat, &$count) { //dbg($response); $xml = simplexml_load_string($response); @@ -1441,7 +1452,7 @@ public static function processListResults($response, $returnFormat = api_returnF * @throws Exception * @return Mixed string or object depending on return format */ - private static function processReadResults($response, $returnFormat = api_returnFormat::PHPOBJ, &$count) { + private static function processReadResults($response, $returnFormat, &$count) { $objAry = array(); $csv = ''; $json = ''; $xml = ''; if ($returnFormat == api_returnFormat::PHPOBJ) { $objAry = api_util::csvToPhp($response); diff --git a/api_session.php b/api_session.php index 8b096a1..ba0d50b 100755 --- a/api_session.php +++ b/api_session.php @@ -207,8 +207,10 @@ private static function validateConnection($response) { if (isset($simpleXml->operation->authentication->status)) { if ($simpleXml->operation->authentication->status != 'success') { + print_r($simpleXml); $error = $simpleXml->operation->errormessage; - throw new Exception(" [Error] " . (string)$error->error[0]->description2); + $desc2 = (string)$error->error[0]->description2 ?? ''; + throw new Exception(" [Error] " . $desc2); } } From bb258a3cab0e578b3bc815f399cabee3664057b1 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 28 Sep 2022 14:27:30 -0500 Subject: [PATCH 61/64] fixed implode --- api_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_post.php b/api_post.php index 265a2cc..036afe8 100755 --- a/api_post.php +++ b/api_post.php @@ -525,7 +525,7 @@ public static function readView($viewName, api_session $session, api_viewFilters // append all but the first row to the CSV file $page = explode("\n", $page); array_shift($page); - $csv .= implode($page, "\n"); + $csv .= implode("\n",$page); } elseif ($returnFormat == api_returnFormat::XML) { // just add the xml string From 4338f734e674baf1c7e1e9ec95119075a23f5c0e Mon Sep 17 00:00:00 2001 From: John Campbell Date: Wed, 7 Dec 2022 15:55:03 -0600 Subject: [PATCH 62/64] made process list results cleaner with the result structure --- api_post.php | 15 +++++++++++++-- api_session.php | 4 +++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/api_post.php b/api_post.php index 036afe8..726b767 100755 --- a/api_post.php +++ b/api_post.php @@ -249,6 +249,11 @@ public static function send_xml($xml, api_session $session) { return api_post::post("$xml", $session,"3.0",true); } + public static function get_xml($obj) { + $xml = api_util::phpToXml('content',array($obj)); + return $xml; + } + /** * Run any Intacct API method not directly implemented in this class. You must pass * valid XML for the method you wish to invoke. @@ -270,9 +275,9 @@ public static function call21Method($function, $phpObj, api_session $session) { * @param string $dtdVersion DTD Version. Either "2.1" or "3.0". Defaults to "2.1" * @return String the XML response from Intacct */ - public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0", $returnFormat = api_returnFormat::XML) { + public static function sendFunctions($phpObj, api_session $session, $dtdVersion="3.0", $returnFormat = api_returnFormat::XML,$policy = null) { $xml = api_util::phpToXml('content',array($phpObj)); - $res = api_post::post($xml, $session,$dtdVersion, true); + $res = api_post::post($xml, $session,$dtdVersion, true, $policy); if ($returnFormat == api_returnFormat::PHPOBJ) { $res_xml = simplexml_load_string($res); $json = json_encode($res_xml->operation->result->data,JSON_FORCE_OBJECT); @@ -336,6 +341,9 @@ public static function query(api_session $session, $call, int $limit = null, $re //dbg("READ this many rows" . count($row)); $rows = array_merge($rows,$row); $num_remaining = $array['@attributes']['numremaining']; + if ($limit !== null) { + $num_remaining = $limit - count($rows); + } dbg("REMAINING: $num_remaining. OFFSET is now : " . $phpObj['function']['query']['pagesize']); $phpObj['function']['query']['offset'] += $phpObj['function']['query']['pagesize'] ; @@ -1453,6 +1461,9 @@ public static function processListResults($response, $returnFormat, &$count) { } } } + if (isset($array['dimensions'])) { + $array['dimensions'] = $array['dimensions'][0]['dimension']; + } if (isset($array['arpayment'])) { foreach ($array['arpayment'] as $key => $txn) { if (isset($txn['lineitems']['lineitem'])) { diff --git a/api_session.php b/api_session.php index 7829be0..b5a03f6 100755 --- a/api_session.php +++ b/api_session.php @@ -183,7 +183,9 @@ public function connectSessionId($sessionId, $senderId, $senderPassword, $entity $xml = str_replace("{1%}", $sessionId, $xml); $xml = str_replace("{4%}", $senderId, $xml); $xml = str_replace("{5%}", $senderPassword, $xml); - $xml = str_replace("{6%}", $entityId, $xml); + if ($entityId !== null) { + $xml = str_replace("{6%}", $entityId, $xml); + } $endpoint = ($this->companyId === null || strpos($this->companyId,"-prv") === FALSE) ? self::DEFAULT_LOGIN_URL : self::PRV_LOGIN_URL; $response = api_post::execute($xml, $endpoint); From 1a37937ef5e7ca8a936382e3a619fc5504493347 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Mon, 10 Apr 2023 20:50:08 -0500 Subject: [PATCH 63/64] snapshot update --- api_post.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api_post.php b/api_post.php index 726b767..a60ee3b 100755 --- a/api_post.php +++ b/api_post.php @@ -228,9 +228,9 @@ public static function upsert($object, $records, $nameField, $keyField, api_sess * objects and 'recordno' values for standard objects * @param api_session $session instance of api_session object */ - public static function delete($object, $ids, api_session $session) { + public static function delete($object, $ids, api_session $session,$policy=null) { $deleteXml = "$object$ids"; - api_post::post($deleteXml, $session); + api_post::post($deleteXml, $session,"3.0",false,$policy); } /** @@ -341,11 +341,13 @@ public static function query(api_session $session, $call, int $limit = null, $re //dbg("READ this many rows" . count($row)); $rows = array_merge($rows,$row); $num_remaining = $array['@attributes']['numremaining']; - if ($limit !== null) { - $num_remaining = $limit - count($rows); + $total = count($rows); + if ($num_remaining > 0 && $limit !== null) { + $num_remaining = min($limit - $total,$num_remaining); } - dbg("REMAINING: $num_remaining. OFFSET is now : " . $phpObj['function']['query']['pagesize']); + $phpObj['function']['query']['offset'] += $phpObj['function']['query']['pagesize'] ; + dbg("REMAINING: $num_remaining. OFFSET is now : " . $phpObj['function']['query']['offset']); if ($limit !== null && $phpObj['function']['query']['offset'] >= $limit) { $num_remaining = 0; From 08c9eb0fd98c97d8e392a2cc1de42c61374266c9 Mon Sep 17 00:00:00 2001 From: John Campbell Date: Fri, 9 Feb 2024 12:36:48 -0600 Subject: [PATCH 64/64] adding support for Sage AppID --- api_post.php | 65 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/api_post.php b/api_post.php index 06405cc..7b8fd5f 100755 --- a/api_post.php +++ b/api_post.php @@ -9,7 +9,9 @@ class api_post { private static $lastRequest; private static $lastResponse; + private static $lastResponseHeader; private static $dryRun; + private static $sage_appid; const DEFAULT_PAGESIZE = 1000; const DEFAULT_MAXRETURN = 200000; @@ -1096,10 +1098,17 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult */ - $templateHead = -" + + // if self::$sage_appid is not empty we want to pass above senderid. + + $templateHead = " - + "; +if (!empty(self::$sage_appid)) { + $templateHead .= " + " . self::$sage_appid . ""; +} +$templateHead .= " {$senderId} {$senderPassword} foobar @@ -1142,6 +1151,17 @@ private static function post($xml, api_session $session, $dtdVersion="3.0",$mult $res = ""; while (true) { $res = api_post::execute($xml, $endPoint); + if ( strpos($res, "520 Origin") !== FALSE || strpos($res, "502 Bad Gateway") !== FALSE) { + $count++; + if ($count <= 5) { + dbg("RETRYING due to 520 or 502 error"); + dbg("===REQUEST=============================="); + dbg(api_post::getLastRequest()); + dbg("===RESPONSE=============================="); + dbg($res); + continue; + } + } // If we didn't get a response, we had a poorly constructed XML request. try { @@ -1177,7 +1197,8 @@ public static function execute($body, $endPoint) { $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); - curl_setopt( $ch, CURLOPT_HEADER, 0 ); + curl_setopt( $ch, CURLOPT_HEADER, true ); + curl_setopt( $ch, CURLINFO_HEADER_OUT, true ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_TIMEOUT, 6000 ); //Seconds until timeout @@ -1191,13 +1212,36 @@ public static function execute($body, $endPoint) { $response = curl_exec( $ch ); $error = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $response_header = substr($response, 0, $header_size); + $response_body = substr($response, $header_size); + self::$lastResponse = $response_body; + self::$lastResponseHeader = $response_header; + + if ($code != "200" || strpos($response,"UJPP00") !== FALSE || + strpos($error,"transfer closed") !== FALSE || + strpos($response,"cloudflare-nginx") !== FALSE || + strpos($error,"cloudflare-nginx") !== FALSE || + strpos($response,"") !== FALSE) { + + dbg("SAGE ERROR FULL RESPONSE with header"); + dbg($response); + dbg("RESPONSE BODY"); + dbg($response_body); + dbg("SAGE ERROR REQUEST WAS"); + dbg("SAGE ERROR " . self::$lastRequest); + } + if ($error != "") { + dbg("FULL RESPONSE with header"); + dbg($response); throw new exception($error); } curl_close( $ch ); - self::$lastResponse = $response; - return $response; + return $response_body; } @@ -1370,7 +1414,7 @@ private static function validateReadResults($response) { * @throws Exception * @return Mixed string or object depending on return format */ - public static function processListResults($response, $returnFormat = api_returnFormat::PHPOBJ, &$count) { + public static function processListResults($response, $returnFormat, &$count) { //dbg($response); $xml = simplexml_load_string($response); @@ -1441,7 +1485,7 @@ public static function processListResults($response, $returnFormat = api_returnF * @throws Exception * @return Mixed string or object depending on return format */ - private static function processReadResults($response, $returnFormat = api_returnFormat::PHPOBJ, &$count) { + private static function processReadResults($response, $returnFormat, &$count) { $objAry = array(); $csv = ''; $json = ''; $xml = ''; if ($returnFormat == api_returnFormat::PHPOBJ) { $objAry = api_util::csvToPhp($response); @@ -1498,4 +1542,9 @@ public static function setDryRun($tf=true) self::$dryRun = $tf; } + public static function setAppID($id) + { + self::$sage_appid = $id; + } + }