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

TwitterOAuth::mediaStatus()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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