PluginProbe
Getwid – Gutenberg Blocks / 3.0.2
Getwid – Gutenberg Blocks v3.0.2
3.0.2 3.0.1 3.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.2.0 trunk 1.8.7 1.9.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9
← All changes | includes/libraries/mailchimp-api/src/MailChimp.php +492 -492 2.0.63.0.2 View file →
@@ -1,492 +1,492 @@
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 <drew.mclellan@gmail.com>
11 - * @version 2.5
12 - */
13 -class MailChimp
14 -{
15 - private $api_key;
16 - private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
17 -
18 - const TIMEOUT = 10;
19 -
20 - /* SSL Verification
21 - Read before disabling:
22 - http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
23 - */
24 - public $verify_ssl = true;
25 -
26 - private $request_successful = false;
27 - private $last_error = '';
28 - private $last_response = array();
29 - private $last_request = array();
30 -
31 - /**
32 - * Create a new instance
33 - *
34 - * @param string $api_key Your MailChimp API key
35 - * @param string $api_endpoint Optional custom API endpoint
36 - *
37 - * @throws \Exception
38 - */
39 - public function __construct($api_key, $api_endpoint = null)
40 - {
41 - if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
42 - throw new \Exception("cURL support is required, but can't be found.");
43 - }
44 -
45 - $this->api_key = $api_key;
46 -
47 - if ($api_endpoint === null) {
48 - if (strpos($this->api_key, '-') === false) {
49 - throw new \Exception("Invalid MailChimp API key supplied.");
50 - }
51 - list(, $data_center) = explode('-', $this->api_key);
52 - $this->api_endpoint = str_replace('<dc>', $data_center, $this->api_endpoint);
53 - } else {
54 - $this->api_endpoint = $api_endpoint;
55 - }
56 -
57 - $this->last_response = array('headers' => null, 'body' => null);
58 - }
59 -
60 - /**
61 - * Create a new instance of a Batch request. Optionally with the ID of an existing batch.
62 - *
63 - * @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
64 - *
65 - * @return Batch New Batch object.
66 - */
67 - public function new_batch($batch_id = null)
68 - {
69 - return new Batch($this, $batch_id);
70 - }
71 -
72 - /**
73 - * @return string The url to the API endpoint
74 - */
75 - public function getApiEndpoint()
76 - {
77 - return $this->api_endpoint;
78 - }
79 -
80 -
81 - /**
82 - * Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
83 - *
84 - * @param string $email The subscriber's email address
85 - *
86 - * @return string Hashed version of the input
87 - */
88 - public static function subscriberHash($email)
89 - {
90 - return md5(strtolower($email));
91 - }
92 -
93 - /**
94 - * Was the last request successful?
95 - *
96 - * @return bool True for success, false for failure
97 - */
98 - public function success()
99 - {
100 - return $this->request_successful;
101 - }
102 -
103 - /**
104 - * Get the last error returned by either the network transport, or by the API.
105 - * If something didn't work, this should contain the string describing the problem.
106 - *
107 - * @return string|false describing the error
108 - */
109 - public function getLastError()
110 - {
111 - return $this->last_error ?: false;
112 - }
113 -
114 - /**
115 - * Get an array containing the HTTP headers and the body of the API response.
116 - *
117 - * @return array Assoc array with keys 'headers' and 'body'
118 - */
119 - public function getLastResponse()
120 - {
121 - return $this->last_response;
122 - }
123 -
124 - /**
125 - * Get an array containing the HTTP headers and the body of the API request.
126 - *
127 - * @return array Assoc array
128 - */
129 - public function getLastRequest()
130 - {
131 - return $this->last_request;
132 - }
133 -
134 - /**
135 - * Make an HTTP DELETE request - for deleting data
136 - *
137 - * @param string $method URL of the API request method
138 - * @param array $args Assoc array of arguments (if any)
139 - * @param int $timeout Timeout limit for request in seconds
140 - *
141 - * @return array|false Assoc array of API response, decoded from JSON
142 - */
143 - public function delete($method, $args = array(), $timeout = self::TIMEOUT)
144 - {
145 - return $this->makeRequest('delete', $method, $args, $timeout);
146 - }
147 -
148 - /**
149 - * Make an HTTP GET request - for retrieving data
150 - *
151 - * @param string $method URL of the API request method
152 - * @param array $args Assoc array of arguments (usually your data)
153 - * @param int $timeout Timeout limit for request in seconds
154 - *
155 - * @return array|false Assoc array of API response, decoded from JSON
156 - */
157 - public function get($method, $args = array(), $timeout = self::TIMEOUT)
158 - {
159 - return $this->makeRequest('get', $method, $args, $timeout);
160 - }
161 -
162 - /**
163 - * Make an HTTP PATCH request - for performing partial updates
164 - *
165 - * @param string $method URL of the API request method
166 - * @param array $args Assoc array of arguments (usually your data)
167 - * @param int $timeout Timeout limit for request in seconds
168 - *
169 - * @return array|false Assoc array of API response, decoded from JSON
170 - */
171 - public function patch($method, $args = array(), $timeout = self::TIMEOUT)
172 - {
173 - return $this->makeRequest('patch', $method, $args, $timeout);
174 - }
175 -
176 - /**
177 - * Make an HTTP POST request - for creating and updating items
178 - *
179 - * @param string $method URL of the API request method
180 - * @param array $args Assoc array of arguments (usually your data)
181 - * @param int $timeout Timeout limit for request in seconds
182 - *
183 - * @return array|false Assoc array of API response, decoded from JSON
184 - */
185 - public function post($method, $args = array(), $timeout = self::TIMEOUT)
186 - {
187 - return $this->makeRequest('post', $method, $args, $timeout);
188 - }
189 -
190 - /**
191 - * Make an HTTP PUT request - for creating new items
192 - *
193 - * @param string $method URL of the API request method
194 - * @param array $args Assoc array of arguments (usually your data)
195 - * @param int $timeout Timeout limit for request in seconds
196 - *
197 - * @return array|false Assoc array of API response, decoded from JSON
198 - */
199 - public function put($method, $args = array(), $timeout = self::TIMEOUT)
200 - {
201 - return $this->makeRequest('put', $method, $args, $timeout);
202 - }
203 -
204 - /**
205 - * Performs the underlying HTTP request. Not very exciting.
206 - *
207 - * @param string $http_verb The HTTP verb to use: get, post, put, patch, delete
208 - * @param string $method The API method to be called
209 - * @param array $args Assoc array of parameters to be passed
210 - * @param int $timeout
211 - *
212 - * @return array|false Assoc array of decoded result
213 - */
214 - private function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
215 - {
216 - $url = $this->api_endpoint . '/' . $method;
217 -
218 - $response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
219 -
220 - $httpHeader = array(
221 - 'Accept: application/vnd.api+json',
222 - 'Content-Type: application/vnd.api+json',
223 - 'Authorization: apikey ' . $this->api_key
224 - );
225 -
226 - if (isset($args["language"])) {
227 - $httpHeader[] = "Accept-Language: " . $args["language"];
228 - }
229 -
230 - if ($http_verb === 'put') {
231 - $httpHeader[] = 'Allow: PUT, PATCH, POST';
232 - }
233 -
234 - $ch = curl_init();
235 - curl_setopt($ch, CURLOPT_URL, $url);
236 - curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
237 - curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
238 - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
239 - curl_setopt($ch, CURLOPT_VERBOSE, true);
240 - curl_setopt($ch, CURLOPT_HEADER, true);
241 - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
242 - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
243 - curl_setopt($ch, CURLOPT_ENCODING, '');
244 - curl_setopt($ch, CURLINFO_HEADER_OUT, true);
245 -
246 - switch ($http_verb) {
247 - case 'post':
248 - curl_setopt($ch, CURLOPT_POST, true);
249 - $this->attachRequestPayload($ch, $args);
250 - break;
251 -
252 - case 'get':
253 - $query = http_build_query($args, '', '&');
254 - curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
255 - break;
256 -
257 - case 'delete':
258 - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
259 - break;
260 -
261 - case 'patch':
262 - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
263 - $this->attachRequestPayload($ch, $args);
264 - break;
265 -
266 - case 'put':
267 - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
268 - $this->attachRequestPayload($ch, $args);
269 - break;
270 - }
271 -
272 - $responseContent = curl_exec($ch);
273 - $response['headers'] = curl_getinfo($ch);
274 - $response = $this->setResponseState($response, $responseContent, $ch);
275 - $formattedResponse = $this->formatResponse($response);
276 -
277 - curl_close($ch);
278 -
279 - $isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
280 -
281 - return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
282 - }
283 -
284 - /**
285 - * @param string $http_verb
286 - * @param string $method
287 - * @param string $url
288 - * @param integer $timeout
289 - *
290 - * @return array
291 - */
292 - private function prepareStateForRequest($http_verb, $method, $url, $timeout)
293 - {
294 - $this->last_error = '';
295 -
296 - $this->request_successful = false;
297 -
298 - $this->last_response = array(
299 - 'headers' => null, // array of details from curl_getinfo()
300 - 'httpHeaders' => null, // array of HTTP headers
301 - 'body' => null // content of the response
302 - );
303 -
304 - $this->last_request = array(
305 - 'method' => $http_verb,
306 - 'path' => $method,
307 - 'url' => $url,
308 - 'body' => '',
309 - 'timeout' => $timeout,
310 - );
311 -
312 - return $this->last_response;
313 - }
314 -
315 - /**
316 - * Get the HTTP headers as an array of header-name => header-value pairs.
317 - *
318 - * The "Link" header is parsed into an associative array based on the
319 - * rel names it contains. The original value is available under
320 - * the "_raw" key.
321 - *
322 - * @param string $headersAsString
323 - *
324 - * @return array
325 - */
326 - private function getHeadersAsArray($headersAsString)
327 - {
328 - $headers = array();
329 -
330 - foreach (explode("\r\n", $headersAsString) as $i => $line) {
331 - if (preg_match('/HTTP\/[1-2]/', substr($line, 0, 7)) === 1) { // http code
332 - continue;
333 - }
334 -
335 - $line = trim($line);
336 - if (empty($line)) {
337 - continue;
338 - }
339 -
340 - list($key, $value) = explode(': ', $line);
341 -
342 - if ($key == 'Link') {
343 - $value = array_merge(
344 - array('_raw' => $value),
345 - $this->getLinkHeaderAsArray($value)
346 - );
347 - }
348 -
349 - $headers[$key] = $value;
350 - }
351 -
352 - return $headers;
353 - }
354 -
355 - /**
356 - * Extract all rel => URL pairs from the provided Link header value
357 - *
358 - * Mailchimp only implements the URI reference and relation type from
359 - * RFC 5988, so the value of the header is something like this:
360 - *
361 - * 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy",
362 - * <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
363 - *
364 - * @param string $linkHeaderAsString
365 - *
366 - * @return array
367 - */
368 - private function getLinkHeaderAsArray($linkHeaderAsString)
369 - {
370 - $urls = array();
371 -
372 - if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
373 - foreach ($matches[2] as $i => $relName) {
374 - $urls[$relName] = $matches[1][$i];
375 - }
376 - }
377 -
378 - return $urls;
379 - }
380 -
381 - /**
382 - * Encode the data and attach it to the request
383 - *
384 - * @param resource $ch cURL session handle, used by reference
385 - * @param array $data Assoc array of data to attach
386 - */
387 - private function attachRequestPayload(&$ch, $data)
388 - {
389 - $encoded = json_encode($data);
390 - $this->last_request['body'] = $encoded;
391 - curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
392 - }
393 -
394 - /**
395 - * Decode the response and format any error messages for debugging
396 - *
397 - * @param array $response The response from the curl request
398 - *
399 - * @return array|false The JSON decoded into an array
400 - */
401 - private function formatResponse($response)
402 - {
403 - $this->last_response = $response;
404 -
405 - if (!empty($response['body'])) {
406 - return json_decode($response['body'], true);
407 - }
408 -
409 - return false;
410 - }
411 -
412 - /**
413 - * Do post-request formatting and setting state from the response
414 - *
415 - * @param array $response The response from the curl request
416 - * @param string $responseContent The body of the response from the curl request
417 - * @param resource $ch The curl resource
418 - *
419 - * @return array The modified response
420 - */
421 - private function setResponseState($response, $responseContent, $ch)
422 - {
423 - if ($responseContent === false) {
424 - $this->last_error = curl_error($ch);
425 - } else {
426 -
427 - $headerSize = $response['headers']['header_size'];
428 -
429 - $response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
430 - $response['body'] = substr($responseContent, $headerSize);
431 -
432 - if (isset($response['headers']['request_header'])) {
433 - $this->last_request['headers'] = $response['headers']['request_header'];
434 - }
435 - }
436 -
437 - return $response;
438 - }
439 -
440 - /**
441 - * Check if the response was successful or a failure. If it failed, store the error.
442 - *
443 - * @param array $response The response from the curl request
444 - * @param array|false $formattedResponse The response body payload from the curl request
445 - * @param int $timeout The timeout supplied to the curl request.
446 - *
447 - * @return bool If the request was successful
448 - */
449 - private function determineSuccess($response, $formattedResponse, $timeout)
450 - {
451 - $status = $this->findHTTPStatus($response, $formattedResponse);
452 -
453 - if ($status >= 200 && $status <= 299) {
454 - $this->request_successful = true;
455 - return true;
456 - }
457 -
458 - if (isset($formattedResponse['detail'])) {
459 - $this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
460 - return false;
461 - }
462 -
463 - if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
464 - $this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
465 - return false;
466 - }
467 -
468 - $this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
469 - return false;
470 - }
471 -
472 - /**
473 - * Find the HTTP status code from the headers or API response body
474 - *
475 - * @param array $response The response from the curl request
476 - * @param array|false $formattedResponse The response body payload from the curl request
477 - *
478 - * @return int HTTP status code
479 - */
480 - private function findHTTPStatus($response, $formattedResponse)
481 - {
482 - if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
483 - return (int)$response['headers']['http_code'];
484 - }
485 -
486 - if (!empty($response['body']) && isset($formattedResponse['status'])) {
487 - return (int)$formattedResponse['status'];
488 - }
489 -
490 - return 418;
491 - }
492 -}
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 <drew.mclellan@gmail.com>
11 + * @version 2.5
12 + */
13 +class MailChimp
14 +{
15 + private $api_key;
16 + private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
17 +
18 + const TIMEOUT = 10;
19 +
20 + /* SSL Verification
21 + Read before disabling:
22 + http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
23 + */
24 + public $verify_ssl = true;
25 +
26 + private $request_successful = false;
27 + private $last_error = '';
28 + private $last_response = array();
29 + private $last_request = array();
30 +
31 + /**
32 + * Create a new instance
33 + *
34 + * @param string $api_key Your MailChimp API key
35 + * @param string $api_endpoint Optional custom API endpoint
36 + *
37 + * @throws \Exception
38 + */
39 + public function __construct($api_key, $api_endpoint = null)
40 + {
41 + if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
42 + throw new \Exception("cURL support is required, but can't be found.");
43 + }
44 +
45 + $this->api_key = $api_key;
46 +
47 + if ($api_endpoint === null) {
48 + if (strpos($this->api_key, '-') === false) {
49 + throw new \Exception("Invalid MailChimp API key supplied.");
50 + }
51 + list(, $data_center) = explode('-', $this->api_key);
52 + $this->api_endpoint = str_replace('<dc>', $data_center, $this->api_endpoint);
53 + } else {
54 + $this->api_endpoint = $api_endpoint;
55 + }
56 +
57 + $this->last_response = array('headers' => null, 'body' => null);
58 + }
59 +
60 + /**
61 + * Create a new instance of a Batch request. Optionally with the ID of an existing batch.
62 + *
63 + * @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
64 + *
65 + * @return Batch New Batch object.
66 + */
67 + public function new_batch($batch_id = null)
68 + {
69 + return new Batch($this, $batch_id);
70 + }
71 +
72 + /**
73 + * @return string The url to the API endpoint
74 + */
75 + public function getApiEndpoint()
76 + {
77 + return $this->api_endpoint;
78 + }
79 +
80 +
81 + /**
82 + * Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
83 + *
84 + * @param string $email The subscriber's email address
85 + *
86 + * @return string Hashed version of the input
87 + */
88 + public static function subscriberHash($email)
89 + {
90 + return md5(strtolower($email));
91 + }
92 +
93 + /**
94 + * Was the last request successful?
95 + *
96 + * @return bool True for success, false for failure
97 + */
98 + public function success()
99 + {
100 + return $this->request_successful;
101 + }
102 +
103 + /**
104 + * Get the last error returned by either the network transport, or by the API.
105 + * If something didn't work, this should contain the string describing the problem.
106 + *
107 + * @return string|false describing the error
108 + */
109 + public function getLastError()
110 + {
111 + return $this->last_error ?: false;
112 + }
113 +
114 + /**
115 + * Get an array containing the HTTP headers and the body of the API response.
116 + *
117 + * @return array Assoc array with keys 'headers' and 'body'
118 + */
119 + public function getLastResponse()
120 + {
121 + return $this->last_response;
122 + }
123 +
124 + /**
125 + * Get an array containing the HTTP headers and the body of the API request.
126 + *
127 + * @return array Assoc array
128 + */
129 + public function getLastRequest()
130 + {
131 + return $this->last_request;
132 + }
133 +
134 + /**
135 + * Make an HTTP DELETE request - for deleting data
136 + *
137 + * @param string $method URL of the API request method
138 + * @param array $args Assoc array of arguments (if any)
139 + * @param int $timeout Timeout limit for request in seconds
140 + *
141 + * @return array|false Assoc array of API response, decoded from JSON
142 + */
143 + public function delete($method, $args = array(), $timeout = self::TIMEOUT)
144 + {
145 + return $this->makeRequest('delete', $method, $args, $timeout);
146 + }
147 +
148 + /**
149 + * Make an HTTP GET request - for retrieving data
150 + *
151 + * @param string $method URL of the API request method
152 + * @param array $args Assoc array of arguments (usually your data)
153 + * @param int $timeout Timeout limit for request in seconds
154 + *
155 + * @return array|false Assoc array of API response, decoded from JSON
156 + */
157 + public function get($method, $args = array(), $timeout = self::TIMEOUT)
158 + {
159 + return $this->makeRequest('get', $method, $args, $timeout);
160 + }
161 +
162 + /**
163 + * Make an HTTP PATCH request - for performing partial updates
164 + *
165 + * @param string $method URL of the API request method
166 + * @param array $args Assoc array of arguments (usually your data)
167 + * @param int $timeout Timeout limit for request in seconds
168 + *
169 + * @return array|false Assoc array of API response, decoded from JSON
170 + */
171 + public function patch($method, $args = array(), $timeout = self::TIMEOUT)
172 + {
173 + return $this->makeRequest('patch', $method, $args, $timeout);
174 + }
175 +
176 + /**
177 + * Make an HTTP POST request - for creating and updating items
178 + *
179 + * @param string $method URL of the API request method
180 + * @param array $args Assoc array of arguments (usually your data)
181 + * @param int $timeout Timeout limit for request in seconds
182 + *
183 + * @return array|false Assoc array of API response, decoded from JSON
184 + */
185 + public function post($method, $args = array(), $timeout = self::TIMEOUT)
186 + {
187 + return $this->makeRequest('post', $method, $args, $timeout);
188 + }
189 +
190 + /**
191 + * Make an HTTP PUT request - for creating new items
192 + *
193 + * @param string $method URL of the API request method
194 + * @param array $args Assoc array of arguments (usually your data)
195 + * @param int $timeout Timeout limit for request in seconds
196 + *
197 + * @return array|false Assoc array of API response, decoded from JSON
198 + */
199 + public function put($method, $args = array(), $timeout = self::TIMEOUT)
200 + {
201 + return $this->makeRequest('put', $method, $args, $timeout);
202 + }
203 +
204 + /**
205 + * Performs the underlying HTTP request. Not very exciting.
206 + *
207 + * @param string $http_verb The HTTP verb to use: get, post, put, patch, delete
208 + * @param string $method The API method to be called
209 + * @param array $args Assoc array of parameters to be passed
210 + * @param int $timeout
211 + *
212 + * @return array|false Assoc array of decoded result
213 + */
214 + private function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
215 + {
216 + $url = $this->api_endpoint . '/' . $method;
217 +
218 + $response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
219 +
220 + $httpHeader = array(
221 + 'Accept: application/vnd.api+json',
222 + 'Content-Type: application/vnd.api+json',
223 + 'Authorization: apikey ' . $this->api_key
224 + );
225 +
226 + if (isset($args["language"])) {
227 + $httpHeader[] = "Accept-Language: " . $args["language"];
228 + }
229 +
230 + if ($http_verb === 'put') {
231 + $httpHeader[] = 'Allow: PUT, PATCH, POST';
232 + }
233 +
234 + $ch = curl_init();
235 + curl_setopt($ch, CURLOPT_URL, $url);
236 + curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
237 + curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
238 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
239 + curl_setopt($ch, CURLOPT_VERBOSE, true);
240 + curl_setopt($ch, CURLOPT_HEADER, true);
241 + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
242 + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
243 + curl_setopt($ch, CURLOPT_ENCODING, '');
244 + curl_setopt($ch, CURLINFO_HEADER_OUT, true);
245 +
246 + switch ($http_verb) {
247 + case 'post':
248 + curl_setopt($ch, CURLOPT_POST, true);
249 + $this->attachRequestPayload($ch, $args);
250 + break;
251 +
252 + case 'get':
253 + $query = http_build_query($args, '', '&');
254 + curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
255 + break;
256 +
257 + case 'delete':
258 + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
259 + break;
260 +
261 + case 'patch':
262 + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
263 + $this->attachRequestPayload($ch, $args);
264 + break;
265 +
266 + case 'put':
267 + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
268 + $this->attachRequestPayload($ch, $args);
269 + break;
270 + }
271 +
272 + $responseContent = curl_exec($ch);
273 + $response['headers'] = curl_getinfo($ch);
274 + $response = $this->setResponseState($response, $responseContent, $ch);
275 + $formattedResponse = $this->formatResponse($response);
276 +
277 + curl_close($ch);
278 +
279 + $isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
280 +
281 + return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
282 + }
283 +
284 + /**
285 + * @param string $http_verb
286 + * @param string $method
287 + * @param string $url
288 + * @param integer $timeout
289 + *
290 + * @return array
291 + */
292 + private function prepareStateForRequest($http_verb, $method, $url, $timeout)
293 + {
294 + $this->last_error = '';
295 +
296 + $this->request_successful = false;
297 +
298 + $this->last_response = array(
299 + 'headers' => null, // array of details from curl_getinfo()
300 + 'httpHeaders' => null, // array of HTTP headers
301 + 'body' => null // content of the response
302 + );
303 +
304 + $this->last_request = array(
305 + 'method' => $http_verb,
306 + 'path' => $method,
307 + 'url' => $url,
308 + 'body' => '',
309 + 'timeout' => $timeout,
310 + );
311 +
312 + return $this->last_response;
313 + }
314 +
315 + /**
316 + * Get the HTTP headers as an array of header-name => header-value pairs.
317 + *
318 + * The "Link" header is parsed into an associative array based on the
319 + * rel names it contains. The original value is available under
320 + * the "_raw" key.
321 + *
322 + * @param string $headersAsString
323 + *
324 + * @return array
325 + */
326 + private function getHeadersAsArray($headersAsString)
327 + {
328 + $headers = array();
329 +
330 + foreach (explode("\r\n", $headersAsString) as $i => $line) {
331 + if (preg_match('/HTTP\/[1-2]/', substr($line, 0, 7)) === 1) { // http code
332 + continue;
333 + }
334 +
335 + $line = trim($line);
336 + if (empty($line)) {
337 + continue;
338 + }
339 +
340 + list($key, $value) = explode(': ', $line);
341 +
342 + if ($key == 'Link') {
343 + $value = array_merge(
344 + array('_raw' => $value),
345 + $this->getLinkHeaderAsArray($value)
346 + );
347 + }
348 +
349 + $headers[$key] = $value;
350 + }
351 +
352 + return $headers;
353 + }
354 +
355 + /**
356 + * Extract all rel => URL pairs from the provided Link header value
357 + *
358 + * Mailchimp only implements the URI reference and relation type from
359 + * RFC 5988, so the value of the header is something like this:
360 + *
361 + * 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy",
362 + * <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
363 + *
364 + * @param string $linkHeaderAsString
365 + *
366 + * @return array
367 + */
368 + private function getLinkHeaderAsArray($linkHeaderAsString)
369 + {
370 + $urls = array();
371 +
372 + if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
373 + foreach ($matches[2] as $i => $relName) {
374 + $urls[$relName] = $matches[1][$i];
375 + }
376 + }
377 +
378 + return $urls;
379 + }
380 +
381 + /**
382 + * Encode the data and attach it to the request
383 + *
384 + * @param resource $ch cURL session handle, used by reference
385 + * @param array $data Assoc array of data to attach
386 + */
387 + private function attachRequestPayload(&$ch, $data)
388 + {
389 + $encoded = json_encode($data);
390 + $this->last_request['body'] = $encoded;
391 + curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
392 + }
393 +
394 + /**
395 + * Decode the response and format any error messages for debugging
396 + *
397 + * @param array $response The response from the curl request
398 + *
399 + * @return array|false The JSON decoded into an array
400 + */
401 + private function formatResponse($response)
402 + {
403 + $this->last_response = $response;
404 +
405 + if (!empty($response['body'])) {
406 + return json_decode($response['body'], true);
407 + }
408 +
409 + return false;
410 + }
411 +
412 + /**
413 + * Do post-request formatting and setting state from the response
414 + *
415 + * @param array $response The response from the curl request
416 + * @param string $responseContent The body of the response from the curl request
417 + * @param resource $ch The curl resource
418 + *
419 + * @return array The modified response
420 + */
421 + private function setResponseState($response, $responseContent, $ch)
422 + {
423 + if ($responseContent === false) {
424 + $this->last_error = curl_error($ch);
425 + } else {
426 +
427 + $headerSize = $response['headers']['header_size'];
428 +
429 + $response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
430 + $response['body'] = substr($responseContent, $headerSize);
431 +
432 + if (isset($response['headers']['request_header'])) {
433 + $this->last_request['headers'] = $response['headers']['request_header'];
434 + }
435 + }
436 +
437 + return $response;
438 + }
439 +
440 + /**
441 + * Check if the response was successful or a failure. If it failed, store the error.
442 + *
443 + * @param array $response The response from the curl request
444 + * @param array|false $formattedResponse The response body payload from the curl request
445 + * @param int $timeout The timeout supplied to the curl request.
446 + *
447 + * @return bool If the request was successful
448 + */
449 + private function determineSuccess($response, $formattedResponse, $timeout)
450 + {
451 + $status = $this->findHTTPStatus($response, $formattedResponse);
452 +
453 + if ($status >= 200 && $status <= 299) {
454 + $this->request_successful = true;
455 + return true;
456 + }
457 +
458 + if (isset($formattedResponse['detail'])) {
459 + $this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
460 + return false;
461 + }
462 +
463 + if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
464 + $this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
465 + return false;
466 + }
467 +
468 + $this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
469 + return false;
470 + }
471 +
472 + /**
473 + * Find the HTTP status code from the headers or API response body
474 + *
475 + * @param array $response The response from the curl request
476 + * @param array|false $formattedResponse The response body payload from the curl request
477 + *
478 + * @return int HTTP status code
479 + */
480 + private function findHTTPStatus($response, $formattedResponse)
481 + {
482 + if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
483 + return (int)$response['headers']['http_code'];
484 + }
485 +
486 + if (!empty($response['body']) && isset($formattedResponse['status'])) {
487 + return (int)$formattedResponse['status'];
488 + }
489 +
490 + return 418;
491 + }
492 +}