PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / trunk
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz vtrunk
2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / fields / dropdown-markup.php
sureforms / inc / fields Last commit date
address-markup.php 3 months ago base.php 3 weeks ago checkbox-markup.php 1 year ago countries.json 1 year ago dropdown-markup.php 2 months ago email-markup.php 3 weeks ago gdpr-markup.php 5 months ago inlinebutton-markup.php 3 weeks ago input-markup.php 5 months ago multichoice-markup.php 3 months ago number-markup.php 5 months ago payment-markup.php 3 weeks ago phone-markup.php 2 months ago textarea-markup.php 2 months ago url-markup.php 5 months ago
dropdown-markup.php
309 lines
1 <?php
2 /**
3 * Sureforms Dropdown Markup Class file.
4 *
5 * @package sureforms.
6 * @since 0.0.1
7 */
8
9 namespace SRFM\Inc\Fields;
10
11 require SRFM_DIR . 'modules/gutenberg/classes/class-spec-gb-helper.php';
12
13 use Spec_Gb_Helper;
14 use SRFM\Inc\Compatibility\Multilingual\String_Translator;
15 use SRFM\Inc\Helper;
16 use SRFM\Inc\Smart_Tags;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit; // Exit if accessed directly.
20 }
21
22 /**
23 * Sureforms Dropdown Markup Class.
24 *
25 * @since 0.0.1
26 */
27 class Dropdown_Markup extends Base {
28 /**
29 * Unique instance identifier for this dropdown.
30 *
31 * @var string
32 * @since 1.10.0
33 */
34 protected $unique_slug;
35
36 /**
37 * Stores the multi select attribute value.
38 *
39 * @var string
40 * @since 0.0.7
41 */
42 protected $multi_select_attr;
43
44 /**
45 * Stores the search attribute value.
46 *
47 * @var string
48 * @since 0.0.7
49 */
50 protected $search_attr;
51
52 /**
53 * Flag indicating if the value should be shown.
54 *
55 * @var bool
56 * @since 1.5.0
57 */
58 protected $show_values;
59
60 /**
61 * Array of preselected option indices.
62 *
63 * @var array
64 * @since 2.3.0
65 */
66 protected $preselected_options;
67
68 /**
69 * Static counter for generating unique dropdown instances.
70 *
71 * @var int
72 * @since 1.10.0
73 */
74 private static $instance_counter = 0;
75
76 /**
77 * Initialize the properties based on block attributes.
78 *
79 * @param array<mixed> $attributes Block attributes.
80 * @since 0.0.2
81 */
82 public function __construct( $attributes ) {
83 $this->slug = 'dropdown';
84 $this->set_properties( $attributes );
85 $this->set_input_label( __( 'Dropdown', 'sureforms' ) );
86 $this->set_error_msg( $attributes, 'srfm_dropdown_block_required_text' );
87 $this->multi_select_attr = ! empty( $attributes['multiSelect'] ) ? 'true' : 'false';
88 $this->search_attr = ! empty( $attributes['searchable'] ) ? 'true' : 'false';
89 $this->preselected_options = $attributes['preselectedOptions'] ?? [];
90 $this->resolve_dynamic_default( $attributes );
91 $this->set_markup_properties();
92 $this->set_aria_described_by();
93
94 // Store the original placeholder before set_label_as_placeholder modifies placeholder_attr.
95 $original_placeholder = $this->placeholder;
96 $this->set_label_as_placeholder( $this->input_label );
97
98 // For dropdowns, use the appropriate placeholder text:
99 // 1. If label is being used as placeholder (label_markup is empty), use the label.
100 // 2. Otherwise, use the block's placeholder value.
101 if ( empty( $this->label_markup ) ) {
102 $this->placeholder = $this->label;
103 } elseif ( ! empty( $original_placeholder ) ) {
104 $this->placeholder = $original_placeholder;
105 }
106
107 // Translate the default placeholder text for frontend display. Routed through
108 // the multilingual provider (not just the textdomain) so it is translated on
109 // WPML/Polylang sites too — the default placeholder is not serialized into
110 // block markup, so it never reaches the per-form string package.
111 if ( 'Select an option' === $this->placeholder ) {
112 $this->placeholder = String_Translator::get_instance()->translate_validation_message( 'srfm_dropdown_placeholder', __( 'Select an option', 'sureforms' ) );
113 }
114
115 $this->show_values = apply_filters( 'srfm_show_options_values', false, $attributes['showValues'] ?? false );
116
117 // Generate unique instance identifier.
118 self::$instance_counter++;
119 $this->unique_slug = $this->slug . '-' . self::$instance_counter;
120 }
121
122 /**
123 * Render the sureforms dropdown classic styling
124 *
125 * @since 0.0.2
126 * @return string|bool
127 */
128 public function markup() {
129 $this->class_name = $this->get_field_classes();
130
131 ob_start(); ?>
132 <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>" class="<?php echo esc_attr( $this->class_name ); ?>">
133 <fieldset>
134 <input class="srfm-input-<?php echo esc_attr( $this->slug ); ?>-hidden" data-required="<?php echo esc_attr( $this->data_require_attr ); ?>" aria-required="<?php echo esc_attr( $this->data_require_attr ); ?>" <?php echo wp_kses_post( $this->data_attribute_markup() ); ?> name="srfm-<?php echo esc_attr( $this->unique_slug ); ?>-<?php echo esc_attr( $this->block_id ); ?><?php echo esc_attr( $this->field_name ); ?>" type="hidden" value=""/>
135 <legend class="srfm-block-legend">
136 <?php echo wp_kses_post( $this->label_markup ); ?>
137 </legend>
138 <?php echo wp_kses_post( $this->help_markup ); ?>
139 <div class="srfm-block-wrap srfm-dropdown-common-wrap">
140 <?php
141 if ( is_array( $this->options ) ) {
142 ?>
143 <select
144 class="srfm-dropdown-common srfm-<?php echo esc_attr( $this->slug ); ?>-input"
145 <?php echo ! empty( $this->aria_described_by ) ? "aria-describedby='" . esc_attr( trim( $this->aria_described_by ) ) . "'" : ''; ?>
146 data-required="<?php echo esc_attr( $this->data_require_attr ); ?>" aria-required="<?php echo esc_attr( $this->data_require_attr ); ?>" <?php echo wp_kses_post( $this->data_attribute_markup() ); ?> name="srfm-<?php echo esc_attr( $this->unique_slug ); ?>-<?php echo esc_attr( $this->block_id ); ?><?php echo esc_attr( $this->field_name ); ?>" data-multiple="<?php echo esc_attr( $this->multi_select_attr ); ?>" data-searchable="<?php echo esc_attr( $this->search_attr ); ?>"
147 <?php
148 if ( ! empty( $this->preselected_options ) ) {
149 $preselected_labels = array_map(
150 function( $i ) {
151 $option = $this->options[ $i ] ?? [];
152 return is_array( $option ) ? ( $option['label'] ?? '' ) : '';
153 },
154 $this->preselected_options
155 );
156 $json_encoded = wp_json_encode( $preselected_labels );
157 echo 'data-preselected="' . esc_attr( false !== $json_encoded ? $json_encoded : '' ) . '"';
158 }
159 ?>
160 tabindex="0" aria-hidden="true">
161 <option class="srfm-dropdown-placeholder" value="" disabled <?php echo empty( $this->preselected_options ) ? 'selected' : ''; ?>><?php echo esc_html( $this->placeholder ); ?></option>
162 <?php foreach ( $this->options as $i => $option ) { ?>
163 <?php
164 $icon_svg = Spec_Gb_Helper::render_svg_html( $option['icon'] ?? '', true );
165 $escaped_icon_svg = htmlspecialchars( Helper::get_string_value( $icon_svg ), ENT_QUOTES, 'UTF-8' );
166 $is_preselected = is_array( $this->preselected_options ) && in_array( $i, $this->preselected_options, true );
167 ?>
168 <option value="<?php echo isset( $option['label'] ) ? esc_html( $option['label'] ) : ''; ?>" data-icon="<?php echo ! empty( $escaped_icon_svg ) ? esc_attr( $escaped_icon_svg ) : ''; ?>" <?php echo $this->show_values && isset( $option['value'] ) ? 'option-value="' . esc_attr( $option['value'] ) . '"' : ''; ?> <?php echo $is_preselected ? 'selected' : ''; ?>><?php echo isset( $option['label'] ) ? esc_html( $option['label'] ) : ''; ?></option>
169 <?php
170 }
171 ?>
172 </select>
173 <?php } ?>
174 </div>
175 <div class="srfm-error-wrap">
176 <?php echo wp_kses_post( $this->error_msg_markup ); ?>
177 </div>
178 </fieldset>
179 </div>
180 <?php
181 $markup = ob_get_clean();
182
183 return apply_filters(
184 'srfm_block_field_markup',
185 $markup,
186 [
187 'slug' => $this->slug,
188 'field_name' => $this->field_name,
189 'is_editing' => $this->is_editing,
190 'attributes' => $this->attributes,
191 ]
192 );
193 }
194
195 /**
196 * Resolve dynamic default value from smart tags and override preselected options.
197 *
198 * Single-select dropdowns match the resolved value as a single trimmed
199 * string, so labels that legitimately contain delimiter characters
200 * (commas, pipes, etc.) still match.
201 *
202 * Multi-select dropdowns split the resolved value on the pipe character
203 * (`|`) and match every segment (for example "{get_input:a}|{get_input:b}"
204 * resolving to "Red|Blue", or a single `{get_input:colors}` smart tag with
205 * URL `?colors=Red|Blue`). The same pipe split applies to multi-character
206 * resolved values from `{get_input:...}` and `{get_cookie:...}`, so cookie
207 * payloads that contain pipes are treated as multi-value in multi-select
208 * mode.
209 *
210 * When the `Add Numeric Values to Options` (showValues) setting is on,
211 * each segment is also compared against the option's `value`.
212 *
213 * @param array<mixed> $attributes Block attributes.
214 * @since 2.8.1
215 * @since 2.10.0 Added multi-select (pipe-delimited) value matching.
216 * @return void
217 */
218 protected function resolve_dynamic_default( $attributes ) {
219 // Coalesce first so blocks saved before this attribute existed (pre-2.10.0)
220 // don't trigger an "Undefined array key" warning on every frontend render.
221 $raw_default = $attributes['dynamicDefaultValue'] ?? '';
222 $dynamic_default = is_string( $raw_default ) ? $raw_default : '';
223 if ( empty( $dynamic_default ) ) {
224 return;
225 }
226
227 $smart_tags = new Smart_Tags();
228 $resolved = $smart_tags->process_smart_tags( $dynamic_default );
229
230 if ( empty( $resolved ) || ! is_string( $resolved ) || ! is_array( $this->options ) ) {
231 return;
232 }
233
234 // START: multi-value dynamic default resolution.
235 $is_multi_select = ! empty( $attributes['multiSelect'] );
236
237 if ( $is_multi_select ) {
238 $segments = array_filter(
239 array_map( 'trim', explode( '|', $resolved ) ),
240 static function ( $segment ) {
241 return '' !== $segment;
242 }
243 );
244 // Cap segments to avoid pathological URLs (e.g. ?colors=|||||...) producing thousands of comparisons.
245 $segments = array_slice( $segments, 0, 50 );
246 } else {
247 $trimmed = trim( $resolved );
248 $segments = '' === $trimmed ? [] : [ $trimmed ];
249 }
250
251 if ( empty( $segments ) ) {
252 return;
253 }
254
255 $match_value_too = ! empty( $attributes['showValues'] );
256 $matching_indices = [];
257
258 foreach ( $segments as $segment ) {
259 foreach ( $this->options as $i => $option ) {
260 if ( ! is_array( $option ) || in_array( $i, $matching_indices, true ) ) {
261 continue;
262 }
263
264 $option_label = isset( $option['label'] ) && is_scalar( $option['label'] ) ? (string) $option['label'] : '';
265 $raw_value = $option['value'] ?? '';
266 $option_value = $match_value_too && is_scalar( $raw_value ) ? (string) $raw_value : '';
267
268 $is_match = ( '' !== $option_label && strcasecmp( $option_label, $segment ) === 0 )
269 || ( $match_value_too && '' !== $option_value && strcasecmp( $option_value, $segment ) === 0 );
270
271 if ( $is_match ) {
272 $matching_indices[] = $i;
273 if ( ! $is_multi_select ) {
274 break 2;
275 }
276 break;
277 }
278 }
279 }
280
281 if ( ! empty( $matching_indices ) ) {
282 $this->preselected_options = $is_multi_select ? $matching_indices : [ $matching_indices[0] ];
283 }
284 // END: multi-value dynamic default resolution.
285 }
286
287 /**
288 * Data attribute markup for min and max value
289 *
290 * @since 0.0.13
291 * @return string
292 */
293 protected function data_attribute_markup() {
294 $data_attr = '';
295 if ( 'false' === $this->multi_select_attr ) {
296 return '';
297 }
298
299 if ( $this->min_selection ) {
300 $data_attr .= 'data-min-selection="' . esc_attr( $this->min_selection ) . '"';
301 }
302 if ( $this->max_selection ) {
303 $data_attr .= 'data-max-selection="' . esc_attr( $this->max_selection ) . '"';
304 }
305
306 return $data_attr;
307 }
308 }
309