PluginProbe ʕ •ᴥ•ʔ
MC4WP: Mailchimp for WordPress / 4.13.0
MC4WP: Mailchimp for WordPress v4.13.0
4.13.0 4.12.6 4.12.4 4.12.5 4.12.3 4.12.2 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.15 2.3.16 2.3.17 2.3.18 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 3.0.10 3.0.11 3.0.12 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1 3.1.1 3.1.10 3.1.11 3.1.12 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.11 4.1.12 4.1.13 4.1.14 4.1.15 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.10.0 4.10.1 4.10.2 4.10.3 4.10.4 4.10.5 4.10.6 4.10.7 4.10.8 4.10.9 4.11.0 4.11.1 4.12.0 4.12.1 4.2 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.3 4.3.1 4.3.2 4.3.3 4.4 4.5.0 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.1 4.6.2 4.7 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 4.8 4.8.1 4.8.10 4.8.11 4.8.12 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.8.9 4.9.0 4.9.1 4.9.10 4.9.11 4.9.12 4.9.13 4.9.14 4.9.15 4.9.16 4.9.17 4.9.18 4.9.19 4.9.2 4.9.20 4.9.21 4.9.3 4.9.4 4.9.5 4.9.6 4.9.7 4.9.8 4.9.9 trunk 1.1.5 1.2.1 1.2.3 1.2.4 1.2.5 1.3 1.3.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8
mailchimp-for-wp / includes / class-mailchimp.php
mailchimp-for-wp / includes Last commit date
admin 4 days ago api 1 month ago campaigns 4 days ago forms 4 days ago integrations 4 days ago views 1 week ago class-container.php 4 days ago class-debug-log-reader.php 4 days ago class-debug-log.php 4 days ago class-dynamic-content-tags.php 4 days ago class-field-formatter.php 1 year ago class-field-guesser.php 1 week ago class-list-data-mapper.php 4 months ago class-mailchimp-subscriber.php 2 months ago class-mailchimp.php 4 days ago class-personal-data-exporter.php 1 month ago class-plugin.php 1 year ago class-queue-job.php 1 month ago class-queue.php 1 year ago class-tools.php 1 year ago class-tracking-pixel.php 4 days ago default-actions.php 1 year ago default-filters.php 1 year ago deprecated-functions.php 3 years ago functions.php 4 days ago
class-mailchimp.php
582 lines
1 <?php
2
3 defined('ABSPATH') or exit;
4
5
6 /**
7 * Helper class for dealing with common API requests.
8 */
9 class MC4WP_MailChimp
10 {
11 /**
12 * @var mixed
13 */
14 public $error_code = '';
15
16 /**
17 * @var string
18 */
19 public $error_message = '';
20
21 /**
22 *
23 * Sends a subscription request to the Mailchimp API
24 *
25 * @param string $list_id The list id to subscribe to
26 * @param string $email_address The email address to subscribe
27 * @param array $args
28 * @param bool $update_existing Update information if this email is already on list?
29 * @param bool $replace_interests Replace interest groupings, only if update_existing is true.
30 *
31 * @return null|object
32 * @throws Exception
33 */
34 public function list_subscribe($list_id, $email_address, array $args = [], $update_existing = false, $replace_interests = true)
35 {
36 $this->reset_error();
37 $default_args = [
38 'status' => 'pending',
39 'email_address' => $email_address,
40 ];
41 $existing_member_data = null;
42
43 // setup default args
44 $args = array_merge($default_args, $args);
45 $api = $this->get_api();
46
47 // first, check if subscriber is already on the given list
48 try {
49 $existing_member_data = $api->get_list_member($list_id, $email_address);
50 if ($existing_member_data->status === 'subscribed') {
51 // if we're not supposed to update, bail.
52 if (! $update_existing) {
53 $this->error_code = 214;
54 $this->error_message = 'That subscriber already exists.';
55
56 return null;
57 }
58
59 $args['status'] = 'subscribed';
60
61 // this key only exists if list actually has interests
62 if (isset($existing_member_data->interests)) {
63 $existing_interests = (array) $existing_member_data->interests;
64
65 // if replace, assume all existing interests disabled
66 if ($replace_interests) {
67 $existing_interests = array_fill_keys(array_keys($existing_interests), false);
68 }
69
70 $args['interests'] = array_replace($existing_interests, $args['interests']);
71 }
72 } elseif ($args['status'] === 'pending' && $existing_member_data->status === 'pending') {
73 // this ensures that a new double opt-in email is send out
74 $api->update_list_member(
75 $list_id,
76 $email_address,
77 [
78 'status' => 'unsubscribed',
79 ]
80 );
81 }
82 } catch (MC4WP_API_Resource_Not_Found_Exception $e) {
83 // subscriber does not exist (not an issue in this case)
84 } catch (MC4WP_API_Exception $e) {
85 // other errors.
86 $this->error_code = $e->getCode();
87 $this->error_message = $e;
88 return null;
89 }
90
91 try {
92 // Extract tags from args before subscriber creation/update
93 $tags = [];
94 if (isset($args['tags']) && is_array($args['tags'])) {
95 $tags = $args['tags'];
96 unset($args['tags']);
97 }
98
99 if ($existing_member_data) {
100 $data = $api->update_list_member($list_id, $email_address, $args);
101 $data->was_already_on_list = $existing_member_data->status === 'subscribed';
102 } else {
103 $data = $api->add_new_list_member($list_id, $args);
104 $data->was_already_on_list = false;
105 }
106
107 // update subscriber tags, if supplied
108 $this->update_subscriber_tags($list_id, $email_address, $tags);
109 } catch (MC4WP_API_Exception $e) {
110 $this->error_code = $e->getCode();
111 $this->error_message = $e;
112 return null;
113 }
114
115 return $data;
116 }
117
118 /**
119 * Format tags to send to Mailchimp.
120 *
121 * @param $tags array new tags to add
122 * @return array
123 * @since 4.7.9
124 */
125 private function merge_and_format_member_tags($tags)
126 {
127 $formatted_tags = [];
128 foreach ($tags as $tag) {
129 if (is_string($tag)) {
130 $formatted_tags[] = [
131 'name' => $tag,
132 'status' => 'active'
133 ];
134 } elseif (is_array($tag) && isset($tag['name'])) {
135 $formatted_tags[] = [
136 'name' => $tag['name'],
137 'status' => isset($tag['status']) ? $tag['status'] : 'active'
138 ];
139 }
140 }
141
142 return $formatted_tags;
143 }
144
145 /**
146 * Post the tags on a list member.
147 *
148 * @param $mailchimp_list_id string The list id to subscribe to
149 * @param $email_address Email of the Mailchimp susbcriber
150 * @param $tags array tags to set for the user (can include 'status' key)
151 * @return bool
152 * @throws Exception
153 * @since 4.10.10
154 */
155 private function update_subscriber_tags($mailchimp_list_id, $email_address, array $tags)
156 {
157 // do nothing if no tags given
158 if (count($tags) === 0) {
159 return true;
160 }
161
162 $api = $this->get_api();
163 $data = [
164 'tags' => $this->merge_and_format_member_tags($tags),
165 ];
166
167 try {
168 $api->update_list_member_tags($mailchimp_list_id, $email_address, $data);
169 } catch (MC4WP_API_Exception $ex) {
170 // fail silently
171 return false;
172 }
173
174 return true;
175 }
176
177 /**
178 * Changes the subscriber status to "unsubscribed"
179 *
180 * @param string $list_id
181 * @param string $email_address
182 *
183 * @return boolean
184 */
185 public function list_unsubscribe($list_id, $email_address)
186 {
187 $this->reset_error();
188
189 try {
190 $this->get_api()->update_list_member($list_id, $email_address, [ 'status' => 'unsubscribed' ]);
191 } catch (MC4WP_API_Resource_Not_Found_Exception $e) {
192 // if email wasn't even on the list: great.
193 return true;
194 } catch (MC4WP_API_Exception $e) {
195 $this->error_code = $e->getCode();
196 $this->error_message = $e;
197
198 return false;
199 }
200
201 return true;
202 }
203
204 /**
205 * Checks if an email address is on a given list with status "subscribed"
206 *
207 * @param string $list_id
208 * @param string $email_address
209 *
210 * @return boolean
211 * @throws Exception
212 */
213 public function list_has_subscriber($list_id, $email_address)
214 {
215 try {
216 $data = $this->get_api()->get_list_member($list_id, $email_address);
217 } catch (MC4WP_API_Resource_Not_Found_Exception $e) {
218 return false;
219 }
220
221 return ! empty($data->id) && $data->status === 'subscribed';
222 }
223
224 /**
225 * @param string $list_id
226 *
227 * @return array
228 * @throws Exception
229 */
230 public function get_list_merge_fields($list_id)
231 {
232 $transient_key = "mc4wp_list_{$list_id}_mf";
233 $cached = get_transient($transient_key);
234 if (is_array($cached)) {
235 return $cached;
236 }
237
238 $api = $this->get_api();
239
240 try {
241 // fetch list merge fields
242 $merge_fields = $api->get_list_merge_fields(
243 $list_id,
244 [
245 'count' => 100,
246 'fields' => 'merge_fields.name,merge_fields.tag,merge_fields.type,merge_fields.required,merge_fields.default_value,merge_fields.options,merge_fields.public',
247 ]
248 );
249 } catch (MC4WP_API_Exception $e) {
250 return [];
251 }
252
253 // add EMAIL field
254 array_unshift(
255 $merge_fields,
256 (object) [
257 'tag' => 'EMAIL',
258 'name' => __('Email address', 'mailchimp-for-wp'),
259 'required' => true,
260 'type' => 'email',
261 'options' => [],
262 'public' => true,
263 ]
264 );
265
266 set_transient($transient_key, $merge_fields, HOUR_IN_SECONDS * 24);
267
268 return $merge_fields;
269 }
270
271 /**
272 * @param string $list_id
273 *
274 * @return array
275 * @throws Exception
276 */
277 public function get_list_interest_categories($list_id)
278 {
279 $transient_key = "mc4wp_list_{$list_id}_ic";
280 $cached = get_transient($transient_key);
281 if (is_array($cached)) {
282 return $cached;
283 }
284
285 $api = $this->get_api();
286
287 try {
288 // fetch list interest categories
289 $interest_categories = $api->get_list_interest_categories(
290 $list_id,
291 [
292 'count' => 100,
293 'fields' => 'categories.id,categories.title,categories.type',
294 ]
295 );
296 } catch (MC4WP_API_Exception $e) {
297 return [];
298 }
299
300 foreach ($interest_categories as $interest_category) {
301 $interest_category->interests = [];
302
303 try {
304 // fetch groups for this interest
305 $interests_data = $api->get_list_interest_category_interests(
306 $list_id,
307 $interest_category->id,
308 [
309 'count' => 100,
310 'fields' => 'interests.id,interests.name',
311 ]
312 );
313 foreach ($interests_data as $interest_data) {
314 $interest_category->interests[ (string) $interest_data->id ] = $interest_data->name;
315 }
316 } catch (MC4WP_API_Exception $e) {
317 // ignore
318 }
319 }
320
321 set_transient($transient_key, $interest_categories, HOUR_IN_SECONDS * 24);
322
323 return $interest_categories;
324 }
325
326 /**
327 * Gets marketing permissions from a Mailchimp list.
328 * The list needs to have at least 1 member for this to work.
329 *
330 * @param string $list_id
331 *
332 * @return array
333 * @throws Exception
334 */
335 public function get_list_marketing_permissions($list_id)
336 {
337 $transient_key = "mc4wp_list_{$list_id}_mp";
338 $cached = get_transient($transient_key);
339 if (is_array($cached)) {
340 return $cached;
341 }
342
343 try {
344 $api = $this->get_api();
345 $data = $api->get_list_members(
346 $list_id,
347 [
348 'fields' => [ 'members.marketing_permissions' ],
349 'count' => 1,
350 ]
351 );
352
353 $marketing_permissions = [];
354 if (count($data->members) > 0 && isset($data->members[0]->marketing_permissions)) {
355 foreach ($data->members[0]->marketing_permissions as $mp) {
356 $marketing_permissions[] = (object) [
357 'marketing_permission_id' => $mp->marketing_permission_id,
358 'text' => $mp->text,
359 ];
360 }
361 }
362 } catch (MC4WP_API_Exception $e) {
363 return [];
364 }
365
366 set_transient($transient_key, $marketing_permissions, HOUR_IN_SECONDS * 24);
367 return $marketing_permissions;
368 }
369
370 /**
371 * Get Mailchimp lists, from cache or remote API.
372 *
373 * @param boolean $skip_cache Whether to force a result by hitting Mailchimp API
374 *
375 * @return array
376 */
377 public function get_lists($skip_cache = false)
378 {
379 $cache_key = 'mc4wp_mailchimp_lists';
380 $cached = get_transient($cache_key);
381
382 if (is_array($cached) && ! $skip_cache) {
383 return $cached;
384 }
385
386 $lists = $this->fetch_lists();
387
388 /**
389 * Filters the cache time for Mailchimp lists configuration, in seconds. Defaults to 24 hours.
390 */
391 $cache_ttl = (int) apply_filters('mc4wp_lists_count_cache_time', HOUR_IN_SECONDS * 24);
392
393 // make sure cache ttl is not lower than 60 seconds
394 $cache_ttl = max(60, $cache_ttl);
395 set_transient($cache_key, $lists, $cache_ttl);
396
397 return $lists;
398 }
399
400 private function fetch_lists()
401 {
402 $client = $this->get_api()->get_client();
403 $lists_data = [];
404 $offset = 0;
405 $count = 10;
406 $exceptions_skipped = 0;
407
408 // increase total time limit to 3 minutes
409 @set_time_limit(180);
410
411 // increase HTTP timeout to 30s as MailChimp is super slow to calculate dynamic fields
412 add_filter(
413 'mc4wp_http_request_args',
414 function ($args) {
415 $args['timeout'] = 30;
416 return $args;
417 }
418 );
419
420 // collect all lists in separate HTTP requests
421 do {
422 $data = null;
423 try {
424 $data = $client->get(
425 '/lists',
426 [
427 'count' => $count,
428 'offset' => $offset,
429 'fields' => 'total_items,lists.id,lists.name,lists.web_id,lists.stats.member_count,lists.marketing_permissions',
430 ]
431 );
432
433 $lists_data = array_merge($lists_data, $data->lists);
434 $offset += $count;
435 } catch (MC4WP_API_Connection_Exception $e) {
436 // ignore timeout errors as this is likely due to mailchimp being slow to calculate the lists.stats.member_count property
437 // keep going so we can at least pull-in all other lists
438 $offset += $count;
439 ++$exceptions_skipped;
440
441 // failsafe against infinite loop
442 // bail after 5 skipped exceptions
443 if ($exceptions_skipped >= 5) {
444 break;
445 }
446
447 continue;
448 } catch (MC4WP_API_Exception $e) {
449 // break on other errors, like "API key missing"etc.
450 break;
451 }
452 } while ($data && $data->total_items >= $offset);
453
454 // key by list ID
455 $lists = [];
456 foreach ($lists_data as $list_data) {
457 $lists["$list_data->id"] = $list_data;
458 }
459
460 return $lists;
461 }
462
463 /**
464 * @param string $list_id
465 *
466 * @return object|null
467 */
468 public function get_list($list_id)
469 {
470 $lists = $this->get_lists();
471
472 return isset($lists["$list_id"]) ? $lists["$list_id"] : null;
473 }
474
475 /**
476 * Fetch lists data from Mailchimp.
477 */
478 public function refresh_lists()
479 {
480 $lists = $this->get_lists(true);
481
482 foreach ($lists as $list_id => $list) {
483 // delete cached merge fields
484 delete_transient("mc4wp_list_{$list_id}_mf");
485
486 // delete cached interest categories
487 delete_transient("mc4wp_list_{$list_id}_ic");
488
489 // delete cached marketing permissions
490 delete_transient("mc4wp_list_{$list_id}_mp");
491 }
492
493 return ! empty($lists);
494 }
495
496
497 /**
498 * Returns number of subscribers on given lists.
499 *
500 * @param array|string $list_ids Array of list ID's, or single string.
501 *
502 * @return int Total # subscribers for given lists.
503 */
504 public function get_subscriber_count($list_ids)
505 {
506 // make sure we're getting an array
507 if (! is_array($list_ids)) {
508 $list_ids = [ $list_ids ];
509 }
510
511 // if we got an empty array, return 0
512 if (empty($list_ids)) {
513 return 0;
514 }
515
516 $lists = $this->get_lists();
517
518 // start calculating subscribers count for all given list ID's combined
519 $count = 0;
520 foreach ($list_ids as $list_id) {
521 if (! isset($lists["$list_id"])) {
522 continue;
523 }
524
525 $list = $lists["$list_id"];
526 $count += $list->stats->member_count;
527 }
528
529 /**
530 * Filters the total subscriber_count for the given List ID's.
531 *
532 * @param float|int $count
533 * @param array $list_ids
534 *
535 * @since 2.0
536 */
537 return apply_filters('mc4wp_subscriber_count', $count, $list_ids);
538 }
539
540 /**
541 * Resets error properties.
542 */
543 public function reset_error()
544 {
545 $this->error_message = '';
546 $this->error_code = '';
547 }
548
549 /**
550 * @return bool
551 */
552 public function has_error()
553 {
554 return ! empty($this->error_code);
555 }
556
557 /**
558 * @return string
559 */
560 public function get_error_message()
561 {
562 return $this->error_message;
563 }
564
565 /**
566 * @return string
567 */
568 public function get_error_code()
569 {
570 return $this->error_code;
571 }
572
573 /**
574 * @return MC4WP_API_V3
575 * @throws Exception
576 */
577 private function get_api()
578 {
579 return mc4wp_get_service('api');
580 }
581 }
582