PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.10.0
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.10.0
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
sureforms / inc / fields / dropdown-markup.php

dropdown-markup.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.10.0, at inc/fields/dropdown-markup.php

305 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\Helper;
15 use SRFM\Inc\Smart_Tags;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Sureforms Dropdown Markup Class.
23 *
24 * @since 0.0.1
25 */
26 class Dropdown_Markup extends Base {
27 /**
28 * Unique instance identifier for this dropdown.
29 *
30 * @var string
31 * @since 1.10.0
32 */
33 protected $unique_slug;
34
35 /**
36 * Stores the multi select attribute value.
37 *
38 * @var string
39 * @since 0.0.7
40 */
41 protected $multi_select_attr;
42
43 /**
44 * Stores the search attribute value.
45 *
46 * @var string
47 * @since 0.0.7
48 */
49 protected $search_attr;
50
51 /**
52 * Flag indicating if the value should be shown.
53 *
54 * @var bool
55 * @since 1.5.0
56 */
57 protected $show_values;
58
59 /**
60 * Array of preselected option indices.
61 *
62 * @var array
63 * @since 2.3.0
64 */
65 protected $preselected_options;
66
67 /**
68 * Static counter for generating unique dropdown instances.
69 *
70 * @var int
71 * @since 1.10.0
72 */
73 private static $instance_counter = 0;
74
75 /**
76 * Initialize the properties based on block attributes.
77 *
78 * @param array<mixed> $attributes Block attributes.
79 * @since 0.0.2
80 */
81 public function __construct( $attributes ) {
82 $this->slug = 'dropdown';
83 $this->set_properties( $attributes );
84 $this->set_input_label( __( 'Dropdown', 'sureforms' ) );
85 $this->set_error_msg( $attributes, 'srfm_dropdown_block_required_text' );
86 $this->multi_select_attr = ! empty( $attributes['multiSelect'] ) ? 'true' : 'false';
87 $this->search_attr = ! empty( $attributes['searchable'] ) ? 'true' : 'false';
88 $this->preselected_options = $attributes['preselectedOptions'] ?? [];
89 $this->resolve_dynamic_default( $attributes );
90 $this->set_markup_properties();
91 $this->set_aria_described_by();
92
93 // Store the original placeholder before set_label_as_placeholder modifies placeholder_attr.
94 $original_placeholder = $this->placeholder;
95 $this->set_label_as_placeholder( $this->input_label );
96
97 // For dropdowns, use the appropriate placeholder text:
98 // 1. If label is being used as placeholder (label_markup is empty), use the label.
99 // 2. Otherwise, use the block's placeholder value.
100 if ( empty( $this->label_markup ) ) {
101 $this->placeholder = $this->label;
102 } elseif ( ! empty( $original_placeholder ) ) {
103 $this->placeholder = $original_placeholder;
104 }
105
106 // Translate the default placeholder text for frontend display.
107 if ( 'Select an option' === $this->placeholder ) {
108 $this->placeholder = __( 'Select an option', 'sureforms' );
109 }
110
111 $this->show_values = apply_filters( 'srfm_show_options_values', false, $attributes['showValues'] ?? false );
112
113 // Generate unique instance identifier.
114 self::$instance_counter++;
115 $this->unique_slug = $this->slug . '-' . self::$instance_counter;
116 }
117
118 /**
119 * Render the sureforms dropdown classic styling
120 *
121 * @since 0.0.2
122 * @return string|bool
123 */
124 public function markup() {
125 $this->class_name = $this->get_field_classes();
126
127 ob_start(); ?>
128 <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>" class="<?php echo esc_attr( $this->class_name ); ?>">
129 <fieldset>
130 <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=""/>
131 <legend class="srfm-block-legend">
132 <?php echo wp_kses_post( $this->label_markup ); ?>
133 </legend>
134 <?php echo wp_kses_post( $this->help_markup ); ?>
135 <div class="srfm-block-wrap srfm-dropdown-common-wrap">
136 <?php
137 if ( is_array( $this->options ) ) {
138 ?>
139 <select
140 class="srfm-dropdown-common srfm-<?php echo esc_attr( $this->slug ); ?>-input"
141 <?php echo ! empty( $this->aria_described_by ) ? "aria-describedby='" . esc_attr( trim( $this->aria_described_by ) ) . "'" : ''; ?>
142 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 ); ?>"
143 <?php
144 if ( ! empty( $this->preselected_options ) ) {
145 $preselected_labels = array_map(
146 function( $i ) {
147 $option = $this->options[ $i ] ?? [];
148 return is_array( $option ) ? ( $option['label'] ?? '' ) : '';
149 },
150 $this->preselected_options
151 );
152 $json_encoded = wp_json_encode( $preselected_labels );
153 echo 'data-preselected="' . esc_attr( false !== $json_encoded ? $json_encoded : '' ) . '"';
154 }
155 ?>
156 tabindex="0" aria-hidden="true">
157 <option class="srfm-dropdown-placeholder" value="" disabled <?php echo empty( $this->preselected_options ) ? 'selected' : ''; ?>><?php echo esc_html( $this->placeholder ); ?></option>
158 <?php foreach ( $this->options as $i => $option ) { ?>
159 <?php
160 $icon_svg = Spec_Gb_Helper::render_svg_html( $option['icon'] ?? '', true );
161 $escaped_icon_svg = htmlspecialchars( Helper::get_string_value( $icon_svg ), ENT_QUOTES, 'UTF-8' );
162 $is_preselected = is_array( $this->preselected_options ) && in_array( $i, $this->preselected_options, true );
163 ?>
164 <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>
165 <?php
166 }
167 ?>
168 </select>
169 <?php } ?>
170 </div>
171 <div class="srfm-error-wrap">
172 <?php echo wp_kses_post( $this->error_msg_markup ); ?>
173 </div>
174 </fieldset>
175 </div>
176 <?php
177 $markup = ob_get_clean();
178
179 return apply_filters(
180 'srfm_block_field_markup',
181 $markup,
182 [
183 'slug' => $this->slug,
184 'field_name' => $this->field_name,
185 'is_editing' => $this->is_editing,
186 'attributes' => $this->attributes,
187 ]
188 );
189 }
190
191 /**
192 * Resolve dynamic default value from smart tags and override preselected options.
193 *
194 * Single-select dropdowns match the resolved value as a single trimmed
195 * string, so labels that legitimately contain delimiter characters
196 * (commas, pipes, etc.) still match.
197 *
198 * Multi-select dropdowns split the resolved value on the pipe character
199 * (`|`) and match every segment (for example "{get_input:a}|{get_input:b}"
200 * resolving to "Red|Blue", or a single `{get_input:colors}` smart tag with
201 * URL `?colors=Red|Blue`). The same pipe split applies to multi-character
202 * resolved values from `{get_input:...}` and `{get_cookie:...}`, so cookie
203 * payloads that contain pipes are treated as multi-value in multi-select
204 * mode.
205 *
206 * When the `Add Numeric Values to Options` (showValues) setting is on,
207 * each segment is also compared against the option's `value`.
208 *
209 * @param array<mixed> $attributes Block attributes.
210 * @since 2.8.1
211 * @since 2.10.0 Added multi-select (pipe-delimited) value matching.
212 * @return void
213 */
214 protected function resolve_dynamic_default( $attributes ) {
215 // Coalesce first so blocks saved before this attribute existed (pre-2.10.0)
216 // don't trigger an "Undefined array key" warning on every frontend render.
217 $raw_default = $attributes['dynamicDefaultValue'] ?? '';
218 $dynamic_default = is_string( $raw_default ) ? $raw_default : '';
219 if ( empty( $dynamic_default ) ) {
220 return;
221 }
222
223 $smart_tags = new Smart_Tags();
224 $resolved = $smart_tags->process_smart_tags( $dynamic_default );
225
226 if ( empty( $resolved ) || ! is_string( $resolved ) || ! is_array( $this->options ) ) {
227 return;
228 }
229
230 // START: multi-value dynamic default resolution.
231 $is_multi_select = ! empty( $attributes['multiSelect'] );
232
233 if ( $is_multi_select ) {
234 $segments = array_filter(
235 array_map( 'trim', explode( '|', $resolved ) ),
236 static function ( $segment ) {
237 return '' !== $segment;
238 }
239 );
240 // Cap segments to avoid pathological URLs (e.g. ?colors=|||||...) producing thousands of comparisons.
241 $segments = array_slice( $segments, 0, 50 );
242 } else {
243 $trimmed = trim( $resolved );
244 $segments = '' === $trimmed ? [] : [ $trimmed ];
245 }
246
247 if ( empty( $segments ) ) {
248 return;
249 }
250
251 $match_value_too = ! empty( $attributes['showValues'] );
252 $matching_indices = [];
253
254 foreach ( $segments as $segment ) {
255 foreach ( $this->options as $i => $option ) {
256 if ( ! is_array( $option ) || in_array( $i, $matching_indices, true ) ) {
257 continue;
258 }
259
260 $option_label = isset( $option['label'] ) && is_scalar( $option['label'] ) ? (string) $option['label'] : '';
261 $raw_value = $option['value'] ?? '';
262 $option_value = $match_value_too && is_scalar( $raw_value ) ? (string) $raw_value : '';
263
264 $is_match = ( '' !== $option_label && strcasecmp( $option_label, $segment ) === 0 )
265 || ( $match_value_too && '' !== $option_value && strcasecmp( $option_value, $segment ) === 0 );
266
267 if ( $is_match ) {
268 $matching_indices[] = $i;
269 if ( ! $is_multi_select ) {
270 break 2;
271 }
272 break;
273 }
274 }
275 }
276
277 if ( ! empty( $matching_indices ) ) {
278 $this->preselected_options = $is_multi_select ? $matching_indices : [ $matching_indices[0] ];
279 }
280 // END: multi-value dynamic default resolution.
281 }
282
283 /**
284 * Data attribute markup for min and max value
285 *
286 * @since 0.0.13
287 * @return string
288 */
289 protected function data_attribute_markup() {
290 $data_attr = '';
291 if ( 'false' === $this->multi_select_attr ) {
292 return '';
293 }
294
295 if ( $this->min_selection ) {
296 $data_attr .= 'data-min-selection="' . esc_attr( $this->min_selection ) . '"';
297 }
298 if ( $this->max_selection ) {
299 $data_attr .= 'data-max-selection="' . esc_attr( $this->max_selection ) . '"';
300 }
301
302 return $data_attr;
303 }
304 }
305