isset($_POST[$key]) ? sanitize_text_field(wp_unslash($_POST[$key])) : $default; $post_infos = ABPET_Function::get_all_meta($post_id); $booking_infos = []; $event_info = self::get_booking_info($post_infos); if (!empty($event_info)) { $booking_infos[] = $event_info; } $total_price = 0; if (!empty($booking_infos)) { foreach ($booking_infos as $booking_info) { $total_price += ($booking_info['total'] ?? 0); } } $cart_item['post_id'] = $post_id; $cart_item['booking_infos'] = $booking_infos; $cart_item['total_price'] = $total_price; $cart_item['line_total'] = $total_price; $cart_item['line_subtotal'] = $total_price; $cart_item = apply_filters('abpet_add_cart_item_data', $cart_item, $post_id); if ( function_exists( 'WC' ) && WC()->session ) { WC()->session->set( 'abpet_cart_success', get_the_title( $post_id ) . ' ' . __( 'Add to cart successfully!', 'abp-event-ticket' ) ); } } return $cart_item; } public function before_calculate_totals($cart_object): void { foreach ($cart_object->cart_contents as $value) { $post_id = $value['post_id'] ?? 0; if (get_post_type($post_id) == ABPET_Function::get_cpt()) { $total_price = $value['total_price'] ?? 0; $value['data']->set_price($total_price); $value['data']->set_regular_price($total_price); $value['data']->set_sale_price($total_price); $value['data']->set_sold_individually('yes'); $value['data']->get_price(); } } } public function cart_item_thumbnail($thumbnail, $cart_item) { $post_id = $cart_item['post_id'] ?? 0; if (get_post_type($post_id) == ABPET_Function::get_cpt()) { $url = ABPET_Function::get_image_url($post_id) ?: ABPET_BLANK_IMG_URL; if (!empty($url)) { $thumbnail = '
#
'; } } return $thumbnail; } public function get_item_data($item_data, $cart_item) { $post_id = $cart_item['post_id'] ?? 0; if (get_post_type($post_id) == ABPET_Function::get_cpt()) { global $post; $is_block_cart = false; $is_block_checkout = false; if (is_a($post, 'WP_Post')) { $is_block_cart = has_block('woocommerce/cart', $post->ID); $is_block_checkout = has_block('woocommerce/checkout', $post->ID); } if (is_checkout() && $is_block_checkout) { $item_data = $this->display_cart_item_block($cart_item); } elseif (is_cart() && $is_block_cart) { $item_data = $this->display_cart_item_block($cart_item); } else { ob_start(); do_action('abpet_display_cart_item', $cart_item); $content = ob_get_clean(); if (!empty($content)) { $item_data[] = array( 'name' => __('Booking Details', 'abp-event-ticket'), 'value' => $content ); } } } return $item_data; } public static function get_booking_info($post_infos = [], $context = '') { $booking_info = []; if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'abpet_registration_nonce')) { $post_int_array = fn($key) => (isset($_POST[$key]) && is_array($_POST[$key])) ? array_map('absint', wp_unslash($_POST[$key])) : []; $post_array = fn($key) => (isset($_POST[$key]) && is_array($_POST[$key])) ? array_map('sanitize_text_field', wp_unslash($_POST[$key])) : []; $post_int = fn($key, $default = '') => isset($_POST[$key]) ? absint($_POST[$key]) : $default; $post_val = fn($key, $default = '') => isset($_POST[$key]) ? sanitize_text_field(wp_unslash($_POST[$key])) : $default; $post_id = $post_infos['post_id'] ?? ''; $seat_type = $post_infos['seat_type'] ?? 'sp'; $seat_type = ABPET_Function::on_off('sp') ? $seat_type : 'ticket'; $event_date = $post_val('event_date'); $event_date = $event_date ?: $post_val('start_date'); $session_time = $post_val('session_time'); $session_time = $session_time ?: $post_val('start_time'); $ticket_price = 0; if (!empty($event_date) && !empty($session_time) && !empty($post_id)) { if ($seat_type == 'ticket') { $ticket_types = $post_array( 'item_check'); $item_qty = $post_int_array( 'item_qty'); if (!empty($ticket_types) && !empty($item_qty) && sizeof($ticket_types) > 0) { foreach ($ticket_types as $key => $ticket_type) { $qty = absint($item_qty[$key] ?? ''); if (!empty($ticket_type) && $qty > 0) { $price = ABPET_Function::get_price($post_infos, $ticket_type, $session_time); $booking_info['info'][$ticket_type]['id'] = $ticket_type; $booking_info['info'][$ticket_type]['name'] = ABPET_Function::ticket_name($ticket_type); $booking_info['info'][$ticket_type]['price'] = $price; $booking_info['info'][$ticket_type]['qty'] = $qty; $ticket_price = $ticket_price + $price * $qty; } } } } else { $seats = $post_val( 'sp_selected_seat'); $seats = $seats ? explode(',', $seats) : []; $types = $post_val( 'sp_selected_seat_id'); $types = $types ? explode(',', $types) : []; $sp_id = $post_int( 'sp_id'); if (!empty($sp_id) && !empty($seats) && !empty($types)) { foreach ($types as $index => $type) { $seat = $seats[$index] ?? ''; if (!empty($seat) && !empty($type)) { $price = ABPET_Function::get_price($post_infos, $type, $session_time); $booking_info['info'][$index]['id'] = $type; $booking_info['info'][$index]['name'] = $seat; $booking_info['info'][$index]['sp_id'] = $sp_id; $booking_info['info'][$index]['price'] = $price; $booking_info['info'][$index]['qty'] = 1; $ticket_price = $ticket_price + $price * 1; } } $booking_info['sp_id'] = $sp_id; } } if (!empty($booking_info['info'])) { $additional_info = self::get_additional_info($post_infos); $additional_price = self::get_additional_price($additional_info); $booking_info['seat_type'] = $seat_type; $booking_info['event_date'] = gmdate('Y-m-d', strtotime($event_date)); $booking_info['session_time'] = gmdate('H:i:s', strtotime('1970-01-01 ' . $session_time)); $booking_info['pass_info'] = self::get_passenger_info($post_infos); $booking_info['additional_info'] = $additional_info; $booking_info['price'] = $ticket_price; $booking_info['ex_price'] = $additional_price; $booking_info['total'] = $ticket_price + $additional_price; } } } return apply_filters('abpet_cart_booking_info_filter', $booking_info); } public static function get_additional_price($services) { $price = 0; if (is_array($services) && sizeof($services) > 0) { foreach ($services as $service) { $qty = $service['qty'] ?? ''; if (!empty($qty) && $qty > 0) { $ticket_price = $service['price'] ?? 0; $price = $price + $ticket_price * $qty; } } } return $price; } public static function get_additional_info($post_infos = []): array { $infos = array(); if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'abpet_registration_nonce')) { $services = ABPET_Function::additional_data($post_infos); if (!empty($services) && is_array($services)) { foreach ($services as $id => $service) { $name = isset($_POST['name_' . $id]) ? sanitize_text_field(wp_unslash($_POST[ 'name_' . $id])) : ''; $quantity = isset($_POST[ 'qty_' . $id]) ? sanitize_text_field(wp_unslash($_POST[ 'qty_' . $id])) : ''; if (!empty($name) && !empty($quantity) && $quantity > 0 && !empty($id)) { $infos[$id]['name'] = $name; $infos[$id]['qty'] = $quantity; $infos[$id]['price'] = ABPET_Function::get_additional_price($post_infos, $id); $infos[$id]['icon'] = $service['icon'] ?? ''; $infos[$id]['returnable'] = $service['returnable'] ?? 'no'; } } } } return $infos; } public static function get_passenger_info($post_infos = []): array { $pass_info = []; if (ABPET_Function::on_off('client_info') && isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'abpet_registration_nonce')) { $forms = ABPET_Function::client_data($post_infos); if (!empty($forms) && is_array($forms)) { foreach ($forms as $id => $form) { $infos = isset($_POST[$id]) ? array_map('sanitize_text_field', wp_unslash($_POST[$id])) : []; if (!empty($infos)) { foreach ($infos as $key => $info) { if (!empty($info)) { $pass_info[$key][$id]['label'] = $form['label'] ?? ''; $pass_info[$key][$id]['value'] = $info; } } } } } } return $pass_info; } public function display_cart_item_block($booking_infos): array { $item_data = []; $booking_info = $booking_infos['booking_infos'] ?? []; $post_id = $booking_infos['post_id'] ?? ''; if (!empty($booking_info) && sizeof($booking_info) > 0 && !empty($post_id) && get_post_type($post_id) == ABPET_Function::get_cpt()) { $html = ''; foreach ($booking_info as $cart_item) { if (!empty($cart_item)) { $ticket_infos = $cart_item['info'] ?? []; if (!empty($ticket_infos) && sizeof($ticket_infos) > 0) { $event_date = $cart_item['event_date'] ?? ''; $session_time = $cart_item['session_time'] ?? ''; $seat_type = $cart_item['seat_type'] ?? ''; $html .= esc_html__('Booking Information', 'abp-event-ticket') . '
'; $html .= esc_html__('Event Date', 'abp-event-ticket') . ' : ' . esc_html(ABPET_Function::date_format($event_date)) . '
'; $html .= esc_html__('Session Time', 'abp-event-ticket') . ' : ' . esc_html(ABPET_Function::date_format($event_date . ' ' . $session_time)) . '
'; $html .= esc_html__('Ticket Information', 'abp-event-ticket') . '
'; foreach ($ticket_infos as $ticket_info) { $price = $ticket_info['price'] ?? 0; $qty = $ticket_info['qty'] ?? 1; $price_text = $price > 0 ? wc_price($price) : __('FREE', 'abp-event-ticket'); $price = $price > 0 ? wc_price($price * $qty) : __('FREE', 'abp-event-ticket'); $name = $ticket_info['name'] ?? ''; if ($seat_type == 'sp') { $name = $name . ' - ' . ABPET_Function::sp_label($post_id, ($ticket_info['sp_id'] ?? $cart_item['sp_id'] ?? '')); } $html .= esc_html($name) . ' : ' . wp_kses_post($price_text) . ' X ' . esc_html($qty) . ' = ' . wp_kses_post($price) . '
'; } $additional_info = $cart_item['additional_info'] ?? []; if (ABPET_Function::on_off('additional_info') && !empty($additional_info) && sizeof($additional_info) > 0) { $html .= esc_html__('Additional Information', 'abp-event-ticket') . '
'; foreach ($additional_info as $additional) { if (is_array($additional)) { $qty = $additional['qty'] ?? 1; $price = $additional['price'] ?? 0; $price_text = $price > 0 ? wc_price($price) : __('FREE', 'abp-event-ticket'); $ex_price = $price > 0 ? wc_price($price * $qty) : __('FREE', 'abp-event-ticket'); $html .= esc_html($additional['name'] ?? '') . ' : ' . wp_kses_post($price_text) . ' X ' . esc_html($qty) . ' = ' . wp_kses_post($ex_price) . '
'; } } } $attendee_infos = $cart_item['pass_info'] ?? []; if (ABPET_Function::on_off('client_info') && !empty($attendee_infos) && sizeof($attendee_infos) > 0) { $html .= esc_html__('Client Information', 'abp-event-ticket') . '
'; foreach ($attendee_infos as $attendee_info) { if (!empty($attendee_info)) { foreach ($attendee_info as $attendee) { $label = $attendee['label'] ?? ''; $value = $attendee['value'] ?? ''; if ($label && $value) { $html .= esc_html($label) . ' : ' . esc_html($value) . '
'; } } } } } } } } if (!empty($html)) { $item_data[] = array('name' => __('Booking Details', 'abp-event-ticket'), 'value' => $html); } } return $item_data; } //=============================// public function after_checkout_validation( $data, $errors ): void { if ( ! WC()->cart ) { return; } foreach ( WC()->cart->get_cart() as $booking_infos ) { if ( ! ABPET_Function::checkout_validation( $booking_infos ) ) { $errors->add( 'abpet_booking_invalid', __( 'One or more event tickets are no longer available. Please review your booking and try again.', 'abp-event-ticket' ) ); break; } } } public function checkout_create_order_line_item($item, $_key, $booking_infos, $order = null): void { $booking_info = $booking_infos['booking_infos'] ?? []; $post_id = $booking_infos['post_id'] ?? 0; if (!empty($booking_info) && sizeof($booking_info) > 0 && !empty($post_id) && get_post_type($post_id) == ABPET_Function::get_cpt()) { $return = ''; foreach ($booking_info as $cart_item) { if (!empty($cart_item)) { $ticket_infos = $cart_item['info'] ?? []; $seat_type = $cart_item['seat_type'] ?? ''; if (!empty($ticket_infos) && sizeof($ticket_infos) > 0) { $event_date = $cart_item['event_date'] ?? ''; $session_time = $cart_item['session_time'] ?? ''; $additional_infos = $cart_item['additional_info'] ?? []; $attendee_infos = $cart_item['pass_info'] ?? []; $item->add_meta_data(__('Booking Information', 'abp-event-ticket') . ' ' . $return, ''); $item->add_meta_data(__('Event Date', 'abp-event-ticket'), ABPET_Function::date_format($event_date)); $item->add_meta_data(__('Session Time: ', 'abp-event-ticket'), ABPET_Function::date_format($event_date . ' ' . $session_time)); $item->add_meta_data(__('Ticket Information', 'abp-event-ticket'), ''); foreach ($ticket_infos as $ticket_info) { $price = $ticket_info['price'] ?? 0; $qty = $ticket_info['qty'] ?? 1; $price_text = $price > 0 ? wc_price($price) : __('FREE', 'abp-event-ticket'); $price = $price > 0 ? wc_price($price * $qty) : __('FREE', 'abp-event-ticket'); $name = $ticket_info['name'] ?? ''; if ($seat_type == 'sp') { $name = $name . ' - ' . ABPET_Function::sp_label($post_id, ($ticket_info['sp_id'] ?? $cart_item['sp_id'] ?? '')); } $item->add_meta_data($name, ($price_text . ' X ' . $qty . ' = ' . $price)); } if (ABPET_Function::on_off('additional_info') && !empty($additional_infos) && sizeof($additional_infos) > 0) { $item->add_meta_data(__('Additional Information', 'abp-event-ticket'), ''); foreach ($additional_infos as $additional) { $name = $additional['name'] ?? ''; $qty = $additional['qty'] ?? 1; $price = $additional['price'] ?? 0; $price_text = $price > 0 ? wc_price($price) : __('FREE', 'abp-event-ticket'); if (!empty($name) && $qty > 0) { $ex_price = $price > 0 ? wc_price($price * $qty) : __('FREE', 'abp-event-ticket'); $item->add_meta_data($name, ' ( ' . $price_text . ' X ' . $qty . ') = ' . $ex_price); } } } if (ABPET_Function::on_off('client_info') && !empty($attendee_infos) && sizeof($attendee_infos) > 0) { $item->add_meta_data(__('Client Information', 'abp-event-ticket'), ''); foreach ($attendee_infos as $attendee_info) { if (!empty($attendee_info)) { foreach ($attendee_info as $attendee) { $label = $attendee['label'] ?? ''; $value = $attendee['value'] ?? ''; if (!empty($label) && !empty($value)) { $item->add_meta_data($label, $value); } } } } } //=============================// } } } $item_info = [ 'post_id' => $post_id, 'user_id' => $order instanceof WC_Order ? $order->get_customer_id() : get_current_user_id(), 'booking_infos' => $booking_info, 'item_total' => $cart_item['total_price'] ?? '', ]; $item_info = apply_filters('abpet_checkout_create_order_line_item', $item_info, $booking_infos); $item->add_meta_data('_abpet_items', $item_info, true); } } public static function save_custom_data($order_id): void { if ($order_id) { $order = wc_get_order($order_id); if (!$order) { return; } $order_status = $order->get_status(); $payment_method = $order->get_payment_method_title(); $user_id = $order->get_customer_id(); $_billing_first_name = $order->get_billing_first_name(); $_billing_last_name = $order->get_billing_last_name(); $billing_email = $order->get_billing_email(); $billing_phone = $order->get_billing_phone(); $_billing_address_1 = $order->get_billing_address_1(); $_billing_address_2 = $order->get_billing_address_2(); $billing_name = $_billing_first_name . ' ' . $_billing_last_name; $billing_address = $_billing_address_1 . ' ' . $_billing_address_2; if ($order_status != 'failed') { global $wpdb; // Serialize concurrent order writes so availability is re-checked atomically and the last seat is never sold twice. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching -- MySQL advisory lock, no user input. $got_lock = $wpdb->get_var("SELECT GET_LOCK('abpet_orders_write', 10)"); if (!$got_lock) { $order->update_status('failed', __('Could not finalize the booking due to a temporary database lock. Please review and try again.', 'abp-event-ticket')); return; } try { $total_order = ABPET_Query::get_booking_query(['order_id' => $order_id], 0, 0, true); if ($total_order == 0) { $table_name = $wpdb->prefix . 'abpet_orders'; $rows = array(); foreach ($order->get_items() as $item_id => $item) { $item_infos = wc_get_order_item_meta($item_id, '_abpet_items'); if (!empty($item_infos) && is_array($item_infos) && sizeof($item_infos) > 0) { $post_id = $item_infos['post_id'] ?? ''; $booking_info = $item_infos['booking_infos'] ?? []; if (!empty($post_id) && get_post_type($post_id) == ABPET_Function::get_cpt() && !empty($booking_info) && sizeof($booking_info) > 0) { // Re-validate availability inside the lock to prevent overselling on concurrent checkouts. if (!ABPET_Function::checkout_validation($item_infos)) { $order->update_status('failed', __('One or more seats became unavailable. The order has been cancelled.', 'abp-event-ticket')); return; } foreach ($booking_info as $item_info) { if (empty($item_info)) { continue; } $seat_type = $item_info['seat_type'] ?? ($item_infos['seat_type'] ?? 'ticket'); $ticket_infos = $item_info['info'] ?? []; $additional_info = $item_info['additional_info'] ?? []; if (!empty($ticket_infos) && sizeof($ticket_infos) > 0) { $ticket_id = $ex_id = []; $qty = 0; foreach ($ticket_infos as $ticket_info) { if ($seat_type == 'sp') { $ticket_id[] = $ticket_info['name'] ?? ''; } else { $ticket_id[] = $ticket_info['id'] ?? ''; } $qty = $qty + ($ticket_info['qty'] ?? 1); } if (!empty($additional_info) && sizeof($additional_info) > 0) { foreach ($additional_info as $key => $additional) { $ex_id[] = $key; } } $money = static function ($value): ?string { return ('' === $value || null === $value) ? null : sanitize_text_field((string) $value); }; $others = []; $_order_status = 'wc-' . $order_status; $rows[] = [ 'order_id' => intval($order_id), 'item_id' => intval($item_id), 'post_id' => intval($post_id), 'user_id' => intval($user_id), 'seat_type' => sanitize_text_field($seat_type), 'event_date' => sanitize_text_field($item_info['event_date'] ?? ''), 'session_time' => sanitize_text_field($item_info['session_time'] ?? ''), 'sp_id' => intval($item_info['sp_id'] ?? ''), 'ticket_info' => wp_json_encode($ticket_infos), 'ticket_id' => wp_json_encode($ticket_id), 'qty' => intval($qty), 'price' => $money($item_info['price'] ?? ''), 'ex_info' => wp_json_encode($additional_info), 'ex_id' => wp_json_encode($ex_id), 'ex_price' => $money($item_info['ex_price'] ?? ''), 'total' => $money($item_info['total'] ?? ''), 'pass_info' => wp_json_encode($item_info['pass_info'] ?? []), 'checkin' => 0, 'order_status' => sanitize_text_field($_order_status), 'payment_method' => sanitize_text_field($payment_method), 'billing_name' => sanitize_text_field($billing_name), 'billing_email' => sanitize_text_field($billing_email), 'billing_phone' => sanitize_text_field($billing_phone), 'billing_address' => sanitize_text_field($billing_address), 'payment_status' => null, 'others' => wp_json_encode($others), 'created_at' => current_time('Y-m-d H:i'), 'updated_at' => current_time('Y-m-d H:i') ]; } } } } } foreach ($rows as $row) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->insert($table_name, $row); } if (!empty($rows)) { ABPET_Query::flush_cache(); } } } finally { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching -- MySQL advisory lock, no user input. $wpdb->query("SELECT RELEASE_LOCK('abpet_orders_write')"); } } } } public function checkout_order_processed($order_id): void { self::save_custom_data($order_id); } public function api_checkout_order_processed($order): void { $this->checkout_order_processed($order->get_id()); } public function order_status_changed($order_id): void { if (!empty($order_id) && $order_id > 0) { global $wpdb; $table_name = $wpdb->prefix . 'abpet_orders'; $order = wc_get_order($order_id); $order_status = $order->get_status(); foreach ($order->get_items() as $item_id => $item_values) { if ($item_id) { $order_infos = ABPET_Query::get_booking_query(['item_id' => $item_id]); if (!empty($order_infos) && sizeof($order_infos) > 0) { $order_info = current($order_infos); $others = $order_info['others'] ?? ''; if (!empty($others)) { $others = json_decode($others, true); $user_id = get_current_user_id(); $others['updated_by'] = $user_id; $data = [ 'others' => wp_json_encode($others), 'order_status' => 'wc-' . $order_status, 'updated_at' => current_time('Y-m-d H:i') ]; $where = ['item_id' => $item_id]; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->update($table_name, $data, $where, ['%s', '%s', '%s'], ['%d']); $mail_send = apply_filters('abpet_send_mail', false, $item_id); } } } } ABPET_Query::flush_cache(); } } } new ABPET_Woocommerce(); }