3 * The most popular PHP library for use with the Twitter OAuth REST API.
7 namespace Abraham\TwitterOAuth;
9 use Abraham\TwitterOAuth\Util\JsonDecoder;
12 * TwitterOAuth class for interacting with the Twitter API.
14 * @author Abraham Williams <abraham@abrah.am>
16 class TwitterOAuth extends Config
18 const API_VERSION = '1.1';
19 const API_HOST = 'https://api.twitter.com';
20 const UPLOAD_HOST = 'https://upload.twitter.com';
22 /** @var Response details about the result of the last request */
24 /** @var string|null Application bearer token */
26 /** @var Consumer Twitter application details */
28 /** @var Token|null User access token details */
30 /** @var HmacSha1 OAuth 1 signature type used by Twitter */
31 private $signatureMethod;
36 * @param string $consumerKey The Application Consumer Key
37 * @param string $consumerSecret The Application Consumer Secret
38 * @param string|null $oauthToken The Client Token (optional)
39 * @param string|null $oauthTokenSecret The Client Token Secret (optional)
41 public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
43 $this->resetLastResponse();
44 $this->signatureMethod = new HmacSha1();
45 $this->consumer = new Consumer($consumerKey, $consumerSecret);
46 if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
47 $this->token = new Token($oauthToken, $oauthTokenSecret);
49 if (empty($oauthToken) && !empty($oauthTokenSecret)) {
50 $this->bearer = $oauthTokenSecret;
55 * @param string $oauthToken
56 * @param string $oauthTokenSecret
58 public function setOauthToken($oauthToken, $oauthTokenSecret)
60 $this->token = new Token($oauthToken, $oauthTokenSecret);
66 public function getLastApiPath()
68 return $this->response->getApiPath();
74 public function getLastHttpCode()
76 return $this->response->getHttpCode();
82 public function getLastXHeaders()
84 return $this->response->getXHeaders();
88 * @return array|object|null
90 public function getLastBody()
92 return $this->response->getBody();
96 * Resets the last response cache.
98 public function resetLastResponse()
100 $this->response = new Response();
104 * Make URLs for user browser navigation.
106 * @param string $path
107 * @param array $parameters
111 public function url($path, array $parameters)
113 $this->resetLastResponse();
114 $this->response->setApiPath($path);
115 $query = http_build_query($parameters);
116 return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
120 * Make /oauth/* requests to the API.
122 * @param string $path
123 * @param array $parameters
126 * @throws TwitterOAuthException
128 public function oauth($path, array $parameters = [])
131 $this->resetLastResponse();
132 $this->response->setApiPath($path);
133 $url = sprintf('%s/%s', self::API_HOST, $path);
134 $result = $this->oAuthRequest($url, 'POST', $parameters);
136 if ($this->getLastHttpCode() != 200) {
137 throw new TwitterOAuthException($result);
140 parse_str($result, $response);
141 $this->response->setBody($response);
147 * Make /oauth2/* requests to the API.
149 * @param string $path
150 * @param array $parameters
152 * @return array|object
154 public function oauth2($path, array $parameters = [])
157 $this->resetLastResponse();
158 $this->response->setApiPath($path);
159 $url = sprintf('%s/%s', self::API_HOST, $path);
160 $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
161 $authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
162 $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
163 $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
164 $this->response->setBody($response);
169 * Make GET requests to the API.
171 * @param string $path
172 * @param array $parameters
174 * @return array|object
176 public function get($path, array $parameters = [])
178 return $this->http('GET', self::API_HOST, $path, $parameters);
182 * Make POST requests to the API.
184 * @param string $path
185 * @param array $parameters
187 * @return array|object
189 public function post($path, array $parameters = [])
191 return $this->http('POST', self::API_HOST, $path, $parameters);
195 * Make DELETE requests to the API.
197 * @param string $path
198 * @param array $parameters
200 * @return array|object
202 public function delete($path, array $parameters = [])
204 return $this->http('DELETE', self::API_HOST, $path, $parameters);
208 * Make PUT requests to the API.
210 * @param string $path
211 * @param array $parameters
213 * @return array|object
215 public function put($path, array $parameters = [])
217 return $this->http('PUT', self::API_HOST, $path, $parameters);
221 * Upload media to upload.twitter.com.
223 * @param string $path
224 * @param array $parameters
225 * @param boolean $chunked
227 * @return array|object
229 public function upload($path, array $parameters = [], $chunked = false)
232 return $this->uploadMediaChunked($path, $parameters);
234 return $this->uploadMediaNotChunked($path, $parameters);
239 * Private method to upload media (not chunked) to upload.twitter.com.
241 * @param string $path
242 * @param array $parameters
244 * @return array|object
246 private function uploadMediaNotChunked($path, array $parameters)
248 $file = file_get_contents($parameters['media']);
249 $base = base64_encode($file);
250 $parameters['media'] = $base;
251 return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
255 * Private method to upload media (chunked) to upload.twitter.com.
257 * @param string $path
258 * @param array $parameters
260 * @return array|object
262 private function uploadMediaChunked($path, array $parameters)
264 $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
267 $media = fopen($parameters['media'], 'rb');
268 while (!feof($media))
270 $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
271 'command' => 'APPEND',
272 'media_id' => $init->media_id_string,
273 'segment_index' => $segmentIndex++,
274 'media_data' => base64_encode(fread($media, $this->chunkSize))
279 $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
280 'command' => 'FINALIZE',
281 'media_id' => $init->media_id_string
287 * Private method to get params for upload media chunked init.
288 * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
290 * @param array $parameters
294 private function mediaInitParameters(array $parameters)
298 'media_type' => $parameters['media_type'],
299 'total_bytes' => filesize($parameters['media'])
301 if (isset($parameters['additional_owners'])) {
302 $return['additional_owners'] = $parameters['additional_owners'];
304 if (isset($parameters['media_category'])) {
305 $return['media_category'] = $parameters['media_category'];
311 * @param string $method
312 * @param string $host
313 * @param string $path
314 * @param array $parameters
316 * @return array|object
318 private function http($method, $host, $path, array $parameters)
320 $this->resetLastResponse();
321 $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
322 $this->response->setApiPath($path);
323 $result = $this->oAuthRequest($url, $method, $parameters);
324 $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
325 $this->response->setBody($response);
330 * Format and sign an OAuth / API request
333 * @param string $method
334 * @param array $parameters
337 * @throws TwitterOAuthException
339 private function oAuthRequest($url, $method, array $parameters)
341 $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
342 if (array_key_exists('oauth_callback', $parameters)) {
343 // Twitter doesn't like oauth_callback as a parameter.
344 unset($parameters['oauth_callback']);
346 if ($this->bearer === null) {
347 $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
348 $authorization = $request->toHeader();
349 if (array_key_exists('oauth_verifier', $parameters)) {
350 // Twitter doesn't always work with oauth in the body and in the header
351 // and it's already included in the $authorization header
352 unset($parameters['oauth_verifier']);
355 $authorization = 'Authorization: Bearer ' . $this->bearer;
357 return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
365 private function curlOptions()
368 // CURLOPT_VERBOSE => true,
369 CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
370 CURLOPT_HEADER => true,
371 CURLOPT_RETURNTRANSFER => true,
372 CURLOPT_SSL_VERIFYHOST => 2,
373 CURLOPT_SSL_VERIFYPEER => true,
374 CURLOPT_TIMEOUT => $this->timeout,
375 CURLOPT_USERAGENT => $this->userAgent,
378 if ($this->useCAFile()) {
379 $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
382 if($this->gzipEncoding) {
383 $options[CURLOPT_ENCODING] = 'gzip';
386 if (!empty($this->proxy)) {
387 $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
388 $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
389 $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
390 $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
391 $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
398 * Make an HTTP request
401 * @param string $method
402 * @param string $authorization
403 * @param array $postfields
406 * @throws TwitterOAuthException
408 private function request($url, $method, $authorization, array $postfields)
410 $options = $this->curlOptions($url, $authorization);
411 $options[CURLOPT_URL] = $url;
412 $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
418 $options[CURLOPT_POST] = true;
419 $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
422 $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
425 $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
429 if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
430 $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
434 $curlHandle = curl_init();
435 curl_setopt_array($curlHandle, $options);
436 $response = curl_exec($curlHandle);
438 // Throw exceptions on cURL errors.
439 if (curl_errno($curlHandle) > 0) {
440 throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
443 $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
444 $parts = explode("\r\n\r\n", $response);
445 $responseBody = array_pop($parts);
446 $responseHeader = array_pop($parts);
447 $this->response->setHeaders($this->parseHeaders($responseHeader));
449 curl_close($curlHandle);
451 return $responseBody;
455 * Get the header info to store.
457 * @param string $header
461 private function parseHeaders($header)
464 foreach (explode("\r\n", $header) as $line) {
465 if (strpos($line, ':') !== false) {
466 list ($key, $value) = explode(': ', $line);
467 $key = str_replace('-', '_', strtolower($key));
468 $headers[$key] = trim($value);
475 * Encode application authorization header with base64.
477 * @param Consumer $consumer
481 private function encodeAppAuthorization(Consumer $consumer)
483 $key = rawurlencode($consumer->key);
484 $secret = rawurlencode($consumer->secret);
485 return base64_encode($key . ':' . $secret);
489 * Is the code running from a Phar module.
493 private function pharRunning()
495 return class_exists('Phar') && \Phar::running(false) !== '';
499 * Use included CA file instead of OS provided list.
503 private function useCAFile()
505 /* Use CACert file when not in a PHAR file. */
506 return !$this->pharRunning();