* @link https://www.tassos.gr * @copyright Copyright © 2024 Tassos All Rights Reserved * @license GNU GPLv3 or later */ namespace NRFramework\Integrations; use NRFramework\Functions; // No direct access defined('_JEXEC') or die; class MailChimp extends Integration { /** * MailChimp Endpoint URL * * @var string */ protected $endpoint = 'https://.api.mailchimp.com/3.0'; /** * Create a new instance * * @param array $options The service's required options * @throws \Exception */ public function __construct($options) { parent::__construct(); $this->setKey($options); if (strpos($this->key, '-') === false) { return; } list(, $data_center) = explode('-', $this->key); $this->endpoint = str_replace('', $data_center, $this->endpoint); $this->options->set('headers.Authorization', 'apikey ' . $this->key); } /** * Subscribe user to MailChimp * * @param string $list_id The ID of the MailChimp list * @param string $email The email address of the subscriber * @param object $merge_fields The custom field that are associated with the subscriber where the keys are the merge tags. * @param boolean $double_optin If true, the subscriber will be added with status "pending" and a confirmation email will be sent to the user. * @param boolean $allow_update If true, the subscriber will be updated if it already exists. Otherwise, an error will be thrown. * @param array $tags The tags that are associated with the subscriber. * @param string $tags_replace Determines what changes to make to the subscriber's tags. Values: add_only, replace_all * @param array $interests The interests that are associated with the subscriber. * @param string $interests_replace Determines what changes to make to the subscriber's groups/interests. Values: add_only, replace_all * * @return void */ public function subscribeV2($list_id, $email, $merge_fields = null, $double_optin = true, $allow_update = true, $tags = null, $tags_replace = 'add_only', $interests = [], $interests_replace = 'add_only') { $data = [ 'email_address' => $email, 'status' => $double_optin ? 'pending' : 'subscribed', 'merge_fields' => (object) $merge_fields, 'tags' => Functions::cleanArray($tags) ]; $member = $this->getMemberByEmail($list_id, $email); // Prepare Interests $interests = Functions::cleanArray($interests); $interests = $interests ? array_fill_keys($interests, true) : []; if ($member && isset($member['interests']) && $interests_replace == 'replace_all') { // Disable all existing groups $memberInterests = array_fill_keys(array_keys($member['interests']), false); // Merge new interests with existing interests $interests = array_merge($memberInterests, $interests); } $data['interests'] = (object) $interests; if (!$member) { // API Doc: https://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/#create-post_lists_list_id_members $this->post('lists/' . $list_id . '/members', $data); return; } // Member exists // Since member exists and we don't allow updating existing member, throw an error. if (!$allow_update) { throw new \Exception('Member already exists'); } // Skip double opt-in if the existing member is already confirmed if (isset($member['status']) && $member['status'] == 'subscribed') { $data['status'] = $member['status']; } // Update existing member // API Doc: https://mailchimp.com/developer/marketing/api/list-members/add-or-update-list-member $this->put('lists/' . $list_id . '/members/' . $member['id'], $data); // Remove existing member tags not included in the given Tags. if ($member['tags'] && $tags_replace == 'replace_all') { $currentTags = array_map(function($item) { return $item['name']; }, $member['tags']); if ($removeTags = array_diff($currentTags, $data['tags'])) { $rTags = []; foreach ($removeTags as $removeTag) { $rTags[] = [ 'name' => $removeTag, 'status' => 'inactive' ]; } // API Doc: https://mailchimp.com/developer/marketing/api/list-member-tags/add-or-remove-member-tags/ $this->post('lists/' . $list_id . '/members/' . $member['id'] . '/tags', ['tags' => $rTags]); } } } /** * Subscribe user to MailChimp * * API References: * https://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/#edit-put_lists_list_id_members_subscriber_hash * https://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/#create-post_lists_list_id_members * * @param string $email User's email address * @param string $list The MailChimp list unique ID * @param Object $merge_fields Merge Fields * @param boolean $update_existing Update existing user * @param boolean $double_optin Send MailChimp confirmation email? * * @deprecated Use subscribeV2() * * @return void */ public function subscribe($email, $list, $merge_fields = array(), $update_existing = true, $double_optin = false) { $data = array( 'email_address' => $email, 'status' => $double_optin ? 'pending' : 'subscribed' ); // add support for tags if ($tags = $this->getTags($merge_fields)) { $data['tags'] = $tags; } if (is_array($merge_fields) && count($merge_fields)) { foreach ($merge_fields as $merge_field_key => $merge_field_value) { $value = is_array($merge_field_value) ? implode(',', $merge_field_value) : (string) $merge_field_value; $data['merge_fields'][$merge_field_key] = $value; } } $interests = $this->validateInterestCategories($list, $merge_fields); if (!empty($interests)) { $data = array_merge($data, array('interests' => $interests)); } if ($update_existing) { // Get subscriber information. $subscriberHash = md5(strtolower($email)); $member = $this->get('lists/' . $list . '/members/' . $subscriberHash); // Skip double opt-in if the subscriber exists and it's confirmed if (isset($member['status']) && $member['status'] == 'subscribed') { $data['status'] = $member['status']; } $this->put('lists/' . $list . '/members/' . $subscriberHash, $data); if ($tags) { $tags_ = []; foreach ($tags as $tag) { $tags_[] = [ 'name' => $tag, 'status' => 'active' ]; } $currentTags = $this->getMemberTags($list, $subscriberHash); if ($removeTags = array_diff($currentTags, $tags)) { foreach ($removeTags as $removeTag) { $tags_[] = [ 'name' => $removeTag, 'status' => 'inactive' ]; } } $this->post('lists/' . $list . '/members/' . $subscriberHash . '/tags', ['tags' => $tags_]); } } else { $this->post('lists/' . $list . '/members', $data); } return true; } /** * @deprecated Use subscribeV2() */ private function getMemberTags($list, $subscriberHash) { $tags = $this->get('lists/' . $list . '/members/' . $subscriberHash . '/tags'); $return = []; if (isset($tags['tags'])) { foreach ($tags['tags'] as $tag) { $return[] = $tag['name']; } } return $return; } /** * Find and return all unique tags * * @param array $merge_fields * * @deprecated use subscribeV2() * * @return array */ private function getTags($merge_fields) { $tags = []; // ensure tags are added in the form if (!isset($merge_fields['tags'])) { return $tags; } $mergeFieldsTags = $merge_fields['tags']; // make string array if (is_string($mergeFieldsTags)) { $tags = explode(',', $mergeFieldsTags); } // ensure we have array to manipulate if (is_array($mergeFieldsTags) || is_object($mergeFieldsTags)) { $tags = (array) $mergeFieldsTags; } // remove empty values, keep uniques and reset keys $tags = array_filter($tags); $tags = array_unique($tags); $tags = array_values($tags); $tags = array_map('trim', $tags); return $tags; } /** * Returns all available MailChimp lists * * https://developer.mailchimp.com/documentation/mailchimp/reference/lists/#read-get_lists * * @return array */ public function getLists() { $data = $this->get('/lists'); if (!$this->success()) { return; } if (!isset($data['lists']) || !is_array($data['lists'])) { return; } $lists = []; foreach ($data['lists'] as $key => $list) { $lists[] = array( 'id' => $list['id'], 'name' => $list['name'] ); } return $lists; } /** * Gets the Interest Categories from MailChimp * * @param string $listID The List ID * * @deprecated Use subscribeV2() * * @return array */ public function getInterestCategories($listID) { if (!$listID) { return; } $data = $this->get('/lists/' . $listID . '/interest-categories'); if (!$this->success()) { return; } if (isset($data['total_items']) && $data['total_items'] == 0) { return; } return $data['categories']; } /** * Gets the values accepted for the particular Interest Category * * @param string $listID The List ID * @param string $interestCategoryID The Interest Category ID * * @deprecated Use subscribeV2() * * @return array */ public function getInterestCategoryValues($listID, $interestCategoryID) { if (!$interestCategoryID || !$listID) { return array(); } $data = $this->get('/lists/' . $listID . '/interest-categories/' . $interestCategoryID . '/interests'); if (isset($data['total_items']) && $data['total_items'] == 0) { return array(); } return $data['interests']; } /** * Filters the interests categories through the form fields * and constructs the interests array for the subscribe method * * @param string $listID The List ID * @param array $params The Form fields * * @deprecated Use subscribeV2() * * @return array */ public function validateInterestCategories($listID, $params) { if (!$params || !$listID) { return array(); } $interestCategories = $this->getInterestCategories($listID); if (!$interestCategories) { return array(); } $categories = array(); foreach ($interestCategories as $category) { if (array_key_exists($category['title'], $params)) { $categories[] = array('id' => $category['id'], 'title' => $category['title']); } } if (empty($categories)) { return array(); } $interests = array(); foreach ($categories as $category) { $data = $this->getInterestCategoryValues($listID, $category['id']); if (isset($data['total_items']) && $data['total_items'] == 0) { continue; } foreach ($data as $interest) { if (in_array($interest['name'], (array) $params[$category['title']])) { $interests[$interest['id']] = true; } else { $interests[$interest['id']] = false; } } } return $interests; } /** * Find a subscriber in a list by email address * * @param string $list_id The MailChimp list ID * @param string $email The email address * * @return mixed Object on success, false on failure */ public function getMemberByEmail($list_id, $email) { $subscriberHash = md5(strtolower($email)); $result = $this->get('lists/' . $list_id . '/members/' . $subscriberHash); return $this->success() ? $result : false; } /** * Get the last error returned by either the network transport, or by the API. * * @return string */ public function getLastError() { $body = $this->last_response->body; if (isset($body['errors'])) { $error = $body['errors'][0]; return $error['field'] . ': ' . $error['message']; } if (isset($body['detail'])) { return $body['detail']; } } /** * The get() method overridden so that it handles * the default item paging of MailChimp which is 10 * * @param string $method URL of the API request method * @param array $args Assoc array of arguments (usually your data) * @return array|false Assoc array of API response, decoded from JSON */ public function get($method, $args = array()) { $data = $this->makeRequest('get', $method, $args); if ($data && isset($data['total_items']) && (int) $data['total_items'] > 10) { $args['count'] = $data['total_items']; return $this->makeRequest('get', $method, $args); } return $data; } }