├── .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 |