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
Push — master ( d707ce...1b9dd4 )
by Abraham
12s
created

TwitterOAuth::makeRequests()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 1
nop 3
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->token = new Token($oauthToken, $oauthTokenSecret);
50
        }
51
        if (empty($oauthToken) && !empty($oauthTokenSecret)) {
52
            $this->bearer = $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
    }
64
65
    /**
66
     * @return string|null
67
     */
68
    public function getLastApiPath()
69
    {
70
        return $this->response->getApiPath();
71
    }
72
73
    /**
74
     * @return int
75
     */
76
    public function getLastHttpCode()
77
    {
78
        return $this->response->getHttpCode();
79
    }
80
81
    /**
82
     * @return array
83
     */
84
    public function getLastXHeaders()
85
    {
86
        return $this->response->getXHeaders();
87
    }
88
89
    /**
90
     * @return array|object|null
91
     */
92
    public function getLastBody()
93
    {
94
        return $this->response->getBody();
95
    }
96
97
    /**
98
     * Resets the last response cache.
99
     */
100
    public function resetLastResponse()
101
    {
102
        $this->response = new Response();
103
    }
104
105
    /**
106
     * Resets the attempts number.
107
     */
108
    private function resetAttemptsNumber()
109
    {
110
        $this->attempts = 0;
111
    }
112
113
    /**
114
     * Delays the retries when they're activated.
115
     */
116
    private function sleepIfNeeded()
117
    {
118
        if ($this->maxRetries && $this->attempts) {
119
            sleep($this->retriesDelay);
120
        }
121
    }
122
123
124
    /**
125
     * Make URLs for user browser navigation.
126
     *
127
     * @param string $path
128
     * @param array  $parameters
129
     *
130
     * @return string
131
     */
132
    public function url($path, array $parameters)
133
    {
134
        $this->resetLastResponse();
135
        $this->response->setApiPath($path);
136
        $query = http_build_query($parameters);
137
        return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
138
    }
139
140
    /**
141
     * Make /oauth/* requests to the API.
142
     *
143
     * @param string $path
144
     * @param array  $parameters
145
     *
146
     * @return array
147
     * @throws TwitterOAuthException
148
     */
149
    public function oauth($path, array $parameters = [])
150
    {
151
        $response = [];
152
        $this->resetLastResponse();
153
        $this->response->setApiPath($path);
154
        $url = sprintf('%s/%s', self::API_HOST, $path);
155
        $result = $this->oAuthRequest($url, 'POST', $parameters);
156
157
        if ($this->getLastHttpCode() != 200) {
158
            throw new TwitterOAuthException($result);
159
        }
160
161
        parse_str($result, $response);
162
        $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...
163
164
        return $response;
165
    }
166
167
    /**
168
     * Make /oauth2/* requests to the API.
169
     *
170
     * @param string $path
171
     * @param array  $parameters
172
     *
173
     * @return array|object
174
     */
175
    public function oauth2($path, array $parameters = [])
176
    {
177
        $method = 'POST';
178
        $this->resetLastResponse();
179
        $this->response->setApiPath($path);
180
        $url = sprintf('%s/%s', self::API_HOST, $path);
181
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
182
        $authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
183
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
184
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
185
        $this->response->setBody($response);
186
        return $response;
187
    }
188
189
    /**
190
     * Make GET requests to the API.
191
     *
192
     * @param string $path
193
     * @param array  $parameters
194
     *
195
     * @return array|object
196
     */
197
    public function get($path, array $parameters = [])
198
    {
199
        return $this->http('GET', self::API_HOST, $path, $parameters);
200
    }
201
202
    /**
203
     * Make POST requests to the API.
204
     *
205
     * @param string $path
206
     * @param array  $parameters
207
     *
208
     * @return array|object
209
     */
210
    public function post($path, array $parameters = [])
211
    {
212
        return $this->http('POST', self::API_HOST, $path, $parameters);
213
    }
214
215
    /**
216
     * Make DELETE requests to the API.
217
     *
218
     * @param string $path
219
     * @param array  $parameters
220
     *
221
     * @return array|object
222
     */
223
    public function delete($path, array $parameters = [])
224
    {
225
        return $this->http('DELETE', self::API_HOST, $path, $parameters);
226
    }
227
228
    /**
229
     * Make PUT requests to the API.
230
     *
231
     * @param string $path
232
     * @param array  $parameters
233
     *
234
     * @return array|object
235
     */
236
    public function put($path, array $parameters = [])
237
    {
238
        return $this->http('PUT', self::API_HOST, $path, $parameters);
239
    }
240
241
    /**
242
     * Upload media to upload.twitter.com.
243
     *
244
     * @param string $path
245
     * @param array  $parameters
246
     * @param boolean  $chunked
247
     *
248
     * @return array|object
249
     */
250
    public function upload($path, array $parameters = [], $chunked = false)
251
    {
252
        if ($chunked) {
253
            return $this->uploadMediaChunked($path, $parameters);
254
        } else {
255
            return $this->uploadMediaNotChunked($path, $parameters);
256
        }
257
    }
258
259
    /**
260
     * Private method to upload media (not chunked) to upload.twitter.com.
261
     *
262
     * @param string $path
263
     * @param array  $parameters
264
     *
265
     * @return array|object
266
     */
267
    private function uploadMediaNotChunked($path, array $parameters)
268
    {
269
        $file = file_get_contents($parameters['media']);
270
        $base = base64_encode($file);
271
        $parameters['media'] = $base;
272
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
273
    }
274
275
    /**
276
     * Private method to upload media (chunked) to upload.twitter.com.
277
     *
278
     * @param string $path
279
     * @param array  $parameters
280
     *
281
     * @return array|object
282
     */
283
    private function uploadMediaChunked($path, array $parameters)
284
    {
285
        $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
286
        // Append
287
        $segmentIndex = 0;
288
        $media = fopen($parameters['media'], 'rb');
289
        while (!feof($media)) {
290
            $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
291
                'command' => 'APPEND',
292
                'media_id' => $init->media_id_string,
293
                'segment_index' => $segmentIndex++,
294
                'media_data' => base64_encode(fread($media, $this->chunkSize))
295
            ]);
296
        }
297
        fclose($media);
298
        // Finalize
299
        $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
300
            'command' => 'FINALIZE',
301
            'media_id' => $init->media_id_string
302
        ]);
303
        return $finalize;
304
    }
305
306
    /**
307
     * Private method to get params for upload media chunked init.
308
     * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
309
     *
310
     * @param array  $parameters
311
     *
312
     * @return array
313
     */
314
    private function mediaInitParameters(array $parameters)
315
    {
316
        $return = [
317
            'command' => 'INIT',
318
            'media_type' => $parameters['media_type'],
319
            'total_bytes' => filesize($parameters['media'])
320
        ];
321
        if (isset($parameters['additional_owners'])) {
322
            $return['additional_owners'] = $parameters['additional_owners'];
323
        }
324
        if (isset($parameters['media_category'])) {
325
            $return['media_category'] = $parameters['media_category'];
326
        }
327
        return $return;
328
    }
329
330
    /**
331
     * @param string $method
332
     * @param string $host
333
     * @param string $path
334
     * @param array  $parameters
335
     *
336
     * @return array|object
337
     */
338
    private function http($method, $host, $path, array $parameters)
339
    {
340
        $this->resetLastResponse();
341
        $this->resetAttemptsNumber();
342
        $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
343
        $this->response->setApiPath($path);
344
        return $this->makeRequests($url, $method, $parameters);
345
    }
346
347
    /**
348
     *
349
     * Make requests and retry them (if enabled) in case of Twitter's problems.
350
     *
351
     * @param string $method
352
     * @param string $url
353
     * @param string $method
354
     * @param array  $parameters
355
     *
356
     * @return array|object
357
     */
358
    private function makeRequests($url, $method, array $parameters)
359
    {
360
        do {
361
            $this->sleepIfNeeded();
362
            $result = $this->oAuthRequest($url, $method, $parameters);
363
            $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
364
            $this->response->setBody($response);
365
            $this->attempts++;
366
            // Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
367
        } while ($this->requestsAvailable());
368
369
        return $response;
370
    }
371
372
    /**
373
     * Checks if we have to retry request if API is down.
374
     *
375
     * @return bool
376
     */
377
    private function requestsAvailable()
378
    {
379
        return ($this->maxRetries && ($this->attempts <= $this->maxRetries) && $this->getLastHttpCode() < 500);
380
    }
381
382
    /**
383
     * Format and sign an OAuth / API request
384
     *
385
     * @param string $url
386
     * @param string $method
387
     * @param array  $parameters
388
     *
389
     * @return string
390
     * @throws TwitterOAuthException
391
     */
392
    private function oAuthRequest($url, $method, array $parameters)
393
    {
394
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
395
        if (array_key_exists('oauth_callback', $parameters)) {
396
            // Twitter doesn't like oauth_callback as a parameter.
397
            unset($parameters['oauth_callback']);
398
        }
399
        if ($this->bearer === null) {
400
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
401
            $authorization = $request->toHeader();
402
            if (array_key_exists('oauth_verifier', $parameters)) {
403
                // Twitter doesn't always work with oauth in the body and in the header
404
                // and it's already included in the $authorization header
405
                unset($parameters['oauth_verifier']);
406
            }
407
        } else {
408
            $authorization = 'Authorization: Bearer ' . $this->bearer;
409
        }
410
        return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
411
    }
412
413
    /**
414
     * Set Curl options.
415
     *
416
     * @return array
417
     */
418
    private function curlOptions()
419
    {
420
        $options = [
421
            // CURLOPT_VERBOSE => true,
422
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
423
            CURLOPT_HEADER => true,
424
            CURLOPT_RETURNTRANSFER => true,
425
            CURLOPT_SSL_VERIFYHOST => 2,
426
            CURLOPT_SSL_VERIFYPEER => true,
427
            CURLOPT_TIMEOUT => $this->timeout,
428
            CURLOPT_USERAGENT => $this->userAgent,
429
        ];
430
431
        if ($this->useCAFile()) {
432
            $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
433
        }
434
435
        if ($this->gzipEncoding) {
436
            $options[CURLOPT_ENCODING] = 'gzip';
437
        }
438
439
        if (!empty($this->proxy)) {
440
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
441
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
442
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
443
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
444
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
445
        }
446
447
        return $options;
448
    }
449
450
    /**
451
     * Make an HTTP request
452
     *
453
     * @param string $url
454
     * @param string $method
455
     * @param string $authorization
456
     * @param array $postfields
457
     *
458
     * @return string
459
     * @throws TwitterOAuthException
460
     */
461
    private function request($url, $method, $authorization, array $postfields)
462
    {
463
        $options = $this->curlOptions();
464
        $options[CURLOPT_URL] = $url;
465
        $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
466
467
        switch ($method) {
468
            case 'GET':
469
                break;
470
            case 'POST':
471
                $options[CURLOPT_POST] = true;
472
                $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
473
                break;
474
            case 'DELETE':
475
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
476
                break;
477
            case 'PUT':
478
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
479
                break;
480
        }
481
482
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
483
            $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
484
        }
485
486
487
        $curlHandle = curl_init();
488
        curl_setopt_array($curlHandle, $options);
489
        $response = curl_exec($curlHandle);
490
491
        // Throw exceptions on cURL errors.
492
        if (curl_errno($curlHandle) > 0) {
493
            throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
494
        }
495
496
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
497
        $parts = explode("\r\n\r\n", $response);
498
        $responseBody = array_pop($parts);
499
        $responseHeader = array_pop($parts);
500
        $this->response->setHeaders($this->parseHeaders($responseHeader));
501
502
        curl_close($curlHandle);
503
504
        return $responseBody;
505
    }
506
507
    /**
508
     * Get the header info to store.
509
     *
510
     * @param string $header
511
     *
512
     * @return array
513
     */
514
    private function parseHeaders($header)
515
    {
516
        $headers = [];
517
        foreach (explode("\r\n", $header) as $line) {
518
            if (strpos($line, ':') !== false) {
519
                list ($key, $value) = explode(': ', $line);
520
                $key = str_replace('-', '_', strtolower($key));
521
                $headers[$key] = trim($value);
522
            }
523
        }
524
        return $headers;
525
    }
526
527
    /**
528
     * Encode application authorization header with base64.
529
     *
530
     * @param Consumer $consumer
531
     *
532
     * @return string
533
     */
534
    private function encodeAppAuthorization(Consumer $consumer)
535
    {
536
        $key = rawurlencode($consumer->key);
537
        $secret = rawurlencode($consumer->secret);
538
        return base64_encode($key . ':' . $secret);
539
    }
540
541
    /**
542
     * Is the code running from a Phar module.
543
     *
544
     * @return boolean
545
     */
546
    private function pharRunning()
547
    {
548
        return class_exists('Phar') && \Phar::running(false) !== '';
549
    }
550
551
    /**
552
     * Use included CA file instead of OS provided list.
553
     *
554
     * @return boolean
555
     */
556
    private function useCAFile()
557
    {
558
        /* Use CACert file when not in a PHAR file. */
559
        return !$this->pharRunning();
560
    }
561
}
562