PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.12
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.12
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 / Services / FluentConversational / Classes / Form.php

Form.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.12, at app/Services/FluentConversational/Classes/Form.php

1,035 lines 46.0 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\Services\FluentConversational\Classes;
4
5 defined('ABSPATH') or die;
6
7 use FluentForm\App\Helpers\Helper;
8 use FluentForm\App\Modules\Acl\Acl;
9 use FluentForm\App\Modules\Payments\PaymentHelper;
10 use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\StripeSettings;
11 use FluentForm\Framework\Helpers\ArrayHelper;
12 use FluentForm\App\Modules\Form\Settings\FormCssJs;
13 use FluentForm\App\Services\FluentConversational\Classes\Converter\Converter;
14 use FluentForm\App\Services\FluentConversational\Classes\Elements\WelcomeScreen;
15
16 class Form
17 {
18 protected $addOnKey = 'conversational_forms';
19
20 protected $metaKey = 'ffc_form_settings';
21
22 public function boot()
23 {
24 add_action('wp', [$this, 'render'], 100);
25
26 add_filter('fluentform/editor_components', [$this, 'filterAcceptedFields'], 999, 2);
27
28 add_filter('fluentform/form_admin_menu', [$this, 'pushDesignTab'], 10, 2);
29
30 add_action('fluentform/form_application_view_conversational_design', [$this, 'renderDesignSettings'], 10, 1);
31
32 add_filter('fluentform/editor_element_settings_placement', [$this, 'maybeAlterPlacement'], 10, 2);
33
34 // elements
35 new WelcomeScreen();
36 }
37
38 public function pushDesignTab($menuItems, $formId)
39 {
40 if (!Helper::isConversionForm($formId)) {
41 return $menuItems;
42 }
43
44 $newItems = $menuItems;
45
46 if (Acl::hasPermission('fluentform_forms_manager')) {
47 $newItems = array_slice($menuItems, 0, 1, true) + [
48 'conversational_design' => [
49 'slug' => 'conversational_design',
50 'title' => __('Design', 'fluentform'),
51 'url' => admin_url('admin.php?page=fluent_forms&form_id=' . $formId . '&route=conversational_design'),
52 ],
53 ] + array_slice($menuItems, 1, count($menuItems) - 1, true);
54 }
55
56 return $newItems;
57 }
58
59 public function renderDesignSettings($formId)
60 {
61 if (!Helper::isConversionForm($formId)) {
62 echo 'Sorry! This is not a conversational form';
63 return;
64 }
65
66 if (function_exists('wp_enqueue_editor')) {
67 add_filter('user_can_richedit', '__return_true');
68 wp_enqueue_editor();
69 wp_enqueue_media();
70 }
71
72 wp_enqueue_script(
73 'fluent_forms_conversational_design',
74 fluentFormMix('js/conversational_design.js'),
75 ['jquery'],
76 FLUENTFORM_VERSION,
77 true
78 );
79
80 $slug = apply_filters_deprecated(
81 'fluentform_conversational_url_slug',
82 [
83 'fluent-form'
84 ],
85 FLUENTFORM_FRAMEWORK_UPGRADE,
86 'fluentform/conversational_url_slug',
87 'Use fluentform/conversational_url_slug instead of fluentform_conversational_url_slug.'
88 );
89
90 $paramKey = apply_filters('fluentform/conversational_url_slug', $slug);
91
92 if ('form' == $paramKey) {
93 $paramKey = 'fluent-form';
94 }
95
96 wp_localize_script('fluent_forms_conversational_design', 'ffc_conv_vars', [
97 'form_id' => $formId,
98 'preview_url' => Helper::getFrontendFacingUrl('?' . $paramKey . '=' . $formId),
99 'fonts' => Fonts::getFonts(),
100 'has_pro' => defined('FLUENTFORMPRO'),
101 'has_pro_share_page' => defined('FLUENTFORMPRO') && class_exists('\FluentFormPro\classes\SharePage\SharePage'),
102 'upgrade_url' => fluentform_upgrade_url(),
103 ]);
104
105 wp_enqueue_style(
106 'fluent_forms_conversion_style',
107 fluentFormMix('css/conversational_design.css'),
108 [],
109 FLUENTFORM_VERSION
110 );
111
112 echo '<div id="ff_conversation_form_design_app"><design-skeleton><h1 style="text-align: center; margin: 60px 0px;">Loading App Please wait....</h1></design-skeleton><global-search></global-search></div>';
113 }
114
115 public function getDesignSettings($formId)
116 {
117 $settings = Helper::getFormMeta($formId, $this->metaKey . '_design', []);
118
119 $defaults = [
120 'background_color' => '#FFFFFF',
121 'question_color' => '#191919',
122 'answer_color' => '#0445AF',
123 'button_color' => '#0445AF',
124 'button_text_color' => '#FFFFFF',
125 'background_image' => '',
126 'background_brightness' => 0,
127 'disable_branding' => 'no',
128 'hide_media_on_mobile' => 'no',
129 'key_hint' => 'yes',
130 'enable_scroll_to_top' => 'no',
131 'asteriskPlacement' => $this->getAsteriskPlacement($formId)
132 ];
133
134 return wp_parse_args($settings, $defaults);
135 }
136
137 public function getMetaSettings($formId)
138 {
139 $settings = Helper::getFormMeta($formId, $this->metaKey . '_meta', []);
140 $defaults = [
141 'title' => '',
142 'description' => '',
143 'featured_image' => '',
144 'share_key' => '',
145 'google_font_href' => '',
146 'font_css' => '',
147 'i18n' => [
148 'skip_btn' => 'SKIP',
149 'confirm_btn' => 'OK',
150 'continue' => 'Continue',
151 'keyboard_instruction' => 'Press <b>Enter ↵</b>',
152 'multi_select_hint' => 'Choose as many as you like',
153 'single_select_hint' => 'Choose one option',
154 'progress_text' => '{percent}% completed',
155 'long_text_help' => '<b>Shift ⇧</b> + <b>Enter ↵</b> to make a line break.',
156 'invalid_prompt' => 'Please fill out the field correctly',
157 'errorMaxLength' => 'The maximum {maxLength} number of characters accept',
158 'default_placeholder' => 'Type Your answer here',
159 'key_hint_text' => 'Key',
160 'key_hint_tooltip' => 'Press the key to select',
161 'choose_file' => '<b>Choose file</b> or <b>drag here</b>',
162 'limit' => 'Size limit: ',
163 'ranking_reset' => 'Reset order'
164 ],
165 ];
166
167 if ($settings && !isset($settings['i18n']['key_hint_text'])) {
168 $settings['i18n']['key_hint_text'] = $defaults['i18n']['key_hint_text'];
169 $settings['i18n']['key_hint_tooltip'] = $defaults['i18n']['key_hint_tooltip'];
170 }
171
172 if ($settings && !isset($settings['i18n']['ranking_reset'])) {
173 $settings['i18n']['ranking_reset'] = $defaults['i18n']['ranking_reset'];
174 }
175
176 if (!$settings || empty($settings['title'])) {
177 $form = wpFluent()->table('fluentform_forms')->find($formId);
178 $settings['title'] = $form->title;
179 }
180
181 return wp_parse_args($settings, $defaults);
182 }
183
184 private function getGeneratedCss($formId)
185 {
186 $prefix = '.ff_conv_app_' . $formId;
187 if (defined('FLUENTFORMPRO')) {
188 $css = Helper::getFormMeta($formId, $this->metaKey . '_generated_css', '');
189 if ($css) {
190 return $css;
191 }
192 }
193
194 return $prefix . ' { background-color: #FFFFFF; }' . $prefix . ' .ffc-counter-div span { color: #0445AF; }' . $prefix . ' .ffc-counter-div .counter-icon-span svg { fill: #0445AF !important; }' . $prefix . ' .f-label-wrap, ' . $prefix . ' .f-answer { color: #0445AF !important; }' . $prefix . ' .f-label-wrap .f-key { border-color: #0445AF !important; }' . $prefix . ' .f-label-wrap .f-key-hint { border-color: #0445AF !important; }' . $prefix . ' .f-answer .f-radios-wrap ul li { background-color: rgba(4,69,175, 0.1) !important; border: 1px solid #0445AF; }' . $prefix . ' .f-answer .f-radios-wrap ul li:focus { background-color: rgba(4,69,175, 0.3) !important }' . $prefix . ' .f-answer .f-radios-wrap ul li:hover { background-color: rgba(4,69,175, 0.3) !important }' . $prefix . ' .f-answer .f-radios-wrap ul li.f-selected .f-key { background-color: #0445AF !important; color: white; }' . $prefix . ' .f-answer .f-radios-wrap ul li.f-selected .f-key-hint { background-color: #0445AF; }' . $prefix . ' .f-answer .f-radios-wrap ul li.f-selected svg { fill: #0445AF !important; }' . $prefix . ' .f-answer input, ' . $prefix . ' .f-answer textarea{ color: #0445AF !important; box-shadow: #0445AF 0px 1px; }' . $prefix . ' .f-answer input:focus, ' . $prefix . ' .f-answer textarea:focus { box-shadow: #0445AF 0px 2px !important; }' . $prefix . ' .f-answer textarea::placeholder, ' . $prefix . ' .f-answer input::placeholder { color: #0445AF !important; }' . $prefix . ' .text-success { color: #0445AF !important; }' . $prefix . ' .f-answer .f-matrix-table tbody td { background-color: rgba(4,69,175, 0.1); }' . $prefix . ' .f-answer .f-matrix-table input { border-color: rgba(4,69,175, 0.8); }' . $prefix . ' .f-answer .f-matrix-table input.f-radio-control:checked::after { background-color: #0445AF; }' . $prefix . ' .f-answer .f-matrix-table input:focus::before { border-color: #0445AF; }' . $prefix . ' .f-answer .f-matrix-table input.f-checkbox-control:checked { background-color: #0445AF; }' . $prefix . ' .f-answer .f-matrix-table tbody tr::after { border-right-color: #0445AF; }' . $prefix . ' .f-answer .f-matrix-table .f-table-cell.f-row-cell { box-shadow: rgba(4,69,175, 0.1) 0px 0px 0px 100vh inset; }' . $prefix . ' .f-answer .ff_file_upload_field_wrap { background-color: rgba(4,69,175, 0.1); border-color: rgba(4,69,175, 0.8); }' . $prefix . ' .f-answer .ff_file_upload_field_wrap:hover { background-color: rgba(4,69,175, 0.3);}' . $prefix . ' .f-answer .ff_file_upload_field_wrap:focus-within { background-color: rgba(4,69,175, 0.3); }' . $prefix . ' .f-answer .ff-upload-preview { border-color: rgba(4,69,175, 0.8); }' . $prefix . ' .f-answer .ff-upload-preview .ff-upload-thumb { background-color: rgba(4,69,175, 0.3); }' . $prefix . ' .f-answer .ff-upload-preview .ff-upload-details { border-left-color: rgba(4,69,175, 0.8); }' . $prefix . ' .f-answer .ff-upload-preview .ff-upload-details .ff-el-progress { border-left-color: rgba(4,69,175, 0.8); }' . $prefix . ' .f-answer .ff-upload-preview .ff-upload-details .ff-el-progress { background-color: rgba(4,69,175, 0.1); }' . $prefix . ' .f-answer .ff-upload-preview .ff-upload-details .ff-el-progress .ff-el-progress-bar { background-color: #0445AF; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap::before { background-color: #0445AF; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap .f-star-field .f-star-field-star .symbolOutline { fill: #0445AF; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap .f-star-field .f-star-field-rating { color: #0445AF; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap .f-star-field-star .ff-rating-icon-svg-holder { display: block; line-height: 0; width: 100%; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap .ff-rating-icon-svg { color: var(--ff-rating-inactive-color, rgba(4,69,175, 0.25)); display: block; height: auto; max-height: 64px; max-width: 64px; width: 100%; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap .ff-rating-icon-svg [fill]:not([fill="none"]) { fill: currentColor !important; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap .ff-rating-icon-svg [stroke]:not([stroke="none"]) { stroke: currentColor !important; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap.is-hovered .symbolFill { fill: rgba(4,69,175, 0.1); }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap.is-hovered .ff-rating-icon-svg { color: var(--ff-rating-hover-color, rgba(4,69,175, 0.4)); }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap.is-selected .symbolFill { fill: #0445AF; }' . $prefix . ' .f-answer .f-star-wrap .f-star-field-wrap.is-selected .ff-rating-icon-svg { color: var(--ff-rating-active-color, #0445AF); }' . $prefix . ' .f-answer .f-payment-summary-wrap tbody td { background-color: rgba(4,69,175, 0.1); }' . $prefix . ' .f-answer .f-payment-summary-wrap tfoot th { background-color: rgba(4,69,175, 0.1); }' . $prefix . ' .f-answer .stripe-inline-holder { border-bottom: 1px solid #0445AF; }' . $prefix . ' .f-answer .StripeElement--focus { border-bottom: 2.5px solid #0445AF; }' . $prefix . ' .ff_conv_input .f-info { color: #0445AF; }' . $prefix . ' .fh2 .f-text { color: #191919; }' . $prefix . ' .fh2 .f-tagline, ' . $prefix . ' .f-sub .f-help { color: rgba(25,25,25, 0.70); }' . $prefix . ' .fh2 .stripe-inline-header { color: #191919; }' . $prefix . ' .q-inner .o-btn-action, ' . $prefix . ' .footer-inner-wrap .f-nav { background-color: #0445AF; }' . $prefix . ' .q-inner .o-btn-action span, ' . $prefix . ' .footer-inner-wrap .f-nav a { color: #FFFFFF; } ' . $prefix . ' .f-enter .f-enter-desc { color: #0445AF; }' . $prefix . ' .footer-inner-wrap .f-nav a svg { fill: #FFFFFF; }' . $prefix . ' .vff-footer .f-progress-bar { background-color: rgba(4,69,175, 0.3); }' . $prefix . ' .vff-footer .f-progress-bar-inner { background-color: #0445AF; }' . $prefix . ' .q-inner .o-btn-action:hover { background-color: #0445AFD6; }' . $prefix . ' .q-inner .o-btn-action:focus::after { border-radius: 6px; inset: -3px; box-shadow: #0445AF 0px 0px 0px 2px; }' . $prefix . ' .f-answer .f-radios-wrap ul li.f-selected .f-key { color: #FFFFFF; }';
195 }
196
197 public function render()
198 {
199 $slug = 'fluent-form';
200 $paramKey = apply_filters_deprecated(
201 'fluentform_conversational_url_slug',
202 [
203 $slug
204 ],
205 FLUENTFORM_FRAMEWORK_UPGRADE,
206 'fluentform/conversational_url_slug',
207 'Use fluentform/conversational_url_slug instead of fluentform_conversational_url_slug.'
208 );
209
210 $paramKey = apply_filters('fluentform/conversational_url_slug', $paramKey);
211
212 if ('form' == $paramKey) {
213 $paramKey = $slug;
214 }
215
216 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public form display, no nonce needed
217 if(!isset($_REQUEST[$paramKey])) {
218 return;
219 }
220
221 $request = wpFluentForm('request')->get();
222
223 if ((isset($request[$paramKey])) && !wp_doing_ajax()) {
224 $formId = (int) ArrayHelper::get($request, $paramKey);
225 if (!Helper::isConversionForm($formId)) {
226 return;
227 }
228 $shareKey = ArrayHelper::get($request, 'form');
229 $this->renderFormHtml($formId, $shareKey);
230 }
231 }
232
233 public function isEnabled()
234 {
235 $globalModules = get_option('fluentform_global_modules_status');
236
237 $addOn = ArrayHelper::get($globalModules, $this->addOnKey);
238
239 if (!$addOn || 'yes' == $addOn) {
240 return true;
241 }
242
243 return false;
244 }
245
246 private function getSubmitBttnStyle($form)
247 {
248 $data = $form->submit_button;
249 $styles = '';
250
251 if ('' == ArrayHelper::get($data, 'settings.button_style')) {
252 // it's a custom button
253 $buttonActiveStyles = ArrayHelper::get($data, 'settings.normal_styles', []);
254 $buttonHoverStyles = ArrayHelper::get($data, 'settings.hover_styles', []);
255 $activeStates = '';
256 foreach ($buttonActiveStyles as $styleAtr => $styleValue) {
257 if ('0' != $styleValue && !$styleValue) {
258 continue;
259 }
260 if ('borderRadius' == $styleAtr) {
261 $styleValue .= 'px';
262 }
263 $activeStates .= ltrim(strtolower(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '-$0', $styleAtr)), '_') . ':' . $styleValue . ';';
264 }
265 if ($activeStates) {
266 $styles .= ' .ff-btn-submit { ' . $activeStates . ' }';
267 }
268 $hoverStates = '';
269 foreach ($buttonHoverStyles as $styleAtr => $styleValue) {
270 if ('0' != $styleValue && !$styleValue) {
271 continue;
272 }
273 if ('borderRadius' == $styleAtr) {
274 $styleValue .= 'px';
275 }
276 $hoverStates .= ltrim(strtolower(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '-$0', $styleAtr)), '-') . ':' . $styleValue . ';';
277 }
278 if ($hoverStates) {
279 $styles .= ' .wpf_has_custom_css.ff-btn-submit:hover { ' . $hoverStates . ' } ';
280 }
281 } else {
282 $styles .= ' .ff-btn-submit { background-color: ' . ArrayHelper::get($data, 'settings.background_color') . '; color: ' . ArrayHelper::get($data, 'settings.color') . '; }';
283 }
284
285 if (defined('FLUENTFORMPRO')) {
286 $customCssJsClass = new FormCssJs();
287 $customCss = $customCssJsClass->getCss($form->id);
288 $styles .= $customCss;
289 }
290
291 return $styles;
292 }
293
294 public function filterAcceptedFields($components, $formId)
295 {
296 if (!Helper::isConversionForm($formId)) {
297 return $components;
298 }
299
300 $generalFields = ArrayHelper::get($components, 'general', []);
301 $advancedFields = ArrayHelper::get($components, 'advanced', []);
302 $paymentFields = ArrayHelper::get($components, 'payments', []);
303
304 $acceptedFieldElements = [
305 'phone',
306 'select',
307 'select',
308 'ratings',
309 'textarea',
310 'address',
311 'input_name',
312 'input_url',
313 'input_text',
314 'input_date',
315 'input_file',
316 'input_email',
317 'input_radio',
318 'custom_html',
319 'input_image',
320 'input_hidden',
321 'input_number',
322 'tabular_grid',
323 'section_break',
324 'select_country',
325 'input_checkbox',
326 'input_password',
327 'terms_and_condition',
328 'gdpr_agreement',
329 'multi_payment_component',
330 'subscription_payment_component',
331 'custom_payment_component',
332 'item_quantity_component',
333 'payment_method',
334 'payment_summary_component',
335 'payment_coupon',
336 'recaptcha',
337 'hcaptcha',
338 'turnstile',
339 'quiz_score',
340 'save_progress_button',
341 'dynamic_field',
342 'rangeslider',
343 'net_promoter_score',
344 'input_ranking'
345 ];
346
347 if (defined('FLUENTFORM_SIGNATURE')) {
348 $acceptedFieldElements[] = 'signature';
349 }
350
351 $acceptedFieldElements = apply_filters(
352 'fluentform/conversational_accepted_field_elements',
353 $acceptedFieldElements,
354 $formId
355 );
356
357 $elements = [];
358
359 $allFields = [
360 'general' => $generalFields,
361 'advanced' => $advancedFields,
362 'payments' => $paymentFields,
363 ];
364
365 foreach ($allFields as $groupType => $group) {
366 foreach ($group as $field) {
367 $element = $field['element'];
368 if (in_array($element, $acceptedFieldElements)) {
369 $field['style_pref'] = [
370 'layout' => 'default',
371 'media' => fluentFormGetRandomPhoto(),
372 'brightness' => 0,
373 'alt_text' => '',
374 'media_x_position' => 50,
375 'media_y_position' => 50,
376 ];
377
378 if ('terms_and_condition' == $element || 'gdpr_agreement' == $element) {
379 $existingSettings = $field['settings'];
380 $existingSettings['tc_agree_text'] = __('I accept', 'fluentform');
381 if ('terms_and_condition' == $element) {
382 $existingSettings['tc_dis_agree_text'] = __('I don\'t accept', 'fluentform');
383 $existingSettings['hide_disagree'] = false;
384 }
385 $field['settings'] = $existingSettings;
386 }
387 //adding required settings for captcha in conversational form
388 if ('hcaptcha' == $element || 'recaptcha' == $element || 'turnstile' == $element) {
389 $existingSettings = $field['settings'];
390 if (empty($existingSettings['validation_rules'])) {
391 $existingSettings['validation_rules'] = [
392 'required' => [
393 'value' => true,
394 'message' => __('This field is required', 'fluentform'),
395 ],
396 ];
397 }
398 $field['settings'] = $existingSettings;
399 }
400
401 $elements[$groupType][] = $field;
402 }
403 }
404 }
405
406 $elements = apply_filters_deprecated(
407 'fluent_conversational_editor_elements',
408 [
409 $elements
410 ],
411 FLUENTFORM_FRAMEWORK_UPGRADE,
412 'fluentform/conversational_editor_elements',
413 'Use fluentform/conversational_editor_elements instead of fluent_conversational_editor_elements.'
414 );
415
416 $elements = apply_filters('fluentform/conversational_editor_elements', $elements, $formId);
417
418 return $elements;
419 }
420
421 public function printLoadedScripts()
422 {
423 $jsScripts = $this->getRegisteredScripts();
424 if ($jsScripts) {
425 add_action('fluentform/conversational_frame_footer', function () use ($jsScripts) {
426 foreach ($jsScripts as $handle => $jsScript) {
427 if (empty($jsScript->src)) {
428 continue;
429 }
430 if ($data = ArrayHelper::get($jsScript->extra, 'data')) {
431 printf("<script type='text/javascript' id='%s-js-extra'>\n", esc_attr($handle));
432 echo "$data\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $data is hardcoded localized data and escaped before being passed in.
433 echo "</script>\n";
434 }
435 $src = $jsScript->src;
436 $src = add_query_arg('ver', $jsScript->ver, $src);
437 echo "<script type='text/javascript' id='" . esc_attr($handle) . "' src='" . esc_url($src) . "'></script>\n";
438 }
439 }, 1);
440 }
441
442 $cssStyles = $this->getRegisteredStyles();
443 if ($cssStyles) {
444 add_action('fluentform/conversational_frame_head', function () use ($cssStyles) {
445 foreach ($cssStyles as $styleName => $cssStyle) {
446 if (empty($cssStyle->src)) {
447 continue;
448 }
449 $src = add_query_arg('ver', $cssStyle->ver, $cssStyle->src);
450 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- Completely a custom rendering page.
451 echo "<link rel='stylesheet' id='" . esc_attr($styleName) . "' href='" . esc_url($src) . "' type='text/css' media='all' />\n";
452 }
453 });
454 }
455 }
456
457 private function getRegisteredScripts()
458 {
459 global $wp_scripts;
460 if (!$wp_scripts) {
461 return [];
462 }
463
464 $jsScripts = [];
465
466 $pluginUrl = plugins_url() . '/fluentform';
467
468 foreach ($wp_scripts->queue as $script) {
469
470 if (!isset($wp_scripts->registered[$script])) {
471 continue;
472 }
473
474 $item = $wp_scripts->registered[$script];
475 $src = $wp_scripts->registered[$script]->src;
476
477 if (false === !strpos($src, $pluginUrl)) {
478 continue;
479 }
480
481 foreach ($item->deps as $dep) {
482 if (isset($wp_scripts->registered[$dep])) {
483 $child = $wp_scripts->registered[$dep];
484 if ($child->src) {
485 $jsScripts[$dep] = $child;
486 } else {
487 // this core file maybe
488 $childDependencies = $child->deps;
489 foreach ($childDependencies as $childDependency) {
490 $childX = $wp_scripts->registered[$childDependency];
491 if ($childX->src) {
492 $jsScripts[$childDependency] = $childX;
493 }
494 }
495 }
496 }
497 }
498 $jsScripts[$script] = $item;
499 }
500
501 return $jsScripts;
502 }
503
504 private function getRegisteredStyles()
505 {
506 $wp_styles = wp_styles();
507 if (!$wp_styles) {
508 return [];
509 }
510
511 $cssStyles = [];
512
513 $pluginUrl = plugins_url() . '/fluentform';
514
515 foreach ($wp_styles->queue as $style) {
516
517 if (!isset($wp_styles->registered[$style])) {
518 continue;
519 }
520
521 $item = $wp_styles->registered[$style];
522 $src = $wp_styles->registered[$style]->src;
523
524 if (false === !strpos($src, $pluginUrl)) {
525 continue;
526 }
527
528 if ($item->deps) {
529 foreach ($item->deps as $dep) {
530 if (isset($wp_styles->registered[$dep])) {
531 $child = $wp_styles->registered[$dep];
532 if ($child->src) {
533 $cssStyles[$dep] = $child;
534 } else {
535 // this core file maybe
536 $childDependencies = $child->deps;
537 if ($childDependencies && is_array($childDependencies)) {
538 foreach ($childDependencies as $childDependency) {
539 $childX = $wp_styles->registered[$childDependency];
540 if ($childX->src) {
541 $cssStyles[$childDependency] = $childX;
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549
550 $cssStyles[$style] = $item;
551 }
552
553 return $cssStyles;
554 }
555
556 public function renderShortcode($form)
557 {
558 $formId = $form->id;
559 $fileUploadSettings = apply_filters('fluentform/file_upload_settings_for_js', [], $form);
560
561 $form = Converter::convert($form);
562
563 $this->enqueueScripts();
564 do_action('fluentform/conversational_enqueue_assets', $form, $fileUploadSettings);
565
566 $submitCss = $this->getSubmitBttnStyle($form);
567 $metaSettings = $this->getMetaSettings($formId);
568 $designSettings = $this->getDesignSettings($formId);
569 $instanceId = $form->instance_index;
570 $varName = 'fluent_forms_global_var_' . $instanceId;
571
572 $localizedVars = [
573 // SECURITY (H-01): do not emit the admin AJAX nonce on the public conversational form
574 // page — the public form JS never uses it, and on a page an admin happens to view it
575 // would embed that admin's own valid nonce. Emit it only for a viewer who could use it.
576 'fluent_forms_admin_nonce' => current_user_can('fluentform_dashboard_access') ? wp_create_nonce('fluent_forms_admin_nonce') : '',
577 'ajaxurl' => admin_url('admin-ajax.php'),
578 'nonce' => wp_create_nonce(),
579 'form' => $this->getLocalizedForm($form),
580 'assetBaseUrl' => FLUENT_CONVERSATIONAL_FORM_DIR_URL . 'public',
581 'i18n' => $metaSettings['i18n'],
582 'form_id' => $form->id,
583 'hasPro' => defined('FLUENTFORMPRO'),
584 'is_inline_form' => true,
585 'design' => $designSettings,
586 'extra_inputs' => $this->getExtraHiddenInputs($formId),
587 'uploading_txt' => __('Uploading', 'fluentform'),
588 'upload_completed_txt' => __('100% Completed', 'fluentform'),
589 'unknown_error_txt' => __('An unknown error occurred', 'fluentform'),
590 'request_error_txt' => __('An error occurred while processing your request', 'fluentform'),
591 'paymentConfig' => $this->getPaymentConfig($form),
592 'date_i18n' => \FluentForm\App\Modules\Component\Component::getDatei18n(),
593 'file_delete_nonce' => wp_create_nonce('fluentform_file_delete'),
594 'file_upload_settings' => $fileUploadSettings,
595 ];
596
597 wp_localize_script(
598 'fluent_forms_conversational_form',
599 $varName,
600 apply_filters('fluentform/global_form_vars', $localizedVars)
601 );
602
603 $hasSaveProgressButton = false;
604 $saveProgressButton = [];
605 foreach ($form->fields['fields'] as $item) {
606 if (isset($item['element']) && $item['element'] === 'save_progress_button') {
607 $hasSaveProgressButton = true;
608 $saveProgressButton = $item;
609 }
610 }
611
612 if ($hasSaveProgressButton && $saveProgressButton) {
613 $this->localizeSaveProgressButton($saveProgressButton, $formId);
614 }
615
616 /* This filter is deprecated and will be removed soon */
617 $disableAnalytics = apply_filters('fluentform-disabled_analytics', true);
618
619 if (!apply_filters('fluentform/disabled_analytics', $disableAnalytics)) {
620 if (!Acl::hasAnyFormPermission($form->id)) {
621 (new \FluentForm\App\Services\Analytics\AnalyticsService())->store($formId);
622 }
623 }
624
625 return wpFluentForm('view')->make('public.conversational-form-inline', [
626 'generated_css' => $this->getGeneratedCss($formId),
627 'design' => $designSettings,
628 'submit_css' => $submitCss,
629 'form_id' => $formId,
630 'meta' => $metaSettings,
631 'global_var_name' => $varName,
632 'instance_id' => $instanceId,
633 'is_inline' => 'yes',
634 ]);
635 }
636
637 public function maybeAlterPlacement($placements, $form)
638 {
639 if (!Helper::isConversionForm($form->id) || empty($placements['terms_and_condition']) || empty($placements['gdpr_agreement'])) {
640 return $placements;
641 }
642
643 $placements['terms_and_condition']['general'] = [
644 'admin_field_label',
645 'validation_rules',
646 'tnc_html',
647 'tc_agree_text',
648 'tc_dis_agree_text',
649 ];
650
651 $placements['terms_and_condition']['generalExtras'] = [
652 'tc_agree_text' => [
653 'template' => 'inputText',
654 'label' => 'Agree Button Text',
655 ],
656 'tc_dis_agree_text' => [
657 'template' => 'inputText',
658 'label' => 'Disagree Button Text',
659 ],
660 'hide_disagree' => [
661 'template' => 'inputCheckbox',
662 'options' => [
663 [
664 'value' => false,
665 'label' => __('Hide Disagree Button', 'fluentform'),
666 ],
667 ],
668 ],
669 ];
670
671 $placements['gdpr_agreement']['generalExtras'] = [
672 'tc_agree_text' => [
673 'template' => 'inputText',
674 'label' => 'Agree Option Text',
675 ],
676 ];
677
678 return $placements;
679 }
680
681 private function getExtraHiddenInputs($formId)
682 {
683 $inputs = [
684 '__fluent_form_embded_post_id' => get_the_ID(),
685 '_fluentform_' . $formId . '_fluentformnonce' => wp_create_nonce('fluentform-submit-form'),
686 '_wp_http_referer' => esc_attr(wp_unslash(wpFluentForm('request')->server('REQUEST_URI'))),
687 ];
688
689 // SECURITY (FINDING-25): carry the anti-spam honeypot field (empty) and a freshly minted
690 // token in the conversational submission. The conversational JS forwards every extra_input
691 // into the submission payload, so these reach the server-side checks — which no longer skip
692 // conversational forms — WITHOUT any client/JS change. This closes the bypass where adding
693 // isFFConversational=1 disabled honeypot + token protection entirely.
694 //
695 // The token itself is disabled for conversational forms server-side (see the
696 // fluentform/token_based_spam_protection_status filter in actions.php): its ~1h TTL cannot be
697 // refreshed by the conversational JS, so behind a full-page cache the baked-in token would
698 // expire and reject every submission. getConversationalTokenInput() therefore returns [] for
699 // conversational forms; the honeypot (static empty field) still applies.
700 $honeyPot = new \FluentForm\App\Modules\Form\HoneyPot(wpFluentForm());
701 $inputs = array_merge($inputs, $honeyPot->getConversationalHoneypotInput($formId));
702
703 $inputs = array_merge(
704 $inputs,
705 \FluentForm\App\Modules\Form\TokenBasedSpamProtection::getConversationalTokenInput($formId)
706 );
707
708 return apply_filters('fluentform/conversational_extra_inputs', $inputs, $formId);
709 }
710
711 public function getRandomPhoto()
712 {
713 return fluentFormGetRandomPhoto();
714 }
715
716 public function renderFormHtml($formId, $providedKey = '')
717 {
718 $form = wpFluent()->table('fluentform_forms')->find($formId);
719
720 if (!$form) {
721 return '';
722 }
723
724 $formSettings = wpFluent()
725 ->table('fluentform_form_meta')
726 ->where('form_id', $formId)
727 ->where('meta_key', 'formSettings')
728 ->first();
729
730 if (!$formSettings) {
731 return '';
732 }
733
734 $form->fields = json_decode($form->form_fields, true);
735
736 if (!$form->fields['fields']) {
737 return '';
738 }
739
740 $form->settings = json_decode($formSettings->value, true);
741
742 if ($form->status == 'unpublished') {
743 global $wp_query;
744 $wp_query->set_404();
745 status_header(404);
746 nocache_headers();
747 include(get_query_template('404'));
748 exit();
749 }
750
751 $metaSettings = $this->getMetaSettings($formId);
752
753 $shareKey = ArrayHelper::get($metaSettings, 'share_key');
754 if ($shareKey) {
755 if ($providedKey != $shareKey && !Acl::hasAnyFormPermission($formId)) {
756 return '';
757 }
758 }
759
760 $isRenderable = apply_filters('fluentform/is_form_renderable', [
761 'status' => true,
762 'message' => '',
763 ], $form);
764
765 if (is_array($isRenderable) && !$isRenderable['status'] && !Acl::hasAnyFormPermission($formId)) {
766
767 echo wp_kses_post("<div style='text-align: center; font-size: 16px; margin: 100px 20px;' id='ff_form_{$form->id}' class='ff_form_not_render'>" . $isRenderable['message'] . "</div>");
768 exit(200);
769 }
770
771
772 /* This filter is deprecated and will be removed soon */
773 $form = wpFluentForm()->applyFilters('fluentform_rendering_form', $form);
774
775 $form = wpFluentForm()->applyFilters('fluentform/rendering_form', $form);
776 $fileUploadSettings = apply_filters('fluentform/file_upload_settings_for_js', [], $form);
777
778 $form = Converter::convert($form);
779
780 $this->enqueueScripts();
781 do_action('fluentform/conversational_enqueue_assets', $form, $fileUploadSettings);
782
783 $formSettings = wpFluent()
784 ->table('fluentform_form_meta')
785 ->where('form_id', $form->id)
786 ->where('meta_key', 'formSettings')
787 ->first();
788
789 if (!$formSettings) {
790 return '';
791 }
792
793 $form->settings = json_decode($formSettings->value, true);
794
795 $submitCss = $this->getSubmitBttnStyle($form);
796
797 $designSettings = $this->getDesignSettings($formId);
798
799 $localizedVars = [
800 // SECURITY (H-01): do not emit the admin AJAX nonce on the public conversational form
801 // page — the public form JS never uses it, and on a page an admin happens to view it
802 // would embed that admin's own valid nonce. Emit it only for a viewer who could use it.
803 'fluent_forms_admin_nonce' => current_user_can('fluentform_dashboard_access') ? wp_create_nonce('fluent_forms_admin_nonce') : '',
804 'ajaxurl' => admin_url('admin-ajax.php'),
805 'nonce' => wp_create_nonce(),
806 'form' => $this->getLocalizedForm($form),
807 'form_id' => $form->id,
808 'assetBaseUrl' => FLUENT_CONVERSATIONAL_FORM_DIR_URL . 'public',
809 'i18n' => $metaSettings['i18n'],
810 'design' => $designSettings,
811 'hasPro' => defined('FLUENTFORMPRO'),
812 'extra_inputs' => $this->getExtraHiddenInputs($formId),
813 'uploading_txt' => __('Uploading', 'fluentform'),
814 'upload_completed_txt' => __('100% Completed', 'fluentform'),
815 'unknown_error_txt' => __('An unknown error occurred', 'fluentform'),
816 'request_error_txt' => __('An error occurred while processing your request', 'fluentform'),
817 'paymentConfig' => $this->getPaymentConfig($form),
818 'date_i18n' => \FluentForm\App\Modules\Component\Component::getDatei18n(),
819 'rest' => Helper::getRestInfo(),
820 'file_delete_nonce' => wp_create_nonce('fluentform_file_delete'),
821 'file_upload_settings' => $fileUploadSettings,
822 ];
823
824 wp_localize_script(
825 'fluent_forms_conversational_form',
826 'fluent_forms_global_var',
827 apply_filters('fluentform/global_form_vars', $localizedVars)
828 );
829
830 $hasSaveProgressButton = false;
831 $saveProgressButton = [];
832 foreach ($form->fields['fields'] as $item) {
833 if (isset($item['element']) && $item['element'] === 'save_progress_button') {
834 $hasSaveProgressButton = true;
835 $saveProgressButton = $item;
836 }
837 }
838
839 if ($hasSaveProgressButton && $saveProgressButton) {
840 $this->localizeSaveProgressButton($saveProgressButton, $formId);
841 }
842
843 $this->printLoadedScripts();
844
845 $isRenderable = [
846 'status' => true,
847 'message' => '',
848 ];
849
850 /* This filter is deprecated and will be removed soon */
851 $isRenderable = apply_filters('fluentform_is_form_renderable', $isRenderable, $form);
852
853
854 $isRenderable = apply_filters('fluentform/is_form_renderable', $isRenderable, $form);
855
856 if (is_array($isRenderable) && !$isRenderable['status']) {
857 if (!Acl::hasAnyFormPermission($form->id)) {
858 echo wp_kses_post("<h1 style='width: 600px; margin: 200px auto; text-align: center;' id='ff_form_{$form->id}' class='ff_form_not_render'>" . $isRenderable['message'] . '</h1>');
859 exit();
860 }
861 }
862 /* This filter is deprecated and will be removed soon */
863 $status = apply_filters('fluentform-disabled_analytics', true);
864
865 if (!apply_filters('fluentform/disabled_analytics', $status)) {
866 if (!Acl::hasAnyFormPermission($form->id)) {
867 (new \FluentForm\App\Services\Analytics\AnalyticsService())->store($form->id);
868 }
869 }
870 wpFluentForm('view')->render('public.conversational-form', [
871 'generated_css' => $this->getGeneratedCss($formId),
872 'design' => $designSettings,
873 'submit_css' => $submitCss,
874 'form_id' => $formId,
875 'meta' => $metaSettings,
876 'form' => $form,
877 ]);
878
879 exit(200);
880 }
881
882 /**
883 * Enqueue proper stylesheet based on rtl & JS script.
884 */
885 private function enqueueScripts()
886 {
887 $cssFileName = 'conversationalForm';
888
889 if (is_rtl()) {
890 $cssFileName .= '-rtl';
891 }
892
893 wp_enqueue_style(
894 'fluent_forms_conversational_form',
895 FLUENT_CONVERSATIONAL_FORM_DIR_URL . 'public/css/' . $cssFileName . '.css',
896 [],
897 FLUENTFORM_VERSION
898 );
899
900 wp_enqueue_script(
901 'fluent_forms_conversational_form',
902 FLUENT_CONVERSATIONAL_FORM_DIR_URL . 'public/js/conversationalForm.js',
903 [],
904 FLUENTFORM_VERSION,
905 true
906 );
907 }
908
909 /**
910 * Get the payment configuration of this form.
911 *
912 * @param $form
913 */
914 private function getPaymentConfig($form)
915 {
916 $paymentConfig = null;
917
918 if ($form->has_payment) {
919 $publishableKeyStripe = StripeSettings::getPublishableKey($form->id);
920 $publishableKeyStripe = apply_filters_deprecated(
921 'fluentform-payment_stripe_publishable_key',
922 [
923 $publishableKeyStripe,
924 $form->id
925 ],
926 FLUENTFORM_FRAMEWORK_UPGRADE,
927 'fluentform/payment_stripe_publishable_key',
928 'Use fluentform/payment_stripe_publishable_key instead of fluentform-payment_stripe_publishable_key.'
929 );
930
931 $publishableKey = apply_filters(
932 'fluentform/payment_stripe_publishable_key',
933 $publishableKeyStripe,
934 $form->id
935 );
936
937 $paymentConfig = [
938 'currency_settings' => PaymentHelper::getCurrencyConfig($form->id),
939 'stripe' => [
940 'publishable_key' => $publishableKey,
941 'inlineConfig' => PaymentHelper::getStripeInlineConfig($form->id),
942 ],
943 'stripe_app_info' => [
944 'name' => 'Fluent Forms',
945 'version' => FLUENTFORM_VERSION,
946 'url' => site_url(),
947 'partner_id' => 'pp_partner_FN62GfRLM2Kx5d',
948 ],
949 'i18n' => [
950 'item' => __('Item', 'fluentform'),
951 'price' => __('Price', 'fluentform'),
952 'qty' => __('Qty', 'fluentform'),
953 'line_total' => __('Line Total', 'fluentform'),
954 'total' => __('Total', 'fluentform'),
955 'not_found' => __('No payment item selected yet', 'fluentform'),
956 'discount:' => __('Discount:', 'fluentform'),
957 'processing_text' => __('Processing payment. Please wait...', 'fluentform'),
958 'confirming_text' => __('Confirming payment. Please wait...', 'fluentform'),
959 'signup_fee_for' => __('Signup Fee for', 'fluentform'),
960 ],
961 ];
962
963 $paymentConfig['currency_settings']['currency_symbol'] = \html_entity_decode($paymentConfig['currency_settings']['currency_sign']);
964 }
965
966 return $paymentConfig;
967 }
968
969 protected function getAsteriskPlacement($formId)
970 {
971 $asteriskPlacement = 'asterisk-right';
972
973 $formSettings = wpFluent()
974 ->table('fluentform_form_meta')
975 ->where('form_id', $formId)
976 ->where('meta_key', 'formSettings')
977 ->first();
978
979 if (!$formSettings) {
980 return '';
981 }
982
983 $formSettings = json_decode($formSettings->value, true);
984
985 if (isset($formSettings['layout']['asteriskPlacement'])) {
986 $asteriskPlacement = $formSettings['layout']['asteriskPlacement'];
987 }
988
989 return $asteriskPlacement;
990 }
991
992 private function getLocalizedForm($form)
993 {
994 return [
995 'id' => $form->id,
996 'questions' => $form->questions,
997 'image_preloads' => $form->image_preloads,
998 'submit_button' => $form->submit_button,
999 'hasPayment' => (bool)$form->has_payment,
1000 'hasCalculation' => (bool)$form->hasCalculation,
1001 'reCaptcha' => $form->reCaptcha,
1002 'hCaptcha' => $form->hCaptcha,
1003 'turnstile' => $form->turnstile,
1004 'has_per_step_save' => ArrayHelper::get($form->settings, 'conv_form_per_step_save', false),
1005 'has_resume_from_last_step' => ArrayHelper::get($form->settings, 'conv_form_resume_from_last_step', false),
1006 'has_save_link' => $form->save_state?? false,
1007 'has_save_and_resume_button'=> $form->hasSaveAndResumeButton ?? false,
1008 'step_completed' => $form->stepCompleted ?? 0
1009 ];
1010 }
1011
1012 public function localizeSaveProgressButton($field, $formId)
1013 {
1014 $vars = apply_filters('fluentform/save_progress_vars', [
1015 'ajaxurl' => admin_url('admin-ajax.php'),
1016 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in getFrontendFacingUrl()
1017 'source_url' => Helper::getFrontendFacingUrl($_SERVER['REQUEST_URI']),
1018 'form_id' => $formId,
1019 'nonce' => wp_create_nonce(),
1020 'copy_button' => fluentFormMix('img/copy.svg'),
1021 'copy_success_button' => fluentFormMix('img/check.svg'),
1022 'email_button' => fluentFormMix('img/email.svg'),
1023 'email_placeholder_str' => __('Your Email Here', 'fluentform'),
1024 'email_resume_link_enabled' => false,
1025 'save_progress_btn_name' => ArrayHelper::get($field, 'attributes.name'),
1026 ]);
1027
1028 if (ArrayHelper::get($field, 'settings.email_resume_link_enabled')) {
1029 $vars['email_resume_link_enabled'] = true;
1030 }
1031
1032 wp_localize_script('fluent_forms_conversational_form', 'form_state_save_vars', $vars);
1033 }
1034 }
1035