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 +410 -139 10.10 → 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,17 +333,17 @@
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 - $bk_otions = $attr['options'];
154 - $bk_otions = html_entity_decode( $bk_otions ); // FixIn: 9.8.15.6.
339 + $shortcode_param__options = $attr['options'];
340 + $shortcode_param__options = html_entity_decode( $shortcode_param__options ); // FixIn: 9.8.15.6.
155 341 $custom_params = array();
156 - if (! empty($bk_otions)) {
342 + if (! empty($shortcode_param__options)) {
157 343 $param ='\s*([^\s]+)=[\'"]{1}([^\'"]+)[\'"]{1}\s*'; // Find all possible options
158 344 $pattern_to_search='%\s*{([^\s]+)' . $param .'}\s*[,]?\s*%';
159 - preg_match_all($pattern_to_search, $bk_otions, $matches, PREG_SET_ORDER);
345 + preg_match_all($pattern_to_search, $shortcode_param__options, $matches, PREG_SET_ORDER);
160 346 //debuge($matches);
161 347 /**
162 348 * [bookingtimeline ... options='{resource_link 3="http://beta/resource-apartment3-id3/"},{resource_link 4="http://beta/resource-3-id4/"}' ... ]
163 349 [0] => {resource_link 3="http://beta/resource-apartment3-id3/"},
@@ -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'];
@@ -257,10 +420,41 @@
257 420 public function ajax_init( $attr ) {
258 421
259 422 if ( ! defined( 'WPBC_TIMELINE_AJAX' ) ) { define( 'WPBC_TIMELINE_AJAX', true ); } // FixIn: 8.4.7.13.
260 423
261 - $this->is_frontend = (bool) $attr['is_frontend'];;
424 + // FixIn: 10.14.14.2.
425 + $is_frontend = (bool) $attr['is_frontend'];
262 426
427 + // Not front-end (or possibly changed parameter by attacker to see the bookings).
428 + if ( ! $is_frontend ) {
429 +
430 + // If this is not front-end -- admin side, then allow do this only for logged in users, with minimum user role, defined in the settings.
431 + // Get minimum user role to access the Timeline in admin panel (Booking Listing and Timeline Overview (Calendar Overview).
432 + $curr_user_role = get_bk_option( 'booking_user_role_booking' );
433 +
434 + // Get current user.
435 + $current_user = wpbc_get_current_user();
436 + $user_role_map = array(
437 + 'administrator' => 10,
438 + 'editor' => 7,
439 + 'author' => 2,
440 + 'contributor' => 1,
441 + 'subscriber' => 0,
442 + );
443 +
444 + $level = 0;
445 + if ( isset( $user_role_map[ $curr_user_role ] ) ) {
446 + $level = $user_role_map[ $curr_user_role ];
447 + }
448 +
449 + if ( empty( $current_user ) || empty( $current_user->user_level ) || ( $current_user->user_level < $level ) ) {
450 + // Security Fix: Enforce frontend mode for non-admins.
451 + $attr['is_frontend'] = 1;
452 + }
453 + }
454 +
455 + $this->is_frontend = (bool) $attr['is_frontend'];
456 +
263 457 //Ovverride some parameters
264 458 //if ( isset( $attr['resource_id'] ) ) { $attr['type'] = $attr['resource_id']; }
265 459 if ( isset( $attr['type'] ) ) {
266 460 $attr['wh_booking_type'] = $attr['type']; //Instead of 'wh_booking_type' paramter in shortcode is used 'type' parameter
@@ -429,37 +623,13 @@
429 623 // Get clean parameters to request booking data
430 624 $args = $this->wpbc_get_clean_paramas_from_request_for_timeline();
431 625
432 626
433 - // FixIn: 8.1.3.5.
434 - /**
435 - * If provided valid ['booking_hash'] in timeline_obj in JavaScript param during Ajax request,
436 - * then check, if exist booking for this hash. If exist, get Email of this booking, and
437 - * filter getting all other bookings by email keyword.
438 - * Addtionly set param ['only_booked_resources'] for showing only booking resources with exist bookings
439 - */
440 - 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 );
441 630
442 - // Get booking details by HASH, and then return Email (or other data of booking, or false if error
443 - $booking_details_email = wpbc_get__booking_data_field__by_booking_hash( $attr['booking_hash'] , 'email' );
444 -//debuge($attr, $booking_details_email);
445 - if ( ! empty( $booking_details_email ) ) {
446 631
447 - // Do not show booking resources with no bookings
448 - $this->request_args['only_booked_resources'] = 1;
449 -
450 - //Set keyword for showing bookings ony relative to this email
451 - $args['wh_keyword'] = $booking_details_email; // '[email protected]';
452 - }
453 - if ( ( empty( $booking_details_email ) ) && ( ! empty( $attr['booking_hash'] ) ) ) { // FixIn: 8.4.6.1.
454 - // FixIn: 8.4.5.13.
455 - $this->request_args['only_booked_resources'] = 1;
456 - $args['wh_keyword'] = '``^`````^^````^`````````';
457 - }
458 - }
459 - //FixIn: 8.1.3.5 - End
460 -
461 -
462 632 // Get booking data
463 633 $bk_listing = wpbc_get_bookings_objects( $args );
464 634
465 635 $this->bookings = $bk_listing['bookings'];
@@ -470,9 +640,9 @@
470 640 $this->dates_array = $bookings_date_time[0];
471 641 $this->time_array_new = $bookings_date_time[1];
472 642
473 643
474 - $this->html_client_id = $attr['html_client_id'];
644 + $this->html_client_id = self::normalize_html_client_id( $attr['html_client_id'] );
475 645
476 646 return $this->html_client_id;
477 647 }
478 648
@@ -528,9 +698,12 @@
528 698 'header_title' : "<?php echo esc_js( $this->timeline_titles['header_title'] ); ?>",
529 699 'wh_trash' : "<?php echo esc_js( $this->request_args['wh_trash'] ); ?>",
530 700 'limit_hours' : "<?php echo esc_js( $this->request_args['limit_hours'] ); ?>",
531 701 'only_booked_resources': "<?php echo esc_js( $this->request_args['only_booked_resources'] ); ?>",
532 - '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 + ?>,
533 706 'booking_hash' : "<?php echo esc_js( $this->request_args['booking_hash'] ); ?>"
534 707 };
535 708 </script>
536 709 <div class="flex_tl_nav">
@@ -853,11 +1026,11 @@
853 1026 } // FixIn: 7.0.1.14.
854 1027 if ( isset( $param['booking_hash'] ) ) {
855 1028 $this->request_args['booking_hash'] = $param['booking_hash'];
856 1029 } // FixIn: 8.1.3.5.
857 - if ( ( empty( $this->options ) ) && ( isset( $param['options'] ) ) ) {
858 - $this->options = json_decode( wp_unslash( $param['options'] ), true ); // FixIn: 9.2.1.8.
859 - }
1030 + if ( ( empty( $this->options ) ) && ( isset( $param['options'] ) ) ) {
1031 + $this->options = self::decode_options( $param['options'] ); // FixIn: 9.2.1.8.
1032 + }
860 1033
861 1034 }
862 1035
863 1036
@@ -982,9 +1155,9 @@
982 1155 $booking_id__to_del = $bookings_to_del [ $bk_i ];
983 1156
984 1157 ?><div style="margin:10px;font-weight:600;font-size:1.05em;"><?php
985 1158
986 - echo wp_kses_post( sprintf( 'Delete booking with %s', htmlspecialchars_decode( '<strong></strong><a href="' . esc_url( wpbc_get_bookings_url() . '&view_mode=vm_listing&tab=actions&wh_booking_id=' . $booking_id__to_del ) . '">' . 'ID = ' . $booking_id__to_del . '</a></strong>' ) ) );
1159 + echo wp_kses_post( sprintf( 'Delete booking with %s', htmlspecialchars_decode( '<strong></strong><a href="' . esc_url( wpbc_get_bookings_url() . '&tab=vm_booking_listing&wh_booking_id=' . $booking_id__to_del ) . '">' . 'ID = ' . $booking_id__to_del . '</a></strong>' ) ) );
987 1160
988 1161 ?></div><?php
989 1162 }
990 1163
@@ -1864,9 +2037,11 @@
1864 2037 }
1865 2038
1866 2039 $bk_title .= " \n" . $this->get_booking_title_for_timeline( $booking_id, $row_settings['bookings'] );
1867 2040
1868 - $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'] );
1869 2044
1870 2045 ?><a href="javascript:void(0)"
1871 2046 class="in_cell_date_booking_pipeline_a"
1872 2047 title="<?php echo esc_attr( $bk_title ); ?>"
@@ -1897,9 +2072,10 @@
1897 2072 $bk_a_title_arr[] = $bk_a_title;
1898 2073
1899 2074 $title_in_day = $title = $title_hint = '';
1900 2075
1901 - 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 ) {
1902 2078 $popup_content = $this->wpbc_get_booking_info_4_popover( $booking_id, $row_settings['bookings'], $row_settings['booking_types'] );
1903 2079
1904 2080
1905 2081 $popup_title_arr[] = $popup_content['title'];
@@ -1936,15 +2112,15 @@
1936 2112 // Booking CELL Title
1937 2113 ?><a href="javascript:void(0)"
1938 2114 class="<?php echo esc_attr( implode(' ', array(
1939 2115 'in_cell_date_booking_title',
1940 - ( $is_show_popover_in_timeline ) ? 'popover_bottom' : '',
1941 - ( $is_show_popover_in_timeline ) ? 'popover_click' : '',
2116 + ( ! empty( $popup_content_arr ) ) ? 'popover_bottom' : '',
2117 + ( ! empty( $popup_content_arr ) ) ? 'popover_click' : '',
1942 2118 ( count( $bookings_in_cell ) > 1 ) ? 'several_bookings_in_cell' : ''
1943 2119 ))); ?>"
1944 2120 <?php
1945 2121 // FixIn: 8.9.3.3.
1946 - if ( $is_show_popover_in_timeline ) { ?>
2122 + if ( ! empty( $popup_content_arr ) ) { ?>
1947 2123 data-content="<?php echo esc_html( str_replace( '"', "", $popup_content_arr ) ); ?>"
1948 2124 data-original-title="<?php echo esc_html( str_replace( '"', "", $popup_title_arr ) ); ?>"
1949 2125 <?php } ?>
1950 2126 ><?php
@@ -2549,9 +2725,9 @@
2549 2725 $this->show_timeline_header_start__admin_panel();
2550 2726 }
2551 2727 // New Spinner Loader // FixIn: 10.0.0.25.
2552 2728 ?><div class="flex_tl_table_loading wpbc_spins_loading_container">
2553 - <div class="wpbc_booking_form_spin_loader"><div class="wpbc_spins_loader_wrapper"><div class="wpbc_spins_loader_mini"></div></div></div>
2729 + <div class="wpbc_booking_form_spin_loader"><div class="wpbc_spins_loader_wrapper"><div class="wpbc_spin_loader_one_new"></div></div></div>
2554 2730 <?php // echo '<span class="0glyphicon 0glyphicon-refresh wpbc_icn_autorenew wpbc_spin"></span>' ?>
2555 2731 <span><?php esc_html_e('Loading','booking'); ?>...</span>
2556 2732 </div><?php
2557 2733
@@ -2968,11 +3144,18 @@
2968 3144 * @param string $content_text
2969 3145 *
2970 3146 * @return array
2971 3147 */
2972 - public function wpbc_get_booking_info_4_popover( $bk_id, $bookings, $booking_types ){
2973 -
2974 - 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 ] ) ) {
2975 3158 //$bookings[ $bk_id ]->form_show = str_replace( "&amp;", '&', $bookings[ $bk_id ]->form_show ); // FixIn: 7.1.2.12.
2976 3159 // We escaping at other place: wpbc__legacy__get_form_content_arr()
2977 3160 }
2978 3161
@@ -3002,26 +3185,39 @@
3002 3185 if ( ( ! $this->is_frontend ) && ( $is_can ) ) {
3003 3186 // Link
3004 3187 $header_title .= '<a class=\'button button-secondary\'
3005 3188 title=\'' . esc_attr( str_replace( "'", '', __( 'Booking Listing', 'booking' ) ) ) . '\'
3006 - href=\''.wpbc_get_bookings_url( true, false ).'&wh_booking_id='.$bk_id.'&view_mode=vm_listing&tab=actions\' ><i class=\'wpbc_icn_gps_fixed\'></i></a>';
3007 - //Edit
3008 - if ( class_exists( 'wpdev_bk_personal' ) ) {
3009 - $bk_url_add = wpbc_get_new_booking_url( true, false );
3010 - $bk_hash = (isset( $bookings[$bk_id]->hash )) ? $bookings[$bk_id]->hash : '';
3011 - $bk_booking_type = $bookings[$bk_id]->booking_type;
3012 - $edit_booking_url = $bk_url_add . '&booking_type=' . $bk_booking_type . '&booking_hash=' . $bk_hash . '&parent_res=1';
3013 - $edit_booking_url .= ( 'Off' !== get_bk_option( 'booking_is_resource_no_update__during_editing' ) ) ? '&resource_no_update=1' : ''; // FixIn: 9.4.2.3.
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>';
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>';
3014 3218
3015 - if ( ! empty( $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form'] ) ) {
3016 - $edit_booking_url .= '&booking_form=' . $bookings[ $bk_id ]->form_data['_all_fields_']['wpbc_custom_booking_form']; // FixIn: 9.4.3.12.
3017 - }
3018 3219
3019 - $header_title .= '<a class=\'button button-secondary\'
3020 - title=\'' . esc_attr( str_replace( "'", '', __( 'Edit', 'booking' ) ) ) . '\'
3021 - href=\'' . esc_url($edit_booking_url) . '\' onclick=\'\' ><i class=\'wpbc_icn_draw\'></i></a>';
3022 -
3023 -
3024 3220 $header_title .= '<span class=\'wpbc-buttons-separator\'></span>';
3025 3221 }
3026 3222 // Trash
3027 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>';
@@ -3058,10 +3254,10 @@
3058 3254
3059 3255 //Edit
3060 3256 if ( class_exists( 'wpdev_bk_personal' ) ) {
3061 3257
3062 - // $edit_booking_url_admin = wpbc_get_bookings_url( true, false ).'&wh_booking_id='.$bk_id.'&view_mode=vm_listing&tab=actions';
3063 - // $trash_booking_url_admin = wpbc_get_bookings_url( true, false ).'&wh_booking_id='.$bk_id.'&view_mode=vm_listing&tab=actions';
3258 + // $edit_booking_url_admin = wpbc_get_bookings_url( true, false ).'&wh_booking_id='.$bk_id.'&tab=vm_booking_listing';
3259 + // $trash_booking_url_admin = wpbc_get_bookings_url( true, false ).'&wh_booking_id='.$bk_id.'&tab=vm_booking_listing';
3064 3260
3065 3261 $is_change_hash_after_approvement = get_bk_option( 'booking_is_change_hash_after_approvement' ); // FixIn: 8.6.1.6.
3066 3262 if ( ( ! $is_approved ) || ( 'On' != $is_change_hash_after_approvement ) ) { // FixIn: 8.2.1.14.
3067 3263 $visitorbookingediturl = apply_bk_filter( 'wpdev_booking_set_booking_edit_link_at_email', '[visitorbookingediturl]', $bk_id );
@@ -3076,17 +3272,17 @@
3076 3272 }
3077 3273
3078 3274 $header_title .= '</div>';
3079 3275 }
3080 -
3276 +
3081 3277 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3082 - // Content
3278 + // Content
3083 3279 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3084 3280
3085 3281 // Container
3086 3282 $content_text = '<div id=\'wpbc-booking-id-'.$bk_id.'\' class=\'flex-popover-content-data\' >';
3087 3283
3088 -
3284 +
3089 3285 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3090 3286 // Labels
3091 3287 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3092 3288 $content_text .= '<div class=\'flex-popover-bars\' >';
@@ -3191,12 +3387,12 @@
3191 3387 // Notes
3192 3388 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3193 3389
3194 3390 // Notes
3195 - if ( ! empty( $bookings[$bk_id]->remark ) ) {
3391 + if ( ! empty( $bookings[$bk_id]->remark ) ) {
3196 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.
3197 3393 }
3198 -
3394 +
3199 3395 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3200 3396 // Dates
3201 3397 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3202 3398
@@ -3202,9 +3398,9 @@
3202 3398
3203 3399 $bk_dates_short_id = array(); //BL
3204 3400 if ( count( $bookings[$bk_id]->dates ) > 0 )
3205 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 )
3206 -
3402 +
3207 3403 $short_dates_content = wpbc_get_short_dates_formated_to_show( $bookings[$bk_id]->dates_short, $is_approved, $bk_dates_short_id, $booking_types );
3208 3404 $short_dates_content = str_replace( '"', "'", $short_dates_content );
3209 3405
3210 3406 $content_text .= '<div class=\'flex-label-dates \'>';
@@ -3227,20 +3423,24 @@
3227 3423
3228 3424
3229 3425 $content_text .= '</div>'; // Main Container: 'flex-popover-content-data'
3230 3426
3231 - return array(
3232 - 'title' => $header_title,
3233 - 'content' => $content_text
3234 - );
3235 - }
3236 -
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 +
3237 3437 }
3238 3438
3239 3439
3240 3440
3241 3441 /** Navigation of Timeline in Ajax request */
3242 -function wpbc_ajax_flex_timeline() {
3442 +function wpbc_ajax_flex_timeline() {
3243 3443 /*
3244 3444 [timeline_obj] => Array
3245 3445 (
3246 3446 [is_frontend] => 1
@@ -3247,22 +3447,83 @@
3247 3447 [html_client_id] => wpbc_timeline_1454680376080
3248 3448 [wh_booking_type] => 3,4,1,5,6,7,8,9,2,10,11,12,14
3249 3449 [is_matrix] => 1
3250 3450 [view_days_num] => 30
3251 - [scroll_start_date] =>
3451 + [scroll_start_date] =>
3252 3452 [scroll_day] => 0
3253 3453 [scroll_month] => 0
3254 3454 )
3255 3455 */
3256 3456
3257 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3258 - foreach ( $_POST['timeline_obj'] as $tl_key => $tl_value ) {
3259 - $_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
3260 - }
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 + }
3261 3525
3262 - $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
3263 - $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
3264 -
3265 3526 // FixIn: 9.9.0.18.
3266 3527 $server_zone = date_default_timezone_get(); // If in 'Theme' or 'other plugin' set default timezone other than UTC. Save it.
3267 3528 if ( 'UTC' !== $server_zone ) { // Needed for WP date functions - set timezone to UTC.
3268 3529 // phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
@@ -3273,10 +3534,10 @@
3273 3534
3274 3535 $timeline = new WPBC_TimelineFlex();
3275 3536
3276 3537 $html_client_id = $timeline->ajax_init( $attr ); // Define arameters and get bookings
3277 -//debuge($timeline->options);
3278 -
3538 +//debuge($timeline->options);
3539 +
3279 3540 //echo '<div class="wpbc_timeline_ajax_replace">'; // Replace content of this container
3280 3541 $timeline->show_timeline();
3281 3542
3282 3543
@@ -3292,11 +3553,11 @@
3292 3553 echo $html;
3293 3554
3294 3555 /* ?><script type="text/javascript"> wpbc_define_tippy_popover(); </script><?php */
3295 3556 }
3296 - //echo '</div>';
3557 + //echo '</div>';
3297 3558
3298 -
3559 +
3299 3560 $timeline_results = ob_get_contents();
3300 3561
3301 3562 ob_end_clean();
3302 3563
@@ -3306,9 +3567,9 @@
3306 3567 @date_default_timezone_set( $server_zone );
3307 3568 }
3308 3569
3309 3570 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3310 - echo $timeline_results ;
3571 + echo $timeline_results ;
3311 3572 }
3312 3573 add_bk_action('wpbc_ajax_flex_timeline', 'wpbc_ajax_flex_timeline');
3313 3574
3314 3575
@@ -3322,22 +3583,32 @@
3322 3583 * @param $booking_hash
3323 3584 *
3324 3585 * @return bool
3325 3586 */
3326 -function wpbc_is_show_popover_in_flex_timeline( $is_frontend, $booking_hash ){
3587 +function wpbc_is_show_popover_in_flex_timeline( $is_frontend, $booking_hash ) {
3327 3588
3328 - // Default for admin
3589 + // Default for admin.
3329 3590 $is_show_popover_in_timeline = true;
3330 3591
3331 - // For client Timeline
3332 - if ( $is_frontend )
3333 - $is_show_popover_in_timeline = ( get_bk_option( 'booking_is_show_popover_in_timeline_front_end' ) == 'On' ) ? true : false ;
3592 + // For client Timeline.
3593 + if ( $is_frontend ) {
3334 3594
3335 - // For customer booking listing with ability to edit
3595 + // FixIn: 10.14.11.1.
3596 + if ( WPBC_DISABLE_POPOVER_IN_TIMELINE ) {
3597 + $is_show_popover_in_timeline = false;
3598 + } else {
3599 + $is_show_popover_in_timeline = ( get_bk_option( 'booking_is_show_popover_in_timeline_front_end' ) == 'On' ) ? true : false;
3600 + if ( ! class_exists( 'wpdev_bk_personal' ) ) {
3601 + $is_show_popover_in_timeline = false; // FixIn: 10.14.9.2.
3602 + }
3603 + }
3604 + }
3605 +
3606 + // For customer booking listing with ability to edit.
3336 3607 // FixIn: 8.1.3.5.
3337 3608 if ( ( $is_frontend ) && ( ! empty( $booking_hash ) ) ) {
3338 3609
3339 - //In case if we have valid valid hash then show booking details
3610 + // In case if we have valid valid hash then show booking details.
3340 3611 $my_booking_id_type = wpbc_hash__get_booking_id__resource_id( $booking_hash );
3341 3612
3342 3613 if ( ! empty( $my_booking_id_type ) ) {
3343 3614 $is_show_popover_in_timeline = true;
@@ -3378,9 +3649,9 @@
3378 3649 [bookingtimeline type="4" view_days_num=365 scroll_start_date="" scroll_month=-3]
3379 3650
3380 3651
3381 3652 */
3382 -function bookingflextimeline_shortcode($attr) {
3653 +function bookingflextimeline_shortcode($attr) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
3383 3654
3384 3655 if ( wpbc_is_on_edit_page() ) {
3385 3656 return wpbc_get_preview_for_shortcode( 'bookingflextimeline', $attr ); // FixIn: 9.9.0.39.
3386 3657 }
@@ -3523,9 +3794,9 @@
3523 3794
3524 3795 if ( in_array( $where_to_load, array( 'admin', 'both', 'client' ) ) ) {
3525 3796
3526 3797 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
3527 - if ( ( ( isset($_REQUEST['view_mode']) ) && ( $_REQUEST['view_mode']== 'vm_calendar' ) ) || ( 'client' == $where_to_load ) ){
3798 + if ( ( ( isset($_REQUEST['tab']) ) && ( $_REQUEST['tab']== 'vm_calendar' ) ) || ( 'client' == $where_to_load ) ){
3528 3799
3529 3800 wp_enqueue_style( 'wpbc-flex-timeline'
3530 3801 , trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/timeline_v2.1.css' /* wpbc_plugin_url( '/src/css/code_mirror.css' ) */
3531 3802 , array()
@@ -3532,5 +3803,5 @@
3532 3803 , WP_BK_VERSION_NUM );
3533 3804 }
3534 3805 }
3535 3806 }
3536 -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 );