# booking/11.9/includes/booking-resource-selector/booking-resource-selector__config.php

Booking Calendar, version 11.9. 362 lines.

- Page: https://pluginprobe.com/plugins/booking/11.9/code/includes/booking-resource-selector/booking-resource-selector__config.php
- Raw: https://pluginprobe.com/plugins/booking/11.9/raw/includes/booking-resource-selector/booking-resource-selector__config.php
- Modified: 2026-08-22T14:02:04+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/booking/11.9/code/includes/booking-resource-selector/booking-resource-selector__config.php#L10-L20`.

```php
<?php
/**
 * Booking Resource selector configuration normalization and signing.
 *
 * @package Booking Calendar
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Convert a delimited value or array to unique positive Booking Resource IDs.
 *
 * @param mixed $resource_ids Raw Booking Resource ID collection.
 *
 * @return int[] Normalized Booking Resource IDs.
 */
function wpbc_booking_resource_selector_normalize_ids( $resource_ids ) {
	if ( is_string( $resource_ids ) ) {
		$resource_ids = preg_split( '/[;,\s]+/', $resource_ids, -1, PREG_SPLIT_NO_EMPTY );
	}

	return array_values( array_unique( array_filter( array_map( 'absint', (array) $resource_ids ) ) ) );
}

/**
 * Convert a shortcode-style value to a strict Boolean.
 *
 * @param mixed $raw_value     Raw Boolean-like value.
 * @param bool  $default_value Value used when the raw value is null.
 *
 * @return bool Normalized Boolean.
 */
function wpbc_booking_resource_selector_normalize_boolean( $raw_value, $default_value = false ) {
	if ( null === $raw_value ) {
		return (bool) $default_value;
	}

	if ( is_string( $raw_value ) ) {
		$raw_value = strtolower( trim( $raw_value ) );
	}

	return ! in_array( $raw_value, array( false, 0, '0', 'false', 'off', 'no', '' ), true );
}

/**
 * Normalize a safe public catalog item width.
 *
 * Bare numbers are treated as pixels for shortcode convenience. Only simple
 * dimensions are accepted; CSS functions and arbitrary declarations are
 * rejected before the value can reach an inline custom property.
 *
 * @param mixed $raw_width Raw shortcode width.
 *
 * @return string Normalized CSS width or an empty string for automatic width.
 */
function wpbc_booking_resource_selector_normalize_css_width( $raw_width ) {
	if ( is_int( $raw_width ) || is_float( $raw_width ) ) {
		$raw_width = (string) $raw_width . 'px';
	}

	$raw_width = strtolower( trim( (string) $raw_width ) );
	if ( '' === $raw_width || 'auto' === $raw_width ) {
		return '';
	}
	if ( preg_match( '/^\d+(?:\.\d+)?$/', $raw_width ) ) {
		$raw_width .= 'px';
	}
	if ( ! preg_match( '/^(\d+(?:\.\d+)?)(px|%|rem|em|vw)$/', $raw_width, $matches ) ) {
		return '';
	}

	$numeric_width = (float) $matches[1];
	$width_unit    = $matches[2];
	$maximum_width = in_array( $width_unit, array( '%', 'vw' ), true ) ? 100 : ( 'px' === $width_unit ? 2000 : 100 );
	if ( $numeric_width <= 0 || $numeric_width > $maximum_width ) {
		return '';
	}

	$normalized_width = rtrim( rtrim( number_format( $numeric_width, 4, '.', '' ), '0' ), '.' );

	return $normalized_width . $width_unit;
}

/**
 * Return the Booking Resource that should be checked on the selection screen.
 *
 * The public `resource_id` attribute is the primary default-selection
 * parameter. `selected_resource_id` remains as a compatibility fallback for
 * shortcodes created before `resource_id` adopted that behavior.
 *
 * @param array<string,mixed> $config Normalized selector configuration.
 *
 * @return int Default Booking Resource ID or zero.
 */
function wpbc_booking_resource_selector_get_default_resource_id( $config ) {
	if ( ! empty( $config['resource_id'] ) ) {
		return absint( $config['resource_id'] );
	}

	return ! empty( $config['selected_resource_id'] ) ? absint( $config['selected_resource_id'] ) : 0;
}

/**
 * Normalize public shortcode attributes into the signed AJAX contract.
 *
 * The legacy-compatible aliases are accepted only at this boundary. AJAX and
 * submission requests carry one stable normalized representation.
 *
 * @param mixed $attributes Raw shortcode attributes or decoded configuration.
 *
 * @return array<string,mixed> Safe Booking Resource selector configuration.
 */
function wpbc_booking_resource_selector_normalize_config( $attributes ) {
	$attributes = is_array( $attributes ) ? $attributes : array();
	$defaults   = array(
		'resource_id'               => 0,
		'selected_resource_id'      => 0,
		'resource_ids'              => array(),
		'aggregate_resource_ids'    => array(),
		'cal_count'                 => 1,
		'start_month_calendar'      => false,
		'calendar_dates_start'      => '',
		'calendar_dates_end'        => '',
		'selected_dates'            => '',
		'options'                   => '',
		'form_type'                 => '',
		'auto_select_resource'      => false,
		'catalog_layout'            => 'grid',
		'show_resource_filters'     => false,
		'show_resource_image'       => true,
		'show_resource_title'       => true,
		'show_resource_description' => true,
		'catalog_item_width'        => '',
		'catalog_item_max_width'    => 0,
		'catalog_grid_items_per_row' => 0,
		'catalog_list_items_per_row' => 0,
		'show_resource_hierarchy'   => true,
		'show_availability'         => true,
		'show_starting_price'       => true,
		'show_progress'             => true,
		'progress_item_1_title'     => null,
		'progress_item_1_number'    => null,
		'progress_item_2_title'     => null,
		'progress_item_2_number'    => null,
		'screen_1_title'            => null,
		'screen_1_description'      => null,
		'allow_past'                => false,
		'return_url'                => '',
	);

	$attribute_aliases = array(
		'resources'     => 'resource_ids',
		'type'          => 'resource_ids',
		'aggregate'     => 'aggregate_resource_ids',
		'nummonths'     => 'cal_count',
		'startmonth'    => 'start_month_calendar',
		'selected_type' => 'selected_resource_id',
		'label'         => 'screen_1_title',
	);
	foreach ( $attribute_aliases as $public_attribute => $normalized_attribute ) {
		if ( array_key_exists( $public_attribute, $attributes ) && ! array_key_exists( $normalized_attribute, $attributes ) ) {
			$attributes[ $normalized_attribute ] = $attributes[ $public_attribute ];
		}
		unset( $attributes[ $public_attribute ] );
	}

	$attributes                       = array_intersect_key( $attributes, $defaults );
	$config                           = wp_parse_args( $attributes, $defaults );
	$config['resource_id']            = absint( $config['resource_id'] );
	$config['selected_resource_id']   = absint( $config['selected_resource_id'] );
	$config['resource_ids']           = wpbc_booking_resource_selector_normalize_ids( $config['resource_ids'] );
	$config['aggregate_resource_ids'] = wpbc_booking_resource_selector_normalize_ids( $config['aggregate_resource_ids'] );
	$config['cal_count']              = min( 24, max( 1, absint( $config['cal_count'] ) ) );

	$start_month = $config['start_month_calendar'];
	if ( is_array( $start_month ) ) {
		$year        = isset( $start_month[0] ) ? absint( $start_month[0] ) : 0;
		$month       = isset( $start_month[1] ) ? absint( $start_month[1] ) : 0;
		$start_month = ( $year && $month >= 1 && $month <= 12 ) ? array( $year, $month ) : false;
	} elseif ( is_string( $start_month ) && preg_match( '/^(\d{4})[-\/]?(\d{1,2})$/', $start_month, $matches ) ) {
		$month       = absint( $matches[2] );
		$start_month = ( $month >= 1 && $month <= 12 ) ? array( absint( $matches[1] ), $month ) : false;
	} else {
		$start_month = false;
	}
	$config['start_month_calendar'] = $start_month;

	foreach ( array( 'calendar_dates_start', 'calendar_dates_end' ) as $date_key ) {
		$date_value          = sanitize_text_field( (string) $config[ $date_key ] );
		$config[ $date_key ] = preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date_value ) ? $date_value : '';
	}

	$config['selected_dates']       = sanitize_text_field( (string) $config['selected_dates'] );
	$config['options']              = sanitize_text_field( (string) $config['options'] );
	$config['form_type']            = sanitize_text_field( (string) $config['form_type'] );
	$config['return_url']           = esc_url_raw( (string) $config['return_url'] );
	$config['auto_select_resource'] = wpbc_booking_resource_selector_normalize_boolean( $config['auto_select_resource'] );
	$config['catalog_layout'] = 'list' === sanitize_key( (string) $config['catalog_layout'] ) ? 'list' : 'grid';
	$config['show_resource_filters'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_resource_filters'] );
	$config['show_resource_image'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_resource_image'], true );
	$config['show_resource_title'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_resource_title'], true );
	$config['show_resource_description'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_resource_description'], true );
	$config['catalog_item_width'] = wpbc_booking_resource_selector_normalize_css_width( $config['catalog_item_width'] );
	$config['catalog_item_max_width'] = absint( $config['catalog_item_max_width'] );
	if ( $config['catalog_item_max_width'] > 0 ) {
		$config['catalog_item_max_width'] = min( 1200, max( 280, $config['catalog_item_max_width'] ) );
	}
	$config['catalog_grid_items_per_row'] = min( 12, absint( $config['catalog_grid_items_per_row'] ) );
	$config['catalog_list_items_per_row'] = min( 12, absint( $config['catalog_list_items_per_row'] ) );
	$config['show_resource_hierarchy'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_resource_hierarchy'], true );
	$config['show_availability'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_availability'], true );
	$config['show_starting_price'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_starting_price'], true );
	$config['show_progress'] = wpbc_booking_resource_selector_normalize_boolean( $config['show_progress'], true );
	$config['allow_past'] = wpbc_booking_resource_selector_normalize_boolean( $config['allow_past'] );

	$display_text_keys = array(
		'progress_item_1_title',
		'progress_item_1_number',
		'progress_item_2_title',
		'progress_item_2_number',
		'screen_1_title',
		'screen_1_description',
	);
	foreach ( $display_text_keys as $display_text_key ) {
		if ( null !== $config[ $display_text_key ] ) {
			$config[ $display_text_key ] = sanitize_text_field( (string) $config[ $display_text_key ] );
		}
	}

	return (array) apply_filters( 'wpbc_booking_resource_selector_normalized_config', $config, $attributes );
}

/**
 * Check whether signed selector configuration enables past bookings.
 *
 * @param array<string,mixed> $config Normalized or decoded configuration.
 *
 * @return bool True when the signed shortcode explicitly enables past bookings.
 */
function wpbc_booking_resource_selector_is_past_booking_enabled( $config ) {
	return ! empty( $config['allow_past'] );
}

/**
 * Base64-url encode a binary or text value without padding.
 *
 * @param string $raw_value Value to encode.
 *
 * @return string URL-safe encoded value.
 */
function wpbc_booking_resource_selector_base64url_encode( $raw_value ) {
	return rtrim( strtr( base64_encode( (string) $raw_value ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}

/**
 * Decode a base64-url value with strict validation.
 *
 * @param string $encoded_value Encoded value.
 *
 * @return string|false Decoded value or false.
 */
function wpbc_booking_resource_selector_base64url_decode( $encoded_value ) {
	$encoded_value = strtr( (string) $encoded_value, '-_', '+/' );
	$padding       = strlen( $encoded_value ) % 4;
	if ( $padding ) {
		$encoded_value .= str_repeat( '=', 4 - $padding );
	}

	return base64_decode( $encoded_value, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
}

/**
 * Sign normalized shortcode configuration for public AJAX round trips.
 *
 * @param array<string,mixed> $config Normalized configuration.
 *
 * @return string Signed opaque configuration token.
 */
function wpbc_booking_resource_selector_encode_config( $config ) {
	$payload   = wpbc_booking_resource_selector_base64url_encode( wp_json_encode( wpbc_booking_resource_selector_normalize_config( $config ) ) );
	$signature = hash_hmac( 'sha256', $payload, wp_salt( 'auth' ), true );

	return $payload . '.' . wpbc_booking_resource_selector_base64url_encode( $signature );
}

/**
 * Verify and decode a public AJAX configuration token.
 *
 * @param string $config_token Signed token.
 *
 * @return array<string,mixed>|WP_Error Normalized configuration or validation error.
 */
function wpbc_booking_resource_selector_decode_config( $config_token ) {
	$parts = explode( '.', (string) $config_token, 2 );
	if ( 2 !== count( $parts ) ) {
		return new WP_Error( 'resource_selector_config_invalid', __( 'The Booking Resource selection configuration is invalid. Reload the page and try again.', 'booking' ) );
	}

	$expected_signature = hash_hmac( 'sha256', $parts[0], wp_salt( 'auth' ), true );
	$actual_signature   = wpbc_booking_resource_selector_base64url_decode( $parts[1] );
	if ( false === $actual_signature || ! hash_equals( $expected_signature, $actual_signature ) ) {
		return new WP_Error( 'resource_selector_config_invalid', __( 'The Booking Resource selection configuration is invalid. Reload the page and try again.', 'booking' ) );
	}

	$json_data   = wpbc_booking_resource_selector_base64url_decode( $parts[0] );
	$config_data = false !== $json_data ? json_decode( $json_data, true ) : null;
	if ( ! is_array( $config_data ) ) {
		return new WP_Error( 'resource_selector_config_invalid', __( 'The Booking Resource selection configuration is invalid. Reload the page and try again.', 'booking' ) );
	}

	return wpbc_booking_resource_selector_normalize_config( $config_data );
}

/**
 * Sign one server-validated Booking Resource for final booking submission.
 *
 * @param array<string,mixed> $config      Original normalized configuration.
 * @param int                 $resource_id Selected Booking Resource ID.
 *
 * @return string Signed selection token, or an empty string for an invalid ID.
 */
function wpbc_booking_resource_selector_encode_submission_context( $config, $resource_id ) {
	$resource_id = absint( $resource_id );
	if ( ! $resource_id ) {
		return '';
	}

	$context                         = wpbc_booking_resource_selector_normalize_config( $config );
	$context['resource_id']          = $resource_id;
	$context['resource_ids']         = array( $resource_id );
	$context['selected_resource_id'] = 0;

	return wpbc_booking_resource_selector_encode_config( $context );
}

/**
 * Verify that a signed selector submission context matches a resource.
 *
 * @param string $context_token Signed selector context token.
 * @param int    $resource_id   Submitted Booking Resource ID.
 *
 * @return array<string,mixed>|WP_Error Verified context or controlled error.
 */
function wpbc_booking_resource_selector_validate_submission_context( $context_token, $resource_id ) {
	$resource_id = absint( $resource_id );
	if ( '' === trim( (string) $context_token ) ) {
		return new WP_Error( 'resource_selector_context_required', __( 'The Booking Resource selection has expired. Please start over and try again.', 'booking' ) );
	}

	$context = wpbc_booking_resource_selector_decode_config( $context_token );
	if ( is_wp_error( $context ) ) {
		return new WP_Error( 'resource_selector_context_invalid', __( 'The Booking Resource selection is invalid. Please start over and try again.', 'booking' ) );
	}
	if ( absint( $context['resource_id'] ) !== $resource_id ) {
		return new WP_Error( 'resource_selector_context_mismatch', __( 'The selected Booking Resource does not match this booking form. Please start over and try again.', 'booking' ) );
	}

	return $context;
}

```
