first commit

This commit is contained in:
2024-12-17 13:43:22 +01:00
commit 8e6cd8b410
21292 changed files with 3514826 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
MC_API_KEY=""
MC_LIST_ID=""

View File

@@ -0,0 +1,8 @@
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.scrutinizer.yml export-ignore
/.travis.yml export-ignore
/phpunit.xml export-ignore
/.env export-ignore
/.env.example export-ignore

View File

@@ -0,0 +1,17 @@
## Submitting issues
This is quite a popular project, but it's not my job, so please read the below before posting an issue. Thank you!
- If you have high-level implementation questions about your project ("How do I add this to WordPress", "I've got a form that takes an email address...") you're best to ask somewhere like StackOverflow.
- If you have purchased a commercial product or template that uses this code and now have a problem, *I'm not going to help you with it, sorry.* Talk to the person who took your money. None of it came to me. :smile:
- If your question is about the MailChimp API itself, please check out the [MailChimp Guides](http://developer.mailchimp.com/documentation/mailchimp/guides/). This project doesn't handle any of that logic - we're just helping you form the requests.
If, however, you think you've found a bug, or would like to discuss a change or improvement, feel free to raise an issue and we'll figure it out between us.
## Pull requests
This is a fairly simple wrapper, but it has been made much better by contributions from those using it. If you'd like to suggest an improvement, please raise an issue to discuss it before making your pull request.
Pull requests for bugs are more than welcome - please explain the bug you're trying to fix in the message.
There are a small number of PHPUnit unit tests. To get up and running, copy `.env.example` to `.env` and add your API key details. Unit testing against an API is obviously a bit tricky, but I'd welcome any contributions to this. It would be great to have more test coverage.

View File

@@ -0,0 +1,34 @@
<?php
/**
* 2007-2018 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2018 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;

View File

@@ -0,0 +1,4 @@
.env
/vendor
composer.lock
.idea

View File

@@ -0,0 +1,10 @@
# .scrutinizer.yml
checks:
php: true
filter:
excluded_paths:
- tests/*
- vendor/*
paths:
- src/*

View File

@@ -0,0 +1,14 @@
language: php
sudo: true
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
before_script:
- composer self-update
- composer install --prefer-source --no-interaction --dev --ignore-platform-reqs
script:
- ./scripts/travis.sh

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Drew McLellan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,225 @@
MailChimp API
=============
Super-simple, minimum abstraction MailChimp API v3 wrapper, in PHP.
I hate complex wrappers. This lets you get from the MailChimp API docs to the code as directly as possible.
Requires PHP 5.3 and a pulse. Abstraction is for chimps.
[![Build Status](https://travis-ci.org/drewm/mailchimp-api.svg?branch=master)](https://travis-ci.org/drewm/mailchimp-api)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/drewm/mailchimp-api/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/drewm/mailchimp-api/?branch=master)
[![Packagist](https://img.shields.io/packagist/dt/drewm/mailchimp-api.svg?maxAge=2592000)](https://packagist.org/packages/drewm/mailchimp-api)
Installation
------------
You can install mailchimp-api using Composer:
```
composer require drewm/mailchimp-api
```
You will then need to:
* run ``composer install`` to get these dependencies added to your vendor directory
* add the autoloader to your application with this line: ``require("vendor/autoload.php")``
Alternatively you can just download the `MailChimp.php` file and include it manually:
```php
include('./MailChimp.php');
```
If you wish to use the batch request or webhook interfaces, you'll also need to download and include the `Batch.php` or `Webhook.php` files:
```php
include('./Batch.php');
include('./Webhook.php');
```
These are optional. If you're not using batches or webhooks you can just skip them. You can always come back and add them later.
Examples
--------
Start by `use`-ing the class and creating an instance with your API key
```php
use \DrewM\MailChimp\MailChimp;
$MailChimp = new MailChimp('abc123abc123abc123abc123abc123-us1');
```
Then, list all the mailing lists (with a `get` on the `lists` method)
```php
$result = $MailChimp->get('lists');
print_r($result);
```
Subscribe someone to a list (with a `post` to the `lists/{listID}/members` method):
```php
$list_id = 'b1234346';
$result = $MailChimp->post("lists/$list_id/members", [
'email_address' => 'davy@example.com',
'status' => 'subscribed',
]);
print_r($result);
```
Update a list member with more information (using `patch` to update):
```php
$list_id = 'b1234346';
$subscriber_hash = $MailChimp->subscriberHash('davy@example.com');
$result = $MailChimp->patch("lists/$list_id/members/$subscriber_hash", [
'merge_fields' => ['FNAME'=>'Davy', 'LNAME'=>'Jones'],
'interests' => ['2s3a384h' => true],
]);
print_r($result);
```
Remove a list member using the `delete` method:
```php
$list_id = 'b1234346';
$subscriber_hash = $MailChimp->subscriberHash('davy@example.com');
$MailChimp->delete("lists/$list_id/members/$subscriber_hash");
```
Quickly test for a successful action with the `success()` method:
```php
$list_id = 'b1234346';
$result = $MailChimp->post("lists/$list_id/members", [
'email_address' => 'davy@example.com',
'status' => 'subscribed',
]);
if ($MailChimp->success()) {
print_r($result);
} else {
echo $MailChimp->getLastError();
}
```
Batch Operations
----------------
The MailChimp [Batch Operations](http://developer.mailchimp.com/documentation/mailchimp/guides/how-to-use-batch-operations/) functionality enables you to complete multiple operations with a single call. A good example is adding thousands of members to a list - you can perform this in one request rather than thousands.
```php
use \DrewM\MailChimp\MailChimp;
use \DrewM\MailChimp\Batch;
$MailChimp = new MailChimp('abc123abc123abc123abc123abc123-us1');
$Batch = $MailChimp->new_batch();
```
You can then make requests on the `Batch` object just as you would normally with the `MailChimp` object. The difference is that you need to set an ID for the operation as the first argument, and also that you won't get a response. The ID is used for finding the result of this request in the combined response from the batch operation.
```php
$Batch->post("op1", "lists/$list_id/members", [
'email_address' => 'micky@example.com',
'status' => 'subscribed',
]);
$Batch->post("op2", "lists/$list_id/members", [
'email_address' => 'michael@example.com',
'status' => 'subscribed',
]);
$Batch->post("op3", "lists/$list_id/members", [
'email_address' => 'peter@example.com',
'status' => 'subscribed',
]);
```
Once you've finished all the requests that should be in the batch, you need to execute it.
```php
$result = $Batch->execute();
```
The result includes a batch ID. At a later point, you can check the status of your batch:
```php
$MailChimp->new_batch($batch_id);
$result = $Batch->check_status();
```
When your batch is finished, you can download the results from the URL given in the response. In the JSON, the result of each operation will be keyed by the ID you used as the first argument for the request.
Webhooks
--------
**Note:** Use of the Webhooks functionality requires at least PHP 5.4.
MailChimp [webhooks](http://kb.mailchimp.com/integrations/other-integrations/how-to-set-up-webhooks) enable your code to be notified of changes to lists and campaigns.
When you set up a webhook you specify a URL on your server for the data to be sent to. This wrapper's Webhook class helps you catch that incoming webhook in a tidy way. It uses a subscription model, with your code subscribing to whichever webhook events it wants to listen for. You provide a callback function that the webhook data is passed to.
To listen for the `unsubscribe` webhook:
```php
use \DrewM\MailChimp\Webhook;
Webhook::subscribe('unsubscribe', function($data){
print_r($data);
});
```
At first glance the _subscribe/unsubscribe_ looks confusing - your code is subscribing to the MailChimp `unsubscribe` webhook event. The callback function is passed as single argument - an associative array containing the webhook data.
If you'd rather just catch all webhooks and deal with them yourself, you can use:
```php
use \DrewM\MailChimp\Webhook;
$result = Webhook::receive();
print_r($result);
```
There doesn't appear to be any documentation for the content of the webhook data. It's helpful to use something like [ngrok](https://ngrok.com) for tunneling the webhooks to your development machine - you can then use its web interface to inspect what's been sent and to replay incoming webhooks while you debug your code.
Troubleshooting
---------------
To get the last error returned by either the HTTP client or by the API, use `getLastError()`:
```php
echo $MailChimp->getLastError();
```
For further debugging, you can inspect the headers and body of the response:
```php
print_r($MailChimp->getLastResponse());
```
If you suspect you're sending data in the wrong format, you can look at what was sent to MailChimp by the wrapper:
```php
print_r($MailChimp->getLastRequest());
```
If your server's CA root certificates are not up to date you may find that SSL verification fails and you don't get a response. The correction solution for this [is not to disable SSL verification](http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/). The solution is to update your certificates. If you can't do that, there's an option at the top of the class file. Please don't just switch it off without at least attempting to update your certs -- that's lazy and dangerous. You're not a lazy, dangerous developer are you?
If you have **high-level implementation questions about your project** ("How do I add this to WordPress", "I've got a form that takes an email address...") please **take them to somewhere like StackOverflow**. If you think you've found a bug, or would like to discuss a change or improvement, feel free to raise an issue and we'll figure it out between us.
Contributing
------------
This is a fairly simple wrapper, but it has been made much better by contributions from those using it. If you'd like to suggest an improvement, please raise an issue to discuss it before making your pull request.
Pull requests for bugs are more than welcome - please explain the bug you're trying to fix in the message.
There are a small number of PHPUnit unit tests. To get up and running, copy `.env.example` to `.env` and add your API key details. Unit testing against an API is obviously a bit tricky, but I'd welcome any contributions to this. It would be great to have more test coverage.

View File

@@ -0,0 +1,40 @@
{
"name": "drewm/mailchimp-api",
"description": "Super-simple, minimum abstraction MailChimp API v3 wrapper",
"license": "MIT",
"require": {
"php": ">=5.6",
"ext-curl": "*",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "7.0.*",
"vlucas/phpdotenv": "^2.0"
},
"autoload": {
"psr-4": {
"DrewM\\MailChimp\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"DrewM\\MailChimp\\Tests\\": "tests/"
}
},
"homepage": "https://github.com/drewm/mailchimp-api",
"authors": [
{
"name": "Drew McLellan",
"email": "drew.mclellan@gmail.com",
"homepage": "http://allinthehead.com/"
},
{
"name": "Zoltan Szanto",
"email": "mrbig00@gmail.com",
"homepage": "http://allinthehead.com/"
}
]
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* 2007-2018 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2018 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="./tests/bootstrap.php"
colors="true"
failOnRisky="true"
failOnWarning="true"
stopOnFailure="false">
<testsuites>
<testsuite name="MailChimp Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,34 @@
<?php
/**
* 2007-2018 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2018 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;

View File

@@ -0,0 +1,11 @@
#!/bin/bash
cd ${TRAVIS_BUILD_DIR}
if [[ $TRAVIS_PHP_VERSION = '7.1' ]]; then
phpunit
fi
if [[ $TRAVIS_PHP_VERSION = '7.2' ]]; then
phpunit
fi

View File

@@ -0,0 +1,170 @@
<?php
namespace DrewM\MailChimp;
/**
* A MailChimp Batch operation.
* http://developer.mailchimp.com/documentation/mailchimp/reference/batches/
*
* @author Drew McLellan <drew.mclellan@gmail.com>
*/
class Batch
{
private $MailChimp;
private $operations = array();
private $batch_id;
public function __construct(MailChimp $MailChimp, $batch_id = null)
{
$this->MailChimp = $MailChimp;
$this->batch_id = $batch_id;
}
/**
* Add an HTTP DELETE request operation to the batch - for deleting data
*
* @param string $id ID for the operation within the batch
* @param string $method URL of the API request method
*
* @return void
*/
public function delete($id, $method)
{
$this->queueOperation('DELETE', $id, $method);
}
/**
* Add an HTTP GET request operation to the batch - for retrieving data
*
* @param string $id ID for the operation within the batch
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
*
* @return void
*/
public function get($id, $method, $args = array())
{
$this->queueOperation('GET', $id, $method, $args);
}
/**
* Add an HTTP PATCH request operation to the batch - for performing partial updates
*
* @param string $id ID for the operation within the batch
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
*
* @return void
*/
public function patch($id, $method, $args = array())
{
$this->queueOperation('PATCH', $id, $method, $args);
}
/**
* Add an HTTP POST request operation to the batch - for creating and updating items
*
* @param string $id ID for the operation within the batch
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
*
* @return void
*/
public function post($id, $method, $args = array())
{
$this->queueOperation('POST', $id, $method, $args);
}
/**
* Add an HTTP PUT request operation to the batch - for creating new items
*
* @param string $id ID for the operation within the batch
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
*
* @return void
*/
public function put($id, $method, $args = array())
{
$this->queueOperation('PUT', $id, $method, $args);
}
/**
* Execute the batch request
*
* @param int $timeout Request timeout in seconds (optional)
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function execute($timeout = 10)
{
$req = array('operations' => $this->operations);
$result = $this->MailChimp->post('batches', $req, $timeout);
if ($result && isset($result['id'])) {
$this->batch_id = $result['id'];
}
return $result;
}
/**
* Check the status of a batch request. If the current instance of the Batch object
* was used to make the request, the batch_id is already known and is therefore optional.
*
* @param string $batch_id ID of the batch about which to enquire
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function check_status($batch_id = null)
{
if ($batch_id === null && $this->batch_id) {
$batch_id = $this->batch_id;
}
return $this->MailChimp->get('batches/' . $batch_id);
}
/**
* Get operations
*
* @return array
*/
public function get_operations()
{
return $this->operations;
}
/**
* Add an operation to the internal queue.
*
* @param string $http_verb GET, POST, PUT, PATCH or DELETE
* @param string $id ID for the operation within the batch
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
*
* @return void
*/
private function queueOperation($http_verb, $id, $method, $args = null)
{
$operation = array(
'operation_id' => $id,
'method' => $http_verb,
'path' => $method,
);
if ($args) {
if ($http_verb == 'GET') {
$key = 'params';
$operation[$key] = $args;
} else {
$key = 'body';
$operation[$key] = json_encode($args);
}
}
$this->operations[] = $operation;
}
}

View File

@@ -0,0 +1,506 @@
<?php
namespace DrewM\MailChimp;
/**
* Super-simple, minimum abstraction MailChimp API v3 wrapper
* MailChimp API v3: http://developer.mailchimp.com
* This wrapper: https://github.com/drewm/mailchimp-api
*
* @author Drew McLellan <drew.mclellan@gmail.com>
* @author Zoltan Szanto <mrbig00@gmail.com>
* @version 2.5
*/
class MailChimp
{
const TIMEOUT = 10;
static $USER_AGENT = 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)';
/**
* Enable SSL Verification
* @see http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
* @var bool
*/
public $verify_ssl = true;
protected $request_successful = false;
protected $last_error = '';
protected $last_response = array();
protected $last_request = array();
protected $api_key;
protected $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
/**
* Create a new instance
*
* @param string $api_key Your MailChimp API key
* @param string $api_endpoint Optional custom API endpoint
*
* @throws \Exception
*/
public function __construct($api_key, $api_endpoint = null)
{
if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
throw new \Exception("cURL support is required, but can't be found.");
}
$this->api_key = $api_key;
if ($api_endpoint === null) {
if (strpos($this->api_key, '-') === false) {
throw new \Exception("Invalid MailChimp API key supplied.");
}
list(, $data_center) = explode('-', $this->api_key);
$this->api_endpoint = str_replace('<dc>', $data_center, $this->api_endpoint);
} else {
$this->api_endpoint = $api_endpoint;
}
$this->last_response = array('headers' => null, 'body' => null);
}
/**
* Create a new instance of a Batch request. Optionally with the ID of an existing batch.
*
* @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
*
* @return Batch New Batch object.
*/
public function new_batch($batch_id = null)
{
return new Batch($this, $batch_id);
}
/**
* @return string The url to the API endpoint
*/
public function getApiEndpoint()
{
return $this->api_endpoint;
}
/**
* Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
*
* @param string $email The subscriber's email address
*
* @return string Hashed version of the input
*/
public function subscriberHash($email)
{
return md5(strtolower($email));
}
/**
* Was the last request successful?
*
* @return bool True for success, false for failure
*/
public function success()
{
return $this->request_successful;
}
/**
* Get the last error returned by either the network transport, or by the API.
* If something didn't work, this should contain the string describing the problem.
*
* @return string|false describing the error
*/
public function getLastError()
{
return $this->last_error ?: false;
}
/**
* Get an array containing the HTTP headers and the body of the API response.
*
* @return array Assoc array with keys 'headers' and 'body'
*/
public function getLastResponse()
{
return $this->last_response;
}
/**
* Get an array containing the HTTP headers and the body of the API request.
*
* @return array Assoc array
*/
public function getLastRequest()
{
return $this->last_request;
}
/**
* Make an HTTP DELETE request - for deleting data
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (if any)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function delete($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('delete', $method, $args, $timeout);
}
/**
* Make an HTTP GET request - for retrieving data
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function get($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('get', $method, $args, $timeout);
}
/**
* Make an HTTP PATCH request - for performing partial updates
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function patch($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('patch', $method, $args, $timeout);
}
/**
* Make an HTTP POST request - for creating and updating items
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function post($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('post', $method, $args, $timeout);
}
/**
* Make an HTTP PUT request - for creating new items
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function put($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('put', $method, $args, $timeout);
}
/**
* Performs the underlying HTTP request. Not very exciting.
*
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete
* @param string $method The API method to be called
* @param array $args Assoc array of parameters to be passed
* @param int $timeout
*
* @return array|false Assoc array of decoded result
*/
protected function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
{
$url = $this->api_endpoint . '/' . $method;
$response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
$httpHeader = array(
'Accept: application/vnd.api+json',
'Content-Type: application/vnd.api+json',
'Authorization: OAuth ' . $this->api_key
);
if (isset($args["language"])) {
$httpHeader[] = "Accept-Language: " . $args["language"];
}
$ch = $this->prepareResource($url, $timeout, $httpHeader);
switch ($http_verb) {
case 'post':
curl_setopt($ch, CURLOPT_POST, true);
$this->attachRequestPayload($ch, $args);
break;
case 'get':
$query = http_build_query($args, '', '&');
curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
break;
case 'delete':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
case 'patch':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
$this->attachRequestPayload($ch, $args);
break;
case 'put':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
$this->attachRequestPayload($ch, $args);
break;
}
$responseContent = curl_exec($ch);
$response['headers'] = curl_getinfo($ch);
$response = $this->setResponseState($response, $responseContent, $ch);
$formattedResponse = $this->formatResponse($response);
curl_close($ch);
$isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
}
/**
* Prepare cURL resource before request
*
* @param $url
* @param $timeout
* @param array $headers
*
* @return resource
*/
protected function prepareResource($url, $timeout, $headers = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, static::$USER_AGENT);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
return $ch;
}
/**
* @param string $http_verb
* @param string $method
* @param string $url
* @param integer $timeout
*
* @return array
*/
protected function prepareStateForRequest($http_verb, $method, $url, $timeout)
{
$this->last_error = '';
$this->request_successful = false;
$this->last_response = array(
'headers' => null, // array of details from curl_getinfo()
'httpHeaders' => null, // array of HTTP headers
'body' => null // content of the response
);
$this->last_request = array(
'method' => $http_verb,
'path' => $method,
'url' => $url,
'body' => '',
'timeout' => $timeout,
);
return $this->last_response;
}
/**
* Get the HTTP headers as an array of header-name => header-value pairs.
*
* The "Link" header is parsed into an associative array based on the
* rel names it contains. The original value is available under
* the "_raw" key.
*
* @param string $headersAsString
*
* @return array
*/
protected function getHeadersAsArray($headersAsString)
{
$headers = array();
foreach (explode("\r\n", $headersAsString) as $i => $line) {
if ($i === 0) { // HTTP code
continue;
}
$line = trim($line);
if (empty($line)) {
continue;
}
list($key, $value) = explode(': ', $line);
if ($key == 'Link') {
$value = array_merge(
array('_raw' => $value),
$this->getLinkHeaderAsArray($value)
);
}
$headers[$key] = $value;
}
return $headers;
}
/**
* Extract all rel => URL pairs from the provided Link header value
*
* Mailchimp only implements the URI reference and relation type from
* RFC 5988, so the value of the header is something like this:
*
* 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy",
* <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
*
* @param string $linkHeaderAsString
*
* @return array
*/
protected function getLinkHeaderAsArray($linkHeaderAsString)
{
$urls = array();
if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
foreach ($matches[2] as $i => $relName) {
$urls[$relName] = $matches[1][$i];
}
}
return $urls;
}
/**
* Encode the data and attach it to the request
*
* @param resource $ch cURL session handle, used by reference
* @param array $data Assoc array of data to attach
*/
protected function attachRequestPayload(&$ch, $data)
{
$encoded = json_encode($data);
$this->last_request['body'] = $encoded;
curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
}
/**
* Decode the response and format any error messages for debugging
*
* @param array $response The response from the curl request
*
* @return array|false The JSON decoded into an array
*/
protected function formatResponse($response)
{
$this->last_response = $response;
if (!empty($response['body'])) {
return json_decode($response['body'], true);
}
return false;
}
/**
* Do post-request formatting and setting state from the response
*
* @param array $response The response from the curl request
* @param string $responseContent The body of the response from the curl request
* @param resource $ch The curl resource
*
* @return array The modified response
*/
protected function setResponseState($response, $responseContent, $ch)
{
if ($responseContent === false) {
$this->last_error = curl_error($ch);
} else {
$headerSize = $response['headers']['header_size'];
$response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
$response['body'] = substr($responseContent, $headerSize);
if (isset($response['headers']['request_header'])) {
$this->last_request['headers'] = $response['headers']['request_header'];
}
}
return $response;
}
/**
* Check if the response was successful or a failure. If it failed, store the error.
*
* @param array $response The response from the curl request
* @param array|false $formattedResponse The response body payload from the curl request
* @param int $timeout The timeout supplied to the curl request.
*
* @return bool If the request was successful
*/
protected function determineSuccess($response, $formattedResponse, $timeout)
{
$status = $this->findHTTPStatus($response, $formattedResponse);
if ($status >= 200 && $status <= 299) {
$this->request_successful = true;
return true;
}
if (isset($formattedResponse['detail'])) {
$this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
return false;
}
if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
$this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
return false;
}
$this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
return false;
}
/**
* Find the HTTP status code from the headers or API response body
*
* @param array $response The response from the curl request
* @param array|false $formattedResponse The response body payload from the curl request
*
* @return int HTTP status code
*/
protected function findHTTPStatus($response, $formattedResponse)
{
if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
return (int)$response['headers']['http_code'];
}
if (!empty($response['body']) && isset($formattedResponse['status'])) {
return (int)$formattedResponse['status'];
}
return 418;
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace DrewM\MailChimp;
/**
* A MailChimp Webhook request.
* How to Set Up Webhooks: http://eepurl.com/bs-j_T
*
* @author Drew McLellan <drew.mclellan@gmail.com>
*/
class Webhook
{
private static $eventSubscriptions = array();
private static $receivedWebhook = null;
/**
* Subscribe to an incoming webhook request. The callback will be invoked when a matching webhook is received.
*
* @param string $event Name of the webhook event, e.g. subscribe, unsubscribe, campaign
* @param callable $callback A callable function to invoke with the data from the received webhook
*
* @return void
*/
public static function subscribe($event, callable $callback)
{
if (!isset(self::$eventSubscriptions[$event])) self::$eventSubscriptions[$event] = array();
self::$eventSubscriptions[$event][] = $callback;
self::receive();
}
/**
* Retrieve the incoming webhook request as sent.
*
* @param string $input An optional raw POST body to use instead of php://input - mainly for unit testing.
*
* @return array|false An associative array containing the details of the received webhook
*/
public static function receive($input = null)
{
if (is_null($input)) {
if (self::$receivedWebhook !== null) {
$input = self::$receivedWebhook;
} else {
$input = file_get_contents("php://input");
}
}
if (!is_null($input) && $input != '') {
return self::processWebhook($input);
}
return false;
}
/**
* Process the raw request into a PHP array and dispatch any matching subscription callbacks
*
* @param string $input The raw HTTP POST request
*
* @return array|false An associative array containing the details of the received webhook
*/
private static function processWebhook($input)
{
self::$receivedWebhook = $input;
parse_str($input, $result);
if ($result && isset($result['type'])) {
self::dispatchWebhookEvent($result['type'], $result['data']);
return $result;
}
return false;
}
/**
* Call any subscribed callbacks for this event
*
* @param string $event The name of the callback event
* @param array $data An associative array of the webhook data
*
* @return void
*/
private static function dispatchWebhookEvent($event, $data)
{
if (isset(self::$eventSubscriptions[$event])) {
foreach (self::$eventSubscriptions[$event] as $callback) {
$callback($data);
}
// reset subscriptions
self::$eventSubscriptions[$event] = array();
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* 2007-2018 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2018 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;

View File

@@ -0,0 +1,29 @@
<?php
namespace DrewM\MailChimp\Tests;
use DrewM\MailChimp\MailChimp;
use PHPUnit\Framework\TestCase;
class BatchTest extends TestCase
{
/**
* @throws \Exception
*/
public function testNewBatch()
{
$MC_API_KEY = getenv('MC_API_KEY');
if (!$MC_API_KEY) {
$this->markTestSkipped('No API key in ENV');
}
$MailChimp = new MailChimp($MC_API_KEY);
$Batch = $MailChimp->new_batch('1');
$this->assertInstanceOf('\DrewM\MailChimp\Batch', $Batch);
$this->assertSame(array(), $Batch->get_operations());
}
}

View File

@@ -0,0 +1,21 @@
<?php
use DrewM\MailChimp\MailChimp;
use PHPUnit\Framework\TestCase;
class ListsTest extends TestCase
{
public function testGetLists()
{
$MC_API_KEY = getenv('MC_API_KEY');
if (!$MC_API_KEY) {
$this->markTestSkipped('No API key in ENV');
}
$MailChimp = new MailChimp($MC_API_KEY);
$lists = $MailChimp->get('lists');
$this->assertArrayHasKey('lists', $lists);
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace DrewM\MailChimp\Tests;
use DrewM\MailChimp\MailChimp;
use PHPUnit\Framework\TestCase;
class MailChimpTest extends TestCase
{
/**
* @throws \Exception
*/
public function testInvalidAPIKey()
{
$this->expectException('\Exception');
new MailChimp('abc');
}
public function testTestEnvironment()
{
$MC_API_KEY = getenv('MC_API_KEY');
$this->assertNotEmpty($MC_API_KEY, 'No environment variables! Copy .env.example -> .env and fill out your MailChimp account details.');
}
/**
* @throws \Exception
*/
public function testInstantiation()
{
$MC_API_KEY = getenv('MC_API_KEY');
if (!$MC_API_KEY) {
$this->markTestSkipped('No API key in ENV');
}
$MailChimp = new MailChimp($MC_API_KEY, 'https://api.mailchimp.com/3.0');
$this->assertInstanceOf('\DrewM\MailChimp\MailChimp', $MailChimp);
$this->assertSame('https://api.mailchimp.com/3.0', $MailChimp->getApiEndpoint());
$this->assertFalse($MailChimp->success());
$this->assertFalse($MailChimp->getLastError());
$this->assertSame(array('headers' => null, 'body' => null), $MailChimp->getLastResponse());
$this->assertSame(array(), $MailChimp->getLastRequest());
}
/**
* @throws \Exception
*/
public function testSubscriberHash()
{
$MC_API_KEY = getenv('MC_API_KEY');
if (!$MC_API_KEY) {
$this->markTestSkipped('No API key in ENV');
}
$MailChimp = new MailChimp($MC_API_KEY);
$email = 'Foo@Example.Com';
$expected = md5(strtolower($email));
$result = $MailChimp->subscriberHash($email);
$this->assertEquals($expected, $result);
}
public function testResponseState()
{
$MC_API_KEY = getenv('MC_API_KEY');
if (!$MC_API_KEY) {
$this->markTestSkipped('No API key in ENV');
}
$MailChimp = new MailChimp($MC_API_KEY);
$MailChimp->get('lists');
$this->assertTrue($MailChimp->success());
}
/* This test requires that your test list have:
* a) a list
* b) enough entries that the curl request will timeout after 1 second.
* How many this is may depend on your network connection to the Mailchimp servers.
*/
/*
public function testRequestTimeout()
{
$this->markTestSkipped('CI server too fast to realistically test.');
$MC_API_KEY = getenv('MC_API_KEY');
if (!$MC_API_KEY) {
$this->markTestSkipped('No API key in ENV');
}
$MailChimp = new MailChimp($MC_API_KEY);
$result = $MailChimp->get('lists');
$list_id = $result['lists'][0]['id'];
$args = array( 'count' => 1000 );
$timeout = 1;
$result = $MailChimp->get("lists/$list_id/members", $args, $timeout );
$this->assertFalse( $result );
$error = $MailChimp->getLastError();
$this->assertRegExp( '/Request timed out after 1.\d+ seconds/', $error );
}
*/
}

View File

@@ -0,0 +1,16 @@
<?php
\error_reporting(E_ALL);
include_once \dirname(__DIR__) . '/vendor/autoload.php';
if (!\class_exists('Dotenv\Dotenv')) {
throw new \RuntimeException('You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.');
}
$env_file_path = __DIR__ . '/../';
if (file_exists($env_file_path . '.env')) {
$dotenv = new Dotenv\Dotenv($env_file_path);
$dotenv->load();
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* 2007-2018 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2018 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;