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 +333 -113 11.4.2 → 11.9 View file →
@@ -1,4 +1,4 @@
1 1 <?php /**
2 2 * @version 1.1
3 3 * @package Booking Calendar
4 4 * @category Timeline for Admin Panel
@@ -33,13 +33,21 @@
33 33
34 34 private $html_client_id; // ID of border element at client side.
35 35 public $options; // FixIn: 7.0.1.50.
36 36
37 - private $data_in_previous_cell; // FixIn: 8.5.2.6.
37 + private $data_in_previous_cell; // FixIn: 8.5.2.6.
38 +
39 + /**
40 + * Exact booking scope resolved from a public booking hash.
41 + *
42 + * @var array|false
43 + */
44 + private $booking_hash_scope;
38 45
39 - public function __construct(){// $bookings, $booking_types ) {
46 + public function __construct(){// $bookings, $booking_types ) {
40 47
41 - $this->reset_data_in_previous_cell();
48 + $this->reset_data_in_previous_cell();
49 + $this->booking_hash_scope = false;
42 50
43 51 $this->options = array(); // FixIn: 7.0.1.50.
44 52
45 53 $this->html_client_id = false;
@@ -114,13 +122,191 @@
114 122 )
115 123 );
116 124
117 125
118 - }
119 -
120 -
121 - /**
122 - * Rezet data in previos cell
126 + }
127 +
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 + /**
237 + * Apply an exact booking and resource authorization scope to timeline SQL arguments.
238 + *
239 + * A booking hash is a bearer credential for one booking. It must never be
240 + * converted into a customer-data keyword or used to broaden the query. Invalid
241 + * hashes deliberately select an impossible booking ID so processing fails closed.
242 + *
243 + * @param array $query_args Clean timeline query arguments.
244 + * @param string $booking_hash Public booking hash supplied by the timeline request.
245 + * @return array Query arguments restricted to the authorized booking, when applicable.
246 + */
247 + private function wpbc_apply_booking_hash_scope_to_query_args( $query_args, $booking_hash ) {
248 + $this->booking_hash_scope = false;
249 + $booking_hash = sanitize_text_field( (string) $booking_hash );
250 +
251 + if ( empty( $booking_hash ) ) {
252 + return $query_args;
253 + }
254 +
255 + $this->request_args['only_booked_resources'] = 1;
256 + $booking_scope = wpbc_hash__get_booking_id__resource_id( $booking_hash );
257 +
258 + if ( empty( $booking_scope ) || count( $booking_scope ) < 2 ) {
259 + $query_args['wh_booking_id'] = '-1';
260 + return $query_args;
261 + }
262 +
263 + $booking_id = absint( $booking_scope[0] );
264 + $resource_id = absint( $booking_scope[1] );
265 + $booking_row = wpbc_db_get_booking_details( $booking_id );
266 +
267 + if ( empty( $booking_id ) || empty( $resource_id ) || empty( $booking_row ) || $resource_id !== absint( $booking_row->booking_type ) ) {
268 + $query_args['wh_booking_id'] = '-1';
269 + return $query_args;
270 + }
271 +
272 + $this->booking_hash_scope = array(
273 + 'booking_id' => $booking_id,
274 + 'resource_id' => $resource_id,
275 + );
276 + $query_args['wh_booking_id'] = (string) $booking_id;
277 + $query_args['wh_booking_type'] = (string) $resource_id;
278 +
279 + return $query_args;
280 + }
281 +
282 + /**
283 + * Verify that a returned booking remains inside the resolved hash scope.
284 + *
285 + * This rendering-layer check is intentionally independent of the SQL restriction
286 + * so a future query regression cannot expose another booking's popover data.
287 + * Timelines without a booking hash retain their configured public presentation.
288 + *
289 + * @param int $booking_id Booking ID about to be rendered.
290 + * @param array $bookings Booking objects keyed by booking ID.
291 + * @return bool True when popover rendering is authorized for this row.
292 + */
293 + private function wpbc_is_booking_authorized_for_hash( $booking_id, $bookings ) {
294 + if ( empty( $this->request_args['booking_hash'] ) ) {
295 + return true;
296 + }
297 +
298 + $booking_id = absint( $booking_id );
299 + if ( empty( $this->booking_hash_scope ) || $booking_id !== $this->booking_hash_scope['booking_id'] || empty( $bookings[ $booking_id ] ) ) {
300 + return false;
301 + }
302 +
303 + return $this->booking_hash_scope['resource_id'] === absint( $bookings[ $booking_id ]->booking_type );
304 + }
305 +
306 +
307 + /**
308 + * Rezet data in previos cell
123 309 */
124 310 private function reset_data_in_previous_cell(){
125 311
126 312 $this->data_in_previous_cell = array(
@@ -147,9 +333,9 @@
147 333
148 334 $this->is_frontend = true;
149 335
150 336 // FixIn: 7.0.1.50.
151 - if ( isset( $attr['options'] ) ) {
337 + if ( isset( $attr['options'] ) ) {
152 338
153 339 $shortcode_param__options = $attr['options'];
154 340 $shortcode_param__options = html_entity_decode( $shortcode_param__options ); // FixIn: 9.8.15.6.
155 341 $custom_params = array();
@@ -173,11 +359,12 @@
173 359 $this->options[ $matche_value[1] ][ $matche_value[2] ] = $matche_value[3];
174 360 }
175 361 }
176 362
177 -//debuge($this->options);
178 - }
179 - // FixIn: 7.0.1.50.
363 +//debuge($this->options);
364 + }
365 + $this->options = self::normalize_options( $this->options );
366 + // FixIn: 7.0.1.50.
180 367
181 368
182 369 //Ovverride some parameters
183 370 //if ( isset( $attr['resource_id'] ) ) { $attr['type'] = $attr['resource_id']; }
@@ -197,36 +384,12 @@
197 384 // Get clean parameters to request booking data
198 385 $args = $this->wpbc_get_clean_paramas_from_request_for_timeline();
199 386
200 387
201 - // FixIn: 8.1.3.5.
202 - /** Client - Page first load
203 - *
204 - * If provided valid request_args['booking_hash']
205 - * - Firstly defined in constructor in $_REQUEST['booking_hash']
206 - * - or overwrited in define_request_view_params_from_params from parameters in shortcode 'booking_hash'
207 - * then check, if exist booking for this hash.
208 - * If exist, get Email of this booking, and
209 - * filter getting all other bookings by email keyword.
210 - * Addtionly set param ['only_booked_resources'] for showing only booking resources with exist bookings.
211 - */
212 - if ( isset( $this->request_args['booking_hash'] ) ) {
388 + // A public booking hash authorizes only its exact booking and resource.
389 + $args = $this->wpbc_apply_booking_hash_scope_to_query_args( $args, $this->request_args['booking_hash'] );
213 390
214 - // Get booking details by HASH, and then return Email (or other data of booking, or false if error
215 - $booking_details_email = wpbc_get__booking_data_field__by_booking_hash( $this->request_args['booking_hash'] , 'email' );
216 391
217 - if ( ! empty( $booking_details_email ) ) {
218 -
219 - // Do not show booking resources with no bookings
220 - $this->request_args['only_booked_resources'] = 1;
221 -
222 - //Set keyword for showing bookings ony relative to this email
223 - $args['wh_keyword'] = $booking_details_email; // '[email protected]';
224 - }
225 - }
226 - //FixIn: 8.1.3.5 - End
227 -
228 -
229 392 // Get booking data
230 393 $bk_listing = wpbc_get_bookings_objects( $args );
231 394 $this->bookings = $bk_listing['bookings'];
232 395 $this->booking_types = $bk_listing['resources'];
@@ -460,37 +623,13 @@
460 623 // Get clean parameters to request booking data
461 624 $args = $this->wpbc_get_clean_paramas_from_request_for_timeline();
462 625
463 626
464 - // FixIn: 8.1.3.5.
465 - /**
466 - * If provided valid ['booking_hash'] in timeline_obj in JavaScript param during Ajax request,
467 - * then check, if exist booking for this hash. If exist, get Email of this booking, and
468 - * filter getting all other bookings by email keyword.
469 - * Addtionly set param ['only_booked_resources'] for showing only booking resources with exist bookings
470 - */
471 - if ( isset( $attr['booking_hash'] ) ) {
627 + // Apply the same exact-booking scope to every unauthenticated navigation request.
628 + $booking_hash = isset( $attr['booking_hash'] ) ? $attr['booking_hash'] : '';
629 + $args = $this->wpbc_apply_booking_hash_scope_to_query_args( $args, $booking_hash );
472 630
473 - // Get booking details by HASH, and then return Email (or other data of booking, or false if error
474 - $booking_details_email = wpbc_get__booking_data_field__by_booking_hash( $attr['booking_hash'] , 'email' );
475 -//debuge($attr, $booking_details_email);
476 - if ( ! empty( $booking_details_email ) ) {
477 631
478 - // Do not show booking resources with no bookings
479 - $this->request_args['only_booked_resources'] = 1;
480 -
481 - //Set keyword for showing bookings ony relative to this email
482 - $args['wh_keyword'] = $booking_details_email; // '[email protected]';
483 - }
484 - if ( ( empty( $booking_details_email ) ) && ( ! empty( $attr['booking_hash'] ) ) ) { // FixIn: 8.4.6.1.
485 - // FixIn: 8.4.5.13.
486 - $this->request_args['only_booked_resources'] = 1;
487 - $args['wh_keyword'] = '``^`````^^````^`````````';
488 - }
489 - }
490 - //FixIn: 8.1.3.5 - End
491 -
492 -
493 632 // Get booking data
494 633 $bk_listing = wpbc_get_bookings_objects( $args );
495 634
496 635 $this->bookings = $bk_listing['bookings'];
@@ -501,9 +640,9 @@
501 640 $this->dates_array = $bookings_date_time[0];
502 641 $this->time_array_new = $bookings_date_time[1];
503 642
504 643
505 - $this->html_client_id = $attr['html_client_id'];
644 + $this->html_client_id = self::normalize_html_client_id( $attr['html_client_id'] );
506 645
507 646 return $this->html_client_id;
508 647 }
509 648
@@ -559,9 +698,12 @@
559 698 'header_title' : "<?php echo esc_js( $this->timeline_titles['header_title'] ); ?>",
560 699 'wh_trash' : "<?php echo esc_js( $this->request_args['wh_trash'] ); ?>",
561 700 'limit_hours' : "<?php echo esc_js( $this->request_args['limit_hours'] ); ?>",
562 701 'only_booked_resources': "<?php echo esc_js( $this->request_args['only_booked_resources'] ); ?>",
563 - '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 + ?>,
564 706 'booking_hash' : "<?php echo esc_js( $this->request_args['booking_hash'] ); ?>"
565 707 };
566 708 </script>
567 709 <div class="flex_tl_nav">
@@ -884,11 +1026,11 @@
884 1026 } // FixIn: 7.0.1.14.
885 1027 if ( isset( $param['booking_hash'] ) ) {
886 1028 $this->request_args['booking_hash'] = $param['booking_hash'];
887 1029 } // FixIn: 8.1.3.5.
888 - if ( ( empty( $this->options ) ) && ( isset( $param['options'] ) ) ) {
889 - $this->options = json_decode( wp_unslash( $param['options'] ), true ); // FixIn: 9.2.1.8.
890 - }
1030 + if ( ( empty( $this->options ) ) && ( isset( $param['options'] ) ) ) {
1031 + $this->options = self::decode_options( $param['options'] ); // FixIn: 9.2.1.8.
1032 + }
891 1033
892 1034 }
893 1035
894 1036
@@ -1895,9 +2037,11 @@
1895 2037 }
1896 2038
1897 2039 $bk_title .= " \n" . $this->get_booking_title_for_timeline( $booking_id, $row_settings['bookings'] );
1898 2040
1899 - $bk_title .= " \n" . wp_strip_all_tags( wpbc_get_short_dates_formated_to_show( $row_settings['bookings'][ $booking_id ]->dates_short ) ) ;
2041 + $bk_title .= " \n" . wp_strip_all_tags( wpbc_get_short_dates_formated_to_show( $row_settings['bookings'][ $booking_id ]->dates_short ) ) ;
2042 +
2043 + $bk_title = apply_filters( 'wpbc_timeline_booking_pipeline_title', $bk_title, $booking_id, $row_settings['bookings'] );
1900 2044
1901 2045 ?><a href="javascript:void(0)"
1902 2046 class="in_cell_date_booking_pipeline_a"
1903 2047 title="<?php echo esc_attr( $bk_title ); ?>"
@@ -1928,9 +2072,10 @@
1928 2072 $bk_a_title_arr[] = $bk_a_title;
1929 2073
1930 2074 $title_in_day = $title = $title_hint = '';
1931 2075
1932 - if ( $is_show_popover_in_timeline ) {
2076 + $can_show_booking_popover = $is_show_popover_in_timeline && $this->wpbc_is_booking_authorized_for_hash( $booking_id, $row_settings['bookings'] );
2077 + if ( $can_show_booking_popover ) {
1933 2078 $popup_content = $this->wpbc_get_booking_info_4_popover( $booking_id, $row_settings['bookings'], $row_settings['booking_types'] );
1934 2079
1935 2080
1936 2081 $popup_title_arr[] = $popup_content['title'];
@@ -1967,15 +2112,15 @@
1967 2112 // Booking CELL Title
1968 2113 ?><a href="javascript:void(0)"
1969 2114 class="<?php echo esc_attr( implode(' ', array(
1970 2115 'in_cell_date_booking_title',
1971 - ( $is_show_popover_in_timeline ) ? 'popover_bottom' : '',
1972 - ( $is_show_popover_in_timeline ) ? 'popover_click' : '',
2116 + ( ! empty( $popup_content_arr ) ) ? 'popover_bottom' : '',
2117 + ( ! empty( $popup_content_arr ) ) ? 'popover_click' : '',
1973 2118 ( count( $bookings_in_cell ) > 1 ) ? 'several_bookings_in_cell' : ''
1974 2119 ))); ?>"
1975 2120 <?php
1976 2121 // FixIn: 8.9.3.3.
1977 - if ( $is_show_popover_in_timeline ) { ?>
2122 + if ( ! empty( $popup_content_arr ) ) { ?>
1978 2123 data-content="<?php echo esc_html( str_replace( '"', "", $popup_content_arr ) ); ?>"
1979 2124 data-original-title="<?php echo esc_html( str_replace( '"', "", $popup_title_arr ) ); ?>"
1980 2125 <?php } ?>
1981 2126 ><?php
@@ -2999,11 +3144,18 @@
2999 3144 * @param string $content_text
3000 3145 *
3001 3146 * @return array
3002 3147 */
3003 - public function wpbc_get_booking_info_4_popover( $bk_id, $bookings, $booking_types ){
3004 -
3005 - if ( isset( $bookings[ $bk_id ] ) ) {
3148 + public function wpbc_get_booking_info_4_popover( $bk_id, $bookings, $booking_types ){
3149 +
3150 + if ( ! $this->wpbc_is_booking_authorized_for_hash( $bk_id, $bookings ) ) {
3151 + return array(
3152 + 'title' => '',
3153 + 'content' => '',
3154 + );
3155 + }
3156 +
3157 + if ( isset( $bookings[ $bk_id ] ) ) {
3006 3158 //$bookings[ $bk_id ]->form_show = str_replace( "&amp;", '&', $bookings[ $bk_id ]->form_show ); // FixIn: 7.1.2.12.
3007 3159 // We escaping at other place: wpbc__legacy__get_form_content_arr()
3008 3160 }
3009 3161
@@ -3034,35 +3186,38 @@
3034 3186 // Link
3035 3187 $header_title .= '<a class=\'button button-secondary\'
3036 3188 title=\'' . esc_attr( str_replace( "'", '', __( 'Booking Listing', 'booking' ) ) ) . '\'
3037 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>';
3038 - //Edit
3039 - if ( class_exists( 'wpdev_bk_personal' ) ) {
3040 - $bk_url_add = wpbc_get_new_booking_url( true, false );
3041 - $bk_hash = (isset( $bookings[$bk_id]->hash )) ? $bookings[$bk_id]->hash : '';
3042 - $bk_booking_type = $bookings[$bk_id]->booking_type;
3043 - $edit_booking_url = $bk_url_add . '&booking_type=' . $bk_booking_type . '&booking_hash=' . $bk_hash . '&parent_res=1';
3044 - // 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.
3045 -
3046 - $custom_booking_form = '';
3047 - if ( ! empty( $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'] ) ) {
3048 - $custom_booking_form = $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'];
3049 - $edit_booking_url .= '&booking_form=' . rawurlencode( $custom_booking_form ); // FixIn: 9.4.3.12.
3050 - }
3051 -
3052 - $edit_booking_onclick = "if ( 'function' === typeof wpbc_boo_listing__click__add_booking_modal_from_row ) {"
3053 - . ' wpbc_boo_listing__click__add_booking_modal_from_row('
3054 - . 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 ) . ','
3055 3207 . absint( $bk_booking_type ) . ','
3056 3208 . "'" . esc_js( $bk_hash ) . "',"
3057 3209 . "'" . esc_js( $custom_booking_form ) . "'"
3058 - . ' ); 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>';
3059 3218
3060 - $header_title .= '<a class=\'button button-secondary\'
3061 - title=\'' . esc_attr( str_replace( "'", '', __( 'Edit', 'booking' ) ) ) . '\'
3062 - href=\'' . esc_url( $edit_booking_url ) . '\' onclick=\'' . esc_attr( $edit_booking_onclick ) . '\' ><i class=\'wpbc_icn_draw\'></i></a>';
3063 3219
3064 -
3065 3220 $header_title .= '<span class=\'wpbc-buttons-separator\'></span>';
3066 3221 }
3067 3222 // Trash
3068 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>';
@@ -3268,13 +3423,17 @@
3268 3423
3269 3424
3270 3425 $content_text .= '</div>'; // Main Container: 'flex-popover-content-data'
3271 3426
3272 - return array(
3273 - 'title' => $header_title,
3274 - 'content' => $content_text
3275 - );
3276 - }
3427 + $popover = array(
3428 + 'title' => $header_title,
3429 + 'content' => $content_text
3430 + );
3431 +
3432 + $popover = apply_filters( 'wpbc_timeline_booking_popover', $popover, $bk_id, $bookings, $this->is_frontend );
3433 +
3434 + return $popover;
3435 + }
3277 3436
3278 3437 }
3279 3438
3280 3439
@@ -3279,9 +3438,9 @@
3279 3438
3280 3439
3281 3440
3282 3441 /** Navigation of Timeline in Ajax request */
3283 -function wpbc_ajax_flex_timeline() {
3442 +function wpbc_ajax_flex_timeline() {
3284 3443 /*
3285 3444 [timeline_obj] => Array
3286 3445 (
3287 3446 [is_frontend] => 1
@@ -3294,15 +3453,76 @@
3294 3453 [scroll_month] => 0
3295 3454 )
3296 3455 */
3297 3456
3298 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3299 - foreach ( $_POST['timeline_obj'] as $tl_key => $tl_value ) {
3300 - $_POST['timeline_obj'][ $tl_key ] = wpbc_clean_text_value( $tl_value ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3301 - }
3302 -
3303 - $attr = $_POST['timeline_obj']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
3304 - $attr['nav_step'] = wpbc_clean_text_value( $_POST['nav_step'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
3457 + // Public timeline navigation accepts only one flat scalar attribute map.
3458 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
3459 + if ( ! isset( $_POST['timeline_obj'] ) || ! is_array( $_POST['timeline_obj'] ) ) {
3460 + status_header( 400 );
3461 + wp_die( '' );
3462 + }
3463 +
3464 + $attr = array();
3465 + $allowed_timeline_keys = array_fill_keys(
3466 + array(
3467 + 'is_frontend',
3468 + 'html_client_id',
3469 + 'wh_booking_type',
3470 + 'is_matrix',
3471 + 'view_days_num',
3472 + 'scroll_start_date',
3473 + 'scroll_day',
3474 + 'scroll_month',
3475 + 'header_column1',
3476 + 'header_column2',
3477 + 'header_title',
3478 + 'wh_trash',
3479 + 'limit_hours',
3480 + 'only_booked_resources',
3481 + 'options',
3482 + 'booking_hash',
3483 + ),
3484 + true
3485 + );
3486 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
3487 + foreach ( $_POST['timeline_obj'] as $tl_key => $tl_value ) {
3488 + if ( ! is_scalar( $tl_key ) || ! is_scalar( $tl_value ) ) {
3489 + status_header( 400 );
3490 + wp_die( '' );
3491 + }
3492 +
3493 + $clean_key = sanitize_key( wp_unslash( (string) $tl_key ) );
3494 + if ( '' === $clean_key || ! isset( $allowed_timeline_keys[ $clean_key ] ) ) {
3495 + continue;
3496 + }
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 );
3506 + }
3507 +
3508 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
3509 + if ( isset( $_POST['nav_step'] ) && ! is_scalar( $_POST['nav_step'] ) ) {
3510 + status_header( 400 );
3511 + wp_die( '' );
3512 + }
3513 +
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'] ) {
3522 + status_header( 400 );
3523 + wp_die( '' );
3524 + }
3305 3525
3306 3526 // FixIn: 9.9.0.18.
3307 3527 $server_zone = date_default_timezone_get(); // If in 'Theme' or 'other plugin' set default timezone other than UTC. Save it.
3308 3528 if ( 'UTC' !== $server_zone ) { // Needed for WP date functions - set timezone to UTC.