Skip to content
Snippets Groups Projects
Commit a8dbd82b authored by Jaap Jansma's avatar Jaap Jansma
Browse files

Issue #3393131: Implement Calculation functionality

parent 6929ba45
No related branches found
No related tags found
1 merge request!9Issue #3393131: Implement Calculation functionality
webform.element.form_processor_calculation:
version: VERSION
js:
js/webform.element.form_processor_calculation.js: {}
dependencies:
- core/jquery
- core/once
- webform/webform.announce
/**
* @file
* JavaScript behaviors for computed elements.
*/
(function ($, Drupal, once) {
'use strict';
Drupal.webform = Drupal.webform || {};
Drupal.webform.formProcessorCalculation = Drupal.webform.formProcessorCalculation || {};
Drupal.webform.formProcessorCalculation.delay = Drupal.webform.formProcessorCalculation.delay || 500;
var computedElements = [];
/**
* Initialize computed elements.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.webformFormProcessorCalculation = {
attach: function (context) {
// Find computed elements and build trigger selectors.
$(once('webform-form_processor_calculation', '.js-webform-form_processor_calculation', context)).each(function () {
// Get computed element and form.
var $element = $(this);
var $form = $element.closest('form');
// Get unique id for computed element based on the element name
// and form id.
var id = $form.attr('id') + '-' + $element.find('input[type="hidden"]').attr('name');
// Get elements that are used by the computed element.
var elementKeys = $(this).data('webform-element-keys').split(',');
if (!elementKeys) {
return;
}
// Get computed element trigger selectors.
var inputs = [];
$.each(elementKeys, function (i, key) {
// Exact input match.
inputs.push(':input[name="' + key + '"]');
// Sub inputs. (aka #tree)
inputs.push(':input[name^="' + key + '["]');
});
var triggers = inputs.join(',');
// Track computed elements.
computedElements.push({
id: id,
element: $element,
form: $form,
triggers: triggers
});
// Clear computed last values to ensure that a computed element is
// always re-computed on page load.
$element.attr('data-webform-form_processor_calculation-last', '');
});
// Initialize triggers for each computed element.
$.each(computedElements, function (index, computedElement) {
// Get trigger from the current context.
var $triggers = $(context).find(computedElement.triggers);
// Make sure current context has triggers.
if (!$triggers.length) {
return;
}
// Make sure triggers are within the computed element's form
// and only initialized once.
$triggers = $(once('webform-form_processor_calculation-triggers-' + computedElement.id, computedElement.form.find($triggers)));
// Double check that there are triggers which need to be initialized.
if (!$triggers.length) {
return;
}
computedElement.element.attr('data-webform-form_processor_calculation-last', $triggers.serialize());
initializeTriggers(computedElement.element, $triggers);
});
/**
* Initialize computed element triggers.
*
* @param {jQuery} $element
* An jQuery object containing the computed element.
* @param {jQuery} $triggers
* An jQuery object containing the computed element triggers.
*/
function initializeTriggers($element, $triggers) {
// Add event handler to computed element triggers.
$triggers.on('keyup change', queueUpdate);
// Add event handler to computed element tabledrag.
var $draggable = $triggers.closest('tr.draggable');
if ($draggable.length) {
$draggable.find('.tabledrag-handle').on('mouseup pointerup touchend',
queueUpdate);
}
// Queue an update to make sure trigger values are computed.
queueUpdate();
// Queue computed element updates using a timer.
var timer = null;
function queueUpdate() {
if (timer) {
window.clearTimeout(timer);
timer = null;
}
timer = window.setTimeout(triggerUpdate, Drupal.webform.formProcessorCalculation.delay);
}
function triggerUpdate() {
// Get computed element wrapper.
var $wrapper = $element.find('.js-webform-form_processor_calculation-wrapper');
// If computed element is loading, requeue the update and wait for
// the computed element to be updated.
if ($wrapper.hasClass('webform-form_processor_calculation-loading')) {
queueUpdate();
return;
}
// Prevent duplicate computations.
// @see Drupal.behaviors.formSingleSubmit
var formValues = $triggers.serialize();
var previousValues = $element.attr('data-webform-form_processor_calculation-last');
if (previousValues === formValues) {
return;
}
$element.attr('data-webform-form_processor_calculation-last', formValues);
// Add loading class to computed wrapper.
$wrapper.addClass('webform-form_processor_calculation-loading');
// Trigger computation.
$element.find('.js-form-submit').trigger('mousedown');
}
}
}
};
})(jQuery, Drupal, once);
......@@ -88,7 +88,8 @@ class Factory {
public function formProcessorFields($connection, $formprocessor) {
$connections = $this->core->getConnectors();
$call = $this->core->createCall($connection, 'FormProcessor', 'getfields', ['api_action' => $formprocessor], ['limit' => 0, 'cache' => $this->defaultCache]);
$reply = $this->core->executeCall($call);
$this->core->executeCall($call);
$reply = $call->getReply();
$fields = [];
if (is_array($reply) && isset($reply['values'])) {
$fields = $reply['values'];
......
......@@ -109,6 +109,7 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
'form_processor_current_contact' => 0,
'form_processor_background' => 0,
'form_processor_enable_validation' => 1,
'form_processor_enable_calculation' => 1,
];
}
......@@ -310,6 +311,16 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
1 => "Yes",
],
];
$form['additional']['params']['enable_calculation'] = [
'#type' => 'select',
'#title' => t('Enable Calculations by the Form Processor'),
'#description' => t('Only possible with CiviCRM Form Processor 2.x and later'),
'#default_value' => $this->configuration['form_processor_enable_calculation'],
'#options' => [
0 => "No",
1 => "Yes",
],
];
if (count($fpFields)) {
$form['additional']['form_processor_current_contact'] = [
'#type' => 'select',
......@@ -367,6 +378,7 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
}
$this->configuration['form_processor_enable_validation'] = $values['additional']['params']['enable_validation'];
$this->configuration['form_processor_require_default'] = $values['additional']['params']['require_default'];
$this->configuration['form_processor_enable_calculation'] = $values['additional']['params']['enable_calculation'];
$this->configuration['form_processor_current_contact'] = $values['form_processor_current_contact'];
if ($values['additional']['fields']['update_fields']) {
$builder = new FormProcessorWebformBuilder($this->getWebform());
......@@ -438,46 +450,10 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
if (!$form_state->isSubmitted()) {
$values = $this->formProcessorDefaultsDefault($params);
if (empty($values['is_error'])) {
if (is_array($values) && empty($values['is_error'])) {
unset($values['is_error']);
foreach ($values as $key => $value) {
$element =& WebformElementHelper::getElement($form, $key);
if ($element) {
$elementInstance = $this->webformElementManager->getElementInstance($element);
if ($elementInstance instanceof WebformElementAttachmentInterface) {
$fileDestination = 'private://cmrf_form_processor/' . Drupal::currentUser()->id() . '/' . $webform_submission->getWebform()->id().'/'.$key . '/';
if ($this->fileSystem->prepareDirectory($fileDestination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
if (is_string($value) && strlen($value)) {
$baseName = md5($this->fileSystem->basename($value));
$fileName = $this->fileSystem->createFilename($baseName, $fileDestination);
$uri = $this->fileSystem->saveData(file_get_contents($value), $fileName);
$file = File::create(['uri' => $uri]);
$file->setOwnerId(Drupal::currentUser()->id());
$file->setTemporary();
$mimeTypes = MimeTypes::getDefault();
$mimeType = $mimeTypes->guessMimeType($uri);
if ($mimeType) {
$file->setMimeType($mimeType);
$extensions = $mimeTypes->getExtensions($mimeType);
if (is_array($extensions) && count($extensions)) {
$file->setFilename($baseName.'.' . reset($extensions));
}
}
$file->save();
$element['#default_value'] = [$file->id()];
}
}
} elseif ($element['#type'] == 'datetime') {
try {
$element['#default_value'] = DrupalDateTime::createFromFormat(\DateTime::ATOM, $value);
} catch (\InvalidArgumentException $ex) {
// is it something else than a date ignore
}
}
elseif ($value !== NULL) {
$element['#default_value'] = $value;
}
}
$this->setDefaultValue($form, $form_state, $webform_submission, $key, $value);
}
}
else {
......@@ -486,6 +462,52 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
}
}
}
if (!empty($this->configuration['form_processor_enable_calculation'])) {
$element_keys = $this->formProcessorCalculationParams();
if (count($element_keys)) {
if ($form_state->isSubmitted()) {
$this->doCalculation($form, $form_state, $webform_submission);
}
$wrapperId = $form_state->getFormObject()
->getFormId() . '_cmrf_form_processor';
if (!isset($form['#prefix'])) {
$form['#prefix'] = '';
}
if (!isset($form['#suffix'])) {
$form['#suffix'] = '';
}
$form['#prefix'] .= '<div id="' . $wrapperId . '">';
$form['#suffix'] = '</div>' . $form['#suffix'];
$form['cmrf_form_processor_calculation'] = [
'#prefix' => '<div class="js-webform-form_processor_calculation" data-webform-element-keys="' . implode(',', $element_keys) . '">',
'#suffix' => '</div>',
'update' => [
'#type' => 'submit',
'#value' => t('Recalculate'),
'#ajax' => [
'wrapper' => $wrapperId,
'method' => 'replaceWith',
'progress' => [
'type' => 'throbber',
'message' => t('Calculating'),
],
],
// Disable validation, hide button, add submit button trigger class.
'#attributes' => [
'formnovalidate' => 'formnovalidate',
'class' => [
'use-ajax-submit',
'js-hide',
'js-webform-computed-submit',
],
],
'#name' => 'cmrf_form_processor_calculation_update_button',
],
'#attached' => ['library' => ['cmrf_form_processor/webform.element.form_processor_calculation']],
];
}
}
}
/**
......@@ -502,26 +524,47 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
* This method performs form submission validation.
*/
public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
if (empty($this->configuration['form_processor_enable_validation'])) {
return;
}
$params = $this->webformSubmissionToApiParams($webform_submission);
$call = $this->core->createCall(
$this->configuration['connection'],
'FormProcessorValidation',
$this->configuration['form_processor'],
$params,
[]
);
$this->core->executeCall($call);
if ($call->getStatus() == CallInterface::STATUS_DONE) {
foreach ($call->getReply() as $field => $msg) {
if ($field == 'is_error') {
continue;
if (!empty($this->configuration['form_processor_enable_validation'])) {
$call = $this->core->createCall(
$this->configuration['connection'],
'FormProcessorValidation',
$this->configuration['form_processor'],
$params,
[]
);
$this->core->executeCall($call);
if ($call->getStatus() == CallInterface::STATUS_DONE) {
foreach ($call->getReply() as $field => $msg) {
if ($field == 'is_error') {
continue;
}
$form_state->setErrorByName($field, $msg);
}
$form_state->setErrorByName($field, $msg);
}
}
if (empty($form_state->getErrors()) && !empty($this->configuration['form_processor_enable_calculation'])) {
$form_state->setRebuild();
}
}
protected function doCalculation(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
if (!empty($this->configuration['form_processor_enable_calculation'])) {
$params = $this->webformSubmissionToApiParams($webform_submission);
$call = $this->core->createCall(
$this->configuration['connection'],
'FormProcessorCalculation',
$this->configuration['form_processor'],
$params,
[]
);
$reply = $this->executeCall($call);
foreach ($reply as $field => $value) {
$this->setDefaultValue($form, $form_state, $webform_submission, $field, $value);
}
// Set the calculation value to be reused somewhere else.
$form_state->setTemporaryValue('cmrf_formprocessor_calculation', $reply);
}
}
/**
......@@ -581,6 +624,56 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
$this->core->executeCall($call);
}
private function setDefaultValue(array &$form, FormStateInterface $formState, WebformSubmissionInterface $webformSubmission, string $field, $value) {
$element =& WebformElementHelper::getElement($form, $field);
if ($element) {
$elementInstance = $this->webformElementManager->getElementInstance($element);
if ($elementInstance instanceof WebformElementAttachmentInterface) {
$fileDestination = 'private://cmrf_form_processor/' . Drupal::currentUser()->id() . '/' . $webformSubmission->getWebform()->id().'/'.$field . '/';
if ($this->fileSystem->prepareDirectory($fileDestination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
if (is_string($value) && strlen($value)) {
$baseName = md5($this->fileSystem->basename($value));
$fileName = $this->fileSystem->createFilename($baseName, $fileDestination);
$uri = $this->fileSystem->saveData(file_get_contents($value), $fileName);
$file = File::create(['uri' => $uri]);
$file->setOwnerId(Drupal::currentUser()->id());
$file->setTemporary();
$mimeTypes = MimeTypes::getDefault();
$mimeType = $mimeTypes->guessMimeType($uri);
if ($mimeType) {
$file->setMimeType($mimeType);
$extensions = $mimeTypes->getExtensions($mimeType);
if (is_array($extensions) && count($extensions)) {
$file->setFilename($baseName.'.' . reset($extensions));
}
}
$file->save();
$element['#default_value'] = [$file->id()];
$element['#value'] = $element['#default_value'];
$formState->setValue($field, $element['#default_value']);
$webformSubmission->setElementData($field, $element['#default_value']);
}
}
} elseif ($element['#type'] == 'datetime') {
try {
$value = DrupalDateTime::createFromFormat(\DateTime::ATOM, $value);
$element['#default_value'] = $value;
$element['#value'] = $value;
$formState->setValue($field, $value);
$webformSubmission->setElementData($field, $value);
} catch (\InvalidArgumentException $ex) {
// is it something else than a date ignore
}
}
elseif ($value !== NULL) {
$element['#default_value'] = $value;
$element['#value'] = $value;
$formState->setValue($field, $value);
$webformSubmission->setElementData($field, $value);
}
}
}
/**
* Map the titles from the values array.
*
......@@ -620,6 +713,25 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
}
}
/**
* Get the fields available upon which a calculation can be triggered.
*
* @return array
* The name of the inputs upon which a calculation can be triggered.
*/
private function formProcessorCalculationParams(): array {
$call = $this->core->createCall($this->configuration['connection'], 'FormProcessorCalculation', 'getfields', ['api_action' => $this->configuration['form_processor']], ['limit' => 0, 'cache' => $this->factory->getDefaultCache()], []);
$result = $this->executeCall($call);
if ($result['count'] == 0) {
return [];
}
else {
return array_map(function ($value) {
return $value['name'];
}, $result['values']);
}
}
/**
* Get the default values for the form processor.
*
......@@ -645,6 +757,7 @@ class FormProcessorWebformHandler extends WebformHandlerBase {
* The API parameters derived from the webform submission.
*/
private function webformSubmissionToApiParams(WebformSubmissionInterface $webform_submission): array {
$params = [];
$data = $webform_submission->getData();
$fields = $this->factory->formProcessorFields($this->configuration['connection'], $this->configuration['form_processor']);
if (empty($this->configuration['submission_settings'])) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment