Example #1
0
 public function mark($mark_as, $object_or_action, $ID, $TxnID_or_ListID = null, $errnum = null, $errmsg = null, $mark_as_dequeued = true)
 {
     $Driver = $this->_driver;
     $object = QuickBooks_Utilities::actionToObject($object_or_action);
     $table_and_field = array();
     // Convert to table and primary key, select qbsql id
     QuickBooks_SQL_Schema::mapPrimaryKey($object, QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $table_and_field);
     if (!empty($table_and_field[0]) and !empty($table_and_field[1])) {
         switch ($mark_as) {
             case QuickBooks_Map::MARK_ADD:
                 $arr = array();
                 $where = array(array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ID));
                 if ($TxnID_or_ListID) {
                     $arr[$table_and_field[1]] = $TxnID_or_ListID;
                     // Get the existing temporary ID
                     $errnum = null;
                     $errmsg = null;
                     $existing = $Driver->fetch($Driver->query("SELECT " . $table_and_field[1] . " FROM " . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " WHERE " . QUICKBOOKS_DRIVER_SQL_FIELD_ID . " = " . $ID, $errnum, $errmsg));
                     if (!$existing) {
                         return false;
                     }
                     $existing_TxnID_or_ListID = $existing[$table_and_field[1]];
                 }
                 $resync = true;
                 $discov = true;
                 if ($errnum) {
                     $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER] = $errnum;
                     $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE] = $errmsg;
                     // Don't mark it as synced/discovered if there was an error
                     $resync = false;
                     $discov = false;
                 }
                 /*
                 if ($mark_as_dequeued)
                 {
                 	$arr[QUICKBOOKS_DRIVER_SQL_FIELD_ENQUEUE_TIME] = date('Y-m-d H:i:s');
                 }
                 */
                 $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0], $arr, $where, $resync, $discov);
                 if ($TxnID_or_ListID) {
                     $Object = new QuickBooks_SQL_Object($table_and_field[0], '', array());
                     $Object->set($table_and_field[1], $TxnID_or_ListID);
                     $action = QuickBooks_Utilities::objectToAdd($object_or_action);
                     $this->_updateRelatives($table_and_field[0], $action, $Object, $existing_TxnID_or_ListID);
                 }
                 break;
         }
     }
     return false;
 }
Example #2
0
 /**
  * @TODO Change this to return false by default, and only catch the specific errors we're concerned with.
  * 
  */
 public static function catchall($requestID, $user, $action, $ident, $extra, &$err, $xml, $errnum, $errmsg, $config)
 {
     $Driver = QuickBooks_Driver_Singleton::getInstance();
     /*
     $Parser = new QuickBooks_XML($xml);
     $errnumTemp = 0;
     $errmsgTemp = '';
     $Doc = $Parser->parse($errnumTemp, $errmsgTemp);
     $Root = $Doc->getRoot();		
     $emailStr = var_export($Root->children(), true);
     	
     $List = $Root->getChildAt('QBXML QBXMLMsgsRs '.QuickBooks_Utilities::actionToResponse($action));
     $Node = current($List->children());
     */
     $map = array();
     $others = array();
     QuickBooks_SQL_Schema::mapToSchema(trim(QuickBooks_Utilities::actionToXMLElement($action)), QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $map, $others);
     $sqlObject = new QuickBooks_SQL_Object($map[0], trim(QuickBooks_Utilities::actionToXMLElement($action)));
     $table = $sqlObject->table();
     switch ($errnum) {
         case 1:
             // These errors occur when we search for something and it doesn't exist
         // These errors occur when we search for something and it doesn't exist
         case 500:
             // 	i.e. we query for invoices modified since xyz, but there are none that have been modified since then
             // This isn't really an error, just ignore it
             return true;
         case 1000:
             // An internal error occured
             // @todo Hopefully at some point we'll have a better idea of how to handle this error...
             return true;
         case 3200:
             // Ignore EditSequence errors (the record will be picked up and a conflict reported next time it runs... maybe?)
             // @todo Think about this one more
             return true;
         case 3250:
             // This feature is not enabled or not available in this version of QuickBooks.
             // Do nothing (this can be safely ignored)
             return true;
         case 3100:
             // Name of List Element is already in use.
             $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
             $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER, $errnum);
             $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, $errmsg);
             $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $sqlObject, array($multipart));
             break;
         case 3260:
             // Insufficient permission level to perform this action.
             // There's nothing we can do about this, if they don't grant the user permission, just skip it
             return true;
         case 3200:
             // The provided edit sequence is out-of-date.
             if (!($tmp = $Driver->get(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident)))) {
                 return true;
             }
             switch ($config['conflicts']) {
                 case QUICKBOOKS_SERVER_SQL_CONFLICT_LOG:
                     $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
                     $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER, $errnum);
                     $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, $errmsg);
                     $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $sqlObject, array($multipart));
                     break;
                 case QUICKBOOKS_SERVER_SQL_CONFLICT_NEWER:
                     $Parser = new QuickBooks_XML_Parser($xml);
                     $errnumTemp = 0;
                     $errmsgTemp = '';
                     $Doc = $Parser->parse($errnumTemp, $errmsgTemp);
                     $Root = $Doc->getRoot();
                     $List = $Root->getChildAt('QBXML QBXMLMsgsRs ' . QuickBooks_Utilities::actionToResponse($action));
                     $TimeModified = $Root->getChildDataAt('QBXML QBXMLMsgsRs ' . QuickBooks_Utilities::actionToResponse($action) . ' ' . QuickBooks_Utilities::actionToXMLElement($action) . ' TimeModified');
                     $EditSequence = $Root->getChildDataAt('QBXML QBXMLMsgsRs ' . QuickBooks_Utilities::actionToResponse($action) . ' ' . QuickBooks_Utilities::actionToXMLElement($action) . ' EditSequence');
                     $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
                     if (QuickBooks_Utilities::compareQBTimeToSQLTime($TimeModified, $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY)) >= 0 && $config['mode'] != QUICKBOOKS_SERVER_SQL_MODE_WRITEONLY) {
                         //@TODO: Make this get only a single item, not the whole table
                         $Driver->queueEnqueue($user, QuickBooks_Utilities::convertActionToQuery($action), __FILE__, true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                     } else {
                         if (QuickBooks_Utilities::compareQBTimeToSQLTime($TimeModified, $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY)) < 0) {
                             //Updates the EditSequence without marking the row as resynced.
                             $tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                             $tmpSQLObject->set("EditSequence", $EditSequence);
                             $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array($multipart));
                             $Driver->queueEnqueue($user, QuickBooks_Utilities::convertActionToMod($action), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_ID), true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                         } else {
                             //Trash it, set synced.
                             $tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                             $tmpSQLObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, "Read/Write Mode is WRITEONLY, and Conflict Mode is NEWER, and Quickbooks has Newer data, so no Update Occured.");
                             $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array($multipart));
                         }
                     }
                     break;
                 case QUICKBOOKS_SERVER_SQL_CONFLICT_QUICKBOOKS:
                     if ($config['mode'] == QUICKBOOKS_SERVER_SQL_MODE_READWRITE) {
                         //@TODO: Make this get only a single item, not the whole table
                         $Driver->queueEnqueue($user, QuickBooks_Utilities::convertActionToQuery($action), null, true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                         $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
                         $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER, $errnum);
                         $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, $errmsg);
                         $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $sqlObject, array($multipart));
                         //Use what's on quickbooks, and trash whatever is here.
                     } else {
                         $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
                         $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER, $errnum);
                         $sqlObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, $errmsg);
                         $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $sqlObject, array($multipart));
                         // @TODO: Raise Notification that the conflicts level requires writing to SQL table, but Mode disallows this
                     }
                     break;
                 case QUICKBOOKS_SERVER_SQL_CONFLICT_SQL:
                     // Updates the EditSequence without marking the row as resynced.
                     $tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                     $tmpSQLObject->set("EditSequence", $EditSequence);
                     $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array($multipart));
                     $Driver->queueEnqueue($user, QuickBooks_Utilities::convertActionToMod($action), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_ID), true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                     break;
                 case QUICKBOOKS_SERVER_SQL_CONFLICT_CALLBACK:
                     break;
                 default:
                     break;
             }
             break;
         default:
             if (strstr($xml, 'statusSeverity="Info"') === false) {
                 //
             }
             break;
     }
     // Please don't change this, it stops us from knowing what's actually
     //	going wrong. If an error occurs, we should either catch it if it's
     //	recoverable, or treated as a fatal error so we know about it and
     //	can address it later.
     //return false;
     return true;
 }
Example #3
0
 /**
  * 
  * 
  * 
  */
 protected static function _queryResponse($type, $List, $requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents, $callback_config = array())
 {
     $type = strtolower($type);
     $Driver = QuickBooks_Driver_Singleton::getInstance();
     $objects = array();
     // For each one of the objects we got back in the qbXML response...
     foreach ($List->children() as $Node) {
         // If this object is a base-level object, we're going to keep track of it's TxnID or
         //	ListID so that we can use it to tie child elements back to this base-level
         //	element (about 20 or 30 lines below this is that code)
         // Child records get deleted, and then re-created with the same
         //	qbsql_id values so that we don't muck up people's associated
         //	records. This keeps track of deleted records so we can re-create
         //	the records with the same qbsql_id values.
         $deleted = array();
         // Convert the XML nodes to objects, based on the XML to SQL schema definitions in Schema.php
         $objects = array();
         QuickBooks_Callbacks_SQL_Callbacks::_transformToSQLObjects('', $Node, $objects);
         //print_r($objects);
         //exit;
         // For each object we created from the XML nodes...
         // (might have created more than one, e.g. an Invoice, plus 10 InvoiceLines, plus a Invoice_DataExt, etc.)
         /*
         if (count($objects) > 1)
         {
         	print_r($objects);
         	exit;
         }
         */
         // This keeps track of whether or not we're ignoring this entire batch of UPDATES/INSERTS
         $ignore_this_and_its_children = false;
         foreach ($objects as $key => $object) {
             $Object =& $object;
             if ($ignore_this_and_its_children) {
                 // If we're supposed to ignore this object and it's children, then just continue
                 continue;
             }
             $table = $Object->table();
             $path = $Object->path();
             $map = array();
             QuickBooks_SQL_Schema::mapPrimaryKey($path, QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $map);
             // Special hack for preferences
             if ($Object->table() == 'preferences') {
                 $map = array('qb_preferences', 'qbsql_external_id');
             }
             //print_r($Object);
             //print_r($path);
             //print_r($map);
             //exit;
             //
             if ($table and count($map) and $map[0] and $map[1]) {
                 $addMapTest = array();
                 $addMapTestOthers = array();
                 QuickBooks_SQL_Schema::mapToSchema(trim(QuickBooks_Utilities::actionToXMLElement($action)), QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $addMapTest, $addMapTestOthers);
                 if (!isset($extra['IsAddResponse']) and !isset($extra['is_add_response']) or !(count($addMapTest) and $addMapTest[0]) or $map[0] != $addMapTest[0]) {
                     // GARRETT'S bug Fix -- Arrays with primary keys consisting of multiple fields weren't updating properly
                     // due to failure to check for arrays.
                     $multipart = array();
                     if (is_array($map[1])) {
                         foreach ($map[1] as $table_name) {
                             $multipart[$table_name] = $object->get($table_name);
                         }
                     } else {
                         $multipart[$map[1]] = $object->get($map[1]);
                     }
                 } else {
                     $multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID] = $ID;
                 }
                 $hooks = array();
                 if (isset($callback_config['hooks'])) {
                     $hooks = $callback_config['hooks'];
                 }
                 if ($tmp = $Driver->get(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $multipart)) {
                     $actually_do_update = false;
                     $actually_do_updaterelatives = false;
                     $actually_do_deletechildren = false;
                     if (isset($tmp[Quickbooks_Utilities::keyForAction($action)])) {
                         // I have no idea what this does or what this is for....
                         // > EDIT: This keeps track of what the old TxnID or ListID is, so that we can use it to update relative tables
                         $extra['AddResponse_OldKey'] = $tmp[Quickbooks_Utilities::keyForAction($action)];
                         $extra['temporary_TxnID_or_ListID_or_LineID'] = $tmp[Quickbooks_Utilities::keyForAction($action)];
                     }
                     if (empty($extra['AddResponse_OldKey']) and Quickbooks_Utilities::keyForAction($action) == 'TxnID' and isset($tmp['TxnLineID'])) {
                         //$extra['AddResponse_OldKey'] = $tmp->get("TxnLineID");
                         $extra['AddResponse_OldKey'] = $tmp['TxnLineID'];
                         $extra['temporary_TxnID_or_ListID_or_LineID'] = $tmp['TxnLineID'];
                     }
                     // Make sure a conflict mode has been selected
                     if (empty($callback_config['conflicts'])) {
                         $callback_config['conflicts'] = null;
                     }
                     if (empty($callback_config['mode'])) {
                         $callback_config['mode'] = QuickBooks_WebConnector_Server_SQL::MODE_READONLY;
                     }
                     if (isset($extra['is_query_response']) or isset($extra['is_import_response']) or isset($extra['is_mod_response']) or isset($extra['is_add_response'])) {
                         // @TODO There should probably be some conflict handling code below to handle conflicts
                         $actually_do_update = true;
                         $actually_do_deletechildren = true;
                         $actually_do_updaterelatives = true;
                     }
                     //$Driver->log('Diagnostics for incoming: is_query[' . !empty($extra['is_query_response']) . '], is_import[' . !empty($extra['is_import_response']) . '], is_mod[' . !empty($extra['is_mod_response']) . '], is_add[' . !empty($extra['is_add_response']) . '], conflict mode: ' . $callback_config['conflicts'] . '', null, QUICKBOOKS_LOG_DEVELOP);
                     // Conflict handling code
                     // @todo I think this should only apply to query and improt, right? I mean, if it's a mod or add, then
                     //	*of course* it was modified after resynced, thats how we knew to send it back to QuickBooks...
                     if ($tmp[QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY] > $tmp[QUICKBOOKS_DRIVER_SQL_FIELD_RESYNC] and $callback_config['mode'] != QuickBooks_WebConnector_Server_SQL::MODE_READONLY) {
                         // CONFLICT resolution code
                         switch ($callback_config['conflicts']) {
                             case QuickBooks_WebConnector_Server_SQL::CONFLICT_NEWER:
                                 $msg = 'Conflict mode: (newer) ' . $callback_config['conflicts'] . ' is not supported right now.';
                                 trigger_error($msg);
                                 die($msg);
                             case QuickBooks_WebConnector_Server_SQL::CONFLICT_QUICKBOOKS:
                                 // QuickBooks is master, so remove all existing child records of this record, then apply the QuickBooks version update
                                 $actually_do_deletechildren = true;
                                 $actually_do_update = true;
                                 //QuickBooks_Callbacks_SQL_Callbacks::_DeleteChildren($table, $user, $action, $ID, $object, $extra);
                                 //$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array( $multipart ));
                                 break;
                             case QuickBooks_WebConnector_Server_SQL::CONFLICT_CALLBACK:
                                 $msg = 'Conflict mode: (callback) ' . $callback_config['conflicts'] . ' is not supported right now.';
                                 trigger_error($msg);
                                 die($msg);
                                 break;
                             case QuickBooks_WebConnector_Server_SQL::CONFLICT_SQL:
                                 // The SQL table is the master table, but we have an out-of-date EditSequence value
                                 //	In this case, what we want to do is update our record to the latest EditSequence value,
                                 //	and then re-queue the object so that it gets updated the next time the sync runs to
                                 //	the values from the SQL record
                                 $tmp_editsequence_update = new QuickBooks_SQL_Object($table, null);
                                 $tmp_editsequence_update->set('EditSequence', $object->get('EditSequence'));
                                 // *Just* update the EditSequence value
                                 $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmp_editsequence_update, array($multipart), false);
                                 // Re-queue it so the conflict gets resolved
                                 $Driver->queueEnqueue($user, QuickBooks_Utilities::convertActionToMod($action), $tmp[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                                 break;
                             case QuickBooks_WebConnector_Server_SQL::CONFLICT_LOG:
                             default:
                                 if (isset($extra['IsModResponse']) or isset($extra['is_mod_response']) or isset($extra['is_add_response'])) {
                                     // If it's actually a mod response, then this isn't actually a conflict, it's just the mod response happening normally
                                     $actually_do_update = true;
                                     $actually_do_deletechildren = true;
                                     $actually_do_updaterelatives = true;
                                 } else {
                                     // Log it...?
                                     $Driver->log('Conflict occured at: ' . $table, null, QUICKBOOKS_LOG_NORMAL);
                                 }
                                 break;
                         }
                     }
                     //print_r($object);
                     //print_r($tmp);
                     // If the EditSequence has not changed since the last time this record was updated,
                     //	then we can just skip this update because everything should already be up to
                     //	date.
                     //
                     // This works around a very important issue as a result of Mod requests. When a Mod
                     //	request is issued and succeeds, it updates the record. Then, on the next Query
                     //	request, the record will be re-imported because the DateModified timestamp was
                     //	updated as a result of the Mod request. However, if the record is modified
                     //	by the end-user in between that Mod request and Import, the changes the user
                     //	made will be overwritten/a conflict will occur *even though the Query response
                     //	was only due to a Mod request that we sent ourselves* and the record in
                     //	QuickBooks never actually changed between the Mod and the Query.
                     if (empty($extra['is_query_response']) and isset($tmp['EditSequence']) and $tmp['EditSequence'] == $object->get('EditSequence')) {
                         $actually_do_update = false;
                         $actually_do_deletechildren = false;
                         $actually_do_updaterelatives = false;
                         //$Driver->log('Ignoring UPDATE: ' . $table . ': ' . print_r($object, true) . ' due to EditSequence equality.', null, QUICKBOOKS_LOG_DEVELOP);
                         // Make sure we ignore the children too (invoice lines, data exts, etc.)
                         $ignore_this_and_its_children = true;
                     }
                     if ($callback_config['mode'] == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY) {
                         // In WRITE-ONLY mode, we only write changes to QuickBooks, but never read them back
                         // (but should we update the EditSequence still?)
                         $actually_do_update = false;
                         $actually_do_deletechildren = false;
                         $actually_do_updaterelatives = false;
                     }
                     //$deleted = array();
                     if ($actually_do_deletechildren) {
                         QuickBooks_Callbacks_SQL_Callbacks::_deleteChildren($table, $user, $action, $ID, $object, $extra, $deleted);
                         //$Driver->log('Immediately after deleting: ' . print_r($deleted, true));
                     }
                     if ($actually_do_updaterelatives) {
                         QuickBooks_Callbacks_SQL_Callbacks::_updateRelatives($table, $user, $action, $ID, $object, $extra);
                     }
                     if ($actually_do_update) {
                         // This handles setting certain special fields (SortOrder, booleans, etc.)
                         QuickBooks_Callbacks_SQL_Callbacks::_massageUpdateRecord($table, $object);
                         //print('applying updates, and with these deletes: ');
                         //print_r($deleted);
                         $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY, date('Y-m-d H:i:s'));
                         //$Driver->log('Applying UPDATE: ' . $table . ': ' . print_r($object, true) . ', where: ' . print_r($multipart, true), null, QUICKBOOKS_LOG_DEVELOP);
                         $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array($multipart));
                         $qbsql_id = null;
                         if (!empty($multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID])) {
                             $qbsql_id = $multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID];
                         }
                         // Call any hooks that occur when a record is updated
                         $hook_data = array('hook' => QuickBooks_SQL::HOOK_SQL_UPDATE, 'user' => $user, 'table' => QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, 'object' => $object, 'data' => $object->asArray(), 'qbsql_id' => $qbsql_id, 'where' => array($multipart));
                         $err = null;
                         QuickBooks_Callbacks_SQL_Callbacks::_callHooks($hooks, QuickBooks_SQL::HOOK_SQL_UPDATE, $requestID, $user, $err, $hook_data, $callback_config);
                     } else {
                         //$Driver->log('Skipping UPDATE: ' . $table . ': ' . print_r($object, true) . ', where: ' . print_r($multipart, true), null, QUICKBOOKS_LOG_DEVELOP);
                     }
                     if ($actually_do_update and isset($extra['is_add_response'])) {
                         // It's an add response, call the hooks
                         $qbsql_id = null;
                         if (!empty($multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID])) {
                             $qbsql_id = $multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID];
                         }
                         // Call any hooks that occur when a record is updated
                         $hook_data = array('hook' => QuickBooks_SQL::HOOK_QUICKBOOKS_INSERT, 'user' => $user, 'table' => QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, 'object' => $object, 'data' => $object->asArray(), 'qbsql_id' => $qbsql_id, 'where' => array($multipart));
                         $err = null;
                         QuickBooks_Callbacks_SQL_Callbacks::_callHooks($hooks, QuickBooks_SQL::HOOK_QUICKBOOKS_INSERT, $requestID, $user, $err, $hook_data, $callback_config);
                     } else {
                         if ($actually_do_update and isset($extra['is_mod_response'])) {
                             // It's an add response, call the hooks
                             $qbsql_id = null;
                             if (!empty($multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID])) {
                                 $qbsql_id = $multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID];
                             }
                             // Call any hooks that occur when a record is updated
                             $hook_data = array('hook' => QuickBooks_SQL::HOOK_QUICKBOOKS_UPDATE, 'user' => $user, 'table' => QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, 'object' => $object, 'data' => $object->asArray(), 'qbsql_id' => $qbsql_id, 'where' => array($multipart));
                             $err = null;
                             QuickBooks_Callbacks_SQL_Callbacks::_callHooks($hooks, QuickBooks_SQL::HOOK_QUICKBOOKS_UPDATE, $requestID, $user, $err, $hook_data, $callback_config);
                         }
                     }
                 } else {
                     // The record *DOES NOT* exist in the current table, so just INSERT it
                     if ($callback_config['mode'] != QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY) {
                         // This handles setting certain special fields (booleans, SortOrder, etc.)
                         QuickBooks_Callbacks_SQL_Callbacks::_massageInsertRecord($table, $object);
                         //$Driver->log('DELETED: ' . print_r($deleted, true) . ', table: [' . $table . ']');
                         // This makes sure that re-inserted child records are re-inserted with the
                         //	same qbsql_id values
                         if (isset($deleted[$table][QUICKBOOKS_TXNLINEID][$object->get(QUICKBOOKS_TXNLINEID)][0])) {
                             $tmp = $deleted[$table][QUICKBOOKS_TXNLINEID][$object->get(QUICKBOOKS_TXNLINEID)];
                             unset($deleted[$table][QUICKBOOKS_TXNLINEID][$object->get(QUICKBOOKS_TXNLINEID)]);
                             // Can't use this anymore after it's been used for an INSERT
                             $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_ID, $tmp[0]);
                             $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_USERNAME_ID, $tmp[1]);
                             $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_EXTERNAL_ID, $tmp[2]);
                         } else {
                             if (isset($deleted[$table][QUICKBOOKS_TXNLINEID]) and count($deleted[$table][QUICKBOOKS_TXNLINEID]) > 0) {
                                 // We deleted some child from this table, and what we deleted *should*
                                 //	have been sent to QuickBooks and received from QuickBooks in the
                                 //	same order... so we should be able to just fetch the next deleted
                                 //	thing, and re-use that qbsql_id value
                                 reset($deleted[$table][QUICKBOOKS_TXNLINEID]);
                                 $tmp = array_shift($deleted[$table][QUICKBOOKS_TXNLINEID]);
                                 // Remove it from the list so it can't be used anymore
                                 $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_ID, $tmp[0]);
                                 $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_USERNAME_ID, $tmp[1]);
                                 $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_EXTERNAL_ID, $tmp[2]);
                             }
                         }
                         if ('' == $object->get(QUICKBOOKS_DRIVER_SQL_FIELD_USERNAME_ID)) {
                             $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_USERNAME_ID, null);
                         }
                         if ('' == $object->get(QUICKBOOKS_DRIVER_SQL_FIELD_EXTERNAL_ID)) {
                             $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_EXTERNAL_ID, null);
                         }
                         //print_r($object);
                         //$Driver->log('Applying INSERT: ' . $table . ': ' . print_r($object, true), null, QUICKBOOKS_LOG_DEVELOP);
                         $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY, date('Y-m-d H:i:s'));
                         $Driver->insert(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object);
                         $last = $Driver->last();
                         // Call any hooks that occur when a record is inserted
                         $hook_data = array('hook' => QuickBooks_SQL::HOOK_SQL_INSERT, 'user' => $user, 'table' => QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, 'object' => $object, 'data' => $object->asArray(), 'qbsql_id' => $last);
                         $err = null;
                         QuickBooks_Callbacks_SQL_Callbacks::_callHooks($hooks, QuickBooks_SQL::HOOK_SQL_INSERT, $requestID, $user, $err, $hook_data, $callback_config);
                     } else {
                         //$Driver->log('Skipping INSERT: ' . $table . ': ' . print_r($object, true), null, QUICKBOOKS_LOG_DEVELOP);
                     }
                 }
                 // Triggered actions
                 //	Receive Payment => reload any linked invoices
                 //	Invoice => reload the customer
                 //	Purchase Order => reload the vendor
                 QuickBooks_Callbacks_SQL_Callbacks::_triggerActions($user, $table, $Object, $action);
             }
         }
     }
     // Find out if we need to iterate further to get more results
     $matches = array();
     //$iterator_count = ereg('iteratorRemainingCount="([0-9]*)" iteratorID="([^"]*)"', $xml, $matches);
     $matched_iteratorID = QuickBooks_XML::extractTagAttribute('iteratorID', $xml);
     $matched_iteratorRemainingCount = QuickBooks_XML::extractTagAttribute('iteratorRemainingCount', $xml);
     // If an iterator was used and there's results remaining
     if ($matched_iteratorID and $matched_iteratorRemainingCount > 0) {
         $extra = array('iteratorID' => $matched_iteratorID);
         // Set the iteratorID to be used
         /*
         // What is this code trying to do...? This doesn't look right... 
         if ( (int) $matches[1] < QUICKBOOKS_SERVER_SQL_ITERATOR_MAXRETURNED)
         {
         	$extra['maxReturned'] = (int) $matches[1];
         }
         */
         // 		queueEnqueue($user, $action, $ident, $replace = true, $priority = 0, $extra = null, $qbxml = null)
         $Driver->queueEnqueue($user, $action, null, true, QUICKBOOKS_SERVER_SQL_ITERATOR_PRIORITY, $extra);
         // Queue up another go!
     } else {
         // We're done with this iterator!
         // When the current iterator started...
         $module = __CLASS__;
         $type = null;
         $opts = null;
         // 					configRead($user, $module, $key, &$type, &$opts)
         $curr_sync_datetime = $Driver->configRead($user, $module, QuickBooks_Callbacks_SQL_Callbacks::_keySyncCurr($action), $type, $opts);
         // last sync started...
         //print('WRITING: [' . $curr_sync_datetime . '] from /' . $module . '/ {' . QuickBooks_Callbacks_SQL_Callbacks::_keySyncCurr($action) . '}');
         // Start of the iteration, update the previous timestamp to NOW
         $Driver->configWrite($user, $module, QuickBooks_Callbacks_SQL_Callbacks::_keySyncPrev($action), $curr_sync_datetime, null);
     }
 }
 /**
  * 
  * 
  * 
  */
 protected static function _QueryResponse($type, $List, $requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents, $callback_config = array())
 {
     $type = strtolower($type);
     $Driver = QuickBooks_Driver_Singleton::getInstance();
     $objects = array();
     // For each one of the objects we got back in the qbXML response...
     foreach ($List->children() as $Node) {
         // If this object is a base-level object, we're going to keep track of it's TxnID or
         //	ListID so that we can use it to tie child elements back to this base-level
         //	element (about 20 or 30 lines below this is that code)
         // Convert the XML nodes to objects, based on the XML to SQL schema definitions in Schema.php
         $objects = array();
         QuickBooks_Server_SQL_Callbacks::_transformToSQLObjects('', $Node, $objects);
         // For each object we created from the XML nodes...
         // (might have created more than one, e.g. an Invoice, plus 10 InvoiceLines, plus a Invoice_DataExt, etc.)
         /*
         if (count($objects) > 1)
         {
         	print_r($objects);
         	exit;
         }
         */
         foreach ($objects as $key => $object) {
             $table = $object->table();
             $path = $object->path();
             $map = array();
             QuickBooks_SQL_Schema::mapPrimaryKey($path, QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $map);
             // This is a list of "Boolean" fields in QuickBooks
             //	QuickBooks sends us boolean value as the strings "true" and
             //	"false", so we need to convert them to 1 and 0 so that we
             //	can store this in an INT field in the database (not all
             //	databases support a BOOLEAN type)
             $qb_to_sql_booleans = array('EmployeePayrollInfo_IsUsingTimeDataToCreatePaychecks', 'EmployeePayrollInfo_ClearEarnings', 'EmployeePayrollInfo_SickHours_IsResettingHoursEachNewYear', 'EmployeePayrollInfo_VacationHours_IsResettingHoursEachNewYear', 'IsAdjustment', 'IsActive', 'IsBillable', 'IsBilled', 'IsClosed', 'IsFinanceCharge', 'IsFullyInvoiced', 'IsFullyReceived', 'IsManuallyClosed', 'IsPaid', 'IsPending', 'IsPrintItemsInGroup', 'IsToBeEmailed', 'IsToBePrinted', 'IsSampleCompany', 'IsTaxable', 'IsVendorEligibleFor1099');
             // Cast QuickBooks booleans (strings, "true" and "false") to database booleans (tinyint 1 and 0)
             foreach ($qb_to_sql_booleans as $qb_field_boolean) {
                 $qb_bool = $object->get($qb_field_boolean, false);
                 if ($qb_bool !== false) {
                     if ($qb_bool == 'true') {
                         $object->set($qb_field_boolean, 1);
                     } else {
                         $object->set($qb_field_boolean, 0);
                     }
                 }
             }
             //
             if (count($map) and $map[0] and $map[1]) {
                 $addMapTest = array();
                 $addMapTestOthers = array();
                 QuickBooks_SQL_Schema::mapToSchema(trim(QuickBooks_Utilities::actionToXMLElement($action)), QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $addMapTest, $addMapTestOthers);
                 if (!isset($extra['IsAddResponse']) or !(count($addMapTest) and $addMapTest[0]) or $map[0] != $addMapTest[0]) {
                     //echo "<br />&nbsp;&nbsp;&nbsp;".$path."<br />";
                     //GARRETT'S bug Fix -- Arrays with primary keys consisting of multiple fields weren't updating properly
                     //due to failure to check for arrays.
                     $multipart = array();
                     if (is_array($map[1])) {
                         foreach ($map[1] as $table_name) {
                             $multipart[$table_name] = $object->get($table_name);
                         }
                     } else {
                         $multipart[$map[1]] = $object->get($map[1]);
                     }
                 } else {
                     $multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID] = $ID;
                 }
                 $hooks = array();
                 if (isset($callback_config['hooks'])) {
                     $hooks = $callback_config['hooks'];
                 }
                 if ($tmp = $Driver->get(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $multipart)) {
                     $actually_do_update = false;
                     $actually_do_updaterelatives = false;
                     $actually_do_deletechildren = false;
                     $extra['AddResponse_OldKey'] = $tmp->get(Quickbooks_Utilities::keyForAction($action));
                     if ($extra['AddResponse_OldKey'] == null and Quickbooks_Utilities::keyForAction($action) == "TxnID") {
                         $extra['AddResponse_OldKey'] = $tmp->get("TxnLineID");
                     }
                     if (isset($extra['IsAddResponse']) and stripos($action, 'dataext') === false) {
                         // DATAEXT custom field handling
                         // 	Handling data extensions here...
                         $tmpSQLObject = new QuickBooks_SQL_Object("dataext", null);
                         $tempMulti = array();
                         if (Quickbooks_Utilities::keyForAction($action) == "ListID") {
                             $tmpSQLObject->set('Entity_ListID', $object->get("ListID"));
                             $tempMulti["Entity_ListID"] = $extra['AddResponse_OldKey'];
                             $wField = "Entity_ListID";
                             $wValue = $object->get("ListID");
                         } else {
                             $tmpSQLObject->set('Txn_TxnID', $object->get("TxnID"));
                             $tempMulti["Txn_TxnID"] = $extra['AddResponse_OldKey'];
                             $wField = "Txn_TxnID";
                             $wValue = $object->get("TxnID");
                             if ($object->get("TxnID") == null) {
                                 $tmpSQLObject->set('Txn_TxnID', $object->get("TxnLineID"));
                                 $wValue = $object->get("TxnLineID");
                             }
                         }
                         $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . "dataext", $tmpSQLObject, array($tempMulti));
                         //mail("*****@*****.**", "DataExtStuffData", "Type: ".$table."\n\n\nTable: ".QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . "dataext\n\n\n".var_export($tmpSQLObject, true)."\n\n\n".var_export($tempMulti, true));
                         $sql = "\n\t\t\t\t\t\t\t\t\tSELECT \n\t\t\t\t\t\t\t\t\t\t" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . "\n\t\t\t\t\t\t\t\t\tFROM \n\t\t\t\t\t\t\t\t\t\t" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . "dataext" . " \n\t\t\t\t\t\t\t\t\tWHERE \n\t\t\t\t\t\t\t\t\t\t " . $wField . " = '" . $Driver->escape($wValue) . "'";
                         $errnum = 0;
                         $errmsg = '';
                         $tRes = $Driver->query($sql, $errnum, $errmsg);
                         while ($tArr = $Driver->fetch($tRes)) {
                             $Driver->queueEnqueue($user, QUICKBOOKS_ADD_DATAEXT, $tArr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, array());
                         }
                     } else {
                         if (isset($extra['IsModResponse']) and stripos($action, "dataext") === false) {
                             // *MORE* DATAEXT custom field stuff
                             if (Quickbooks_Utilities::keyForAction($action) == "ListID") {
                                 $wField = "Entity_ListID";
                                 $wValue = $object->get("ListID");
                             } else {
                                 $wField = "Txn_TxnID";
                                 $wValue = $object->get("TxnID");
                                 if ($object->get("TxnID") == null) {
                                     $wValue = $object->get("TxnLineID");
                                 }
                             }
                             $sql = "\n\t\t\t\t\t\t\t\t\tSELECT \n\t\t\t\t\t\t\t\t\t\t" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . "\n\t\t\t\t\t\t\t\t\tFROM \n\t\t\t\t\t\t\t\t\t\t" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . "dataext" . " \n\t\t\t\t\t\t\t\t\tWHERE \n\t\t\t\t\t\t\t\t\t\t " . $wField . " = '" . $Driver->escape($wValue) . "'";
                             $errnum = 0;
                             $errmsg = '';
                             $tRes = $Driver->query($sql, $errnum, $errmsg);
                             while ($tArr = $Driver->fetch($tRes)) {
                                 $Driver->queueEnqueue($user, QUICKBOOKS_MOD_DATAEXT, $tArr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, array());
                             }
                         }
                     }
                     // Make sure a conflict mode has been selected
                     if (empty($callback_config['conflicts'])) {
                         $callback_config['conflicts'] = null;
                     }
                     if (!isset($extra['IsAddResponse']) and $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY) > $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_RESYNC) and $callback_config['mode'] != QUICKBOOKS_SERVER_SQL_MODE_READONLY) {
                         // CONFLICT resolution code
                         switch ($callback_config['conflicts']) {
                             case QUICKBOOKS_SERVER_SQL_CONFLICT_NEWER:
                                 $msg = 'Conflict mode: ' . $callback_config['conflicts'] . ' is not supported right now.';
                                 trigger_error($msg);
                                 die($msg);
                                 /*
                                 									
                                 									This code needs to be looked over and fixed:
                                 									 - Needs to use the $actually_do_update stuff
                                 									 - Needs to not depend on those deprecated QBTimeToSQLTime functions which are MySQL dependent
                                 									 - Needs to be documented
                                 										
                                 									if (QuickBooks_Utilities::compareQBTimeToSQLTime($object->get('TimeModified'), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY)) >= 0 and 
                                 										$callback_config['mode'] != QUICKBOOKS_SERVER_SQL_MODE_WRITEONLY)
                                 									{
                                 										//mail("*****@*****.**", "April 10TM", "QB Was Newer or Equal\n\n"."QB: ".$object->get('TimeModified')."\n\nSQL: ".$tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY));
                                 										QuickBooks_Server_SQL_Callbacks::_DeleteChildren($table, $user, $action, $ID, $object, $extra);
                                 										$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array( $multipart ));
                                 										
                                 										//$actually_do_update = true;
                                 										//$actually_do_deletechildren = true;
                                 										
                                 									}
                                 									else if (QuickBooks_Utilities::compareQBTimeToSQLTime($object->get('TimeModified'), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY)) < 0)
                                 									{
                                 										//mail("*****@*****.**", "April 10TM", "SQL Was Newer\n\n"."QB: ".$object->get('TimeModified')."\n\nSQL: ".$tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY));
                                 										//Updates the EditSequence without marking the row as resynced.
                                 										$tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                                 										$tmpSQLObject->set('EditSequence', $object->get('EditSequence'));
                                 										//mail("*****@*****.**", "April10 ES", $object->name());
                                 										$tempmap = '';
                                 										$tempothers = array();
                                 										QuickBooks_SQL_Schema::mapToSchema($table . ".*", QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $tempmap, $tempothers);
                                 										$tempmap = str_replace(" *", "", $tempmap);
                                 										$tempmap = str_replace("Ret", "Mod", $tempmap);
                                 										
                                 										$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array( $multipart ), false);
                                 										$Driver->queueEnqueue($user, $tempmap, $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_ID), true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                                 									}
                                 									else
                                 									{
                                 										//Trash it, set synced.
                                 										$tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                                 										$tmpSQLObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, "Read/Write Mode is WRITEONLY, and Conflict Mode is NEWER, and Quickbooks has Newer data, so no Update Occured.");
                                 										$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array( $multipart ));
                                 									}
                                 									
                                 									break;*/
                             /*
                             									
                             									This code needs to be looked over and fixed:
                             									 - Needs to use the $actually_do_update stuff
                             									 - Needs to not depend on those deprecated QBTimeToSQLTime functions which are MySQL dependent
                             									 - Needs to be documented
                             										
                             									if (QuickBooks_Utilities::compareQBTimeToSQLTime($object->get('TimeModified'), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY)) >= 0 and 
                             										$callback_config['mode'] != QUICKBOOKS_SERVER_SQL_MODE_WRITEONLY)
                             									{
                             										//mail("*****@*****.**", "April 10TM", "QB Was Newer or Equal\n\n"."QB: ".$object->get('TimeModified')."\n\nSQL: ".$tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY));
                             										QuickBooks_Server_SQL_Callbacks::_DeleteChildren($table, $user, $action, $ID, $object, $extra);
                             										$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array( $multipart ));
                             										
                             										//$actually_do_update = true;
                             										//$actually_do_deletechildren = true;
                             										
                             									}
                             									else if (QuickBooks_Utilities::compareQBTimeToSQLTime($object->get('TimeModified'), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY)) < 0)
                             									{
                             										//mail("*****@*****.**", "April 10TM", "SQL Was Newer\n\n"."QB: ".$object->get('TimeModified')."\n\nSQL: ".$tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY));
                             										//Updates the EditSequence without marking the row as resynced.
                             										$tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                             										$tmpSQLObject->set('EditSequence', $object->get('EditSequence'));
                             										//mail("*****@*****.**", "April10 ES", $object->name());
                             										$tempmap = '';
                             										$tempothers = array();
                             										QuickBooks_SQL_Schema::mapToSchema($table . ".*", QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $tempmap, $tempothers);
                             										$tempmap = str_replace(" *", "", $tempmap);
                             										$tempmap = str_replace("Ret", "Mod", $tempmap);
                             										
                             										$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array( $multipart ), false);
                             										$Driver->queueEnqueue($user, $tempmap, $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_ID), true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                             									}
                             									else
                             									{
                             										//Trash it, set synced.
                             										$tmpSQLObject = new QuickBooks_SQL_Object($table, null);
                             										$tmpSQLObject->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, "Read/Write Mode is WRITEONLY, and Conflict Mode is NEWER, and Quickbooks has Newer data, so no Update Occured.");
                             										$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmpSQLObject, array( $multipart ));
                             									}
                             									
                             									break;*/
                             case QUICKBOOKS_SERVER_SQL_CONFLICT_QUICKBOOKS:
                                 // QuickBooks is master, so remove all existing child records of this record, then apply the QuickBooks version update
                                 $actually_do_deletechildren = true;
                                 $actually_do_update = true;
                                 //QuickBooks_Server_SQL_Callbacks::_DeleteChildren($table, $user, $action, $ID, $object, $extra);
                                 //$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array( $multipart ));
                                 break;
                             case QUICKBOOKS_SERVER_SQL_CONFLICT_CALLBACK:
                                 $msg = 'Conflict mode: ' . $callback_config['conflicts'] . ' is not supported right now.';
                                 trigger_error($msg);
                                 die($msg);
                                 break;
                             case QUICKBOOKS_SERVER_SQL_CONFLICT_SQL:
                                 // The SQL table is the master table, but we have an out-of-date EditSequence value
                                 //	In this case, what we want to do is update our record to the latest EditSequence value,
                                 //	and then re-queue the object so that it gets updated the next time the sync runs to
                                 //	the values from the SQL record
                                 $tmp_editsequence_update = new QuickBooks_SQL_Object($table, null);
                                 $tmp_editsequence_update->set('EditSequence', $object->get('EditSequence'));
                                 // *Just* update the EditSequence value
                                 $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $tmp_editsequence_update, array($multipart), false);
                                 // Re-queue it so the conflict gets resolved
                                 $Driver->queueEnqueue($user, QuickBooks_Utilities::convertActionToMod($action), $tmp->get(QUICKBOOKS_DRIVER_SQL_FIELD_ID), true, QUICKBOOKS_SERVER_SQL_CONFLICT_QUEUE_PRIORITY, $extra);
                                 break;
                             case QUICKBOOKS_SERVER_SQL_CONFLICT_LOG:
                             default:
                                 if (isset($extra['IsModResponse'])) {
                                     // If it's actually a mod response, then this isn't actually a conflict, it's just the mod response happening normally
                                     $actually_do_update = true;
                                     $actually_do_deletechildren = true;
                                     $actually_do_updaterelatives = true;
                                 } else {
                                     // Log it...?
                                     $Driver->log('Conflict occured at: ' . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, null, QUICKBOOKS_LOG_NORMAL);
                                 }
                                 break;
                         }
                     } else {
                         if (isset($extra['IsAddResponse'])) {
                             // If this is set, it means we just added something
                             //	to QuickBooks, and now we need to record the
                             //	ListID and EditSequence provided to us by QuickBooks
                             $actually_do_update = true;
                             $actually_do_updaterelatives = true;
                             $actually_do_deletechildren = true;
                         }
                     }
                     /*else if (!isset($callback_config['conflicts']) or $callback_config['mode'] != QUICKBOOKS_SERVER_SQL_MODE_WRITEONLY)
                     		{
                     			// No conflicts have occurred, let's update the record in the SQL table
                     			QuickBooks_Server_SQL_Callbacks::_DeleteChildren($table, $user, $action, $ID, $object, $extra);
                     			QuickBooks_Server_SQL_Callbacks::_UpdateRelatives($table, $user, $action, $ID, $object, $extra);
                     			$Driver->log('Applying UPDATE: ' . $table . ': ' . print_r($object) . ', where: ' . print_r($multipart), null, QUICKBOOKS_LOG_DEVELOP);
                     			$Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array( $multipart ));
                     		}*/
                     if ($callback_config['mode'] == QUICKBOOKS_SERVER_SQL_MODE_WRITEONLY) {
                         // In WRITE-ONLY mode, we only write changes to QuickBooks, but never read them back
                         // (but should we update the EditSequence still?)
                         $actually_do_update = false;
                         $actually_do_deletechildren = false;
                         $actually_do_updaterelatives = false;
                     }
                     if ($actually_do_deletechildren) {
                         QuickBooks_Server_SQL_Callbacks::_DeleteChildren($table, $user, $action, $ID, $object, $extra);
                     }
                     if ($actually_do_updaterelatives) {
                         QuickBooks_Server_SQL_Callbacks::_UpdateRelatives($table, $user, $action, $ID, $object, $extra);
                     }
                     if ($actually_do_update) {
                         $Driver->log('Applying UPDATE: ' . $table . ': ' . print_r($object, true) . ', where: ' . print_r($multipart, true), null, QUICKBOOKS_LOG_DEVELOP);
                         $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array($multipart));
                         $qbsql_id = null;
                         if (!empty($multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID])) {
                             $qbsql_id = $multipart[QUICKBOOKS_DRIVER_SQL_FIELD_ID];
                         }
                         // Call any hooks that occur when a record is updated
                         $hook_data = array('hook' => QUICKBOOKS_SQL_HOOK_SQL_UPDATE, 'table' => QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, 'object' => $object, 'data' => $object->asArray(), 'qbsql_id' => $qbsql_id, 'where' => array($multipart));
                         $err = null;
                         Quickbooks_Server_SQL_Callbacks::_callHooks($hooks, QUICKBOOKS_SQL_HOOK_SQL_UPDATE, $requestID, $user, $err, $hook_data, $callback_config);
                     }
                 } else {
                     // The record *DOES NOT exist in the current table, so just INSERT it
                     if ($callback_config['mode'] != QUICKBOOKS_SERVER_SQL_MODE_WRITEONLY) {
                         $Driver->log('Applying INSERT: ' . $table . ': ' . print_r($object, true), null, QUICKBOOKS_LOG_DEVELOP);
                         $Driver->insert(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object);
                         $last = $Driver->last();
                         // Call any hooks that occur when a record is inserted
                         $hook_data = array('hook' => QUICKBOOKS_SQL_HOOK_SQL_INSERT, 'table' => QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, 'object' => $object, 'data' => $object->asArray(), 'qbsql_id' => $last);
                         $err = null;
                         Quickbooks_Server_SQL_Callbacks::_callHooks($hooks, QUICKBOOKS_SQL_HOOK_SQL_INSERT, $requestID, $user, $err, $hook_data, $callback_config);
                     }
                 }
             }
         }
         //exit;
     }
     // Find out if we need to iterate further to get more results
     $matches = array();
     $iterator_count = ereg('iteratorRemainingCount="([0-9]*)" iteratorID="([^"]*)"', $xml, $matches);
     // If an iterator was used and there's results remaining
     if ($iterator_count !== false and isset($matches[1]) and (int) $matches[1] > 0) {
         $extra = array('iteratorID' => $matches[2]);
         // Set the iteratorID to be used
         if ((int) $matches[1] < QUICKBOOKS_SERVER_SQL_ITERATOR_MAXRETURNED) {
             $extra['maxReturned'] = (int) $matches[1];
         }
         // 		queueEnqueue($user, $action, $ident, $replace = true, $priority = 0, $extra = null, $qbxml = null)
         $Driver->queueEnqueue($user, $action, null, true, QUICKBOOKS_SERVER_SQL_ITERATOR_PRIORITY, $extra);
         // Queue up another go!
     } else {
         // We're done with this iterator!
         // When the current iterator started...
         $module = __CLASS__;
         $type = null;
         $opts = null;
         // 					configRead($user, $module, $key, &$type, &$opts)
         $curr_sync_datetime = $Driver->configRead($user, $module, QuickBooks_Server_SQL_Callbacks::_keySyncCurr($action), $type, $opts);
         // last sync started...
         // Start of the iteration, update the previous timestamp to NOW
         $Driver->configWrite($user, $module, QuickBooks_Server_SQL_Callbacks::_keySyncPrev($action), $curr_sync_datetime, null);
     }
 }
Example #5
0
 /**
  * @TODO Change this to return false by default, and only catch the specific errors we're concerned with.
  * 
  */
 public static function catchall($requestID, $user, $action, $ident, $extra, &$err, $xml, $errnum, $errmsg, $config)
 {
     $Driver = QuickBooks_Driver_Singleton::getInstance();
     $ignore = array(QUICKBOOKS_IMPORT_DELETEDTXNS => true, QUICKBOOKS_QUERY_DELETEDTXNS => true, QUICKBOOKS_IMPORT_DELETEDLISTS => true, QUICKBOOKS_QUERY_DELETEDLISTS => true, QUICKBOOKS_VOID_TRANSACTION => true, QUICKBOOKS_DELETE_TRANSACTION => true, QUICKBOOKS_DELETE_LIST => true);
     if (isset($ignore[$action])) {
         // Ignore errors for these requests
         return true;
     }
     /*
     $Parser = new QuickBooks_XML($xml);
     $errnumTemp = 0;
     $errmsgTemp = '';
     $Doc = $Parser->parse($errnumTemp, $errmsgTemp);
     $Root = $Doc->getRoot();		
     $emailStr = var_export($Root->children(), true);
     	
     $List = $Root->getChildAt('QBXML QBXMLMsgsRs '.QuickBooks_Utilities::actionToResponse($action));
     $Node = current($List->children());
     */
     $map = array();
     $others = array();
     QuickBooks_SQL_Schema::mapToSchema(trim(QuickBooks_Utilities::actionToXMLElement($action)), QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $map, $others);
     $object = new QuickBooks_SQL_Object($map[0], trim(QuickBooks_Utilities::actionToXMLElement($action)));
     $table = $object->table();
     $existing = null;
     if ($table and is_numeric($ident)) {
         $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
         $existing = $Driver->get(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $multipart);
     }
     switch ($errnum) {
         case 1:
             // These errors occur when we search for something and it doesn't exist
         // These errors occur when we search for something and it doesn't exist
         case 500:
             // 	i.e. we query for invoices modified since xyz, but there are none that have been modified since then
             // This isn't really an error, just ignore it
             if ($action == QUICKBOOKS_DERIVE_CUSTOMER) {
                 // Tried to derive, doesn't exist, add it
                 $Driver->queueEnqueue($user, QUICKBOOKS_ADD_CUSTOMER, $ident, true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_CUSTOMER));
             } else {
                 if ($action == QUICKBOOKS_DERIVE_INVOICE) {
                     // Tried to derive, doesn't exist, add it
                     $Driver->queueEnqueue($user, QUICKBOOKS_ADD_INVOICE, $ident, true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_INVOICE));
                 } else {
                     if ($action == QUICKBOOKS_DERIVE_RECEIVEPAYMENT) {
                         // Tried to derive, doesn't exist, add it
                         $Driver->queueEnqueue($user, QUICKBOOKS_ADD_RECEIVEPAYMENT, $ident, true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_RECEIVEPAYMENT));
                     }
                 }
             }
             return true;
         case 1000:
             // An internal error occured
             // @todo Hopefully at some point we'll have a better idea of how to handle this error...
             return true;
             //case 3120:			// 3120 errors are handled in the 3210 error handler section
             //	break;
         //case 3120:			// 3120 errors are handled in the 3210 error handler section
         //	break;
         case 3170:
             // This list has been modified by another user.
         // This list has been modified by another user.
         case 3175:
         case 3176:
         case 3180:
             // This error can occur in several different situations, so we test per situation
             if (false !== strpos($errmsg, 'list has been modified by another user') or false !== strpos($errmsg, 'internals could not be locked') or false !== strpos($errmsg, 'failed to acquire the lock') or false !== strpos($errmsg, 'list element is in use')) {
                 // This is *not* an error, we can just send the request again, and it'll go through just fine
                 return true;
             }
             break;
         case 3200:
             // Ignore EditSequence errors (the record will be picked up and a conflict reported next time it runs... maybe?)
             if ($action == QUICKBOOKS_MOD_CUSTOMER and $existing) {
                 // Queue up a derive customer request
                 // Tried to derive, doesn't exist, add it
                 $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_CUSTOMER, $ident, true, 9999, array('ListID' => $existing['ListID']));
             } else {
                 if ($action == QUICKBOOKS_MOD_INVOICE and $existing) {
                     // Queue up a derive customer request
                     // Tried to derive, doesn't exist, add it
                     $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVOICE, $ident, true, 9999, array('TxnID' => $existing['TxnID']));
                 }
             }
             return true;
         case 3120:
         case 3210:
             //print_r($existing);
             //print('TXNID: [' . $existing['TxnID'] . ']');
             // 3210: The &quot;AppliedToTxnAdd payment amount&quot; field has an invalid value &quot;129.43&quot;.  QuickBooks error message: You cannot pay more than the amount due.
             if ($action == QUICKBOOKS_ADD_RECEIVEPAYMENT and (false !== strpos($errmsg, 'pay more than the amount due') or false !== strpos($errmsg, 'cannot be found')) and $existing) {
                 // If this happens, we're going to try to re-submit the payment, *without* the AppliedToTxn element
                 $db_errnum = null;
                 $db_errmsg = null;
                 $Driver->query("\n\t\t\t\t\t\tUPDATE \n\t\t\t\t\t\t\t" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . "receivepayment_appliedtotxn \n\t\t\t\t\t\tSET \n\t\t\t\t\t\t\tqbsql_to_skip = 1 \n\t\t\t\t\t\tWHERE \n\t\t\t\t\t\t\tReceivePayment_TxnID = '%s' ", $db_errnum, $db_errmsg, null, null, array($existing['TxnID']));
                 return true;
             }
             break;
         case 3250:
             // This feature is not enabled or not available in this version of QuickBooks.
             // Do nothing (this can be safely ignored)
             return true;
         case 3260:
             // Insufficient permission level to perform this action.
         // Insufficient permission level to perform this action.
         case 3261:
             // The integrated application has no permission to ac...
             // There's nothing we can do about this, if they don't grant the user permission, just skip it
             return true;
         case 3100:
             // Name of List Element is already in use.
             break;
         case '0x8004040D':
             // The ticket parameter is invalid  (how does this happen!?!)
             return true;
     }
     // This is our catch-all which marks the item as errored out
     if (strstr($xml, 'statusSeverity="Info"') === false) {
         $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident);
         $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER, $errnum);
         $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, $errmsg);
         // Do not set the resync field, we want resync and modified timestamps to be different
         $update_resync_field = false;
         $update_discov_field = false;
         $update_derive_field = false;
         if ($table and is_numeric($ident)) {
             // Set the error message
             $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array($multipart), $update_resync_field, $update_discov_field, $update_derive_field);
         }
     }
     // Please don't change this, it stops us from knowing what's actually
     //	going wrong. If an error occurs, we should either catch it if it's
     //	recoverable, or treated as a fatal error so we know about it and
     //	can address it later.
     //return false;
     // I'm changing it because otherwise the sync never completes if a
     //	single error occurs... we need a way to skip errored-out records
     return true;
 }