/** * SendRequestXML method for the QuickBooks Web Connector SOAP server - Generate and send a request to QuickBooks * * The QuickBooks Web Connector calls this method to ask for things to do. * So, calling this method is the Web Connectors way of saying: "Please * send me a command so that I can pass that command on to QuickBooks." * After it passes the command to QuickBooks, it will pass the response * back via a call to receiveResponseXML(). * * The stdClass object passed as a parameter should contain these members: * - ticket The login session ticket * - strHCPResponse * - strCompanyFileName * - qbXMLCountry The country code for whatever version of QuickBooks is sitting behind the Web Connector * - qbXMLMajorVers The major version code of the QuickBooks web connector * - qbXMLMinorVers The minor version code of the QuickBooks web connector * * You should return either an empty string "" to signal an error state, or * a valid qbXML or qbposXML request. * * The following user-defined hooks are invoked by this method: * - QUICKBOOKS_HANDLERS_HOOK_SENDREQUESTXML * * @param stdClass $obj * @return QuickBooks_Result_SendRequestXML */ public function sendRequestXML($obj) { $this->_driver->log('sendRequestXML()', $obj->ticket, QUICKBOOKS_LOG_VERBOSE); if ($this->_driver->authCheck($obj->ticket)) { $user = $this->_driver->authResolve($obj->ticket); $hookdata = array('username' => $user, 'ticket' => $obj->ticket); $hookerr = ''; $this->_callHook($obj->ticket, QUICKBOOKS_HANDLERS_HOOK_SENDREQUESTXML, null, null, null, null, $hookerr, null, array(), $hookdata); // _callHook($ticket, $hook, $requestID, $action, $ident, $extra, &$err, $xml = '', $qb_identifiers = array(), $hook_data = array()) // Move recurring events which are due to run to the queue table // We *CAN'T* re-register recurring events here, otherwise, we run // the risk of re-adding an event which has occured, *before* the // entire session has finishing running. Thus, we'd create an // infinite loop of web connector that would never end. //$this->_handleRecurringEvents($obj->ticket); if ($next = $this->_driver->queueDequeue($user, true)) { $this->_driver->log('Dequeued: ( ' . $next['qb_action'] . ', ' . $next['ident'] . ' ) ', $obj->ticket, QUICKBOOKS_LOG_DEBUG); $this->_driver->queueStatus($obj->ticket, $next['qb_action'], $next['ident'], QUICKBOOKS_STATUS_PROCESSING); // Here's a strange case, interactive mode handler if ($next['qb_action'] == QUICKBOOKS_INTERACTIVE_MODE) { // Set the error to "Interactive mode" $this->_driver->errorLog($obj->ticket, QUICKBOOKS_ERROR_OK, QUICKBOOKS_INTERACTIVE_MODE); // This will cause ->getLastError() to be called, and ->getLastError() will then return the string "Interactive mode" which will cause QuickBooks to call ->getInteractiveURL() and start an interactive session... I think...? return new QuickBooks_Result_SendRequestXML(''); } $extra = ''; if ($next['extra']) { $extra = unserialize($next['extra']); } $err = ''; $xml = ''; $last_action_time = $this->_driver->queueActionLast($user, $next['qb_action']); $last_actionident_time = $this->_driver->queueActionIdentLast($user, $next['qb_action'], $next['ident']); // Call the mapped function that should generate an appropriate qbXML request $xml = $this->_callMappedFunction(0, $user, $next['qb_action'], $next['ident'], $extra, $err, $last_action_time, $last_actionident_time, $obj->qbXMLMajorVers . '.' . $obj->qbXMLMinorVers, $obj->qbXMLCountry, $next['qbxml']); // NoOp can be returned to skip this current operation. This will cause getLastError // to be called, at which point NoOp should be returned *again* to tell the Web // Connector to then pause for 5 seconds before asking for another request. if ($xml == QUICKBOOKS_NOOP) { $this->_driver->errorLog($obj->ticket, 0, QUICKBOOKS_NOOP); // Mark it as a NoOp to remove it from the queue $this->_driver->queueStatus($obj->ticket, $next['qb_action'], $next['ident'], QUICKBOOKS_STATUS_NOOP, 'Handler function returned: ' . QUICKBOOKS_NOOP); return new QuickBooks_Result_SendRequestXML(''); } // If the requestID="..." attribute was not specified, we can try to automatically add it to the request if (!($requestID = $this->_extractRequestID($xml)) and $this->_config['autoadd_missing_requestid']) { // Find the <DoSomethingRq tag foreach (QuickBooks_Utilities::listActions() as $action) { $request = QuickBooks_Utilities::actionToRequest($action); if (false !== strpos($xml, '<' . $request . ' ')) { $xml = str_replace('<' . $request . ' ', '<' . $request . ' requestID="' . $this->_constructRequestID($next['qb_action'], $next['ident']) . '" ', $xml); break; } else { if (false !== strpos($xml, '<' . $request . '>')) { $xml = str_replace('<' . $request . '>', '<' . $request . ' requestID="' . $this->_constructRequestID($next['qb_action'], $next['ident']) . '">', $xml); break; } } } } else { // They embedded a requestID="..." attribute, let's make sure it's valid $embedded_action = null; $embedded_ident = null; $this->_parseRequestID($requestID, $embedded_action, $embedded_ident); if ($embedded_action != $next['qb_action'] or $embedded_ident != $next['ident']) { // They are sending this request with an INVALID requestID! Error this out and warn them! $err = 'This request contains an invalid embedded requestID="..." attribute; either embed the $requestID parameter, or leave out the requestID="..." attribute entirely!'; } } if ($this->_config['convert_unix_newlines'] and false === strpos($xml, "\r") and false !== strpos($xml, "\n")) { // (this is currently broken/unimplemented) } if ($err) { //$this->_driver->errorLog($obj->ticket, QUICKBOOKS_ERROR_HANDLER, $err); //$this->_driver->log('ERROR: ' . $err, $obj->ticket, QUICKBOOKS_LOG_NORMAL); //$this->_driver->queueStatus($obj->ticket, $next['qb_action'], $next['ident'], QUICKBOOKS_STATUS_ERROR, 'Registered handler returned error: ' . $err); $errerr = ''; $this->_handleError($obj->ticket, QUICKBOOKS_ERROR_HANDLER, $err, $this->_constructRequestID($next['qb_action'], $next['ident']), $next['qb_action'], $next['ident'], $extra, $errerr, $xml); return new QuickBooks_Result_SendRequestXML(''); } else { $this->_driver->log('Outgoing XML request: ' . $xml, $obj->ticket, QUICKBOOKS_LOG_DEBUG); if (strlen($xml) and !$this->_extractRequestID($xml)) { // Mark it as successful right now $this->_driver->queueStatus($obj->ticket, $next['qb_action'], $next['ident'], QUICKBOOKS_STATUS_SUCCESS, 'Unverified... no requestID attribute in XML stream.'); } // Queue PROCESSING status code has been moved to immediately following the ->dequeue //else //{ // // Mark it as in progress // $this->_driver->queueStatus($obj->ticket, $next['qb_action'], $next['ident'], QUICKBOOKS_STATUS_PROCESSING); //} return new QuickBooks_Result_SendRequestXML($xml); } } } // Reporting an error, this will cause the QBWC to call ->getLastError() return new QuickBooks_Result_SendRequestXML(''); }
protected static function _ChildObjectsToXML($type, $action, $children, $parentPath = '') { $Driver = QuickBooks_Driver_Singleton::getInstance(); $nodes = array(); $file = '/QuickBooks/QBXML/Schema/Object/' . QuickBooks_Utilities::actionToRequest($action) . '.php'; $class = 'QuickBooks_QBXML_Schema_Object_' . QuickBooks_Utilities::actionToRequest($action); QuickBooks_Loader::load($file); $schema_object = new $class(); $usePath = ''; if ($parentPath != '') { $usePath .= $parentPath . ' '; } foreach ($children as $child) { // Figure out which LinkedTxn method should be used... if (strpos($child['table'], "linkedtxn") !== false) { if (stripos($action, "add") !== false) { $part = preg_replace("/add/i", "", $action); $part .= "LineAdd"; } else { if (stripos($action, 'mod') !== false) { $part = preg_replace("/mod/i", "", $action); $part .= "LineMod"; } } if ($schema_object->exists($usePath . 'LinkToTxnID')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($action . ' ' . $part . ' ' . 'LinkToTxn')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($usePath . 'LinkedTxn')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($action . ' ' . $part . ' ' . 'LinkedTxn')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($usePath . 'ApplyCheckToTxnAdd')) { $Node = new QuickBooks_XML_Node("ApplyCheckToTxnAdd"); $Node->setChildDataAt($Node->name() . ' ' . 'TxnID', $child['data']->get("ToTxnID")); $Node->setChildDataAt($Node->name() . ' ' . 'Amount', $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($usePath . 'ApplyCheckToTxnMod')) { $Node = new QuickBooks_XML_Node("ApplyCheckToTxnMod"); $Node->setChildDataAt($Node->name() . ' ' . 'TxnID', $child['data']->get("ToTxnID")); $Node->setChildDataAt($Node->name() . ' ' . 'Amount', $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { continue; } } } } } } } else { if (strpos($child['table'], "dataext") !== false) { continue; } } $map = ''; $others = array(); QuickBooks_SQL_Schema::mapToSchema($child['table'] . '.*', QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $map, $others); $map = str_replace(' *', '', $map); $explode = explode(' ', $map); $first = trim(current($explode)); $map = trim(implode(' ', array_slice($explode, 1))); if (stripos($action, 'add') !== false) { $map = str_replace('Ret', 'Add', $map); } else { $map = str_replace('Ret', 'Mod', $map); } // Journal entries have an unusual JournalEntryMod syntax. Instead of // the typical CreditLineMod and DebitLineMod entries, they instead // have just a single combined entry, JournalLineMod. if ($action == QUICKBOOKS_MOD_JOURNALENTRY) { if ($child['table'] == 'journalentry_journaldebitline' or $child['table'] == 'journalentry_journalcreditline') { $map = 'JournalLineMod'; } } $Node = new QuickBooks_XML_Node($map); /* $retArr[$index]["table"] = $table; $retArr[$index]["data"] = QuickBooks_SQL_Object($table, null, $arr); $retArr[$index]["children"] */ foreach ($child['data']->asArray() as $field => $value) { $map = ''; $others = array(); QuickBooks_SQL_Schema::mapToSchema($child['table'] . '.' . $field, QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $map, $others); if ($Driver->foldsToLower()) { $retpos = strpos($map, 'Ret '); $retval = substr($map, 0, $retpos + 4); $map = substr($map, $retpos + 4); if (stripos($action, 'add') !== false) { $map = str_replace('Ret ', 'Add ', $map); } else { $map = str_replace('Ret ', 'Mod ', $map); } //print('unfolding: {' . $map . '}' . "\n"); $map = $schema_object->unfold($map); //print(' unfolded to: [' . $map . ']' . "\n"); } //print($field . ' => ' . $value . "\n"); //print_r($map); //print("\n\n"); if (!$map or !strlen($value)) { continue; } // OK, the paths look like this: // CustomerRet FirstName // // We don't need the 'CustomerRet' part of it, that's actually incorrect, so we'll strip it off $explode = explode(' ', $map); $first = trim(current($explode)); $map = trim(implode(' ', array_slice($explode, 1))); if (stripos($action, "add") !== false) { $map = str_replace("Ret", "Add", $map); } else { $map = str_replace("Ret", "Mod", $map); } $map = preg_replace("/.*" . $Node->name() . " /", "", $map); /* if (strtolower($Node->name()) == "estimatelinemod" and strpos($map, 'TxnLineID') !== false ) { $value = -1; } */ if (false === strpos($map, ' ')) { if ($schema_object->exists($usePath . $Node->name() . ' ' . $map)) { $use_in_request = true; switch ($schema_object->dataType($usePath . $Node->name() . ' ' . $map)) { case 'AMTTYPE': $value = str_replace(',', '', number_format($value, 2)); break; case 'BOOLTYPE': if ($value == 1) { $value = 'true'; } else { if ($value == 0) { $value = 'false'; } else { $use_in_request = false; } } break; default: break; } if ($use_in_request) { $Child = new QuickBooks_XML_Node($map); $Child->setData($value); $Node->addChild($Child); } } else { // ignore it } } else { // Please see comments about JournalEntries above! if ($action == QUICKBOOKS_MOD_JOURNALENTRY) { $map = str_replace(array('JournalCreditLine ', 'JournalDebitLine '), '', $map); } if ($schema_object->exists($usePath . $Node->name() . ' ' . $map)) { $use_in_request = true; switch ($schema_object->dataType($usePath . $Node->name() . ' ' . $map)) { case 'AMTTYPE': $value = str_replace(',', '', number_format($value, 2)); break; case 'BOOLTYPE': if ($value == 1) { $value = 'true'; } else { if ($value == 0) { $value = 'false'; } else { $use_in_request = false; } } break; default: break; } if ($use_in_request) { $Node->setChildDataAt($Node->name() . ' ' . $map, $value, true); } } } } $tNodes = QuickBooks_Callbacks_SQL_Callbacks::_ChildObjectsToXML(strtolower($child['table']), $action, $child['children'], $usePath . $Node->name()); foreach ($tNodes as $tn) { $Node->addChild($tn); } $nodes[count($nodes)] = $Node; } return $nodes; }
/** * * * @param string $method * @param string $action * @param string $type * @param QuickBooks_Object $obj * @param mixed $callbacks * @param integer $priority * @param string $err * @return boolean */ protected function _doQuery($method, $action, $type, $obj, $callbacks, $webapp_ID, $priority, &$err, $recur) { // If this action is supported by this API source... if ($this->supportsAction($action)) { $request = QuickBooks_Utilities::actionToRequest($action); // if (!is_array($callbacks)) { $callbacks = array($callbacks); } // We *can't* always map them, because they might change between // when we queue the request up, and when we actually send the // request to be processed. // //$err = ''; //$this->_mapApplicationIDs($request, $obj, $this->_source->supportsApplicationIDs(), $err); if (is_null($priority)) { $priority = $this->_guessPriority($action); } // bla? //$webapp_ID = ''; if ($this->_source->understandsObjects()) { $tmp = $this->_source->handleObject($method, $action, $type, $obj, $callbacks, $webapp_ID, $priority, $err, $recur); return $tmp; } else { if ($this->_source->understandsQBXML()) { $qbxml = $obj->asQBXML($request, QUICKBOOKS_OBJECT_XML_DROP, "\t", $action); //print_r($obj); //print('qbxml: {' . $qbxml . '}'); //exit; $tmp = $this->_source->handleQBXML($method, $action, $type, $qbxml, $callbacks, $webapp_ID, $priority, $err, $recur); return $tmp; } else { if ($this->_source->understandsArrays()) { $array = $obj->asArray(); $tmp = $this->_source->handleArray($method, $action, $type, $array, $callbacks, $webapp_ID, $priority, $err, $recur); return $tmp; } else { $err = 'Source does not understand any available input method!'; return false; } } } } $err = 'Source does not support method: ' . $method . '(...)'; return false; }
protected static function _ChildObjectsToXML($type, $action, $children, $parentPath = '') { $nodes = array(); $file = 'QuickBooks/QBXML/Schema/Object/' . QuickBooks_Utilities::actionToRequest($action) . '.php'; $class = 'QuickBooks_QBXML_Schema_Object_' . QuickBooks_Utilities::actionToRequest($action); require_once $file; $schema_object = new $class(); $usePath = ''; if ($parentPath != "") { $usePath .= $parentPath . ' '; } foreach ($children as $child) { // Figure out which LinkedTxn method should be used... if (strpos($child['table'], "linkedtxn") !== false) { if (stripos($action, "add") !== false) { $part = preg_replace("/add/i", "", $action); $part .= "LineAdd"; } else { if (stripos($action, "mod") !== false) { $part = preg_replace("/mod/i", "", $action); $part .= "LineMod"; } } if ($schema_object->exists($usePath . 'LinkToTxnID')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($action . ' ' . $part . ' ' . 'LinkToTxn')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($usePath . 'LinkedTxn')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($action . ' ' . $part . ' ' . 'LinkedTxn')) { $Node = new QuickBooks_XML_Node("LinkToTxnID", $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($usePath . 'ApplyCheckToTxnAdd')) { $Node = new QuickBooks_XML_Node("ApplyCheckToTxnAdd"); $Node->setChildDataAt($Node->name() . ' ' . 'TxnID', $child['data']->get("ToTxnID")); $Node->setChildDataAt($Node->name() . ' ' . 'Amount', $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { if ($schema_object->exists($usePath . 'ApplyCheckToTxnMod')) { $Node = new QuickBooks_XML_Node("ApplyCheckToTxnMod"); $Node->setChildDataAt($Node->name() . ' ' . 'TxnID', $child['data']->get("ToTxnID")); $Node->setChildDataAt($Node->name() . ' ' . 'Amount', $child['data']->get("ToTxnID")); $nodes[count($nodes)] = $Node; continue; } else { continue; } } } } } } } else { if (strpos($child['table'], "dataext") !== false) { continue; } } $map = ''; $others = array(); QuickBooks_SQL_Schema::mapToSchema($child['table'] . ".*", QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $map, $others); $map = str_replace(" *", "", $map); $explode = explode(' ', $map); $first = trim(current($explode)); $map = trim(implode(' ', array_slice($explode, 1))); if (stripos($action, "add") !== false) { $map = str_replace("Ret", "Add", $map); } else { $map = str_replace("Ret", "Mod", $map); } $Node = new QuickBooks_XML_Node($map); /* $retArr[$index]["table"] = $table; $retArr[$index]["data"] = QuickBooks_SQL_Object($table, null, $arr); $retArr[$index]["children"] */ foreach ($child['data']->asArray() as $field => $value) { $map = ''; $others = array(); QuickBooks_SQL_Schema::mapToSchema($child['table'] . '.' . $field, QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $map, $others); /* print($field . ' => ' . $value . "\n"); print_r($map); print("\n\n"); */ if (!$map or !strlen($value)) { continue; } // OK, the paths look like this: // CustomerRet FirstName // // We don't need the 'CustomerRet' part of it, that's actually incorrect, so we'll strip it off $explode = explode(' ', $map); $first = trim(current($explode)); $map = trim(implode(' ', array_slice($explode, 1))); if (stripos($action, "add") !== false) { $map = str_replace("Ret", "Add", $map); } else { $map = str_replace("Ret", "Mod", $map); } $map = preg_replace("/.*" . $Node->name() . " /", "", $map); if (strtolower($Node->name()) == "estimatelinemod" and strpos($map, 'TxnLineID') !== false) { $value = -1; } if (false === strpos($map, ' ')) { if ($schema_object->exists($usePath . $Node->name() . ' ' . $map)) { $use_in_request = true; switch ($schema_object->dataType($usePath . $Node->name() . ' ' . $map)) { case 'AMTTYPE': $value = str_replace(",", "", number_format($value, 2)); break; case 'BOOLTYPE': if ($value == 1) { $value = 'true'; } else { if ($value == 0) { $value = 'false'; } else { $use_in_request = false; } } break; default: break; } if ($use_in_request) { $Child = new QuickBooks_XML_Node($map); $Child->setData($value); $Node->addChild($Child); } } else { //ignore it } } else { if ($schema_object->exists($usePath . $Node->name() . ' ' . $map)) { $use_in_request = true; switch ($schema_object->dataType($usePath . $Node->name() . ' ' . $map)) { case 'AMTTYPE': $value = str_replace(",", "", number_format($value, 2)); break; case 'BOOLTYPE': if ($value == 1) { $value = 'true'; } else { if ($value == 0) { $value = 'false'; } else { $use_in_request = false; } } break; default: break; } if ($use_in_request) { $Node->setChildDataAt($Node->name() . ' ' . $map, $value, true); } } } } $tNodes = QuickBooks_Server_SQL_Callbacks::_ChildObjectsToXML(strtolower($child['table']), $action, $child['children'], $usePath . $Node->name()); foreach ($tNodes as $tn) { $Node->addChild($tn); } $nodes[count($nodes)] = $Node; } return $nodes; }