]> git.mxchange.org Git - friendica-addons.git/blob - twitter/vendor/abraham/twitteroauth/src/TwitterOAuth.php
Merge branch '3.6-rc'
[friendica-addons.git] / twitter / vendor / abraham / twitteroauth / src / TwitterOAuth.php
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 <abraham@abrah.am>
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);
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 }