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