PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / trunk
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder vtrunk
6.2.15 6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 All 197 releases
← All changes | app/Services/FluentConversational/Classes/Converter/Converter.php +80 -5 6.2.8 → trunk View file →
@@ -6,8 +6,9 @@
6 6
7 7 use FluentForm\App\Helpers\Helper;
8 8 use FluentForm\App\Modules\Payments\PaymentHelper;
9 9 use FluentForm\App\Modules\Component\Component;
10 +use FluentForm\App\Services\FormBuilder\AutocompleteTokens;
10 11 use FluentForm\App\Services\FormBuilder\RatingIcon;
11 12 use FluentForm\App\Services\FormBuilder\Components\DateTime;
12 13 use FluentForm\App\Modules\Form\FormFieldsParser;
13 14 use FluentForm\Framework\Helpers\ArrayHelper;
@@ -201,8 +202,16 @@
201 202
202 203 if (!$hasSaveAndResume) {
203 204 $item['attributes']['value'] = self::setDefaultValue(ArrayHelper::get($item, 'attributes.value', ''), $item, $form);;
204 205 }
206 + // Per-sub-field token, resolved exactly as the classic renderer
207 + // does, so both renderers agree on the same saved form.
208 + $item['attributes']['autocomplete'] = ArrayHelper::get(AutocompleteTokens::normalizeAttributes(['autocomplete' => AutocompleteTokens::forSubField(
209 + ArrayHelper::get($item, 'attributes.name'),
210 + ArrayHelper::get($field, 'attributes.autocomplete'),
211 + ArrayHelper::get($item, 'attributes.autocomplete')
212 + )]), 'autocomplete');
213 +
205 214 $question['fields'][] = wp_parse_args($itemQuestion, $item);
206 215 }
207 216 }
208 217 } elseif ('input_name' === $field['element']) {
@@ -232,8 +241,16 @@
232 241 }
233 242
234 243 $item['attributes']['value'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($item, 'attributes.value'), $form);
235 244
245 + // Per-sub-field token, resolved exactly as the classic renderer
246 + // does, so both renderers agree on the same saved form.
247 + $item['attributes']['autocomplete'] = ArrayHelper::get(AutocompleteTokens::normalizeAttributes(['autocomplete' => AutocompleteTokens::forSubField(
248 + ArrayHelper::get($item, 'attributes.name'),
249 + ArrayHelper::get($field, 'attributes.autocomplete'),
250 + ArrayHelper::get($item, 'attributes.autocomplete')
251 + )]), 'autocomplete');
252 +
236 253 $question['fields'][] = wp_parse_args($itemQuestion, $item);
237 254 }
238 255 }
239 256 } elseif ('input_text' === $field['element']) {
@@ -257,10 +274,17 @@
257 274
258 275 if ($isMultiple) {
259 276 $question['multiple'] = true;
260 277 $question['placeholder'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.placeholder', false), $form);
261 - $question['max_selection'] = ArrayHelper::get($field, 'settings.max_selection');
262 - $question['max_selection'] = $question['max_selection'] ? intval($question['max_selection']) : 0;
278 + $maxSelection = Helper::resolveMaxSelection($field);
279 + $question['max_selection'] = $maxSelection ? intval($maxSelection) : 0;
280 +
281 + // Resolved here so a form on the legacy setting — which has no
282 + // rule to read a message from — still has wording.
283 + $question['max_selection_message'] = Helper::getSelectionLimitMessage(
284 + ArrayHelper::get($field, 'settings.validation_rules', []),
285 + 'max_selection'
286 + );
263 287 }
264 288 } elseif ('select_country' === $field['element']) {
265 289 $countryComponent = new \FluentForm\App\Services\FormBuilder\Components\SelectCountry();
266 290 $field = $countryComponent->loadCountries($field);
@@ -288,9 +312,9 @@
288 312 $question['searchable'] = ArrayHelper::get($field, 'settings.enable_select_2');
289 313 } elseif ('dynamic_field' === $field['element']) {
290 314 $dynamicFetchValue = 'yes' == ArrayHelper::get($field, 'settings.dynamic_fetch');
291 315 if ($dynamicFetchValue) {
292 - $field = apply_filters('fluentform/dynamic_field_re_fetch_result_and_resolve_value', $field);
316 + $field = apply_filters('fluentform/dynamic_field_re_fetch_result_and_resolve_value', $field, $form->id);
293 317 $question['answer'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.value'), $form);
294 318 }
295 319 $type = ArrayHelper::get($field, 'settings.field_type', 'select');
296 320 if (in_array($type, ['checkbox', 'radio'])) {
@@ -728,9 +752,9 @@
728 752 }
729 753 }
730 754 } elseif ('payment_summary_component' === $field['element']) {
731 755 $question['title'] = self::getComponent()->replaceEditorSmartCodes(__('Payment Summary', 'fluentform'), $form);
732 - $question['emptyText'] = $field['settings']['cart_empty_text'];
756 + $question['emptyText'] = fluentform_sanitize_html(ArrayHelper::get($field, 'settings.cart_empty_text', ''));
733 757 } elseif ('net_promoter_score' === $field['element']) {
734 758 if (!ArrayHelper::exists($question, 'answer')) {
735 759 $question['answer'] = (int) $field['attributes']['value'];
736 760 }
@@ -871,9 +895,9 @@
871 895
872 896 public static function convertExistingForm($form)
873 897 {
874 898 $formFields = json_decode($form->form_fields, true);
875 - $fields = $formFields['fields'];
899 + $fields = static::flattenLayoutContainers(ArrayHelper::get($formFields, 'fields', []));
876 900 $formattedFields = [];
877 901
878 902 if (is_array($fields) && ! empty($fields)) {
879 903 foreach ($fields as $field) {
@@ -900,8 +924,58 @@
900 924 $formFields['fields'] = $formattedFields;
901 925
902 926 return json_encode($formFields);
903 927 }
928 +
929 + /**
930 + * Lift fields out of layout containers so they survive conversion.
931 + *
932 + * A conversational form is a flat sequence of questions, so `container` has
933 + * no equivalent and is not in fieldTypes(). Dropping it wholesale used to
934 + * take every input nested inside it as well — silent, irreversible data loss
935 + * on a one-click action, and column layouts are common.
936 + *
937 + * Only `columns` is unwrapped. Composite fields (input_name, address) keep
938 + * their sub-inputs under `fields` and have their own conversational type, so
939 + * they must stay whole. Nested containers are handled recursively.
940 + *
941 + * @param array $fields
942 + * @return array
943 + */
944 + protected static function flattenLayoutContainers($fields)
945 + {
946 + if (! is_array($fields)) {
947 + return [];
948 + }
949 +
950 + $flattened = [];
951 +
952 + foreach ($fields as $field) {
953 + if (! is_array($field)) {
954 + continue;
955 + }
956 +
957 + $columns = ArrayHelper::get($field, 'columns');
958 +
959 + if ('container' !== ArrayHelper::get($field, 'element') || ! is_array($columns)) {
960 + $flattened[] = $field;
961 +
962 + continue;
963 + }
964 +
965 + foreach ($columns as $column) {
966 + $columnFields = static::flattenLayoutContainers(
967 + ArrayHelper::get($column, 'fields', [])
968 + );
969 +
970 + foreach ($columnFields as $columnField) {
971 + $flattened[] = $columnField;
972 + }
973 + }
974 + }
975 +
976 + return $flattened;
977 + }
904 978
905 979 private static function buildBaseQuestion($field, $validationsRules, $form)
906 980 {
907 981 return [
@@ -910,8 +984,9 @@
910 984 'title' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.label'), $form),
911 985 'type' => ArrayHelper::get(static::fieldTypes(), $field['element']),
912 986 'ff_input_type' => ArrayHelper::get($field, 'element'),
913 987 'container_class' => ArrayHelper::get($field, 'settings.container_class'),
988 + 'autocomplete' => ArrayHelper::get(AutocompleteTokens::normalizeAttributes((array) ArrayHelper::get($field, 'attributes', [])), 'autocomplete'),
914 989 'placeholder' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.placeholder'), $form),
915 990 'maxLength' => ArrayHelper::get($field, 'attributes.maxlength'),
916 991 'required' => ArrayHelper::get($validationsRules, 'required.value'),
917 992 'requiredMsg' => ArrayHelper::get($validationsRules, 'required.message'),