app = $parentApp; } $this->user = new OAuthUser(); $this->curl = new CurlObject(); } /** * request * * Implemented for a standard OAuth adapter interface * @param mixed $method * @param mixed $uri * @param array $data * @param array $options * @access public * @return void */ public function request($method, $uri, $data = array(), $options = array()) { $uri = $this->app->removeBaseUri($uri); $url = $this->app->getBaseUri() . $uri; # WARNING: non-primative items in data must be json serialized in GET and POST. if ($method == 'POST' or $method == 'GET') { foreach ($data as $key => $value) { if (is_array($value)) { $data[$key] = json_encode($value); } } } $response = $this->makeRequest($method, $url, $data); if (!empty($options['return'])) { if ($options['return'] == 'status') { return $response->headers['Status-Code']; } if ($options['return'] == 'headers') { return $response->headers; } if ($options['return'] == 'integer') { return intval($response->body); } } $data = json_decode($response->body, true); if (empty($options['allow_empty']) && !isset($data)) { throw new AWeberResponseError($uri); } return $data; } /** * getRequestToken * * Gets a new request token / secret for this user. * @access public * @return void */ public function getRequestToken($callbackUrl=false) { $data = ($callbackUrl)? array('oauth_callback' => $callbackUrl) : array(); $resp = $this->makeRequest('POST', $this->app->getRequestTokenUrl(), $data); $data = $this->parseResponse($resp); $this->requiredFromResponse($data, array('oauth_token', 'oauth_token_secret')); $this->user->requestToken = $data['oauth_token']; $this->user->tokenSecret = $data['oauth_token_secret']; return $data['oauth_token']; } /** * getAccessToken * * Makes a request for access tokens. Requires that the current user has an authorized * token and token secret. * * @access public * @return void */ public function getAccessToken() { $resp = $this->makeRequest('POST', $this->app->getAccessTokenUrl(), array('oauth_verifier' => $this->user->verifier) ); $data = $this->parseResponse($resp); $this->requiredFromResponse($data, array('oauth_token', 'oauth_token_secret')); if (empty($data['oauth_token'])) { throw new AWeberOAuthDataMissing('oauth_token'); } $this->user->accessToken = $data['oauth_token']; $this->user->tokenSecret = $data['oauth_token_secret']; return array($data['oauth_token'], $data['oauth_token_secret']); } /** * parseAsError * * Checks if response is an error. If it is, raise an appropriately * configured exception. * * @param mixed $response Data returned from the server, in array form * @access public * @throws AWeberOAuthException * @return void */ public function parseAsError($response) { if (!empty($response['error'])) { throw new AWeberOAuthException($response['error']['type'], $response['error']['message']); } } /** * requiredFromResponse * * Enforce that all the fields in requiredFields are present and not * empty in data. If a required field is empty, throw an exception. * * @param mixed $data Array of data * @param mixed $requiredFields Array of required field names. * @access protected * @return void */ protected function requiredFromResponse($data, $requiredFields) { foreach ($requiredFields as $field) { if (empty($data[$field])) { throw new AWeberOAuthDataMissing($field); } } } /** * get * * Make a get request. Used to exchange user tokens with serice provider. * @param mixed $url URL to make a get request from. * @param array $data Data for the request. * @access protected * @return void */ protected function get($url, $data) { $url = $this->_addParametersToUrl($url, $data); $handle = $this->curl->init($url); $resp = $this->_sendRequest($handle); return $resp; } /** * _addParametersToUrl * * Adds the parameters in associative array $data to the * given URL * @param String $url URL * @param array $data Parameters to be added as a query string to * the URL provided * @access protected * @return void */ protected function _addParametersToUrl($url, $data) { if (!empty($data)) { if (strpos($url, '?') === false) { $url .= '?'.$this->buildData($data); } else { $url .= '&'.$this->buildData($data); } } return $url; } /** * generateNonce * * Generates a 'nonce', which is a unique request id based on the * timestamp. If no timestamp is provided, generate one. * @param mixed $timestamp Either a timestamp (epoch seconds) or false, * in which case it will generate a timestamp. * @access public * @return string Returns a unique nonce */ public function generateNonce($timestamp = false) { if (!$timestamp) $timestamp = $this->generateTimestamp(); return md5($timestamp.'-'.rand(10000,99999).'-'.uniqid()); } /** * generateTimestamp * * Generates a timestamp, in seconds * @access public * @return int Timestamp, in epoch seconds */ public function generateTimestamp() { return time(); } /** * createSignature * * Creates a signature on the signature base and the signature key * @param mixed $sigBase Base string of data to sign * @param mixed $sigKey Key to sign the data with * @access public * @return string The signature */ public function createSignature($sigBase, $sigKey) { switch ($this->signatureMethod) { case 'HMAC-SHA1': default: return base64_encode(hash_hmac('sha1', $sigBase, $sigKey, true)); } } /** * encode * * Short-cut for utf8_encode / rawurlencode * @param mixed $data Data to encode * @access protected * @return void Encoded data */ protected function encode($data) { return rawurlencode(utf8_encode($data)); } /** * createSignatureKey * * Creates a key that will be used to sign our signature. Signatures * are signed with the consumerSecret for this consumer application and * the token secret of the user that the application is acting on behalf * of. * @access public * @return void */ public function createSignatureKey() { return $this->consumerSecret.'&'.$this->user->tokenSecret; } /** * getOAuthRequestData * * Get all the pre-signature, OAuth specific parameters for a request. * @access public * @return void */ public function getOAuthRequestData() { $token = $this->user->getHighestPriorityToken(); $ts = $this->generateTimestamp(); $nonce = $this->generateNonce($ts); return array( 'oauth_token' => $token, 'oauth_consumer_key' => $this->consumerKey, 'oauth_version' => $this->version, 'oauth_timestamp' => $ts, 'oauth_signature_method' => $this->signatureMethod, 'oauth_nonce' => $nonce); } /** * mergeOAuthData * * @param mixed $requestData * @access public * @return void */ public function mergeOAuthData($requestData) { $oauthData = $this->getOAuthRequestData(); return array_merge($requestData, $oauthData); } /** * createSignatureBase * * @param mixed $method String name of HTTP method, such as "GET" * @param mixed $url URL where this request will go * @param mixed $data Array of params for this request. This should * include ALL oauth properties except for the signature. * @access public * @return void */ public function createSignatureBase($method, $url, $data) { $method = $this->encode(strtoupper($method)); $query = parse_url($url, PHP_URL_QUERY); if ($query) { $parts = explode('?', $url, 2); $url = array_shift($parts); $items = explode('&', $query); foreach ($items as $item) { list($key, $value) = explode('=', $item); $data[rawurldecode($key)] = rawurldecode($value); } } $url = $this->encode($url); $data = $this->encode($this->collapseDataForSignature($data)); return $method.'&'.$url.'&'.$data; } /** * collapseDataForSignature * * Turns an array of request data into a string, as used by the oauth * signature * @param mixed $data * @access public * @return void */ public function collapseDataForSignature($data) { ksort($data); $collapse = ''; foreach ($data as $key => $val) { if (!empty($collapse)) $collapse .= '&'; $collapse .= $key.'='.$this->encode($val); } return $collapse; } /** * signRequest * * Signs the request. * * @param mixed $method HTTP method * @param mixed $url URL for the request * @param mixed $data The data to be signed * @access public * @return array The data, with the signature. */ public function signRequest($method, $url, $data) { $base = $this->createSignatureBase($method, $url, $data); $key = $this->createSignatureKey(); $data['oauth_signature'] = $this->createSignature($base, $key); ksort($data); return $data; } /** * makeRequest * * Public facing function to make a request * * @param mixed $method * @param mixed $url - Reserved characters in query params MUST be escaped * @param mixed $data - Reserved characters in values MUST NOT be escaped * @access public * @return void */ public function makeRequest($method, $url, $data=array()) { if ($this->debug) echo "\n** {$method}: $url\n"; switch (strtoupper($method)) { case 'POST': $oauth = $this->prepareRequest($method, $url, $data); $resp = $this->post($url, $oauth); break; case 'GET': $oauth = $this->prepareRequest($method, $url, $data); $resp = $this->get($url, $oauth, $data); break; case 'DELETE': $oauth = $this->prepareRequest($method, $url, $data); $resp = $this->delete($url, $oauth); break; case 'PATCH': $oauth = $this->prepareRequest($method, $url, array()); $resp = $this->patch($url, $oauth, $data); break; } // enable debug output if ($this->debug) { echo "
";
            print_r($oauth);
            echo " --> Status: {$resp->headers['Status-Code']}\n";
            echo " --> Body: {$resp->body}";
            echo "
"; } if (!$resp) { $msg = 'Unable to connect to the AWeber API. (' . $this->error . ')'; $error = array('message' => $msg, 'type' => 'APIUnreachableError', 'documentation_url' => 'https://labs.aweber.com/docs/troubleshooting'); throw new AWeberAPIException($error, $url); } if($resp->headers['Status-Code'] >= 400) { $data = json_decode($resp->body, true); throw new AWeberAPIException($data['error'], $url); } return $resp; } /** * put * * Prepare an OAuth put method. * * @param mixed $url URL where we are making the request to * @param mixed $data Data that is used to make the request * @access protected * @return void */ protected function patch($url, $oauth, $data) { $url = $this->_addParametersToUrl($url, $oauth); $handle = $this->curl->init($url); $this->curl->setopt($handle, CURLOPT_CUSTOMREQUEST, 'PATCH'); $this->curl->setopt($handle, CURLOPT_POSTFIELDS, json_encode($data)); $resp = $this->_sendRequest($handle, array('Expect:', 'Content-Type: application/json')); return $resp; } /** * post * * Prepare an OAuth post method. * * @param mixed $url URL where we are making the request to * @param mixed $data Data that is used to make the request * @access protected * @return void */ protected function post($url, $oauth) { $handle = $this->curl->init($url); $postData = $this->buildData($oauth); $this->curl->setopt($handle, CURLOPT_POST, true); $this->curl->setopt($handle, CURLOPT_POSTFIELDS, $postData); $resp = $this->_sendRequest($handle); return $resp; } /** * delete * * Makes a DELETE request * @param mixed $url URL where we are making the request to * @param mixed $data Data that is used in the request * @access protected * @return void */ protected function delete($url, $data) { $url = $this->_addParametersToUrl($url, $data); $handle = $this->curl->init($url); $this->curl->setopt($handle, CURLOPT_CUSTOMREQUEST, 'DELETE'); $resp = $this->_sendRequest($handle); return $resp; } /** * buildData * * Creates a string of data for either post or get requests. * @param mixed $data Array of key value pairs * @access public * @return void */ public function buildData($data) { ksort($data); $params = array(); foreach ($data as $key => $value) { $params[] = $key.'='.$this->encode($value); } return implode('&', $params); } /** * _sendRequest * * Actually makes a request. * @param mixed $handle Curl handle * @param array $headers Additional headers needed for request * @access private * @return void */ private function _sendRequest($handle, $headers = array('Expect:')) { $this->curl->setopt($handle, CURLOPT_RETURNTRANSFER, true); $this->curl->setopt($handle, CURLOPT_HEADER, true); $this->curl->setopt($handle, CURLOPT_HTTPHEADER, $headers); $this->curl->setopt($handle, CURLOPT_USERAGENT, $this->userAgent); $this->curl->setopt($handle, CURLOPT_SSL_VERIFYPEER, FALSE); $this->curl->setopt($handle, CURLOPT_VERBOSE, FALSE); $this->curl->setopt($handle, CURLOPT_CONNECTTIMEOUT, 10); $this->curl->setopt($handle, CURLOPT_TIMEOUT, 90); $resp = $this->curl->execute($handle); if ($resp) { return new CurlResponse($resp); } $this->error = $this->curl->errno($handle) . ' - ' . $this->curl->error($handle); return false; } /** * prepareRequest * * @param mixed $method HTTP method * @param mixed $url URL for the request * @param mixed $data The data to generate oauth data and be signed * @access public * @return void The data, with all its OAuth variables and signature */ public function prepareRequest($method, $url, $data) { $data = $this->mergeOAuthData($data); $data = $this->signRequest($method, $url, $data); return $data; } /** * parseResponse * * Parses the body of the response into an array * @param mixed $string The body of a response * @access public * @return void */ public function parseResponse($resp) { $data = array(); if (!$resp) { return $data; } if (empty($resp)) { return $data; } if (empty($resp->body)) { return $data; } switch ($this->format) { case 'json': $data = json_decode($resp->body); break; default: parse_str($resp->body, $data); } $this->parseAsError($data); return $data; } } /** * OAuthUser * * Simple data class representing the user in an OAuth application. * @package * @version $id$ */ class OAuthUser { public $authorizedToken = false; public $requestToken = false; public $verifier = false; public $tokenSecret = false; public $accessToken = false; /** * isAuthorized * * Checks if this user is authorized. * @access public * @return void */ public function isAuthorized() { if (empty($this->authorizedToken) && empty($this->accessToken)) { return false; } return true; } /** * getHighestPriorityToken * * Returns highest priority token - used to define authorization * state for a given OAuthUser * @access public * @return void */ public function getHighestPriorityToken() { if (!empty($this->accessToken)) return $this->accessToken; if (!empty($this->authorizedToken)) return $this->authorizedToken; if (!empty($this->requestToken)) return $this->requestToken; // Return no token, new user return ''; } } ?>