├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── services.yaml ├── contao ├── dca │ └── tl_form_field.php └── languages │ ├── de │ └── tl_form_field.php │ └── en │ └── tl_form_field.php ├── public └── conditionalformfields.js ├── src ├── ContaoManager │ └── Plugin.php ├── DependencyInjection │ └── Terminal42ConditionalformfieldsExtension.php ├── EventListener │ ├── ConditionValidationListener.php │ └── FormListener.php ├── FormHandler.php ├── Migration │ └── ConditionMigration.php └── Terminal42ConditionalformfieldsBundle.php └── translations ├── conditionalformfields.de.yaml └── conditionalformfields.en.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.{php,twig,yml}] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [*.{svg,min.css,min.js}] 17 | insert_final_newline = false 18 | 19 | [*.html5] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: terminal42 2 | ko_fi: terminal42 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tools/*/vendor 2 | /vendor 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 terminal42 gmbh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | contao-conditionalformfields 2 | ============================ 3 | 4 | Allows you to display a form field based on a condition which allows you to do something like "only display the field 5 | when value of field 'foo' is 'bar' and 'bla' is 'yes'". 6 | 7 | The condition is not entered directly at the form field, but a field set with start and end must be created. 8 | The condition can be entered in the start of field set. The field set can also be used to control several form 9 | fields in the view. 10 | 11 | ``` 12 | foo == 'bar' && bla == 'yes' 13 | ``` 14 | 15 | You can also check the array (e.g. multiple checkboxes or select menu): 16 | 17 | ``` 18 | in_array('bar', foo) 19 | ``` 20 | 21 | To validate a single checkbox simply compare its value: 22 | 23 | ``` 24 | foo == '1' 25 | ``` 26 | 27 | ### Note for Version 3 28 | The field names had a prefix `$` until version 3 - this is no longer necessary. 29 | When updating to version 3, the conditions are automatically adjusted. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"terminal42/contao-conditionalformfields", 3 | "description":"conditionalformfields extension for Contao Open Source CMS; Display form fields based on conditionis!", 4 | "keywords":["contao", "form", "conditions", "fields"], 5 | "type":"contao-bundle", 6 | "license":"MIT", 7 | "authors":[ 8 | { 9 | "name":"terminal42 gmbh", 10 | "homepage": "https://www.terminal42.ch" 11 | } 12 | ], 13 | "funding": [ 14 | { 15 | "type": "github", 16 | "url": "https://github.com/terminal42" 17 | }, 18 | { 19 | "type": "other", 20 | "url": "https://ko-fi.com/terminal42" 21 | } 22 | ], 23 | "support": { 24 | "issues": "https://github.com/terminal42/contao-conditionalformfields/issues", 25 | "source": "https://github.com/terminal42/contao-conditionalformfields" 26 | }, 27 | "require": { 28 | "php": "^7.4 || ^8.0", 29 | "ext-json": "*", 30 | "contao/core-bundle": "^4.13 || ^5.0", 31 | "doctrine/dbal": "^2.11 || ^3" 32 | }, 33 | "require-dev": { 34 | "contao/manager-plugin": "^2.0", 35 | "terminal42/contao-build-tools": "dev-main", 36 | "terminal42/contao-mp_forms": "^4.4 || ^5.0" 37 | }, 38 | "conflict": { 39 | "contao/manager-plugin": "<2.0 || >=3.0", 40 | "terminal42/contao-mp_forms": "<4.4" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Terminal42\\ConditionalformfieldsBundle\\": "src/" 45 | } 46 | }, 47 | "extra": { 48 | "contao-manager-plugin": "Terminal42\\ConditionalformfieldsBundle\\ContaoManager\\Plugin" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autoconfigure: true 4 | autowire: true 5 | 6 | Terminal42\ConditionalformfieldsBundle\: 7 | resource: ../src/ 8 | -------------------------------------------------------------------------------- /contao/dca/tl_form_field.php: -------------------------------------------------------------------------------- 1 | addField('isConditionalFormField', 'expert_legend', PaletteManipulator::POSITION_APPEND) 7 | ->applyToPalette('fieldsetStart', 'tl_form_field') 8 | ; 9 | 10 | $GLOBALS['TL_DCA']['tl_form_field']['palettes']['__selector__'][] = 'isConditionalFormField'; 11 | $GLOBALS['TL_DCA']['tl_form_field']['subpalettes']['isConditionalFormField'] = 'conditionalFormFieldCondition'; 12 | 13 | $GLOBALS['TL_DCA']['tl_form_field']['fields']['isConditionalFormField'] = [ 14 | 'exclude' => true, 15 | 'inputType' => 'checkbox', 16 | 'eval' => ['submitOnChange' => true, 'tl_class' => 'clr'], 17 | 'sql' => "char(1) NOT NULL default ''", 18 | ]; 19 | 20 | $GLOBALS['TL_DCA']['tl_form_field']['fields']['conditionalFormFieldCondition'] = [ 21 | 'exclude' => true, 22 | 'inputType' => 'textarea', 23 | 'eval' => ['mandatory' => true, 'useRawRequestData' => true, 'style' => 'height:40px', 'tl_class' => 'clr'], 24 | 'sql' => 'text NULL', 25 | ]; 26 | -------------------------------------------------------------------------------- /contao/languages/de/tl_form_field.php: -------------------------------------------------------------------------------- 1 | (anderesfeld == \'de\' && nocheinweiteres == \'test\') oder für Arrays in_array(\'en\', languages)']; 5 | -------------------------------------------------------------------------------- /contao/languages/en/tl_form_field.php: -------------------------------------------------------------------------------- 1 | otherfield == \'de\' && anotherone == \'test\') or for arrays in_array(\'en\', languages)']; 5 | -------------------------------------------------------------------------------- /public/conditionalformfields.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | const initialized = []; 5 | 6 | function init (node) { 7 | node.querySelectorAll('fieldset[data-cff-condition]').forEach(function (el) { 8 | if (initialized.includes(el)) { 9 | return; 10 | } 11 | 12 | initialized.push(el); 13 | 14 | const form = el.form; 15 | const condition = el.getAttribute('data-cff-condition'); 16 | 17 | Array.from(form.elements).forEach(function (control) { 18 | control.addEventListener('change', function () { 19 | toggleFieldset(el, condition, getFormData(form)); 20 | }); 21 | }) 22 | 23 | toggleFieldset(el, condition, getFormData(form)); 24 | }); 25 | } 26 | 27 | function toggleFieldset (fieldset, condition, formData) { 28 | let fnBody = '"use strict";\n\n'; 29 | fnBody += 'function in_array (needle, haystack) { return !!Object.values(haystack).find(v => v == needle) }\n'; 30 | fnBody += 'function str_contains (haystack, needle) { return String(haystack).includes(needle) }\n\n' 31 | 32 | formData.forEach(function (value, key) { 33 | 34 | if (/^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)[$A-Z_a-z][$A-Z_a-z0-9]*$/.test(key)) { 35 | fnBody += `const ${key} = values.get('${key}');\n`; 36 | } else { 37 | console.warn(`terminal42/contao-conditionalformfields: skipping "${key}", this name is not supported in JavaScript variables.`); 38 | } 39 | }); 40 | 41 | fnBody += `\nreturn ${condition};`; 42 | let fn = new Function('values', fnBody); 43 | 44 | if (fn.call(undefined, formData)) { 45 | fieldset.disabled = false 46 | fieldset.style.display = ''; 47 | } else { 48 | fieldset.disabled = true 49 | fieldset.style.display = 'none'; 50 | } 51 | } 52 | 53 | function getFormData (form) { 54 | const data = new Map(); 55 | const formData = new FormData(form); 56 | 57 | if (form.hasAttribute('data-cff-previous')) { 58 | const previous = JSON.parse(form.getAttribute('data-cff-previous')); 59 | Object.keys(previous).forEach(function (key) { 60 | data.set(key, previous[key]); 61 | }); 62 | } 63 | 64 | for (let { 0: name, 1: value } of formData) { 65 | // Array 66 | if (name.substring(name.length - 2) === '[]') { 67 | name = name.substring(0, name.length - 2); 68 | 69 | if (!(data.get(name) instanceof Array)) { 70 | data.set(name, []); 71 | } 72 | 73 | data.get(name).push(value); 74 | } else { 75 | data.set(name, value); 76 | } 77 | } 78 | 79 | // Initialize empty values (e.g. no radio option selected) 80 | Array.from(form.elements).forEach(function (control) { 81 | if (!control.name) { 82 | return; 83 | } 84 | 85 | let name = control.name; 86 | let value = ''; 87 | 88 | if (name.substring(name.length - 2) === '[]') { 89 | name = name.substring(0, name.length - 2); 90 | value = []; 91 | } 92 | 93 | if (!data.has(name)) { 94 | data.set(control.name, value); 95 | } 96 | }); 97 | 98 | // Convert arrays to temporary objects to enforce in_array check 99 | data.forEach(function (value, key) { 100 | if (Array.isArray(value)) { 101 | data.set(key, Object.fromEntries(value.entries())); 102 | } 103 | }); 104 | 105 | return data; 106 | } 107 | 108 | function load () { 109 | init(document); 110 | new MutationObserver(function (mutationsList) { 111 | for (const mutation of mutationsList) { 112 | if (mutation.type === 'childList') { 113 | mutation.addedNodes.forEach(function (element) { 114 | if (element.querySelectorAll) { 115 | init(element) 116 | } 117 | }) 118 | } 119 | } 120 | }).observe(document, { 121 | attributes: false, 122 | childList: true, 123 | subtree: true 124 | }); 125 | } 126 | 127 | if (document.readyState === 'loading') { 128 | document.addEventListener('DOMContentLoaded', load); 129 | } else { 130 | load(); 131 | } 132 | })(); 133 | -------------------------------------------------------------------------------- /src/ContaoManager/Plugin.php: -------------------------------------------------------------------------------- 1 | setLoadAfter([ContaoCoreBundle::class]), 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/DependencyInjection/Terminal42ConditionalformfieldsExtension.php: -------------------------------------------------------------------------------- 1 | load('services.yaml'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EventListener/ConditionValidationListener.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 26 | $this->translator = $translator; 27 | } 28 | 29 | public function __invoke(string $expression, DataContainer $dc): string 30 | { 31 | if (empty($expression)) { 32 | return $expression; 33 | } 34 | 35 | // Replace old syntax where variables started with $ 36 | $expression = preg_replace('{(^|[^\'"])\$([a-z0-9_]+)}i', '$1$2', $expression); 37 | 38 | /** @var array $tokens */ 39 | $tokens = []; 40 | 41 | $tokenStream = (new Lexer())->tokenize($expression); 42 | 43 | while (!$tokenStream->isEOF()) { 44 | $tokens[] = $tokenStream->current; 45 | $tokenStream->next(); 46 | } 47 | 48 | $variables = []; 49 | 50 | /** @noinspection ForeachInvariantsInspection */ 51 | for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { 52 | if ( 53 | Token::OPERATOR_TYPE === $tokens[$i]->type 54 | && !\in_array($tokens[$i]->value, ['&&', '||', '!', '==', '!=', '<', '>', '<=', '>='], true) 55 | ) { 56 | $this->error('operand', $tokens[$i]->value, $tokens[$i]->cursor); 57 | } 58 | 59 | if (!$tokens[$i]->test(Token::NAME_TYPE)) { 60 | continue; 61 | } 62 | 63 | $value = $tokens[$i]->value; 64 | 65 | // Skip constant nodes (see Symfony/Component/ExpressionLanguage/Parser#parsePrimaryExpression() 66 | if (\in_array($value, ['true', 'TRUE', 'false', 'FALSE', 'null', 'NULL'], true)) { 67 | continue; 68 | } 69 | 70 | // Validate functions 71 | if (isset($tokens[$i + 1]) && '(' === $tokens[$i + 1]->value) { 72 | if (!\in_array($tokens[$i]->value, ['in_array', 'str_contains'], true)) { 73 | $this->error('function', $tokens[$i]->value, $tokens[$i]->cursor); 74 | } 75 | 76 | ++$i; 77 | 78 | continue; 79 | } 80 | 81 | if (!isset($variables[$value])) { 82 | $variables[$value] = $tokens[$i]->cursor; 83 | } 84 | } 85 | 86 | if (!$dc->activeRecord->pid) { 87 | return $expression; 88 | } 89 | 90 | /** @noinspection SqlNoDataSourceInspection */ 91 | $fieldNames = $this->connection->fetchFirstColumn("SELECT name FROM tl_form_field WHERE pid=? AND invisible='' AND name!=''", [$dc->activeRecord->pid]); 92 | $unknown = array_keys(array_diff_key($variables, array_flip($fieldNames))); 93 | 94 | if (!empty($unknown)) { 95 | $unknown = $unknown[0]; 96 | 97 | $this->error('field', $unknown, $variables[$unknown]); 98 | } 99 | 100 | foreach ($variables as $variable => $position) { 101 | if (!preg_match('/^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)[$A-Z_a-z][$A-Z_a-z0-9]*$/', $variable)) { 102 | $this->error('variable', $variable, $position); 103 | } 104 | } 105 | 106 | return $expression; 107 | } 108 | 109 | private function error(string $id, string $value, int $cursor): void 110 | { 111 | $message = $this->translator->trans($id, ['{value}' => $value, '{cursor}' => $cursor], 'conditionalformfields'); 112 | 113 | throw new \RuntimeException($message); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/EventListener/FormListener.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | private array $handlers = []; 28 | 29 | public function __construct(RequestStack $requestStack, ScopeMatcher $scopeMatcher, ?FormManagerFactoryInterface $formManagerFactory = null) 30 | { 31 | $this->requestStack = $requestStack; 32 | $this->scopeMatcher = $scopeMatcher; 33 | $this->formManagerFactory = $formManagerFactory; 34 | } 35 | 36 | /** 37 | * @Hook("compileFormFields") 38 | */ 39 | public function onCompileFormFields(array $fields, string $formId, Form $form): array 40 | { 41 | // mp_forms is calling the "compileFormFields" hook in the back end 42 | if (!($request = $this->requestStack->getCurrentRequest()) || $this->scopeMatcher->isBackendRequest($request)) { 43 | return $fields; 44 | } 45 | 46 | if (!$this->hasConditions($fields)) { 47 | return $fields; 48 | } 49 | 50 | if (!isset($this->handlers[$formId])) { 51 | $this->handlers[$formId] = new FormHandler($form, $fields, $this->formManagerFactory); 52 | } 53 | 54 | $this->handlers[$formId]->init($form); 55 | 56 | return $fields; 57 | } 58 | 59 | /** 60 | * @Hook("loadFormField") 61 | */ 62 | public function onLoadFormField(Widget $widget, string $formId): Widget 63 | { 64 | if (isset($this->handlers[$formId])) { 65 | $this->handlers[$formId]->prepareField($widget); 66 | } 67 | 68 | return $widget; 69 | } 70 | 71 | /** 72 | * @Hook("validateFormField") 73 | */ 74 | public function onValidateFormField(Widget $widget, string $formId): Widget 75 | { 76 | if (isset($this->handlers[$formId])) { 77 | $this->handlers[$formId]->validateField($widget); 78 | } 79 | 80 | return $widget; 81 | } 82 | 83 | /** 84 | * @param array $fields 85 | */ 86 | private function hasConditions(array $fields): bool 87 | { 88 | foreach ($fields as $field) { 89 | if ('fieldsetStart' === $field->type && $field->isConditionalFormField) { 90 | return true; 91 | } 92 | } 93 | 94 | return false; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/FormHandler.php: -------------------------------------------------------------------------------- 1 | $fields 33 | */ 34 | public function __construct(Form $form, array $fields, ?FormManagerFactoryInterface $formManagerFactory = null) 35 | { 36 | $this->form = $form; 37 | $this->formManagerFactory = $formManagerFactory; 38 | 39 | $this->expressionLanguage = new ExpressionLanguage(); 40 | $this->expressionLanguage->addFunction( 41 | new ExpressionFunction( 42 | 'in_array', 43 | static fn ($needle, $haystack) => sprintf('\in_array(%s, (array) %s, false))', $needle, $haystack), 44 | static fn ($arguments, $needle, $haystack) => \in_array($needle, (array) $haystack, false), 45 | ), 46 | ); 47 | $this->expressionLanguage->addFunction(ExpressionFunction::fromPhp('str_contains')); 48 | 49 | $conditions = []; 50 | $fieldsets = []; 51 | 52 | foreach ($fields as $field) { 53 | if ('fieldsetStart' === $field->type) { 54 | $fieldsets[] = $field->id; 55 | 56 | if ($field->isConditionalFormField) { 57 | $conditions[] = $field->id; 58 | $this->conditions[$field->id] = $this->createCondition($field->conditionalFormFieldCondition); 59 | } 60 | 61 | continue; 62 | } 63 | 64 | if ('fieldsetStop' === $field->type) { 65 | // If the current condition is equal to the current entry of all fieldsets, close the "condition" fieldset 66 | if (array_pop($fieldsets) === end($conditions)) { 67 | array_pop($conditions); 68 | } 69 | continue; 70 | } 71 | 72 | $this->fields[(string) $field->id] = $conditions; 73 | 74 | if (!empty($field->name)) { 75 | $this->formData[$field->name] = $this->getInput($field->name); 76 | } 77 | } 78 | 79 | if (!empty($this->conditions)) { 80 | $GLOBALS['TL_JAVASCRIPT'][] = 'bundles/terminal42conditionalformfields/conditionalformfields.js'; 81 | } 82 | } 83 | 84 | public function init(Form $form): void 85 | { 86 | // Add CSS class for current form 87 | $formAttributes = StringUtil::deserialize($form->attributes, true); 88 | $formAttributes[1] = trim(($formAttributes[1] ?? '').' cff'); 89 | 90 | if (!empty($previousData = $this->getPreviousDataFromMpForms())) { 91 | $formAttributes[1] .= '" data-cff-previous="'.StringUtil::specialcharsAttribute(json_encode($previousData, JSON_THROW_ON_ERROR)); 92 | } 93 | 94 | $form->attributes = $formAttributes; 95 | } 96 | 97 | public function prepareField(Widget $widget): void 98 | { 99 | // Add a CSS class to conditional fieldset, so we can find and trigger them through JS 100 | if ($widget instanceof FormFieldsetStart && $widget->isConditionalFormField) { 101 | $widget->class = '" data-cff-condition="'.StringUtil::specialcharsAttribute($widget->conditionalFormFieldCondition); 102 | } 103 | } 104 | 105 | public function validateField(Widget $widget): void 106 | { 107 | // At this stage, widgets are already validated by the Form class 108 | // The mandatory (or any other restriction such as rgxp) settings are thus 109 | // already checked for fields that are conditional. We thus reset the 110 | // errors to none on them (ugly with reflection but there's no setter 111 | // on the Widget class so...) 112 | 113 | if ($this->isHidden((string) $widget->id)) { 114 | $reflection = new \ReflectionClass($widget); 115 | 116 | $errors = $reflection->getProperty('arrErrors'); 117 | $errors->setAccessible(true); 118 | $errors->setValue($widget, []); 119 | 120 | $class = $reflection->getProperty('strClass'); 121 | $class->setAccessible(true); 122 | $class->setValue($widget, preg_replace('{(^| )error( |$)}', '', (string) $widget->class)); 123 | 124 | // Widget must not submit their input if they are hidden 125 | // We previously used "disabled = true" (see #18) but that will result in the 126 | // field being disabled on subsequent run, since v3 only toggles the fieldset. 127 | $submitInput = $reflection->getProperty('blnSubmitInput'); 128 | $submitInput->setAccessible(true); 129 | $submitInput->setValue($widget, false); 130 | 131 | $widget->value = null; 132 | } 133 | } 134 | 135 | private function isHidden(string $fieldId): bool 136 | { 137 | if (empty($this->fields[$fieldId])) { 138 | return false; 139 | } 140 | 141 | foreach ($this->fields[$fieldId] as $fieldset) { 142 | if (!isset($this->conditions[$fieldset])) { 143 | continue; 144 | } 145 | 146 | // Lazy-evaluate conditions 147 | if (\is_callable($this->conditions[$fieldset])) { 148 | $this->conditions[$fieldset] = $this->conditions[$fieldset](); 149 | } 150 | 151 | // If condition does not match, the field is hidden 152 | if (!$this->conditions[$fieldset]) { 153 | return true; 154 | } 155 | } 156 | 157 | return false; 158 | } 159 | 160 | /** 161 | * @return \Closure|true 162 | */ 163 | private function createCondition(?string $condition) 164 | { 165 | if (empty($condition)) { 166 | return true; 167 | } 168 | 169 | return fn () => (bool) $this->expressionLanguage->evaluate($condition, $this->formData); 170 | } 171 | 172 | /** 173 | * @return array|string|null 174 | */ 175 | private function getInput(string $fieldName) 176 | { 177 | $value = 'get' === $this->form->method ? Input::get($fieldName, false, true) : Input::post($fieldName); 178 | 179 | if (null !== $value) { 180 | return $value; 181 | } 182 | 183 | $previousStepsData = $this->getPreviousDataFromMpForms(); 184 | 185 | if (isset($previousStepsData[$fieldName])) { 186 | return $previousStepsData[$fieldName]; 187 | } 188 | 189 | return null; 190 | } 191 | 192 | private function getPreviousDataFromMpForms(): array 193 | { 194 | // MP Forms v5 195 | if (null !== $this->formManagerFactory) { 196 | $manager = $this->formManagerFactory->forFormId((int) $this->form->id); 197 | 198 | if ($manager->isPreparing()) { 199 | return []; 200 | } 201 | 202 | $previousStepsData = $manager->getDataOfAllSteps(); 203 | 204 | return $previousStepsData->getAllSubmitted(); 205 | } 206 | 207 | // MP Forms v4 208 | if (class_exists(\MPFormsSessionManager::class)) { 209 | $manager = new \MPFormsSessionManager($this->form->id); 210 | $previousStepsData = $manager->getDataOfAllSteps(); 211 | 212 | return $previousStepsData['submitted']; 213 | } 214 | 215 | return []; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/Migration/ConditionMigration.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 18 | } 19 | 20 | public function shouldRun(): bool 21 | { 22 | $schemaManager = $this->connection->getSchemaManager(); 23 | 24 | if ( 25 | null === $schemaManager 26 | || !$schemaManager->tablesExist('tl_form_field') 27 | || !\array_key_exists('conditionalformfieldcondition', $schemaManager->listTableColumns('tl_form_field')) 28 | ) { 29 | return false; 30 | } 31 | 32 | return 33 | $this->connection->fetchOne( 34 | "SELECT COUNT(*) FROM tl_form_field WHERE conditionalFormFieldCondition REGEXP '(^|[^\\'\"])[$]([a-z0-9_]+)'", 35 | ) > 0; 36 | } 37 | 38 | public function run(): MigrationResult 39 | { 40 | $fields = $this->connection->fetchAllAssociative( 41 | "SELECT id, conditionalFormFieldCondition FROM tl_form_field WHERE conditionalFormFieldCondition REGEXP '(^|[^\\'\"])[$]([a-z0-9_]+)'", 42 | ); 43 | 44 | foreach ($fields as $field) { 45 | $expression = preg_replace('{(^|[^\'"])\$([a-z0-9_]+)}i', '$1$2', $field['conditionalFormFieldCondition']); 46 | 47 | $this->connection->update( 48 | 'tl_form_field', 49 | ['conditionalFormFieldCondition' => $expression], 50 | ['id' => $field['id']], 51 | ); 52 | } 53 | 54 | return $this->createResult(true, sprintf('Updated %s form field conditions', \count($fields))); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Terminal42ConditionalformfieldsBundle.php: -------------------------------------------------------------------------------- 1 |