├── .gitignore ├── README.md ├── _config.php ├── certs └── ca-bundle.crt ├── composer.json ├── index.php ├── lib ├── OAuthSimple.php └── XeroOAuth.php ├── license.txt ├── partner.php ├── private.php ├── public.php └── tests ├── testRunner.php └── tests.php /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .project 3 | .buildpath 4 | .gitattributes 5 | *.pem 6 | *.p12 7 | *.cer 8 | *.pfx 9 | <<<<<<< HEAD 10 | .* 11 | ======= 12 | >>>>>>> 65378c19b093593e1019e5cf6b6ffc72e389ecc5 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warning: This lib is no longer maintained. 2 | 3 | ## Looking for OAuth 2.0? 4 | Please checkout [Xero PHP sdk for OAuth 2.0](https://github.com/XeroAPI/xero-php-oauth2) and it's companion [kitchen sync app](https://github.com/XeroAPI/xero-php-oauth2-app) 5 | 6 | ## Looking for OAuth 1.0a? 7 | Please checkout the community project https://github.com/calcinai/xero-php 8 | 9 | 10 | XeroOAuth-PHP (DEPRECATED) 11 | ----------------------- 12 | This repository has been archived and no further issues or pull requests will reviewed. Feel free to fork the repo and work with the code. 13 | 14 | PHP library for working with the Xero OAuth API. 15 | 16 | Intro 17 | ====== 18 | XeroOAuth-PHP is a sample library for use with the Xero API (). The Xero API uses OAuth 1.0a, but we would not recommend using this library for other OAuth 1.0a APIs as 19 | the Xero API has one of the more advanced implementations (RSA-SHA1, etc) and thus has many configuration options not typically used in other APIs. 20 | 21 | This library is designed to get a developer up and running quickly with the OAuth authentication layer, but there will be some customisation of its implementation required before it can be used in a production environment. 22 | 23 | ## Requirements 24 | * PHP 5+ 25 | * php\_curl extension - ensure a recent version (7.30+) 26 | * php\_openssl extension 27 | 28 | 29 | ## Setup 30 | To get setup, you will need to modify the values in the \_config.php file to your own requirements and application settings or see the customised example file for each different application type, public.php, private.php or partner.php. 31 | 32 | ## Usage 33 | 34 | There are a number of functions used when interacting with Xero: 35 | 36 | #### Make a request 37 | The request function lies at the core of any communication with the API. There are a number of types of requests you may wish to make, all handled by the request() function. 38 | 39 | request($method, $url, $parameters, $xml, $format) 40 | 41 | ###### Parameters 42 | * Method: the API method to be used (GET, PUT, POST) 43 | * URL: the URL of the API endpoint. This is handled by a special function (see below) 44 | * Parameters: an associative array of parameters such as where, order by etc (see ) 45 | * XML: request data (for PUT and POST operations) 46 | * Format: response format (currently xml, json & pdf are supported). Note that PDF is not supported for all endpoints 47 | 48 | #### Generate a URL 49 | Create a properly formatted request URL. 50 | 51 | url($endpoint, $api) 52 | 53 | ###### Parameters 54 | * Endpoint: the endpoint you wish to work with. Note there are OAuth endpoints such as 'RequestToken' and 'AccessToken' in addition to various API endpoints such as Invoices, Contacts etc. When specifying a resource, such as Invoices/$GUID, you can construct the request by appending the GUID to the base URL. 55 | * API: there are two APIs: core (core accounting API) and payroll (payroll application API). Default is core. 56 | 57 | #### Parse the response 58 | Once you get data back, you can pass it through the parseResponse function to turn it into something usable. 59 | 60 | parseResponse($response, $format) 61 | 62 | ###### Parameters 63 | * Response: the raw API response to be parsed 64 | * Format: xml pdf and json are supported, but you cannot use this function to parse an XML API response as JSON - must correspond to the requested response format. 65 | 66 | #### Authorise 67 | For public and partner API type applications using the 3-legged OAuth process, we need to redirect the user to Xero to authorise the API connection. To do so, redirect the user to a url generated with a call like this: 68 | 69 | url("Authorize", '') . "?oauth_token=".$oauth_token."&scope=" . $scope; 70 | 71 | ###### Appendages 72 | * oauth\_token: this is a request token generated in a prior RequestToken call 73 | * scope: the Payroll API is a permissioned API and required a comma separated list of endpoints the application is requesting access to e.g. $scope = 'payroll.payrollcalendars,payroll.superfunds,payroll.payruns,payroll.payslip,payroll.employees'; 74 | 75 | 76 | #### Refresh an access token 77 | For partner API applications where the 30 minute access tokens can be programatically refreshed via the API, you can use the refreshToken function: 78 | 79 | refreshToken('the access token', 'the session handle') 80 | 81 | ###### Parameters 82 | * Access token: the current access token 83 | * Session handle: the session identifier handle 84 | 85 | ## Debug 86 | 87 | ###### Setup Diagnostics 88 | As you are getting set up, you may run into a few configuration issues, particularly with some of the more advanced application types such as partner. 89 | 90 | To make sure your configuration is correct, you can run a diagnostics function: 91 | 92 | diagnostics(); 93 | 94 | This returns an array of error messages (if there are any). These are in human readable form so should be enough to put you on the right track. If not, check the Xero developer centre and forum for more detail. 95 | 96 | It would probably be a bad idea to run this in your production code as the errors returned ones only a developer can resolve, not the end user. 97 | 98 | ###### Runtime errors 99 | 100 | There are many reasons why an error may be encountered: data validation, token issues, authorisation revocation etc. It is important to inspect not just the HTTP response code, but also the associated error string. 101 | 102 | A very basic error output function is included in the sample code, which outputs all available information related to an error. It would need to be substantially tidied up before the results could be surfaced in a production environment. 103 | 104 | outputError($object); 105 | 106 | 107 | ## Response Helpers 108 | Understanding the type of message you are getting from the API could be useful. In each response that is not successful, a helper element is returned: 109 | 110 | * **TokenExpired:** This means that the access token has expired. If you are using a partner API type application, you can renew it automatically, or if using a public application, prompt the user to re-authenticate 111 | * **TokenFatal:** In this scenario, a token is in a state that it cannot be renewed, and the user will need to re-authenticate 112 | * **SetupIssue:** There is an issue within the setup/configuration of the connection - check the diagnostics function 113 | 114 | ## TODO 115 | 116 | - [ ] Reading a value from a report 117 | - [x] Better WHERE and ORDER examples 118 | - [ ] Merge OAuthsimple changes for RSA-SHA1 back to parent repo 119 | 120 | 121 | ## License & Credits 122 | 123 | This software is published under the [MIT License](http://en.wikipedia.org/wiki/MIT_License). 124 | 125 | ###### OAuthSimple 126 | OAuthsimple.php contains minor adaptations from the OAuthSimple PHP class by [United Heroes](http://unitedheroes.net/OAuthSimple/). 127 | 128 | ###### tmhOAuth 129 | XeroOAuth class is based on code and structure derived from the [tmhOAuth](https://github.com/themattharris/tmhOAuth) library. 130 | 131 | ## Major change history 132 | 133 | #### 0.8 - 16th December 2016 134 | Deprecated Entrust Certificates for Partner Apps by commenting out related code and updating base URL to api.xero.com 135 | 136 | #### 0.7 - 1st Feb 2016 137 | PHP7 support via @tomcastleman 138 | Moved releases to Github release function 139 | #### 0.6 - 19th April 2015 140 | 141 | Added composer support. 142 | Modified content-type so is also set for PUT requests 143 | 144 | #### 0.5 - 16th November 2014 145 | 146 | Added examples for CRU of tracking categories and options. 147 | Updated the CA certs to a recent one - warning that if you are using a very old version of curl you may get 'cert invalid' type error. 148 | Removed an unused function and tidied up comments on another to make them more sensible. 149 | 150 | #### 0.4 - 29th September 2014 151 | 152 | Merged some pull requests, addressed an issue with multiple calls having signature validation issues. 153 | 154 | #### 0.3 - 3rd January 2014 155 | 156 | Merged a number of pull requests, tidied up formatting and extended sample tests. 157 | 158 | #### 0.2 - 13th May 2013 159 | 160 | Merged to master, added more tests and improved security handling for partner API apps. 161 | 162 | 163 | #### 0.1 - 10th May 2013 164 | 165 | Initial release candidate prepared and released to 'refactor' branch. 166 | -------------------------------------------------------------------------------- /_config.php: -------------------------------------------------------------------------------- 1 | '——YOUR-CONSUMER-KEY—', 42 | 'shared_secret' => '——YOUR-CONSUMER-SECRET—', 43 | // API versions 44 | 'core_version'=> '2.0', 45 | 'payroll_version'=> '1.0', 46 | 'file_version' => '1.0' 47 | ); 48 | 49 | if (XRO_APP_TYPE=="Private"||XRO_APP_TYPE=="Partner") { 50 | $signatures['rsa_private_key']= BASE_PATH . '/certs/privatekey.pem'; 51 | $signatures['rsa_public_key']= BASE_PATH . '/certs/publickey.cer'; 52 | } 53 | 54 | /** ENTRUST CERTIFICATE DEPRECATED 55 | * Partner applications require a Client SSL certificate which is issued by Xero 56 | * the certificate is issued as a .p12 cert which you will then need to split into a cert and private key: 57 | * openssl pkcs12 -in entrust-client.p12 -clcerts -nokeys -out entrust-cert.pem 58 | * openssl pkcs12 -in entrust-client.p12 -nocerts -out entrust-private.pem <- you will be prompted to enter a password 59 | 60 | if (XRO_APP_TYPE=="Partner") { 61 | $signatures['curl_ssl_cert'] = BASE_PATH . '/certs/entrust-cert.pem'; 62 | $signatures['curl_ssl_password'] = '1234'; 63 | $signatures['curl_ssl_key'] = BASE_PATH . '/certs/entrust-private-nopass.pem'; 64 | } 65 | */ 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xero/xerooauth-php", 3 | "description": "PHP class for the Xero APIs", 4 | "type": "library", 5 | "keywords": ["xero","accounting","api","oauth"], 6 | "homepage": "https://github.com/XeroAPI/XeroOAuth-PHP", 7 | "license": "MIT", 8 | "support": { 9 | "forum": "https://community.xero.com/developer/", 10 | "issues": "https://github.com/XeroAPI/XeroOAuth-PHP/issues" 11 | }, 12 | "autoload": { 13 | "classmap": ["lib/"] 14 | }, 15 | "require": { 16 | "ext-openssl": "*", 17 | "ext-curl": "*", 18 | "php": ">=5.0.0" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Xero OAuth PHP 4 | 5 | 6 |

Howdy

7 |
    8 |
  1. A sample script for Private API applications private.php 9 |
  2. 10 |
  3. A sample script for Public API applications public.php 11 |
  4. 12 |
  5. A sample script for Partner API applications partner.php 13 |
  6. 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/OAuthSimple.php: -------------------------------------------------------------------------------- 1 | ing uses OAuth without that minimal set of 60 | * signatures. 61 | * 62 | * If you want to use the higher order security that comes from the 63 | * OAuth token (sorry, I don't provide the functions to fetch that because 64 | * sites aren't horribly consistent about how they offer that), you need to 65 | * pass those in either with .signatures() or as an argument to the 66 | * .sign() or .getHeaderString() functions. 67 | * 68 | * Example: 69 | 70 | sign(Array('path'=>'http://example.com/rest/', 73 | 'parameters'=> 'foo=bar&gorp=banana', 74 | 'signatures'=> Array( 75 | 'api_key'=>'12345abcd', 76 | 'shared_secret'=>'xyz-5309' 77 | ))); 78 | ?> 79 | Some Link; 80 | 81 | * 82 | * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than 83 | * that, read on, McDuff. 84 | */ 85 | 86 | /** OAuthSimple creator 87 | * 88 | * Create an instance of OAuthSimple 89 | * 90 | * @param api_key {string} The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use. 91 | * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use. 92 | */ 93 | public function __construct ($APIKey = "",$sharedSecret=""){ 94 | if (!empty($APIKey)) 95 | $this->_secrets{'consumer_key'}=$APIKey; 96 | if (!empty($sharedSecret)) 97 | $this->_secrets{'shared_secret'}=$sharedSecret; 98 | $this->_default_signature_method="HMAC-SHA1"; 99 | $this->_action="GET"; 100 | $this->_nonce_chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 101 | return $this; 102 | } 103 | 104 | /** reset the parameters and url 105 | * 106 | */ 107 | function reset() { 108 | $this->_parameters=null; 109 | $this->path=null; 110 | $this->sbs=null; 111 | return $this; 112 | } 113 | 114 | /** set the parameters either from a hash or a string 115 | * 116 | * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash) 117 | */ 118 | function setParameters ($parameters=Array()) { 119 | 120 | if (is_string($parameters)) 121 | $parameters = $this->_parseParameterString($parameters); 122 | if (empty($this->_parameters)) 123 | $this->_parameters = $parameters; 124 | elseif (!empty($parameters)) 125 | $this->_parameters = array_merge($this->_parameters,$parameters); 126 | if (empty($this->_parameters['oauth_nonce'])) 127 | $this->_getNonce(); 128 | if (empty($this->_parameters['oauth_timestamp'])) 129 | $this->_getTimeStamp(); 130 | if (empty($this->_parameters['oauth_consumer_key'])) 131 | $this->_getApiKey(); 132 | if (empty($this->_parameters['oauth_token'])) 133 | $this->_getAccessToken(); 134 | if (empty($this->_parameters['oauth_signature_method'])) 135 | $this->setSignatureMethod(); 136 | if (empty($this->_parameters['oauth_version'])) 137 | $this->_parameters['oauth_version']="1.0"; 138 | return $this; 139 | } 140 | 141 | // convienence method for setParameters 142 | function setQueryString ($parameters) { 143 | return $this->setParameters($parameters); 144 | } 145 | 146 | /** Set the target URL (does not include the parameters) 147 | * 148 | * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo") 149 | */ 150 | function setURL ($path) { 151 | if (empty($path)) 152 | throw new OAuthSimpleException('No path specified for OAuthSimple.setURL'); 153 | $this->_path=$path; 154 | return $this; 155 | } 156 | 157 | /** convienence method for setURL 158 | * 159 | * @param path {string} see .setURL 160 | */ 161 | function setPath ($path) { 162 | return $this->_path=$path; 163 | } 164 | 165 | /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.) 166 | * 167 | * @param action {string} HTTP Action word. 168 | */ 169 | function setAction ($action) { 170 | if (empty($action)) 171 | $action = 'GET'; 172 | $action = strtoupper($action); 173 | if (preg_match('/[^A-Z]/',$action)) 174 | throw new OAuthSimpleException('Invalid action specified for OAuthSimple.setAction'); 175 | $this->_action = $action; 176 | return $this; 177 | } 178 | 179 | /** set the signatures (as well as validate the ones you have) 180 | * 181 | * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:} 182 | */ 183 | function signatures ($signatures) { 184 | if (!empty($signatures) && !is_array($signatures)) 185 | throw new OAuthSimpleException('Must pass dictionary array to OAuthSimple.signatures'); 186 | if (!empty($signatures)){ 187 | if (empty($this->_secrets)) { 188 | $this->_secrets=Array(); 189 | } 190 | $this->_secrets=array_merge($this->_secrets,$signatures); 191 | } 192 | // Aliases 193 | if (isset($this->_secrets['api_key'])) 194 | $this->_secrets['consumer_key'] = $this->_secrets['api_key']; 195 | if (isset($this->_secrets['access_token'])) 196 | $this->_secrets['oauth_token'] = $this->_secrets['access_token']; 197 | if (isset($this->_secrets['access_secret'])) 198 | $this->_secrets['oauth_secret'] = $this->_secrets['access_secret']; 199 | if (isset($this->_secrets['access_token_secret'])) 200 | $this->_secrets['oauth_secret'] = $this->_secrets['access_token_secret']; 201 | if (isset($this->_secrets['rsa_private_key'])) 202 | $this->_secrets['private_key'] = $this->_secrets['rsa_private_key']; 203 | if (isset($this->_secrets['rsa_public_key'])) 204 | $this->_secrets['public_key'] = $this->_secrets['rsa_public_key']; 205 | // Gauntlet 206 | if (empty($this->_secrets['consumer_key'])) 207 | throw new OAuthSimpleException('Missing required consumer_key in OAuthSimple.signatures'); 208 | if (empty($this->_secrets['shared_secret'])) 209 | throw new OAuthSimpleException('Missing requires shared_secret in OAuthSimple.signatures'); 210 | if (!empty($this->_secrets['oauth_token']) && empty($this->_secrets['oauth_secret'])) 211 | throw new OAuthSimpleException('Missing oauth_secret for supplied oauth_token in OAuthSimple.signatures'); 212 | return $this; 213 | } 214 | 215 | function setTokensAndSecrets($signatures) { 216 | return $this->signatures($signatures); 217 | } 218 | 219 | /** set the signature method (currently only Plaintext or SHA-MAC1) 220 | * 221 | * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now) 222 | */ 223 | function setSignatureMethod ($method="") { 224 | if (empty($method)) 225 | $method = $this->_default_signature_method; 226 | $method = strtoupper($method); 227 | switch($method) 228 | { 229 | case 'RSA-SHA1': 230 | $this->_parameters['oauth_signature_method']=$method; 231 | break; 232 | case 'PLAINTEXT': 233 | case 'HMAC-SHA1': 234 | $this->_parameters['oauth_signature_method']=$method; 235 | break; 236 | default: 237 | throw new OAuthSimpleException ("Unknown signing method $method specified for OAuthSimple.setSignatureMethod"); 238 | } 239 | return $this; 240 | } 241 | 242 | /** sign the request 243 | * 244 | * note: all arguments are optional, provided you've set them using the 245 | * other helper functions. 246 | * 247 | * @param args {object} hash of arguments for the call 248 | * {action, path, parameters (array), method, signatures (array)} 249 | * all arguments are optional. 250 | */ 251 | function sign($args=array()) { 252 | if (!empty($args['action'])) 253 | $this->setAction($args['action']); 254 | if (!empty($args['path'])) 255 | $this->setPath($args['path']); 256 | if (!empty($args['method'])) 257 | $this->setSignatureMethod($args['method']); 258 | if (!empty($args['signatures'])) 259 | $this->signatures($args['signatures']); 260 | if (empty($args['parameters'])) 261 | $args['parameters']=array(); // squelch the warning. 262 | $this->setParameters($args['parameters']); 263 | $normParams = $this->_normalizedParameters(); 264 | $this->_parameters['oauth_signature'] = $this->_generateSignature($normParams); 265 | return Array( 266 | 'parameters' => $this->_parameters, 267 | 'signature' => $this->_oauthEscape($this->_parameters['oauth_signature']), 268 | 'signed_url' => $this->_path . '?' . $this->_normalizedParameters('true'), 269 | 'header' => $this->getHeaderString(), 270 | 'sbs'=> $this->sbs 271 | ); 272 | } 273 | 274 | /** Return a formatted "header" string 275 | * 276 | * NOTE: This doesn't set the "Authorization: " prefix, which is required. 277 | * I don't set it because various set header functions prefer different 278 | * ways to do that. 279 | * 280 | * @param args {object} see .sign 281 | */ 282 | function getHeaderString ($args=array()) { 283 | if (empty($this->_parameters['oauth_signature'])) 284 | $this->sign($args); 285 | 286 | $result = 'OAuth '; 287 | 288 | foreach ($this->_parameters as $pName=>$pValue) 289 | { 290 | if (strpos($pName,'oauth_') !== 0) 291 | continue; 292 | if (is_array($pValue)) 293 | { 294 | foreach ($pValue as $val) 295 | { 296 | $result .= $pName .'="' . $this->_oauthEscape($val) . '", '; 297 | } 298 | } 299 | else 300 | { 301 | $result .= $pName . '="' . $this->_oauthEscape($pValue) . '", '; 302 | } 303 | } 304 | return preg_replace('/, $/','',$result); 305 | } 306 | 307 | // Start private methods. Here be Dragons. 308 | // No promises are kept that any of these functions will continue to exist 309 | // in future versions. 310 | function _parseParameterString ($paramString) { 311 | $elements = explode('&',$paramString); 312 | $result = array(); 313 | foreach ($elements as $element) 314 | { 315 | list ($key,$token) = explode('=',$element); 316 | if ($token) 317 | $token = urldecode($token); 318 | if (!empty($result[$key])) 319 | { 320 | if (!is_array($result[$key])) 321 | $result[$key] = array($result[$key],$token); 322 | else 323 | array_push($result[$key],$token); 324 | } 325 | else 326 | $result[$key]=$token; 327 | } 328 | //error_log('Parse parameters : '.print_r($result,1)); 329 | return $result; 330 | } 331 | 332 | function _oauthEscape($string) { 333 | if ($string === 0) 334 | return 0; 335 | if (empty($string)) 336 | return ''; 337 | if (is_array($string)) 338 | throw new OAuthSimpleException('Array passed to _oauthEscape'); 339 | $string = rawurlencode($string); 340 | $string = str_replace('+','%20',$string); 341 | $string = str_replace('!','%21',$string); 342 | $string = str_replace('*','%2A',$string); 343 | $string = str_replace('\'','%27',$string); 344 | $string = str_replace('(','%28',$string); 345 | $string = str_replace(')','%29',$string); 346 | return $string; 347 | } 348 | 349 | function _getNonce($length=5) { 350 | $result = ''; 351 | $cLength = strlen($this->_nonce_chars); 352 | for ($i=0; $i < $length; $i++) 353 | { 354 | $rnum = rand(0,$cLength); 355 | $result .= substr($this->_nonce_chars,$rnum,1); 356 | } 357 | $this->_parameters['oauth_nonce'] = $result; 358 | return $result; 359 | } 360 | 361 | function _getApiKey() { 362 | if (empty($this->_secrets['consumer_key'])) 363 | { 364 | throw new OAuthSimpleException('No consumer_key set for OAuthSimple'); 365 | } 366 | $this->_parameters['oauth_consumer_key']=$this->_secrets['consumer_key']; 367 | return $this->_parameters['oauth_consumer_key']; 368 | } 369 | 370 | function _getAccessToken() { 371 | if (!isset($this->_secrets['oauth_secret'])) 372 | return ''; 373 | if (!isset($this->_secrets['oauth_token'])) 374 | throw new OAuthSimpleException('No access token (oauth_token) set for OAuthSimple.'); 375 | $this->_parameters['oauth_token'] = $this->_secrets['oauth_token']; 376 | return $this->_parameters['oauth_token']; 377 | } 378 | 379 | function _getTimeStamp() { 380 | return $this->_parameters['oauth_timestamp'] = time(); 381 | } 382 | 383 | function _normalizedParameters($filter='false') { 384 | $elements = array(); 385 | $ra = 0; 386 | ksort($this->_parameters); 387 | foreach ( $this->_parameters as $paramName=>$paramValue) { 388 | if($paramName=='xml'){ 389 | if($filter=="true") 390 | continue; 391 | } 392 | if (preg_match('/\w+_secret/',$paramName)) 393 | continue; 394 | if (is_array($paramValue)) 395 | { 396 | sort($paramValue); 397 | foreach($paramValue as $element) 398 | array_push($elements,$this->_oauthEscape($paramName).'='.$this->_oauthEscape($element)); 399 | continue; 400 | } 401 | array_push($elements,$this->_oauthEscape($paramName).'='.$this->_oauthEscape($paramValue)); 402 | 403 | } 404 | return join('&',$elements); 405 | } 406 | 407 | function _readFile($filePath) { 408 | 409 | $fp = fopen($filePath,"r"); 410 | 411 | $file_contents = fread($fp,8192); 412 | 413 | fclose($fp); 414 | 415 | return $file_contents; 416 | } 417 | 418 | function _generateSignature () { 419 | $secretKey = ''; 420 | if(isset($this->_secrets['shared_secret'])) 421 | $secretKey = $this->_oauthEscape($this->_secrets['shared_secret']); 422 | $secretKey .= '&'; 423 | if(isset($this->_secrets['oauth_secret'])) 424 | $secretKey .= $this->_oauthEscape($this->_secrets['oauth_secret']); 425 | switch($this->_parameters['oauth_signature_method']) 426 | { 427 | case 'RSA-SHA1': 428 | 429 | $publickey = ""; 430 | // Fetch the public key 431 | if($publickey = openssl_get_publickey($this->_readFile($this->_secrets['public_key']))){ 432 | 433 | }else{ 434 | throw new OAuthSimpleException('Cannot access public key for signing'); 435 | } 436 | 437 | $privatekeyid = ""; 438 | // Fetch the private key 439 | if($privatekeyid = openssl_pkey_get_private($this->_readFile($this->_secrets['private_key']))) 440 | { 441 | // Sign using the key 442 | $this->sbs = $this->_oauthEscape($this->_action).'&'.$this->_oauthEscape($this->_path).'&'.$this->_oauthEscape($this->_normalizedParameters()); 443 | 444 | $ok = openssl_sign($this->sbs, $signature, $privatekeyid); 445 | 446 | // Release the key resource 447 | openssl_free_key($privatekeyid); 448 | 449 | return base64_encode($signature); 450 | 451 | }else{ 452 | throw new OAuthSimpleException('Cannot access private key for signing'); 453 | } 454 | 455 | 456 | case 'PLAINTEXT': 457 | return urlencode($secretKey); 458 | 459 | case 'HMAC-SHA1': 460 | $this->sbs = $this->_oauthEscape($this->_action).'&'.$this->_oauthEscape($this->_path).'&'.$this->_oauthEscape($this->_normalizedParameters()); 461 | //error_log('SBS: '.$sigString); 462 | return base64_encode(hash_hmac('sha1',$this->sbs,$secretKey,true)); 463 | 464 | default: 465 | throw new OAuthSimpleException('Unknown signature method for OAuthSimple'); 466 | } 467 | } 468 | } 469 | ?> 470 | -------------------------------------------------------------------------------- /lib/XeroOAuth.php: -------------------------------------------------------------------------------- 1 | params = array (); 27 | $this->headers = array (); 28 | $this->auto_fixed_time = false; 29 | $this->buffer = null; 30 | $this->request_params = array(); 31 | 32 | if (! empty ( $config ['application_type'] )) { 33 | switch ($config ['application_type']) { 34 | case "Public" : 35 | $this->_xero_defaults = array ( 36 | 'xero_url' => 'https://api.xero.com/', 37 | 'site' => 'https://api.xero.com', 38 | 'authorize_url' => 'https://api.xero.com/oauth/Authorize', 39 | 'signature_method' => 'HMAC-SHA1' 40 | ); 41 | break; 42 | case "Private" : 43 | $this->_xero_defaults = array ( 44 | 'xero_url' => 'https://api.xero.com/', 45 | 'site' => 'https://api.xero.com', 46 | 'authorize_url' => 'https://api.xero.com/oauth/Authorize', 47 | 'signature_method' => 'RSA-SHA1' 48 | ); 49 | break; 50 | case "Partner" : 51 | $this->_xero_defaults = array ( 52 | 'xero_url' => 'https://api.xero.com/', 53 | 'site' => 'https://api.xero.com', 54 | 'authorize_url' => 'https://api.xero.com/oauth/Authorize', 55 | 'signature_method' => 'RSA-SHA1' 56 | ); 57 | break; 58 | } 59 | } 60 | 61 | $this->_xero_consumer_options = array ( 62 | 'request_token_path' => 'oauth/RequestToken', 63 | 'access_token_path' => 'oauth/AccessToken', 64 | 'authorize_path' => 'oauth/Authorize' 65 | ); 66 | 67 | // Remove forced dependency on BASE_PATH constant. 68 | // Note that __DIR__ is PHP 5.3 and above only. 69 | $base_path = defined ( 'BASE_PATH' ) ? BASE_PATH : dirname ( __DIR__ ); 70 | 71 | $this->_xero_curl_options = array ( // you probably don't want to change any of these curl values 72 | 'curl_connecttimeout' => 30, 73 | 'curl_timeout' => 20, 74 | // for security you may want to set this to TRUE. If you do you need 75 | // to install the servers certificate in your local certificate store. 76 | 'curl_ssl_verifypeer' => 2, 77 | // include ca-bundle.crt from http://curl.haxx.se/ca/cacert.pem 78 | 'curl_cainfo' => $base_path . '/certs/ca-bundle.crt', 79 | 'curl_followlocation' => false, // whether to follow redirects or not 80 | // TRUE/1 is not a valid ssl verifyhost value with curl >= 7.28.1 and 2 is more secure as well. 81 | // More details here: http://php.net/manual/en/function.curl-setopt.php 82 | 'curl_ssl_verifyhost' => 2, 83 | // support for proxy servers 84 | 'curl_proxy' => false, // really you don't want to use this if you are using streaming 85 | 'curl_proxyuserpwd' => false, // format username:password for proxy, if required 86 | 'curl_encoding' => '', // leave blank for all supported formats, else use gzip, deflate, identity 87 | 'curl_verbose' => true 88 | ); 89 | 90 | $this->config = array_merge ( $this->_xero_defaults, $this->_xero_consumer_options, $this->_xero_curl_options, $config ); 91 | } 92 | 93 | /** 94 | * Utility function to parse the returned curl headers and store them in the 95 | * class array variable. 96 | * 97 | * @param object $ch 98 | * curl handle 99 | * @param string $header 100 | * the response headers 101 | * @return the string length of the header 102 | */ 103 | private function curlHeader($ch, $header) { 104 | $i = strpos ( $header, ':' ); 105 | if (! empty ( $i )) { 106 | $key = str_replace ( '-', '_', strtolower ( substr ( $header, 0, $i ) ) ); 107 | $value = trim ( substr ( $header, $i + 2 ) ); 108 | $this->response ['headers'] [$key] = $value; 109 | } 110 | return strlen ( $header ); 111 | } 112 | 113 | /** 114 | * Utility function to parse the returned curl buffer and store them until 115 | * an EOL is found. 116 | * The buffer for curl is an undefined size so we need 117 | * to collect the content until an EOL is found. 118 | * 119 | * This function calls the previously defined streaming callback method. 120 | * 121 | * @param object $ch 122 | * curl handle 123 | * @param string $data 124 | * the current curl buffer 125 | */ 126 | private function curlWrite($ch, $data) { 127 | $l = strlen ( $data ); 128 | if (strpos ( $data, $this->config ['streaming_eol'] ) === false) { 129 | $this->buffer .= $data; 130 | return $l; 131 | } 132 | 133 | $buffered = explode ( $this->config ['streaming_eol'], $data ); 134 | $content = $this->buffer . $buffered [0]; 135 | 136 | $this->metrics ['tweets'] ++; 137 | $this->metrics ['bytes'] += strlen ( $content ); 138 | 139 | if (! function_exists ( $this->config ['streaming_callback'] )) 140 | return 0; 141 | 142 | $metrics = $this->update_metrics (); 143 | $stop = call_user_func ( $this->config ['streaming_callback'], $content, strlen ( $content ), $metrics ); 144 | $this->buffer = $buffered [1]; 145 | if ($stop) 146 | return 0; 147 | 148 | return $l; 149 | } 150 | 151 | /** 152 | * Extracts and decodes OAuth parameters from the passed string 153 | * 154 | * @param string $body 155 | * the response body from an OAuth flow method 156 | * @return array the response body safely decoded to an array of key => values 157 | */ 158 | function extract_params($body) { 159 | $kvs = explode ( '&', $body ); 160 | $decoded = array (); 161 | foreach ( $kvs as $kv ) { 162 | $kv = explode ( '=', $kv, 2 ); 163 | $kv [0] = $this->safe_decode ( $kv [0] ); 164 | $kv [1] = $this->safe_decode ( $kv [1] ); 165 | $decoded [$kv [0]] = $kv [1]; 166 | } 167 | return $decoded; 168 | } 169 | 170 | /** 171 | * Encodes the string or array passed in a way compatible with OAuth. 172 | * If an array is passed each array value will will be encoded. 173 | * 174 | * @param mixed $data 175 | * the scalar or array to encode 176 | * @return $data encoded in a way compatible with OAuth 177 | */ 178 | private function safe_encode($data) { 179 | if (is_array ( $data )) { 180 | return array_map ( array ( 181 | $this, 182 | 'safe_encode' 183 | ), $data ); 184 | } else if (is_scalar ( $data )) { 185 | return str_ireplace ( array ( 186 | '+', 187 | '%7E' 188 | ), array ( 189 | ' ', 190 | '~' 191 | ), rawurlencode ( $data ) ); 192 | } else { 193 | return ''; 194 | } 195 | } 196 | 197 | /** 198 | * Decodes the string or array from it's URL encoded form 199 | * If an array is passed each array value will will be decoded. 200 | * 201 | * @param mixed $data 202 | * the scalar or array to decode 203 | * @return $data decoded from the URL encoded form 204 | */ 205 | private function safe_decode($data) { 206 | if (is_array ( $data )) { 207 | return array_map ( array ( 208 | $this, 209 | 'safe_decode' 210 | ), $data ); 211 | } else if (is_scalar ( $data )) { 212 | return rawurldecode ( $data ); 213 | } else { 214 | return ''; 215 | } 216 | } 217 | 218 | /** 219 | * Prepares the HTTP method for use in the base string by converting it to 220 | * uppercase. 221 | * 222 | * @param string $method 223 | * an HTTP method such as GET or POST 224 | * @return void value is stored to a class variable 225 | * @author themattharris 226 | */ 227 | private function prepare_method($method) { 228 | $this->method = strtoupper ( $method ); 229 | } 230 | 231 | /** 232 | * Makes a curl request. 233 | * Takes no parameters as all should have been prepared 234 | * by the request method 235 | * 236 | * @return void response data is stored in the class variable 'response' 237 | */ 238 | private function curlit() { 239 | $this->request_params = array(); 240 | 241 | 242 | // configure curl 243 | $c = curl_init (); 244 | $useragent = (isset ( $this->config ['user_agent'] )) ? (empty ( $this->config ['user_agent'] ) ? 'XeroOAuth-PHP' : $this->config ['user_agent']) : 'XeroOAuth-PHP'; 245 | curl_setopt_array ( $c, array ( 246 | CURLOPT_USERAGENT => $useragent, 247 | CURLOPT_CONNECTTIMEOUT => $this->config ['curl_connecttimeout'], 248 | CURLOPT_TIMEOUT => $this->config ['curl_timeout'], 249 | CURLOPT_RETURNTRANSFER => TRUE, 250 | CURLOPT_SSL_VERIFYPEER => $this->config ['curl_ssl_verifypeer'], 251 | CURLOPT_CAINFO => $this->config ['curl_cainfo'], 252 | CURLOPT_SSL_VERIFYHOST => $this->config ['curl_ssl_verifyhost'], 253 | CURLOPT_FOLLOWLOCATION => $this->config ['curl_followlocation'], 254 | CURLOPT_PROXY => $this->config ['curl_proxy'], 255 | CURLOPT_ENCODING => $this->config ['curl_encoding'], 256 | CURLOPT_URL => $this->sign ['signed_url'], 257 | CURLOPT_VERBOSE => $this->config ['curl_verbose'], 258 | // process the headers 259 | CURLOPT_HEADERFUNCTION => array ( 260 | $this, 261 | 'curlHeader' 262 | ), 263 | CURLOPT_HEADER => FALSE, 264 | CURLINFO_HEADER_OUT => TRUE 265 | ) ); 266 | 267 | /* ENTRUST CERTIFICATE DEPRECATED 268 | if ($this->config ['application_type'] == "Partner") { 269 | curl_setopt_array ( $c, array ( 270 | // ssl client cert options for partner apps 271 | CURLOPT_SSLCERT => $this->config ['curl_ssl_cert'], 272 | CURLOPT_SSLKEYPASSWD => $this->config ['curl_ssl_password'], 273 | CURLOPT_SSLKEY => $this->config ['curl_ssl_key'] 274 | ) ); 275 | } 276 | */ 277 | 278 | if ($this->config ['curl_proxyuserpwd'] !== false) 279 | curl_setopt ( $c, CURLOPT_PROXYUSERPWD, $this->config ['curl_proxyuserpwd'] ); 280 | 281 | if (isset ( $this->config ['is_streaming'] )) { 282 | // process the body 283 | $this->response ['content-length'] = 0; 284 | curl_setopt ( $c, CURLOPT_TIMEOUT, 0 ); 285 | curl_setopt ( $c, CURLOPT_WRITEFUNCTION, array ( 286 | $this, 287 | 'curlWrite' 288 | ) ); 289 | } 290 | 291 | switch ($this->method) { 292 | case 'GET' : 293 | $contentLength = 0; 294 | break; 295 | case 'POST' : 296 | curl_setopt ( $c, CURLOPT_POST, TRUE ); 297 | $post_body = $this->safe_encode ( $this->xml ); 298 | curl_setopt ( $c, CURLOPT_POSTFIELDS, $post_body ); 299 | $this->request_params ['xml'] = $post_body; 300 | $contentLength = strlen ( $post_body ); 301 | $this->headers ['Content-Type'] = 'application/x-www-form-urlencoded'; 302 | 303 | break; 304 | case 'PUT' : 305 | $fh = tmpfile(); 306 | if ($this->format == "file") { 307 | $put_body = $this->xml; 308 | } else { 309 | $put_body = $this->safe_encode ( $this->xml ); 310 | $this->headers ['Content-Type'] = 'application/x-www-form-urlencoded'; 311 | } 312 | fwrite ( $fh, $put_body ); 313 | rewind ( $fh ); 314 | curl_setopt ( $c, CURLOPT_PUT, true ); 315 | curl_setopt ( $c, CURLOPT_INFILE, $fh ); 316 | curl_setopt ( $c, CURLOPT_INFILESIZE, strlen ( $put_body ) ); 317 | $contentLength = strlen ( $put_body ); 318 | 319 | break; 320 | default : 321 | curl_setopt ( $c, CURLOPT_CUSTOMREQUEST, $this->method ); 322 | } 323 | 324 | if (! empty ( $this->request_params )) { 325 | // if not doing multipart we need to implode the parameters 326 | if (! $this->config ['multipart']) { 327 | foreach ( $this->request_params as $k => $v ) { 328 | $ps [] = "{$k}={$v}"; 329 | } 330 | $this->request_payload = implode ( '&', $ps ); 331 | } 332 | curl_setopt ( $c, CURLOPT_POSTFIELDS, $this->request_payload); 333 | } else { 334 | // CURL will set length to -1 when there is no data 335 | $this->headers ['Content-Length'] = $contentLength; 336 | } 337 | 338 | $this->headers ['Expect'] = ''; 339 | 340 | if (! empty ( $this->headers )) { 341 | foreach ( $this->headers as $k => $v ) { 342 | $headers [] = trim ( $k . ': ' . $v ); 343 | } 344 | curl_setopt ( $c, CURLOPT_HTTPHEADER, $headers ); 345 | } 346 | 347 | if (isset ( $this->config ['prevent_request'] ) && false == $this->config ['prevent_request']) 348 | return; 349 | 350 | // do it! 351 | $response = curl_exec ( $c ); 352 | if ($response === false) { 353 | $response = 'Curl error: ' . curl_error ( $c ); 354 | $code = 1; 355 | } else { 356 | $code = curl_getinfo ( $c, CURLINFO_HTTP_CODE ); 357 | } 358 | 359 | $info = curl_getinfo ( $c ); 360 | 361 | curl_close ( $c ); 362 | if (isset ( $fh )) { 363 | fclose( $fh ); 364 | } 365 | 366 | // store the response 367 | $this->response ['code'] = $code; 368 | $this->response ['response'] = $response; 369 | $this->response ['info'] = $info; 370 | $this->response ['format'] = $this->format; 371 | return $code; 372 | } 373 | 374 | /** 375 | * Make an HTTP request using this library. 376 | * This method doesn't return anything. 377 | * Instead the response should be inspected directly. 378 | * 379 | * @param string $method 380 | * the HTTP method being used. e.g. POST, GET, HEAD etc 381 | * @param string $url 382 | * the request URL without query string parameters 383 | * @param array $params 384 | * the request parameters as an array of key=value pairs 385 | * @param string $format 386 | * the format of the response. Default json. Set to an empty string to exclude the format 387 | * 388 | */ 389 | function request($method, $url, $params = array(), $xml = "", $format = 'xml') { 390 | // removed these as function parameters for now 391 | $useauth = true; 392 | $multipart = false; 393 | $this->headers = array (); 394 | 395 | if (isset ( $format )) { 396 | switch ($format) { 397 | case "pdf" : 398 | $this->headers ['Accept'] = 'application/pdf'; 399 | break; 400 | case "json" : 401 | $this->headers ['Accept'] = 'application/json'; 402 | break; 403 | case "xml" : 404 | default : 405 | $this->headers ['Accept'] = 'application/xml'; 406 | break; 407 | } 408 | } 409 | 410 | if (isset ( $params ['If-Modified-Since'] )) { 411 | $modDate = "If-Modified-Since: " . $params ['If-Modified-Since']; 412 | $this->headers ['If-Modified-Since'] = $params ['If-Modified-Since']; 413 | } 414 | 415 | if ($xml !== "") { 416 | $xml = trim($xml); 417 | $this->xml = $xml; 418 | } 419 | 420 | if ($method == "POST") 421 | $params ['xml'] = $xml; 422 | 423 | $this->prepare_method ( $method ); 424 | $this->config ['multipart'] = $multipart; 425 | $this->url = $url; 426 | $oauthObject = new OAuthSimple (); 427 | try { 428 | $this->sign = $oauthObject->sign ( array ( 429 | 'path' => $url, 430 | 'action' => $method, 431 | 'parameters' => array_merge ( $params, array ( 432 | 'oauth_signature_method' => $this->config ['signature_method'] 433 | ) ), 434 | 'signatures' => $this->config 435 | ) ); 436 | } 437 | 438 | catch ( Exception $e ) { 439 | $errorMessage = 'XeroOAuth::request() ' . $e->getMessage (); 440 | $this->response['response'] = $errorMessage; 441 | $this->response['helper'] = $url; 442 | return $this->response; 443 | } 444 | $this->format = $format; 445 | 446 | $curlRequest = $this->curlit (); 447 | 448 | if ($this->response ['code'] == 401 && isset ( $this->config ['session_handle'] )) { 449 | if ((strpos ( $this->response ['response'], "oauth_problem=token_expired" ) !== false)) { 450 | $this->response ['helper'] = "TokenExpired"; 451 | } else { 452 | $this->response ['helper'] = "TokenFatal"; 453 | } 454 | } 455 | if ($this->response ['code'] == 403) { 456 | $errorMessage = "It looks like your Xero Entrust cert issued by Xero is either invalid or has expired. See http://developer.xero.com/api-overview/http-response-codes/#403 for more"; 457 | // default IIS page isn't informative, a little swap 458 | $this->response ['response'] = $errorMessage; 459 | $this->response ['helper'] = "SetupIssue"; 460 | } 461 | if ($this->response ['code'] == 0) { 462 | $errorMessage = "It looks like your Xero Entrust cert issued by Xero is either invalid or has expired. See http://developer.xero.com/api-overview/http-response-codes/#403 for more"; 463 | $this->response ['response'] = $errorMessage; 464 | $this->response ['helper'] = "SetupIssue"; 465 | } 466 | 467 | return $this->response; 468 | } 469 | 470 | /** 471 | * Convert the response into usable data 472 | * 473 | * @param string $response 474 | * the raw response from the API 475 | * @param string $format 476 | * the format of the response 477 | * @return string the concatenation of the host, API version and API method 478 | */ 479 | function parseResponse($response, $format) { 480 | if (isset ( $format )) { 481 | switch ($format) { 482 | case "pdf" : 483 | $theResponse = $response; 484 | break; 485 | case "json" : 486 | $theResponse = json_decode ( $response ); 487 | break; 488 | default : 489 | $theResponse = simplexml_load_string ( $response ); 490 | break; 491 | } 492 | } 493 | return $theResponse; 494 | } 495 | 496 | /** 497 | * Utility function to create the request URL in the requested format 498 | * 499 | * @param string $request 500 | * the API method without extension 501 | * @return string the concatenation of the host, API version and API method 502 | */ 503 | function url($request, $api = "core") { 504 | if ($request == "RequestToken") { 505 | $this->config ['host'] = $this->config ['site'] . '/oauth/'; 506 | } elseif ($request == "Authorize") { 507 | $this->config ['host'] = $this->config ['authorize_url']; 508 | $request = ""; 509 | } elseif ($request == "AccessToken") { 510 | $this->config ['host'] = $this->config ['site'] . '/oauth/'; 511 | } else { 512 | if (isset ( $api )) { 513 | if ($api == "core") { 514 | $api_stem = "api.xro"; 515 | $api_version = $this->config ['core_version']; 516 | } 517 | if ($api == "payroll") { 518 | $api_stem = "payroll.xro"; 519 | $api_version = $this->config ['payroll_version']; 520 | } 521 | if ($api == "file") { 522 | $api_stem = "files.xro"; 523 | $api_version = $this->config ['file_version']; 524 | } 525 | } 526 | $this->config ['host'] = $this->config ['xero_url'] . $api_stem . '/' . $api_version . '/'; 527 | } 528 | 529 | return implode ( array ( 530 | $this->config ['host'], 531 | $request 532 | ) ); 533 | } 534 | 535 | /** 536 | * Refreshes the access token for partner API type applications 537 | * 538 | * @param string $accessToken 539 | * the current access token for the session 540 | * @param string $sessionHandle 541 | * the current session handle for the session 542 | * @return array response array from request 543 | */ 544 | function refreshToken($accessToken, $sessionHandle) { 545 | $code = $this->request ( 'GET', $this->url ( 'AccessToken', '' ), array ( 546 | 'oauth_token' => $accessToken, 547 | 'oauth_session_handle' => $sessionHandle 548 | ) ); 549 | if ($this->response ['code'] == 200) { 550 | 551 | $response = $this->extract_params ( $this->response ['response'] ); 552 | 553 | return $response; 554 | } else { 555 | $this->response ['helper'] = "TokenFatal"; 556 | return $this->response; 557 | } 558 | } 559 | 560 | /** 561 | * Returns the current URL. 562 | * This is instead of PHP_SELF which is unsafe 563 | * 564 | * @param bool $dropqs 565 | * whether to drop the querystring or not. Default true 566 | * @return string the current URL 567 | */ 568 | public static function php_self($dropqs = true) 569 | { 570 | $protocol = 'http'; 571 | if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') 572 | { 573 | $protocol = 'https'; 574 | } 575 | elseif (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == '443')) 576 | { 577 | $protocol = 'https'; 578 | } 579 | 580 | $url = sprintf('%s://%s%s', $protocol, $_SERVER['SERVER_NAME'], $_SERVER['REQUEST_URI']); 581 | $parts = parse_url($url); 582 | $port = $_SERVER['SERVER_PORT']; 583 | $scheme = $parts['scheme']; 584 | $host = $parts['host']; 585 | $path = @$parts['path']; 586 | $qs = @$parts['query']; 587 | $port or $port = ($scheme == 'https') ? '443' : '80'; 588 | if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) 589 | { 590 | $host = "$host:$port"; 591 | } 592 | 593 | $url = "$scheme://$host$path"; 594 | if (!$dropqs) return "{$url}?{$qs}"; 595 | else return $url; 596 | } 597 | 598 | /* 599 | * Run some basic checks on our config options etc to make sure all is ok 600 | */ 601 | function diagnostics() { 602 | $testOutput = array (); 603 | 604 | /* ENTRUST CERTIFICATE DEPRECATED 605 | if ($this->config ['application_type'] == 'Partner') { 606 | if (! file_get_contents ( $this->config ['curl_ssl_cert'] )) { 607 | $testOutput ['ssl_cert_error'] = "Can't read the Xero Entrust cert. You need one for partner API applications. http://developer.xero.com/documentation/getting-started/partner-applications/ \n"; 608 | } else { 609 | $data = openssl_x509_parse ( file_get_contents ( $this->config ['curl_ssl_cert'] ) ); 610 | $validFrom = date ( 'Y-m-d H:i:s', $data ['validFrom_time_t'] ); 611 | if (time () < $data ['validFrom_time_t']) { 612 | $testOutput ['ssl_cert_error'] = "Xero Entrust cert not yet valid - cert valid from " . $validFrom . "\n"; 613 | } 614 | $validTo = date ( 'Y-m-d H:i:s', $data ['validTo_time_t'] ); 615 | if (time () > $data ['validTo_time_t']) { 616 | $testOutput ['ssl_cert_error'] = "Xero Entrust cert expired - cert valid to " . $validFrom . "\n"; 617 | } 618 | } 619 | } 620 | */ 621 | 622 | if ($this->config ['application_type'] == 'Partner' || $this->config ['application_type'] == 'Private') { 623 | 624 | if (! file_exists ( $this->config ['rsa_public_key'] )) 625 | $testOutput ['rsa_cert_error'] = "Can't read the self-signed SSL cert. Private and Partner API applications require a self-signed X509 cert http://developer.xero.com/documentation/advanced-docs/public-private-keypair/ \n"; 626 | if (file_exists ( $this->config ['rsa_public_key'] )) { 627 | $data = openssl_x509_parse ( file_get_contents ( $this->config ['rsa_public_key'] ) ); 628 | $validFrom = date ( 'Y-m-d H:i:s', $data ['validFrom_time_t'] ); 629 | if (time () < $data ['validFrom_time_t']) { 630 | $testOutput ['ssl_cert_error'] = "Application cert not yet valid - cert valid from " . $validFrom . "\n"; 631 | } 632 | $validTo = date ( 'Y-m-d H:i:s', $data ['validTo_time_t'] ); 633 | if (time () > $data ['validTo_time_t']) { 634 | $testOutput ['ssl_cert_error'] = "Application cert cert expired - cert valid to " . $validFrom . "\n"; 635 | } 636 | } 637 | if (! file_exists ( $this->config ['rsa_private_key'] )) 638 | $testOutput ['rsa_cert_error'] = "Can't read the self-signed cert key. Check your rsa_private_key config variable. Private and Partner API applications require a self-signed X509 cert http://developer.xero.com/documentation/advanced-docs/public-private-keypair/ \n"; 639 | if (file_exists ( $this->config ['rsa_private_key'] )) { 640 | $cert_content = file_get_contents ( $this->config ['rsa_public_key'] ); 641 | $priv_key_content = file_get_contents ( $this->config ['rsa_private_key'] ); 642 | if (! openssl_x509_check_private_key ( $cert_content, $priv_key_content )) 643 | $testOutput ['rsa_cert_error'] = "Application certificate and key do not match \n"; 644 | ; 645 | } 646 | } 647 | 648 | return $testOutput; 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Xero Limited 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /partner.php: -------------------------------------------------------------------------------- 1 | '——YOUR-CONSUMER-KEY—', 40 | 'shared_secret' => '——YOUR-CONSUMER-SECRET—', 41 | // API versions 42 | 'core_version' => '2.0', 43 | 'payroll_version' => '1.0', 44 | 'file_version' => '1.0' 45 | ); 46 | 47 | if (XRO_APP_TYPE == "Private" || XRO_APP_TYPE == "Partner") { 48 | $signatures ['rsa_private_key'] = BASE_PATH . '/certs/privatekey.pem'; 49 | $signatures ['rsa_public_key'] = BASE_PATH . '/certs/publickey.cer'; 50 | } 51 | 52 | /* ENTRUST CERTIFICATE DEPRECATED 53 | if (XRO_APP_TYPE == "Partner") { 54 | $signatures ['curl_ssl_cert'] = BASE_PATH . '/certs/entrust-cert.pem'; 55 | $signatures ['curl_ssl_password'] = '1234'; 56 | $signatures ['curl_ssl_key'] = BASE_PATH . '/certs/entrust-private-nopass.pem'; 57 | } 58 | */ 59 | 60 | $XeroOAuth = new XeroOAuth ( array_merge ( array ( 61 | 'application_type' => XRO_APP_TYPE, 62 | 'oauth_callback' => OAUTH_CALLBACK, 63 | 'user_agent' => $useragent 64 | ), $signatures ) ); 65 | 66 | $initialCheck = $XeroOAuth->diagnostics (); 67 | $checkErrors = count ( $initialCheck ); 68 | if ($checkErrors > 0) { 69 | // you could handle any config errors here, or keep on truckin if you like to live dangerously 70 | foreach ( $initialCheck as $check ) { 71 | echo 'Error: ' . $check . PHP_EOL; 72 | } 73 | } else { 74 | 75 | $here = XeroOAuth::php_self (); 76 | session_start (); 77 | $oauthSession = retrieveSession (); 78 | 79 | include 'tests/tests.php'; 80 | 81 | if (isset ( $_REQUEST ['oauth_verifier'] )) { 82 | $XeroOAuth->config ['access_token'] = $_SESSION ['oauth'] ['oauth_token']; 83 | $XeroOAuth->config ['access_token_secret'] = $_SESSION ['oauth'] ['oauth_token_secret']; 84 | 85 | $code = $XeroOAuth->request ( 'GET', $XeroOAuth->url ( 'AccessToken', '' ), array ( 86 | 'oauth_verifier' => $_REQUEST ['oauth_verifier'], 87 | 'oauth_token' => $_REQUEST ['oauth_token'] 88 | ) ); 89 | 90 | if ($XeroOAuth->response ['code'] == 200) { 91 | 92 | $response = $XeroOAuth->extract_params ( $XeroOAuth->response ['response'] ); 93 | $session = persistSession ( $response ); 94 | 95 | unset ( $_SESSION ['oauth'] ); 96 | header ( "Location: {$here}" ); 97 | } else { 98 | outputError ( $XeroOAuth ); 99 | } 100 | // start the OAuth dance 101 | } elseif (isset ( $_REQUEST ['authenticate'] ) || isset ( $_REQUEST ['authorize'] )) { 102 | $params = array ( 103 | 'oauth_callback' => OAUTH_CALLBACK 104 | ); 105 | 106 | $response = $XeroOAuth->request ( 'GET', $XeroOAuth->url ( 'RequestToken', '' ), $params ); 107 | 108 | if ($XeroOAuth->response ['code'] == 200) { 109 | 110 | $scope = ""; 111 | // $scope = 'payroll.payrollcalendars,payroll.superfunds,payroll.payruns,payroll.payslip,payroll.employees,payroll.TaxDeclaration'; 112 | if ($_REQUEST ['authenticate'] > 1) 113 | $scope = 'payroll.employees,payroll.payruns,payroll.timesheets'; 114 | 115 | print_r ( $XeroOAuth->extract_params ( $XeroOAuth->response ['response'] ) ); 116 | $_SESSION ['oauth'] = $XeroOAuth->extract_params ( $XeroOAuth->response ['response'] ); 117 | 118 | $authurl = $XeroOAuth->url ( "Authorize", '' ) . "?oauth_token={$_SESSION['oauth']['oauth_token']}&scope=" . $scope; 119 | echo '

To complete the OAuth flow follow this URL: ' . $authurl . '

'; 120 | } else { 121 | outputError ( $XeroOAuth ); 122 | } 123 | } 124 | 125 | testLinks (); 126 | } 127 | -------------------------------------------------------------------------------- /private.php: -------------------------------------------------------------------------------- 1 | 'YOURCONSUMERKEY', 11 | 'shared_secret' => 'YOURSECRET', 12 | // API versions 13 | 'core_version' => '2.0', 14 | 'payroll_version' => '1.0', 15 | 'file_version' => '1.0' 16 | ); 17 | 18 | if (XRO_APP_TYPE == "Private" || XRO_APP_TYPE == "Partner") { 19 | $signatures ['rsa_private_key'] = BASE_PATH . '/certs/privatekey.pem'; 20 | $signatures ['rsa_public_key'] = BASE_PATH . '/certs/publickey.cer'; 21 | } 22 | 23 | $XeroOAuth = new XeroOAuth ( array_merge ( array ( 24 | 'application_type' => XRO_APP_TYPE, 25 | 'oauth_callback' => OAUTH_CALLBACK, 26 | 'user_agent' => $useragent 27 | ), $signatures ) ); 28 | include 'tests/testRunner.php'; 29 | 30 | $initialCheck = $XeroOAuth->diagnostics (); 31 | $checkErrors = count ( $initialCheck ); 32 | if ($checkErrors > 0) { 33 | // you could handle any config errors here, or keep on truckin if you like to live dangerously 34 | foreach ( $initialCheck as $check ) { 35 | echo 'Error: ' . $check . PHP_EOL; 36 | } 37 | } else { 38 | $session = persistSession ( array ( 39 | 'oauth_token' => $XeroOAuth->config ['consumer_key'], 40 | 'oauth_token_secret' => $XeroOAuth->config ['shared_secret'], 41 | 'oauth_session_handle' => '' 42 | ) ); 43 | $oauthSession = retrieveSession (); 44 | 45 | if (isset ( $oauthSession ['oauth_token'] )) { 46 | $XeroOAuth->config ['access_token'] = $oauthSession ['oauth_token']; 47 | $XeroOAuth->config ['access_token_secret'] = $oauthSession ['oauth_token_secret']; 48 | 49 | include 'tests/tests.php'; 50 | } 51 | 52 | testLinks (); 53 | } 54 | -------------------------------------------------------------------------------- /public.php: -------------------------------------------------------------------------------- 1 | 'YOURCONSUMERKEY', 43 | 'shared_secret' => 'YOURSECRET', 44 | // API versions 45 | 'core_version' => '2.0', 46 | 'payroll_version' => '1.0', 47 | 'file_version' => '1.0' 48 | ); 49 | 50 | if (XRO_APP_TYPE == "Private" || XRO_APP_TYPE == "Partner") { 51 | $signatures ['rsa_private_key'] = BASE_PATH . '/certs/privatekey.pem'; 52 | $signatures ['rsa_public_key'] = BASE_PATH . '/certs/publickey.cer'; 53 | } 54 | if (XRO_APP_TYPE == "Partner") { 55 | $signatures ['curl_ssl_cert'] = BASE_PATH . '/certs/entrust-cert-RQ3.pem'; 56 | $signatures ['curl_ssl_password'] = '1234'; 57 | $signatures ['curl_ssl_key'] = BASE_PATH . '/certs/entrust-private-RQ3.pem'; 58 | } 59 | 60 | $XeroOAuth = new XeroOAuth ( array_merge ( array ( 61 | 'application_type' => XRO_APP_TYPE, 62 | 'oauth_callback' => OAUTH_CALLBACK, 63 | 'user_agent' => $useragent 64 | ), $signatures ) ); 65 | 66 | $initialCheck = $XeroOAuth->diagnostics (); 67 | $checkErrors = count ( $initialCheck ); 68 | if ($checkErrors > 0) { 69 | // you could handle any config errors here, or keep on truckin if you like to live dangerously 70 | foreach ( $initialCheck as $check ) { 71 | echo 'Error: ' . $check . PHP_EOL; 72 | } 73 | } else { 74 | 75 | $here = XeroOAuth::php_self (); 76 | session_start (); 77 | $oauthSession = retrieveSession (); 78 | 79 | include 'tests/tests.php'; 80 | 81 | if (isset ( $_REQUEST ['oauth_verifier'] )) { 82 | $XeroOAuth->config ['access_token'] = $_SESSION ['oauth'] ['oauth_token']; 83 | $XeroOAuth->config ['access_token_secret'] = $_SESSION ['oauth'] ['oauth_token_secret']; 84 | 85 | $code = $XeroOAuth->request ( 'GET', $XeroOAuth->url ( 'AccessToken', '' ), array ( 86 | 'oauth_verifier' => $_REQUEST ['oauth_verifier'], 87 | 'oauth_token' => $_REQUEST ['oauth_token'] 88 | ) ); 89 | 90 | if ($XeroOAuth->response ['code'] == 200) { 91 | 92 | $response = $XeroOAuth->extract_params ( $XeroOAuth->response ['response'] ); 93 | $session = persistSession ( $response ); 94 | 95 | unset ( $_SESSION ['oauth'] ); 96 | header ( "Location: {$here}" ); 97 | } else { 98 | outputError ( $XeroOAuth ); 99 | } 100 | // start the OAuth dance 101 | } elseif (isset ( $_REQUEST ['authenticate'] ) || isset ( $_REQUEST ['authorize'] )) { 102 | $params = array ( 103 | 'oauth_callback' => OAUTH_CALLBACK 104 | ); 105 | 106 | $response = $XeroOAuth->request ( 'GET', $XeroOAuth->url ( 'RequestToken', '' ), $params ); 107 | 108 | if ($XeroOAuth->response ['code'] == 200) { 109 | 110 | $scope = ""; 111 | // $scope = 'payroll.payrollcalendars,payroll.superfunds,payroll.payruns,payroll.payslip,payroll.employees,payroll.TaxDeclaration'; 112 | if ($_REQUEST ['authenticate'] > 1) 113 | $scope = 'payroll.employees,payroll.payruns,payroll.timesheets'; 114 | 115 | print_r ( $XeroOAuth->extract_params ( $XeroOAuth->response ['response'] ) ); 116 | $_SESSION ['oauth'] = $XeroOAuth->extract_params ( $XeroOAuth->response ['response'] ); 117 | 118 | $authurl = $XeroOAuth->url ( "Authorize", '' ) . "?oauth_token={$_SESSION['oauth']['oauth_token']}&scope=" . $scope; 119 | echo '

To complete the OAuth flow follow this URL: ' . $authurl . '

'; 120 | } else { 121 | outputError ( $XeroOAuth ); 122 | } 123 | } 124 | 125 | testLinks (); 126 | } 127 | -------------------------------------------------------------------------------- /tests/testRunner.php: -------------------------------------------------------------------------------- 1 |
Accounting API 8 | 37 | 38 |
Payroll API 39 | 45 | 46 |
File API 47 | 52 | 53 |
Connection Admin 54 |

'; 66 | 67 | } 68 | 69 | 70 | /** 71 | * Persist the OAuth access token and session handle somewhere 72 | * In my example I am just using the session, but in real world, this is should be a storage engine 73 | * 74 | * @param array $params the response parameters as an array of key=value pairs 75 | */ 76 | function persistSession($response) 77 | { 78 | if (isset($response)) { 79 | $_SESSION['access_token'] = $response['oauth_token']; 80 | $_SESSION['oauth_token_secret'] = $response['oauth_token_secret']; 81 | if(isset($response['oauth_session_handle'])) $_SESSION['session_handle'] = $response['oauth_session_handle']; 82 | } else { 83 | return false; 84 | } 85 | 86 | } 87 | 88 | /** 89 | * Retrieve the OAuth access token and session handle 90 | * In my example I am just using the session, but in real world, this is should be a storage engine 91 | * 92 | */ 93 | function retrieveSession() 94 | { 95 | if (isset($_SESSION['access_token'])) { 96 | $response['oauth_token'] = $_SESSION['access_token']; 97 | $response['oauth_token_secret'] = $_SESSION['oauth_token_secret']; 98 | $response['oauth_session_handle'] = $_SESSION['session_handle']; 99 | return $response; 100 | } else { 101 | return false; 102 | } 103 | 104 | } 105 | 106 | function outputError($XeroOAuth) 107 | { 108 | echo 'Error: ' . $XeroOAuth->response['response'] . PHP_EOL; 109 | pr($XeroOAuth); 110 | } 111 | 112 | /** 113 | * Debug function for printing the content of an object 114 | * 115 | * @param mixes $obj 116 | */ 117 | function pr($obj) 118 | { 119 | 120 | if (!is_cli()) 121 | echo '
';
122 |     if (is_object($obj))
123 |         print_r($obj);
124 |     elseif (is_array($obj))
125 |         print_r($obj);
126 |     else
127 |         echo $obj;
128 |     if (!is_cli())
129 |         echo '
'; 130 | } 131 | 132 | function is_cli() 133 | { 134 | return (PHP_SAPI == 'cli' && empty($_SERVER['REMOTE_ADDR'])); 135 | } 136 | -------------------------------------------------------------------------------- /tests/tests.php: -------------------------------------------------------------------------------- 1 | refreshToken($oauthSession['oauth_token'], $oauthSession['oauth_session_handle']); 13 | if ($XeroOAuth->response['code'] == 200) { 14 | $session = persistSession($response); 15 | $oauthSession = retrieveSession(); 16 | } else { 17 | outputError($XeroOAuth); 18 | if ($XeroOAuth->response['helper'] == "TokenExpired") $XeroOAuth->refreshToken($oauthSession['oauth_token'], $oauthSession['session_handle']); 19 | } 20 | 21 | } elseif ( isset($oauthSession['oauth_token']) && isset($_REQUEST) ) { 22 | 23 | $XeroOAuth->config['access_token'] = $oauthSession['oauth_token']; 24 | $XeroOAuth->config['access_token_secret'] = $oauthSession['oauth_token_secret']; 25 | $XeroOAuth->config['session_handle'] = $oauthSession['oauth_session_handle']; 26 | 27 | 28 | if (isset($_REQUEST['accounts'])) { 29 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Accounts', 'core'), array('Where' => $_REQUEST['where'])); 30 | if ($XeroOAuth->response['code'] == 200) { 31 | $accounts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 32 | echo "There are " . count($accounts->Accounts[0]). " accounts in this Xero organisation, the first one is:
"; 33 | pr($accounts->Accounts[0]->Account); 34 | } else { 35 | outputError($XeroOAuth); 36 | } 37 | } 38 | 39 | if (isset($_REQUEST['payments'])) { 40 | if (!isset($_REQUEST['method'])) { 41 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Payments', 'core'), array('Where' => 'Status=="AUTHORISED"')); 42 | if ($XeroOAuth->response['code'] == 200) { 43 | $payments = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 44 | echo "There are " . count($payments->Payments[0]). " payments in this Xero organisation, the first one is:
"; 45 | pr($payments->Payments[0]->Payment); 46 | } else { 47 | outputError($XeroOAuth); 48 | } 49 | 50 | } elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "post" && $_REQUEST['payments']== 1 ) { 51 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Payments', 'core'), array('Where' => 'Status=="AUTHORISED"')); 52 | if ($XeroOAuth->response['code'] == 200) { 53 | $payment = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 54 | if(count($payment->Payments[0]) > 0){ 55 | echo "Deleting the first available payment with ID: " . $payment->Payments[0]->Payment->PaymentID . "
"; 56 | } 57 | } 58 | $xml = " 59 | DELETED 60 | "; 61 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('Payments/'.$payment->Payments[0]->Payment->PaymentID, 'core'), array(), $xml); 62 | if ($XeroOAuth->response['code'] == 200) { 63 | $payments = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 64 | echo count($payments->Payments[0]). " payment deleted in this Xero organisation:
"; 65 | pr($payments->Payments[0]->Payment); 66 | } else { 67 | outputError($XeroOAuth); 68 | } 69 | 70 | } 71 | } 72 | 73 | if (isset($_REQUEST['accountsfilter'])) { 74 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Accounts', 'core'), array('Where' => 'Type=="BANK"')); 75 | if ($XeroOAuth->response['code'] == 200) { 76 | $accounts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 77 | echo "There are " . count($accounts->Accounts[0]). " accounts in this Xero organisation, the first one is:
"; 78 | pr($accounts->Accounts[0]->Account); 79 | } else { 80 | outputError($XeroOAuth); 81 | } 82 | } 83 | if (isset($_REQUEST['payrollemployees'])) { 84 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Employees', 'payroll'), array()); 85 | if ($XeroOAuth->response['code'] == 200) { 86 | $employees = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 87 | echo "There are " . count($employees->Employees[0]). " employees in this Xero organisation, the first one is:
"; 88 | pr($employees->Employees[0]->Employee); 89 | } else { 90 | outputError($XeroOAuth); 91 | } 92 | } 93 | if (isset($_REQUEST['payrollsuperfunds'])) { 94 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('SuperFunds', 'payroll'), array()); 95 | if ($XeroOAuth->response['code'] == 200) { 96 | $superfunds = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 97 | echo "There are " . count($superfunds->SuperFunds[0]). " superfunds in this Xero organisation, the first one is:
"; 98 | pr($superfunds->SuperFunds[0]->SuperFund); 99 | } else { 100 | outputError($XeroOAuth); 101 | } 102 | } 103 | if (isset($_REQUEST['payruns'])) { 104 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('PayRuns', 'payroll'), array('Where' => $_REQUEST['where'])); 105 | if ($XeroOAuth->response['code'] == 200) { 106 | $accounts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 107 | echo "There are " . count($accounts->PayRuns[0]). " PayRuns in this Xero organisation, the first one is:
"; 108 | pr($accounts->PayRuns[0]->PayRun); 109 | } else { 110 | outputError($XeroOAuth); 111 | } 112 | } 113 | if (isset($_REQUEST['timesheets'])) { 114 | $xml = " 115 | 5e493b2e-c3ed-4172-95b2-593438101f76 116 | 2015-04-13T00:00:00 117 | 2015-04-20T00:00:00 118 | Draft 119 | "; 120 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('Timesheets', 'payroll'), array(), $xml, 'xml'); 121 | if ($XeroOAuth->response['code'] == 200) { 122 | $timesheets = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 123 | echo "There are " . count($timesheets->Timesheets[0]). " Timesheet created in this Xero organisation, the first one is:
"; 124 | pr($timesheets->Timesheets[0]->Timesheet); 125 | } else { 126 | outputError($XeroOAuth); 127 | } 128 | } 129 | if (isset($_REQUEST['superfundproducts'])) { 130 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('SuperFundProducts', 'payroll'), array('ABN' => $_REQUEST['where'])); 131 | if ($XeroOAuth->response['code'] == 200) { 132 | $accounts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 133 | echo "There are " . count($accounts->SuperFundProducts[0]). " SuperFundProducts in this Xero organisation, the first one is:
"; 134 | pr($accounts->SuperFundProducts[0]->SuperFundProduct[0]); 135 | } else { 136 | outputError($XeroOAuth); 137 | } 138 | } 139 | if (isset($_REQUEST['invoice'])) { 140 | if (!isset($_REQUEST['method'])) { 141 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Invoices', 'core'), array('order' => 'Total DESC')); 142 | if ($XeroOAuth->response['code'] == 200) { 143 | $invoices = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 144 | echo "There are " . count($invoices->Invoices[0]). " invoices in this Xero organisation, the first one is:
"; 145 | pr($invoices->Invoices[0]->Invoice); 146 | if ($_REQUEST['invoice']=="pdf") { 147 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Invoice/'.$invoices->Invoices[0]->Invoice->InvoiceID, 'core'), array(), "", 'pdf'); 148 | if ($XeroOAuth->response['code'] == 200) { 149 | $myFile = $invoices->Invoices[0]->Invoice->InvoiceID.".pdf"; 150 | $fh = fopen($myFile, 'w') or die("can't open file"); 151 | fwrite($fh, $XeroOAuth->response['response']); 152 | fclose($fh); 153 | echo "PDF copy downloaded, check your the directory of this script.
"; 154 | } else { 155 | outputError($XeroOAuth); 156 | } 157 | } 158 | } else { 159 | outputError($XeroOAuth); 160 | } 161 | } elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "put" && $_REQUEST['invoice']== 1 ) { 162 | $xml = " 163 | 164 | ACCREC 165 | 166 | Martin Hudson 167 | 168 | 2013-05-13T00:00:00 169 | 2013-05-20T00:00:00 170 | Exclusive 171 | 172 | 173 | Monthly rental for property at 56a Wilkins Avenue 174 | 4.3400 175 | 395.00 176 | 200 177 | 178 | 179 | 180 | "; 181 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('Invoices', 'core'), array(), $xml); 182 | if ($XeroOAuth->response['code'] == 200) { 183 | $invoice = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 184 | echo "" . count($invoice->Invoices[0]). " invoice created in this Xero organisation."; 185 | if (count($invoice->Invoices[0])>0) { 186 | echo "The first one is:
"; 187 | pr($invoice->Invoices[0]->Invoice); 188 | } 189 | } else { 190 | outputError($XeroOAuth); 191 | } 192 | } elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "4dp" && $_REQUEST['invoice']== 1 ) { 193 | $xml = " 194 | 195 | ACCREC 196 | 197 | Steve Buscemi 198 | 199 | 2014-05-13T00:00:00 200 | 2014-05-20T00:00:00 201 | Exclusive 202 | 203 | 204 | Monthly rental for property at 56b Wilkins Avenue 205 | 4.3400 206 | 395.6789 207 | 200 208 | 209 | 210 | 211 | "; 212 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('Invoices', 'core'), array('unitdp' => '4'), $xml); 213 | if ($XeroOAuth->response['code'] == 200) { 214 | $invoice = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 215 | echo "" . count($invoice->Invoices[0]). " invoice created in this Xero organisation."; 216 | if (count($invoice->Invoices[0])>0) { 217 | echo "The first one is:
"; 218 | pr($invoice->Invoices[0]->Invoice); 219 | } 220 | } else { 221 | outputError($XeroOAuth); 222 | } 223 | } elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "post" ) { 224 | $xml = " 225 | 226 | ACCREC 227 | 228 | Martin Hudson 229 | 230 | 2013-05-13T00:00:00 231 | 2013-05-20T00:00:00 232 | Exclusive 233 | 234 | 235 | Monthly rental for property at 56a Wilkins Avenue 236 | 4.3400 237 | 395.00 238 | 200 239 | 240 | 241 | 242 | "; 243 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('Invoices', 'core'), array(), $xml); 244 | if ($XeroOAuth->response['code'] == 200) { 245 | $invoice = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 246 | echo "" . count($invoice->Invoices[0]). " invoice created in this Xero organisation."; 247 | if (count($invoice->Invoices[0])>0) { 248 | echo "The first one is:
"; 249 | pr($invoice->Invoices[0]->Invoice); 250 | } 251 | } else { 252 | outputError($XeroOAuth); 253 | } 254 | }elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "put" && $_REQUEST['invoice']=="attachment" ) { 255 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Invoices', 'core'), array('Where' => 'Status=="DRAFT"')); 256 | if ($XeroOAuth->response['code'] == 200) { 257 | $invoices = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 258 | echo "There are " . count($invoices->Invoices[0]). " draft invoices in this Xero organisation, the first one is:
"; 259 | pr($invoices->Invoices[0]->Invoice); 260 | if ($_REQUEST['invoice']=="attachment") { 261 | $attachmentFile = file_get_contents('http://i.imgur.com/mkDFLf2.png'); 262 | 263 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('Invoice/'.$invoices->Invoices[0]->Invoice->InvoiceID.'/Attachments/image.png', 'core'), array(), $attachmentFile, 'file'); 264 | if ($XeroOAuth->response['code'] == 200) { 265 | echo "Attachment successfully created against this invoice."; 266 | } else { 267 | outputError($XeroOAuth); 268 | } 269 | } 270 | } else { 271 | outputError($XeroOAuth); 272 | } 273 | 274 | } 275 | 276 | 277 | } 278 | if (isset($_REQUEST['invoicesfilter'])) { 279 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Invoices', 'core'), array('Where' => 'Contact.Name.Contains("Martin")')); 280 | 281 | if ($XeroOAuth->response['code'] == 200) { 282 | $accounts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 283 | echo "There are " . count($accounts->Invoices[0]). " matching invoices in this Xero organisation, the first one is:
"; 284 | pr($accounts->Invoices[0]->Invoice); 285 | } else { 286 | outputError($XeroOAuth); 287 | } 288 | } 289 | if (isset($_REQUEST['invoicesmodified'])) { 290 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Invoices', 'core'), array('If-Modified-Since' => gmdate("M d Y H:i:s",(time() - (1 * 24 * 60 * 60))))); 291 | if ($XeroOAuth->response['code'] == 200) { 292 | $accounts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 293 | echo "There are " . count($accounts->Invoices[0]). " matching invoices in this Xero organisation, the first one is:
"; 294 | pr($accounts->Invoices[0]->Invoice); 295 | } else { 296 | outputError($XeroOAuth); 297 | } 298 | } 299 | if (isset($_REQUEST['banktransactions'])) { 300 | if (!isset($_REQUEST['method'])) { 301 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('BankTransactions', 'core'), array(), "", "xml"); 302 | if ($XeroOAuth->response['code'] == 200) { 303 | $banktransactions = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 304 | echo "There are " . count($banktransactions->BankTransactions[0]). " bank transactions in this Xero organisation."; 305 | if (count($banktransactions->BankTransactions[0])>0) { 306 | echo "The first one is:
"; 307 | pr($banktransactions->BankTransactions[0]->BankTransaction); 308 | } 309 | } else { 310 | outputError($XeroOAuth); 311 | } 312 | } elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "put" ) { 313 | $xml = " 314 | 315 | SPEND 316 | 317 | Westpac 318 | 319 | 2013-04-16T00:00:00 320 | 321 | 322 | Yearly Bank & Account Fee 323 | 1.0000 324 | 20.00 325 | 400 326 | 327 | 328 | 329 | 090 330 | 331 | 332 | "; 333 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('BankTransactions', 'core'), array(), $xml); 334 | if ($XeroOAuth->response['code'] == 200) { 335 | $banktransactions = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 336 | echo "There are " . count($banktransactions->BankTransactions[0]). " successful bank transaction(s) created in this Xero organisation."; 337 | if (count($banktransactions->BankTransactions[0])>0) { 338 | echo "The first one is:
"; 339 | pr($banktransactions->BankTransactions[0]->BankTransaction); 340 | } 341 | } else { 342 | outputError($XeroOAuth); 343 | } 344 | } 345 | } 346 | 347 | if( isset($_REQUEST['contacts'])) { 348 | if (!isset($_REQUEST['method'])) { 349 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Contacts', 'core'), array()); 350 | if ($XeroOAuth->response['code'] == 200) { 351 | $contacts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 352 | echo "There are " . count($contacts->Contacts[0]). " contacts in this Xero organisation, the first one is:
"; 353 | pr($contacts->Contacts[0]->Contact); 354 | 355 | } else { 356 | outputError($XeroOAuth); 357 | } 358 | } elseif(isset($_REQUEST['method']) && $_REQUEST['method'] == "post" ){ 359 | $xml = " 360 | 361 | Matthew and son 362 | emailaddress@yourdomain.com 363 | matthewson_test99 364 | Matthew 365 | Masters 366 | 367 | 368 | "; 369 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('Contacts', 'core'), array(), $xml); 370 | if ($XeroOAuth->response['code'] == 200) { 371 | $contact = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 372 | echo "" . count($contact->Contacts[0]). " contact created/updated in this Xero organisation."; 373 | if (count($contact->Contacts[0])>0) { 374 | echo "The first one is:
"; 375 | pr($contact->Contacts[0]->Contact); 376 | } 377 | } else { 378 | outputError($XeroOAuth); 379 | } 380 | }elseif(isset($_REQUEST['method']) && $_REQUEST['method'] == "put" ){ 381 | $xml = " 382 | 383 | Orlena Greenville 384 | 385 | "; 386 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('Contacts', 'core'), array(), $xml); 387 | if ($XeroOAuth->response['code'] == 200) { 388 | $contacts = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 389 | echo "There are " . count($contacts->Contacts[0]). " successful contact(s) created in this Xero organisation."; 390 | if(count($contacts->Contacts[0])>0){ 391 | echo "The first one is:
"; 392 | pr($contacts->Contacts[0]->Contact); 393 | } 394 | } else { 395 | outputError($XeroOAuth); 396 | } 397 | } 398 | } 399 | 400 | if( isset($_REQUEST['items'])) { 401 | if (!isset($_REQUEST['method'])) { 402 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Items', 'core'), array()); 403 | if ($XeroOAuth->response['code'] == 200) { 404 | $items = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 405 | echo "There are " . count($items->Items[0]). " items in this Xero organisation, the first one is:
"; 406 | pr($items->Items[0]->Item); 407 | 408 | } else { 409 | outputError($XeroOAuth); 410 | } 411 | } elseif(isset($_REQUEST['method']) && $_REQUEST['method'] == "put" ){ 412 | $xml = " 413 | 414 | ITEM-CODE-01 415 | 416 | 417 | "; 418 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('Items', 'core'), array(), $xml); 419 | if ($XeroOAuth->response['code'] == 200) { 420 | $item = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 421 | echo "" . count($item->Items). " item created in this Xero organisation. "; 422 | if (count($item->Items[0])>0) { 423 | echo "The item is:
"; 424 | pr($item->Items[0]->Item); 425 | } 426 | } else { 427 | outputError($XeroOAuth); 428 | } 429 | } 430 | } 431 | 432 | if (isset($_REQUEST['organisation'])&&$_REQUEST['request']=="") { 433 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Organisation', 'core'), array('page' => 0)); 434 | if ($XeroOAuth->response['code'] == 200) { 435 | $organisation = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 436 | echo "Organisation name: " . $organisation->Organisations[0]->Organisation->Name; 437 | } else { 438 | outputError($XeroOAuth); 439 | } 440 | }elseif (isset($_REQUEST['organisation'])&&$_REQUEST['request']=="json") { 441 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Organisation', 'core'), array(), $xml, 'json'); 442 | if ($XeroOAuth->response['code'] == 200) { 443 | $organisation = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 444 | $json = json_decode(json_encode($organisation),true); 445 | echo "Organisation name: " . $json['Organisations'][0]['Name']; 446 | } else { 447 | outputError($XeroOAuth); 448 | } 449 | } 450 | 451 | if (isset($_REQUEST['trialbalance'])) { 452 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Reports/TrialBalance', 'core'), array('page' => 0)); 453 | if ($XeroOAuth->response['code'] == 200) { 454 | $report = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 455 | echo "Organisation name: " . $report->Organisations[0]->Organisation->Name; 456 | } else { 457 | outputError($XeroOAuth); 458 | } 459 | } 460 | 461 | if (isset($_REQUEST['trackingcategories'])) { 462 | if (!isset($_REQUEST['method'])) { 463 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('TrackingCategories', 'core'), array('page' => 0)); 464 | if ($XeroOAuth->response['code'] == 200) { 465 | $tracking = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 466 | echo "There are " . count($tracking->TrackingCategories[0]). " tracking categories in this Xero organisation, the first with ". count($tracking->TrackingCategories[0]->TrackingCategory->Options) ." options.
"; 467 | echo "The first one has tracking category name: " . $tracking->TrackingCategories[0]->TrackingCategory->Name; 468 | echo "
The first option in that category is: " . $tracking->TrackingCategories[0]->TrackingCategory->Options->Option[0]->Name; 469 | } else { 470 | outputError($XeroOAuth); 471 | } 472 | }elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "getarchived") { 473 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('TrackingCategories', 'core'), array('includeArchived' => 'true')); 474 | if ($XeroOAuth->response['code'] == 200) { 475 | $tracking = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 476 | echo "There are " . count($tracking->TrackingCategories[0]). " tracking categories in this Xero organisation, the first with ". count($tracking->TrackingCategories[0]->TrackingCategory->Options) ." options.
"; 477 | echo "The first one has tracking category name: " . $tracking->TrackingCategories[0]->TrackingCategory->Name; 478 | echo "
The first option in that category is: " . $tracking->TrackingCategories[0]->TrackingCategory->Options->Option[0]->Name; 479 | } else { 480 | outputError($XeroOAuth); 481 | } 482 | }elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "put" && $_REQUEST['trackingcategories']== 1 ) { 483 | $xml = " 484 | 485 | Salespersons 486 | ACTIVE 487 | 488 | "; 489 | $response = $XeroOAuth->request('PUT', $XeroOAuth->url('TrackingCategories', 'core'), array(), $xml); 490 | if ($XeroOAuth->response['code'] == 200) { 491 | $tracking = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 492 | echo "" . count($tracking->TrackingCategories[0]). " tracking created in this Xero organisation."; 493 | if (count($tracking->TrackingCategories[0])>0) { 494 | echo "The first one is:
"; 495 | pr($tracking->TrackingCategories[0]->TrackingCategory); 496 | } 497 | } else { 498 | outputError($XeroOAuth); 499 | } 500 | }elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "archive" && $_REQUEST['trackingcategories']== 1 ) { 501 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('TrackingCategories', 'core'), array()); 502 | if ($XeroOAuth->response['code'] == 200) { 503 | $tracking = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 504 | echo "There are " . count($tracking->TrackingCategories[0]). " tracking categories in this Xero organisation.
"; 505 | if(count($tracking->TrackingCategories[0]) > 0){ 506 | echo "The first one has tracking category name: " . $tracking->TrackingCategories[0]->TrackingCategory->Name; 507 | echo ", and will be archived.
"; 508 | } 509 | } 510 | $xml = " 511 | 512 | ".$tracking->TrackingCategories[0]->TrackingCategory->Name." 513 | ".$tracking->TrackingCategories[0]->TrackingCategory->TrackingCategoryID." 514 | ARCHIVED 515 | 516 | "; 517 | if(count($tracking->TrackingCategories[0]) > 0){ 518 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('TrackingCategories', 'core'), array(), $xml); 519 | if ($XeroOAuth->response['code'] == 200) { 520 | $tracking = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 521 | echo "" . count($tracking->TrackingCategories[0]). " tracking archived in this Xero organisation."; 522 | if (count($tracking->TrackingCategories[0])>0) { 523 | echo "The first one is:
"; 524 | pr($tracking); 525 | } 526 | } else { 527 | outputError($XeroOAuth); 528 | } 529 | } 530 | }elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "restore" && $_REQUEST['trackingcategories']== 1 ) { 531 | $xml = " 532 | 533 | Region 534 | 535 | ACTIVE 536 | 537 | "; 538 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('TrackingCategories', 'core'), array(), $xml); 539 | if ($XeroOAuth->response['code'] == 200) { 540 | $tracking = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 541 | echo "" . count($tracking->TrackingCategories[0]). " tracking restored in this Xero organisation."; 542 | if (count($tracking->TrackingCategories[0])>0) { 543 | echo "The first one is:
"; 544 | pr($tracking->TrackingCategories[0]); 545 | } 546 | } else { 547 | outputError($XeroOAuth); 548 | } 549 | } 550 | } 551 | 552 | if (isset($_REQUEST['folders'])) { 553 | if (!isset($_REQUEST['method'])) { 554 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Folders', 'file'), array()); 555 | if ($XeroOAuth->response['code'] == 200) { 556 | $folders = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 557 | echo "There are " . count($folders). " folders in this Xero organisation, the first one is:
"; 558 | pr($folders->Folder[0]); 559 | } else { 560 | outputError($XeroOAuth); 561 | } 562 | }elseif (isset($_REQUEST['method']) && $_REQUEST['method'] == "files" && $_REQUEST['folders']== 1 ) { 563 | 564 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Folders', 'file'), array()); 565 | if ($XeroOAuth->response['code'] == 200) { 566 | $folder = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 567 | echo "There are " . count($folder). " folders in this Xero organisation.
"; 568 | if(count($folder->Folder[0]) > 0){ 569 | echo "The first one has the name: " . $folder->Folder[0]->Name; 570 | echo ", and will be checked for files.
"; 571 | } 572 | } 573 | 574 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Folders/'.$folder->Folder[0]->Id.'/Files', 'file'), array(), $xml); 575 | if ($XeroOAuth->response['code'] == 200) { 576 | $folders = $XeroOAuth->parseResponse($XeroOAuth->response['response'], $XeroOAuth->response['format']); 577 | echo "" . $folders->FileCount. " files in this folder."; 578 | if (count($folders->Files[0])>0) { 579 | echo "The first one is:
"; 580 | pr($folders->Files[0]->Items[0]->File[0]); 581 | } 582 | } else { 583 | outputError($XeroOAuth); 584 | } 585 | } 586 | } 587 | 588 | if (isset($_REQUEST['multipleoperations'])) { 589 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('Organisation', 'core'), array('page' => 0)); 590 | if ($XeroOAuth->response['code'] == 200) { 591 | } else { 592 | outputError($XeroOAuth); 593 | } 594 | 595 | $xml = " 596 | 597 | Test group 598 | ACTIVE 599 | 600 | "; 601 | $response = $XeroOAuth->request('POST', $XeroOAuth->url('ContactGroups', 'core'), array(), $xml); 602 | if ($XeroOAuth->response['code'] == 200) { 603 | } else { 604 | outputError($XeroOAuth); 605 | } 606 | $response = $XeroOAuth->request('GET', $XeroOAuth->url('ContactGroups', 'core'), array('page' => 0)); 607 | if ($XeroOAuth->response['code'] == 200) { 608 | } else { 609 | outputError($XeroOAuth); 610 | } 611 | 612 | } 613 | 614 | } 615 | --------------------------------------------------------------------------------