Skip to content

Instantly share code, notes, and snippets.

@Twipped
Created June 26, 2012 20:08
Show Gist options
  • Select an option

  • Save Twipped/2998567 to your computer and use it in GitHub Desktop.

Select an option

Save Twipped/2998567 to your computer and use it in GitHub Desktop.
InfusionSoft API Class
<?php
namespace InfusionSoft;
use \SimpleXMLElement, \DateTime;
class API {
public $key = '';
public $application = '';
protected $rpc_domain = 'infusionsoft.com';
protected $rpc_endpoint = '/api/xmlrpc';
protected $xmlroot;
protected $xmlparams;
public $response;
public $raw_response;
public $result = false;
public $fault = false;
protected $class;
protected $function;
protected $arguments;
function __construct($class = null, $function = null) {
if ($class !== null) {
$this->_setClass($class);
}
if ($function !== null) {
$this->_setFunction($function);
}
$this->xmlroot = new SimpleXMLElement('<methodCall></methodCall>');
$this->xmlroot->methodName = "{$this->class}.{$this->function}";
// $this->xmlparams = $this->root->addChild('params');
}
public static function __callStatic($name, $arguments) {
$o = new static($name);
//if anything arguments were passed, assume first argument is the API key.
if (!empty($arguments)) {
$o->key = array_shift($arguments);
}
return $o;
}
public function __call($called_name, $called_arguments) {
//verify Class name
if (!$this->class || !isset(static::$classes[$this->class])) {
throw new InvalidClassException("No or Unknown InfusionSoft class defined.");
}
$this->_setFunction($called_name);
$this->_setArguments($called_arguments);
$this->_buildParams();
// echo $this->xmlroot->asXML(), "\n-------------------------\n";
$this->_makeRequest();
// var_dump($this->raw_response, $this->response);
$this->result = $this->_parseResponse();
if (count($this->result)===1) return reset($this->result);
else return $this->result;
}
protected function _setClass($name) {
if (!isset(static::$classes[$name])) {
throw new InvalidClassException("Unknown InfusionSoft class: $name");
}
$this->class = $name;
$this->xmlroot->methodName = "{$this->class}.{$this->function}";
}
protected function _setFunction($name) {
//verify Function name
if (!isset(static::$classes[$this->class][$name])) {
throw new InvalidFunctionException("Unknown function name: $name");
}
$this->function = $name;
$this->xmlroot->methodName = "{$this->class}.{$this->function}";
}
protected function _setArguments($input) {
// $schema = static::$classes[$this->class][$this->function];
//
// //if the passed data is not an index array, handle it as if it were named parameters
// if (count($input)===1 && is_array($input[0]) && array_values($input[0]) !== $input[0]) {
//
// $input = array_shift($input);
// $arguments = array_fill(0, count($schema), null);
//
// //loop through schema keys. if a value is defined for the key, place it in the correct position in the arguments.
// foreach (array_flip(array_keys($schema)) as $key=>$index) {
// if (isset($called_arguments[$key])) {
// $arguments[$index] = $called_arguments[$key];
// }
// }
// } else {
// //data was not a keyed array, so we use as is.
// $arguments = $input;
// }
$this->arguments = $input;
}
protected function _buildParams() {
$schema = static::$classes[$this->class][$this->function];
$types = array_values($schema);
$this->xmlroot->params = new SimpleXMLElement("<params></params>");
$this->xmlroot->params->addChild('param')->addChild('value')->string = $this->key;
foreach ($types as $index=>$type) {
if (!isset($this->arguments[$index])) continue;
$argument = $this->arguments[$index];
$value = $this->xmlroot->params->addChild('param')->addChild('value');
switch ($type) {
case 'int':
case 'i4':
$value->int = (int)$argument;
break;
case 'double':
$value->double = (float)$argument;
break;
case 'boolean':
$value->boolean = (boolean)$argument;
break;
case 'dateTime':
case 'dateTime.iso8601':
$value->{'dateTime.iso8601'} = static::ParseDate($argument);
break;
case 'base64':
$value->base64 = base64_encode($argument);
break;
case 'array-int':
var_dump($this->arguments);
$struct = $value->addChild('array')->addChild('data');
foreach ($argument as $arg_data) {
$struct->addChild('value')->int = (int)$arg_data;
}
break;
case 'array-string':
$struct = $value->addChild('array')->addChild('data');
foreach ($argument as $arg_data) {
$struct->addChild('value')->string = (string)$arg_data;
}
break;
case 'struct':
$struct = $value->addChild('struct');
foreach ($argument as $arg_index => $arg_data) {
$member = $struct->addChild('member');
$member->name = $arg_index;
if (is_numeric($arg_data)) {
$member->addChild('value')->int = $arg_data;
} else {
$member->addChild('value')->string = (string)$arg_data;
}
}
break;
case 'string':
default:
$value->string = (string)$argument;
break;
}
}
}
protected function _makeRequest() {
$url = "https://{$this->application}.{$this->rpc_domain}{$this->rpc_endpoint}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-type: text/xml; charset=UTF-8"));
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->xmlroot->asXML());
curl_setopt($ch, CURLOPT_FAILONERROR,true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$this->raw_response = curl_exec($ch);
if ($this->raw_response===false) {
if ($err = curl_error($ch)) {
throw new CURLException($err);
}
return false;
}
curl_close($ch);
$this->response = new SimpleXMLElement($this->raw_response);
return true;
}
protected function _parseResponse() {
if (isset($this->response->params)) {
$result = array();
foreach ($this->response->params->param as $param) {
foreach ($param->value->children() as $child) {
$result[] = static::CrawlRPC($child);
}
}
return $result;
} elseif (isset($this->response->fault)) {
//$this->fault = (array)$this->response->fault->value->struct->member;
$message = "API produced an unknown fault response.";
foreach ($this->response->fault->value->struct->member as $arr) {
if ($arr->name == 'faultString') {
$message = $arr->value;
break;
}
}
throw new APIFault($message);
}
}
protected static function CrawlRPC($input) {
switch ($input->getName()) {
case 'array':
$result = array();
foreach ($input->data[0] as $value) {
foreach ($value as $val) $result[] = static::CrawlRPC($val);
}
return $result;
case 'base64':
return base64_decode($input[0]);
case 'boolean':
return (bool)$input[0];
case 'dateTime.iso8601':
return new DateTime($input[0]);
case 'double':
return (float)$input[0];
case 'i4':
case 'int':
return (int)$input[0];
case 'string':
return (string)$input[0];
case 'struct':
$result = array();
foreach ($input->member as $member) {
$result[(string)$member->name] = (string)$member->value;
}
return $result;
case 'nil':
return null;
default:
var_dump($input);
}
}
/**
* Internal function for parsing passed date values into mysql compatible values
*
* @param string $input
* @return void
* @author Jarvis Badgley
*/
protected static function ParseDate($input) {
if ($input) {
if (!($input instanceof DateTime)) {
if (is_string($input)) {
if (strtolower($input) === 'now') {
$input = new DateTime();
} else {
try {
$input = new DateTime($input);
} catch (Exception $e) {
return null;
}
}
} elseif (is_integer($input) && $input>0) {
$input = new DateTime();
$input->setTimestamp($input);
} else {
return null;
}
}
return $input->format('Ymd\TH:i:s');
}
return null;
}
static $classes = array(
'AffiliateService' => array(
"affCrawlbacks" => array(
'affiliateId' =>'int', //The Id number for the affiliate record you would like the claw backs for
'filterStartDate' =>'dateTime', //The starting date for the date range which you would like affiliate claw backs for
'filterEndDate' =>'dateTime', //The ending date for the date range which you would like the affiliate claw backs for
),
"affCommissions" => array(
'affiliateId' =>'int', //The Id number for the affiliate record you would like the claw backs for
'filterStartDate' =>'dateTime', //The starting date for the date range which you would like affiliate commissions for
'filterEndDate' =>'dateTime', //The ending date for the date range which you would like the affiliate commissions for
),
"affPayouts" => array(
'affiliateId' =>'int', //The Id number for the affiliate record you would like the claw backs for
'filterStartDate' =>'dateTime', //The starting date for the date range which you would like affiliate payments for
'filterEndDate' =>'dateTime', //The ending date for the date range which you would like the affiliate payments for
),
"affRunningTotals" => array(
'affiliateIds' =>'array-int', //An integer array of the affiliate Id numbers that you would like the balances for
),
"affSummary" => array(
'affiliateId' =>'int', //An array of Affiliate Id numbers you would like stats for
'filterStartDate' =>'dateTime', //The starting date for the date range which you would like affiliate stats for
'filterEndDate' =>'dateTime', //The ending date for the date range which you would like the affiliate stats for
),
),
'ContactService' => array(
"add" => array(
'data' =>'struct' //an associative array of the data for this new contact record. The array key is the field name to store the value within
),
"addWithDupCheck" => array(
'data' =>'struct', //an associative array of the data for this new contact record. The array key is the field name to store the value within
'dupCheckType' =>'string', //Determines how to consider a duplicate record. Options: 'Email', 'EmailAndName', 'EmailAndNameAndCompany'
),
"addToCampaign" => array(
'contactId' =>'int', //the contact Id number you would like to start the follow-up sequence for
'campaignId' =>'int', //the Id number of the follow-up sequence you would like to start
),
"addToGroup" => array(
'contactId' =>'int', //the contact Id number you would like to start the follow-up sequence for
'groupId' =>'int', //the Id number of the group to be added to
),
"findByEmail" => array(
'email' =>'email', //The email address to search contacts by
'selectedFields' =>'array-string', //The contact fields you would like returned
),
"load" => array(
'contactId' =>'int', //The Id number of the contact you would like to load data from
'selectedFields' =>'array-string' //An array of strings where each string is the database field name of the field you would like sent back
),
"getNextCampaignStep" => array(
'contactId' =>'int', //the Id number of the contact record you would like to get the next sequence step for
'campaignId' =>'int', //the follow-up sequence Id number you would like to get the next step for the given contact
),
"pauseCampaign" => array(
'contactId' =>'int', //The Id number of the contact record you are pausing the sequence on
'campaignId' =>'int', //The follow-up sequence Id number
),
"removeFromCampaign" => array(
'contactId' =>'int', //the Id number of the contact record you would like to get the next sequence step for
'campaignId' =>'int', //the follow-up sequence Id number you would like to get the next step for the given contact
),
"removeFromGroup" => array(
'contactId' =>'int', //the Id number of the contact record you would like to get the next sequence step for
'groupID' =>'int', //The group Id number.
),
"runActionSequence" => array(
'contactId' =>'int', //the Id number of the contact record you would like to get the next sequence step for
'actionSetId' =>'int' //The Id number of the action set you would like to run. This is found on the Setup->Action sets menu
),
"update" => array(
'contactId' =>'int', //the Id number of the contact record you would like to get the next sequence step for
'data' =>'struct', //an associative array of the data for this new contact record. The array key is the field name to store the value within
),
),
'DataService' => array(
"add" => array(
'table' =>'string', //The Infusionsoft database table name
'values' =>'struct' //An associative array of data you would like stored into this new row in the table
),
"load" => array(
'table' =>'string', //The Infusionsoft database table name
'recordId' =>'int', //The unique Id number for the record you would like to load
'wantedFields' =>'array-string', //The fields you would like returned from this row in the database
),
"update" => array(
'table' =>'string', //The Infusionsoft database table name
'recordId' =>'int', //The unique Id number for the record you would like to load
'data' =>'struct', //an associative array of the data for this new contact record. The array key is the field name to store the value within
),
"delete" => array(
'table' =>'string', //The Infusionsoft database table name
'recordId' =>'int', //The unique Id number for the record you would like to load
),
"findByField" => array(
'table' =>'string', //The Infusionsoft database table name
'limit' =>'string', //How many records you would like returned. The maximum possible is 1000
'page' =>'string', //The page of results you would like returned. The first page is page 0 (loop through pages to get more than 1000 records)
'fieldName' =>'string', //The name of the field you would like to run the search on
'fieldValue' =>'string', //The value stored in the field you would like to search by
'returnFields' =>'array-string' //The fields you would like returned from the table you are searching on
),
"query" => array(
'table' =>'string', //The Infusionsoft database table name
'limit' =>'int', //How many records you would like returned. The maximum possible is 1000
'page' =>'int', //The page of results you would like returned. The first page is page 0 (loop through pages to get more than 1000 records)
'queryData' =>'struct', //A struct containing query data. The key is the field to search on, and the value is the data to look for. % is the wild card operator and all searches are case insensitive. If you would like to query for an empty(null) field, use ~null~ in your query parameter, such as ‘FirstName' => ‘~null~'
'returnFields' =>'array-string' //The fields you would like returned from the table you are searching on
),
"addCustomField" => array(
'customFieldType' =>'string', //Where the custom field will be used inside Infusionsoft. Options include Contact, Company, Affiliate, Task/Appt/Note, Order, Subscription, or Opportunity
'displayName' =>'string', //The label/name of this new custom field
'dataType' =>'string', //What type of data this field will support. Text, Dropdown, TextArea, etc.
'headerId' =>'int', //Which custom field header you want this field to appear under. Customer headers are located on custom tabs
),
"authenticateUser" => array(
'username' =>'string', //The Infusionsoft username
'passwordHash' =>'string', //An MD5 hash of the Infusionsoft users' password
),
"getAppSetting" => array(
'module' =>'string', //The application module this setting is a part of
'setting' =>'string', //The database name of the setting you would like the values returned for
),
"updateCustomField" => array(
'customFieldId' =>'int', //The Id number of the custom field you would like to update
'values' =>'struct', //The preset values for the given custom field
),
),
'DiscountService' => array(
),
'EmailService' => array(
"addEmailTemplate" => array(
'pieceTitle' =>'string', //The name of the template. This will be displayed within the Infusionsoft template library
'categories' =>'string', //The category of templates you would like this template saved in fromAddress - The from email address this template sends as. Format: "FirstName LastName "
'toAddress' =>'string', //The email address this template sends to. Typically this will be "~Contact.Email~"
'ccAddress' =>'string', //Any email addresses you would like to CC when this template is sent
'bccAddress' =>'string', //Any email address you would like to BCC when this template is sent
'subject' =>'string', //The subject line for this email template
'textBody' =>'string', //The content you would like sent in the body of the plain text version of this email template
'htmlBody' =>'string', //The HTML that makes up the body of this template. If you only send plain text emails
'contentType' =>'string', //HTML, Text, or Multipart
'mergeContext' =>'string', //Contact, Opportunity, Invoice, or CreditCard
),
"attachEmail" => array(
'contactId' =>'int', //The Id of the contact you would like to add this email history to
'fromName' =>'string', //The name portion of the from address. I.E. FirstName LastName, not the email@domain.com portion
'fromAddress' =>'string', //The email address the email was sent from
'toAddress' =>'string', //The email address the email was sent to
'ccAddresses' =>'string', //Any email address that was CC'd
'bccAddresses' =>'string', //Any email address that was BCC'd
'contentType' =>'string', //What type of email this was. Text, HTML, or Multipart (case sensitive)
'subject' =>'string', //The subject line
'htmlBody' =>'string', //The html from the body of the email
'textBody' =>'string', //The plain text body of the email
'header' =>'string', //The email header information
'receivedDate' =>'string', //The date this email was received. This determines where this displays in comparison to other emails
'sentDate' =>'string', //The date this email was sent
'emailSentType' =>'int', //A boolean int, 1 is used for marking the email as sent inside the contact history and 0 is used for marking the email as received
),
"getAvailableMergeFields" => array(
'mergeContext' =>'string' //Contact, Opportunity, Invoice, or CreditCard
),
"getEmailTemplate" => array(
'templateId' =>'int' //The Id number for the template you wish to retrieve details
),
"getOptStatus" => array(
'email' =>'string' //The email address you wish to retrieve the status of
),
"optIn" => array(
'email' =>'string', //The email address to opt-in
'optInReason' =>'string' //This is how you can note why/how this email was opted-in. *If a blank optInReason is passed the system will default a reason of "API Opt In"
),
"optOut" => array(
'email' =>'string', //The email address to opt-out
'optOutreason' =>'string' //Reason the address is being opt-out
),
"sendEmail" => array(
'contactList' =>'array-int', //An integer array of Contact Id numbers you would like to send this email to
'fromAddress' =>'string', //The email address this template will be sent from
'toAddress' =>'string', //The email address this template will send to. This is typically the merge field "~Contact.Email~"
'ccAddresses' =>'string', //The email address(es) this template will carbon copy. Multiples separated by comma
'bccAddresses' =>'string', //the email address(es) this template will blind carbon copy. Multiples separated by comma
'contentType' =>'string', //HTML, Text, or Multipart (this is case sensitive)
'subject' =>'string', //The subject line of this email
'htmlBody' =>'string', //The HTML content the body of this email is comprised of
'textBody' =>'string', //The content of the plain text body of this template
'templateId' =>'string', //The Id number of the template you would like to send
),
"sendTemplate" => array(
'contactList' =>'array-int', //An integer array of Contact Id numbers you would like to send this email to
'templateId' =>'string', //The Id number of the template you would like to send
),
"updateEmailTemplate" => array(
'templateId' =>'string', //The Id number of the template you would like to send
'pieceTitle' =>'string', //The name of the template. This will be displayed within the Infusionsoft template library
'category' =>'string', //The category you would like to save this template in
'fromAddress' =>'string', //The email address this template will be sent from. Format: "First Last"
'toAddress' =>'string', //The email address this template sends to. Typically this will be "~Contact.Email~"
'ccAddress' =>'string', //Any email addresses you would like to CC when this template is sent
'bccAddress' =>'string', //Any email address you would like to BCC when this template is sent
'subject' =>'string', //The subject line for this email template
'textBody' =>'string', //The content you would like sent in the body of the plain text version of this email template
'htmlBody' =>'string', //The HTML that makes up the body of this template. If you only send plain text emails
'contentType' =>'string', //HTML, Text, or Multipart
'mergeContext' =>'string', //Contact, Opportunity, Invoice, or CreditCard
),
),
'FileService' => array(
'getFile' => array(
'fileId' =>'int' //The ID of the file you would like to return.
),
'getDownloadUrl' => array(
'fileId' =>'int' //The ID of the file you would like to return.
),
'uploadFile' => array(
'fileName' =>'string', //The name of the file to be uploaded
'contactId' =>'int', //ID of the contact record to add the file to.
'contents' =>'string', //The file's contents encoded as a base64 string
),
'replaceFile' => array(
'fileId' =>'int', //The ID of the file you would like to return.
'contents' =>'string', //A string that is 64 base encoded.
),
'renameFile' => array(
'fileId' =>'int', //The ID of the file you would like to return.
'fileName' =>'string', //The new file name
),
),
'InvoiceService' => array(
"createBlankOrder" => array(
'contactId' =>'int', //The Id number of the contact record this order will be connected to
'description' =>'string', //The name this order will display. This is also the hyperlink to the order found on the contact's order tab
'orderDate' =>'dateTime', //Date of order
'leadAffiliateId' =>'int', //The Id number of the affiliate you would like placed as the lead affiliate. 0 if no affiliate should be attached
'saleAffiliateId' =>'int', //The Id number of the affiliate you would like placed as the sale affiliate. 0 if no affiliate should be attached
),
"addOrderItem" => array(
'invoiceId' =>'int', //The Id number of the invoice this item is being added to
'productId' =>'int', //The Id number of the product you are adding. If you are adding something such as tax, shipping, or a discount, make this 0
'type' =>'int', //The line item type - defined in the table below
'price' =>'double', //The price of this line item.
'quantity' =>'int', //The quantity of item added
'title' =>'string', //The name you want this line item to appear as
'description' =>'string', //A full description for this line item
),
"chargeInvoice" => array(
'invoiceId' =>'int', //The Invoice Id you would like to charge the balance due on
'notes' =>'string', //A note about the payment. Ex: "API Upsell Payment"
'creditCardId' =>'int', //The Id number of the credit card being charged
'merchantAccountId' =>'int', //The Id number of the merchant account the payment is to process through
'bypassCommissions' =>'boolean', //A boolean telling the system if this payment should count towards affiliate commissions earned. This is almost always false.
),
"deleteSubscription" => array(
'recurringOrderId' =>'int' //The Id number of the particular subscription being deleted
),
"addRecurringOrder" => array(
'contactId' =>'int', //The Id number of the contact this subscription will be connected with
'allowDuplicate' =>'boolean', //If duplicate subscriptions should be allowed
'cProgramId' =>'int', //The Id number of the subscription you are adding. This is from the cProgram table
'qty' =>'int', //Quantity of this service/product price - price charged per quantity
'price' =>'double', //Price to charge for the subscription
'taxable' =>'boolean', //If tax should be charged on this subscription
'merchantAccountId' =>'int', //The merchant account this subscription will bill through
'creditCardId' =>'int', //The Id of the credit card this subscription will charge to
'affiliateId' =>'int', //The Id number for the affiliate being placed as the sale affiliate. 0 if no affiliate
'daysTillCharge' =>'int', //The number of days until this subscription should be charged. Essentially free trial days
),
"addRecurringCommissionOverride" => array(
),
"createInvoiceForRecurring" => array(
),
"addManualPayment" => array(
),
"addPaymentPlan" => array(
),
"calculateAmountOwed" => array(
),
"getAllPaymentOptions" => array(
),
"getPayments" => array(
),
"locateExistingCard" => array(
),
"recalculateTax" => array(
),
"validateCreditCard" => array(
),
"getAllShippingOptions" => array(
),
"updateJobRecurringNextBillDate" => array(
),
"addOrderCommissionOverride" => array(
),
),
'ProductService' => array(
''
),
'SearchService' => array(
''
),
'ShippingService' => array(
''
),
'WebFormService' => array(
''
)
);
}
class Exception extends \Exception {}
class InvalidClassException extends Exception {}
class InvalidFunctionException extends Exception {}
class CURLException extends Exception {}
class APIFault extends Exception {}
print_r( API::ContactService()->load(219, array('FirstName','LastName', '_NEBInceptionDate', '_RenewalReminderLastSubmittedDate')) );
//print_r( API::DataService()->query('Contact', 10, 0, array('FirstName'=>'%'), array('Id','FirstName','LastName', '_NEBInceptionDate', '_RenewalReminderLastSubmittedDate')) );
// $ms = microtime(true);
//
// $i = 0;
// while ($i++ < 10) {
// print_r( API::ContactService()->load(219, array('FirstName','LastName', '_NEBInceptionDate', '_RenewalReminderLastSubmittedDate')) );
// }
//
// echo microtime(true) - $ms;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment