* @copyright 2013 */ /*************************************** config ***************************************/ $config = array( 'consumer_key' => '', // Your Twitter App Consumer Key 'consumer_secret' => '', // Your Twitter App Consumer Secret 'oauth_token' => '', // Your Twitter App Access Token 'oauth_token_secret' => '', // Your Twitter App Access Token Secret 'output_format' => 'object' ); /**************************************************************************************/ //use TwitterOAuth\Exception\TwitterException; class TwitterOAuth { protected $url = 'https://api.twitter.com/1.1/'; protected $auth_url = 'https://api.twitter.com/'; protected $outputFormats = array('text', 'json', 'array', 'object'); protected $defaultFormat = 'object'; protected $config = array(); protected $call = ''; protected $method = 'GET'; protected $getParams = array(); protected $postParams = array(); protected $encoded_bearer_credentials = null; protected $bearer_access_token = null; protected $headers = null; protected $response = null; protected $cachefile = ''; /** * Prepare a new conection with Twitter API via OAuth * * The application ``consumer_key`` and ``consumer_key_secret`` are required * for most actions, unless when using application-only authentication with a bearer-token. * The ``oauth_token`` and ``oauth_token_secret`` are required for user type actions. * * @param array $config Configuration array with OAuth access data */ public function __construct(array $config) { $required = array( 'consumer_key' => '', 'consumer_secret' => '', ); if (count(array_intersect_key($required, $config)) !== count($required)) { //throw new \Exception('Missing parameters in configuration array'); } if (!isset($config['output_format']) || !in_array($config['output_format'], $this->outputFormats)) { $config['output_format'] = $this->defaultFormat; } $this->config = $config; unset($required, $config); } private function object_to_array($data) { if (is_array($data) || is_object($data)) { $result = array(); foreach ($data as $key => $value) { $result[$key] = $this->object_to_array($value); } return $result; } return $data; } /** * Send a GET call to Twitter API via OAuth * * @param string $call Twitter resource string * @param array $getParams GET parameters to send * @return mixed Output with selected format */ public function get($call, array $getParams = null) { $this->call = $call; $this->method = 'GET'; $this->resetParams(); $this->cachefile = '.tweetcache'; if ($getParams !== null && is_array($getParams)) { $this->getParams = $getParams; } $result = $this->object_to_array($this->sendRequest()); if (is_file($this->cachefile)) { $cache = json_decode(file_get_contents($this->cachefile),true); } if (!isset($result['errors'])) { $cache[$this->getParams['screen_name']]['time'] = time(); $cache[$this->getParams['screen_name']]['tweets'] = $result; $file = $this->cachefile; file_put_contents($file,json_encode($cache)); } else { $result = isset($cache[$this->getParams['screen_name']]) ? $cache[$this->getParams['screen_name']]['tweets'] : $result['errors'][0]['message']; } return json_encode( array_slice($result, 0, $getParams['count']) ); } /** * Send a POST call to Twitter API via OAuth * * @param string $call Twitter resource string * @param array $postParams POST parameters to send * @param array $getParams GET parameters to send * @return mixed Output with selected format */ public function post($call, array $postParams = null, array $getParams = null) { $this->call = $call; $this->method = 'POST'; $this->resetParams(); if ($postParams !== null && is_array($postParams)) { $this->postParams = $postParams; } if ($getParams !== null && is_array($getParams)) { $this->getParams = $getParams; } return $this->sendRequest(); } protected function resetParams() { $this->headers = null; $this->response = null; $this->postParams = array(); $this->getParams = array(); } /** * Returns raw response body * * @return string Single string with encoded values */ public function getResponse() { return $this->response; } /** * Returns response headers as array. * This can be useful to avoid extra requests for rate-limit info * x-rate-limit-limit (max request per period) * x-rate-limit-remaining (remaining this period) * x-rate-limit-reset (start of next period, UTC timestamp) * * @return array with http_code and header lines */ public function getHeaders() { return $this->headers; } /** * Converting parameters array to a single string with encoded values * * @param array $params Input parameters * @return string Single string with encoded values */ protected function getParams(array $params) { $r = ''; ksort($params); foreach ($params as $key => $value) { $r .= '&' . $key . '=' . rawurlencode($value); } unset($params, $key, $value); return trim($r, '&'); } /** * Getting full URL from a Twitter resource * * @param bool $withParams If true then parameters will be outputted * @return string Full URL */ protected function getUrl($withParams = false) { $getParams = ''; if ($withParams === true) { $getParams = $this->getParams($this->getParams); if (!empty($getParams)) { $getParams = '?' . $getParams; } } if ($this->encoded_bearer_credentials && !$this->bearer_access_token) { $url = $this->auth_url . $this->call; } else { $url = $this->url . $this->call . '.json' . $getParams; } return $url; } /** * Getting OAuth parameters to be used in request headers * * @return array OAuth parameters */ protected function getOauthParameters() { $time = time(); return array( 'oauth_consumer_key' => $this->config['consumer_key'], 'oauth_nonce' => trim(base64_encode($time), '='), 'oauth_signature_method' => 'HMAC-SHA1', 'oauth_timestamp' => $time, 'oauth_token' => $this->config['oauth_token'], 'oauth_version' => '1.0' ); } /** * Converting all parameters arrays to a single string with encoded values * * @return string Single string with encoded values */ protected function getRequestString() { $params = array_merge($this->getParams, $this->postParams, $this->getOauthParameters()); $params = $this->getParams($params); return rawurlencode($params); } /** * Getting OAuth signature base string * * @return string OAuth signature base string */ protected function getSignatureBaseString() { $method = strtoupper($this->method); $url = rawurlencode($this->getUrl()); return $method . '&' . $url . '&' . $this->getRequestString(); } /** * Getting a signing key * * @return string Signing key */ protected function getSigningKey() { return $this->config['consumer_secret'] . '&' . $this->config['oauth_token_secret']; } /** * Calculating the signature * * @return string Signature */ protected function calculateSignature() { return base64_encode(hash_hmac('sha1', $this->getSignatureBaseString(), $this->getSigningKey(), true)); } /** * Converting OAuth parameters array to a single string with encoded values * * @return string Single string with encoded values */ protected function getOauthString() { // User-keys check moved here for app-only token support $required = array( 'oauth_token' => '', 'oauth_token_secret' => '' ); if (count(array_intersect_key($required, $this->config)) !== count($required)) { //throw new \Exception('Missing parameters in configuration array'); } $oauth = array_merge($this->getOauthParameters(), array('oauth_signature' => $this->calculateSignature())); ksort($oauth); $values = array(); foreach ($oauth as $key => $value) { $values[] = $key . '="' . rawurlencode($value) . '"'; } $oauth = implode(', ', $values); unset($values, $key, $value); return $oauth; } /** * Building request HTTP headers * * @return array HTTP headers */ protected function buildRequestHeader() { if ($this->encoded_bearer_credentials) { if ($this->bearer_access_token) { return array( "Authorization: Bearer " . $this->bearer_access_token ); } else { return array( "Authorization: Basic " . $this->encoded_bearer_credentials, "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" ); } } else { // OAuth headers return array( 'Authorization: OAuth ' . $this->getOauthString(), 'Expect:' ); } } /** * Processing Twitter Exceptions in case of error * * @param string $type Depends of response format (array|object) * @param mixed $ex Exceptions * @throws Exception\TwitterException */ protected function processExceptions($type, $ex) { switch ($type) { case 'array': foreach ($ex['errors'] as $error) { //throw new TwitterException($error['message'], $error['code']); } break; default: foreach ($ex->errors as $error) { //throw new TwitterException($error->message, $error->code); } } unset($type, $ex, $error); } /** * Outputs the response in the selected format * * @param string $response * @return mixed */ protected function processOutput($response) { $format = $this->config['output_format']; switch ($format) { case 'text': if (substr($response, 2, 6) == 'errors') { $response = json_decode($response); $this->processExceptions('object', $response); } break; case 'json': if (!headers_sent()) { header('Cache-Control: no-cache, must-revalidate'); header('Expires: Tue, 19 May 1981 00:00:00 GMT'); header('Content-type: application/json'); } if (substr($response, 2, 6) == 'errors') { $response = json_decode($response); $this->processExceptions('object', $response); } break; case 'array': $response = json_decode($response, true); if (isset($response['errors'])) { $this->processExceptions('array', $response); } break; default: $response = json_decode($response); if (isset($response->errors)) { $this->processExceptions('object', $response); } } unset($format); return $response; } /** * Process curl headers to array */ protected function processCurlHeaders($headerContent) { $this->headers = array(); // Split the string on every "double" new line (multiple headers). $arrRequests = explode("\r\n\r\n", $headerContent); // Loop of response headers. The "count() -1" is to // skip an empty row for the extra line break before the body of the response. for ($index = 0; $foo = count($arrRequests) -1, $index < $foo; $index++) { foreach (explode("\r\n", $arrRequests[$index]) as $i => $line) { if ($i === 0) $this->headers[$index]['http_code'] = $line; else { list ($key, $value) = explode(': ', $line); $this->headers[$index][$key] = $value; } } } } /** * Send GET or POST requests to Twitter API * * @throws Exception\TwitterException * @return mixed Response output */ protected function sendRequest() { $url = $this->getUrl(true); $header = $this->buildRequestHeader(); $options = array( CURLOPT_URL => $url, CURLOPT_HEADER => true, CURLOPT_HTTPHEADER => $header, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, ); if (!empty($this->postParams)) { $options[CURLOPT_POST] = count($this->postParams); $options[CURLOPT_POSTFIELDS] = $this->getParams($this->postParams); } $c = curl_init(); curl_setopt_array($c, $options); $response = curl_exec($c); if ($n = curl_errno($c)) { //throw new \Exception("cURL error ($n) : ".curl_error($c)); } $header_size = curl_getinfo($c, CURLINFO_HEADER_SIZE); $headers = substr($response, 0, $header_size); $this->processCurlHeaders($headers); $this->response = substr($response, $header_size); curl_close($c); unset($response, $options, $c); if (!in_array($this->response[0], array('{', '['))) { //throw new TwitterException("($url) ".str_replace(array("\n", "\r", "\t"), '', strip_tags($this->response)), 0); } return $this->processOutput($this->response); } /** * Set an Application-only bearer-token * * When set, API-requests will use the app-token * instead of OAuth consumer keys. * https://dev.twitter.com/docs/auth/application-only-auth * * @throws Exception * @param string $token bearer-token */ public function setBearerToken($token = null) { if (empty($token)) { //throw new \Exception('Token invalid (empty)'); } $this->generateEncodedBearerCredentials(); $this->bearer_access_token = $token; } /** * Get an application-only token from consumer keys * * @return string Returns access-token on success */ public function getBearerToken() { $this->generateEncodedBearerCredentials(); $this->post('oauth2/token', array('grant_type' => 'client_credentials')); $this->bearer_access_token = $this->processTokenResponse('oauth2/token'); return $this->bearer_access_token; } /** * Revoke / invalidate an application-only token * * @param string $token Bearer-token * @throws Exception * @return string Returns the same token on success */ public function invalidateBearerToken($token = null) { if (empty($token)) { //throw new \Exception('Token invalid (empty)'); } $this->generateEncodedBearerCredentials(); $this->post("oauth2/invalidate_token", array("access_token" => rawurldecode($token))); $return_token = $this->processTokenResponse('oauth2/invalidate_token'); return $return_token; } /** * Generates basic authorization credentials for token request * */ protected function generateEncodedBearerCredentials() { $this->bearer_access_token = null; $this->encoded_bearer_credentials = null; $bearer_credentials = urlencode($this->config['consumer_key']) . ":" . urlencode($this->config['consumer_secret']); $this->encoded_bearer_credentials = base64_encode($bearer_credentials); } /** * Process oauth2 response, returns the bearer-access-token * * @param string $response * @throws Exception\TwitterException * @return mixed */ protected function processTokenResponse($path) { // json-decode raw response (as object) $response = json_decode($this->response); $token = false; switch ($path) { case 'oauth2/token': if (isset($response->token_type) && $response->token_type == 'bearer') { $token = $response->access_token; } break; case 'oauth2/invalidate_token': if (isset($response->access_token)) { $token = $response->access_token; } break; } unset($response); return $token; } } $tw = new TwitterOAuth($config); $params = array( 'screen_name' => 'creative_ws', 'count' => 6, 'exclude_replies' => false ); $res = $tw->get('statuses/user_timeline', $params); echo $res;