| @@ -44,10 +44,26 @@ | ||
| 44 | 44 | wp_enqueue_style('ff_choices'); |
| 45 | 45 | $data['attributes']['class'] .= ' ff_has_multi_select'; |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | - if ($maxSelection = ArrayHelper::get($data, 'settings.max_selection')) { | |
| 48 | + $maxSelection = Helper::resolveMaxSelection($data); | |
| 49 | + | |
| 50 | + if ($maxSelection) { | |
| 49 | 51 | $data['attributes']['data-max_selected_options'] = $maxSelection; |
| 52 | + | |
| 53 | + // Choices.js blocks the pick and draws its own notice, so that notice | |
| 54 | + // is the only wording a visitor ever sees for this limit. Only a | |
| 55 | + // message the site owner actually wrote overrides it — Choices' own | |
| 56 | + // string is translated and carries the count, so a field left on the | |
| 57 | + // global default is better served by it. | |
| 58 | + $rules = ArrayHelper::get($data, 'settings.validation_rules', []); | |
| 59 | + $customMessage = ArrayHelper::isTrue($rules, 'max_selection.global') | |
| 60 | + ? '' | |
| 61 | + : ArrayHelper::get($rules, 'max_selection.message', ''); | |
| 62 | + | |
| 63 | + if ($customMessage) { | |
| 64 | + $data['attributes']['data-max_selection_message'] = $customMessage; | |
| 65 | + } | |
| 50 | 66 | } |
| 51 | 67 | |
| 52 | 68 | $data['attributes']['data-calc_value'] = 0; |
| 53 | 69 | |
| @@ -74,9 +90,9 @@ | ||
| 74 | 90 | if (ArrayHelper::get($data, 'settings.validation_rules.required.value')) { |
| 75 | 91 | $ariaRequired = 'true'; |
| 76 | 92 | } |
| 77 | 93 | |
| 78 | - $ariaLabelledBy = 'label_' . ArrayHelper::get($data, 'attributes.id'); | |
| 94 | + $ariaLabelledBy = esc_attr('label_' . ArrayHelper::get($data, 'attributes.id')); | |
| 79 | 95 | |
| 80 | 96 | $elMarkup = '<select ' . $atts . ' aria-invalid="false" aria-required="' . $ariaRequired . '" aria-labelledby="' . $ariaLabelledBy .'"'.'>' . $options . '</select>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $atts, $options are escaped before being passed in. |
| 81 | 97 | |
| 82 | 98 | $html = $this->buildElementMarkup($elMarkup, $data, $form); |
| @@ -126,8 +142,32 @@ | ||
| 126 | 142 | $opts .= '<option value="">' . wp_strip_all_tags($data['settings']['placeholder']) . '</option>'; |
| 127 | 143 | } elseif (! empty($data['attributes']['placeholder'])) { |
| 128 | 144 | $opts .= '<option value="">' . wp_strip_all_tags($data['attributes']['placeholder']) . '</option>'; |
| 129 | 145 | } |
| 146 | + foreach ($formattedOptions as $option) { | |
| 147 | + if (Helper::isOptionGroup($option)) { | |
| 148 | + $groupLabel = wp_strip_all_tags(ArrayHelper::get($option, 'label', '')); | |
| 149 | + $groupOptions = $this->buildOptionMarkup( | |
| 150 | + ArrayHelper::get($option, 'options', []), | |
| 151 | + $defaultValues | |
| 152 | + ); | |
| 153 | + | |
| 154 | + if ($groupOptions) { | |
| 155 | + $opts .= '<optgroup label="' . esc_attr($groupLabel) . '">' . $groupOptions . '</optgroup>'; | |
| 156 | + } | |
| 157 | + continue; | |
| 158 | + } | |
| 159 | + | |
| 160 | + $opts .= $this->buildOptionMarkup([$option], $defaultValues); | |
| 161 | + } | |
| 162 | + | |
| 163 | + return $opts; | |
| 164 | + } | |
| 165 | + | |
| 166 | + protected function buildOptionMarkup($formattedOptions, $defaultValues) | |
| 167 | + { | |
| 168 | + $opts = ''; | |
| 169 | + | |
| 130 | 170 | foreach ($formattedOptions as $option) { |
| 131 | 171 | if (in_array($option['value'], $defaultValues)) { |
| 132 | 172 | $selected = 'selected'; |
| 133 | 173 | } else { |