← All changes
|
app/Services/FormBuilder/Components/BaseComponent.php
+193
-85
3.6.41
→
6.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 | } |