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 +350 -120 10.15.7 → 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,25 +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.
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 ) . ','
3207 + . absint( $bk_booking_type ) . ','
3208 + . "'" . esc_js( $bk_hash ) . "',"
3209 + . "'" . esc_js( $custom_booking_form ) . "'"
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>';
3045 3218
3046 - if ( ! empty( $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'] ) ) {
3047 - $edit_booking_url .= '&booking_form=' . $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form']; // FixIn: 9.4.3.12.
3048 - }
3049 3219
3050 - $header_title .= '<a class=\'button button-secondary\'
3051 - title=\'' . esc_attr( str_replace( "'", '', __( 'Edit', 'booking' ) ) ) . '\'
3052 - href=\'' . esc_url($edit_booking_url) . '\' onclick=\'\' ><i class=\'wpbc_icn_draw\'></i></a>';
3053 -
3054 -
3055 3220 $header_title .= '<span class=\'wpbc-buttons-separator\'></span>';
3056 3221 }
3057 3222 // Trash
3058 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>';
@@ -3107,17 +3272,17 @@
3107 3272 }
3108 3273
3109 3274 $header_title .= '</div>';
3110 3275 }
3111 -
3276 +
3112 3277 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3113 - // Content
3278 + // Content
3114 3279 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3115 3280
3116 3281 // Container
3117 3282 $content_text = '<div id=\'wpbc-booking-id-'.$bk_id.'\' class=\'flex-popover-content-data\' >';
3118 3283
3119 -
3284 +
3120 3285 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3121 3286 // Labels
3122 3287 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3123 3288 $content_text .= '<div class=\'flex-popover-bars\' >';
@@ -3222,12 +3387,12 @@
3222 3387 // Notes
3223 3388 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3224 3389
3225 3390 // Notes
3226 - if ( ! empty( $bookings[$bk_id]->remark ) ) {
3391 + if ( ! empty( $bookings[$bk_id]->remark ) ) {
3227 3392 $content_text .= '<div class=\'wpbc-popover-booking-notes\'>' . '<strong>' . esc_js( __('Note', 'booking') ). ':</strong> ' . esc_textarea( $bookings[$bk_id]->remark ) . '</div>'; //FixIn: 7.1.1.2 // FixIn: 7.1.1.3.
3228 3393 }
3229 -
3394 +
3230 3395 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3231 3396 // Dates
3232 3397 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3233 3398
@@ -3233,9 +3398,9 @@
3233 3398
3234 3399 $bk_dates_short_id = array(); //BL
3235 3400 if ( count( $bookings[$bk_id]->dates ) > 0 )
3236 3401 $bk_dates_short_id = (isset( $bookings[$bk_id]->dates_short_id )) ? $bookings[$bk_id]->dates_short_id : array(); // Array ([0] => [1] => .... [4] => 6... [11] => [12] => 8 )
3237 -
3402 +
3238 3403 $short_dates_content = wpbc_get_short_dates_formated_to_show( $bookings[$bk_id]->dates_short, $is_approved, $bk_dates_short_id, $booking_types );
3239 3404 $short_dates_content = str_replace( '"', "'", $short_dates_content );
3240 3405
3241 3406 $content_text .= '<div class=\'flex-label-dates \'>';
@@ -3258,20 +3423,24 @@
3258 3423
3259 3424
3260 3425 $content_text .= '</div>'; // Main Container: 'flex-popover-content-data'
3261 3426
3262 - return array(
3263 - 'title' => $header_title,
3264 - 'content' => $content_text
3265 - );
3266 - }
3267 -
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 + }
3436 +
3268 3437 }
3269 3438
3270 3439
3271 3440
3272 3441 /** Navigation of Timeline in Ajax request */
3273 -function wpbc_ajax_flex_timeline() {
3442 +function wpbc_ajax_flex_timeline() {
3274 3443 /*
3275 3444 [timeline_obj] => Array
3276 3445 (
3277 3446 [is_frontend] => 1
@@ -3278,22 +3447,83 @@
3278 3447 [html_client_id] => wpbc_timeline_1454680376080
3279 3448 [wh_booking_type] => 3,4,1,5,6,7,8,9,2,10,11,12,14
3280 3449 [is_matrix] => 1
3281 3450 [view_days_num] => 30
3282 - [scroll_start_date] =>
3451 + [scroll_start_date] =>
3283 3452 [scroll_day] => 0
3284 3453 [scroll_month] => 0
3285 3454 )
3286 3455 */
3287 3456
3288 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3289 - foreach ( $_POST['timeline_obj'] as $tl_key => $tl_value ) {
3290 - $_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
3291 - }
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 + }
3292 3525
3293 - $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
3294 - $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
3295 -
3296 3526 // FixIn: 9.9.0.18.
3297 3527 $server_zone = date_default_timezone_get(); // If in 'Theme' or 'other plugin' set default timezone other than UTC. Save it.
3298 3528 if ( 'UTC' !== $server_zone ) { // Needed for WP date functions - set timezone to UTC.
3299 3529 // phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
@@ -3304,10 +3534,10 @@
3304 3534
3305 3535 $timeline = new WPBC_TimelineFlex();
3306 3536
3307 3537 $html_client_id = $timeline->ajax_init( $attr ); // Define arameters and get bookings
3308 -//debuge($timeline->options);
3309 -
3538 +//debuge($timeline->options);
3539 +
3310 3540 //echo '<div class="wpbc_timeline_ajax_replace">'; // Replace content of this container
3311 3541 $timeline->show_timeline();
3312 3542
3313 3543
@@ -3323,11 +3553,11 @@
3323 3553 echo $html;
3324 3554
3325 3555 /* ?><script type="text/javascript"> wpbc_define_tippy_popover(); </script><?php */
3326 3556 }
3327 - //echo '</div>';
3557 + //echo '</div>';
3328 3558
3329 -
3559 +
3330 3560 $timeline_results = ob_get_contents();
3331 3561
3332 3562 ob_end_clean();
3333 3563
@@ -3337,9 +3567,9 @@
3337 3567 @date_default_timezone_set( $server_zone );
3338 3568 }
3339 3569
3340 3570 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3341 - echo $timeline_results ;
3571 + echo $timeline_results ;
3342 3572 }
3343 3573 add_bk_action('wpbc_ajax_flex_timeline', 'wpbc_ajax_flex_timeline');
3344 3574
3345 3575
@@ -3573,5 +3803,5 @@
3573 3803 , WP_BK_VERSION_NUM );
3574 3804 }
3575 3805 }
3576 3806 }
3577 -add_action( 'wpbc_enqueue_css_files', 'wpbc_timeline_enqueue_css_files', 50 );
3807 +add_action( 'wpbc_enqueue_css_files', 'wpbc_timeline_enqueue_css_files', 50 );