$attributes Block attributes.
* @since 0.0.2
*/
public function __construct( $attributes ) {
$this->slug = 'dropdown';
$this->set_properties( $attributes );
$this->set_input_label( __( 'Dropdown', 'sureforms' ) );
$this->set_error_msg( $attributes, 'srfm_dropdown_block_required_text' );
$this->multi_select_attr = ! empty( $attributes['multiSelect'] ) ? 'true' : 'false';
$this->search_attr = ! empty( $attributes['searchable'] ) ? 'true' : 'false';
$this->preselected_options = $attributes['preselectedOptions'] ?? [];
$this->resolve_dynamic_default( $attributes );
$this->set_markup_properties();
$this->set_aria_described_by();
// Store the original placeholder before set_label_as_placeholder modifies placeholder_attr.
$original_placeholder = $this->placeholder;
$this->set_label_as_placeholder( $this->input_label );
// For dropdowns, use the appropriate placeholder text:
// 1. If label is being used as placeholder (label_markup is empty), use the label.
// 2. Otherwise, use the block's placeholder value.
if ( empty( $this->label_markup ) ) {
$this->placeholder = $this->label;
} elseif ( ! empty( $original_placeholder ) ) {
$this->placeholder = $original_placeholder;
}
// Translate the default placeholder text for frontend display. Routed through
// the multilingual provider (not just the textdomain) so it is translated on
// WPML/Polylang sites too — the default placeholder is not serialized into
// block markup, so it never reaches the per-form string package.
if ( 'Select an option' === $this->placeholder ) {
$this->placeholder = String_Translator::get_instance()->translate_validation_message( 'srfm_dropdown_placeholder', __( 'Select an option', 'sureforms' ) );
}
$this->show_values = apply_filters( 'srfm_show_options_values', false, $attributes['showValues'] ?? false );
// Generate unique instance identifier.
self::$instance_counter++;
$this->unique_slug = $this->slug . '-' . self::$instance_counter;
}
/**
* Render the sureforms dropdown classic styling
*
* @since 0.0.2
* @return string|bool
*/
public function markup() {
$this->class_name = $this->get_field_classes();
ob_start(); ?>
$this->slug,
'field_name' => $this->field_name,
'is_editing' => $this->is_editing,
'attributes' => $this->attributes,
]
);
}
/**
* Resolve dynamic default value from smart tags and override preselected options.
*
* Single-select dropdowns match the resolved value as a single trimmed
* string, so labels that legitimately contain delimiter characters
* (commas, pipes, etc.) still match.
*
* Multi-select dropdowns split the resolved value on the pipe character
* (`|`) and match every segment (for example "{get_input:a}|{get_input:b}"
* resolving to "Red|Blue", or a single `{get_input:colors}` smart tag with
* URL `?colors=Red|Blue`). The same pipe split applies to multi-character
* resolved values from `{get_input:...}` and `{get_cookie:...}`, so cookie
* payloads that contain pipes are treated as multi-value in multi-select
* mode.
*
* When the `Add Numeric Values to Options` (showValues) setting is on,
* each segment is also compared against the option's `value`.
*
* @param array $attributes Block attributes.
* @since 2.8.1
* @since 2.10.0 Added multi-select (pipe-delimited) value matching.
* @return void
*/
protected function resolve_dynamic_default( $attributes ) {
// Coalesce first so blocks saved before this attribute existed (pre-2.10.0)
// don't trigger an "Undefined array key" warning on every frontend render.
$raw_default = $attributes['dynamicDefaultValue'] ?? '';
$dynamic_default = is_string( $raw_default ) ? $raw_default : '';
if ( empty( $dynamic_default ) ) {
return;
}
$smart_tags = new Smart_Tags();
$resolved = $smart_tags->process_smart_tags( $dynamic_default );
if ( empty( $resolved ) || ! is_string( $resolved ) || ! is_array( $this->options ) ) {
return;
}
// START: multi-value dynamic default resolution.
$is_multi_select = ! empty( $attributes['multiSelect'] );
if ( $is_multi_select ) {
$segments = array_filter(
array_map( 'trim', explode( '|', $resolved ) ),
static function ( $segment ) {
return '' !== $segment;
}
);
// Cap segments to avoid pathological URLs (e.g. ?colors=|||||...) producing thousands of comparisons.
$segments = array_slice( $segments, 0, 50 );
} else {
$trimmed = trim( $resolved );
$segments = '' === $trimmed ? [] : [ $trimmed ];
}
if ( empty( $segments ) ) {
return;
}
$match_value_too = ! empty( $attributes['showValues'] );
$matching_indices = [];
foreach ( $segments as $segment ) {
foreach ( $this->options as $i => $option ) {
if ( ! is_array( $option ) || in_array( $i, $matching_indices, true ) ) {
continue;
}
$option_label = isset( $option['label'] ) && is_scalar( $option['label'] ) ? (string) $option['label'] : '';
$raw_value = $option['value'] ?? '';
$option_value = $match_value_too && is_scalar( $raw_value ) ? (string) $raw_value : '';
$is_match = ( '' !== $option_label && strcasecmp( $option_label, $segment ) === 0 )
|| ( $match_value_too && '' !== $option_value && strcasecmp( $option_value, $segment ) === 0 );
if ( $is_match ) {
$matching_indices[] = $i;
if ( ! $is_multi_select ) {
break 2;
}
break;
}
}
}
if ( ! empty( $matching_indices ) ) {
$this->preselected_options = $is_multi_select ? $matching_indices : [ $matching_indices[0] ];
}
// END: multi-value dynamic default resolution.
}
/**
* Data attribute markup for min and max value
*
* @since 0.0.13
* @return string
*/
protected function data_attribute_markup() {
$data_attr = '';
if ( 'false' === $this->multi_select_attr ) {
return '';
}
if ( $this->min_selection ) {
$data_attr .= 'data-min-selection="' . esc_attr( $this->min_selection ) . '"';
}
if ( $this->max_selection ) {
$data_attr .= 'data-max-selection="' . esc_attr( $this->max_selection ) . '"';
}
return $data_attr;
}
}