QuickBooks/Callbacks/SQL/Callbacks.php
static public function onAuthenticate($requestID, $user, $hook, &$err, $hook_data, $callback_config)
	{
		// $requestID, $user, $hook, &$err, $hook_data, $callback_config
		
		// Driver instance
		$Driver = QuickBooks_Driver_Singleton::getInstance();
		
		// Map instance 
		$Map = new QuickBooks_Map_Qbxml($Driver);
		
		// Mode (read-onlyl, write-only, read/write)
		$mode = $callback_config['mode'];
		
		// How often recurring events happen
		$run_every = $callback_config['recur'];
		
		// $map parameter
		$sql_map = $callback_config['map'];
		
		// Which things do you want to query? (QuickBooks => SQL database)
		/*
		$sql_query = array();
		foreach (QuickBooks_Utilities::listActions('*QUERY*') as $action)
		{
			$sql_query[$action] = QuickBooks_Utilities::priorityForAction($action);
		}
		
		$sql_query = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_query, $callback_config['_only_query'], $callback_config['_dont_query'], QUICKBOOKS_QUERY);
		*/

		//$start = microtime(true);
		//$_start = microtime(true);

		$sql_import = array();
		$tmp = QuickBooks_Utilities::listActions('*IMPORT*');
		//print('0.01 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		foreach ($tmp as $action)
		{
			$sql_import[$action] = QuickBooks_Utilities::priorityForAction($action);
		}
		//print('0.02 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		$sql_import = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_import, $callback_config['_only_import'], $callback_config['_dont_import'], QUICKBOOKS_IMPORT);
		//print('0.03 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		//print('0.1 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		// Which things you want to *add* to QuickBooks (SQL => QuickBooks (adds only!))
		//	@todo These should be changed to use QuickBooks_Utilities::listActions('*ADD*')
		$sql_add = array();
		foreach (QuickBooks_Utilities::listActions('*ADD*') as $action)
		{
			$sql_add[$action] = QuickBooks_Utilities::priorityForAction($action);	
		}
		
		$sql_add = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_add, $callback_config['_only_add'], $callback_config['_dont_add'], QUICKBOOKS_ADD);
			
		//print('0.2 [' . (microtime(true) - $start) . ']' . "\n\n"); 
			
		// Which things you want to *modify* in QuickBooks (SQL => QuickBooks (modifys only!))
		//	@todo These should be changed to use QuickBooks_Utilities::listActions('*MOD*')
		$sql_mod = array();
		foreach (QuickBooks_Utilities::listActions('*MOD*') as $action)
		{
			$sql_mod[$action] = QuickBooks_Utilities::priorityForAction($action);	
		}
		
		$sql_mod = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_mod, $callback_config['_only_modify'], $callback_config['_dont_modify'], QUICKBOOKS_MOD);
		
		//print('0.3 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		// Which things you want to *audit* in QuickBooks (QuickBooks => SQL)
		$sql_audit = array();
		foreach (QuickBooks_Utilities::listActions('*AUDIT*') as $action)
		{
			$sql_audit[$action] = QuickBooks_Utilities::priorityForAction($action);
		}
		
		//print('1 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		// Queueing class
		//$Queue = new QuickBooks_Queue($dsn_or_conn);
		
		// List of all actions we're performing
		$actions = array();
		
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_READONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			//print_r($sql_query);
			//print_r($sql_map);
			
			// Register recurring events for things you want to query
			//foreach ($sql_query as $action => $priority)
			foreach ($sql_import as $action => $priority)
			{
				//$Driver->log('Registering recurring event for: ' . $action, null, QUICKBOOKS_LOG_DEBUG);
				
				// Make sure that there are handlers registered for this recurring action
				if (!isset($sql_map[$action]))
				{
					//trigger_error('No registered handler for: ' . $action);
					continue;
				}
				
				//$Queue->recurring($run_every, $action, md5(__FILE__), $priority, null, $user);
				$Driver->recurEnqueue($user, $run_every, $action, md5(__FILE__), true, $priority);
				
				$actions[] = $action;
			}
			
			if (in_array(QUICKBOOKS_QUERY_DELETEDLISTS, $callback_config['_only_misc']))
			{
				// Also grab any deleted records
				$Driver->queueEnqueue($user, QUICKBOOKS_QUERY_DELETEDLISTS, 1, true, 0);
			}
			
			if (in_array(QUICKBOOKS_QUERY_DELETEDTXNS, $callback_config['_only_misc']))
			{
				$Driver->queueEnqueue($user, QUICKBOOKS_QUERY_DELETEDTXNS, 1, true, 0);
			}
			
			if (in_array(QUICKBOOKS_DERIVE_INVENTORYLEVELS, $callback_config['_only_misc']))
			{
				// Update inventory levels
				$Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVENTORYLEVELS, 1, true, 0);
			}
			
			if (in_array(QUICKBOOKS_DERIVE_INVENTORYASSEMBLYLEVELS, $callback_config['_only_misc']))
			{
				// Update inventory assembly levels
				$Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVENTORYASSEMBLYLEVELS, 1, true, 0);
			}
		}
		
		//print('2 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		/*
		// Queue up the audit requests
		//	Audit requests pull in just the calculated TotalAmount and the 
		//	TimeModified timestamp from transactions, and store these in 
		//	the qbsql_audit_* fields so we can tell if there are transactions 
		//	in QuickBooks that don't match up correctly to what's in the SQL 
		//	database.
		foreach ($sql_audit as $action => $priority)
		{
			if (!isset($sql_map[$action]))
			{
				continue;
			}
			
			$Driver->queueEnqueue($user, $action, 1, true, $priority);
		}
		*/
		
		// Searching the tables can take a long time, you could potentially 
		//	have 10 or 15 seconds between when we search for new customers, and 
		//	when we search for new invoices. What we don't want to have happen 
		//	is that we search for new customers to be queued up, then a new 
		//	customer and invoice gets added, then we search for invoices to be 
		//	queued up, queue up the invoice, but the customer it depends on 
		//	didn't get queued up yet... results in an error! 
		//	
		// We avoid this by keeping track of when NOW is, and only queueing up 
		//	things that are older than NOW()
		$NOW = date('Y-m-d H:i:s');
		
		// Limit to max adds by time per auth
		$time_limit = 15; 		// 60 *second* limit
		$time_start = time();	// When we started
		
		// Objects that need to be *ADDED* to QuickBooks
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			$mark_as_queued = false;
			$map = $Map->adds($sql_add, $mark_as_queued);
			
			//$Driver->log('ADDS: ' . print_r($map, true));
			
			// Go through each action in the returned map
			foreach ($map as $action => $list)
			{
				//$Driver->log('Now doing: ' . $action . ', ' . print_r($list, true));
				
				//$__start = microtime(true);
				
				// Go through each ID for each action
				$counter = 0;
				foreach ($list as $ID => $priority)
				{
					$counter++;
					
					if (time() - $time_start > $time_limit)
					{
						//print('HIT LIMIT SO WE\'RE BREAKING OUT OF HERE [' . $action . '] [ ' . $counter . ' of ' . count($list) . ']!' . "\n");
						break 2;
					}
										// Queue it up to be added to QuickBooks
					$Driver->queueEnqueue($user, $action, $ID, true, $priority);
				}
				
				//print('now ' . $action . ' [' . (microtime(true) - $__start) . ']' . "\n");
			}
		}
		
		//print('3 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		// Objects that need to be *MODIFIED* within QuickBooks
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			// Check if any objects need to be pushed back to QuickBooks 
			foreach ($sql_mod as $action => $priority)
			{
				$object = QuickBooks_Utilities::actionToObject($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);  
				
				//$Driver->log('Searching table: ' . print_r($table_and_field, true) . ' for MODIFIED records.', null, QUICKBOOKS_LOG_DEBUG);
				
				// If we managed to map a table, we need to search that table for changed records
				if (!empty($table_and_field[0]) and !empty($table_and_field[1]))
				{
					// For MODs:
					//	- Do not sync if to_delete = 1
					//	- Do not sync if to_skip = 1
					//	- Do not sync if an error occurred on this record
					
					$sql = "
						SELECT 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . ", 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER . "
						FROM 
							" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " 
						WHERE 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_DISCOVER . " IS NOT NULL AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_RESYNC . " IS NOT NULL AND
							" . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " > " . QUICKBOOKS_DRIVER_SQL_FIELD_RESYNC . " AND
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_DELETE . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_DELETED . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_VOID . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_SKIP . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " <= '" . $NOW . "' ";
						
					$errnum = 0;
					$errmsg = '';
					$res = $Driver->query($sql, $errnum, $errmsg);
					while ($arr = $Driver->fetch($res))
					{
						if (strlen($arr[QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER]))
						{
							// Do not sync this record until the error is resolved
							
							continue;
						}
						
						// Queue up this MOD request
						$Driver->queueEnqueue($user, $action, $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, $priority);
						
						$actions[] = $action;
						
						// Mark the record as enqueued   - let's wait until the hashing is in for this
						//$Driver->query(..., $errnum, $errmsg);
					}
				}
			}
		}		
		
		//print('4 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			// Check if any *voided* objects need to be voided in QuickBooks
			foreach ($sql_add as $action => $priority)
			{
				$object = QuickBooks_Utilities::actionToObject($action);
				
				$dependency = null;
				if ($object == QUICKBOOKS_OBJECT_BILL)
				{
					// Bill VOID dependency is PurchaseOrderMod because we want to be able to manually close POs (but need to VOID the bills first)
					$dependency = QUICKBOOKS_MOD_PURCHASEORDER;
				}
				
				$priority = QuickBooks_Utilities::priorityForAction(QUICKBOOKS_VOID_TRANSACTION, $dependency);
				
				$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);  
				
				//$Driver->log('Searching table: ' . print_r($table_and_field, true) . ' for VOIDED records.', null, QUICKBOOKS_LOG_DEBUG);
				
				if (!empty($table_and_field[0]))
				{
					$sql = "
						SELECT 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . "
						FROM 
							" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " 
						WHERE 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_DISCOVER . " IS NOT NULL AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_DELETE . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_DELETED . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_VOID . " = 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_VOIDED . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " <= '" . $NOW . "' ";
					
					$errnum = 0;
					$errmsg = '';
					$res = $Driver->query($sql, $errnum, $errmsg);
					
					$extra = array(
						'object' => $object, 
						);
					
					while ($arr = $Driver->fetch($res))
					{
						$Driver->queueEnqueue($user, QUICKBOOKS_VOID_TRANSACTION, $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, $priority, $extra);
					}
				}
			}
		}
		
		//print('5 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			// Check if any *deleted* objects need to be deleted from QuickBooks 
			foreach ($sql_add as $action => $priority)
			{
				break;
				
				$priority = 1000 - $priority;
				$object = QuickBooks_Utilities::actionToObject($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);  
				
				// Delete if it's marked for deletion and it hasn't been deleted already
				if (!empty($table_and_field[0]))
				{
					$sql = "
						SELECT 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . "
						FROM 
							" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " 
						WHERE 
							 " . QUICKBOOKS_DRIVER_SQL_FIELD_TO_DELETE . " = 1 AND 
							 " . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_DELETED . " != 1 AND 
							 " . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " <= '" . $NOW . "' ";
						
					$errnum = 0;
					$errmsg = '';
					$res = $Driver->query($sql, $errnum, $errmsg);
					$key = QuickBooks_Utilities::keyForAction($action);
					
					if ($key == 'ListID')
					{
						$useAction = 'ListDel';
					}
					else
					{
						$useAction = 'TxnDel';
					}
					
					$extra['objectType'] = $object;
					while ($arr = $Driver->fetch($res))
					{
						$Driver->queueEnqueue($user, $useAction, $extra['objectType'] . $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, $priority, $extra);
					}
				}
			}
		}
		
		//print('6 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		/*
		// This makes sure that timestamps are set up for every action we're doing (fixes a bug where timestamps never get recorded on initial sync without iterator)
		foreach ($actions as $action)
		{
			$module = __CLASS__;
			
			$key_curr = QuickBooks_Callbacks_SQL_Callbacks::_keySyncCurr($action);
			$key_prev = QuickBooks_Callbacks_SQL_Callbacks::_keySyncPrev($action);
			
			$type = null;
			$opts = null;
			$curr_sync_datetime = $Driver->configRead($user, $module, $key_curr, $type, $opts);	// last sync started... 
			$prev_sync_datetime = $Driver->configRead($user, $module, $key_prev, $type, $opts);	// last sync started... 
			
			$datetime = QuickBooks_Utilities::datetime('1983-01-02');
			
			if (!$curr_sync_datetime)
			{
				$Driver->configWrite($user, $module, $key_curr, $datetime, null);
			}
			
			if (!$prev_sync_datetime)
			{
				$Driver->configWrite($user, $module, $key_prev, $datetime, null);
			}
		}
		*/
		
		//print("\n\n" . 'here [ ' . (microtime(true) - $_start) . ']' . "\n\n\n");
		
		return true;
	}
static public function onAuthenticate($requestID, $user, $hook, &$err, $hook_data, $callback_config)
	{
		// $requestID, $user, $hook, &$err, $hook_data, $callback_config
		
		// Driver instance
		$Driver = QuickBooks_Driver_Singleton::getInstance();
		
		// Map instance 
		$Map = new QuickBooks_Map_Qbxml($Driver);
		
		// Mode (read-onlyl, write-only, read/write)
		$mode = $callback_config['mode'];
		
		// How often recurring events happen
		$run_every = $callback_config['recur'];
		
		// $map parameter
		$sql_map = $callback_config['map'];
		
		// Which things do you want to query? (QuickBooks => SQL database)
		/*
		$sql_query = array();
		foreach (QuickBooks_Utilities::listActions('*QUERY*') as $action)
		{
			$sql_query[$action] = QuickBooks_Utilities::priorityForAction($action);
		}
		
		$sql_query = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_query, $callback_config['_only_query'], $callback_config['_dont_query'], QUICKBOOKS_QUERY);
		*/

		//$start = microtime(true);
		//$_start = microtime(true);

		$sql_import = array();
		$tmp = QuickBooks_Utilities::listActions('*IMPORT*');
		//print('0.01 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		foreach ($tmp as $action)
		{
			$sql_import[$action] = QuickBooks_Utilities::priorityForAction($action);
		}
		//print('0.02 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		$sql_import = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_import, $callback_config['_only_import'], $callback_config['_dont_import'], QUICKBOOKS_IMPORT);
		//print('0.03 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		//print('0.1 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		// Which things you want to *add* to QuickBooks (SQL => QuickBooks (adds only!))
		//	@todo These should be changed to use QuickBooks_Utilities::listActions('*ADD*')
		$sql_add = array();
		foreach (QuickBooks_Utilities::listActions('*ADD*') as $action)
		{
			$sql_add[$action] = QuickBooks_Utilities::priorityForAction($action);	
		}
		
		$sql_add = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_add, $callback_config['_only_add'], $callback_config['_dont_add'], QUICKBOOKS_ADD);
			
		//print('0.2 [' . (microtime(true) - $start) . ']' . "\n\n"); 
			
		// Which things you want to *modify* in QuickBooks (SQL => QuickBooks (modifys only!))
		//	@todo These should be changed to use QuickBooks_Utilities::listActions('*MOD*')
		$sql_mod = array();
		foreach (QuickBooks_Utilities::listActions('*MOD*') as $action)
		{
			$sql_mod[$action] = QuickBooks_Utilities::priorityForAction($action);	
		}
		
		$sql_mod = QuickBooks_Callbacks_SQL_Callbacks::_filterActions($sql_mod, $callback_config['_only_modify'], $callback_config['_dont_modify'], QUICKBOOKS_MOD);
		
		//print('0.3 [' . (microtime(true) - $start) . ']' . "\n\n"); 
		
		// Which things you want to *audit* in QuickBooks (QuickBooks => SQL)
		$sql_audit = array();
		foreach (QuickBooks_Utilities::listActions('*AUDIT*') as $action)
		{
			$sql_audit[$action] = QuickBooks_Utilities::priorityForAction($action);
		}
		
		//print('1 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		// Queueing class
		//$Queue = new QuickBooks_Queue($dsn_or_conn);
		
		// List of all actions we're performing
		$actions = array();
		
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_READONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			//print_r($sql_query);
			//print_r($sql_map);
			
			// Register recurring events for things you want to query
			//foreach ($sql_query as $action => $priority)
			foreach ($sql_import as $action => $priority)
			{
				//$Driver->log('Registering recurring event for: ' . $action, null, QUICKBOOKS_LOG_DEBUG);
				
				// Make sure that there are handlers registered for this recurring action
				if (!isset($sql_map[$action]))
				{
					//trigger_error('No registered handler for: ' . $action);
					continue;
				}
				
				//$Queue->recurring($run_every, $action, md5(__FILE__), $priority, null, $user);
				$Driver->recurEnqueue($user, $run_every, $action, md5(__FILE__), true, $priority);
				
				$actions[] = $action;
			}
			
			if (in_array(QUICKBOOKS_QUERY_DELETEDLISTS, $callback_config['_only_misc']))
			{
				// Also grab any deleted records
				$Driver->queueEnqueue($user, QUICKBOOKS_QUERY_DELETEDLISTS, 1, true, 0);
			}
			
			if (in_array(QUICKBOOKS_QUERY_DELETEDTXNS, $callback_config['_only_misc']))
			{
				$Driver->queueEnqueue($user, QUICKBOOKS_QUERY_DELETEDTXNS, 1, true, 0);
			}
			
			if (in_array(QUICKBOOKS_DERIVE_INVENTORYLEVELS, $callback_config['_only_misc']))
			{
				// Update inventory levels
				$Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVENTORYLEVELS, 1, true, 0);
			}
			
			if (in_array(QUICKBOOKS_DERIVE_INVENTORYASSEMBLYLEVELS, $callback_config['_only_misc']))
			{
				// Update inventory assembly levels
				$Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVENTORYASSEMBLYLEVELS, 1, true, 0);
			}
		}
		
		//print('2 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		/*
		// Queue up the audit requests
		//	Audit requests pull in just the calculated TotalAmount and the 
		//	TimeModified timestamp from transactions, and store these in 
		//	the qbsql_audit_* fields so we can tell if there are transactions 
		//	in QuickBooks that don't match up correctly to what's in the SQL 
		//	database.
		foreach ($sql_audit as $action => $priority)
		{
			if (!isset($sql_map[$action]))
			{
				continue;
			}
			
			$Driver->queueEnqueue($user, $action, 1, true, $priority);
		}
		*/
		
		// Searching the tables can take a long time, you could potentially 
		//	have 10 or 15 seconds between when we search for new customers, and 
		//	when we search for new invoices. What we don't want to have happen 
		//	is that we search for new customers to be queued up, then a new 
		//	customer and invoice gets added, then we search for invoices to be 
		//	queued up, queue up the invoice, but the customer it depends on 
		//	didn't get queued up yet... results in an error! 
		//	
		// We avoid this by keeping track of when NOW is, and only queueing up 
		//	things that are older than NOW()
		$NOW = date('Y-m-d H:i:s');
		
		// Limit to max adds by time per auth
		$time_limit = 15; 		// 60 *second* limit
		$time_start = time();	// When we started
		
		// Objects that need to be *ADDED* to QuickBooks
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			$mark_as_queued = false;
			$map = $Map->adds($sql_add, $mark_as_queued);
			
			//$Driver->log('ADDS: ' . print_r($map, true));
			
			// Go through each action in the returned map
			foreach ($map as $action => $list)
			{
				//$Driver->log('Now doing: ' . $action . ', ' . print_r($list, true));
				
				//$__start = microtime(true);
				
				// Go through each ID for each action
				$counter = 0;
				foreach ($list as $ID => $priority)
				{
					$counter++;
					
					if (time() - $time_start > $time_limit)
					{
						//print('HIT LIMIT SO WE\'RE BREAKING OUT OF HERE [' . $action . '] [ ' . $counter . ' of ' . count($list) . ']!' . "\n");
						break 2;
					}
										// Queue it up to be added to QuickBooks
					$Driver->queueEnqueue($user, $action, $ID, true, $priority);
				}
				
				//print('now ' . $action . ' [' . (microtime(true) - $__start) . ']' . "\n");
			}
		}
		
		//print('3 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		// Objects that need to be *MODIFIED* within QuickBooks
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			// Check if any objects need to be pushed back to QuickBooks 
			foreach ($sql_mod as $action => $priority)
			{
				$object = QuickBooks_Utilities::actionToObject($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);  
				
				//$Driver->log('Searching table: ' . print_r($table_and_field, true) . ' for MODIFIED records.', null, QUICKBOOKS_LOG_DEBUG);
				
				// If we managed to map a table, we need to search that table for changed records
				if (!empty($table_and_field[0]) and !empty($table_and_field[1]))
				{
					// For MODs:
					//	- Do not sync if to_delete = 1
					//	- Do not sync if to_skip = 1
					//	- Do not sync if an error occurred on this record
					
					$sql = "
						SELECT 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . ", 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER . "
						FROM 
							" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " 
						WHERE 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_DISCOVER . " IS NOT NULL AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_RESYNC . " IS NOT NULL AND
							" . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " > " . QUICKBOOKS_DRIVER_SQL_FIELD_RESYNC . " AND
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_DELETE . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_DELETED . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_VOID . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_SKIP . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " <= '" . $NOW . "' ";
						
					$errnum = 0;
					$errmsg = '';
					$res = $Driver->query($sql, $errnum, $errmsg);
					while ($arr = $Driver->fetch($res))
					{
						if (strlen($arr[QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER]))
						{
							// Do not sync this record until the error is resolved
							
							continue;
						}
						
						// Queue up this MOD request
						$Driver->queueEnqueue($user, $action, $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, $priority);
						
						$actions[] = $action;
						
						// Mark the record as enqueued   - let's wait until the hashing is in for this
						//$Driver->query(..., $errnum, $errmsg);
					}
				}
			}
		}		
		
		//print('4 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			// Check if any *voided* objects need to be voided in QuickBooks
			foreach ($sql_add as $action => $priority)
			{
				$object = QuickBooks_Utilities::actionToObject($action);
				
				$dependency = null;
				if ($object == QUICKBOOKS_OBJECT_BILL)
				{
					// Bill VOID dependency is PurchaseOrderMod because we want to be able to manually close POs (but need to VOID the bills first)
					$dependency = QUICKBOOKS_MOD_PURCHASEORDER;
				}
				
				$priority = QuickBooks_Utilities::priorityForAction(QUICKBOOKS_VOID_TRANSACTION, $dependency);
				
				$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);  
				
				//$Driver->log('Searching table: ' . print_r($table_and_field, true) . ' for VOIDED records.', null, QUICKBOOKS_LOG_DEBUG);
				
				if (!empty($table_and_field[0]))
				{
					$sql = "
						SELECT 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . "
						FROM 
							" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " 
						WHERE 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_DISCOVER . " IS NOT NULL AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_DELETE . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_DELETED . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_TO_VOID . " = 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_VOIDED . " != 1 AND 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " <= '" . $NOW . "' ";
					
					$errnum = 0;
					$errmsg = '';
					$res = $Driver->query($sql, $errnum, $errmsg);
					
					$extra = array(
						'object' => $object, 
						);
					
					while ($arr = $Driver->fetch($res))
					{
						$Driver->queueEnqueue($user, QUICKBOOKS_VOID_TRANSACTION, $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, $priority, $extra);
					}
				}
			}
		}
		
		//print('5 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		if ($mode == QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY or 
			$mode == QuickBooks_WebConnector_Server_SQL::MODE_READWRITE)
		{
			// Check if any *deleted* objects need to be deleted from QuickBooks 
			foreach ($sql_add as $action => $priority)
			{
				break;
				
				$priority = 1000 - $priority;
				$object = QuickBooks_Utilities::actionToObject($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);  
				
				// Delete if it's marked for deletion and it hasn't been deleted already
				if (!empty($table_and_field[0]))
				{
					$sql = "
						SELECT 
							" . QUICKBOOKS_DRIVER_SQL_FIELD_ID . "
						FROM 
							" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table_and_field[0] . " 
						WHERE 
							 " . QUICKBOOKS_DRIVER_SQL_FIELD_TO_DELETE . " = 1 AND 
							 " . QUICKBOOKS_DRIVER_SQL_FIELD_FLAG_DELETED . " != 1 AND 
							 " . QUICKBOOKS_DRIVER_SQL_FIELD_MODIFY . " <= '" . $NOW . "' ";
						
					$errnum = 0;
					$errmsg = '';
					$res = $Driver->query($sql, $errnum, $errmsg);
					$key = QuickBooks_Utilities::keyForAction($action);
					
					if ($key == 'ListID')
					{
						$useAction = 'ListDel';
					}
					else
					{
						$useAction = 'TxnDel';
					}
					
					$extra['objectType'] = $object;
					while ($arr = $Driver->fetch($res))
					{
						$Driver->queueEnqueue($user, $useAction, $extra['objectType'] . $arr[QUICKBOOKS_DRIVER_SQL_FIELD_ID], true, $priority, $extra);
					}
				}
			}
		}
		
		//print('6 [' . (microtime(true) - $start) . ']' . "\n\n"); $start = microtime(true);
		
		/*
		// This makes sure that timestamps are set up for every action we're doing (fixes a bug where timestamps never get recorded on initial sync without iterator)
		foreach ($actions as $action)
		{
			$module = __CLASS__;
			
			$key_curr = QuickBooks_Callbacks_SQL_Callbacks::_keySyncCurr($action);
			$key_prev = QuickBooks_Callbacks_SQL_Callbacks::_keySyncPrev($action);
			
			$type = null;
			$opts = null;
			$curr_sync_datetime = $Driver->configRead($user, $module, $key_curr, $type, $opts);	// last sync started... 
			$prev_sync_datetime = $Driver->configRead($user, $module, $key_prev, $type, $opts);	// last sync started... 
			
			$datetime = QuickBooks_Utilities::datetime('1983-01-02');
			
			if (!$curr_sync_datetime)
			{
				$Driver->configWrite($user, $module, $key_curr, $datetime, null);
			}
			
			if (!$prev_sync_datetime)
			{
				$Driver->configWrite($user, $module, $key_prev, $datetime, null);
			}
		}
		*/
		
		//print("\n\n" . 'here [ ' . (microtime(true) - $_start) . ']' . "\n\n\n");
		
		return true;
	}
Hook which gets called when the Web Connector authenticates to the server

@param string $requestID			Not applicable to this hook
@param string $user				The username of the user who connected
@param string $hook				The name of the hook which connected
@param string $err				If an error occurs, you should return an error message here
@param array $hook_data			An array of hook data
@param array $callback_config	An array of callback configuration params
@return boolean