Test Setup Failed
Push — master ( 792804...edde1f )
by Clive
02:07
created

CampaignMonitor::setListId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace CliveWalkden\CampaignMonitor;
4
5
class CampaignMonitor
6
{
7
    private $api_key;
8
    private $api_endpoint = 'https://api.createsend.com/api/v3.1';
9
    private $format = 'json';
10
11
    private $client_id;
12
    private $list_id;
13
14
    const TIMEOUT = 10;
15
16
    public $verify_ssl = true;
17
18
    private $request_successful = false;
19
    private $last_error = '';
20
    private $last_response = [];
21
    private $last_request = [];
22
23
    public function __construct($api_key, $client_id = null)
24
    {
25
        if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
26
            throw new \Exception('cURL not found and is required for this to work.');
27
        }
28
29
        if (!preg_match('/^[a-f0-9]{30,100}$/', $api_key)) {
30
            throw new \Exception("Invalid Campaign Monitor API Key `{$api_key}` supplied");
31
            throw new \Exception("Invalid Campain Monitor API Key `{$api_key}` supplied");
0 ignored issues
show
Unused Code introduced by
ThrowNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
32
        } else {
33
            $this->api_key = $api_key;
34
        }
35
36
        if ($client_id) {
37
            $this->client_id = trim($client_id);
38
        }
39
40
        $this->last_response = ['headers' => null, 'body' => null];
41
    }
42
43
    /**
44
     * @param string $client_id
45
     */
46
    public function setClientId($client_id)
47
    {
48
        $this->client_id = $client_id;
49
50
        return $this;
51
    }
52
53
    /**
54
     * @param mixed $list_id
55
     */
56
    public function setListId($list_id)
57
    {
58
        $this->list_id = $list_id;
59
60
        return $this;
61
    }
62
63
    public function getApiEndpoint()
64
    {
65
        return $this->api_endpoint;
66
    }
67
68
    public function success()
69
    {
70
        return $this->request_successful;
71
    }
72
73
    /**
74
     * @return string|boolean The error message
75
     */
76
    public function getLastError()
77
    {
78
        return $this->last_error ?: false;
79
    }
80
81
    /**
82
     * @return array
83
     */
84
    public function getLastRequest()
85
    {
86
        return $this->last_request;
87
    }
88
89
    /**
90
     * Make an HTTP DELETE request - for deleting data
91
     *
92
     * @param   string $method URL of the API request method
93
     * @param   array $args Assoc array of arguments (if any)
94
     * @param   int $timeout Timeout limit for request in seconds
95
     *
96
     * @return  array|boolean   Assoc array of API response, decoded from JSON
97
     */
98
    public function delete($method, $args = array(), $timeout = self::TIMEOUT)
99
    {
100
        return $this->makeRequest('delete', $method, $args, $timeout);
101
    }
102
103
    /**
104
     * Make an HTTP GET request - for retrieving data
105
     *
106
     * @param   string $method URL of the API request method
107
     * @param   array $args Assoc array of arguments (usually your data)
108
     * @param   int $timeout Timeout limit for request in seconds
109
     *
110
     * @return  array|boolean   Assoc array of API response, decoded from JSON
111
     */
112
    public function get($method, $args = array(), $timeout = self::TIMEOUT)
113
    {
114
        return $this->makeRequest('get', $method, $args, $timeout);
115
    }
116
117
    /**
118
     * Make an HTTP PATCH request - for performing partial updates
119
     *
120
     * @param   string $method URL of the API request method
121
     * @param   array $args Assoc array of arguments (usually your data)
122
     * @param   int $timeout Timeout limit for request in seconds
123
     *
124
     * @return  array|boolean   Assoc array of API response, decoded from JSON
125
     */
126
    public function patch($method, $args = array(), $timeout = self::TIMEOUT)
127
    {
128
        return $this->makeRequest('patch', $method, $args, $timeout);
129
    }
130
131
    /**
132
     * Make an HTTP POST request - for creating and updating items
133
     *
134
     * @param   string $method URL of the API request method
135
     * @param   array $args Assoc array of arguments (usually your data)
136
     * @param   int $timeout Timeout limit for request in seconds
137
     *
138
     * @return  array|boolean   Assoc array of API response, decoded from JSON
139
     */
140
    public function post($method, $args = array(), $timeout = self::TIMEOUT)
141
    {
142
        return $this->makeRequest('post', $method, $args, $timeout);
143
    }
144
145
    /**
146
     * Make an HTTP PUT request - for creating new items
147
     *
148
     * @param   string $method URL of the API request method
149
     * @param   array $args Assoc array of arguments (usually your data)
150
     * @param   int $timeout Timeout limit for request in seconds
151
     *
152
     * @return  array|boolean   Assoc array of API response, decoded from JSON
153
     */
154
    public function put($method, $args = array(), $timeout = self::TIMEOUT)
155
    {
156
        return $this->makeRequest('put', $method, $args, $timeout);
157
    }
158
159
    private function formatMethod($method)
160
    {
161
        $method = str_replace(
162
            ['{client_id}', '{list_id}'],
163
            [$this->client_id, $this->list_id],
164
            $method
165
        );
166
167
        return $method;
168
    }
169
170
    private function makeRequest($http_verb, $method, $args = [], $timeout = self::TIMEOUT)
171
    {
172
        $method = $this->formatMethod($method);
173
174
        $url = $this->api_endpoint . $method . '.' . $this->format;
175
176
        $response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
177
178
        $httpHeader = [
179
            'Accept: application/vnd.api+json',
180
            'Content-Type: application/vnd.api+json'
181
        ];
182
183
        if (isset($args["language"])) {
184
            $httpHeader[] = "Accept-Language: " . $args["language"];
185
        }
186
187
        $ch = curl_init();
188
        curl_setopt($ch, CURLOPT_URL, $url);
189
        curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
190
        curl_setopt($ch, CURLOPT_USERAGENT,
191
            'CliveWalkden/CampaignMonitor-API/3.1 (github.com/clivewalkden/campaign-monitor-api)');
192
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
193
        curl_setopt($ch, CURLOPT_USERPWD, $this->api_key . ':nopass');
194
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
195
        curl_setopt($ch, CURLOPT_VERBOSE, true);
196
        curl_setopt($ch, CURLOPT_HEADER, true);
197
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
198
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
199
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
200
        curl_setopt($ch, CURLOPT_ENCODING, '');
201
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
202
203
        // Debug
204
//        $fp = fopen(SOZO_LOG_DIR . 'curl.txt', 'w');
205
//        curl_setopt($ch, CURLOPT_VERBOSE, 1);
206
//        curl_setopt($ch, CURLOPT_STDERR, $fp);
207
208
        switch ($http_verb) {
209
            case 'post':
210
                curl_setopt($ch, CURLOPT_POST, true);
211
                $this->attachRequestPayload($ch, $args);
212
                break;
213
214
            case 'get':
215
                $query = http_build_query($args, '', '&');
216
                curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
217
                break;
218
219
            case 'delete':
220
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
221
                break;
222
223
            case 'patch':
224
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
225
                $this->attachRequestPayload($ch, $args);
226
                break;
227
228
            case 'put':
229
                if ($args['email']) {
230
                    $query = http_build_query(['email' => $args['email']], '', '&');
231
                    curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
232
                }
233
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
234
                $this->attachRequestPayload($ch, $args);
235
                break;
236
        }
237
238
        $responseContent = curl_exec($ch);
239
        $response['headers'] = curl_getinfo($ch);
240
        $response = $this->setResponseState($response, $responseContent, $ch);
241
        $formattedResponse = $this->formatResponse($response);
242
243
        curl_close($ch);
244
245
        $isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
246
247
        return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
248
    }
249
250
    private function prepareStateForRequest($http_verb, $method, $url, $timeout)
251
    {
252
        $this->last_error = '';
253
254
        $this->request_successful = false;
255
256
        $this->last_response = array(
257
            'headers' => null, // array of details from curl_getinfo()
258
            'httpHeaders' => null, // array of HTTP headers
259
            'body' => null // content of the response
260
        );
261
262
        $this->last_request = array(
263
            'method' => $http_verb,
264
            'path' => $method,
265
            'url' => $url,
266
            'body' => '',
267
            'timeout' => $timeout,
268
        );
269
270
        return $this->last_response;
271
    }
272
273
    /**
274
     * Get the HTTP headers as an array of header-name => header-value pairs.
275
     *
276
     * The "Link" header is parsed into an associative array based on the
277
     * rel names it contains. The original value is available under
278
     * the "_raw" key.
279
     *
280
     * @param string $headersAsString
281
     *
282
     * @return array
283
     */
284
    private function getHeadersAsArray($headersAsString)
285
    {
286
        $headers = array();
287
288
        foreach (explode("\r\n", $headersAsString) as $i => $line) {
289
            if ($i === 0) { // HTTP code
290
                continue;
291
            }
292
293
            $line = trim($line);
294
            if (empty($line)) {
295
                continue;
296
            }
297
298
            list($key, $value) = explode(': ', $line);
299
300
//            if ($key == 'Link') {
301
//                $value = array_merge(
302
//                    array('_raw' => $value),
303
//                    $this->getLinkHeaderAsArray($value)
304
//                );
305
//            }
306
307
            $headers[$key] = $value;
308
        }
309
310
        return $headers;
311
    }
312
313
    /**
314
     * Encode the data and attach it to the request
315
     *
316
     * @param   resource $ch cURL session handle, used by reference
317
     * @param   array $data Assoc array of data to attach
318
     */
319
    private function attachRequestPayload(&$ch, $data)
320
    {
321
        $encoded = json_encode($data);
322
        $this->last_request['body'] = $encoded;
323
        curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
324
    }
325
326
    /**
327
     * Decode the response and format any error messages for debugging
328
     *
329
     * @param array $response The response from the curl request
330
     *
331
     * @return array|boolean    The JSON decoded into an array
332
     */
333
    private function formatResponse($response)
334
    {
335
        $this->last_response = $response;
336
337
        if (!empty($response['body'])) {
338
            return json_decode($response['body'], true);
339
        }
340
341
        return false;
342
    }
343
344
    /**
345
     * Do post-request formatting and setting state from the response
346
     *
347
     * @param array $response The response from the curl request
348
     * @param string $responseContent The body of the response from the curl request
349
     * @param resource $ch The curl resource
350
     *
351
     * @return array    The modified response
352
     */
353
    private function setResponseState($response, $responseContent, $ch)
354
    {
355
        if ($responseContent === false) {
0 ignored issues
show
introduced by
The condition $responseContent === false is always false.
Loading history...
356
            $this->last_error = curl_error($ch);
357
        } else {
358
359
            $headerSize = $response['headers']['header_size'];
360
361
            $response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
362
            $response['body'] = substr($responseContent, $headerSize);
363
364
            if (isset($response['headers']['request_header'])) {
365
                $this->last_request['headers'] = $response['headers']['request_header'];
366
            }
367
        }
368
369
        return $response;
370
    }
371
372
    /**
373
     * Check if the response was successful or a failure. If it failed, store the error.
374
     *
375
     * @param array $response The response from the curl request
376
     * @param array|false $formattedResponse The response body payload from the curl request
377
     * @param int $timeout The timeout supplied to the curl request.
378
     *
379
     * @return boolean     If the request was successful
380
     */
381
    private function determineSuccess($response, $formattedResponse, $timeout)
382
    {
383
        $status = $this->findHTTPStatus($response, $formattedResponse);
384
385
        if ($status >= 200 && $status <= 299) {
386
            $this->request_successful = true;
387
            return true;
388
        }
389
390
        if (isset($formattedResponse['detail'])) {
391
            $this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
392
            return false;
393
        }
394
395
        if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
396
            $this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
397
            return false;
398
        }
399
400
        $this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
401
        return false;
402
    }
403
404
    /**
405
     * Find the HTTP status code from the headers or API response body
406
     *
407
     * @param array $response The response from the curl request
408
     * @param array|false $formattedResponse The response body payload from the curl request
409
     *
410
     * @return int  HTTP status code
411
     */
412
    private function findHTTPStatus($response, $formattedResponse)
413
    {
414
        if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
415
            return (int)$response['headers']['http_code'];
416
        }
417
418
        if (!empty($response['body']) && isset($formattedResponse['status'])) {
419
            return (int)$formattedResponse['status'];
420
        }
421
422
        return 418;
423
    }
424
}