These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * The most popular PHP library for use with the Twitter OAuth REST API. |
||
4 | * |
||
5 | * @license MIT |
||
6 | */ |
||
7 | namespace Abraham\TwitterOAuth; |
||
8 | |||
9 | use Abraham\TwitterOAuth\Util\JsonDecoder; |
||
10 | |||
11 | /** |
||
12 | * TwitterOAuth class for interacting with the Twitter API. |
||
13 | * |
||
14 | * @author Abraham Williams <[email protected]> |
||
15 | */ |
||
16 | class TwitterOAuth extends Config |
||
17 | { |
||
18 | const API_VERSION = '1.1'; |
||
19 | const API_HOST = 'https://api.twitter.com'; |
||
20 | const UPLOAD_HOST = 'https://upload.twitter.com'; |
||
21 | |||
22 | /** @var Response details about the result of the last request */ |
||
23 | private $response; |
||
24 | /** @var string|null Application bearer token */ |
||
25 | private $bearer; |
||
26 | /** @var Consumer Twitter application details */ |
||
27 | private $consumer; |
||
28 | /** @var Token|null User access token details */ |
||
29 | private $token; |
||
30 | /** @var HmacSha1 OAuth 1 signature type used by Twitter */ |
||
31 | private $signatureMethod; |
||
32 | |||
33 | /** |
||
34 | * Constructor |
||
35 | * |
||
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) |
||
40 | */ |
||
41 | public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null) |
||
42 | { |
||
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); |
||
48 | } |
||
49 | if (empty($oauthToken) && !empty($oauthTokenSecret)) { |
||
50 | $this->bearer = $oauthTokenSecret; |
||
51 | } |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * @param string $oauthToken |
||
56 | * @param string $oauthTokenSecret |
||
57 | */ |
||
58 | public function setOauthToken($oauthToken, $oauthTokenSecret) |
||
59 | { |
||
60 | $this->token = new Token($oauthToken, $oauthTokenSecret); |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * @return string|null |
||
65 | */ |
||
66 | public function getLastApiPath() |
||
67 | { |
||
68 | return $this->response->getApiPath(); |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * @return int |
||
73 | */ |
||
74 | public function getLastHttpCode() |
||
75 | { |
||
76 | return $this->response->getHttpCode(); |
||
77 | } |
||
78 | |||
79 | /** |
||
80 | * @return array |
||
81 | */ |
||
82 | public function getLastXHeaders() |
||
83 | { |
||
84 | return $this->response->getXHeaders(); |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * @return array|object|null |
||
89 | */ |
||
90 | public function getLastBody() |
||
91 | { |
||
92 | return $this->response->getBody(); |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Resets the last response cache. |
||
97 | */ |
||
98 | public function resetLastResponse() |
||
99 | { |
||
100 | $this->response = new Response(); |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * Make URLs for user browser navigation. |
||
105 | * |
||
106 | * @param string $path |
||
107 | * @param array $parameters |
||
108 | * |
||
109 | * @return string |
||
110 | */ |
||
111 | public function url($path, array $parameters) |
||
112 | { |
||
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); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Make /oauth/* requests to the API. |
||
121 | * |
||
122 | * @param string $path |
||
123 | * @param array $parameters |
||
124 | * |
||
125 | * @return array |
||
126 | * @throws TwitterOAuthException |
||
127 | */ |
||
128 | public function oauth($path, array $parameters = []) |
||
129 | { |
||
130 | $response = []; |
||
131 | $this->resetLastResponse(); |
||
132 | $this->response->setApiPath($path); |
||
133 | $url = sprintf('%s/%s', self::API_HOST, $path); |
||
134 | $result = $this->oAuthRequest($url, 'POST', $parameters); |
||
135 | |||
136 | if ($this->getLastHttpCode() != 200) { |
||
137 | throw new TwitterOAuthException($result); |
||
138 | } |
||
139 | |||
140 | parse_str($result, $response); |
||
141 | $this->response->setBody($response); |
||
142 | |||
143 | return $response; |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * Make /oauth2/* requests to the API. |
||
148 | * |
||
149 | * @param string $path |
||
150 | * @param array $parameters |
||
151 | * |
||
152 | * @return array|object |
||
153 | */ |
||
154 | public function oauth2($path, array $parameters = []) |
||
155 | { |
||
156 | $method = 'POST'; |
||
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); |
||
165 | return $response; |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * Make GET requests to the API. |
||
170 | * |
||
171 | * @param string $path |
||
172 | * @param array $parameters |
||
173 | * |
||
174 | * @return array|object |
||
175 | */ |
||
176 | public function get($path, array $parameters = []) |
||
177 | { |
||
178 | return $this->http('GET', self::API_HOST, $path, $parameters); |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Make POST requests to the API. |
||
183 | * |
||
184 | * @param string $path |
||
185 | * @param array $parameters |
||
186 | * |
||
187 | * @return array|object |
||
188 | */ |
||
189 | public function post($path, array $parameters = []) |
||
190 | { |
||
191 | return $this->http('POST', self::API_HOST, $path, $parameters); |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Make DELETE requests to the API. |
||
196 | * |
||
197 | * @param string $path |
||
198 | * @param array $parameters |
||
199 | * |
||
200 | * @return array|object |
||
201 | */ |
||
202 | public function delete($path, array $parameters = []) |
||
203 | { |
||
204 | return $this->http('DELETE', self::API_HOST, $path, $parameters); |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Make PUT requests to the API. |
||
209 | * |
||
210 | * @param string $path |
||
211 | * @param array $parameters |
||
212 | * |
||
213 | * @return array|object |
||
214 | */ |
||
215 | public function put($path, array $parameters = []) |
||
216 | { |
||
217 | return $this->http('PUT', self::API_HOST, $path, $parameters); |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Upload media to upload.twitter.com. |
||
222 | * |
||
223 | * @param string $path |
||
224 | * @param array $parameters |
||
225 | * @param boolean $chunked |
||
226 | * |
||
227 | * @return array|object |
||
228 | */ |
||
229 | public function upload($path, array $parameters = [], $chunked = false) |
||
230 | { |
||
231 | if ($chunked) { |
||
232 | return $this->uploadMediaChunked($path, $parameters); |
||
233 | } else { |
||
234 | return $this->uploadMediaNotChunked($path, $parameters); |
||
235 | } |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * Private method to upload media (not chunked) to upload.twitter.com. |
||
240 | * |
||
241 | * @param string $path |
||
242 | * @param array $parameters |
||
243 | * |
||
244 | * @return array|object |
||
245 | */ |
||
246 | private function uploadMediaNotChunked($path, array $parameters) |
||
247 | { |
||
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); |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Private method to upload media (chunked) to upload.twitter.com. |
||
256 | * |
||
257 | * @param string $path |
||
258 | * @param array $parameters |
||
259 | * |
||
260 | * @return array|object |
||
261 | */ |
||
262 | private function uploadMediaChunked($path, array $parameters) |
||
263 | { |
||
264 | $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters)); |
||
265 | // Append |
||
266 | $segmentIndex = 0; |
||
267 | $media = fopen($parameters['media'], 'rb'); |
||
268 | while (!feof($media)) |
||
269 | { |
||
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)) |
||
275 | ]); |
||
276 | } |
||
277 | fclose($media); |
||
278 | // Finalize |
||
279 | $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [ |
||
280 | 'command' => 'FINALIZE', |
||
281 | 'media_id' => $init->media_id_string |
||
282 | ]); |
||
283 | return $finalize; |
||
284 | } |
||
285 | |||
286 | /** |
||
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 |
||
289 | * |
||
290 | * @param array $parameters |
||
291 | * |
||
292 | * @return array |
||
293 | */ |
||
294 | private function mediaInitParameters(array $parameters) |
||
295 | { |
||
296 | $return = [ |
||
297 | 'command' => 'INIT', |
||
298 | 'media_type' => $parameters['media_type'], |
||
299 | 'total_bytes' => filesize($parameters['media']) |
||
300 | ]; |
||
301 | if (isset($parameters['additional_owners'])) { |
||
302 | $return['additional_owners'] = $parameters['additional_owners']; |
||
303 | } |
||
304 | if (isset($parameters['media_category'])) { |
||
305 | $return['media_category'] = $parameters['media_category']; |
||
306 | } |
||
307 | return $return; |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * @param string $method |
||
312 | * @param string $host |
||
313 | * @param string $path |
||
314 | * @param array $parameters |
||
315 | * |
||
316 | * @return array|object |
||
317 | */ |
||
318 | private function http($method, $host, $path, array $parameters) |
||
319 | { |
||
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); |
||
326 | return $response; |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Format and sign an OAuth / API request |
||
331 | * |
||
332 | * @param string $url |
||
333 | * @param string $method |
||
334 | * @param array $parameters |
||
335 | * |
||
336 | * @return string |
||
337 | * @throws TwitterOAuthException |
||
338 | */ |
||
339 | private function oAuthRequest($url, $method, array $parameters) |
||
340 | { |
||
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']); |
||
345 | } |
||
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']); |
||
353 | } |
||
354 | } else { |
||
355 | $authorization = 'Authorization: Bearer ' . $this->bearer; |
||
356 | } |
||
357 | return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters); |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Set Curl options. |
||
362 | * |
||
363 | * @return array |
||
364 | */ |
||
365 | private function curlOptions() |
||
366 | { |
||
367 | $options = [ |
||
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, |
||
376 | ]; |
||
377 | |||
378 | if ($this->useCAFile()) { |
||
379 | $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem'; |
||
380 | } |
||
381 | |||
382 | if($this->gzipEncoding) { |
||
383 | $options[CURLOPT_ENCODING] = 'gzip'; |
||
384 | } |
||
385 | |||
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; |
||
392 | } |
||
393 | |||
394 | return $options; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * Make an HTTP request |
||
399 | * |
||
400 | * @param string $url |
||
401 | * @param string $method |
||
402 | * @param string $authorization |
||
403 | * @param array $postfields |
||
404 | * |
||
405 | * @return string |
||
406 | * @throws TwitterOAuthException |
||
407 | */ |
||
408 | private function request($url, $method, $authorization, array $postfields) |
||
409 | { |
||
410 | $options = $this->curlOptions($url, $authorization); |
||
0 ignored issues
–
show
|
|||
411 | $options[CURLOPT_URL] = $url; |
||
412 | $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:']; |
||
413 | |||
414 | switch ($method) { |
||
415 | case 'GET': |
||
416 | break; |
||
417 | case 'POST': |
||
418 | $options[CURLOPT_POST] = true; |
||
419 | $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields); |
||
420 | break; |
||
421 | case 'DELETE': |
||
422 | $options[CURLOPT_CUSTOMREQUEST] = 'DELETE'; |
||
423 | break; |
||
424 | case 'PUT': |
||
425 | $options[CURLOPT_CUSTOMREQUEST] = 'PUT'; |
||
426 | break; |
||
427 | } |
||
428 | |||
429 | if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) { |
||
430 | $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields); |
||
431 | } |
||
432 | |||
433 | |||
434 | $curlHandle = curl_init(); |
||
435 | curl_setopt_array($curlHandle, $options); |
||
436 | $response = curl_exec($curlHandle); |
||
437 | |||
438 | // Throw exceptions on cURL errors. |
||
439 | if (curl_errno($curlHandle) > 0) { |
||
440 | throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle)); |
||
441 | } |
||
442 | |||
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)); |
||
448 | |||
449 | curl_close($curlHandle); |
||
450 | |||
451 | return $responseBody; |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * Get the header info to store. |
||
456 | * |
||
457 | * @param string $header |
||
458 | * |
||
459 | * @return array |
||
460 | */ |
||
461 | private function parseHeaders($header) |
||
462 | { |
||
463 | $headers = []; |
||
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); |
||
469 | } |
||
470 | } |
||
471 | return $headers; |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Encode application authorization header with base64. |
||
476 | * |
||
477 | * @param Consumer $consumer |
||
478 | * |
||
479 | * @return string |
||
480 | */ |
||
481 | private function encodeAppAuthorization(Consumer $consumer) |
||
482 | { |
||
483 | $key = rawurlencode($consumer->key); |
||
484 | $secret = rawurlencode($consumer->secret); |
||
485 | return base64_encode($key . ':' . $secret); |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * Is the code running from a Phar module. |
||
490 | * |
||
491 | * @return boolean |
||
492 | */ |
||
493 | private function pharRunning() |
||
494 | { |
||
495 | return class_exists('Phar') && \Phar::running(false) !== ''; |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Use included CA file instead of OS provided list. |
||
500 | * |
||
501 | * @return boolean |
||
502 | */ |
||
503 | private function useCAFile() |
||
504 | { |
||
505 | /* Use CACert file when not in a PHAR file. */ |
||
506 | return !$this->pharRunning(); |
||
507 | } |
||
508 | } |
||
509 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.