Completed
Branch new-instance (cdb4e5)
by Hector
02:51
created

TwitterAds::getMethod()   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
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * A Twitter supported and maintained Ads API SDK for PHP.
4
 *
5
 * @license MIT
6
 */
7
namespace Hborras\TwitterAdsSDK;
8
9
use Exception;
10
use Hborras\TwitterAdsSDK\TwitterAds\Account;
11
use Hborras\TwitterAdsSDK;
12
use Hborras\TwitterAdsSDK\TwitterAds\Cursor;
13
use Hborras\TwitterAdsSDK\TwitterAds\Errors\BadRequest;
14
use Hborras\TwitterAdsSDK\TwitterAds\Errors\Forbidden;
15
use Hborras\TwitterAdsSDK\TwitterAds\Errors\NotAuthorized;
16
use Hborras\TwitterAdsSDK\TwitterAds\Errors\NotFound;
17
use Hborras\TwitterAdsSDK\TwitterAds\Errors\RateLimit;
18
use Hborras\TwitterAdsSDK\TwitterAds\Errors\ServerError;
19
use Hborras\TwitterAdsSDK\TwitterAds\Errors\ServiceUnavailable;
20
use Hborras\TwitterAdsSDK\Util\JsonDecoder;
21
use GuzzleHttp;
22
23
/**
24
 * TwitterAds class for interacting with the Twitter API.
25
 *
26
 * @author Hector Borras <[email protected]>
27
 */
28
class TwitterAds extends Config
29
{
30
    const API_VERSION      = '1';
31
    const API_REST_VERSION = '1.1';
32
    const API_HOST         = 'https://ads-api.twitter.com';
33
    const API_HOST_SANDBOX = 'https://ads-api-sandbox.twitter.com';
34
    const API_HOST_OAUTH   = 'https://api.twitter.com';
35
    const UPLOAD_HOST      = 'https://upload.twitter.com';
36
    const UPLOAD_PATH      = 'media/upload.json';
37
    const UPLOAD_CHUNK     = 40960; // 1024 * 40
38
39
    /** @var TwitterAds */
40
    protected static $instance;
41
    /** @var  string Method used for the request */
42
    private $method;
43
    /** @var  string Resource used for the request */
44
    private $resource;
45
    /** @var Response details about the result of the last request */
46
    private $response;
47
    /** @var string|null Application bearer token */
48
    private $bearer;
49
    /** @var Consumer Twitter application details */
50
    private $consumer;
51
    /** @var Token|null User access token details */
52
    private $token;
53
    /** @var HmacSha1 OAuth 1 signature type used by Twitter */
54
    private $signatureMethod;
55
    /** @var  bool Sandbox allows to make requests thought sandbox environment */
56
    private $sandbox;
57
    /** @var string */
58
    private $accountId;
59
60
    /**
61
     * @return TwitterAds|null
62
     */
63
    public static function instance() {
64
        return static::$instance;
65
    }
66
67
    /**
68
     * @param TwitterAds $instance
69
     */
70
    public static function setInstance(TwitterAds $instance) {
71
        static::$instance = $instance;
72
    }
73
74
    /**
75
     * Constructor.
76
     *
77
     * @param string $consumerKey The Application Consumer Key
78
     * @param string $consumerSecret The Application Consumer Secret
79
     * @param string|null $oauthToken The Client Token
80
     * @param string|null $oauthTokenSecret The Client Token Secret
81
     * @param string $accountId
82
     * @param bool $sandbox The Sandbox environment (optional)
83
     */
84
    public function __construct($consumerKey, $consumerSecret, $oauthToken, $oauthTokenSecret, $accountId = '', $sandbox = false)
85
    {
86
        $this->resetLastResponse();
87
        $this->signatureMethod = new HmacSha1();
88
        $this->consumer = new Consumer($consumerKey, $consumerSecret);
89
        if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
90
            $this->token = new Token($oauthToken, $oauthTokenSecret);
91
        }
92
        if (empty($oauthToken) && !empty($oauthTokenSecret)) {
93
            $this->bearer = $oauthTokenSecret;
94
        }
95
        $this->sandbox = $sandbox;
96
        $this->accountId = $accountId;
97
    }
98
99
    /**
100
     * @param $consumerKey
101
     * @param $consumerSecret
102
     * @param $oauthToken
103
     * @param $oauthTokenSecret
104
     * @param string $accountId
105
     * @param bool $sandbox
106
     * @return static
107
     */
108
    public static function init($consumerKey, $consumerSecret, $oauthToken, $oauthTokenSecret, $accountId = '', $sandbox = false) {
109
        $api = new static($consumerKey, $consumerSecret, $oauthToken, $oauthTokenSecret, $accountId, $sandbox);
110
        static::setInstance($api);
111
112
        return $api;
113
    }
114
115
    /**
116
     * @return Account|Cursor
117
     */
118
    public function getAccounts()
119
    {
120
        return (new Account($this))->all();
121
    }
122
123
    /**
124
     * @param string $oauthToken
125
     * @param string $oauthTokenSecret
126
     */
127
    public function setOauthToken($oauthToken, $oauthTokenSecret)
128
    {
129
        $this->token = new Token($oauthToken, $oauthTokenSecret);
130
    }
131
132
    /**
133
     * @return string|null
134
     */
135
    public function getLastApiPath()
136
    {
137
        return $this->response->getApiPath();
138
    }
139
140
    /**
141
     * @return int
142
     */
143
    public function getLastHttpCode()
144
    {
145
        return $this->response->getHttpCode();
146
    }
147
148
    /**
149
     * @return array
150
     */
151
    public function getLastXHeaders()
152
    {
153
        return $this->response->getXHeaders();
154
    }
155
156
    /**
157
     * @return array|object|null
158
     */
159
    public function getLastBody()
160
    {
161
        return $this->response->getBody();
162
    }
163
164
    /**
165
     * Resets the last response cache.
166
     */
167
    public function resetLastResponse()
168
    {
169
        $this->response = new Response();
170
    }
171
172
    /**
173
     * Make URLs for user browser navigation.
174
     *
175
     * @param string $path
176
     * @param array $parameters
177
     *
178
     * @return string
179
     */
180
    public function url($path, array $parameters)
181
    {
182
        $this->resetLastResponse();
183
        $this->response->setApiPath($path);
184
        $query = http_build_query($parameters);
185
186
        return sprintf('%s/%s?%s', self::API_HOST_OAUTH, $path, $query);
187
    }
188
189
    /**
190
     * Make /oauth/* requests to the API.
191
     *
192
     * @param string $path
193
     * @param array $parameters
194
     * @return array
195
     * @throws Exception
196
     */
197
    public function oauth($path, array $parameters = [])
198
    {
199
        $response = [];
200
        $this->resetLastResponse();
201
        $this->response->setApiPath($path);
202
        $url = sprintf('%s/%s', self::API_HOST_OAUTH, $path);
203
        $result = $this->oAuthRequest($url, 'POST', $parameters);
204
205
        if ($this->getLastHttpCode() != 200) {
206
            throw new TwitterAdsException($result, 500, null, $result);
207
        }
208
209
        parse_str($result, $response);
210
        $this->response->setBody($response);
211
212
        return $response;
213
    }
214
215
    /**
216
     * Make /oauth2/* requests to the API.
217
     *
218
     * @param string $path
219
     * @param array $parameters
220
     *
221
     * @return array|object
222
     */
223
    public function oauth2($path, array $parameters = [])
224
    {
225
        $method = 'POST';
226
        $this->resetLastResponse();
227
        $this->response->setApiPath($path);
228
        $url = sprintf('%s/%s', self::API_HOST_OAUTH, $path);
229
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
230
        $authorization = 'Authorization: Basic '.$this->encodeAppAuthorization($this->consumer);
231
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
232
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
233
        $this->response->setBody($response);
234
235
        return $response;
236
    }
237
238
    public function verifyCredentials($parameters = [])
239
    {
240
        return $this->http('GET', self::API_HOST_OAUTH, 'account/verify_credentials', $parameters);
241
    }
242
243
    /**
244
     * Make GET requests to the API.
245
     *
246
     * @param string $path
247
     * @param array $parameters
248
     *
249
     * @return Response
250
     */
251
    public function get($path, array $parameters = [])
252
    {
253
        return $this->http('GET', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters);
254
    }
255
256
    /**
257
     * Make POST requests to the API.
258
     *
259
     * @param string $path
260
     * @param array $parameters
261
     *
262
     * @param array $headers
263
     * @return Response
264
     */
265
    public function post($path, array $parameters = [], array $headers = [])
266
    {
267
        return $this->http('POST', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters, $headers);
268
    }
269
270
    /**
271
     * Make DELETE requests to the API.
272
     *
273
     * @param string $path
274
     * @param array $parameters
275
     *
276
     * @return Response
277
     */
278
    public function delete($path, array $parameters = [])
279
    {
280
        return $this->http('DELETE', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters);
281
    }
282
283
    /**
284
     * Make PUT requests to the API.
285
     *
286
     * @param string $path
287
     * @param array $parameters
288
     *
289
     * @return Response
290
     */
291
    public function put($path, array $parameters = [], array $headers = [])
292
    {
293
        return $this->http('PUT', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters, $headers);
294
    }
295
296
    /**
297
     * Upload media to upload.twitter.com.
298
     *
299
     * @param array $parameters
300
     * @param bool $chunked
301
     *
302
     * @return array|object
303
     */
304
    public function upload(array $parameters = [], $chunked = false)
305
    {
306
        if ($chunked) {
307
            return $this->uploadMediaChunked(self::UPLOAD_PATH, $parameters);
308
        } else {
309
            return $this->uploadMediaNotChunked(self::UPLOAD_PATH, $parameters)->getBody();
310
        }
311
    }
312
313
    /**
314
     * Private method to upload media (not chunked) to upload.twitter.com.
315
     *
316
     * @param string $path
317
     * @param array $parameters
318
     *
319
     * @return array|object
320
     */
321
    private function uploadMediaNotChunked($path, $parameters)
322
    {
323
        $file = file_get_contents($parameters['media']);
324
        $base = base64_encode($file);
325
        $parameters['media'] = $base;
326
327
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
328
    }
329
330
    /**
331
     * Private method to upload media (chunked) to upload.twitter.com.
332
     *
333
     * @param string $path
334
     * @param array $parameters
335
     *
336
     * @return array|object
337
     */
338
    private function uploadMediaChunked($path, $parameters)
339
    {
340
        if ($parameters['media_type'] == 'video/mp4') {
341
            $parameters['media_category'] = "amplify_video";
342
        } elseif($parameters['media_type'] == 'image/gif'){
343
            $parameters['media_category'] = 'tweet_gif';
344
        }
345
346
        // Init
347
        $init = $this->http(
348
            'POST',
349
            self::UPLOAD_HOST,
350
            $path,
351
            [
352
                'command' => 'INIT',
353
                'media_type' => $parameters['media_type'],
354
                'media_category' => $parameters['media_category'],
355
                'total_bytes' => filesize($parameters['media'])
356
            ]
357
        )->getBody();
358
359
        // Append
360
        $segment_index = 0;
361
        $media = fopen($parameters['media'], 'rb');
362
        while (!feof($media)) {
363
            $this->http(
0 ignored issues
show
Unused Code introduced by
The call to the method Hborras\TwitterAdsSDK\Response::getBody() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
364
                'POST',
365
                self::UPLOAD_HOST,
366
                $path,
367
                [
368
                    'command' => 'APPEND',
369
                    'media_id' => $init->media_id_string,
370
                    'segment_index' => $segment_index++,
371
                    'media_data' => base64_encode(fread($media, self::UPLOAD_CHUNK)),
372
                ]
373
            )->getBody();
374
        }
375
        fclose($media);
376
        // Finalize
377
        $finalize = $this->http(
378
            'POST',
379
            self::UPLOAD_HOST,
380
            $path,
381
            [
382
                'command' => 'FINALIZE',
383
                'media_id' => $init->media_id_string,
384
            ]
385
        )->getBody();
386
387
        while (isset($finalize->processing_info)) {
388
            if (isset($finalize->processing_info->check_after_secs)) {
389
                sleep($finalize->processing_info->check_after_secs);
390
                $data = array(
391
                    'command' => 'STATUS',
392
                    'media_id' => $finalize->media_id
393
                );
394
                $finalize = $this->http('GET', self::UPLOAD_HOST, $path, $data)->getBody();
395
            } else {
396
                if (isset($finalize->processing_info->state) && $finalize->processing_info->state == 'succeeded') {
397
                    break;
398
                }
399
            }
400
        }
401
402
        return $finalize;
403
    }
404
405
    /**
406
     * @param string $method
407
     * @param string $host
408
     * @param string $path
409
     * @param array $parameters
410
     * @param array $headers
411
     * @return Response
412
     * @throws BadRequest
413
     * @throws Forbidden
414
     * @throws NotAuthorized
415
     * @throws NotFound
416
     * @throws RateLimit
417
     * @throws ServerError
418
     * @throws ServiceUnavailable
419
     */
420
    private function http($method, $host, $path, array $parameters, $headers = [])
421
    {
422
        $this->method = $method;
423
        $this->resource = $path;
424
        $this->resetLastResponse();
425
        if(strpos($path, TONUpload::DEFAULT_DOMAIN) === 0) {
426
            $url = $path;
427
        } else {
428
            if($host == self::UPLOAD_HOST){
429
                $url = sprintf('%s/%s/%s', $host, self::API_REST_VERSION, $path);
430
            } else {
431
                $url = sprintf('%s/%s/%s', $host, self::API_VERSION, $path);
432
            }
433
        }
434
435
        $this->response->setApiPath($path);
436
        $result = $this->oAuthRequest($url, $method, $parameters, $headers);
437
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
438
        $this->response->setBody($response);
439
        if ($this->getLastHttpCode() > 399) {
440
            $this->manageErrors($response);
441
        }
442
        return $this->response;
443
    }
444
445
    /**
446
     * @param $response
447
     * @throws BadRequest
448
     * @throws Forbidden
449
     * @throws NotAuthorized
450
     * @throws NotFound
451
     * @throws RateLimit
452
     * @throws ServerError
453
     * @throws ServiceUnavailable
454
     */
455
    public function manageErrors($response){
456
        switch ($this->getLastHttpCode()) {
457
            case 400:
458
                throw new BadRequest(TwitterAdsException::BAD_REQUEST, 400, null, $response->errors);
459
            case 401:
460
                throw new NotAuthorized(TwitterAdsException::NOT_AUTHORIZED, 401, null, $response->errors);
461
            case 403:
462
                throw new Forbidden(TwitterAdsException::FORBIDDEN, 403, null, $response->errors);
463
            case 404:
464
                throw new NotFound(TwitterAdsException::NOT_FOUND, 404, null, $response->errors);
465
            case 429:
466
                throw new RateLimit(TwitterAdsException::RATE_LIMIT, 429, null, $response->errors, $this->response->getsHeaders());
467
            case 500:
468
                throw new ServerError(TwitterAdsException::SERVER_ERROR, 500, null, $response->errors);
469
            case 503:
470
                throw new ServiceUnavailable(TwitterAdsException::SERVICE_UNAVAILABLE, 503, null, $response->errors, $this->response->getsHeaders());
471
            default:
472
                throw new ServerError(TwitterAdsException::SERVER_ERROR, 500, null, $response->errors);
473
        }
474
    }
475
476
    /**
477
     * Format and sign an OAuth / API request.
478
     *
479
     * @param string $url
480
     * @param string $method
481
     * @param array $parameters
482
     *
483
     * @param array $headers
484
     * @return string
485
     * @throws TwitterAdsException
486
     * @throws TwitterOAuthException
487
     */
488
    private function oAuthRequest($url, $method, array $parameters, $headers = [])
489
    {
490
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
491
        if (array_key_exists('oauth_callback', $parameters)) {
492
            // Twitter doesn't like oauth_callback as a parameter.
493
            unset($parameters['oauth_callback']);
494
        }
495
        if ($this->bearer === null) {
496
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
497
            $authorization = $request->toHeader();
498
        } else {
499
            $authorization = 'Authorization: Bearer '.$this->bearer;
500
        }
501
        if(strpos($url, TONUpload::DEFAULT_DOMAIN) === 0) {
502
            return $this->request($url, $method, $authorization, $parameters, $headers);
503
        } else {
504
            return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters, $headers);
505
        }
506
507
    }
508
509
    /**
510
     * Make an HTTP request.
511
     *
512
     * @param string $url
513
     * @param string $method
514
     * @param string $authorization
515
     * @param array $postfields
516
     *
517
     * @param array $headers
518
     * @return string
519
     * @throws TwitterAdsException
520
     */
521
    private function request($url, $method, $authorization, $postfields, $headers = [])
522
    {
523
        /* Curl settings */
524
        $options = [
525
            CURLOPT_VERBOSE => false,
526
            CURLOPT_CAINFO => __DIR__.DIRECTORY_SEPARATOR.'cacert.pem',
527
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
528
            CURLOPT_HEADER => true,
529
            CURLOPT_HTTPHEADER => array_merge(['Accept: */*', $authorization, 'Expect:'],$headers,['Connection: close']),
530
            CURLOPT_RETURNTRANSFER => true,
531
            CURLOPT_SSL_VERIFYHOST => 2,
532
            CURLOPT_SSL_VERIFYPEER => true,
533
            CURLOPT_TIMEOUT => $this->timeout,
534
            CURLOPT_URL => $url,
535
            CURLOPT_USERAGENT => $this->userAgent,
536
            CURLOPT_ENCODING => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
537
        ];
538
539
        if (!empty($this->proxy)) {
540
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
541
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
542
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
543
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
544
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
545
        }
546
547
        switch ($method) {
548
            case 'GET':
549
                break;
550
            case 'POST':
551
                $options[CURLOPT_POST] = true;
552
                if(isset($postfields['raw'])){
553
                    $options[CURLOPT_POSTFIELDS] = $postfields['raw'];
554
                } else {
555
                    $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
556
                }
557
558
                break;
559
            case 'DELETE':
560
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
561
                break;
562
            case 'PUT':
563
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
564
                if(isset($postfields['raw'])){
565
                    $options[CURLOPT_POSTFIELDS] = $postfields['raw'];
566
                }
567
                break;
568
        }
569
570
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields) && !isset($postfields['raw'])) {
571
            $options[CURLOPT_URL] .= '?'.Util::buildHttpQuery($postfields);
572
        }
573
574
        $curlHandle = curl_init();
575
        curl_setopt_array($curlHandle, $options);
576
        $response = curl_exec($curlHandle);
577
        // Throw exceptions on cURL errors.
578
        if (curl_errno($curlHandle) > 0) {
579
            throw new TwitterAdsException(curl_error($curlHandle), curl_errno($curlHandle), null, null);
580
        }
581
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
582
        $parts = explode("\r\n\r\n", $response);
583
        $responseBody = array_pop($parts);
584
        $responseHeader = array_pop($parts);
585
        $this->response->setHeaders($this->parseHeaders($responseHeader));
586
        curl_close($curlHandle);
587
        return $responseBody;
588
    }
589
590
    /**
591
     * Get the header info to store.
592
     *
593
     * @param string $header
594
     *
595
     * @return array
596
     */
597
    private function parseHeaders($header)
598
    {
599
        $headers = [];
600
        foreach (explode("\r\n", $header) as $line) {
601
            if (strpos($line, ':') !== false) {
602
                list($key, $value) = explode(': ', $line);
603
                $key = str_replace('-', '_', strtolower($key));
604
                $headers[$key] = trim($value);
605
            }
606
        }
607
608
        return $headers;
609
    }
610
611
    /**
612
     * Encode application authorization header with base64.
613
     *
614
     * @param Consumer $consumer
615
     *
616
     * @return string
617
     */
618
    private function encodeAppAuthorization($consumer)
619
    {
620
        // TODO: key and secret should be rfc 1738 encoded
621
        $key = $consumer->key;
622
        $secret = $consumer->secret;
623
624
        return base64_encode($key.':'.$secret);
625
    }
626
627
    /**
628
     * Return current response. Allows inheritance.
629
     *
630
     * @return Response
631
     */
632
    public function getResponse()
633
    {
634
        return $this->response;
635
    }
636
637
    /**
638
     * @return string
639
     */
640
    public function getMethod()
641
    {
642
        return $this->method;
643
    }
644
645
    /**
646
     * @return string
647
     */
648
    public function getResource()
649
    {
650
        return $this->resource;
651
    }
652
653
    /**
654
     * @return string
655
     */
656
    public function getAccountId()
657
    {
658
        return $this->accountId;
659
    }
660
661
    /**
662
     * @param string $accountId
663
     */
664
    public function setAccountId($accountId)
665
    {
666
        $this->accountId = $accountId;
667
    }
668
}
669