QuickBooks/Callbacks/SQL/Callbacks.php
protected static function _AddRequest($type, $Object, $requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale, $config = array())
	{
		// Driver instance... 
		$Driver = QuickBooks_Driver_Singleton::getInstance();
		
		$type = strtolower($type);
		
		// This should actually always happen now that we fixed the Driver->get method to return an array
		if (!is_object($Object) and 
			is_array($Object))
		{
			$Object = new QuickBooks_SQL_Object(null, null, $Object);
		}
		
		$xml = '';
		
		$xml .= '<?xml version="1.0" encoding="utf-8"?>' . QUICKBOOKS_CRLF;
		$xml .= '<?qbxml version="' . QuickBooks_Callbacks_SQL_Callbacks::_version($version, $locale) . '"?>' . QUICKBOOKS_CRLF;
		$xml .= "\t" . '<QBXML>' . QUICKBOOKS_CRLF;
		$xml .= "\t" . "\t" . '<QBXMLMsgsRq onError="' . QUICKBOOKS_SERVER_SQL_ON_ERROR . '">' . QUICKBOOKS_CRLF;
		$xml .= "\t" . "\t" . "\t" . '<' . QuickBooks_Utilities::actionToRequest($action) . ' requestID="' . $requestID . '">' . QUICKBOOKS_CRLF;
		
		$file = '/QuickBooks/QBXML/Schema/Object/' . QuickBooks_Utilities::actionToRequest($action) . '.php';
		$class = 'QuickBooks_QBXML_Schema_Object_' . QuickBooks_Utilities::actionToRequest($action);
		
		QuickBooks_Loader::load($file);
		$schema_object = new $class();
		
		//print_r($Object->asArray());
		//exit;
		
		$Node = new QuickBooks_XML_Node($action);
		foreach ($Object->asArray() as $field => $value)	// map the SQL fields back to XML
		{
			$map = '';
			$others = array();
			QuickBooks_SQL_Schema::mapToSchema($type . '.' . $field, QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $map, $others);
			
			// Some drivers case fold all of the field names, which means that 
			//	we can't depend on the field names to use in our XML requests 
			// 
			// If that happens, we need to go over to the schema object and 
			//	try to fetch the correct, unfolded XML node name and set that 
			//	instead.
			$unmapped = null;
			if ($Driver->foldsToLower())
			{
				$unmapped = $map;
				
				$retpos = strpos($map, 'Ret ');
				$retval = substr($map, 0, $retpos + 4);
				
				$map = substr($map, $retpos + 4);
				$map = $retval . $schema_object->unfold($map);
			}
			
			//print('dealing with: [' . $map . '] unmapped from (' . $unmapped . ')' . "\n");
			
			if (!$map)
			{
				// This schema field doesn't map to anything in QuickBooks...
				continue;
			}
			else if (!strlen($value))
			{
				// There's no value there, don't send it
				
				// There are some special cases here... addresses commonly get 
				//	changes to set blank lines for some of the address lines, 
				//	and we need to send these blank values to overwrite the 
				//	existing values in QuickBooks.
				//
				// Here, we send these blank address lines only if at least one 
				//	address line is being sent.
				$begi = substr($field, 0, -5);
				$last = substr($field, -5, 5);
				if (($last == 'Addr2' or $last == 'Addr3' or $last == 'Addr4' or $last == 'Addr5') and 
					strlen($Object->get($begi . 'Addr1')))
				{
					// ... but don't allow 4 or 5 if they set the city, state, zip, or country?
					//  EDIT: NEVER ALLOW ADDR4 OR ADDR5, IT JUST FUCKS SHIT UP! I HATE YOU INTUIT!
					//   WHAT DOES THIS MEAN?!?   "The &quot;address&quot; field has an invalid value &quot;&quot;.  QuickBooks error message: The parameter is incorrect."
					if (($last == 'Addr4' or $last == 'Addr5'))
					{
						continue;
					}
					
					/* and 
						( strlen($Object->get($begi . 'City')) or strlen($Object->get($begi . 'State')) or strlen($Object->get($begi . 'Country')) or strlen($Object->get($begi . 'PostalCode')) ))
					{
						continue;
					}
					*/

					; // <Judge Roy Snyder>... I'll allow it! 
				}
				else
				{
					continue;
				}
			}
			else
			{
				if ($value == QUICKBOOKS_SERVER_SQL_VALUE_CLEAR)
				{
					$value = '';
				}
				
				$use_abbrevs = false;
				$htmlspecialchars = true;
				
				//print('THIS RAN [' . $value . ']');
				
				$value = QuickBooks_Cast::cast(
					null, 
					null, 
					$value, 
					$use_abbrevs, 
					$htmlspecialchars);
					
				//print(' => [' . $value . ']' . "\n");
			}
			
			// Special handling for non-US versions of QuickBooks
			
			$begi = substr($field, 0, -5);
			$last = substr($field, -5, 5);
			if ($locale == QUICKBOOKS_LOCALE_UK and 
				$last == 'State' and 
				!strlen($Object->get($begi . 'County')))		// UK *County* support instead of states
			{
				$Object->set($begi . 'County', $value);
				//print_r($map);
				$map = substr($map, 0, -5) . 'County';
				//die();
			}
						
			// OK, the paths look like this: 
			// 	CustomerRet FirstName
			//	
			// We don't need the 'CustomerRet' part of it, that's actually incorrect, so we'll strip it off
			$explode = explode(' ', $map);
			$first = trim(current($explode));
			$map = trim(implode(' ', array_slice($explode, 1)));
			
			if (stripos($action, 'add') !== false)
			{
				$map = str_replace('Ret', 'Add', $map);
			}
			else
			{
				$map = str_replace('Ret', 'Mod', $map);
			}
			
			//print('	OK, handling [' . $map . ']' . "\n");
			
			if (false === strpos($map, ' '))
			{
				if ($schema_object->exists($map))
				{
					$use_in_request = true;
					
					// If this version doesn't support this field, skip it
					if ($schema_object->sinceVersion($map) > $version and 
						$schema_object->sinceVersion($map) < 100.0)			// For some reason I set the ->sinceVersion to return 999.99 if we don't know the support version...?
					{
						$use_in_request = false;
					}
					
					switch ($schema_object->dataType($map))
					{
						case 'AMTTYPE':
							
							$value = str_replace(',', '', number_format($value, 2));
							
							break;
						case 'DATETYPE':
							
							if (!$value or $value == '0000-00-00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::date($value);
							}
							
							break;
						case 'DATETIMETYPE':
							
							if (!$value or $value == '0000-00-00 00:00:00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::datetime($value);
							}
							
							break;
						case 'BOOLTYPE':
							
							if ($value == 1)
							{
								$value = 'true';
							}
							else if ($value == 0)
							{
								$value = 'false';
							}
							else
							{
								$use_in_request = false;
							}
							
							break;
						default:
							break;
					}
					
					if ($use_in_request)
					{
						$Child = new QuickBooks_XML_Node($map);
						$Child->setData($value);
						$Node->addChild($Child);
					}
				}
				else
				{
					; // ignore it 
				}
			}
			else
			{
				$parts = explode(' ', $map);
				foreach($parts as $key => $part)
				{
					if (stripos($action, 'Mod') !== false)
					{
						if ($part == 'SalesAndPurchase' or 
						   $part == 'SalesOrPurchase')
						{
							$parts[$key] = $part . 'Mod';
						}
					}
					else if (stripos($action, 'Add') !== false)
					{
						
					}
				}
				
				$map = implode(' ', $parts);
				if ($schema_object->exists($map))
				{
					$use_in_request = true;
					
					// If this version doesn't support this field, skip it
					if ($schema_object->sinceVersion($map) > $version and 
						$schema_object->sinceVersion($map) < 100.0)			// For some reason I set the ->sinceVersion to return 999.99 if we don't know the support version...?
					{
						$use_in_request = false;
					}
					
					switch ($schema_object->dataType($map))
					{
						case 'AMTTYPE':
							
							$value = str_replace(',', '', number_format($value, 2));
							
							break;
						case 'DATETYPE':
							
							if (!$value or $value == '0000-00-00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::date($value);
							}
							
							break;
						case 'DATETIMETYPE':
							
							if (!$value or $value == '0000-00-00 00:00:00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::datetime($value);
							}
							
							break;							
						case 'BOOLTYPE':
							
							if ($value == 1)
							{
								$value = 'true';
							}
							else if ($value == 0)
							{
								$value = 'false';
							}
							else
							{
								$use_in_request = false;
							}
							
							break;
						default:
							break;
					}
					
					if ($use_in_request)
					{
						$Node->setChildDataAt($action . ' ' . $map, $value, true);
					}
				}
			}
		}
		
		// Get the child tables here - make sure they're in the proper order for the xml schema
		$children = QuickBooks_Callbacks_SQL_Callbacks::_getChildTables(strtolower($type));
		
		//print('for type: [' . $type . ']');
		//print_r($children);
		//exit;
		
		if (!empty($children)) // Get the rows
		{
			$data = QuickBooks_Callbacks_SQL_Callbacks::_queryChildren($children, $Object->get($children[0]['parent']));
		}
		else
		{
			// @todo Why not just assign an empty array here...? 
			$data = $children;
		}
			
		$nodes = QuickBooks_Callbacks_SQL_Callbacks::_childObjectsToXML(strtolower($type), $action, $data);
		
		//print('NODES:');
		//print_r($nodes);
		//exit;
		
		if (count($nodes))
		{
			foreach ($nodes as $nd)
			{
				$Node->addChild($nd);
			}
		}
		else
		{
			// If we're adding a payment, but not applying it to anything, set IsAutoApply to TRUE
			if ($action == QUICKBOOKS_ADD_RECEIVEPAYMENT)
			{
				$Node->setChildDataAt($action . ' IsAutoApply', 'true', true);
			}
			else if ($action == QUICKBOOKS_MOD_RECEIVEPAYMENT)
			{
				
			}
		}
		
		//print('ACTION IS: [' . $action . ']');
		
		//print_r($Node);
		
		$xml .= $Node->asXML(QuickBooks_XML::XML_PRESERVE);
		
		// Bad hack... 
		$xml = str_replace('&amp;#', '&#', $xml);
		$xml = str_replace('&amp;amp;', '&amp;', $xml);
		$xml = str_replace('&amp;quot;', '&quot;', $xml); 
		
		$xml .= '</' . QuickBooks_Utilities::actionToRequest($action) .'>
				</QBXMLMsgsRq>
			</QBXML>';
			
		return $xml;
	}
protected static function _AddRequest($type, $Object, $requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale, $config = array())
	{
		// Driver instance... 
		$Driver = QuickBooks_Driver_Singleton::getInstance();
		
		$type = strtolower($type);
		
		// This should actually always happen now that we fixed the Driver->get method to return an array
		if (!is_object($Object) and 
			is_array($Object))
		{
			$Object = new QuickBooks_SQL_Object(null, null, $Object);
		}
		
		$xml = '';
		
		$xml .= '<?xml version="1.0" encoding="utf-8"?>' . QUICKBOOKS_CRLF;
		$xml .= '<?qbxml version="' . QuickBooks_Callbacks_SQL_Callbacks::_version($version, $locale) . '"?>' . QUICKBOOKS_CRLF;
		$xml .= "\t" . '<QBXML>' . QUICKBOOKS_CRLF;
		$xml .= "\t" . "\t" . '<QBXMLMsgsRq onError="' . QUICKBOOKS_SERVER_SQL_ON_ERROR . '">' . QUICKBOOKS_CRLF;
		$xml .= "\t" . "\t" . "\t" . '<' . QuickBooks_Utilities::actionToRequest($action) . ' requestID="' . $requestID . '">' . QUICKBOOKS_CRLF;
		
		$file = '/QuickBooks/QBXML/Schema/Object/' . QuickBooks_Utilities::actionToRequest($action) . '.php';
		$class = 'QuickBooks_QBXML_Schema_Object_' . QuickBooks_Utilities::actionToRequest($action);
		
		QuickBooks_Loader::load($file);
		$schema_object = new $class();
		
		//print_r($Object->asArray());
		//exit;
		
		$Node = new QuickBooks_XML_Node($action);
		foreach ($Object->asArray() as $field => $value)	// map the SQL fields back to XML
		{
			$map = '';
			$others = array();
			QuickBooks_SQL_Schema::mapToSchema($type . '.' . $field, QUICKBOOKS_SQL_SCHEMA_MAP_TO_XML, $map, $others);
			
			// Some drivers case fold all of the field names, which means that 
			//	we can't depend on the field names to use in our XML requests 
			// 
			// If that happens, we need to go over to the schema object and 
			//	try to fetch the correct, unfolded XML node name and set that 
			//	instead.
			$unmapped = null;
			if ($Driver->foldsToLower())
			{
				$unmapped = $map;
				
				$retpos = strpos($map, 'Ret ');
				$retval = substr($map, 0, $retpos + 4);
				
				$map = substr($map, $retpos + 4);
				$map = $retval . $schema_object->unfold($map);
			}
			
			//print('dealing with: [' . $map . '] unmapped from (' . $unmapped . ')' . "\n");
			
			if (!$map)
			{
				// This schema field doesn't map to anything in QuickBooks...
				continue;
			}
			else if (!strlen($value))
			{
				// There's no value there, don't send it
				
				// There are some special cases here... addresses commonly get 
				//	changes to set blank lines for some of the address lines, 
				//	and we need to send these blank values to overwrite the 
				//	existing values in QuickBooks.
				//
				// Here, we send these blank address lines only if at least one 
				//	address line is being sent.
				$begi = substr($field, 0, -5);
				$last = substr($field, -5, 5);
				if (($last == 'Addr2' or $last == 'Addr3' or $last == 'Addr4' or $last == 'Addr5') and 
					strlen($Object->get($begi . 'Addr1')))
				{
					// ... but don't allow 4 or 5 if they set the city, state, zip, or country?
					//  EDIT: NEVER ALLOW ADDR4 OR ADDR5, IT JUST FUCKS SHIT UP! I HATE YOU INTUIT!
					//   WHAT DOES THIS MEAN?!?   "The &quot;address&quot; field has an invalid value &quot;&quot;.  QuickBooks error message: The parameter is incorrect."
					if (($last == 'Addr4' or $last == 'Addr5'))
					{
						continue;
					}
					
					/* and 
						( strlen($Object->get($begi . 'City')) or strlen($Object->get($begi . 'State')) or strlen($Object->get($begi . 'Country')) or strlen($Object->get($begi . 'PostalCode')) ))
					{
						continue;
					}
					*/

					; // <Judge Roy Snyder>... I'll allow it! 
				}
				else
				{
					continue;
				}
			}
			else
			{
				if ($value == QUICKBOOKS_SERVER_SQL_VALUE_CLEAR)
				{
					$value = '';
				}
				
				$use_abbrevs = false;
				$htmlspecialchars = true;
				
				//print('THIS RAN [' . $value . ']');
				
				$value = QuickBooks_Cast::cast(
					null, 
					null, 
					$value, 
					$use_abbrevs, 
					$htmlspecialchars);
					
				//print(' => [' . $value . ']' . "\n");
			}
			
			// Special handling for non-US versions of QuickBooks
			
			$begi = substr($field, 0, -5);
			$last = substr($field, -5, 5);
			if ($locale == QUICKBOOKS_LOCALE_UK and 
				$last == 'State' and 
				!strlen($Object->get($begi . 'County')))		// UK *County* support instead of states
			{
				$Object->set($begi . 'County', $value);
				//print_r($map);
				$map = substr($map, 0, -5) . 'County';
				//die();
			}
						
			// OK, the paths look like this: 
			// 	CustomerRet FirstName
			//	
			// We don't need the 'CustomerRet' part of it, that's actually incorrect, so we'll strip it off
			$explode = explode(' ', $map);
			$first = trim(current($explode));
			$map = trim(implode(' ', array_slice($explode, 1)));
			
			if (stripos($action, 'add') !== false)
			{
				$map = str_replace('Ret', 'Add', $map);
			}
			else
			{
				$map = str_replace('Ret', 'Mod', $map);
			}
			
			//print('	OK, handling [' . $map . ']' . "\n");
			
			if (false === strpos($map, ' '))
			{
				if ($schema_object->exists($map))
				{
					$use_in_request = true;
					
					// If this version doesn't support this field, skip it
					if ($schema_object->sinceVersion($map) > $version and 
						$schema_object->sinceVersion($map) < 100.0)			// For some reason I set the ->sinceVersion to return 999.99 if we don't know the support version...?
					{
						$use_in_request = false;
					}
					
					switch ($schema_object->dataType($map))
					{
						case 'AMTTYPE':
							
							$value = str_replace(',', '', number_format($value, 2));
							
							break;
						case 'DATETYPE':
							
							if (!$value or $value == '0000-00-00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::date($value);
							}
							
							break;
						case 'DATETIMETYPE':
							
							if (!$value or $value == '0000-00-00 00:00:00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::datetime($value);
							}
							
							break;
						case 'BOOLTYPE':
							
							if ($value == 1)
							{
								$value = 'true';
							}
							else if ($value == 0)
							{
								$value = 'false';
							}
							else
							{
								$use_in_request = false;
							}
							
							break;
						default:
							break;
					}
					
					if ($use_in_request)
					{
						$Child = new QuickBooks_XML_Node($map);
						$Child->setData($value);
						$Node->addChild($Child);
					}
				}
				else
				{
					; // ignore it 
				}
			}
			else
			{
				$parts = explode(' ', $map);
				foreach($parts as $key => $part)
				{
					if (stripos($action, 'Mod') !== false)
					{
						if ($part == 'SalesAndPurchase' or 
						   $part == 'SalesOrPurchase')
						{
							$parts[$key] = $part . 'Mod';
						}
					}
					else if (stripos($action, 'Add') !== false)
					{
						
					}
				}
				
				$map = implode(' ', $parts);
				if ($schema_object->exists($map))
				{
					$use_in_request = true;
					
					// If this version doesn't support this field, skip it
					if ($schema_object->sinceVersion($map) > $version and 
						$schema_object->sinceVersion($map) < 100.0)			// For some reason I set the ->sinceVersion to return 999.99 if we don't know the support version...?
					{
						$use_in_request = false;
					}
					
					switch ($schema_object->dataType($map))
					{
						case 'AMTTYPE':
							
							$value = str_replace(',', '', number_format($value, 2));
							
							break;
						case 'DATETYPE':
							
							if (!$value or $value == '0000-00-00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::date($value);
							}
							
							break;
						case 'DATETIMETYPE':
							
							if (!$value or $value == '0000-00-00 00:00:00')
							{
								$use_in_request = false;
							}
							else
							{
								$value = QuickBooks_Utilities::datetime($value);
							}
							
							break;							
						case 'BOOLTYPE':
							
							if ($value == 1)
							{
								$value = 'true';
							}
							else if ($value == 0)
							{
								$value = 'false';
							}
							else
							{
								$use_in_request = false;
							}
							
							break;
						default:
							break;
					}
					
					if ($use_in_request)
					{
						$Node->setChildDataAt($action . ' ' . $map, $value, true);
					}
				}
			}
		}
		
		// Get the child tables here - make sure they're in the proper order for the xml schema
		$children = QuickBooks_Callbacks_SQL_Callbacks::_getChildTables(strtolower($type));
		
		//print('for type: [' . $type . ']');
		//print_r($children);
		//exit;
		
		if (!empty($children)) // Get the rows
		{
			$data = QuickBooks_Callbacks_SQL_Callbacks::_queryChildren($children, $Object->get($children[0]['parent']));
		}
		else
		{
			// @todo Why not just assign an empty array here...? 
			$data = $children;
		}
			
		$nodes = QuickBooks_Callbacks_SQL_Callbacks::_childObjectsToXML(strtolower($type), $action, $data);
		
		//print('NODES:');
		//print_r($nodes);
		//exit;
		
		if (count($nodes))
		{
			foreach ($nodes as $nd)
			{
				$Node->addChild($nd);
			}
		}
		else
		{
			// If we're adding a payment, but not applying it to anything, set IsAutoApply to TRUE
			if ($action == QUICKBOOKS_ADD_RECEIVEPAYMENT)
			{
				$Node->setChildDataAt($action . ' IsAutoApply', 'true', true);
			}
			else if ($action == QUICKBOOKS_MOD_RECEIVEPAYMENT)
			{
				
			}
		}
		
		//print('ACTION IS: [' . $action . ']');
		
		//print_r($Node);
		
		$xml .= $Node->asXML(QuickBooks_XML::XML_PRESERVE);
		
		// Bad hack... 
		$xml = str_replace('&amp;#', '&#', $xml);
		$xml = str_replace('&amp;amp;', '&amp;', $xml);
		$xml = str_replace('&amp;quot;', '&quot;', $xml); 
		
		$xml .= '</' . QuickBooks_Utilities::actionToRequest($action) .'>
				</QBXMLMsgsRq>
			</QBXML>';
			
		return $xml;
	}
Send an *Add request to QuickBooks to add an object to the QuickBooks database

@param string $type			The QuickBooks object type
@param object $Object		The object to add to QuickBooks
@param string $requestID		The requestID to use in the qbXML request
@param string $user			The username of the Web Connector user
@param string $action		The action type (example: "CustomerAdd", QUICKBOOKS_ADD_CUSTOMER)
@param string $ID			The qbsql_id value of the object to add
@param mixed $extra			Any extra data
@param string $err			Set this to an error message if an error occurs
@param integer $last_action_time			The UNIX timestamp indicating the last time this type of action occured
@param integer $last_actionident_time	The UNIX timestamp indicating the (last time this type of action and $ID value) occured
@param float $version		The maximum qbXML version supports
@param string $locale		The QuickBooks locale (example: "US")
@param array $config			Callback configuration information
@return string				The qbXML string