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