PluginProbe
Easy Hotel – Powerful Hotel Booking / 2.0.2
Easy Hotel – Powerful Hotel Booking v2.0.2
2.0.8 2.0.7 2.0.6 2.0.5 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.9.9 1.9.8 1.9.7 1.9.6 1.9.5 1.9.4 1.9.3 1.9.2 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 All 110 releases
easy-hotel / admin / includes / classes / class.booking.php

class.booking.php in Easy Hotel – Powerful Hotel Booking 2.0.2, at admin/includes/classes/class.booking.php

2,940 lines 108.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 use SureCart\Support\Currency;
3 use SureCart\Models\Product;
4 use SureCart\Models\Price;
5 use SureCart\Models\LineItem;
6 use SureCart\Models\ProductMedia;
7 use SureCart\Models\Order;
8 use SureCart\Models\Checkout;
9 use SureCart\Models\Customer;
10 use SureCart\Models\Coupon;
11
12 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
13
14 class ESHB_Booking {
15
16 private static $_instance = null;
17 private $eshb_notice_printed = false;
18
19 public static function instance() {
20
21 if ( is_null( self::$_instance ) ) {
22 self::$_instance = new self();
23 }
24 return self::$_instance;
25
26 }
27
28 public function __construct() {
29
30 add_filter('litespeed_cacheable', [$this, 'disable_eshb_cache_from_cacheplugin'], 10, 1);
31
32 if(!is_admin()){
33 add_action( 'woocommerce_before_calculate_totals', [ $this, 'apply_custom_price_to_cart_item' ], 20, 1 );
34 add_action( 'woocommerce_checkout_create_order_line_item', [ $this, 'save_custom_meta_to_order' ], 10, 4 );
35 add_filter( 'woocommerce_get_item_data', [ $this, 'display_custom_meta_in_cart_and_order' ], 10, 2 );
36
37 add_action( 'woocommerce_thankyou', [$this, 'create_woocommerce_booking_on_checkout'], 10, 1 );
38 add_action( 'woocommerce_thankyou', [$this, 'capture_payment_after_checkout'], 20, 1 );
39 add_filter( 'woocommerce_hidden_order_itemmeta', [$this, 'hide_meta_from_display'] );
40 add_filter( 'woocommerce_order_item_get_formatted_meta_data', [$this, 'unset_specific_order_item_meta_data'], 10, 2 );
41 add_action( 'woocommerce_cart_item_removed', [ $this, 'eshb_clear_cart_block_on_removal' ], 10, 2 );
42 add_action( 'woocommerce_thankyou', [ $this, 'eshb_clear_session_cart_blocks_on_order' ], 5, 1 );
43 }
44
45 add_action( 'woocommerce_order_status_changed', [$this, 'update_booking_status_on_woocommerce_order_status_change'], 10, 4 );
46 add_action( 'save_post_eshb_booking', [$this, 'update_woocommerce_order_status_on_booking_status_change'], 20, 3 );
47 add_action( 'save_post_eshb_booking', [$this, 'send_booking_email_notification_for_booking_status'], 20, 3 );
48 add_filter('woocommerce_payment_complete_order_status', [$this, 'filter_status_based_on_booking_meta'], 10, 2);
49
50 add_action( "wp_ajax_nopriv_eshb_get_extra_services_charge", [$this, 'eshb_get_extra_services_charge'] );
51 add_action( "wp_ajax_eshb_get_extra_services_charge", [$this, 'eshb_get_extra_services_charge'] );
52 add_action( "wp_ajax_nopriv_eshb_get_booking_prices", [$this, 'eshb_get_booking_prices'] );
53 add_action( "wp_ajax_eshb_get_booking_prices", [$this, 'eshb_get_booking_prices'] );
54 add_action( "wp_ajax_nopriv_eshb_add_to_cart_reservation", [$this, 'eshb_add_to_cart_reservation'] );
55 add_action( "wp_ajax_eshb_add_to_cart_reservation", [$this, 'eshb_add_to_cart_reservation'] );
56 add_action( "wp_ajax_nopriv_eshb_send_reservation_request", [$this, 'eshb_send_reservation_request'] );
57 add_action( "wp_ajax_eshb_send_reservation_request", [$this, 'eshb_send_reservation_request'] );
58 add_action( 'wp_ajax_nopriv_eshb_get_accomodation_available_capacity_counts', [ $this, 'eshb_get_accomodation_available_capacity_counts' ] );
59 add_action( 'wp_ajax_eshb_get_accomodation_available_capacity_counts', [ $this, 'eshb_get_accomodation_available_capacity_counts' ] );
60 add_action( 'wp_ajax_nopriv_eshb_get_available_rooms_counts_data', [ $this, 'eshb_get_available_rooms_counts_data' ] );
61 add_action( 'wp_ajax_eshb_get_available_rooms_counts_data', [ $this, 'eshb_get_available_rooms_counts_data' ] );
62 add_action( 'wp_ajax_nopriv_eshb_get_cart_blocks', [ $this, 'eshb_get_cart_blocks_ajax' ] );
63 add_action( 'wp_ajax_eshb_get_cart_blocks', [ $this, 'eshb_get_cart_blocks_ajax' ] );
64 add_action( 'wp_ajax_nopriv_eshb_clear_cart_on_block_expiry', [ $this, 'eshb_clear_cart_on_block_expiry' ] );
65 add_action( 'wp_ajax_eshb_clear_cart_on_block_expiry', [ $this, 'eshb_clear_cart_on_block_expiry' ] );
66 add_action( 'wp_ajax_nopriv_eshb_check_my_session_blocks', [ $this, 'eshb_check_my_session_blocks' ] );
67 add_action( 'wp_ajax_eshb_check_my_session_blocks', [ $this, 'eshb_check_my_session_blocks' ] );
68 add_filter( 'eshb_get_disabled_dates_in_search', [ $this, 'eshb_add_blocked_dates_to_calendar' ], 10, 3 );
69 // Inline notice: shortcode-based cart/checkout (block themes use JS injection instead)
70 add_action( 'woocommerce_before_cart', [ $this, 'eshb_render_cart_block_notice_inline' ] );
71 add_action( 'woocommerce_before_checkout_form', [ $this, 'eshb_render_cart_block_notice_inline' ] );
72 // Cart item remove via AJAX on checkout (NOTE: woocommerce_checkout_cart_item_quantity filter
73 // is registered once at file load, not in the constructor — see bottom of file. Constructor
74 // runs each time `new ESHB_Booking()` is called, which would duplicate the filter.)
75 add_action( 'wp_ajax_nopriv_eshb_remove_cart_item', [ $this, 'eshb_remove_cart_item' ] );
76 add_action( 'wp_ajax_eshb_remove_cart_item', [ $this, 'eshb_remove_cart_item' ] );
77 add_action( 'eshb_before_availability_calendar', [ $this, 'eshb_render_calendar_legend' ], 10, 1 );
78
79 add_action('rest_api_init', function() {
80 register_rest_route('eshb/v1', '/booking-prices', [
81 'methods' => 'POST',
82 'callback' => [ $this, 'eshb_get_booking_prices_rest' ], // class method
83 'permission_callback' => function($request) {
84 return true;
85 },
86 'args' => [
87 'accomodationId' => [
88 'required' => true,
89 'sanitize_callback' => 'absint',
90 ],
91 'startDate' => [
92 'sanitize_callback' => 'sanitize_text_field',
93 ],
94 'endDate' => [
95 'sanitize_callback' => 'sanitize_text_field',
96 ],
97 'startTime' => [
98 'sanitize_callback' => 'sanitize_text_field',
99 ],
100 'endTime' => [
101 'sanitize_callback' => 'sanitize_text_field',
102 ],
103 'roomQuantity' => [
104 'sanitize_callback' => 'absint',
105 ],
106 'extraBedQuantity' => [
107 'sanitize_callback' => 'absint',
108 ],
109 'adultQuantity' => [
110 'sanitize_callback' => 'absint',
111 ],
112 'childrenQuantity' => [
113 'sanitize_callback' => 'absint',
114 ],
115 'selectedServices' => [
116 'sanitize_callback' => function($param) {
117 return json_decode(sanitize_text_field($param), true);
118 }
119 ],
120 'nonce' => [
121 'required' => true,
122 'sanitize_callback' => 'sanitize_text_field',
123 ],
124 ],
125 ]);
126 });
127
128
129 }
130
131 function disable_eshb_cache_from_cacheplugin($cacheable){
132 // Disable LSCache for accommodation single pages
133 if( is_singular('eshb_accomodation') ){
134 return false; // don't cache
135 }
136
137 // Never cache the Native Checkout / customer account pages: they are
138 // per-visitor (reservation token + a freshly generated nonce baked
139 // into the page). A shared cache would serve a stale nonce to other
140 // visitors — which makes checkout fail for logged-out users (logged-in
141 // users bypass the cache, so it "only works when logged in").
142 if ( is_singular() ) {
143 global $post;
144 if ( $post instanceof WP_Post ) {
145 $eshb_content = (string) $post->post_content;
146 if ( has_shortcode( $eshb_content, 'eshb_native_checkout' ) || has_shortcode( $eshb_content, 'eshb_account' ) ) {
147 return false; // don't cache
148 }
149 }
150 }
151
152 return $cacheable;
153 }
154
155 public function update_product_price_by_id($product_id, $new_price) {
156 // Update the regular price
157 update_post_meta($product_id, '_regular_price', $new_price);
158
159 // Update the price (current price)
160 update_post_meta($product_id, '_price', $new_price);
161
162 // Optional: if the product is on sale, update the sale price to match the new price
163 //update_post_meta($product_id, '_sale_price', $new_price);
164
165 // Clear WooCommerce product cache
166 wc_delete_product_transients($product_id);
167 }
168
169 public function get_booked_dates($accomodation_id, $start_date = '', $end_date = '') {
170
171 $single_day_booking = false;
172 if($start_date == $end_date){
173 $single_day_booking = true;
174 }
175
176 $booked_dates = [];
177 $total_room_bookings_per_date = []; // Track room bookings per date
178 $eshb_settings = get_option('eshb_settings', []);
179
180 // Get total rooms allowed for this accommodation
181 $accomodation_metas = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
182 $allowed_total_rooms = isset($accomodation_metas['total_rooms']) ? intval($accomodation_metas['total_rooms']) : 0;
183
184 // Find all bookings for this accommodation
185 $bookings_args = [
186 'post_type' => 'eshb_booking',
187 'posts_per_page' => -1,
188 'post_status' => ['publish', 'deposit-payment', 'pending', 'processing', 'on-hold', 'completed'],
189 'fields' => 'ids',
190 ];
191
192 $bookings = new WP_Query($bookings_args);
193
194 if ($bookings->have_posts()) {
195 while ($bookings->have_posts()) {
196 $bookings->the_post();
197 $meta_values = get_post_meta(get_the_ID(), 'eshb_booking_metaboxes', true);
198 $booking_status = isset($meta_values['booking_status']) ? $meta_values['booking_status'] : '';
199
200 $completed_booking_status_maps = array(
201 'pending',
202 'deposit-payment',
203 'processing',
204 'on-hold',
205 'completed'
206 );
207
208 // Ensure required keys exist
209 if (is_array($meta_values) && in_array($booking_status, $completed_booking_status_maps) && isset($meta_values['booking_accomodation_id'], $meta_values['booking_start_date'], $meta_values['booking_end_date'])) {
210 $booking_accomodation_id = intval($meta_values['booking_accomodation_id']);
211 $booking_start_date = $meta_values['booking_start_date'];
212 $booking_end_date = $meta_values['booking_end_date'];
213
214 $booked_accomodation_ids = [];
215 $booking_accomodation_id = ESHB_Helper::get_main_post_id_for_translated($booking_accomodation_id);
216
217 if ($booking_accomodation_id == $accomodation_id) {
218 // Convert dates to DateTime objects
219 $booking_start_date = new DateTime($booking_start_date);
220 $booking_end_date = new DateTime($booking_end_date);
221
222 // if($booking_end_date == $booking_end_date) {
223 // $booking_end_date->modify('+1 day');
224 // }
225
226 while ($booking_start_date <= $booking_end_date) {
227
228 $formatted_date = $booking_start_date->format('Y-m-d');
229
230 if($meta_values['booking_end_date'] != $formatted_date){
231 $booked_id = get_the_ID();
232 $booking_metas = get_post_meta($booked_id, 'eshb_booking_metaboxes', true);
233 $booked_room_quantity = isset($booking_metas['room_quantity']) ? intval($booking_metas['room_quantity']) : 0;
234
235
236 // Track total booked rooms per date
237 if (!isset($total_room_bookings_per_date[$formatted_date])) {
238 $total_room_bookings_per_date[$formatted_date] = 0;
239 }
240 $total_room_bookings_per_date[$formatted_date] = $booked_room_quantity;
241 }
242 // Move to next day
243 $booking_start_date->modify('+1 day');
244 }
245
246 }
247 }
248 }
249 }
250
251 if(!$single_day_booking){
252 unset($total_room_bookings_per_date[$end_date]);
253 }
254
255 // Determine which dates should be disabled (fully booked)
256 foreach ($total_room_bookings_per_date as $date => $total_booked_rooms) {
257
258 // skip this booking if slots hours available
259 // allow external plugins to modify available slots
260 $available_slots = apply_filters(
261 'eshb_available_slots_in_booked_dates_loop',
262 false,
263 $date,
264 $total_booked_rooms,
265 $accomodation_id
266 );
267
268 if($available_slots) {
269 $total_booked_rooms = 0;
270 }
271
272
273 if ($allowed_total_rooms <= $total_booked_rooms) {
274 $date_obj = new DateTime($date);
275 $booked_dates[] = $date_obj->format('Y, m, d');
276 }
277 }
278
279
280 // Remove duplicate dates
281 $booked_dates = array_unique($booked_dates);
282
283 // Apply filter hook to allow external modifications
284 $booked_dates = apply_filters('eshb_modify_booked_dates', $booked_dates, $accomodation_id);
285
286
287 return $booked_dates;
288 }
289
290 public function get_holiday_dates($accomodation_id, $start_date = '', $end_date = '') {
291
292 $eshb_settings = get_option('eshb_settings', []);
293
294 $holidays = isset($eshb_settings['holidays']) ? $eshb_settings['holidays'] : [];
295
296 $finally_holidays = [];
297
298 if ( ! empty( $holidays ) ) {
299 foreach ( $holidays as $holiday ) {
300 $accommodation_ids = $holiday['accomodation-ids'] ?? [];
301 $holiday_date = $holiday['holiday-date'] ?? null;
302
303
304 // Check if holiday is applicable for all or specific accommodation
305 $is_applicable = empty($accommodation_ids) || (is_array($accommodation_ids) && in_array($accomodation_id, $accommodation_ids));
306
307 if ( $is_applicable && $holiday_date ) {
308 if ( is_array($holiday_date) && isset($holiday_date['from'], $holiday_date['to']) ) {
309 $start = DateTime::createFromFormat('Y-m-d', $holiday_date['from']);
310 $end = DateTime::createFromFormat('Y-m-d', $holiday_date['to']);
311 if ( $start && $end ) {
312 $end->modify('+1 day'); // Include end date
313 $interval = new DateInterval('P1D');
314 $dateRange = new DatePeriod($start, $interval, $end);
315
316 foreach ( $dateRange as $date ) {
317 $finally_holidays[] = $date->format('Y-m-d');
318 }
319 }
320 } else {
321 $finally_holidays[] = $holiday_date;
322 }
323 }
324 }
325 }
326
327 return $finally_holidays;
328 }
329
330 public function get_holiday_dates_in_date_ranges($accomodation_id, $start_date, $end_date){
331
332 $overlap = [];
333 if(!empty($accomodation_id)){
334 $holiday_dates = $this->get_holiday_dates($accomodation_id, $start_date, $end_date);
335 $overlap = [];
336 // Generate all dates in the range
337 $range_dates = [];
338 $current_date = new DateTime($start_date);
339 $end_date_obj = new DateTime($end_date);
340
341 while ($current_date <= $end_date_obj) {
342 $range_dates[] = $current_date->format('Y-m-d');
343 $current_date->modify('+1 day');
344 }
345
346 // Check for overlap
347 $overlap = array_intersect($range_dates, $holiday_dates);
348 }
349 return $overlap;
350 }
351
352 public function get_actual_booked_dates_in_date_ranges($accomodation_id, $start_date, $end_date){
353
354 $booked_dates = $this->get_booked_dates($accomodation_id, $start_date, $end_date); // retun arary like ['2025, 01, 27', '2025, 01, 28', '2025, 01, 13']
355
356 // Format booked dates to 'Y-m-d' for comparison
357 $booked_dates = array_map(function($date) {
358 return DateTime::createFromFormat('Y, m, d', $date)->format('Y-m-d');
359 }, $booked_dates);
360
361 // Generate all dates in the range
362 $range_dates = [];
363 $current_date = new DateTime($start_date);
364 $end_date_obj = new DateTime($end_date);
365
366 while ($current_date <= $end_date_obj) {
367 $range_dates[] = $current_date->format('Y-m-d');
368 $current_date->modify('+1 day');
369 }
370
371 $single_day_booking = false;
372 if($start_date == $end_date){
373 $single_day_booking = true;
374 }
375
376 if(!$single_day_booking){
377 array_pop($range_dates); // Remove the last date if it's a range booking
378 }
379
380 // Check for overlap
381 $overlap = array_intersect($range_dates, $booked_dates);
382
383 return $overlap;
384
385 }
386
387 public function save_booking_request($booking_args = array(), $customer_args = array()) {
388 $defaults = array(
389 'booking_accomodation_id' => '',
390 'booking_start_date' => '',
391 'booking_end_date' => '',
392 'room_quantity' => 1,
393 'extra_bed_quantity'=> 0,
394 'adult_quantity' => 1,
395 'children_quantity' => 0,
396 'details_html' => '',
397 'extra_services' => array(),
398 'extra_services_html' => '',
399 'booking_status' => 'pending'
400 );
401
402 // Merge with default values
403 $booking_args = wp_parse_args($booking_args, $defaults);
404
405 $accomodation_id = $booking_args['booking_accomodation_id'];
406
407 if(!empty($accomodation_id)){
408
409 $start_date = $booking_args['booking_start_date'];
410 $end_date = $booking_args['booking_end_date'];
411
412 // Insert the post
413 $post_id = wp_insert_post(array(
414 'post_type' => 'eshb_booking_request',
415 'post_title' => 'Booking request for ' . $start_date . ' - ' . $end_date,
416 'post_status' => 'publish',
417 'meta_input' => array(
418 'eshb_booking_request_metaboxes' => $booking_args,
419 'eshb_booking_request_customer_metaboxes' => $customer_args,
420 )
421 ));
422
423 // Check if the post was inserted successfully
424 if (!is_wp_error($post_id)) {
425 // Update the post title to include the post ID
426
427 $accomodation_title = get_the_title($accomodation_id);
428
429 wp_update_post(array(
430 'ID' => $post_id,
431 'post_title' => 'Booking request #' . $post_id . ' for: ' . $accomodation_title,
432 'post_status' => 'publish' // Update status to 'publish'
433 ));
434 }
435
436 }
437
438 }
439
440 public function eshb_send_reservation_request(){
441
442 // Verify nonce for security
443 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
444
445 $error = array(
446 'code' => 'invalid_nonce',
447 'message' => __('invalid_nonce', 'easy-hotel')
448 );
449
450 wp_send_json_error([
451 'error' => $error,
452 ]);
453
454 die();
455 }
456
457
458 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId'])){
459
460 $hotel_core = new ESHB_Core();
461 $customer = !empty($_POST['customerInfo']) ? map_deep( sanitize_text_field( wp_unslash($_POST['customerInfo'])), 'sanitize_text_field' ) : [];
462 $accomodation_id = isset( $_POST['accomodationId'] ) ? (int) sanitize_text_field( wp_unslash($_POST['accomodationId']) ) : '';
463 $today_date = esc_html(gmdate('Y-m-d')); // Get today's date
464 $date = new DateTime($today_date); // Create a DateTime object from today's date
465 $date->modify('+1 day'); // Add one day
466 $tomorrow = $date->format('Y-m-d'); // Get the new date in 'Y-m-d' format
467 $start_date = isset( $_POST['startDate'] ) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : $today_date;
468 $end_date = isset( $_POST['endDate'] ) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : $tomorrow;
469 $start_time = isset( $_POST['startTime'] ) ? sanitize_text_field( wp_unslash($_POST['startTime']) ) : '';
470 $end_time = isset( $_POST['endTime'] ) ? sanitize_text_field( wp_unslash($_POST['endTime']) ) : '';
471 $start_day_name = $start_date ? strtolower(gmdate('l', strtotime($start_date))) : '';
472 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
473 $eshb_settings = get_option( 'eshb_settings', [] );
474 $string_booking_success_msg = isset($eshb_settings['string_booking_request_success_msg']) && !empty($eshb_settings['string_booking_request_success_msg']) ? $eshb_settings['string_booking_request_success_msg'] : 'Booking request has been sent successfully.';
475 $string_booking_failed_msg = isset($eshb_settings['string_booking_request_failed_msg']) && !empty($eshb_settings['string_booking_request_failed_msg']) ? $eshb_settings['string_booking_request_failed_msg'] : 'Ops! Booking request can\'t be sent.';
476 $string_already_booked_msg = isset($eshb_settings['string_already_booked_msg']) && !empty($eshb_settings['string_already_booked_msg']) ? $eshb_settings['string_already_booked_msg'] : 'Ops! The date range is already booked:';
477 $room_visibility = in_array('rooms', $eshb_settings['booking-form-fields']) ? true : false;
478 $adult_visibility = in_array('adults', $eshb_settings['booking-form-fields']) ? true : false;
479 $childrens_visibility = in_array('childrens', $eshb_settings['booking-form-fields']) ? true : false;
480 $extra_beds_visibility = in_array('extra_beds', $eshb_settings['booking-form-fields']) ? true : false;
481
482 $eshb_week_settings = [
483 'string_check_in_day_error_msg' => 'Only allowed check in day is',
484 'eshb_booking_allowed_check_in_day' => 'all',
485 ];
486 $eshb_week_settings = apply_filters( 'eshb_week_settings', $eshb_week_settings );
487 $allowed_check_in_day = apply_filters( 'eshb_booking_allowed_check_in_day', 'all', $accomodation_id, $accomodation_metaboxes );
488 $string_check_in_day_error_msg = !empty($eshb_week_settings['string_check_in_day_error_msg']) ? $eshb_week_settings['string_check_in_day_error_msg'] : 'Only allowed check in day is : ';
489
490
491 // Min Max Booking Configurations
492 $min_max_settings = [
493 'required_min_nights' => '',
494 'required_max_nights' => '',
495 'is_global_source_for_min_max' => true,
496 ];
497 $min_max_settings = apply_filters( 'eshb_min_max_settings', $min_max_settings, $accomodation_id, $accomodation_metaboxes );
498 $required_min_nights = isset($min_max_settings['required_min_nights']) ? $min_max_settings['required_min_nights'] : '';
499 $required_max_nights = isset($min_max_settings['required_max_nights']) ? $min_max_settings['required_max_nights'] : '';
500 $string_required_minimum_nights_msg = esc_html__( 'Ops! This Reservation has been failed. Requried Minumum', 'easy-hotel' );
501 $string_required_maximum_nights_msg = esc_html__( 'Ops! This Reservation has been failed. Requried Maximum', 'easy-hotel' );
502
503 $min_stay_night_by_session = ESHB_Helper::get_eshb_min_stay_night_by_session($accomodation_id, $start_date, $end_date);
504
505 // get booked dates in the range
506 $actual_booked_dates = $this->get_actual_booked_dates_in_date_ranges($accomodation_id, $start_date, $end_date);
507
508 if(!empty($actual_booked_dates)){
509
510 $booked_dates_str = implode( ', ', array_map( 'esc_html', $actual_booked_dates ) );
511
512 $error_message = sprintf(
513 /* translators: 1: already booked message text, 2: list of booked dates */
514 esc_html__( '%1$s %2$s', 'easy-hotel' ),
515 esc_html( $string_already_booked_msg ),
516 $booked_dates_str
517 );
518
519 $error = array(
520 'code' => 'already_booked',
521 'message' => $error_message,
522 );
523
524 wp_send_json_error([
525 'error' => $error,
526 'booked_dates' => implode(', ', $actual_booked_dates),
527 ]);
528
529 wp_die();
530
531 }
532
533 // check rooms capacity
534 if(isset($_POST['roomQuantity']) && !empty($_POST['roomQuantity'])){
535 $roomQuantity = isset( $_POST['roomQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['roomQuantity'] ) ) : 1;
536
537 $availableRoom = $this->get_available_room_count_by_date_range($accomodation_id, $start_date, $end_date);
538
539 if($roomQuantity > $availableRoom){
540 $error = array(
541 'code' => 'room_capacity_not_enough',
542 'message' => sprintf(
543 /* translators: %s: number of available rooms */
544 esc_html__( 'Selected room is not available. Available room: %s', 'easy-hotel' ),
545 esc_html( $availableRoom )
546 ),
547 );
548
549 wp_send_json_error([
550 'error' => $error,
551 ]);
552 }
553
554 }
555
556
557 // Validate check-in day
558 if($allowed_check_in_day != 'all' && $start_day_name != '' && !empty($allowed_check_in_day) && strpos($allowed_check_in_day, $start_day_name) === false){
559 $error = array(
560 'code' => 'check_in_day_error',
561 'message' => sprintf(
562 /* translators: %s: allowed check-in day */
563 esc_html__( '%1$s %1$s', 'easy-hotel' ),
564 esc_html( $string_check_in_day_error_msg ),
565 esc_html( $allowed_check_in_day )
566 ),
567 );
568
569 wp_send_json_error([
570 'error' => $error,
571 ]);
572 }
573
574 $start = new DateTime($start_date);
575 $end = new DateTime($end_date);
576 $interval = $start->diff($end);
577 $days_count = $interval->days;
578 $room_quantity = isset( $_POST['roomQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['roomQuantity'] ) ) : 1;
579 $extra_bed_quantity = isset( $_POST['extraBedQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['extraBedQuantity'] ) ) : 0;
580 $adult_quantity = isset( $_POST['adultQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['adultQuantity'] ) ) : 1;
581 $children_quantity = isset( $_POST['childrenQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['childrenQuantity'] ) ) : 0;
582 $total_guest_quantity = $adult_quantity + $children_quantity;
583
584 $selected_services = [];
585 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
586
587 $selected_services = sanitize_text_field(wp_unslash($_POST['selectedServices']));
588 $selected_services = json_decode($selected_services, true);
589
590 }
591
592
593 if(!empty($allowed_check_in_day) && $allowed_check_in_day != 'all'){
594 $days_count = 1;
595 }
596
597 // validate min stay night by session
598 if(class_exists('ESHB_ADVANCED_PRICING') && !empty($min_stay_night_by_session) && $days_count < $min_stay_night_by_session){
599 $error = array(
600 'code' => 'min_stay_night_by_session',
601 'message' => sprintf(
602 /* translators: 1: minimum nights message text, 2: required minimum nights */
603 esc_html__( '%1$s %2$s nights!', 'easy-hotel' ),
604 esc_html( $string_required_minimum_nights_msg ),
605 esc_html( $min_stay_night_by_session )
606 ),
607 );
608 wp_send_json_error([
609 'error' => $error,
610 ]);
611 }
612
613 // validate minimum nights
614 if(!empty($required_min_nights) && $days_count < $required_min_nights){
615 $error = array(
616 'code' => 'required_min_nights',
617 'message' => sprintf(
618 /* translators: 1: minimum nights message text, 2: required minimum nights */
619 esc_html__( '%1$s %2$s nights!', 'easy-hotel' ),
620 esc_html( $string_required_minimum_nights_msg ),
621 esc_html( $required_min_nights )
622 ),
623 );
624
625 wp_send_json_error([
626 'error' => $error,
627 ]);
628 }
629 if(!empty($required_max_nights) && $days_count > $required_max_nights){
630 $error = array(
631 'code' => 'required_max_nights',
632 'message' => sprintf(
633 /* translators: 1: maximum nights message text, 2: required maximum nights */
634 esc_html__( '%1$s %2$s nights allowed!', 'easy-hotel' ),
635 esc_html( $string_required_maximum_nights_msg ),
636 esc_html( $required_max_nights )
637 ),
638 );
639
640 wp_send_json_error([
641 'error' => $error,
642 ]);
643 }
644
645
646
647
648 if (get_post_type($accomodation_id) === 'eshb_accomodation') {
649
650 $details = [];
651
652 if ($room_visibility && $room_quantity > 0) {
653 $details[] = 'Room: ' . $room_quantity;
654 }
655 if ($extra_beds_visibility && $extra_bed_quantity > 0) {
656 $details[] = 'Extra Bed: ' . $extra_bed_quantity;
657 }
658 if ($adult_visibility && $adult_quantity > 0) {
659 $details[] = 'Adult: ' . $adult_quantity;
660 }
661 if ($childrens_visibility && $children_quantity > 0) {
662 $details[] = 'Children: ' . $children_quantity;
663 }
664
665 $details_html = implode(', ', $details);
666
667 $extra_service_titles = [];
668 $extra_services_html = '';
669
670 if(!empty($selected_services) && is_array($selected_services) && count($selected_services) > 0 ){
671
672 foreach ($selected_services as $service) {
673
674 $service_id = $service['id'];
675
676 $service_quantity = $service['quantity'];
677
678 if(!empty($service_quantity)){
679 $eshb_service_metaboxes = get_post_meta($service_id, 'eshb_service_metaboxes', true);
680
681 $charge_type = $eshb_service_metaboxes['service_charge_type'];
682
683 $service_title = get_the_title($service_id) . ' For ' . $service_quantity . ' ' . ucfirst(strtolower($charge_type));
684
685 if(!$room_visibility && $charge_type == 'room'){
686 $service_title = get_the_title($service_id);
687 }
688 }else{
689 $service_title = get_the_title($service_id);
690 }
691
692 array_push($extra_service_titles, $service_title);
693
694 }
695 $extra_services_html = implode(", ",$extra_service_titles);
696 }
697
698
699 $pricing = $this->calculate_booking_pricing($accomodation_id, $start_date, $end_date, $room_quantity, $extra_bed_quantity, $adult_quantity, $children_quantity, $selected_services, true, $start_time, $end_time);
700
701 $base_price = $pricing['basePrice'];
702 $extra_services_charge = $pricing['extraServicesPrice'];
703 $extra_bed_price= $pricing['extraBedPrice'];
704 $subtotal_price = $pricing['subtotalPrice'];
705 $total_price = $pricing['totalPrice'];
706
707 $dates = date_i18n( get_option('date_format'), strtotime( $start_date ) ) .' - '. date_i18n( get_option('date_format'), strtotime( $end_date ) );
708 if($start_date == $end_date){
709 $dates = date_i18n( get_option('date_format'), strtotime( $start_date ) );
710 }
711
712
713 $times = '';
714 if(!empty($start_time) && !empty($end_time)) {
715 // Get WP formatted time (according to Settings > General > Time Format)
716 $formated_start_timestamp = strtotime( $start_time );
717 $formated_end_timestamp = strtotime( $end_time );
718 $wp_time_format = get_option( 'time_format' ); // WP settings
719 $formatted_start_time = wp_date( $wp_time_format, $formated_start_timestamp );
720 $formatted_end_time = wp_date( $wp_time_format, $formated_end_timestamp );
721 $times = $formatted_start_time . ' - ' . $formatted_end_time;
722 }
723
724
725 $accomodation_title = get_the_title($accomodation_id);
726
727 $customer_name = $customer['name'];
728 $customer_email = $customer['email'];
729 $customer_phone = $customer['phone'];
730 $customer_message = $customer['message'];
731
732 $admin_email = get_option('admin_email');
733 $recipent_email = $eshb_settings['recipent_email'];
734 $recipent_email = empty($recipent_email) ? $admin_email : $recipent_email;
735 $from_name = wp_parse_url(get_site_url(), PHP_URL_HOST);
736
737 $subject = __('Booking Request!', 'easy-hotel');
738
739 $message = '';
740 $message .= '<div>';
741 $message .= '<h2>Great news!</h2><p>You have a new booking request.</p>';
742 $message .= '<h3>Customer Details</h3>';
743 $message .= '<table style="text-align:left;">';
744 $message .= '<tr><th>Name: ' . $customer_name . '</th></tr>';
745 $message .= '<tr><th>Email: ' . $customer_email . '</th></tr>';
746 $message .= '<tr><th>Phone: ' . $customer_phone . '</th></tr>';
747 $message .= '</table>';
748 $message .= '<h3>Booking Details</h3>';
749 $message .= '<table style="text-align:left;">';
750 $message .= '<tr><th>Accomodation: ' . $accomodation_title . '</th></tr>';
751 $message .= '<tr><th>Date: ' . date_i18n( get_option('date_format'), strtotime( $start_date ) ) .' - '. date_i18n( get_option('date_format'), strtotime( $end_date ) ) . '</th></tr>';
752
753 if(!empty($times)) {
754 $message .= '<tr><th>Times: ' . $times . '</th></tr>';
755 }
756
757 $message .= '<tr><th>Details: ' . $details_html . '</th></tr>';
758
759 if(!empty($extra_services_html)){
760 $message .= '<tr><th>Exra Services: ' . $extra_services_html . '</th></tr>';
761 }
762
763 $message .= '</table>';
764 $message .= '<h5>Message:</h5><p>'.$customer_message.'</p>';
765 $message .= '</div>';
766
767 // Prepare the data array to store in post meta
768 $form_data = [
769 'booking_status' => 'processing',
770 'booking_accomodation_id' => $accomodation_id,
771 'total_price' => $total_price,
772 'subtotal_price' => $subtotal_price,
773 'base_price' => $base_price,
774 'extra_service_price' => $extra_services_charge,
775 'extra_bed_price' => $extra_bed_price,
776 'booking_start_date' => $start_date,
777 'booking_end_date' => $end_date,
778 'booking_start_time' => $start_time,
779 'booking_end_time' => $end_time,
780 'dates' => $dates,
781 'room_quantity' => $room_quantity,
782 'extra_bed_quantity' => $extra_bed_quantity,
783 'adult_quantity' => $adult_quantity,
784 'children_quantity' => $children_quantity,
785 'extra_services' => $selected_services,
786 'details_html' => $details_html,
787 'extra_services_html' => $extra_services_html,
788 'customer' => $customer
789 ];
790
791 if(!empty($start_time)) {
792 $form_data['start_time'] = $start_time;
793 }
794
795 if(!empty($end_time)) {
796 $form_data['end_time'] = $end_time;
797 }
798
799 if(!empty($times)) {
800 $form_data['times'] = $times;
801 }
802
803 // Insert the post
804 $post_id = wp_insert_post(array(
805 'post_type' => 'eshb_booking',
806 'post_title' => 'Booking for ' . $start_date . ' - ' . $end_date,
807 'post_status' => 'publish',
808 'meta_input' => array(
809 'eshb_booking_metaboxes' => $form_data
810 )
811 ));
812
813 // Check if the post was inserted successfully
814
815 if (!is_wp_error($post_id)) {
816
817 // Update the post title to include the post ID
818 wp_update_post(array(
819 'ID' => $post_id,
820 'post_title' => 'Booking #' . $post_id . ' for: ' . $accomodation_title,
821 'post_status' => 'publish' // Update status to 'publish'
822 ));
823
824 // Update Available Rooms Count For This Accomodation
825 $accomodation_metaboxes = get_post_meta( $accomodation_id, 'eshb_accomodation_metaboxes', true );
826 $total_rooms = !empty($accomodation_metaboxes['total_rooms']) ? floatval($accomodation_metaboxes['total_rooms']) : 1;
827 $current_available_rooms = !empty($accomodation_metaboxes['available_rooms']) ? floatval($accomodation_metaboxes['available_rooms']) : 0;
828
829 if(!empty($current_available_rooms)){
830 $available_rooms = $current_available_rooms - floatval($room_quantity);
831 }else{
832 $available_rooms = $total_rooms - floatval($room_quantity);
833 }
834
835 $accomodation_metaboxes['available_rooms'] = $available_rooms;
836 update_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', $accomodation_metaboxes);
837
838 }
839
840 $save_booking = $this->save_booking_request($form_data, $customer);
841 $send_email = $hotel_core->eshb_send_html_email($recipent_email, $subject, $message, $from_name, $customer_email);
842
843 if ( $send_email ) {
844
845 // Return success response
846 wp_send_json_success([
847 'message' => esc_html( $string_booking_success_msg ),
848 'admin_email' => esc_html( $admin_email ),
849 ]);
850
851 } else {
852 $error = array(
853 'code' => 'email_failed',
854 'message' => esc_html( $string_booking_failed_msg ),
855 );
856
857 wp_send_json_error([
858 'error' => $error,
859 ]);
860 }
861
862 }else{
863 $error = array(
864 'code' => 'accomodation_post_type_not_found',
865 'message' => esc_html( $string_booking_failed_msg ),
866 );
867
868 wp_send_json_error([
869 'error' => $error,
870 ]);
871 }
872 wp_die();
873 }
874
875 }
876
877 public function apply_custom_price_to_cart_item($cart) {
878
879 // Early return for admin requests (except AJAX)
880 if (is_admin() && !defined('DOING_AJAX')) {
881 return;
882 }
883
884 // Use the passed cart object if valid, otherwise fallback
885 if (!$cart || !is_object($cart) || !method_exists($cart, 'get_cart')) {
886 $cart = WC()->cart;
887 if (!$cart || !is_object($cart) || !method_exists($cart, 'get_cart')) {
888 return;
889 }
890 }
891
892 $redirect_added = false;
893
894 foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
895 if (empty($cart_item['total_price']) || $cart_item['total_price'] <= 0) {
896 continue;
897 }
898 if (isset($cart_item['data']) && is_object($cart_item['data'])) {
899 $cart_item['data']->set_price($cart_item['total_price']);
900 }
901 }
902 }
903
904 private function add_currency_redirect() {
905 static $redirect_added = false;
906 if ($redirect_added) {
907 return;
908 }
909 add_action('wp_footer', function() {
910 echo '<script>if(typeof window.eshb_currency_redirect==="undefined"){window.eshb_currency_redirect=true;window.location.href="' . esc_url(get_permalink(get_the_ID())) . '";}</script>';
911 });
912 $redirect_added = true;
913 }
914
915 public function save_custom_meta_to_order($item, $cart_item_key, $values, $order) {
916
917 if (isset($values['accomodation_id'])) {
918 $item->add_meta_data('Accomodation ID', $values['accomodation_id'], true);
919 }
920
921 if (isset($values['total_price'])) {
922 $item->add_meta_data('Total Price', $values['total_price'], true);
923 }
924
925 if (isset($values['subtotal_price'])) {
926 $item->add_meta_data('Subtotal Price', $values['subtotal_price'], true);
927 }
928
929 if (isset($values['base_price'])) {
930 $item->add_meta_data('Base Price', $values['base_price'], true);
931 }
932
933 if (isset($values['extra_services_charge'])) {
934 $item->add_meta_data('Extra Services Charge', $values['extra_services_charge'], true);
935 }
936
937 if (isset($values['extra_bed_price'])) {
938 $item->add_meta_data('Extra Bed Price', $values['extra_bed_price'], true);
939 }
940
941 if(class_exists('ESHB_CURRENCY_WOO_FUNCTIONS')){
942 $currency_functions = new ESHB_CURRENCY_WOO_FUNCTIONS();
943
944 if (isset($values['total_price'])) {
945 $item->add_meta_data('Total Price', $currency_functions->apply_custom_currency_rate($values['total_price']), true);
946 }
947
948 if (isset($values['subtotal_price'])) {
949 $item->add_meta_data('Subtotal Price', $currency_functions->apply_custom_currency_rate($values['subtotal_price']), true);
950 }
951
952 if (isset($values['base_price'])) {
953 $item->add_meta_data('Base Price', $currency_functions->apply_custom_currency_rate($values['base_price']), true);
954 }
955
956 if (isset($values['extra_services_charge'])) {
957 $item->add_meta_data('Extra Services Charge', $currency_functions->apply_custom_currency_rate($values['extra_services_charge']), true);
958 }
959
960 if (isset($values['extra_bed_price'])) {
961 $item->add_meta_data('Extra Bed Price', $currency_functions->apply_custom_currency_rate($values['extra_bed_price']), true);
962 }
963 }
964
965 if (isset($values['room_quantity'])) {
966 $item->add_meta_data('Rooms', $values['room_quantity'], true);
967 }
968
969 if (isset($values['extra_bed_quantity'])) {
970 $item->add_meta_data('Extra Bed', $values['extra_bed_quantity'], true);
971 }
972
973 if (isset($values['adult_quantity'])) {
974 $item->add_meta_data('Adults', $values['adult_quantity'], true);
975 }
976
977 if (isset($values['children_quantity'])) {
978 $item->add_meta_data('Children', $values['children_quantity'], true);
979 }
980
981 if (isset($values['start_date'])) {
982 $item->add_meta_data('Start Date', $values['start_date'], true);
983 }
984
985 if (isset($values['end_date'])) {
986 $item->add_meta_data('End Date', $values['end_date'], true);
987 }
988
989 if (isset($values['dates'])) {
990 $item->add_meta_data('Date', $values['dates'], true);
991 }
992
993 if (isset($values['start_time'])) {
994 $item->add_meta_data('Start Time', $values['start_time'], true);
995 }
996
997 if (isset($values['end_time'])) {
998 $item->add_meta_data('End Time', $values['end_time'], true);
999 }
1000
1001 if (!empty($values['times'])) {
1002 $item->add_meta_data('Time', $values['times'], true);
1003 }
1004
1005 if (isset($values['details_html'])) {
1006 $item->add_meta_data('Details', $values['details_html'], true);
1007 }
1008
1009 if (isset($values['extra_services'])) {
1010 $item->add_meta_data('Extra Services IDs', $values['extra_services'], true);
1011 }
1012
1013 if (isset($values['extra_services_html'])) {
1014 $item->add_meta_data('Extra Services', $values['extra_services_html'], true);
1015 }
1016
1017 }
1018
1019 public function capture_payment_after_checkout($order_id){
1020 // capture payment
1021 ESHB_Helper::eshb_capture_payment($order_id);
1022 }
1023 public function create_woocommerce_booking_on_checkout($order_id){
1024
1025 if (!$order_id) {
1026 // error_log("Error: Missing order ID.");
1027 return;
1028 }
1029
1030 // Get the order object
1031 $order = wc_get_order($order_id);
1032
1033 if (!$order) {
1034 // error_log("Error: Invalid order ID: $order_id");
1035 return;
1036 }
1037
1038 $eshb_settings = get_option('eshb_settings', []);
1039
1040 // Get the order object
1041 $order = wc_get_order($order_id);
1042 $order_status = $order->get_status();
1043 $booking_status = $order_status;
1044
1045 if ( $order && $order->is_paid() && $order->get_status() !== 'completed' && isset($eshb_settings['booking-auto-approval']) && $eshb_settings['booking-auto-approval'] == true) {
1046 $order->update_status('completed', 'Payment successful, status updated via code.');
1047 $booking_status = 'completed';
1048 }
1049
1050 $first_name = $order->get_billing_first_name();
1051 $last_name = $order->get_billing_last_name();
1052 $customer_name = trim($first_name . ' ' . $last_name); // Concatenate first and last name
1053 $customer_email = $order->get_billing_email(); // Get customer email
1054 $customer_phone = $order->get_billing_phone(); // Get customer phone number
1055
1056 // Check if the booking post has already been created
1057 if (get_post_meta($order_id, '_booking_post_created', true)) {
1058 return; // Exit if the booking post has already been created
1059 }
1060
1061 // Loop through order items
1062 foreach ($order->get_items() as $item_id => $item) {
1063
1064 // Retrieve meta data saved in the order item
1065 $accomodation_id = $item->get_meta('Accomodation ID');
1066 $accomodation_title = get_the_title( $accomodation_id );
1067 $room_quantity = $item->get_meta('Rooms');
1068 $extra_bed_quantity = $item->get_meta('Extra Bed');
1069 $adult_quantity = $item->get_meta('Adults');
1070 $children_quantity = $item->get_meta('Children');
1071 $start_date = $item->get_meta('Start Date');
1072 $end_date = $item->get_meta('End Date');
1073 $dates = $item->get_meta('Date');
1074 $times = $item->get_meta('Time');
1075 $start_time = $item->get_meta('Start Time');
1076 $end_time = $item->get_meta('End Time');
1077 $details_html = $item->get_meta('Details');
1078 $extra_services = $item->get_meta('Extra Services IDs');
1079 $extra_services_html = $item->get_meta('Extra Services');
1080 $base_price = $item->get_meta('Base Price');
1081 $extra_services_charge = $item->get_meta('Extra Service Price');
1082 $extra_bed_price = $item->get_meta('Extra Bed Price');
1083 $total_without_discount = $item->get_meta('Subtotal Price');
1084 $total_price = $item->get_meta('Total Price');
1085
1086 // Prepare the data array to store in post meta
1087 $cart_item_data = [
1088 'booking_status' => $booking_status,
1089 'order_id' => $order_id,
1090 'booking_accomodation_id' => $accomodation_id,
1091 'subtotal_price' => $order->get_subtotal(),
1092 'total_price' => $total_price,
1093 'total_paid' => 0,
1094 'base_price' => $base_price,
1095 'extra_service_price' => $extra_services_charge,
1096 'extra_bed_price' => $extra_bed_price,
1097 'booking_start_date' => $start_date,
1098 'booking_end_date' => $end_date,
1099 'booking_start_time' => !empty($start_time) ? $start_time : '10:00',
1100 'booking_end_time' => !empty($end_time) ? $end_time : '22:00',
1101 'dates' => $dates,
1102 'room_quantity' => $room_quantity,
1103 'extra_bed_quantity' => $extra_bed_quantity,
1104 'adult_quantity' => $adult_quantity,
1105 'children_quantity' => $children_quantity,
1106 'extra_services' => $extra_services,
1107 'start_time' => !empty($start_time) ? $start_time : '10:00',
1108 'end_time' => !empty($end_time) ? $end_time : '22:00',
1109 'times' => $times,
1110 'details_html' => $details_html,
1111 'extra_services_html' => $extra_services_html,
1112 ];
1113
1114 // filter for $cart_item_data;
1115 $cart_item_data = apply_filters( 'on_booking_cart_item_data', $cart_item_data );
1116
1117 // Insert the booking
1118 if($accomodation_id && !empty($accomodation_id)) {
1119 ESHB_Helper::eshb_insert_booking($order_id, $booking_status, $cart_item_data);
1120 }
1121
1122
1123 }
1124
1125 }
1126
1127 public function display_custom_meta_in_cart_and_order($item_data, $cart_item) {
1128
1129 if (!empty($cart_item['discount']) && is_cart()) {
1130 $discount = $cart_item['discount'];
1131 $currency_symbol = $cart_item['currency_symbol'];
1132
1133 $item_data[] = array(
1134 'key' => __('Save', 'easy-hotel'),
1135 'value' => wc_price($discount)
1136 );
1137 }
1138
1139 if (isset($cart_item['dates'])) {
1140
1141 $dates = $cart_item['dates'];
1142
1143 $item_data[] = array(
1144 'key' => __('Date', 'easy-hotel'),
1145 'value' => esc_html( $dates )
1146 );
1147 }
1148
1149 if (!empty($cart_item['times'])) {
1150
1151 $times = $cart_item['times'];
1152
1153 $item_data[] = array(
1154 'key' => __('Time', 'easy-hotel'),
1155 'value' => esc_html( $times )
1156 );
1157 }
1158
1159
1160 if (!empty($cart_item['night'])) {
1161
1162 $night = $cart_item['night'];
1163 $night_label = esc_html__('Night', 'easy-hotel');
1164 if ($night > 1) {
1165 $night_label = esc_html__('Nights', 'easy-hotel');
1166 }
1167
1168 $item_data[] = array(
1169 'key' => $night_label,
1170 'value' => esc_html( $night )
1171 );
1172 }
1173
1174
1175
1176 if (isset($cart_item['details_html'])) {
1177
1178 $details_html = $cart_item['details_html'];
1179
1180 $item_data[] = array(
1181 'key' => __('Details', 'easy-hotel'),
1182 'value' => esc_html( $details_html )
1183 );
1184 }
1185
1186 $extra_services_ids = isset($cart_item['extra_services']) ? $cart_item['extra_services'] : '';
1187
1188 if(!empty($extra_services_ids)){
1189 if (isset($cart_item['extra_services_html'])) {
1190
1191 $extra_services_html = $cart_item['extra_services_html'];
1192
1193 $item_data[] = array(
1194 'key' => __('Extra Services', 'easy-hotel'),
1195 'value' => esc_html( $extra_services_html )
1196 );
1197 }
1198 }
1199
1200 // make unique item data
1201 $item_data = array_unique($item_data, SORT_REGULAR);
1202
1203 return $item_data;
1204
1205 }
1206
1207 public function hide_meta_from_display($hidden_meta_keys) {
1208 // Add meta keys to hide
1209 $keys_to_hide = array(
1210 'Accomodation ID',
1211 'Subtotal Price',
1212 'Total Price',
1213 'Base Price',
1214 'Extra Services Charge',
1215 'Extra Bed Price',
1216 'Rooms',
1217 'Extra Bed',
1218 'Adults',
1219 'Children',
1220 'Start Date',
1221 'End Date',
1222 'Start Time',
1223 'End Time',
1224 'Save'
1225 );
1226
1227 return array_merge($hidden_meta_keys, $keys_to_hide);
1228 }
1229
1230 public function unset_specific_order_item_meta_data($formatted_meta, $item){
1231
1232 $keys_to_hide = array(
1233 'Accomodation ID',
1234 'Subtotal Price',
1235 'Total Price',
1236 'Base Price',
1237 'Extra Services Charge',
1238 'Extra Bed Price',
1239 'Rooms',
1240 'Extra Bed',
1241 'Adults',
1242 'Children',
1243 'Start Date',
1244 'End Date',
1245 'Start Time',
1246 'End Time',
1247 'Save'
1248 );
1249
1250 foreach($formatted_meta as $key => $meta){
1251 if(in_array($meta->key, $keys_to_hide)) {
1252 unset($formatted_meta[$key]);
1253 }
1254 }
1255 return $formatted_meta;
1256 }
1257
1258 public function calculate_booking_pricing($accomodation_id, $start_date, $end_date, $room_quantity, $extra_bed_quantity, $adult_quantity, $children_quantity, $selected_services, $currency_converter = false, $start_time = '', $end_time = '') {
1259 if (empty($accomodation_id)) return;
1260
1261 $accomodation_id = (int) $accomodation_id;
1262 $total_guest_quantity = (int) $adult_quantity + (int) $children_quantity;
1263 $start = new DateTime($start_date);
1264 $end = new DateTime($end_date);
1265 $days_count = max(1, $start->diff($end)->days);
1266 $hours_count = ESHB_Helper::eshb_calculate_time_diff($start_date, $end_date, $start_time, $end_time);
1267 $today = new DateTime('now', new DateTimeZone('GMT'));
1268 $today_date = $today->format('Y-m-d');
1269 $tomorrow = $today->modify('+1 day')->format('Y-m-d');
1270 $hotel_core = new ESHB_Core();
1271 $metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
1272 $allowed_check_in_day = apply_filters( 'eshb_booking_allowed_check_in_day', 'all', $accomodation_id, $metaboxes );
1273 $single_day_price = 0;
1274
1275 // Handle week-based or same-day bookings
1276 if (!empty($allowed_check_in_day) && $allowed_check_in_day !== 'all') {
1277 $days_count = 1;
1278 }
1279
1280 $pricing_periodicity = false;
1281
1282 if ($start_date === $end_date) {
1283 $days_count = 1;
1284 $pricing_periodicity = apply_filters( 'eshb_pricing_periodicity', false, $accomodation_id, $metaboxes );
1285 $single_day_price = apply_filters( 'eshb_single_day_price', 0, $accomodation_id);
1286 }
1287
1288
1289 // Base price
1290 $base_price = 0;
1291 $base_price = $hotel_core->get_eshb_day_wise_price($start_date, $end_date, $accomodation_id, true, $days_count, $adult_quantity, $children_quantity);
1292 $base_price = !empty($single_day_price) ? $single_day_price : $base_price;
1293 $has_session_price = $hotel_core->has_upcoming_or_current_session_price($accomodation_id, $start_date, $end_date, $days_count, $adult_quantity, $children_quantity);
1294
1295 if($has_session_price){
1296 $session_price = $hotel_core->get_eshb_price_by_session($accomodation_id, $start_date, $end_date, $days_count, $adult_quantity, $children_quantity);
1297 }
1298
1299 $base_price = !empty($session_price) && $session_price > 0 ? $session_price : $base_price;
1300 $regular_base_price = $hotel_core->get_eshb_day_wise_price($start_date, $end_date, $accomodation_id, true, $days_count, $adult_quantity, $children_quantity);
1301
1302 // Pricing logic
1303 $bed_price = (int) ($metaboxes['extra_bed_price'] ?? 0);
1304 $pricing_type = $metaboxes['pricing_type'] ?? 'room_wise';
1305
1306
1307 $pricing_base = $pricing_type === 'room_wise'
1308 ? $base_price * $room_quantity
1309 : $base_price * $total_guest_quantity;
1310
1311
1312 $regular_pricing_base = $pricing_type === 'room_wise'
1313 ? $regular_base_price * $room_quantity
1314 : $regular_base_price * $total_guest_quantity;
1315
1316
1317 $extra_bed_price = $extra_bed_quantity > 0
1318 ? $extra_bed_quantity * $bed_price * $days_count
1319 : 0;
1320
1321
1322
1323 $subtotal_price = $pricing_base + $extra_bed_price;
1324 $regular_subtotal_price = $regular_pricing_base + $extra_bed_price;
1325
1326 if($pricing_periodicity && !empty($hours_count)){
1327 $subtotal_price = $pricing_periodicity == 'per_hour' ? $subtotal_price * (int) $hours_count : $subtotal_price;
1328 $regular_subtotal_price = $pricing_periodicity == 'per_hour' ? $regular_subtotal_price * (int) $hours_count : $regular_subtotal_price;
1329 }
1330
1331
1332 // Extra services
1333 $extra_services_charge = 0;
1334
1335 if (!empty($selected_services) && is_array($selected_services)) {
1336 $extra_services_charge = array_reduce($selected_services, function ($carry, $service) use ($days_count, $room_quantity, $total_guest_quantity) {
1337 $service_id = !empty($service['id']) ? $service['id'] : 0;
1338 $service_quantity = !empty($service['quantity']) ? $service['quantity'] : 0;
1339
1340 if(empty($service_id) || empty($service_quantity)) {
1341 return $carry;
1342 }
1343
1344 $meta = get_post_meta($service_id, 'eshb_service_metaboxes', true);
1345 $price = $meta['service_price'] ?? 0;
1346 $periodicity = $meta['service_periodicity'] ?? 'once';
1347 $charge_type = $meta['service_charge_type'] ?? 'room';
1348
1349 //$single_price = $price * $service_quantity;
1350 $single_price = $price ? $price : 0;
1351
1352 if ($periodicity == 'per_day') {
1353 $single_price *= $days_count;
1354 }
1355
1356 if($charge_type == 'room'){
1357 $single_price *= $room_quantity;
1358 }else{
1359 $single_price *= $service_quantity;
1360 }
1361
1362 return $carry + floatval($single_price);
1363 }, 0);
1364 }
1365
1366
1367 $eshb_settings = get_option('eshb_settings');
1368 $booking_type = isset($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : '';
1369
1370 $regular_subtotal_price = $regular_subtotal_price + $extra_services_charge;
1371 $regular_total_price = $regular_subtotal_price;
1372
1373 $subtotal_price = $subtotal_price + $extra_services_charge;
1374 $total_price = $subtotal_price;
1375
1376 $discount = $total_price < $regular_total_price ? abs( (float) $regular_total_price - (float) $total_price) : 0;
1377
1378 $currency_symbol = $hotel_core->get_eshb_currency_symbol();
1379 $currency_symbol = html_entity_decode($currency_symbol);
1380 $currency_position = $hotel_core->get_eshb_currency_position();
1381
1382
1383 $extra_bed_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $extra_bed_price, $currency_converter);
1384 $extra_services_charge = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $extra_services_charge, $currency_converter);
1385 $base_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $base_price, $currency_converter);
1386 $regular_base_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $regular_base_price, $currency_converter);
1387 $subtotal_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $subtotal_price, $currency_converter);
1388 $regular_subtotal_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $regular_subtotal_price, $currency_converter);
1389 $total_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $total_price, $currency_converter);
1390 $regular_total_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $regular_total_price, $currency_converter);
1391 $single_day_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $single_day_price, $currency_converter);
1392 $discount = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $discount, $currency_converter);
1393
1394
1395 $single_day_price_html = $hotel_core->eshb_price( $single_day_price );
1396 $regular_base_price_html = $hotel_core->eshb_price( $regular_base_price );
1397 $base_price_html = $hotel_core->eshb_price( $base_price );
1398 $extra_bed_price_html = $hotel_core->eshb_price( $extra_bed_price );
1399 $extra_services_charge_html = $hotel_core->eshb_price( $extra_services_charge );
1400 $subtotal_price_html = $hotel_core->eshb_price( $subtotal_price );
1401 $regular_subtotal_price_html = $hotel_core->eshb_price( $regular_subtotal_price );
1402 $total_price_html = $hotel_core->eshb_price( $total_price );
1403 $regular_total_price_html = $hotel_core->eshb_price( $regular_total_price );
1404 $discount_html = $hotel_core->eshb_price( $discount );
1405
1406 $is_wc_price = ($booking_type === 'woocommerce' && class_exists('WooCommerce')) ? true : false;
1407
1408 return [
1409 'startDate' => $start_date,
1410 'endDate' => $end_date,
1411 'today' => $today_date,
1412 'tomorrow' => $tomorrow,
1413 'daysCount' => $days_count,
1414 'singleDayPrice' => $single_day_price,
1415 'extraServicesPrice' => $extra_services_charge,
1416 'extraBedPrice' => $extra_bed_price,
1417 'basePrice' => $base_price,
1418 'regularBasePrice' => $regular_base_price,
1419 'subtotalPrice' => $subtotal_price,
1420 'regularSubtotalPrice'=> $regular_subtotal_price,
1421 'totalPrice' => $total_price,
1422 'regularTotalPrice' => $regular_total_price,
1423 'discount' => $discount,
1424 'currencySymbol' => $currency_symbol,
1425 'currencyPosition' => $currency_position,
1426
1427 'singleDayPriceHtml' => $single_day_price_html,
1428 'extraServicesPriceHtml' => $extra_services_charge_html,
1429 'extraBedPriceHtml' => $extra_bed_price_html,
1430 'basePriceHtml' => $base_price_html,
1431 'regularBasePriceHtml' => $regular_base_price_html,
1432 'subtotalPriceHtml' => $subtotal_price_html,
1433 'regularSubtotalPriceHtml'=> $regular_subtotal_price_html,
1434 'totalPriceHtml' => $total_price_html,
1435 'regularTotalPriceHtml' => $regular_total_price_html,
1436 'discountHtml' => $discount_html,
1437
1438 'isWcPrice' => $is_wc_price
1439 ];
1440 }
1441
1442 public function eshb_add_to_cart_reservation(){
1443
1444 // Verify nonce for security
1445 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
1446
1447 $error = array(
1448 'code' => 'invalid_nonce',
1449 'message' => __('invalid_nonce', 'easy-hotel')
1450 );
1451
1452 wp_send_json_error([
1453 'error' => $error,
1454 ]);
1455
1456 die();
1457 }
1458
1459
1460 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId'])){
1461
1462 $hotel_core = new ESHB_Core();
1463 $accomodation_id = isset( $_POST['accomodationId'] ) ? (int) sanitize_text_field( wp_unslash($_POST['accomodationId']) ) : '';
1464 $today_date = esc_html(gmdate('Y-m-d')); // Get today's date
1465 $date = new DateTime($today_date); // Create a DateTime object from today's date
1466 $date->modify('+1 day'); // Add one day
1467 $tomorrow = $date->format('Y-m-d'); // Get the new date in 'Y-m-d' format
1468 $start_date = isset( $_POST['startDate'] ) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : $today_date;
1469 $end_date = isset( $_POST['endDate'] ) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : $tomorrow;
1470 $start_day_name = $start_date ? strtolower(gmdate('l', strtotime($start_date))) : '';
1471 $start_time = isset( $_POST['startTime'] ) ? sanitize_text_field( wp_unslash($_POST['startTime']) ) : '';
1472 $end_time = isset( $_POST['endTime'] ) ? sanitize_text_field( wp_unslash($_POST['endTime']) ) : '';
1473 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
1474
1475 $start = new DateTime($start_date);
1476 $end = new DateTime($end_date);
1477 $interval = $start->diff($end);
1478 $days_count = $interval->days;
1479
1480 $room_quantity = isset( $_POST['roomQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['roomQuantity'] ) ) : 1;
1481 $extra_bed_quantity = isset( $_POST['extraBedQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['extraBedQuantity'] ) ) : 0;
1482 $adult_quantity = isset( $_POST['adultQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['adultQuantity'] ) ) : 1;
1483 $children_quantity = isset( $_POST['childrenQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['childrenQuantity'] ) ) : 0;
1484 $total_guest_quantity = $adult_quantity + $children_quantity;
1485
1486 $selected_services = [];
1487 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
1488
1489 $selected_services = sanitize_text_field(wp_unslash($_POST['selectedServices']));
1490 $selected_services = json_decode($selected_services, true);
1491
1492 }
1493
1494 $eshb_settings = get_option( 'eshb_settings', [] );
1495 $booking_type = isset($eshb_settings['booking-type']) && !empty($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : 'woocommerce';
1496 $string_booking_success_msg = isset($eshb_settings['string_book_success_msg']) && !empty($eshb_settings['string_booking_success_msg']) ? $eshb_settings['string_booking_success_msg'] : 'Reservation Successfully added to your cart.';
1497 $string_booking_failed_msg = isset($eshb_settings['string_booking_failed_msg']) && !empty($eshb_settings['string_booking_failed_msg']) ? $eshb_settings['string_booking_failed_msg'] : 'Ops! This Reservation has been failed.';
1498 $string_already_booked_msg = isset($eshb_settings['string_already_booked_msg']) && !empty($eshb_settings['string_already_booked_msg']) ? $eshb_settings['string_already_booked_msg'] : 'Ops! The date range is already booked:';
1499 $string_minimum_week_nights_msg = isset($eshb_settings['string_minimum_week_nights_msg']) && !empty($eshb_settings['string_minimum_week_nights_msg']) ? $eshb_settings['string_minimum_week_nights_msg'] : 'Please select more than 1 night!';
1500 $string_time_error_msg = isset($eshb_settings['string_time_error_msg']) && !empty($eshb_settings['string_time_error_msg']) ? $eshb_settings['string_time_error_msg'] : 'Selected time slot is not available!';
1501 $room_visibility = in_array('rooms', $eshb_settings['booking-form-fields']) ? true : false;
1502 $adult_visibility = in_array('adults', $eshb_settings['booking-form-fields']) ? true : false;
1503 $childrens_visibility = in_array('childrens', $eshb_settings['booking-form-fields']) ? true : false;
1504 $extra_beds_visibility = in_array('extra_beds', $eshb_settings['booking-form-fields']) ? true : false;
1505
1506 $eshb_week_settings = [
1507 'string_check_in_day_error_msg' => 'Only allowed check in day is',
1508 'eshb_booking_allowed_check_in_day' => 'all',
1509 ];
1510 $eshb_week_settings = apply_filters( 'eshb_week_settings', $eshb_week_settings );
1511 $allowed_check_in_day = apply_filters( 'eshb_booking_allowed_check_in_day', 'all', $accomodation_id, $accomodation_metaboxes );
1512 $string_check_in_day_error_msg = !empty($eshb_week_settings['string_check_in_day_error_msg']) ? $eshb_week_settings['string_check_in_day_error_msg'] : 'Only allowed check in day is : ';
1513
1514 // Min Max Booking Configurations
1515 $min_max_settings = [
1516 'required_min_nights' => '',
1517 'required_max_nights' => '',
1518 'is_global_source_for_min_max' => true,
1519 ];
1520 $min_max_settings = apply_filters( 'eshb_min_max_settings', $min_max_settings, $accomodation_id, $accomodation_metaboxes );
1521 $required_min_nights = $start_date !== $end_date && isset($min_max_settings['required_min_nights']) ? $min_max_settings['required_min_nights'] : '';
1522 $required_max_nights = $start_date !== $end_date && isset($min_max_settings['required_max_nights']) ? $min_max_settings['required_max_nights'] : '';
1523 $string_required_minimum_nights_msg = esc_html__( 'Ops! This Reservation has been failed. Requried Minumum', 'easy-hotel' );
1524 $string_required_maximum_nights_msg = esc_html__( 'Ops! This Reservation has been failed. Requried Maximum', 'easy-hotel' );
1525 $pricing_periodicity = apply_filters( 'eshb_pricing_periodicity', false, $accomodation_id, $accomodation_metaboxes );
1526
1527 $min_stay_night_by_session = ESHB_Helper::get_eshb_min_stay_night_by_session($accomodation_id, $start_date, $end_date);
1528
1529 // Validate booking type
1530 if ( $booking_type == 'woocommerce' && ! class_exists( 'WooCommerce' ) ) {
1531 $error = array(
1532 'code' => 'woocommerce_not_activated',
1533 'message' => esc_html__( 'Ops! Woocommerce not activated. Activate Woocommerce.', 'easy-hotel'),
1534 );
1535 wp_send_json_error( [ 'error' => $error ] );
1536
1537 } elseif ( $booking_type == 'surecart' && ! class_exists( 'SureCart' ) ) {
1538 $error = array(
1539 'code' => 'surecart_not_activated',
1540 'message' => esc_html__( 'Ops! Surecart not activated. Activate Surecart.', 'easy-hotel' ),
1541 );
1542 wp_send_json_error( [ 'error' => $error ] );
1543
1544 } elseif ( $booking_type == 'surecart' && ! class_exists( 'ESHB_SURECART' ) ) {
1545 $error = array(
1546 'code' => 'surecart_addon_not_activated',
1547 'message' => esc_html__( 'Ops! EHB Surecart addons not activated. Activate EHB Surecart.', 'easy-hotel' ),
1548 );
1549 wp_send_json_error( [ 'error' => $error ] );
1550 }
1551
1552
1553 $available_times = ESHB_Helper::get_available_times_by_date($accomodation_id, $start_date);
1554 $is_valid_time = false;
1555
1556 if($pricing_periodicity == 'per_hour' && (!empty($accomodation_metaboxes['single_day_price']) || !empty($accomodation_metaboxes['single_day_sale_price'])) && !empty($available_times)){
1557 $available_slots = isset($available_times['available_slots']) ? $available_times['available_slots'] : [];
1558
1559 if(count($available_slots) > 0) {
1560 foreach ($available_slots as $slot) {
1561 if ($start_time >= $slot[0] && $end_time <= $slot[1]) {
1562 $is_valid_time = true;
1563 break;
1564 }
1565 }
1566 }else{
1567 $is_valid_time = false;
1568 }
1569
1570 if(!$is_valid_time){
1571 $error = array('code' => 'invalid_time', 'message' => esc_html($string_time_error_msg));
1572 wp_send_json_error([
1573 'error' => $error,
1574 ]);
1575 }
1576 }
1577
1578 $actual_booked_dates = $this->get_actual_booked_dates_in_date_ranges($accomodation_id, $start_date, $end_date);
1579 $booked_dates_str = '';
1580 if(!empty($actual_booked_dates)){
1581
1582 $error_message = sprintf(
1583 /* translators: 1: already booked message text, 2: list of booked dates */
1584 esc_html__( '%1$s %2$s', 'easy-hotel' ),
1585 esc_html( $string_already_booked_msg ),
1586 $booked_dates_str
1587 );
1588
1589 $error = array(
1590 'code' => 'already_booked',
1591 'message' => $error_message,
1592 );
1593 wp_send_json_error([
1594 'error' => $error,
1595 'booked_dates' => implode(', ', $actual_booked_dates),
1596 ]);
1597
1598 }
1599
1600 $holiday_dates = $this->get_holiday_dates_in_date_ranges($accomodation_id, $start_date, $end_date);
1601
1602 if(!empty($holiday_dates)){
1603 $error = array('code' => 'holiday_dates', 'message' => esc_html__('Selected date range is not available. These dates are in holidays.', 'easy-hotel'));
1604 wp_send_json_error([
1605 'error' => $error,
1606 'holiday_dates' => implode(', ', $holiday_dates),
1607 ]);
1608 }
1609
1610 // Check cart blocking (temporary holds by other users)
1611 $cart_block_msg = $this->eshb_check_cart_block($accomodation_id, $start_date, $end_date);
1612 if (!empty($cart_block_msg)) {
1613 $error = array('code' => 'cart_blocked', 'message' => esc_html($cart_block_msg));
1614 wp_send_json_error(['error' => $error]);
1615 }
1616
1617 // check rooms capacity
1618 if(!$is_valid_time && isset($_POST['roomQuantity']) && !empty($_POST['roomQuantity'])){
1619 $roomQuantity = isset( $_POST['roomQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['roomQuantity'] ) ) : 1;
1620
1621 $availableRoom = $this->get_available_room_count_by_date_range($accomodation_id, $start_date, $end_date);
1622
1623 if($roomQuantity > $availableRoom){
1624 $error = array(
1625 'code' => 'room_capacity_not_enough',
1626 'message' => sprintf(
1627 /* translators: %s: number of available rooms */
1628 esc_html__( 'Selected room is not available. Available room: %s', 'easy-hotel' ),
1629 esc_html( $availableRoom )
1630 ),
1631 );
1632 wp_send_json_error([
1633 'error' => $error,
1634 ]);
1635 }
1636 }
1637
1638
1639 // Validate check-in day
1640 if($allowed_check_in_day != 'all' && $start_day_name != '' && !empty($allowed_check_in_day) && $allowed_check_in_day == $start_day_name) {
1641 $error = array(
1642 'code' => 'check_in_day_error',
1643 'message' => sprintf(
1644 /* translators: %s: allowed check-in day */
1645 esc_html__( '%1$s %1$s', 'easy-hotel' ),
1646 esc_html( $string_check_in_day_error_msg ),
1647 esc_html( $allowed_check_in_day )
1648 ),
1649 );
1650 wp_send_json_error([
1651 'error' => $error,
1652 ]);
1653 }
1654
1655
1656 if(!empty($allowed_check_in_day) && $allowed_check_in_day != 'all'){
1657 $days_count = 1;
1658 }
1659
1660 // validate min stay night by session
1661 if(class_exists('ESHB_ADVANCED_PRICING') && !empty($min_stay_night_by_session) && $days_count < $min_stay_night_by_session){
1662 $error = array(
1663 'code' => 'min_stay_night_by_session',
1664 'message' => sprintf(
1665 /* translators: 1: minimum nights message text, 2: required minimum nights */
1666 esc_html__( '%1$s %2$s nights!', 'easy-hotel' ),
1667 esc_html( $string_required_minimum_nights_msg ),
1668 esc_html( $min_stay_night_by_session )
1669 ),
1670 );
1671 wp_send_json_error([
1672 'error' => $error,
1673 ]);
1674 }
1675
1676 if(!empty($required_min_nights) && $days_count < $required_min_nights){
1677 $error = array(
1678 'code' => 'required_min_nights',
1679 'message' => sprintf(
1680 /* translators: 1: minimum nights message text, 2: required minimum nights */
1681 esc_html__( '%1$s %2$s nights!', 'easy-hotel' ),
1682 esc_html( $string_required_minimum_nights_msg ),
1683 esc_html( $required_min_nights )
1684 ),
1685 );
1686 wp_send_json_error([
1687 'error' => $error,
1688 ]);
1689 }
1690 if(!empty($required_max_nights) && $days_count > $required_max_nights){
1691 $error = array(
1692 'code' => 'required_max_nights',
1693 'message' => sprintf(
1694 /* translators: 1: maximum nights message text, 2: required maximum nights */
1695 esc_html__( '%1$s %2$s nights allowed!', 'easy-hotel' ),
1696 esc_html( $string_required_maximum_nights_msg ),
1697 esc_html( $required_max_nights )
1698 ),
1699 );
1700 wp_send_json_error([
1701 'error' => $error,
1702 ]);
1703 }
1704
1705
1706
1707 // validate capacities
1708 $adult_capacity = !empty($accomodation_metaboxes['adult_capacity']) ? $accomodation_metaboxes['adult_capacity'] : 0;
1709 $children_capacity = !empty($accomodation_metaboxes['children_capacity']) ? $accomodation_metaboxes['children_capacity'] : 0;
1710 $total_capacity = !empty($accomodation_metaboxes['total_capacity']) ? $accomodation_metaboxes['total_capacity'] : 0;
1711 $extra_bed_capacity = !empty($accomodation_metaboxes['total_extra_beds']) ? $accomodation_metaboxes['total_extra_beds'] : 0;
1712
1713 // multiply total capacity by per rooms
1714 $total_capacity = $total_capacity * $room_quantity;
1715
1716 if(!empty($total_capacity)){
1717 if($adult_capacity < 1 && $children_capacity > 0){
1718 $adult_capacity = $total_capacity - $children_quantity;
1719 }elseif($adult_capacity < 1 && $children_capacity < 1){
1720 $adult_capacity = $total_capacity;
1721 }
1722
1723 if($children_capacity < 1 && $adult_capacity > 0){
1724 $children_capacity = $total_capacity - $adult_quantity;
1725 }elseif($children_capacity < 1 && $adult_capacity < 1){
1726 $children_capacity = $total_capacity;
1727 }
1728 }
1729
1730 // multiply capacity by per rooms
1731 $adult_capacity = $adult_capacity * $room_quantity;
1732 $children_capacity = $children_capacity * $room_quantity;
1733 $extra_bed_capacity = $extra_bed_capacity * $room_quantity;
1734
1735 if(!empty($adult_capacity) && $adult_quantity > 0 && $adult_capacity < $adult_quantity){
1736 $error = array('code' => 'adult_capacity_not_enough', 'message' => esc_html__('Maximum Adult Capacity', 'easy-hotel') . ' ' . $adult_capacity);
1737 wp_send_json_error([
1738 'error' => $error,
1739 ]);
1740 }
1741
1742 if(!empty($children_capacity) && $children_quantity > 0 && $children_capacity < $children_quantity){
1743 $error = array('code' => 'children_capacity_not_enough', 'message' => esc_html__('Maximum Children Capacity', 'easy-hotel') . ' ' . $children_capacity);
1744 wp_send_json_error([
1745 'error' => $error,
1746 ]);
1747 }
1748
1749 if($extra_bed_quantity > 0 && $extra_bed_capacity < $extra_bed_quantity){
1750 $error = array('code' => 'extra_bed_capacity_not_enough', 'message' => esc_html__('Maximum Extra Bed Capacity', 'easy-hotel') . ' ' . $extra_bed_capacity );
1751 wp_send_json_error([
1752 'error' => $error,
1753 ]);
1754 }
1755
1756
1757
1758 $pricing = $this->calculate_booking_pricing($accomodation_id, $start_date, $end_date, $room_quantity, $extra_bed_quantity, $adult_quantity, $children_quantity, $selected_services, false, $start_time, $end_time );
1759
1760 $base_price = $pricing['basePrice'];
1761 $extra_services_charge = $pricing['extraServicesPrice'];
1762 $extra_bed_price= $pricing['extraBedPrice'];
1763 $subtotal_price = $pricing['subtotalPrice'];
1764 $total_price = $pricing['totalPrice'];
1765 $discount = $pricing['discount'];
1766 $currency_symbol = $pricing['currencySymbol'];
1767 $quantity = 1;
1768
1769 $details = [];
1770
1771 if ($room_visibility && $room_quantity > 0) {
1772 $details[] = 'Room: ' . $room_quantity;
1773 }
1774 if ($extra_beds_visibility && $extra_bed_quantity > 0) {
1775 $details[] = 'Extra Bed: ' . $extra_bed_quantity;
1776 }
1777 if ($adult_visibility && $adult_quantity > 0) {
1778 $details[] = 'Adult: ' . $adult_quantity;
1779 }
1780 if ($childrens_visibility && $children_quantity > 0) {
1781 $details[] = 'Children: ' . $children_quantity;
1782 }
1783
1784 $details_html = implode(', ', $details);
1785
1786
1787 $extra_service_titles = [];
1788 $extra_services_html = '';
1789
1790 if(!empty($selected_services) && is_array($selected_services) && count($selected_services) > 0 ){
1791
1792 foreach ($selected_services as $service) {
1793
1794 $service_id = $service['id'];
1795
1796 $service_quantity = $service['quantity'];
1797
1798 if(!empty($service_quantity)){
1799 $eshb_service_metaboxes = get_post_meta($service_id, 'eshb_service_metaboxes', true);
1800
1801 $charge_type = $eshb_service_metaboxes['service_charge_type'];
1802
1803 $service_title = get_the_title($service_id) . ' For ' . $service_quantity . ' ' . ucfirst(strtolower($charge_type));
1804
1805 if(!$room_visibility && $charge_type == 'room'){
1806 $service_title = get_the_title($service_id);
1807 }
1808 }else{
1809 $service_title = get_the_title($service_id);
1810 }
1811
1812
1813
1814 array_push($extra_service_titles, $service_title);
1815
1816 }
1817 $extra_services_html = implode(", ",$extra_service_titles);
1818 }
1819
1820 $dates = date_i18n( get_option('date_format'), strtotime( $start_date ) ) .' - '. date_i18n( get_option('date_format'), strtotime( $end_date ) );
1821 if($start_date == $end_date){
1822 $dates = date_i18n( get_option('date_format'), strtotime( $start_date ) );
1823 }
1824
1825 $formated_start_timestamp = strtotime( $start_time );
1826 $formated_end_timestamp = strtotime( $end_time );
1827
1828 $cart_item_data = [
1829 'accomodation_id' => $accomodation_id,
1830 'subtotal_price' => $subtotal_price,
1831 'total_price' => $total_price,
1832 'base_price' => $base_price,
1833 'discount' => $discount,
1834 'extra_services_charge' => $extra_services_charge,
1835 'extra_bed_price' => $extra_bed_price,
1836 'currency_symbol' => $currency_symbol,
1837 'start_date' => $start_date,
1838 'end_date' => $end_date,
1839 'night' => $days_count,
1840 'dates' => $dates,
1841 'room_quantity' => $room_quantity,
1842 'adult_quantity' => $adult_quantity,
1843 'details_html' => $details_html,
1844 'extra_services_html' => $extra_services_html
1845 ];
1846
1847 if(!empty($extra_bed_quantity)) {
1848 $cart_item_data['extra_bed_quantity'] = $extra_bed_quantity;
1849 }
1850 if(!empty($start_time)) {
1851 $cart_item_data['start_time'] = $start_time;
1852 }
1853 if(!empty($end_time)) {
1854 $cart_item_data['end_time'] = $end_time;
1855 }
1856
1857 if(!empty($start_time) && !empty($end_time)) {
1858 // Get WP formatted time (according to Settings > General > Time Format)
1859 $wp_time_format = get_option( 'time_format' ); // WP settings
1860 $formatted_start_time = wp_date( $wp_time_format, $formated_start_timestamp );
1861 $formatted_end_time = wp_date( $wp_time_format, $formated_end_timestamp );
1862 $times = $formatted_start_time . ' - ' . $formatted_end_time;
1863 $cart_item_data['times'] = $times;
1864 }
1865
1866 if(!empty($children_quantity)) {
1867 $cart_item_data['children_quantity'] = $children_quantity;
1868 }
1869
1870 if(!empty($selected_services)) {
1871 $cart_item_data['extra_services'] = $selected_services;
1872 }
1873
1874 if(!empty($extra_services_charge)) {
1875 $cart_item_data['extra_services_html'] = $extra_services_html;
1876 }
1877
1878 if (get_post_type($accomodation_id) === 'eshb_accomodation') {
1879
1880 $thumbnail_id = get_post_thumbnail_id($accomodation_id);
1881 $price_id = 0;
1882 $product_id = ESHB_Helper::get_product_id_for_cart($accomodation_id, $booking_type);
1883 $product_id = apply_filters( 'eshb_product_id_for_cart', $product_id, $accomodation_id, $thumbnail_id );
1884
1885 $cart_item_data['product_id'] = $product_id;
1886 $cart_item_data['price_id'] = $price_id;
1887
1888 do_action( 'eshb_before_add_to_cart', $booking_type, $cart_item_data, $string_booking_success_msg );
1889
1890 if($booking_type == 'woocommerce'){
1891
1892 update_post_meta($product_id, '_accomodation_id', $accomodation_id);
1893 $this->update_product_price_by_id($product_id, $subtotal_price);
1894
1895 $cart_item_data['unique_key'] = uniqid( '', true );
1896 $cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, 0, [], $cart_item_data);
1897
1898 if ( $cart_item_key ) {
1899
1900 $blocked_until = $this->eshb_set_cart_block($accomodation_id, $start_date, $end_date, $room_quantity);
1901
1902 wp_send_json_success([
1903 'booking-type' => $booking_type,
1904 'message' => esc_html( $string_booking_success_msg ),
1905 'cart_item_key' => $cart_item_key,
1906 'cart_count' => WC()->cart->get_cart_contents_count(),
1907 'blocked_until' => $blocked_until,
1908 'accomodation_id' => $accomodation_id,
1909 'start_date' => $start_date,
1910 'end_date' => $end_date,
1911 ]);
1912
1913 } else {
1914
1915 $error = array(
1916 'code' => 'cart_item_key_not_found',
1917 'message' => esc_html( $string_booking_failed_msg ),
1918 );
1919
1920 wp_send_json_error([
1921 'error' => $error,
1922 'cart_item_data' => $cart_item_data,
1923 'product_id' => $product_id,
1924 ]);
1925 }
1926
1927 }
1928
1929 do_action( 'eshb_after_add_to_cart', $booking_type, $cart_item_data, $string_booking_success_msg );
1930
1931 }else{
1932 $error = array('code' => 'accomodation_post_type_not_found', 'message' => esc_html($string_booking_failed_msg));
1933 wp_send_json_error([
1934 'error' => $error,
1935 ]);
1936 }
1937 die();
1938 }
1939
1940 }
1941
1942 public function eshb_get_booking_prices() {
1943 // Ensure external filters are loaded during AJAX requests
1944 do_action('eshb_before_calculate_pricing');
1945
1946 if (isset($_POST['accomodationId']) && !empty($_POST['accomodationId'])) {
1947
1948 // Verify nonce for security
1949 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
1950 wp_send_json_error(['message' => 'Invalid nonce']);
1951 die();
1952 }
1953
1954 $accomodation_id = (int) sanitize_text_field(wp_unslash($_POST['accomodationId']));
1955
1956 $start_date = sanitize_text_field(wp_unslash($_POST['startDate'] ?? $today_date));
1957 $end_date = sanitize_text_field(wp_unslash($_POST['endDate'] ?? $tomorrow));
1958
1959 $start_time = sanitize_text_field(wp_unslash($_POST['startTime'] ?? ''));
1960 $end_time = sanitize_text_field(wp_unslash($_POST['endTime'] ?? ''));
1961
1962 $room_quantity = (int) sanitize_text_field(wp_unslash($_POST['roomQuantity'] ?? 1));
1963 $extra_bed_quantity = (int) sanitize_text_field(wp_unslash($_POST['extraBedQuantity'] ?? 0));
1964 $adult_quantity = (int) sanitize_text_field(wp_unslash($_POST['adultQuantity'] ?? 1));
1965 $children_quantity = (int) sanitize_text_field(wp_unslash($_POST['childrenQuantity'] ?? 0));
1966 $selected_services = [];
1967 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
1968
1969 $selected_services = sanitize_text_field(wp_unslash($_POST['selectedServices']));
1970 $selected_services = json_decode($selected_services, true);
1971
1972 }
1973
1974
1975 $pricing = $this->calculate_booking_pricing($accomodation_id, $start_date, $end_date, $room_quantity, $extra_bed_quantity, $adult_quantity, $children_quantity, $selected_services, true, $start_time, $end_time);
1976
1977 wp_send_json_success($pricing);
1978 }
1979
1980 wp_die();
1981 }
1982
1983 public function eshb_get_booking_prices_rest( $request ) {
1984 do_action('eshb_before_calculate_pricing');
1985
1986 $params = $request->get_params();
1987
1988 // // Nonce check
1989 if ( ! isset($params['nonce']) || ! wp_verify_nonce($params['nonce'], ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action')) ) {
1990 return new WP_Error('invalid_nonce', 'Invalid nonce', ['status' => 403]);
1991 }
1992
1993 $accomodation_id = (int) ($params['accomodationId'] ?? 0);
1994 $start_date = $params['startDate'] ?? gmdate('Y-m-d');
1995 $end_date = $params['endDate'] ?? gmdate('Y-m-d', strtotime('+1 day'));
1996 $start_time = $params['startTime'] ?? '';
1997 $end_time = $params['endTime'] ?? '';
1998 $room_quantity = (int) ($params['roomQuantity'] ?? 1);
1999 $extra_bed_quantity = (int) ($params['extraBedQuantity'] ?? 0);
2000 $adult_quantity = (int) ($params['adultQuantity'] ?? 1);
2001 $children_quantity = (int) ($params['childrenQuantity'] ?? 0);
2002 $selected_services = $params['selectedServices'] ?? [];
2003
2004 $pricing = $this->calculate_booking_pricing(
2005 $accomodation_id,
2006 $start_date,
2007 $end_date,
2008 $room_quantity,
2009 $extra_bed_quantity,
2010 $adult_quantity,
2011 $children_quantity,
2012 $selected_services,
2013 true,
2014 $start_time,
2015 $end_time
2016 );
2017
2018 return rest_ensure_response($pricing);
2019 }
2020
2021 public function eshb_get_extra_services_charge(){
2022
2023 // Verify nonce for security
2024 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2025 wp_send_json_error(['message' => 'Invalid nonce']);
2026 die();
2027 }
2028
2029 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
2030
2031 $selected_services = array_map('sanitize_text_field', wp_unslash($_POST['selectedServices']));
2032 $days_count = isset($_POST['dayCount']) ? sanitize_text_field(wp_unslash($_POST['dayCount'])) : 1;
2033
2034 $price = 0;
2035
2036 if(is_array($selected_services)){
2037 foreach ($selected_services as $service) {
2038
2039 $service_id = $service['id'];
2040 $quantity = $service['quantity'];
2041
2042 $eshb_service_metaboxes = get_post_meta($service_id, 'eshb_service_metaboxes', true);
2043
2044 $service_periodicity = $eshb_service_metaboxes['service_periodicity'];
2045
2046 if (isset($eshb_service_metaboxes['service_price'])) { // Ensure 'service_price' is set
2047 $single_price = $eshb_service_metaboxes['service_price'] * $quantity;
2048 if($service_periodicity == 'perday' && !empty($days_count)){
2049 $single_price = $days_count * $single_price;
2050 }
2051 $price += floatval($single_price); // Add the single price to the total price
2052 }
2053
2054 }
2055 }
2056
2057 wp_send_json_success( ['price' => $price] );
2058
2059 wp_die();
2060
2061 }
2062
2063 }
2064
2065 public function get_available_room_count_by_date_range($accomodation_id, $start_date, $end_date, $start_time = '', $end_time = '') {
2066 $accomodation_id = intval($accomodation_id);
2067 $start_date_obj = new DateTime($start_date);
2068 $end_date_obj = new DateTime($end_date);
2069
2070 if ($end_date_obj < $start_date_obj) {
2071 return new WP_Error('invalid_date_range', 'End date cannot be earlier than start date.');
2072 }
2073
2074 // Fetch total rooms
2075 $accomodation_metas = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2076 $total_rooms = isset($accomodation_metas['total_rooms']) ? intval($accomodation_metas['total_rooms']) : 1;
2077
2078 // Initialize bookings count array
2079 $bookings_per_date = [];
2080
2081 // Query all bookings
2082 $bookings_args = [
2083 'post_type' => 'eshb_booking',
2084 'posts_per_page' => -1,
2085 //'post_status' => 'publish',
2086 'post_status' => ['publish', 'completed', 'processing', 'deposit-payment', 'pending'],
2087 'fields' => 'ids',
2088 ];
2089 $bookings = new WP_Query($bookings_args);
2090
2091 if ($bookings->have_posts()) {
2092 while ($bookings->have_posts()) {
2093 $bookings->the_post();
2094 $booking_id = get_the_ID();
2095 $booking_metas = get_post_meta($booking_id, 'eshb_booking_metaboxes', true);
2096 $booking_accomodation_id = $booking_metas['booking_accomodation_id'];
2097 $booking_status = isset($booking_metas['booking_status']) ? $booking_metas['booking_status'] : '';
2098 $valid_statuses = array('pending', 'processing', 'on-hold', 'completed', 'deposit-payment');
2099
2100 if (
2101 is_array($booking_metas) &&
2102 intval($booking_accomodation_id) === $accomodation_id &&
2103 isset($booking_metas['booking_start_date'], $booking_metas['booking_end_date']) &&
2104 in_array($booking_status, $valid_statuses)
2105 ) {
2106 $booking_start = new DateTime($booking_metas['booking_start_date']);
2107 $booking_end = new DateTime($booking_metas['booking_end_date']);
2108 $booked_rooms = !empty($booking_metas['room_quantity']) ? intval($booking_metas['room_quantity']) : 1;
2109 $accomodation_metaboxes = get_post_meta( $booking_accomodation_id, 'eshb_accomodation_metaboxes', true );
2110 $total_rooms = !empty($accomodation_metaboxes['total_rooms']) ? $accomodation_metaboxes['total_rooms'] : 1;
2111
2112 if(!empty($start_time) && !empty($end_time)) {
2113 $booking_start_time = !empty($booking_metas['booking_start_time']) ? $booking_metas['booking_start_time'] : '';
2114 $booking_end_time = !empty($booking_metas['booking_end_time']) ? $booking_metas['booking_end_time'] : '';
2115
2116 if($booking_start_time !== $start_time) {
2117 $booked_rooms = 0;
2118 }
2119 if($booking_start_time === $start_time && $booking_end_time === $end_time) {
2120
2121 $booked_rooms = 0;
2122 }
2123 }
2124
2125 // Loop through each date in this booking
2126 $period = new DatePeriod($booking_start, new DateInterval('P1D'), (clone $booking_end)->modify('+0 day'));
2127 if($booking_start == $booking_end) {
2128 $period = new DatePeriod($booking_start, new DateInterval('P1D'), (clone $booking_end)->modify('+1 day'));
2129 }
2130
2131 foreach ($period as $date) {
2132 $formatted = $date->format('Y-m-d');
2133 if (!isset($bookings_per_date[$formatted])) {
2134 $bookings_per_date[$formatted] = 0;
2135 }
2136 $bookings_per_date[$formatted] += $booked_rooms;
2137 }
2138
2139 }
2140 }
2141 wp_reset_postdata();
2142 }
2143
2144
2145
2146 // Now calculate the max number of rooms booked in the given date range
2147 $range_period = new DatePeriod($start_date_obj, new DateInterval('P1D'), (clone $end_date_obj)->modify('+0 day'));
2148 $max_booked_in_range = 0;
2149
2150 if($start_date_obj == $end_date_obj){
2151 $formatted = $start_date_obj->format('Y-m-d');
2152 $available_rooms = isset($bookings_per_date[$formatted]) ? $total_rooms - $bookings_per_date[$formatted] : $total_rooms;
2153 return $available_rooms;
2154 }
2155
2156 foreach ($range_period as $date) {
2157 $formatted = $date->format('Y-m-d');
2158 $booked = isset($bookings_per_date[$formatted]) ? $bookings_per_date[$formatted] : 0;
2159 if ($booked > $max_booked_in_range) {
2160 $max_booked_in_range = $booked;
2161 }
2162 }
2163
2164 // Final available room count
2165 $available_rooms = max(0, $total_rooms - $max_booked_in_range);
2166
2167 if($available_rooms < 0) {
2168 $available_rooms = 0;
2169 }
2170
2171 return $available_rooms;
2172 }
2173
2174 public function eshb_get_available_rooms_counts_data(){
2175
2176 // Verify nonce for security
2177 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2178 wp_send_json_error(['message' => 'Invalid nonce']);
2179 die();
2180 }
2181
2182
2183 $accomodation_id = !empty($_POST['accomodationId']) ? sanitize_text_field( wp_unslash($_POST['accomodationId']) ) : '';
2184 $start_date = !empty($_POST['startDate']) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : '';
2185 $end_date = !empty($_POST['endDate']) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : '';
2186
2187 $start_time = !empty($_POST['startTime']) ? sanitize_text_field( wp_unslash($_POST['startTime']) ) : '';
2188 $end_time = !empty($_POST['endTime']) ? sanitize_text_field( wp_unslash($_POST['endTime']) ) : '';
2189
2190
2191 $start_date = new DateTime($start_date);
2192 $end_date = new DateTime($end_date);
2193
2194 $eshb_accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2195 $total_rooms = !empty($eshb_accomodation_metaboxes['total_rooms']) ? $eshb_accomodation_metaboxes['total_rooms'] : 0;
2196
2197 $available = 1;
2198 $available = $this->get_available_room_count_by_date_range($accomodation_id, $start_date->format('Y-m-d'), $end_date->format('Y-m-d'), $start_time, $end_time);
2199 if($available < 0 ) {
2200 $available = 0;
2201 }
2202
2203 wp_send_json_success(
2204 array(
2205 'available' => $available,
2206 'total' => $total_rooms,
2207 )
2208 );
2209
2210 die();
2211
2212 }
2213
2214 public function eshb_get_accomodation_available_capacity_counts(){
2215
2216 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2217 wp_send_json_error(['message' => 'Invalid nonce']);
2218 die();
2219 }
2220
2221
2222 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId']) && isset($_POST['type']) && !empty($_POST['type'])){
2223 $type = sanitize_text_field( wp_unslash($_POST['type']) );
2224 $accomodation_id = sanitize_text_field( wp_unslash($_POST['accomodationId']) );
2225 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2226 $total_guest_quantity = $accomodation_metaboxes['total_capacity'];
2227 $selected_rooms_count = !empty($_POST['rooms']) ? sanitize_text_field( wp_unslash($_POST['rooms']) ) : 1;
2228 $start_date = !empty($_POST['startDate']) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : '';
2229 $end_date = !empty($_POST['endDate']) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : '';
2230 $start_date = new DateTime($start_date);
2231 $end_date = new DateTime($end_date);
2232
2233 $available = 1;
2234 $msg = __('Maximum Capacity', 'easy-hotel');
2235
2236 if($type == 'room_quantity'){
2237 $total = $accomodation_metaboxes['total_rooms'];
2238
2239 $available = $this->get_available_room_count_by_date_range($accomodation_id, $start_date->format('Y-m-d'), $end_date->format('Y-m-d'));
2240
2241 if($available < 1){
2242 $msg = __('Available Room', 'easy-hotel');
2243 }
2244
2245 }
2246
2247 if($type == 'adult_quantity'){
2248
2249 if(!empty($accomodation_metaboxes['adult_capacity'])){
2250 $available = $accomodation_metaboxes['adult_capacity'];
2251 $available = $available * $selected_rooms_count;
2252 }else{
2253 $childrenQuantity = !empty($_POST['childrenQuantity']) ? sanitize_text_field( wp_unslash( $_POST['childrenQuantity'] ) ) : 0;
2254
2255 $total_capacity = $accomodation_metaboxes['total_capacity'];
2256
2257 $available = $total_capacity - $childrenQuantity;
2258
2259 $available = $available * $selected_rooms_count;
2260
2261 $msg = __('Maximum Adult and Children Capacity', 'easy-hotel');
2262 }
2263
2264 }
2265
2266 if($type == 'children_quantity'){
2267
2268 $total_capacity = $accomodation_metaboxes['total_capacity'];
2269
2270 if(!empty($accomodation_metaboxes['children_capacity'])){
2271 $available = $accomodation_metaboxes['children_capacity'];
2272 $available = $available * $selected_rooms_count;
2273 }else{
2274 $childrenQuantity = !empty($_POST['adultQuantity']) ? sanitize_text_field( wp_unslash( $_POST['adultQuantity'] ) ) : 0;
2275
2276 $available = $total_capacity - $childrenQuantity;
2277
2278 $available = $available * $selected_rooms_count;
2279
2280 $msg = __('Maximum Adult and Children Capacity', 'easy-hotel');
2281 }
2282
2283 }
2284
2285 if($type == 'extra_bed_quantity'){
2286 $available = $accomodation_metaboxes['total_extra_beds'];
2287 $available = $available * $selected_rooms_count;
2288 }
2289
2290 if($type == 'service-quantity'){
2291 $available = $total_guest_quantity;
2292 }
2293
2294
2295 if(($type != 'room_quantity') && (empty($available) || $available < 1)){
2296 $available = $total;
2297 }
2298
2299 if($available < 0) {
2300 $available = 0;
2301 }
2302
2303 $msg = $type != 'adult_quantity' || $type != 'children_quantity' ? $msg . ' ' . $available : $msg;
2304
2305 wp_send_json_success(
2306 array(
2307 'message' => $msg,
2308 'available' => $available,
2309 'type' => $type,
2310 )
2311 );
2312 }
2313 }
2314
2315 public function get_accomodation_available_extra_bed_counts(){
2316
2317 // Verify nonce for security
2318 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2319 wp_send_json_error(['message' => 'Invalid nonce']);
2320 die();
2321 }
2322
2323 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId']) && !empty($_POST['accomodationId'])){
2324
2325 $total_extra_beds = 0;
2326 $accomodation_id = (int) sanitize_text_field( wp_unslash($_POST['accomodationId']) );
2327 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2328
2329 $total_extra_beds = $accomodation_metaboxes['total_extra_beds'];
2330
2331 wp_send_json_success( $total_extra_beds );
2332
2333 }
2334 }
2335
2336 public function send_email_woocommerce_style($email, $subject, $heading, $message) {
2337 if ( ! function_exists( 'WC' ) ) {
2338 return;
2339 }
2340
2341 // Get WooCommerce mailer
2342 $mailer = WC()->mailer();
2343
2344 // Wrap message with WooCommerce email template
2345 $wrapped_message = $mailer->wrap_message($heading, $message);
2346
2347 // Create new WC_Email instance
2348 $wc_email = new WC_Email();
2349
2350 // Style the wrapped message with WooCommerce inline styles
2351 $html_message = $wc_email->style_inline($wrapped_message);
2352
2353 // Headers
2354 $headers = array('Content-Type: text/html; charset=UTF-8');
2355
2356 // Send the email
2357 $mailer->send($email, $subject, $html_message, $headers);
2358 }
2359
2360 public function filter_status_based_on_booking_meta($status, $order_id) {
2361 // Check if custom meta _booking_post_created exists
2362 $booking_post_id = get_post_meta($order_id, '_booking_post_created', true);
2363
2364 $eshb_settings = get_option('eshb_settings', []);
2365
2366
2367 if ( ! empty($booking_post_id) ) {
2368 if(isset($eshb_settings['booking-auto-approval']) && $eshb_settings['booking-auto-approval'] == true){
2369 return 'completed'; // Mark order as completed
2370 }
2371 }
2372
2373 // Otherwise, keep default status (usually 'processing')
2374 return $status;
2375 }
2376
2377 public function update_booking_status_on_woocommerce_order_status_change($order_id, $old_status, $new_status, $order) {
2378
2379 if($old_status == $new_status) {
2380 return;
2381 }
2382
2383 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
2384
2385 //if ( $screen && $screen->id !== 'woocommerce_page_wc-orders' ) return;
2386 if ( $screen && $screen->id === 'edit-eshb_booking' ) return;
2387
2388 if ( $screen && 'woocommerce_page_wc-orders' === $screen->id ) {
2389 //return;
2390 }
2391
2392 // Retrieve the order object
2393 $order = wc_get_order($order_id);
2394
2395 // Get the booking post ID from order meta
2396 $booking_post_id = get_post_meta($order_id, '_booking_post_created', true); // Adjusted to your meta key
2397
2398 if ($booking_post_id) {
2399
2400 // update booking status
2401 $updated_booking = array(
2402 'ID' => $booking_post_id,
2403 'post_status' => $new_status,
2404 );
2405
2406 wp_update_post( $updated_booking );
2407
2408 // Get the existing meta for the custom post
2409 $booking_meta = get_post_meta($booking_post_id, 'eshb_booking_metaboxes', true);
2410
2411 // Ensure the meta is an array before modifying it
2412 if (is_array($booking_meta)) {
2413 // Update the `booking_status` field
2414 $booking_meta['booking_status'] = $new_status;
2415
2416 // Save the updated meta back to the post
2417 update_post_meta($booking_post_id, 'eshb_booking_metaboxes', $booking_meta);
2418 }
2419 }
2420 }
2421
2422 public function update_woocommerce_order_status_on_booking_status_change($post_id, $post, $update) {
2423
2424 if(class_exists('ESHB_MANUAL_BOOKING') && is_admin()){
2425 return;
2426 }
2427
2428 if ( ! function_exists( 'WC' ) ) {
2429 return;
2430 }
2431
2432 // Ensure this runs only during post update
2433 if (!$update || wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
2434 return;
2435 }
2436
2437 // Check if the post type matches
2438 if ($post->post_type !== 'eshb_booking') {
2439 return;
2440 }
2441
2442
2443
2444 // Retrieve the metabox data
2445 $eshb_booking_metaboxes = get_post_meta($post_id, 'eshb_booking_metaboxes', true) ?? [];
2446
2447 // Validate the required fields
2448 if (empty($eshb_booking_metaboxes) || empty($eshb_booking_metaboxes['booking_status']) || empty($eshb_booking_metaboxes['order_id'])) {
2449 return;
2450 }
2451
2452 $booking_status = sanitize_text_field($eshb_booking_metaboxes['booking_status']);
2453 $order_id = intval($eshb_booking_metaboxes['order_id']);
2454
2455 // Get the WooCommerce order object
2456 $order = wc_get_order($order_id);
2457 if (!$order) {
2458 return; // Invalid order
2459 }
2460
2461 // Check if the current order status matches the booking status to avoid redundant updates
2462 if ($order->get_status() === $booking_status) {
2463 return; // Status is already up-to-date
2464 }
2465
2466 try {
2467 $order->update_status($booking_status, 'Booking status updated to: ' . $booking_status);
2468 } catch (Exception $e) {
2469 // Log error for debugging
2470 //error_log('Error updating order status: ' . $e->getMessage());
2471 }
2472 }
2473
2474 public function send_booking_email_notification_for_booking_status($post_id, $post, $update){
2475
2476 // Ensure this runs only during post update
2477 if (!$update || wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
2478 return;
2479 }
2480
2481 // Check if the post type matches
2482 if ($post->post_type !== 'eshb_booking') {
2483 return;
2484 }
2485
2486 // Retrieve the metabox data
2487 $eshb_booking_metaboxes = get_post_meta($post_id, 'eshb_booking_metaboxes', true) ?? [];
2488
2489
2490 // Validate the required fields
2491 if (empty($eshb_booking_metaboxes) || empty($eshb_booking_metaboxes['booking_status']) || empty($eshb_booking_metaboxes['order_id'])) {
2492 return;
2493 }
2494
2495 $booking_status = $post->post_status;
2496 $order_id = $eshb_booking_metaboxes['order_id'];
2497
2498 $eshb_settings = get_option('eshb_settings', []);
2499 $booking_type = isset($eshb_settings['booking-type']) && !empty($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : 'woocommerce';
2500
2501
2502 do_action('eshb_booking_status_changed', $post_id, $booking_status, $booking_type, $order_id);
2503
2504 if(class_exists('ESHB_Booking_Templates')) {
2505 return;
2506 }
2507 // send email for woocommerce
2508 if ( $booking_type == 'woocommerce' && function_exists( 'WC' ) ) {
2509
2510 $order = wc_get_order($order_id);
2511 if (!$order) {
2512 return; // Invalid order
2513 }
2514 // Trigger email manually
2515 $mailer = WC()->mailer();
2516
2517 switch ( $booking_status ) {
2518
2519 case 'cancelled':
2520 $to = $order->get_billing_email();
2521 $subject = sprintf(
2522 /* translators: %s: booking/post ID */
2523 __( 'Your Booking #%s has been Cancelled', 'easy-hotel' ),
2524 esc_html( $post_id )
2525 );
2526 $heading = __( 'Booking Cancelled', 'easy-hotel' );
2527
2528 $first_name = esc_html( $order->get_billing_first_name() );
2529 $post_title = esc_html( get_the_title( $post_id ) );
2530
2531 $message = '<p>' . sprintf(
2532 /* translators: %s: customer first name */
2533 __( 'Hi %s,', 'easy-hotel' ),
2534 $first_name
2535 ) . '</p>';
2536
2537 $message .= '<p>' . sprintf(
2538 /* translators: %s: booking title */
2539 __( 'We regret to inform you that your <strong>%s</strong> has been <strong>cancelled</strong>.', 'easy-hotel' ),
2540 $post_title
2541 ) . '</p>';
2542
2543 $message .= '<p>' . __( 'If you have any questions, please feel free to reply to this email.', 'easy-hotel' ) . '</p>';
2544 $message .= '<p>' . __( 'Thank you for shopping with us.', 'easy-hotel' ) . '</p>';
2545
2546 $this->send_email_woocommerce_style( $to, $subject, $heading, $message );
2547 break;
2548 }
2549
2550 }
2551 }
2552
2553 // -------------------------------------------------------------------------
2554 // Cart Blocking Methods
2555 // -------------------------------------------------------------------------
2556
2557 private function eshb_get_session_token() {
2558 // Native Checkout does not use a WooCommerce session — the visitor
2559 // is identified by the per-visitor reservation token instead. Use
2560 // that as the hold key so holds created during the native flow are
2561 // attributed to (and excludable for) the right user across the
2562 // booking form, calendar and checkout page.
2563 if ( function_exists( 'eshb_native_checkout_is_enabled' ) && eshb_native_checkout_is_enabled()
2564 && function_exists( 'eshb_native_checkout_get_token' ) ) {
2565 $native_token = eshb_native_checkout_get_token( false );
2566 if ( ! empty( $native_token ) ) {
2567 return 'native_' . $native_token;
2568 }
2569 }
2570 if ( class_exists( 'WooCommerce' ) && WC()->session ) {
2571 return (string) WC()->session->get_customer_id();
2572 }
2573 return '';
2574 }
2575
2576 private function eshb_dates_overlap( $s1, $e1, $s2, $e2 ) {
2577 return $s1 < $e2 && $s2 < $e1;
2578 }
2579
2580 private function eshb_get_blocking_settings() {
2581 $eshb_settings = get_option( 'eshb_settings', [] );
2582 $enabled = ! empty( $eshb_settings['cart-blocking-switcher'] );
2583 $blocking_time = ! empty( $eshb_settings['cart-blocking-time'] ) ? (int) $eshb_settings['cart-blocking-time'] : 5;
2584 return [ 'enabled' => $enabled, 'minutes' => $blocking_time ];
2585 }
2586
2587 private function eshb_set_cart_block( $accom_id, $start_date, $end_date, $room_qty ) {
2588 $cfg = $this->eshb_get_blocking_settings();
2589 if ( ! $cfg['enabled'] ) return 0;
2590
2591 $session = $this->eshb_get_session_token();
2592 if ( empty( $session ) ) return 0;
2593
2594 $key = 'eshb_cart_holds_' . (int) $accom_id;
2595 $holds = get_transient( $key );
2596 if ( ! is_array( $holds ) ) $holds = [];
2597
2598 $now = time();
2599 $until = $now + ( $cfg['minutes'] * 60 );
2600
2601 // Clean expired entries
2602 foreach ( $holds as $token => $hold ) {
2603 if ( $hold['until'] < $now ) unset( $holds[ $token ] );
2604 }
2605
2606 $holds[ $session ] = [
2607 'start_date' => $start_date,
2608 'end_date' => $end_date,
2609 'qty' => (int) $room_qty,
2610 'until' => $until,
2611 ];
2612
2613 set_transient( $key, $holds, $cfg['minutes'] * 60 + 60 );
2614 return $until;
2615 }
2616
2617 private function eshb_check_cart_block( $accom_id, $start_date, $end_date ) {
2618 $cfg = $this->eshb_get_blocking_settings();
2619 if ( ! $cfg['enabled'] ) return '';
2620
2621 $key = 'eshb_cart_holds_' . (int) $accom_id;
2622 $holds = get_transient( $key );
2623 if ( ! is_array( $holds ) ) return '';
2624
2625 $session = $this->eshb_get_session_token();
2626 $now = time();
2627
2628 foreach ( $holds as $token => $hold ) {
2629 if ( $token === $session ) continue;
2630 if ( $hold['until'] < $now ) continue;
2631 if ( $this->eshb_dates_overlap( $start_date, $end_date, $hold['start_date'], $hold['end_date'] ) ) {
2632 $remaining = (int) ceil( ( $hold['until'] - $now ) / 60 );
2633 /* translators: %d: remaining minutes */
2634 return sprintf( __( 'These dates are temporarily reserved by another user. Please try again in %d minute(s).', 'easy-hotel' ), $remaining );
2635 }
2636 }
2637 return '';
2638 }
2639
2640 /**
2641 * Remove the current visitor's hold for one accommodation, deleting the
2642 * transient entirely when no holds remain. Shared by the cart-removal,
2643 * order-completion and Native Checkout release paths.
2644 */
2645 private function eshb_remove_session_hold( $accom_id ) {
2646 $accom_id = (int) $accom_id;
2647 if ( ! $accom_id ) return;
2648
2649 $session = $this->eshb_get_session_token();
2650 if ( empty( $session ) ) return;
2651
2652 $key = 'eshb_cart_holds_' . $accom_id;
2653 $holds = get_transient( $key );
2654 if ( ! is_array( $holds ) ) return;
2655
2656 unset( $holds[ $session ] );
2657 if ( empty( $holds ) ) {
2658 delete_transient( $key );
2659 } else {
2660 $cfg = $this->eshb_get_blocking_settings();
2661 set_transient( $key, $holds, $cfg['minutes'] * 60 + 60 );
2662 }
2663 }
2664
2665 // -------------------------------------------------------------------------
2666 // Public cart-blocking API (used by the Native Checkout controller, which
2667 // is a separate class and short-circuits the WooCommerce add-to-cart flow)
2668 // -------------------------------------------------------------------------
2669
2670 /**
2671 * Place a temporary hold for a native reservation. Returns the unix
2672 * timestamp the hold expires at, or 0 when blocking is disabled.
2673 */
2674 public function eshb_block_dates_for_reservation( $accom_id, $start_date, $end_date, $room_qty ) {
2675 return $this->eshb_set_cart_block( $accom_id, $start_date, $end_date, $room_qty );
2676 }
2677
2678 /**
2679 * Whether the given dates collide with another visitor's active hold.
2680 * Returns a translated message on conflict, or '' when clear.
2681 */
2682 public function eshb_get_cart_block_conflict( $accom_id, $start_date, $end_date ) {
2683 return $this->eshb_check_cart_block( $accom_id, $start_date, $end_date );
2684 }
2685
2686 /**
2687 * Release the current visitor's hold for an accommodation (on
2688 * completed booking or expired/abandoned native reservation).
2689 */
2690 public function eshb_release_cart_block( $accom_id ) {
2691 $this->eshb_remove_session_hold( $accom_id );
2692 }
2693
2694 /**
2695 * Expiry timestamp (unix) of the current visitor's own hold for an
2696 * accommodation, or 0 when there is none. Lets the Native Checkout
2697 * page render an accurate countdown.
2698 */
2699 public function eshb_get_my_block_until( $accom_id ) {
2700 $accom_id = (int) $accom_id;
2701 if ( ! $accom_id ) return 0;
2702
2703 $holds = get_transient( 'eshb_cart_holds_' . $accom_id );
2704 if ( ! is_array( $holds ) ) return 0;
2705
2706 $session = $this->eshb_get_session_token();
2707 if ( empty( $session ) || empty( $holds[ $session ]['until'] ) ) return 0;
2708
2709 return (int) $holds[ $session ]['until'];
2710 }
2711
2712 /**
2713 * Expose the hold-countdown notice markup so the Native Checkout
2714 * template can render the same banner used by the WooCommerce flow.
2715 */
2716 public function eshb_cart_block_notice_html( $mode = 'inline' ) {
2717 return $this->eshb_get_blocking_notice_html( $mode );
2718 }
2719
2720 public function eshb_add_blocked_dates_to_calendar( $all_dates, $accom_id, $accom_metas ) {
2721 $key = 'eshb_cart_holds_' . (int) $accom_id;
2722 $holds = get_transient( $key );
2723
2724 $blocked_ranges = [];
2725 if ( is_array( $holds ) ) {
2726 $now = time();
2727 // Show every active hold as "Reserved" on the calendar,
2728 // including the current visitor's own. The native flow keys
2729 // holds by a cookie token that is reliably present on this
2730 // AJAX request, so skipping the own session here would hide
2731 // the held dates from the holder (whereas the WooCommerce
2732 // session token isn't consistently resolved here, so its
2733 // holds always showed). Own-hold exclusion is kept only in
2734 // the booking-validation path so the holder can still finish
2735 // their own reservation.
2736 foreach ( $holds as $token => $hold ) {
2737 if ( $hold['until'] < $now ) continue;
2738 $blocked_ranges[] = [
2739 'start_date' => $hold['start_date'],
2740 'end_date' => $hold['end_date'],
2741 'until' => $hold['until'],
2742 ];
2743 }
2744 }
2745
2746 $all_dates['blocked_dates'] = $blocked_ranges;
2747 return $all_dates;
2748 }
2749
2750 public function eshb_get_cart_blocks_ajax() {
2751 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2752
2753 $accom_id = isset( $_POST['accomodation_id'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['accomodation_id'] ) ) : 0;
2754 if ( ! $accom_id ) {
2755 wp_send_json_error( [ 'message' => 'Invalid accommodation ID' ] );
2756 return;
2757 }
2758
2759 $key = 'eshb_cart_holds_' . $accom_id;
2760 $holds = get_transient( $key );
2761
2762 $blocked_ranges = [];
2763 if ( is_array( $holds ) ) {
2764 $now = time();
2765 // Include every active hold (own included) so the calendar
2766 // renders the held dates as "Reserved" consistently across
2767 // the WooCommerce and Native Checkout flows. See the note in
2768 // eshb_add_blocked_dates_to_calendar().
2769 foreach ( $holds as $token => $hold ) {
2770 if ( $hold['until'] < $now ) continue;
2771 $blocked_ranges[] = [
2772 'start_date' => $hold['start_date'],
2773 'end_date' => $hold['end_date'],
2774 'until' => $hold['until'],
2775 ];
2776 }
2777 }
2778
2779 wp_send_json_success( [ 'blocked_ranges' => $blocked_ranges ] );
2780 }
2781
2782 public function eshb_clear_cart_block_on_removal( $cart_item_key, $cart ) {
2783 $removed = isset( $cart->removed_cart_contents[ $cart_item_key ] ) ? $cart->removed_cart_contents[ $cart_item_key ] : null;
2784 if ( ! $removed ) return;
2785
2786 $accom_id = isset( $removed['accomodation_id'] ) ? (int) $removed['accomodation_id'] : 0;
2787 if ( ! $accom_id ) return;
2788
2789 $this->eshb_remove_session_hold( $accom_id );
2790 }
2791
2792 public function eshb_render_calendar_legend( $accomodation_id = 0 ) {
2793 static $rendered = false;
2794 if ( $rendered || is_admin() ) return;
2795 $rendered = true;
2796
2797 $eshb_settings = get_option( 'eshb_settings', [] );
2798 $cart_blocking_on = ! empty( $eshb_settings['cart-blocking-switcher'] );
2799
2800 $items = [
2801 'available' => [ 'class' => 'eshb-legend-available', 'label' => __( 'Available', 'easy-hotel' ) ],
2802 'selected' => [ 'class' => 'eshb-legend-selected', 'label' => __( 'Selected', 'easy-hotel' ) ],
2803 'booked' => [ 'class' => 'eshb-legend-booked', 'label' => __( 'Booked', 'easy-hotel' ) ],
2804 'holiday' => [ 'class' => 'eshb-legend-holiday', 'label' => __( 'Holiday', 'easy-hotel' ) ],
2805 ];
2806
2807 if ( $cart_blocking_on ) {
2808 $items['reserved'] = [ 'class' => 'eshb-legend-reserved', 'label' => __( 'Reserved', 'easy-hotel' ) ];
2809 }
2810
2811 $items = apply_filters( 'eshb_calendar_legend_items', $items, $accomodation_id );
2812 ?>
2813 <div class="eshb-calendar-legend">
2814 <?php foreach ( $items as $item ) : ?>
2815 <div class="eshb-legend-item">
2816 <span class="eshb-legend-swatch <?php echo esc_attr( $item['class'] ); ?>"></span>
2817 <span class="eshb-legend-label"><?php echo esc_html( $item['label'] ); ?></span>
2818 </div>
2819 <?php endforeach; ?>
2820 </div>
2821 <?php
2822 }
2823
2824 private function eshb_get_blocking_notice_html( $mode = 'fixed' ) {
2825 $eshb_settings = get_option( 'eshb_settings', [] );
2826 if ( empty( $eshb_settings['cart-blocking-switcher'] ) ) return '';
2827
2828 $msg = ! empty( $eshb_settings['cart-blocking-notice-msg'] ) ? esc_html( $eshb_settings['cart-blocking-notice-msg'] ) : esc_html__( 'Your reservation is held for', 'easy-hotel' );
2829
2830 $class = $mode === 'inline'
2831 ? 'woocommerce-info eshb-cart-block-notice eshb-cart-block-notice--inline'
2832 : 'eshb-cart-block-notice eshb-cart-block-notice--fixed';
2833
2834 return '<div id="eshb-cart-block-notice" class="' . $class . '" data-mode="' . esc_attr( $mode ) . '" style="display:none;" aria-live="polite">'
2835 . '&#x23F3; <span class="eshb-block-msg">' . $msg . '</span> <span class="eshb-block-timer">0:00</span>'
2836 . '</div>';
2837 }
2838
2839 public function eshb_render_cart_block_notice_inline() {
2840 if ( $this->eshb_notice_printed || is_admin() ) return;
2841 $this->eshb_notice_printed = true;
2842 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2843 echo $this->eshb_get_blocking_notice_html( 'inline' );
2844 }
2845
2846 public function eshb_prepend_notice_to_woo_block( $block_content, $block ) {
2847 if ( $this->eshb_notice_printed || is_admin() ) return $block_content;
2848 if ( ! in_array( $block['blockName'], [ 'woocommerce/cart', 'woocommerce/checkout' ], true ) ) return $block_content;
2849 $html = $this->eshb_get_blocking_notice_html( 'inline' );
2850 if ( ! $html ) return $block_content;
2851 $this->eshb_notice_printed = true;
2852 // Append for checkout to avoid breaking React hydration; prepend for cart
2853 return $block['blockName'] === 'woocommerce/checkout'
2854 ? $block_content . $html
2855 : $html . $block_content;
2856 }
2857
2858 public function eshb_footer_notice_fallback() {
2859 if ( $this->eshb_notice_printed || is_admin() ) return;
2860 if ( ! is_cart() && ! is_checkout() ) return;
2861 $html = $this->eshb_get_blocking_notice_html( 'inline' );
2862 if ( ! $html ) return;
2863 $this->eshb_notice_printed = true;
2864 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2865 echo $html;
2866 }
2867
2868 public function eshb_clear_cart_on_block_expiry() {
2869 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2870 if ( class_exists( 'WooCommerce' ) && WC()->cart ) {
2871 WC()->cart->empty_cart();
2872 }
2873 wp_send_json_success( [ 'cleared' => true ] );
2874 }
2875
2876 public function eshb_check_my_session_blocks() {
2877 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2878
2879 $accom_ids = isset( $_POST['accom_ids'] ) ? array_map( 'intval', (array) $_POST['accom_ids'] ) : [];
2880 if ( empty( $accom_ids ) ) {
2881 wp_send_json_success( [ 'active_ids' => [] ] );
2882 return;
2883 }
2884
2885 $session = $this->eshb_get_session_token();
2886 $now = time();
2887 $active_ids = [];
2888
2889 foreach ( $accom_ids as $accom_id ) {
2890 if ( ! $accom_id ) continue;
2891 $holds = get_transient( 'eshb_cart_holds_' . $accom_id );
2892 if ( is_array( $holds ) && isset( $holds[ $session ] ) && $holds[ $session ]['until'] > $now ) {
2893 $active_ids[] = $accom_id;
2894 }
2895 }
2896
2897 wp_send_json_success( [ 'active_ids' => $active_ids ] );
2898 }
2899
2900 public function eshb_clear_session_cart_blocks_on_order( $order_id ) {
2901 if ( ! $order_id ) return;
2902
2903 $session = $this->eshb_get_session_token();
2904 if ( empty( $session ) ) return;
2905
2906 $order = wc_get_order( $order_id );
2907 if ( ! $order ) return;
2908
2909 foreach ( $order->get_items() as $item ) {
2910 $accom_id = (int) $item->get_meta( 'accomodation_id' );
2911 if ( ! $accom_id ) continue;
2912
2913 $this->eshb_remove_session_hold( $accom_id );
2914 }
2915 }
2916
2917 public function eshb_checkout_order_review_qty_input( $qty_html, $cart_item, $cart_item_key ) {
2918 if ( ! is_checkout() ) return $qty_html;
2919 $eshb_settings = get_option( 'eshb_settings', [] );
2920 if ( empty( $eshb_settings['direct-booking'] ) ) return $qty_html;
2921 $nonce = wp_create_nonce( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ) );
2922 $ajaxurl = esc_url( admin_url( 'admin-ajax.php' ) );
2923 return $qty_html
2924 . ' <button type="button" class="eshb-cart-remove-btn" aria-label="' . esc_attr__( 'Remove', 'easy-hotel' ) . '" data-cart-key="' . esc_attr( $cart_item_key ) . '" data-nonce="' . esc_attr( $nonce ) . '" data-ajaxurl="' . esc_attr( $ajaxurl ) . '"><i class="fas fa-times" aria-hidden="true"></i></button>';
2925 }
2926
2927 public function eshb_remove_cart_item() {
2928 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2929 $cart_item_key = sanitize_text_field( wp_unslash( $_POST['cart_item_key'] ?? '' ) );
2930 if ( $cart_item_key && class_exists( 'WooCommerce' ) && WC()->cart ) {
2931 WC()->cart->remove_cart_item( $cart_item_key );
2932 }
2933 wp_send_json_success();
2934 }
2935
2936 }
2937 ESHB_Booking::instance();
2938
2939 // Registered once on the singleton (constructor runs multiple times due to `new ESHB_Booking()` calls elsewhere)
2940 add_filter( 'woocommerce_checkout_cart_item_quantity', [ ESHB_Booking::instance(), 'eshb_checkout_order_review_qty_input' ], 10, 3 );