PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.11
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.11
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 / FormBuilder / FormBuilder.php

FormBuilder.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.11, at app/Services/FormBuilder/FormBuilder.php

604 lines 22.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\FormBuilder;
4
5 use FluentForm\App\Helpers\Helper;
6 use FluentForm\Framework\Helpers\ArrayHelper;
7
8 class FormBuilder
9 {
10 /**
11 * The Applivcation instance
12 *
13 * @var \FluentForm\Framework\Foundation\Application
14 */
15 protected $app = null;
16
17 protected $form = null;
18
19 /**
20 * Conditional logic for elements
21 *
22 * @var array
23 */
24 public $conditions = [];
25
26 /**
27 * Validation rules for elements
28 *
29 * @var array
30 */
31 public $validationRules = [];
32
33 public $tabIndex = 1;
34
35 public $fieldLists = [];
36
37 public $containerCounter;
38
39 /**
40 * Construct the form builder instance
41 *
42 * @param \FluentForm\Framework\Foundation\Application $app
43 */
44 public function __construct($app)
45 {
46 $this->app = $app;
47 $this->containerCounter = 1;
48 }
49
50 /**
51 * Render the form
52 *
53 * @param \StdClass $form [Form entry from database]
54 *
55 * @return mixed
56 */
57 public function build($form, $extraCssClass = '', $instanceCssClass = '', $atts = [])
58 {
59 $this->form = $form;
60 $hasStepWrapper = isset($form->fields['stepsWrapper']) && $form->fields['stepsWrapper'];
61
62 $labelPlacement = ArrayHelper::get($form->settings, 'layout.labelPlacement', 'top');
63
64 $formClass = "frm-fluent-form fluent_form_{$form->id} ff-el-form-{$labelPlacement}";
65
66 if ($extraCssClass) {
67 $formClass .= " {$extraCssClass}";
68 }
69
70 if ($hasStepWrapper) {
71 $formClass .= ' ff-form-has-steps';
72 }
73
74 if ($extraFormClass = Helper::formExtraCssClass($form)) {
75 $formClass .= ' ' . $extraFormClass;
76 }
77
78 $themeStyle = ArrayHelper::get($atts, 'theme');
79
80 if (!$themeStyle) {
81 $selectedStyle = Helper::getFormMeta($form->id, '_ff_selected_style');
82
83 $selectedStyle = $selectedStyle ? $selectedStyle : 'ffs_default';
84
85 $themeStyle = $selectedStyle;
86
87 $atts['theme'] = $selectedStyle;
88 }
89
90 $form->theme = $themeStyle;
91
92 $formBody = $this->buildFormBody($form);
93
94 if (strpos($formBody, '{dynamic.')) {
95 $formClass .= ' ff_has_dynamic_smartcode';
96 wp_enqueue_script('fluentform-advanced');
97 }
98
99 $formClass = apply_filters_deprecated(
100 'fluentform_form_class',
101 [
102 $formClass,
103 $form
104 ],
105 FLUENTFORM_FRAMEWORK_UPGRADE,
106 'fluentform/form_class',
107 'Use fluentform/form_class instead of fluentform_form_class.'
108 );
109
110 $formClass = apply_filters('fluentform/form_class', $formClass, $form);
111
112 if ($form->has_payment) {
113 $formClass .= ' fluentform_has_payment';
114 }
115
116 if ($themeStyle) {
117 $formClass .= ' ' . $themeStyle;
118 }
119
120 $data = [
121 'data-form_id' => $form->id,
122 'id' => 'fluentform_' . $form->id,
123 'class' => $formClass,
124 'data-form_instance' => $instanceCssClass,
125 'method' => 'POST',
126 ];
127
128 $data = apply_filters_deprecated(
129 'fluent_form_html_attributes',
130 [
131 $data,
132 $form
133 ],
134 FLUENTFORM_FRAMEWORK_UPGRADE,
135 'fluentform/html_attributes',
136 'Use fluentform/html_attributes instead of fluent_form_html_attributes.'
137 );
138
139 $formAttributes = apply_filters('fluentform/html_attributes', $data, $form);
140
141 $formAtts = $this->buildAttributes($formAttributes);
142
143
144 $wrapperClasses = trim('fluentform ff-default fluentform_wrapper_' . $form->id . ' ' . ArrayHelper::get($atts, 'css_classes'));
145
146 if ($themeStyle === 'ffs_inherit_theme') {
147 $wrapperClasses = str_replace("ff-default", "ff-inherit-theme-style", $wrapperClasses);
148 } else if ($themeStyle) {
149 $wrapperClasses .= ' ' . $themeStyle . '_wrap';
150 }
151
152 $wrapperClasses = apply_filters('fluentform/form_wrapper_classes', $wrapperClasses, $form);
153 ob_start();
154
155 echo "<div class='" . esc_attr($wrapperClasses) . "'>";
156
157 do_action_deprecated(
158 'fluentform_before_form_render',
159 [
160 $form
161 ],
162 FLUENTFORM_FRAMEWORK_UPGRADE,
163 'fluentform/before_form_render',
164 'Use fluentform/before_form_render instead of fluentform_before_form_render.'
165 );
166
167 do_action('fluentform/before_form_render', $form, $atts);
168 echo '<form ' . $formAtts . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $formAtts is escaped before being passed in.
169
170 $isAccessible = apply_filters_deprecated(
171 'fluentform_disable_accessibility_fieldset',
172 [
173 true,
174 $form
175 ],
176 FLUENTFORM_FRAMEWORK_UPGRADE,
177 'fluentform/disable_accessibility_fieldset',
178 'Use fluentform/disable_accessibility_fieldset instead of fluentform_disable_accessibility_fieldset.'
179 );
180 $isAccessible = apply_filters('fluentform/disable_accessibility_fieldset', true, $form);
181
182 if ($isAccessible) {
183 echo $this->fieldsetHtml($form); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $form is escaped before being passed in.
184 }
185
186 /* This hook is deprecated & will be removed soon */
187 do_action('fluentform_form_element_start', $form);
188
189 do_action('fluentform/form_element_start', $form);
190
191 echo "<input type='hidden' name='__fluent_form_embded_post_id' value='" . esc_attr(get_the_ID()) . "' />";
192
193 wp_nonce_field(
194 'fluentform-submit-form',
195 '_fluentform_' . $form->id . '_fluentformnonce',
196 true,
197 true
198 );
199
200 echo $formBody; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $formBody is escaped before being passed in.
201
202 if ($isAccessible) {
203 echo '</fieldset>';
204 }
205
206 echo "</form><div id='fluentform_" . (int) $form->id . "_errors' class='ff-errors-in-stack ";
207
208 echo esc_attr($extraCssClass) . '_errors ' . esc_attr($instanceCssClass) . "_errors'></div></div>";
209
210 do_action_deprecated(
211 'fluentform_after_form_render',
212 [
213 $form
214 ],
215 FLUENTFORM_FRAMEWORK_UPGRADE,
216 'fluentform/after_form_render',
217 'Use fluentform/after_form_render instead of fluentform_after_form_render.'
218 );
219 do_action('fluentform/after_form_render', $form);
220
221 return ob_get_clean();
222 }
223
224 /**
225 * @param \stdClass $form
226 *
227 * @return string form body
228 */
229 public function buildFormBody($form)
230 {
231 $hasStepWrapper = isset($form->fields['stepsWrapper']) && $form->fields['stepsWrapper'];
232
233 ob_start();
234
235 $stepCounter = 1;
236
237 foreach ($form->fields['fields'] as $item) {
238 if ($hasStepWrapper && 'form_step' == $item['element']) {
239 $stepCounter++;
240 }
241
242 $this->setUniqueIdentifier($item);
243
244 $item = apply_filters_deprecated(
245 'fluentform_before_render_item',
246 [
247 $item,
248 $form
249 ],
250 FLUENTFORM_FRAMEWORK_UPGRADE,
251 'fluentform/before_render_item',
252 'Use fluentform/before_render_item instead of fluentform_before_render_item.'
253 );
254 $item = apply_filters('fluentform/before_render_item', $item, $form);
255
256 do_action_deprecated(
257 'fluentform_render_item_' . $item['element'],
258 [
259 $item,
260 $form
261 ],
262 FLUENTFORM_FRAMEWORK_UPGRADE,
263 'fluentform/render_item_' . $item['element'],
264 'Use fluentform/render_item_' . $item['element'] . ' instead of fluentform_render_item_' . $item['element']
265 );
266
267 do_action('fluentform/render_item_' . $item['element'], $item, $form);
268
269 $this->extractValidationRules($item);
270
271 $this->extractConditionalLogic($item);
272 }
273
274 if ($hasStepWrapper) {
275 do_action_deprecated(
276 'fluentform_render_item_step_end',
277 [
278 $form->fields['stepsWrapper']['stepEnd'],
279 $form
280 ],
281 FLUENTFORM_FRAMEWORK_UPGRADE,
282 'fluentform/render_item_step_end',
283 'Use fluentform/render_item_step_end instead of fluentform_render_item_step_end.'
284 );
285 do_action('fluentform/render_item_step_end', $form->fields['stepsWrapper']['stepEnd'], $form);
286 } else {
287 do_action_deprecated(
288 'fluentform_render_item_submit_button',
289 [
290 $form->fields['submitButton'],
291 $form
292 ],
293 FLUENTFORM_FRAMEWORK_UPGRADE,
294 'fluentform/render_item_submit_button',
295 'Use fluentform/render_item_submit_button instead of fluentform_render_item_submit_button.'
296 );
297 do_action('fluentform/render_item_submit_button', $form->fields['submitButton'], $form);
298 }
299
300 $content = ob_get_clean();
301
302 if ($hasStepWrapper) {
303 $startElement = $form->fields['stepsWrapper']['stepStart'];
304
305 $steps = ArrayHelper::get($startElement, 'settings.step_titles');
306
307 // check if $stepCounter == count()
308 if ($stepCounter > count($steps)) {
309 $fillCount = $stepCounter - count($steps);
310 foreach (range(1, $fillCount) as $item) {
311 $steps[] = '';
312 }
313 $startElement['settings']['step_titles'] = $steps;
314 }
315
316 $this->setUniqueIdentifier($startElement);
317
318 ob_start();
319
320 do_action_deprecated(
321 'fluentform_render_item_step_start',
322 [
323 $startElement,
324 $form
325 ],
326 FLUENTFORM_FRAMEWORK_UPGRADE,
327 'fluentform/render_item_step_start',
328 'Use fluentform/render_item_step_start instead of fluentform_render_item_step_start.'
329 );
330 do_action('fluentform/render_item_step_start', $startElement, $form);
331
332 $stepStatrt = ob_get_clean();
333
334 $content = $stepStatrt . $content;
335 }
336
337 return $content;
338 }
339
340 /**
341 * Set unique name/data-name for an element
342 *
343 * @param array &$item
344 *
345 * @return void
346 */
347 protected function setUniqueIdentifier(&$item)
348 {
349 if (isset($item['columns'])) {
350 $item['attributes']['data-name'] = 'ff_cn_id_' . $this->containerCounter;
351 $this->containerCounter++;
352 foreach ($item['columns'] as &$column) {
353 foreach ($column['fields'] as &$field) {
354 $this->setUniqueIdentifier($field);
355 }
356 }
357 } else {
358 if (! isset($item['attributes']['name'])) {
359 if ($this->form) {
360 if (empty($this->form->attr_name_index)) {
361 $this->form->attr_name_index = 1;
362 } else {
363 $this->form->attr_name_index += 1;
364 }
365 $uniqueId = $this->form->id . '_' . $this->form->attr_name_index;
366 } else {
367 $uniqueId = uniqid(wp_rand(), true);
368 }
369
370 $item['attributes']['name'] = $item['element'] . '-' . $uniqueId;
371 }
372 $item['attributes']['data-name'] = $item['attributes']['name'];
373 $this->fieldLists[] = $item['element'];
374 }
375 }
376
377 /**
378 * Recursively extract validation rules from a given element
379 *
380 * @param array $item
381 *
382 * @return void
383 */
384 protected function extractValidationRules($item)
385 {
386 if (isset($item['columns']) && 'repeater_container' !== $item['element']) {
387 foreach ($item['columns'] as $column) {
388 foreach ($column['fields'] as $field) {
389 $this->extractValidationRules($field);
390 }
391 }
392 } elseif (isset($item['fields'])) {
393 $rootName = $item['attributes']['name'];
394 foreach ($item['fields'] as $key => $innerItem) {
395 if ('address' == $item['element'] || 'input_name' == $item['element']) {
396 $itemName = $innerItem['attributes']['name'];
397 $innerItem['attributes']['name'] = $rootName . '[' . $itemName . ']';
398 } else {
399 if ('input_repeat' == $item['element'] || 'repeater_field' == $item['element']) {
400 if (empty($innerItem['settings']['validation_rules']['email'])) {
401 unset($innerItem['settings']['validation_rules']['email']);
402 }
403 $innerItem['attributes']['name'] = $rootName . '[' . $key . ']';
404 } else {
405 $innerItem['attributes']['name'] = $rootName;
406 }
407 }
408 $this->extractValidationRule($innerItem);
409 }
410 } elseif ('tabular_grid' == $item['element']) {
411 $gridName = $item['attributes']['name'];
412 $gridRows = $item['settings']['grid_rows'];
413 $gridType = $item['settings']['tabular_field_type'];
414 foreach ($gridRows as $rowKey => $rowValue) {
415 $rowKey = trim(sanitize_text_field($rowKey));
416 if ('radio' == $gridType) {
417 $item['attributes']['name'] = $gridName . '[' . $rowKey . ']';
418 $this->extractValidationRule($item);
419 } else {
420 $item['attributes']['name'] = $gridName . '[' . $rowKey . ']';
421 $this->extractValidationRule($item);
422 }
423 }
424 } elseif ('chained_select' == $item['element']) {
425 $chainedSelectName = $item['attributes']['name'];
426 foreach ($item['settings']['data_source']['headers'] as $select) {
427 $item['attributes']['name'] = $chainedSelectName . '[' . $select . ']';
428 $this->extractValidationRule($item);
429 }
430 } elseif ('repeater_container' == $item['element']) {
431 $rootName = $item['attributes']['name'];
432 $ruleIndex = 0;
433 foreach ($item['columns'] as $key => $innerItem) {
434 foreach ($innerItem['fields'] as &$innerField) {
435 $innerField['attributes']['name'] = $rootName . '[' . $key . ']';
436 $rules = ArrayHelper::get($innerField, 'settings.validation_rules', []);
437 $innerField['settings']['validation_rules'] = $rules;
438 $this->extractValidationRule($innerField, $rootName . '[' . $ruleIndex . ']');
439 $ruleIndex++;
440 }
441 }
442 } else {
443 $this->extractValidationRule($item);
444 }
445 }
446
447 /**
448 * Extract validation rules from a given element
449 *
450 * @param array $item
451 *
452 * @return void
453 */
454 protected function extractValidationRule($item, $name = '')
455 {
456 if (isset($item['settings']['validation_rules'])) {
457 $rules = $item['settings']['validation_rules'];
458 foreach ($rules as $ruleName => $rule) {
459 if (isset($rule['message'])) {
460 if (isset($rule['global']) && $rule['global']) {
461 $rule['message'] = apply_filters('fluentform/get_global_message_' . $ruleName, $rule['message']);
462 }
463 // Shortcode parse on validation message
464 $rule['message'] = Helper::shortCodeParseOnValidationMessage($rule['message'], $this->form, ArrayHelper::get($item, 'attributes.name'));
465 $rules[$ruleName]['message'] = apply_filters_deprecated(
466 'fluentform_validation_message_' . $ruleName,
467 [
468 $rule['message'],
469 $item
470 ],
471 FLUENTFORM_FRAMEWORK_UPGRADE,
472 'fluentform/validation_message_' . $ruleName,
473 'Use fluentform/validation_message_' . $ruleName . ' instead of fluentform_validation_message_' . $ruleName
474 );
475 $rules[$ruleName]['message'] = apply_filters('fluentform/validation_message_' . $ruleName, $rule['message'], $item);
476
477 $rules[$ruleName]['message'] = apply_filters_deprecated(
478 'fluentform_validation_message_' . $item['element'] . '_' . $ruleName,
479 [
480 $rules[$ruleName]['message'],
481 $item
482 ],
483 FLUENTFORM_FRAMEWORK_UPGRADE,
484 'fluentform/validation_message_' . $item['element'] . '_' . $ruleName,
485 'Use fluentform/validation_message_' . $item['element'] . '_' . $ruleName . ' instead of fluentform_validation_message_' . $item['element'] . '_' . $ruleName
486 );
487 $rules[$ruleName]['message'] = apply_filters('fluentform/validation_message_' . $item['element'] . '_' . $ruleName, $rules[$ruleName]['message'], $item);
488 }
489 }
490
491 $rules = apply_filters_deprecated(
492 'fluentform_item_rules_' . $item['element'],
493 [
494 $rules,
495 $item
496 ],
497 FLUENTFORM_FRAMEWORK_UPGRADE,
498 'fluentform/item_rules_' . $item['element'],
499 'Use fluentform/item_rules_' . $item['element'] . ' instead of fluentform_item_rules_' . $item['element']
500 );
501
502 $rules = apply_filters('fluentform/item_rules_' . $item['element'], $rules, $item);
503 if (!$name) {
504 $name = $item['attributes']['name'];
505 }
506 $this->validationRules[$name] = $rules;
507 }
508 }
509
510 /**
511 * Extract conditional logic from a given element
512 *
513 * @param array $item
514 *
515 * @return void
516 */
517 protected function extractConditionalLogic($item, $itemName = '')
518 {
519 // If container like element, then recurse
520 if (isset($item['columns'])) {
521 $containerConditions = false;
522 if (isset($item['settings']['conditional_logics'])) {
523 $conditionals = $item['settings']['conditional_logics'];
524 if (isset($conditionals['status'])) {
525 if ($conditionals['status'] && $conditionals['conditions']) {
526 $containerConditions = $item['settings']['conditional_logics'];
527 $this->conditions[$item['attributes']['data-name']] = $containerConditions;
528 }
529 }
530 }
531 $parentName = ArrayHelper::get($item, 'attributes.name');
532 $isRepeaterContainer = ArrayHelper::get($item, 'element') === 'repeater_container';
533
534 foreach ($item['columns'] as $colIndex => $column) {
535 foreach ($column['fields'] as $fieldIndex => $field) {
536 if ($containerConditions) {
537 $field['container_conditions'] = $containerConditions;
538 }
539 if ($isRepeaterContainer) {
540 $conditionName = $parentName . '_condition_' . $colIndex . '_' . $fieldIndex;
541 $this->extractConditionalLogic($field, $conditionName);
542 } else {
543 $this->extractConditionalLogic($field);
544 }
545 }
546 }
547 } elseif (isset($item['settings']['conditional_logics'])) {
548 $conditionals = $item['settings']['conditional_logics'];
549 if (!$itemName) {
550 $itemName = $item['attributes']['data-name'];
551 }
552 if (isset($conditionals['status'])) {
553 if ($conditionals['status'] && $conditionals['conditions']) {
554 $this->conditions[$itemName] = $item['settings']['conditional_logics'];
555 }
556 }
557 if (isset($item['container_conditions'])) {
558 if (! isset($this->conditions[$item['attributes']['data-name']])) {
559 $this->conditions[$itemName] = [
560 'conditions' => [],
561 'status' => false,
562 'type' => 'any',
563 ];
564 }
565 $this->conditions[$itemName]['container_condition'] = $item['container_conditions'];
566 }
567 }
568 }
569
570 /**
571 * Build attributes for any html element
572 *
573 * @param array $attributes
574 *
575 * @return string [Compiled key='value' attributes]
576 */
577 protected function buildAttributes($attributes, $form = null)
578 {
579 $atts = '';
580 foreach ($attributes as $key => $value) {
581 if ($value || 0 === $value || '0' === $value) {
582 $value = htmlspecialchars($value);
583 $atts .= $key . '="' . $value . '" ';
584 }
585 }
586 return $atts;
587 }
588
589 /**
590 * Get hidden fieldset html
591 *
592 * @param $form
593 *
594 * @return string
595 */
596 private function fieldsetHtml($form)
597 {
598 return '<fieldset style="border: none!important;margin: 0!important;padding: 0!important;background-color: transparent!important;box-shadow: none!important;outline: none!important; min-inline-size: 100%;">
599 <legend class="ff_screen_reader_title" style="display: block; margin: 0!important;padding: 0!important;height: 0!important;text-indent: -999999px;width: 0!important;overflow:hidden;">'
600 .esc_html($form->title).
601 '</legend>';
602 }
603 }
604