vendor/drewm/mailchimp-api/src/MailChimp.php line 48

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