/**
  * Function to actually build the form
  *
  * @return None
  * @access public
  */
 public function buildQuickForm()
 {
     $this->addFormRule(array('CRM_Mailchimp_Form_Setting', 'formRule'), $this);
     CRM_Core_Resources::singleton()->addStyleFile('uk.co.vedaconsulting.mailchimp', 'css/mailchimp.css');
     $webhook_url = CRM_Utils_System::url('civicrm/mailchimp/webhook', 'reset=1', TRUE, NULL, FALSE, TRUE);
     $this->assign('webhook_url', 'Webhook URL - ' . $webhook_url);
     // Add the API Key Element
     $this->addElement('text', 'api_key', ts('API Key'), array('size' => 48));
     // Add the User Security Key Element
     $this->addElement('text', 'security_key', ts('Security Key'), array('size' => 24));
     // Add Enable or Disable Debugging
     $enableOptions = array(1 => ts('Yes'), 0 => ts('No'));
     $this->addRadio('enable_debugging', ts('Enable Debugging'), $enableOptions, NULL);
     // Create the Submit Button.
     $buttons = array(array('type' => 'submit', 'name' => ts('Save & Test')));
     $groups = CRM_Mailchimp_Utils::getGroupsToSync(array(), null, $membership_only = TRUE);
     foreach ($groups as $group_id => $details) {
         $list = new Mailchimp_Lists(CRM_Mailchimp_Utils::mailchimp());
         $webhookoutput = $list->webhooks($details['list_id']);
         if ($webhookoutput[0]['sources']['api'] == 1) {
             CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Form_Setting - API is set in Webhook setting for listID', $details['list_id']);
             $listID = $details['list_id'];
             CRM_Core_Session::setStatus(ts('API is set in Webhook setting for listID %1', array(1 => $listID)), ts('Error'), 'error');
             break;
         }
     }
     // Add the Buttons.
     $this->addButtons($buttons);
 }
 /**
  * Tests CRM_Mailchimp_Utils::splitGroupTitlesFromMailchimp.
  *
  * Similar data as above except that:
  *
  * - it's the Mailchimp `interest_name` that's used for comparison.
  *
  * - titles from the Mailchimp API are comma-space separated and commas in
  *   names are escaped with `\`
  *
  */
 public function testGroupTitleSplittingFromMailchimp()
 {
     $groups = [1 => ['is_mc_update_grouping' => 1, 'interest_name' => 'sponsored walk'], 2 => ['is_mc_update_grouping' => 1, 'interest_name' => 'sponsored walk, 2015'], 3 => ['is_mc_update_grouping' => 1, 'interest_name' => 'Never used'], 3 => ['is_mc_update_grouping' => 0, 'interest_name' => 'Invalid - not is_mc_update_grouping']];
     $tests = ['aye, sponsored walk' => [1], 'aye, sponsored walk, bee' => [1], 'sponsored walk, bee' => [1], 'sponsored walk, sponsored walk\\, 2015' => [1, 2], 'sponsored walk' => [1], 'sponsored walk\\, 2015' => [2], 'Invalid - not is_mc_update_grouping' => []];
     foreach ($tests as $input => $expected) {
         $result = CRM_Mailchimp_Utils::splitGroupTitlesFromMailchimp($input, $groups);
         sort($result);
         $this->assertEquals($expected, $result, "Test case '{$input}' failed");
     }
 }
 /**
  * Sugar function for adjusting fixture: uses CiviCRM API to delete all
  * GroupContact records between the contact and the group specified.
  *
  * @param array $contact Set to static::$civicrm_contact_{1,2}
  * @param int   $group_id Set to
  *              static::$civicrm_group_id_interest_{1,2}
  */
 public function deleteGroup($contact, $group_id, $disable_post_hooks = FALSE)
 {
     if ($disable_post_hooks) {
         $original_state = CRM_Mailchimp_Utils::$post_hook_enabled;
         CRM_Mailchimp_Utils::$post_hook_enabled = FALSE;
     }
     $result = civicrm_api3('GroupContact', 'delete', ['group_id' => $group_id, 'contact_id' => $contact['contact_id']]);
     if ($disable_post_hooks) {
         CRM_Mailchimp_Utils::$post_hook_enabled = $original_state;
     }
     return $result;
 }
 /**
  * New contacts from Mailchimp need bringing into CiviCRM.
  */
 static function syncPullUpdates(CRM_Queue_TaskContext $ctx, $listID)
 {
     // Prepare the groups that we need to update
     $stats[$listID]['added'] = $stats[$listID]['removed'] = 0;
     // We need the membership group and any groups mapped to interest groupings with the allow MC updates option set.
     $membership_group_id = FALSE;
     $updatable_grouping_groups = array();
     foreach (CRM_Mailchimp_Utils::getGroupsToSync(array(), $listID) as $groupID => $details) {
         if (!$details['grouping_id']) {
             $membership_group_id = $groupID;
         } elseif ($details['is_mc_update_grouping']) {
             // This group is one that we allow Mailchimp to update CiviCRM with.
             $updatable_grouping_groups[$groupID] = $details;
         }
     }
     // all Mailchimp table
     $dao = CRM_Core_DAO::executeQuery("SELECT m.*, c.groupings c_groupings\n      FROM tmp_mailchimp_push_m m\n      LEFT JOIN tmp_mailchimp_push_c c ON m.email = c.email\n      ;");
     // Loop the $dao object creating/finding contacts in CiviCRM.
     $groupContactRemoves = $groupContact = array();
     while ($dao->fetch()) {
         $params = array('FNAME' => $dao->first_name, 'LNAME' => $dao->last_name, 'EMAIL' => $dao->email);
         // Update/create contact.
         $contact_id = CRM_Mailchimp_Utils::updateContactDetails($params);
         if ($contact_id) {
             // Ensure the contact is in the membership group.
             if (!$dao->c_groupings) {
                 // This contact was not found in the CiviCRM table.
                 // Therefore they are not in the membership group.
                 // (actually they could have an email problem as well, but that's OK).
                 // Add them into the membership group.
                 $groupContact[$membership_group_id][] = $contact_id;
                 $civi_groupings = array();
                 $stats[$listID]['added']++;
             } else {
                 // This contact is in C and MC, but has differences.
                 // unpack the group membership from CiviCRM.
                 $civi_groupings = unserialize($dao->c_groupings);
             }
             // unpack the group membership reported by MC
             $mc_groupings = unserialize($dao->groupings);
             // Now sort out the grouping_groups for those we are supposed to allow updates for
             foreach ($updatable_grouping_groups as $groupID => $details) {
                 // Should this person be in this grouping:group according to MC?
                 if (!empty($mc_groupings[$details['grouping_id']][$details['group_id']])) {
                     // They should be in this group.
                     if (empty($civi_groupings[$details['grouping_id']][$details['group_id']])) {
                         // But they're not! Plan to add them in.
                         $groupContact[$groupID][] = $contact_id;
                     }
                 } else {
                     // They should NOT be in this group.
                     if (!empty($civi_groupings[$details['grouping_id']][$details['group_id']])) {
                         // But they ARE. Plan to remove them.
                         $groupContactRemoves[$groupID][] = $contact_id;
                     }
                 }
             }
         }
     }
     // And now, what if a contact is not in the Mailchimp list? We must remove them from the membership group.
     $dao = CRM_Core_DAO::executeQuery("SELECT c.contact_id\n      FROM tmp_mailchimp_push_c c\n      WHERE NOT EXISTS (\n        SELECT m.email FROM tmp_mailchimp_push_m m WHERE m.email=c.email\n      );");
     // Loop the $dao object creating/finding contacts in CiviCRM.
     while ($dao->fetch()) {
         $groupContactRemoves[$membership_group_id][] = $dao->contact_id;
         $stats[$listID]['removed']++;
     }
     // Log group contacts which are going to be added to CiviCRM
     CRM_Core_Error::debug_var('Mailchimp $groupContact= ', $groupContact);
     // FIXME: dirty hack setting a variable in session to skip post hook
     require_once 'CRM/Core/Session.php';
     $session = CRM_Core_Session::singleton();
     $session->set('skipPostHook', 'yes');
     if ($groupContact) {
         // We have some contacts to add into groups...
         foreach ($groupContact as $groupID => $contactIDs) {
             CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIDs, $groupID, 'Admin', 'Added');
         }
     }
     // Log group contacts which are going to be removed from CiviCRM
     CRM_Core_Error::debug_var('Mailchimp $groupContactRemoves= ', $groupContactRemoves);
     if ($groupContactRemoves) {
         // We have some contacts to add into groups...
         foreach ($groupContactRemoves as $groupID => $contactIDs) {
             CRM_Contact_BAO_GroupContact::removeContactsFromGroup($contactIDs, $groupID, 'Admin', 'Removed');
         }
     }
     // FIXME: unset variable in session
     $session->set('skipPostHook', '');
     static::updatePullStats($stats);
     // Finally, finish up by removing the two temporary tables
     CRM_Core_DAO::executeQuery("DROP TABLE tmp_mailchimp_push_m;");
     CRM_Core_DAO::executeQuery("DROP TABLE tmp_mailchimp_push_c;");
     return CRM_Queue_Task::TASK_SUCCESS;
 }
 /**
  * Mailchimp in their wisdom changed all the Ids for interests.
  *
  * So we have to map on names and then update our stored Ids.
  *
  * Also change cronjobs.
  */
 public function upgrade_20()
 {
     $this->ctx->log->info('Applying update to v2.0 Updating Mailchimp Interest Ids to fit their new API');
     // New
     $api = CRM_Mailchimp_Utils::getMailchimpApi();
     // Old
     $mcLists = new Mailchimp_Lists(CRM_Mailchimp_Utils::mailchimp());
     // Use new API to get lists. Allow for 10,000 lists so we don't bother
     // batching.
     $lists = [];
     foreach ($api->get("/lists", ['fields' => 'lists.id,lists.name', 'count' => 10000])->data->lists as $list) {
         $lists[$list->id] = ['name' => $list->name];
     }
     $queries = [];
     // Loop lists.
     foreach (array_keys($lists) as $list_id) {
         // Fetch Interest categories.
         $categories = $api->get("/lists/{$list_id}/interest-categories", ['count' => 10000, 'fields' => 'categories.id,categories.title'])->data->categories;
         if (!$categories) {
             continue;
         }
         // Old: fetch all categories (groupings) and interests (groups) in one go:
         $old = $mcLists->interestGroupings($list_id);
         // New: fetch interests for each category.
         foreach ($categories as $category) {
             // $lists[$list_id]['categories'][$category->id] = ['name' => $category->title];
             // Match this category by name with the old 'groupings'
             $matched_old_grouping = FALSE;
             foreach ($old as $old_grouping) {
                 if ($old_grouping['name'] == $category->title) {
                     $matched_old_grouping = $old_grouping;
                     break;
                 }
             }
             if ($matched_old_grouping) {
                 // Found a match.
                 $cat_queries[] = ['list_id' => $list_id, 'old' => $matched_old_grouping['id'], 'new' => $category->id];
                 // Now do interests (old: groups)
                 $interests = $api->get("/lists/{$list_id}/interest-categories/{$category->id}/interests", ['fields' => 'interests.id,interests.name', 'count' => 10000])->data->interests;
                 foreach ($interests as $interest) {
                     // Can we find this interest by name?
                     $matched_old_group = FALSE;
                     foreach ($matched_old_grouping['groups'] as $old_group) {
                         if ($old_group['name'] == $interest->name) {
                             $int_queries[] = ['list_id' => $list_id, 'old' => $old_group['id'], 'new' => $interest->id];
                             break;
                         }
                     }
                 }
             }
         }
     }
     foreach ($cat_queries as $params) {
         CRM_Core_DAO::executeQuery('UPDATE civicrm_value_mailchimp_settings ' . 'SET mc_grouping_id = %1 ' . 'WHERE mc_list_id = %2 AND mc_grouping_id = %3;', [1 => [$params['new'], 'String'], 2 => [$params['list_id'], 'String'], 3 => [$params['old'], 'String']]);
     }
     foreach ($int_queries as $params) {
         CRM_Core_DAO::executeQuery('UPDATE civicrm_value_mailchimp_settings ' . 'SET mc_group_id = %1 ' . 'WHERE mc_list_id = %2 AND mc_group_id = %3;', [1 => [$params['new'], 'String'], 2 => [$params['list_id'], 'String'], 3 => [$params['old'], 'String']]);
     }
     // Now cron jobs. Delete all mailchimp ones.
     $result = civicrm_api3('Job', 'get', array('sequential' => 1, 'api_entity' => "mailchimp"));
     if ($result['count']) {
         // Should only be one, but just in case...
         foreach ($result['values'] as $old) {
             // Double check id exists!
             if (!empty($old['id'])) {
                 civicrm_api3('Job', 'delete', ['id' => $old['id']]);
             }
         }
     }
     // Create Push Sync job.
     $params = array('sequential' => 1, 'name' => 'Mailchimp Push Sync', 'description' => 'Sync contacts between CiviCRM and MailChimp, assuming CiviCRM to be correct. Please understand the implications before using this.', 'run_frequency' => 'Daily', 'api_entity' => 'Mailchimp', 'api_action' => 'pushsync', 'is_active' => 0);
     $result = civicrm_api3('job', 'create', $params);
     // Create Pull Sync job.
     $params = array('sequential' => 1, 'name' => 'Mailchimp Pull Sync', 'description' => 'Sync contacts between CiviCRM and MailChimp, assuming Mailchimp to be correct. Please understand the implications before using this.', 'run_frequency' => 'Daily', 'api_entity' => 'Mailchimp', 'api_action' => 'pullsync', 'is_active' => 0);
     $result = civicrm_api3('job', 'create', $params);
     return TRUE;
 }
 /**
  * Function to process the form
  *
  * @access public
  *
  * @return None
  */
 public function postProcess()
 {
     // Store the submitted values in an array.
     $params = $this->controller->exportValues($this->_name);
     // Save the API Key & Save the Security Key
     if (CRM_Utils_Array::value('api_key', $params) || CRM_Utils_Array::value('security_key', $params)) {
         CRM_Core_BAO_Setting::setItem($params['api_key'], self::MC_SETTING_GROUP, 'api_key');
         CRM_Core_BAO_Setting::setItem($params['security_key'], self::MC_SETTING_GROUP, 'security_key');
         CRM_Core_BAO_Setting::setItem($params['enable_debugging'], self::MC_SETTING_GROUP, 'enable_debugging');
         try {
             $mcClient = CRM_Mailchimp_Utils::getMailchimpApi(TRUE);
             $response = $mcClient->get('/');
             if (empty($response->data->account_name)) {
                 throw new Exception("Could not retrieve account details, although a response was received. Somthing's not right.");
             }
         } catch (Exception $e) {
             CRM_Core_Session::setStatus($e->getMessage());
             return FALSE;
         }
         $message = "Following is the account information received from API callback:<br/>\n      <table class='mailchimp-table'>\n      <tr><td>Account Name:</td><td>" . htmlspecialchars($response->data->account_name) . "</td></tr>\n      <tr><td>Account Email:</td><td>" . htmlspecialchars($response->data->email) . "</td></tr>\n      </table>";
         CRM_Core_Session::setStatus($message);
     }
 }
 /**
  * Check that the contact's email is not a member of the test list at
  * Mailchimp.
  *
  * @param array $contact e.g. static::$civicrm_contact_1
  */
 public function assertContactNotListMember($contact)
 {
     $api = CRM_Mailchimp_Utils::getMailchimpApi();
     try {
         $subscriber_hash = static::$civicrm_contact_1['subscriber_hash'];
         $result = $api->get("/lists/" . static::$test_list_id . "/members/{$contact['subscriber_hash']}", ['fields' => 'status']);
     } catch (CRM_Mailchimp_RequestErrorException $e) {
         $this->assertEquals(404, $e->response->http_code);
     }
 }
 static function manageCiviCRMGroupSubcription($contactID = array(), $requestData, $action)
 {
     CRM_Mailchimp_Utils::checkDebug('Start- CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $contactID= ', $contactID);
     CRM_Mailchimp_Utils::checkDebug('Start- CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $requestData= ', $requestData);
     CRM_Mailchimp_Utils::checkDebug('Start- CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $requestType= ', $action);
     if (empty($contactID) || empty($requestData['list_id']) || empty($action)) {
         return NULL;
     }
     $listID = $requestData['list_id'];
     $groupContactRemoves = $groupContactAdditions = array();
     // Deal with subscribe/unsubscribe.
     // We need the CiviCRM membership group for this list.
     $groups = CRM_Mailchimp_Utils::getGroupsToSync(array(), $listID, $membership_only = TRUE);
     $allGroups = CRM_Mailchimp_Utils::getGroupsToSync(array(), $listID, $membership_only = FALSE);
     if (!$groups) {
         // This list is not mapped to a group in CiviCRM.
         return NULL;
     }
     $_ = array_keys($groups);
     $membershipGroupID = $_[0];
     if ($action == 'subscribe') {
         $groupContactAdditions[$membershipGroupID][] = $contactID;
     } elseif ($action == 'unsubscribe') {
         $groupContactRemoves[$membershipGroupID][] = $contactID;
         $mcGroupings = array();
         foreach (empty($requestData['merges']['GROUPINGS']) ? array() : $requestData['merges']['GROUPINGS'] as $grouping) {
             foreach (explode(', ', $grouping['groups']) as $group) {
                 $mcGroupings[$grouping['id']][$group] = 1;
             }
         }
         foreach ($allGroups as $groupID => $details) {
             if ($groupID != $membershipGroupID && $details['is_mc_update_grouping']) {
                 if (!empty($mcGroupings[$details['grouping_id']][$details['group_name']])) {
                     $groupContactRemoves[$groupID][] = $contactID;
                 }
             }
         }
     }
     // Now deal with all the groupings that are mapped to CiviCRM groups for this list
     // and that have the allow MC updates flag set.
     /* Sample groupings from MC:
      *
      *     [GROUPINGS] => Array(
      *       [0] => Array(
      *           [id] => 11365
      *           [name] => CiviCRM
      *           [groups] => special
      *       ))
      * Re-map to mcGroupings[grouping_id][group_name] = 1;
      */
     $mcGroupings = array();
     foreach (empty($requestData['merges']['GROUPINGS']) ? array() : $requestData['merges']['GROUPINGS'] as $grouping) {
         foreach (explode(', ', $grouping['groups']) as $group) {
             $mcGroupings[$grouping['id']][$group] = 1;
         }
     }
     $groups = CRM_Mailchimp_Utils::getGroupsToSync(array(), $listID, $membership_only = FALSE);
     CRM_Mailchimp_Utils::checkDebug('Middle- CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $groups ', $groups);
     CRM_Mailchimp_Utils::checkDebug('Middle- CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $mcGroupings ', $mcGroupings);
     foreach ($groups as $groupID => $details) {
         if ($groupID != $membershipGroupID && $details['is_mc_update_grouping']) {
             // This is a group we allow updates for.
             if (empty($mcGroupings[$details['grouping_id']][$details['group_name']])) {
                 $groupContactRemoves[$groupID][] = $contactID;
             } else {
                 $groupContactAdditions[$groupID][] = $contactID;
             }
         }
     }
     // Add contacts to groups, if anything to do.
     foreach ($groupContactAdditions as $groupID => $contactIDs) {
         CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIDs, $groupID, 'Admin', 'Added');
     }
     // Remove contacts from groups, if anything to do.
     foreach ($groupContactRemoves as $groupID => $contactIDs) {
         CRM_Contact_BAO_GroupContact::removeContactsFromGroup($contactIDs, $groupID, 'Admin', 'Removed');
     }
     CRM_Mailchimp_Utils::checkDebug('End - CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $groupContactRemoves ', $groupContactRemoves);
     CRM_Mailchimp_Utils::checkDebug('End - CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $groupContactAdditions ', $groupContactAdditions);
     CRM_Mailchimp_Utils::checkDebug('End - CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $contactID= ', $contactID);
     CRM_Mailchimp_Utils::checkDebug('End - CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $requestData= ', $requestData);
     CRM_Mailchimp_Utils::checkDebug('End - CRM_Mailchimp_Page_WebHook manageCiviCRMGroupSubcription $requestType= ', $action);
 }
/**
 * Mailchimp Get CiviCRM Group Mailchimp settings (Mailchimp List Id and Group)
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 */
function civicrm_api3_mailchimp_getcivicrmgroupmailchimpsettings($params)
{
    $groupIds = empty($params['ids']) ? array() : explode(',', $params['ids']);
    $groups = CRM_Mailchimp_Utils::getGroupsToSync($groupIds);
    return civicrm_api3_create_success($groups);
}
/**
 * CiviCRM to Mailchimp Sync
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 */
function civicrm_api3_mailchimp_sync($params)
{
    $groups = CRM_Mailchimp_Utils::getGroupsToSync(array(), null, $membership_only = TRUE);
    foreach ($groups as $group_id => $details) {
        $list = new Mailchimp_Lists(CRM_Mailchimp_Utils::mailchimp());
        $webhookoutput = $list->webhooks($details['list_id']);
        if ($webhookoutput[0]['sources']['api'] == 1) {
            return civicrm_api3_create_error('civicrm_api3_mailchimp_sync -  API is set in Webhook setting for listID ' . $details['list_id'] . ' Please uncheck API');
        }
    }
    $result = $pullResult = array();
    // Do pull first from mailchimp to CiviCRM
    $pullRunner = CRM_Mailchimp_Form_Pull::getRunner($skipEndUrl = TRUE);
    if ($pullRunner) {
        $pullResult = $pullRunner->runAll();
    }
    // Do push from CiviCRM to mailchimp
    $runner = CRM_Mailchimp_Form_Sync::getRunner($skipEndUrl = TRUE);
    if ($runner) {
        $result = $runner->runAll();
    }
    if ($pullResult['is_error'] == 0 && $result['is_error'] == 0) {
        return civicrm_api3_create_success();
    } else {
        return civicrm_api3_create_error();
    }
}
 /**
  * Removes from the temporary tables those records that do not need processing.
  */
 static function syncIdentical()
 {
     //CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Form_Sync syncIdentical $count= ', $count);
     // Delete records have the same hash - these do not need an update.
     // count
     $dao = CRM_Core_DAO::executeQuery("SELECT COUNT(c.email) co FROM tmp_mailchimp_push_m m\n      INNER JOIN tmp_mailchimp_push_c c ON m.email = c.email AND m.hash = c.hash;");
     $dao->fetch();
     $count = $dao->co;
     CRM_Core_DAO::executeQuery("DELETE m, c\n       FROM tmp_mailchimp_push_m m\n       INNER JOIN tmp_mailchimp_push_c c ON m.email = c.email AND m.hash = c.hash;");
     CRM_Mailchimp_Utils::checkDebug('End-CRM_Mailchimp_Form_Sync syncIdentical $count= ', $count);
     return $count;
 }
 /**
  * Sets a mock Mailchimp API that will pass the webhook is configured
  * correctly test.
  *
  * This code is used in many methods.
  *
  * @return Prophecy.
  */
 protected function prepMockForWebhookConfig()
 {
     // Make mock API that will return a webhook with the sources.API setting
     // set, which is wrong.
     $api_prophecy = $this->prophesize('CRM_Mailchimp_Api3');
     CRM_Mailchimp_Utils::setMailchimpApi($api_prophecy->reveal());
     $url = CRM_Mailchimp_Utils::getWebhookUrl();
     $api_prophecy->get("/lists/dummylistid/webhooks", Argument::any())->shouldBeCalled()->willReturn(json_decode('{"http_code":200,"data":{"webhooks":[{"url":"' . $url . '","sources":{"api":false}}]}}'));
     return $api_prophecy;
 }
 /**
  * Update the push stats setting.
  */
 public static function updatePushStats($updates)
 {
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Form_Sync updatePushStats $updates= ', $updates);
     $stats = CRM_Core_BAO_Setting::getItem(CRM_Mailchimp_Form_Setting::MC_SETTING_GROUP, 'push_stats');
     foreach ($updates as $listId => $settings) {
         if ($listId == 'dry_run') {
             continue;
         }
         foreach ($settings as $key => $val) {
             $stats[$listId][$key] = $val;
         }
     }
     CRM_Core_BAO_Setting::setItem($stats, CRM_Mailchimp_Form_Setting::MC_SETTING_GROUP, 'push_stats');
 }
 /**
  * Get Mailchimp group ID group name
  */
 public static function getMailchimpGroupIdFromName($listID, $groupName)
 {
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Utils getMailchimpGroupIdFromName $listID', $listID);
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Utils getMailchimpGroupIdFromName $groupName', $groupName);
     if (empty($listID) || empty($groupName)) {
         return NULL;
     }
     $mcLists = new Mailchimp_Lists(CRM_Mailchimp_Utils::mailchimp());
     try {
         $results = $mcLists->interestGroupings($listID);
     } catch (Exception $e) {
         return NULL;
     }
     foreach ($results as $grouping) {
         foreach ($grouping['groups'] as $group) {
             if ($group['name'] == $groupName) {
                 CRM_Mailchimp_Utils::checkDebug('End-CRM_Mailchimp_Utils getMailchimpGroupIdFromName= ', $group['id']);
                 return $group['id'];
             }
         }
     }
 }
 /**
  * Remove anything that's the same.
  */
 public static function syncPullIgnoreInSync(CRM_Queue_TaskContext $ctx, $listID)
 {
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Form_Pull syncPullIgnoreInSync $listID= ', $listID);
     $sync = new CRM_Mailchimp_Sync($listID);
     $stats[$listID]['in_sync'] = $sync->removeInSync('pull');
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Form_Pull syncPullIgnoreInSync in-sync= ', $stats[$listID]['in_sync']);
     static::updatePullStats($stats);
     return CRM_Queue_Task::TASK_SUCCESS;
 }
/**
 * Implementation of hook_civicrm_post
 *
 * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_post
 */
function mailchimp_civicrm_post($op, $objectName, $objectId, &$objectRef)
{
    if (!CRM_Mailchimp_Utils::$post_hook_enabled) {
        // Post hook is disabled at this point in the running.
        return;
    }
    /***** NO BULK EMAILS (User Opt Out) *****/
    if ($objectName == 'Individual' || $objectName == 'Organization' || $objectName == 'Household') {
        // Contact Edited
        // @todo artfulrobot: I don't understand the cases this is dealing with.
        //                    Perhaps it was trying to check that if someone's been
        //                    marked as 'opt out' then they're unsubscribed from all
        //                    mailings. I could not follow the logic though -
        //                    without tests in place I thought it was better
        //                    disabled.
        if (FALSE) {
            if ($op == 'edit' || $op == 'create') {
                if ($objectRef->is_opt_out == 1) {
                    $action = 'unsubscribe';
                } else {
                    $action = 'subscribe';
                }
                // Get all groups, the contact is subscribed to
                $civiGroups = CRM_Contact_BAO_GroupContact::getGroupList($objectId);
                $civiGroups = array_keys($civiGroups);
                if (empty($civiGroups)) {
                    return;
                }
                // Get mailchimp details
                $groups = CRM_Mailchimp_Utils::getGroupsToSync($civiGroups);
                if (!empty($groups)) {
                    // Loop through all groups and unsubscribe the email address from mailchimp
                    foreach ($groups as $groupId => $groupDetails) {
                        // method removed. CRM_Mailchimp_Utils::subscribeOrUnsubsribeToMailchimpList($groupDetails, $objectId, $action);
                    }
                }
            }
        }
    }
    /***** Contacts added/removed/deleted from CiviCRM group *****/
    if ($objectName == 'GroupContact') {
        // Determine if the action being taken needs to affect Mailchimp at all.
        if ($op == 'view') {
            // Nothing changed; nothing to do.
            return;
        }
        // Get mailchimp details for the group.
        // $objectId here means CiviCRM group Id.
        $groups = CRM_Mailchimp_Utils::getGroupsToSync(array($objectId));
        if (empty($groups[$objectId])) {
            // This group has nothing to do with Mailchimp.
            return;
        }
        // The updates we need to make can be complex.
        // If someone left/joined a group synced as the membership group for a
        // Mailchimp list, then that's a subscribe/unsubscribe option.
        // If however it was a group synced to an interest in Mailchimp, then
        // the join/leave on the CiviCRM side only means updating interests on the
        // Mailchimp side, not a subscribe/unsubscribe.
        // There is also the case that somone's been put into an interest group, but
        // is not in the membership group, which should not result in them being
        // subscribed at MC.
        if ($groups[$objectId]['interest_id']) {
            // This is a change to an interest grouping.
            // We only need update Mailchimp about this if the contact is in the
            // membership group.
            $list_id = $groups[$objectId]['list_id'];
            // find membership group, then find out if the contact is in that group.
            $membership_group_details = CRM_Mailchimp_Utils::getGroupsToSync(array(), $list_id, TRUE);
            $result = civicrm_api3('Contact', 'getsingle', ['return' => 'group', 'contact_id' => $objectRef[0]]);
            if (!CRM_Mailchimp_Utils::getGroupIds($result['groups'], $membership_group_details)) {
                // This contact is not in the membership group, so don't bother telling
                // Mailchimp about a change in their interests.
                return;
            }
        }
        // Finally this hook is useful for small changes only; if you just added
        // thousands of people to a group then this is NOT the way to tell Mailchimp
        // about it as it would require thousands of separate API calls. This would
        // probably cause big problems (like hitting the API rate limits, or
        // crashing CiviCRM due to PHP max execution times etc.). Such updates must
        // happen in the more controlled bulk update (push).
        if (count($objectRef) > 1) {
            // Limit application to one contact only.
            CRM_Core_Session::setStatus(ts('You have made a bulk update that means CiviCRM contacts and Mailchimp are no longer in sync. You should do an "Update Mailchimp from CiviCRM" sync to ensure the changes you have made are applied at Mailchimp.'), ts('Update Mailchimp from CiviCRM required.'));
            return;
        }
        // Trigger mini sync for this person and this list.
        $sync = new CRM_Mailchimp_Sync($groups[$objectId]['list_id']);
        $sync->updateMailchimpFromCiviSingleContact($objectRef[0]);
    }
}
/**
 * Implementation of hook_civicrm_post
 *
 * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_post
 */
function mailchimp_civicrm_post($op, $objectName, $objectId, &$objectRef)
{
    /***** NO BULK EMAILS (User Opt Out) *****/
    if ($objectName == 'Individual' || $objectName == 'Organization' || $objectName == 'Household') {
        // Contact Edited
        if ($op == 'edit' || $op == 'create') {
            if ($objectRef->is_opt_out == 1) {
                $action = 'unsubscribe';
            } else {
                $action = 'subscribe';
            }
            // Get all groups, the contact is subscribed to
            $civiGroups = CRM_Contact_BAO_GroupContact::getGroupList($objectId);
            $civiGroups = array_keys($civiGroups);
            if (empty($civiGroups)) {
                return;
            }
            // Get mailchimp details
            $groups = CRM_Mailchimp_Utils::getGroupsToSync($civiGroups);
            if (!empty($groups)) {
                // Loop through all groups and unsubscribe the email address from mailchimp
                foreach ($groups as $groupId => $groupDetails) {
                    CRM_Mailchimp_Utils::subscribeOrUnsubsribeToMailchimpList($groupDetails, $objectId, $action);
                }
            }
        }
    }
    /***** Contacts added/removed/deleted from CiviCRM group *****/
    if ($objectName == 'GroupContact') {
        // FIXME: Dirty hack to skip hook
        require_once 'CRM/Core/Session.php';
        $session = CRM_Core_Session::singleton();
        $skipPostHook = $session->get('skipPostHook');
        // Added/Removed/Deleted - This works for both bulk action and individual add/remove/delete
        if (($op == 'create' || $op == 'edit' || $op == 'delete') && empty($skipPostHook)) {
            // Decide mailchimp action based on $op
            // Add / Rejoin Group
            if ($op == 'create' || $op == 'edit') {
                $action = 'subscribe';
            } elseif ($op == 'delete') {
                $action = 'unsubscribe';
            }
            // Get mailchimp details for the group
            $groups = CRM_Mailchimp_Utils::getGroupsToSync(array($objectId));
            // Proceed only if the group is configured with mailing list/groups
            if (!empty($groups[$objectId])) {
                // Loop through all contacts added/removed from the group
                foreach ($objectRef as $contactId) {
                    // Subscribe/Unsubscribe in Mailchimp
                    CRM_Mailchimp_Utils::subscribeOrUnsubsribeToMailchimpList($groups[$objectId], $contactId, $action);
                }
            }
        }
    }
}
 /**
  * Mailchimp still sends interests to webhooks in an old school way.
  *
  * So it's left to us to identify the interests and groups that they refer to.
  */
 public function updateInterestsFromMerges()
 {
     // Get a list of CiviCRM group Ids that this contact should be in.
     $should_be_in = $this->sync->splitMailchimpWebhookGroupsToCiviGroupIds($this->request_data['merges']['INTERESTS']);
     // Now get a list of all the groups they *are* in.
     $result = civicrm_api3('Contact', 'getsingle', ['return' => 'group', 'contact_id' => $this->contact_id]);
     $is_in = CRM_Mailchimp_Utils::getGroupIds($result['groups'], $this->sync->interest_group_details);
     // Finally loop all the mapped interest groups and process any differences.
     foreach ($this->sync->interest_group_details as $group_id => $details) {
         if ($details['is_mc_update_grouping'] == 1) {
             // We're allowed to update Civi from Mailchimp for this one.
             if (in_array($group_id, $should_be_in) && !in_array($group_id, $is_in)) {
                 // Not in this group, but should be.
                 civicrm_api3('GroupContact', 'create', ['contact_id' => $this->contact_id, 'group_id' => $group_id, 'status' => 'Added']);
             } elseif (!in_array($group_id, $should_be_in) && in_array($group_id, $is_in)) {
                 // Is in this group, but should not be.
                 civicrm_api3('GroupContact', 'create', ['contact_id' => $this->contact_id, 'group_id' => $group_id, 'status' => 'Removed']);
             }
         }
     }
 }
 static function subscribeOrUnsubsribeToMailchimpList($groupDetails, $contactID, $action)
 {
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Utils subscribeOrUnsubsribeToMailchimpList $groupDetails', $groupDetails);
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Utils subscribeOrUnsubsribeToMailchimpList $contactID', $contactID);
     CRM_Mailchimp_Utils::checkDebug('Start-CRM_Mailchimp_Utils subscribeOrUnsubsribeToMailchimpList $action', $action);
     if (empty($groupDetails) || empty($contactID) || empty($action)) {
         return NULL;
     }
     // We need to get contact's email before subscribing in Mailchimp
     $contactParams = array('version' => 3, 'id' => $contactID);
     $contactResult = civicrm_api('Contact', 'get', $contactParams);
     // This is the primary email address of the contact
     $email = $contactResult['values'][$contactID]['email'];
     if (empty($email)) {
         // Its possible to have contacts in CiviCRM without email address
         // and add to group offline
         return;
     }
     // Optional merges for the email (FNAME, LNAME)
     $merge = array('FNAME' => $contactResult['values'][$contactID]['first_name'], 'LNAME' => $contactResult['values'][$contactID]['last_name']);
     $listID = $groupDetails['list_id'];
     $grouping_id = $groupDetails['grouping_id'];
     $group_id = $groupDetails['group_id'];
     if (!empty($grouping_id) and !empty($group_id)) {
         $merge_groups[$grouping_id] = array('id' => $groupDetails['grouping_id'], 'groups' => array());
         $merge_groups[$grouping_id]['groups'][] = CRM_Mailchimp_Utils::getMCGroupName($listID, $grouping_id, $group_id);
         // remove the significant array indexes, in case Mailchimp cares.
         $merge['groupings'] = array_values($merge_groups);
     }
     // Send Mailchimp Lists API Call.
     $list = new Mailchimp_Lists(CRM_Mailchimp_Utils::mailchimp());
     switch ($action) {
         case "subscribe":
             // http://apidocs.mailchimp.com/api/2.0/lists/subscribe.php
             try {
                 $result = $list->subscribe($listID, array('email' => $email), $merge, $email_type = 'html', $double_optin = FALSE, $update_existing = FALSE, $replace_interests = TRUE, $send_welcome = FALSE);
             } catch (Exception $e) {
                 // Don't display if the error is that we're already subscribed.
                 $message = $e->getMessage();
                 if ($message !== $email . ' is already subscribed to the list.') {
                     CRM_Core_Session::setStatus($message);
                 }
             }
             break;
         case "unsubscribe":
             // https://apidocs.mailchimp.com/api/2.0/lists/unsubscribe.php
             try {
                 $result = $list->unsubscribe($listID, array('email' => $email), $delete_member = false, $send_goodbye = false, $send_notify = false);
             } catch (Exception $e) {
                 CRM_Core_Session::setStatus($e->getMessage());
             }
             break;
     }
     CRM_Mailchimp_Utils::checkDebug('End-CRM_Mailchimp_Utils subscribeOrUnsubsribeToMailchimpList $groupDetails', $groupDetails);
     CRM_Mailchimp_Utils::checkDebug('End-CRM_Mailchimp_Utils subscribeOrUnsubsribeToMailchimpList $contactID', $contactID);
     CRM_Mailchimp_Utils::checkDebug('End-CRM_Mailchimp_Utils subscribeOrUnsubsribeToMailchimpList $action', $action);
 }
 /**
  * Sync a single contact's membership and interests for this list from their
  * details in CiviCRM.
  *
  */
 public function updateMailchimpFromCiviSingleContact($contact_id)
 {
     // Get all the groups related to this list that the contact is currently in.
     // We have to use this dodgy API that concatenates the titles of the groups
     // with a comma (making it unsplittable if a group title has a comma in it).
     $contact = civicrm_api3('Contact', 'getsingle', ['contact_id' => $contact_id, 'return' => ['first_name', 'last_name', 'email_id', 'email', 'group'], 'sequential' => 1]);
     $in_groups = CRM_Mailchimp_Utils::getGroupIds($contact['groups'], $this->group_details);
     $currently_a_member = in_array($this->membership_group_id, $in_groups);
     if (empty($contact['email'])) {
         // Without an email we can't do anything.
         return;
     }
     $subscriber_hash = md5(strtolower($contact['email']));
     $api = CRM_Mailchimp_Utils::getMailchimpApi();
     if (!$currently_a_member) {
         // They are not currently a member.
         //
         // We should ensure they are unsubscribed from Mailchimp. They might
         // already be, but as we have no way of telling exactly what just changed
         // at our end, we have to make sure.
         //
         // Nb. we don't bother updating their interests for unsubscribes.
         try {
             $result = $api->patch("/lists/{$this->list_id}/members/{$subscriber_hash}", ['status' => 'unsubscribed']);
         } catch (CRM_Mailchimp_RequestErrorException $e) {
             if ($e->response->http_code == 404) {
                 // OK. Mailchimp didn't know about them anyway. Fine.
             } else {
                 CRM_Core_Session::setStatus(ts('There was a problem trying to unsubscribe this contact at Mailchimp; any differences will remain until a CiviCRM to Mailchimp Sync is done.'));
             }
         } catch (CRM_Mailchimp_NetworkErrorException $e) {
             CRM_Core_Session::setStatus(ts('There was a network problem trying to unsubscribe this contact at Mailchimp; any differences will remain until a CiviCRM to Mailchimp Sync is done.'));
         }
         return;
     }
     // Now left with 'subscribe' case.
     //
     // Do this with a PUT as this allows for both updating existing and
     // creating new members.
     $data = ['status' => 'subscribed', 'email_address' => $contact['email'], 'merge_fields' => ['FNAME' => $contact['first_name'], 'LNAME' => $contact['last_name']]];
     // Do interest groups.
     $data['interests'] = $this->getComparableInterestsFromCiviCrmGroups($contact['groups'], 'push');
     if (empty($data['interests'])) {
         unset($data['interests']);
     }
     try {
         $result = $api->put("/lists/{$this->list_id}/members/{$subscriber_hash}", $data);
     } catch (CRM_Mailchimp_RequestErrorException $e) {
         CRM_Core_Session::setStatus(ts('There was a problem trying to subscribe this contact at Mailchimp:') . $e->getMessage());
     } catch (CRM_Mailchimp_NetworkErrorException $e) {
         CRM_Core_Session::setStatus(ts('There was a network problem trying to unsubscribe this contact at Mailchimp; any differences will remain until a CiviCRM to Mailchimp Sync is done.'));
     }
 }