Image
2020
Implements PHP library for v3 of the MailChimp API and provides composite Field plugin with FieldFormatter and FieldWidget as well as Block plugin. From MailChimp API and via custom composite Element which holds configuration data for both, plugins retrieve all the data from remote, such as Audiences, Groups, Fields etc. and relations between these. Either by field (render in twig template for instance) or as a block assigned to any region, we are able to render a MailChimp subscription forms (dynamic instance of standard Drupal Form) which we previously "build" for functionality and UI in mentioned configuration on entity form (as a field) or block plugin form.
/* \Drupal\[module]\Form */
Code
namespace Drupal\diplo_mailchimp\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\filter\Render\FilteredMarkup;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Html;
use Drupal\diplo_mailchimp\DiploMailChimpTrait;
/**
* Class DiploMailchimpForm
*/
class DiploMailchimpForm extends FormBase {
use DiploMailChimpTrait;
/**
* {@inheritdoc}
*/
public function getFormId() {
static $count = 0;
$count++;
return 'diplo_mailchimp_form_' . $count;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, array $config = []) {
$config_storage = \Drupal::config('diplo_mailchimp.settings')->getRawData();
$config = array_merge($config_storage, $config);
// Set config variables
$form_state->set('subscribe_config', $config);
// Get the form values
$values = $form_state->getValues();
$form_class = isset($config['form_class']) && !empty($config['form_class']) ? $config['form_class'] : '';
$form['#theme'] = 'diplo_mailchimp_form';
$form_container = Html::getId('mailchimp-subscriptions-' . $config['block_id']);
$form['#prefix'] = '<div id="' . $form_container . '" class="' . $form_class . '">';
$form['#suffix'] = '</div>';
// Force this, may be essential for multiple forms on the same page
$form['#id'] = $this->getFormId();
// Kudos to MDJ
$form_state->setRequestMethod('POST');
$form_state->setCached(TRUE);
// $form['#attached']['library'][] = 'diplo_mailchimp/geturlparam';
// We do use a little JS for a special event, like a delayed 'keyup'
// @see js/diplo_mailchimp.js
$form['#attached']['library'][] = 'diplo_mailchimp/main';
$form['#attributes']['class'][] = 'diplo-mailchimp-form';
$audiences = isset($config['audiences']) ? $config['audiences'] : NULL;
$form['audiences'] = [
'#type' => 'value',
'#value' => $audiences,
];
if (isset($config['form_title']) && !empty($config['form_title'])) {
$form['form_title'] = [
'#type' => 'markup',
'#markup' => $config['form_title'],
'#weight' => -5,
];
}
if (isset($config['description']) && isset($config['description']['value']) && !empty($config['description']['value'])) {
$form['description'] = [
'#prefix' => '<div class="mailchimp-subscription description">',
'#suffix' => '</div>',
'#type' => 'markup',
'#markup' => FilteredMarkup::create($config['description']['value']),
'#weight' => -4,
];
}
// Use static method to "hardcode" from time to time mandatory fields
// Those are the ones that are set so in MC - actually none of the fields should be set there so, then we do validation on our end, possibly front-end too
$mandatory_fields = static::mandatoryFields();
$member_fields = [];
$member_info = NULL;
if (!empty($values['fields']['EMAIL']) && $audiences) {
$member_info = static::getMemberInfo($audiences, $values['fields']['EMAIL'], TRUE);
$form_state->set('member_info', (array)$member_info);
$member_fields = is_object($member_info) && isset($member_info->merge_fields) ? (array) $member_info->merge_fields : [];
}
$fields_container = Html::getId($config['block_id'] . '-fetch-container');
$form['fields'] = [
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div id="' . $fields_container .'">',
'#suffix' => '</div>',
];
if (isset($config['fields']) && !empty($config['fields'])) {
foreach ($config['fields'] as $field_id => $field_type) {
if (isset($field_type['type'])) { // Precaution, due many possible scenarios
$form['fields'][$field_id] = [
'#type' => $field_type['type'],
'#title' => $field_type['label'],
'#title_display' => 'invisible',
'#default_value' => isset($values[$field_id]) ? $values[$field_id] : NULL,
'#required' => FALSE, //TRUE,
'#attributes' => [
'class' => []
],
];
switch ($field_type['type']) {
// Dropdown/select widget
case 'select':
$form['fields'][$field_id]['#options'] = $field_type['options'];
$form['fields'][$field_id]['#multiple'] = FALSE;
$form['fields'][$field_id]['#empty_option'] = $field_type['label'];
break;
// Checkboxes and radios
case 'checkboxes':
case 'radios':
$form['fields'][$field_id]['#options'] = $field_type['options'];
break;
// Textfield, email and the others for now
default:
$form['fields'][$field_id]['#size'] = 60;
$form['fields'][$field_id]['#maxlength'] = 128;
$form['fields'][$field_id]['#attributes']['placeholder'] = $field_type['label'];
break;
}
// Set required fields
if ($field_id == 'EMAIL' || in_array($field_id, $mandatory_fields)) {
$form['fields'][$field_id]['#required'] = TRUE;
}
// Set value callback for all the other fields than email which triggers retrieving those from remote MC
if ($field_id != 'EMAIL') {
//$form['fields'][$field_id]['#value_callback'] = [$this, 'valuesCallback'];
}
}
}
}
else {
$form['fields']['EMAIL'] = [
'#title' => $this->t('Email address'),
'#title_display' => 'invisible',
'#type' => 'email',
'#required' => TRUE,
'#default_value' => isset($values['EMAIL']) ? $values['EMAIL'] : NULL,
//'#attached' => [],
//'#prefix' => '<div id="' . $fields_container .'">',
//'#suffix' => '</div>',
'#attributes' => [
'placeholder' => $this->t('Email address'),
'class' => []
],
'#weight' => -1,
];
}
// Add ajax callback on email field, retrieves fields' data from remote MC
$form['fields']['EMAIL']['#ajax'] = [
'disable-refocus' => TRUE, // Essential for multiple forms on the same page
'event' => 'finishedinput',
'callback' => [$this, 'fetchMember'],
'effect' => 'fade',
'wrapper' => $fields_container,
'progress' => [
'type' => 'throbber',
'message' => t('Fetching your data from MailChimp...'),
],
];
// If we do NOT have some of mandatory fields displayed on the form - example dig.watch newsletter
// Then set either existing values (previously retrieved from MC or 'None' in case of brand new member
foreach ($mandatory_fields as $mid) {
if (!isset($config['fields'][$mid])) {
$field_value = !empty($member_fields) && isset($member_fields[$mid]) ? $member_fields[$mid] : 'None';
$form['fields'][$mid] = [
'#type' => 'value',
'#value' => $field_value
];
}
}
// Prepare and render groups
if (isset($config['groups']) && !empty($config['groups'])) {
if ($config['hide_groups']) { // If we opted for not showing them set them as value elements on the form
$form['groups'] = [
'#type' => 'value',
'#value' => $config['groups']
];
}
else {
$list_id = isset($config['audiences']) && !empty($config['audiences']) ? $config['audiences'] : NULL;
$group_options = [];
$member_groups = [];
if ($list_id) {
$groups = static::getGroups($list_id);
if (!empty($groups)) {
// Get all the groups this existing member belongs to
$member_groups_all = static::getMemberGroups($member_info);
foreach ($groups as $delta => $group) {
if (in_array($group->id, array_keys($config['groups']))) {
// Match possible existing member groups with those attached to the current form
if (!empty($member_groups_all) && in_array($group->id, array_keys($member_groups_all))) {
$member_groups[$group->id] = $group->name;
}
// Define groups #options
$group_options[$group->id] = $group->name;
}
}
}
}
$form_state->set('member_groups', $member_groups);
$groups_default_value = isset($values['groups']) && !empty($values['groups']) ? $values['groups'] : [];
// Seems like we do want chekboxes to be default checked for user
// Even though for existing member only the ones that this member was previously subscribed will remain checked (via ajax call on email input)
if (empty($groups_default_value)) {
// $groups_default_value = array_keys($group_options);
}
$form['groups'] = [
'#type' => 'checkboxes',
'#title' => t('Subscription type'),
'#title_display' => 'invisible',
'#required' => TRUE,
'#default_value' => $groups_default_value,
// '#validated' => TRUE,
'#options' => $group_options,
'#attributes' => [
'class' => []
]
];
}
}
if (isset($config['privacy']) && $config['privacy']) {
if (isset($config['privacy_note']) && isset($config['privacy_note']['value']) && !empty($config['privacy_note']['value'])) {
$accept_label = FilteredMarkup::create($config['privacy_note']['value']);
}
else {
$accept_label = $this->t('Subscribe and Agree - by submitting this form, you are agreeing to our privacy policy');
}
$form['subscribe_op'] =[
'#type' => 'radios',
'#title' => $this->t('Please select your preference'),
'#title_display' => 'invisible',
'#default_value' => isset($values['subscribe_op']) ? $values['subscribe_op'] : NULL,
'#required' => TRUE,
'#options' => [
'accept' => $accept_label
],
];
// Unsubscribe
if (isset($config['unsubscribe']) && $config['unsubscribe']) {
$unsubscribe_label = isset($config['unsubscribe_label']) && isset($config['unsubscribe_label']['value']) && !empty($config['unsubscribe_label']['value']) ? $config['unsubscribe_label']['value'] : $this->t('Unsubscribe - tick if you are already subscribed and you wish to stop receiving our emails.');
$form['subscribe_op']['#options'] = ['unsubscribe' => $unsubscribe_label] + $form['subscribe_op']['#options'];
}
}
// Buttons
$form['actions'] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'container-inline'
]
]
];
$btn_value = isset($config['subscribe_button']) && !empty($config['subscribe_button']) ? $this->t('@subscribe_button', ['@subscribe_button' => $config['subscribe_button']]) : $this->t('Subscribe');
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $btn_value,
'#ajax' => [
'callback' => [get_class($this), 'submitAjax'],
'wrapper' => $form_container,
'progress' => [
'type' => 'throbber',
'message' => t('Verifying entry...'),
],
],
'#attributes' => [
],
];
if (isset($config['cancel']) && $config['cancel']) {
$form['actions']['cancel'] = [
'#type' => 'button',
'#value' => $this->t('Cancel'),
'#attributes' => [
'id' => 'cancel-subscription',
],
];
}
return $form;
}
/**
* {@inheritdoc}
* Values callback, for all the other fields but email which triggers request to MC for field values
*/
/*
public function valuesCallback(&$element, $input, FormStateInterface $form_state) {
if ($input != '' && $input !== FALSE) {
return $input;
}
$values = $form_state->getValues();
$trigger = $form_state->getTriggeringElement();
$email = $form_state->getValue(['fields', 'EMAIL']);
$audiences = $form_state->getValue('audiences');
if ($audiences && $email) {
$member_fields = [];
$member_info = static::getMemberInfo($audiences, $email);
$form_state->set('member_info', (array)$member_info);
$name = end($element['#parents']);
$member_fields = isset($member_info->merge_fields) ? (array) $member_info->merge_fields : [];
$default_value = !empty($member_fields) && isset($member_fields[$name]) && $member_fields[$name] != 'None' ? $member_fields[$name] : NULL;
if ($default_value) {
return $default_value;
}
else {
return isset($element['#default_value']) ? $element['#default_value'] : NULL;
}
}
else {
return isset($element['#default_value']) ? $element['#default_value'] : NULL;
}
}
*/
/**
* {@inheritdoc}
* Mandatory parent method
* Checks on member subscription, based on email, via MailChimp API and fires appropriate action, based on action option selection subscribe/unsubscribe
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// If basic Drupal form validation failed we do not want any further processing
if (!empty($form_state->getErrors())) {
return;
}
$config = $form_state->get('subscribe_config');
$values = $form_state->getValues();
$audiences = isset($values['audiences']) && !empty($values['audiences']) ? $values['audiences'] : NULL;
if (isset($values['fields']) && isset($values['fields']['EMAIL'])) {
$email = !empty($values['fields']['EMAIL']) ? $values['fields']['EMAIL'] : NULL;
}
$groups_opt = isset($values['groups']) && !empty($values['groups']) ? $values['groups'] : [];
if ($audiences && $email && !empty($groups_opt)) {
// Fetch this list object
$list = static::getList($audiences);
// Get all the infor of this member (if existing one)
$member_info = static::getMemberInfo($list->id, $email, TRUE);
// Fetch groups to which this user is already subcribed
$member_groups_subscriptions = static::matchSubscriptions($form['groups'], $member_info, $values);
if (is_object($list)) {
$op = isset($values['subscribe_op']) && !empty($values['subscribe_op']) ? $values['subscribe_op'] : 'accept';
if ($op == 'accept') {
// A main subscribe callback now
$this->subscribe($form, $form_state, $list, $values, $email, $audiences, $member_info, $member_groups_subscriptions, $config);
}
else if ($op == 'unsubscribe') {
foreach ($groups_opt as $group_id => $group) {
if ($group && $group != '0') {
// A main un-subscribe callback
$this->unsubscribe($form, $form_state, $list, $email, $audiences, $config, $member_groups_subscriptions, $group_id, $group);
}
}
}
}
}
}
/**
* Custom method for a various requests to remote MC API
*/
protected function subscribe(array &$form, FormStateInterface $form_state, $list, $values, $email, $audiences, $member_info, $member_groups_subscriptions, &$config) {
$merge_vars = NULL;
$double_optin =isset($config['double_optin']) && $config['double_optin'] ? TRUE : FALSE; // Seems like we need to turn integers into bool here
$zoom_id =isset($config['zoom_id']) && !empty($config['zoom_id']) ? $config['zoom_id'] : NULL;
$zoom_type = isset($config['zoom_type']) && !empty($config['zoom_type']) ? $config['zoom_type'] : NULL;
$debug = isset($config['debug']) && $config['debug'] ? TRUE : FALSE;
$op = isset($values['subscribe_op']) && !empty($values['subscribe_op']) ? $values['subscribe_op'] : 'accept';
if (is_object($member_info) && isset($member_info->status) && $member_info->status == 'subscribed') { // Anyhow we do not want double optin for existing member?
$double_optin = FALSE;
}
$groups = [
'already_subscribed' => [
'ids' => [],
'labels' => [],
'op' => $op,
],
'subscribe' => [
'ids' => [],
'labels' => [],
'op' => $op,
],
];
foreach ($values['groups'] as $group_id => $group) {
if ($group && $group != '0') {
$groups['subscribe']['ids'][] = $group_id;
$groups['subscribe']['labels'][] = isset($form['groups'][$group_id]) ? $form['groups'][$group_id]['#title'] : $list->name . ': ' . $group_id;
}
else {
// This group was un-checked by user in UI yet the user was previously subscribed to it so unsubscribe right away
if (isset($member_groups_subscriptions[$group_id]) && !empty($member_groups_subscriptions[$group_id])) {
// Unsubscribe member from this group
if (!$debug) {
$this->unsubscribe($form, $form_state, $list, $email, $audiences, $config, $member_groups_subscriptions, $group_id, $group);
}
}
}
}
// Subscribe user now into the groups that is not member yet
if (!empty($groups['subscribe']['ids'])) {
$labels = !empty($groups['subscribe']['labels']) ? implode(', ' , $groups['subscribe']['labels']) : $list->name;
$messages = $this->validationStrings($config, $list, ['label' => $labels]);
$merge_vars = [];
if (isset($values['fields'])) {
$merge_vars = $values['fields'];
}
// First check on Zoom opt
if ($zoom_id && $zoom_type && !empty($merge_vars)) {
$zoom_body = [
'email' => $email,
'first_name' => $merge_vars['FNAME'], // Note that these are not hardcode, since we secure all the mandatory fields have value in $this->buildForm()
'last_name' => $merge_vars['LNAME'],
];
/**
* Add zoom registrant
* @var \Drupal\diplo_mailchimp\DiploMailChimpTrait
*/
static::zoomAddRegistrant($zoom_id, $zoom_type, $zoom_body, [], $debug);
}
// Finally a "subscribe" to groups (or even first time member) request to remote API
$result = !$debug ? static::subscribeMember($list->id, $email, $merge_vars, $groups['subscribe']['ids'], $double_optin) : NULL;
if ($result) {
if (isset($values['tags']) && !empty($values['tags'])) {
$tags = $values['tags'];
static::addTags($list, $email, $tags);
}
// This is our case for now, the above is assumption of possible tag on the form as a field
else {
// Do this explicitely for entity/i.e. our diplo mailchimp form
// This is because apart from a form attached via field there can be another form on the same page
$route = \Drupal::service('current_route_match');
$is_node = $route->getParameters()->has('node') ? $route->getParameter('node') : NULL;
if (is_object($is_node)) { // && strpos($config['block_id'], 'entity') !== FALSE) {
$tag = substr($is_node->getTitle(), 0, 99);
$tags = [$tag];
static::addTags($list, $email, $tags);
}
}
// Send success message to user
if (!empty($messages['success'])) {
\Drupal::service('messenger')->addStatus($messages['success']);
}
// Now log this for admin
$log = $this->t('A new subscriber @email on @labels MailChimp groups', ['@email' => $email, '@labels' => $labels]);
\Drupal::service('logger.factory')->get('Diplo Mailchimp')->notice($log);
}
// Something went wrong, "blame" MC
else {
if (!$debug) {
\Drupal::service('messenger')->addError($this->t('Remote service MailChimp returned an error, please try again later. If you are authenticated user check logs.'));
}
else {
\Drupal::service('messenger')->addWarning('Working in the debug mode, no (un) subscriptions calls are sent to remote API. turn off Debug mode via checkbox on configuration.');
}
}
}
}
/**
* Custom method for a Unsubscribe member to remote MC API
* This actually "updates" on membership in MC grooups, this is in line with our basic logic set on MC that groups are distinction, not audiences / lists
*/
protected function unsubscribe(array &$form, FormStateInterface $form_state, $list, $email, $audiences, $config, $member_groups_subscriptions, $group_id, $group) {
if ($debug) {
\Drupal::service('messenger')->addWarning('Working in the debug mode, no (un) subscriptions calls are sent to remote API. turn off Debug mode via checkbox on configuration.');
return;
}
// Set drupal message to user about this event
$label = isset($form['groups'][$group_id]) ? $form['groups'][$group_id]['#title'] : $group_id;
$messages = $this->validationStrings($config, $list, ['label' => $label]);
// This group was un-checked by user in UI yet the user was previously subscribed to it so unsubscribe right away
if (!isset($member_groups_subscriptions[$group_id]) || empty($member_groups_subscriptions[$group_id])) {
\Drupal::service('messenger')->addWarning($messages['unsubscribed_warning']);
}
else {
// Get MC API
$api = static::getApi('api_lists');
// This is the "MC way" to do it
$parameters = [
'interests' => []
];
$parameters['interests'][$group_id] = FALSE;
if (is_object($api) && $unsubscribed = $api->updateMember($audiences, $email, $parameters)) {
\Drupal::service('messenger')->addStatus($messages['unsubscribed_message']);
// Log the same for admin
$log = $this->t('An existing subscriber @email un-subscribed from @labels MailChimp groups', ['@email' => $email, '@labels' => $label]);
\Drupal::service('logger.factory')->get('Diplo Mailchimp')->notice($log);
}
// Something went wrong, "blame" MC
else {
\Drupal::service('messenger')->addError($this->t('Remote service MailChimp returned an error, please try again later. If you are authenticated user check logs.'));
}
}
// DO NOT uncomment - this is example call to unsubscribe member from the list itself, we do not use it
// $unsubscribed = static::unsubscribeMember($list_id, $email);
}
/**
* Ajax callback - fetch member data from remote MC
*/
public function fetchMember(array &$form, FormStateInterface $form_state) {
/*
// This chunk of code goes when valuesCallback() is active and set on form elements
$trigger = $form_state->getTriggeringElement();
$parents = array_slice($trigger['#array_parents'], 0, -1);
$element = NestedArray::getValue($form, $parents);
return $element;
*/
$config = $form_state->get('subscribe_config');
$values = $form_state->getValues();
$trigger = $form_state->getTriggeringElement();
$email = $form_state->getValue(['fields', 'EMAIL']);
$audiences = $form_state->getValue('audiences');
$response = new AjaxResponse();
if ($audiences && $email) {
$member_fields = [];
$member_info = static::getMemberInfo($audiences, $email);
$form_state->set('member_info', (array)$member_info);
$member_fields = isset($member_info->merge_fields) ? (array) $member_info->merge_fields : [];
$member_groups = $form_state->get('member_groups') ? $form_state->get('member_groups') : [];
$data = [
'parent' => Html::getId('mailchimp-subscriptions-' . $config['block_id']),
'groups' => empty($member_groups) ? $member_groups : array_keys($member_groups),
'fields' => $member_fields
];
$response->addCommand(new InvokeCommand(NULL, 'fetchMemberCallback', [$data]));
}
return $response;
}
/**
* Ajax callback for form submit event
*/
public static function submitAjax(array $form, FormStateInterface $form_state) {
return $form;
}
/**
* Custom method - prepare and process messages strings
*/
protected function validationStrings(array &$config, object $list, array $group_info = []) {
$label = isset($group_info['label']) && !empty($group_info['label']) ? $group_info['label'] : $list->name;
// Unsubscribe user action success
if (isset($config['unsubscribe_message']) && isset($config['unsubscribe_message']['value'])) {
if (!empty($config['unsubscribe_message']['value']) ){
$unsubscribed_text = str_replace('@label', $label, $config['unsubscribe_message']['value']);
$unsubscribed_message = FilteredMarkup::create($unsubscribed_text);
}
else {
$unsubscribed_message = FilteredMarkup::create($this->t('You have successfully un-subscribed from <em>@label</em>. No further notifications will come.', ['@label' => $label]));
}
}
else {
$unsubscribed_message = FilteredMarkup::create($this->t('You have successfully un-subscribed from <em>@label</em>. No further notifications will come.', ['@label' => $label]));
}
// Unsubscribe warning (already subscribed)
if (isset($config['unsubscribed_message']) && isset($config['unsubscribed_message']['value'])) {
if (!empty($config['unsubscribed_message']['value'])) {
$unsubscribed_warning_text = str_replace('@label', $label, $config['unsubscribed_message']['value']);
$unsubscribed_warning = FilteredMarkup::create($unsubscribed_warning_text);
}
else {
$unsubscribed_warning = FilteredMarkup::create($this->t('You were previously un-subscribed to <em>@label</em> and no further action is needed.', ['@label' => $label]));
}
}
else {
$unsubscribed_warning = FilteredMarkup::create($this->t('You were previously un-subscribed to <em>@label</em> and no further action is needed.', ['@label' => $label]));
}
// Subscribed success message
if (isset($config['subscribe_message']) && isset($config['subscribe_message']['value']) && !empty($config['subscribe_message']['value'])) {
$subscribed_text = str_replace('@label', $label, $config['subscribe_message']['value']);
$success = FilteredMarkup::create($subscribed_text);
}
else {
$success = FilteredMarkup::create($this->t('Thank you! Yo have successfully subscribed to @label', ['@label' => $label]));
}
// Subscribe warning - user is previously subscribed
if (isset($config['subscribed_message']) && isset($config['subscribed_message']['value']) && !empty($config['subscribed_message']['value'])) {
$subscribed_warning_text = str_replace('@label', $label, $config['subscribed_message']['value']);
$subscribed_warning = FilteredMarkup::create($subscribed_warning_text);
}
else {
$subscribed_warning = FilteredMarkup::create($this->t('You were previously subscribed to <em>@label</em>.', ['@label' => $label]));
}
return [
'unsubscribed_message' => $unsubscribed_message,
'unsubscribed_warning' => $unsubscribed_warning,
'subscribed_warning' => $subscribed_warning,
'success' => $success,
];
}
}
/* \Drupal\[module]\Element */
Code
namespace Drupal\diplo_mailchimp\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Component\Utility\NestedArray;
use Drupal\diplo_mailchimp\DiploMailChimpTrait;
/**
* Provides a form element for extending MailChimp related custom data.
*
* Usage example:
* @code
* $form['mailchimp_data'] = [
* '#type' => 'diplo_mailchimp_related_data',
* '#title' => t('Related data'),
* '#default_value' => [
* 'audiences' => $audiences,
* 'groups' => [
* "$gid" => $gid
* ],
'fields' => [
$field_id => $field_name
],
* 'description' => $description
* ],
* ];
* @endcode
*
* @see \Drupal\Core\Render\Element\Checkboxes
* @see \Drupal\Core\Render\Element\Radios
* @see \Drupal\Core\Render\Element\Select
*
* @FormElement("diplo_mailchimp_related_data")
*/
class DiploMailchimpRelatedData extends FormElement {
use DiploMailChimpTrait;
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
$element = [
'#input' => TRUE,
'#process' => [
[$class, 'process'],
],
];
return $element;
}
/**
* Process this custom composite element
*/
public static function process(&$element, FormStateInterface $form_state, &$complete_form) {
// When invoking this element we may have a chance to place some custom data/variables that were sent "from invoker"
$context = $form_state->get('mailchimp_context');
// Pick current form_state values
$values = $form_state->getValues();
if (!empty($context) && isset($context['name'])) {
// This is a call from field formatter, very specific array
if (isset($values['fields']) && isset($values['fields'][$context['name']]) && isset($values['fields'][$context['name']]['settings_edit_form']) && isset($values['fields'][$context['name']]['settings_edit_form']['settings'])) {
if (isset($values['fields'][$context['name']]['settings_edit_form']['settings']['related_data']) && isset($values['fields'][$context['name']]['settings_edit_form']['settings']['related_data']['audiences'])) {
$values = $values['fields'][$context['name']]['settings_edit_form']['settings']['related_data'];
}
}
else {
if (isset($values['default_value_input'])) {
if (isset($values['default_value_input'][$context['name']]) && !empty($values['default_value_input'][$context['name']]) && isset($values['default_value_input'][$context['name']][0]['related_data'])) {
$values = $values['default_value_input'][$context['name']][0]['related_data'];
}
}
else {
if (isset($values[$context['name']]) && !empty($values[$context['name']]) && isset($values[$context['name']][0]['related_data'])) {
$values = $values[$context['name']][0]['related_data'];
}
}
}
}
$audiences = NULL;
// MailChimpa API key was entered, store it in mailchimp config
// Note, this way all the calls on diplo related data element run out of the box
$config = \Drupal::service('config.factory')->getEditable('diplo_mailchimp.settings');
$config_data = $config->getRawData();
$api_key = $config_data['api_key'];
if (!$api_key) {
$api_key = isset($values['api_key']) && !empty($values['api_key']) ? $values['api_key'] : NULL;
}
if ($api_key) {
// We want this exclusively for entity/node attached fields and NOT for Drupal blocks (those should be disabled or deleted in drupal when we do not want to show a form anymore
if (is_array($context) && isset($context['name']) && strpos($context['name'], 'field_') !== FALSE) {
$element['hide_form'] = [
'#type' => 'checkbox',
'#title' => t('Do not show subscription form'),
'#description' => t('Check this to exclude / do not show a form at all. <strong>Important:</strong> This shows only on node/content form because it is more than recommended that in case of a "block" you just delete or disable such block in Drupal, on the block config. '),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['hide_form']) ? $element['#default_value']['hide_form'] : NULL,
'#attributes' => [
'id' => 'hide-form-wrapper',
]
];
$date_field_value = NULL;
$date_object = NULL;
if (isset($values['date_field']) && !empty($values['date_field'])) {
$date_field_value = is_array($values['date_field']) ? implode(' ', array_values($values['date_field'])) : $values['date_field'];
}
else {
if (isset($element['#default_value']) && isset($element['#default_value']['date_field'])) {
$date_field_value = is_array($element['#default_value']['date_field']) ? implode(' ', array_values($element['#default_value']['date_field'])) : $element['#default_value']['date_field'];
}
}
$element['date_field'] = [
'#title' => t('Expiration date'),
'#description' => t('Select a date/time from which a form will not be shown anymore.'),
'#type' => 'datetime',
'#validated' => TRUE,
// '#date_date_element' =>
'#default_value' => is_array($element['#default_value']['date_field']) ? implode(' ', array_values($element['#default_value']['date_field'])) : $element['#default_value']['date_field'],
'#states' => [
'invisible' => [
':input[id="hide-form-wrapper"]' => ['checked' => TRUE],
],
],
];
$element['form_title'] = [
'#type' => 'textfield',
'#title' => t('Subscription form title'),
'#description' => t('Choose a specific title for this form.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['form_title']) ? $element['#default_value']['form_title'] : NULL,
];
}
$list_options = static::listsWidget();
$element['audiences'] = [
'#type' => 'radios',
'#title' => t('MailChimp Audience'),
//'#required' => TRUE,
'#description' => t('Select your Audience, it will retrieve available groups and render here. See how to find Audience ID < a href="https://mailchimp.com/help/find-audience-id/" target="blank_">here</a>'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['audiences']) ? $element['#default_value']['audiences'] : NULL,
'#options' => $list_options,
'#validated' => TRUE,
'#ajax' => [
//'method' => 'after',
'event' => 'change',
'callback' => __CLASS__ . '::fetch',
'effect' => 'fade',
'wrapper' => 'diplo-mailchimp-fetch-container',
'progress' => [
'type' => 'throbber',
'message' => t('Requesting groups from MailChimp...'),
],
],
];
$element['fetch_group'] = [
'#type' => 'container',
'#prefix' => '<div id="diplo-mailchimp-fetch-container">',
'#suffix' => '</div>',
];
$element['fetch_group']['groups'] = [
'#title' => t('MailChimp Interest Groups IDs'),
'#type' => 'checkboxes',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['groups']) && is_array($element['#default_value']['groups']) ? array_keys($element['#default_value']['groups']) : [],
//'#value_callback' => '\Drupal\diplo_mailchimp\Element\DiploMailchimpRelatedData::groupsValueCallback',
'#validated' => TRUE,
'#options' => [],
'#prefix' => '<div id="diplo-mailchimp-groups-container">',
'#suffix' => '</div>',
];
$element['fetch_group']['hide_groups'] =[
'#type' => 'checkbox',
'#title' => t('Do not show Groups in UI'),
'#description' => t('Checkboxes to select group will not be shown on the subscription form. Default values (group id for subscribing) will be taken from config above.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['hide_groups']) ? $element['#default_value']['hide_groups'] : NULL,
];
$element['fetch_group']['fields'] = [
'#type' => 'checkboxes',
'#title' => t('Available MailChimp fields'),
'#description' => t('The fields are automatically retrieved from remote MC API, in order as those are set there.'),
'#validated' => TRUE, // Essential
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['fields']) ? $element['#default_value']['fields'] : [],
'#options' => [],
];
if (isset($values['audiences']) && !empty($values['audiences'])) { // This is ajax on Main configuration
$audiences = $values['audiences'];
}
else if (isset($values['related_data']) && isset($values['related_data']['audiences'])) {
$audiences = !empty($values['related_data']['audiences']) ? $values['related_data']['audiences'] : NULL;
}
// This is the case when ajax change, called from a Block (create new)
else if (isset($values['settings']) && isset($values['settings']['related_data']) && isset($values['settings']['related_data']['audiences'])) {
$audiences = !empty($values['settings']['related_data']['audiences']) ? $values['settings']['related_data']['audiences'] : NULL;
}
// Default value, editing either existing main configuration form or block
else {
if (isset($element['#default_value']) && isset($element['#default_value']['audiences'])) {
$audiences = !empty($element['#default_value']['audiences']) ? $element['#default_value']['audiences'] : NULL;
}
}
}
$merge_vars = NULL;
$groups_options = [];
if ($audiences) {
$groups_options = static::groupsWidget($audiences);
if (!empty($groups_options)) {
$element['fetch_group']['groups']['#options'] = $groups_options;
}
$merge_vars = static::mergeVars([$audiences]);
$fields = static::getFields([$audiences], TRUE);
if (!empty($fields)) {
$fields_options = [];
foreach ($fields as $field_id => $field) {
$fields_options[$field_id] = $field['name'];
}
$element['fetch_group']['fields']['#options'] = $fields_options;
$element['fetch_group']['fields']['#after_build'][] = [__CLASS__ , 'processCheckboxes'];
}
}
$element['subscribe_button'] = [
'#type' => 'textfield',
'#title' => t('Subscribe button text'),
'#description' => t('Text for a subscribe button, can be anything of meaningful length.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['subscribe_button']) ? $element['#default_value']['subscribe_button'] : NULL,
];
$element['double_optin'] = [
'#type' => 'checkbox',
'#title' => t('Double optin'),
'#description' => t('Check this to have MailChimp sends confirmation email to a brand new subscriber. Un-check is useful developing as you can with any placeholder email see change in MC right away.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['double_optin']) ? $element['#default_value']['double_optin'] : NULL,
'#attributes' => [
'id' => 'unsubscribe_option',
]
];
// Zoom
if (\Drupal::service('module_handler')->moduleExists('zoomapi')) { // Make zoom feature optional (only if zoomapi module installed)
$zoomapi = \Drupal::config('zoomapi.settings');
if ($zoomapi->get('api_key') && $zoomapi->get('api_secret')) { // Double check that credentials were previously stored
$element['zoom'] = [
'#type' => 'fieldset',
'#title' => t('Zoom'),
'#prefix' => '<div id="diplo-mailchimp-fetch-zoom-container">',
'#suffix' => '</div>',
];
$element['zoom']['zoom_id'] = [
'#type' => 'textfield',
'#title' => t('Zoom ID'),
'#description' => t('Provide zoom meeting or webinar ID here. Can be something like this "94479752531" Note: <strong>make sure you do not leave any spaces</strong> between characters/numbers'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['zoom_id']) ? $element['#default_value']['zoom_id'] : NULL,
'#attributes' => [
'id' => 'zoom-id-value',
]
];
$element['zoom']['zoom_type'] = [
'#type' => 'select',
'#title' => t('Zoom type'),
'#description' => t('Select which type of zoom event you are referring to'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['zoom_type']) ? $element['#default_value']['zoom_type'] : NULL,
'#options' => [
//'' => t('- Select type -'),
'meetings' => t('Meeting'),
'webinars' => t('Webinar'),
],
'#empty_option' => t('- Select type -'),
'#ajax' => [
'event' => 'change',
'callback' => __CLASS__ . '::fetchZoom',
'effect' => 'fade',
'wrapper' => 'diplo-mailchimp-fetch-zoom-container',
'progress' => [
'type' => 'throbber',
'message' => t('Please stand by, validating Zoom event...'),
],
],
'#states' => [
'invisible' => [
':input[id="zoom-id-value"]' => ['value' => ''],
],
],
];
}
}
$element['description'] = [
'#base_type' => 'textarea',
'#type' => 'text_format',
'#title' => t('Description'),
'#description' => t('A short information about subscription.'),
'#format' => isset($element['#default_value']) && isset($element['#default_value']['description']) && isset($element['#default_value']['description']['format']) ? $element['#default_value']['description']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['description']) && isset($element['#default_value']['description']['value']) ? $element['#default_value']['description']['value'] : NULL,
];
$element['subscribe_message'] = [
'#base_type' => 'textfield',
'#type' => 'text_format',
'#title' => t('Override subscribe message'),
'#description' => t('A text/message to display for successful subscription.'),
'#maxlength' => '1024',
'#format' => isset($element['#default_value']) && isset($element['#default_value']['subscribe_message']) && isset($element['#default_value']['subscribe_message']['format']) ? $element['#default_value']['subscribe_message']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['subscribe_message']) && isset($element['#default_value']['subscribe_message']['value']) ? $element['#default_value']['subscribe_message']['value'] : NULL,
];
$element['subscribed_message'] = [
'#base_type' => 'textfield',
'#type' => 'text_format',
'#title' => t('Override already subscribed warning'),
'#description' => t('If user was previously subscribed to the same list this is information returned.'),
'#maxlength' => '1024',
'#format' => isset($element['#default_value']) && isset($element['#default_value']['subscribed_message']) && isset($element['#default_value']['subscribed_message']['format']) ? $element['#default_value']['subscribed_message']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['subscribed_message']) && isset($element['#default_value']['subscribed_message']['value']) ? $element['#default_value']['subscribed_message']['value'] : NULL,
];
$element['form_class'] =[
'#type' => 'textfield',
'#title' => t('Additional CSS class(es)'),
'#description' => t('This goes on the first parent of a "form" element. Just separate classes with space as in html.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['form_class']) ? $element['#default_value']['form_class'] : NULL,
];
$element['unsubscribe'] = [
'#type' => 'checkbox',
'#title' => t('Show unsubscribe option'),
'#description' => t('An additional radio for user to be able to unsubscribe from the mailing list.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribe']) ? $element['#default_value']['unsubscribe'] : NULL,
];
$element['unsubscribe_wrapper'] = [
'#type' => 'fieldset',
'#states' => [ // @see https://www.drupal.org/docs/8/api/form-api/conditional-form-fields
'invisible' => [
':input[name="field_mailchimp_subscriptions[0][related_data][unsubscribe]"]' => ['checked' => FALSE],
],
],
];
$element['unsubscribe_wrapper']['unsubscribe_label'] = [
'#base_type' => 'textfield',
'#type' => 'text_format',
'#title' => t('Override Unsubscribe note'),
'#description' => t('Override default unsubscribe note for unsubscribe checkbox'),
'#maxlength' => '1024',
'#format' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribe_label']) && isset($element['#default_value']['unsubscribe_label']['format']) ? $element['#default_value']['unsubscribe_label']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribe_label']) && isset($element['#default_value']['unsubscribe_label']['value']) ? $element['#default_value']['unsubscribe_label']['value'] : NULL,
];
$element['unsubscribe_wrapper']['unsubscribe_message'] = [
'#base_type' => 'textfield',
'#type' => 'text_format',
'#title' => t('Override unsubscribe message'),
'#description' => t('A text/message to display upon cancelling subscription (unsubscribed)'),
'#maxlength' => '1024',
'#format' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribe_message']) && isset($element['#default_value']['unsubscribe_message']['format']) ? $element['#default_value']['unsubscribe_message']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribe_message']) && isset($element['#default_value']['unsubscribe_message']['value']) ? $element['#default_value']['unsubscribe_message']['value'] : NULL,
];
$element['unsubscribe_wrapper']['unsubscribed_message'] = [
'#base_type' => 'textfield',
'#type' => 'text_format',
'#title' => t('Override already unsubscribed message'),
'#description' => t('A text/message to display to user who tries to unsubscribe yet already, previously, being unsubscribed.'),
'#maxlength' => '1024',
'#format' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribed_message']) && isset($element['#default_value']['unsubscribed_message']['format']) ? $element['#default_value']['unsubscribed_message']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['unsubscribed_message']) && isset($element['#default_value']['unsubscribed_message']['value']) ? $element['#default_value']['unsubscribed_message']['value'] : NULL,
];
$element['privacy'] =[
'#type' => 'checkbox',
'#title' => t('Show Privacy policy note'),
'#description' => t('Show checkbox for user to confirm reading privacy policy.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['privacy']) ? $element['#default_value']['privacy'] : NULL,
'#attributes' => [
'id' => 'privacy_option',
]
];
$element['privacy_wrapper'] = [
'#type' => 'fieldset',
'#states' => [ // @see https://www.drupal.org/docs/8/api/form-api/conditional-form-fields
'invisible' => [
':input[id="privacy_option"]' => ['checked' => FALSE],
],
],
];
$element['privacy_wrapper']['privacy_note'] = [
'#base_type' => 'textfield',
'#type' => 'text_format',
'#title' => t('Privacy policy note'),
'#description' => t('Set default Privacy policy note for accepting checkbox'),
'#format' => isset($element['#default_value']) && isset($element['#default_value']['privacy_note']) && isset($element['#default_value']['privacy_note']['format']) ? $element['#default_value']['privacy_note']['format'] : 'basic_html',
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['privacy_note']) && isset($element['#default_value']['privacy_note']['value']) ? $element['#default_value']['privacy_note']['value'] : NULL,
'#maxlength' => '1024',
'#states' => [ // @see https://www.drupal.org/docs/8/api/form-api/conditional-form-fields
'invisible' => [
':input[id="privacy_option"]' => ['checked' => FALSE],
],
],
];
$element['cancel'] = [
'#type' => 'checkbox',
'#title' => t('Show cancel button'),
'#description' => t('This does not make any sense for now, therefore disabled.'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['cancel']) ? $element['#default_value']['cancel'] : NULL,
'#disabled' => TRUE,
'#attributes' => [
'id' => 'cancel_button',
]
];
$element['developer_data'] = [
'#type' => 'details',
'#title' => t('Developer data'),
'#description' => t('Just some additional useful data returned from MailChimp.'),
];
$element['developer_data']['debug'] = [
'#type' => 'checkbox',
'#title' => t('Debug'),
//'#disabled' => TRUE,
'#description' => t('Check this if you want to prevent new subscriptions during testing or similar. <strong>Not ready yet.</strong>'),
'#default_value' => isset($element['#default_value']) && isset($element['#default_value']['debug']) ? $element['#default_value']['debug'] : NULL,
];
$element['developer_data']['list_id'] = [
'#type' => 'textfield',
'#title' => $audiences ? $audiences : NULL,
'#description' => t('Selected MailChimp Audience ID'),
'#default_value' => $audiences,
'#disabled' => TRUE
];
if (!empty($groups_options)) {
$element['developer_data']['groups_ids'] = [
'#type' => 'details',
'#title' => t('MailChimp Groups IDs'),
'#description' => t('Selected MailChimp Groups IDs'),
];
foreach ($groups_options as $group_key => $group_value) {
$element['developer_data']['groups_ids'][$group_key] = [
'#type' => 'textfield',
'#title' => $group_value,
'#default_value' => $group_key,
'#disabled' => TRUE
];
}
}
// Developer data part, just printing some MC API keys/values/variables
if (is_array($merge_vars) && !empty($merge_vars)) {
$vars = static::prepareVars($merge_vars);
$element['developer_data']['merge_vars'] = [
'#type' => 'details',
'#title' => t('MailChimp fields list'),
'#description' => t('Merge vars data retrieved from MailChimp'),
];
foreach ($vars as $tag_id => $var) {
$element['developer_data']['merge_vars'][$tag_id] = [
'#type' => 'textfield',
'#size' => 20,
'#title' => $var->name . ' ID',
'#default_value' => $tag_id,
'#disabled' => TRUE
];
}
}
return $element;
}
/**
* A custom after_build method for our checkboxes
*/
public static function processCheckboxes($element, FormStateInterface $form_state) {
if (isset($element['EMAIL']) && !empty($element['EMAIL'])) {
$element['EMAIL']['#attributes']['disabled'] = 'disabled'; //TRUE;
$element['EMAIL']['#checked'] = TRUE;
$element['EMAIL']['#return_value'] = 1; //'EMAIL';
$element['EMAIL']['#value'] = 1; //'EMAIL';
}
return $element;
}
/**
* Ajax callback, return refreshed group element
*/
public static function fetch(array &$form, FormStateInterface $form_state) {
$trigger = $form_state->getTriggeringElement();
$parents = array_slice($trigger['#array_parents'], 0, -2);
$parents[] = 'fetch_group';
$element = NestedArray::getValue($form, $parents);
return $element;
}
/**
* Ajax callback, validate zoom event
*/
public static function fetchZoom(array &$form, FormStateInterface $form_state) {
$values = $form_state->getvalues();
$trigger = $form_state->getTriggeringElement();
$parents = array_slice($trigger['#array_parents'], 0, -2);
$parents[] = 'zoom';
$field_name = $trigger['#parents'][0];
$element = NestedArray::getValue($form, $parents);
if (empty($values[$field_name])) {
return $element;
}
// This is a configuration on a Block plugin
if ($trigger['#parents'][0] == 'settings' && isset($values[$field_name]['related_data']) && isset($values[$field_name]['related_data']['zoom'])) {
$zoom_type = isset($values[$field_name]['related_data']['zoom']['zoom_type']) && !empty($values[$field_name]['related_data']['zoom']['zoom_type']) ? $values[$field_name]['related_data']['zoom']['zoom_type'] : NULL;
$zoom_id = isset($values[$field_name]['related_data']['zoom']['zoom_id']) && !empty($values[$field_name]['related_data']['zoom']['zoom_id']) ? $values[$field_name]['related_data']['zoom']['zoom_id'] : NULL;
// Validate zoom event (for exitance or expiration or any other errors)
if ($zoom_type && $zoom_id) {
static::validateZoom($zoom_type, $zoom_id);
}
}
else {
// Yet this is a field widget (i.e. node edit form)
if (isset($values[$field_name][0]['related_data']) && isset($values[$field_name][0]['related_data']['zoom'])) {
$zoom_type = isset($values[$field_name][0]['related_data']['zoom']['zoom_type']) && !empty($values[$field_name][0]['related_data']['zoom']['zoom_type']) ? $values[$field_name][0]['related_data']['zoom']['zoom_type'] : NULL;
$zoom_id = isset($values[$field_name][0]['related_data']['zoom']['zoom_id']) && !empty($values[$field_name][0]['related_data']['zoom']['zoom_id']) ? $values[$field_name][0]['related_data']['zoom']['zoom_id'] : NULL;
// Validate zoom event (for existance or expiration or any other errors)
if ($zoom_type && $zoom_id) {
static::validateZoom($zoom_type, $zoom_id);
}
}
}
return $element;
}
/**
* Custom method, // Validate zoom event (for existance or expiration or any other errors)
*/
public static function validateZoom($zoom_type, $zoom_id) {
$response = static::zoomCheckEvent($zoom_id, $zoom_type);
if (isset($response['success']) && !empty($response['success'])) {
\Drupal::messenger()->addStatus(t('Valid event <em>@topic</em>', [
'@topic' => $response['success']['topic']
]));
}
else if (isset($response['error']) && !empty($response['error'])) {
\Drupal::messenger()->addError($response['error']);
}
}
}
Videos
Site user
Video file
Developer
Video file