PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.3
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.3
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.3, at app/Helpers/Helper.php

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