├── lib ├── XmlDomConstruct.php └── FreshBooksRequest.php ├── example.php └── README.markdown /lib/XmlDomConstruct.php: -------------------------------------------------------------------------------- 1 | 19 | * TEXT 20 | * 21 | * HELLO 22 | * WORLD 23 | * 24 | * 25 | * 26 | * ARRAY SHOULD THEN LOOK LIKE: 27 | * 28 | * ARRAY ( 29 | * "NODES" => ARRAY ( 30 | * "NODE" => ARRAY ( 31 | * 0 => "TEXT" 32 | * 1 => ARRAY ( 33 | * "FIELD" => ARRAY ( 34 | * 0 => "HELLO" 35 | * 1 => "WORLD" 36 | * ) 37 | * ) 38 | * ) 39 | * ) 40 | * ) 41 | * 42 | * @PARAM MIXED $MIXED AN ARRAY OR STRING. 43 | * 44 | * @PARAM DOMELEMENT[OPTIONAL] $DOMELEMENT THEN ELEMENT 45 | * FROM WHERE THE ARRAY WILL BE CONSTRUCT TO. 46 | * 47 | */ 48 | public function fromMixed($mixed, DOMElement $domElement = null) { 49 | 50 | $domElement = is_null($domElement) ? $this : $domElement; 51 | 52 | if (is_array($mixed)) { 53 | foreach( $mixed as $index => $mixedElement ) { 54 | 55 | if ( is_int($index) ) { 56 | if ( $index == 0 ) { 57 | $node = $domElement; 58 | } else { 59 | $node = $this->createElement($domElement->tagName); 60 | $domElement->parentNode->appendChild($node); 61 | } 62 | } 63 | 64 | else { 65 | $node = $this->createElement($index); 66 | $domElement->appendChild($node); 67 | } 68 | 69 | $this->fromMixed($mixedElement, $node); 70 | 71 | } 72 | } else { 73 | $domElement->appendChild($this->createTextNode($mixed)); 74 | } 75 | 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | post(array( 14 | 'email' => 'some@email.com' 15 | )); 16 | $fb->request(); 17 | if($fb->success()) 18 | { 19 | echo 'successful! the full response is in an array below'; 20 | print_r($fb->getResponse()); 21 | } 22 | else 23 | { 24 | echo $fb->getError(); 25 | print_r($fb->getResponse()); 26 | } 27 | 28 | /********************************************** 29 | * List invoices from a specific client 30 | **********************************************/ 31 | $fb = new FreshBooksRequest('invoice.list'); 32 | $fb->post(array( 33 | 'client_id' => 41 34 | )); 35 | $fb->request(); 36 | if($fb->success()) 37 | { 38 | print_r($fb->getResponse()); 39 | } 40 | else 41 | { 42 | echo $fb->getError(); 43 | print_r($fb->getResponse()); 44 | } 45 | 46 | /********************************************** 47 | * Create a recurring profile with multiple line items 48 | **********************************************/ 49 | $fb = new FreshBooksRequest('recurring.create'); 50 | $fb->post(array( 51 | 'recurring' => array( 52 | 'client_id' => 41, 53 | 'lines' => array( 54 | 'line' => array( 55 | array( 56 | 'name' => 'A prod name', 57 | 'description' => 'The description', 58 | 'unit_cost' => 10, 59 | 'quantity' => 2 60 | ), 61 | array( 62 | 'name' => 'Another prod name', 63 | 'description' => 'The other description', 64 | 'unit_cost' => 20, 65 | 'quantity' => 1 66 | ) 67 | ) 68 | ) 69 | ) 70 | )); 71 | //print_r($fb->getGeneratedXML()); 72 | $fb->request(); 73 | if($fb->success()) 74 | { 75 | $res = $fb->getResponse(); 76 | $recurrng_id = $res['recurring_id']; 77 | // Do something with the recurring_id you were returned 78 | } 79 | else 80 | { 81 | echo $fb->getError(); 82 | print_r($fb->getResponse()); 83 | } 84 | ?> -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # FreshBooksRequest PHP API 2 | 3 | This class is meant to give you the ability to quickly access the FreshBooks API. 4 | 5 | ## Usage 6 | 7 | * You need to know the method names and params when you're creating a new FreshBooksRequest instance. See all here http://developers.freshbooks.com/ 8 | * The XML tag parameters you see on the freshbooks API page are the ones you pass to $fb->post() (as an array) 9 | * Require the lib and setup with your credentials (domain and token) 10 | 11 | ```php 12 | require('lib/FreshBooksRequest.php'); 13 | 14 | $domain = 'your-subdomain'; // https://your-subdomain.freshbooks.com/ 15 | $token = '1234567890'; // your api token found in your account 16 | FreshBooksRequest::init($domain, $token); 17 | ``` 18 | * Now let's say we want to list clients with an email of some@email.com 19 | 20 | ```php 21 | /********************************************** 22 | * Fetch all clients by a specific id 23 | **********************************************/ 24 | // Method name found on the freshbooks API 25 | $fb = new FreshBooksRequest('client.list'); 26 | // Any arguments you want to pass it 27 | $fb->post(array( 28 | 'email' => 'some@email.com' 29 | )); 30 | // Make the request 31 | $fb->request(); 32 | if($fb->success()) 33 | { 34 | echo 'successful! the full response is in an array below'; 35 | print_r($fb->getResponse()); 36 | } 37 | else 38 | { 39 | echo $fb->getError(); 40 | print_r($fb->getResponse()); 41 | } 42 | ``` 43 | * If you're creating a recurring profile with multiple line items, it might look something like this: 44 | 45 | ```php 46 | /********************************************** 47 | * Create a recurring profile with multiple line items 48 | **********************************************/ 49 | $fb = new FreshBooksRequest('recurring.create'); 50 | $fb->post(array( 51 | 'recurring' => array( 52 | 'client_id' => 41, 53 | 'lines' => array( 54 | 'line' => array( 55 | array( 56 | 'name' => 'A prod name', 57 | 'description' => 'The description', 58 | 'unit_cost' => 10, 59 | 'quantity' => 2 60 | ), 61 | array( 62 | 'name' => 'Another prod name', 63 | 'description' => 'The other description', 64 | 'unit_cost' => 20, 65 | 'quantity' => 1 66 | ) 67 | ) 68 | ) 69 | ) 70 | )); 71 | // You can view what the XML looks like that we're about to send over the wire 72 | //print_r($fb->getGeneratedXML()); 73 | $fb->request(); 74 | if($fb->success()) 75 | { 76 | $res = $fb->getResponse(); 77 | $recurrng_id = $res['recurring_id']; 78 | // Do something with the recurring_id you were returned 79 | } 80 | ``` 81 | 82 | ## Change Log 83 | 84 | ### Changes in 1.0 (Sept 18, 2011) 85 | 86 | * Launched! 87 | -------------------------------------------------------------------------------- /lib/FreshBooksRequest.php: -------------------------------------------------------------------------------- 1 | 11 | * @license Dual licensed under the MIT and GPL licenses. 12 | * @version 1.0 13 | */ 14 | class FreshBooksRequestException extends Exception {} 15 | class FreshBooksRequest { 16 | 17 | /* 18 | * The domain you need when making a request 19 | */ 20 | protected static $_domain = ''; 21 | 22 | /* 23 | * The API token you need when making a request 24 | */ 25 | protected static $_token = ''; 26 | 27 | /* 28 | * The API url we're hitting. {{ DOMAIN }} will get replaced with $domain 29 | * when you set FreshBooksRequest::init($domain, $token) 30 | */ 31 | protected $_api_url = 'https://{{ DOMAIN }}.freshbooks.com/api/2.1/xml-in'; 32 | 33 | /* 34 | * Stores the current method we're using. Example: 35 | * new FreshBooksRequest('client.create'), 'client.create' would be the method 36 | */ 37 | protected $_method = ''; 38 | 39 | /* 40 | * Any arguments to pass to the request 41 | */ 42 | protected $_args = array(); 43 | 44 | /* 45 | * Determines whether or not the request was successful 46 | */ 47 | protected $_success = false; 48 | 49 | /* 50 | * Holds the error returned from our request 51 | */ 52 | protected $_error = ''; 53 | 54 | /* 55 | * Holds the response after our request 56 | */ 57 | protected $_response = array(); 58 | 59 | /* 60 | * Initialize the and store the domain/token for making requests 61 | * 62 | * @param string $domain The subdomain like 'yoursite'.freshbooks.com 63 | * @param string $token The token found in your account settings area 64 | * @return null 65 | */ 66 | public static function init($domain, $token) 67 | { 68 | self::$_domain = $domain; 69 | self::$_token = $token; 70 | } 71 | 72 | /* 73 | * Set up the request object and assign a method name 74 | * 75 | * @param string $method The method name from the API, like 'client.update' etc 76 | * @return null 77 | */ 78 | public function __construct($method) 79 | { 80 | $this->_method = $method; 81 | } 82 | 83 | /* 84 | * Set the data/arguments we're about to request with 85 | * 86 | * @return null 87 | */ 88 | public function post($data) 89 | { 90 | $this->_args = $data; 91 | } 92 | 93 | /* 94 | * Determine whether or not it was successful 95 | * 96 | * @return bool 97 | */ 98 | public function success() 99 | { 100 | return $this->_success; 101 | } 102 | 103 | /* 104 | * Get the error (if there was one returned from the request) 105 | * 106 | * @return string 107 | */ 108 | public function getError() 109 | { 110 | return $this->_error; 111 | } 112 | 113 | /* 114 | * Get the response from the request we made 115 | * 116 | * @return array 117 | */ 118 | public function getResponse() 119 | { 120 | return $this->_response; 121 | } 122 | 123 | /* 124 | * Get the generated XML to view. This is useful for debugging 125 | * to see what you're actually sending over the wire. Call this 126 | * after $fb->post() but before your make your $fb->request() 127 | * 128 | * @return array 129 | */ 130 | public function getGeneratedXML() 131 | { 132 | 133 | $dom = new XmlDomConstruct('1.0', 'utf-8'); 134 | $dom->fromMixed(array( 135 | 'request' => $this->_args 136 | )); 137 | $post_data = $dom->saveXML(); 138 | $post_data = str_replace('', '', $post_data); 139 | $post_data = str_replace('', '', $post_data); 140 | 141 | return $post_data; 142 | 143 | } 144 | 145 | /* 146 | * Send the request over the wire. Return result will be binary data if the FreshBooks response is 147 | * a PDF, array if it is a normal request. 148 | * 149 | * @return mixed 150 | */ 151 | public function request() 152 | { 153 | 154 | if(!self::$_domain || !self::$_token) 155 | { 156 | throw new FreshBooksRequestException('You need to call FreshBooksRequest::init($domain, $token) with your domain and token.'); 157 | } 158 | 159 | $post_data = $this->getGeneratedXML(); 160 | $url = str_replace('{{ DOMAIN }}', self::$_domain, $this->_api_url); 161 | $ch = curl_init(); // initialize curl handle 162 | curl_setopt($ch, CURLOPT_URL, $url); // set url to post to 163 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return into a variable 164 | curl_setopt($ch, CURLOPT_TIMEOUT, 40); // times out after 40s 165 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); // add POST fields 166 | curl_setopt($ch, CURLOPT_USERPWD, self::$_token . ':X'); 167 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 168 | 169 | $result = curl_exec($ch); 170 | 171 | if(curl_errno($ch)) 172 | { 173 | $this->_error = 'A cURL error occured: ' . curl_error($ch); 174 | return; 175 | } 176 | else 177 | { 178 | curl_close($ch); 179 | } 180 | 181 | // With the new invoice.getPDF request, we sometimes have non-XML come through here 182 | if (substr($result, 0, 4) == "%PDF") 183 | { 184 | // it's a PDF file 185 | $response = $result; 186 | $this->_success = true; 187 | } 188 | else 189 | { 190 | $response = json_decode(json_encode(simplexml_load_string($result)), true); 191 | $this->_success = ($response['@attributes']['status'] == 'ok'); 192 | } 193 | 194 | $this->_response = $response; 195 | if(isset($response['error'])) 196 | { 197 | $this->_error = $response['error']; 198 | } 199 | 200 | } 201 | 202 | } 203 | --------------------------------------------------------------------------------