PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.5
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.5
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 / Helpers / Helper.php

Helper.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.5, at app/Helpers/Helper.php

1,661 lines 55.3 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\Helpers;
4
5 use FluentForm\App\Models\EntryDetails;
6 use FluentForm\App\Models\Form;
7 use FluentForm\App\Models\FormMeta;
8 use FluentForm\App\Models\Submission;
9 use FluentForm\App\Models\SubmissionMeta;
10 use FluentForm\App\Services\FormBuilder\Components\SelectCountry;
11 use FluentForm\App\Services\FormBuilder\ShortCodeParser;
12 use FluentForm\Framework\Helpers\ArrayHelper;
13 use FluentForm\App\Helpers\Traits\GlobalDefaultMessages;
14 use FluentForm\Framework\Support\Arr;
15
16 class Helper
17 {
18 use GlobalDefaultMessages;
19
20 public static $tabIndex = 0;
21
22 public static $formInstance = 0;
23
24 public static $loadedForms = [];
25
26 public static $tabIndexStatus = 'na';
27
28 protected static $formMetaCache = [];
29
30
31 /**
32 * Sanitize form inputs recursively.
33 *
34 * @param $input
35 *
36 * @return string $input
37 */
38 public static function sanitizer($input, $attribute = null, $fields = [])
39 {
40 if (is_string($input)) {
41 if ('textarea' === ArrayHelper::get($fields, $attribute . '.element')) {
42 $input = sanitize_textarea_field($input);
43 } else {
44 $input = sanitize_text_field($input);
45 }
46 } elseif (is_array($input)) {
47 foreach ($input as $key => &$value) {
48 $attribute = $attribute ? $attribute . '[' . $key . ']' : $key;
49
50 $value = static::sanitizer($value, $attribute, $fields);
51
52 $attribute = null;
53 }
54 }
55
56 return $input;
57 }
58
59 public static function isOptionGroup($option)
60 {
61 return is_array($option)
62 && ArrayHelper::get($option, 'type') === 'group'
63 && is_array(ArrayHelper::get($option, 'options'));
64 }
65
66 public static function sanitizeAdvancedOptions($options, $depth = 0)
67 {
68 if (!is_array($options)) {
69 return [];
70 }
71
72 $sanitized = [];
73
74 foreach ($options as $option) {
75 if (!is_array($option)) {
76 continue;
77 }
78
79 if (self::isOptionGroup($option)) {
80 $groupOptions = self::sanitizeAdvancedOptions(
81 ArrayHelper::get($option, 'options', []),
82 $depth + 1
83 );
84
85 if ($depth > 0) {
86 $sanitized = array_merge($sanitized, $groupOptions);
87 continue;
88 }
89
90 $sanitized[] = [
91 'type' => 'group',
92 'label' => wp_kses_post(ArrayHelper::get($option, 'label', '')),
93 'options' => $groupOptions,
94 ];
95 continue;
96 }
97
98 $sanitized[] = [
99 'label' => wp_kses_post(ArrayHelper::get($option, 'label', '')),
100 'value' => sanitize_text_field(ArrayHelper::get($option, 'value', '')),
101 'image' => sanitize_url(ArrayHelper::get($option, 'image', '')),
102 'calc_value' => sanitize_text_field(ArrayHelper::get($option, 'calc_value', '')),
103 'disabled' => ArrayHelper::isTrue($option, 'disabled'),
104 ];
105 }
106
107 return $sanitized;
108 }
109
110 public static function flattenAdvancedOptions($options)
111 {
112 if (!is_array($options)) {
113 return [];
114 }
115
116 $flattened = [];
117
118 foreach ($options as $option) {
119 if (self::isOptionGroup($option)) {
120 $flattened = array_merge(
121 $flattened,
122 self::flattenAdvancedOptions(ArrayHelper::get($option, 'options', []))
123 );
124 continue;
125 }
126
127 if (!is_array($option)) {
128 continue;
129 }
130
131 $flattened[] = $option;
132 }
133
134 return $flattened;
135 }
136
137 public static function advancedOptionsValueLabelMap($options)
138 {
139 $formatted = [];
140
141 foreach (self::flattenAdvancedOptions($options) as $option) {
142 $formatted[ArrayHelper::get($option, 'value')] = ArrayHelper::get($option, 'label');
143 }
144
145 return $formatted;
146 }
147
148 public static function makeMenuUrl($page = 'fluent_forms_settings', $component = null)
149 {
150 $baseUrl = admin_url('admin.php?page=' . $page);
151
152 $hash = ArrayHelper::get($component, 'hash', '');
153 if ($hash) {
154 $baseUrl = $baseUrl . '#' . $hash;
155 }
156
157 $query = ArrayHelper::get($component, 'query');
158
159 if ($query) {
160 $paramString = http_build_query($query);
161 if ($hash) {
162 $baseUrl .= '?' . $paramString;
163 } else {
164 $baseUrl .= '&' . $paramString;
165 }
166 }
167
168 return $baseUrl;
169 }
170
171 public static function getHtmlElementClass($value1, $value2, $class = 'active', $default = '')
172 {
173 return $value1 === $value2 ? $class : $default;
174 }
175
176 /**
177 * Determines if the given string is a valid json.
178 *
179 * @param $string
180 *
181 * @return bool
182 */
183 public static function isJson($string)
184 {
185 json_decode($string);
186
187 return JSON_ERROR_NONE === json_last_error();
188 }
189
190 public static function isSlackEnabled()
191 {
192 $globalModules = get_option('fluentform_global_modules_status');
193
194 return $globalModules && isset($globalModules['slack']) && 'yes' == $globalModules['slack'];
195 }
196
197 public static function getEntryStatuses($form_id = false)
198 {
199 $statuses = [
200 'unread' => __('Unread', 'fluentform'),
201 'read' => __('Read', 'fluentform'),
202 'favorites' => __('Favorites', 'fluentform'),
203 ];
204
205 $statuses = apply_filters_deprecated(
206 'fluentform_entry_statuses_core',
207 [
208 $statuses,
209 $form_id
210 ],
211 FLUENTFORM_FRAMEWORK_UPGRADE,
212 'fluentform/entry_statuses_core',
213 'Use fluentform/entry_statuses_core instead of fluentform_entry_statuses_core.'
214 );
215
216 $statuses = apply_filters('fluentform/entry_statuses_core', $statuses, $form_id);
217
218 $statuses['trashed'] = 'Trashed';
219
220 return $statuses;
221 }
222
223 public static function getReportableInputs()
224 {
225 $data = [
226 'select',
227 'input_radio',
228 'input_checkbox',
229 'ratings',
230 'net_promoter',
231 'select_country',
232 'net_promoter_score',
233 ];
234
235 $data = apply_filters_deprecated(
236 'fluentform_reportable_inputs',
237 [
238 $data
239 ],
240 FLUENTFORM_FRAMEWORK_UPGRADE,
241 'fluentform/reportable_inputs',
242 'Use fluentform/reportable_inputs instead of fluentform_reportable_inputs.'
243 );
244
245 return apply_filters('fluentform/reportable_inputs', $data);
246 }
247
248 public static function getSubFieldReportableInputs()
249 {
250 $grid = apply_filters_deprecated(
251 'fluentform_subfield_reportable_inputs',
252 [
253 ['tabular_grid']
254 ],
255 FLUENTFORM_FRAMEWORK_UPGRADE,
256 'fluentform/subfield_reportable_inputs',
257 'Use fluentform/subfield_reportable_inputs instead of fluentform_subfield_reportable_inputs.'
258 );
259
260 return apply_filters('fluentform/subfield_reportable_inputs', $grid);
261 }
262
263 public static function getFormMeta($formId, $metaKey, $default = '', $forced = false)
264 {
265 $formattedValues = self::$formMetaCache[$formId] ?? [];
266
267 if (!isset(self::$formMetaCache[$formId]) || $forced) {
268 $formMetas = FormMeta::where('form_id', $formId)
269 ->get();
270
271 $formattedValues = [];
272 foreach ($formMetas as $formMeta) {
273 $value = $formMeta->value;
274
275 $decoded = json_decode($value ?? '', true);
276 if (is_array($decoded)) {
277 $value = $decoded;
278 }
279
280 $formattedValues[$formMeta->meta_key] = $value;
281 }
282 self::$formMetaCache[$formId] = $formattedValues;
283 }
284
285 return Arr::get($formattedValues, $metaKey, $default);
286 }
287
288
289 public static function setFormMeta($formId, $metaKey, $value)
290 {
291 if ($meta = FormMeta::persist($formId, $metaKey, $value)) {
292 // Update the cache with the new value
293 if (!isset(self::$formMetaCache[$formId])) {
294 self::$formMetaCache[$formId] = [];
295 }
296 self::$formMetaCache[$formId][$metaKey] = $value;
297
298 return $meta->id;
299 }
300 return null;
301 }
302
303 public static function deleteFormMeta($formId, $metaKey)
304 {
305 try {
306 FormMeta::remove($formId, $metaKey);
307 return true;
308 } catch (\Exception $ex) {
309 return null;
310 }
311 }
312
313 /**
314 * Resolve an entry's column => value map regardless of whether the entry is
315 * a stdClass DB row (columns are real properties) or a WPFluent Model
316 * (columns live in an internal attribute bag reached via __get).
317 *
318 * @param object|array $entry
319 * @return array
320 */
321 public static function getEntryColumns($entry)
322 {
323 if (is_object($entry) && method_exists($entry, 'getAttributes')) {
324 return $entry->getAttributes();
325 }
326
327 return (array) $entry;
328 }
329
330 public static function getSubmissionMeta($submissionId, $metaKey, $default = false)
331 {
332 return SubmissionMeta::retrieve($metaKey, $submissionId, $default);
333 }
334
335 public static function setSubmissionMeta($submissionId, $metaKey, $value, $formId = false)
336 {
337 if ($meta = SubmissionMeta::persist($submissionId, $metaKey, $value, $formId)) {
338 return $meta->id;
339 }
340 return null;
341 }
342
343 public static function setSubmissionMetaAsArrayPush($submissionId, $metaKey, $value, $formId = false)
344 {
345 if ($meta = SubmissionMeta::persistArray($submissionId, $metaKey, $value, $formId)) {
346 return $meta->id;
347 }
348 return null;
349 }
350
351 public static function isEntryAutoDeleteEnabled($formId)
352 {
353 if (
354 'yes' == ArrayHelper::get(static::getFormMeta($formId, 'formSettings', []), 'delete_entry_on_submission',
355 '')
356 ) {
357 return true;
358 }
359 return false;
360 }
361
362 public static function formExtraCssClass($form)
363 {
364 if (!$form->settings) {
365 $formSettings = static::getFormMeta($form->id, 'formSettings');
366 } else {
367 $formSettings = $form->settings;
368 }
369
370 if (!$formSettings) {
371 return '';
372 }
373
374 if ($extraClass = ArrayHelper::get($formSettings, 'form_extra_css_class')) {
375 return esc_attr($extraClass);
376 }
377
378 return '';
379 }
380
381 public static function getNextTabIndex($increment = 1)
382 {
383 if (static::isTabIndexEnabled()) {
384 static::$tabIndex += $increment;
385
386 return static::$tabIndex;
387 }
388
389 return '';
390 }
391
392 public static function getFormInstaceClass($formId)
393 {
394 static::$formInstance += 1;
395
396 return 'ff_form_instance_' . $formId . '_' . static::$formInstance;
397 }
398
399 public static function resetTabIndex()
400 {
401 static::$tabIndex = 0;
402 }
403
404 public static function isFluentAdminPage()
405 {
406 $fluentPages = [
407 'fluent_forms',
408 'fluent_forms_all_entries',
409 'fluent_forms_transfer',
410 'fluent_forms_settings',
411 'fluent_forms_add_ons',
412 'fluent_forms_docs',
413 'fluent_forms_payment_entries',
414 'fluent_forms_reports'
415 ];
416
417 $status = true;
418
419 $page = wpFluentForm('request')->get('page');
420
421 if (!$page || !in_array($page, $fluentPages)) {
422 $status = false;
423 }
424
425 $status = apply_filters_deprecated(
426 'fluentform_is_admin_page',
427 [
428 $status
429 ],
430 FLUENTFORM_FRAMEWORK_UPGRADE,
431 'fluentform/is_admin_page',
432 'Use fluentform/is_admin_page instead of fluentform_is_admin_page.'
433 );
434
435 return apply_filters('fluentform/is_admin_page', $status);
436 }
437
438 public static function getShortCodeIds($content, $tag = 'fluentform', $selector = 'id')
439 {
440 if (false === strpos($content, '[')) {
441 return [];
442 }
443
444 preg_match_all('/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER);
445 if (empty($matches)) {
446 return [];
447 }
448
449 $ids = [];
450 $attributes = [];
451
452 foreach ($matches as $shortcode) {
453 if (count($shortcode) >= 2 && $tag === $shortcode[2]) {
454 // Replace braces with empty string.
455 $parsedCode = str_replace(['[', ']', '&#91;', '&#93;'], '', $shortcode[0]);
456
457 $result = shortcode_parse_atts($parsedCode);
458
459 if (!empty($result[$selector])) {
460 if ($tag == 'fluentform' && !empty($result['type']) && $result['type'] == 'conversational') {
461 continue;
462 }
463
464 $ids[$result[$selector]] = $result[$selector];
465
466 $theme = ArrayHelper::get($result, 'theme');
467
468 if ($theme) {
469 $attributes[] = [
470 'formId' => $result[$selector],
471 'theme' => $theme
472 ];
473 }
474 }
475 }
476 }
477
478 if ($attributes) {
479 $ids['attributes'] = $attributes;
480 }
481
482 return $ids;
483 }
484
485 public static function getFormsIdsFromBlocks($content)
486 {
487 $ids = [];
488 $attributes = [];
489
490 if (!function_exists('parse_blocks')) {
491 return $ids;
492 }
493
494 $has_block = false !== strpos($content, '<!-- wp:fluentfom/guten-block' . ' ');
495
496 if (!$has_block) {
497 return $ids;
498 }
499
500 $parsedBlocks = parse_blocks($content);
501 foreach ($parsedBlocks as $block) {
502 if (!ArrayHelper::exists($block, 'blockName') || !ArrayHelper::get($block, 'attrs.formId')) {
503 continue;
504 }
505
506 $hasBlock = strpos($block['blockName'], 'fluentfom/guten-block') === 0;
507 if ($hasBlock) {
508 $formId = (int)$block['attrs']['formId'];
509
510 $ids[] = $formId;
511
512 $theme = ArrayHelper::get($block, 'attrs.themeStyle');
513
514 if ($theme) {
515 $attributes[] = [
516 'formId' => $formId,
517 'theme' => $theme
518 ];
519 }
520 }
521 }
522
523 if ($attributes) {
524 $ids['attributes'] = $attributes;
525 }
526
527 return $ids;
528 }
529
530 public static function isTabIndexEnabled()
531 {
532 if ('na' == static::$tabIndexStatus) {
533 $globalSettings = get_option('_fluentform_global_form_settings');
534 static::$tabIndexStatus = 'yes' == ArrayHelper::get($globalSettings, 'misc.tabIndex');
535 }
536
537 return static::$tabIndexStatus;
538 }
539
540 public static function isMultiStepForm($formOrId)
541 {
542 // Accept both form object and form ID to avoid re-querying
543 if (is_object($formOrId)) {
544 $form = $formOrId;
545 } else {
546 $form = Form::find($formOrId);
547 }
548
549 if (!$form) {
550 return false;
551 }
552
553 $fieldsJson = (string)($form->form_fields ?? '');
554 if ($fieldsJson === '') {
555 return false;
556 }
557
558 $fields = json_decode($fieldsJson, true);
559 if (!is_array($fields)) {
560 return false;
561 }
562
563 return (bool)ArrayHelper::get($fields, 'stepsWrapper');
564 }
565
566 public static function hasFormElement($formId, $elementName)
567 {
568 $form = Form::find($formId);
569 $fieldsJson = $form->form_fields;
570
571 return false != strpos($fieldsJson, '"element":"' . $elementName . '"');
572 }
573
574 public static function isUniqueValidation($validation, $field, $formData, $fields, $form)
575 {
576 if ('yes' == ArrayHelper::get($field, 'raw.settings.is_unique')) {
577 $fieldName = ArrayHelper::get($field, 'name');
578 if ($inputValue = ArrayHelper::get($formData, $fieldName)) {
579 $exist = EntryDetails::where('form_id', $form->id)
580 ->where('field_name', $fieldName)
581 ->where('field_value', $inputValue)
582 ->exists();
583
584 // if form has pending payment then the value doesn't exist in EntryDetails table
585 // further checking on Submission table if the value exists
586 if (!$exist && $form->has_payment) {
587 $escapedKey = json_encode($fieldName);
588 $escapedValue = json_encode($inputValue);
589 $searchPattern = trim($escapedKey, '"') . '":' . $escapedValue;
590 $searchPattern = addcslashes($searchPattern, '%_');
591
592 $exist = Submission::where('form_id', $form->id)
593 ->where('response', 'LIKE', '%' . $searchPattern . '%')
594 ->exists();
595 }
596
597 if ($exist) {
598 $typeName = ArrayHelper::get($field, 'element', 'input_text');
599 return [
600 'unique' => apply_filters('fluentform/validation_message_unique_' . $typeName,
601 ArrayHelper::get($field, 'raw.settings.unique_validation_message'), $field),
602 ];
603 }
604 }
605 }
606
607 return $validation;
608 }
609
610
611 public static function hasPartialEntries($formId)
612 {
613 static $cache = [];
614 if (isset($cache[$formId])) {
615 return $cache[$formId];
616 }
617
618 $cache[$formId] = 'yes' == static::getFormMeta($formId, 'form_save_state_status');
619
620 return $cache[$formId];
621 }
622
623 public static function getNumericFormatters()
624 {
625 $data = [
626 'none' => [
627 'value' => '',
628 'label' => 'None',
629 ],
630 'comma_dot_style' => [
631 'value' => 'comma_dot_style',
632 'label' => __('US Style with Decimal (EX: 123,456.00)', 'fluentform'),
633 'settings' => [
634 'decimal' => '.',
635 'separator' => ',',
636 'precision' => 2,
637 'symbol' => '',
638 ],
639 ],
640 'dot_comma_style_zero' => [
641 'value' => 'dot_comma_style_zero',
642 'label' => __('US Style without Decimal (Ex: 123,456,789)', 'fluentform'),
643 'settings' => [
644 'decimal' => '.',
645 'separator' => ',',
646 'precision' => 0,
647 'symbol' => '',
648 ],
649 ],
650 'dot_comma_style' => [
651 'value' => 'dot_comma_style',
652 'label' => __('EU Style with Decimal (Ex: 123.456,00)', 'fluentform'),
653 'settings' => [
654 'decimal' => ',',
655 'separator' => '.',
656 'precision' => 2,
657 'symbol' => '',
658 ],
659 ],
660 'comma_dot_style_zero' => [
661 'value' => 'comma_dot_style_zero',
662 'label' => __('EU Style without Decimal (EX: 123.456.789)', 'fluentform'),
663 'settings' => [
664 'decimal' => ',',
665 'separator' => '.',
666 'precision' => 0,
667 'symbol' => '',
668 ],
669 ],
670 ];
671
672 $data = apply_filters_deprecated(
673 'fluentform_numeric_styles',
674 [
675 $data
676 ],
677 FLUENTFORM_FRAMEWORK_UPGRADE,
678 'fluentform/numeric_styles',
679 'Use fluentform/numeric_styles instead of fluentform_numeric_styles.'
680 );
681
682 return apply_filters('fluentform/numeric_styles', $data);
683 }
684
685 public static function getNumericValue($input, $formatterName)
686 {
687 $formatters = static::getNumericFormatters();
688 if (empty($formatters[$formatterName]['settings'])) {
689 return $input;
690 }
691 $settings = $formatters[$formatterName]['settings'];
692 $number = floatval(str_replace($settings['decimal'], '.',
693 preg_replace('/[^-?\d' . preg_quote($settings['decimal']) . ']/', '', $input)));
694
695 return number_format($number, $settings['precision'], '.', '');
696 }
697
698 public static function getNumericFormatted($input, $formatterName)
699 {
700 if (!is_numeric($input)) {
701 return $input;
702 }
703 $formatters = static::getNumericFormatters();
704 if (empty($formatters[$formatterName]['settings'])) {
705 return $input;
706 }
707 $settings = $formatters[$formatterName]['settings'];
708
709 return number_format($input, $settings['precision'], $settings['decimal'], $settings['separator']);
710 }
711
712 public static function getDuplicateFieldNames($fields)
713 {
714 $fields = json_decode($fields, true);
715 $items = $fields['fields'];
716 $inputNames = static::getFieldNamesStatuses($items);
717 $uniqueNames = array_unique($inputNames);
718
719 if (count($inputNames) == count($uniqueNames)) {
720 return [];
721 }
722
723 return array_diff_assoc($inputNames, $uniqueNames);
724 }
725
726 public static function getRankingFieldsWithDuplicateOptionValues($fields)
727 {
728 if (is_string($fields)) {
729 $fields = json_decode($fields, true);
730 }
731
732 if (!is_array($fields)) {
733 return [];
734 }
735
736 $items = ArrayHelper::get($fields, 'fields', []);
737
738 if (!is_array($items)) {
739 return [];
740 }
741
742 return static::collectRankingFieldsWithDuplicateOptionValues($items);
743 }
744
745 protected static function getFieldNamesStatuses($fields)
746 {
747 $names = [];
748
749 foreach ($fields as $field) {
750 if ('container' == ArrayHelper::get($field, 'element')) {
751 $columns = ArrayHelper::get($field, 'columns', []);
752 foreach ($columns as $column) {
753 $columnInputs = static::getFieldNamesStatuses(ArrayHelper::get($column, 'fields', []));
754 $names = array_merge($names, $columnInputs);
755 }
756 } else {
757 if ($name = ArrayHelper::get($field, 'attributes.name')) {
758 $names[] = $name;
759 }
760 }
761 }
762
763 return $names;
764 }
765
766 protected static function collectRankingFieldsWithDuplicateOptionValues($fields)
767 {
768 $duplicates = [];
769
770 foreach ($fields as $field) {
771 if ('container' === ArrayHelper::get($field, 'element')) {
772 $columns = ArrayHelper::get($field, 'columns', []);
773 foreach ($columns as $column) {
774 $columnFields = ArrayHelper::get($column, 'fields', []);
775 $duplicates = array_merge(
776 $duplicates,
777 static::collectRankingFieldsWithDuplicateOptionValues($columnFields)
778 );
779 }
780 continue;
781 }
782
783 if (!empty($field['fields']) && is_array($field['fields'])) {
784 $duplicates = array_merge(
785 $duplicates,
786 static::collectRankingFieldsWithDuplicateOptionValues($field['fields'])
787 );
788 continue;
789 }
790
791 if ('input_ranking' !== ArrayHelper::get($field, 'element')) {
792 continue;
793 }
794
795 $formattedOptions = ArrayHelper::get($field, 'settings.advanced_options', []);
796 if (!$formattedOptions) {
797 $formattedOptions = [];
798 foreach (ArrayHelper::get($field, 'options', []) as $value => $label) {
799 $formattedOptions[] = [
800 'label' => $label,
801 'value' => $value,
802 ];
803 }
804 }
805
806 $optionValues = array_values(array_filter(array_map(
807 'sanitize_text_field',
808 array_column(static::flattenAdvancedOptions($formattedOptions), 'value')
809 ), function ($value) {
810 return $value !== '';
811 }));
812
813 if (count($optionValues) !== count(array_unique($optionValues))) {
814 $duplicates[] = ArrayHelper::get($field, 'settings.admin_field_label')
815 ?: ArrayHelper::get($field, 'settings.label')
816 ?: ArrayHelper::get($field, 'attributes.name')
817 ?: __('Ranking Field', 'fluentform');
818 }
819 }
820
821 return $duplicates;
822 }
823
824 public static function isConversionForm($formId)
825 {
826 static $cache = [];
827 if (isset($cache[$formId])) {
828 return $cache[$formId];
829 }
830
831 $cache[$formId] = 'yes' == static::getFormMeta($formId, 'is_conversion_form');
832
833 return $cache[$formId];
834 }
835
836 public static function getPreviewUrl($formId, $type = '')
837 {
838 if ('conversational' == $type) {
839 return static::getConversionUrl($formId);
840 } elseif ('classic' == $type) {
841 return site_url('?fluent_forms_pages=1&design_mode=1&preview_id=' . $formId) . '#ff_preview';
842 } else {
843 if (static::isConversionForm($formId)) {
844 return static::getConversionUrl($formId);
845 }
846 }
847
848 return site_url('?fluent_forms_pages=1&design_mode=1&preview_id=' . $formId) . '#ff_preview';
849 }
850
851 public static function getFormAdminPermalink($route, $form)
852 {
853 $baseUrl = admin_url('admin.php?page=fluent_forms');
854
855 return $baseUrl . '&route=' . $route . '&form_id=' . $form->id;
856 }
857
858 public static function getFormSettingsUrl($form)
859 {
860 $baseUrl = admin_url('admin.php?page=fluent_forms');
861
862 return $baseUrl . '&form_id=' . $form->id . '&route=settings&sub_route=form_settings#basic_settings';
863 }
864
865 private static function getConversionUrl($formId)
866 {
867 $meta = static::getFormMeta($formId, 'ffc_form_settings_meta', []);
868 $key = ArrayHelper::get($meta, 'share_key', '');
869
870 $slug = apply_filters_deprecated(
871 'fluentform_conversational_url_slug',
872 [
873 'fluent-form'
874 ],
875 FLUENTFORM_FRAMEWORK_UPGRADE,
876 'fluentform/conversational_url_slug',
877 'Use fluentform/conversational_url_slug instead of fluentform_conversational_url_slug.'
878 );
879
880 $paramKey = apply_filters('fluentform/conversational_url_slug', $slug);
881
882 if ('form' == $paramKey) {
883 $paramKey = 'fluent-form';
884 }
885 if ($key) {
886 return static::getFrontendFacingUrl('?' . $paramKey . '=' . $formId . '&form=' . $key);
887 }
888 return static::getFrontendFacingUrl('?' . $paramKey . '=' . $formId);
889 }
890
891 public static function fileUploadLocations()
892 {
893 $locations = [
894 [
895 'value' => 'default',
896 'label' => __('Fluent Forms Default', 'fluentform'),
897 ],
898 [
899 'value' => 'wp_media',
900 'label' => __('Media Library', 'fluentform'),
901 ],
902 ];
903
904 $locations = apply_filters_deprecated(
905 'fluentform_file_upload_options',
906 [
907 $locations
908 ],
909 FLUENTFORM_FRAMEWORK_UPGRADE,
910 'fluentform/file_upload_options',
911 'Use fluentform/file_upload_options instead of fluentform_file_upload_options'
912 );
913
914 return apply_filters('fluentform/file_upload_options', $locations);
915 }
916
917 public static function unreadCount($formId)
918 {
919 return Submission::where('status', 'unread')
920 ->where('form_id', $formId)
921 ->count();
922 }
923
924 public static function getForms()
925 {
926 $ff_list = Form::select(['id', 'title'])->orderBy('id', 'DESC')->get();
927 $forms = [];
928
929 if (count($ff_list) > 0) {
930 $forms[0] = esc_html__('Select a Fluent Forms', 'fluentform');
931 foreach ($ff_list as $form) {
932 $forms[$form->id] = esc_html($form->title) . ' (' . $form->id . ')';
933 }
934 } else {
935 $forms[0] = esc_html__('Create a Form First', 'fluentform');
936 }
937
938 return $forms;
939 }
940
941 public static function replaceBrTag($content, $with = '')
942 {
943 if (is_array($content)) {
944 foreach ($content as $key => $value) {
945 $content[$key] = static::replaceBrTag($value, $with);
946 }
947 } elseif (static::hasBrTag($content)) {
948 $content = str_replace('<br />', $with, $content);
949 }
950
951 return $content;
952 }
953
954 public static function hasBrTag($content)
955 {
956 return is_string($content) && false !== strpos($content, '<br />');
957 }
958
959 public static function sanitizeForCSV($content)
960 {
961 $formulas = ['=', '-', '+', '@', "\t", "\r"];
962
963 $formulas = apply_filters('fluentform/csv_sanitize_formulas', $formulas);
964
965 if (Str::startsWith($content, $formulas)) {
966 $content = "'" . $content;
967 }
968
969 return $content;
970 }
971
972 public static function sanitizeOrderValue($orderType = '')
973 {
974 $orderBys = ['ASC', 'DESC'];
975
976 $orderType = trim(strtoupper($orderType));
977
978 return in_array($orderType, $orderBys) ? $orderType : 'DESC';
979 }
980
981 public static function getForm($id)
982 {
983 return Form::where('id', $id)->first();
984 }
985
986 public static function shouldHidePassword($formId)
987 {
988 $isTruncate = apply_filters_deprecated(
989 'fluentform_truncate_password_values',
990 [
991 true,
992 $formId
993 ],
994 FLUENTFORM_FRAMEWORK_UPGRADE,
995 'fluentform/truncate_password_values',
996 'Use fluentform/truncate_password_values instead of fluentform_truncate_password_values.'
997 );
998
999 return apply_filters('fluentform/truncate_password_values', $isTruncate, $formId) &&
1000 (
1001 (defined('FLUENTFORM_RENDERING_ENTRIES') && FLUENTFORM_RENDERING_ENTRIES) ||
1002 (defined('FLUENTFORM_RENDERING_ENTRY') && FLUENTFORM_RENDERING_ENTRY) ||
1003 (defined('FLUENTFORM_EXPORTING_ENTRIES') && FLUENTFORM_EXPORTING_ENTRIES)
1004 );
1005 }
1006
1007 // make tabular-grid value markdown format
1008 public static function getTabularGridFormatValue(
1009 $girdData,
1010 $field,
1011 $rowJoiner = '<br />',
1012 $colJoiner = ', ',
1013 $type = ''
1014 )
1015 {
1016 if (!$girdData || !$field) {
1017 return '';
1018 }
1019 $girdRows = ArrayHelper::get($field, 'raw.settings.grid_rows', []);
1020 $girdRows = fluentFormSanitizer($girdRows);
1021 $girdCols = ArrayHelper::get($field, 'raw.settings.grid_columns', []);
1022 $girdCols = fluentFormSanitizer($girdCols);
1023
1024 $value = '';
1025 $lastRow = key(array_slice($girdData, -1, 1, true));
1026 foreach ($girdData as $row => $column) {
1027 $_row = $row;
1028 if ($girdRows && isset($girdRows[$row])) {
1029 $row = $girdRows[$row];
1030 }
1031 if ('markdown' === $type) {
1032 $value .= '- *' . $row . '* : ';
1033 } else {
1034 $value .= $row . ': ';
1035 }
1036 if (is_array($column)) {
1037 foreach ($column as $index => $item) {
1038 $_colJoiner = $colJoiner;
1039 if ($girdCols && isset($girdCols[$item])) {
1040 $item = $girdCols[$item];
1041 }
1042 if ($index == (count($column) - 1)) {
1043 $_colJoiner = '';
1044 }
1045 $value .= $item . $_colJoiner;
1046 }
1047 } else {
1048 if ($girdCols && isset($girdCols[$column])) {
1049 $column = $girdCols[$column];
1050 }
1051 $value .= $column;
1052 }
1053 if ($_row != $lastRow) {
1054 $value .= $rowJoiner;
1055 }
1056 }
1057
1058 return $value;
1059 }
1060
1061 public static function getInputNameFromShortCode($value)
1062 {
1063 preg_match('/{+(.*?)}/', $value, $matches);
1064 if ($matches && false !== strpos($matches[1], 'inputs.')) {
1065 return substr($matches[1], strlen('inputs.'));
1066 }
1067
1068 return '';
1069 }
1070
1071 public static function getRestInfo()
1072 {
1073 $config = wpFluentForm('config');
1074
1075 $namespace = $config->get('app.rest_namespace');
1076 $version = $config->get('app.rest_version');
1077 $restUrl = rest_url($namespace . '/' . $version);
1078 $restUrl = rtrim($restUrl, '/\\');
1079
1080 return [
1081 'base_url' => esc_url_raw(rest_url()),
1082 'url' => $restUrl,
1083 'nonce' => wp_create_nonce('wp_rest'),
1084 'namespace' => $namespace,
1085 'version' => $version,
1086 ];
1087 }
1088
1089 public static function getLogInitiator($action, $type = 'log')
1090 {
1091 if ('log' === $type) {
1092 $title = ucwords(implode(' ', preg_split('/(?=[A-Z])/', $action)));
1093 } else {
1094 $title = ucwords(
1095 str_replace(
1096 ['fluentform/integration_notify_', 'fluentform_', '_notification_feed', '_'],
1097 ['', '', '', ' '],
1098 $action
1099 )
1100 );
1101 }
1102
1103 return $title;
1104 }
1105
1106 public static function getIpinfo()
1107 {
1108 return ArrayHelper::get(get_option('_fluentform_global_form_settings'), 'misc.geo_provider_token');
1109 }
1110
1111 public static function isAutoloadCaptchaEnabled()
1112 {
1113 return ArrayHelper::get(get_option('_fluentform_global_form_settings'), 'misc.autoload_captcha');
1114 }
1115
1116 public static function isAutosaveEnabled()
1117 {
1118 $autosaveEnabled = ArrayHelper::get(get_option('_fluentform_global_form_settings'), 'misc.autosave_enabled', 'no');
1119 return $autosaveEnabled === 'yes';
1120 }
1121
1122 public static function maybeDecryptUrl($url)
1123 {
1124 $uploadDir = str_replace('/', '\/', FLUENTFORM_UPLOAD_DIR . '/temp');
1125 $pattern = "/(?<={$uploadDir}\/).*$/";
1126 preg_match($pattern, $url, $match);
1127 if (!empty($match)) {
1128 $url = str_replace($match[0], Protector::decrypt($match[0]), $url);
1129 }
1130 return $url;
1131 }
1132
1133 public static function arrayFilterRecursive($arrayItems)
1134 {
1135 foreach ($arrayItems as $key => $item) {
1136 is_array($item) && $arrayItems[$key] = self::arrayFilterRecursive($item);
1137 if (empty($arrayItems[$key])) {
1138 unset($arrayItems[$key]);
1139 }
1140 }
1141 return $arrayItems;
1142 }
1143
1144 public static function isBlockEditor()
1145 {
1146 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking REST API context
1147 return defined('REST_REQUEST') && REST_REQUEST && !empty($_REQUEST['context']) && $_REQUEST['context'] === 'edit';
1148 }
1149
1150 public static function resolveValidationRulesGlobalOption(&$field)
1151 {
1152 if (isset($field['fields']) && is_array($field['fields'])) {
1153 foreach ($field['fields'] as &$subField) {
1154 static::resolveValidationRulesGlobalOption($subField);
1155 }
1156 } else {
1157 if (ArrayHelper::get($field, 'settings.validation_rules')) {
1158 foreach ($field['settings']['validation_rules'] as $key => &$rule) {
1159 if (!isset($rule['global'])) {
1160 $rule['global'] = false;
1161 }
1162 $rule['global_message'] = static::getGlobalDefaultMessage($key);
1163 }
1164 }
1165 }
1166 }
1167
1168 /**
1169 * Validate form input value against database values
1170 *
1171 * @param $field array Form Field
1172 * @param $formData array From Data
1173 * @param $form object From
1174 * @param $fieldName string optional
1175 * @param $inputValue mixed optional
1176 *
1177 * @return string
1178 * Return Error message on fail. Otherwise, return empty string
1179 */
1180 public static function validateInput($field, $formData, $form, $fieldName = '', $inputValue = [])
1181 {
1182 $error = '';
1183 if (!$fieldName) {
1184 $fieldName = ArrayHelper::get($field, 'name');
1185 }
1186 if (!$fieldName) {
1187 return $error;
1188 }
1189 if (!$inputValue) {
1190 $inputValue = ArrayHelper::get($formData, $fieldName);
1191 }
1192 if ($inputValue) {
1193 $rawField = ArrayHelper::get($field, 'raw');
1194 if (!$rawField) {
1195 $rawField = $field;
1196 }
1197 $fieldType = ArrayHelper::get($rawField, 'element');
1198 $rawField = apply_filters('fluentform/rendering_field_data_' . $fieldType, $rawField, $form);
1199 $options = [];
1200 if ("net_promoter_score" === $fieldType) {
1201 $options = array_flip(ArrayHelper::get($rawField, 'options', []));
1202 } elseif ('ratings' == $fieldType) {
1203 $options = array_keys(ArrayHelper::get($rawField, 'options', []));
1204 } elseif ('gdpr_agreement' == $fieldType) {
1205 $options = ['on'];
1206 } elseif ('terms_and_condition' == $fieldType) {
1207 $options = ['on', 'off'];
1208 } elseif (in_array($fieldType, ['input_radio', 'select', 'input_checkbox', 'input_ranking'])) {
1209 if (ArrayHelper::isTrue($rawField, 'attributes.multiple')) {
1210 $fieldType = 'multi_select';
1211 }
1212 $formattedOptions = ArrayHelper::get($rawField, 'settings.advanced_options', []);
1213 if (!$formattedOptions) {
1214 $formattedOptions = [];
1215 foreach (ArrayHelper::get($rawField, 'options', []) as $value => $label) {
1216 $formattedOptions[] = [
1217 'label' => $label,
1218 'value' => $value,
1219 ];
1220 }
1221 // @todo : Update all reference in form templates
1222 }
1223
1224 $options = array_column(self::flattenAdvancedOptions($formattedOptions), 'value');
1225
1226 // Add field-specific __ff_other__ to options if "Other" option is enabled
1227 if (in_array($fieldType, ['input_checkbox', 'input_radio']) &&
1228 ArrayHelper::get($rawField, 'settings.enable_other_option') === 'yes') {
1229 $fieldName = sanitize_key(str_replace(['[', ']'], '', ArrayHelper::get($rawField, 'attributes.name', '')));
1230 $options[] = '__ff_other_' . $fieldName . '__';
1231 }
1232 } elseif ("dynamic_field" == $fieldType) {
1233 $dynamicFetchValue = 'yes' == ArrayHelper::get($rawField, 'settings.dynamic_fetch');
1234 if ($dynamicFetchValue) {
1235 $rawField = apply_filters('fluentform/dynamic_field_re_fetch_result_and_resolve_value', $rawField);
1236 }
1237 $dfElementType = ArrayHelper::get($rawField, 'attributes.type');
1238 if (in_array($dfElementType, ['radio', 'select', 'checkbox'])) {
1239 $fieldType = 'dynamic_field_options';
1240 $options = array_column(
1241 ArrayHelper::get($rawField, 'settings.advanced_options', []),
1242 'value'
1243 );
1244 }
1245 }
1246
1247 if ($options) {
1248 $options = array_map('sanitize_text_field', $options);
1249 }
1250
1251 $isValid = true;
1252 switch ($fieldType) {
1253 case 'input_ranking':
1254 $skipValidationInputsWithOptions = apply_filters('fluentform/skip_validation_inputs_with_options', false, $fieldType, $form, $formData);
1255 if ($skipValidationInputsWithOptions) {
1256 break;
1257 }
1258
1259 if (!is_array($inputValue)) {
1260 $isValid = false;
1261 break;
1262 }
1263
1264 $filteredValues = array_values(array_filter(array_map('sanitize_text_field', $inputValue), function ($value) {
1265 return $value !== '';
1266 }));
1267
1268 $normalizedOptions = array_values(array_filter(array_map('sanitize_text_field', $options), function ($value) {
1269 return $value !== '';
1270 }));
1271
1272 sort($filteredValues);
1273 sort($normalizedOptions);
1274
1275 $isValid = count($inputValue) === count($options)
1276 && count($filteredValues) === count(array_unique($filteredValues))
1277 && $filteredValues === $normalizedOptions;
1278 break;
1279 case 'input_radio':
1280 case 'select':
1281 case 'net_promoter_score':
1282 case 'ratings':
1283 case 'gdpr_agreement':
1284 case 'terms_and_condition':
1285 case 'input_checkbox':
1286 case 'multi_select':
1287 case 'dynamic_field_options':
1288
1289 $skipValidationInputsWithOptions = apply_filters('fluentform/skip_validation_inputs_with_options', false, $fieldType, $form, $formData);
1290 if ($skipValidationInputsWithOptions) {
1291 break;
1292 }
1293 if (is_array($inputValue)) {
1294 // Handle field-specific "Other" options for checkboxes
1295 $filteredValues = array_filter($inputValue, function($value) {
1296 // Skip field-specific other values and processed other values
1297 return !preg_match('/^__ff_other_.*__$/', $value) &&
1298 !preg_match('/^Other:\s/', $value);
1299 });
1300 $isValid = array_diff($filteredValues, $options);
1301 $isValid = empty($isValid);
1302 } else {
1303 // Handle field-specific "Other" option for single values
1304 if (preg_match('/^__ff_other_.*__$/', $inputValue) ||
1305 preg_match('/^Other:\s/', $inputValue)) {
1306 $isValid = true;
1307 } else {
1308 $isValid = in_array($inputValue, $options);
1309 }
1310 }
1311 break;
1312 case 'input_number':
1313 if (is_array($inputValue)) {
1314 $hasNonNumricValue = in_array(false, array_map('is_numeric', $inputValue));
1315 if ($hasNonNumricValue) {
1316 $isValid = false;
1317 }
1318 } else {
1319 $isValid = is_numeric($inputValue);
1320 }
1321 break;
1322 case 'select_country':
1323 $fieldData = ArrayHelper::get($field, 'raw');
1324 $data = (new SelectCountry())->loadCountries($fieldData);
1325 $validCountries = ArrayHelper::get($fieldData, 'settings.country_list.priority_based', []);
1326 $validCountries = array_merge($validCountries, array_keys(ArrayHelper::get($data, 'options')));
1327 $isValid = in_array($inputValue, $validCountries);
1328 break;
1329 case 'repeater_field':
1330 case 'repeater_container':
1331 foreach (ArrayHelper::get($rawField, 'fields', []) as $index => $repeaterField) {
1332 $repeaterFieldValue = array_filter(array_column($inputValue, $index));
1333 if ($repeaterFieldValue && $error = static::validateInput($repeaterField, $formData, $form,
1334 $fieldName, $repeaterFieldValue)) {
1335 $isValid = false;
1336 break;
1337 }
1338 }
1339 break;
1340 case 'tabular_grid':
1341 $rows = array_keys(ArrayHelper::get($rawField, 'settings.grid_rows', []));
1342 $rows = array_map(function ($row) {
1343 return trim(sanitize_text_field($row));
1344 }, $rows);
1345
1346 $submittedRows = array_keys(ArrayHelper::get($formData, $fieldName, []));
1347 $submittedRows = array_map('trim', $submittedRows);
1348
1349 $rowDiff = array_diff($submittedRows, $rows);
1350
1351 $isValid = empty($rowDiff);
1352 if ($isValid) {
1353 $columns = array_keys(ArrayHelper::get($rawField, 'settings.grid_columns', []));
1354 $columns = array_map(function ($column) {
1355 return trim(sanitize_text_field($column));
1356 }, $columns);
1357 $submittedCols = ArrayHelper::flatten(ArrayHelper::get($formData, $fieldName, []));
1358 $submittedCols = array_map('trim', $submittedCols);
1359 $colDiff = array_diff($submittedCols, $columns);
1360 $isValid = empty($colDiff);
1361 }
1362 break;
1363 default:
1364 break;
1365 }
1366 if (!$isValid) {
1367 $error = __('The given data was invalid', 'fluentform');
1368 }
1369 }
1370 return $error;
1371 }
1372
1373 public static function getWhiteListedFields($formId)
1374 {
1375 $whiteListedFields = [
1376 '__fluent_form_embded_post_id',
1377 '_fluentform_' . $formId . '_fluentformnonce',
1378 '_wp_http_referer',
1379 'g-recaptcha-response',
1380 'h-captcha-response',
1381 'cf-turnstile-response',
1382 '__stripe_payment_method_id',
1383 '__ff_all_applied_coupons',
1384 '__entry_intermediate_hash',
1385 '__square_payment_method_id',
1386 '__square_verify_buyer_id',
1387 'ct_bot_detector_event_token',
1388 'ff_ct_form_load_time'
1389 ];
1390
1391 return apply_filters('fluentform/white_listed_fields', $whiteListedFields, $formId);
1392 }
1393
1394 /**
1395 * Shortcode parse on validation message
1396 * @param string $message
1397 * @param object $form
1398 * @param string $fieldName
1399 * @return string
1400 */
1401 public static function shortCodeParseOnValidationMessage($message, $form, $fieldName)
1402 {
1403 // Return early if form is null to prevent errors
1404 if ($form === null) {
1405 return $message;
1406 }
1407
1408 // For validation message there is no entry & form data
1409 // Add 'current_field' name as data array to resolve {labels.current_field} shortcode if it has
1410 return ShortCodeParser::parse(
1411 $message,
1412 (object)['response' => "", 'form_id' => $form->id],
1413 ['current_field' => $fieldName],
1414 $form
1415 );
1416 }
1417
1418 public static function getAjaxUrl()
1419 {
1420 return apply_filters('fluentform/ajax_url', admin_url('admin-ajax.php'));
1421 }
1422
1423 public static function getDefaultDateTimeFormatForMoment()
1424 {
1425 $phpFormat = get_option('date_format') . ' ' . get_option('time_format');
1426
1427 $replacements = [
1428 'A' => 'A', // for the sake of escaping below
1429 'a' => 'a', // for the sake of escaping below
1430 'B' => '', // Swatch internet time (.beats), no equivalent
1431 'c' => 'YYYY-MM-DD[T]HH:mm:ssZ', // ISO 8601
1432 'D' => 'ddd',
1433 'd' => 'DD',
1434 'e' => 'zz', // deprecated since version 1.6.0 of moment.js
1435 'F' => 'MMMM',
1436 'G' => 'H',
1437 'g' => 'h',
1438 'H' => 'HH',
1439 'h' => 'hh',
1440 'I' => '', // Daylight Saving Time? => moment().isDST();
1441 'i' => 'mm',
1442 'j' => 'D',
1443 'L' => '', // Leap year? => moment().isLeapYear();
1444 'l' => 'dddd',
1445 'M' => 'MMM',
1446 'm' => 'MM',
1447 'N' => 'E',
1448 'n' => 'M',
1449 'O' => 'ZZ',
1450 'o' => 'YYYY',
1451 'P' => 'Z',
1452 'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822
1453 'S' => 'o',
1454 's' => 'ss',
1455 'T' => 'z', // deprecated since version 1.6.0 of moment.js
1456 't' => '', // days in the month => moment().daysInMonth();
1457 'U' => 'X',
1458 'u' => 'SSSSSS', // microseconds
1459 'v' => 'SSS', // milliseconds (from PHP 7.0.0)
1460 'W' => 'W', // for the sake of escaping below
1461 'w' => 'e',
1462 'Y' => 'YYYY',
1463 'y' => 'YY',
1464 'Z' => '', // time zone offset in minutes => moment().zone();
1465 'z' => 'DDD',
1466 ];
1467
1468 // Converts escaped characters.
1469 foreach ($replacements as $from => $to) {
1470 $replacements['\\' . $from] = '[' . $from . ']';
1471 }
1472
1473 $format = strtr($phpFormat, $replacements);
1474
1475 return apply_filters('fluentform/moment_date_time_format', $format);
1476 }
1477
1478 public static function isDefaultWPDateEnabled()
1479 {
1480 $globalSettings = get_option('_fluentform_global_form_settings');
1481 return 'wp_default' === ArrayHelper::get($globalSettings, 'misc.default_admin_date_time');
1482 }
1483
1484 public static function isPaymentCompatible()
1485 {
1486 if (!self::hasPro()) {
1487 return true;
1488 } else {
1489 return version_compare(FLUENTFORMPRO_VERSION, FLUENTFORM_MINIMUM_PRO_VERSION, '>=');
1490 }
1491 }
1492
1493 /**
1494 * Determine pro payment script is compatible or not
1495 * Script is compatible if pro version meets the minimum required version
1496 *
1497 * @return bool
1498 */
1499 public static function isProPaymentScriptCompatible()
1500 {
1501 if (self::hasPro()) {
1502 return version_compare(FLUENTFORMPRO_VERSION, FLUENTFORM_MINIMUM_PRO_VERSION, '>=');
1503 }
1504 return false;
1505 }
1506
1507 public static function hasPro()
1508 {
1509 return defined('FLUENTFORMPRO');
1510 }
1511
1512 public static function getLandingPageEnabledForms()
1513 {
1514 if (class_exists(\FluentFormPro\classes\SharePage\SharePage::class)) {
1515 if (method_exists(\FluentFormPro\classes\SharePage\SharePage::class, 'getLandingPageFormIds')) {
1516 $sharePage = new \FluentFormPro\classes\SharePage\SharePage();
1517 return $sharePage->getLandingPageFormIds();
1518 }
1519 }
1520 return [];
1521 }
1522
1523 public static function sanitizeArrayKeysAndValues($values)
1524 {
1525 if (is_array($values)) {
1526 $sanitized = [];
1527 foreach ($values as $key => $value) {
1528 $trimmedKey = sanitize_text_field(trim($key));
1529 $trimmedValue = sanitize_text_field(trim($value));
1530 $sanitized[$trimmedKey] = $trimmedValue;
1531 }
1532 return $sanitized;
1533 }
1534 return sanitize_text_field(trim($values));
1535 }
1536 public static function getFrontendFacingUrl($args = '')
1537 {
1538 return home_url($args);
1539 }
1540
1541 public static function getCountryCodeFromHeaders()
1542 {
1543 $headers = [
1544 // Cloudflare (most common)
1545 'HTTP_CF_IPCOUNTRY',
1546 'CF-IPCountry',
1547
1548 // AWS CloudFront (widely used)
1549 'HTTP_CLOUDFRONT_VIEWER_COUNTRY',
1550 'CloudFront-Viewer-Country',
1551
1552 // Common standard headers
1553 'HTTP_X_COUNTRY_CODE',
1554 'X-Country-Code',
1555 'HTTP_X_FORWARDED_COUNTRY',
1556 'X-Forwarded-Country',
1557
1558 // GeoIP (used by many systems)
1559 'HTTP_GEOIP_COUNTRY_CODE',
1560 'GEOIP_COUNTRY_CODE',
1561 'HTTP_X_GEOIP_COUNTRY',
1562 'X-GeoIP-Country',
1563
1564 // General purpose country headers
1565 'HTTP_X_COUNTRY',
1566 'X-Country',
1567 'HTTP_X_COUNTRY_ISO',
1568 'X-Country-ISO'
1569 ];
1570
1571 foreach ($headers as $header) {
1572 // Try directly from $_SERVER
1573 if (isset($_SERVER[$header])) {
1574 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Country code from CDN/proxy header, validated below
1575 $code = trim(sanitize_text_field(wp_unslash($_SERVER[$header])));
1576 } // Try with HTTP_ prefix if not already present
1577 elseif (strpos($header, 'HTTP_') !== 0) {
1578 $httpHeader = 'HTTP_' . str_replace('-', '_', strtoupper($header));
1579 if (isset($_SERVER[$httpHeader])) {
1580 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Country code from CDN/proxy header, validated below
1581 $code = trim(sanitize_text_field(wp_unslash($_SERVER[$httpHeader])));
1582 } else {
1583 continue;
1584 }
1585 } else {
1586 continue;
1587 }
1588
1589 // Basic validation - should be 2-letter country code
1590 if (!empty($code) && is_string($code) && strlen($code) === 2 && ctype_alpha($code) && $code !== 'XX') {
1591 return strtoupper($code);
1592 }
1593 }
1594
1595 return null;
1596 }
1597
1598 /**
1599 * Fixes PHP Object Injection Vulnerability
1600 * @param $data
1601 * @return mixed
1602 */
1603 public static function safeUnserialize($data)
1604 {
1605 if (is_serialized($data)) { // Don't attempt to unserialize data that wasn't serialized going in.
1606 return @unserialize(trim($data), ['allowed_classes' => false]);
1607 }
1608 return $data;
1609 }
1610
1611 /**
1612 * If elementor editor is open
1613 * @return bool
1614 */
1615 public static function isElementorEditor()
1616 {
1617 return defined('ELEMENTOR_VERSION') &&
1618 class_exists('\Elementor\Plugin') &&
1619 isset(\Elementor\Plugin::$instance) &&
1620 \Elementor\Plugin::$instance->editor->is_edit_mode();
1621 }
1622
1623 /**
1624 * Check if we're in block editor context (Site Editor, Template Editor, or Post/Page Editor)
1625 * Covers all Gutenberg block editor contexts including mobile/tablet preview iframes
1626 * @return bool
1627 */
1628 public static function isSiteEditor()
1629 {
1630 if (!is_admin() || !function_exists('get_current_screen')) {
1631 return false;
1632 }
1633
1634 $screen = get_current_screen();
1635
1636 // Check for Site Editor and Template Editor contexts
1637 if ($screen && in_array($screen->base, ['site-editor', 'edit-site', 'appearance_page_gutenberg-edit-site'], true )) {
1638 return true;
1639 }
1640
1641 // Check for Post/Page block editor contexts
1642 if ($screen && in_array($screen->base, ['post', 'page'], true) && $screen->is_block_editor()) {
1643 return true;
1644 }
1645
1646 // Check for custom post types with block editor
1647 if ($screen && $screen->is_block_editor()) {
1648 return true;
1649 }
1650
1651 // Fallback checks for various block editor contexts
1652 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- REQUEST_URI used for string comparison only
1653 $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
1654 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking block editor context
1655 return isset( $_GET['_wp-find-template'] ) ||
1656 strpos( $request_uri, 'site-editor.php' ) !== false ||
1657 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking REST API context
1658 (defined('REST_REQUEST') && REST_REQUEST && !empty($_REQUEST['context']) && $_REQUEST['context'] === 'edit');
1659 }
1660 }
1661