PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
← All changes | core/timeline/v2/wpbc-class-timeline_v2.php +166 -42 11.5 → 11.9 View file →
@@ -42,9 +42,9 @@
42 42 * @var array|false
43 43 */
44 44 private $booking_hash_scope;
45 45
46 - public function __construct(){// $bookings, $booking_types ) {
46 + public function __construct(){// $bookings, $booking_types ) {
47 47
48 48 $this->reset_data_in_previous_cell();
49 49 $this->booking_hash_scope = false;
50 50
@@ -125,8 +125,116 @@
125 125
126 126 }
127 127
128 128 /**
129 + * Validate the server-generated DOM identifier used by Timeline navigation.
130 + *
131 + * Public AJAX requests may return this value in JavaScript and inline event
132 + * attributes. Accepting only the established prefix and decimal suffix keeps
133 + * it an identifier rather than caller-controlled markup or selector syntax.
134 + *
135 + * @param mixed $html_client_id Candidate Timeline DOM identifier.
136 + * @return string Valid identifier, or an empty string.
137 + */
138 + public static function normalize_html_client_id( $html_client_id ) {
139 + if ( ! is_scalar( $html_client_id ) ) {
140 + return '';
141 + }
142 +
143 + $html_client_id = (string) $html_client_id;
144 + if ( 64 < strlen( $html_client_id ) ) {
145 + return '';
146 + }
147 +
148 + return preg_match( '/\Awpbc_timeline_[0-9]+\z/D', $html_client_id )
149 + ? $html_client_id
150 + : '';
151 + }
152 +
153 + /**
154 + * Normalize the public timeline options contract.
155 + *
156 + * Timeline navigation round-trips options through the browser. Only Resource
157 + * links are consumed by the renderer, so every other key is discarded rather
158 + * than retained as attacker-controlled state for a later response.
159 + *
160 + * @param mixed $options Candidate timeline options.
161 + * @return array<string,array<int,string>> Valid Resource links keyed by Resource ID.
162 + */
163 + public static function normalize_options( $options ) {
164 + if (
165 + ! is_array( $options )
166 + || empty( $options['resource_link'] )
167 + || ! is_array( $options['resource_link'] )
168 + ) {
169 + return array();
170 + }
171 +
172 + $resource_links = array();
173 + foreach ( $options['resource_link'] as $resource_key => $resource_url ) {
174 + if ( ! is_scalar( $resource_key ) || ! is_scalar( $resource_url ) ) {
175 + continue;
176 + }
177 +
178 + $resource_id = absint( $resource_key );
179 + $resource_url = esc_url_raw( (string) $resource_url );
180 + if ( empty( $resource_id ) || '' === $resource_url ) {
181 + continue;
182 + }
183 +
184 + $resource_links[ $resource_id ] = $resource_url;
185 + }
186 +
187 + return empty( $resource_links )
188 + ? array()
189 + : array( 'resource_link' => $resource_links );
190 + }
191 +
192 + /**
193 + * Decode and normalize browser-submitted timeline options.
194 + *
195 + * @param mixed $encoded_options JSON text received from the timeline client.
196 + * @return array<string,array<int,string>> Valid Resource links, or an empty array.
197 + */
198 + public static function decode_options( $encoded_options ) {
199 + if ( ! is_scalar( $encoded_options ) ) {
200 + return array();
201 + }
202 +
203 + $decoded_options = json_decode( (string) $encoded_options, true, 32 );
204 + if ( JSON_ERROR_NONE !== json_last_error() ) {
205 + return array();
206 + }
207 +
208 + return self::normalize_options( $decoded_options );
209 + }
210 +
211 + /**
212 + * Encode timeline options as one complete JavaScript string literal.
213 + *
214 + * The timeline browser contract stores JSON text, rather than an object, in
215 + * `timeline_obj.options`. Encoding the normalized options twice preserves that
216 + * contract while the hexadecimal flags prevent quotes or HTML delimiters in a
217 + * URL from terminating the inline script context.
218 + *
219 + * @param mixed $options Candidate timeline options.
220 + * @return string JavaScript-safe JSON string literal, including its delimiters.
221 + */
222 + public static function encode_options_for_inline_script( $options ) {
223 + $options_json = wp_json_encode( self::normalize_options( $options ) );
224 + if ( false === $options_json ) {
225 + $options_json = '{}';
226 + }
227 +
228 + $javascript_literal = wp_json_encode(
229 + $options_json,
230 + JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
231 + );
232 +
233 + return false === $javascript_literal ? '"{}"' : $javascript_literal;
234 + }
235 +
236 + /**
129 237 * Apply an exact booking and resource authorization scope to timeline SQL arguments.
130 238 *
131 239 * A booking hash is a bearer credential for one booking. It must never be
132 240 * converted into a customer-data keyword or used to broaden the query. Invalid
@@ -225,9 +333,9 @@
225 333
226 334 $this->is_frontend = true;
227 335
228 336 // FixIn: 7.0.1.50.
229 - if ( isset( $attr['options'] ) ) {
337 + if ( isset( $attr['options'] ) ) {
230 338
231 339 $shortcode_param__options = $attr['options'];
232 340 $shortcode_param__options = html_entity_decode( $shortcode_param__options ); // FixIn: 9.8.15.6.
233 341 $custom_params = array();
@@ -251,11 +359,12 @@
251 359 $this->options[ $matche_value[1] ][ $matche_value[2] ] = $matche_value[3];
252 360 }
253 361 }
254 362
255 -//debuge($this->options);
256 - }
257 - // FixIn: 7.0.1.50.
363 +//debuge($this->options);
364 + }
365 + $this->options = self::normalize_options( $this->options );
366 + // FixIn: 7.0.1.50.
258 367
259 368
260 369 //Ovverride some parameters
261 370 //if ( isset( $attr['resource_id'] ) ) { $attr['type'] = $attr['resource_id']; }
@@ -531,9 +640,9 @@
531 640 $this->dates_array = $bookings_date_time[0];
532 641 $this->time_array_new = $bookings_date_time[1];
533 642
534 643
535 - $this->html_client_id = $attr['html_client_id'];
644 + $this->html_client_id = self::normalize_html_client_id( $attr['html_client_id'] );
536 645
537 646 return $this->html_client_id;
538 647 }
539 648
@@ -589,9 +698,12 @@
589 698 'header_title' : "<?php echo esc_js( $this->timeline_titles['header_title'] ); ?>",
590 699 'wh_trash' : "<?php echo esc_js( $this->request_args['wh_trash'] ); ?>",
591 700 'limit_hours' : "<?php echo esc_js( $this->request_args['limit_hours'] ); ?>",
592 701 'only_booked_resources': "<?php echo esc_js( $this->request_args['only_booked_resources'] ); ?>",
593 - 'options' : '<?php echo wp_json_encode( $this->options ); ?>',
702 + 'options' : <?php
703 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Returns a complete JSON-encoded JavaScript string literal.
704 + echo self::encode_options_for_inline_script( $this->options );
705 + ?>,
594 706 'booking_hash' : "<?php echo esc_js( $this->request_args['booking_hash'] ); ?>"
595 707 };
596 708 </script>
597 709 <div class="flex_tl_nav">
@@ -914,11 +1026,11 @@
914 1026 } // FixIn: 7.0.1.14.
915 1027 if ( isset( $param['booking_hash'] ) ) {
916 1028 $this->request_args['booking_hash'] = $param['booking_hash'];
917 1029 } // FixIn: 8.1.3.5.
918 - if ( ( empty( $this->options ) ) && ( isset( $param['options'] ) ) ) {
919 - $this->options = json_decode( wp_unslash( $param['options'] ), true ); // FixIn: 9.2.1.8.
920 - }
1030 + if ( ( empty( $this->options ) ) && ( isset( $param['options'] ) ) ) {
1031 + $this->options = self::decode_options( $param['options'] ); // FixIn: 9.2.1.8.
1032 + }
921 1033
922 1034 }
923 1035
924 1036
@@ -1927,11 +2039,9 @@
1927 2039 $bk_title .= " \n" . $this->get_booking_title_for_timeline( $booking_id, $row_settings['bookings'] );
1928 2040
1929 2041 $bk_title .= " \n" . wp_strip_all_tags( wpbc_get_short_dates_formated_to_show( $row_settings['bookings'][ $booking_id ]->dates_short ) ) ;
1930 2042
1931 - if ( function_exists( 'wpbc_is_11_5_features_enabled' ) && wpbc_is_11_5_features_enabled() ) {
1932 - $bk_title = apply_filters( 'wpbc_timeline_booking_pipeline_title', $bk_title, $booking_id, $row_settings['bookings'] );
1933 - }
2043 + $bk_title = apply_filters( 'wpbc_timeline_booking_pipeline_title', $bk_title, $booking_id, $row_settings['bookings'] );
1934 2044
1935 2045 ?><a href="javascript:void(0)"
1936 2046 class="in_cell_date_booking_pipeline_a"
1937 2047 title="<?php echo esc_attr( $bk_title ); ?>"
@@ -3076,35 +3186,38 @@
3076 3186 // Link
3077 3187 $header_title .= '<a class=\'button button-secondary\'
3078 3188 title=\'' . esc_attr( str_replace( "'", '', __( 'Booking Listing', 'booking' ) ) ) . '\'
3079 3189 href=\''.wpbc_get_bookings_url( true, false ).'&wh_booking_id='.$bk_id.'&tab=vm_booking_listing\' ><i class=\'wpbc_icn_gps_fixed\'></i></a>';
3080 - //Edit
3081 - if ( class_exists( 'wpdev_bk_personal' ) ) {
3082 - $bk_url_add = wpbc_get_new_booking_url( true, false );
3083 - $bk_hash = (isset( $bookings[$bk_id]->hash )) ? $bookings[$bk_id]->hash : '';
3084 - $bk_booking_type = $bookings[$bk_id]->booking_type;
3085 - $edit_booking_url = $bk_url_add . '&booking_type=' . $bk_booking_type . '&booking_hash=' . $bk_hash . '&parent_res=1';
3086 - // FixIn: 10.10.1.2 $edit_booking_url .= ( 'Off' !== get_bk_option( 'booking_is_resource_no_update__during_editing' ) ) ? '&resource_no_update=1' : ''; // FixIn: 9.4.2.3.
3087 -
3088 - $custom_booking_form = '';
3089 - if ( ! empty( $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'] ) ) {
3090 - $custom_booking_form = $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'];
3091 - $edit_booking_url .= '&booking_form=' . rawurlencode( $custom_booking_form ); // FixIn: 9.4.3.12.
3092 - }
3093 -
3094 - $edit_booking_onclick = "if ( 'function' === typeof wpbc_boo_listing__click__add_booking_modal_from_row ) {"
3095 - . ' wpbc_boo_listing__click__add_booking_modal_from_row('
3096 - . absint( $bk_id ) . ','
3190 + //Edit
3191 + if ( class_exists( 'wpdev_bk_personal' ) ) {
3192 + $bk_hash = (isset( $bookings[$bk_id]->hash )) ? $bookings[$bk_id]->hash : '';
3193 + $bk_booking_type = $bookings[$bk_id]->booking_type;
3194 + // FixIn: 10.10.1.2 $edit_booking_url .= ( 'Off' !== get_bk_option( 'booking_is_resource_no_update__during_editing' ) ) ? '&resource_no_update=1' : ''; // FixIn: 9.4.2.3.
3195 +
3196 + $custom_booking_form = '';
3197 + if ( ! empty( $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'] ) ) {
3198 + $custom_booking_form = $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'];
3199 + }
3200 + $edit_booking_url = wpbc_get_booking_admin_edit_url( $bk_booking_type, $bk_hash, $custom_booking_form );
3201 +
3202 + $edit_booking_onclick = '';
3203 + if ( ! wpbc_is_booking_admin_edit_page_enabled() ) {
3204 + $edit_booking_onclick = "if ( 'function' === typeof wpbc_boo_listing__click__add_booking_modal_from_row ) {"
3205 + . ' wpbc_boo_listing__click__add_booking_modal_from_row('
3206 + . absint( $bk_id ) . ','
3097 3207 . absint( $bk_booking_type ) . ','
3098 3208 . "'" . esc_js( $bk_hash ) . "',"
3099 3209 . "'" . esc_js( $custom_booking_form ) . "'"
3100 - . ' ); return false; }';
3210 + . ' ); return false; }';
3211 + }
3212 +
3213 + $header_title .= '<a class=\'button button-secondary\'
3214 + title=\'' . esc_attr( str_replace( "'", '', __( 'Edit', 'booking' ) ) ) . '\'
3215 + href=\'' . esc_url( $edit_booking_url ) . '\''
3216 + . ( '' !== $edit_booking_onclick ? ' onclick=\'' . esc_attr( $edit_booking_onclick ) . '\'' : '' )
3217 + . ' ><i class=\'wpbc_icn_draw\'></i></a>';
3101 3218
3102 - $header_title .= '<a class=\'button button-secondary\'
3103 - title=\'' . esc_attr( str_replace( "'", '', __( 'Edit', 'booking' ) ) ) . '\'
3104 - href=\'' . esc_url( $edit_booking_url ) . '\' onclick=\'' . esc_attr( $edit_booking_onclick ) . '\' ><i class=\'wpbc_icn_draw\'></i></a>';
3105 3219
3106 -
3107 3220 $header_title .= '<span class=\'wpbc-buttons-separator\'></span>';
3108 3221 }
3109 3222 // Trash
3110 3223 //$header_title .= '<a class=\'button button-secondary\' href=\'javascript:;\' onclick=\'javascript:delete_booking(' . $bk_id . ', ' . $this->current_user_id . ', &quot;' . wpbc_get_maybe_reloaded_booking_locale() . '&quot; , 1 );\' ><i class=\'wpbc_icn_delete_outline\'></i></a>';
@@ -3315,11 +3428,9 @@
3315 3428 'title' => $header_title,
3316 3429 'content' => $content_text
3317 3430 );
3318 3431
3319 - if ( function_exists( 'wpbc_is_11_5_features_enabled' ) && wpbc_is_11_5_features_enabled() ) {
3320 - $popover = apply_filters( 'wpbc_timeline_booking_popover', $popover, $bk_id, $bookings, $this->is_frontend );
3321 - }
3432 + $popover = apply_filters( 'wpbc_timeline_booking_popover', $popover, $bk_id, $bookings, $this->is_frontend );
3322 3433
3323 3434 return $popover;
3324 3435 }
3325 3436
@@ -3382,9 +3493,17 @@
3382 3493 $clean_key = sanitize_key( wp_unslash( (string) $tl_key ) );
3383 3494 if ( '' === $clean_key || ! isset( $allowed_timeline_keys[ $clean_key ] ) ) {
3384 3495 continue;
3385 3496 }
3386 - $attr[ $clean_key ] = wpbc_clean_text_value( wp_unslash( (string) $tl_value ) );
3497 + $clean_value = wp_unslash( (string) $tl_value );
3498 + if ( 'options' === $clean_key ) {
3499 + $normalized_options = WPBC_TimelineFlex::decode_options( $clean_value );
3500 + $encoded_options = wp_json_encode( $normalized_options );
3501 + $attr[ $clean_key ] = false === $encoded_options ? '{}' : $encoded_options;
3502 + continue;
3503 + }
3504 +
3505 + $attr[ $clean_key ] = wpbc_clean_text_value( $clean_value );
3387 3506 }
3388 3507
3389 3508 // phpcs:ignore WordPress.Security.NonceVerification.Missing
3390 3509 if ( isset( $_POST['nav_step'] ) && ! is_scalar( $_POST['nav_step'] ) ) {
@@ -3391,11 +3510,16 @@
3391 3510 status_header( 400 );
3392 3511 wp_die( '' );
3393 3512 }
3394 3513
3395 - $attr['nav_step'] = isset( $_POST['nav_step'] ) ? wpbc_clean_text_value( wp_unslash( (string) $_POST['nav_step'] ) ) : '0'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
3396 - $attr['is_frontend'] = isset( $attr['is_frontend'] ) ? $attr['is_frontend'] : '1';
3397 - if ( empty( $attr['html_client_id'] ) ) {
3514 + $attr['nav_step'] = isset( $_POST['nav_step'] )
3515 + ? wpbc_clean_text_value( wp_unslash( (string) $_POST['nav_step'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
3516 + : '0';
3517 + $attr['is_frontend'] = isset( $attr['is_frontend'] ) ? $attr['is_frontend'] : '1';
3518 + $attr['html_client_id'] = isset( $attr['html_client_id'] )
3519 + ? WPBC_TimelineFlex::normalize_html_client_id( $attr['html_client_id'] )
3520 + : '';
3521 + if ( '' === $attr['html_client_id'] ) {
3398 3522 status_header( 400 );
3399 3523 wp_die( '' );
3400 3524 }
3401 3525