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/Services/FormBuilder/Components/BaseComponent.php +196 -84 3.6.42 → 6.2.15 View file →
@@ -1,101 +1,153 @@
1 1 <?php
2 2
3 3 namespace FluentForm\App\Services\FormBuilder\Components;
4 4
5 -use FluentForm\App;
5 +use FluentForm\Framework\Helpers\ArrayHelper;
6 +use FluentForm\App\Services\FormBuilder\AutocompleteTokens;
6 7 use FluentForm\App\Modules\Component\Component;
7 -use FluentForm\Framework\Helpers\ArrayHelper;
8 +use FluentForm\Framework\Support\Helper;
9 +use stdClass;
8 10
9 11 class BaseComponent
10 12 {
11 -
12 13 public $app;
13 -
14 +
14 15 public function __construct($key = '', $title = '', $tags = [], $position = 'advanced')
15 16 {
16 17 $this->app = wpFluentForm();
17 18 }
18 -
19 +
19 20 /**
20 - * Build unique ID concating form id and name attribute
21 - * @param array $data $form
21 + * Build unique ID concatenating form id and name attribute
22 + *
23 + * @param array $data $form
24 + *
22 25 * @return string for id value
23 26 */
24 27 protected function makeElementId($data, $form)
25 28 {
26 29 if (isset($data['attributes']['name'])) {
27 - if(!empty($data['attributes']['id'])) {
30 + $formInstance = \FluentForm\App\Helpers\Helper::$formInstance;
31 + if (!empty($data['attributes']['id'])) {
28 32 return $data['attributes']['id'];
29 33 }
30 34 $elementName = $data['attributes']['name'];
31 35 $elementName = str_replace(['[', ']', ' '], '_', $elementName);
32 - return "ff_{$form->id}_{$elementName}";
36 +
37 + $suffix = esc_attr($form->id);
38 + if ($formInstance > 1) {
39 + $suffix = $suffix . '_' . $formInstance;
40 + }
41 +
42 + $suffix .= '_' . $elementName;
43 +
44 + return 'ff_' . esc_attr($suffix);
33 45 }
34 46 }
35 -
47 +
36 48 /**
37 49 * Build attributes for any html element
38 - * @param array $attributes
50 + *
51 + * @param array $attributes
52 + *
39 53 * @return string [Compiled key='value' attributes]
40 54 */
41 55 protected function buildAttributes($attributes, $form = null)
42 56 {
43 57 $atts = '';
58 +
59 + $attributes = AutocompleteTokens::normalizeAttributes($attributes);
44 60
45 61 foreach ($attributes as $key => $value) {
46 - if ($value || $value === 0 || $value === '0') {
62 +
63 + if ($value || 0 === $value || '0' === $value) {
47 64 $value = htmlspecialchars($value);
48 - $atts .= $key.'="'.$value.'" ';
65 + $atts .= esc_attr($key) . '="' . $value . '" ';
49 66 }
50 67 }
51 -
68 +
52 69 return $atts;
53 70 }
54 -
71 +
55 72 /**
56 73 * Extract value attribute from attribute list
57 - * @param array &$element
74 + *
75 + * @param array &$element
76 + *
58 77 * @return string
59 78 */
60 79 protected function extractValueFromAttributes(&$element)
61 80 {
62 81 $value = '';
63 -
64 - if(isset($element['attributes']['value'])) {
82 +
83 + if (isset($element['attributes']['value'])) {
65 84 $value = $element['attributes']['value'];
66 85 unset($element['attributes']['value']);
67 86 }
68 -
87 +
69 88 return $value;
70 89 }
71 -
90 +
91 + protected function extractDynamicValues($data, $form)
92 + {
93 + $defaultValues = [];
94 + if ($dynamicDefaultValue = ArrayHelper::get($data, 'settings.dynamic_default_value')) {
95 + $parseValue = $this->parseEditorSmartCode($dynamicDefaultValue, $form);
96 + if (is_array($parseValue)) {
97 + $defaultValues = $parseValue;
98 + } elseif (!empty($parseValue) && is_string($parseValue)) {
99 + $defaultValues = explode(',', $parseValue);
100 + $defaultValues = array_map('trim', $defaultValues);
101 + }
102 + }
103 + return $defaultValues;
104 + }
105 +
72 106 /**
73 107 * Determine if the given element has conditions bound
74 - * @param array $element [Html element being compiled]
75 - * @return boolean
108 + *
109 + * @param array $element [Html element being compiled]
110 + *
111 + * @return bool
76 112 */
77 113 protected function hasConditions($element)
78 114 {
79 115 $conditionals = ArrayHelper::get($element, 'settings.conditional_logics');
116 +
117 + if (isset($conditionals['status']) && $conditionals['status']) {
118 + if (isset($conditionals['type']) && $conditionals['type'] === 'group') {
119 + $groups = ArrayHelper::get($conditionals, 'condition_groups');
120 + if (!is_array($groups)) {
121 + return false;
122 + }
123 +
124 + $groups = array_filter($groups, function ($group) {
125 + $rules = ArrayHelper::get($group, 'rules', []);
126 + return !empty(array_filter($rules, fn($rule) => !empty($rule['field']) && !empty($rule['operator'])));
127 + });
128 + return !!$groups;
129 + }
80 130
81 - if (isset($conditionals['status']) && $conditionals['status']) {
82 - return array_filter($conditionals['conditions'], function($item) {
131 + return !!array_filter($conditionals['conditions'], function ($item) {
83 132 return $item['field'] && $item['operator'];
84 133 });
85 134 }
135 + return false;
86 136 }
87 -
137 +
88 138 /**
89 139 * Generate a unique id for an element
90 - * @param string $str [preix]
140 + *
141 + * @param string $str [preix]
142 + *
91 143 * @return string [Unique id]
92 144 */
93 145 protected function getUniqueId($str)
94 146 {
95 - return $str.'_'.md5(uniqid(mt_rand(), true));
147 + return $str . '_' . md5(uniqid(wp_rand(), true));
96 148 }
97 -
149 +
98 150 /**
99 151 * Get a default class for each form element wrapper
100 152 * @return string
101 153 */
@@ -102,12 +154,14 @@
102 154 protected function getDefaultContainerClass()
103 155 {
104 156 return 'ff-el-group ';
105 157 }
106 -
158 +
107 159 /**
108 160 * Get required class for form element wrapper
109 - * @param array $rules [Validation rules]
161 + *
162 + * @param array $rules [Validation rules]
163 + *
110 164 * @return mixed
111 165 */
112 166 protected function getRequiredClass($rules)
113 167 {
@@ -114,51 +168,56 @@
114 168 if (isset($rules['required'])) {
115 169 return $rules['required']['value'] ? 'ff-el-is-required ' : '';
116 170 }
117 171 }
118 -
172 +
119 173 /**
120 174 * Get asterisk placement for the required form elements
121 - * @return String
175 + * @return string
122 176 */
123 177 protected function getAsteriskPlacement($form)
124 178 {
125 179 // for older version compatibility
126 180 $asteriskPlacement = 'asterisk-right';
127 -
181 +
128 182 if (isset($form->settings['layout']['asteriskPlacement'])) {
129 183 $asteriskPlacement = $form->settings['layout']['asteriskPlacement'];
130 184 }
131 -
132 - return $asteriskPlacement.' ';
185 +
186 + return $asteriskPlacement . ' ';
133 187 }
134 -
188 +
135 189 /**
136 190 * Generate a label for any element
137 - * @param array $data
191 + *
192 + * @param array $data
193 + *
138 194 * @return string [label Html element]
139 195 */
140 196 protected function buildElementLabel($data, $form)
141 197 {
142 198 $helpMessage = '';
143 - if ($form->settings['layout']['helpMessagePlacement'] == 'with_label') {
199 + $helpMessagePlacement = ArrayHelper::get($form->settings, 'layout.helpMessagePlacement', 'with_label');
200 + if ('with_label' == $helpMessagePlacement) {
144 201 $helpMessage = $this->getLabelHelpMessage($data);
145 202 }
146 -
203 +
147 204 $id = isset($data['attributes']['id']) ? $data['attributes']['id'] : '';
148 205 $label = isset($data['settings']['label']) ? $data['settings']['label'] : '';
149 206 $requiredClass = $this->getRequiredClass(ArrayHelper::get($data, 'settings.validation_rules', []));
150 207 $classes = trim('ff-el-input--label ' . $requiredClass . $this->getAsteriskPlacement($form));
151 -
152 - return "<div class='".$classes."'><label for='".$id."'>".$label."</label>{$helpMessage}</div>";
208 +
209 + return "<div class='" . esc_attr($classes) . "'><label aria-label='" . esc_attr($this->removeShortcode($label)) . "' for='" . esc_attr($id) . "'>" . fluentform_sanitize_html($label) . '</label>' . $helpMessage . '</div>';
153 210 }
154 -
211 +
155 212 /**
156 213 * Generate html/markup for any element
157 - * @param string $elMarkup [Predifined partial markup]
158 - * @param array $data
159 - * @param StdClass $form [Form object]
160 - * @return string [Compiled markup]
214 + *
215 + * @param string $elMarkup [Predifined partial markup]
216 + * @param array $data
217 + * @param stdClass $form [Form object]
218 + *
219 + * @return string [Compiled markup]
161 220 */
162 221 protected function buildElementMarkup($elMarkup, $data, $form)
163 222 {
164 223 $hasConditions = $this->hasConditions($data) ? 'has-conditions ' : '';
@@ -164,97 +223,150 @@
164 223 $hasConditions = $this->hasConditions($data) ? 'has-conditions ' : '';
165 224
166 225 $labelPlacement = ArrayHelper::get($data, 'settings.label_placement');
167 226
168 - $labelPlacementClass = $labelPlacement ? 'ff-el-form-'.$labelPlacement.' ' : '';
169 -
227 + $labelPlacementClass = $labelPlacement ? 'ff-el-form-' . $labelPlacement . ' ' : '';
228 +
170 229 $validationRules = ArrayHelper::get($data, 'settings.validation_rules');
171 -
230 +
231 + $requiredClass = $this->getRequiredClass($validationRules);
232 +
172 233 $labelClass = trim(
173 - 'ff-el-input--label '.
174 - $this->getRequiredClass($validationRules).
234 + 'ff-el-input--label ' .
235 + $requiredClass .
175 236 $this->getAsteriskPlacement($form)
176 237 );
177 -
238 +
178 239 $formGroupClass = trim(
179 - $this->getDefaultContainerClass().
180 - $labelPlacementClass.
181 - $hasConditions.
240 + $this->getDefaultContainerClass() .
241 + $labelPlacementClass .
242 + $hasConditions .
182 243 ArrayHelper::get($data, 'settings.container_class')
183 244 );
184 -
245 +
185 246 $labelHelpText = $inputHelpText = '';
186 247
187 - if ($form->settings['layout']['helpMessagePlacement'] == 'with_label') {
248 + $labelPlacement = ArrayHelper::get($form->settings, 'layout.helpMessagePlacement', 'with_label');
249 + if ('with_label' == $labelPlacement) {
188 250 $labelHelpText = $this->getLabelHelpMessage($data);
189 - } elseif ($form->settings['layout']['helpMessagePlacement'] == 'on_focus') {
251 + } elseif ('on_focus' == $labelPlacement) {
190 252 $inputHelpText = $this->getInputHelpMessage($data, 'ff-hidden');
253 + } elseif ('after_label' == $labelPlacement) {
254 + $inputHelpText = $this->getInputHelpMessage($data, 'ff_ahm');
191 255 } else {
192 256 $inputHelpText = $this->getInputHelpMessage($data);
193 257 }
194 -
258 +
195 259 $forStr = '';
260 + $LabelId = '';
196 261 if (isset($data['attributes']['id'])) {
197 - $forStr = "for='{$data['attributes']['id']}'";
262 + $forStr = "for='" . esc_attr($data['attributes']['id']) . "'";
263 + $LabelId = "id='" . esc_attr('label_' . $data['attributes']['id']) . "'";
198 264 }
199 -
265 +
200 266 $labelMarkup = '';
201 -
267 +
202 268 if (!empty($data['settings']['label'])) {
203 269 $label = ArrayHelper::get($data, 'settings.label');
270 + $ariaLabel = $label;
204 271
272 + $hasShortCodeIndex = strpos($label, '{dynamic.');
273 +
274 + //Handle name field duplicate label accessibility
275 + $isNameField = strpos(ArrayHelper::get($data, 'attributes.name', ''), 'name') !== false;
276 + if ($isNameField) {
277 + $ariaLabel = '';
278 + } elseif ($hasShortCodeIndex !== false) {
279 + $ariaLabel = trim(substr($label, 0, $hasShortCodeIndex));
280 + } else {
281 + $ariaLabel = $label;
282 + }
283 +
205 284 $labelMarkup = sprintf(
206 - "<div class='%s'><label %s>%s</label> %s</div>",
207 - $labelClass,
285 + '<div class="%1$s"><label %2$s %3$s %4$s>%5$s</label>%6$s</div>',
286 + esc_attr($labelClass),
208 287 $forStr,
209 - $label,
210 - $labelHelpText
288 + $LabelId,
289 + $ariaLabel != '' ? 'aria-label="' . esc_attr($this->removeShortcode($label)) . '"' : '',
290 + fluentform_sanitize_html($label),
291 + fluentform_sanitize_html($labelHelpText)
211 292 );
212 -
213 - // $labelMarkup = "<div class='".$labelClass."'><label ".$forStr.">".$label."</label> {$labelHelpText}</div>";
214 293 }
215 -
294 +
295 + $inputHelpText = fluentform_sanitize_html($inputHelpText);
296 +
297 + if ('after_label' == $labelPlacement) {
298 + $elMarkup = $inputHelpText . $elMarkup;
299 + $inputHelpText = '';
300 + }
301 +
216 302 return sprintf(
217 303 "<div class='%s'>%s<div class='ff-el-input--content'>%s%s</div></div>",
218 - $formGroupClass,
304 + esc_attr($formGroupClass),
219 305 $labelMarkup,
220 306 $elMarkup,
221 307 $inputHelpText
222 308 );
223 -
224 - // return "<div class='".$formGroupClass."'>{$labelMarkup}<div class='ff-el-input--content'>{$elMarkup}{$inputHelpText}</div></div>";
225 309 }
226 -
310 +
227 311 /**
228 312 * Generate a help message for any element beside label
229 - * @param array $data
313 + *
314 + * @param array $data
315 + *
230 316 * @return string [Html]
231 317 */
232 318 protected function getLabelHelpMessage($data)
233 319 {
234 - if (isset($data['settings']['help_message']) && $data['settings']['help_message'] != '') {
235 - return "<div class='ff-el-tooltip' data-content='{$data['settings']['help_message']}'>
236 - <i class='ff-icon icon-info-circle'></i>
237 - </div>";
320 + if (isset($data['settings']['help_message']) && '' != $data['settings']['help_message']) {
321 + $text = htmlspecialchars($data['settings']['help_message']);
322 + $icon = '<svg width="16" height="16" viewBox="0 0 25 25"><path d="m329 393l0-46c0-2-1-4-2-6-2-2-4-3-7-3l-27 0 0-146c0-3-1-5-3-7-2-1-4-2-7-2l-91 0c-3 0-5 1-7 2-1 2-2 4-2 7l0 46c0 2 1 5 2 6 2 2 4 3 7 3l27 0 0 91-27 0c-3 0-5 1-7 3-1 2-2 4-2 6l0 46c0 3 1 5 2 7 2 1 4 2 7 2l128 0c3 0 5-1 7-2 1-2 2-4 2-7z m-36-256l0-46c0-2-1-4-3-6-2-2-4-3-7-3l-54 0c-3 0-5 1-7 3-2 2-3 4-3 6l0 46c0 3 1 5 3 7 2 1 4 2 7 2l54 0c3 0 5-1 7-2 2-2 3-4 3-7z m182 119c0 40-9 77-29 110-20 34-46 60-80 80-33 20-70 29-110 29-40 0-77-9-110-29-34-20-60-46-80-80-20-33-29-70-29-110 0-40 9-77 29-110 20-34 46-60 80-80 33-20 70-29 110-29 40 0 77 9 110 29 34 20 60 46 80 80 20 33 29 70 29 110z" transform="scale(0.046875 0.046875)"></path></svg>';
323 + return sprintf('<div class="ff-el-tooltip" data-content="%s">%s</div>', $text, $icon);
238 324 }
239 325 }
240 -
326 +
241 327 /**
242 328 * Generate a help message for any element beside form element
243 - * @param array $data
329 + *
330 + * @param array $data
331 + *
244 332 * @return string [Html]
245 333 */
246 334 protected function getInputHelpMessage($data, $hideClass = '')
247 335 {
248 - $class = trim('ff-el-help-message '.$hideClass);
249 -
336 + $class = trim('ff-el-help-message ' . $hideClass);
337 +
250 338 if (isset($data['settings']['help_message']) && !empty($data['settings']['help_message'])) {
251 - return "<div class='{$class}'>{$data['settings']['help_message']}</div>";
339 + return "<div class='" . esc_attr($class) . "'>" . fluentform_sanitize_html($data['settings']['help_message']) . '</div>';
252 340 }
253 341 return false;
254 342 }
255 -
343 +
256 344 protected function parseEditorSmartCode($text, $form)
257 345 {
258 346 return (new Component($this->app))->replaceEditorSmartCodes($text, $form);
347 + }
348 +
349 + protected function printContent($hook, $html, $data, $form)
350 + {
351 + echo apply_filters($hook, $html, $data, $form); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped -- Dynamic hook name for component content filter. $html is escaped before being passed in.
352 + }
353 +
354 + /**
355 + * A helper method for remove shortcode from aria label
356 + *
357 + * @param string $label
358 + *
359 + * @return string $label
360 + */
361 + protected function removeShortcode($label)
362 + {
363 + // Find all occurrences of text enclosed in curly braces.
364 + preg_match_all('/{(.*?)}/', $label, $matches);
365 +
366 + // If there are matches, remove them from the label.
367 + if ($matches[0]) {
368 + $label = trim(str_replace($matches[0], '', $label));
369 + }
370 + return wp_strip_all_tags($label);
259 371 }
260 372 }