Completed
Push — master ( 5c8a08...cc6b39 )
by Drew
07:05
created

src/MailChimp.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace DrewM\MailChimp;
4
5
/**
6
 * Super-simple, minimum abstraction MailChimp API v3 wrapper
7
 * MailChimp API v3: http://developer.mailchimp.com
8
 * This wrapper: https://github.com/drewm/mailchimp-api
9
 *
10
 * @author Drew McLellan <[email protected]>
11
 * @version 2.2
12
 */
13
class MailChimp
14
{
15
    private $api_key;
16
    private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
17
18
    /*  SSL Verification
19
        Read before disabling:
20
        http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
21
    */
22
    public $verify_ssl = true;
23
24
    private $request_successful = false;
25
    private $last_error         = '';
26
    private $last_response      = array();
27
    private $last_request       = array();
28
29
    /**
30
     * Create a new instance
31
     * @param string $api_key Your MailChimp API key
32
     * @throws \Exception
33
     */
34
    public function __construct($api_key)
35
    {
36
        $this->api_key = $api_key;
37
38
        if (strpos($this->api_key, '-') === false) {
39
            throw new \Exception('Invalid MailChimp API key supplied.');
40
        }
41
42
        list(, $data_center) = explode('-', $this->api_key);
43
        $this->api_endpoint  = str_replace('<dc>', $data_center, $this->api_endpoint);
44
45
        $this->last_response = array('headers' => null, 'body' => null);
46
    }
47
48
    /**
49
     * Create a new instance of a Batch request. Optionally with the ID of an existing batch.
50
     * @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
51
     * @return Batch            New Batch object.
52
     */
53
    public function new_batch($batch_id = null)
54
    {
55
        return new Batch($this, $batch_id);
56
    }
57
58
    /**
59
     * Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
60
     * @param   string $email The subscriber's email address
61
     * @return  string          Hashed version of the input
62
     */
63
    public function subscriberHash($email)
64
    {
65
        return md5(strtolower($email));
66
    }
67
68
    /**
69
     * Was the last request successful?
70
     * @return bool  True for success, false for failure
71
     */
72
    public function success()
73
    {
74
        return $this->request_successful;
75
    }
76
77
    /**
78
     * Get the last error returned by either the network transport, or by the API.
79
     * If something didn't work, this should contain the string describing the problem.
80
     * @return  array|false  describing the error
81
     */
82
    public function getLastError()
83
    {
84
        return $this->last_error ?: false;
85
    }
86
87
    /**
88
     * Get an array containing the HTTP headers and the body of the API response.
89
     * @return array  Assoc array with keys 'headers' and 'body'
90
     */
91
    public function getLastResponse()
92
    {
93
        return $this->last_response;
94
    }
95
96
    /**
97
     * Get an array containing the HTTP headers and the body of the API request.
98
     * @return array  Assoc array
99
     */
100
    public function getLastRequest()
101
    {
102
        return $this->last_request;
103
    }
104
105
    /**
106
     * Make an HTTP DELETE request - for deleting data
107
     * @param   string $method URL of the API request method
108
     * @param   array $args Assoc array of arguments (if any)
109
     * @param   int $timeout Timeout limit for request in seconds
110
     * @return  array|false   Assoc array of API response, decoded from JSON
111
     */
112
    public function delete($method, $args = array(), $timeout = 10)
113
    {
114
        return $this->makeRequest('delete', $method, $args, $timeout);
115
    }
116
117
    /**
118
     * Make an HTTP GET request - for retrieving data
119
     * @param   string $method URL of the API request method
120
     * @param   array $args Assoc array of arguments (usually your data)
121
     * @param   int $timeout Timeout limit for request in seconds
122
     * @return  array|false   Assoc array of API response, decoded from JSON
123
     */
124
    public function get($method, $args = array(), $timeout = 10)
125
    {
126
        return $this->makeRequest('get', $method, $args, $timeout);
127
    }
128
129
    /**
130
     * Make an HTTP PATCH request - for performing partial updates
131
     * @param   string $method URL of the API request method
132
     * @param   array $args Assoc array of arguments (usually your data)
133
     * @param   int $timeout Timeout limit for request in seconds
134
     * @return  array|false   Assoc array of API response, decoded from JSON
135
     */
136
    public function patch($method, $args = array(), $timeout = 10)
137
    {
138
        return $this->makeRequest('patch', $method, $args, $timeout);
139
    }
140
141
    /**
142
     * Make an HTTP POST request - for creating and updating items
143
     * @param   string $method URL of the API request method
144
     * @param   array $args Assoc array of arguments (usually your data)
145
     * @param   int $timeout Timeout limit for request in seconds
146
     * @return  array|false   Assoc array of API response, decoded from JSON
147
     */
148
    public function post($method, $args = array(), $timeout = 10)
149
    {
150
        return $this->makeRequest('post', $method, $args, $timeout);
151
    }
152
153
    /**
154
     * Make an HTTP PUT request - for creating new items
155
     * @param   string $method URL of the API request method
156
     * @param   array $args Assoc array of arguments (usually your data)
157
     * @param   int $timeout Timeout limit for request in seconds
158
     * @return  array|false   Assoc array of API response, decoded from JSON
159
     */
160
    public function put($method, $args = array(), $timeout = 10)
161
    {
162
        return $this->makeRequest('put', $method, $args, $timeout);
163
    }
164
165
    /**
166
     * Performs the underlying HTTP request. Not very exciting.
167
     * @param  string $http_verb The HTTP verb to use: get, post, put, patch, delete
168
     * @param  string $method The API method to be called
169
     * @param  array $args Assoc array of parameters to be passed
170
     * @param int $timeout
171
     * @return array|false Assoc array of decoded result
172
     * @throws \Exception
173
     */
174
    private function makeRequest($http_verb, $method, $args = array(), $timeout = 10)
175
    {
176
        if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
177
            throw new \Exception("cURL support is required, but can't be found.");
178
        }
179
180
        $url = $this->api_endpoint . '/' . $method;
181
182
        $this->last_error         = '';
183
        $this->request_successful = false;
184
        $response                 = array('headers' => null, 'body' => null);
185
        $this->last_response      = $response;
186
187
        $this->last_request = array(
188
            'method'  => $http_verb,
189
            'path'    => $method,
190
            'url'     => $url,
191
            'body'    => '',
192
            'timeout' => $timeout,
193
        );
194
195
        $ch = curl_init();
196
        curl_setopt($ch, CURLOPT_URL, $url);
197
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
198
            'Accept: application/vnd.api+json',
199
            'Content-Type: application/vnd.api+json',
200
            'Authorization: apikey ' . $this->api_key
201
        ));
202
        curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
203
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
204
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
205
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
206
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
207
        curl_setopt($ch, CURLOPT_ENCODING, '');
208
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
209
210
        switch ($http_verb) {
211
            case 'post':
212
                curl_setopt($ch, CURLOPT_POST, true);
213
                $this->attachRequestPayload($ch, $args);
214
                break;
215
216
            case 'get':
217
                $query = http_build_query($args);
218
                curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
219
                break;
220
221
            case 'delete':
222
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
223
                break;
224
225
            case 'patch':
226
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
227
                $this->attachRequestPayload($ch, $args);
228
                break;
229
230
            case 'put':
231
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
232
                $this->attachRequestPayload($ch, $args);
233
                break;
234
        }
235
236
        $response['body']    = curl_exec($ch);
237
        $response['headers'] = curl_getinfo($ch);
238
239
        if (isset($response['headers']['request_header'])) {
240
            $this->last_request['headers'] = $response['headers']['request_header'];
241
        }
242
243
        if ($response['body'] === false) {
244
            $this->last_error = curl_error($ch);
245
        }
246
247
        curl_close($ch);
248
249
        $formattedResponse = $this->formatResponse($response);
250
251
        $this->determineSuccess($response, $formattedResponse);
0 ignored issues
show
It seems like $formattedResponse defined by $this->formatResponse($response) on line 249 can also be of type boolean; however, DrewM\MailChimp\MailChimp::determineSuccess() does only seem to accept array, 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...
252
253
        return $formattedResponse;
254
    }
255
256
    /**
257
     * Encode the data and attach it to the request
258
     * @param   resource $ch cURL session handle, used by reference
259
     * @param   array $data Assoc array of data to attach
260
     */
261
    private function attachRequestPayload(&$ch, $data)
262
    {
263
        $encoded = json_encode($data);
264
        $this->last_request['body'] = $encoded;
265
        curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
266
    }
267
268
    /**
269
     * Decode the response and format any error messages for debugging
270
     * @param array $response The response from the curl request
271
     * @return array|bool     The JSON decoded into an array
272
     */
273
    private function formatResponse($response)
274
    {
275
        $this->last_response = $response;
276
277
        if (!empty($response['body'])) {
278
            return json_decode($response['body'], true);
279
        }
280
281
        return false;
282
    }
283
284
    /**
285
     * Check if the response was successful or a failure. If it failed, store the error.
286
     * @param array $response The response from the curl request
287
     * @param array $formattedResponse The response body payload from the curl request
288
     * @return bool     If the request was successful
289
     */
290
    private function determineSuccess($response, $formattedResponse)
291
    {
292
        $status = $this->findHTTPStatus($response, $formattedResponse);
293
294
        if ($status >= 200 && $status <= 299) {
295
            $this->request_successful = true;
296
            return true;
297
        }
298
299
        if (isset($formattedResponse['detail'])) {
300
            $this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
301
            return false;
302
        }
303
304
        $this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
305
        return false;
306
    }
307
308
    /**
309
     * Find the HTTP status code from the headers or API response body
310
     * @param array $response The response from the curl request
311
     * @param array $formattedResponse The response body payload from the curl request
312
     * @return int  HTTP status code
313
     */
314
    private function findHTTPStatus($response, $formattedResponse)
315
    {
316
        if (!empty($response['body']) && isset($formattedResponse['status'])) {
317
            return (int) $formattedResponse['status'];
318
        }
319
320
        if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
321
            return (int) $response['headers']['http_code'];
322
        }
323
324
        return 418;
325
    }
326
}
327