Commit e9daa05b authored by Andreas Fernandez's avatar Andreas Fernandez Committed by Richard Haeser

[BUGFIX] FormEngine: Fix several validation issues in complex field types

The FormEngine field type `selectMutlipleSideBySide` is very special, as
it's a combination of four input and select fields. With the refactoring
done in #87324 an issue in the validation has been introduced which
caused the validation of the wrong field which contains no validation
rules. This patch uses the correct field to obtain the validation rules
from, now.

Another issue is that values of `selectCheckBox` never have been
validated as the necessary validation rules were missing. The rules are
now appended to the outermost div container. In the same run, minor
styling and markup changes were necessary.

Resolves: #93191
Related: #87324
Releases: master, 10.4
Change-Id: I7195213a768fa4814eb3ecd81953a500c77ede5d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67317Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: default avatarRichard Haeser <richard@richardhaeser.com>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: default avatarRichard Haeser <richard@richardhaeser.com>
parent e3ae1106
......@@ -339,7 +339,8 @@ select {
display: table-cell;
width: 100%;
.col-icon img {
.col-icon img,
.inline-icon img {
max-width: 2em;
max-height: 2em;
}
......
......@@ -11,7 +11,6 @@
* The TYPO3 project - inspiring people to share!
*/
import $ from 'jquery';
import FormEngine = require('TYPO3/CMS/Backend/FormEngine');
import FormEngineValidation = require('TYPO3/CMS/Backend/FormEngineValidation');
......@@ -117,6 +116,8 @@ export abstract class AbstractSortableSelectItems {
e.preventDefault();
const relatedFieldName = target.dataset.fieldname;
const relatedField = FormEngine.getFieldElement(relatedFieldName).get(0) as HTMLSelectElement;
const relatedAvailableValuesField = FormEngine.getFieldElement(relatedFieldName,'_avail').get(0) as HTMLSelectElement;
if (target.classList.contains('t3js-btn-moveoption-top')) {
AbstractSortableSelectItems.moveOptionToTop(fieldElement);
......@@ -129,14 +130,14 @@ export abstract class AbstractSortableSelectItems {
} else if (target.classList.contains('t3js-btn-removeoption')) {
AbstractSortableSelectItems.removeOption(
fieldElement,
<HTMLSelectElement>FormEngine.getFieldElement(relatedFieldName, '_avail').get(0),
relatedAvailableValuesField,
);
}
FormEngine.updateHiddenFieldValueFromSelect(fieldElement, FormEngine.getFieldElement(relatedFieldName).get(0));
FormEngine.updateHiddenFieldValueFromSelect(fieldElement, relatedField);
FormEngine.legacyFieldChangedCb();
FormEngineValidation.markFieldAsChanged($(fieldElement));
FormEngineValidation.validateField(fieldElement);
FormEngineValidation.markFieldAsChanged(relatedAvailableValuesField);
FormEngineValidation.validateField(relatedAvailableValuesField);
});
}
}
......@@ -162,7 +162,7 @@ class SelectCheckBoxElement extends AbstractFormElement
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = '<div class="formengine-field-item t3js-formengine-field-item" data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
......@@ -198,11 +198,11 @@ class SelectCheckBoxElement extends AbstractFormElement
. ($item['checked'] ? 'checked=checked ' : '')
. ($item['disabled'] ? 'disabled=disabled ' : '') . '>';
$tableRows[] = '</td>';
$tableRows[] = '<td class="col-icon">';
$tableRows[] = '<label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>';
$tableRows[] = '</td>';
$tableRows[] = '<td class="col-title">';
$tableRows[] = '<label class="label-block nowrap-disabled" for="' . $item['id'] . '">' . htmlspecialchars($this->appendValueToLabelInDebugMode($item['title'], $item['value']), ENT_COMPAT, 'UTF-8', false) . '</label>';
$tableRows[] = '<label class="label-block nowrap-disabled" for="' . $item['id'] . '">';
$tableRows[] = '<span class="inline-icon">' . $item['icon'] . '</span>';
$tableRows[] = htmlspecialchars($this->appendValueToLabelInDebugMode($item['title'], $item['value']), ENT_COMPAT, 'UTF-8', false);
$tableRows[] = '</label>';
$tableRows[] = '</td>';
$tableRows[] = '<td class="text-right">' . $item['help'] . '</td>';
$tableRows[] = '</tr>';
......@@ -233,7 +233,7 @@ class SelectCheckBoxElement extends AbstractFormElement
$html[] = '<th class="col-checkbox">';
$html[] = '<input type="checkbox" id="' . $checkboxId . '" class="t3js-toggle-checkboxes" data-bs-trigger="hover" data-bs-placement="right" data-title="' . $title . '" data-bs-toggle="tooltip" />';
$html[] = '</th>';
$html[] = '<th class="col-title" colspan="2"><label for="' . $checkboxId . '">' . $title . '</label></th>';
$html[] = '<th class="col-title"><label for="' . $checkboxId . '">' . $title . '</label></th>';
$html[] = '<th class="text-right">' . $resetGroupBtn . '</th>';
$html[] = '</tr>';
$html[] = '</thead>';
......
......@@ -130,9 +130,10 @@ define(['jquery',
}
if (isMultiple || isList) {
var $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
// If multiple values are not allowed, clear anything that is in the control already
if (!isMultiple) {
var $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
$fieldEl.find('option').each(function() {
$availableFieldEl
.find('option[value="' + $.escapeSelector($(this).attr('value')) + '"]')
......@@ -200,6 +201,7 @@ define(['jquery',
// execute the phpcode from $FormEngine->TBE_EDITOR_fieldChanged_func
FormEngine.legacyFieldChangedCb();
FormEngineValidation.markFieldAsChanged($originalFieldEl);
FormEngine.Validation.validateField($availableFieldEl);
}
} else {
......@@ -216,8 +218,6 @@ define(['jquery',
// Change the selected value
$fieldEl.val(value);
}
if (typeof FormEngine.Validation !== 'undefined' && typeof FormEngine.Validation.validate === 'function') {
FormEngine.Validation.validateField($fieldEl);
}
};
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Backend/FormEngineValidation"],(function(e,t,o,n,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AbstractSortableSelectItems=void 0,o=__importDefault(o);class i{constructor(){this.registerSortableEventHandler=e=>{const t=e.closest(".form-wizards-wrap").querySelector(".form-wizards-items-aside");null!==t&&t.addEventListener("click",t=>{let l;if(null===(l=t.target.closest(".t3js-btn-option")))return void(t.target.matches(".t3js-btn-option")&&(l=t.target));t.preventDefault();const s=l.dataset.fieldname;l.classList.contains("t3js-btn-moveoption-top")?i.moveOptionToTop(e):l.classList.contains("t3js-btn-moveoption-up")?i.moveOptionUp(e):l.classList.contains("t3js-btn-moveoption-down")?i.moveOptionDown(e):l.classList.contains("t3js-btn-moveoption-bottom")?i.moveOptionToBottom(e):l.classList.contains("t3js-btn-removeoption")&&i.removeOption(e,n.getFieldElement(s,"_avail").get(0)),n.updateHiddenFieldValueFromSelect(e,n.getFieldElement(s).get(0)),n.legacyFieldChangedCb(),r.markFieldAsChanged(o.default(e)),r.validateField(e)})}}static moveOptionToTop(e){Array.from(e.querySelectorAll(":checked")).reverse().forEach(t=>{e.insertBefore(t,e.firstElementChild)})}static moveOptionToBottom(e){e.querySelectorAll(":checked").forEach(t=>{e.insertBefore(t,null)})}static moveOptionUp(e){const t=Array.from(e.children),o=Array.from(e.querySelectorAll(":checked"));for(let n of o){if(0===t.indexOf(n)&&null===n.previousElementSibling)break;e.insertBefore(n,n.previousElementSibling)}}static moveOptionDown(e){const t=Array.from(e.children).reverse(),o=Array.from(e.querySelectorAll(":checked")).reverse();for(let n of o){if(0===t.indexOf(n)&&null===n.nextElementSibling)break;e.insertBefore(n,n.nextElementSibling.nextElementSibling)}}static removeOption(e,t){e.querySelectorAll(":checked").forEach(o=>{const n=t.querySelector('option[value="'+o.value+'"]');null!==n&&(n.classList.remove("hidden"),n.disabled=!1),e.removeChild(o)})}}t.AbstractSortableSelectItems=i}));
\ No newline at end of file
define(["require","exports","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Backend/FormEngineValidation"],(function(e,t,o,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AbstractSortableSelectItems=void 0;class r{constructor(){this.registerSortableEventHandler=e=>{const t=e.closest(".form-wizards-wrap").querySelector(".form-wizards-items-aside");null!==t&&t.addEventListener("click",t=>{let i;if(null===(i=t.target.closest(".t3js-btn-option")))return void(t.target.matches(".t3js-btn-option")&&(i=t.target));t.preventDefault();const l=i.dataset.fieldname,s=o.getFieldElement(l).get(0),a=o.getFieldElement(l,"_avail").get(0);i.classList.contains("t3js-btn-moveoption-top")?r.moveOptionToTop(e):i.classList.contains("t3js-btn-moveoption-up")?r.moveOptionUp(e):i.classList.contains("t3js-btn-moveoption-down")?r.moveOptionDown(e):i.classList.contains("t3js-btn-moveoption-bottom")?r.moveOptionToBottom(e):i.classList.contains("t3js-btn-removeoption")&&r.removeOption(e,a),o.updateHiddenFieldValueFromSelect(e,s),o.legacyFieldChangedCb(),n.markFieldAsChanged(a),n.validateField(a)})}}static moveOptionToTop(e){Array.from(e.querySelectorAll(":checked")).reverse().forEach(t=>{e.insertBefore(t,e.firstElementChild)})}static moveOptionToBottom(e){e.querySelectorAll(":checked").forEach(t=>{e.insertBefore(t,null)})}static moveOptionUp(e){const t=Array.from(e.children),o=Array.from(e.querySelectorAll(":checked"));for(let n of o){if(0===t.indexOf(n)&&null===n.previousElementSibling)break;e.insertBefore(n,n.previousElementSibling)}}static moveOptionDown(e){const t=Array.from(e.children).reverse(),o=Array.from(e.querySelectorAll(":checked")).reverse();for(let n of o){if(0===t.indexOf(n)&&null===n.nextElementSibling)break;e.insertBefore(n,n.nextElementSibling.nextElementSibling)}}static removeOption(e,t){e.querySelectorAll(":checked").forEach(o=>{const n=t.querySelector('option[value="'+o.value+'"]');null!==n&&(n.classList.remove("hidden"),n.disabled=!1),e.removeChild(o)})}}t.AbstractSortableSelectItems=r}));
\ No newline at end of file
......@@ -321,9 +321,12 @@ define([
$relatedField = $(document).find('[name="' + field.dataset.relatedfieldname + '"]');
if ($relatedField.length) {
selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
} else {
} else if (field instanceof HTMLSelectElement) {
selected = field.querySelectorAll('option:checked').length;
} else {
selected = field.querySelectorAll('input:checked').length;
}
if (typeof rule.minItems !== 'undefined') {
minItems = rule.minItems * 1;
if (!isNaN(minItems) && selected < minItems) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment