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']);
}
}
}