You are here

mailchimp.module in Mailchimp 7.4

Mailchimp module.

File

mailchimp.module
View source
<?php

/**
 * @file
 * Mailchimp module.
 */
use Mailchimp\MailchimpLists;
define('MAILCHIMP_QUEUE_CRON', 'mailchimp');
define('MAILCHIMP_BATCH_QUEUE_CRON', 'mailchimp_batch');
define('MAILCHIMP_STATUS_SENT', 'sent');
define('MAILCHIMP_STATUS_SAVE', 'save');
define('MAILCHIMP_STATUS_PAUSED', 'paused');
define('MAILCHIMP_STATUS_SCHEDULE', 'schedule');
define('MAILCHIMP_STATUS_SENDING', 'sending');

/**
 * Implements hook_libraries_info().
 */
function mailchimp_libraries_info() {
  $libraries['mailchimp'] = array(
    'name' => 'Mailchimp API',
    'vendor url' => 'https://github.com/thinkshout/mailchimp-api-php',
    'download url' => 'https://github.com/thinkshout/mailchimp-api-php/releases/download/v1.0.10/v1.0.10-package.zip',
    'version arguments' => array(
      'file' => 'composer.json',
      'pattern' => '/"version": "([0-9a-zA-Z.-]+)"/',
    ),
    'files' => array(
      'php' => array(
        'src/Mailchimp.php',
        'src/MailchimpAPIException.php',
        'src/MailchimpCampaigns.php',
        'src/MailchimpConnectedSites.php',
        'src/MailchimpLists.php',
        'src/MailchimpReports.php',
        'src/MailchimpTemplates.php',
        'vendor/autoload.php',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_menu().
 */
function mailchimp_menu() {
  $items = array();
  $items['admin/config/services/mailchimp'] = array(
    'title' => 'Mailchimp',
    'description' => 'Manage Mailchimp Settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mailchimp_admin_settings',
    ),
    'access arguments' => array(
      'administer mailchimp',
    ),
    'file' => 'includes/mailchimp.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/services/mailchimp/global'] = array(
    'title' => 'Global Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/config/services/mailchimp/list_cache_clear'] = array(
    'title' => 'Mailchimp webhooks endpoint',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mailchimp_clear_list_cache_form',
    ),
    'access callback' => 'mailchimp_apikey_ready_access',
    'access arguments' => array(
      'administer mailchimp',
    ),
    'file' => 'includes/mailchimp.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['mailchimp/webhook'] = array(
    'title' => 'Mailchimp webhooks endpoint',
    'page callback' => 'mailchimp_process_webhook',
    'access callback' => 'mailchimp_process_webhook_access',
    'access arguments' => array(
      2,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function mailchimp_permission() {
  return array(
    'administer mailchimp' => array(
      'title' => t('administer mailchimp'),
      'description' => t('Access the Mailchimp configuration options.'),
    ),
  );
}

/**
 * Access callback for mailchimp submodule menu items.
 */
function mailchimp_apikey_ready_access($permission) {
  if (mailchimp_get_api_object() && user_access($permission)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Get an instance of the Mailchimp library.
 *
 * @param string $classname
 *   The Mailchimp library class to instantiate.
 * @param string $api_key
 *   The Mailchimp api key to use if not the default.
 *
 * @return Mailchimp
 *   Instance of the Mailchimp library class. Can be overridden by $classname.
 */
function mailchimp_get_api_object($classname = 'Mailchimp', $api_key = NULL) {

  // Set correct library class namespace depending on test mode.
  if (variable_get('mailchimp_test_mode', FALSE)) {
    $classname = '\\Mailchimp\\Tests\\' . $classname;
  }
  else {
    $classname = '\\Mailchimp\\' . $classname;
  }
  $mailchimp =& drupal_static(__FUNCTION__);
  if (!$api_key && isset($mailchimp) && $mailchimp instanceof $classname) {
    return $mailchimp;
  }
  if (module_exists('libraries')) {
    $library = libraries_load('mailchimp');
  }
  else {
    $library = FALSE;
  }
  if (!$library['installed'] && !class_exists('Mailchimp\\Mailchimp')) {
    $msg = t('Failed to load the Mailchimp PHP library. Please refer to the installation requirements.');
    watchdog('mailchimp', $msg, array(), WATCHDOG_ERROR);
    drupal_set_message($msg, 'error', FALSE);
    return NULL;
  }
  if (!class_exists('\\GuzzleHttp\\Client')) {
    $msg = t('The Mailchimp PHP library is missing the required GuzzleHttp library. Please check the installation notes in README.txt.');
    watchdog('mailchimp', $msg, array(), WATCHDOG_ERROR);
    drupal_set_message($msg, 'error', FALSE);
    return NULL;
  }
  $context = array(
    'api_class' => $classname,
  );
  if (!$api_key) {
    $api_key = $default_api_key = variable_get('mailchimp_api_key', '');

    // Allow modules to alter the default.
    drupal_alter('mailchimp_api_key', $api_key, $context);

    // Check to see if the key was altered.
    if ($api_key !== $default_api_key) {

      // Invalidate all caches because the key was altered.
      mailchimp_cache_clear_all();
    }
  }
  if (!strlen($api_key)) {
    watchdog('mailchimp', 'Mailchimp Error: API Key cannot be blank.', array(), WATCHDOG_ERROR);
    return NULL;
  }

  // Set the timeout to something that won't take down the Drupal site:
  $http_options = [
    'timeout' => 60,
  ];
  $proxy_server = variable_get('proxy_server', '');
  if ($proxy_server) {
    $proxy_url = sprintf('tcp://%s:%d', $proxy_server, variable_get('proxy_port', 8080));
    $http_options['proxy'] = [
      'http' => $proxy_url,
      'https' => $proxy_url,
    ];
    if ($proxy_username = variable_get('proxy_username', '')) {
      $proxy_password = variable_get('proxy_password', '');
      $http_options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : ''));
    }
  }
  $http_options['headers']['User-Agent'] = _mailchimp_get_user_agent();
  $mailchimp = new $classname($api_key, 'apikey', $http_options);
  return $mailchimp;
}

/**
 * Gets the user agent string for this installation of Mailchimp.
 *
 * @return string
 *   The user agent string.
 */
function _mailchimp_get_user_agent() {
  $version = '7.x-4.x';
  if (module_exists('system')) {
    $info = system_get_info('module', 'mailchimp');
    if (!empty($info['version'])) {
      $version = $info['version'];
    }
  }
  $user_agent = "DrupalMailchimp/{$version} " . \GuzzleHttp\default_user_agent();
  return $user_agent;
}

/**
 * Gets a single Mailchimp list by ID.
 *
 * @param string $list_id
 *   The unique ID of the list provided by Mailchimp.
 * @param bool $use_interest_groups
 *   TRUE to load interest groups for the list.
 * @param bool $reset
 *   TRUE to reset list cache and load from Mailchimp.
 *
 * @return object
 *   A Mailchimp list object.
 */
function mailchimp_get_list($list_id, $use_interest_groups = TRUE, $reset = FALSE) {
  $cache = $reset ? NULL : cache_get('list-' . $list_id, 'cache_mailchimp');
  if (!empty($cache)) {

    // Use cached list only if interest group requirement is met.
    if (!$use_interest_groups || $use_interest_groups && isset($cache->data->intgroups)) {
      return $cache->data;
    }
  }
  $list = NULL;
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot get list without Mailchimp API. Check API key has been entered.');
    }
    $list = $mc_lists
      ->getList($list_id);
    if (!empty($list)) {
      if ($use_interest_groups) {

        // Add interest categories to the list.
        $int_category_data = $mc_lists
          ->getInterestCategories($list->id, array(
          'count' => 500,
        ));
        if ($int_category_data->total_items > 0) {
          $list->intgroups = array();
          foreach ($int_category_data->categories as $interest_category) {
            if (isset($interest_category->type) && $interest_category->type == 'hidden') {
              continue;
            }
            $interest_data = $mc_lists
              ->getInterests($list->id, $interest_category->id, array(
              'count' => 500,
            ));
            if ($interest_data->total_items > 0) {
              $interest_category->interests = $interest_data->interests;
            }
            $list->intgroups[] = $interest_category;
          }
        }
      }

      // Add mergefields to the list.
      $mergefields = $mc_lists
        ->getMergeFields($list->id, array(
        'count' => 500,
      ));
      if ($mergefields->total_items > 0) {
        $list->mergevars = $mergefields->merge_fields;
      }
    }
    cache_set('list-' . $list_id, $list, 'cache_mailchimp', CACHE_PERMANENT);
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred requesting list information from Mailchimp. "%message"', array(
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $list;
}

/**
 * Gets Mailchimp lists. Can be filtered by an array of list IDs.
 *
 * @param array $list_ids
 *   An array of list IDs to filter the results by.
 * @param bool $use_interest_groups
 *   TRUE to load interest groups for the list.
 * @param bool $reset
 *   TRUE to reset list cache and load from Mailchimp.
 *
 * @return array
 *   An array of Mailchimp list objects.
 */
function mailchimp_get_lists($list_ids = array(), $use_interest_groups = TRUE, $reset = FALSE) {
  $cache = $reset ? NULL : cache_get('lists', 'cache_mailchimp');

  // Return cached lists:
  if ($cache) {
    $lists = $cache->data;
  }
  else {
    $lists = array();
    try {

      /* @var \Mailchimp\MailchimpLists $mc_lists */
      $mc_lists = mailchimp_get_api_object('MailchimpLists');
      if (!$mc_lists) {
        throw new MailchimpException('Cannot get lists without Mailchimp API. Check API key has been entered.');
      }
      $result = $mc_lists
        ->getLists(array(
        'count' => 500,
      ));
      if ($result->total_items > 0) {
        foreach ($result->lists as $list) {
          if ($use_interest_groups) {

            // Add interest categories to the list.
            $int_category_data = $mc_lists
              ->getInterestCategories($list->id, array(
              'count' => 500,
            ));
            if ($int_category_data->total_items > 0) {
              $list->intgroups = array();
              foreach ($int_category_data->categories as $interest_category) {
                if (isset($interest_category->type) && $interest_category->type == 'hidden') {
                  continue;
                }
                $interest_data = $mc_lists
                  ->getInterests($list->id, $interest_category->id, array(
                  'count' => 500,
                ));
                if ($interest_data->total_items > 0) {
                  $interest_category->interests = $interest_data->interests;
                }
                $list->intgroups[] = $interest_category;
              }
            }
          }
          $lists[$list->id] = $list;

          // Add mergefields to the list.
          $mergefields = $mc_lists
            ->getMergeFields($list->id, array(
            'count' => 500,
          ));
          if ($mergefields->total_items > 0) {
            $lists[$list->id]->mergevars = $mergefields->merge_fields;
          }
        }
      }
      uasort($lists, '_mailchimp_list_cmp');
      cache_set('lists', $lists, 'cache_mailchimp', CACHE_PERMANENT);
    } catch (Exception $e) {
      watchdog('mailchimp', 'An error occurred requesting list information from Mailchimp. "%message"', array(
        '%message' => $e
          ->getMessage(),
      ), WATCHDOG_ERROR);
    }
  }

  // Filter by given ids:
  if (!empty($list_ids)) {
    $filtered_lists = array();
    foreach ($list_ids as $id) {
      if (array_key_exists($id, $lists)) {
        $filtered_lists[$id] = $lists[$id];
      }
    }
    return $filtered_lists;
  }
  else {
    return $lists;
  }
}

/**
 * Helper function used by uasort() to sort lists alphabetically by name.
 *
 * @param array $a
 *   An array representing the first list.
 * @param array $b
 *   An array representing the second list.
 *
 * @return int
 *   One of the values -1, 0, 1
 */
function _mailchimp_list_cmp($a, $b) {
  if ($a->name == $b->name) {
    return 0;
  }
  return $a->name < $b->name ? -1 : 1;
}

/**
 * Wrapper around MailchimpLists->getMergeFields().
 *
 * @param array $list_ids
 *   Array of Mailchimp list IDs.
 * @param bool $reset
 *   Set to TRUE if mergevars should not be loaded from cache.
 *
 * @return array
 *   Struct describing mergevars for the specified lists.
 */
function mailchimp_get_mergevars($list_ids, $reset = FALSE) {
  $mergevars = array();
  if (!$reset) {
    foreach ($list_ids as $key => $list_id) {
      $cache = cache_get($list_id . '-mergevars', 'cache_mailchimp');

      // Get cached data and unset from our remaining lists to query.
      if ($cache) {
        $mergevars[$list_id] = $cache->data;
        unset($list_ids[$key]);
      }
    }
  }

  /* @var \Mailchimp\MailchimpLists $mc_lists */
  $mc_lists = mailchimp_get_api_object('MailchimpLists');
  try {
    if (!$mc_lists) {
      throw new MailchimpException('Cannot get merge vars without Mailchimp API. Check API key has been entered.');
    }

    // Get the uncached merge vars from Mailchimp.
    foreach ($list_ids as $list_id) {

      // Add default EMAIL merge var for all lists.
      $mergevars[$list_id] = array(
        (object) array(
          'tag' => 'EMAIL',
          'name' => t('Email Address'),
          'type' => 'email',
          'required' => TRUE,
          'default_value' => '',
          'public' => TRUE,
          'display_order' => 1,
          'options' => (object) array(
            'size' => 25,
          ),
        ),
      );
      $result = $mc_lists
        ->getMergeFields($list_id, array(
        'count' => 500,
      ));
      if ($result->total_items > 0) {
        $mergevars[$list_id] = array_merge($mergevars[$list_id], $result->merge_fields);
      }
      cache_set($list_id . '-mergevars', $mergevars[$list_id], 'cache_mailchimp', CACHE_TEMPORARY);
    }
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred requesting mergevars for list @list. "%message"', array(
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $mergevars;
}

/**
 * Get the Mailchimp member info for a given email address and list.
 *
 * Results are cached in the cache_mailchimp bin which is cleared by the
 * Mailchimp web hooks system when needed.
 *
 * @param string $list_id
 *   The Mailchimp list ID to get member info for.
 * @param string $email
 *   The Mailchimp user email address to load member info for.
 * @param bool $reset
 *   Set to TRUE if member info should not be loaded from cache.
 *
 * @return object
 *   Member info object, empty if there is no valid info.
 */
function mailchimp_get_memberinfo($list_id, $email, $reset = FALSE) {
  $cache = $reset ? NULL : cache_get($list_id . '-' . $email, 'cache_mailchimp');

  // Return cached lists:
  if ($cache) {
    return $cache->data;
  }

  // Query lists from the MCAPI and store in cache:
  $memberinfo = new stdClass();

  /* @var \Mailchimp\MailchimpLists $mc_lists */
  $mc_lists = mailchimp_get_api_object('MailchimpLists');
  try {
    if (!$mc_lists) {
      throw new MailchimpException('Cannot get member info without Mailchimp API. Check API key has been entered.');
    }
    try {
      $memberinfo = $mc_lists
        ->getMemberInfo($list_id, $email);
      cache_set($list_id . '-' . $email, $memberinfo, 'cache_mailchimp', CACHE_TEMPORARY);
    } catch (Exception $e) {

      // Throw exception only for errors other than member not found.
      if ($e
        ->getCode() != 404) {
        throw new Exception($e
          ->getMessage(), $e
          ->getCode(), $e);
      }
    }
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred requesting memberinfo for @email in list @list. "%message"', array(
      '@email' => $email,
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $memberinfo;
}

/**
 * Check if the given email is subscribed to the given list.
 *
 * Simple wrapper around mailchimp_get_memberinfo().
 *
 * @param string $list_id
 *   Unique string identifier for the list on your Mailchimp account.
 * @param string $email
 *   Email address to check for on the identified Mailchimp List.
 * @param bool $reset
 *   Set to TRUE to ignore the cache. (Used heavily in testing functions.)
 *
 * @return bool
 *   Indicates subscription status.
 */
function mailchimp_is_subscribed($list_id, $email, $reset = FALSE) {
  $subscribed = FALSE;
  $memberinfo = mailchimp_get_memberinfo($list_id, $email, $reset);
  if (isset($memberinfo->status) && $memberinfo->status == 'subscribed') {
    $subscribed = TRUE;
  }
  return $subscribed;
}

/**
 * Subscribe a user to a Mailchimp list in real time or by adding to the queue.
 *
 * @see Mailchimp_Lists::subscribe()
 *
 * @return bool
 *   True on success.
 */
function mailchimp_subscribe($list_id, $email, $merge_vars = NULL, $interests = array(), $double_optin = FALSE, $format = 'html') {
  if (variable_get('mailchimp_cron', FALSE)) {
    $args = array(
      'list_id' => $list_id,
      'email' => $email,
      'merge_vars' => $merge_vars,
      'interests' => $interests,
      'double_optin' => $double_optin,
      'format' => $format,
    );
    return mailchimp_addto_queue('mailchimp_subscribe_process', $args);
  }
  return mailchimp_subscribe_process($list_id, $email, $merge_vars, $interests, $double_optin, $format);
}

/**
 * Wrapper around MailchimpLists::addOrUpdateMember().
 *
 * @see MailchimpLists::addOrUpdateMember()
 *
 * @return object
 *   On success a result object will be returned from Mailchimp. On failure an
 *   object will be returned with the property success set to FALSE, the
 *   response code as a property, and the message as a property. To check for
 *   a failure, look for the property 'success' of the object returned to
 *   be set to FALSE.
 */
function mailchimp_subscribe_process($list_id, $email, $merge_vars = NULL, $interests = array(), $double_optin = FALSE, $format = 'html') {
  $result = FALSE;
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot subscribe to list without Mailchimp API. Check API key has been entered.');
    }
    $parameters = array(
      // If double opt-in is required, set member status to 'pending', but only
      // if the user isn't already subscribed.
      'status' => $double_optin && !mailchimp_is_subscribed($list_id, $email) ? \Mailchimp\MailchimpLists::MEMBER_STATUS_PENDING : \Mailchimp\MailchimpLists::MEMBER_STATUS_SUBSCRIBED,
      'email_type' => $format,
    );

    // Set interests.
    if (!empty($interests)) {
      $parameters['interests'] = (object) $interests;
    }

    // Set merge fields.
    if (!empty($merge_vars)) {
      $parameters['merge_fields'] = (object) $merge_vars;
    }

    // Add member to list.
    $result = $mc_lists
      ->addOrUpdateMember($list_id, $email, $parameters);
    if (isset($result->id)) {
      module_invoke_all('mailchimp_subscribe_user', $list_id, $email, $merge_vars);

      // Clear user cache, just in case there's some cruft leftover:
      mailchimp_cache_clear_member($list_id, $email);
      watchdog('mailchimp', '@email was subscribed to list @list.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_NOTICE);
    }
    else {
      if (!variable_get('mailchimp_test_mode')) {
        watchdog('mailchimp', 'A problem occurred subscribing @email to list @list.', array(
          '@email' => $email,
          '@list' => $list_id,
        ), WATCHDOG_WARNING);
      }
    }
  } catch (Exception $e) {
    if ($e
      ->getCode() == '400' && strpos($e
      ->getMessage(), 'Member In Compliance State') !== false && !$double_optin) {
      watchdog('mailchimp', 'Detected "Member In Compliance State" subscribing @email to list @list.  Trying again using double-opt in.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_INFO);
      return mailchimp_subscribe_process($list_id, $email, $merge_vars, $interests, TRUE, $format);
    }
    watchdog('mailchimp', 'An error occurred subscribing @email to list @list. Status code @code. "%message"', array(
      '@email' => $email,
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
      '@code' => $e
        ->getCode(),
    ), WATCHDOG_ERROR);
    $result = new stdClass();
    $result->success = FALSE;
    $result->status = $e
      ->getCode();
    $result->message = $e
      ->getMessage();
  }
  if ($double_optin) {
    drupal_set_message(t('Please check your email to confirm your subscription'), 'status', FALSE);
  }
  return $result;
}

/**
 * Add a Mailchimp subscription task to the queue.
 *
 * @string $function
 *   The name of the function the queue runner should call.
 * @array $args
 *   The list of args to pass to the function.
 *
 * @return bool
 *   Success or failure.
 */
function mailchimp_addto_queue($function, $args) {
  $queue = DrupalQueue::get(MAILCHIMP_QUEUE_CRON);
  $queue
    ->createQueue();
  mailchimp_update_local_cache($function, $args);
  return $queue
    ->createItem(array(
    'function' => $function,
    'args' => $args,
  ));
}

/**
 * Updates the local cache for a user as though a queued request had been
 * processed.
 *
 * If we don't do this, then users can make changes, but not have them shown on
 * the site until cron runs, which is intensely confusing. See
 * https://www.drupal.org/node/2503597
 *
 * @param string $function
 *   The name of the function that the queue runner will call when the update
 *   is processed.
 * @param array $args
 *   The list of args that will be passed to the queue runner.
 *
 * @return bool
 */
function mailchimp_update_local_cache($function, $args) {
  $list_id = isset($args['list_id']) ? $args['list_id'] : NULL;
  $email = isset($args['email']) ? $args['email'] : NULL;
  if (empty($list_id) || empty($email)) {
    return FALSE;
  }
  $cache = mailchimp_get_memberinfo($list_id, $email);
  if (empty($cache)) {

    // Create a new entry.
    cache_set($list_id . '-' . $email, (object) array(
      'merge_fields' => new stdClass(),
    ), 'cache_mailchimp', CACHE_TEMPORARY);
    $cache = cache_get($list_id . '-' . $email, 'cache_mailchimp');
    $cache = $cache->data;
  }

  // Handle unsubscribes.
  if ($function == 'mailchimp_unsubscribe_process') {
    $cache->status = 'unsubscribed';

    // Reset interests.
    $cache->interests = new stdClass();
  }

  // Handle subscribes.
  if ($function == 'mailchimp_subscribe_process') {
    $cache->status = 'subscribed';
  }

  // Handle member updates.
  if ($function == 'mailchimp_update_member_process' || $function == 'mailchimp_subscribe_process') {

    // Update cached merge vars.
    if (!isset($cache->merge_fields)) {
      $cache->merge_fields = new stdClass();
    }
    foreach ($args['merge_vars'] as $key => $value) {
      $cache->merge_fields->{$key} = $value;
    }

    // Update cached interests.
    $cache->interests = new stdClass();
    foreach ($args['interests'] as $interest_group => $interests) {
      if (is_array($interests)) {
        foreach ($interests as $interest_id => $value) {
          if ($value !== 0) {
            $cache->interests->{$interest_id} = TRUE;
          }
        }
      }
      elseif ($interests) {
        $cache->interests->{$interest_group} = TRUE;
      }
    }
  }

  // Store the data back in the local cache.
  cache_set($list_id . '-' . $email, $cache, 'cache_mailchimp', CACHE_TEMPORARY);
}

/**
 * Update a members list subscription in real time or by adding to the queue.
 *
 * @see Mailchimp_Lists::updateMember()
 *
 * @return bool
 *   Success or failure.
 */
function mailchimp_update_member($list_id, $email, $merge_vars, $interests, $format = 'html', $double_optin = FALSE) {
  if (variable_get('mailchimp_cron', FALSE)) {
    $args = array(
      'list_id' => $list_id,
      'email' => $email,
      'merge_vars' => $merge_vars,
      'interests' => $interests,
      'format' => $format,
      'double_optin' => $double_optin,
    );
    return mailchimp_addto_queue('mailchimp_update_member_process', $args);
  }
  return mailchimp_update_member_process($list_id, $email, $merge_vars, $interests, $format, $double_optin);
}

/**
 * Wrapper around MailchimpLists::updateMember().
 *
 * @see MailchimpLists::updateMember()
 *
 * @return bool
 *   Success or failure.
 */
function mailchimp_update_member_process($list_id, $email, $merge_vars, $interests, $format, $double_optin = FALSE) {
  $result = FALSE;
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot update member without Mailchimp API. Check API key has been entered.');
    }
    $parameters = array(
      'status' => $double_optin ? \Mailchimp\MailchimpLists::MEMBER_STATUS_PENDING : \Mailchimp\MailchimpLists::MEMBER_STATUS_SUBSCRIBED,
      'email_type' => $format,
    );

    // Set interests.
    if (!empty($interests)) {
      $parameters['interests'] = (object) $interests;
    }

    // Set merge fields.
    if (!empty($merge_vars)) {
      $parameters['merge_fields'] = (object) $merge_vars;
    }

    // Update member.
    $result = $mc_lists
      ->updateMember($list_id, $email, $parameters);
    if (!empty($result) && isset($result->id)) {
      watchdog('mailchimp', '@email was updated in list @list_id.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_NOTICE);

      // Clear user cache:
      mailchimp_cache_clear_member($list_id, $email);
    }
    else {
      watchdog('mailchimp', 'A problem occurred updating @email on list @list.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_WARNING);
    }
  } catch (Exception $e) {
    if ($e
      ->getCode() == '400' && strpos($e
      ->getMessage(), 'Member In Compliance State') !== false && !$double_optin) {
      watchdog('mailchimp', 'Detected "Member In Compliance State" updating @email to list @list.  Trying again using double-opt in.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_INFO);
      return mailchimp_update_member_process($list_id, $email, $merge_vars, $interests, $format, TRUE);
    }
    watchdog('mailchimp', 'An error occurred updating @email on list @list. "%message"', array(
      '@email' => $email,
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  if ($double_optin) {
    drupal_set_message(t('Please check your email to confirm your subscription'), 'status', FALSE);
  }
  return $result;
}

/**
 * Retrieve all members of a given list with a given status.
 *
 * Note that this function can cause locking an is somewhat slow. It is not
 * recommended unless you know what you are doing! See the MCAPI documentation.
 */
function mailchimp_get_members($list_id, $status = 'subscribed', $options = array()) {
  $results = FALSE;
  if (lock_acquire('mailchimp_get_members', 60)) {
    try {

      /* @var \Mailchimp\MailchimpLists $mc_lists */
      $mc_lists = mailchimp_get_api_object('MailchimpLists');
      if (!$mc_lists) {
        throw new MailchimpException('Cannot get members without Mailchimp API. Check API key has been entered.');
      }
      $options['status'] = $status;
      if (!isset($options['count']) || empty($options['count'])) {
        $options['count'] = 500;
      }
      $results = $mc_lists
        ->getMembers($list_id, $options);
    } catch (Exception $e) {
      watchdog('mailchimp', 'An error occurred pulling member info for a list. "%message"', array(
        '%message' => $e
          ->getMessage(),
      ), WATCHDOG_ERROR);
    }
    lock_release('mailchimp_get_members');
  }
  return $results;
}

/**
 * Wrapper around MailchimpLists->addOrUpdateMember().
 *
 * $batch is an array where each element is an array formatted thus:
 *   'email' => 'example@example.com',
 *   'email_type' => 'html' or 'text',
 *   'merge_vars' => array('MERGEKEY' => 'value', 'MERGEKEY2' => 'value2'),
 */
function mailchimp_batch_update_members($list_id, $batch, $double_in = FALSE) {
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot batch subscribe to list without Mailchimp API. Check API key has been entered.');
    }
    if (!empty($batch)) {

      // Create a new batch update operation for each member.
      foreach ($batch as $batch_data) {

        // TODO: Remove 'advanced' earlier? Needed at all?
        unset($batch_data['merge_vars']['advanced']);
        $parameters = array(
          'email_type' => $batch_data['email_type'],
          'merge_fields' => (object) $batch_data['merge_vars'],
        );
        $mc_lists
          ->addOrUpdateMember($list_id, $batch_data['email'], $parameters, TRUE);
      }

      // Process batch operations.
      return $mc_lists
        ->processBatchOperations();
    }
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred performing batch subscribe/update. "%message"', array(
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
}

/**
 * Unsubscribe a member from a list.
 *
 * @param string $list_id
 *   A mailchimp list id.
 * @param string $email
 *   Email address to be unsubscribed.
 * @param bool $delete
 *   Indicates whether an email should be deleted or just unsubscribed.
 * @param bool $goodbye
 *   Indicates whether to send the goodbye email to the email address.
 * @param bool $notify
 *   Indicates whether to send the unsubscribe notification email to the address
 *   defined in the list email notification settings.
 *
 * @return bool
 *   Indicates whether unsubscribe was successful.
 */
function mailchimp_unsubscribe_member($list_id, $email, $delete = FALSE, $goodbye = FALSE, $notify = FALSE) {
  $result = FALSE;
  if (mailchimp_is_subscribed($list_id, $email)) {
    if (variable_get('mailchimp_cron', FALSE)) {
      $result = mailchimp_addto_queue('mailchimp_unsubscribe_process', array(
        'list_id' => $list_id,
        'email' => $email,
        'delete' => $delete,
        'goodbye' => $goodbye,
        'notify' => $notify,
      ));
    }
    else {
      $result = mailchimp_unsubscribe_process($list_id, $email, $delete, $goodbye, $notify);
    }
  }
  return $result;
}

/**
 * Unsubscribe a member from a list.
 *
 * @return bool
 *   Success or failure.
 */
function mailchimp_unsubscribe_process($list_id, $email, $delete, $goodbye, $notify) {
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot unsubscribe from list without Mailchimp API. Check API key has been entered.');
    }
    if ($delete) {

      // Remove member from list.
      $mc_lists
        ->removeMember($list_id, $email);
      watchdog('mailchimp', '@email was removed from list @list_id.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_INFO);
    }
    else {

      // Unsubscribe member.
      $parameters = array(
        'status' => MailchimpLists::MEMBER_STATUS_UNSUBSCRIBED,
      );
      $mc_lists
        ->updateMember($list_id, $email, $parameters);
      watchdog('mailchimp', '@email was unsubscribed from list @list_id.', array(
        '@email' => $email,
        '@list' => $list_id,
      ), WATCHDOG_INFO);
    }
    module_invoke_all('mailchimp_unsubscribe_user', $list_id, $email);

    // Clear user cache:
    mailchimp_cache_clear_member($list_id, $email);
    return TRUE;
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred unsubscribing @email from list @list. "%message"', array(
      '@email' => $email,
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Wrapper around MailchimpLists->getSegments().
 *
 * @param string $list_id
 *   A Mailchimp list id.
 * @param bool $reset
 *   Set to TRUE if list segments should not be loaded from cache.
 *
 * @return array
 *   Array of segments details.
 */
function mailchimp_get_segments($list_id, $reset = NULL) {
  $cache = $reset ? NULL : cache_get($list_id . '-segments', 'cache_mailchimp');

  // Return cached lists:
  if ($cache) {
    return $cache->data;
  }

  // Query segments from the MCAPI and store in cache:
  $segments = array();
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot get list segments without Mailchimp API. Check API key has been entered.');
    }
    $result = $mc_lists
      ->getSegments($list_id, array(
      'count' => 500,
    ));
    $segments = $result->total_items > 0 ? $result->segments : array();
    cache_set($list_id . '-segments', $segments, 'cache_mailchimp', CACHE_TEMPORARY);
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred requesting list segment information from Mailchimp. "%message"', array(
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $segments;
}

/**
 * Wrapper around MailchimpLists->addSegment().
 *
 * @param string $list_id
 *   A Mailchimp list id.
 * @param string $name
 *   A label for the segment.
 * @param string $type
 *   Can be 'static' or 'saved'.
 * @param array $segment_options
 *   Array of options for 'saved' segments. See Mailchimp API docs.
 *
 * @return int
 *   ID of the new segment.
 */
function mailchimp_segment_create($list_id, $name, $type, $segment_options = NULL) {
  $segment_id = FALSE;
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot add list segment without Mailchimp API. Check API key has been entered.');
    }
    $parameters = array(
      'type' => $type,
    );
    if ($type == 'saved') {
      $parameters['options'] = $segment_options;
    }
    $result = $mc_lists
      ->addSegment($list_id, $name, $parameters);
    if (!empty($result->id)) {
      $segment_id = $result->id;
    }

    // Clear the segment cache:
    mailchimp_get_segments($list_id, TRUE);
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred creating segment @segment for list @list. "%message"', array(
      '@segment' => $name,
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $segment_id;
}

/**
 * Add a specific subscriber to a static segment of a list.
 *
 * @param string $list_id
 *   ID of a Mailchimp list.
 * @param string $segment_id
 *   ID of a segment of the Mailchimp list.
 * @param string $email
 *   Email address to add to the segment (does NOT subscribe to the list).
 * @param bool $batch
 *   Whether to queue this for the batch processor. Defaults to TRUE.
 * @param string $queue_id
 *   The ID of the queue to use in batch processing.
 *
 * @return bool
 *   Success boolean
 */
function mailchimp_segment_add_subscriber($list_id, $segment_id, $email, $batch = TRUE, $queue_id = MAILCHIMP_BATCH_QUEUE_CRON) {
  $item = array(
    'email' => $email,
  );
  if (!$batch) {
    $batch = array(
      $item,
    );
    $success = mailchimp_segment_batch_add_subscribers($list_id, $segment_id, $batch);
  }
  else {
    $queue = DrupalQueue::get($queue_id);
    $queue
      ->createQueue();
    $success = $queue
      ->createItem(array(
      'function' => 'mailchimp_segment_batch_add_subscribers',
      'list_id' => $list_id,
      'arg' => $segment_id,
      'item' => $item,
    ));
    if (!$success) {
      watchdog('mailchimp', 'A problem occurred adding a Mailchimp segment subscribe to the queue. Email: @email List: @list Segment: @segment.', array(
        '@email' => $email,
        '@list' => $list_id,
        '@segment' => $segment_id,
      ), WATCHDOG_WARNING);
    }
  }
  return $success;
}

/**
 * Add a batch of email addresses to a static segment of a list.
 *
 * @param string $list_id
 *   ID of a Mailchimp list.
 * @param string $segment_id
 *   ID of a segment of the Mailchimp list.
 * @param array $batch
 *   Batch of email addresses to add to the segment (does NOT subscribe new).
 *
 * @return int
 *   Successful subscribe count
 */
function mailchimp_segment_batch_add_subscribers($list_id, $segment_id, $batch) {
  $count = 0;
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot batch add segment subscribers without Mailchimp API. Check API key has been entered.');
    }
    $segments_data = $mc_lists
      ->getSegments($list_id, array(
      'count' => 500,
    ));
    $matched_segment = NULL;
    foreach ($segments_data->segments as $segment) {
      if ($segment->id == $segment_id) {
        $matched_segment = $segment;
        continue;
      }
    }
    if ($matched_segment != NULL) {
      $parameters = array(
        'static_segment' => $batch,
      );
      $result = $mc_lists
        ->updateSegment($list_id, $segment_id, $matched_segment->name, $parameters);
      $count = isset($result->member_count) ? $result->member_count : 0;
    }
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred on batch segment add. List: @list_id Segment: @segment_id. "%message"', array(
      '@list_id' => $list_id,
      '@segment_id' => $segment_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $count;
}

/**
 * Wrapper around MailchimpCampaigns->getCampaign() to return data for a given
 * campaign.
 *
 * Data is stored in the Mailchimp cache.
 *
 * @param string $campaign_id
 *   The ID of the campaign to get data for.
 * @param bool $reset
 *   Set to TRUE if campaign data should not be loaded from cache.
 *
 * @return mixed
 *   Array of campaign data or FALSE if not found.
 */
function mailchimp_get_campaign_data($campaign_id, $reset = FALSE) {
  $cache = $reset ? NULL : cache_get('campaign_' . $campaign_id, 'cache_mailchimp');
  $campaign_data = FALSE;

  // Return cached lists:
  if ($cache) {
    return $campaign_data = $cache->data;
  }
  try {

    /* @var \Mailchimp\MailchimpCampaigns $mc_campaigns */
    $mc_campaigns = mailchimp_get_api_object('MailchimpCampaigns');
    if (!$mc_campaigns) {
      throw new MailchimpException('Cannot get list without Mailchimp API. Check API key has been entered.');
    }
    $response = $mc_campaigns
      ->getCampaign($campaign_id);
    if (!empty($response->id)) {
      $campaign_data = $response;
      cache_set('campaign_' . $campaign_id, $response, 'cache_mailchimp', CACHE_TEMPORARY);
    }
    else {
      $campaign_data = FALSE;
    }
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred retrieving campaign data for @campaign. "%message"', array(
      '@campaign' => $campaign_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
  }
  return $campaign_data;
}

/**
 * Wrapper around MailchimpLists->getListsForEmail()`.
 *
 * Returns all lists a given email address is currently subscribed to.
 *
 * @param string $email
 *   Email address to search.
 *
 * @return array
 *   Campaign structs containing id, web_id, name.
 */
function mailchimp_get_lists_for_email($email) {
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot get lists without Mailchimp API. Check API key has been entered.');
    }
    $lists = $mc_lists
      ->getListsForEmail($email);
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred retreiving lists data for @email. "%message"', array(
      '@email' => $email,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    $lists = array();
  }
  return $lists;
}

/**
 * Wrapper around MailchimpLists->getWebhooks().
 *
 * @param string $list_id
 *   Mailchimp API List ID.
 *
 * @return mixed
 *   Array of existing webhooks, or FALSE.
 */
function mailchimp_webhook_get($list_id) {
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot get webhook without Mailchimp API. Check API key has been entered.');
    }
    $result = $mc_lists
      ->getWebhooks($list_id);
    return $result->total_items > 0 ? $result->webhooks : FALSE;
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred reading webhooks for list @list. "%message"', array(
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Wrapper around MailchimpLists->addWebhook().
 *
 * @return mixed
 *   New webhook ID if added, FALSE otherwise.
 */
function mailchimp_webhook_add($list_id, $url, $events = array(), $sources = array()) {
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot add webhook without Mailchimp API. Check API key has been entered.');
    }
    $parameters = array(
      'events' => (object) $events,
      'sources' => (object) $sources,
    );
    $result = $mc_lists
      ->addWebhook($list_id, $url, $parameters);
    return $result->id;
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred adding webhook for list @list. "%message"', array(
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Wrapper around MailchimpLists->deleteWebhook().
 *
 * @return bool
 *   TRUE if deletion was successful, otherwise FALSE.
 */
function mailchimp_webhook_delete($list_id, $url) {
  try {

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    if (!$mc_lists) {
      throw new MailchimpException('Cannot delete webhook without Mailchimp API. Check API key has been entered.');
    }
    $result = $mc_lists
      ->getWebhooks($list_id);
    if ($result->total_items > 0) {
      foreach ($result->webhooks as $webhook) {
        if ($webhook->url == $url) {
          $mc_lists
            ->deleteWebhook($list_id, $webhook->id);
          return TRUE;
        }
      }
    }
    return FALSE;
  } catch (Exception $e) {
    watchdog('mailchimp', 'An error occurred deleting webhook for list @list. "%message"', array(
      '@list' => $list_id,
      '%message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Clear a mailchimp user memberinfo cache.
 *
 * @string $list_id
 * @string $email
 */
function mailchimp_cache_clear_member($list_id, $email) {
  cache_clear_all($list_id . '-' . $email, 'cache_mailchimp');
}

/**
 * Clear a mailchimp activity cache.
 *
 * @string $list_id
 */
function mailchimp_cache_clear_list_activity($list_id) {
  cache_clear_all('mailchimp_activity_' . $list_id, 'cache_mailchimp');
}

/**
 * Clear a mailchimp activity cache.
 *
 * @string $list_id
 */
function mailchimp_cache_clear_campaign($campaign_id) {
  cache_clear_all('mailchimp_campaign_' . $campaign_id, 'cache_mailchimp');
}

/**
 * Clear all mailchimp caches.
 */
function mailchimp_cache_clear_all() {
  cache_clear_all('*', 'cache_mailchimp', TRUE);
}

/**
 * Implements hook_flush_caches().
 */
function mailchimp_flush_caches() {
  return array(
    'cache_mailchimp',
  );
}

/**
 * Access callback for mailchimp_process_webhook().
 *
 * @string $key
 */
function mailchimp_process_webhook_access($key) {
  return $key == mailchimp_webhook_key();
}

/**
 * Process a webhook post from Mailchimp.
 */
function mailchimp_process_webhook() {
  if (empty($_POST)) {
    return "Mailchimp Webhook Endpoint.";
  }
  $data = $_POST['data'];
  $type = $_POST['type'];
  switch ($type) {
    case 'unsubscribe':
    case 'profile':
    case 'cleaned':
      mailchimp_get_memberinfo($data['list_id'], $data['email'], TRUE);
      break;
    case 'upemail':
      mailchimp_cache_clear_member($data['list_id'], $data['old_email']);
      mailchimp_get_memberinfo($data['list_id'], $data['new_email'], TRUE);
      break;
    case 'campaign':
      mailchimp_cache_clear_list_activity($data['list_id']);
      mailchimp_cache_clear_campaign($data['id']);
      break;
  }

  // Allow other modules to act on a webhook.
  module_invoke_all('mailchimp_process_webhook', $type, $data);

  // Log event:
  watchdog('mailchimp', 'Webhook type @type has been processed.', array(
    '@type' => $type,
  ), WATCHDOG_INFO);
  return NULL;
}

/**
 * Generate a key to include in the webhook url based on a hash.
 *
 * @string $list_id
 *
 * @return string
 *   The key.
 */
function mailchimp_webhook_key() {
  return drupal_hash_base64($GLOBALS['base_url'] . drupal_get_private_key() . drupal_get_hash_salt());
}

/**
 * Generate the webhook endpoint URL.
 *
 * @string $list_id
 *
 * @return string
 *   The endpoint URL.
 */
function mailchimp_webhook_url() {
  return $GLOBALS['base_url'] . '/mailchimp/webhook/' . mailchimp_webhook_key();
}

/**
 * Helper function to generate form elements for a list's interest groups.
 *
 * @param array $list
 *   Fully loaded array with mailchimp list settings as returned by
 *   mailchimp_get_list()
 * @param array $defaults
 *   Array of default values to use if no group subscription values already
 *   exist at Mailchimp.
 * @param string $email
 *   Optional email address to pass to the MCAPI and retrieve existing values
 *   for use as defaults.
 *
 * @return array
 *   A collection of form elements, one per interest group.
 */
function mailchimp_interest_groups_form_elements($list, $defaults = array(), $email = NULL) {
  $return = array();
  foreach ($list->intgroups as $group) {
    if ($group->type == 'hidden') {
      continue;
    }

    /* @var \Mailchimp\MailchimpLists $mc_lists */
    $mc_lists = mailchimp_get_api_object('MailchimpLists');
    $interest_data = $mc_lists
      ->getInterests($list->id, $group->id, array(
      'count' => 500,
    ));
    if (!empty($email)) {
      $memberinfo = mailchimp_get_memberinfo($list->id, $email);
    }

    // Set the form field type:
    switch ($group->type) {
      case 'radio':
        $field_type = 'radios';
        break;
      case 'dropdown':
        $field_type = 'select';
        break;
      default:
        $field_type = $group->type;
    }

    // Extract the field options:
    $options = array();
    $default_values = array();

    // Set interest options and default values.
    foreach ($interest_data->interests as $interest) {
      $options[$interest->id] = $interest->name;
      if (isset($memberinfo)) {
        if (isset($memberinfo->interests->{$interest->id}) && $memberinfo->interests->{$interest->id} === TRUE) {
          $default_values[$group->id][] = $interest->id;
        }
      }
      elseif (!empty($defaults)) {
        if (isset($defaults[$group->id][$interest->id]) && !empty($defaults[$group->id][$interest->id])) {
          $default_values[$group->id][] = $interest->id;
        }
      }
    }
    $return[$group->id] = array(
      '#type' => $field_type,
      '#title' => $group->title,
      '#options' => $options,
      '#empty_option' => t('-- select --'),
      '#default_value' => isset($default_values[$group->id]) ? $default_values[$group->id] : array(),
      '#attributes' => array(
        'class' => array(
          'mailchimp-newsletter-interests-' . $list->id,
        ),
      ),
    );
  }
  return $return;
}

/**
 * Convert mailchimp form elements to Drupal Form API.
 *
 * @param array $mergevar
 *   The mailchimp-formatted form element to convert.
 *
 * @return array
 *   A properly formatted drupal form element.
 */
function mailchimp_insert_drupal_form_tag($mergevar, $placeholder) {

  // Insert common FormAPI properties:
  $input = array(
    '#weight' => $mergevar->display_order,
    '#required' => $mergevar->required,
    '#default_value' => $mergevar->default_value,
  );
  $placeholder_req = $mergevar->required ? ' *' : '';
  $title = t('@mergevar', array(
    '@mergevar' => $mergevar->name,
  ));

  // Check to see if we should set placeholder or #title attribute
  if ($placeholder) {
    $input['#attributes']['placeholder'] = $title . $placeholder_req;
  }
  else {
    $input['#title'] = $title;
  }
  switch ($mergevar->type) {
    case 'address':

      // Sub-array of address elements according to Mailchimp specs.
      // https://apidocs.mailchimp.com/api/2.0/lists/subscribe.php
      $input['#type'] = 'container';
      $input['#tree'] = TRUE;
      $input['addr1'] = array(
        '#type' => 'textfield',
      );
      $input['addr2'] = array(
        '#type' => 'textfield',
      );
      $input['city'] = array(
        '#type' => 'textfield',
      );
      $input['state'] = array(
        '#type' => 'textfield',
        '#size' => 2,
        '#maxlength' => 2,
      );
      $input['zip'] = array(
        '#type' => 'textfield',
        '#size' => 6,
        '#maxlength' => 6,
      );
      $input['country'] = array(
        '#type' => 'textfield',
        '#size' => 2,
        '#maxlength' => 2,
      );
      if (!$placeholder) {
        $input['addr1']['#title'] = t('Address 1');
        $input['addr2']['#title'] = t('Address 2');
        $input['city']['#title'] = t('City');
        $input['state']['#title'] = t('State');
        $input['zip']['#title'] = t('Zip');
        $input['country']['#title'] = t('Country');
      }
      else {
        $input['addr1']['#attributes']['placeholder'] = t('Address 1') . $placeholder_req;
        $input['addr2']['#attributes']['placeholder'] = t('Address 2') . $placeholder_req;
        $input['city']['#attributes']['placeholder'] = t('City') . $placeholder_req;
        $input['state']['#attributes']['placeholder'] = t('State') . $placeholder_req;
        $input['zip']['#attributes']['placeholder'] = t('Zip') . $placeholder_req;
        $input['country']['#attributes']['placeholder'] = t('Country') . $placeholder_req;
      }
      break;
    case 'dropdown':

      // Dropdown is mapped to <select> element in Drupal Form API.
      $input['#type'] = 'select';

      // Creates options, we must delete array keys to have relevant information
      // on Mailchimp.
      $choices = array();
      foreach ($mergevar->options->choices as $choice) {
        $choices[$choice] = $choice;
      }
      $input['#options'] = $choices;
      break;
    case 'radio':

      // Radio is mapped to <input type='radio' /> i.e. 'radios' element in
      // Drupal Form API.
      $input['#type'] = 'radios';

      // Creates options, we must delete array keys to have relevant information
      // on Mailchimp.
      $choices = array();
      foreach ($mergevar->options->choices as $choice) {
        $choices[$choice] = $choice;
      }
      $input['#options'] = $choices;
      break;
    case 'email':
      if (element_info_property('emailfield', '#type')) {

        // Set to an HTML5 email type if 'emailfield' is supported:
        $input['#type'] = 'emailfield';
      }
      else {

        // Set to standard text type if 'emailfield' isn't defined:
        $input['#type'] = 'textfield';

        // Add validation - not necessary for emailfield type input because
        // the "elements" module provides it for elements of type emailfield.
        $input['#element_validate'] = array(
          'mailchimp_validate_email',
        );
      }
      $input['#size'] = isset($mergevar->options->size) ? $mergevar->options->size : 25;
      break;
    case 'phone':
      $input['#type'] = 'textfield';
      $input['#size'] = isset($mergevar->options->size) ? $mergevar->options->size : 25;
      $input['#attributes'] = array(
        'type' => 'tel',
      );
      break;
    default:

      // This is a standard input[type=text] or something we can't handle with
      // Drupal FormAPI.
      $input['#type'] = 'textfield';
      $input['#size'] = isset($mergevar->options->size) ? $mergevar->options->size : 25;
      break;
  }

  // Special cases for Mailchimp hidden defined fields:
  if ($mergevar->public === FALSE) {
    $input['#access'] = FALSE;
  }
  return $input;
}

/**
 * Form element validation for email input of #type 'textfield'.
 *
 * Note: #maxlength and #required are validated by _form_validate() already.
 */
function mailchimp_validate_email(&$element, &$form_state) {
  if ($element['#value'] && !valid_email_address($element['#value'])) {
    form_error($element, t('"%mail" is not a valid email address.', array(
      '%mail' => $element['#value'],
    )));
  }
}

/**
 * Implements hook_cron().
 *
 * We don't use batch API calls currently as it would require sorting through
 * a lot of options here. Instead, we will provide VBO functions to perform
 * large unsubscribes and subscribes and specifically call the batch functions.
 */
function mailchimp_cron() {
  $queue = DrupalQueue::get(MAILCHIMP_QUEUE_CRON);
  $queue
    ->createQueue();
  $queue_count = $queue
    ->numberOfItems();
  if ($queue_count > 0) {
    $batch_limit = variable_get('mailchimp_batch_limit', 100);
    $batch_size = $queue_count < $batch_limit ? $queue_count : $batch_limit;
    $count = 0;
    while ($count < $batch_size) {
      if ($item = $queue
        ->claimItem()) {
        call_user_func_array($item->data['function'], $item->data['args']);
        $queue
          ->deleteItem($item);
      }
      $count++;
    }
  }
}

/**
 * Implements hook_variable_group_info().
 */
function mailchimp_variable_group_info() {
  $groups['mailchimp'] = array(
    'title' => t('Mailchimp'),
    'description' => t('Settings related to Mailchimp.'),
    'access' => 'administer mailchimp',
    'path' => array(
      'admin/config/services/mailchimp',
    ),
  );
  return $groups;
}

/**
 * Implements hook_variable_info().
 */
function mailchimp_variable_info($options) {
  $variable['mailchimp_api_key'] = array(
    'title' => t('Mailchimp API Key', array(), $options),
    'group' => 'mailchimp',
  );
  $variable['mailchimp_cron'] = array(
    'title' => t('Use batch processing', array(), $options),
    'type' => 'boolean',
    'group' => 'mailchimp',
    'default' => FALSE,
  );
  $variable['mailchimp_batch_limit'] = array(
    'title' => t('Batch limit', array(), $options),
    'type' => 'number',
    'group' => 'mailchimp',
    'default' => 100,
  );
  return $variable;
}

/**
 * Replace an array of merge field placeholder tokens with their values.
 *
 * @param array $mergefields
 *   An array of merge fields and token place holders to be expanded. The key
 *   of the array is the Mailchimp field name, the value is the token that
 *   should be expanded.
 * @param object $entity
 *   The entity that contains the values to use for token replacement.
 * @param string $entity_type
 *   The entity type of the entity being used.
 * @param string $list_id
 *
 * @return mixed
 *   Associative array of Mailchimp fields with values taken from the provided
 *   entity.
 */
function mailchimp_mergevars_populate($mergefields, $entity, $entity_type, $list_id = NULL) {

  // Get mergevar settings from the list.
  $list_mergevars = array();
  if (!empty($list_id)) {
    $list = mailchimp_get_list($list_id, FALSE);
    if (!empty($list) && isset($list->mergevars)) {

      // Create array of mergevars indexed by tag. Used to map to mergefields.
      foreach ($list->mergevars as $list_mergevar) {
        $list_mergevars[$list_mergevar->tag] = $list_mergevar;
      }
    }
  }
  $mergevars = drupal_static(__FUNCTION__, array());
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  if (!isset($mergevars[$bundle . ':' . $id])) {
    foreach ($mergefields as $key => $token) {
      $mergevar_value = token_replace($token, array(
        $entity_type => $entity,
      ), array(
        'clear' => TRUE,
      ));
      if (isset($list_mergevars[$key])) {
        if ($list_mergevars[$key]->type == 'date') {
          $mergevar_value = mailchimp_format_date($mergevar_value);
        }
        elseif ($list_mergevars[$key]->type == 'phone') {
          $mergevar_value = mailchimp_format_phone_number($mergevar_value);
        }
      }
      if (!empty($mergevar_value)) {
        $mergevars[$bundle . ':' . $id][$key] = $mergevar_value;
      }
    }
  }
  return $mergevars[$bundle . ':' . $id];
}

/**
 * Formats a phone number string before sending to Mailchimp's API.
 *
 * Currently works only for USA and Canada phone numbers.
 *
 * @param string $input
 *   The input phone number.
 *
 * @return string
 *   The formatted phone number.
 *
 * @see http://kb.mailchimp.com/lists/growth/format-guidelines-for-your-import-file#phone
 */
function mailchimp_format_phone_number($input) {
  $base_phone_number = preg_replace('/[^0-9]/s', '', $input);

  // Only attempt to format ten-digit USA and Canada phone numbers.
  if (strlen($base_phone_number) != 10) {
    return $input;
  }

  // Convert phone number to format: "555-123-4567".
  $formatted_number = substr($base_phone_number, 0, 3) . '-' . substr($base_phone_number, 3, 3) . '-' . substr($base_phone_number, 6);
  return $formatted_number;
}

/**
 * Formats a date string before sending to Mailchimp's API.
 *
 * @param string $input
 *   The input date.
 *
 * @return string
 *   The formatted date.
 *
 * @see http://kb.mailchimp.com/lists/growth/format-guidelines-for-your-import-file#date
 */
function mailchimp_format_date($input) {

  // Attempt to convert date string to a timestamp.
  // Remove time in standard Drupal date formats. It confuses strtotime().
  $date = trim(preg_replace('/(- )?[0-9]+:[0-9]+/', '', $input));
  $timestamp = strtotime($date);
  if ($timestamp !== FALSE) {

    // Return formatted date.
    return date('Y-m-d', $timestamp);
  }
  else {

    // Unable to parse the date string.
    return NULL;
  }
}

/**
 * Implements hook_page_build().
 */
function mailchimp_page_build(&$page) {

  // Insert JavaScript for Mailchimp Connected Sites, if enabled.
  if (variable_get('mailchimp_enable_connected', FALSE) && mailchimp_get_api_object() && class_exists('\\Mailchimp\\MailchimpConnectedSites')) {

    // Limit JavaScript embed to pre-configured paths.
    $connected_site_paths = variable_get('mailchimp_connected_paths', FALSE);
    $valid_paths = explode("\r\n", $connected_site_paths);
    $path = current_path();
    if (drupal_is_front_page() && in_array('<front>', $valid_paths) || in_array($path, $valid_paths)) {
      $connected_site_id = variable_get('mailchimp_connected_id', FALSE);
      if (!empty($connected_site_id)) {
        try {

          /* @var \Mailchimp\MailchimpConnectedSites $mc_connected */
          $mc_connected = mailchimp_get_api_object('MailchimpConnectedSites');

          // Verify Connected Site exists on the Mailchimp side and insert JS.
          try {
            $connected_site = $mc_connected
              ->getConnectedSite($connected_site_id);
            if (!empty($connected_site)) {
              $mcjs = array(
                '#type' => 'markup',
                '#markup' => $connected_site->site_script->fragment,
              );
              drupal_add_html_head($mcjs, 'mcjs');
            }
          } catch (Exception $e) {

            // Throw exception only for errors other than member not found.
            if ($e
              ->getCode() != 404) {
              throw new Exception($e
                ->getMessage(), $e
                ->getCode(), $e);
            }
          }
        } catch (Exception $e) {
          watchdog('mailchimp', 'An error occurred requesting connected site information. "%message"', array(
            '%message' => $e
              ->getMessage(),
          ), WATCHDOG_ERROR);
        }
      }
    }
  }
}

Functions

Namesort descending Description
mailchimp_addto_queue Add a Mailchimp subscription task to the queue.
mailchimp_apikey_ready_access Access callback for mailchimp submodule menu items.
mailchimp_batch_update_members Wrapper around MailchimpLists->addOrUpdateMember().
mailchimp_cache_clear_all Clear all mailchimp caches.
mailchimp_cache_clear_campaign Clear a mailchimp activity cache.
mailchimp_cache_clear_list_activity Clear a mailchimp activity cache.
mailchimp_cache_clear_member Clear a mailchimp user memberinfo cache.
mailchimp_cron Implements hook_cron().
mailchimp_flush_caches Implements hook_flush_caches().
mailchimp_format_date Formats a date string before sending to Mailchimp's API.
mailchimp_format_phone_number Formats a phone number string before sending to Mailchimp's API.
mailchimp_get_api_object Get an instance of the Mailchimp library.
mailchimp_get_campaign_data Wrapper around MailchimpCampaigns->getCampaign() to return data for a given campaign.
mailchimp_get_list Gets a single Mailchimp list by ID.
mailchimp_get_lists Gets Mailchimp lists. Can be filtered by an array of list IDs.
mailchimp_get_lists_for_email Wrapper around MailchimpLists->getListsForEmail()`.
mailchimp_get_memberinfo Get the Mailchimp member info for a given email address and list.
mailchimp_get_members Retrieve all members of a given list with a given status.
mailchimp_get_mergevars Wrapper around MailchimpLists->getMergeFields().
mailchimp_get_segments Wrapper around MailchimpLists->getSegments().
mailchimp_insert_drupal_form_tag Convert mailchimp form elements to Drupal Form API.
mailchimp_interest_groups_form_elements Helper function to generate form elements for a list's interest groups.
mailchimp_is_subscribed Check if the given email is subscribed to the given list.
mailchimp_libraries_info Implements hook_libraries_info().
mailchimp_menu Implements hook_menu().
mailchimp_mergevars_populate Replace an array of merge field placeholder tokens with their values.
mailchimp_page_build Implements hook_page_build().
mailchimp_permission Implements hook_permission().
mailchimp_process_webhook Process a webhook post from Mailchimp.
mailchimp_process_webhook_access Access callback for mailchimp_process_webhook().
mailchimp_segment_add_subscriber Add a specific subscriber to a static segment of a list.
mailchimp_segment_batch_add_subscribers Add a batch of email addresses to a static segment of a list.
mailchimp_segment_create Wrapper around MailchimpLists->addSegment().
mailchimp_subscribe Subscribe a user to a Mailchimp list in real time or by adding to the queue.
mailchimp_subscribe_process Wrapper around MailchimpLists::addOrUpdateMember().
mailchimp_unsubscribe_member Unsubscribe a member from a list.
mailchimp_unsubscribe_process Unsubscribe a member from a list.
mailchimp_update_local_cache Updates the local cache for a user as though a queued request had been processed.
mailchimp_update_member Update a members list subscription in real time or by adding to the queue.
mailchimp_update_member_process Wrapper around MailchimpLists::updateMember().
mailchimp_validate_email Form element validation for email input of #type 'textfield'.
mailchimp_variable_group_info Implements hook_variable_group_info().
mailchimp_variable_info Implements hook_variable_info().
mailchimp_webhook_add Wrapper around MailchimpLists->addWebhook().
mailchimp_webhook_delete Wrapper around MailchimpLists->deleteWebhook().
mailchimp_webhook_get Wrapper around MailchimpLists->getWebhooks().
mailchimp_webhook_key Generate a key to include in the webhook url based on a hash.
mailchimp_webhook_url Generate the webhook endpoint URL.
_mailchimp_get_user_agent Gets the user agent string for this installation of Mailchimp.
_mailchimp_list_cmp Helper function used by uasort() to sort lists alphabetically by name.

Constants