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;
}
}
@contemplate
Copy link
Author

Fixes API error in Mailchimp "someemail@gmailcom is already a list member. Use PUT to insert or update list members." by using the PUT command to insert or update the subscriber.

In Bloom Replace this File:
/wp-content/plugins/bloom/core/components/api/email/MailChimp.php

If using Divi Theme Also Replace this File:
/wp-content/themes/Divi/core/components/api/email/MailChimp.php

@XTard
Copy link

XTard commented Mar 10, 2023

Hey there! I think this is outdated now and for me it doesn't update custom fields (checkboxes). Also, even with the default source for Divi Opt-ins and Bloom - there's no API error anymore if the user already exists in the MailChimp database.

Anywho, this solution I found that actually works and updates custom fields (really only checkboxes, as radio buttons and hidden fields don't work with Divi/Bloom).

Note: it doesn't replace checkbox group labels, it adds labels. Example:
Checkbox group 1 has three subgroups: Sci-fi, Fantasy, Action
First time visitor checks Sci-fi and subscribes/submits the form -> new record is added on MC's side with the Sci-fi label in the Checkbox group 1 column -> Success message is rendered
This same visitors decides they want to fill in the subscription form again, this time checking Fantasy -> record with that user's email is updated in MC and now there are two labels in the column - Sci-fi **and** Fantasy.

Code reference - Greg Freeman @ www.peeayecreative.com

Replace just in case in both files:
wp-content/themes/Divi/core/components/api/email/MailChimp.php
and
wp-content/plugins/bloom/core/components/api/email/MailChimp.php

Note: this solution will most likely override itself by updating Divi and Bloom

Full code:

<?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->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 ) {
			$is_group = self::$_->array_get( $custom_fields_data, "{$field_id}.is_group", false );

			if ( is_array( $value ) && $value ) {
				foreach ( $value as $id => $field_value ) {
					if ( $is_group ) {
						// If it is a group custom field, set as `interests` and don't process the `merge_fields`
						self::$_->array_set( $args, "interests.{$id}", true );
						$field_id = false;
					} else {
						$value = $field_value;
					}
				}
			}

			if ( false === $field_id ) {
				continue;
			}

			// 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}" );

			// Need to strips existing slash chars.
			self::$_->array_set( $args, "merge_fields.{$custom_field_tag}", stripslashes( $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' );
		$url       = "{$this->BASE_URL}/lists/{$list_id}/members";
		$email     = $args['email_address'];
		$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']    = $dbl_optin ? 'pending' : 'subscribed';
		$args['list_id']   = $list_id;

		$args = $this->_process_custom_fields( $args );

		$this->prepare_request( $url, 'POST', false, $args, true );
		$result = parent::subscribe( $args, $url );

		if ( false !== stripos( $result, 'already a list member' ) ) {
          $result = $err;

          if ( $user = $this->get_subscriber( $list_id, $email ) ) {
			 // old code doesn't send the PUT request to mailchimp when custom fields are changed
			 // we need to do this in order to use the mailchimp groups workaround: https://www.peeayecreative.com/how-to-add-mailchimp-groups-to-the-bloom-email-opt-in-plugin/
			 //          if ( 'subscribed' === $user['status'] ) {
			 //             $result = 'success';
			 //          } else {
			 //             $this->prepare_request( implode( '/', array( $url, $user['id'] ) ), 'PUT', false, $args, true );
			 //
			 //             $result = parent::subscribe( $args, $url );
			 //          }


                 $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;
	}
}

@contemplate
Copy link
Author

Thanks @XTard for the update on this! We haven't used Bloom much in the last few years so I had no need to see if this was still needed.

@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