PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.2
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 3.6.65 All 196 releases
fluentform / app / Services / FluentConversational / Classes / Converter / Converter.php

Converter.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.2, at app/Services/FluentConversational/Classes/Converter/Converter.php

1,400 lines 67.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Services\FluentConversational\Classes\Converter;
4
5 defined('ABSPATH') or die;
6
7 use FluentForm\App\Helpers\Helper;
8 use FluentForm\App\Modules\Payments\PaymentHelper;
9 use FluentForm\Framework\Helpers\ArrayHelper;
10 use FluentForm\App\Modules\Component\Component;
11 use FluentForm\App\Services\FormBuilder\Components\DateTime;
12 use FluentForm\App\Modules\Form\FormFieldsParser;
13
14 class Converter
15 {
16 /**
17 * Get Component instance
18 */
19 private static function getComponent()
20 {
21 static $component = null;
22 if ($component === null) {
23 $component = new Component(wpFluentForm());
24 }
25 return $component;
26 }
27
28 public static function convert($form)
29 {
30 // Return early if form is null or invalid
31 if (!$form || !is_object($form)) {
32 return $form;
33 }
34
35 $fields = $form->fields['fields'];
36
37 $form->submit_button = $form->fields['submitButton'];
38
39 $form->reCaptcha = false;
40 $form->hCaptcha = false;
41 $form->turnstile = false;
42 $form->hasCalculation = false;
43
44 $questions = [];
45
46 $imagePreloads = [];
47
48 $allowedFields = static::fieldTypes();
49
50 $hasSaveAndResume = static::hasSaveAndResume($form);
51 $saveAndResumeData = [];
52
53 if ($hasSaveAndResume) {
54 $saveAndResumeData = static::getSaveAndResumeData($form);
55 $form->stepCompleted = intval(ArrayHelper::get($saveAndResumeData, 'step_completed', 0));
56 }
57
58 foreach ($fields as $field) {
59 $field = apply_filters_deprecated('fluentform_rendering_field_data_' . $field['element'],
60 [
61 $field,
62 $form
63 ],
64 FLUENTFORM_FRAMEWORK_UPGRADE,
65 'fluentform/rendering_field_data_' . $field['element'],
66 'Use fluentform/rendering_field_data_' . $field['element'] . ' instead of fluentform_rendering_field_data_' . $field['element']
67 );
68
69 $field = apply_filters('fluentform/rendering_field_data_' . $field['element'], $field, $form);
70
71 $validationsRules = self::resolveValidationsRules($field, $form);
72
73 $question = static::buildBaseQuestion($field, $validationsRules, $form);
74
75 if (!$hasSaveAndResume && $answer = self::setDefaultValue(ArrayHelper::get($field, 'attributes.value'), $field, $form)) {
76 $question['answer'] = $answer;
77 }
78
79 if ($hasSaveAndResume && $saveAndResumeData) {
80 $response = ArrayHelper::get($saveAndResumeData, 'response');
81 $questionId = ArrayHelper::get($question, 'id');
82
83 // Special handling for section breaks and custom HTML
84 if (ArrayHelper::get($field, 'element') == 'section_break' || ArrayHelper::get($field, 'element') == 'custom_html') {
85 $question['answered'] = true;
86 $question['answer'] = 'section_viewed';
87 } else {
88 $value = $questionId ? ArrayHelper::get($response, $questionId) : null;
89 if (!empty($value)) {
90 if (in_array(ArrayHelper::get($field, 'element'), ['input_file', 'input_image'])) {
91 $files = ArrayHelper::get($response, $questionId);
92 foreach ($files as $file) {
93 if (is_array($file)) {
94 $question['answer'][] = ArrayHelper::get($file, 'data_src');
95 } else {
96 // Already a plain URL string (legacy or non-encrypted)
97 $question['answer'][] = $file;
98 }
99 }
100 } elseif (ArrayHelper::get($field, 'element') == 'signature') {
101 // Signature is stored as a URL string
102 $question['answer'] = $value;
103 } elseif (
104 ArrayHelper::get($field, 'element') == 'rangeslider' ||
105 ArrayHelper::get($field, 'element') == 'subscription_payment_component' ||
106 ArrayHelper::get($field, 'element') == 'net_promoter_score'
107 ) {
108 $question['answer'] = +$value;
109 } else {
110 $question['answer'] = $value;
111 }
112 }
113 }
114 }
115
116 if ('default' != ArrayHelper::get($question, 'style_pref.layout')) {
117 $media = ArrayHelper::get($question, 'style_pref.media');
118 if ($media) {
119 $imagePreloads[] = $media;
120 }
121 }
122 if ('address' === $field['element']) {
123 if ($order = ArrayHelper::get($field, 'settings.field_order')) {
124 $order = array_values(array_column($order, 'value'));
125 $fields = ArrayHelper::get($field, 'fields');
126 $field['fields'] = array_merge(array_flip($order), $fields);
127 }
128
129 $provider = ArrayHelper::get($field, 'settings.autocomplete_provider');
130 $isLegacyProvider = !ArrayHelper::has($field, 'settings.autocomplete_provider');
131 $googleAutoComplete = 'yes' === ArrayHelper::get($field, 'settings.enable_g_autocomplete');
132 if (defined('FLUENTFORMPRO') && ($provider === 'google' || ($isLegacyProvider && $googleAutoComplete))) {
133 $question['ff_map_autocomplete'] = true;
134 $question['ff_with_g_map'] = 'yes' == ArrayHelper::get($field, 'settings.enable_g_map');
135 $question['ff_with_auto_locate'] = ArrayHelper::get($field, 'settings.enable_auto_locate', false);
136 $question['GmapApiKey'] = apply_filters('fluentform/conversational_form_address_gmap_api_key', '');
137 }
138
139 // Handle HTML5 geolocation
140 if ($provider === 'html5') {
141 $question['ff_html5_geolocate'] = true;
142 $question['ff_html5_locate'] = ArrayHelper::get($field, 'settings.enable_auto_locate', 'on_click');
143 }
144
145 foreach ($field['fields'] as $item) {
146 if ($item['settings']['visible']) {
147 $itemName = ArrayHelper::get($item, 'attributes.name');
148 if ($parentName = ArrayHelper::get($question, 'name')) {
149 $itemName = $parentName . '.' . $itemName;
150 }
151 $validationsRules = self::resolveValidationsRules($item, $form, $itemName);
152 $itemQuestion = [
153 'title' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($item, 'settings.label'), $form),
154 'container_class' => ArrayHelper::get($item, 'settings.container_class'),
155 'required' => ArrayHelper::get($validationsRules, 'required.value'),
156 'requiredMsg' => ArrayHelper::get($validationsRules, 'required.message'),
157 'errorMessage' => ArrayHelper::get($validationsRules, 'required.message'),
158 'validationRules' => $validationsRules,
159 'conditional_logics' => self::parseConditionalLogic($item),
160 ];
161 if ('select_country' === $item['element']) {
162 $countryComponent = new \FluentForm\App\Services\FormBuilder\Components\SelectCountry();
163 $item = $countryComponent->loadCountries($item);
164 $activeList = ArrayHelper::get($item, 'settings.country_list.active_list');
165 if ('priority_based' == $activeList) {
166 $selectCountries = ArrayHelper::get($item, 'settings.country_list.priority_based', []);
167 $priorityCountries = $countryComponent->getSelectedCountries($selectCountries);
168 $item['options'] = array_merge($priorityCountries, $item['options']);
169 }
170
171 if ('visible_list' === $activeList && $googleAutoComplete) {
172 $restrictionCountries = (array) ArrayHelper::get($item, 'attributes.value', []);
173 $restrictionCountries = array_unique(
174 array_merge(
175 $restrictionCountries,
176 ArrayHelper::get($item, 'settings.country_list.visible_list', [])
177 )
178 );
179 $question['autocomplete_restrictions'] = array_filter($restrictionCountries);
180 }
181
182 $options = [];
183 $countries = $item['options'];
184 foreach ($countries as $key => $value) {
185 $options[] = [
186 'label' => html_entity_decode($value, ENT_QUOTES, 'UTF-8'),
187 'value' => $key,
188 ];
189 }
190 $item['type'] = 'FlowFormTextType';
191 $item['options'] = $options;
192 }
193 if ($itemQuestion['required']) {
194 $question['requiredFields'][] = [
195 "name" => ArrayHelper::get($item, 'attributes.name', ''),
196 'requiredMessage' => ArrayHelper::get($itemQuestion, 'requiredMsg')
197 ];
198 $question['required'] = true;
199 }
200
201 if (!$hasSaveAndResume) {
202 $item['attributes']['value'] = self::setDefaultValue(ArrayHelper::get($item, 'attributes.value', ''), $item, $form);;
203 }
204 $question['fields'][] = wp_parse_args($itemQuestion, $item);
205 }
206 }
207 } elseif ('input_name' === $field['element']) {
208
209 foreach ($field['fields'] as $item) {
210 if ($item['settings']['visible']) {
211 $itemName = ArrayHelper::get($item, 'attributes.name');
212 if ($parentName = ArrayHelper::get($question, 'name')) {
213 $itemName = $parentName . '.' . $itemName;
214 }
215 $validationsRules = self::resolveValidationsRules($item, $form, $itemName);
216 $itemQuestion = [
217 'title' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($item, 'settings.label'), $form),
218 'container_class' => ArrayHelper::get($item, 'settings.container_class'),
219 'required' => ArrayHelper::get($validationsRules, 'required.value'),
220 'requiredMsg' => ArrayHelper::get($validationsRules, 'required.message'),
221 'errorMessage' => ArrayHelper::get($validationsRules, 'required.message'),
222 'validationRules' => $validationsRules,
223 'conditional_logics' => self::parseConditionalLogic($item),
224 ];
225 if ($itemQuestion['required']) {
226 $question['requiredFields'][] = [
227 "name" => ArrayHelper::get($item, 'attributes.name', ''),
228 'requiredMessage' => ArrayHelper::get($itemQuestion, 'requiredMsg', '')
229 ];
230 $question['required'] = true;
231 }
232
233 $item['attributes']['value'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($item, 'attributes.value'), $form);
234
235 $question['fields'][] = wp_parse_args($itemQuestion, $item);
236 }
237 }
238 } elseif ('input_text' === $field['element']) {
239 $mask = ArrayHelper::get($field, 'settings.temp_mask');
240
241 $mask = 'custom' === $mask ? ArrayHelper::get($field, 'attributes.data-mask') : $mask;
242
243 if ($mask) {
244 $question['mask'] = $mask;
245 }
246 } elseif ('welcome_screen' === $field['element']) {
247 $question['settings'] = ArrayHelper::get($field, 'settings', []);
248 $question['subtitle'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.description'), $form);
249 $question['required'] = false;
250 // $question['css'] = (new \FluentConversational\Form)->getSubmitBttnStyle($field);
251 } elseif ('select' === $field['element']) {
252 $question['options'] = self::getAdvancedOptions($field, $form);
253 $question['placeholder'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.placeholder', null), $form);
254 $question['searchable'] = ArrayHelper::get($field, 'settings.enable_select_2');
255 $isMultiple = ArrayHelper::get($field, 'attributes.multiple', false);
256
257 if ($isMultiple) {
258 $question['multiple'] = true;
259 $question['placeholder'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.placeholder', false), $form);
260 $question['max_selection'] = ArrayHelper::get($field, 'settings.max_selection');
261 $question['max_selection'] = $question['max_selection'] ? intval($question['max_selection']) : 0;
262 }
263 } elseif ('select_country' === $field['element']) {
264 $countryComponent = new \FluentForm\App\Services\FormBuilder\Components\SelectCountry();
265 $field = $countryComponent->loadCountries($field);
266 $activeList = ArrayHelper::get($field, 'settings.country_list.active_list');
267 if ('priority_based' == $activeList) {
268 $selectCountries = ArrayHelper::get($field, 'settings.country_list.priority_based', []);
269 $priorityCountries = $countryComponent->getSelectedCountries($selectCountries);
270 // @todo : add opt group in conversation js
271 $question['has_opt_grp'] = true;
272 $primaryListLabel = ArrayHelper::get($field, 'settings.primary_label');
273 $otherListLabel = ArrayHelper::get($field, 'settings.other_label');
274 $field['options'] = array_merge($priorityCountries, $field['options']);
275 }
276
277 $options = [];
278 $countries = $field['options'];
279 foreach ($countries as $key => $value) {
280 $options[] = [
281 'label' => html_entity_decode($value, ENT_QUOTES, 'UTF-8'),
282 'value' => $key,
283 ];
284 }
285 $question['options'] = $options;
286 $question['placeholder'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.placeholder', null), $form);
287 $question['searchable'] = ArrayHelper::get($field, 'settings.enable_select_2');
288 } elseif ('dynamic_field' === $field['element']) {
289 $dynamicFetchValue = 'yes' == ArrayHelper::get($field, 'settings.dynamic_fetch');
290 if ($dynamicFetchValue) {
291 $field = apply_filters('fluentform/dynamic_field_re_fetch_result_and_resolve_value', $field);
292 $question['answer'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.value'), $form);
293 }
294 $type = ArrayHelper::get($field, 'settings.field_type', 'select');
295 if (in_array($type, ['checkbox', 'radio'])) {
296 $question['type'] = 'FlowFormMultipleChoiceType';
297 $question['options'] = self::getAdvancedOptions($field, $form);
298 if ('checkbox' == $type) {
299 $question['multiple'] = true;
300 }
301 } elseif (in_array($type, ['select', 'multi_select'])) {
302 $question['type'] = 'FlowFormDropdownType';
303 $question['options'] = self::getAdvancedOptions($field, $form);
304 $question['searchable'] = ArrayHelper::get($field, 'settings.enable_select_2');
305 $question['multiple'] = ArrayHelper::isTrue($field, 'attributes.multiple');
306 } else {
307 continue;
308 }
309 $question['nextStepOnAnswer'] = true;
310 } elseif ('input_checkbox' === $field['element']) {
311 $question['options'] = self::getAdvancedOptions($field, $form);
312 $question['multiple'] = true;
313 $question = static::hasPictureMode($field, $question);
314 $question = static::maybeAddOtherOption($field, $question);
315 } elseif ('input_radio' === $field['element']) {
316 $question['options'] = self::getAdvancedOptions($field, $form);
317 $question['nextStepOnAnswer'] = true;
318 $question = static::hasPictureMode($field, $question);
319 $question = static::maybeAddOtherOption($field, $question);
320 } elseif ('custom_html' === $field['element']) {
321 $question['content'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.html_codes', ''), $form);
322 } elseif ('section_break' === $field['element']) {
323 $question['content'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.description', ''), $form);
324 $question['contentAlign'] = ArrayHelper::get($field, 'settings.align', '');
325 } elseif ('phone' === $field['element']) {
326 if (defined('FLUENTFORMPRO')) {
327 $question['phone_settings'] = self::getPhoneFieldSettings($field, $form);
328
329 if ($question['phone_settings']['enabled']) {
330 $cssSource = FLUENTFORMPRO_DIR_URL . 'public/libs/intl-tel-input/css/intlTelInput.min.css';
331 if (is_rtl()) {
332 $cssSource = FLUENTFORMPRO_DIR_URL . 'public/libs/intl-tel-input/css/intlTelInput-rtl.min.css';
333 }
334 wp_enqueue_style('intlTelInput', $cssSource, [], '25.5.2');
335 if (version_compare( FLUENTFORMPRO_VERSION, '6.1.0', '>' ) ) {
336 wp_enqueue_script('intlTelInputWithUtils', FLUENTFORMPRO_DIR_URL . 'public/libs/intl-tel-input/js/intlTelInputWithUtils.min.js', [], '25.5.2', true);
337 } else {
338 wp_enqueue_script('intlTelInputUtils', FLUENTFORMPRO_DIR_URL . 'public/libs/intl-tel-input/js/utils.js', [], '18.1.1', true);
339 wp_enqueue_script('intlTelInput', FLUENTFORMPRO_DIR_URL . 'public/libs/intl-tel-input/js/intlTelInput.min.js', [], '18.1.1', true);
340 }
341 }
342 }
343 } elseif ('input_number' === $field['element']) {
344 $question['min'] = ArrayHelper::get($field, 'settings.validation_rules.min.value');
345 $question['max'] = ArrayHelper::get($field, 'settings.validation_rules.max.value');
346 $question['min'] = is_numeric($question['min']) ? $question['min'] : null;
347 $question['max'] = is_numeric($question['max']) ? $question['max'] : null;
348 $question['is_calculable'] = true;
349
350 if (!$form->hasCalculation) {
351 $form->hasCalculation = static::hasFormula($question);
352 }
353
354 do_action_deprecated(
355 'ff_rendering_calculation_form',
356 [
357 $form,
358 $field
359 ],
360 FLUENTFORM_FRAMEWORK_UPGRADE,
361 'fluentform/rendering_calculation_form',
362 'Use fluentform/rendering_calculation_form instead of ff_rendering_calculation_form'
363 );
364
365 do_action('fluentform/rendering_calculation_form', $form, $field);
366 } elseif (in_array($field['element'], ['terms_and_condition', 'gdpr_agreement'])) {
367 $question['options'] = [
368 [
369 'label' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.tc_agree_text', 'I accept'), $form),
370 'value' => 'on',
371 ],
372 ];
373
374 if ('terms_and_condition' === $field['element']) {
375 $hideDisagreeOption = ArrayHelper::isTrue($field, 'settings.hide_disagree');
376
377 if (!$hideDisagreeOption) {
378 $question['options'][] = [
379 'label' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.tc_dis_agree_text', 'I don\'t accept'), $form),
380 'value' => 'off',
381 ];
382 }
383 }
384
385 $question['nextStepOnAnswer'] = true;
386 $question['title'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.tnc_html'), $form);
387 if ('gdpr_agreement' === $field['element']) {
388 $question['required'] = true;
389 }
390 } elseif ('ratings' === $field['element']) {
391 $question['show_text'] = ArrayHelper::get($field, 'settings.show_text');
392 $question['rateOptions'] = ArrayHelper::get($field, 'options', []);
393 $question['nextStepOnAnswer'] = true;
394 } elseif ('input_date' === $field['element']) {
395 $app = wpFluentForm();
396 $dateField = new DateTime();
397
398 wp_enqueue_style('flatpickr', fluentFormMix('libs/flatpickr/flatpickr.min.css'), [], FLUENTFORM_VERSION);
399 wp_enqueue_script('flatpickr', fluentFormMix('libs/flatpickr/flatpickr.min.js'), [], FLUENTFORM_VERSION, true);
400
401 $question['dateConfig'] = json_decode($dateField->getDateFormatConfigJSON($field['settings'], $form));
402 $question['dateCustomConfig'] = $dateField->getCustomConfig($field['settings']);
403 } elseif (in_array($field['element'], ['input_image', 'input_file'])) {
404 $question['multiple'] = true;
405
406 $maxFileCount = ArrayHelper::get($field, 'settings.validation_rules.max_file_count');
407 $maxFileSize = ArrayHelper::get($field, 'settings.validation_rules.max_file_size');
408
409 if ('input_image' === $field['element']) {
410 $allowedFieldTypes = ArrayHelper::get($field, 'settings.validation_rules.allowed_image_types.value');
411
412 $question['validationRules']['allowed_file_types'] = $question['validationRules']['allowed_image_types'];
413 } else {
414 $allowedFieldTypes = ArrayHelper::get($field, 'settings.validation_rules.allowed_file_types.value');
415 }
416
417 if ($maxFileCount) {
418 $question['max'] = $maxFileCount['value'];
419 }
420
421 if ($maxFileSize) {
422 $question['maxSize'] = $maxFileSize['value'];
423 }
424
425 if ($allowedFieldTypes) {
426 $question['accept'] = implode('|', $allowedFieldTypes);
427 }
428 } elseif ('signature' === $field['element']) {
429 // Signature field settings
430 $question['signature_settings'] = [
431 'background_color' => ArrayHelper::get($field, 'settings.sign_background_color', '#ffffff'),
432 'border_color' => ArrayHelper::get($field, 'settings.sign_border_color', '#FFEB3B'),
433 'pen_color' => ArrayHelper::get($field, 'settings.sign_pen_color', '#333'),
434 'pen_size' => ArrayHelper::get($field, 'settings.sign_pen_size', 2),
435 'pad_height' => ArrayHelper::get($field, 'settings.sign_pad_height', 200),
436 'instruction' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.sign_instruction', __('Sign Here', 'fluentform')), $form),
437 ];
438 $question['multiple'] = false;
439
440 // Enqueue signature scripts and styles
441 if (defined('FLUENTFORM_SIGNATURE')) {
442 wp_enqueue_style(
443 'fluentform-signature',
444 FLUENTFORM_SIGNATURE_URL . 'public/css/fluentform-signature.css',
445 [],
446 FLUENTFORM_SIGNATURE_VERSION
447 );
448
449 wp_enqueue_script(
450 'signature_pad',
451 FLUENTFORM_SIGNATURE_URL . 'public/js/signature_pad.js',
452 ['jquery'],
453 '2.3.2',
454 true
455 );
456
457 wp_enqueue_script(
458 'fluentform-signature',
459 FLUENTFORM_SIGNATURE_URL . 'public/js/fluentform-signature.js',
460 ['jquery', 'signature_pad'],
461 FLUENTFORM_SIGNATURE_VERSION,
462 true
463 );
464 }
465 } elseif ('tabular_grid' === $field['element']) {
466 $question['grid_columns'] = $field['settings']['grid_columns'];
467 $question['grid_rows'] = $field['settings']['grid_rows'];
468 $question['selected_grids'] = $field['settings']['selected_grids'];
469 $question['multiple'] = 'checkbox' === $field['settings']['tabular_field_type'];
470
471 if ($field['settings']['selected_grids']) {
472 $rowValues = array_keys($question['grid_rows']);
473 $colValues = array_keys($question['grid_columns']);
474
475 foreach ($field['settings']['selected_grids'] as $selected) {
476 if (in_array($selected, $rowValues)) {
477 $question['answer'][$selected] = $colValues;
478 } else {
479 foreach ($rowValues as $rowValue) {
480 if ($question['multiple']) {
481 $question['answer'][$rowValue][] = $selected;
482 } else {
483 $question['answer'][$rowValue] = $selected;
484 }
485 }
486 }
487 }
488 }
489
490 $question['requiredPerRow'] = ArrayHelper::get($field, 'settings.validation_rules.required.per_row');
491 } elseif ('rangeslider' === $field['element']) {
492 $question['min'] = intval($field['attributes']['min']);
493 $question['max'] = intval($field['attributes']['max']);
494
495 if ($step = ArrayHelper::get($field, 'settings.number_step')) {
496 $question['step'] = intval($step);
497 } else {
498 $question['step'] = 1;
499 }
500
501 $enabledQtyMapping = 'yes' === ArrayHelper::get($field, 'settings.enable_target_product');
502 if ($enabledQtyMapping && $targetProductName = ArrayHelper::get($field, 'settings.target_product')) {
503 $question['targetProduct'] = $targetProductName;
504 }
505
506 $question['is_calculable'] = true;
507 $question['type'] = 'FlowFormRangesliderType';
508 } elseif ('save_progress_button' === $field['element']) {
509 // Add the save progress button data to the previous question
510 if ($questions && count($questions) > 0) {
511 $lastQuestion = &$questions[count($questions) - 1];
512 $question['id'] = ArrayHelper::get($field, 'attributes.name');
513 $question['parent_id'] = $lastQuestion['id'];
514 $question['title'] = self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'editor_options.title'), $form);
515 $question['settings'] = ArrayHelper::get($field, 'settings');
516 $question['counter'] = count($questions) - 1;
517 $lastQuestion['has_save_and_resume_button'] = true;
518 $lastQuestion['save_and_resume_button'] = $question;
519 $form->hasSaveAndResumeButton = true;
520 }
521 // Skip Save Progress Button as a separate question
522 continue;
523 } elseif ('multi_payment_component' === $field['element']) {
524 $type = $field['attributes']['type'];
525
526 if ('single' == $type) {
527 $question['priceLabel'] = self::getComponent()->replaceEditorSmartCodes($field['settings']['price_label'], $form);
528 } else {
529 $question['nextStepOnAnswer'] = true;
530
531 if ('radio' == $type || 'checkbox' == $type) {
532 $question['paymentType'] = 'MultipleChoiceType';
533 $question['type'] = 'FlowFormMultipleChoiceType';
534 $question = static::hasPictureMode($field, $question);
535
536 if ('checkbox' == $type) {
537 $question['multiple'] = true;
538 }
539 } else {
540 $question['paymentType'] = 'DropdownType';
541 $question['type'] = 'FlowFormDropdownType';
542 }
543
544 $pricingOptions = ArrayHelper::get($field, 'settings.pricing_options');
545 foreach ($pricingOptions as &$option) {
546 $option['label'] = self::getComponent()->replaceEditorSmartCodes($option['label'], $form);
547 }
548 $question['options'] = $pricingOptions;
549 }
550
551 $question['is_payment_field'] = true;
552 $question['is_calculable'] = true;
553 } elseif ('subscription_payment_component' === $field['element']) {
554 $question['is_payment_field'] = true;
555 $question['is_subscription_field'] = true;
556
557 if (!$hasSaveAndResume) {
558 $question['answer'] = null;
559 }
560
561 $type = $field['attributes']['type'];
562 $question['subscriptionFieldType'] = $type;
563 $currency = PaymentHelper::getFormCurrency($form->id);
564
565 // Filter out expired plans that are set to hide
566 $subscriptionOptions = $field['settings']['subscription_options'];
567 $visibleOptions = array_filter($subscriptionOptions, function ($opt) {
568 return !PaymentHelper::isPlanExpiredAndHidden($opt);
569 });
570
571 if (empty($visibleOptions)) {
572 continue;
573 }
574
575 foreach ($subscriptionOptions as $index => &$option) {
576 if (PaymentHelper::isPlanExpiredAndHidden($option)) {
577 continue;
578 }
579
580 $hasCustomPayment = false;
581
582 if (array_key_exists('user_input', $option) && 'yes' == $option['user_input']) {
583 $hasCustomPayment = true;
584 $option['subscription_amount'] = 0;
585
586 if (array_key_exists('user_input_default_value', $option)) {
587 $option['subscription_amount'] = $option['user_input_default_value'];
588 }
589 }
590
591 $paymentSummaryText = PaymentHelper::getPaymentSummaryText($option, $form->id, $currency, false);
592
593 $planValue = 'single' == $type ? $option['subscription_amount'] : $index;
594
595 $field['plans'][] = [
596 'label' => self::getComponent()->replaceEditorSmartCodes($option['name'], $form),
597 'value' => $planValue,
598 'sub' => wp_strip_all_tags($paymentSummaryText),
599 'subscription_amount' => $planValue,
600 ];
601
602 $option['sub'] = wp_strip_all_tags($paymentSummaryText);
603
604 if ('yes' == $option['is_default'] && !$hasSaveAndResume) {
605 $question['answer'] = $index;
606 }
607
608 if ($hasCustomPayment) {
609 $option['customInput'] = $question['name'] . '_custom_' . $index;
610
611 if ($hasSaveAndResume && $saveAndResumeData) {
612 if ($customPayment = ArrayHelper::get($saveAndResumeData, 'response.' . $option['customInput'])) {
613 $question['customPayment'] = $customPayment;
614 }
615 } else {
616 $question['customPayment'] = $option['subscription_amount'];
617 }
618 }
619 }
620
621 $filteredPlans = array_values(array_filter($subscriptionOptions, function ($opt) {
622 return !PaymentHelper::isPlanExpiredAndHidden($opt);
623 }));
624 $question['plans'] = $filteredPlans;
625
626 // Re-map default answer to the new re-indexed position
627 foreach ($filteredPlans as $newIndex => $plan) {
628 if ('yes' == $plan['is_default'] && !$hasSaveAndResume) {
629 $question['answer'] = $newIndex;
630 break;
631 }
632 }
633
634 if ('single' != $type) {
635 $question['options'] = $field['plans'];
636 $question['subscriptionFieldType'] = 'radio' == $field['settings']['selection_type'] ? 'FlowFormMultipleChoiceType' : 'FlowFormDropdownType';
637 $question['nextStepOnAnswer'] = true;
638 }
639 } elseif ('custom_payment_component' === $field['element']) {
640 $question['type'] = 'FlowFormNumberType';
641 $question['min'] = ArrayHelper::get($field, 'settings.validation_rules.min.value');
642 $question['max'] = ArrayHelper::get($field, 'settings.validation_rules.max.value');
643 $question['min'] = is_numeric($question['min']) ? $question['min'] : null;
644 $question['max'] = is_numeric($question['max']) ? $question['max'] : null;
645
646 $question['is_payment_field'] = true;
647 $question['is_calculable'] = true;
648
649 if (!$form->hasCalculation) {
650 $form->hasCalculation = static::hasFormula($question);
651 }
652
653 do_action_deprecated(
654 'ff_rendering_calculation_form',
655 [
656 $form,
657 $field
658 ],
659 FLUENTFORM_FRAMEWORK_UPGRADE,
660 'fluentform/rendering_calculation_form',
661 'Use fluentform/rendering_calculation_form instead of ff_rendering_calculation_form'
662 );
663
664 do_action('fluentform/rendering_calculation_form', $form, $field);
665 } elseif ('item_quantity_component' === $field['element']) {
666 $question['type'] = $allowedFields['input_number'];
667 $question['targetProduct'] = $field['settings']['target_product'];
668
669 $question['min'] = ArrayHelper::get($field, 'settings.validation_rules.min.value');
670 $question['max'] = ArrayHelper::get($field, 'settings.validation_rules.max.value');
671 $question['min'] = is_numeric($question['min']) ? $question['min'] : null;
672 $question['max'] = is_numeric($question['max']) ? $question['max'] : null;
673 $question['step'] = 1;
674 $question['stepErrorMsg'] = __('Please enter a valid value. The two nearest valid values are {lower_value} and {upper_value}', 'fluentform');
675 } elseif ('payment_method' === $field['element']) {
676 $question['nextStepOnAnswer'] = true;
677 $question['options'] = [];
678 $question['paymentMethods'] = [];
679
680 foreach ($field['settings']['payment_methods'] as $methodName => $paymentMethod) {
681 if ('yes' === $paymentMethod['enabled']) {
682 $question['options'][] = [
683 'label' => self::getComponent()->replaceEditorSmartCodes($paymentMethod['settings']['option_label']['value'], $form),
684 'value' => $paymentMethod['method_value'],
685 ];
686
687 $question['paymentMethods'][$methodName] = $paymentMethod;
688
689 do_action_deprecated(
690 'fluentform_rendering_payment_method_' . $methodName,
691 [
692 $paymentMethod,
693 $field,
694 $form
695 ],
696 FLUENTFORM_FRAMEWORK_UPGRADE,
697 'fluentform/rendering_payment_method_' . $methodName,
698 'Use fluentform/rendering_payment_method_' . $methodName . ' instead of fluentform_rendering_payment_method_' . $methodName
699 );
700
701 do_action(
702 'fluentform/rendering_payment_method_' . $methodName,
703 $paymentMethod,
704 $field,
705 $form
706 );
707 }
708 }
709 } elseif ('payment_summary_component' === $field['element']) {
710 $question['title'] = self::getComponent()->replaceEditorSmartCodes(__('Payment Summary', 'fluentform'), $form);
711 $question['emptyText'] = $field['settings']['cart_empty_text'];
712 } elseif ('net_promoter_score' === $field['element']) {
713 if (!ArrayHelper::exists($question, 'answer')) {
714 $question['answer'] = (int) $field['attributes']['value'];
715 }
716
717 $question['start_text'] = ArrayHelper::get($field, 'settings.start_text');
718 $question['end_text'] = ArrayHelper::get($field, 'settings.end_text');
719 $question['is_calculable'] = true;
720 $question['nextStepOnAnswer'] = true;
721 $question['type'] = 'FlowFormNetPromoterScoreType';
722 } elseif ('recaptcha' === $field['element']) {
723 $reCaptchaConfig = get_option('_fluentform_reCaptcha_details');
724 $siteKey = ArrayHelper::get($reCaptchaConfig, 'siteKey');
725
726 if (! $siteKey) {
727 continue;
728 }
729
730 $question['siteKey'] = $siteKey;
731 $question['answer'] = '';
732
733 $apiVersion = ArrayHelper::get($reCaptchaConfig, 'api_version', 'v2_visible');
734 $apiVersion = 'v3_invisible' == $apiVersion ? 3 : 2;
735 $api = 'https://www.google.com/recaptcha/api.js';
736
737 $form->reCaptcha = [
738 'version' => $apiVersion,
739 'siteKey' => $siteKey,
740 ];
741
742 if (3 === $apiVersion) {
743 $api .= '?render=' . $siteKey;
744 }
745
746 wp_enqueue_script(
747 'google-recaptcha',
748 $api,
749 [],
750 FLUENTFORM_VERSION,
751 true
752 );
753
754 if (3 === $apiVersion) {
755 $shouldRenderBadge = ArrayHelper::get($field, 'settings.render_recaptcha_v3_badge', false);
756
757 if (!$shouldRenderBadge) {
758 // Add CSS to hide reCAPTCHA badge
759 add_action('fluentform/conversational_frame_footer', function() {
760 echo
761 "<style>
762 .grecaptcha-badge {
763 visibility: hidden;
764 }
765 </style>";
766 });
767 }
768
769 continue;
770 }
771 } elseif (('hcaptcha' === $field['element'])) {
772 $hCaptchaConfig = get_option('_fluentform_hCaptcha_details');
773 $siteKey = ArrayHelper::get($hCaptchaConfig, 'siteKey');
774
775 if (! $siteKey) {
776 continue;
777 }
778
779 $question['siteKey'] = $siteKey;
780
781 $api = 'https://js.hcaptcha.com/1/api.js';
782
783 $form->hCaptcha = [
784 'siteKey' => $siteKey,
785 ];
786
787 wp_enqueue_script(
788 'hcaptcha',
789 $api,
790 [],
791 FLUENTFORM_VERSION,
792 true
793 );
794 } elseif ('turnstile' === $field['element']) {
795 $turnstileConfig = get_option('_fluentform_turnstile_details');
796 $siteKey = ArrayHelper::get($turnstileConfig, 'siteKey');
797 $appearance = ArrayHelper::get($turnstileConfig, 'appearance', 'always');
798 $theme = ArrayHelper::get($turnstileConfig, 'theme', 'auto');
799
800 if (! $siteKey) {
801 continue;
802 }
803
804 $question['siteKey'] = $siteKey;
805 $question['appearance'] = $appearance;
806 $question['theme'] = $theme;
807
808 $form->turnstile = [
809 'siteKey' => $siteKey,
810 'appearance' => $appearance,
811 ];
812
813 wp_enqueue_script(
814 'turnstile_conv',
815 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit', // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent -- Cloudflare Turnstile requires loading from their CDN for CAPTCHA functionality
816 [],
817 FLUENTFORM_VERSION,
818 false
819 );
820
821 // for WP Rocket compatibility
822 wp_script_add_data('turnstile_conv', 'data-cfasync', 'false');
823
824 if ('interaction-only' === $appearance) {
825 continue;
826 }
827 } elseif ('payment_coupon' === $field['element']) {
828 if ($hasSaveAndResume && $saveAndResumeData) {
829 if ($coupons = ArrayHelper::get($saveAndResumeData, 'response.__ff_all_applied_coupons')) {
830 $question['answer'] = json_decode($coupons);
831 }
832 }
833 }
834
835 do_action('fluentform/conversational_question', $question, $field, $form);
836
837 if ($question['type']) {
838 $questions[] = $question;
839 }
840 if ('custom_submit_button' === $field['element']) {
841 $form->submit_button = $field;
842 }
843 }
844 $form->questions = $questions;
845
846 $form->image_preloads = $imagePreloads;
847
848 return $form;
849 }
850
851 public static function convertExistingForm($form)
852 {
853 $formFields = json_decode($form->form_fields, true);
854 $fields = $formFields['fields'];
855 $formattedFields = [];
856
857 if (is_array($fields) && ! empty($fields)) {
858 foreach ($fields as $field) {
859 $element = ArrayHelper::get($field, 'element');
860 $allowedFields = array_keys(static::fieldTypes());
861 if (!in_array($element, $allowedFields)) {
862 continue;
863 }
864
865 if (! ArrayHelper::exists($field, 'style_pref')) {
866 $field['style_pref'] = [
867 'layout' => 'default',
868 'media' => fluentFormGetRandomPhoto(),
869 'brightness' => 0,
870 'alt_text' => '',
871 'media_x_position' => 50,
872 'media_y_position' => 50,
873 ];
874 }
875 $formattedFields[] = $field;
876 }
877 }
878
879 $formFields['fields'] = $formattedFields;
880
881 return json_encode($formFields);
882 }
883
884 private static function buildBaseQuestion($field, $validationsRules, $form)
885 {
886 return [
887 'id' => ArrayHelper::get($field, 'attributes.name'),
888 'name' => ArrayHelper::get($field, 'attributes.name'),
889 'title' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.label'), $form),
890 'type' => ArrayHelper::get(static::fieldTypes(), $field['element']),
891 'ff_input_type' => ArrayHelper::get($field, 'element'),
892 'container_class' => ArrayHelper::get($field, 'settings.container_class'),
893 'placeholder' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'attributes.placeholder'), $form),
894 'maxLength' => ArrayHelper::get($field, 'attributes.maxlength'),
895 'required' => ArrayHelper::get($validationsRules, 'required.value'),
896 'requiredMsg' => ArrayHelper::get($validationsRules, 'required.message'),
897 'errorMessage' => ArrayHelper::get($validationsRules, 'required.message'),
898 'validationRules' => $validationsRules,
899 'tagline' => self::getComponent()->replaceEditorSmartCodes(ArrayHelper::get($field, 'settings.help_message'), $form),
900 'style_pref' => ArrayHelper::get($field, 'style_pref', [
901 'layout' => 'default',
902 'media' => '',
903 'brightness' => 0,
904 'alt_text' => '',
905 'media_x_position' => 50,
906 'media_y_position' => 50,
907 ]),
908 'conditional_logics' => self::parseConditionalLogic($field),
909 'calculation_settings' => ArrayHelper::get($field, 'settings.calculation_settings'),
910 'is_calculable' => ArrayHelper::get($field, 'settings.calc_value_status', false),
911 'has_save_and_resume' => static::hasSaveAndResume($form)
912 ];
913 }
914
915 public static function fieldTypes()
916 {
917 $fieldTypes = [
918 'input_url' => 'FlowFormUrlType',
919 'input_date' => 'FlowFormDateType',
920 'input_text' => 'FlowFormTextType',
921 'input_email' => 'FlowFormEmailType',
922 'input_hidden' => 'FlowFormHiddenType',
923 'input_number' => 'FlowFormNumberType',
924 'select' => 'FlowFormDropdownType',
925 'select_country' => 'FlowFormDropdownType',
926 'textarea' => 'FlowFormLongTextType',
927 'input_password' => 'FlowFormPasswordType',
928 'custom_html' => 'FlowFormSectionBreakType',
929 'section_break' => 'FlowFormSectionBreakType',
930 'welcome_screen' => 'FlowFormWelcomeScreenType',
931 'input_checkbox' => 'FlowFormMultipleChoiceType',
932 'input_radio' => 'FlowFormMultipleChoiceType',
933 'terms_and_condition' => 'FlowFormTermsAndConditionType',
934 'gdpr_agreement' => 'FlowFormTermsAndConditionType',
935 'MultiplePictureChoice' => 'FlowFormMultiplePictureChoiceType',
936 'recaptcha' => 'FlowFormReCaptchaType',
937 'hcaptcha' => 'FlowFormHCaptchaType',
938 'turnstile' => 'FlowFormTurnstileType',
939 'address' => 'FlowFormAddressType',
940 'input_name' => 'FlowFormNameType',
941 'ffc_custom' => 'FlowFormCustomType',
942 'payment_method' => 'FlowFormPaymentMethodType',
943 'multi_payment_component' => 'FlowFormPaymentType',
944 'custom_payment_component' => 'FlowFormPaymentType',
945 'item_quantity_component' => 'FlowFormPaymentType',
946 'payment_summary_component' => 'FlowFormPaymentSummaryType',
947 'subscription_payment_component' => 'FlowFormSubscriptionType',
948 ];
949
950 if (defined('FLUENTFORM_SIGNATURE')) {
951 $fieldTypes['signature'] = 'FlowFormSignatureType';
952 }
953
954 if (Helper::hasPro()) {
955 $fieldTypes['phone'] = 'FlowFormPhoneType';
956 $fieldTypes['input_image'] = 'FlowFormFileType';
957 $fieldTypes['input_file'] = 'FlowFormFileType';
958 $fieldTypes['ratings'] = 'FlowFormRateType';
959 $fieldTypes['tabular_grid'] = 'FlowFormMatrixType';
960 $fieldTypes['payment_coupon'] = 'FlowFormCouponType';
961 $fieldTypes['quiz_score'] = 'FlowFormHiddenType';
962 $fieldTypes['rangeslider'] = 'FlowFormRangesliderType';
963 $fieldTypes['save_progress_button'] = 'FlowFormSaveAndResumeType';
964 $fieldTypes['dynamic_field'] = 'FlowFormDynamicFieldType';
965 $fieldTypes['net_promoter_score'] = 'FlowFormNetPromoterScoreType';
966 }
967
968 return apply_filters('fluentform/conversational_field_types', $fieldTypes);
969 }
970
971 public static function hasPictureMode($field, $question)
972 {
973 $pictureMode = ArrayHelper::get($field, 'settings.enable_image_input');
974
975 if ($pictureMode) {
976 $question['type'] = static::fieldTypes()['MultiplePictureChoice'];
977 }
978
979 return $question;
980 }
981
982 public static function hex2rgb($color, $opacity = 0.3)
983 {
984 if (! $color) {
985 return;
986 }
987 $rgbValues = [$r, $g, $b] = array_map(
988 function ($c) {
989 return hexdec(str_pad($c, 2, $c));
990 },
991 str_split(ltrim($color, '#'), strlen($color) > 4 ? 2 : 1)
992 );
993 $rgbValues[3] = $opacity;
994 $formattedValues = implode(',', $rgbValues);
995
996 return "rgb({$formattedValues})";
997 }
998
999 public static function getPhoneFieldSettings($data, $form)
1000 {
1001 $geoLocate = 'yes' == ArrayHelper::get($data, 'settings.auto_select_country');
1002
1003 // todo:: remove the 'with_extended_validation' check in future.
1004 $enabled = ArrayHelper::get($data, 'settings.validation_rules.valid_phone_number.value');
1005 if (! $enabled) {
1006 $enabled = 'with_extended_validation' == ArrayHelper::get($data, 'settings.int_tel_number');
1007 }
1008
1009 $itlOptions = [
1010 'separateDialCode' => false,
1011 'nationalMode' => true,
1012 'autoPlaceholder' => 'aggressive',
1013 'formatOnDisplay' => true,
1014 'validationNumberTypes' => [
1015 'MOBILE',
1016 'FIXED_LINE_OR_MOBILE',
1017 'FIXED_LINE',
1018 'TOLL_FREE',
1019 ]
1020 ];
1021
1022 if ($geoLocate) {
1023 $itlOptions['initialCountry'] = 'auto';
1024 } else {
1025 $itlOptions['initialCountry'] = ArrayHelper::get($data, 'settings.default_country', '');
1026 }
1027
1028 $activeList = ArrayHelper::get($data, 'settings.phone_country_list.active_list');
1029 if ('priority_based' == $activeList) {
1030 $selectCountries = ArrayHelper::get($data, 'settings.phone_country_list.priority_based', []);
1031 $priorityCountries = self::getSelectedCountries($selectCountries);
1032 $key = version_compare(FLUENTFORMPRO_VERSION, '6.1.0', '>') ? 'countryOrder' : 'preferredCountries';
1033 $itlOptions[$key] = array_keys($priorityCountries);
1034 } elseif ('visible_list' == $activeList) {
1035 $onlyCountries = ArrayHelper::get($data, 'settings.phone_country_list.visible_list', []);
1036 $itlOptions['onlyCountries'] = $onlyCountries;
1037 } elseif ('hidden_list' == $activeList) {
1038 $countries = self::loadCountries($data);
1039 $itlOptions['onlyCountries'] = array_keys($countries);
1040 }
1041
1042 $itlOptions = apply_filters_deprecated(
1043 'fluentform_itl_options',
1044 [
1045 $itlOptions,
1046 $data,
1047 $form
1048 ],
1049 FLUENTFORM_FRAMEWORK_UPGRADE,
1050 'fluentform/itl_options',
1051 'Use fluentform/itl_options instead of fluentform_itl_options'
1052 );
1053
1054 $itlOptions = apply_filters('fluentform/itl_options', $itlOptions, $data, $form);
1055 $itlOptions = json_encode($itlOptions);
1056
1057 $settings = get_option('_fluentform_global_form_settings');
1058 $token = ArrayHelper::get($settings, 'misc.geo_provider_token');
1059
1060 $url = 'https://ipinfo.io';
1061 if ($token) {
1062 $url = 'https://ipinfo.io/?token=' . $token;
1063 }
1064
1065 $url = apply_filters_deprecated(
1066 'fluentform_ip_provider',
1067 [
1068 $url
1069 ],
1070 FLUENTFORM_FRAMEWORK_UPGRADE,
1071 'fluentform/ip_provider',
1072 'Use fluentform/ip_provider instead of fluentform_ip_provider'
1073 );
1074
1075 $ipProviderUrl = apply_filters('fluentform/ip_provider', $url);
1076
1077 return [
1078 'enabled' => $enabled,
1079 'itlOptions' => $itlOptions,
1080 'ipLookupUrl' => ($geoLocate && $ipProviderUrl) ? $ipProviderUrl : false,
1081 ];
1082 }
1083
1084 /**
1085 * Load country list from file
1086 *
1087 * @param array $data
1088 *
1089 * @return array
1090 */
1091 public static function loadCountries($data)
1092 {
1093 $data['options'] = [];
1094 $activeList = ArrayHelper::get($data, 'settings.phone_country_list.active_list');
1095 $countries = getFluentFormCountryList();
1096 $filteredCountries = [];
1097 if ('visible_list' == $activeList) {
1098 $selectCountries = ArrayHelper::get($data, 'settings.phone_country_list.' . $activeList, []);
1099 foreach ($selectCountries as $value) {
1100 $filteredCountries[$value] = $countries[$value];
1101 }
1102 } elseif ('hidden_list' == $activeList || 'priority_based' == $activeList) {
1103 $filteredCountries = $countries;
1104 $selectCountries = ArrayHelper::get($data, 'settings.phone_country_list.' . $activeList, []);
1105 foreach ($selectCountries as $value) {
1106 unset($filteredCountries[$value]);
1107 }
1108 } else {
1109 $filteredCountries = $countries;
1110 }
1111
1112 return $filteredCountries;
1113 }
1114
1115 public static function getSelectedCountries($keys = [])
1116 {
1117 $options = [];
1118 $countries = getFluentFormCountryList();
1119 foreach ($keys as $value) {
1120 $options[$value] = $countries[$value];
1121 }
1122
1123 return $options;
1124 }
1125
1126 public static function setDefaultValue($value, $field, $form)
1127 {
1128 if ($dynamicValue = ArrayHelper::get($field, 'settings.dynamic_default_value')) {
1129 $dynamicVal = self::getComponent()->replaceEditorSmartCodes($dynamicValue, $form);
1130
1131 $element = $field['element'];
1132
1133 if ($dynamicVal && 'input_checkbox' == $element) {
1134 $defaultValues = explode(',', $dynamicVal);
1135
1136 return array_map('trim', $defaultValues);
1137 }
1138
1139 if ($dynamicVal) {
1140 return $dynamicVal;
1141 }
1142 }
1143
1144 if (! $value) {
1145 return $value;
1146 }
1147
1148 if (is_string($value)) {
1149 return self::getComponent()->replaceEditorSmartCodes($value, $form);
1150 }
1151
1152 return $value;
1153 }
1154
1155 private static function parseConditionalLogic($field)
1156 {
1157 $logics = ArrayHelper::get($field, 'settings.conditional_logics', []);
1158
1159 if (! $logics || ! $logics['status']) {
1160 return [];
1161 }
1162
1163 $type = ArrayHelper::get($logics, 'type', '');
1164 if ($type === 'group') {
1165 return self::parseGroupConditionalLogic($logics);
1166 } else {
1167 return self::parseSimpleConditionalLogic($logics);
1168 }
1169 }
1170
1171 private static function parseSimpleConditionalLogic($logics)
1172 {
1173 $validConditions = self::parseConditions(ArrayHelper::get($logics, 'conditions'));
1174
1175 if (!$validConditions) {
1176 return [];
1177 }
1178
1179 $logics['conditions'] = $validConditions;
1180 return $logics;
1181 }
1182
1183 private static function parseGroupConditionalLogic($logics)
1184 {
1185 $validGroups = [];
1186 $conditionGroups = ArrayHelper::get($logics, 'condition_groups', []);
1187
1188 foreach ($conditionGroups as $group) {
1189 $validConditions = self::parseConditions(ArrayHelper::get($group, 'rules'));
1190
1191 if ($validConditions) {
1192 $validGroups[] = [
1193 'rules' => $validConditions,
1194 ];
1195 }
1196 }
1197
1198 if (!$validGroups) {
1199 return [];
1200 }
1201
1202 $logics['condition_groups'] = $validGroups;
1203 return $logics;
1204 }
1205
1206 private static function parseConditions($conditions)
1207 {
1208 if (!$conditions) {
1209 return [];
1210 }
1211 return array_filter($conditions, function($condition) {
1212 return !empty($condition['field']) && !empty($condition['operator']);
1213 });
1214 }
1215
1216 private static function getAdvancedOptions($field, $form)
1217 {
1218 $options = ArrayHelper::get($field, 'settings.advanced_options', []);
1219
1220 foreach ($options as &$option) {
1221 $option['label'] = self::getComponent()->replaceEditorSmartCodes($option['label'], $form);
1222 }
1223
1224 if ($options && 'yes' == ArrayHelper::get($field, 'settings.randomize_options')) {
1225 shuffle($options);
1226 }
1227
1228 return $options;
1229 }
1230
1231 private static function maybeAddOtherOption($field, $question)
1232 {
1233 // Add "Other" option if enabled
1234 $enableOtherOption = ArrayHelper::get($field, 'settings.enable_other_option') === 'yes';
1235 $attributeType = ArrayHelper::get($field, 'attributes.type');
1236 if ($enableOtherOption && in_array($attributeType, ['checkbox', 'radio']) && Helper::hasPro()) {
1237 $otherLabel = ArrayHelper::get($field, 'settings.other_option_label', __('Other', 'fluentform'));
1238 $placeholder = ArrayHelper::get($field, 'settings.other_option_placeholder', __('Please specify', 'fluentform'));
1239 $fieldName = sanitize_text_field(str_replace(['[', ']'], '', ArrayHelper::get($field, 'attributes.name', '')));
1240 $otherValue = '__ff_other_' . $fieldName . '__';
1241 $otherInputName = $fieldName . '__ff_other_input__';
1242 $question['allowOther'] = true;
1243 $question['otherValue'] = $otherValue;
1244 $question['otherInputLabel'] = $otherLabel;
1245 $question['otherInputPlaceholder'] = $placeholder;
1246 $question['otherInputName'] = $otherInputName;
1247 $question['otherInputValue'] = '';
1248 }
1249 return $question;
1250 }
1251
1252 private static function hasFormula($question)
1253 {
1254 return (bool) (
1255 ArrayHelper::get($question, 'calculation_settings.status')
1256 && ArrayHelper::get($question, 'calculation_settings.formula')
1257 );
1258 }
1259
1260 private static function hasSaveAndResume($form)
1261 {
1262 if (!defined('FLUENTFORMPRO')){
1263 return false;
1264 }
1265
1266 if (!version_compare(FLUENTFORMPRO_VERSION,'5.1.13' ,'>=')){
1267 return false;
1268 }
1269
1270 $hasSaveProgressButton = FormFieldsParser::hasElement($form, 'save_progress_button');
1271 $perStepSave = ArrayHelper::get($form->settings, 'conv_form_per_step_save');
1272
1273 if ($perStepSave || $hasSaveProgressButton) {
1274 $saveAndResume = false;
1275 $hash = '';
1276 $form->save_state = false;
1277
1278 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public form state parameter for save & resume functionality, verified by hash comparison in database
1279 $key = isset($_GET['fluent_state']) ? sanitize_text_field(wp_unslash($_GET['fluent_state'])) : false;
1280
1281 if ($key) {
1282 $hash = base64_decode($key);
1283 $form->save_state = true;
1284 } else {
1285 $cookieName = 'fluentform_step_form_hash_' . $form->id;
1286 $hash = ArrayHelper::get($_COOKIE, $cookieName, wp_generate_uuid4());
1287 }
1288
1289 \FluentFormPro\classes\DraftSubmissionsManager::migrate();
1290
1291 $draftForm = \FluentFormPro\classes\DraftSubmissionsManager::get($hash);
1292
1293 if ($draftForm) {
1294 $saveAndResume = true;
1295 }
1296
1297 return $saveAndResume;
1298 }
1299
1300 return false;
1301 }
1302
1303
1304 /**
1305 * @param array $field
1306 * @param object $form
1307 * @param string $fieldName
1308 * @return array
1309 */
1310 private static function resolveValidationsRules($field, $form, $fieldName = '')
1311 {
1312 $validationsRules = ArrayHelper::get($field, 'settings.validation_rules', []);
1313 if ($validationsRules) {
1314 if (!$fieldName) {
1315 $fieldName = ArrayHelper::get($field, 'attributes.name');
1316 }
1317 foreach ($validationsRules as $ruleName => $rule) {
1318 if (ArrayHelper::exists($rule, 'message')) {
1319 if (ArrayHelper::isTrue($rule, 'global')) {
1320 $rule['message'] = apply_filters('fluentform/get_global_message_' . $ruleName, $rule['message']);;
1321 }
1322 // Shortcode parse on validation message
1323 $rule['message'] = Helper::shortCodeParseOnValidationMessage($rule['message'], $form, $fieldName);
1324 $validationsRules[$ruleName]['message'] = apply_filters('fluentform/validation_message_' . $ruleName, $rule['message'], $field);
1325 }
1326 }
1327 }
1328 return $validationsRules;
1329 }
1330
1331 private static function getSaveAndResumeData($form)
1332 {
1333 $draftForm = null;
1334 $data = [];
1335 $formId = $form->id;
1336 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public form state parameter for save & resume functionality, verified by hash comparison in database
1337 $key = isset($_GET['fluent_state']) ? sanitize_text_field(wp_unslash($_GET['fluent_state'])) : false;
1338 if ($key) {
1339 $hash = base64_decode($key);
1340 } else {
1341 $cookieName = 'fluentform_step_form_hash_' . $formId;
1342 $hash = ArrayHelper::get($_COOKIE, $cookieName, wp_generate_uuid4());
1343 }
1344
1345 if ($hash) {
1346 $draftForm = \FluentFormPro\classes\DraftSubmissionsManager::get($hash, $formId);
1347 } elseif (!$draftForm && $userId = get_current_user_id()) {
1348 $draftForm = wpFluent()->table('fluentform_draft_submissions')
1349 ->where('user_id', $userId)
1350 ->where('form_id', $formId)
1351 ->first();
1352 } else {
1353 return $data;
1354 }
1355
1356 if ($draftForm) {
1357 $data['step_completed'] = (int)$draftForm->step_completed;
1358 $data['response'] = json_decode($draftForm->response, true);
1359
1360 $fileFieldTypes = ['input_file', 'input_image'];
1361 if (defined('FLUENTFORM_SIGNATURE')) {
1362 $fileFieldTypes[] = 'signature';
1363 }
1364 $fields = FormFieldsParser::getInputsByElementTypes($form, $fileFieldTypes);
1365 foreach ($fields as $name => $field) {
1366 if ($urls = ArrayHelper::get($data['response'], $name)) {
1367 if (!is_array($urls)) {
1368 // Single value (e.g. signature stored as a string)
1369 $urls = [$urls];
1370 }
1371 foreach ($urls as $index => $url) {
1372 if (is_array($url)) {
1373 // Already wrapped (e.g. from a previous load) — keep as-is
1374 continue;
1375 }
1376 if (!is_string($url) || $url === '[object Object]') {
1377 // Corrupted data — skip this entry
1378 unset($data['response'][$name][$index]);
1379 continue;
1380 }
1381 $data['response'][$name][$index] = [
1382 "data_src" => $url,
1383 "url" => \FluentForm\App\Helpers\Helper::maybeDecryptUrl($url)
1384 ];
1385 }
1386 // Re-index in case entries were removed
1387 $data['response'][$name] = array_values($data['response'][$name]);
1388 }
1389 }
1390 unset(
1391 $data['response']['_wp_http_referer'],
1392 $data['response']['__fluent_form_embded_post_id'],
1393 $data['response']['_fluentform_' . $draftForm->form_id . '_fluentformnonce']
1394 );
1395 }
1396
1397 return $data;
1398 }
1399 }
1400