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 +169 -70 3.6.51 → 6.2.15 View file →
@@ -1,81 +1,102 @@
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 + * Build unique ID concatenating form id and name attribute
22 + *
21 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'])) {
30 + $formInstance = \FluentForm\App\Helpers\Helper::$formInstance;
27 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
50 + *
38 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 -
82 +
64 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 +
72 91 protected function extractDynamicValues($data, $form)
73 92 {
74 93 $defaultValues = [];
75 94 if ($dynamicDefaultValue = ArrayHelper::get($data, 'settings.dynamic_default_value')) {
76 95 $parseValue = $this->parseEditorSmartCode($dynamicDefaultValue, $form);
77 - if ($parseValue) {
96 + if (is_array($parseValue)) {
97 + $defaultValues = $parseValue;
98 + } elseif (!empty($parseValue) && is_string($parseValue)) {
78 99 $defaultValues = explode(',', $parseValue);
79 100 $defaultValues = array_map('trim', $defaultValues);
80 101 }
81 102 }
@@ -80,35 +101,53 @@
80 101 }
81 102 }
82 103 return $defaultValues;
83 104 }
84 -
105 +
85 106 /**
86 107 * Determine if the given element has conditions bound
108 + *
87 109 * @param array $element [Html element being compiled]
88 - * @return boolean
110 + *
111 + * @return bool
89 112 */
90 113 protected function hasConditions($element)
91 114 {
92 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 + }
93 130
94 - if (isset($conditionals['status']) && $conditionals['status']) {
95 - return array_filter($conditionals['conditions'], function ($item) {
131 + return !!array_filter($conditionals['conditions'], function ($item) {
96 132 return $item['field'] && $item['operator'];
97 133 });
98 134 }
135 + return false;
99 136 }
100 -
137 +
101 138 /**
102 139 * Generate a unique id for an element
140 + *
103 141 * @param string $str [preix]
142 + *
104 143 * @return string [Unique id]
105 144 */
106 145 protected function getUniqueId($str)
107 146 {
108 - return $str . '_' . md5(uniqid(mt_rand(), true));
147 + return $str . '_' . md5(uniqid(wp_rand(), true));
109 148 }
110 -
149 +
111 150 /**
112 151 * Get a default class for each form element wrapper
113 152 * @return string
114 153 */
@@ -115,12 +154,14 @@
115 154 protected function getDefaultContainerClass()
116 155 {
117 156 return 'ff-el-group ';
118 157 }
119 -
158 +
120 159 /**
121 160 * Get required class for form element wrapper
161 + *
122 162 * @param array $rules [Validation rules]
163 + *
123 164 * @return mixed
124 165 */
125 166 protected function getRequiredClass($rules)
126 167 {
@@ -127,68 +168,75 @@
127 168 if (isset($rules['required'])) {
128 169 return $rules['required']['value'] ? 'ff-el-is-required ' : '';
129 170 }
130 171 }
131 -
172 +
132 173 /**
133 174 * Get asterisk placement for the required form elements
134 - * @return String
175 + * @return string
135 176 */
136 177 protected function getAsteriskPlacement($form)
137 178 {
138 179 // for older version compatibility
139 180 $asteriskPlacement = 'asterisk-right';
140 -
181 +
141 182 if (isset($form->settings['layout']['asteriskPlacement'])) {
142 183 $asteriskPlacement = $form->settings['layout']['asteriskPlacement'];
143 184 }
144 -
185 +
145 186 return $asteriskPlacement . ' ';
146 187 }
147 -
188 +
148 189 /**
149 190 * Generate a label for any element
191 + *
150 192 * @param array $data
193 + *
151 194 * @return string [label Html element]
152 195 */
153 196 protected function buildElementLabel($data, $form)
154 197 {
155 198 $helpMessage = '';
156 - if ($form->settings['layout']['helpMessagePlacement'] == 'with_label') {
199 + $helpMessagePlacement = ArrayHelper::get($form->settings, 'layout.helpMessagePlacement', 'with_label');
200 + if ('with_label' == $helpMessagePlacement) {
157 201 $helpMessage = $this->getLabelHelpMessage($data);
158 202 }
159 -
203 +
160 204 $id = isset($data['attributes']['id']) ? $data['attributes']['id'] : '';
161 205 $label = isset($data['settings']['label']) ? $data['settings']['label'] : '';
162 206 $requiredClass = $this->getRequiredClass(ArrayHelper::get($data, 'settings.validation_rules', []));
163 207 $classes = trim('ff-el-input--label ' . $requiredClass . $this->getAsteriskPlacement($form));
164 -
165 - 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>';
166 210 }
167 -
211 +
168 212 /**
169 213 * Generate html/markup for any element
214 + *
170 215 * @param string $elMarkup [Predifined partial markup]
171 216 * @param array $data
172 - * @param StdClass $form [Form object]
173 - * @return string [Compiled markup]
217 + * @param stdClass $form [Form object]
218 + *
219 + * @return string [Compiled markup]
174 220 */
175 221 protected function buildElementMarkup($elMarkup, $data, $form)
176 222 {
177 223 $hasConditions = $this->hasConditions($data) ? 'has-conditions ' : '';
178 -
224 +
179 225 $labelPlacement = ArrayHelper::get($data, 'settings.label_placement');
180 -
226 +
181 227 $labelPlacementClass = $labelPlacement ? 'ff-el-form-' . $labelPlacement . ' ' : '';
182 -
228 +
183 229 $validationRules = ArrayHelper::get($data, 'settings.validation_rules');
184 -
230 +
231 + $requiredClass = $this->getRequiredClass($validationRules);
232 +
185 233 $labelClass = trim(
186 234 'ff-el-input--label ' .
187 - $this->getRequiredClass($validationRules) .
235 + $requiredClass .
188 236 $this->getAsteriskPlacement($form)
189 237 );
190 -
238 +
191 239 $formGroupClass = trim(
192 240 $this->getDefaultContainerClass() .
193 241 $labelPlacementClass .
194 242 $hasConditions .
@@ -193,81 +241,132 @@
193 241 $labelPlacementClass .
194 242 $hasConditions .
195 243 ArrayHelper::get($data, 'settings.container_class')
196 244 );
197 -
245 +
198 246 $labelHelpText = $inputHelpText = '';
199 247
200 - if ($form->settings['layout']['helpMessagePlacement'] == 'with_label') {
248 + $labelPlacement = ArrayHelper::get($form->settings, 'layout.helpMessagePlacement', 'with_label');
249 + if ('with_label' == $labelPlacement) {
201 250 $labelHelpText = $this->getLabelHelpMessage($data);
202 - } elseif ($form->settings['layout']['helpMessagePlacement'] == 'on_focus') {
251 + } elseif ('on_focus' == $labelPlacement) {
203 252 $inputHelpText = $this->getInputHelpMessage($data, 'ff-hidden');
253 + } elseif ('after_label' == $labelPlacement) {
254 + $inputHelpText = $this->getInputHelpMessage($data, 'ff_ahm');
204 255 } else {
205 256 $inputHelpText = $this->getInputHelpMessage($data);
206 257 }
207 -
258 +
208 259 $forStr = '';
260 + $LabelId = '';
209 261 if (isset($data['attributes']['id'])) {
210 - $forStr = "for='{$data['attributes']['id']}'";
262 + $forStr = "for='" . esc_attr($data['attributes']['id']) . "'";
263 + $LabelId = "id='" . esc_attr('label_' . $data['attributes']['id']) . "'";
211 264 }
212 -
265 +
213 266 $labelMarkup = '';
214 -
267 +
215 268 if (!empty($data['settings']['label'])) {
216 269 $label = ArrayHelper::get($data, 'settings.label');
217 -
270 + $ariaLabel = $label;
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 +
218 284 $labelMarkup = sprintf(
219 - "<div class='%s'><label %s>%s</label> %s</div>",
220 - $labelClass,
285 + '<div class="%1$s"><label %2$s %3$s %4$s>%5$s</label>%6$s</div>',
286 + esc_attr($labelClass),
221 287 $forStr,
222 - $label,
223 - $labelHelpText
288 + $LabelId,
289 + $ariaLabel != '' ? 'aria-label="' . esc_attr($this->removeShortcode($label)) . '"' : '',
290 + fluentform_sanitize_html($label),
291 + fluentform_sanitize_html($labelHelpText)
224 292 );
225 -
226 - // $labelMarkup = "<div class='".$labelClass."'><label ".$forStr.">".$label."</label> {$labelHelpText}</div>";
227 293 }
228 -
294 +
295 + $inputHelpText = fluentform_sanitize_html($inputHelpText);
296 +
297 + if ('after_label' == $labelPlacement) {
298 + $elMarkup = $inputHelpText . $elMarkup;
299 + $inputHelpText = '';
300 + }
301 +
229 302 return sprintf(
230 303 "<div class='%s'>%s<div class='ff-el-input--content'>%s%s</div></div>",
231 - $formGroupClass,
304 + esc_attr($formGroupClass),
232 305 $labelMarkup,
233 306 $elMarkup,
234 307 $inputHelpText
235 308 );
236 -
237 - // return "<div class='".$formGroupClass."'>{$labelMarkup}<div class='ff-el-input--content'>{$elMarkup}{$inputHelpText}</div></div>";
238 309 }
239 -
310 +
240 311 /**
241 312 * Generate a help message for any element beside label
313 + *
242 314 * @param array $data
315 + *
243 316 * @return string [Html]
244 317 */
245 318 protected function getLabelHelpMessage($data)
246 319 {
247 - if (isset($data['settings']['help_message']) && $data['settings']['help_message'] != '') {
248 - return "<div class='ff-el-tooltip' data-content='{$data['settings']['help_message']}'>
249 - <i class='ff-icon icon-info-circle'></i>
250 - </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);
251 324 }
252 325 }
253 -
326 +
254 327 /**
255 328 * Generate a help message for any element beside form element
329 + *
256 330 * @param array $data
331 + *
257 332 * @return string [Html]
258 333 */
259 334 protected function getInputHelpMessage($data, $hideClass = '')
260 335 {
261 336 $class = trim('ff-el-help-message ' . $hideClass);
262 -
337 +
263 338 if (isset($data['settings']['help_message']) && !empty($data['settings']['help_message'])) {
264 - return "<div class='{$class}'>{$data['settings']['help_message']}</div>";
339 + return "<div class='" . esc_attr($class) . "'>" . fluentform_sanitize_html($data['settings']['help_message']) . '</div>';
265 340 }
266 341 return false;
267 342 }
268 -
343 +
269 344 protected function parseEditorSmartCode($text, $form)
270 345 {
271 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);
272 371 }
273 372 }