Services/Xero/OAuth.php
authoredward <edward@roojs.com>
Wed, 27 Jun 2018 05:56:38 +0000 (13:56 +0800)
committeredward <edward@roojs.com>
Wed, 27 Jun 2018 05:56:38 +0000 (13:56 +0800)
Services/Xero/OAuth.php

index 1939454..515553d 100644 (file)
 <?php
 
-//require_once 'Services/Xero/OAuthSign.php';
-//
-///**
-// * Define a custom Exception for easy trap and detection
-// */
-////class XeroOAuthException extends Exception {}
-//
-//class Services_Xero_OAuth
-//{
-//    var $_xero_defaults =array (
-//        'xero_url' => 'https://api.xero.com/',
-//        'site' => 'https://api.xero.com',
-//        'authorize_url' => 'https://api.xero.com/oauth/Authorize',
-//        'signature_method' => 'RSA-SHA1'
-//    );
-//    
-//    
-//    var $_xero_consumer_options = array (
-//        'request_token_path' => 'oauth/RequestToken',
-//        'access_token_path' => 'oauth/AccessToken',
-//        'authorize_path' => 'oauth/Authorize',
-//    );
-//    
-//    var $_xero_curl_options = array ( // you probably don't want to change any of these curl values
-//        CURLOPT_USERAGENT           => 'XeroOAuth-PHP',
-//        CURLOPT_CONNECTTIMEOUT      => 30,
-//        CURLOPT_TIMEOUT             => 20,
-//        CURLOPT_RETURNTRANSFER      => true,
-//        // for security you may want to set this to TRUE. If you do you need
-//        // to install the servers certificate in your local certificate store.
-//        CURLOPT_SSL_VERIFYPEER      => 2,
-//        // include ca-bundle.crt from http://curl.haxx.se/ca/cacert.pem
-//        //'curl_cainfo' => $ca_cert_path . '/ca-bundle.crt',
-////        CURLOPT_SSL_VERIFYPEER => '/etc/ssl/certs/ca-certificates.crt', // ??? wrong name??
-//        CURLOPT_CAINFO              => '/etc/ssl/certs/ca-certificates.crt',
-//        CURLOPT_FOLLOWLOCATION      => false, // whether to follow redirects or not
-//        // TRUE/1 is not a valid ssl verifyhost value with curl >= 7.28.1 and 2 is more secure as well.
-//        // More details here: http://php.net/manual/en/function.curl-setopt.php
-//        CURLOPT_SSL_VERIFYHOST      => 2,
-//        // support for proxy servers
-//        CURLOPT_PROXY               => false, // really you don't want to use this if you are using streaming
-//        CURLOPT_PROXYUSERPWD        => false, // format username:password for proxy, if required
-//        CURLOPT_ENCODING            => '', // leave blank for all supported formats, else use gzip, deflate, identity
-//        CURLOPT_VERBOSE             => true,
-//        CURLOPT_HEADER              => false,
-//        CURLINFO_HEADER_OUT         => true
-//    );
-//                
-//      
-//    var $_action;
-//    var $_nonce_chars;
-//    
-//    var $config = array (
-//        'application_type' => 'Private',
-//        'core_version' => '2.0',
-//        'payroll_version' => '1.0',
-//        'file_version' => '1.0',
-//        'rsa_private_key' => '',
-//        'rsa_public_key' => ''
-//    );
-//     
-//   /**
-//    * Creates a new XeroOAuth object
-//    *
-//    * @param string $config,
-//    *                the configuration settings
-//    */
-//    function __construct($config) 
-//    {    
-//        $this->params = array ();
-//        $this->headers = array ();
-//        $this->auto_fixed_time = false;
-//        $this->buffer = null;
-//        $this->request_params = array();
-//        
-//        foreach($this->_xero_defaults as $k => $v) {
-//            if (isset($config[$k])) {
-//                $this->_xero_defaults[$k] = $config[$k];
-//            }
-//        }
-//        
-//        if (!empty($config['application_type']) && ($config['application_type'] == "Public")) {
-//            $this->_xero_defaults['signature_method'] =  'HMAC-SHA1';
-//        }
-//        
-//        foreach($this->_xero_curl_options as $k => $v) {
-//            if (isset($config[$k])) {
-//                $this->_xero_curl_options[$k] = $config[$k];
-//            }
-//        }
-//        
-//        foreach ($config as $k => $v){
-//            $this->config[$k] = $v;
-//        }
-//        
-//        $this->config = array_merge ($this->_xero_defaults, $this->_xero_consumer_options, $this->_xero_curl_options, $this->config);
-//    }
-//     
-//   /**
-//    * Utility function to parse the returned curl headers and store them in the
-//    * class array variable.
-//    *
-//    * @param object $ch
-//    *                curl handle
-//    * @param string $header
-//    *                the response headers
-//    * @return the string length of the header
-//    */
-//    private function curlHeader($ch, $header) 
-//    {
-//        $i = strpos($header, ':');
-//        if (!empty($i)) {
-//            $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
-//            $value = trim(substr($header, $i + 2));
-//            $this->response ['headers'] [$key] = $value;
-//        }
-//        return strlen($header);
-//    }
-//
-//    /**
-//    * Utility function to parse the returned curl buffer and store them until
-//    * an EOL is found.
-//    * The buffer for curl is an undefined size so we need
-//    * to collect the content until an EOL is found.
-//    *
-//    * This function calls the previously defined streaming callback method.
-//    *
-//    * @param object $ch
-//    *                curl handle
-//    * @param string $data
-//    *                the current curl buffer
-//    */
-//    private function curlWrite($ch, $data) 
-//    {
-//        $l = strlen($data);
-//        if (strpos($data, $this->config['streaming_eol']) === false) {
-//            $this->buffer .= $data;
-//            return $l;
-//        }
-//
-//        $buffered = explode($this->config['streaming_eol'], $data);
-//        $content = $this->buffer . $buffered [0];
-//
-//        $this->metrics ['tweets'] ++;
-//        $this->metrics ['bytes'] += strlen($content);
-//
-//        if (!function_exists($this->config['streaming_callback'])) {
-//            return 0;
-//        }
-//        
-//        $metrics = $this->update_metrics();
-//        $stop = call_user_func($this->config['streaming_callback'], $content, strlen($content), $metrics);
-//        $this->buffer = $buffered [1];
-//        if ($stop) {
-//            return 0;
-//        }
-//        
-//        return $l;
-//    }
-//     
-//   /**
-//    * Extracts and decodes OAuth parameters from the passed string
-//    *
-//    * @param string $body
-//    *                the response body from an OAuth flow method
-//    * @return array the response body safely decoded to an array of key => values
-//    */
-//    function extract_params($body) 
-//    {
-//        $kvs = explode('&', $body);
-//        $decoded = array();
-//        foreach ($kvs as $kv) {
-//            $kv = explode('=', $kv, 2);
-//            $kv [0] = $this->safe_decode($kv [0]);
-//            $kv [1] = $this->safe_decode($kv [1]);
-//            $decoded [$kv [0]] = $kv [1];
-//        }
-//        return $decoded;
-//    }
-//
-//    /**
-//    * Encodes the string or array passed in a way compatible with OAuth.
-//    * If an array is passed each array value will will be encoded.
-//    *
-//    * @param mixed $data
-//    *                the scalar or array to encode
-//    * @return $data encoded in a way compatible with OAuth
-//    */
-//    private function safe_encode($data) 
-//    {
-//        if (is_array($data)) {
-//            return array_map(array(
-//                $this,
-//                'safe_encode'
-//            ), $data);
-//        } else if (is_scalar($data)) {
-//            return str_ireplace(array(
-//                '+',
-//                '%7E'
-//            ), array(
-//                ' ',
-//                '~'
-//            ), rawurlencode($data));
-//        } else {
-//            return '';
-//        }
-//    }
-//
-//    /**
-//    * Decodes the string or array from it's URL encoded form
-//    * If an array is passed each array value will will be decoded.
-//    *
-//    * @param mixed $data
-//    *                the scalar or array to decode
-//    * @return $data decoded from the URL encoded form
-//    */
-//    private function safe_decode($data) 
-//    {
-//        if (is_array($data)) {
-//            return array_map(array(
-//                $this,
-//                'safe_decode'
-//            ), $data);
-//        } else if (is_scalar($data)) {
-//            return rawurldecode($data);
-//        } else {
-//            return '';
-//        }
-//    }
-//
-//    /**
-//    * Prepares the HTTP method for use in the base string by converting it to
-//    * uppercase.
-//    *
-//    * @param string $method
-//    *                an HTTP method such as GET or POST
-//    * @return void value is stored to a class variable
-//    * @author themattharris
-//    */
-//    private function prepare_method($method) 
-//    {
-//        $this->method = strtoupper ( $method );
-//    }
-//     
-//   /**
-//    * Makes a curl request.
-//    * Takes no parameters as all should have been prepared
-//    * by the request method
-//    *
-//    * @return void response data is stored in the class variable 'response'
-//    */
-//    private function curlit() 
-//    {
-//        $this->request_params = array();
-//        
-//        // configure curl
-//        $c = curl_init();
-//
-//        curl_setopt_array($c, $this->_xero_curl_options);
-//        
-//        curl_setopt_array($c, array(
-//            CURLOPT_URL => $this->sign['signed_url'],
-//            CURLOPT_HEADERFUNCTION => array (
-//                $this,
-//                'curlHeader' 
-//            ),
-//        ));
-//
-//        /* ENTRUST CERTIFICATE DEPRECATED
-//          if ($this->config ['application_type'] == "Partner") {
-//          curl_setopt_array ( $c, array (
-//          // ssl client cert options for partner apps
-//          CURLOPT_SSLCERT => $this->config ['curl_ssl_cert'],
-//          CURLOPT_SSLKEYPASSWD => $this->config ['curl_ssl_password'],
-//          CURLOPT_SSLKEY => $this->config ['curl_ssl_key']
-//          ) );
-//          }
-//         */
-//
-//        if (isset($this->config ['is_streaming'])) {
-//            // process the body
-//            $this->response['content-length'] = 0;
-//            curl_setopt($c, CURLOPT_TIMEOUT, 0);
-//            curl_setopt($c, CURLOPT_WRITEFUNCTION, array(
-//                $this,
-//                'curlWrite'
-//            ));
-//        }
-//        
-//        switch ($this->method) {
-//            case 'GET' :
-//                $contentLength = 0;
-//                break;
-//
-//            case 'POST' :
-//                curl_setopt($c, CURLOPT_POST, TRUE);
-//                $post_body = $this->safe_encode($this->xml);
-//                curl_setopt($c, CURLOPT_POSTFIELDS, $post_body);
-//                $this->request_params['xml'] = $post_body;
-//                $contentLength = strlen($post_body);
-//                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
-//
-//                break;
-//
-//            case 'PUT' :
-//                $fh = tmpfile();
-//                if ($this->format == "file") {
-//                    $put_body = $this->xml;
-//                } else {
-//                    $put_body = $this->safe_encode($this->xml);
-//                    $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
-//                }
-//                fwrite($fh, $put_body);
-//                rewind($fh);
-//                curl_setopt($c, CURLOPT_PUT, true);
-//                curl_setopt($c, CURLOPT_INFILE, $fh);
-//                curl_setopt($c, CURLOPT_INFILESIZE, strlen($put_body));
-//                $contentLength = strlen($put_body);
-//
-//                break;
-//
-//
-//            default :
-//                curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method);
-//        }
-//
-//        if (!empty($this->request_params)) {
-//            // if not doing multipart we need to implode the parameters
-//            if (!$this->config ['multipart']) {
-//                foreach ($this->request_params as $k => $v) {
-//                    $ps[] = "{$k}={$v}";
-//                }
-//                $this->request_payload = implode('&', $ps);
-//            }
-//            curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_payload);
-//        } else {
-//            // CURL will set length to -1 when there is no data
-//            $this->headers['Content-Length'] = $contentLength;
-//        }
-//
-//        $this->headers['Expect'] = '';
-//
-//        if (!empty($this->headers)) {
-//            foreach ($this->headers as $k => $v) {
-//                $headers [] = trim($k . ': ' . $v);
-//            }
-//            curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
-//        }
-//
-//        if (isset($this->config ['prevent_request']) && false == $this->config ['prevent_request']) {
-//            return;
-//        }
-//
-//        // do it!
-//        $response = curl_exec($c);
-//        if ($response === false) {
-//            $response = 'Curl error: ' . curl_error($c);
-//            $code = 1;
-//        } else {
-//            $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
-//        }
-//
-//        $info = curl_getinfo($c);
-//
-//        curl_close($c);
-//        if (isset($fh)) {
-//            fclose($fh);
-//        }
-//
-//        // store the response
-//        $this->response['code'] = $code;
-//        $this->response['response'] = $response;
-//        $this->response['info'] = $info;
-//        $this->response['format'] = $this->format;
-//
-//        return $code;
-//    }
-//
-//    /**
-//    * Make an HTTP request using this library.
-//    * This method doesn't return anything.
-//    * Instead the response should be inspected directly.
-//    *
-//    * @param string $method
-//    *                the HTTP method being used. e.g. POST, GET, HEAD etc
-//    * @param string $url
-//    *                the request URL without query string parameters
-//    * @param array $params
-//    *                the request parameters as an array of key=value pairs
-//    * @param string $format
-//    *                the format of the response. Default json. Set to an empty string to exclude the format
-//    *                
-//    */
-//    function request($method, $url, $params = array(), $xml = "", $format = 'xml') 
-//    {
-//       // removed these as function parameters for now
-//       
-//        $useauth = true;
-//        $multipart = false;
-//        $this->headers = array();
-//          
-//        if (isset ( $format )) {
-//           switch ($format) {
-//              case "pdf" :
-//                 $this->headers['Accept'] = 'application/pdf';
-//                 break;
-//              case "json" :
-//                 $this->headers['Accept'] = 'application/json';
-//                 break;
-//              case "xml" :
-//              default :
-//                 $this->headers['Accept'] = 'application/xml';
-//                 break;
-//           }
-//        }
-//          
-//        if (isset ( $params ['If-Modified-Since'] )) {
-//           $modDate = "If-Modified-Since: " . $params ['If-Modified-Since'];
-//           $this->headers['If-Modified-Since'] = $params ['If-Modified-Since'];
-//        }
-//          
-//        if ($xml !== "") {
-//            $xml = trim($xml);
-//            $this->xml = $xml;
-//        }
-//          
-//        if ($method == "POST") {
-//            $params ['xml'] = $xml;
-//        }
-//        
-//        $this->prepare_method( $method );
-//        $this->config['multipart'] = $multipart;
-//        $this->url = $url;
-//        $oauthObject = new Services_Xero_OAuthSign();
-//        try {
-//            $this->sign = $oauthObject->sign(array (
-//                'path' => $url,
-//                'action' => $method,
-//                'parameters' => array_merge ( $params, array (
-//                    'oauth_signature_method' => $this->_xero_defaults['signature_method'] 
-//                )),
-//                'signatures' => $this->config 
-//            ));
-//        } catch ( Exception $e ) {
-//           $errorMessage = 'XeroOAuth::request() ' . $e->getMessage ();
-//           $this->response['response'] = $errorMessage;
-//           $this->response['helper'] = $url;
-//           return $this->response;
-//        }
-//        
-//        $this->format = $format;
-//          
-//        $curlRequest = $this->curlit();
-//         
-//        if ($this->response['code'] == 401 && isset( $this->config['session_handle'] )) {
-//            if ((strpos ( $this->response['response'], "oauth_problem=token_expired" ) !== false)) {
-//               $this->response['helper'] = "TokenExpired";
-//            } else {
-//               $this->response['helper'] = "TokenFatal";
-//            }
-//        }
-//        if ($this->response['code'] == 403) {
-//           $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";
-//           // default IIS page isn't informative, a little swap
-//           $this->response['response'] = $errorMessage;
-//           $this->response['helper'] = "SetupIssue";
-//        }
-//        if ($this->response['code'] == 0) {
-//           $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";
-//           $this->response['response'] = $errorMessage;
-//           $this->response['helper'] = "SetupIssue";
-//        }
-//        
-//        $this->response['result']  = false;
-//        if ($this->response['code'] == 200 && !empty($this->response['format'])) {
-//            $this->response['result'] = $this->parseResponse($this->response['response'], $this->response['format']);
-//        }
-//        
-//        return $this->response;
-//    }
-//     
-//   /**
-//    * Convert the response into usable data
-//    *
-//    * @param string $response
-//    *                the raw response from the API
-//    * @param string $format
-//    *                the format of the response
-//    * @return string the concatenation of the host, API version and API method
-//    */
-//    function parseResponse($response, $format) 
-//    {
-//        if (isset($format)) {
-//            switch ($format) {
-//                case "pdf" :
-//                    $theResponse = $response;
-//                    break;
-//                case "json" :
-//                    $theResponse = json_decode($response);
-//                    break;
-//                default :
-//                    $theResponse = simplexml_load_string($response);
-//                    break;
-//            }
-//        }
-//        return $theResponse;
-//    }
-//
-//    /**
-//    * Utility function to create the request URL in the requested format
-//    *
-//    * @param string $request
-//    *                the API method without extension
-//    * @return string the concatenation of the host, API version and API method
-//    */
-//    function url($request, $api = "core") 
-//    {
-//        if ($request == "RequestToken") {
-//            $this->config['host'] = $this->_xero_defaults ['site'] . '/oauth/';
-//        } elseif ($request == "Authorize") {
-//            $this->config['host'] = $this->_xero_defaults ['authorize_url'];
-//            $request = "";
-//        } elseif ($request == "AccessToken") {
-//            $this->config['host'] = $this->_xero_defaults ['site'] . '/oauth/';
-//        } else {
-//            if (isset($api)) {
-//                if ($api == "core") {
-//                    $api_stem = "api.xro";
-//                    $api_version = $this->config['core_version'];
-//                }
-//                if ($api == "payroll") {
-//                    $api_stem = "payroll.xro";
-//                    $api_version = $this->config ['payroll_version'];
-//                }
-//                if ($api == "file") {
-//                    $api_stem = "files.xro";
-//                    $api_version = $this->config ['file_version'];
-//                }
-//            }
-//            $this->config ['host'] = $this->_xero_defaults['xero_url'] . $api_stem . '/' . $api_version . '/';
-//        }
-//
-//        return implode(array(
-//            $this->config ['host'],
-//            $request
-//        ));
-//    }
-//
-//    /**
-//    * Refreshes the access token for partner API type applications
-//    *
-//    * @param string $accessToken
-//    *                the current access token for the session
-//    * @param string $sessionHandle
-//    *                the current session handle for the session
-//    * @return array response array from request
-//    */
-//    function refreshToken($accessToken, $sessionHandle) 
-//    {
-//        $code = $this->request('GET', $this->url('AccessToken', ''), array(
-//            'oauth_token' => $accessToken,
-//            'oauth_session_handle' => $sessionHandle
-//        ));
-//        if ($this->response['code'] == 200) {
-//
-//            $response = $this->extract_params($this->response ['response']);
-//
-//            return $response;
-//        } else {
-//            $this->response['helper'] = "TokenFatal";
-//            return $this->response;
-//        }
-//    }
-//     
-//    /**
-//    * Returns the current URL.
-//    * This is instead of PHP_SELF which is unsafe
-//    *
-//    * @param bool $dropqs
-//    *                whether to drop the querystring or not. Default true
-//    * @return string the current URL
-//    */
-//    public static function php_self($dropqs = true)
-//    {
-//        $protocol = 'http';
-//        if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {
-//            $protocol = 'https';
-//        } elseif (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == '443')) {
-//            $protocol = 'https';
-//        }
-//        $url = sprintf('%s://%s%s', $protocol, $_SERVER['SERVER_NAME'], $_SERVER['REQUEST_URI']);
-//        $parts = parse_url($url);
-//        $port = $_SERVER['SERVER_PORT'];
-//        $scheme = $parts['scheme'];
-//        $host = $parts['host'];
-//        $path = @$parts['path'];
-//        $qs = @$parts['query'];
-//        $port or $port = ($scheme == 'https') ? '443' : '80';
-//        if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) {
-//            $host = "$host:$port";
-//        }
-//        $url = "$scheme://$host$path";
-//        if (!$dropqs) {
-//            return "{$url}?{$qs}";
-//        }
-//        
-//        return $url;
-//    }
-//
-//    /*
-//    * Run some basic checks on our config options etc to make sure all is ok
-//    * Oddly - this is run from the caller???
-//    * 
-//    */
-//    function diagnostics() 
-//    {
-//        $testOutput = array ();
-//          
-//        if ($this->config ['application_type'] == 'Partner' || $this->config ['application_type'] == 'Private') {
-//              
-//            if (!file_exists($this->config['rsa_public_key'] )) {
-//                $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";
-//            }
-//            
-//            if (file_exists($this->config['rsa_public_key'] )) {
-//                $data = openssl_x509_parse ( file_get_contents ( $this->config ['rsa_public_key'] ) );
-//                $validFrom = date ( 'Y-m-d H:i:s', $data ['validFrom_time_t'] );
-//                if (time () < $data ['validFrom_time_t']) {
-//                    $testOutput ['ssl_cert_error'] = "Application cert not yet valid - cert valid from " . $validFrom . "\n";
-//                }
-//                $validTo = date ( 'Y-m-d H:i:s', $data ['validTo_time_t'] );
-//                if (time () > $data ['validTo_time_t']) {
-//                    $testOutput ['ssl_cert_error'] = "Application cert cert expired - cert valid to " . $validFrom . "\n";
-//                }
-//            }
-//            if (! file_exists ( $this->config ['rsa_private_key'] )) {
-//                $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";
-//            }
-//            
-//            if (file_exists ( $this->config ['rsa_private_key'] )) {
-//                $cert_content = file_get_contents ( $this->config ['rsa_public_key'] );
-//                $priv_key_content = file_get_contents ( $this->config ['rsa_private_key'] );
-//                if (! openssl_x509_check_private_key ( $cert_content, $priv_key_content )) {
-//                     $testOutput ['rsa_cert_error'] = "Application certificate and key do not match \n";
-//                }
-//               
-//            }
-//        }
-//          
-//        return $testOutput;
-//    }
-//}
+require_once 'Services/Xero/OAuthSign.php';
+
+/**
+ * Define a custom Exception for easy trap and detection
+ */
+//class XeroOAuthException extends Exception {}
+
+class Services_Xero_OAuth
+{
+    var $_xero_defaults =array (
+        'xero_url' => 'https://api.xero.com/',
+        'site' => 'https://api.xero.com',
+        'authorize_url' => 'https://api.xero.com/oauth/Authorize',
+        'signature_method' => 'RSA-SHA1'
+    );
+    
+    
+    var $_xero_consumer_options = array (
+        'request_token_path' => 'oauth/RequestToken',
+        'access_token_path' => 'oauth/AccessToken',
+        'authorize_path' => 'oauth/Authorize',
+    );
+    
+    var $_xero_curl_options = array ( // you probably don't want to change any of these curl values
+        CURLOPT_USERAGENT           => 'XeroOAuth-PHP',
+        CURLOPT_CONNECTTIMEOUT      => 30,
+        CURLOPT_TIMEOUT             => 20,
+        CURLOPT_RETURNTRANSFER      => true,
+        // for security you may want to set this to TRUE. If you do you need
+        // to install the servers certificate in your local certificate store.
+        CURLOPT_SSL_VERIFYPEER      => 2,
+        // include ca-bundle.crt from http://curl.haxx.se/ca/cacert.pem
+        //'curl_cainfo' => $ca_cert_path . '/ca-bundle.crt',
+//        CURLOPT_SSL_VERIFYPEER => '/etc/ssl/certs/ca-certificates.crt', // ??? wrong name??
+        CURLOPT_CAINFO              => '/etc/ssl/certs/ca-certificates.crt',
+        CURLOPT_FOLLOWLOCATION      => false, // whether to follow redirects or not
+        // TRUE/1 is not a valid ssl verifyhost value with curl >= 7.28.1 and 2 is more secure as well.
+        // More details here: http://php.net/manual/en/function.curl-setopt.php
+        CURLOPT_SSL_VERIFYHOST      => 2,
+        // support for proxy servers
+        CURLOPT_PROXY               => false, // really you don't want to use this if you are using streaming
+        CURLOPT_PROXYUSERPWD        => false, // format username:password for proxy, if required
+        CURLOPT_ENCODING            => '', // leave blank for all supported formats, else use gzip, deflate, identity
+        CURLOPT_VERBOSE             => true,
+        CURLOPT_HEADER              => false,
+        CURLINFO_HEADER_OUT         => true
+    );
+                
+      
+    var $_action;
+    var $_nonce_chars;
+    
+    var $config = array (
+        'application_type' => 'Private',
+        'core_version' => '2.0',
+        'payroll_version' => '1.0',
+        'file_version' => '1.0',
+        'rsa_private_key' => '',
+        'rsa_public_key' => ''
+    );
+       
+   /**
+    * Creates a new XeroOAuth object
+    *
+    * @param string $config,
+    *          the configuration settings
+    */
+    function __construct($config) 
+    {    
+        $this->params = array ();
+        $this->headers = array ();
+        $this->auto_fixed_time = false;
+        $this->buffer = null;
+        $this->request_params = array();
+        
+        foreach($this->_xero_defaults as $k => $v) {
+            if (isset($config[$k])) {
+                $this->_xero_defaults[$k] = $config[$k];
+            }
+        }
+        
+        if (!empty($config['application_type']) && ($config['application_type'] == "Public")) {
+            $this->_xero_defaults['signature_method'] =  'HMAC-SHA1';
+        }
+        
+        foreach($this->_xero_curl_options as $k => $v) {
+            if (isset($config[$k])) {
+                $this->_xero_curl_options[$k] = $config[$k];
+            }
+        }
+        
+        foreach ($config as $k => $v){
+            $this->config[$k] = $v;
+        }
+        
+        $this->config = array_merge ($this->_xero_defaults, $this->_xero_consumer_options, $this->_xero_curl_options, $this->config);
+    }
+       
+   /**
+    * Utility function to parse the returned curl headers and store them in the
+    * class array variable.
+    *
+    * @param object $ch
+    *          curl handle
+    * @param string $header
+    *          the response headers
+    * @return the string length of the header
+    */
+    private function curlHeader($ch, $header) 
+    {
+        $i = strpos($header, ':');
+        if (!empty($i)) {
+            $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
+            $value = trim(substr($header, $i + 2));
+            $this->response ['headers'] [$key] = $value;
+        }
+        return strlen($header);
+    }
+
+    /**
+    * Utility function to parse the returned curl buffer and store them until
+    * an EOL is found.
+    * The buffer for curl is an undefined size so we need
+    * to collect the content until an EOL is found.
+    *
+    * This function calls the previously defined streaming callback method.
+    *
+    * @param object $ch
+    *          curl handle
+    * @param string $data
+    *          the current curl buffer
+    */
+    private function curlWrite($ch, $data) 
+    {
+        $l = strlen($data);
+        if (strpos($data, $this->config['streaming_eol']) === false) {
+            $this->buffer .= $data;
+            return $l;
+        }
+
+        $buffered = explode($this->config['streaming_eol'], $data);
+        $content = $this->buffer . $buffered [0];
+
+        $this->metrics ['tweets'] ++;
+        $this->metrics ['bytes'] += strlen($content);
+
+        if (!function_exists($this->config['streaming_callback'])) {
+            return 0;
+        }
+        
+        $metrics = $this->update_metrics();
+        $stop = call_user_func($this->config['streaming_callback'], $content, strlen($content), $metrics);
+        $this->buffer = $buffered [1];
+        if ($stop) {
+            return 0;
+        }
+        
+        return $l;
+    }
+       
+   /**
+    * Extracts and decodes OAuth parameters from the passed string
+    *
+    * @param string $body
+    *          the response body from an OAuth flow method
+    * @return array the response body safely decoded to an array of key => values
+    */
+    function extract_params($body) 
+    {
+        $kvs = explode('&', $body);
+        $decoded = array();
+        foreach ($kvs as $kv) {
+            $kv = explode('=', $kv, 2);
+            $kv [0] = $this->safe_decode($kv [0]);
+            $kv [1] = $this->safe_decode($kv [1]);
+            $decoded [$kv [0]] = $kv [1];
+        }
+        return $decoded;
+    }
+
+    /**
+    * Encodes the string or array passed in a way compatible with OAuth.
+    * If an array is passed each array value will will be encoded.
+    *
+    * @param mixed $data
+    *          the scalar or array to encode
+    * @return $data encoded in a way compatible with OAuth
+    */
+    private function safe_encode($data) 
+    {
+        if (is_array($data)) {
+            return array_map(array(
+                $this,
+                'safe_encode'
+            ), $data);
+        } else if (is_scalar($data)) {
+            return str_ireplace(array(
+                '+',
+                '%7E'
+            ), array(
+                ' ',
+                '~'
+            ), rawurlencode($data));
+        } else {
+            return '';
+        }
+    }
+
+    /**
+    * Decodes the string or array from it's URL encoded form
+    * If an array is passed each array value will will be decoded.
+    *
+    * @param mixed $data
+    *          the scalar or array to decode
+    * @return $data decoded from the URL encoded form
+    */
+    private function safe_decode($data) 
+    {
+        if (is_array($data)) {
+            return array_map(array(
+                $this,
+                'safe_decode'
+            ), $data);
+        } else if (is_scalar($data)) {
+            return rawurldecode($data);
+        } else {
+            return '';
+        }
+    }
+
+    /**
+    * Prepares the HTTP method for use in the base string by converting it to
+    * uppercase.
+    *
+    * @param string $method
+    *          an HTTP method such as GET or POST
+    * @return void value is stored to a class variable
+    * @author themattharris
+    */
+    private function prepare_method($method) 
+    {
+        $this->method = strtoupper ( $method );
+    }
+       
+   /**
+    * Makes a curl request.
+    * Takes no parameters as all should have been prepared
+    * by the request method
+    *
+    * @return void response data is stored in the class variable 'response'
+    */
+    private function curlit() 
+    {
+        $this->request_params = array();
+        
+        // configure curl
+        $c = curl_init();
+
+        curl_setopt_array($c, $this->_xero_curl_options);
+        
+        curl_setopt_array($c, array(
+            CURLOPT_URL => $this->sign['signed_url'],
+            CURLOPT_HEADERFUNCTION => array (
+                $this,
+                'curlHeader' 
+            ),
+        ));
+
+        /* ENTRUST CERTIFICATE DEPRECATED
+          if ($this->config ['application_type'] == "Partner") {
+          curl_setopt_array ( $c, array (
+          // ssl client cert options for partner apps
+          CURLOPT_SSLCERT => $this->config ['curl_ssl_cert'],
+          CURLOPT_SSLKEYPASSWD => $this->config ['curl_ssl_password'],
+          CURLOPT_SSLKEY => $this->config ['curl_ssl_key']
+          ) );
+          }
+         */
+
+        if (isset($this->config ['is_streaming'])) {
+            // process the body
+            $this->response['content-length'] = 0;
+            curl_setopt($c, CURLOPT_TIMEOUT, 0);
+            curl_setopt($c, CURLOPT_WRITEFUNCTION, array(
+                $this,
+                'curlWrite'
+            ));
+        }
+        
+        switch ($this->method) {
+            case 'GET' :
+                $contentLength = 0;
+                break;
+
+            case 'POST' :
+                curl_setopt($c, CURLOPT_POST, TRUE);
+                $post_body = $this->safe_encode($this->xml);
+                curl_setopt($c, CURLOPT_POSTFIELDS, $post_body);
+                $this->request_params['xml'] = $post_body;
+                $contentLength = strlen($post_body);
+                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
+
+                break;
+
+            case 'PUT' :
+                $fh = tmpfile();
+                if ($this->format == "file") {
+                    $put_body = $this->xml;
+                } else {
+                    $put_body = $this->safe_encode($this->xml);
+                    $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
+                }
+                fwrite($fh, $put_body);
+                rewind($fh);
+                curl_setopt($c, CURLOPT_PUT, true);
+                curl_setopt($c, CURLOPT_INFILE, $fh);
+                curl_setopt($c, CURLOPT_INFILESIZE, strlen($put_body));
+                $contentLength = strlen($put_body);
+
+                break;
+
+
+            default :
+                curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method);
+        }
+
+        if (!empty($this->request_params)) {
+            // if not doing multipart we need to implode the parameters
+            if (!$this->config ['multipart']) {
+                foreach ($this->request_params as $k => $v) {
+                    $ps[] = "{$k}={$v}";
+                }
+                $this->request_payload = implode('&', $ps);
+            }
+            curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_payload);
+        } else {
+            // CURL will set length to -1 when there is no data
+            $this->headers['Content-Length'] = $contentLength;
+        }
+
+        $this->headers['Expect'] = '';
+
+        if (!empty($this->headers)) {
+            foreach ($this->headers as $k => $v) {
+                $headers [] = trim($k . ': ' . $v);
+            }
+            curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
+        }
+
+        if (isset($this->config ['prevent_request']) && false == $this->config ['prevent_request']) {
+            return;
+        }
+
+        // do it!
+        $response = curl_exec($c);
+        if ($response === false) {
+            $response = 'Curl error: ' . curl_error($c);
+            $code = 1;
+        } else {
+            $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+        }
+
+        $info = curl_getinfo($c);
+
+        curl_close($c);
+        if (isset($fh)) {
+            fclose($fh);
+        }
+
+        // store the response
+        $this->response['code'] = $code;
+        $this->response['response'] = $response;
+        $this->response['info'] = $info;
+        $this->response['format'] = $this->format;
+
+        return $code;
+    }
+
+    /**
+    * Make an HTTP request using this library.
+    * This method doesn't return anything.
+    * Instead the response should be inspected directly.
+    *
+    * @param string $method
+    *          the HTTP method being used. e.g. POST, GET, HEAD etc
+    * @param string $url
+    *          the request URL without query string parameters
+    * @param array $params
+    *          the request parameters as an array of key=value pairs
+    * @param string $format
+    *          the format of the response. Default json. Set to an empty string to exclude the format
+    *          
+    */
+    function request($method, $url, $params = array(), $xml = "", $format = 'xml') 
+    {
+       // removed these as function parameters for now
+       
+        $useauth = true;
+        $multipart = false;
+        $this->headers = array();
+          
+        if (isset ( $format )) {
+           switch ($format) {
+              case "pdf" :
+                 $this->headers['Accept'] = 'application/pdf';
+                 break;
+              case "json" :
+                 $this->headers['Accept'] = 'application/json';
+                 break;
+              case "xml" :
+              default :
+                 $this->headers['Accept'] = 'application/xml';
+                 break;
+           }
+        }
+          
+        if (isset ( $params ['If-Modified-Since'] )) {
+           $modDate = "If-Modified-Since: " . $params ['If-Modified-Since'];
+           $this->headers['If-Modified-Since'] = $params ['If-Modified-Since'];
+        }
+          
+        if ($xml !== "") {
+            $xml = trim($xml);
+            $this->xml = $xml;
+        }
+          
+        if ($method == "POST") {
+            $params ['xml'] = $xml;
+        }
+        
+        $this->prepare_method( $method );
+        $this->config['multipart'] = $multipart;
+        $this->url = $url;
+        $oauthObject = new Services_Xero_OAuthSign();
+        try {
+            $this->sign = $oauthObject->sign(array (
+                'path' => $url,
+                'action' => $method,
+                'parameters' => array_merge ( $params, array (
+                    'oauth_signature_method' => $this->_xero_defaults['signature_method'] 
+                )),
+                'signatures' => $this->config 
+            ));
+        } catch ( Exception $e ) {
+           $errorMessage = 'XeroOAuth::request() ' . $e->getMessage ();
+           $this->response['response'] = $errorMessage;
+           $this->response['helper'] = $url;
+           return $this->response;
+        }
+        
+        $this->format = $format;
+          
+        $curlRequest = $this->curlit();
+         
+        if ($this->response['code'] == 401 && isset( $this->config['session_handle'] )) {
+            if ((strpos ( $this->response['response'], "oauth_problem=token_expired" ) !== false)) {
+               $this->response['helper'] = "TokenExpired";
+            } else {
+               $this->response['helper'] = "TokenFatal";
+            }
+        }
+        if ($this->response['code'] == 403) {
+           $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";
+           // default IIS page isn't informative, a little swap
+           $this->response['response'] = $errorMessage;
+           $this->response['helper'] = "SetupIssue";
+        }
+        if ($this->response['code'] == 0) {
+           $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";
+           $this->response['response'] = $errorMessage;
+           $this->response['helper'] = "SetupIssue";
+        }
+        
+        $this->response['result']  = false;
+        if ($this->response['code'] == 200 && !empty($this->response['format'])) {
+            $this->response['result'] = $this->parseResponse($this->response['response'], $this->response['format']);
+        }
+        
+        return $this->response;
+    }
+       
+   /**
+    * Convert the response into usable data
+    *
+    * @param string $response
+    *          the raw response from the API
+    * @param string $format
+    *          the format of the response
+    * @return string the concatenation of the host, API version and API method
+    */
+    function parseResponse($response, $format) 
+    {
+        if (isset($format)) {
+            switch ($format) {
+                case "pdf" :
+                    $theResponse = $response;
+                    break;
+                case "json" :
+                    $theResponse = json_decode($response);
+                    break;
+                default :
+                    $theResponse = simplexml_load_string($response);
+                    break;
+            }
+        }
+        return $theResponse;
+    }
+
+    /**
+    * Utility function to create the request URL in the requested format
+    *
+    * @param string $request
+    *          the API method without extension
+    * @return string the concatenation of the host, API version and API method
+    */
+    function url($request, $api = "core") 
+    {
+        if ($request == "RequestToken") {
+            $this->config['host'] = $this->_xero_defaults ['site'] . '/oauth/';
+        } elseif ($request == "Authorize") {
+            $this->config['host'] = $this->_xero_defaults ['authorize_url'];
+            $request = "";
+        } elseif ($request == "AccessToken") {
+            $this->config['host'] = $this->_xero_defaults ['site'] . '/oauth/';
+        } else {
+            if (isset($api)) {
+                if ($api == "core") {
+                    $api_stem = "api.xro";
+                    $api_version = $this->config['core_version'];
+                }
+                if ($api == "payroll") {
+                    $api_stem = "payroll.xro";
+                    $api_version = $this->config ['payroll_version'];
+                }
+                if ($api == "file") {
+                    $api_stem = "files.xro";
+                    $api_version = $this->config ['file_version'];
+                }
+            }
+            $this->config ['host'] = $this->_xero_defaults['xero_url'] . $api_stem . '/' . $api_version . '/';
+        }
+
+        return implode(array(
+            $this->config ['host'],
+            $request
+        ));
+    }
+
+    /**
+    * Refreshes the access token for partner API type applications
+    *
+    * @param string $accessToken
+    *          the current access token for the session
+    * @param string $sessionHandle
+    *          the current session handle for the session
+    * @return array response array from request
+    */
+    function refreshToken($accessToken, $sessionHandle) 
+    {
+        $code = $this->request('GET', $this->url('AccessToken', ''), array(
+            'oauth_token' => $accessToken,
+            'oauth_session_handle' => $sessionHandle
+        ));
+        if ($this->response['code'] == 200) {
+
+            $response = $this->extract_params($this->response ['response']);
+
+            return $response;
+        } else {
+            $this->response['helper'] = "TokenFatal";
+            return $this->response;
+        }
+    }
+       
+    /**
+    * Returns the current URL.
+    * This is instead of PHP_SELF which is unsafe
+    *
+    * @param bool $dropqs
+    *          whether to drop the querystring or not. Default true
+    * @return string the current URL
+    */
+    public static function php_self($dropqs = true)
+    {
+        $protocol = 'http';
+        if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {
+            $protocol = 'https';
+        } elseif (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == '443')) {
+            $protocol = 'https';
+        }
+        $url = sprintf('%s://%s%s', $protocol, $_SERVER['SERVER_NAME'], $_SERVER['REQUEST_URI']);
+        $parts = parse_url($url);
+        $port = $_SERVER['SERVER_PORT'];
+        $scheme = $parts['scheme'];
+        $host = $parts['host'];
+        $path = @$parts['path'];
+        $qs = @$parts['query'];
+        $port or $port = ($scheme == 'https') ? '443' : '80';
+        if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) {
+            $host = "$host:$port";
+        }
+        $url = "$scheme://$host$path";
+        if (!$dropqs) {
+            return "{$url}?{$qs}";
+        }
+        
+        return $url;
+    }
+
+    /*
+    * Run some basic checks on our config options etc to make sure all is ok
+    * Oddly - this is run from the caller???
+    * 
+    */
+    function diagnostics() 
+    {
+        $testOutput = array ();
+          
+        if ($this->config ['application_type'] == 'Partner' || $this->config ['application_type'] == 'Private') {
+              
+            if (!file_exists($this->config['rsa_public_key'] )) {
+                $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";
+            }
+            
+            if (file_exists($this->config['rsa_public_key'] )) {
+                $data = openssl_x509_parse ( file_get_contents ( $this->config ['rsa_public_key'] ) );
+                $validFrom = date ( 'Y-m-d H:i:s', $data ['validFrom_time_t'] );
+                if (time () < $data ['validFrom_time_t']) {
+                    $testOutput ['ssl_cert_error'] = "Application cert not yet valid - cert valid from " . $validFrom . "\n";
+                }
+                $validTo = date ( 'Y-m-d H:i:s', $data ['validTo_time_t'] );
+                if (time () > $data ['validTo_time_t']) {
+                    $testOutput ['ssl_cert_error'] = "Application cert cert expired - cert valid to " . $validFrom . "\n";
+                }
+            }
+            if (! file_exists ( $this->config ['rsa_private_key'] )) {
+                $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";
+            }
+            
+            if (file_exists ( $this->config ['rsa_private_key'] )) {
+                $cert_content = file_get_contents ( $this->config ['rsa_public_key'] );
+                $priv_key_content = file_get_contents ( $this->config ['rsa_private_key'] );
+                if (! openssl_x509_check_private_key ( $cert_content, $priv_key_content )) {
+                     $testOutput ['rsa_cert_error'] = "Application certificate and key do not match \n";
+                }
+               
+            }
+        }
+          
+        return $testOutput;
+    }
+}