Web Push notifications with Push framework
Provides web push notifications for DANSE events. Implements Web Push library for PHP, DANSE and Push framework, tailored and tested - a single case scenario though - to work with PWA.
Provides web push notifications for DANSE events. Implements Web Push library for PHP, DANSE and Push framework, tailored and tested - a single case scenario though - to work with PWA.
Recently contributed to Drupal.org. Enables API connections with Swapcard, with a plugin manager for implementing its various callbacks. Swapcard is a modern web platform for managing all kind of events and related activities.
The module contains base module (API implementation) Swapcard as well as some sort of ready-to-use Swapcard Content module that creates and keeps in sync nodes of several possible content types in Drupal with source in Swapcard app. This includes mapping all default Swapcard fields as well as relations between entities, as it is in Swapcard web app. Additional Swapcard Content Media module adds possibility to sync various Swapcard images in our content.
Recently contributed to Drupal.org. Provides Drupal integration with the one of the most modern swiping/sliding libraries. Swiper is mobile first, layout and gestures wise, it provides great amount of options for designing your own widget and related events.
Four unique ways with two Field formatter plugins - Swiper images and Swiper markup as well as basic usage in Views with provided Swiper formatter style plugin. Includes go-over all the possible configurations, including a big number of Swiper's parameters and modules provided on config entity form from and info for site builders.
At least three different ways for using with Views, default one is covered in a previous video and here is two more: 1. Rendering swiper out of multiple value field (image in our example) from multiple nodes in sequential way; 2. Rendering swiper out of content view_mode with swiper field belonging to each of the items (i.e. nodes).
A demonstration of Solr integration within Drupal Search API ecosystem as well as front-end solutions on top of that.
/* JavaScript factory */
/* A sample from main front-end factory in jQuery-in-drupal */
var factory = {
getViewArgument: function (data) {
var currentView;
if (drupalSettings.views && drupalSettings.views.ajaxViews) {
$.each(drupalSettings.views.ajaxViews, function (view_id, params) {
if (data.view_id === params.view_name && data.display_id ===
params.view_display_id) {
currentView = params;
}
});
}
return currentView;
},
setViewArgument: function (value, data) {
if (drupalSettings.views && drupalSettings.views.ajaxViews) {
$.each(drupalSettings.views.ajaxViews, function (view_id, params) {
if (data.view_id === params.view_name && data.display_id ===
params.view_display_id) {
drupalSettings.views.ajaxViews[view_id].view_args = value;
}
});
}
},
processQuery: function (path) {
var queryString = window.location.search || '';
if (queryString !== '') {
queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/,
'');
if (queryString !== '') {
queryString = (/\?/.test(path) ? '&' : '?') + queryString;
}
}
return queryString;
},
isJson: function (input) {
input = typeof input !== 'string' ? JSON.stringify(input) : input;
try {
input = JSON.parse(input);
} catch (e) {
console.log(e);
return false;
}
if (typeof input === 'object' && input !== null) {
return true;
}
return false;
},
getViewContentHighlight: function (params, highlightClass, hasArgument,
hasQueryParam) {
if (params.view && params.view.view_dom_id) {
var view = $('.js-view-dom-id-' + params.view.view_dom_id);
view.find('.text-highlight').each(function () {
var highlightElement = $(this);
var highlightText = $(this).text() ? $(this).text().toUpperCase() :
null;
var argumentValues;
if (hasArgument) {
argumentValues = hasArgument.indexOf(' ') > 0 ? hasArgument.toUpperCase()
.split(' ') : [hasArgument.toUpperCase()];
} else {
if (hasQueryParam) {
argumentValues = hasQueryParam.indexOf(' ') > 0 ?
hasQueryParam.toUpperCase().split(' ') : [hasQueryParam.toUpperCase()];
} else {
argumentValues = ['all'];
}
}
if (highlightElement.length && argumentValues) {
$.each(argumentValues, function (i, argumentValue) {
if (highlightText.indexOf(argumentValue) > -1) {
highlightElement.addClass(highlightClass);
}
});
}
});
}
},
processViewSubtitle: function (element, parentWrapper, widgetType = 'text',
existing = {}, titleId = null) {
if (!titleId) {
titleId = element.data('title') ? 'diplo-ajax-filter-' + element.data(
'title').toLowerCase().replace(' ', '_') : null;
}
if (titleId) {
var titleValue;
if (widgetType === 'select') {
var option = element.val() ? element.find('option[value="' +
element.val() + '"]') : null;
if (option && option.length) {
titleValue = option.text();
}
} else {
titleValue = element.val();
}
if (titleValue && titleValue.length > 41) {
titleValue = titleValue.replace(/^(.{41}[^\s]*).*/, "$1") + '...';
}
if (existing.length) {
var currentValue = existing.find('.views-subtitle').text();
if (currentValue !== element.val()) {
existing.find('.views-subtitle').text(element.val());
}
} else {
var titleHtml;
if (element.hasClass('diplo-search-input')) {
titleHtml = '<div id="' + titleId +
'" class="animate bounceIn col-xs-12 pl-0 pr-0 mb-16">';
titleHtml += '<div class="pl-0 col-xs-11">';
titleHtml +=
'<span class="views-subtitle pl-8 pr-8 bg-highlight-orange text-white border">' +
titleValue;
titleHtml += '</span></div></div>';
} else {
titleHtml = '<div id="' + titleId +
'" class="animate bounceIn col-xs-12 pl-0 pr-0 mb-16">';
titleHtml += '<div class="pl-0 col-xs-11">' + element.data(
'title');
titleHtml +=
': <span class="views-subtitle pl-8 pr-8 bg-highlight-blue border">' +
titleValue + '</span></div></div>';
}
parentWrapper.append(titleHtml);
}
}
}
};
// Highlight for filters and on search page
Drupal.AjaxCommands.prototype.diploHighlight = function (ajax, response) {
var params = {
view: {
view_dom_id: response.view_dom_id,
}
};
factory.getViewContentHighlight(params, 'orange', response.view_arg[0]);
};
// Append Views filters values to view/listing title
Drupal.AjaxCommands.prototype.diploSubtitles = function (ajax, response) {
factory.processViewSubtitle($(this), $(response.append_block), 'search',
response.existing, 'search-term-title-append');
};
/* PHP Drupal way */
<?php
namespace Drupal\diplo_formatters\EventSubscriber;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\views\Ajax\ViewAjaxResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Custom EventSubscriber in Drupal/PHP.
*/
class AjaxResponseSubscriber implements EventSubscriberInterface {
/**
* Renders the ajax commands right before preparing the result.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The response event, which contains the possible AjaxResponse object.
*/
public function onResponse(FilterResponseEvent $event): void {
$response = $event->getResponse();
// Only alter views ajax responses.
if ($response instanceof ViewAjaxResponse) {
$commands = &$response->getCommands();
$view = $response->getView();
$filters = [];
if (!empty($view->getExposedInput())) {
$skip = ['content_identifier', 'single_filter'];
foreach ($view->getExposedInput() as $key => $value) {
if (!in_array($key, $skip) && !empty($value) && $value != 'All') {
$filters[$key] = $value;
}
}
}
if ($view->display_handler->hasPath()) {
$commands[] = [
'command' => 'diploAjaxPager',
'selector' => '.js-view-dom-id-' . $view->dom_id,
'view_dom_id' => $view->dom_id,
'view_arg' => $view->args,
'view_filters' => $filters,
'view_pager' => $view->pager->current_page,
'view_path' => '/' . $view->display_handler->getPath(),
];
}
$commands[] = [
'command' => 'diploHighlight',
'selector' => '.js-view-dom-id-' . $view->dom_id,
'view_dom_id' => $view->dom_id,
'view_arg' => $view->args
];
}
else if ($response instanceof AjaxResponse) {
$commands = &$response->getCommands();
$commands[] = [
'command' => 'diploAjax',
];
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [KernelEvents::RESPONSE => [['onResponse']]];
}
}
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 */
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 */
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']);
}
}
}
Basic stuff and Sources for now. To be continued...
Lenient
# Install Composer Lenient for being able to place non-compatible packages.
# @see https://github.com/mglaman/composer-drupal-lenient
composer require mglaman/composer-drupal-lenient
# To allow a package to have a lenient Drupal core version constraint, you must add it to: extra.drupal-lenient.allowed-list
composer config --merge --json extra.drupal-lenient.allowed-list '["drupal/token"]'
# Now you can require the module in question
composer require drupal/token:1.10.0
Rector
# Make sure you are at the project's root. Then, get and copy rector.php
composer require --dev palantirnet/drupal-rector
cp vendor/palantirnet/drupal-rector/rector.php .
# Run check, in this example for a particular module. Remove dry run when ready.
vendor/bin/rector process web/modules/contrib/token --dry-run