Skip to content

Instantly share code, notes, and snippets.

@contemplate
Created October 16, 2019 02:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save contemplate/65c6cde1cd243f22ed5479eba650c94d to your computer and use it in GitHub Desktop.
Save contemplate/65c6cde1cd243f22ed5479eba650c94d to your computer and use it in GitHub Desktop.
Bloom better Mailchimp integration for groups and custom field updates
<?php
/**
* Wrapper for MailChimp's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MailChimp extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = '';
/**
* Use this variable to hold the pattern and update $BASE_URL dynamically when needed
*/
public $BASE_URL_PATTERN = 'https://@datacenter@.api.mailchimp.com/3.0';
/**
* @inheritDoc
*/
public $http_auth = array(
'username' => '-',
'password' => 'api_key',
);
/**
* @inheritDoc
*/
public $name = 'MailChimp';
/**
* @inheritDoc
*/
public $slug = 'mailchimp';
public function __construct( $owner, $account_name, $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
if ( ! empty( $this->data['api_key'] ) ) {
$this->_set_base_url();
}
$this->http_auth['username'] = $owner;
}
protected function _add_note_to_subscriber( $email, $url ) {
//$email = md5( $email );
//$this->prepare_request( "{$url}/$email/notes", 'POST' );
$this->prepare_request( "{$url}/notes", 'POST' ); //added by contemplate
$this->request->BODY = json_encode( array( 'note' => $this->SUBSCRIBED_VIA ) );
$this->make_remote_request();
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->response_data_key = 'merge_fields';
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/merge-fields?count={$this->COUNT}" );
$fields = parent::_fetch_custom_fields( $list_id, $list );
foreach ( $fields as $id => $field ) {
if ( in_array( $id, array( 1, 2 ) ) ) {
unset( $fields[ $id ] );
}
}
// MailChimp is weird in that they treat checkbox fields as an entirely different concept in their API (Groups)
// We'll grab the groups and treat them as checkbox fields in our UI.
$groups = $this->_fetch_subscriber_list_groups( $list_id );
return $fields + $groups;
}
protected function _fetch_subscriber_list_group_options( $list_id, $group_id ) {
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/interest-categories/{$group_id}/interests?count={$this->COUNT}" );
$this->make_remote_request();
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return array();
}
$data = $this->response->DATA['interests'];
$options = array();
foreach ( $data as $option ) {
$option = $this->transform_data_to_our_format( $option, 'group_option' );
$id = $option['id'];
$options[ $id ] = $option['name'];
}
return $options;
}
protected function _fetch_subscriber_list_groups( $list_id ) {
$this->response_data_key = 'categories';
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/interest-categories?count={$this->COUNT}" );
$this->make_remote_request();
$groups = array();
if ( false !== $this->response_data_key && empty( $this->response_data_key ) ) {
// Let child class handle parsing the response data themselves.
return $groups;
}
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return $groups;
}
$data = $this->response->DATA[ $this->response_data_key ];
foreach ( $data as $group ) {
$group = $this->transform_data_to_our_format( $group, 'group' );
$field_id = $group['field_id'];
$type = $group['type'];
if ( 'hidden' === $type ) {
// MailChimp only allows groups of type: 'checkbox' to be hidden.
$group['type'] = 'checkbox';
$group['hidden'] = true;
}
$group['is_group'] = true;
$group['options'] = $this->_fetch_subscriber_list_group_options( $list_id, $field_id );
$group['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'text' );
$groups[ $field_id ] = $group;
}
return $groups;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
$list_id = self::$_->array_get( $args, 'list_id', '' );
unset( $args['custom_fields'] );
unset( $args['list_id'] );
$custom_fields_data = self::$_->array_get( $this->data, "lists.{$list_id}.custom_fields", array() );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
foreach ( $value as $id => $field_value ) {
self::$_->array_set( $args, "interests.{$id}", true );
}
} else {
// In previous version of Mailchimp implementation we only supported default field tag, but it can be customized and our code fails.
// Added `field_tag` attribute which is actual field tag. Fallback to default field tag if `field_tag` doesn't exist for backward compatibility.
$custom_field_tag = self::$_->array_get( $custom_fields_data, "{$field_id}.field_tag", "MMERGE{$field_id}" );
self::$_->array_set( $args, "merge_fields.{$custom_field_tag}", $value );
}
}
return $args;
}
protected function _set_base_url() {
$api_key_pieces = explode( '-', $this->data['api_key'] );
$datacenter = empty( $api_key_pieces[1] ) ? '' : $api_key_pieces[1];
$this->BASE_URL = str_replace( '@datacenter@', $datacenter, $this->BASE_URL_PATTERN );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_set_base_url();
/**
* The maximum number of subscriber lists to request from MailChimp's API.
*
* @since 2.0.0
*
* @param int $max_lists
*/
$max_lists = (int) apply_filters( 'et_core_api_email_mailchimp_max_lists', 250 );
$url = "{$this->BASE_URL}/lists?count={$max_lists}&fields=lists.name,lists.id,lists.stats,lists.double_optin";
$this->prepare_request( $url );
$this->response_data_key = 'lists';
$result = parent::fetch_subscriber_lists();
return $result;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'double_optin' => 'double_optin',
'subscribers_count' => 'stats.member_count',
),
'subscriber' => array(
'email' => 'email_address',
'name' => 'merge_fields.FNAME',
'last_name' => 'merge_fields.LNAME',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'detail',
),
'custom_field' => array(
'field_id' => 'merge_id',
'field_tag' => 'tag',
'name' => 'name',
'type' => 'type',
'hidden' => '!public',
'options' => 'options.choices',
),
'custom_field_type' => array(
// Us <=> Them
'radio' => 'radio',
// Us => Them
'input' => 'text',
'select' => 'dropdown',
'checkbox' => 'checkboxes',
// Them => Us
'text' => 'input',
'dropdown' => 'select',
'checkboxes' => 'checkbox',
),
'group' => array(
'field_id' => 'id',
'name' => 'title',
'type' => 'type',
),
'group_option' => array(
'id' => 'id',
'name' => 'name',
),
);
return parent::get_data_keymap( $keymap );
}
public function get_subscriber( $list_id, $email ) {
$hash = md5( strtolower( $email ) );
$this->prepare_request( implode( '/', array( $this->BASE_URL, 'lists', $list_id, 'members', $hash ) ) );
$this->make_remote_request();
return $this->response->STATUS_CODE !== 200 ? null : $this->response->DATA;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$list_id = $args['list_id'];
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$email = $args['email_address'];
$hash = md5( strtolower( $email ) ); //added by contemplate
$url = "{$this->BASE_URL}/lists/{$list_id}/members/{$hash}"; //editted by contemplate
$err = esc_html__( 'An error occurred, please try later.', 'et_core' );
$dbl_optin = self::$_->array_get( $this->data, "lists.{$list_id}.double_optin", true );
$ip_address = 'true' === self::$_->array_get( $args, 'ip_address', 'true' ) ? et_core_get_ip_address() : '0.0.0.0';
$args['ip_signup'] = $ip_address;
$args['status_if_new'] = $dbl_optin ? 'pending' : 'subscribed'; //added by contemplate
$args['status'] = $dbl_optin ? 'pending' : 'subscribed';
$args['list_id'] = $list_id;
$args = $this->_process_custom_fields( $args );
//$this->prepare_request( $url, 'POST', false, $args, true );
$this->prepare_request( $url, 'PUT', false, $args, true ); //added by contemplate
$result = parent::subscribe( $args, $url );
if ( false !== stripos( $result, 'already a list member' ) ) {
$result = $err;
if ( $user = $this->get_subscriber( $list_id, $email ) ) {
if ( 'subscribed' === $user['status'] ) {
$result = 'success';
} else {
$this->prepare_request( implode( '/', array( $url, $user['id'] ) ), 'PUT', false, $args, true );
$result = parent::subscribe( $args, $url );
}
}
}
if ( 'success' === $result ) {
$this->_add_note_to_subscriber( $email, $url );
} else if ( false !== stripos( $result, 'has signed up to a lot of lists ' ) ) {
// return message which can be translated. Generic Mailchimp messages are not translatable.
$result = esc_html__( 'You have signed up to a lot of lists very recently, please try again later', 'et_core' );
} else {
$result = $err;
}
return $result;
}
}
@XTard
Copy link

XTard commented Mar 11, 2023

No worries! Bloom and Divi opt-ins are notorious for being "incapable" ... I hope this helps other peeps struggling with finding solutions.

Another snippet I'd like to add to this thread is for enabling editing custom fields' titles and content. It works both in Bloom's dashboard for custom fields and also in Divi's page builder Opt-ins.

Example application of this - you have a consent checkbox for privacy policy. This checkbox is standard and you need to collect it. Instead of having to add different consents checkboxes that have the same meaning but have a different "label", you can just edit the field's text label.
I have read the privacy policy and agree to it. - you could change it to:
I have read the privacy policy and also agree to receive free newsletter bulletin.

To enable this edit the following files:

CAUTION: these changes might get overridden by a Bloom and/or Divi theme/pagebuilder update

wp-content/themes/Divi/core/components/api/email/Fields.php
wp-content/plugins/bloom/core/components/api/email/Fields.php

What do edit:

  1. Find this code:
'checkbox_options' => array(
	'label'           => esc_html__( 'Options', 'et_core' ),
	'type'            => 'sortable_list',
	'checkbox'        => true,
	'option_category' => 'basic_option',
	'toggle_slug'     => 'field_options',
	'readonly_if'     => array(
		$readonly_dependency => self::$_predefined_custom_field_support,
	),
	'readonly_if_not' => array(
		$readonly_dependency => self::$_any_custom_field_type_support,
	),
	'show_if'         => array(
		'field_type' => 'checkbox',
	),
	'right_actions'   => 'move|link|copy|delete',
	'right_actions_readonly' => 'move|link',
	'labels'          => $labels,
),
  1. Comment out the readonly_if and readonly_if_not settings like this:
'checkbox_options' => array(
	'label'           => esc_html__( 'Options', 'et_core' ),
	'type'            => 'sortable_list',
	'checkbox'        => true,
	'option_category' => 'basic_option',
	'toggle_slug'     => 'field_options',
	// 'readonly_if'     => array(
	// 	$readonly_dependency => self::$_predefined_custom_field_support,
	// ),
	// 'readonly_if_not' => array(
	// 	$readonly_dependency => self::$_any_custom_field_type_support,
	// ),
	'show_if'         => array(
		'field_type' => 'checkbox',
	),
	'right_actions'   => 'move|link|copy|delete',
	'right_actions_readonly' => 'move|link',
	'labels'          => $labels,
),
  1. Save the file and make sure to edit both Bloom's file and Divi's file. Enjoy.
  2. !! untested behaviour for other fields other than checkboxes !! You could actually do this for any type of field, not just checkboxes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment