PluginProbe
Easy Hotel – Powerful Hotel Booking / trunk
Easy Hotel – Powerful Hotel Booking vtrunk
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.admin-booking.php

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

454 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly.
3 class ESHB_Admin_Booking {
4 public function __construct() {
5 add_action( 'wp_after_insert_post', [$this, 'create_order_after_save_booking_post'], 10, 3 );
6 add_action( 'save_post_eshb_booking', [$this, 'eshb_update_booking_title_on_save'], 10, 3 );
7 add_filter( 'eshb_eshb_booking_metaboxes_save', [$this, 'update_booking_metaboxes'], 10, 3 );
8 }
9
10 function update_booking_metaboxes ($data, $post_id, $obj) {
11
12 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action') ) ) {
13 $new_status = '';
14 }else{
15 $new_status = !empty($_POST['post_status']) ? sanitize_text_field( wp_unslash( $_POST['post_status'] ) ) : '';
16 }
17
18 $order_id = get_post_meta($post_id, '_order_post_created', true);
19
20 $eshb_booking_metaboxes = $data;
21
22 $assigned_order_id = $eshb_booking_metaboxes['order_id'] ?? false;
23
24 if(!empty($new_status)){
25 $eshb_booking_metaboxes['booking_status'] = $new_status;
26 }
27 $eshb_booking_metaboxes['order_id'] = $order_id;
28
29 return $eshb_booking_metaboxes;
30 }
31
32 function create_woocommerce_order($product_id, $subtotal, $total_paid = 0, $address = '', $status = 'processing', $item_meta = []) {
33 // 1. Create the order
34 $order = wc_create_order();
35
36 // 2. Add products (product_id, quantity)
37 $item_id = $order->add_product( wc_get_product( $product_id ), 1 );
38
39 // 3. Calculate totals
40 $order->calculate_totals();
41
42 // 4. Set customer billing details
43 if ( ! empty($address) && is_array($address) ) {
44 $order->set_address( $address, 'billing' );
45 }
46
47 // 5. Set payment method
48 $order->set_payment_method( 'cod' );
49
50 // 6. Get the line item object
51 $item = $order->get_item( $item_id );
52
53 // 7. Set a custom line subtotal/total if needed
54 if ( $item && $item instanceof WC_Order_Item_Product ) {
55
56 // Set custom item totals (without tax)
57 $item->set_subtotal( $subtotal );
58 $item->set_total( $subtotal );
59
60 // --- Add/Update line item meta ---
61 // Pass associative array: ['_internal_note' => 'x', 'room_type' => 'Deluxe']
62 if ( ! empty( $item_meta ) && is_array( $item_meta ) ) {
63 foreach ( $item_meta as $key => $value ) {
64 // update_meta_data prevents duplicate keys; use add_meta_data for duplicates
65 $item->update_meta_data( $key, $value );
66 }
67 }
68
69 // Save the line item with meta and totals
70 $item->save();
71 }
72
73
74 // 8. Set the order total
75 $order->set_total( $total_paid ); // without tax
76
77 // 9. Add custom order meta (loop through meta_data array)
78 if ( ! empty($meta_data) && is_array($meta_data) ) {
79 foreach ($meta_data as $key => $value) {
80 $order->update_meta_data($key, $value);
81 }
82 }
83
84 // 10. (Optional) Set status
85 $order->update_status( $status, 'Order created programmatically.' );
86
87 // 11. Save order with meta
88 $order->save();
89
90 return $order->get_id(); // Returns the order ID
91 }
92
93 function update_woocommerce_order($order_id, $subtotal = '', $total_paid = '', $status = 'processing', $product_id = '', $address = '', $item_meta = []) {
94 // 1. Get the order
95 $order = wc_get_order( $order_id );
96
97 if ( !$order ) {
98 return false; // Order not found
99 }
100
101 // 2. Update products (product_id, quantity)
102 if( !empty($product_id) ) {
103
104 // Remove existing items if needed
105 $items = $order->get_items();
106
107 if ( ! empty( $items ) ) {
108 $first_item = reset( $items ); // Get the first item object
109 $order->remove_item( $first_item->get_id() );
110 }
111
112 $item_id = $order->add_product( wc_get_product( $product_id ), 1 ); // 123 = Product ID, 2 = Qty
113 }
114
115 // If no product_id is provided, we assume the order already has items and we won't add a new one.
116 $item_id = !empty($item_id) ? $item_id : array_key_first($order->get_items());
117
118 // 3. Calculate totals
119 $order->calculate_totals();
120
121 // 4. Set customer billing details
122 if( !empty($address) ) {
123 // Ensure address is an array
124 if ( !is_array($address) ) {
125 $address = [];
126 }
127 $order->set_address( $address, 'billing' );
128 }
129
130
131 // 5. Set payment method
132 //$order->set_payment_method( 'cod' ); // cod = Cash on Delivery
133
134 // 6. Get the line item object
135 $item = $order->get_item( $item_id );
136
137 // 7. Set a custom line subtotal/total if needed
138 if ( $item && $item instanceof WC_Order_Item_Product ) {
139
140 if( !is_numeric($subtotal) ) {
141 $subtotal = $order->get_subtotal(); // If subtotal is not provided, use get the current subtotal
142 }
143
144 if( !is_numeric($total_paid) ) {
145 $total_paid = $order->get_total(); // If total_paid is not provided, use get the current total
146 }
147
148 // Set custom item totals (without tax)
149 $item->set_subtotal( $subtotal );
150 $item->set_total( $subtotal );
151
152 // --- Add/Update line item meta ---
153 // Pass associative array: ['_internal_note' => 'x', 'room_type' => 'Deluxe']
154 if ( ! empty( $item_meta ) && is_array( $item_meta ) ) {
155 foreach ( $item_meta as $key => $value ) {
156 // update_meta_data prevents duplicate keys; use add_meta_data for duplicates
157 $item->update_meta_data( $key, $value );
158 }
159 }
160
161 // Save the line item with meta and totals
162 $item->save();
163 }
164
165 // 8. Set the order total
166 if ( !is_numeric($total_paid) ) {
167 $total_paid = $order->get_total(); // If total_paid is not provided, use get the current total
168 }
169
170 $order->set_total( $total_paid ); // without tax
171
172 if (!empty($status) && $status !== $order->get_status()) {
173 $order->update_status( $status, 'Order updated programmatically.' );
174 }
175
176 // 9. Add custom order meta (loop through meta_data array)
177 if ( ! empty($meta_data) && is_array($meta_data) ) {
178 foreach ($meta_data as $key => $value) {
179 $order->update_meta_data($key, $value);
180 }
181 }
182
183 // Save the order
184 $order->save();
185
186 return true; // Order updated successfully
187
188 }
189
190 function create_order_after_save_booking_post($booking_id, $booking, $update){
191
192
193 if( (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) || $booking->post_type !== 'eshb_booking' ) return;
194
195 static $processing = false;
196
197 if ( $processing ) {
198 return;
199 }
200
201 if ( $update !== true) {
202 return;
203 }
204
205 if ( ! isset( $_POST['nonce'] ) ||
206 ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action') ) ) {
207 $new_status = $booking->post_status;
208 }else{
209 $new_status = !empty($_POST['post_status']) ? sanitize_text_field( wp_unslash( $_POST['post_status'] ) ) : 'not found';
210 }
211
212 $processing = true;
213 $order_id = get_post_meta($booking_id, '_order_post_created', true);
214
215 $booking_status = $booking->post_status;
216
217
218 // create woocommerce order if post status is
219 $allowed_statuses = array_keys(ESHB_Helper::eshb_get_booking_statuses());
220 if( !in_array($booking_status, $allowed_statuses) ) {
221 return;
222 }
223
224 $eshb_settings = get_option('eshb_settings');
225 $booking_type = isset($eshb_settings['booking-type']) ? $eshb_settings['booking-type'] : '';
226
227 $room_visibility = in_array('rooms', $eshb_settings['booking-form-fields']) ? true : false;
228 $visible_fields = $eshb_settings['booking-form-fields'] ?? [];
229
230 $thumbnail_id = get_post_meta( $booking_id, '_thumbnail_id', true );
231 $address = get_post_meta( $booking_id, 'eshb_booking_customer_details_metaboxes', true);
232
233 $eshb_booking_metaboxes = get_post_meta( $booking_id, 'eshb_booking_metaboxes', true);
234 $accomodation_id = !empty($eshb_booking_metaboxes['booking_accomodation_id']) ? $eshb_booking_metaboxes['booking_accomodation_id'] : '';
235 $room_quantity = !empty($eshb_booking_metaboxes['room_quantity']) ? $eshb_booking_metaboxes['room_quantity'] : 1;
236 $extra_bed_quantity = !empty($eshb_booking_metaboxes['extra_bed_quantity']) ? $eshb_booking_metaboxes['extra_bed_quantity'] : 0;
237 $adult_quantity = !empty($eshb_booking_metaboxes['adult_quantity']) ? $eshb_booking_metaboxes['adult_quantity'] : 1;
238 $children_quantity = !empty($eshb_booking_metaboxes['children_quantity']) ? $eshb_booking_metaboxes['children_quantity'] : 1;
239 $start_date = !empty($eshb_booking_metaboxes['start_date']) ? $eshb_booking_metaboxes['start_date'] : '';
240 $end_date = !empty($eshb_booking_metaboxes['start_date']) ? $eshb_booking_metaboxes['end_date'] : '';
241 $dates = !empty($eshb_booking_metaboxes['dates']) ? $eshb_booking_metaboxes['dates'] : '';
242 $details_html = !empty($eshb_booking_metaboxes['details_html']) ? $eshb_booking_metaboxes['details_html'] : '';
243 $extra_services = !empty($eshb_booking_metaboxes['extra_services']) ? $eshb_booking_metaboxes['extra_services'] : '';
244 $subtotal_price = !empty($eshb_booking_metaboxes['subtotal_price']) ? $eshb_booking_metaboxes['subtotal_price'] : 0;
245 $base_price = !empty($eshb_booking_metaboxes['base_price']) ? $eshb_booking_metaboxes['base_price'] : 0;
246 $total_price = !empty($eshb_booking_metaboxes['total_price']) ? $eshb_booking_metaboxes['total_price'] : 0;
247 $extra_services_charge = !empty($eshb_booking_metaboxes['extra_service_price']) ? $eshb_booking_metaboxes['extra_service_price'] : 0;
248 $extra_bed_price = !empty($eshb_booking_metaboxes['extra_bed_price']) ? $eshb_booking_metaboxes['extra_bed_price'] : 0;
249 $total_paid = !empty($eshb_booking_metaboxes['total_paid']) ? $eshb_booking_metaboxes['total_paid'] : 0;
250 $new_due = (float) $total_price - (float) $total_paid;
251 $extra_services_html = $this->format_extra_services($extra_services, $room_visibility);
252 $details_html = $this->format_details($eshb_booking_metaboxes, $visible_fields);
253
254 if(!empty($accomodation_id)){
255 $meta_data = [
256 'Accomodation ID' => $accomodation_id,
257 'Total Price' => $total_price,
258 'Subtotal Price' => $subtotal_price,
259 'Base Price' => $base_price,
260 'Extra Services Charge' => $extra_services_charge,
261 'Extra Bed Price' => $extra_bed_price,
262 'Rooms' => $room_quantity,
263 'Extra Bed' => $extra_bed_quantity,
264 'Adults' => $adult_quantity,
265 'Children' => $children_quantity,
266 'Start Date' => $start_date,
267 'End Date' => $end_date,
268 'Date' => $dates,
269 'Details' => $details_html,
270 'Extra Services IDs' => $extra_services,
271 'Extra Services' => $extra_services_html,
272 ];
273
274 $eshb_booking_metaboxes['extra_services_html'] = $extra_services_html;
275
276 // update woocommerce order if it exists
277 if(!empty($order_id)) {
278
279
280 $is_done_initial_update = get_post_meta($booking_id, 'is_done_initial_update', true);
281
282
283 if($is_done_initial_update == 'yes') {
284
285 // booking status change
286 if($new_due > 0 && $total_paid > 0){
287 $booking_status = 'deposit-payment';
288 }
289
290 if($new_due > 0){
291 update_post_meta($order_id, 'eshb_booking_due_amount', $new_due);
292 }
293
294 if($new_due < 1){
295
296 if (!empty($eshb_settings['booking-auto-approval']) && $eshb_settings['booking-auto-approval'] == true) {
297 $booking_status = 'completed';
298 }else{
299 $booking_status = 'processing';
300 }
301 delete_post_meta( $order_id, 'eshb_booking_due_amount');
302 }
303
304 if(in_array($new_status, ['processing', 'on-hold', 'cancelled', 'failed', 'refunded', 'trash'])){
305 $booking_status = $new_status;
306 }
307
308
309 if ($booking_type == 'woocommerce' && class_exists( 'woocommerce' )) {
310 $product_id = get_post_meta($accomodation_id, '_woocommerce_product_id', true);
311 $this->update_woocommerce_order($order_id, $subtotal_price, $total_paid, $booking_status, $product_id, $address, $meta_data);
312
313 // assign accomodation_id to woocommerce product
314 update_post_meta($product_id, '_accomodation_id', $accomodation_id);
315 }
316 }
317
318 update_post_meta($booking_id, 'is_done_initial_update', 'yes');
319
320 $processing = false;
321 return;
322 }else{
323
324 if ($booking_type == 'woocommerce' && class_exists( 'woocommerce' )) {
325
326 $product_id = ESHB_Helper::get_or_create_woocommerce_product($accomodation_id, $thumbnail_id);
327
328 // assign product_id to booking post
329 update_post_meta($booking_id, '_woocommerce_product_id', $product_id);
330
331 // create a woocommerce order
332 $order_id = $this->create_woocommerce_order($product_id, $subtotal_price, '', $address, $booking_status, $meta_data);
333
334
335 // update due
336 if($new_due > 0) {
337 update_post_meta($order_id, 'eshb_booking_due_amount', $new_due);
338 }
339
340 // assign accomodation_id to woocommerce product
341 update_post_meta($product_id, '_accomodation_id', $accomodation_id);
342 update_post_meta($order_id, '_booking_post_created', $booking_id);
343 update_post_meta($booking_id, '_order_post_created', $order_id);
344
345 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Public hook name kept for backward compatibility with existing integrations.
346 do_action( 'after_created_manual_booking', ['booking_id' => $booking_id, 'order_id' => $order_id, 'due' => $new_due] );
347
348
349
350 $processing = false;
351 return;
352 }
353 }
354 }
355 }
356
357 function eshb_update_booking_title_on_save( $post_id, $post, $update ) {
358
359 // Skip autosave/revision
360 if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
361 return;
362 }
363
364 // Run only on update (not insert)
365 if ( !$update ) {
366 return;
367 }
368
369 // Skip if deleting, trashing, or being moved to trash
370 if ( in_array( $post->post_status, [ 'trash', 'auto-draft' ], true ) ) {
371 return;
372 }
373
374 // Re-entrancy guard
375 static $running = false;
376 if ( $running ) return;
377 $running = true;
378
379 if ( ! isset( $_POST['nonce'] ) ||
380 ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), ESHB_Helper::generate_secure_nonce_action('eshb_global_nonce_action') ) ) {
381 $eshb_booking_metaboxes = get_post_meta( $post_id, 'eshb_booking_metaboxes', true );
382 }else{
383 $eshb_booking_metaboxes = !empty($_POST['eshb_booking_metaboxes']) ? map_deep( wp_unslash( $_POST['eshb_booking_metaboxes']), 'sanitize_text_field' ) : $eshb_booking_metaboxes;
384 }
385
386
387 $accommodation_id = is_array( $eshb_booking_metaboxes ) && ! empty( $eshb_booking_metaboxes['booking_accomodation_id'] ) ? (int) $eshb_booking_metaboxes['booking_accomodation_id'] : 0;
388 $accommodation_title = $accommodation_id ? get_the_title( $accommodation_id ) : '';
389 $new_title = 'Booking #' . $post_id . ( $accommodation_title ? ' for: ' . $accommodation_title : '' );
390
391 // update title if its not same to old title
392 if ( get_post_field( 'post_title', $post_id ) !== $new_title ) {
393 // Recursion is prevented by the static $running guard above; no
394 // need to remove/re-add the hook here.
395 wp_update_post( [
396 'ID' => $post_id,
397 'post_title' => $new_title,
398 ] );
399 }
400
401 // Prevent duplicate execution
402 set_transient( 'eshb_manual_booking_done_' . $post_id, true, 10 ); // expires
403 $running = false;
404 }
405
406 /**
407 * Build extra services titles.
408 */
409 private function format_extra_services($extra_services, $room_visibility) {
410 if (empty($extra_services) || !is_array($extra_services)) return '';
411
412 $titles = [];
413 foreach ($extra_services as $service) {
414 $service_id = $service['id'];
415 $qty = (int) ($service['quantity'] ?? 0);
416 $title = get_the_title($service_id);
417
418 if ($qty > 0) {
419 $meta = get_post_meta($service_id, 'eshb_service_metaboxes', true);
420 $charge_type = $meta['service_charge_type'] ?? '';
421 if ($room_visibility || $charge_type !== 'room') {
422 $title .= " For {$qty} " . ucfirst(strtolower($charge_type));
423 }
424 }
425 $titles[] = $title;
426 }
427 return implode(', ', $titles);
428 }
429
430 /**
431 * Build booking details string.
432 */
433 private function format_details($data, $visible_fields) {
434 $map = [
435 'rooms' => ['label' => 'Room', 'qty' => $data['room_quantity']],
436 'extra_beds' => ['label' => 'Extra Bed','qty' => $data['extra_bed_quantity']],
437 'adults' => ['label' => 'Adult', 'qty' => $data['adult_quantity']],
438 'childrens' => ['label' => 'Children', 'qty' => $data['children_quantity']],
439 ];
440
441 $details = [];
442 foreach ($map as $key => $info) {
443 if (in_array($key, $visible_fields) && $info['qty'] > 0) {
444 $details[] = "{$info['label']}: {$info['qty']}";
445 }
446 }
447 return implode(', ', $details);
448 }
449
450
451 }
452 if(is_admin()) {
453 new ESHB_Admin_Booking();
454 }