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 (#593)
by
unknown
01:30
created

TwitterOAuth::isDecodeJsonAsArray()   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 API_EXTENSION = 'json';
21
    const UPLOAD_HOST = 'https://upload.twitter.com';
22
    const STATUS_SUCCES = 200;
23
24
    /** @var Response details about the result of the last request */
25
    private $response;
26
    /** @var string|null Application bearer token */
27
    private $bearer;
28
    /** @var Consumer Twitter application details */
29
    private $consumer;
30
    /** @var Token|null User access token details */
31
    private $token;
32
    /** @var HmacSha1 OAuth 1 signature type used by Twitter */
33
    private $signatureMethod;
34
    /** @var int Number of attempts we made for the request */
35
    private $attempts = 0;
36
    /** @var string|null Version of the API */
37
    private $apiVersion;
38
    /** @var string|null Host of the API */
39
    private $apiHost;
40
    /** @var string|null Extension of the API */
41
    private $apiExtension;
42
43
    /**
44
     * Constructor
45
     *
46
     * @param string      $consumerKey      The Application Consumer Key
47
     * @param string      $consumerSecret   The Application Consumer Secret
48
     * @param string|null $oauthToken       The Client Token (optional)
49
     * @param string|null $oauthTokenSecret The Client Token Secret (optional)
50
     */
51
    public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
52
    {
53
        $this->setApiVersion(self::API_VERSION);
54
        $this->setApiHost(self::API_HOST);
55
        $this->setApiExtension(self::API_EXTENSION);
56
        $this->resetLastResponse();
57
        $this->signatureMethod = new HmacSha1();
58
        $this->consumer = new Consumer($consumerKey, $consumerSecret);
59
        if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
60
            $this->token = new Token($oauthToken, $oauthTokenSecret);
61
        }
62
        if (empty($oauthToken) && !empty($oauthTokenSecret)) {
63
            $this->bearer = $oauthTokenSecret;
64
        }
65
    }
66
67
    /**
68
     * @param string $oauthToken
69
     * @param string $oauthTokenSecret
70
     */
71
    public function setOauthToken($oauthToken, $oauthTokenSecret)
72
    {
73
        $this->token = new Token($oauthToken, $oauthTokenSecret);
74
    }
75
76
    /**
77
     * @param string $version
78
     */
79
    public function setApiVersion($version)
80
    {
81
        $this->apiVersion = $version;
82
    }
83
84
    /**
85
     * @return string
86
     */
87
    public function getApiVersion()
88
    {
89
        return $this->apiVersion;
90
    }
91
92
    /**
93
     * @param string $host
94
     */
95
    public function setApiHost($host)
96
    {
97
        $this->apiHost = $host;
98
    }
99
100
    /**
101
     * @return string
102
     */
103
    public function getApiHost()
104
    {
105
        return $this->apiHost;
106
    }
107
108
    /**
109
     * @param string $extension
110
     */
111
    public function setApiExtension($extension)
112
    {
113
        $this->apiExtension = $extension;
114
    }
115
116
    /**
117
     * @return string
118
     */
119
    public function getApiExtension()
120
    {
121
        return $this->apiExtension;
122
    }
123
124
    /**
125
     * @param boolean $decodeJsonAsArray
126
     */
127
    public function setDecodeJsonAsArray($decodeJsonAsArray)
128
    {
129
        $this->decodeJsonAsArray = $decodeJsonAsArray;
130
    }
131
132
    /**
133
     * @return boolean
134
     */
135
    public function isDecodeJsonAsArray()
136
    {
137
        return $this->decodeJsonAsArray;
138
    }
139
140
    /**
141
     * @return string|null
142
     */
143
    public function getLastApiPath()
144
    {
145
        return $this->response->getApiPath();
146
    }
147
148
    /**
149
     * @return int
150
     */
151
    public function getLastHttpCode()
152
    {
153
        return $this->response->getHttpCode();
154
    }
155
156
    /**
157
     * @return array
158
     */
159
    public function getLastXHeaders()
160
    {
161
        return $this->response->getXHeaders();
162
    }
163
164
    /**
165
     * @return array|object|null
166
     */
167
    public function getLastBody()
168
    {
169
        return $this->response->getBody();
170
    }
171
172
    /**
173
     * Resets the last response cache.
174
     */
175
    public function resetLastResponse()
176
    {
177
        $this->response = new Response();
178
    }
179
180
    /**
181
     * Resets the attempts number.
182
     */
183
    private function resetAttemptsNumber()
184
    {
185
        $this->attempts = 0;
186
    }
187
188
    /**
189
     * Delays the retries when they're activated.
190
     */
191
    private function sleepIfNeeded()
192
    {
193
        if ($this->maxRetries && $this->attempts) {
194
            sleep($this->retriesDelay);
195
        }
196
    }
197
198
    /**
199
     * Make URLs for user browser navigation.
200
     *
201
     * @param integer $code
202
     *
203
     * @return string
204
     */
205
    private function getError($code)
206
    {
207
        $error = array(
208
            304 => 'Not Modified',
209
            400 => 'Bad Request',
210
            401 => 'Unauthorized',
211
            403 => 'Forbidden',
212
            404 => 'Not Found',
213
            406 => 'Not Acceptable',
214
            410 => 'Gone',
215
            420 => 'Enhance Your Calm',
216
            422 => 'Unprocessable Entity',
217
            429 => 'Too Many Requests',
218
            500 => 'Internal Server Error',
219
            502 => 'Bad Gateway',
220
            503 => 'Service Unavailable',
221
            504 => 'Gateway timeout',
222
        );
223
224
        return array_key_exists($code, $error) ? $error[$code] : 'Unknown error';
225
    }
226
227
    /**
228
     * Make URLs for user browser navigation.
229
     *
230
     * @param string $path
231
     * @param array  $parameters
232
     *
233
     * @return string
234
     */
235
    public function url($path, array $parameters)
236
    {
237
        $this->resetLastResponse();
238
        $this->response->setApiPath($path);
239
        $host = $this->getApiHost();
240
        $query = http_build_query($parameters);
241
        return sprintf('%s/%s?%s', $host, $path, $query);
242
    }
243
244
    /**
245
     * Make /oauth/* requests to the API.
246
     *
247
     * @param string $path
248
     * @param array  $parameters
249
     *
250
     * @return array
251
     * @throws TwitterOAuthException
252
     */
253
    public function oauth($path, array $parameters = [])
254
    {
255
        $response = [];
256
        $this->resetLastResponse();
257
        $this->response->setApiPath($path);
258
        $host = $this->getApiHost();
259
        $url = sprintf('%s/%s', $host, $path);
260
        $result = $this->oAuthRequest($url, 'POST', $parameters);
261
262
        if ($this->getLastHttpCode() != self::STATUS_SUCCES) {
263
            throw new TwitterOAuthException($result);
264
        }
265
266
        parse_str($result, $response);
267
        $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...
268
269
        return $response;
270
    }
271
272
    /**
273
     * Make /oauth2/* requests to the API.
274
     *
275
     * @param string $path
276
     * @param array  $parameters
277
     *
278
     * @return array|object
279
     */
280
    public function oauth2($path, array $parameters = [])
281
    {
282
        $method = 'POST';
283
        $this->resetLastResponse();
284
        $this->response->setApiPath($path);
285
        $host = $this->getApiHost();
286
        $url = sprintf('%s/%s', $host, $path);
287
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
288
        $authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
289
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
290
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
291
        $this->response->setBody($response);
292
        return $response;
293
    }
294
295
    /**
296
     * Make GET requests to the API.
297
     *
298
     * @param string $path
299
     * @param array  $parameters
300
     *
301
     * @return array|object
302
     */
303
    public function get($path, array $parameters = [])
304
    {
305
        $host = $this->getApiHost();
306
307
        return $this->http('GET', $host, $path, $parameters);
308
    }
309
310
    /**
311
     * Make POST requests to the API.
312
     *
313
     * @param string $path
314
     * @param array  $parameters
315
     *
316
     * @return array|object
317
     */
318
    public function post($path, array $parameters = [])
319
    {
320
        $host = $this->getApiHost();
321
322
        return $this->http('POST', $host, $path, $parameters);
323
    }
324
325
    /**
326
     * Make DELETE requests to the API.
327
     *
328
     * @param string $path
329
     * @param array  $parameters
330
     *
331
     * @return array|object
332
     */
333
    public function delete($path, array $parameters = [])
334
    {
335
        $host = $this->getApiHost();
336
337
        return $this->http('DELETE', $host, $path, $parameters);
338
    }
339
340
    /**
341
     * Make PUT requests to the API.
342
     *
343
     * @param string $path
344
     * @param array  $parameters
345
     *
346
     * @return array|object
347
     */
348
    public function put($path, array $parameters = [])
349
    {
350
        $host = $this->getApiHost();
351
352
        return $this->http('PUT', $host, $path, $parameters);
353
    }
354
355
    /**
356
     * Upload media to upload.twitter.com.
357
     *
358
     * @param string $path
359
     * @param array  $parameters
360
     * @param boolean  $chunked
361
     *
362
     * @return array|object
363
     */
364
    public function upload($path, array $parameters = [], $chunked = false)
365
    {
366
        if ($chunked) {
367
            return $this->uploadMediaChunked($path, $parameters);
368
        } else {
369
            return $this->uploadMediaNotChunked($path, $parameters);
370
        }
371
    }
372
373
    /**
374
     * Private method to upload media (not chunked) to upload.twitter.com.
375
     *
376
     * @param string $path
377
     * @param array  $parameters
378
     *
379
     * @return array|object
380
     */
381
    private function uploadMediaNotChunked($path, array $parameters)
382
    {
383
        $file = file_get_contents($parameters['media']);
384
        $base = base64_encode($file);
385
        $parameters['media'] = $base;
386
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
387
    }
388
389
    /**
390
     * Private method to upload media (chunked) to upload.twitter.com.
391
     *
392
     * @param string $path
393
     * @param array  $parameters
394
     *
395
     * @return array|object
396
     */
397
    private function uploadMediaChunked($path, array $parameters)
398
    {
399
        $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
400
        // Append
401
        $segmentIndex = 0;
402
        $media = fopen($parameters['media'], 'rb');
403
        while (!feof($media)) {
404
            $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
405
                'command' => 'APPEND',
406
                'media_id' => $init->media_id_string,
407
                'segment_index' => $segmentIndex++,
408
                'media_data' => base64_encode(fread($media, $this->chunkSize))
409
            ]);
410
        }
411
        fclose($media);
412
        // Finalize
413
        $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
414
            'command' => 'FINALIZE',
415
            'media_id' => $init->media_id_string
416
        ]);
417
        return $finalize;
418
    }
419
420
    /**
421
     * Private method to get params for upload media chunked init.
422
     * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
423
     *
424
     * @param array  $parameters
425
     *
426
     * @return array
427
     */
428
    private function mediaInitParameters(array $parameters)
429
    {
430
        $return = [
431
            'command' => 'INIT',
432
            'media_type' => $parameters['media_type'],
433
            'total_bytes' => filesize($parameters['media'])
434
        ];
435
        if (isset($parameters['additional_owners'])) {
436
            $return['additional_owners'] = $parameters['additional_owners'];
437
        }
438
        if (isset($parameters['media_category'])) {
439
            $return['media_category'] = $parameters['media_category'];
440
        }
441
        return $return;
442
    }
443
444
    /**
445
     * @param string $method
446
     * @param string $host
447
     * @param string $path
448
     * @param array  $parameters
449
     *
450
     * @return array|object
451
     */
452
    private function http($method, $host, $path, array $parameters)
453
    {
454
        $this->resetLastResponse();
455
        $this->resetAttemptsNumber();
456
        $version = $this->getApiVersion();
457
        $extension = $this->getApiExtension();
458
        $pathWithExtension = $path;
459
        if ($extension !== null) {
460
            $pathWithExtension .= '.'.$extension;
461
        }
462
        $url = sprintf('%s/%s/%s', $host, $version, $pathWithExtension);
463
        $this->response->setApiPath($path);
464
        return $this->makeRequests($url, $method, $parameters);
465
    }
466
467
    /**
468
     *
469
     * Make requests and retry them (if enabled) in case of Twitter's problems.
470
     *
471
     * @param string $method
472
     * @param string $url
473
     * @param string $method
474
     * @param array  $parameters
475
     *
476
     * @return array|object
477
     * @throws TwitterOAuthException
478
     */
479
    private function makeRequests($url, $method, array $parameters)
480
    {
481
        do {
482
            $this->sleepIfNeeded();
483
            $result = $this->oAuthRequest($url, $method, $parameters);
484
            $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
485
            $this->response->setBody($response);
486
            $this->attempts++;
487
            // Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
488
        } while ($this->requestsAvailable());
489
490
        $httpCode = $this->getLastHttpCode();
491
492
        if ($httpCode !== self::STATUS_SUCCES) {
493
            $error = $this->getError($httpCode);
494
495
            throw new TwitterOAuthException($error);
496
        }
497
498
        return $response;
499
    }
500
501
    /**
502
     * Checks if we have to retry request if API is down.
503
     *
504
     * @return bool
505
     */
506
    private function requestsAvailable()
507
    {
508
        return ($this->maxRetries && ($this->attempts <= $this->maxRetries) && $this->getLastHttpCode() >= 500);
509
    }
510
511
    /**
512
     * Format and sign an OAuth / API request
513
     *
514
     * @param string $url
515
     * @param string $method
516
     * @param array  $parameters
517
     *
518
     * @return string
519
     * @throws TwitterOAuthException
520
     */
521
    private function oAuthRequest($url, $method, array $parameters)
522
    {
523
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
524
        if (array_key_exists('oauth_callback', $parameters)) {
525
            // Twitter doesn't like oauth_callback as a parameter.
526
            unset($parameters['oauth_callback']);
527
        }
528
        if ($this->bearer === null) {
529
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
530
            $authorization = $request->toHeader();
531
            if (array_key_exists('oauth_verifier', $parameters)) {
532
                // Twitter doesn't always work with oauth in the body and in the header
533
                // and it's already included in the $authorization header
534
                unset($parameters['oauth_verifier']);
535
            }
536
        } else {
537
            $authorization = 'Authorization: Bearer ' . $this->bearer;
538
        }
539
        return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
540
    }
541
542
    /**
543
     * Set Curl options.
544
     *
545
     * @return array
546
     */
547
    private function curlOptions()
548
    {
549
        $options = [
550
            // CURLOPT_VERBOSE => true,
551
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
552
            CURLOPT_HEADER => true,
553
            CURLOPT_RETURNTRANSFER => true,
554
            CURLOPT_SSL_VERIFYHOST => 2,
555
            CURLOPT_SSL_VERIFYPEER => true,
556
            CURLOPT_TIMEOUT => $this->timeout,
557
            CURLOPT_USERAGENT => $this->userAgent,
558
        ];
559
560
        if ($this->useCAFile()) {
561
            $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
562
        }
563
564
        if ($this->gzipEncoding) {
565
            $options[CURLOPT_ENCODING] = 'gzip';
566
        }
567
568
        if (!empty($this->proxy)) {
569
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
570
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
571
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
572
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
573
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
574
        }
575
576
        return $options;
577
    }
578
579
    /**
580
     * Make an HTTP request
581
     *
582
     * @param string $url
583
     * @param string $method
584
     * @param string $authorization
585
     * @param array $postfields
586
     *
587
     * @return string
588
     * @throws TwitterOAuthException
589
     */
590
    private function request($url, $method, $authorization, array $postfields)
591
    {
592
        $options = $this->curlOptions();
593
        $options[CURLOPT_URL] = $url;
594
        $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
595
596
        switch ($method) {
597
            case 'GET':
598
                break;
599
            case 'POST':
600
                $options[CURLOPT_POST] = true;
601
                $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
602
                break;
603
            case 'DELETE':
604
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
605
                break;
606
            case 'PUT':
607
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
608
                break;
609
        }
610
611
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
612
            $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
613
        }
614
615
616
        $curlHandle = curl_init();
617
        curl_setopt_array($curlHandle, $options);
618
        $response = curl_exec($curlHandle);
619
620
        // Throw exceptions on cURL errors.
621
        if (curl_errno($curlHandle) > 0) {
622
            throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
623
        }
624
625
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
626
        $parts = explode("\r\n\r\n", $response);
627
        $responseBody = array_pop($parts);
628
        $responseHeader = array_pop($parts);
629
        $this->response->setHeaders($this->parseHeaders($responseHeader));
630
631
        curl_close($curlHandle);
632
633
        return $responseBody;
634
    }
635
636
    /**
637
     * Get the header info to store.
638
     *
639
     * @param string $header
640
     *
641
     * @return array
642
     */
643
    private function parseHeaders($header)
644
    {
645
        $headers = [];
646
        foreach (explode("\r\n", $header) as $line) {
647
            if (strpos($line, ':') !== false) {
648
                list ($key, $value) = explode(': ', $line);
649
                $key = str_replace('-', '_', strtolower($key));
650
                $headers[$key] = trim($value);
651
            }
652
        }
653
        return $headers;
654
    }
655
656
    /**
657
     * Encode application authorization header with base64.
658
     *
659
     * @param Consumer $consumer
660
     *
661
     * @return string
662
     */
663
    private function encodeAppAuthorization(Consumer $consumer)
664
    {
665
        $key = rawurlencode($consumer->key);
666
        $secret = rawurlencode($consumer->secret);
667
        return base64_encode($key . ':' . $secret);
668
    }
669
670
    /**
671
     * Is the code running from a Phar module.
672
     *
673
     * @return boolean
674
     */
675
    private function pharRunning()
676
    {
677
        return class_exists('Phar') && \Phar::running(false) !== '';
678
    }
679
680
    /**
681
     * Use included CA file instead of OS provided list.
682
     *
683
     * @return boolean
684
     */
685
    private function useCAFile()
686
    {
687
        /* Use CACert file when not in a PHAR file. */
688
        return !$this->pharRunning();
689
    }
690
}
691