PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.2
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Helpers / Helper.php

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

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