| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * Admin Panel UI - Parts |
| 4 | 4 | * |
| @@ -68,9 +68,51 @@ | ||
| 68 | 68 | echo '</div>'; |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | 71 | |
| 72 | +/** | |
| 73 | + * Order validated starter pages for the Booking Calendar top dropdown. | |
| 74 | + * | |
| 75 | + * The publishing module remains responsible for discovering valid pages. This | |
| 76 | + * helper controls presentation only and appends unknown future page purposes in | |
| 77 | + * their original order so new integrations are not hidden. | |
| 78 | + * | |
| 79 | + * @param array $wpbc_starter_pages Validated starter pages keyed by purpose. | |
| 80 | + * | |
| 81 | + * @return array Starter pages in top-dropdown display order. | |
| 82 | + */ | |
| 83 | +function wpbc_ui__top_nav__order_starter_pages( $wpbc_starter_pages ) { | |
| 72 | 84 | |
| 85 | + if ( empty( $wpbc_starter_pages ) || ! is_array( $wpbc_starter_pages ) ) { | |
| 86 | + return array(); | |
| 87 | + } | |
| 88 | + | |
| 89 | + $preferred_page_order = array( | |
| 90 | + 'full_day_booking', | |
| 91 | + 'appointment_booking', | |
| 92 | + 'time_slots_booking', | |
| 93 | + 'resource_selector_booking', | |
| 94 | + 'contact_form', | |
| 95 | + ); | |
| 96 | + $ordered_starter_pages = array(); | |
| 97 | + | |
| 98 | + foreach ( $preferred_page_order as $page_key ) { | |
| 99 | + if ( array_key_exists( $page_key, $wpbc_starter_pages ) ) { | |
| 100 | + $ordered_starter_pages[ $page_key ] = $wpbc_starter_pages[ $page_key ]; | |
| 101 | + } | |
| 102 | + } | |
| 103 | + | |
| 104 | + foreach ( $wpbc_starter_pages as $page_key => $wpbc_starter_page ) { | |
| 105 | + if ( ! array_key_exists( $page_key, $ordered_starter_pages ) ) { | |
| 106 | + $ordered_starter_pages[ $page_key ] = $wpbc_starter_page; | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 110 | + return $ordered_starter_pages; | |
| 111 | +} | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 73 | 115 | /** |
| 74 | 116 | * Show element - "WPBC - Main Dropdown" |
| 75 | 117 | * |
| 76 | 118 | * @return void |
| @@ -81,17 +123,11 @@ | ||
| 81 | 123 | $svg_icon_style = 'margin:5px 5px 0 0;'; // 'background-position: 0 0;background-size: ' . $svg_size . ' ' . $svg_size . ';width: ' . $svg_size . ';height: ' . $svg_size . ';'; //. |
| 82 | 124 | $svg_icon = wpbc_get_svg_logo_for_background( '#555', '#e5e5e5', '1.0' ); |
| 83 | 125 | |
| 84 | 126 | $wpbc_starter_pages = function_exists( 'wpbc_get_published_activation_booking_pages' ) ? wpbc_get_published_activation_booking_pages() : array(); |
| 127 | + $wpbc_starter_pages = wpbc_ui__top_nav__order_starter_pages( $wpbc_starter_pages ); | |
| 85 | 128 | $wp_post_booking_absolute = false; |
| 86 | 129 | |
| 87 | - if ( | |
| 88 | - empty( $wpbc_starter_pages ) && | |
| 89 | - function_exists( 'wpbc_stp_wiz__is_exist_published_page_with_booking_form' ) | |
| 90 | - ) { | |
| 91 | - $wp_post_booking_absolute = wpbc_stp_wiz__is_exist_published_page_with_booking_form(); | |
| 92 | - } | |
| 93 | - | |
| 94 | 130 | $el_arr = array( |
| 95 | 131 | // 'title' => 'Booking Calendar', |
| 96 | 132 | // 'font_icon' => 'wpbc-bi-calendar2-range', |
| 97 | 133 | 'title_html' => '<span class="nav-tab-text" style="margin: -3px 0 0 5px;font-size: 16px;padding: 0;"><span style="position: absolute;font-size: 7px;margin-top: 13px;margin-left: 1px;">WP</span>Booking Calendar</span>', |
| @@ -108,13 +144,8 @@ | ||
| 108 | 144 | $el_arr['items'][] = array( |
| 109 | 145 | 'type' => 'header', |
| 110 | 146 | 'title' => __( 'Visit Booking Pages', 'booking' ), |
| 111 | 147 | ); |
| 112 | - $el_arr['items'][] = array( | |
| 113 | - 'type' => 'link', | |
| 114 | - 'title' => __( 'Visit Home Page', 'booking' ), | |
| 115 | - 'url' => esc_url( home_url( '/' ) ), | |
| 116 | - ); | |
| 117 | 148 | |
| 118 | 149 | foreach ( $wpbc_starter_pages as $wpbc_starter_page ) { |
| 119 | 150 | if ( empty( $wpbc_starter_page['url'] ) ) { |
| 120 | 151 | continue; |
| @@ -132,8 +163,14 @@ | ||
| 132 | 163 | 'title' => $wpbc_starter_page_title, |
| 133 | 164 | 'url' => esc_url( $wpbc_starter_page['url'] ), |
| 134 | 165 | ); |
| 135 | 166 | } |
| 167 | + | |
| 168 | + $el_arr['items'][] = array( | |
| 169 | + 'type' => 'link', | |
| 170 | + 'title' => __( 'Visit Home Page', 'booking' ), | |
| 171 | + 'url' => esc_url( home_url( '/' ) ), | |
| 172 | + ); | |
| 136 | 173 | } elseif ( ! empty( $wp_post_booking_absolute ) ) { |
| 137 | 174 | $el_arr['items'][] = array( |
| 138 | 175 | 'type' => 'header', |
| 139 | 176 | 'title' => __( 'Visit Booking Page', 'booking' ), |
| @@ -139,15 +176,15 @@ | ||
| 139 | 176 | 'title' => __( 'Visit Booking Page', 'booking' ), |
| 140 | 177 | ); |
| 141 | 178 | $el_arr['items'][] = array( |
| 142 | 179 | 'type' => 'link', |
| 143 | - 'title' => __( 'Visit Home Page', 'booking' ), | |
| 144 | - 'url' => esc_url( home_url( '/' ) ), | |
| 180 | + 'title' => __( 'Go to page with booking form', 'booking' ), | |
| 181 | + 'url' => esc_url( $wp_post_booking_absolute ), | |
| 145 | 182 | ); |
| 146 | 183 | $el_arr['items'][] = array( |
| 147 | 184 | 'type' => 'link', |
| 148 | - 'title' => __( 'Go to page with booking form', 'booking' ), | |
| 149 | - 'url' => esc_url( $wp_post_booking_absolute ), | |
| 185 | + 'title' => __( 'Visit Home Page', 'booking' ), | |
| 186 | + 'url' => esc_url( home_url( '/' ) ), | |
| 150 | 187 | ); |
| 151 | 188 | } |
| 152 | 189 | $el_arr['items'][] = array( |
| 153 | 190 | 'type' => 'header', |
| @@ -309,32 +346,172 @@ | ||
| 309 | 346 | } |
| 310 | 347 | |
| 311 | 348 | |
| 312 | 349 | /** |
| 313 | - * Get Full Screen mode from the immediate browser cookie, falling back to saved user meta value. | |
| 350 | + * Build a short-lived browser value for an immediately changed fullscreen preference. | |
| 314 | 351 | * |
| 315 | - * @param string $saved_value Saved user-meta value. | |
| 352 | + * The timestamp distinguishes a pending navigation safeguard from legacy | |
| 353 | + * year-long cookies, which must not override a successfully stored user value. | |
| 316 | 354 | * |
| 317 | - * @return string 'On', 'Off', or empty string. | |
| 355 | + * @param string $mode Fullscreen mode, either `On` or `Off`. | |
| 356 | + * @param int|null $issued_timestamp Optional Unix timestamp for deterministic tests. | |
| 357 | + * | |
| 358 | + * @return string Pending cookie value, or an empty string for an invalid mode. | |
| 318 | 359 | */ |
| 319 | -function wpbc_ui__get_full_screen_mode_from_cookie( $saved_value = '' ) { | |
| 360 | +function wpbc_ui__create_full_screen_mode_cookie_value( $mode, $issued_timestamp = null ) { | |
| 320 | 361 | |
| 362 | + if ( ! in_array( $mode, array( 'On', 'Off' ), true ) ) { | |
| 363 | + return ''; | |
| 364 | + } | |
| 365 | + | |
| 366 | + $issued_timestamp = null === $issued_timestamp ? time() : absint( $issued_timestamp ); | |
| 367 | + | |
| 368 | + return $mode . '|' . $issued_timestamp; | |
| 369 | +} | |
| 370 | + | |
| 371 | +/** | |
| 372 | + * Return every server-side cookie path used by Booking Calendar administration. | |
| 373 | + * | |
| 374 | + * Multiple paths are updated together to replace legacy cookies created by | |
| 375 | + * root, subdirectory, or WordPress administration requests. | |
| 376 | + * | |
| 377 | + * @return string[] Unique absolute cookie paths. | |
| 378 | + */ | |
| 379 | +function wpbc_ui__get_full_screen_mode_cookie_paths() { | |
| 380 | + | |
| 381 | + $cookie_paths = array( '/' ); | |
| 382 | + | |
| 383 | + if ( defined( 'COOKIEPATH' ) && COOKIEPATH ) { | |
| 384 | + $cookie_paths[] = COOKIEPATH; | |
| 385 | + } | |
| 386 | + | |
| 387 | + if ( defined( 'ADMIN_COOKIE_PATH' ) && ADMIN_COOKIE_PATH ) { | |
| 388 | + $cookie_paths[] = ADMIN_COOKIE_PATH; | |
| 389 | + } | |
| 390 | + | |
| 391 | + return array_values( array_unique( array_filter( $cookie_paths ) ) ); | |
| 392 | +} | |
| 393 | + | |
| 394 | +/** | |
| 395 | + * Set the pending fullscreen cookie on every applicable administration path. | |
| 396 | + * | |
| 397 | + * User metadata remains authoritative. This short-lived cookie only bridges | |
| 398 | + * immediate navigation and replaces stale same-name cookies on narrower paths. | |
| 399 | + * | |
| 400 | + * @param string $mode Fullscreen mode, either `On` or `Off`. | |
| 401 | + * | |
| 402 | + * @return bool True when the mode was valid; otherwise false. | |
| 403 | + */ | |
| 404 | +function wpbc_ui__set_full_screen_mode_cookie( $mode ) { | |
| 405 | + | |
| 406 | + $cookie_value = wpbc_ui__create_full_screen_mode_cookie_value( $mode ); | |
| 407 | + if ( '' === $cookie_value ) { | |
| 408 | + return false; | |
| 409 | + } | |
| 410 | + | |
| 411 | + if ( ! headers_sent() ) { | |
| 412 | + $cookie_domain = defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : ''; | |
| 413 | + | |
| 414 | + foreach ( wpbc_ui__get_full_screen_mode_cookie_paths() as $cookie_path ) { | |
| 415 | + setcookie( | |
| 416 | + 'wpbc_admin_full_screen', | |
| 417 | + $cookie_value, | |
| 418 | + time() + ( 5 * 60 ), | |
| 419 | + $cookie_path, | |
| 420 | + $cookie_domain, | |
| 421 | + is_ssl(), | |
| 422 | + false | |
| 423 | + ); | |
| 424 | + } | |
| 425 | + } | |
| 426 | + | |
| 427 | + $_COOKIE['wpbc_admin_full_screen'] = $cookie_value; | |
| 428 | + | |
| 429 | + return true; | |
| 430 | +} | |
| 431 | + | |
| 432 | +/** | |
| 433 | + * Resolve fullscreen mode from saved user data and an optional pending cookie. | |
| 434 | + * | |
| 435 | + * A fresh timestamped cookie temporarily wins so immediate navigation remains | |
| 436 | + * deterministic if the asynchronous AJAX request is interrupted. A legacy | |
| 437 | + * plain `On` or `Off` cookie is accepted only when no valid user preference | |
| 438 | + * exists, preventing stale or duplicate-path cookies from overriding storage. | |
| 439 | + * | |
| 440 | + * @param string $saved_value Saved user-meta value. | |
| 441 | + * @param string $cookie_value Browser cookie value. | |
| 442 | + * @param int|null $current_timestamp Optional Unix timestamp for deterministic tests. | |
| 443 | + * | |
| 444 | + * @return string `On`, `Off`, or an empty string. | |
| 445 | + */ | |
| 446 | +function wpbc_ui__resolve_full_screen_mode( $saved_value = '', $cookie_value = '', $current_timestamp = null ) { | |
| 447 | + | |
| 321 | 448 | $saved_value = in_array( $saved_value, array( 'On', 'Off' ), true ) ? $saved_value : ''; |
| 449 | + $cookie_value = is_scalar( $cookie_value ) ? (string) $cookie_value : ''; | |
| 322 | 450 | |
| 323 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash | |
| 324 | - if ( ! isset( $_COOKIE['wpbc_admin_full_screen'] ) ) { | |
| 451 | + if ( in_array( $cookie_value, array( 'On', 'Off' ), true ) ) { | |
| 452 | + return '' === $saved_value ? $cookie_value : $saved_value; | |
| 453 | + } | |
| 454 | + | |
| 455 | + if ( ! preg_match( '/^(On|Off)\|([0-9]{10,})$/', $cookie_value, $cookie_parts ) ) { | |
| 325 | 456 | return $saved_value; |
| 326 | 457 | } |
| 327 | 458 | |
| 328 | - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash | |
| 329 | - $cookie_value = sanitize_text_field( $_COOKIE['wpbc_admin_full_screen'] ); | |
| 459 | + $current_timestamp = null === $current_timestamp ? time() : absint( $current_timestamp ); | |
| 460 | + $issued_timestamp = absint( $cookie_parts[2] ); | |
| 461 | + $pending_cookie_lifetime = 5 * 60; | |
| 462 | + $allowed_clock_skew = 60; | |
| 330 | 463 | |
| 331 | - if ( in_array( $cookie_value, array( 'On', 'Off' ), true ) ) { | |
| 332 | - return $cookie_value; | |
| 464 | + if ( | |
| 465 | + $issued_timestamp > ( $current_timestamp + $allowed_clock_skew ) | |
| 466 | + || $issued_timestamp < ( $current_timestamp - $pending_cookie_lifetime ) | |
| 467 | + ) { | |
| 468 | + return $saved_value; | |
| 333 | 469 | } |
| 334 | 470 | |
| 335 | - return $saved_value; | |
| 471 | + return $cookie_parts[1]; | |
| 336 | 472 | } |
| 473 | + | |
| 474 | +/** | |
| 475 | + * Get fullscreen mode from the immediate browser cookie and saved user metadata. | |
| 476 | + * | |
| 477 | + * @param string $saved_value Saved user-meta value. | |
| 478 | + * | |
| 479 | + * @return string `On`, `Off`, or an empty string. | |
| 480 | + */ | |
| 481 | +function wpbc_ui__get_full_screen_mode_from_cookie( $saved_value = '' ) { | |
| 482 | + | |
| 483 | + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Optional same-origin UI preference. | |
| 484 | + $cookie_value = isset( $_COOKIE['wpbc_admin_full_screen'] ) && is_scalar( $_COOKIE['wpbc_admin_full_screen'] ) | |
| 485 | + ? sanitize_text_field( wp_unslash( $_COOKIE['wpbc_admin_full_screen'] ) ) | |
| 486 | + : ''; | |
| 487 | + | |
| 488 | + return wpbc_ui__resolve_full_screen_mode( $saved_value, $cookie_value ); | |
| 489 | +} | |
| 490 | + | |
| 491 | +/** | |
| 492 | + * Synchronize the fullscreen navigation cookie after verified user-meta storage. | |
| 493 | + * | |
| 494 | + * @param int $user_id User whose preference was saved. | |
| 495 | + * @param string $data_name Saved Booking Calendar preference name. | |
| 496 | + * @param array $sanitized_data Sanitized value stored in user metadata. | |
| 497 | + * | |
| 498 | + * @return void | |
| 499 | + */ | |
| 500 | +function wpbc_ui__sync_full_screen_cookie_after_user_data_save( $user_id, $data_name, $sanitized_data ) { | |
| 501 | + | |
| 502 | + if ( | |
| 503 | + 'is_full_screen' !== $data_name | |
| 504 | + || wpbc_get_current_user_id() !== absint( $user_id ) | |
| 505 | + || ! isset( $sanitized_data['value'] ) | |
| 506 | + || ! is_scalar( $sanitized_data['value'] ) | |
| 507 | + ) { | |
| 508 | + return; | |
| 509 | + } | |
| 510 | + | |
| 511 | + wpbc_ui__set_full_screen_mode_cookie( (string) $sanitized_data['value'] ); | |
| 512 | +} | |
| 513 | +add_action( 'wpbc_user_custom_data_saved', 'wpbc_ui__sync_full_screen_cookie_after_user_data_save', 10, 3 ); | |
| 337 | 514 | |
| 338 | 515 | |
| 339 | 516 | /** |
| 340 | 517 | * Set the admin full screen class |