/**
 * 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]);
    }
}
 /**
  * Test new mailchimp contacts added to CiviCRM.
  *
  * Add contact1 and subscribe, then delete contact 1 from CiviCRM, then do a
  * pull. This should result in contact 1 being re-created with all their
  * details.
  *
  * WARNING if this test fails at a particular place it messes up the fixture,
  * but that's unlikely.
  *
  * @group pull
  *
  */
 public function testPullAddsContact()
 {
     // Give contact 1 an interest.
     $this->joinGroup(static::$civicrm_contact_1, static::$civicrm_group_id_interest_1, TRUE);
     // Add contact 1 to membership group thus subscribing them at Mailchimp.
     $this->joinMembershipGroup(static::$civicrm_contact_1);
     // Delete contact1 from CiviCRM
     // We have to ensure no post hooks are fired, so we disable the API.
     CRM_Mailchimp_Utils::$post_hook_enabled = FALSE;
     $result = civicrm_api3('Contact', 'delete', ['id' => static::$civicrm_contact_1['contact_id'], 'skip_undelete' => 1]);
     static::$civicrm_contact_1['contact_id'] = 0;
     CRM_Mailchimp_Utils::$post_hook_enabled = TRUE;
     try {
         // Collect data from Mailchimp and CiviCRM.
         $sync = new CRM_Mailchimp_Sync(static::$test_list_id);
         $sync->collectCiviCrm('pull');
         $sync->collectMailchimp('pull');
         $matches = $sync->matchMailchimpMembersToContacts();
         $this->assertEquals(['bySubscribers' => 0, 'byUniqueEmail' => 0, 'byNameEmail' => 0, 'bySingle' => 0, 'totalMatched' => 0, 'newContacts' => 1, 'failures' => 0], $matches);
         // Remove in-sync things (nothing should be in sync)
         $in_sync = $sync->removeInSync('pull');
         $this->assertEquals(0, $in_sync);
         // Make changes in Civi.
         $stats = $sync->updateCiviFromMailchimp();
         $this->assertEquals(['created' => 1, 'joined' => 0, 'in_sync' => 0, 'removed' => 0, 'updated' => 0], $stats);
         // Ensure expected change was made.
         $result = civicrm_api3('Contact', 'getsingle', ['email' => static::$civicrm_contact_1['email'], 'first_name' => static::$civicrm_contact_1['first_name'], 'last_name' => static::$civicrm_contact_1['last_name'], 'return' => 'group']);
         // If that didn't throw an exception, the contact was created.
         // Store the new contact id in the fixture to enable clearup.
         static::$civicrm_contact_1['contact_id'] = (int) $result['contact_id'];
         // Check they're in the membership group.
         $in_groups = CRM_Mailchimp_Utils::getGroupIds($result['groups'], $sync->group_details);
         $this->assertContains(static::$civicrm_group_id_membership, $in_groups, "New contact was not in membership group, but should be.");
         $this->assertContains(static::$civicrm_group_id_interest_1, $in_groups, "New contact was not in interest group 1, but should be.");
     } catch (CRM_Mailchimp_Exception $e) {
         // Spit out request and response for debugging.
         print "Request:\n";
         print_r($e->request);
         print "Response:\n";
         print_r($e->response);
         // re-throw exception.
         throw $e;
     }
 }
 /**
  * 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']);
             }
         }
     }
 }
 /**
  * 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.'));
     }
 }