| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * @version 1.0 |
| 4 | 4 | * @package Booking Calendar |
| @@ -135,10 +135,40 @@ | ||
| 135 | 135 | return $time_m_h . ':' . $time_m_m; |
| 136 | 136 | } |
| 137 | 137 | |
| 138 | 138 | |
| 139 | -/** | |
| 140 | - * Get Times from booking Form, if these times fields exist | |
| 139 | +/** | |
| 140 | + * Parse a duration field value into numeric hours and minutes. | |
| 141 | + * | |
| 142 | + * Duration values come from serialized front-end form data. Placeholder or | |
| 143 | + * otherwise malformed values must not reach arithmetic because PHP 8 throws | |
| 144 | + * a TypeError when a non-numeric string is multiplied by an integer. | |
| 145 | + * | |
| 146 | + * @param string $duration_time_value Duration in `HH:MM` format. | |
| 147 | + * | |
| 148 | + * @return array|false Numeric hours and minutes, or false for an invalid duration. | |
| 149 | + */ | |
| 150 | +function wpbc_parse_duration_time_value( $duration_time_value ) { | |
| 151 | + | |
| 152 | + $duration_time_value = trim( (string) $duration_time_value ); | |
| 153 | + | |
| 154 | + if ( ! preg_match( '/^([0-9]+):([0-9]{1,2})$/', $duration_time_value, $duration_time_matches ) ) { | |
| 155 | + return false; | |
| 156 | + } | |
| 157 | + | |
| 158 | + $duration_hours = intval( $duration_time_matches[1] ); | |
| 159 | + $duration_minutes = intval( $duration_time_matches[2] ); | |
| 160 | + | |
| 161 | + if ( 59 < $duration_minutes ) { | |
| 162 | + return false; | |
| 163 | + } | |
| 164 | + | |
| 165 | + return array( $duration_hours, $duration_minutes ); | |
| 166 | +} | |
| 167 | + | |
| 168 | + | |
| 169 | +/** | |
| 170 | + * Get Times from booking Form, if these times fields exist | |
| 141 | 171 | * |
| 142 | 172 | * @param type $booking_form_data |
| 143 | 173 | * @param type $booking_type |
| 144 | 174 | * @return mixed |
| @@ -233,34 +263,36 @@ | ||
| 233 | 263 | } else { |
| 234 | 264 | $end_time = explode( ':', $end_time ); |
| 235 | 265 | } |
| 236 | 266 | |
| 237 | - if ( strpos( $booking_form_data, 'durationtime' . $booking_type ) !== false ) { // Get END TIME From form request | |
| 238 | - $pos1 = strpos( $booking_form_data, 'durationtime' . $booking_type ); // Find start time pos | |
| 239 | - $pos1 = strpos( $booking_form_data, '^', $pos1 ) + 1; // Find TIME pos | |
| 240 | - $pos2 = strpos( $booking_form_data, '~', $pos1 ); // Find TIME length | |
| 267 | + if ( strpos( $booking_form_data, 'durationtime' . $booking_type ) !== false ) { // Get END TIME From form request | |
| 268 | + $pos1 = strpos( $booking_form_data, 'durationtime' . $booking_type ); // Find start time pos | |
| 269 | + $pos1 = strpos( $booking_form_data, '^', $pos1 ) + 1; // Find TIME pos | |
| 270 | + $pos2 = strpos( $booking_form_data, '~', $pos1 ); // Find TIME length | |
| 241 | 271 | if ( $pos2 === false ) { |
| 242 | 272 | $pos2 = strlen( $booking_form_data ); |
| 243 | 273 | } |
| 244 | - $pos2 = $pos2 - $pos1; | |
| 245 | - $end_time = substr( $booking_form_data, $pos1, $pos2 ); | |
| 274 | + $pos2 = $pos2 - $pos1; | |
| 275 | + $duration_time_value = substr( $booking_form_data, $pos1, $pos2 ); | |
| 276 | + $duration_time_parts = wpbc_parse_duration_time_value( $duration_time_value ); | |
| 277 | + | |
| 278 | + if ( false !== $duration_time_parts ) { | |
| 279 | + $is_time_exist = true; | |
| 280 | + | |
| 281 | + // Get the selected start time and add the validated duration to calculate the end time. | |
| 282 | + $new_end_time = mktime( intval( $start_time[0] ), intval( $start_time[1] ) ); | |
| 283 | + $new_end_time += $duration_time_parts[0] * 60 * 60; | |
| 284 | + $new_end_time += $duration_time_parts[1] * 60; | |
| 285 | + $end_time = gmdate( 'H:i', $new_end_time ); | |
| 286 | + | |
| 287 | + if ( '00:00' === $end_time ) { | |
| 288 | + $end_time = '23:59'; | |
| 289 | + } | |
| 290 | + $end_time = explode( ':', $end_time ); | |
| 291 | + $end_time[2] = '02'; | |
| 292 | + } | |
| 293 | + } | |
| 246 | 294 | |
| 247 | - $is_time_exist = true; | |
| 248 | - | |
| 249 | - $end_time = explode( ':', $end_time ); | |
| 250 | - | |
| 251 | - // Here we are get start time and add duration for end time | |
| 252 | - $new_end_time = mktime( intval( $start_time[0] ), intval( $start_time[1] ) ); | |
| 253 | - $new_end_time = $new_end_time + $end_time[0] * 60 * 60 + $end_time[1] * 60; | |
| 254 | - $end_time = gmdate( 'H:i', $new_end_time ); | |
| 255 | - | |
| 256 | - if ( $end_time == '00:00' ) { | |
| 257 | - $end_time = '23:59'; | |
| 258 | - } | |
| 259 | - $end_time = explode( ':', $end_time ); | |
| 260 | - $end_time[2] = '02'; | |
| 261 | - } | |
| 262 | - | |
| 263 | 295 | } |
| 264 | 296 | |
| 265 | 297 | if ( $is_time_exist ) { |
| 266 | 298 | return array( $start_time, $end_time ); |
| @@ -651,9 +683,9 @@ | ||
| 651 | 683 | * |
| 652 | 684 | * @param string $some_day : '2015-05-29' |
| 653 | 685 | * @return boolean : true | false |
| 654 | 686 | */ |
| 655 | -function wpbc_is_date_in_past( $some_day ) { | |
| 687 | +function wpbc_is_date_in_past( $some_day ) { | |
| 656 | 688 | |
| 657 | 689 | $some_day_d = gmdate( 'm.d.Y', mysql2date( 'U', $some_day ) ); |
| 658 | 690 | $some_array = explode( '.', $some_day_d ); |
| 659 | 691 | $some_day = mktime( 0, 0, 0, intval($some_array[0]), ( intval($some_array[1]) + 1 ), intval($some_array[2]) ); |
| @@ -664,9 +696,49 @@ | ||
| 664 | 696 | return true; |
| 665 | 697 | } else { |
| 666 | 698 | return false; |
| 667 | 699 | } |
| 668 | -} | |
| 700 | +} | |
| 701 | + | |
| 702 | + | |
| 703 | +/** | |
| 704 | + * Check whether a booking can still be changed by a visitor. | |
| 705 | + * | |
| 706 | + * Visitor edit and cancellation actions must fail closed when the booking does | |
| 707 | + * not exist, is in the trash, has no dates, or its final booked date has | |
| 708 | + * already passed. The comparison deliberately uses wpbc_is_date_in_past() so | |
| 709 | + * the rule remains compatible with the established date-based front-end edit | |
| 710 | + * restriction and does not unexpectedly expire time-slot bookings mid-day. | |
| 711 | + * | |
| 712 | + * @param int $booking_id Booking ID resolved from a verified visitor hash. | |
| 713 | + * | |
| 714 | + * @return bool True when at least one booked date is current or future. | |
| 715 | + */ | |
| 716 | +function wpbc_is_visitor_booking_action_allowed( $booking_id ) { | |
| 717 | + global $wpdb; | |
| 718 | + | |
| 719 | + $booking_id = absint( $booking_id ); | |
| 720 | + if ( empty( $booking_id ) ) { | |
| 721 | + return false; | |
| 722 | + } | |
| 723 | + | |
| 724 | + $last_booking_date = $wpdb->get_var( | |
| 725 | + $wpdb->prepare( | |
| 726 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 727 | + "SELECT MAX(dt.booking_date) | |
| 728 | + FROM {$wpdb->prefix}bookingdates AS dt | |
| 729 | + INNER JOIN {$wpdb->prefix}booking AS bk ON bk.booking_id = dt.booking_id | |
| 730 | + WHERE bk.booking_id = %d AND bk.trash = 0", | |
| 731 | + $booking_id | |
| 732 | + ) | |
| 733 | + ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 734 | + | |
| 735 | + if ( empty( $last_booking_date ) || ! is_string( $last_booking_date ) ) { | |
| 736 | + return false; | |
| 737 | + } | |
| 738 | + | |
| 739 | + return ! wpbc_is_date_in_past( $last_booking_date ); | |
| 740 | +} | |
| 669 | 741 | |
| 670 | 742 | |
| 671 | 743 | /** |
| 672 | 744 | * Check if the booking was made without calendar ( no booking date submited.) |
| @@ -1009,5 +1081,5 @@ | ||
| 1009 | 1081 | |
| 1010 | 1082 | } |
| 1011 | 1083 | |
| 1012 | 1084 | return $days_availability; |
| 1013 | - } | |
| 1085 | + } | |