Completed
Push — master ( 179c68...7ebb95 )
by Hector
03:48
created

TwitterAds::resetLastResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
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  string Method used for the request */
40
    private $method;
41
    /** @var  string Resource used for the request */
42
    private $resource;
43
    /** @var Response details about the result of the last request */
44
    private $response;
45
    /** @var string|null Application bearer token */
46
    private $bearer;
47
    /** @var Consumer Twitter application details */
48
    private $consumer;
49
    /** @var Token|null User access token details */
50
    private $token;
51
    /** @var HmacSha1 OAuth 1 signature type used by Twitter */
52
    private $signatureMethod;
53
    /** @var  bool Sandbox allows to make requests thought sandbox environment */
54
    private $sandbox;
55
56
    /**
57
     * Constructor.
58
     *
59
     * @param string $consumerKey The Application Consumer Key
60
     * @param string $consumerSecret The Application Consumer Secret
61
     * @param string|null $oauthToken The Client Token (optional)
62
     * @param string|null $oauthTokenSecret The Client Token Secret (optional)
63
     * @param bool $sandbox The Sandbox environment (optional)
64
     */
65
    public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null, $sandbox = false)
66
    {
67
        $this->resetLastResponse();
68
        $this->signatureMethod = new HmacSha1();
69
        $this->consumer = new Consumer($consumerKey, $consumerSecret);
70
        if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
71
            $this->token = new Token($oauthToken, $oauthTokenSecret);
72
        }
73
        if (empty($oauthToken) && !empty($oauthTokenSecret)) {
74
            $this->bearer = $oauthTokenSecret;
75
        }
76
        $this->sandbox = $sandbox;
77
    }
78
79
    /**
80
     * @param string $accountId
81
     *
82
     * @return Account|Cursor
83
     */
84
    public function getAccounts($accountId = '')
85
    {
86
        $account = new Account($this);
87
88
        return $accountId ? $account->load($accountId) : $account->all();
89
    }
90
91
    /**
92
     * @param string $oauthToken
93
     * @param string $oauthTokenSecret
94
     */
95
    public function setOauthToken($oauthToken, $oauthTokenSecret)
96
    {
97
        $this->token = new Token($oauthToken, $oauthTokenSecret);
98
    }
99
100
    /**
101
     * @return string|null
102
     */
103
    public function getLastApiPath()
104
    {
105
        return $this->response->getApiPath();
106
    }
107
108
    /**
109
     * @return int
110
     */
111
    public function getLastHttpCode()
112
    {
113
        return $this->response->getHttpCode();
114
    }
115
116
    /**
117
     * @return array
118
     */
119
    public function getLastXHeaders()
120
    {
121
        return $this->response->getXHeaders();
122
    }
123
124
    /**
125
     * @return array|object|null
126
     */
127
    public function getLastBody()
128
    {
129
        return $this->response->getBody();
130
    }
131
132
    /**
133
     * Resets the last response cache.
134
     */
135
    public function resetLastResponse()
136
    {
137
        $this->response = new Response();
138
    }
139
140
    /**
141
     * Make URLs for user browser navigation.
142
     *
143
     * @param string $path
144
     * @param array $parameters
145
     *
146
     * @return string
147
     */
148
    public function url($path, array $parameters)
149
    {
150
        $this->resetLastResponse();
151
        $this->response->setApiPath($path);
152
        $query = http_build_query($parameters);
153
154
        return sprintf('%s/%s?%s', self::API_HOST_OAUTH, $path, $query);
155
    }
156
157
    /**
158
     * Make /oauth/* requests to the API.
159
     *
160
     * @param string $path
161
     * @param array $parameters
162
     * @return array
163
     * @throws Exception
164
     */
165
    public function oauth($path, array $parameters = [])
166
    {
167
        $response = [];
168
        $this->resetLastResponse();
169
        $this->response->setApiPath($path);
170
        $url = sprintf('%s/%s', self::API_HOST_OAUTH, $path);
171
        $result = $this->oAuthRequest($url, 'POST', $parameters);
172
173
        if ($this->getLastHttpCode() != 200) {
174
            throw new TwitterAdsException($result, 500, null, $result);
175
        }
176
177
        parse_str($result, $response);
178
        $this->response->setBody($response);
179
180
        return $response;
181
    }
182
183
    /**
184
     * Make /oauth2/* requests to the API.
185
     *
186
     * @param string $path
187
     * @param array $parameters
188
     *
189
     * @return array|object
190
     */
191
    public function oauth2($path, array $parameters = [])
192
    {
193
        $method = 'POST';
194
        $this->resetLastResponse();
195
        $this->response->setApiPath($path);
196
        $url = sprintf('%s/%s', self::API_HOST_OAUTH, $path);
197
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
198
        $authorization = 'Authorization: Basic '.$this->encodeAppAuthorization($this->consumer);
199
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
200
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
201
        $this->response->setBody($response);
202
203
        return $response;
204
    }
205
206
    public function verifyCredentials($parameters = [])
207
    {
208
        return $this->http('GET', self::API_HOST_OAUTH, 'account/verify_credentials', $parameters);
209
    }
210
211
    /**
212
     * Make GET requests to the API.
213
     *
214
     * @param string $path
215
     * @param array $parameters
216
     *
217
     * @return Response
218
     */
219
    public function get($path, array $parameters = [])
220
    {
221
        return $this->http('GET', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters);
222
    }
223
224
    /**
225
     * Make POST requests to the API.
226
     *
227
     * @param string $path
228
     * @param array $parameters
229
     *
230
     * @param array $headers
231
     * @return Response
232
     */
233
    public function post($path, array $parameters = [], array $headers = [])
234
    {
235
        return $this->http('POST', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters, $headers);
236
    }
237
238
    /**
239
     * Make DELETE requests to the API.
240
     *
241
     * @param string $path
242
     * @param array $parameters
243
     *
244
     * @return Response
245
     */
246
    public function delete($path, array $parameters = [])
247
    {
248
        return $this->http('DELETE', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters);
249
    }
250
251
    /**
252
     * Make PUT requests to the API.
253
     *
254
     * @param string $path
255
     * @param array $parameters
256
     *
257
     * @return Response
258
     */
259
    public function put($path, array $parameters = [], array $headers = [])
260
    {
261
        return $this->http('PUT', !$this->sandbox ? self::API_HOST : self::API_HOST_SANDBOX, $path, $parameters, $headers);
262
    }
263
264
    /**
265
     * Upload media to upload.twitter.com.
266
     *
267
     * @param array $parameters
268
     * @param bool $chunked
269
     *
270
     * @return array|object
271
     */
272
    public function upload(array $parameters = [], $chunked = false)
273
    {
274
        if ($chunked) {
275
            return $this->uploadMediaChunked(self::UPLOAD_PATH, $parameters)->getBody();
276
        } else {
277
            return $this->uploadMediaNotChunked(self::UPLOAD_PATH, $parameters)->getBody();
278
        }
279
    }
280
281
    /**
282
     * Private method to upload media (not chunked) to upload.twitter.com.
283
     *
284
     * @param string $path
285
     * @param array $parameters
286
     *
287
     * @return array|object
288
     */
289
    private function uploadMediaNotChunked($path, $parameters)
290
    {
291
        $file = file_get_contents($parameters['media']);
292
        $base = base64_encode($file);
293
        $parameters['media'] = $base;
294
295
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
296
    }
297
298
    /**
299
     * Private method to upload media (chunked) to upload.twitter.com.
300
     *
301
     * @param string $path
302
     * @param array $parameters
303
     *
304
     * @return array|object
305
     */
306
    private function uploadMediaChunked($path, $parameters)
307
    {
308
        // Init
309
        $init = $this->http(
310
            'POST',
311
            self::UPLOAD_HOST,
312
            $path,
313
            [
314
                'command' => 'INIT',
315
                'media_type' => $parameters['media_type'],
316
                'total_bytes' => filesize($parameters['media']),
317
            ]
318
        );
319
        // Append
320
        $segment_index = 0;
321
        $media = fopen($parameters['media'], 'rb');
322
        while (!feof($media)) {
323
            $this->http(
324
                'POST',
325
                self::UPLOAD_HOST,
326
                'media/upload',
327
                [
328
                    'command' => 'APPEND',
329
                    'media_id' => $init->media_id_string,
0 ignored issues
show
Bug introduced by
The property media_id_string does not seem to exist in Hborras\TwitterAdsSDK\Response.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
330
                    'segment_index' => $segment_index++,
331
                    'media_data' => base64_encode(fread($media, self::UPLOAD_CHUNK)),
332
                ]
333
            );
334
        }
335
        fclose($media);
336
        // Finalize
337
        $finalize = $this->http(
338
            'POST',
339
            self::UPLOAD_HOST,
340
            'media/upload',
341
            [
342
                'command' => 'FINALIZE',
343
                'media_id' => $init->media_id_string,
344
            ]
345
        );
346
347
        return $finalize;
348
    }
349
350
    /**
351
     * @param string $method
352
     * @param string $host
353
     * @param string $path
354
     * @param array $parameters
355
     * @param array $headers
356
     * @return Response
357
     * @throws BadRequest
358
     * @throws Forbidden
359
     * @throws NotAuthorized
360
     * @throws NotFound
361
     * @throws RateLimit
362
     * @throws ServerError
363
     * @throws ServiceUnavailable
364
     */
365
    private function http($method, $host, $path, array $parameters, $headers = [])
366
    {
367
        $this->method = $method;
368
        $this->resource = $path;
369
        $this->resetLastResponse();
370
        if(strpos($path, TONUpload::DEFAULT_DOMAIN) === 0) {
371
            $url = $path;
372
        } else {
373
            if($host == self::UPLOAD_HOST){
374
                $url = sprintf('%s/%s/%s', $host, self::API_REST_VERSION, $path);
375
            } else {
376
                $url = sprintf('%s/%s/%s', $host, self::API_VERSION, $path);
377
            }
378
        }
379
380
        $this->response->setApiPath($path);
381
        $result = $this->oAuthRequest($url, $method, $parameters, $headers);
382
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
383
        $this->response->setBody($response);
384
        if ($this->getLastHttpCode() > 399) {
385
            $this->manageErrors($response);
386
        }
387
        return $this->response;
388
    }
389
390
    /**
391
     * @param $response
392
     * @throws BadRequest
393
     * @throws Forbidden
394
     * @throws NotAuthorized
395
     * @throws NotFound
396
     * @throws RateLimit
397
     * @throws ServerError
398
     * @throws ServiceUnavailable
399
     */
400
    public function manageErrors($response){
401
        switch ($this->getLastHttpCode()) {
402
            case 400:
403
                throw new BadRequest(TwitterAdsException::BAD_REQUEST, 400, null, $response->errors);
404
            case 401:
405
                throw new NotAuthorized(TwitterAdsException::NOT_AUTHORIZED, 401, null, $response->errors);
406
            case 403:
407
                throw new Forbidden(TwitterAdsException::FORBIDDEN, 403, null, $response->errors);
408
            case 404:
409
                throw new NotFound(TwitterAdsException::NOT_FOUND, 404, null, $response->errors);
410
            case 429:
411
                throw new RateLimit(TwitterAdsException::RATE_LIMIT, 429, null, $response->errors, $this->response->getsHeaders());
412
            case 500:
413
                throw new ServerError(TwitterAdsException::SERVER_ERROR, 500, null, $response->errors);
414
            case 503:
415
                throw new ServiceUnavailable(TwitterAdsException::SERVICE_UNAVAILABLE, 503, null, $response->errors, $this->response->getsHeaders());
416
            default:
417
                throw new ServerError(TwitterAdsException::SERVER_ERROR, 500, null, $response->errors);
418
        }
419
    }
420
421
    /**
422
     * Format and sign an OAuth / API request.
423
     *
424
     * @param string $url
425
     * @param string $method
426
     * @param array $parameters
427
     *
428
     * @param array $headers
429
     * @return string
430
     * @throws TwitterAdsException
431
     * @throws TwitterOAuthException
432
     */
433
    private function oAuthRequest($url, $method, array $parameters, $headers = [])
434
    {
435
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
436
        if (array_key_exists('oauth_callback', $parameters)) {
437
            // Twitter doesn't like oauth_callback as a parameter.
438
            unset($parameters['oauth_callback']);
439
        }
440
        if ($this->bearer === null) {
441
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
442
            $authorization = $request->toHeader();
443
        } else {
444
            $authorization = 'Authorization: Bearer '.$this->bearer;
445
        }
446
        if(strpos($url, TONUpload::DEFAULT_DOMAIN) === 0) {
447
            return $this->request($url, $method, $authorization, $parameters, $headers);
448
        } else {
449
            return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters, $headers);
450
        }
451
452
    }
453
454
    /**
455
     * Make an HTTP request.
456
     *
457
     * @param string $url
458
     * @param string $method
459
     * @param string $authorization
460
     * @param array $postfields
461
     *
462
     * @param array $headers
463
     * @return string
464
     * @throws TwitterAdsException
465
     */
466
    private function request($url, $method, $authorization, $postfields, $headers = [])
467
    {
468
        /* Curl settings */
469
        $options = [
470
            CURLOPT_VERBOSE => false,
471
            CURLOPT_CAINFO => __DIR__.DIRECTORY_SEPARATOR.'cacert.pem',
472
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
473
            CURLOPT_HEADER => true,
474
            CURLOPT_HTTPHEADER => array_merge(['Accept: */*', $authorization, 'Expect:'],$headers,['Connection: close']),
475
            CURLOPT_RETURNTRANSFER => true,
476
            CURLOPT_SSL_VERIFYHOST => 2,
477
            CURLOPT_SSL_VERIFYPEER => true,
478
            CURLOPT_TIMEOUT => $this->timeout,
479
            CURLOPT_URL => $url,
480
            CURLOPT_USERAGENT => $this->userAgent,
481
            CURLOPT_ENCODING => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
482
        ];
483
484
        if (!empty($this->proxy)) {
485
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
486
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
487
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
488
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
489
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
490
        }
491
492
        switch ($method) {
493
            case 'GET':
494
                break;
495
            case 'POST':
496
                $options[CURLOPT_POST] = true;
497
                if(isset($postfields['raw'])){
498
                    $options[CURLOPT_POSTFIELDS] = $postfields['raw'];
499
                } else {
500
                    $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
501
                }
502
503
                break;
504
            case 'DELETE':
505
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
506
                break;
507
            case 'PUT':
508
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
509
                if(isset($postfields['raw'])){
510
                    $options[CURLOPT_POSTFIELDS] = $postfields['raw'];
511
                }
512
                break;
513
        }
514
515
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields) && !isset($postfields['raw'])) {
516
            $options[CURLOPT_URL] .= '?'.Util::buildHttpQuery($postfields);
517
        }
518
519
        $curlHandle = curl_init();
520
        curl_setopt_array($curlHandle, $options);
521
        $response = curl_exec($curlHandle);
522
        // Throw exceptions on cURL errors.
523
        if (curl_errno($curlHandle) > 0) {
524
            throw new TwitterAdsException(curl_error($curlHandle), curl_errno($curlHandle), null, null);
525
        }
526
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
527
        $parts = explode("\r\n\r\n", $response);
528
        $responseBody = array_pop($parts);
529
        $responseHeader = array_pop($parts);
530
        $this->response->setHeaders($this->parseHeaders($responseHeader));
531
        curl_close($curlHandle);
532
        return $responseBody;
533
    }
534
535
    /**
536
     * Get the header info to store.
537
     *
538
     * @param string $header
539
     *
540
     * @return array
541
     */
542
    private function parseHeaders($header)
543
    {
544
        $headers = [];
545
        foreach (explode("\r\n", $header) as $line) {
546
            if (strpos($line, ':') !== false) {
547
                list($key, $value) = explode(': ', $line);
548
                $key = str_replace('-', '_', strtolower($key));
549
                $headers[$key] = trim($value);
550
            }
551
        }
552
553
        return $headers;
554
    }
555
556
    /**
557
     * Encode application authorization header with base64.
558
     *
559
     * @param Consumer $consumer
560
     *
561
     * @return string
562
     */
563
    private function encodeAppAuthorization($consumer)
564
    {
565
        // TODO: key and secret should be rfc 1738 encoded
566
        $key = $consumer->key;
567
        $secret = $consumer->secret;
568
569
        return base64_encode($key.':'.$secret);
570
    }
571
572
    /**
573
     * Return current response. Allows inheritance.
574
     *
575
     * @return Response
576
     */
577
    public function getResponse()
578
    {
579
        return $this->response;
580
    }
581
582
    /**
583
     * @return string
584
     */
585
    public function getMethod()
586
    {
587
        return $this->method;
588
    }
589
590
    /**
591
     * @return string
592
     */
593
    public function getResource()
594
    {
595
        return $this->resource;
596
    }
597
}
598