PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.13
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.13
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 / Settings / SettingsService.php

SettingsService.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.13, at app/Services/Settings/SettingsService.php

471 lines 17.2 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\Settings;
4
5 use FluentForm\App\Models\Form;
6 use FluentForm\App\Helpers\Helper;
7 use FluentForm\App\Models\FormMeta;
8 use FluentForm\Framework\Support\Arr;
9 use FluentForm\App\Services\FluentConversational\Classes\Form as FluentConversational;
10
11 class SettingsService
12 {
13 public function get($attributes = [])
14 {
15 $metaKey = sanitize_text_field(Arr::get($attributes, 'meta_key'));
16
17 $formId = (int) Arr::get($attributes, 'form_id');
18
19 $result = FormMeta::where(['meta_key' => $metaKey, 'form_id' => $formId])->get();
20
21 foreach ($result as $item) {
22 $value = Helper::isJson($item->value) ? json_decode($item->value, true) : $item->value;
23
24 if ('notifications' == $metaKey) {
25 if (!$value) {
26 $value = ['name' => ''];
27 }
28 }
29
30 if (isset($value['layout']) && !isset($value['layout']['asteriskPlacement'])) {
31 $value['layout']['asteriskPlacement'] = 'asterisk-right';
32 }
33
34 $item->value = $value;
35 }
36
37 $result = apply_filters_deprecated(
38 'fluentform_get_meta_key_settings_response',
39 [
40 $result,
41 $formId,
42 $metaKey
43 ],
44 FLUENTFORM_FRAMEWORK_UPGRADE,
45 'fluentform/get_meta_key_settings_response',
46 'Use fluentform/get_meta_key_settings_response instead of fluentform_get_meta_key_settings_response'
47 );
48
49 return apply_filters('fluentform/get_meta_key_settings_response', $result, $formId, $metaKey);
50 }
51
52 public function general($formId)
53 {
54 $settings = [
55 'generalSettings' => Form::getFormsDefaultSettings($formId),
56 'advancedValidationSettings' => Form::getAdvancedValidationSettings($formId),
57 ];
58
59 $settings = apply_filters_deprecated(
60 'fluentform_form_settings_ajax',
61 [
62 $settings,
63 $formId
64 ],
65 FLUENTFORM_FRAMEWORK_UPGRADE,
66 'fluentform/form_settings_ajax',
67 'Use fluentform/form_settings_ajax instead of fluentform/form_settings_ajax'
68 );
69
70 $settings = apply_filters('fluentform/form_settings_ajax', $settings, $formId);
71
72 return $settings;
73 }
74
75 public function saveGeneral($attributes = [])
76 {
77 $formId = (int) Arr::get($attributes, 'form_id');
78
79 $formSettings = json_decode(Arr::get($attributes, 'formSettings'), true);
80
81 $formSettings = $this->sanitizeData($formSettings);
82
83 $advancedValidationSettings = json_decode(Arr::get($attributes, 'advancedValidationSettings'), true);
84
85 $advancedValidationSettings = $this->sanitizeData($advancedValidationSettings);
86
87 Validator::validate(
88 'confirmations',
89 Arr::get($formSettings, 'confirmation', [])
90 );
91
92 FormMeta::persist($formId, 'formSettings', $formSettings);
93
94 FormMeta::persist($formId, 'advancedValidationSettings', $advancedValidationSettings);
95
96 $deleteAfterXDaysStatus = Arr::get($formSettings, 'delete_after_x_days');
97 $deleteDaysCount = Arr::get($formSettings, 'auto_delete_days');
98 $deleteOnSubmission = Arr::get($formSettings, 'delete_entry_on_submission');
99
100 if ('yes' != $deleteOnSubmission && $deleteDaysCount && 'yes' == $deleteAfterXDaysStatus) {
101 // We have to set meta values
102 FormMeta::persist($formId, 'auto_delete_days', $deleteDaysCount);
103 } else {
104 // we have to delete meta values
105 FormMeta::remove($formId, 'auto_delete_days');
106 }
107
108 $convFormPerStepSave = Arr::get($formSettings, 'conv_form_per_step_save') && Helper::isConversionForm($formId);
109
110 if ($convFormPerStepSave) {
111 FormMeta::persist($formId, 'conv_form_per_step_save', true);
112 } else {
113 FormMeta::remove($formId, 'conv_form_per_step_save');
114 }
115
116 $convFormResumeFromLastStep = $convFormPerStepSave && Arr::get($formSettings, 'conv_form_resume_from_last_step');
117 if ($convFormResumeFromLastStep) {
118 FormMeta::persist($formId, 'conv_form_resume_from_last_step', true);
119 } else {
120 FormMeta::remove($formId, 'conv_form_resume_from_last_step');
121 }
122
123
124
125 do_action('fluentform/after_save_form_settings', $formId, $attributes);
126 }
127
128 private function sanitizeData($settings)
129 {
130 if (fluentformCanUnfilteredHTML()) {
131 return $settings;
132 }
133
134 $sanitizerMap = [
135 'redirectTo' => 'sanitize_text_field',
136 'redirectMessage' => 'fluentform_sanitize_html',
137 'messageToShow' => 'fluentform_sanitize_html',
138 'customPage' => 'sanitize_text_field',
139 'samePageFormBehavior' => 'sanitize_text_field',
140 'customUrl' => 'sanitize_url',
141 'enabled' => 'rest_sanitize_boolean',
142 'numberOfEntries' => 'intval',
143 'period' => 'sanitize_text_field',
144 'limitReachedMsg' => 'sanitize_text_field',
145 'start' => 'sanitize_text_field',
146 'end' => 'sanitize_text_field',
147 'pendingMsg' => 'sanitize_text_field',
148 'expiredMsg' => 'sanitize_text_field',
149 'requireLoginMsg' => 'sanitize_text_field',
150 'labelPlacement' => 'sanitize_text_field',
151 'helpMessagePlacement' => 'sanitize_text_field',
152 'errorMessagePlacement' => 'sanitize_text_field',
153 'asteriskPlacement' => 'sanitize_text_field',
154 'delete_entry_on_submission' => 'sanitize_text_field',
155 'id' => 'intval',
156 'showLabel' => 'rest_sanitize_boolean',
157 'showCount' => 'rest_sanitize_boolean',
158 'status' => 'rest_sanitize_boolean',
159 'type' => 'sanitize_text_field',
160 'field' => 'sanitize_text_field',
161 'operator' => 'sanitize_text_field',
162 'value' => 'sanitize_text_field',
163 'error_message' => 'sanitize_text_field',
164 'validation_type' => 'sanitize_text_field',
165 'name' => 'sanitize_text_field',
166 'email' => 'sanitize_text_field',
167 'fromName' => 'sanitize_text_field',
168 'fromEmail' => 'sanitize_text_field',
169 'replyTo' => 'sanitize_text_field',
170 'bcc' => 'sanitize_text_field',
171 'subject' => 'sanitize_text_field',
172 'message' => 'fluentform_sanitize_html',
173 'url' => 'sanitize_url',
174 'webhook' => 'sanitize_url',
175 'textTitle' => 'sanitize_text_field',
176 'conv_form_per_step_save' => 'rest_sanitize_boolean'
177 ];
178
179 return fluentform_backend_sanitizer($settings, $sanitizerMap);
180 }
181
182 public function store($attributes = [])
183 {
184 $formId = (int) Arr::get($attributes, 'form_id');
185
186 $value = Arr::get($attributes, 'value', '');
187
188 $valueArray = $value ? json_decode($value, true) : [];
189
190 $key = sanitize_text_field(Arr::get($attributes, 'meta_key'));
191
192 // SECURITY (FINDING-14): this generic settings store accepted an arbitrary meta_key with no
193 // allowlist, letting a forms_manager overwrite meta owned by dedicated, capability-gated
194 // endpoints — most importantly the unfiltered_html-gated custom JS/CSS keys (bypassing the
195 // boundary Customizer::store() enforces) and payment settings. Reject those keys here; each
196 // has its own proper route. Filterable so first-party code can extend the protected set.
197 $protectedKeys = apply_filters('fluentform/protected_form_meta_keys', [
198 '_custom_form_js',
199 '_custom_form_css',
200 '_payment_settings',
201 ]);
202 if (in_array($key, $protectedKeys, true)) {
203 throw new \FluentForm\Framework\Validator\ValidationException('', 422, null, [
204 'errors' => [
205 'meta_key' => [__('This settings key cannot be modified from this endpoint.', 'fluentform')],
206 ],
207 ]);
208 }
209
210 if ('formSettings' == $key) {
211 Validator::validate(
212 'confirmations',
213 Arr::get(
214 $valueArray,
215 'confirmation',
216 []
217 )
218 );
219 } else {
220 Validator::validate($key, $valueArray);
221 }
222
223 $valueArray = $this->sanitizeData($valueArray);
224
225 $value = json_encode($valueArray);
226
227 $data = [
228 'meta_key' => $key,
229 'value' => $value,
230 'form_id' => $formId,
231 ];
232
233 // If the request has an valid id field it's safe to assume
234 // that the user wants to update an existing settings.
235 // So, we'll proceed to do so by finding it first.
236 $id = (int) Arr::get($attributes, 'meta_id');
237
238 $settingsQuery = FormMeta::where('form_id', $formId);
239
240 $settings = null;
241 if ($id) {
242 $settings = $settingsQuery->find($id);
243 }
244
245 if (!empty($settings)) {
246 $settingsQuery->where('id', $settings->id)->update($data);
247 $insertId = $settings->id;
248 } else {
249 $insertId = $settingsQuery->insertGetId($data);
250 }
251
252 return [
253 $insertId,
254 $valueArray,
255 ];
256 }
257
258 public function remove($attributes = [])
259 {
260 $formId = intval(Arr::get($attributes, 'form_id'));
261 $id = intval(Arr::get($attributes, 'meta_id'));
262
263 FormMeta::where('form_id', $formId)->where('id', $id)->delete();
264 }
265
266 public function conversationalDesign($formId)
267 {
268 $conversationalForm = new FluentConversational();
269
270 return [
271 'design_settings' => $conversationalForm->getDesignSettings($formId),
272 'meta_settings' => $conversationalForm->getMetaSettings($formId),
273 'form_settings' => Form::getFormsDefaultSettings($formId),
274 'pretty_url' => $this->getPrettyUrlSettings($formId),
275 'has_pro' => defined('FLUENTFORMPRO'),
276 'has_pro_share_page' => $this->hasProSharePage(),
277 ];
278 }
279
280 public function storeConversationalDesign($attributes, $formId)
281 {
282 $metaKey = "ffc_form_settings";
283 $formId = intval($formId);
284
285 $attributes = fluentFormSanitizer($attributes);
286 $settings = Arr::get($attributes, 'design_settings');
287 FormMeta::persist($formId, $metaKey . '_design', $settings);
288
289 $generatedCss = wp_strip_all_tags(Arr::get($attributes, 'generated_css'));
290 if ($generatedCss) {
291 FormMeta::persist($formId, $metaKey . '_generated_css', $generatedCss);
292 }
293
294 $meta = Arr::get($attributes, 'meta_settings', []);
295 $metaSanitizationMap = [
296 'title' => 'sanitize_text_field',
297 'description' => [$this, 'secureMetaDescription'],
298 'featured_image' => 'esc_url_raw',
299 'share_key' => 'sanitize_text_field',
300 'google_font_href' => 'esc_url_raw',
301 'font_css' => 'wp_kses_post',
302 ];
303 foreach ($metaSanitizationMap as $key => $sanitizer) {
304 if (isset($meta[$key])) {
305 $meta[$key] = call_user_func($sanitizer, $meta[$key]);
306 }
307 }
308
309 if ($meta) {
310 FormMeta::persist($formId, $metaKey . '_meta', $meta);
311 }
312
313 $prettyUrl = $this->savePrettyUrlSettings(Arr::get($attributes, 'pretty_url'), $formId);
314
315 $formSettings = Arr::get($attributes, 'form_settings');
316 if (is_array($formSettings) && isset($formSettings['restrictions'])) {
317 $this->saveFormRestrictions($formId, Arr::get($formSettings, 'restrictions', []));
318 }
319
320 $params = [
321 'fluent-form' => $formId,
322 ];
323 if (isset($meta['share_key']) && !empty($meta['share_key'])) {
324 $params['form'] = $meta['share_key'];
325 }
326
327 $shareUrl = add_query_arg($params, Helper::getFrontendFacingUrl());
328 return [
329 'message' => __('Settings successfully updated','fluentform'),
330 'share_url' => $shareUrl,
331 'pretty_url' => $prettyUrl,
332 ];
333 }
334
335 public function saveFormRestrictions($formId, $restrictions)
336 {
337 $formId = intval($formId);
338
339 if (!is_array($restrictions)) {
340 return Form::getFormsDefaultSettings($formId);
341 }
342
343 $existingFormSettings = Form::getFormsDefaultSettings($formId);
344 $existingFormSettings['restrictions'] = $restrictions;
345 $existingFormSettings = $this->sanitizeData($existingFormSettings);
346
347 FormMeta::persist($formId, 'formSettings', $existingFormSettings);
348
349 return $existingFormSettings;
350 }
351
352 public function getPreset($formId)
353 {
354 $formId = intval($formId);
355 $selectedPreset = Helper::getFormMeta($formId, '_ff_selected_style', 'ffs_default');
356 $selectedPreset = $selectedPreset ?: 'ffs_default';
357
358 // Use Pro FormStyler presets if available
359 if (class_exists('\FluentFormPro\classes\FormStyler')) {
360 $formStyler = new \FluentFormPro\classes\FormStyler();
361 $presets = $formStyler->getPresets();
362 } else {
363 $presets = [
364 'ffs_default' => [
365 'label' => __('Default', 'fluentform'),
366 'style' => '[]',
367 ],
368 'ffs_inherit_theme' => [
369 'label' => __('Inherit Theme Style', 'fluentform'),
370 'style' => '{}',
371 ],
372 ];
373 }
374
375 return [
376 'selected_preset'=> $selectedPreset,
377 'presets' => $presets,
378 ];
379 }
380
381 /**
382 * @throws \Exception
383 */
384 public function savePreset($attributes)
385 {
386 $formId = intval(Arr::get($attributes, 'form_id'));
387 $selectedPreset = Arr::get($attributes, 'selected_preset');
388 if ($selectedPreset && Helper::setFormMeta($formId, '_ff_selected_style', $selectedPreset)) {
389 return [
390 'message' => __('Settings save successfully', 'fluentform'),
391 ];
392 }
393 throw new \Exception(esc_html__('Settings save failed', 'fluentform'));
394 }
395
396 public function secureMetaDescription($description) {
397 $clean = preg_replace(
398 [
399 '/url\s*=/', // Remove URL assignments
400 '/http-equiv\s*=/', // Remove HTTP equiv
401 '/refresh/', // Remove refresh attempts
402 ],
403 '',
404 $description
405 );
406
407 $clean = wp_strip_all_tags($clean);
408 $clean = sanitize_text_field($clean);
409
410 return trim($clean);
411 }
412
413 private function hasProSharePage()
414 {
415 return defined('FLUENTFORMPRO') && class_exists('\FluentFormPro\classes\SharePage\SharePage');
416 }
417
418 private function hasProPrettyUrl()
419 {
420 return defined('FLUENTFORMPRO') && class_exists('\FluentFormPro\classes\SharePage\FormPrettyUrlService');
421 }
422
423 private function getPrettyUrlSettings($formId)
424 {
425 if (!$this->hasProPrettyUrl()) {
426 return [
427 'available' => false,
428 'slug' => '',
429 'enabled' => false,
430 'pretty_url' => '',
431 'base_slug' => 'form',
432 ];
433 }
434
435 $service = '\FluentFormPro\classes\SharePage\FormPrettyUrlService';
436 $slug = $service::getSlug($formId);
437 if (!$slug) {
438 $form = Form::find($formId);
439 $slug = $form ? $service::generateSlug($form->title, $formId) : '';
440 }
441
442 return [
443 'available' => true,
444 'slug' => $slug,
445 'enabled' => $service::isEnabled($formId),
446 'pretty_url' => $service::getFormPrettyUrl($formId),
447 'base_slug' => $service::getBaseSlug(),
448 ];
449 }
450
451 private function savePrettyUrlSettings($prettyUrl, $formId)
452 {
453 if (!is_array($prettyUrl) || !$this->hasProPrettyUrl()) {
454 return $this->getPrettyUrlSettings($formId);
455 }
456
457 $service = '\FluentFormPro\classes\SharePage\FormPrettyUrlService';
458 $slug = sanitize_text_field(Arr::get($prettyUrl, 'slug', ''));
459 $enabled = Arr::isTrue($prettyUrl, 'enabled');
460 $savedSlug = $service::saveSlug($formId, $slug, $enabled);
461
462 return [
463 'available' => true,
464 'slug' => $savedSlug,
465 'enabled' => $service::isEnabled($formId),
466 'pretty_url' => $service::getFormPrettyUrl($formId),
467 'base_slug' => $service::getBaseSlug(),
468 ];
469 }
470 }
471