PluginProbe
Easy Hotel – Powerful Hotel Booking / 2.0.4
Easy Hotel – Powerful Hotel Booking v2.0.4
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.4, at admin/includes/classes/class.booking.php

2,974 lines 110.3 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 return;
1027 }
1028
1029 // Check if the booking post has already been created
1030 if (get_post_meta($order_id, '_booking_post_created', true)) {
1031 return; // Exit if the booking post has already been created
1032 }
1033
1034 // Get the order object
1035 $order = wc_get_order($order_id);
1036
1037 if (!$order) {
1038 return;
1039 }
1040
1041 $eshb_settings = get_option('eshb_settings', []);
1042
1043 // Get the order object
1044 $order = wc_get_order($order_id);
1045 $order_status = $order->get_status();
1046 $booking_status = $order_status;
1047
1048 // A deposit order still has a balance owed, so auto-approval must
1049 // NOT mark it completed — it has to stay 'deposit-payment' until the
1050 // remaining balance is collected. Detect a deposit by the captured
1051 // deposit meta (set only on the deposit path, before this runs) or
1052 // the deposit-payment status. A FULL payment has neither, so it must
1053 // auto-complete normally.
1054 $is_deposit_order = ( (float) get_post_meta( $order_id, 'initial_deposit', true ) > 0 )
1055 || ( 'deposit-payment' === $order->get_status() );
1056
1057 if ( $order && $order->is_paid() && $order->get_status() !== 'completed' && ! $is_deposit_order && isset($eshb_settings['booking-auto-approval']) && $eshb_settings['booking-auto-approval'] == true) {
1058 $order->update_status('completed', 'Payment successful, status updated via code.');
1059 $booking_status = 'completed';
1060 }
1061
1062 $first_name = $order->get_billing_first_name();
1063 $last_name = $order->get_billing_last_name();
1064 $customer_name = trim($first_name . ' ' . $last_name); // Concatenate first and last name
1065 $customer_email = $order->get_billing_email(); // Get customer email
1066 $customer_phone = $order->get_billing_phone(); // Get customer phone number
1067
1068
1069
1070 // Loop through order items
1071 foreach ($order->get_items() as $item_id => $item) {
1072
1073 // Retrieve meta data saved in the order item
1074 $accomodation_id = $item->get_meta('Accomodation ID');
1075 $accomodation_title = get_the_title( $accomodation_id );
1076 $room_quantity = $item->get_meta('Rooms');
1077 $extra_bed_quantity = $item->get_meta('Extra Bed');
1078 $adult_quantity = $item->get_meta('Adults');
1079 $children_quantity = $item->get_meta('Children');
1080 $start_date = $item->get_meta('Start Date');
1081 $end_date = $item->get_meta('End Date');
1082 $dates = $item->get_meta('Date');
1083 $times = $item->get_meta('Time');
1084 $start_time = $item->get_meta('Start Time');
1085 $end_time = $item->get_meta('End Time');
1086 $details_html = $item->get_meta('Details');
1087 $extra_services = $item->get_meta('Extra Services IDs');
1088 $extra_services_html = $item->get_meta('Extra Services');
1089 $base_price = $item->get_meta('Base Price');
1090 $extra_services_charge = $item->get_meta('Extra Service Price');
1091 $extra_bed_price = $item->get_meta('Extra Bed Price');
1092 $total_without_discount = $item->get_meta('Subtotal Price');
1093 $total_price = $item->get_meta('Total Price');
1094
1095 // Prepare the data array to store in post meta
1096 $cart_item_data = [
1097 'booking_status' => $booking_status,
1098 'order_id' => $order_id,
1099 'booking_accomodation_id' => $accomodation_id,
1100 'subtotal_price' => $order->get_subtotal(),
1101 'total_price' => $total_price,
1102 'total_paid' => 0,
1103 'base_price' => $base_price,
1104 'extra_service_price' => $extra_services_charge,
1105 'extra_bed_price' => $extra_bed_price,
1106 'booking_start_date' => $start_date,
1107 'booking_end_date' => $end_date,
1108 'booking_start_time' => !empty($start_time) ? $start_time : '10:00',
1109 'booking_end_time' => !empty($end_time) ? $end_time : '22:00',
1110 'dates' => $dates,
1111 'room_quantity' => $room_quantity,
1112 'extra_bed_quantity' => $extra_bed_quantity,
1113 'adult_quantity' => $adult_quantity,
1114 'children_quantity' => $children_quantity,
1115 'extra_services' => $extra_services,
1116 'start_time' => !empty($start_time) ? $start_time : '10:00',
1117 'end_time' => !empty($end_time) ? $end_time : '22:00',
1118 'times' => $times,
1119 'details_html' => $details_html,
1120 'extra_services_html' => $extra_services_html,
1121 ];
1122
1123 // filter for $cart_item_data;
1124 $cart_item_data = apply_filters( 'on_booking_cart_item_data', $cart_item_data );
1125
1126 // Insert the booking
1127 if($accomodation_id && !empty($accomodation_id)) {
1128 ESHB_Helper::eshb_insert_booking($order_id, $booking_status, $cart_item_data);
1129 }
1130
1131
1132 }
1133
1134 }
1135
1136 public function display_custom_meta_in_cart_and_order($item_data, $cart_item) {
1137
1138 if (!empty($cart_item['discount']) && is_cart()) {
1139 $discount = $cart_item['discount'];
1140 $currency_symbol = $cart_item['currency_symbol'];
1141
1142 $item_data[] = array(
1143 'key' => __('Save', 'easy-hotel'),
1144 'value' => wc_price($discount)
1145 );
1146 }
1147
1148 if (isset($cart_item['dates'])) {
1149
1150 $dates = $cart_item['dates'];
1151
1152 $item_data[] = array(
1153 'key' => __('Date', 'easy-hotel'),
1154 'value' => esc_html( $dates )
1155 );
1156 }
1157
1158 if (!empty($cart_item['times'])) {
1159
1160 $times = $cart_item['times'];
1161
1162 $item_data[] = array(
1163 'key' => __('Time', 'easy-hotel'),
1164 'value' => esc_html( $times )
1165 );
1166 }
1167
1168
1169 if (!empty($cart_item['night'])) {
1170
1171 $night = $cart_item['night'];
1172 $night_label = esc_html__('Night', 'easy-hotel');
1173 if ($night > 1) {
1174 $night_label = esc_html__('Nights', 'easy-hotel');
1175 }
1176
1177 $item_data[] = array(
1178 'key' => $night_label,
1179 'value' => esc_html( $night )
1180 );
1181 }
1182
1183
1184
1185 if (isset($cart_item['details_html'])) {
1186
1187 $details_html = $cart_item['details_html'];
1188
1189 $item_data[] = array(
1190 'key' => __('Details', 'easy-hotel'),
1191 'value' => esc_html( $details_html )
1192 );
1193 }
1194
1195 $extra_services_ids = isset($cart_item['extra_services']) ? $cart_item['extra_services'] : '';
1196
1197 if(!empty($extra_services_ids)){
1198 if (isset($cart_item['extra_services_html'])) {
1199
1200 $extra_services_html = $cart_item['extra_services_html'];
1201
1202 $item_data[] = array(
1203 'key' => __('Extra Services', 'easy-hotel'),
1204 'value' => esc_html( $extra_services_html )
1205 );
1206 }
1207 }
1208
1209 // make unique item data
1210 $item_data = array_unique($item_data, SORT_REGULAR);
1211
1212 return $item_data;
1213
1214 }
1215
1216 public function hide_meta_from_display($hidden_meta_keys) {
1217 // Add meta keys to hide
1218 $keys_to_hide = array(
1219 'Accomodation ID',
1220 'Subtotal Price',
1221 'Total Price',
1222 'Base Price',
1223 'Extra Services Charge',
1224 'Extra Bed Price',
1225 'Rooms',
1226 'Extra Bed',
1227 'Adults',
1228 'Children',
1229 'Start Date',
1230 'End Date',
1231 'Start Time',
1232 'End Time',
1233 'Save'
1234 );
1235
1236 return array_merge($hidden_meta_keys, $keys_to_hide);
1237 }
1238
1239 public function unset_specific_order_item_meta_data($formatted_meta, $item){
1240
1241 $keys_to_hide = array(
1242 'Accomodation ID',
1243 'Subtotal Price',
1244 'Total Price',
1245 'Base Price',
1246 'Extra Services Charge',
1247 'Extra Bed Price',
1248 'Rooms',
1249 'Extra Bed',
1250 'Adults',
1251 'Children',
1252 'Start Date',
1253 'End Date',
1254 'Start Time',
1255 'End Time',
1256 'Save'
1257 );
1258
1259 foreach($formatted_meta as $key => $meta){
1260 if(in_array($meta->key, $keys_to_hide)) {
1261 unset($formatted_meta[$key]);
1262 }
1263 }
1264 return $formatted_meta;
1265 }
1266
1267 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 = '') {
1268 if (empty($accomodation_id)) return;
1269
1270 $accomodation_id = (int) $accomodation_id;
1271 $total_guest_quantity = (int) $adult_quantity + (int) $children_quantity;
1272 $start = new DateTime($start_date);
1273 $end = new DateTime($end_date);
1274 $days_count = max(1, $start->diff($end)->days);
1275 $hours_count = ESHB_Helper::eshb_calculate_time_diff($start_date, $end_date, $start_time, $end_time);
1276 $today = new DateTime('now', new DateTimeZone('GMT'));
1277 $today_date = $today->format('Y-m-d');
1278 $tomorrow = $today->modify('+1 day')->format('Y-m-d');
1279 $hotel_core = new ESHB_Core();
1280 $metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
1281 $allowed_check_in_day = apply_filters( 'eshb_booking_allowed_check_in_day', 'all', $accomodation_id, $metaboxes );
1282 $single_day_price = 0;
1283
1284 // Handle week-based or same-day bookings
1285 if (!empty($allowed_check_in_day) && $allowed_check_in_day !== 'all') {
1286 $days_count = 1;
1287 }
1288
1289 $pricing_periodicity = false;
1290
1291 if ($start_date === $end_date) {
1292 $days_count = 1;
1293 $pricing_periodicity = apply_filters( 'eshb_pricing_periodicity', false, $accomodation_id, $metaboxes );
1294 $single_day_price = apply_filters( 'eshb_single_day_price', 0, $accomodation_id);
1295 }
1296
1297
1298 // Base price
1299 $base_price = 0;
1300 $base_price = $hotel_core->get_eshb_day_wise_price($start_date, $end_date, $accomodation_id, true, $days_count, $adult_quantity, $children_quantity);
1301 $base_price = !empty($single_day_price) ? $single_day_price : $base_price;
1302 $has_session_price = $hotel_core->has_upcoming_or_current_session_price($accomodation_id, $start_date, $end_date, $days_count, $adult_quantity, $children_quantity);
1303
1304 if($has_session_price){
1305 $session_price = $hotel_core->get_eshb_price_by_session($accomodation_id, $start_date, $end_date, $days_count, $adult_quantity, $children_quantity);
1306 }
1307
1308 $base_price = !empty($session_price) && $session_price > 0 ? $session_price : $base_price;
1309 $regular_base_price = $hotel_core->get_eshb_day_wise_price($start_date, $end_date, $accomodation_id, true, $days_count, $adult_quantity, $children_quantity);
1310
1311 // Pricing logic
1312 $bed_price = (int) ($metaboxes['extra_bed_price'] ?? 0);
1313 $pricing_type = $metaboxes['pricing_type'] ?? 'room_wise';
1314
1315
1316 $pricing_base = $pricing_type === 'room_wise'
1317 ? $base_price * $room_quantity
1318 : $base_price * $total_guest_quantity;
1319
1320
1321 $regular_pricing_base = $pricing_type === 'room_wise'
1322 ? $regular_base_price * $room_quantity
1323 : $regular_base_price * $total_guest_quantity;
1324
1325
1326 $extra_bed_price = $extra_bed_quantity > 0
1327 ? $extra_bed_quantity * $bed_price * $days_count
1328 : 0;
1329
1330
1331
1332 $subtotal_price = $pricing_base + $extra_bed_price;
1333 $regular_subtotal_price = $regular_pricing_base + $extra_bed_price;
1334
1335 if($pricing_periodicity && !empty($hours_count)){
1336 $subtotal_price = $pricing_periodicity == 'per_hour' ? $subtotal_price * (int) $hours_count : $subtotal_price;
1337 $regular_subtotal_price = $pricing_periodicity == 'per_hour' ? $regular_subtotal_price * (int) $hours_count : $regular_subtotal_price;
1338 }
1339
1340
1341 // Extra services
1342 $extra_services_charge = 0;
1343
1344 if (!empty($selected_services) && is_array($selected_services)) {
1345 $extra_services_charge = array_reduce($selected_services, function ($carry, $service) use ($days_count, $room_quantity, $total_guest_quantity) {
1346 $service_id = !empty($service['id']) ? $service['id'] : 0;
1347 $service_quantity = !empty($service['quantity']) ? $service['quantity'] : 0;
1348
1349 if(empty($service_id) || empty($service_quantity)) {
1350 return $carry;
1351 }
1352
1353 $meta = get_post_meta($service_id, 'eshb_service_metaboxes', true);
1354 $price = $meta['service_price'] ?? 0;
1355 $periodicity = $meta['service_periodicity'] ?? 'once';
1356 $charge_type = $meta['service_charge_type'] ?? 'room';
1357
1358 //$single_price = $price * $service_quantity;
1359 $single_price = $price ? $price : 0;
1360
1361 if ($periodicity == 'per_day') {
1362 $single_price *= $days_count;
1363 }
1364
1365 if($charge_type == 'room'){
1366 $single_price *= $room_quantity;
1367 }else{
1368 $single_price *= $service_quantity;
1369 }
1370
1371 return $carry + floatval($single_price);
1372 }, 0);
1373 }
1374
1375
1376 $eshb_settings = get_option('eshb_settings');
1377 $booking_type = isset($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : '';
1378
1379 $regular_subtotal_price = $regular_subtotal_price + $extra_services_charge;
1380 $regular_total_price = $regular_subtotal_price;
1381
1382 $subtotal_price = $subtotal_price + $extra_services_charge;
1383 $total_price = $subtotal_price;
1384
1385 $discount = $total_price < $regular_total_price ? abs( (float) $regular_total_price - (float) $total_price) : 0;
1386
1387 $currency_symbol = $hotel_core->get_eshb_currency_symbol();
1388 $currency_symbol = html_entity_decode($currency_symbol);
1389 $currency_position = $hotel_core->get_eshb_currency_position();
1390
1391
1392 $extra_bed_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $extra_bed_price, $currency_converter);
1393 $extra_services_charge = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $extra_services_charge, $currency_converter);
1394 $base_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $base_price, $currency_converter);
1395 $regular_base_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $regular_base_price, $currency_converter);
1396 $subtotal_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $subtotal_price, $currency_converter);
1397 $regular_subtotal_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $regular_subtotal_price, $currency_converter);
1398 $total_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $total_price, $currency_converter);
1399 $regular_total_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $regular_total_price, $currency_converter);
1400 $single_day_price = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $single_day_price, $currency_converter);
1401 $discount = apply_filters( 'eshb_apply_currency_converter_on_pricing_calculation', $discount, $currency_converter);
1402
1403
1404 $single_day_price_html = $hotel_core->eshb_price( $single_day_price );
1405 $regular_base_price_html = $hotel_core->eshb_price( $regular_base_price );
1406 $base_price_html = $hotel_core->eshb_price( $base_price );
1407 $extra_bed_price_html = $hotel_core->eshb_price( $extra_bed_price );
1408 $extra_services_charge_html = $hotel_core->eshb_price( $extra_services_charge );
1409 $subtotal_price_html = $hotel_core->eshb_price( $subtotal_price );
1410 $regular_subtotal_price_html = $hotel_core->eshb_price( $regular_subtotal_price );
1411 $total_price_html = $hotel_core->eshb_price( $total_price );
1412 $regular_total_price_html = $hotel_core->eshb_price( $regular_total_price );
1413 $discount_html = $hotel_core->eshb_price( $discount );
1414
1415 $is_wc_price = ($booking_type === 'woocommerce' && class_exists('WooCommerce')) ? true : false;
1416
1417 return [
1418 'startDate' => $start_date,
1419 'endDate' => $end_date,
1420 'today' => $today_date,
1421 'tomorrow' => $tomorrow,
1422 'daysCount' => $days_count,
1423 'singleDayPrice' => $single_day_price,
1424 'extraServicesPrice' => $extra_services_charge,
1425 'extraBedPrice' => $extra_bed_price,
1426 'basePrice' => $base_price,
1427 'regularBasePrice' => $regular_base_price,
1428 'subtotalPrice' => $subtotal_price,
1429 'regularSubtotalPrice'=> $regular_subtotal_price,
1430 'totalPrice' => $total_price,
1431 'regularTotalPrice' => $regular_total_price,
1432 'discount' => $discount,
1433 'currencySymbol' => $currency_symbol,
1434 'currencyPosition' => $currency_position,
1435
1436 'singleDayPriceHtml' => $single_day_price_html,
1437 'extraServicesPriceHtml' => $extra_services_charge_html,
1438 'extraBedPriceHtml' => $extra_bed_price_html,
1439 'basePriceHtml' => $base_price_html,
1440 'regularBasePriceHtml' => $regular_base_price_html,
1441 'subtotalPriceHtml' => $subtotal_price_html,
1442 'regularSubtotalPriceHtml'=> $regular_subtotal_price_html,
1443 'totalPriceHtml' => $total_price_html,
1444 'regularTotalPriceHtml' => $regular_total_price_html,
1445 'discountHtml' => $discount_html,
1446
1447 'isWcPrice' => $is_wc_price
1448 ];
1449 }
1450
1451 public function eshb_add_to_cart_reservation(){
1452
1453 // Verify nonce for security
1454 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
1455
1456 $error = array(
1457 'code' => 'invalid_nonce',
1458 'message' => __('invalid_nonce', 'easy-hotel')
1459 );
1460
1461 wp_send_json_error([
1462 'error' => $error,
1463 ]);
1464
1465 die();
1466 }
1467
1468
1469 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId'])){
1470
1471 $hotel_core = new ESHB_Core();
1472 $accomodation_id = isset( $_POST['accomodationId'] ) ? (int) sanitize_text_field( wp_unslash($_POST['accomodationId']) ) : '';
1473 $today_date = esc_html(gmdate('Y-m-d')); // Get today's date
1474 $date = new DateTime($today_date); // Create a DateTime object from today's date
1475 $date->modify('+1 day'); // Add one day
1476 $tomorrow = $date->format('Y-m-d'); // Get the new date in 'Y-m-d' format
1477 $start_date = isset( $_POST['startDate'] ) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : $today_date;
1478 $end_date = isset( $_POST['endDate'] ) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : $tomorrow;
1479 $start_day_name = $start_date ? strtolower(gmdate('l', strtotime($start_date))) : '';
1480 $start_time = isset( $_POST['startTime'] ) ? sanitize_text_field( wp_unslash($_POST['startTime']) ) : '';
1481 $end_time = isset( $_POST['endTime'] ) ? sanitize_text_field( wp_unslash($_POST['endTime']) ) : '';
1482 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
1483
1484 $start = new DateTime($start_date);
1485 $end = new DateTime($end_date);
1486 $interval = $start->diff($end);
1487 $days_count = $interval->days;
1488
1489 $room_quantity = isset( $_POST['roomQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['roomQuantity'] ) ) : 1;
1490 $extra_bed_quantity = isset( $_POST['extraBedQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['extraBedQuantity'] ) ) : 0;
1491 $adult_quantity = isset( $_POST['adultQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['adultQuantity'] ) ) : 1;
1492 $children_quantity = isset( $_POST['childrenQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['childrenQuantity'] ) ) : 0;
1493 $total_guest_quantity = $adult_quantity + $children_quantity;
1494
1495 $selected_services = [];
1496 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
1497
1498 $selected_services = sanitize_text_field(wp_unslash($_POST['selectedServices']));
1499 $selected_services = json_decode($selected_services, true);
1500
1501 }
1502
1503 $eshb_settings = get_option( 'eshb_settings', [] );
1504 $booking_type = isset($eshb_settings['booking-type']) && !empty($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : 'woocommerce';
1505 $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.';
1506 $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.';
1507 $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:';
1508 $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!';
1509 $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!';
1510 $room_visibility = in_array('rooms', $eshb_settings['booking-form-fields']) ? true : false;
1511 $adult_visibility = in_array('adults', $eshb_settings['booking-form-fields']) ? true : false;
1512 $childrens_visibility = in_array('childrens', $eshb_settings['booking-form-fields']) ? true : false;
1513 $extra_beds_visibility = in_array('extra_beds', $eshb_settings['booking-form-fields']) ? true : false;
1514
1515 $eshb_week_settings = [
1516 'string_check_in_day_error_msg' => 'Only allowed check in day is',
1517 'eshb_booking_allowed_check_in_day' => 'all',
1518 ];
1519 $eshb_week_settings = apply_filters( 'eshb_week_settings', $eshb_week_settings );
1520 $allowed_check_in_day = apply_filters( 'eshb_booking_allowed_check_in_day', 'all', $accomodation_id, $accomodation_metaboxes );
1521 $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 : ';
1522
1523 // Min Max Booking Configurations
1524 $min_max_settings = [
1525 'required_min_nights' => '',
1526 'required_max_nights' => '',
1527 'is_global_source_for_min_max' => true,
1528 ];
1529 $min_max_settings = apply_filters( 'eshb_min_max_settings', $min_max_settings, $accomodation_id, $accomodation_metaboxes );
1530 $required_min_nights = $start_date !== $end_date && isset($min_max_settings['required_min_nights']) ? $min_max_settings['required_min_nights'] : '';
1531 $required_max_nights = $start_date !== $end_date && isset($min_max_settings['required_max_nights']) ? $min_max_settings['required_max_nights'] : '';
1532 $string_required_minimum_nights_msg = esc_html__( 'Ops! This Reservation has been failed. Requried Minumum', 'easy-hotel' );
1533 $string_required_maximum_nights_msg = esc_html__( 'Ops! This Reservation has been failed. Requried Maximum', 'easy-hotel' );
1534 $pricing_periodicity = apply_filters( 'eshb_pricing_periodicity', false, $accomodation_id, $accomodation_metaboxes );
1535
1536 $min_stay_night_by_session = ESHB_Helper::get_eshb_min_stay_night_by_session($accomodation_id, $start_date, $end_date);
1537
1538 // Validate booking type
1539 if ( $booking_type == 'woocommerce' && ! class_exists( 'WooCommerce' ) ) {
1540 $error = array(
1541 'code' => 'woocommerce_not_activated',
1542 'message' => esc_html__( 'Ops! Woocommerce not activated. Activate Woocommerce.', 'easy-hotel'),
1543 );
1544 wp_send_json_error( [ 'error' => $error ] );
1545
1546 } elseif ( $booking_type == 'surecart' && ! class_exists( 'SureCart' ) ) {
1547 $error = array(
1548 'code' => 'surecart_not_activated',
1549 'message' => esc_html__( 'Ops! Surecart not activated. Activate Surecart.', 'easy-hotel' ),
1550 );
1551 wp_send_json_error( [ 'error' => $error ] );
1552
1553 } elseif ( $booking_type == 'surecart' && ! class_exists( 'ESHB_SURECART' ) ) {
1554 $error = array(
1555 'code' => 'surecart_addon_not_activated',
1556 'message' => esc_html__( 'Ops! EHB Surecart addons not activated. Activate EHB Surecart.', 'easy-hotel' ),
1557 );
1558 wp_send_json_error( [ 'error' => $error ] );
1559 }
1560
1561
1562 $available_times = ESHB_Helper::get_available_times_by_date($accomodation_id, $start_date);
1563 $is_valid_time = false;
1564
1565 if($pricing_periodicity == 'per_hour' && (!empty($accomodation_metaboxes['single_day_price']) || !empty($accomodation_metaboxes['single_day_sale_price'])) && !empty($available_times)){
1566 $available_slots = isset($available_times['available_slots']) ? $available_times['available_slots'] : [];
1567
1568 if(count($available_slots) > 0) {
1569 foreach ($available_slots as $slot) {
1570 if ($start_time >= $slot[0] && $end_time <= $slot[1]) {
1571 $is_valid_time = true;
1572 break;
1573 }
1574 }
1575 }else{
1576 $is_valid_time = false;
1577 }
1578
1579 if(!$is_valid_time){
1580 $error = array('code' => 'invalid_time', 'message' => esc_html($string_time_error_msg));
1581 wp_send_json_error([
1582 'error' => $error,
1583 ]);
1584 }
1585 }
1586
1587 $actual_booked_dates = $this->get_actual_booked_dates_in_date_ranges($accomodation_id, $start_date, $end_date);
1588 $booked_dates_str = '';
1589 if(!empty($actual_booked_dates)){
1590
1591 $error_message = sprintf(
1592 /* translators: 1: already booked message text, 2: list of booked dates */
1593 esc_html__( '%1$s %2$s', 'easy-hotel' ),
1594 esc_html( $string_already_booked_msg ),
1595 $booked_dates_str
1596 );
1597
1598 $error = array(
1599 'code' => 'already_booked',
1600 'message' => $error_message,
1601 );
1602 wp_send_json_error([
1603 'error' => $error,
1604 'booked_dates' => implode(', ', $actual_booked_dates),
1605 ]);
1606
1607 }
1608
1609 $holiday_dates = $this->get_holiday_dates_in_date_ranges($accomodation_id, $start_date, $end_date);
1610
1611 if(!empty($holiday_dates)){
1612 $error = array('code' => 'holiday_dates', 'message' => esc_html__('Selected date range is not available. These dates are in holidays.', 'easy-hotel'));
1613 wp_send_json_error([
1614 'error' => $error,
1615 'holiday_dates' => implode(', ', $holiday_dates),
1616 ]);
1617 }
1618
1619 // Check cart blocking (temporary holds by other users)
1620 $cart_block_msg = $this->eshb_check_cart_block($accomodation_id, $start_date, $end_date);
1621 if (!empty($cart_block_msg)) {
1622 $error = array('code' => 'cart_blocked', 'message' => esc_html($cart_block_msg));
1623 wp_send_json_error(['error' => $error]);
1624 }
1625
1626 // check rooms capacity
1627 if(!$is_valid_time && isset($_POST['roomQuantity']) && !empty($_POST['roomQuantity'])){
1628 $roomQuantity = isset( $_POST['roomQuantity'] ) ? (int) sanitize_text_field( wp_unslash($_POST['roomQuantity'] ) ) : 1;
1629
1630 $availableRoom = $this->get_available_room_count_by_date_range($accomodation_id, $start_date, $end_date);
1631
1632 if($roomQuantity > $availableRoom){
1633 $error = array(
1634 'code' => 'room_capacity_not_enough',
1635 'message' => sprintf(
1636 /* translators: %s: number of available rooms */
1637 esc_html__( 'Selected room is not available. Available room: %s', 'easy-hotel' ),
1638 esc_html( $availableRoom )
1639 ),
1640 );
1641 wp_send_json_error([
1642 'error' => $error,
1643 ]);
1644 }
1645 }
1646
1647
1648 // Validate check-in day
1649 if($allowed_check_in_day != 'all' && $start_day_name != '' && !empty($allowed_check_in_day) && $allowed_check_in_day == $start_day_name) {
1650 $error = array(
1651 'code' => 'check_in_day_error',
1652 'message' => sprintf(
1653 /* translators: %s: allowed check-in day */
1654 esc_html__( '%1$s %1$s', 'easy-hotel' ),
1655 esc_html( $string_check_in_day_error_msg ),
1656 esc_html( $allowed_check_in_day )
1657 ),
1658 );
1659 wp_send_json_error([
1660 'error' => $error,
1661 ]);
1662 }
1663
1664
1665 if(!empty($allowed_check_in_day) && $allowed_check_in_day != 'all'){
1666 $days_count = 1;
1667 }
1668
1669 // validate min stay night by session
1670 if(class_exists('ESHB_ADVANCED_PRICING') && !empty($min_stay_night_by_session) && $days_count < $min_stay_night_by_session){
1671 $error = array(
1672 'code' => 'min_stay_night_by_session',
1673 'message' => sprintf(
1674 /* translators: 1: minimum nights message text, 2: required minimum nights */
1675 esc_html__( '%1$s %2$s nights!', 'easy-hotel' ),
1676 esc_html( $string_required_minimum_nights_msg ),
1677 esc_html( $min_stay_night_by_session )
1678 ),
1679 );
1680 wp_send_json_error([
1681 'error' => $error,
1682 ]);
1683 }
1684
1685 if(!empty($required_min_nights) && $days_count < $required_min_nights){
1686 $error = array(
1687 'code' => 'required_min_nights',
1688 'message' => sprintf(
1689 /* translators: 1: minimum nights message text, 2: required minimum nights */
1690 esc_html__( '%1$s %2$s nights!', 'easy-hotel' ),
1691 esc_html( $string_required_minimum_nights_msg ),
1692 esc_html( $required_min_nights )
1693 ),
1694 );
1695 wp_send_json_error([
1696 'error' => $error,
1697 ]);
1698 }
1699 if(!empty($required_max_nights) && $days_count > $required_max_nights){
1700 $error = array(
1701 'code' => 'required_max_nights',
1702 'message' => sprintf(
1703 /* translators: 1: maximum nights message text, 2: required maximum nights */
1704 esc_html__( '%1$s %2$s nights allowed!', 'easy-hotel' ),
1705 esc_html( $string_required_maximum_nights_msg ),
1706 esc_html( $required_max_nights )
1707 ),
1708 );
1709 wp_send_json_error([
1710 'error' => $error,
1711 ]);
1712 }
1713
1714
1715
1716 // validate capacities
1717 $adult_capacity = !empty($accomodation_metaboxes['adult_capacity']) ? $accomodation_metaboxes['adult_capacity'] : 0;
1718 $children_capacity = !empty($accomodation_metaboxes['children_capacity']) ? $accomodation_metaboxes['children_capacity'] : 0;
1719 $total_capacity = !empty($accomodation_metaboxes['total_capacity']) ? $accomodation_metaboxes['total_capacity'] : 0;
1720 $extra_bed_capacity = !empty($accomodation_metaboxes['total_extra_beds']) ? $accomodation_metaboxes['total_extra_beds'] : 0;
1721
1722 // multiply total capacity by per rooms
1723 $total_capacity = $total_capacity * $room_quantity;
1724
1725 if(!empty($total_capacity)){
1726 if($adult_capacity < 1 && $children_capacity > 0){
1727 $adult_capacity = $total_capacity - $children_quantity;
1728 }elseif($adult_capacity < 1 && $children_capacity < 1){
1729 $adult_capacity = $total_capacity;
1730 }
1731
1732 if($children_capacity < 1 && $adult_capacity > 0){
1733 $children_capacity = $total_capacity - $adult_quantity;
1734 }elseif($children_capacity < 1 && $adult_capacity < 1){
1735 $children_capacity = $total_capacity;
1736 }
1737 }
1738
1739 // multiply capacity by per rooms
1740 $adult_capacity = $adult_capacity * $room_quantity;
1741 $children_capacity = $children_capacity * $room_quantity;
1742 $extra_bed_capacity = $extra_bed_capacity * $room_quantity;
1743
1744 if(!empty($adult_capacity) && $adult_quantity > 0 && $adult_capacity < $adult_quantity){
1745 $error = array('code' => 'adult_capacity_not_enough', 'message' => esc_html__('Maximum Adult Capacity', 'easy-hotel') . ' ' . $adult_capacity);
1746 wp_send_json_error([
1747 'error' => $error,
1748 ]);
1749 }
1750
1751 if(!empty($children_capacity) && $children_quantity > 0 && $children_capacity < $children_quantity){
1752 $error = array('code' => 'children_capacity_not_enough', 'message' => esc_html__('Maximum Children Capacity', 'easy-hotel') . ' ' . $children_capacity);
1753 wp_send_json_error([
1754 'error' => $error,
1755 ]);
1756 }
1757
1758 if($extra_bed_quantity > 0 && $extra_bed_capacity < $extra_bed_quantity){
1759 $error = array('code' => 'extra_bed_capacity_not_enough', 'message' => esc_html__('Maximum Extra Bed Capacity', 'easy-hotel') . ' ' . $extra_bed_capacity );
1760 wp_send_json_error([
1761 'error' => $error,
1762 ]);
1763 }
1764
1765
1766
1767 $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 );
1768
1769 $base_price = $pricing['basePrice'];
1770 $extra_services_charge = $pricing['extraServicesPrice'];
1771 $extra_bed_price= $pricing['extraBedPrice'];
1772 $subtotal_price = $pricing['subtotalPrice'];
1773 $total_price = $pricing['totalPrice'];
1774 $discount = $pricing['discount'];
1775 $currency_symbol = $pricing['currencySymbol'];
1776 $quantity = 1;
1777
1778 $details = [];
1779
1780 if ($room_visibility && $room_quantity > 0) {
1781 $details[] = 'Room: ' . $room_quantity;
1782 }
1783 if ($extra_beds_visibility && $extra_bed_quantity > 0) {
1784 $details[] = 'Extra Bed: ' . $extra_bed_quantity;
1785 }
1786 if ($adult_visibility && $adult_quantity > 0) {
1787 $details[] = 'Adult: ' . $adult_quantity;
1788 }
1789 if ($childrens_visibility && $children_quantity > 0) {
1790 $details[] = 'Children: ' . $children_quantity;
1791 }
1792
1793 $details_html = implode(', ', $details);
1794
1795
1796 $extra_service_titles = [];
1797 $extra_services_html = '';
1798
1799 if(!empty($selected_services) && is_array($selected_services) && count($selected_services) > 0 ){
1800
1801 foreach ($selected_services as $service) {
1802
1803 $service_id = $service['id'];
1804
1805 $service_quantity = $service['quantity'];
1806
1807 if(!empty($service_quantity)){
1808 $eshb_service_metaboxes = get_post_meta($service_id, 'eshb_service_metaboxes', true);
1809
1810 $charge_type = $eshb_service_metaboxes['service_charge_type'];
1811
1812 $service_title = get_the_title($service_id) . ' For ' . $service_quantity . ' ' . ucfirst(strtolower($charge_type));
1813
1814 if(!$room_visibility && $charge_type == 'room'){
1815 $service_title = get_the_title($service_id);
1816 }
1817 }else{
1818 $service_title = get_the_title($service_id);
1819 }
1820
1821
1822
1823 array_push($extra_service_titles, $service_title);
1824
1825 }
1826 $extra_services_html = implode(", ",$extra_service_titles);
1827 }
1828
1829 $dates = date_i18n( get_option('date_format'), strtotime( $start_date ) ) .' - '. date_i18n( get_option('date_format'), strtotime( $end_date ) );
1830 if($start_date == $end_date){
1831 $dates = date_i18n( get_option('date_format'), strtotime( $start_date ) );
1832 }
1833
1834 $formated_start_timestamp = strtotime( $start_time );
1835 $formated_end_timestamp = strtotime( $end_time );
1836
1837 $cart_item_data = [
1838 'accomodation_id' => $accomodation_id,
1839 'subtotal_price' => $subtotal_price,
1840 'total_price' => $total_price,
1841 'base_price' => $base_price,
1842 'discount' => $discount,
1843 'extra_services_charge' => $extra_services_charge,
1844 'extra_bed_price' => $extra_bed_price,
1845 'currency_symbol' => $currency_symbol,
1846 'start_date' => $start_date,
1847 'end_date' => $end_date,
1848 'night' => $days_count,
1849 'dates' => $dates,
1850 'room_quantity' => $room_quantity,
1851 'adult_quantity' => $adult_quantity,
1852 'details_html' => $details_html,
1853 'extra_services_html' => $extra_services_html
1854 ];
1855
1856 if(!empty($extra_bed_quantity)) {
1857 $cart_item_data['extra_bed_quantity'] = $extra_bed_quantity;
1858 }
1859 if(!empty($start_time)) {
1860 $cart_item_data['start_time'] = $start_time;
1861 }
1862 if(!empty($end_time)) {
1863 $cart_item_data['end_time'] = $end_time;
1864 }
1865
1866 if(!empty($start_time) && !empty($end_time)) {
1867 // Get WP formatted time (according to Settings > General > Time Format)
1868 $wp_time_format = get_option( 'time_format' ); // WP settings
1869 $formatted_start_time = wp_date( $wp_time_format, $formated_start_timestamp );
1870 $formatted_end_time = wp_date( $wp_time_format, $formated_end_timestamp );
1871 $times = $formatted_start_time . ' - ' . $formatted_end_time;
1872 $cart_item_data['times'] = $times;
1873 }
1874
1875 if(!empty($children_quantity)) {
1876 $cart_item_data['children_quantity'] = $children_quantity;
1877 }
1878
1879 if(!empty($selected_services)) {
1880 $cart_item_data['extra_services'] = $selected_services;
1881 }
1882
1883 if(!empty($extra_services_charge)) {
1884 $cart_item_data['extra_services_html'] = $extra_services_html;
1885 }
1886
1887 if (get_post_type($accomodation_id) === 'eshb_accomodation') {
1888
1889 $thumbnail_id = get_post_thumbnail_id($accomodation_id);
1890 $price_id = 0;
1891 $product_id = ESHB_Helper::get_product_id_for_cart($accomodation_id, $booking_type);
1892 $product_id = apply_filters( 'eshb_product_id_for_cart', $product_id, $accomodation_id, $thumbnail_id );
1893
1894 $cart_item_data['product_id'] = $product_id;
1895 $cart_item_data['price_id'] = $price_id;
1896
1897 do_action( 'eshb_before_add_to_cart', $booking_type, $cart_item_data, $string_booking_success_msg );
1898
1899 if($booking_type == 'woocommerce'){
1900
1901 update_post_meta($product_id, '_accomodation_id', $accomodation_id);
1902 $this->update_product_price_by_id($product_id, $subtotal_price);
1903
1904 $cart_item_data['unique_key'] = uniqid( '', true );
1905 $cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, 0, [], $cart_item_data);
1906
1907 if ( $cart_item_key ) {
1908
1909 $blocked_until = $this->eshb_set_cart_block($accomodation_id, $start_date, $end_date, $room_quantity);
1910
1911 wp_send_json_success([
1912 'booking-type' => $booking_type,
1913 'message' => esc_html( $string_booking_success_msg ),
1914 'cart_item_key' => $cart_item_key,
1915 'cart_count' => WC()->cart->get_cart_contents_count(),
1916 'blocked_until' => $blocked_until,
1917 'accomodation_id' => $accomodation_id,
1918 'start_date' => $start_date,
1919 'end_date' => $end_date,
1920 ]);
1921
1922 } else {
1923
1924 $error = array(
1925 'code' => 'cart_item_key_not_found',
1926 'message' => esc_html( $string_booking_failed_msg ),
1927 );
1928
1929 wp_send_json_error([
1930 'error' => $error,
1931 'cart_item_data' => $cart_item_data,
1932 'product_id' => $product_id,
1933 ]);
1934 }
1935
1936 }
1937
1938 do_action( 'eshb_after_add_to_cart', $booking_type, $cart_item_data, $string_booking_success_msg );
1939
1940 }else{
1941 $error = array('code' => 'accomodation_post_type_not_found', 'message' => esc_html($string_booking_failed_msg));
1942 wp_send_json_error([
1943 'error' => $error,
1944 ]);
1945 }
1946 die();
1947 }
1948
1949 }
1950
1951 public function eshb_get_booking_prices() {
1952 // Ensure external filters are loaded during AJAX requests
1953 do_action('eshb_before_calculate_pricing');
1954
1955 if (isset($_POST['accomodationId']) && !empty($_POST['accomodationId'])) {
1956
1957 // Verify nonce for security
1958 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
1959 wp_send_json_error(['message' => 'Invalid nonce']);
1960 die();
1961 }
1962
1963 $accomodation_id = (int) sanitize_text_field(wp_unslash($_POST['accomodationId']));
1964
1965 $start_date = sanitize_text_field(wp_unslash($_POST['startDate'] ?? $today_date));
1966 $end_date = sanitize_text_field(wp_unslash($_POST['endDate'] ?? $tomorrow));
1967
1968 $start_time = sanitize_text_field(wp_unslash($_POST['startTime'] ?? ''));
1969 $end_time = sanitize_text_field(wp_unslash($_POST['endTime'] ?? ''));
1970
1971 $room_quantity = (int) sanitize_text_field(wp_unslash($_POST['roomQuantity'] ?? 1));
1972 $extra_bed_quantity = (int) sanitize_text_field(wp_unslash($_POST['extraBedQuantity'] ?? 0));
1973 $adult_quantity = (int) sanitize_text_field(wp_unslash($_POST['adultQuantity'] ?? 1));
1974 $children_quantity = (int) sanitize_text_field(wp_unslash($_POST['childrenQuantity'] ?? 0));
1975 $selected_services = [];
1976 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
1977
1978 $selected_services = sanitize_text_field(wp_unslash($_POST['selectedServices']));
1979 $selected_services = json_decode($selected_services, true);
1980
1981 }
1982
1983
1984 $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);
1985
1986 wp_send_json_success($pricing);
1987 }
1988
1989 wp_die();
1990 }
1991
1992 public function eshb_get_booking_prices_rest( $request ) {
1993 do_action('eshb_before_calculate_pricing');
1994
1995 $params = $request->get_params();
1996
1997 // // Nonce check
1998 if ( ! isset($params['nonce']) || ! wp_verify_nonce($params['nonce'], ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action')) ) {
1999 return new WP_Error('invalid_nonce', 'Invalid nonce', ['status' => 403]);
2000 }
2001
2002 $accomodation_id = (int) ($params['accomodationId'] ?? 0);
2003 $start_date = $params['startDate'] ?? gmdate('Y-m-d');
2004 $end_date = $params['endDate'] ?? gmdate('Y-m-d', strtotime('+1 day'));
2005 $start_time = $params['startTime'] ?? '';
2006 $end_time = $params['endTime'] ?? '';
2007 $room_quantity = (int) ($params['roomQuantity'] ?? 1);
2008 $extra_bed_quantity = (int) ($params['extraBedQuantity'] ?? 0);
2009 $adult_quantity = (int) ($params['adultQuantity'] ?? 1);
2010 $children_quantity = (int) ($params['childrenQuantity'] ?? 0);
2011 $selected_services = $params['selectedServices'] ?? [];
2012
2013 $pricing = $this->calculate_booking_pricing(
2014 $accomodation_id,
2015 $start_date,
2016 $end_date,
2017 $room_quantity,
2018 $extra_bed_quantity,
2019 $adult_quantity,
2020 $children_quantity,
2021 $selected_services,
2022 true,
2023 $start_time,
2024 $end_time
2025 );
2026
2027 return rest_ensure_response($pricing);
2028 }
2029
2030 public function eshb_get_extra_services_charge(){
2031
2032 // Verify nonce for security
2033 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2034 wp_send_json_error(['message' => 'Invalid nonce']);
2035 die();
2036 }
2037
2038 if(isset($_POST['selectedServices']) && !empty($_POST['selectedServices'])){
2039
2040 $selected_services = array_map('sanitize_text_field', wp_unslash($_POST['selectedServices']));
2041 $days_count = isset($_POST['dayCount']) ? sanitize_text_field(wp_unslash($_POST['dayCount'])) : 1;
2042
2043 $price = 0;
2044
2045 if(is_array($selected_services)){
2046 foreach ($selected_services as $service) {
2047
2048 $service_id = $service['id'];
2049 $quantity = $service['quantity'];
2050
2051 $eshb_service_metaboxes = get_post_meta($service_id, 'eshb_service_metaboxes', true);
2052
2053 $service_periodicity = $eshb_service_metaboxes['service_periodicity'];
2054
2055 if (isset($eshb_service_metaboxes['service_price'])) { // Ensure 'service_price' is set
2056 $single_price = $eshb_service_metaboxes['service_price'] * $quantity;
2057 if($service_periodicity == 'perday' && !empty($days_count)){
2058 $single_price = $days_count * $single_price;
2059 }
2060 $price += floatval($single_price); // Add the single price to the total price
2061 }
2062
2063 }
2064 }
2065
2066 wp_send_json_success( ['price' => $price] );
2067
2068 wp_die();
2069
2070 }
2071
2072 }
2073
2074 public function get_available_room_count_by_date_range($accomodation_id, $start_date, $end_date, $start_time = '', $end_time = '') {
2075 $accomodation_id = intval($accomodation_id);
2076 $start_date_obj = new DateTime($start_date);
2077 $end_date_obj = new DateTime($end_date);
2078
2079 if ($end_date_obj < $start_date_obj) {
2080 return new WP_Error('invalid_date_range', 'End date cannot be earlier than start date.');
2081 }
2082
2083 // Fetch total rooms
2084 $accomodation_metas = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2085 $total_rooms = isset($accomodation_metas['total_rooms']) ? intval($accomodation_metas['total_rooms']) : 1;
2086
2087 // Initialize bookings count array
2088 $bookings_per_date = [];
2089
2090 // Query all bookings
2091 $bookings_args = [
2092 'post_type' => 'eshb_booking',
2093 'posts_per_page' => -1,
2094 //'post_status' => 'publish',
2095 'post_status' => ['publish', 'completed', 'processing', 'deposit-payment', 'pending'],
2096 'fields' => 'ids',
2097 ];
2098 $bookings = new WP_Query($bookings_args);
2099
2100 if ($bookings->have_posts()) {
2101 while ($bookings->have_posts()) {
2102 $bookings->the_post();
2103 $booking_id = get_the_ID();
2104 $booking_metas = get_post_meta($booking_id, 'eshb_booking_metaboxes', true);
2105 $booking_accomodation_id = $booking_metas['booking_accomodation_id'];
2106 $booking_status = isset($booking_metas['booking_status']) ? $booking_metas['booking_status'] : '';
2107 $valid_statuses = array('pending', 'processing', 'on-hold', 'completed', 'deposit-payment');
2108
2109 if (
2110 is_array($booking_metas) &&
2111 intval($booking_accomodation_id) === $accomodation_id &&
2112 isset($booking_metas['booking_start_date'], $booking_metas['booking_end_date']) &&
2113 in_array($booking_status, $valid_statuses)
2114 ) {
2115 $booking_start = new DateTime($booking_metas['booking_start_date']);
2116 $booking_end = new DateTime($booking_metas['booking_end_date']);
2117 $booked_rooms = !empty($booking_metas['room_quantity']) ? intval($booking_metas['room_quantity']) : 1;
2118 $accomodation_metaboxes = get_post_meta( $booking_accomodation_id, 'eshb_accomodation_metaboxes', true );
2119 $total_rooms = !empty($accomodation_metaboxes['total_rooms']) ? $accomodation_metaboxes['total_rooms'] : 1;
2120
2121 if(!empty($start_time) && !empty($end_time)) {
2122 $booking_start_time = !empty($booking_metas['booking_start_time']) ? $booking_metas['booking_start_time'] : '';
2123 $booking_end_time = !empty($booking_metas['booking_end_time']) ? $booking_metas['booking_end_time'] : '';
2124
2125 if($booking_start_time !== $start_time) {
2126 $booked_rooms = 0;
2127 }
2128 if($booking_start_time === $start_time && $booking_end_time === $end_time) {
2129
2130 $booked_rooms = 0;
2131 }
2132 }
2133
2134 // Loop through each date in this booking
2135 $period = new DatePeriod($booking_start, new DateInterval('P1D'), (clone $booking_end)->modify('+0 day'));
2136 if($booking_start == $booking_end) {
2137 $period = new DatePeriod($booking_start, new DateInterval('P1D'), (clone $booking_end)->modify('+1 day'));
2138 }
2139
2140 foreach ($period as $date) {
2141 $formatted = $date->format('Y-m-d');
2142 if (!isset($bookings_per_date[$formatted])) {
2143 $bookings_per_date[$formatted] = 0;
2144 }
2145 $bookings_per_date[$formatted] += $booked_rooms;
2146 }
2147
2148 }
2149 }
2150 wp_reset_postdata();
2151 }
2152
2153
2154
2155 // Now calculate the max number of rooms booked in the given date range
2156 $range_period = new DatePeriod($start_date_obj, new DateInterval('P1D'), (clone $end_date_obj)->modify('+0 day'));
2157 $max_booked_in_range = 0;
2158
2159 if($start_date_obj == $end_date_obj){
2160 $formatted = $start_date_obj->format('Y-m-d');
2161 $available_rooms = isset($bookings_per_date[$formatted]) ? $total_rooms - $bookings_per_date[$formatted] : $total_rooms;
2162 return $available_rooms;
2163 }
2164
2165 foreach ($range_period as $date) {
2166 $formatted = $date->format('Y-m-d');
2167 $booked = isset($bookings_per_date[$formatted]) ? $bookings_per_date[$formatted] : 0;
2168 if ($booked > $max_booked_in_range) {
2169 $max_booked_in_range = $booked;
2170 }
2171 }
2172
2173 // Final available room count
2174 $available_rooms = max(0, $total_rooms - $max_booked_in_range);
2175
2176 if($available_rooms < 0) {
2177 $available_rooms = 0;
2178 }
2179
2180 return $available_rooms;
2181 }
2182
2183 public function eshb_get_available_rooms_counts_data(){
2184
2185 // Verify nonce for security
2186 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2187 wp_send_json_error(['message' => 'Invalid nonce']);
2188 die();
2189 }
2190
2191
2192 $accomodation_id = !empty($_POST['accomodationId']) ? sanitize_text_field( wp_unslash($_POST['accomodationId']) ) : '';
2193 $start_date = !empty($_POST['startDate']) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : '';
2194 $end_date = !empty($_POST['endDate']) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : '';
2195
2196 $start_time = !empty($_POST['startTime']) ? sanitize_text_field( wp_unslash($_POST['startTime']) ) : '';
2197 $end_time = !empty($_POST['endTime']) ? sanitize_text_field( wp_unslash($_POST['endTime']) ) : '';
2198
2199
2200 $start_date = new DateTime($start_date);
2201 $end_date = new DateTime($end_date);
2202
2203 $eshb_accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2204 $total_rooms = !empty($eshb_accomodation_metaboxes['total_rooms']) ? $eshb_accomodation_metaboxes['total_rooms'] : 0;
2205
2206 $available = 1;
2207 $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);
2208 if($available < 0 ) {
2209 $available = 0;
2210 }
2211
2212 wp_send_json_success(
2213 array(
2214 'available' => $available,
2215 'total' => $total_rooms,
2216 )
2217 );
2218
2219 die();
2220
2221 }
2222
2223 public function eshb_get_accomodation_available_capacity_counts(){
2224
2225 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2226 wp_send_json_error(['message' => 'Invalid nonce']);
2227 die();
2228 }
2229
2230
2231 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId']) && isset($_POST['type']) && !empty($_POST['type'])){
2232 $type = sanitize_text_field( wp_unslash($_POST['type']) );
2233 $accomodation_id = sanitize_text_field( wp_unslash($_POST['accomodationId']) );
2234 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2235 $total_guest_quantity = $accomodation_metaboxes['total_capacity'];
2236 $selected_rooms_count = !empty($_POST['rooms']) ? sanitize_text_field( wp_unslash($_POST['rooms']) ) : 1;
2237 $start_date = !empty($_POST['startDate']) ? sanitize_text_field( wp_unslash($_POST['startDate']) ) : '';
2238 $end_date = !empty($_POST['endDate']) ? sanitize_text_field( wp_unslash($_POST['endDate']) ) : '';
2239 $start_date = new DateTime($start_date);
2240 $end_date = new DateTime($end_date);
2241
2242 $available = 1;
2243 $msg = __('Maximum Capacity', 'easy-hotel');
2244
2245 if($type == 'room_quantity'){
2246 $total = $accomodation_metaboxes['total_rooms'];
2247
2248 $available = $this->get_available_room_count_by_date_range($accomodation_id, $start_date->format('Y-m-d'), $end_date->format('Y-m-d'));
2249
2250 if($available < 1){
2251 $msg = __('Available Room', 'easy-hotel');
2252 }
2253
2254 }
2255
2256 if($type == 'adult_quantity'){
2257
2258 if(!empty($accomodation_metaboxes['adult_capacity'])){
2259 $available = $accomodation_metaboxes['adult_capacity'];
2260 $available = $available * $selected_rooms_count;
2261 }else{
2262 $childrenQuantity = !empty($_POST['childrenQuantity']) ? sanitize_text_field( wp_unslash( $_POST['childrenQuantity'] ) ) : 0;
2263
2264 $total_capacity = $accomodation_metaboxes['total_capacity'];
2265
2266 $available = $total_capacity - $childrenQuantity;
2267
2268 $available = $available * $selected_rooms_count;
2269
2270 $msg = __('Maximum Adult and Children Capacity', 'easy-hotel');
2271 }
2272
2273 }
2274
2275 if($type == 'children_quantity'){
2276
2277 $total_capacity = $accomodation_metaboxes['total_capacity'];
2278
2279 if(!empty($accomodation_metaboxes['children_capacity'])){
2280 $available = $accomodation_metaboxes['children_capacity'];
2281 $available = $available * $selected_rooms_count;
2282 }else{
2283 $childrenQuantity = !empty($_POST['adultQuantity']) ? sanitize_text_field( wp_unslash( $_POST['adultQuantity'] ) ) : 0;
2284
2285 $available = $total_capacity - $childrenQuantity;
2286
2287 $available = $available * $selected_rooms_count;
2288
2289 $msg = __('Maximum Adult and Children Capacity', 'easy-hotel');
2290 }
2291
2292 }
2293
2294 if($type == 'extra_bed_quantity'){
2295 $available = $accomodation_metaboxes['total_extra_beds'];
2296 $available = $available * $selected_rooms_count;
2297 }
2298
2299 if($type == 'service-quantity'){
2300 $available = $total_guest_quantity;
2301 }
2302
2303
2304 if(($type != 'room_quantity') && (empty($available) || $available < 1)){
2305 $available = $total;
2306 }
2307
2308 if($available < 0) {
2309 $available = 0;
2310 }
2311
2312 $msg = $type != 'adult_quantity' || $type != 'children_quantity' ? $msg . ' ' . $available : $msg;
2313
2314 wp_send_json_success(
2315 array(
2316 'message' => $msg,
2317 'available' => $available,
2318 'type' => $type,
2319 )
2320 );
2321 }
2322 }
2323
2324 public function get_accomodation_available_extra_bed_counts(){
2325
2326 // Verify nonce for security
2327 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action'))) {
2328 wp_send_json_error(['message' => 'Invalid nonce']);
2329 die();
2330 }
2331
2332 if(isset($_POST['accomodationId']) && !empty($_POST['accomodationId']) && !empty($_POST['accomodationId'])){
2333
2334 $total_extra_beds = 0;
2335 $accomodation_id = (int) sanitize_text_field( wp_unslash($_POST['accomodationId']) );
2336 $accomodation_metaboxes = get_post_meta($accomodation_id, 'eshb_accomodation_metaboxes', true);
2337
2338 $total_extra_beds = $accomodation_metaboxes['total_extra_beds'];
2339
2340 wp_send_json_success( $total_extra_beds );
2341
2342 }
2343 }
2344
2345 public function send_email_woocommerce_style($email, $subject, $heading, $message) {
2346 if ( ! function_exists( 'WC' ) ) {
2347 return;
2348 }
2349
2350 // Get WooCommerce mailer
2351 $mailer = WC()->mailer();
2352
2353 // Wrap message with WooCommerce email template
2354 $wrapped_message = $mailer->wrap_message($heading, $message);
2355
2356 // Create new WC_Email instance
2357 $wc_email = new WC_Email();
2358
2359 // Style the wrapped message with WooCommerce inline styles
2360 $html_message = $wc_email->style_inline($wrapped_message);
2361
2362 // Headers
2363 $headers = array('Content-Type: text/html; charset=UTF-8');
2364
2365 // Send the email
2366 $mailer->send($email, $subject, $html_message, $headers);
2367 }
2368
2369 public function filter_status_based_on_booking_meta($status, $order_id) {
2370 // Check if custom meta _booking_post_created exists
2371 $booking_post_id = get_post_meta($order_id, '_booking_post_created', true);
2372
2373 $eshb_settings = get_option('eshb_settings', []);
2374
2375 // Don't auto-complete an order that still owes a deposit balance —
2376 // it must remain 'deposit-payment' until the rest is paid. A full
2377 // payment never has a captured deposit recorded, so it auto-completes.
2378 $is_deposit_order = ( (float) get_post_meta( $order_id, 'initial_deposit', true ) > 0 );
2379
2380 if ( ! empty($booking_post_id) && ! $is_deposit_order ) {
2381 if(isset($eshb_settings['booking-auto-approval']) && $eshb_settings['booking-auto-approval'] == true){
2382 return 'completed'; // Mark order as completed
2383 }
2384 }
2385
2386 // Otherwise, keep default status (usually 'processing')
2387 return $status;
2388 }
2389
2390 public function update_booking_status_on_woocommerce_order_status_change($order_id, $old_status, $new_status, $order) {
2391
2392 if($old_status == $new_status) {
2393 return;
2394 }
2395
2396 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
2397
2398 //if ( $screen && $screen->id !== 'woocommerce_page_wc-orders' ) return;
2399 if ( $screen && $screen->id === 'edit-eshb_booking' ) return;
2400
2401 if ( $screen && 'woocommerce_page_wc-orders' === $screen->id ) {
2402 //return;
2403 }
2404
2405 // Retrieve the order object
2406 $order = wc_get_order($order_id);
2407
2408 // Get the booking post ID from order meta
2409 $booking_post_id = get_post_meta($order_id, '_booking_post_created', true); // Adjusted to your meta key
2410
2411 if ($booking_post_id) {
2412
2413 // Auto-approval: a fully-paid, non-deposit hotel order must not be
2414 // synced down to 'processing' — it should be 'completed'. Without
2415 // this, a later order transition to 'processing' (gateway / IPN)
2416 // overwrites the 'completed' status set at checkout. Deposits keep
2417 // their status (they still owe a balance).
2418 if ( 'processing' === $new_status && $order ) {
2419 $eshb_settings = get_option( 'eshb_settings', [] );
2420 $auto_approve = ! empty( $eshb_settings['booking-auto-approval'] ) && $eshb_settings['booking-auto-approval'] == true;
2421 $is_deposit = ( (float) get_post_meta( $order_id, 'initial_deposit', true ) > 0 )
2422 || ( (float) get_post_meta( $order_id, 'eshb_booking_due_amount', true ) > 0 );
2423
2424 if ( $auto_approve && ! $is_deposit ) {
2425 $new_status = 'completed';
2426 if ( $order->get_status() !== 'completed' ) {
2427 // Re-enters this method with new_status='completed', which
2428 // then falls through to the normal sync below.
2429 $order->update_status( 'completed', 'Auto-approved: full payment received.' );
2430 }
2431 }
2432 }
2433
2434 // update booking status
2435 $updated_booking = array(
2436 'ID' => $booking_post_id,
2437 'post_status' => $new_status,
2438 );
2439
2440 wp_update_post( $updated_booking );
2441
2442 // Get the existing meta for the custom post
2443 $booking_meta = get_post_meta($booking_post_id, 'eshb_booking_metaboxes', true);
2444
2445 // Ensure the meta is an array before modifying it
2446 if (is_array($booking_meta)) {
2447 // Update the `booking_status` field
2448 $booking_meta['booking_status'] = $new_status;
2449
2450 // Save the updated meta back to the post
2451 update_post_meta($booking_post_id, 'eshb_booking_metaboxes', $booking_meta);
2452 }
2453 }
2454 }
2455
2456 public function update_woocommerce_order_status_on_booking_status_change($post_id, $post, $update) {
2457
2458 if(class_exists('ESHB_MANUAL_BOOKING') && is_admin()){
2459 return;
2460 }
2461
2462 if ( ! function_exists( 'WC' ) ) {
2463 return;
2464 }
2465
2466 // Ensure this runs only during post update
2467 if (!$update || wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
2468 return;
2469 }
2470
2471 // Check if the post type matches
2472 if ($post->post_type !== 'eshb_booking') {
2473 return;
2474 }
2475
2476
2477
2478 // Retrieve the metabox data
2479 $eshb_booking_metaboxes = get_post_meta($post_id, 'eshb_booking_metaboxes', true) ?? [];
2480
2481 // Validate the required fields
2482 if (empty($eshb_booking_metaboxes) || empty($eshb_booking_metaboxes['booking_status']) || empty($eshb_booking_metaboxes['order_id'])) {
2483 return;
2484 }
2485
2486 $booking_status = sanitize_text_field($eshb_booking_metaboxes['booking_status']);
2487 $order_id = intval($eshb_booking_metaboxes['order_id']);
2488
2489 // Get the WooCommerce order object
2490 $order = wc_get_order($order_id);
2491 if (!$order) {
2492 return; // Invalid order
2493 }
2494
2495 // Check if the current order status matches the booking status to avoid redundant updates
2496 if ($order->get_status() === $booking_status) {
2497 return; // Status is already up-to-date
2498 }
2499
2500 try {
2501 $order->update_status($booking_status, 'Booking status updated to: ' . $booking_status);
2502 } catch (Exception $e) {
2503 // Log error for debugging
2504 //error_log('Error updating order status: ' . $e->getMessage());
2505 }
2506 }
2507
2508 public function send_booking_email_notification_for_booking_status($post_id, $post, $update){
2509
2510 // Ensure this runs only during post update
2511 if (!$update || wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
2512 return;
2513 }
2514
2515 // Check if the post type matches
2516 if ($post->post_type !== 'eshb_booking') {
2517 return;
2518 }
2519
2520 // Retrieve the metabox data
2521 $eshb_booking_metaboxes = get_post_meta($post_id, 'eshb_booking_metaboxes', true) ?? [];
2522
2523
2524 // Validate the required fields
2525 if (empty($eshb_booking_metaboxes) || empty($eshb_booking_metaboxes['booking_status']) || empty($eshb_booking_metaboxes['order_id'])) {
2526 return;
2527 }
2528
2529 $booking_status = $post->post_status;
2530 $order_id = $eshb_booking_metaboxes['order_id'];
2531
2532 $eshb_settings = get_option('eshb_settings', []);
2533 $booking_type = isset($eshb_settings['booking-type']) && !empty($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : 'woocommerce';
2534
2535
2536 do_action('eshb_booking_status_changed', $post_id, $booking_status, $booking_type, $order_id);
2537
2538 if(class_exists('ESHB_Booking_Templates')) {
2539 return;
2540 }
2541 // send email for woocommerce
2542 if ( $booking_type == 'woocommerce' && function_exists( 'WC' ) ) {
2543
2544 $order = wc_get_order($order_id);
2545 if (!$order) {
2546 return; // Invalid order
2547 }
2548 // Trigger email manually
2549 $mailer = WC()->mailer();
2550
2551 switch ( $booking_status ) {
2552
2553 case 'cancelled':
2554 $to = $order->get_billing_email();
2555 $subject = sprintf(
2556 /* translators: %s: booking/post ID */
2557 __( 'Your Booking #%s has been Cancelled', 'easy-hotel' ),
2558 esc_html( $post_id )
2559 );
2560 $heading = __( 'Booking Cancelled', 'easy-hotel' );
2561
2562 $first_name = esc_html( $order->get_billing_first_name() );
2563 $post_title = esc_html( get_the_title( $post_id ) );
2564
2565 $message = '<p>' . sprintf(
2566 /* translators: %s: customer first name */
2567 __( 'Hi %s,', 'easy-hotel' ),
2568 $first_name
2569 ) . '</p>';
2570
2571 $message .= '<p>' . sprintf(
2572 /* translators: %s: booking title */
2573 __( 'We regret to inform you that your <strong>%s</strong> has been <strong>cancelled</strong>.', 'easy-hotel' ),
2574 $post_title
2575 ) . '</p>';
2576
2577 $message .= '<p>' . __( 'If you have any questions, please feel free to reply to this email.', 'easy-hotel' ) . '</p>';
2578 $message .= '<p>' . __( 'Thank you for shopping with us.', 'easy-hotel' ) . '</p>';
2579
2580 $this->send_email_woocommerce_style( $to, $subject, $heading, $message );
2581 break;
2582 }
2583
2584 }
2585 }
2586
2587 // -------------------------------------------------------------------------
2588 // Cart Blocking Methods
2589 // -------------------------------------------------------------------------
2590
2591 private function eshb_get_session_token() {
2592 // Native Checkout does not use a WooCommerce session — the visitor
2593 // is identified by the per-visitor reservation token instead. Use
2594 // that as the hold key so holds created during the native flow are
2595 // attributed to (and excludable for) the right user across the
2596 // booking form, calendar and checkout page.
2597 if ( function_exists( 'eshb_native_checkout_is_enabled' ) && eshb_native_checkout_is_enabled()
2598 && function_exists( 'eshb_native_checkout_get_token' ) ) {
2599 $native_token = eshb_native_checkout_get_token( false );
2600 if ( ! empty( $native_token ) ) {
2601 return 'native_' . $native_token;
2602 }
2603 }
2604 if ( class_exists( 'WooCommerce' ) && WC()->session ) {
2605 return (string) WC()->session->get_customer_id();
2606 }
2607 return '';
2608 }
2609
2610 private function eshb_dates_overlap( $s1, $e1, $s2, $e2 ) {
2611 return $s1 < $e2 && $s2 < $e1;
2612 }
2613
2614 private function eshb_get_blocking_settings() {
2615 $eshb_settings = get_option( 'eshb_settings', [] );
2616 $enabled = ! empty( $eshb_settings['cart-blocking-switcher'] );
2617 $blocking_time = ! empty( $eshb_settings['cart-blocking-time'] ) ? (int) $eshb_settings['cart-blocking-time'] : 5;
2618 return [ 'enabled' => $enabled, 'minutes' => $blocking_time ];
2619 }
2620
2621 private function eshb_set_cart_block( $accom_id, $start_date, $end_date, $room_qty ) {
2622 $cfg = $this->eshb_get_blocking_settings();
2623 if ( ! $cfg['enabled'] ) return 0;
2624
2625 $session = $this->eshb_get_session_token();
2626 if ( empty( $session ) ) return 0;
2627
2628 $key = 'eshb_cart_holds_' . (int) $accom_id;
2629 $holds = get_transient( $key );
2630 if ( ! is_array( $holds ) ) $holds = [];
2631
2632 $now = time();
2633 $until = $now + ( $cfg['minutes'] * 60 );
2634
2635 // Clean expired entries
2636 foreach ( $holds as $token => $hold ) {
2637 if ( $hold['until'] < $now ) unset( $holds[ $token ] );
2638 }
2639
2640 $holds[ $session ] = [
2641 'start_date' => $start_date,
2642 'end_date' => $end_date,
2643 'qty' => (int) $room_qty,
2644 'until' => $until,
2645 ];
2646
2647 set_transient( $key, $holds, $cfg['minutes'] * 60 + 60 );
2648 return $until;
2649 }
2650
2651 private function eshb_check_cart_block( $accom_id, $start_date, $end_date ) {
2652 $cfg = $this->eshb_get_blocking_settings();
2653 if ( ! $cfg['enabled'] ) return '';
2654
2655 $key = 'eshb_cart_holds_' . (int) $accom_id;
2656 $holds = get_transient( $key );
2657 if ( ! is_array( $holds ) ) return '';
2658
2659 $session = $this->eshb_get_session_token();
2660 $now = time();
2661
2662 foreach ( $holds as $token => $hold ) {
2663 if ( $token === $session ) continue;
2664 if ( $hold['until'] < $now ) continue;
2665 if ( $this->eshb_dates_overlap( $start_date, $end_date, $hold['start_date'], $hold['end_date'] ) ) {
2666 $remaining = (int) ceil( ( $hold['until'] - $now ) / 60 );
2667 /* translators: %d: remaining minutes */
2668 return sprintf( __( 'These dates are temporarily reserved by another user. Please try again in %d minute(s).', 'easy-hotel' ), $remaining );
2669 }
2670 }
2671 return '';
2672 }
2673
2674 /**
2675 * Remove the current visitor's hold for one accommodation, deleting the
2676 * transient entirely when no holds remain. Shared by the cart-removal,
2677 * order-completion and Native Checkout release paths.
2678 */
2679 private function eshb_remove_session_hold( $accom_id ) {
2680 $accom_id = (int) $accom_id;
2681 if ( ! $accom_id ) return;
2682
2683 $session = $this->eshb_get_session_token();
2684 if ( empty( $session ) ) return;
2685
2686 $key = 'eshb_cart_holds_' . $accom_id;
2687 $holds = get_transient( $key );
2688 if ( ! is_array( $holds ) ) return;
2689
2690 unset( $holds[ $session ] );
2691 if ( empty( $holds ) ) {
2692 delete_transient( $key );
2693 } else {
2694 $cfg = $this->eshb_get_blocking_settings();
2695 set_transient( $key, $holds, $cfg['minutes'] * 60 + 60 );
2696 }
2697 }
2698
2699 // -------------------------------------------------------------------------
2700 // Public cart-blocking API (used by the Native Checkout controller, which
2701 // is a separate class and short-circuits the WooCommerce add-to-cart flow)
2702 // -------------------------------------------------------------------------
2703
2704 /**
2705 * Place a temporary hold for a native reservation. Returns the unix
2706 * timestamp the hold expires at, or 0 when blocking is disabled.
2707 */
2708 public function eshb_block_dates_for_reservation( $accom_id, $start_date, $end_date, $room_qty ) {
2709 return $this->eshb_set_cart_block( $accom_id, $start_date, $end_date, $room_qty );
2710 }
2711
2712 /**
2713 * Whether the given dates collide with another visitor's active hold.
2714 * Returns a translated message on conflict, or '' when clear.
2715 */
2716 public function eshb_get_cart_block_conflict( $accom_id, $start_date, $end_date ) {
2717 return $this->eshb_check_cart_block( $accom_id, $start_date, $end_date );
2718 }
2719
2720 /**
2721 * Release the current visitor's hold for an accommodation (on
2722 * completed booking or expired/abandoned native reservation).
2723 */
2724 public function eshb_release_cart_block( $accom_id ) {
2725 $this->eshb_remove_session_hold( $accom_id );
2726 }
2727
2728 /**
2729 * Expiry timestamp (unix) of the current visitor's own hold for an
2730 * accommodation, or 0 when there is none. Lets the Native Checkout
2731 * page render an accurate countdown.
2732 */
2733 public function eshb_get_my_block_until( $accom_id ) {
2734 $accom_id = (int) $accom_id;
2735 if ( ! $accom_id ) return 0;
2736
2737 $holds = get_transient( 'eshb_cart_holds_' . $accom_id );
2738 if ( ! is_array( $holds ) ) return 0;
2739
2740 $session = $this->eshb_get_session_token();
2741 if ( empty( $session ) || empty( $holds[ $session ]['until'] ) ) return 0;
2742
2743 return (int) $holds[ $session ]['until'];
2744 }
2745
2746 /**
2747 * Expose the hold-countdown notice markup so the Native Checkout
2748 * template can render the same banner used by the WooCommerce flow.
2749 */
2750 public function eshb_cart_block_notice_html( $mode = 'inline' ) {
2751 return $this->eshb_get_blocking_notice_html( $mode );
2752 }
2753
2754 public function eshb_add_blocked_dates_to_calendar( $all_dates, $accom_id, $accom_metas ) {
2755 $key = 'eshb_cart_holds_' . (int) $accom_id;
2756 $holds = get_transient( $key );
2757
2758 $blocked_ranges = [];
2759 if ( is_array( $holds ) ) {
2760 $now = time();
2761 // Show every active hold as "Reserved" on the calendar,
2762 // including the current visitor's own. The native flow keys
2763 // holds by a cookie token that is reliably present on this
2764 // AJAX request, so skipping the own session here would hide
2765 // the held dates from the holder (whereas the WooCommerce
2766 // session token isn't consistently resolved here, so its
2767 // holds always showed). Own-hold exclusion is kept only in
2768 // the booking-validation path so the holder can still finish
2769 // their own reservation.
2770 foreach ( $holds as $token => $hold ) {
2771 if ( $hold['until'] < $now ) continue;
2772 $blocked_ranges[] = [
2773 'start_date' => $hold['start_date'],
2774 'end_date' => $hold['end_date'],
2775 'until' => $hold['until'],
2776 ];
2777 }
2778 }
2779
2780 $all_dates['blocked_dates'] = $blocked_ranges;
2781 return $all_dates;
2782 }
2783
2784 public function eshb_get_cart_blocks_ajax() {
2785 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2786
2787 $accom_id = isset( $_POST['accomodation_id'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['accomodation_id'] ) ) : 0;
2788 if ( ! $accom_id ) {
2789 wp_send_json_error( [ 'message' => 'Invalid accommodation ID' ] );
2790 return;
2791 }
2792
2793 $key = 'eshb_cart_holds_' . $accom_id;
2794 $holds = get_transient( $key );
2795
2796 $blocked_ranges = [];
2797 if ( is_array( $holds ) ) {
2798 $now = time();
2799 // Include every active hold (own included) so the calendar
2800 // renders the held dates as "Reserved" consistently across
2801 // the WooCommerce and Native Checkout flows. See the note in
2802 // eshb_add_blocked_dates_to_calendar().
2803 foreach ( $holds as $token => $hold ) {
2804 if ( $hold['until'] < $now ) continue;
2805 $blocked_ranges[] = [
2806 'start_date' => $hold['start_date'],
2807 'end_date' => $hold['end_date'],
2808 'until' => $hold['until'],
2809 ];
2810 }
2811 }
2812
2813 wp_send_json_success( [ 'blocked_ranges' => $blocked_ranges ] );
2814 }
2815
2816 public function eshb_clear_cart_block_on_removal( $cart_item_key, $cart ) {
2817 $removed = isset( $cart->removed_cart_contents[ $cart_item_key ] ) ? $cart->removed_cart_contents[ $cart_item_key ] : null;
2818 if ( ! $removed ) return;
2819
2820 $accom_id = isset( $removed['accomodation_id'] ) ? (int) $removed['accomodation_id'] : 0;
2821 if ( ! $accom_id ) return;
2822
2823 $this->eshb_remove_session_hold( $accom_id );
2824 }
2825
2826 public function eshb_render_calendar_legend( $accomodation_id = 0 ) {
2827 static $rendered = false;
2828 if ( $rendered || is_admin() ) return;
2829 $rendered = true;
2830
2831 $eshb_settings = get_option( 'eshb_settings', [] );
2832 $cart_blocking_on = ! empty( $eshb_settings['cart-blocking-switcher'] );
2833
2834 $items = [
2835 'available' => [ 'class' => 'eshb-legend-available', 'label' => __( 'Available', 'easy-hotel' ) ],
2836 'selected' => [ 'class' => 'eshb-legend-selected', 'label' => __( 'Selected', 'easy-hotel' ) ],
2837 'booked' => [ 'class' => 'eshb-legend-booked', 'label' => __( 'Booked', 'easy-hotel' ) ],
2838 'holiday' => [ 'class' => 'eshb-legend-holiday', 'label' => __( 'Holiday', 'easy-hotel' ) ],
2839 ];
2840
2841 if ( $cart_blocking_on ) {
2842 $items['reserved'] = [ 'class' => 'eshb-legend-reserved', 'label' => __( 'Reserved', 'easy-hotel' ) ];
2843 }
2844
2845 $items = apply_filters( 'eshb_calendar_legend_items', $items, $accomodation_id );
2846 ?>
2847 <div class="eshb-calendar-legend">
2848 <?php foreach ( $items as $item ) : ?>
2849 <div class="eshb-legend-item">
2850 <span class="eshb-legend-swatch <?php echo esc_attr( $item['class'] ); ?>"></span>
2851 <span class="eshb-legend-label"><?php echo esc_html( $item['label'] ); ?></span>
2852 </div>
2853 <?php endforeach; ?>
2854 </div>
2855 <?php
2856 }
2857
2858 private function eshb_get_blocking_notice_html( $mode = 'fixed' ) {
2859 $eshb_settings = get_option( 'eshb_settings', [] );
2860 if ( empty( $eshb_settings['cart-blocking-switcher'] ) ) return '';
2861
2862 $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' );
2863
2864 $class = $mode === 'inline'
2865 ? 'woocommerce-info eshb-cart-block-notice eshb-cart-block-notice--inline'
2866 : 'eshb-cart-block-notice eshb-cart-block-notice--fixed';
2867
2868 return '<div id="eshb-cart-block-notice" class="' . $class . '" data-mode="' . esc_attr( $mode ) . '" style="display:none;" aria-live="polite">'
2869 . '&#x23F3; <span class="eshb-block-msg">' . $msg . '</span> <span class="eshb-block-timer">0:00</span>'
2870 . '</div>';
2871 }
2872
2873 public function eshb_render_cart_block_notice_inline() {
2874 if ( $this->eshb_notice_printed || is_admin() ) return;
2875 $this->eshb_notice_printed = true;
2876 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2877 echo $this->eshb_get_blocking_notice_html( 'inline' );
2878 }
2879
2880 public function eshb_prepend_notice_to_woo_block( $block_content, $block ) {
2881 if ( $this->eshb_notice_printed || is_admin() ) return $block_content;
2882 if ( ! in_array( $block['blockName'], [ 'woocommerce/cart', 'woocommerce/checkout' ], true ) ) return $block_content;
2883 $html = $this->eshb_get_blocking_notice_html( 'inline' );
2884 if ( ! $html ) return $block_content;
2885 $this->eshb_notice_printed = true;
2886 // Append for checkout to avoid breaking React hydration; prepend for cart
2887 return $block['blockName'] === 'woocommerce/checkout'
2888 ? $block_content . $html
2889 : $html . $block_content;
2890 }
2891
2892 public function eshb_footer_notice_fallback() {
2893 if ( $this->eshb_notice_printed || is_admin() ) return;
2894 if ( ! is_cart() && ! is_checkout() ) return;
2895 $html = $this->eshb_get_blocking_notice_html( 'inline' );
2896 if ( ! $html ) return;
2897 $this->eshb_notice_printed = true;
2898 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2899 echo $html;
2900 }
2901
2902 public function eshb_clear_cart_on_block_expiry() {
2903 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2904 if ( class_exists( 'WooCommerce' ) && WC()->cart ) {
2905 WC()->cart->empty_cart();
2906 }
2907 wp_send_json_success( [ 'cleared' => true ] );
2908 }
2909
2910 public function eshb_check_my_session_blocks() {
2911 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2912
2913 $accom_ids = isset( $_POST['accom_ids'] ) ? array_map( 'intval', (array) $_POST['accom_ids'] ) : [];
2914 if ( empty( $accom_ids ) ) {
2915 wp_send_json_success( [ 'active_ids' => [] ] );
2916 return;
2917 }
2918
2919 $session = $this->eshb_get_session_token();
2920 $now = time();
2921 $active_ids = [];
2922
2923 foreach ( $accom_ids as $accom_id ) {
2924 if ( ! $accom_id ) continue;
2925 $holds = get_transient( 'eshb_cart_holds_' . $accom_id );
2926 if ( is_array( $holds ) && isset( $holds[ $session ] ) && $holds[ $session ]['until'] > $now ) {
2927 $active_ids[] = $accom_id;
2928 }
2929 }
2930
2931 wp_send_json_success( [ 'active_ids' => $active_ids ] );
2932 }
2933
2934 public function eshb_clear_session_cart_blocks_on_order( $order_id ) {
2935 if ( ! $order_id ) return;
2936
2937 $session = $this->eshb_get_session_token();
2938 if ( empty( $session ) ) return;
2939
2940 $order = wc_get_order( $order_id );
2941 if ( ! $order ) return;
2942
2943 foreach ( $order->get_items() as $item ) {
2944 $accom_id = (int) $item->get_meta( 'accomodation_id' );
2945 if ( ! $accom_id ) continue;
2946
2947 $this->eshb_remove_session_hold( $accom_id );
2948 }
2949 }
2950
2951 public function eshb_checkout_order_review_qty_input( $qty_html, $cart_item, $cart_item_key ) {
2952 if ( ! is_checkout() ) return $qty_html;
2953 $eshb_settings = get_option( 'eshb_settings', [] );
2954 if ( empty( $eshb_settings['direct-booking'] ) ) return $qty_html;
2955 $nonce = wp_create_nonce( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ) );
2956 $ajaxurl = esc_url( admin_url( 'admin-ajax.php' ) );
2957 return $qty_html
2958 . ' <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>';
2959 }
2960
2961 public function eshb_remove_cart_item() {
2962 check_ajax_referer( ESHB_Helper::generate_secure_nonce_action( 'eshb_global_nonce_action' ), 'nonce' );
2963 $cart_item_key = sanitize_text_field( wp_unslash( $_POST['cart_item_key'] ?? '' ) );
2964 if ( $cart_item_key && class_exists( 'WooCommerce' ) && WC()->cart ) {
2965 WC()->cart->remove_cart_item( $cart_item_key );
2966 }
2967 wp_send_json_success();
2968 }
2969
2970 }
2971 ESHB_Booking::instance();
2972
2973 // Registered once on the singleton (constructor runs multiple times due to `new ESHB_Booking()` calls elsewhere)
2974 add_filter( 'woocommerce_checkout_cart_item_quantity', [ ESHB_Booking::instance(), 'eshb_checkout_order_review_qty_input' ], 10, 3 );