• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grueneschweiz / mailchimpservice / 16296819851

15 Jul 2025 02:57PM UTC coverage: 70.196% (-1.4%) from 71.551%
16296819851

push

github

web-flow
Merge pull request #85 from grueneschweiz/ignore_mailchimp_new_subscription

Allow admins to disable Notifications for direct Subscriptions in Mailchimp

3 of 9 new or added lines in 2 files covered. (33.33%)

11 existing lines in 2 files now uncovered.

822 of 1171 relevant lines covered (70.2%)

11.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.95
/app/Synchronizer/MailchimpToCrmSynchronizer.php
1
<?php
2

3

4
namespace App\Synchronizer;
5

6

7
use App\Exceptions\ConfigException;
8
use App\Http\CrmClient;
9
use App\Http\MailChimpClient;
10
use App\Mail\WrongSubscription;
11
use App\Synchronizer\Mapper\FieldMaps\FieldMapGroup;
12
use App\Synchronizer\Mapper\Mapper;
13
use GuzzleHttp\Exception\ClientException;
14
use GuzzleHttp\Exception\GuzzleException;
15
use Illuminate\Support\Facades\Mail;
16

17
class MailchimpToCrmSynchronizer
18
{
19
    use LogTrait;
20
    
21
    /**
22
     * Mailchimp webhook event types
23
     *
24
     * @see https://developer.mailchimp.com/documentation/mailchimp/guides/about-webhooks/
25
     */
26
    private const MC_EMAIL_UPDATE = 'upemail';
27
    private const MC_CLEANED_EMAIL = 'cleaned';
28
    private const MC_SUBSCRIBE = 'subscribe';
29
    private const MC_UNSUBSCRIBE = 'unsubscribe';
30
    private const MC_PROFILE_UPDATE = 'profile';
31
    
32
    /**
33
     * @var Config
34
     */
35
    private $config;
36
    
37
    /**
38
     * @var string
39
     */
40
    private $configName;
41
    
42
    /**
43
     * @var CrmClient
44
     */
45
    private $crmClient;
46
    
47
    /**
48
     * @var MailChimpClient
49
     */
50
    private $mcClient;
51
    
52
    /**
53
     * Synchronizer constructor.
54
     *
55
     * @param Config $config
56
     * @param int $userId
57
     *
58
     * @throws \App\Exceptions\ConfigException
59
     * @throws \Exception
60
     */
61
    public function __construct(string $configFileName)
62
    {
63
        $this->config = new Config($configFileName);
×
64
        $this->configName = $configFileName;
×
65
        
66
        $crmCred = $this->config->getCrmCredentials();
×
67
        
68
        $this->crmClient = new CrmClient($crmCred['clientId'], $crmCred['clientSecret'], $crmCred['url']);
×
69
        $this->mcClient = new MailChimpClient($this->config->getMailchimpCredentials()['apikey'], $this->config->getMailchimpListId());
×
70
    }
71
    
72
    /**
73
     * Sync single record from mailchimp to the crm. Usually called via mailchimp webhook.
74
     *
75
     * @param array $mcData
76
     *
77
     * @throws \App\Exceptions\ConfigException
78
     * @throws \App\Exceptions\ParseMailchimpDataException
79
     * @throws GuzzleException
80
     * @throws \App\Exceptions\ParseCrmDataException
81
     * @throws \Exception
82
     */
83
    public function syncSingle(array $mcData)
84
    {
85
        $mapper = new Mapper($this->config->getFieldMaps());
6✔
86
        
87
        $email = isset($mcData['data']['new_email']) ? $mcData['data']['new_email'] : $mcData['data']['email'];
6✔
88
        
89
        $callType = $mcData['type'];
6✔
90
        $mailchimpId = MailChimpClient::calculateSubscriberId($email);
6✔
91
    
92
        $this->logWebhook('debug', $callType, $mailchimpId, "Sync single record from Mailchimp to CRM.");
6✔
93
        
94
        switch ($callType) {
95
            case self::MC_SUBSCRIBE:
96
                // if configured, don't send notifications, if not, keep backwards compatibility
97
                if(!$this->config->getIgnoreSubscribeThroughMailchimp()) {
2✔
NEW
98
                    $mcData = $this->mcClient->getSubscriber($email);
×
NEW
99
                    $mergeFields = $this->extractMergeFields($mcData);
×
100

101
                    // if there is no crm id
NEW
102
                    if (empty($mergeFields[$this->config->getMailchimpKeyOfCrmId()])) {
×
103
                        // send mail to dataOwner, that he should
104
                        // add the subscriber to webling not mailchimp
NEW
105
                        $this->sendMailSubscribeOnlyInWebling($this->config->getDataOwner(), $mcData);
×
NEW
106
                        $this->logWebhook('debug', $callType, $mailchimpId, "Notified data owner.");
×
107
                    }
108
                }
109

110
                return;
2✔
111

112
            case self::MC_UNSUBSCRIBE:
113
                $mergeFields = $this->extractMergeFields($mcData['data']);
1✔
114
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
115
                if (empty($crmId)) {
1✔
116
                    $this->logWebhook('debug', $callType, $mailchimpId, "Record not linked to crm. No action taken.");
×
117
                    return;
×
118
                }
119
    
120
                // get contact from crm
121
                // set all subscriptions, that are configured in the currently loaded config file, to NO
122
                try {
123
                    $get = $this->crmClient->get('member/' . $crmId);
1✔
124
                } catch (ClientException $e) {
×
125
                    if ($e->getResponse()->getStatusCode() === 404) {
×
126
                        $this->logWebhook('debug', $callType, $mailchimpId, "Tried to unsubscribe member, but member not found in Webling. So there is also nothing to unsubscribe. No action taken.", $crmId);
×
127
                        return;
×
128
                    }
129
        
130
                    throw $e;
×
131
                }
132
                $crmData = json_decode((string)$get->getBody(), true);
1✔
133
                $crmData = $this->unsubscribeAll($crmData);
1✔
134
                $this->logWebhook('debug', $callType, $mailchimpId, "Unsubscribe member in crm.", $crmId);
1✔
135
                break;
1✔
136
            
137
            case self::MC_CLEANED_EMAIL:
138
                // set email1 to invalid
139
                // add note 'email set to invalid because it bounced in mailchimp'
140
                if ('hard' !== $mcData['data']['reason']) {
1✔
141
                    $this->logWebhook('debug', $callType, $mailchimpId, "Bounce not hard. No action taken.");
×
142
    
143
                    return;
×
144
                }
145
                $mcData = $this->mcClient->getSubscriber($email);
1✔
146
                $mergeFields = $this->extractMergeFields($mcData);
1✔
147
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
148
                $note = sprintf("%s: Mailchimp reported the email as invalid. Email status changed.", date('Y-m-d H:i'));
1✔
149
                $crmData['emailStatus'] = [['value' => 'invalid', 'mode' => CrmValue::MODE_REPLACE]];
1✔
150
                $crmData['notesCountry'] = [['value' => $note, 'mode' => CrmValue::MODE_APPEND]];
1✔
151
                $this->logWebhook('debug', $callType, $mailchimpId, "Mark email invalid in crm.", $crmId);
1✔
152
                break;
1✔
153
            
154
            case self::MC_PROFILE_UPDATE:
155
                // get subscriber from mailchimp (so we have the interessts (groups) in a usable format)
156
                // update email1, subscriptions
157
                $mcData = $this->mcClient->getSubscriber($email);
1✔
158
                $mergeFields = $this->extractMergeFields($mcData);
1✔
159
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
160
                $crmData = $mapper->mailchimpToCrm($mcData);
1✔
161
                $this->logWebhook('debug', $callType, $mailchimpId, "Update subscriptions in crm.", $crmId);
1✔
162
                break;
1✔
163
            
164
            case self::MC_EMAIL_UPDATE:
165
                // update email1
166
                $mcData = $this->mcClient->getSubscriber($email);
1✔
167
                $mergeFields = $this->extractMergeFields($mcData);
1✔
168
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
169
                $crmValue = $this->updateEmail($mcData)[0];
1✔
170
                $crmData = [$crmValue->getKey() => [['value' => $crmValue->getValue(), 'mode' => $crmValue->getMode()]]];
1✔
171
                $this->logWebhook('debug', $callType, $mailchimpId, "Update email in crm.", $crmId);
1✔
172
                break;
1✔
173
    
174
            default:
175
                $this->logWebhook('error', $callType, $mailchimpId, __METHOD__ . " was called with an undefined webhook event.");
×
176
                return;
×
177
        }
178
    
179
        try {
180
            $this->crmClient->put('member/' . $crmId, $crmData);
4✔
181
        } catch (ClientException $e) {
×
182
            if (404 === $e->getResponse()->getStatusCode()) {
×
183
                $this->logWebhook('info', $callType, $mailchimpId, "Member not found in Webling. Action could not be executed: $callType", $crmId);
×
184
                return;
×
185
            }
186
            throw $e;
×
187
        }
188
    
189
        $this->logWebhook('debug', $callType, $mailchimpId, "Sync successful");
4✔
190
    }
191
    
192
    private function extractMergeFields(array $mcData)
193
    {
194
        return $mcData['merges'] ?? $mcData['merge_fields'];
4✔
195
    }
196
    
197
    /**
198
     * Inform data owner that he should only add contact in the crm not in mailchimp
199
     *
200
     * @param array $dataOwner
201
     * @param array $mcData
202
     */
203
    private function sendMailSubscribeOnlyInWebling(array $dataOwner, array $mcData)
204
    {
UNCOV
205
        $mergeFields = $this->extractMergeFields($mcData);
×
206
        
UNCOV
207
        $mailData = new \stdClass();
×
UNCOV
208
        $mailData->dataOwnerName = $dataOwner['name'];
×
UNCOV
209
        $mailData->contactFirstName = $mergeFields['FNAME']; // todo: check if we cant get the field keys dynamically
×
UNCOV
210
        $mailData->contactLastName = $mergeFields['LNAME']; // todo: dito
×
UNCOV
211
        $mailData->contactEmail = $mcData['email_address'];
×
UNCOV
212
        $mailData->adminEmail = env('ADMIN_EMAIL');
×
UNCOV
213
        $mailData->configName = $this->configName;
×
214
        
UNCOV
215
        Mail::to($dataOwner['email'])
×
UNCOV
216
            ->send(new WrongSubscription($mailData));
×
217
    }
218
    
219
    /**
220
     * Return $crmData with all subscriptions disabled
221
     *
222
     * @param array $crmData
223
     *
224
     * @return array in crmData format
225
     *
226
     * @throws \App\Exceptions\ConfigException
227
     * @throws \App\Exceptions\ParseCrmDataException
228
     * @throws \App\Exceptions\ParseMailchimpDataException
229
     */
230
    private function unsubscribeAll(array $crmData): array
231
    {
232
        $mapper = new Mapper($this->config->getFieldMaps());
1✔
233
        $mcData = $mapper->crmToMailchimp($crmData);
1✔
234
        
235
        foreach ($mcData[FieldMapGroup::MAILCHIMP_PARENT_KEY] as $key => $value) {
1✔
236
            $mcData[FieldMapGroup::MAILCHIMP_PARENT_KEY][$key] = false;
1✔
237
        }
238
        
239
        return $mapper->mailchimpToCrm($mcData);
1✔
240
    }
241
    
242
    /**
243
     * Update the email field in $crmData according to the email in $mcData
244
     *
245
     * @param array $mcData
246
     *
247
     * @return CrmValue[] of the email field
248
     *
249
     * @throws \App\Exceptions\ConfigException
250
     * @throws \App\Exceptions\ParseMailchimpDataException
251
     */
252
    private function updateEmail(array $mcData): array
253
    {
254
        foreach ($this->config->getFieldMaps() as $map) {
1✔
255
            if ($map->isEmail()) {
1✔
256
                $map->addMailchimpData($mcData);
1✔
257
            
258
                return $map->getCrmData();
1✔
259
            }
260
        }
261
    
262
        throw new ConfigException('No field of type "email"');
×
263
    }
264
    
265
    private function logWebhook(string $method, string $webhook, string $record, string $message, int $crmId = -1)
266
    {
267
        $crmIdParam = $crmId >= 0 ? " crmId=\"$crmId\" " : " ";
6✔
268
        $this->log($method, $message, "webhook=\"$webhook\" record=\"$record\"$crmIdParam");
6✔
269
    }
270
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc