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/multichoice-markup.php +185 -26 0.0.10 → 2.12.8 View file →
@@ -9,8 +9,9 @@
9 9 namespace SRFM\Inc\Fields;
10 10
11 11 use Spec_Gb_Helper;
12 12 use SRFM\Inc\Helper;
13 +use SRFM\Inc\Smart_Tags;
13 14
14 15 if ( ! defined( 'ABSPATH' ) ) {
15 16 exit; // Exit if accessed directly.
16 17 }
@@ -20,9 +21,8 @@
20 21 *
21 22 * @since 0.0.1
22 23 */
23 24 class Multichoice_Markup extends Base {
24 -
25 25 /**
26 26 * Flag indicating if only a single selection is allowed.
27 27 *
28 28 * @var bool
@@ -86,8 +86,24 @@
86 86 */
87 87 protected $option_type;
88 88
89 89 /**
90 + * Flag indicating if the value should be shown.
91 + *
92 + * @var bool
93 + * @since 1.5.0
94 + */
95 + protected $show_values;
96 +
97 + /**
98 + * Array of preselected option indices.
99 + *
100 + * @var array
101 + * @since 2.3.0
102 + */
103 + protected $preselected_options;
104 +
105 + /**
90 106 * Initialize the properties based on block attributes.
91 107 *
92 108 * @param array<mixed> $attributes Block attributes.
93 109 * @since 0.0.2
@@ -92,21 +108,25 @@
92 108 * @param array<mixed> $attributes Block attributes.
93 109 * @since 0.0.2
94 110 */
95 111 public function __construct( $attributes ) {
112 + $this->slug = 'multi-choice';
96 113 $this->set_properties( $attributes );
97 114 $this->set_input_label( __( 'Multi Choice', 'sureforms' ) );
98 115 $this->set_error_msg( $attributes, 'srfm_multi_choice_block_required_text' );
99 - $this->slug = 'multi-choice';
100 - $this->single_selection = isset( $attributes['singleSelection'] ) ? $attributes['singleSelection'] : false;
101 - $this->choice_width = isset( $attributes['choiceWidth'] ) ? $attributes['choiceWidth'] : '';
102 - $this->vertical_layout = isset( $attributes['verticalLayout'] ) ? $attributes['verticalLayout'] : false;
103 - $this->option_type = ! empty( $attributes['optionType'] ) && in_array( $attributes['optionType'], [ 'icon', 'image' ], true ) ? $attributes['optionType'] : 'icon';
104 - $this->type_attr = $this->single_selection ? 'radio' : 'checkbox';
105 - $this->svg_type = $this->single_selection ? 'circle' : 'square';
106 - $this->name_attr = $this->single_selection ? 'name="srfm-input-' . esc_attr( $this->slug ) . '-' . esc_attr( $this->block_id ) . '"' : '';
107 - $this->choice_width_attr = $this->choice_width ? 'srfm-choice-width-' . str_replace( '.', '-', $this->choice_width ) : '';
116 + $this->single_selection = $attributes['singleSelection'] ?? false;
117 + $this->choice_width = $attributes['choiceWidth'] ?? '';
118 + $this->vertical_layout = $attributes['verticalLayout'] ?? false;
119 + $this->option_type = ! empty( $attributes['optionType'] ) && in_array( $attributes['optionType'], [ 'icon', 'image' ], true ) ? $attributes['optionType'] : 'icon';
120 + $this->type_attr = $this->single_selection ? 'radio' : 'checkbox';
121 + $this->svg_type = $this->single_selection ? 'circle' : 'square';
122 + $this->name_attr = $this->single_selection ? 'name="srfm-input-' . esc_attr( $this->slug ) . '-' . esc_attr( $this->block_id ) . '"' : '';
123 + $this->choice_width_attr = $this->choice_width ? 'srfm-choice-width-' . str_replace( '.', '-', $this->choice_width ) : '';
124 + $this->show_values = apply_filters( 'srfm_show_options_values', false, $attributes['showValues'] ?? false );
125 + $this->preselected_options = $attributes['preselectedOptions'] ?? [];
126 + $this->resolve_dynamic_default( $attributes );
108 127 $this->set_markup_properties();
128 + $this->set_aria_described_by();
109 129 }
110 130
111 131 /**
112 132 * Render the sureforms Multichoice classic styling
@@ -111,40 +131,58 @@
111 131 /**
112 132 * Render the sureforms Multichoice classic styling
113 133 *
114 134 * @since 0.0.2
115 - * @return string|boolean
135 + * @return string|bool
116 136 */
117 137 public function markup() {
118 - $check_svg = Helper::fetch_svg( $this->svg_type . '-checked', 'srfm-' . $this->slug . '-icon' );
119 - $unchecked_svg = Helper::fetch_svg( $this->svg_type . '-unchecked', 'srfm-' . $this->slug . '-icon-unchecked' );
120 - ob_start(); ?>
121 - <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>" class="srfm-block-single srfm-block srfm-<?php echo esc_attr( $this->type_attr ); ?>-mode 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 wp_kses_post( $this->block_width ); ?><?php echo esc_attr( $this->class_name ); ?> <?php echo esc_attr( $this->conditional_class ); ?>">
138 + $check_svg = Helper::fetch_svg( $this->svg_type . '-checked', 'srfm-' . $this->slug . '-icon', 'aria-hidden="true"' );
139 + $unchecked_svg = Helper::fetch_svg( $this->svg_type . '-unchecked', 'srfm-' . $this->slug . '-icon-unchecked', 'aria-hidden="true"' );
140 +
141 + $this->class_name = $this->get_field_classes( [ "srfm-{$this->type_attr}-mode" ] );
142 +
143 + $allowed_tags_svg = Helper::$allowed_tags_svg;
144 +
145 + ob_start();
146 + ?>
147 + <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>" class="<?php echo esc_attr( $this->class_name ); ?>">
122 148 <fieldset>
123 - <input class="srfm-input-<?php echo esc_attr( $this->slug ); ?>-hidden" aria-required="<?php echo esc_attr( $this->aria_require_attr ); ?>" name="srfm-input-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id ); ?><?php echo esc_attr( $this->field_name ); ?>" type="hidden" value=""/>
124 - <legend><?php echo wp_kses_post( $this->label_markup ); ?></legend>
149 + <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-input-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id ); ?><?php echo esc_attr( $this->field_name ); ?>" type="hidden" value=""/>
150 + <legend class="srfm-block-legend">
151 + <?php echo wp_kses_post( $this->label_markup ); ?>
152 + </legend>
125 153 <?php echo wp_kses_post( $this->help_markup ); ?>
126 154 <?php if ( is_array( $this->options ) ) { ?>
127 - <div class="srfm-block-wrap <?php echo esc_attr( $this->choice_width_attr ); ?> <?php echo( $this->vertical_layout ? 'srfm-vertical-layout' : '' ); ?>">
155 + <div class="srfm-block-wrap <?php echo esc_attr( $this->choice_width_attr ); ?> <?php echo $this->vertical_layout ? 'srfm-vertical-layout' : ''; ?>">
128 156 <?php foreach ( $this->options as $i => $option ) { ?>
129 - <label class="srfm-<?php echo esc_attr( $this->slug ); ?>-single">
130 - <input type="<?php echo esc_attr( $this->type_attr ); ?>" id="srfm-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id . '-' . $i ); ?>" class="srfm-input-<?php echo esc_attr( $this->slug ); ?>-single" <?php echo wp_kses_post( $this->name_attr ); ?>/>
157 + <div class="srfm-<?php echo esc_attr( $this->slug ); ?>-single">
158 + <input type="<?php echo esc_attr( $this->type_attr ); ?>"
159 + id="srfm-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id . '-' . $i ); ?>"
160 + class="srfm-input-<?php echo esc_attr( $this->slug ); ?>-single" <?php echo wp_kses_post( $this->name_attr ); ?>
161 + <?php echo 0 === $i ? 'aria-describedby="' . ( ! empty( $this->aria_described_by ) ? esc_attr( trim( $this->aria_described_by ) ) : '' ) . '"' : ''; ?>
162 + <?php echo 0 === $i ? 'aria-required="' . ( ! empty( $this->data_require_attr ) ? esc_attr( $this->data_require_attr ) : '' ) . '"' : ''; ?>
163 + <?php echo $this->show_values && isset( $option['value'] ) ? 'option-value="' . esc_attr( $option['value'] ) . '"' : ''; ?>
164 + <?php echo is_array( $this->preselected_options ) && in_array( $i, $this->preselected_options, true ) ? 'checked' : ''; ?>
165 + />
131 166 <div class="srfm-block-content-wrap">
132 167 <div class="srfm-option-container">
133 168 <?php if ( 'icon' === $this->option_type && ! empty( $option['icon'] ) ) { ?>
134 - <span class="srfm-option-icon">
169 + <span class="srfm-option-icon" aria-hidden="true">
135 170 <?php Spec_Gb_Helper::render_svg_html( $option['icon'] ); ?>
136 171 </span>
137 172 <?php } elseif ( 'image' === $this->option_type && ! empty( $option['image'] ) ) { ?>
138 - <span class="srfm-option-image">
173 + <span class="srfm-option-image" aria-hidden="true">
139 174 <img src="<?php echo esc_url( $option['image'] ); ?>"/>
140 175 </span>
141 176 <?php } ?>
142 - <p><?php echo isset( $option['optionTitle'] ) ? esc_html( $option['optionTitle'] ) : ''; ?></p>
177 + <!-- This label content is used in conditional logic and for storing values in a hidden input field as comma-separated values.
178 + The markup should remain unchanged.
179 + If you make any changes here, ensure the corresponding JavaScript value functionality and conditional logic are also updated. -->
180 + <label for="srfm-<?php echo esc_attr( $this->slug ); ?>-<?php echo esc_attr( $this->block_id . '-' . $i ); ?>" data-option-text="<?php echo isset( $option['optionTitle'] ) ? esc_attr( $option['optionTitle'] ) : ''; ?>"><?php echo isset( $option['optionTitle'] ) ? esc_html( $option['optionTitle'] ) : ''; ?></label>
143 181 </div>
144 - <div class="srfm-icon-container"><?php echo $check_svg . $unchecked_svg; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Ignored to render svg ?></div>
182 + <div class="srfm-icon-container"><?php echo wp_kses( $check_svg, $allowed_tags_svg ) . wp_kses( $unchecked_svg, $allowed_tags_svg ); ?></div>
145 183 </div>
146 - </label>
184 + </div>
147 185 <?php } ?>
148 186 </div>
149 187 <?php } ?>
150 188 <div class="srfm-error-wrap"><?php echo wp_kses_post( $this->error_msg_markup ); ?></div>
@@ -150,8 +188,129 @@
150 188 <div class="srfm-error-wrap"><?php echo wp_kses_post( $this->error_msg_markup ); ?></div>
151 189 </fieldset>
152 190 </div>
153 191 <?php
154 - return ob_get_clean();
192 + $markup = ob_get_clean();
155 193
194 + return apply_filters(
195 + 'srfm_block_field_markup',
196 + $markup,
197 + [
198 + 'slug' => $this->slug,
199 + 'is_editing' => $this->is_editing,
200 + 'field_name' => $this->field_name,
201 + 'attributes' => $this->attributes,
202 + ]
203 + );
204 + }
205 +
206 + /**
207 + * Resolve dynamic default value from smart tags and override preselected options.
208 + *
209 + * Radio (single-selection) mode matches the resolved value as a single
210 + * trimmed string, so option titles that legitimately contain delimiter
211 + * characters (commas, pipes, etc.) still match.
212 + *
213 + * Checkbox mode splits the resolved value on the pipe character (`|`) and
214 + * matches every segment (for example "{get_input:a}|{get_input:b}" resolving
215 + * to "Red|Blue", or a single `{get_input:colors}` with URL
216 + * `?colors=Red|Blue`). The same pipe split applies to multi-character
217 + * resolved values from `{get_input:...}` and `{get_cookie:...}`, so cookie
218 + * payloads that contain pipes are treated as multi-value in checkbox mode.
219 + *
220 + * When the `Add Numeric Values to Options` (showValues) setting is on,
221 + * each segment is also compared against the option's `value`.
222 + *
223 + * @param array<mixed> $attributes Block attributes.
224 + * @since 2.8.1
225 + * @since 2.10.0 Added checkbox (pipe-delimited) multi-value matching.
226 + * @return void
227 + */
228 + protected function resolve_dynamic_default( $attributes ) {
229 + // Coalesce first so blocks saved before this attribute existed (pre-2.10.0)
230 + // don't trigger an "Undefined array key" warning on every frontend render.
231 + $raw_default = $attributes['dynamicDefaultValue'] ?? '';
232 + $dynamic_default = is_string( $raw_default ) ? $raw_default : '';
233 + if ( empty( $dynamic_default ) ) {
234 + return;
235 + }
236 +
237 + $smart_tags = new Smart_Tags();
238 + $resolved = $smart_tags->process_smart_tags( $dynamic_default );
239 +
240 + if ( empty( $resolved ) || ! is_string( $resolved ) || ! is_array( $this->options ) ) {
241 + return;
242 + }
243 +
244 + // START: multi-value dynamic default resolution.
245 + if ( ! $this->single_selection ) {
246 + $segments = array_filter(
247 + array_map( 'trim', explode( '|', $resolved ) ),
248 + static function ( $segment ) {
249 + return '' !== $segment;
250 + }
251 + );
252 + // Cap segments to avoid pathological URLs (e.g. ?colors=|||||...) producing thousands of comparisons.
253 + $segments = array_slice( $segments, 0, 50 );
254 + } else {
255 + $trimmed = trim( $resolved );
256 + $segments = '' === $trimmed ? [] : [ $trimmed ];
257 + }
258 +
259 + if ( empty( $segments ) ) {
260 + return;
261 + }
262 +
263 + $match_value_too = ! empty( $attributes['showValues'] );
264 + $matching_indices = [];
265 +
266 + foreach ( $segments as $segment ) {
267 + foreach ( $this->options as $i => $option ) {
268 + if ( ! is_array( $option ) || in_array( $i, $matching_indices, true ) ) {
269 + continue;
270 + }
271 +
272 + $option_title = isset( $option['optionTitle'] ) && is_scalar( $option['optionTitle'] ) ? (string) $option['optionTitle'] : '';
273 + $raw_value = $option['value'] ?? '';
274 + $option_value = $match_value_too && is_scalar( $raw_value ) ? (string) $raw_value : '';
275 +
276 + $is_match = ( '' !== $option_title && strcasecmp( $option_title, $segment ) === 0 )
277 + || ( $match_value_too && '' !== $option_value && strcasecmp( $option_value, $segment ) === 0 );
278 +
279 + if ( $is_match ) {
280 + $matching_indices[] = $i;
281 + if ( $this->single_selection ) {
282 + break 2;
283 + }
284 + break;
285 + }
286 + }
287 + }
288 +
289 + if ( ! empty( $matching_indices ) ) {
290 + $this->preselected_options = $this->single_selection ? [ $matching_indices[0] ] : $matching_indices;
291 + }
292 + // END: multi-value dynamic default resolution.
293 + }
294 +
295 + /**
296 + * Data attribute markup for min and max value
297 + *
298 + * @since 0.0.13
299 + * @return string
300 + */
301 + protected function data_attribute_markup() {
302 + $data_attr = '';
303 + if ( $this->single_selection ) {
304 + return '';
305 + }
306 +
307 + if ( $this->min_selection ) {
308 + $data_attr .= 'data-min-selection="' . esc_attr( $this->min_selection ) . '"';
309 + }
310 + if ( $this->max_selection ) {
311 + $data_attr .= 'data-max-selection="' . esc_attr( $this->max_selection ) . '"';
312 + }
313 +
314 + return $data_attr;
156 315 }
157 316 }