/** * 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 */ 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 = ''; $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 F***S SHIT UP! I HATE YOU INTUIT! // WHAT DOES THIS MEAN?!? "The "address" field has an invalid value "". 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'))) { $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) { $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) { $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)) { $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('&#', '&#', $xml); $xml = str_replace('&amp;', '&', $xml); $xml = str_replace('&quot;', '"', $xml); $xml .= '</' . QuickBooks_Utilities::actionToRequest($action) . '> </QBXMLMsgsRq> </QBXML>'; return $xml; }