Sanitize (single entry point). * * @since 11.0.x */ class WPBC_FE_Shortcode_Params { /** * Get primary resource id from attrs: * * IMPORTANT: does NOT apply aggregate/agregate. * * @param array $attr - array with all parameters if shortcode. * @param int $default_id - default booking resource ID. * * @return int */ public static function get_from_attr__primary_resource_id( $attr, $default_id = 1 ) { $resource_id = intval( $default_id ); $type_value = null; if ( isset( $attr['type'] ) ) { $type_value = intval( $attr['type'] ); } if ( isset( $attr['resource_id'] ) ) { $type_value = intval( $attr['resource_id'] ); } if ( null !== $type_value ) { $resource_id = intval( $type_value ); } return $resource_id; } /** * Get resource id with aggregate/agregate support (legacy for [booking] / [bookingcalendar] / [bookingedit]). * * Sanitizes resource_id with support of: * - aliases: resource_id -> type * - aggregate/agregate * - commas as separators * * Returns string (may include aggregate IDs "5;3;9") for renderer to split later. * * @param array $attr - array with all parameters if shortcode. * @param int $default_id - default booking resource ID. * * @return int|string -> 5 | '5;9;10' */ public static function get_from_attr__resource_id_with_aggregate( $attr, $default_id = 1 ) { $resource_id = self::get_from_attr__primary_resource_id( $attr, $default_id ); // Aggregate (two spellings). if ( isset( $attr['agregate'] ) && ( '' !== (string) $attr['agregate'] ) ) { $resource_id .= ';' . (string) $attr['agregate']; } if ( isset( $attr['aggregate'] ) && ( '' !== (string) $attr['aggregate'] ) ) { $resource_id .= ';' . (string) $attr['aggregate']; } // Normalize separators. $resource_id = str_replace( ',', ';', $resource_id ); // Strict sanitize: digits and separators. $resource_id__or__csd = wpbc_clean_digit_or_csd( $resource_id ); $resource_id = str_replace( ',', ';', $resource_id__or__csd ); return ( '' !== $resource_id ) ? $resource_id : (string) (int) $default_id; } /** * Get calendar months count from shortcode attrs (nummonths) with fallback. * * @param array $attr - array with all parameters if shortcode. * @param int $default_count - default count of months. * * @return int */ public static function get_from_attr__months_count( $attr, $default_count = 1 ) { $cal_count = intval( $default_count ); if ( isset( $attr['nummonths'] ) ) { $cal_count = intval( $attr['nummonths'] ); } if ( isset( $attr['monthsnum'] ) ) { $cal_count = intval( $attr['monthsnum'] ); } if ( isset( $attr['monthscount'] ) ) { $cal_count = intval( $attr['monthscount'] ); } if ( isset( $attr['months_count'] ) ) { $cal_count = intval( $attr['months_count'] ); } $cal_count = max( 1, min( 36, (int) $cal_count ) ); // choose your max. return $cal_count; } /** * Parse startmonth into legacy format array( year, month ) or false. * Accepts "YYYY-M" or "YYYY-MM". * * @param array $attr - array with all parameters if shortcode. * @param false|mixed $default_val - false, if not defined. * * @return false|mixed|string[] */ public static function get_from_attr__start_month( $attr, $default_val = false ) { $val = ''; if ( isset( $attr['startmonth'] ) ) { $val = trim( (string) $attr['startmonth'] ); } if ( '' === $val ) { return $default_val; } if ( ! preg_match( '/^(\d{4})-(\d{1,2})$/', $val, $m ) ) { return $default_val; } $year = (int) $m[1]; $month = (int) $m[2]; if ( ( $year < 1900 ) || ( $year > 2100 ) ) { return $default_val; } if ( ( $month < 1 ) || ( $month > 12 ) ) { return $default_val; } return array( (string) $year, (string) $month ); } /** * Get calendar_dates_start usualy in format '2027-01-01' * * @param array $attr - array with all parameters if shortcode. * @param string $default_val - ''. * * @return string */ public static function get_from_attr__calendar_dates_start( $attr, $default_val = '' ) { $val = ''; if ( isset( $attr['calendar_dates_start'] ) ) { $val = (string) wpbc_sanitize_date( $attr['calendar_dates_start'] ); } if ( '' !== $val ) { return $val; } return (string) $default_val; } /** * Get calendar_dates_end usualy in format '2027-12-31' * * @param array $attr - array with all parameters if shortcode. * @param string $default_val - ''. * * @return string */ public static function get_from_attr__calendar_dates_end( $attr, $default_val = '' ) { $val = ''; if ( isset( $attr['calendar_dates_end'] ) ) { $val = (string) wpbc_sanitize_date( $attr['calendar_dates_end'] ); } if ( '' !== $val ) { return $val; } return (string) $default_val; } /** * Get selected_dates for Only 'booking form' without calendar - usualy in format '24.12.2027, 25.12.2027'; * * @param array $attr - array with all parameters if shortcode. * @param string $default_val - ''. * * @return string */ public static function get_from_attr__selected_dates_without_calendar( $attr, $default_val = '' ) { $val = ''; // '24.12.2027, 25.12.2027' if ( isset( $attr['selected_dates'] ) ) { $val = (string) wpbc_sanitize_csv_dates( $attr['selected_dates'] ); } if ( '' !== $val ) { return $val; } return (string) $default_val; } /** * Get options parameter from shortcode [booking ... options="..."] value (legacy key: 'options'). * * @param array $attr - array with all parameters if shortcode. * @param string $default_val - ''. * * @return string */ public static function get_from_attr__options( $attr, $default_val = '' ) { $options = ''; if ( isset( $attr['options'] ) ) { $options = (string) $attr['options']; $options = sanitize_text_field( wp_unslash( $options ) ); } if ( ! empty( $options ) ) { return $options; } return $default_val; } /** * Get custom booking form name (form type) from shortcode attrs (key: form_type). * * @param array $attr - array with all parameters if shortcode. * @param string $default_val - 'standard'. * * @return string */ public static function get_from_attr__custom_form( $attr, $default_val = 'standard' ) { $custom_form = ''; if ( isset( $attr['form_type'] ) ) { $custom_form = sanitize_text_field( wp_unslash( $attr['form_type'] ) ); } if ( isset( $attr['custom_form'] ) ) { $custom_form = sanitize_text_field( wp_unslash( $attr['custom_form'] ) ); } if ( isset( $attr['booking_form'] ) ) { $custom_form = sanitize_text_field( wp_unslash( $attr['booking_form'] ) ); } if ( isset( $attr['custom_booking_form'] ) ) { $custom_form = sanitize_text_field( wp_unslash( $attr['custom_booking_form'] ) ); } if ( ! empty( $custom_form ) ) { return (string) $custom_form; } // Get Primary booking resource ID from shortcode attr. $resource_id = self::get_from_attr__primary_resource_id( $attr, WPBC_FE_Attr_Postprocessor::get_default_booking_resource_id() ); // ------------------------------------------------------------------------------------------------------------- // Maybe get "Default Custom Form" for specific booking resource, in >= BM , if in shortcode 'custom_form' is EMPTY! // ------------------------------------------------------------------------------------------------------------- $default_custom_form_name = apply_bk_filter( 'wpbc_get_default_custom_form', '', $resource_id ); if ( ! empty( $default_custom_form_name ) ) { return (string) $default_custom_form_name; } return (string) $default_val; } /** * Get status of form, e.g.: 'published' | 'preview' -> from shortcode attrs (key: form_status). * * @param array $attr - array with all parameters if shortcode. * @param string $default_val - 'published'. * * @return string */ public static function get_from_attr__form_status( $attr, $default_val = 'published' ) { $val = ''; if ( isset( $attr['form_status'] ) ) { $val = sanitize_key( wp_unslash( $attr['form_status'] ) ); } // Normalize synonyms (optional). if ( in_array( $val, array( 'publish', 'published' ), true ) ) { $val = 'published'; } if ( in_array( $val, array( 'preview' ), true ) ) { $val = 'preview'; } // Whitelist (keep tight to avoid future “draft” leakage on frontend). if ( ! in_array( $val, array( 'published', 'preview' ), true ) ) { $val = (string) $default_val; } return ( '' !== $val ) ? $val : (string) $default_val; } } /** * Post-processing helpers for normalized attributes. * * This class contains logic that operates on already-sanitized values: * - aggregate resource splitting * - resource validation * - booking_hash resolution and parent-resource rewrite (legacy behavior) * - date-range completion (calendar_dates_start/end) * * @since 11.0.x */ class WPBC_FE_Attr_Postprocessor { /** * Get default Booking resource. * * @return int|mixed */ public static function get_default_booking_resource_id() { $resource_id = 1; $legacy = wpbc_get_legacy_booking_instance(); if ( ( ! empty( $legacy ) ) && ( false !== $legacy->wpdev_bk_personal ) ) { $resource_id = (int) $legacy->wpdev_bk_personal->get_default_booking_resource_id(); } return (int) $resource_id; } /** * If resource_id contains aggregate IDs "5;3;9", return first + full list, e.g: [ 'resource_id' => 5, 'aggregate_resource_id_arr' => [5,3,9] ] * * @param mixed $resource_id - e.g. "5;3;9" or "5" or 5. * * @return array( * 'resource_id' => string, * 'aggregate_resource_id_arr' => array * ) */ public static function split_aggregate_resource_id( $resource_id ) { $aggregate_resource_id_arr = array(); $resource_id = (string) $resource_id; $resource_id = str_replace( ',', ';', wpbc_clean_digit_or_csd( $resource_id ) ); if ( false !== strpos( $resource_id, ';' ) ) { $aggregate_resource_id_arr = explode( ';', $resource_id ); $resource_id = $aggregate_resource_id_arr[0]; } $aggregate_resource_id_arr = array_values( array_filter( array_map( 'absint', $aggregate_resource_id_arr ) ) ); $aggregate_resource_id_arr = array_values( array_unique( $aggregate_resource_id_arr ) ); return array( 'resource_id' => $resource_id, 'aggregate_resource_id_arr' => $aggregate_resource_id_arr, ); } /** * Validate resource_id parameter and check if booking resource exists. * * @param int|string $resource_id ID of booking resource. * * @return true|string True if valid, otherwise error message. */ public static function validate_resource_id( $resource_id ) { $resource_id = absint( $resource_id ); if ( 0 === $resource_id ) { return __( 'Booking resource type is not defined. This can be, when at the URL is wrong booking hash.', 'booking' ); } // Validation should not echo. Rendering layer decides about output. $is_echo = false; $is_booking_resource_exist = apply_bk_filter( 'wpdev_is_booking_resource_exist', true, $resource_id, $is_echo ); if ( ! $is_booking_resource_exist ) { return 'Booking resource does not exist. [ID=' . esc_attr( $resource_id ) . ']'; } return true; } /** * Hash resolution, e.g. $_GET['booking_hash'] for Booking Form rendering (calendar+form). Keeps legacy messages and parent resource rewrite behavior. * * @param array $params_arr array of parameters from shortcode. * @param string $context can be 'form'|'calendar'. * * @return array('ok' => bool, 'params_arr' => array, 'error_html' => string) */ public static function resolve_booking_hash_and_maybe_get_parent_resource_id( $params_arr, $context = 'form' ) { // Legacy behavior: ONLY checks key existence, not “non-empty”. $get_booking_hash = isset( $params_arr['booking_hash'] ) ? sanitize_text_field( wp_unslash( (string) $params_arr['booking_hash'] ) ) : ''; if ( '' === $get_booking_hash ) { $get_booking_hash = WPBC_GET_Request::has_non_empty_get( 'booking_hash' ) ? WPBC_GET_Request::get_sanitized( 'booking_hash' ) : ''; } if ( '' === $get_booking_hash ) { return array( 'ok' => true, 'params_arr' => $params_arr, 'error_html' => '', ); } // If booking_hash exists and this is calendar-only, show warning. if ( 'calendar' === $context ) { $is_error = '