%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
<?php
/**
* HTTP Client library
*
* @author Matt Bernier <dx@sendgrid.com>
* @author Elmer Thomas <dx@sendgrid.com>
* @copyright 2018 SendGrid
* @license https://opensource.org/licenses/MIT The MIT License
* @version GIT: <git_id>
* @link http://packagist.org/packages/sendgrid/php-http-client
*/
namespace SendGrid;
/**
*
* Class Client
* @package SendGrid
* @version 3.9.5
*
* Quickly and easily access any REST or REST-like API.
*
* @method Response get($body = null, $query = null, $headers = null)
* @method Response post($body = null, $query = null, $headers = null)
* @method Response patch($body = null, $query = null, $headers = null)
* @method Response put($body = null, $query = null, $headers = null)
* @method Response delete($body = null, $query = null, $headers = null)
*
* @method Client version($value)
* @method Client|Response send()
*
* Adding all the endpoints as a method so code completion works
*
* General
* @method Client stats()
* @method Client search()
* @method Client monthly()
* @method Client sums()
* @method Client monitor()
* @method Client test()
*
* Access settings
* @method Client access_settings()
* @method Client activity()
* @method Client whitelist()
*
* Alerts
* @method Client alerts()
*
* Api keys
* @method Client api_keys()
*
* ASM
* @method Client asm()
* @method Client groups()
*
* Browsers
* @method Client browsers()
*
* Campaigns
* @method Client campaigns()
* @method Client schedules()
* @method Client now()
*
* Categories
* @method Client categories()
*
* Clients
* @method Client clients()
*
* ContactDB
* @method Client contactdb()
* @method Client custom_fields()
* @method Client lists()
* @method Client recipients()
* @method Client billable_count()
* @method Client count()
* @method Client reserved_fields()
* @method Client segments()
*
* Devices
* @method Client devices()
*
* Geo
* @method Client geo()
*
* Ips
* @method Client ips()
* @method Client assigned()
* @method Client pools()
* @method Client warmup()
*
* Mail
* @method Client mail()
* @method Client batch()
*
* Mailbox Providers
* @method Client mailbox_providers()
*
* Mail settings
* @method Client mail_settings()
* @method Client address_whitelist()
* @method Client bcc()
* @method Client bounce_purge()
* @method Client footer()
* @method Client forward_bounce()
* @method Client forward_spam()
* @method Client plain_content()
* @method Client spam_check()
* @method Client template()
*
* Partner settings
* @method Client partner_settings()
* @method Client new_relic()
*
* Scopes
* @method Client scopes()
*
* Senders
* @method Client senders()
* @method Client resend_verification()
*
* Sub Users
* @method Client subusers()
* @method Client reputations()
*
* Supressions
* @method Client suppressions()
* @method Client global()
* @method Client blocks()
* @method Client bounces()
* @method Client invalid_emails()
* @method Client spam_reports()
* @method Client unsubcribes()
*
* Templates
* @method Client templates()
* @method Client versions()
* @method Client activate()
*
* Tracking settings
* @method Client tracking_settings()
* @method Client click()
* @method Client google_analytics()
* @method Client open()
* @method Client subscription()
*
* User
* @method Client user()
* @method Client account()
* @method Client credits()
* @method Client email()
* @method Client password()
* @method Client profile()
* @method Client scheduled_sends()
* @method Client enforced_tls()
* @method Client settings()
* @method Client username()
* @method Client webhooks()
* @method Client event()
* @method Client parse()
*
* Missed any? Simply add them by doing: @method Client method()
*/
class Client
{
const TOO_MANY_REQUESTS_HTTP_CODE = 429;
/**
* @var string
*/
protected $host;
/**
* @var array
*/
protected $headers;
/**
* @var string
*/
protected $version;
/**
* @var array
*/
protected $path;
/**
* @var array
*/
protected $curlOptions;
/**
* @var bool
*/
protected $isConcurrentRequest;
/**
* @var array
*/
protected $savedRequests;
/**
* @var bool
*/
protected $retryOnLimit;
/**
* These are the supported HTTP verbs
*
* @var array
*/
private $methods = ['get', 'post', 'patch', 'put', 'delete'];
/**
* Initialize the client
*
* @param string $host the base url (e.g. https://api.sendgrid.com)
* @param array $headers global request headers
* @param string $version api version (configurable) - this is specific to the SendGrid API
* @param array $path holds the segments of the url path
* @param array $curlOptions extra options to set during curl initialization
* @param bool $retryOnLimit set default retry on limit flag
*/
public function __construct($host, $headers = null, $version = null, $path = null, $curlOptions = null, $retryOnLimit = false)
{
$this->host = $host;
$this->headers = $headers ?: [];
$this->version = $version;
$this->path = $path ?: [];
$this->curlOptions = $curlOptions ?: [];
$this->retryOnLimit = $retryOnLimit;
$this->isConcurrentRequest = false;
$this->savedRequests = [];
}
/**
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* @return string|null
*/
public function getVersion()
{
return $this->version;
}
/**
* @return array
*/
public function getPath()
{
return $this->path;
}
/**
* @return array
*/
public function getCurlOptions()
{
return $this->curlOptions;
}
/**
* Set extra options to set during curl initialization
*
* @param array $options
*
* @return Client
*/
public function setCurlOptions(array $options)
{
$this->curlOptions = $options;
return $this;
}
/**
* Set default retry on limit flag
*
* @param bool $retry
*
* @return Client
*/
public function setRetryOnLimit($retry)
{
$this->retryOnLimit = $retry;
return $this;
}
/**
* Set concurrent request flag
*
* @param bool $isConcurrent
*
* @return Client
*/
public function setIsConcurrentRequest($isConcurrent)
{
$this->isConcurrentRequest = $isConcurrent;
return $this;
}
/**
* Build the final URL to be passed
*
* @param array $queryParams an array of all the query parameters
*
* @return string
*/
private function buildUrl($queryParams = null)
{
$path = '/' . implode('/', $this->path);
if (isset($queryParams)) {
$path .= '?' . http_build_query($queryParams);
}
return sprintf('%s%s%s', $this->host, $this->version ?: '', $path);
}
/**
* Creates curl options for a request
* this function does not mutate any private variables
*
* @param string $method
* @param array $body
* @param array $headers
*
* @return array
*/
private function createCurlOptions($method, $body = null, $headers = null)
{
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_FAILONERROR => false
] + $this->curlOptions;
if (isset($headers)) {
$headers = array_merge($this->headers, $headers);
} else {
$headers = $this->headers;
}
if (isset($body)) {
$encodedBody = json_encode($body);
$options[CURLOPT_POSTFIELDS] = $encodedBody;
$headers = array_merge($headers, ['Content-Type: application/json']);
}
$options[CURLOPT_HTTPHEADER] = $headers;
return $options;
}
/**
* @param array $requestData
* e.g. ['method' => 'POST', 'url' => 'www.example.com', 'body' => 'test body', 'headers' => []]
* @param bool $retryOnLimit
*
* @return array
*/
private function createSavedRequest(array $requestData, $retryOnLimit = false)
{
return array_merge($requestData, ['retryOnLimit' => $retryOnLimit]);
}
/**
* @param array $requests
*
* @return array
*/
private function createCurlMultiHandle(array $requests)
{
$channels = [];
$multiHandle = curl_multi_init();
foreach ($requests as $id => $data) {
$channels[$id] = curl_init($data['url']);
$curlOpts = $this->createCurlOptions($data['method'], $data['body'], $data['headers']);
curl_setopt_array($channels[$id], $curlOpts);
curl_multi_add_handle($multiHandle, $channels[$id]);
}
return [$channels, $multiHandle];
}
/**
* Prepare response object
*
* @param resource $channel the curl resource
* @param string $content
*
* @return Response object
*/
private function parseResponse($channel, $content)
{
$headerSize = curl_getinfo($channel, CURLINFO_HEADER_SIZE);
$statusCode = curl_getinfo($channel, CURLINFO_HTTP_CODE);
$responseBody = substr($content, $headerSize);
$responseHeaders = substr($content, 0, $headerSize);
$responseHeaders = explode("\n", $responseHeaders);
$responseHeaders = array_map('trim', $responseHeaders);
return new Response($statusCode, $responseBody, $responseHeaders);
}
/**
* Retry request
*
* @param array $responseHeaders headers from rate limited response
* @param string $method the HTTP verb
* @param string $url the final url to call
* @param array $body request body
* @param array $headers original headers
*
* @return Response response object
*/
private function retryRequest(array $responseHeaders, $method, $url, $body, $headers)
{
$sleepDurations = $responseHeaders['X-Ratelimit-Reset'] - time();
sleep($sleepDurations > 0 ? $sleepDurations : 0);
return $this->makeRequest($method, $url, $body, $headers, false);
}
/**
* Make the API call and return the response.
* This is separated into it's own function, so we can mock it easily for testing.
*
* @param string $method the HTTP verb
* @param string $url the final url to call
* @param array $body request body
* @param array $headers any additional request headers
* @param bool $retryOnLimit should retry if rate limit is reach?
*
* @return Response object
*/
public function makeRequest($method, $url, $body = null, $headers = null, $retryOnLimit = false)
{
$channel = curl_init($url);
$options = $this->createCurlOptions($method, $body, $headers);
curl_setopt_array($channel, $options);
$content = curl_exec($channel);
$response = $this->parseResponse($channel, $content);
if ($response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE && $retryOnLimit) {
$responseHeaders = $response->headers(true);
return $this->retryRequest($responseHeaders, $method, $url, $body, $headers);
}
curl_close($channel);
return $response;
}
/**
* Send all saved requests at once
*
* @param array $requests
*
* @return Response[]
*/
public function makeAllRequests(array $requests = [])
{
if (empty($requests)) {
$requests = $this->savedRequests;
}
list($channels, $multiHandle) = $this->createCurlMultiHandle($requests);
// running all requests
$isRunning = null;
do {
curl_multi_exec($multiHandle, $isRunning);
} while ($isRunning);
// get response and close all handles
$retryRequests = [];
$responses = [];
$sleepDurations = 0;
foreach ($channels as $id => $channel) {
$content = curl_multi_getcontent($channel);
$response = $this->parseResponse($channel, $content);
if ($response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE && $requests[$id]['retryOnLimit']) {
$headers = $response->headers(true);
$sleepDurations = max($sleepDurations, $headers['X-Ratelimit-Reset'] - time());
$requestData = [
'method' => $requests[$id]['method'],
'url' => $requests[$id]['url'],
'body' => $requests[$id]['body'],
'headers' => $headers,
];
$retryRequests[] = $this->createSavedRequest($requestData, false);
} else {
$responses[] = $response;
}
curl_multi_remove_handle($multiHandle, $channel);
}
curl_multi_close($multiHandle);
// retry requests
if (!empty($retryRequests)) {
sleep($sleepDurations > 0 ? $sleepDurations : 0);
$responses = array_merge($responses, $this->makeAllRequests($retryRequests));
}
return $responses;
}
/**
* Add variable values to the url. (e.g. /your/api/{variable_value}/call)
* Another example: if you have a PHP reserved word, such as and, in your url, you must use this method.
*
* @param string $name name of the url segment
*
* @return Client object
*/
public function _($name = null)
{
if (isset($name)) {
$this->path[] = $name;
}
$client = new static($this->host, $this->headers, $this->version, $this->path);
$client->setCurlOptions($this->curlOptions);
$client->setRetryOnLimit($this->retryOnLimit);
$this->path = [];
return $client;
}
/**
* Dynamically add method calls to the url, then call a method.
* (e.g. client.name.name.method())
*
* @param string $name name of the dynamic method call or HTTP verb
* @param array $args parameters passed with the method call
*
* @return Client|Response|Response[]|null object
*/
public function __call($name, $args)
{
$name = strtolower($name);
if ($name === 'version') {
$this->version = $args[0];
return $this->_();
}
// send all saved requests
if (($name === 'send') && $this->isConcurrentRequest) {
return $this->makeAllRequests();
}
if (in_array($name, $this->methods, true)) {
$body = isset($args[0]) ? $args[0] : null;
$queryParams = isset($args[1]) ? $args[1] : null;
$url = $this->buildUrl($queryParams);
$headers = isset($args[2]) ? $args[2] : null;
$retryOnLimit = isset($args[3]) ? $args[3] : $this->retryOnLimit;
if ($this->isConcurrentRequest) {
// save request to be sent later
$requestData = ['method' => $name, 'url' => $url, 'body' => $body, 'headers' => $headers];
$this->savedRequests[] = $this->createSavedRequest($requestData, $retryOnLimit);
return null;
}
return $this->makeRequest($name, $url, $body, $headers, $retryOnLimit);
}
return $this->_($name);
}
}