GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#626)
by
unknown
01:38
created

TwitterOAuth::postData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
    /** @var int Number of attempts we made for the request */
33
    private $attempts = 0;
34
35
    /**
36
     * Constructor
37
     *
38
     * @param string      $consumerKey      The Application Consumer Key
39
     * @param string      $consumerSecret   The Application Consumer Secret
40
     * @param string|null $oauthToken       The Client Token (optional)
41
     * @param string|null $oauthTokenSecret The Client Token Secret (optional)
42
     */
43
    public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
44
    {
45
        $this->resetLastResponse();
46
        $this->signatureMethod = new HmacSha1();
47
        $this->consumer = new Consumer($consumerKey, $consumerSecret);
48
        if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
49
            $this->setOauthToken($oauthToken, $oauthTokenSecret);
50
        }
51
        if (empty($oauthToken) && !empty($oauthTokenSecret)) {
52
            $this->setBearer($oauthTokenSecret);
53
        }
54
    }
55
56
    /**
57
     * @param string $oauthToken
58
     * @param string $oauthTokenSecret
59
     */
60
    public function setOauthToken($oauthToken, $oauthTokenSecret)
61
    {
62
        $this->token = new Token($oauthToken, $oauthTokenSecret);
63
        $this->bearer = null;
64
    }
65
66
    /**
67
     * @param string $oauthTokenSecret
68
     */
69
    public function setBearer($oauthTokenSecret)
70
    {
71
        $this->bearer = $oauthTokenSecret;
72
        $this->token = null;
73
    }
74
75
    /**
76
     * @return string|null
77
     */
78
    public function getLastApiPath()
79
    {
80
        return $this->response->getApiPath();
81
    }
82
83
    /**
84
     * @return int
85
     */
86
    public function getLastHttpCode()
87
    {
88
        return $this->response->getHttpCode();
89
    }
90
91
    /**
92
     * @return array
93
     */
94
    public function getLastXHeaders()
95
    {
96
        return $this->response->getXHeaders();
97
    }
98
99
    /**
100
     * @return array|object|null
101
     */
102
    public function getLastBody()
103
    {
104
        return $this->response->getBody();
105
    }
106
107
    /**
108
     * Resets the last response cache.
109
     */
110
    public function resetLastResponse()
111
    {
112
        $this->response = new Response();
113
    }
114
115
    /**
116
     * Resets the attempts number.
117
     */
118
    private function resetAttemptsNumber()
119
    {
120
        $this->attempts = 0;
121
    }
122
123
    /**
124
     * Delays the retries when they're activated.
125
     */
126
    private function sleepIfNeeded()
127
    {
128
        if ($this->maxRetries && $this->attempts) {
129
            sleep($this->retriesDelay);
130
        }
131
    }
132
133
134
    /**
135
     * Make URLs for user browser navigation.
136
     *
137
     * @param string $path
138
     * @param array  $parameters
139
     *
140
     * @return string
141
     */
142
    public function url($path, array $parameters)
143
    {
144
        $this->resetLastResponse();
145
        $this->response->setApiPath($path);
146
        $query = http_build_query($parameters);
147
        return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
148
    }
149
150
    /**
151
     * Make /oauth/* requests to the API.
152
     *
153
     * @param string $path
154
     * @param array  $parameters
155
     *
156
     * @return array
157
     * @throws TwitterOAuthException
158
     */
159
    public function oauth($path, array $parameters = [])
160
    {
161
        $response = [];
162
        $this->resetLastResponse();
163
        $this->response->setApiPath($path);
164
        $url = sprintf('%s/%s', self::API_HOST, $path);
165
        $result = $this->oAuthRequest($url, 'POST', $parameters);
166
167
        if ($this->getLastHttpCode() != 200) {
168
            throw new TwitterOAuthException($result);
169
        }
170
171
        parse_str($result, $response);
172
        $this->response->setBody($response);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type null; however, Abraham\TwitterOAuth\Response::setBody() does only seem to accept array|object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
173
174
        return $response;
175
    }
176
177
    /**
178
     * Make /oauth2/* requests to the API.
179
     *
180
     * @param string $path
181
     * @param array  $parameters
182
     *
183
     * @return array|object
184
     */
185
    public function oauth2($path, array $parameters = [])
186
    {
187
        $method = 'POST';
188
        $this->resetLastResponse();
189
        $this->response->setApiPath($path);
190
        $url = sprintf('%s/%s', self::API_HOST, $path);
191
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
192
        $authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
193
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
194
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
195
        $this->response->setBody($response);
196
        return $response;
197
    }
198
199
    /**
200
     * Make GET requests to the API.
201
     *
202
     * @param string $path
203
     * @param array  $parameters
204
     *
205
     * @return array|object
206
     */
207
    public function get($path, array $parameters = [])
208
    {
209
        return $this->http('GET', self::API_HOST, $path, $parameters);
210
    }
211
212
    /**
213
     * Make POST requests to the API.
214
     *
215
     * @param string $path
216
     * @param array  $parameters
217
     * @param string $host
218
     *
219
     * @return array|object
220
     */
221
    public function post($path, array $parameters = [], $host = self::API_HOST)
222
    {
223
        return $this->http('POST', $host, $path, $parameters);
224
    }
225
226
    /**
227
     * Make DELETE requests to the API.
228
     *
229
     * @param string $path
230
     * @param array  $parameters
231
     *
232
     * @return array|object
233
     */
234
    public function delete($path, array $parameters = [])
235
    {
236
        return $this->http('DELETE', self::API_HOST, $path, $parameters);
237
    }
238
239
    /**
240
     * Make PUT requests to the API.
241
     *
242
     * @param string $path
243
     * @param array  $parameters
244
     *
245
     * @return array|object
246
     */
247
    public function put($path, array $parameters = [])
248
    {
249
        return $this->http('PUT', self::API_HOST, $path, $parameters);
250
    }
251
252
    public function postData()
253
    {
254
        return $this->http('POST', self::DATA_API_HOST, $path, $parameters);
0 ignored issues
show
Bug introduced by
The variable $path does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $parameters does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
255
    }
256
257
    /**
258
     * Upload media to upload.twitter.com.
259
     *
260
     * @param string $path
261
     * @param array  $parameters
262
     * @param boolean  $chunked
263
     *
264
     * @return array|object
265
     */
266
    public function upload($path, array $parameters = [], $chunked = false)
267
    {
268
        if ($chunked) {
269
            return $this->uploadMediaChunked($path, $parameters);
270
        } else {
271
            return $this->uploadMediaNotChunked($path, $parameters);
272
        }
273
    }
274
275
    /**
276
     * Private method to upload media (not chunked) to upload.twitter.com.
277
     *
278
     * @param string $path
279
     * @param array  $parameters
280
     *
281
     * @return array|object
282
     */
283
    private function uploadMediaNotChunked($path, array $parameters)
284
    {
285
        $file = file_get_contents($parameters['media']);
286
        $base = base64_encode($file);
287
        $parameters['media'] = $base;
288
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
289
    }
290
291
    /**
292
     * Private method to upload media (chunked) to upload.twitter.com.
293
     *
294
     * @param string $path
295
     * @param array  $parameters
296
     *
297
     * @return array|object
298
     */
299
    private function uploadMediaChunked($path, array $parameters)
300
    {
301
        $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
302
        // Append
303
        $segmentIndex = 0;
304
        $media = fopen($parameters['media'], 'rb');
305
        while (!feof($media)) {
306
            $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
307
                'command' => 'APPEND',
308
                'media_id' => $init->media_id_string,
309
                'segment_index' => $segmentIndex++,
310
                'media_data' => base64_encode(fread($media, $this->chunkSize))
311
            ]);
312
        }
313
        fclose($media);
314
        // Finalize
315
        $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
316
            'command' => 'FINALIZE',
317
            'media_id' => $init->media_id_string
318
        ]);
319
        return $finalize;
320
    }
321
322
    /**
323
     * Private method to get params for upload media chunked init.
324
     * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
325
     *
326
     * @param array  $parameters
327
     *
328
     * @return array
329
     */
330
    private function mediaInitParameters(array $parameters)
331
    {
332
        $return = [
333
            'command' => 'INIT',
334
            'media_type' => $parameters['media_type'],
335
            'total_bytes' => filesize($parameters['media'])
336
        ];
337
        if (isset($parameters['additional_owners'])) {
338
            $return['additional_owners'] = $parameters['additional_owners'];
339
        }
340
        if (isset($parameters['media_category'])) {
341
            $return['media_category'] = $parameters['media_category'];
342
        }
343
        return $return;
344
    }
345
346
    /**
347
     * @param string $method
348
     * @param string $host
349
     * @param string $path
350
     * @param array  $parameters
351
     *
352
     * @return array|object
353
     */
354
    private function http($method, $host, $path, array $parameters)
355
    {
356
        $this->resetLastResponse();
357
        $this->resetAttemptsNumber();
358
        $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
359
        $this->response->setApiPath($path);
360
        return $this->makeRequests($url, $method, $parameters);
361
    }
362
363
    /**
364
     *
365
     * Make requests and retry them (if enabled) in case of Twitter's problems.
366
     *
367
     * @param string $method
368
     * @param string $url
369
     * @param string $method
370
     * @param array  $parameters
371
     *
372
     * @return array|object
373
     */
374
    private function makeRequests($url, $method, array $parameters)
375
    {
376
        do {
377
            $this->sleepIfNeeded();
378
            $result = $this->oAuthRequest($url, $method, $parameters);
379
            $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
380
            $this->response->setBody($response);
381
            $this->attempts++;
382
            // Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
383
        } while ($this->requestsAvailable());
384
385
        return $response;
386
    }
387
388
    /**
389
     * Checks if we have to retry request if API is down.
390
     *
391
     * @return bool
392
     */
393
    private function requestsAvailable()
394
    {
395
        return ($this->maxRetries && ($this->attempts <= $this->maxRetries) && $this->getLastHttpCode() >= 500);
396
    }
397
398
    /**
399
     * Format and sign an OAuth / API request
400
     *
401
     * @param string $url
402
     * @param string $method
403
     * @param array  $parameters
404
     *
405
     * @return string
406
     * @throws TwitterOAuthException
407
     */
408
    private function oAuthRequest($url, $method, array $parameters)
409
    {
410
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
411
        if (array_key_exists('oauth_callback', $parameters)) {
412
            // Twitter doesn't like oauth_callback as a parameter.
413
            unset($parameters['oauth_callback']);
414
        }
415
        if ($this->bearer === null) {
416
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
417
            $authorization = $request->toHeader();
418
            if (array_key_exists('oauth_verifier', $parameters)) {
419
                // Twitter doesn't always work with oauth in the body and in the header
420
                // and it's already included in the $authorization header
421
                unset($parameters['oauth_verifier']);
422
            }
423
        } else {
424
            $authorization = 'Authorization: Bearer ' . $this->bearer;
425
        }
426
        return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
427
    }
428
429
    /**
430
     * Set Curl options.
431
     *
432
     * @return array
433
     */
434
    private function curlOptions()
435
    {
436
        $options = [
437
            // CURLOPT_VERBOSE => true,
438
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
439
            CURLOPT_HEADER => true,
440
            CURLOPT_RETURNTRANSFER => true,
441
            CURLOPT_SSL_VERIFYHOST => 2,
442
            CURLOPT_SSL_VERIFYPEER => true,
443
            CURLOPT_TIMEOUT => $this->timeout,
444
            CURLOPT_USERAGENT => $this->userAgent,
445
        ];
446
447
        if ($this->useCAFile()) {
448
            $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
449
        }
450
451
        if ($this->gzipEncoding) {
452
            $options[CURLOPT_ENCODING] = 'gzip';
453
        }
454
455
        if (!empty($this->proxy)) {
456
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
457
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
458
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
459
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
460
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
461
        }
462
463
        return $options;
464
    }
465
466
    /**
467
     * Make an HTTP request
468
     *
469
     * @param string $url
470
     * @param string $method
471
     * @param string $authorization
472
     * @param array $postfields
473
     *
474
     * @return string
475
     * @throws TwitterOAuthException
476
     */
477
    private function request($url, $method, $authorization, array $postfields)
478
    {
479
        $options = $this->curlOptions();
480
        $options[CURLOPT_URL] = $url;
481
        $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
482
483
        switch ($method) {
484
            case 'GET':
485
                break;
486
            case 'POST':
487
                $options[CURLOPT_POST] = true;
488
                $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
489
                break;
490
            case 'DELETE':
491
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
492
                break;
493
            case 'PUT':
494
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
495
                break;
496
        }
497
498
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
499
            $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
500
        }
501
502
503
        $curlHandle = curl_init();
504
        curl_setopt_array($curlHandle, $options);
505
        $response = curl_exec($curlHandle);
506
507
        // Throw exceptions on cURL errors.
508
        if (curl_errno($curlHandle) > 0) {
509
            throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
510
        }
511
512
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
513
        $parts = explode("\r\n\r\n", $response);
514
        $responseBody = array_pop($parts);
515
        $responseHeader = array_pop($parts);
516
        $this->response->setHeaders($this->parseHeaders($responseHeader));
517
518
        curl_close($curlHandle);
519
520
        return $responseBody;
521
    }
522
523
    /**
524
     * Get the header info to store.
525
     *
526
     * @param string $header
527
     *
528
     * @return array
529
     */
530
    private function parseHeaders($header)
531
    {
532
        $headers = [];
533
        foreach (explode("\r\n", $header) as $line) {
534
            if (strpos($line, ':') !== false) {
535
                list ($key, $value) = explode(': ', $line);
536
                $key = str_replace('-', '_', strtolower($key));
537
                $headers[$key] = trim($value);
538
            }
539
        }
540
        return $headers;
541
    }
542
543
    /**
544
     * Encode application authorization header with base64.
545
     *
546
     * @param Consumer $consumer
547
     *
548
     * @return string
549
     */
550
    private function encodeAppAuthorization(Consumer $consumer)
551
    {
552
        $key = rawurlencode($consumer->key);
553
        $secret = rawurlencode($consumer->secret);
554
        return base64_encode($key . ':' . $secret);
555
    }
556
557
    /**
558
     * Is the code running from a Phar module.
559
     *
560
     * @return boolean
561
     */
562
    private function pharRunning()
563
    {
564
        return class_exists('Phar') && \Phar::running(false) !== '';
565
    }
566
567
    /**
568
     * Use included CA file instead of OS provided list.
569
     *
570
     * @return boolean
571
     */
572
    private function useCAFile()
573
    {
574
        /* Use CACert file when not in a PHAR file. */
575
        return !$this->pharRunning();
576
    }
577
}
578