| @@ -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( "&", '&', $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 . ', "' . wpbc_get_maybe_reloaded_booking_locale() . '" , 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. |