PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 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 All 98 releases
← All changes | inc/fields/dropdown-markup.php +219 -16 0.0.11 → 2.12.8 View file →
@@ -8,10 +8,13 @@
8 8
9 9 namespace SRFM\Inc\Fields;
10 10
11 11 require SRFM_DIR . 'modules/gutenberg/classes/class-spec-gb-helper.php';
12 +
12 13 use Spec_Gb_Helper;
14 +use SRFM\Inc\Compatibility\Multilingual\String_Translator;
13 15 use SRFM\Inc\Helper;
16 +use SRFM\Inc\Smart_Tags;
14 17
15 18 if ( ! defined( 'ABSPATH' ) ) {
16 19 exit; // Exit if accessed directly.
17 20 }
@@ -21,8 +24,15 @@
21 24 *
22 25 * @since 0.0.1
23 26 */
24 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;
25 35
26 36 /**
27 37 * Stores the multi select attribute value.
28 38 *
@@ -39,8 +49,32 @@
39 49 */
40 50 protected $search_attr;
41 51
42 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 + /**
43 77 * Initialize the properties based on block attributes.
44 78 *
45 79 * @param array<mixed> $attributes Block attributes.
46 80 * @since 0.0.2
@@ -45,19 +79,45 @@
45 79 * @param array<mixed> $attributes Block attributes.
46 80 * @since 0.0.2
47 81 */
48 82 public function __construct( $attributes ) {
83 + $this->slug = 'dropdown';
49 84 $this->set_properties( $attributes );
50 85 $this->set_input_label( __( 'Dropdown', 'sureforms' ) );
51 86 $this->set_error_msg( $attributes, 'srfm_dropdown_block_required_text' );
52 - $this->slug = 'dropdown';
53 - $this->multi_select_attr = ! empty( $attributes['multiSelect'] ) ? 'true' : 'false';
54 - $this->search_attr = ! empty( $attributes['searchable'] ) ? 'true' : 'false';
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 );
55 91 $this->set_markup_properties();
56 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;
57 96 $this->set_label_as_placeholder( $this->input_label );
58 - $this->placeholder = ! empty( $this->placeholder_attr ) ? $this->label : __( 'Select an option', 'sureforms' );
59 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;
60 120 }
61 121
62 122 /**
63 123 * Render the sureforms dropdown classic styling
@@ -62,33 +122,51 @@
62 122 /**
63 123 * Render the sureforms dropdown classic styling
64 124 *
65 125 * @since 0.0.2
66 - * @return string|boolean
126 + * @return string|bool
67 127 */
68 128 public function markup() {
129 + $this->class_name = $this->get_field_classes();
130 +
69 131 ob_start(); ?>
70 - <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>" class="srfm-block-single srfm-block srfm-<?php echo esc_attr( $this->slug ); ?>-block srf-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id ); ?>-block<?php echo esc_attr( $this->block_width ); ?><?php echo esc_attr( $this->class_name ); ?> <?php echo esc_attr( $this->conditional_class ); ?>">
132 + <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>" class="<?php echo esc_attr( $this->class_name ); ?>">
71 133 <fieldset>
72 - <input class="srfm-input-<?php echo esc_attr( $this->slug ); ?>-hidden" aria-required="<?php echo esc_attr( $this->aria_require_attr ); ?>" name="srfm-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id ); ?><?php echo esc_attr( $this->field_name ); ?>" type="hidden" value=""/>
73 - <legend>
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">
74 136 <?php echo wp_kses_post( $this->label_markup ); ?>
75 - <?php echo wp_kses_post( $this->help_markup ); ?>
76 137 </legend>
138 + <?php echo wp_kses_post( $this->help_markup ); ?>
77 139 <div class="srfm-block-wrap srfm-dropdown-common-wrap">
78 140 <?php
79 141 if ( is_array( $this->options ) ) {
80 142 ?>
81 - <select class="srfm-dropdown-common srfm-<?php echo esc_attr( $this->slug ); ?>-input"
143 + <select
144 + class="srfm-dropdown-common srfm-<?php echo esc_attr( $this->slug ); ?>-input"
82 145 <?php echo ! empty( $this->aria_described_by ) ? "aria-describedby='" . esc_attr( trim( $this->aria_described_by ) ) . "'" : ''; ?>
83 - aria-required="<?php echo esc_attr( $this->aria_require_attr ); ?>" name="srfm-<?php echo esc_attr( $this->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 ); ?>" tabindex="0" aria-hidden="true">
84 - <option class="srfm-dropdown-placeholder" value="" disabled selected><?php echo esc_html( $this->placeholder ); ?></option>
85 - <?php foreach ( $this->options as $option ) { ?>
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 ) { ?>
86 163 <?php
87 - $icon_svg = Spec_Gb_Helper::render_svg_html( isset( $option['icon'] ) ? $option['icon'] : '', true );
164 + $icon_svg = Spec_Gb_Helper::render_svg_html( $option['icon'] ?? '', true );
88 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 );
89 167 ?>
90 - <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 isset( $option['label'] ) ? esc_html( $option['label'] ) : ''; ?></option>
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>
91 169 <?php
92 170 }
93 171 ?>
94 172 </select>
@@ -99,7 +177,132 @@
99 177 </div>
100 178 </fieldset>
101 179 </div>
102 180 <?php
103 - return ob_get_clean();
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;
104 307 }
105 308 }