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 +93 -0 6.2.11 → 6.2.15 View file →
@@ -4,8 +4,9 @@
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,
@@ -71,8 +72,23 @@
71 72 (new \FluentForm\App\Modules\Renderer\GlobalSettings\Settings($app))->render();
72 73 }
73 74 );
74 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 +
75 91 // Register DefaultStyleApplicator on init so it works for REST API requests too
76 92 add_action('init', function () {
77 93 new \FluentForm\App\Modules\Form\DefaultStyleApplicator();
78 94 }, 9);
@@ -162,8 +178,9 @@
162 178 remove_all_actions('admin_notices');
163 179 \FluentForm\App\Modules\Registerer\ReviewQuery::register();
164 180 \FluentForm\App\Modules\Registerer\MigrationNotice::register();
165 181 \FluentForm\App\Modules\Registerer\StripeKeyNotice::register();
182 + \FluentForm\App\Modules\Registerer\CaptchaKeyNotice::register();
166 183 }
167 184 });
168 185
169 186 add_action('wp_print_scripts', function () {
@@ -281,8 +298,50 @@
281 298 if (!isset($element['settings']['dynamic_default_value'])) {
282 299 $element['settings']['dynamic_default_value'] = '';
283 300 }
284 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 +
285 344 if ('select_country' != $upgradeElement && !isset($element['settings']['randomize_options'])) {
286 345 $element['settings']['randomize_options'] = 'no';
287 346 }
288 347
@@ -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) {
@@ -478,8 +542,17 @@
478 542
479 543 return $item;
480 544 });
481 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 +
482 555 add_filter('fluentform/editor_init_element_input_mask', function ($item) {
483 556 if (!isset($item['settings']['mobile_keyboard_type'])) {
484 557 $item['settings']['mobile_keyboard_type'] = '';
485 558 }
@@ -968,8 +1041,20 @@
968 1041 $tokenBasedSpamProtection = new \FluentForm\App\Modules\Form\TokenBasedSpamProtection($app);
969 1042 $tokenBasedSpamProtection->verify($insertData, $requestData, $form->id);
970 1043 }, 9, 3);
971 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 +
972 1057 // Maybe update current user allowed form ids,
973 1058 // if current user has specific form permission and capable to create form
974 1059 $app->addAction('fluentform/inserted_new_form', function ($formId) {
975 1060 \FluentForm\App\Services\Manager\FormManagerService::maybeAddUserAllowedFormIds($formId);
@@ -1038,8 +1123,12 @@
1038 1123 if (!$note) {
1039 1124 $note = $status;
1040 1125 }
1041 1126
1127 + $note = is_scalar($note)
1128 + ? sanitize_text_field(wp_unslash((string) $note))
1129 + : sanitize_text_field((string) wp_json_encode($note));
1130 +
1042 1131 if (strlen($note) > 255) {
1043 1132 if (function_exists('mb_substr')) {
1044 1133 $note = mb_substr($note, 0, 251) . '...';
1045 1134 } else {
@@ -1068,8 +1157,12 @@
1068 1157 }
1069 1158 if (!$note) {
1070 1159 $note = $status;
1071 1160 }
1161 +
1162 + $note = is_scalar($note)
1163 + ? sanitize_text_field(wp_unslash((string) $note))
1164 + : sanitize_text_field((string) wp_json_encode($note));
1072 1165
1073 1166 if (strlen($note) > 255) {
1074 1167 if (function_exists('mb_substr')) {
1075 1168 $note = mb_substr($note, 0, 251) . '...';