├── lib
├── XmlDomConstruct.php
└── FreshBooksRequest.php
├── example.php
└── README.markdown
/lib/XmlDomConstruct.php:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * EXTENDS THE DOMDOCUMENT TO IMPLEMENT PERSONAL (UTILITY) METHODS.
4 | *
5 | * @AUTHOR TONI VAN DE VOORDE
6 | */
7 | class XmlDomConstruct extends DOMDocument {
8 |
9 | /**
10 | * CONSTRUCTS ELEMENTS AND TEXTS FROM AN ARRAY OR STRING.
11 | * THE ARRAY CAN CONTAIN AN ELEMENT'S NAME IN THE INDEX PART
12 | * AND AN ELEMENT'S TEXT IN THE VALUE PART.
13 | *
14 | * IT CAN ALSO CREATES AN XML WITH THE SAME ELEMENT TAGNAME ON THE SAME
15 | * LEVEL.
16 | *
17 | * EX:
18 | *
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 |
2 | require('lib/FreshBooksRequest.php');
3 |
4 | // Setup the login credentials
5 | $domain = '';
6 | $token = '';
7 | FreshBooksRequest::init($domain, $token);
8 |
9 | /**********************************************
10 | * Fetch all clients by a specific id
11 | **********************************************/
12 | $fb = new FreshBooksRequest('client.list');
13 | $fb->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 |
--------------------------------------------------------------------------------