PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.15
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.15
6.2.15 6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 All 197 releases
← All changes | app/Hooks/actions.php +130 -47 6.2.7 → 6.2.15 View file →
@@ -1,11 +1,12 @@
1 1 <?php
2 2
3 -defined('ABSPATH') or die;
3 +defined('ABSPATH') || die;
4 4
5 5 use FluentForm\App\Modules\Component\Component;
6 6 use FluentForm\App\Modules\Acl\Acl;
7 7 use FluentForm\App\Helpers\Helper;
8 +use FluentForm\App\Services\FormBuilder\LegacyDateConfigDecoder;
8 9 use FluentForm\Framework\Helpers\ArrayHelper;
9 10
10 11 /**
11 12 * All registered action's handlers should be in app\Hooks\Handlers,
@@ -10,14 +11,16 @@
10 11 /**
11 12 * All registered action's handlers should be in app\Hooks\Handlers,
12 13 * addAction is similar to add_action and addCustomAction is just a
13 14 * wrapper over add_action which will add a prefix to the hook name
14 - * using the plugin slug to make it unique in all wordpress plugins,
15 + * using the plugin slug to make it unique in all WordPress plugins,
15 16 * ex: $app->addCustomAction('foo', ['FooHandler', 'handleFoo']) is
16 17 * equivalent to add_action('slug-foo', ['FooHandler', 'handleFoo']).
17 18 */
18 19
19 20 /**
21 + * Application instance.
22 + *
20 23 * @var $app FluentForm\Framework\Foundation\Application
21 24 */
22 25
23 26 // From MenuProvider.php
@@ -69,8 +72,23 @@
69 72 (new \FluentForm\App\Modules\Renderer\GlobalSettings\Settings($app))->render();
70 73 }
71 74 );
72 75
76 +/**
77 + * Pro 6.2.13+ supplies the REST API used by Free's Vue license screen. Older
78 + * Pro versions keep rendering their PHP page through this component action.
79 + */
80 +$app->addAction(
81 + 'fluentform/global_settings_component_license_page',
82 + function () use ($app) {
83 + if (!defined('FLUENTFORMPRO_VERSION') || version_compare(FLUENTFORMPRO_VERSION, '6.2.13', '<')) {
84 + return;
85 + }
86 +
87 + (new \FluentForm\App\Modules\Renderer\GlobalSettings\Settings($app))->render('license');
88 + }
89 +);
90 +
73 91 // Register DefaultStyleApplicator on init so it works for REST API requests too
74 92 add_action('init', function () {
75 93 new \FluentForm\App\Modules\Form\DefaultStyleApplicator();
76 94 }, 9);
@@ -150,9 +168,9 @@
150 168 'fluent_forms_docs',
151 169 'fluent_forms_all_entries',
152 170 'msformentries',
153 171 'fluent_forms_payment_entries',
154 - 'fluent_forms_reports'
172 + 'fluent_forms_reports',
155 173 ];
156 174
157 175 $page = wpFluentForm('request')->get('page');
158 176
@@ -160,8 +178,9 @@
160 178 remove_all_actions('admin_notices');
161 179 \FluentForm\App\Modules\Registerer\ReviewQuery::register();
162 180 \FluentForm\App\Modules\Registerer\MigrationNotice::register();
163 181 \FluentForm\App\Modules\Registerer\StripeKeyNotice::register();
182 + \FluentForm\App\Modules\Registerer\CaptchaKeyNotice::register();
164 183 }
165 184 });
166 185
167 186 add_action('wp_print_scripts', function () {
@@ -172,9 +191,9 @@
172 191
173 192 $isSkip = apply_filters_deprecated(
174 193 'fluentform_skip_no_conflict',
175 194 [
176 - $isSkip
195 + $isSkip,
177 196 ],
178 197 FLUENTFORM_FRAMEWORK_UPGRADE,
179 198 'fluentform/skip_no_conflict',
180 199 'Use fluentform/skip_no_conflict instead of fluentform_skip_no_conflict.'
@@ -279,8 +298,50 @@
279 298 if (!isset($element['settings']['dynamic_default_value'])) {
280 299 $element['settings']['dynamic_default_value'] = '';
281 300 }
282 301
302 + // The editor only renders a rule the field already carries, so forms
303 + // built before selection limits existed need the keys backfilled.
304 + $isMultiSelect = 'select' == $upgradeElement
305 + && \FluentForm\Framework\Helpers\ArrayHelper::get($element, 'attributes.multiple');
306 +
307 + if ('input_checkbox' == $upgradeElement || $isMultiSelect) {
308 + $rules = \FluentForm\Framework\Helpers\ArrayHelper::get($element, 'settings.validation_rules', []);
309 +
310 + foreach (['max_selection', 'min_selection'] as $selectionRule) {
311 + if (isset($rules[$selectionRule])) {
312 + continue;
313 + }
314 +
315 + $globalMessage = \FluentForm\App\Helpers\Helper::getGlobalDefaultMessage($selectionRule);
316 +
317 + // Carry the legacy ceiling across, or the editor would show
318 + // "no limit" on a form that has one and drop it on save.
319 + $value = '';
320 + if ('max_selection' === $selectionRule && $isMultiSelect) {
321 + $value = \FluentForm\Framework\Helpers\ArrayHelper::get($element, 'settings.max_selection', '');
322 + }
323 +
324 + $rules[$selectionRule] = [
325 + 'value' => $value,
326 + 'message' => $globalMessage,
327 + 'global_message' => $globalMessage,
328 + 'global' => true,
329 + ];
330 + }
331 +
332 + // Key order is the panel's layout order. Rebuilt rather than
333 + // appended, so forms saved by an earlier build get it too.
334 + $ordered = [];
335 + foreach (['required', 'max_selection', 'min_selection'] as $key) {
336 + if (isset($rules[$key])) {
337 + $ordered[$key] = $rules[$key];
338 + }
339 + }
340 +
341 + $element['settings']['validation_rules'] = $ordered + $rules;
342 + }
343 +
283 344 if ('select_country' != $upgradeElement && !isset($element['settings']['randomize_options'])) {
284 345 $element['settings']['randomize_options'] = 'no';
285 346 }
286 347
@@ -313,10 +374,8 @@
313 374 if ('select' == $upgradeElement && !isset($element['settings']['enable_option_groups'])) {
314 375 $element['settings']['enable_option_groups'] = 'no';
315 376 }
316 377
317 -
318 -
319 378 return $element;
320 379 });
321 380 }
322 381
@@ -338,9 +397,9 @@
338 397 }
339 398 if (!isset($element['settings']['crop_mode'])) {
340 399 $element['settings']['crop_mode'] = (
341 400 isset($element['settings']['enforce_image_dimensions']) &&
342 - $element['settings']['enforce_image_dimensions'] === 'yes'
401 + 'yes' === $element['settings']['enforce_image_dimensions']
343 402 ) ? 'dimensions' : 'ratio';
344 403 }
345 404 if (!isset($element['settings']['crop_ratio'])) {
346 405 $element['settings']['crop_ratio'] = 'free';
@@ -357,15 +416,15 @@
357 416 }
358 417 return $element;
359 418 });
360 419 }
361 -
420 +
362 421 $prefixSuffixInputs = [
363 422 'textarea',
364 423 'input_url',
365 424 'input_password',
366 425 ];
367 -
426 +
368 427 foreach ($prefixSuffixInputs as $inputType) {
369 428 add_filter('fluentform/editor_init_element_' . $inputType, function ($item) {
370 429 if (!isset($item['settings']['prefix_label'])) {
371 430 $item['settings']['prefix_label'] = '';
@@ -375,9 +434,9 @@
375 434 }
376 435 return $item;
377 436 });
378 437 }
379 -
438 +
380 439 add_filter('fluentform/editor_init_element_gdpr_agreement', function ($element) {
381 440 if (!isset($element['settings']['required_field_message'])) {
382 441 $element['settings']['required_field_message'] = '';
383 442 }
@@ -401,8 +460,13 @@
401 460 add_filter('fluentform/editor_init_element_input_date', function ($item) {
402 461 if (!isset($item['settings']['date_config'])) {
403 462 $item['settings']['date_config'] = '';
404 463 }
464 + // Show the executable form of any legacy 6.2.7-6.2.12 tokens; only a trusted author's verbatim save persists it (restricted saves keep the stored value).
465 + $decoded = LegacyDateConfigDecoder::decode((string) $item['settings']['date_config']);
466 + if (null !== $decoded) {
467 + $item['settings']['date_config'] = $decoded;
468 + }
405 469 return $item;
406 470 });
407 471
408 472 add_filter('fluentform/editor_init_element_ratings', function ($item) {
@@ -428,9 +492,8 @@
428 492
429 493 return $item;
430 494 });
431 495
432 -
433 496 add_filter('fluentform/editor_init_element_container', function ($item) {
434 497 if (!isset($item['settings']['conditional_logics'])) {
435 498 $item['settings']['conditional_logics'] = [];
436 499 }
@@ -479,8 +542,17 @@
479 542
480 543 return $item;
481 544 });
482 545
546 + foreach (['input_text', 'input_email', 'textarea', 'input_number', 'select', 'input_url', 'input_password', 'input_date', 'input_name', 'address', 'phone'] as $autocompleteElement) {
547 + add_filter('fluentform/editor_init_element_' . $autocompleteElement, function ($item) {
548 + if (!isset($item['attributes']['autocomplete'])) {
549 + $item['attributes']['autocomplete'] = '';
550 + }
551 + return $item;
552 + });
553 + }
554 +
483 555 add_filter('fluentform/editor_init_element_input_mask', function ($item) {
484 556 if (!isset($item['settings']['mobile_keyboard_type'])) {
485 557 $item['settings']['mobile_keyboard_type'] = '';
486 558 }
@@ -502,9 +574,8 @@
502 574 }
503 575 return $item;
504 576 });
505 577
506 -
507 578 add_filter('fluentform/editor_init_element_input_text', function ($item) {
508 579 if (isset($item['attributes']['data-mask'])) {
509 580 if (!isset($item['settings']['data-mask-reverse'])) {
510 581 $item['settings']['data-mask-reverse'] = 'no';
@@ -531,9 +602,9 @@
531 602 });
532 603
533 604 if ($inputs = \FluentForm\App\Modules\Form\FormFieldsParser::getInputs($form, ['element'])) {
534 605 foreach ($inputs as $input) {
535 - add_filter('fluentform/editor_init_element_'. $input['element'], function ($field) {
606 + add_filter('fluentform/editor_init_element_' . $input['element'], function ($field) {
536 607 Helper::resolveValidationRulesGlobalOption($field);
537 608 return $field;
538 609 });
539 610 }
@@ -609,19 +680,19 @@
609 680 });
610 681
611 682 add_filter('fluentform/editor_init_element_gdpr_agreement', function ($item, $form) {
612 683 $isConversationalForm = Helper::isConversionForm($form->id);
613 -
684 +
614 685 if ($isConversationalForm) {
615 686 $item['settings']['tc_agree_text'] = __('I accept', 'fluentform');
616 687 }
617 -
688 +
618 689 return $item;
619 690 }, 10, 2);
620 691
621 692 add_filter('fluentform/editor_init_element_terms_and_condition', function ($item, $form) {
622 693 $isConversationalForm = Helper::isConversionForm($form->id);
623 -
694 +
624 695 if ($isConversationalForm) {
625 696 $item['settings']['hide_disagree'] = false;
626 697 }
627 698
@@ -670,11 +741,9 @@
670 741 }
671 742
672 743 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified by WordPress save_post action
673 744 $post_content = isset($_REQUEST['post_content']) ? wp_kses_post(wp_unslash($_REQUEST['post_content'])) : false;
674 - if ($post_content && is_string($post_content)) {
675 - // Already sanitized above
676 - } else {
745 + if (!$post_content || !is_string($post_content)) {
677 746 $post = get_post($post_id);
678 747 $post_content = $post->post_content;
679 748 }
680 749
@@ -760,9 +829,9 @@
760 829 );
761 830 $globalVars = [
762 831 'ajaxurl' => Helper::getAjaxUrl(),
763 832 'global_search_active' => apply_filters('fluentform/global_search_active', 'yes'),
764 - 'rest' => Helper::getRestInfo()
833 + 'rest' => Helper::getRestInfo(),
765 834 ];
766 835 if (Acl::hasAnyFormPermission()) {
767 836 $globalVars['fluent_forms_admin_nonce'] = wp_create_nonce('fluent_forms_admin_nonce');
768 837 }
@@ -768,9 +837,9 @@
768 837 }
769 838 wp_localize_script('fluent_forms_global', 'fluent_forms_global_var', $globalVars);
770 839 wp_enqueue_style('fluent-form-styles');
771 840 $form = wpFluent()->table('fluentform_forms')->find(intval($app->request->get('preview_id')));
772 - $postId = get_the_ID() ?: 0;
841 + $postId = get_the_ID() ? get_the_ID() : 0;
773 842
774 843 $loadPublicStyle = apply_filters_deprecated(
775 844 'fluentform_load_default_public',
776 845 [
@@ -775,9 +844,9 @@
775 844 'fluentform_load_default_public',
776 845 [
777 846 true,
778 847 $form,
779 - $postId
848 + $postId,
780 849 ],
781 850 FLUENTFORM_FRAMEWORK_UPGRADE,
782 851 'fluentform/load_default_public',
783 852 'Use fluentform/load_default_public instead of fluentform_load_default_public.'
@@ -797,9 +866,9 @@
797 866 true
798 867 );
799 868
800 869 wp_localize_script('fluentform-preview_app', 'fluent_preview_var', [
801 - 'i18n' => \FluentForm\App\Modules\Registerer\TranslationString::getPreviewI18n()
870 + 'i18n' => \FluentForm\App\Modules\Registerer\TranslationString::getPreviewI18n(),
802 871 ]);
803 872 }
804 873 });
805 874
@@ -835,9 +904,9 @@
835 904 'fluentform_api_success_log',
836 905 [
837 906 $isDev,
838 907 $form,
839 - $feed
908 + $feed,
840 909 ],
841 910 FLUENTFORM_FRAMEWORK_UPGRADE,
842 911 'fluentform/api_success_log',
843 912 'Use fluentform/api_success_log instead of fluentform_api_success_log.'
@@ -875,9 +944,9 @@
875 944 'fluentform_api_failed_log',
876 945 [
877 946 $isDev,
878 947 $form,
879 - $feed
948 + $feed,
880 949 ],
881 950 FLUENTFORM_FRAMEWORK_UPGRADE,
882 951 'fluentform/api_failed_log',
883 952 'Use fluentform/api_failed_log instead of fluentform_api_failed_log.'
@@ -972,11 +1041,23 @@
972 1041 $tokenBasedSpamProtection = new \FluentForm\App\Modules\Form\TokenBasedSpamProtection($app);
973 1042 $tokenBasedSpamProtection->verify($insertData, $requestData, $form->id);
974 1043 }, 9, 3);
975 1044
1045 +// The token-based spam check (FINDING-25) enforces on conversational forms too, but its ~1h TTL
1046 +// token cannot be refreshed by the conversational JS app — it bakes hidden inputs statically at
1047 +// render, so behind a full-page cache the token expires and rejects every legitimate submission.
1048 +// Disable ONLY the token for conversational forms, resolved from server-side form meta (never the
1049 +// client-supplied isFFConversational flag, which was the original bypass). The honeypot still applies.
1050 +$app->addFilter('fluentform/token_based_spam_protection_status', function ($status, $formId) {
1051 + if ($status && \FluentForm\App\Helpers\Helper::isConversionForm($formId)) {
1052 + return false;
1053 + }
1054 + return $status;
1055 +}, 10, 2);
1056 +
976 1057 // Maybe update current user allowed form ids,
977 1058 // if current user has specific form permission and capable to create form
978 -$app->addAction('fluentform/inserted_new_form', function ($formId){
1059 +$app->addAction('fluentform/inserted_new_form', function ($formId) {
979 1060 \FluentForm\App\Services\Manager\FormManagerService::maybeAddUserAllowedFormIds($formId);
980 1061 });
981 1062
982 1063 add_action('fluentform/log_data', function ($data) use ($app) {
@@ -1042,8 +1123,12 @@
1042 1123 if (!$note) {
1043 1124 $note = $status;
1044 1125 }
1045 1126
1127 + $note = is_scalar($note)
1128 + ? sanitize_text_field(wp_unslash((string) $note))
1129 + : sanitize_text_field((string) wp_json_encode($note));
1130 +
1046 1131 if (strlen($note) > 255) {
1047 1132 if (function_exists('mb_substr')) {
1048 1133 $note = mb_substr($note, 0, 251) . '...';
1049 1134 } else {
@@ -1073,8 +1158,12 @@
1073 1158 if (!$note) {
1074 1159 $note = $status;
1075 1160 }
1076 1161
1162 + $note = is_scalar($note)
1163 + ? sanitize_text_field(wp_unslash((string) $note))
1164 + : sanitize_text_field((string) wp_json_encode($note));
1165 +
1077 1166 if (strlen($note) > 255) {
1078 1167 if (function_exists('mb_substr')) {
1079 1168 $note = mb_substr($note, 0, 251) . '...';
1080 1169 } else {
@@ -1095,9 +1184,9 @@
1095 1184 $isTruncate = apply_filters_deprecated(
1096 1185 'fluentform_truncate_password_values',
1097 1186 [
1098 1187 true,
1099 - $form->id
1188 + $form->id,
1100 1189 ],
1101 1190 FLUENTFORM_FRAMEWORK_UPGRADE,
1102 1191 'fluentform/truncate_password_values',
1103 1192 'Use fluentform/truncate_password_values instead of fluentform_truncate_password_values.'
@@ -1151,9 +1240,8 @@
1151 1240 ['wp-edit-blocks'],
1152 1241 FLUENTFORM_VERSION
1153 1242 );
1154 1243
1155 -
1156 1244 $forms = wpFluent()->table('fluentform_forms')
1157 1245 ->select(['id', 'title'])
1158 1246 ->orderBy('id', 'DESC')
1159 1247 ->get()
@@ -1170,9 +1258,9 @@
1170 1258 'value' => '',
1171 1259 ],
1172 1260 [
1173 1261 'label' => __('Inherit Theme Style', 'fluentform'),
1174 - 'value' => 'ffs_inherit_theme'
1262 + 'value' => 'ffs_inherit_theme',
1175 1263 ],
1176 1264 ];
1177 1265
1178 1266 $presets = apply_filters('fluentform/block_editor_style_presets', $presets);
@@ -1182,9 +1270,9 @@
1182 1270 'forms' => $forms,
1183 1271 'style_presets' => $presets,
1184 1272 'theme_style' => apply_filters('fluentform/load_theme_style', false) ? 'ffs_inherit_theme' : '',
1185 1273 'conversational_demo_img' => fluentFormMix('img/conversational-form-demo.png'),
1186 - 'rest' => Helper::getRestInfo()
1274 + 'rest' => Helper::getRestInfo(),
1187 1275 ]);
1188 1276
1189 1277 wp_enqueue_style(
1190 1278 'fluentform-gutenberg-block',
@@ -1207,15 +1295,15 @@
1207 1295 FLUENTFORM_VERSION
1208 1296 );
1209 1297
1210 1298 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking post ID in admin context
1211 - $post_id = isset($_GET['post']) ? (int)$_GET['post'] : 0;
1299 + $post_id = isset($_GET['post']) ? (int) $_GET['post'] : 0;
1212 1300 $loadPublicStyle = apply_filters_deprecated(
1213 1301 'fluentform_load_default_public',
1214 1302 [
1215 1303 true,
1216 - (object)[],
1217 - $post_id
1304 + (object) [],
1305 + $post_id,
1218 1306 ],
1219 1307 FLUENTFORM_FRAMEWORK_UPGRADE,
1220 1308 'fluentform/load_default_public',
1221 1309 'Use fluentform/load_default_public instead of fluentform_load_default_public.'
@@ -1220,9 +1308,9 @@
1220 1308 'fluentform/load_default_public',
1221 1309 'Use fluentform/load_default_public instead of fluentform_load_default_public.'
1222 1310 );
1223 1311
1224 - if (apply_filters('fluentform/load_default_public', $loadPublicStyle, (object)[], $post_id)) {
1312 + if (apply_filters('fluentform/load_default_public', $loadPublicStyle, (object) [], $post_id)) {
1225 1313 wp_enqueue_style(
1226 1314 'fluentform-public-default',
1227 1315 $fluentFormPublicDefaultCss,
1228 1316 [],
@@ -1239,29 +1327,25 @@
1239 1327 });
1240 1328 }
1241 1329
1242 1330
1243 -add_action('fluentform/before_updating_form',function ($form, $postData){
1331 +add_action('fluentform/before_updating_form', function ($form, $postData) {
1244 1332 (new FluentForm\App\Services\Form\HistoryService())->init($form, $postData);
1245 -},10,2);
1333 +}, 10, 2);
1246 1334
1247 1335
1248 1336 // WordPress 6.3+ uses iframes for block editor preview
1249 1337 // Official WordPress solution: Use enqueue_block_assets with proper context checking
1250 1338 // See: https://make.wordpress.org/core/2023/07/18/miscellaneous-editor-changes-in-wordpress-6-3/
1251 -add_action('enqueue_block_assets', function() {
1252 - // Check if we're in the block editor context (not frontend)
1253 - // This works for both the editor UI and the iframe preview in WordPress 6.3+
1254 - if (!is_admin() && !wp_is_block_theme()) {
1339 +add_action('enqueue_block_assets', function () {
1340 + // enqueue_block_assets also fires on the front end, where wp_enqueue_scripts already loads these conditionally.
1341 + if (!is_admin()) {
1255 1342 return;
1256 1343 }
1257 1344
1258 - // Additional check: Only load if we're actually in a block editor screen
1259 - if (is_admin()) {
1260 - $current_screen = function_exists('get_current_screen') ? get_current_screen() : null;
1261 - if ($current_screen && !$current_screen->is_block_editor()) {
1262 - return;
1263 - }
1345 + $current_screen = function_exists('get_current_screen') ? get_current_screen() : null;
1346 + if ($current_screen && !$current_screen->is_block_editor()) {
1347 + return;
1264 1348 }
1265 1349
1266 1350 // Enqueue Fluent Forms CSS for block editor iframe preview
1267 1351 // These styles are necessary for the live form preview in the block editor
@@ -1266,9 +1350,8 @@
1266 1350 // Enqueue Fluent Forms CSS for block editor iframe preview
1267 1351 // These styles are necessary for the live form preview in the block editor
1268 1352 wp_enqueue_style('fluent-forms-public', fluentFormMix('css/fluent-forms-public.css'), [], FLUENTFORM_VERSION);
1269 1353 wp_enqueue_style('fluentform-public-default', fluentFormMix('css/fluentform-public-default.css'), [], FLUENTFORM_VERSION);
1270 -
1271 1354 });
1272 1355
1273 1356
1274 1357