Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wp-travel-engine/admin/class-wp-travel-engine-booking-export.php
+++ b/wp-travel-engine/admin/class-wp-travel-engine-booking-export.php
@@ -17,6 +17,18 @@
class WP_Travel_Engine_Booking_Export {
/**
+ * Register Booking Export hooks.
+ *
+ * @since 6.8.0
+ */
+ public static function register_hooks() {
+ $self = new self();
+
+ add_action( 'admin_init', array( $self, 'init' ) );
+ add_action( 'admin_head', array( $self, 'add_booking_export_button' ) );
+ }
+
+ /**
* Safely unserialize data with restricted classes.
* Tries JSON first, then falls back to unserialize with security restrictions.
*
@@ -135,8 +147,7 @@
* @param array $filter_ids Booking IDs.
*
* @since 5.7.4
- *
- * @modified_since 6.3.5 - Trip Name and booking status filter added.
+ * @since 6.3.5 Trip Name and booking status filter added.
*/
public function export_query( $start_date, $end_date, $booking_status, $filter_ids ) {
global $wpdb;
@@ -2081,4 +2092,103 @@
return $row_data;
}
+
+ /**
+ * Add Booking export button.
+ *
+ * @since 5.7.4
+ * @since 6.3.5 Added the booking export button to the booking page.
+ * @since 6.8.0 Migrated from WPTravelEngineCorePostTypesBooking to here.
+ */
+ public function add_booking_export_button() {
+ global $post_type;
+
+ $current_screen = get_current_screen();
+
+ if ( 'edit-booking' !== $current_screen->id ) {
+ return;
+ }
+
+ if ( isset( $_GET['post_type'] ) && 'booking' === $_GET['post_type'] && 'booking' === $post_type ) {
+ // Remove admin notices.
+ remove_all_actions( 'admin_notices' );
+
+ $trips = wp_travel_engine_get_trips_array() ?? array();
+ $trips = array( 'all' => __( 'Select Trip', 'wp-travel-engine' ) ) + $trips;
+
+ $status = wp_travel_engine_get_booking_status() ?? array();
+ $status = array_merge(
+ array(
+ 'all' => array(
+ 'color' => '',
+ 'text' => __( 'Select Booking Status', 'wp-travel-engine' ),
+ ),
+ ),
+ $status
+ );
+
+ $trip_selected = isset( $_REQUEST['trip_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['trip_id'] ) ) : 'all';
+ $status_selected = isset( $_REQUEST['booking_status'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['booking_status'] ) ) : 'all';
+
+ ?>
+ <form id="wpte-booking-export-form" class="wpte-export-form" method="post">
+ <?php wp_nonce_field( 'booking_export_nonce_action', 'booking_export_nonce' ); ?>
+ <input type="text" data-fpconfig='{"mode":"range","showMonths":"2"}' id="wte-flatpickr__date-range"
+ class="wte-flatpickr">
+ <button id="wpte-booking-export-open-modal" type="button" class="button button-primary">
+ <?php esc_html_e( 'Export Bookings', 'wp-travel-engine' ); ?>
+ </button>
+ <div class="wpte-booking-export-modal-overlay">
+ <div class="wpte-booking-export-modal">
+ <div class="wpte-booking-export-modal-header">
+ <h2><?php esc_html_e( 'Export Bookings', 'wp-travel-engine' ); ?></h2>
+ <button type="button" class="wpte-booking-modal-close">
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
+ xmlns="http://www.w3.org/2000/svg">
+ <path d="M18 6L6 18M6 6L18 18" stroke="#F04438" stroke-width="2"
+ stroke-linecap="round" stroke-linejoin="round" />
+ </svg>
+ </button>
+ </div>
+ <div class="wpte-booking-export-modal-body">
+ <div class="wpte-field">
+ <label
+ for="wpte-booking-export-date"><?php esc_html_e( 'Date', 'wp-travel-engine' ); ?></label>
+ <input style="max-width: 320px;" id="wpte-booking-export-date" type="text"
+ name="wte_booking_range" data-fpconfig='{"mode":"range","showMonths":"2"}'
+ value="<?php echo esc_attr( isset( $_POST['wte_booking_range'] ) ? wp_unslash( $_POST['wte_booking_range'] ) : '' ); ?>"
+ class="wte-flatpickr">
+ </div>
+ <div class="wpte-field">
+ <label
+ for="wpte-booking-export-trip"><?php esc_html_e( 'Trip', 'wp-travel-engine' ); ?></label>
+ <select name="wptravelengine_trip_id" id="wpte-booking-export-trip">
+ <?php foreach ( $trips as $key => $value ) : ?>
+ <option value="<?php echo esc_attr( $key ); ?>"
+ name="wptravelengine_trip_id" <?php selected( $trip_selected, $key ); ?>><?php echo esc_html( $value ); ?></option>
+ <?php endforeach; ?>
+ </select>
+ </div>
+ <div class="wpte-field">
+ <label
+ for="wpte-booking-export-status"><?php esc_html_e( 'Booking Status', 'wp-travel-engine' ); ?></label>
+ <select style="max-width: 320px;" name="wptravelengine_booking_status"
+ id="wpte-booking-export-status">
+ <?php foreach ( $status as $key => $value ) : ?>
+ <option value="<?php echo esc_attr( $key ); ?>"
+ name="wptravelengine_booking_status" <?php selected( $status_selected, $key ); ?>><?php echo esc_html( $value['text'] ); ?></option>
+ <?php endforeach; ?>
+ </select>
+ </div>
+ </div>
+ <div class="wpte-booking-export-modal-footer">
+ <input type="submit" name="booking_export_submit" class="wpte-booking-export-submit button"
+ value="<?php esc_attr_e( 'Export', 'wp-travel-engine' ); ?>">
+ </div>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ }
}
--- a/wp-travel-engine/dist/admin/booking-edit.asset.php
+++ b/wp-travel-engine/dist/admin/booking-edit.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-i18n'), 'version' => 'c30929729bcdea7303bf');
+<?php return array('dependencies' => array('wp-i18n'), 'version' => '7a28bad3ade0a613e7ef');
--- a/wp-travel-engine/dist/admin/booking-legacy-edit.asset.php
+++ b/wp-travel-engine/dist/admin/booking-legacy-edit.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-i18n'), 'version' => '3090575531c8970e6e4f');
+<?php return array('dependencies' => array('wp-i18n'), 'version' => 'c608167bc485788df351');
--- a/wp-travel-engine/dist/admin/trip-edit.asset.php
+++ b/wp-travel-engine/dist/admin/trip-edit.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => '32788390b2e2e37e223f');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => '1f1c91fe03df7252efff');
--- a/wp-travel-engine/dist/admin/wte-admin.asset.php
+++ b/wp-travel-engine/dist/admin/wte-admin.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'fb5532390c924eabb31f');
+<?php return array('dependencies' => array(), 'version' => '8808f3a6a134852bd273');
--- a/wp-travel-engine/includes/backend/templates/booking/create.php
+++ b/wp-travel-engine/includes/backend/templates/booking/create.php
@@ -27,12 +27,12 @@
<p><?php esc_html_e( 'This action cannot be undone. It will permanently remove this traveller from the booking.', 'wp-travel-engine' ); ?></p>
</div>
<div class="wpte-button-group">
- <button type="button" class="wpte-button wpte-solid wpte-traveller-delete">
- <?php esc_html_e( 'Delete Traveller', 'wp-travel-engine' ); ?>
- </button>
<button type="button" class="wpte-button wpte-outlined wpte-cancel">
<?php esc_html_e( 'Cancel', 'wp-travel-engine' ); ?>
</button>
+ <button type="button" class="wpte-button wpte-solid wpte-traveller-delete wpte-user-delete">
+ <?php esc_html_e( 'Delete Traveller', 'wp-travel-engine' ); ?>
+ </button>
</div>
</div>
</div>
@@ -82,7 +82,7 @@
<!-- end booking-detail-fields -->
</div>
- <div class="wpte-booking-summary-area">
+ <div class="wpte-booking-summary-area" data-target-id="booking-summary">
<?php wptravelengine_get_admin_template( 'booking/partials/edit/booking-summary.php' ); ?>
<?php wptravelengine_get_admin_template( 'booking/partials/edit/booking-status.php' ); ?>
--- a/wp-travel-engine/includes/backend/templates/booking/index.php
+++ b/wp-travel-engine/includes/backend/templates/booking/index.php
@@ -38,10 +38,11 @@
</div> <!-- end .wpte-booking-fields-area -->
<!-- .wpte-booking-summary-area -->
- <div class="wpte-booking-summary-area">
+ <div class="wpte-booking-summary-area" data-target-id="booking-summary">
<?php wptravelengine_get_admin_template( 'booking/partials/booking-summary.php' ); ?>
<?php wptravelengine_get_admin_template( 'booking/partials/remaining-payment.php' ); ?>
<?php wptravelengine_get_admin_template( 'booking/partials/purchase-receipt.php' ); ?>
+ <?php wptravelengine_get_admin_template( 'booking/partials/migrate-button.php' ); ?>
<?php do_action( 'wptravelengine_booking_details_sidebar', $booking ); ?>
<?php
// If no action is registered, show the commission template from the core plugin.
--- a/wp-travel-engine/includes/backend/templates/booking/legacy/create.php
+++ b/wp-travel-engine/includes/backend/templates/booking/legacy/create.php
@@ -1,41 +0,0 @@
-<?php
-/**
- * Booking Details Metabox Content.
- */
-
-$date_picker_config = array(
- 'enableTime' => true,
- 'dateFormat' => 'Y-m-d H:i',
-);
-?>
-<div id="wptravelengine-booking-details">
- <?php wptravelengine_get_admin_template( 'booking/legacy/partials/header.php' ); ?>
- <div class="wpte-form-container">
- <?php wptravelengine_get_admin_template( 'booking/legacy/partials/tab-title.php' ); ?>
- <div class="wpte-booking-details-layout">
-
- <!-- .wpte-booking-fields-area -->
- <div class="wpte-booking-fields-area">
- <?php wptravelengine_get_admin_template( 'booking/legacy/partials/booking-info.php' ); ?>
- <div class="wpte-booking-collapsible-content">
- <?php
- wptravelengine_get_admin_template( 'booking/legacy/partials/traveller-info.php' );
- wptravelengine_get_admin_template( 'booking/legacy/partials/emergency-contact.php' );
- wptravelengine_get_admin_template( 'booking/legacy/partials/payment-details.php' );
- wptravelengine_get_admin_template( 'booking/legacy/partials/billing-details.php' );
- wptravelengine_get_admin_template( 'booking/legacy/partials/additional-field.php' );
- wptravelengine_get_admin_template( 'booking/legacy/partials/admin-notes.php' );
- ?>
- </div>
- <!-- end booking-detail-fields -->
- </div>
-
- <div class="wpte-booking-summary-area">
- <?php wptravelengine_get_admin_template( 'booking/legacy/partials/edit/booking-summary.php' ); ?>
- <?php wptravelengine_get_admin_template( 'booking/legacy/partials/edit/booking-status.php' ); ?>
- </div>
- <!-- Create nonce for booking creation -->
- <input type="hidden" name="wptravelengine_new_booking_nonce" value="<?php echo wp_create_nonce( 'wptravelengine_new_booking' ); ?>">
- </div>
- </div>
-</div>
--- a/wp-travel-engine/includes/backend/templates/booking/legacy/index.php
+++ b/wp-travel-engine/includes/backend/templates/booking/legacy/index.php
@@ -33,8 +33,9 @@
<!-- .wpte-booking-summary-area -->
<div class="wpte-booking-summary-area">
<?php wptravelengine_get_admin_template( 'booking/legacy/partials/booking-summary.php' ); ?>
- <?php wptravelengine_get_admin_template( 'booking/legacy/partials/remaining-payment.php' ); ?>
+ <?php // wptravelengine_get_admin_template( 'booking/legacy/partials/remaining-payment.php' ); ?>
<?php wptravelengine_get_admin_template( 'booking/legacy/partials/purchase-receipt.php' ); ?>
+ <?php wptravelengine_get_admin_template( 'booking/partials/migrate-button.php' ); ?>
<?php do_action( 'wptravelengine_booking_details_sidebar', $booking ); ?>
</div> <!-- end .wpte-booking-summary-area -->
</div>
--- a/wp-travel-engine/includes/backend/templates/booking/legacy/partials/header.php
+++ b/wp-travel-engine/includes/backend/templates/booking/legacy/partials/header.php
@@ -43,20 +43,25 @@
);
?>
</div>
+ <?php
+ $_migrated_to_id = absint( $booking->get_meta( '_migrated_to' ) );
+ $_already_migrated = $_migrated_to_id && get_post( $_migrated_to_id );
+ $_migrate_label = $_already_migrated
+ ? __( 'Re-Migrate Booking', 'wp-travel-engine' )
+ : __( 'Migrate to New Booking', 'wp-travel-engine' );
+ ?>
<div class="wpte-button-group">
- <?php if ( 'edit' === $template_mode ) : ?>
- <div class="wpte-button-group">
- <button style="display:block;" id="wpte-booking-submit-button" type="submit"
- class="wpte-button wpte-solid"><?php echo __( 'Save', 'wp-travel-engine' ); ?></button>
- </div>
- <?php else : ?>
- <a id="wpte-booking-edit-button"
- type="button"
- href="<?php echo esc_url( admin_url( "post.php?post={$booking->get_id()}&action=edit&wptravelengine_action=edit" ) ); ?>"
- class="wpte-button wpte-outlined">
- <?php echo __( 'Edit', 'wp-travel-engine' ); ?>
- </a>
- <?php endif ?>
+ <div>
+ <button type="button" class="wpte-button wpte-outlined" id="wpte-migrate-booking"
+ data-booking-id="<?php echo esc_attr( $booking->get_id() ); ?>"
+ data-nonce="<?php echo wp_create_nonce( 'wptravelengine_migrate_booking' ); ?>"
+ data-migrated-booking-id="<?php echo esc_attr( $_already_migrated ? $_migrated_to_id : '' ); ?>">
+ <?php echo esc_html( $_migrate_label ); ?>
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M21 9H7.5C5.01472 9 3 11.0147 3 13.5C3 15.9853 5.01472 18 7.5 18H12M21 9L17 5M21 9L17 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>
+ </button>
+ </div>
</div>
<!-- </div> -->
</header> <!-- end .wpte-page-header -->
--- a/wp-travel-engine/includes/backend/templates/booking/legacy/partials/purchase-receipt.php
+++ b/wp-travel-engine/includes/backend/templates/booking/legacy/partials/purchase-receipt.php
@@ -1,18 +1,17 @@
<?php
/**
+ * @var WPTravelEngineCoreModelsPostBooking $booking
* @since 6.4.0
*/
-/**
- * @var Booking $booking
- */
-
-use WPTravelEngineCoreModelsPostBooking;
-
+$migrated_to_id = absint( $booking->get_meta( '_migrated_to' ) );
+if ( $migrated_to_id && get_post( $migrated_to_id ) ) {
+ return;
+}
?>
<div>
- <button type="button" class="wpte-button wpte-outlined"
+ <button type="button" class="wpte-button wpte-outlined wpte-button-full"
data-booking-id="<?php echo esc_attr( $booking->get_id() ); ?>" id="wte-resend-purchase-receipt"
data-nonce="<?php echo wp_create_nonce( 'wte_resend_purchase_receipt' ); ?>">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
--- a/wp-travel-engine/includes/backend/templates/booking/partials/booking-info.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/booking-info.php
@@ -24,7 +24,7 @@
<?php endif ?>
<!-- booking-detail-fields -->
<div class="wpte-form-section" data-target-id="trip-details">
- <div class="wpte-fields-grid" data-columns="3">
+ <div class="wpte-fields-grid wpte-booking-info-grid" data-columns="6">
<?php $order_trip_form_fields->render(); ?>
</div>
</div>
No newline at end of file
--- a/wp-travel-engine/includes/backend/templates/booking/partials/booking-summary.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/booking-summary.php
@@ -41,13 +41,18 @@
);
foreach ( $cart_line_items as $item_type => $line_items ) :
+ $is_active = ( 'pricing_category' === $item_type ) ?: wptravelengine_is_addon_active( $item_type );
$line_item_group_title = apply_filters( 'wptravelengine_booking_line_item_group_title', $item_type, $line_items );
if ( empty( $line_items ) ) {
continue;
}
?>
<tr class="title">
- <td colspan="1"><strong><?php echo esc_html( $line_item_group_title ); ?></strong></td>
+ <td colspan="1"><strong><?php echo esc_html( $line_item_group_title ); ?></strong>
+ <?php if ( ! $is_active ) { ?>
+ <span class="wpte-tag error"><?php echo esc_html__( 'Not Active', 'wp-travel-engine' ); ?></span>
+ <?php } ?>
+ </td>
</tr>
<?php
foreach ( $line_items as $line_item ) {
--- a/wp-travel-engine/includes/backend/templates/booking/partials/edit/accommodation.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/edit/accommodation.php
@@ -14,7 +14,7 @@
// Check if accommodation addon is active
$is_accommodation_active = wptravelengine_is_addon_active( 'accommodation' );
$readonly_attr = ! $is_accommodation_active || 'readonly' === $template_mode ? 'readonly' : '';
-$disabled_attr = ! $is_accommodation_active || 'readonly' === $template_mode ? 'disabled' : '';
+
?>
<div class="wpte-form-section" data-target-id="accommodation">
@@ -55,14 +55,14 @@
name="line_items[accommodation][label][<?php echo $accommodation_index; ?>]"
value="<?php echo esc_attr( $accommodation_item['label'] ); ?>"
class="wpte-checkout__input" data-parsley-required-message="This value is required"
- <?php echo $disabled_attr; ?>>
+ <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
<input type="number"
name="line_items[accommodation][quantity][<?php echo $accommodation_index; ?>]"
value="<?php echo esc_attr( $accommodation_item['quantity'] ); ?>" min="1"
class="wpte-checkout__input" data-parsley-required-message="This value is required"
- <?php echo $disabled_attr; ?>>
+ <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
<input type="number"
@@ -70,13 +70,13 @@
value="<?php echo esc_attr( $accommodation_item['price'] ); ?>" min="0" step="0.01"
class="wpte-checkout__input wpte-cost-input"
data-parsley-required-message="This value is required" data-parsley-type="number"
- data-parsley-pattern="^d+(.d{1,2})?$" <?php echo $disabled_attr; ?>>
+ data-parsley-pattern="^d+(.d{1,2})?$" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
<input type="hidden"
name="line_items[accommodation][total][<?php echo $accommodation_index; ?>]"
value="<?php echo esc_attr( $accommodation_item['total'] ); ?>">
- <button class="wpte-button wpte-delete-button wpte-table-delete-row" type="button" <?php echo $disabled_attr; ?>>
+ <button class="wpte-button wpte-delete-button wpte-table-delete-row" type="button" <?php echo $readonly_attr; ?>>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
--- a/wp-travel-engine/includes/backend/templates/booking/partials/edit/booking-summary/line-items.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/edit/booking-summary/line-items.php
@@ -32,15 +32,16 @@
);
foreach ( $cart_line_items as $item_type => $line_items ) :
- if ( $item_type == 'pricing_category' || $item_type == 'pickup_point' ) {
- $is_booking_edit_enabled = true;
- } else {
- $is_booking_edit_enabled = false;
- }
+ $is_active = ( 'pricing_category' === $item_type ) ?: wptravelengine_is_addon_active( $item_type );
+
$line_item_group_title = apply_filters( 'wptravelengine_booking_line_item_group_title', $item_type, $line_items );
?>
<tr class="title">
- <td colspan="2"><strong><?php echo esc_html( $line_item_group_title ); ?></strong></td>
+ <td colspan="2"><strong><?php echo esc_html( $line_item_group_title ); ?></strong>
+ <?php if ( ! $is_active ) { ?>
+ <span class="wpte-tag error"><?php echo esc_html__( 'Not Active', 'wp-travel-engine' ); ?></span>
+ <?php } ?>
+ </td>
</tr>
<tr>
<td colspan="2" style="padding: 0;">
@@ -62,18 +63,20 @@
<div style="display: flex;align-items:center;gap:.5em;">
<span class="wpte-line-item-label" data-traveller-category-id="<?php echo esc_attr( $category_id ); ?>"><?php echo esc_html( $line_item['label'] ); ?>:</span>
<span class="wpte-line-item-quantity"><?php echo esc_html( $quantity ); ?></span> ×
- <?php if ( $item_type == 'pricing_category' || $item_type == 'pickup_point' ) { ?>
+ <?php if ( $item_type === 'pricing_category' || $item_type === 'pickup_point' ) { ?>
<input type="hidden"
name="line_items[<?php echo esc_attr( $item_type ); ?>][<?php echo esc_attr( $category_id ); ?>][quantity][]"
value="<?php echo esc_attr( $quantity ); ?>"
class="wpte-line-item-quantity-input"
- data-category-id="<?php echo esc_attr( $category_id ); ?>">
+ data-category-id="<?php echo esc_attr( $category_id ); ?>"
+ <?php echo $is_active ? '' : 'disabled'; ?>
+ >
<input type="number"
name="line_items[<?php echo esc_attr( $item_type ); ?>][<?php echo esc_attr( $category_id ); ?>][price][]"
value="<?php echo esc_attr( $line_item['price'] ); ?>"
style="min-width: 50px;" min="0" step="any"
data-category-id="<?php echo esc_attr( $category_id ); ?>"
- <?php echo $is_booking_edit_enabled ? '' : 'readonly'; ?>>
+ <?php echo $is_active ? '' : 'disabled'; ?>>
<?php } else { ?>
<span class="wpte-line-item-price"><?php echo esc_html( $price ); ?></span>
<?php } ?>
@@ -89,7 +92,9 @@
</table>
</td>
</tr>
-<?php endforeach; ?>
+ <?php
+endforeach;
+?>
<script type="text/html" id="tmpl-cart-line-item-pricing-category">
<tr data-category-id="{{data.categoryId}}">
--- a/wp-travel-engine/includes/backend/templates/booking/partials/edit/extra-services.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/edit/extra-services.php
@@ -17,7 +17,6 @@
// Check if extra-services addon is active
$is_extra_services_active = wptravelengine_is_addon_active( 'extra-services' );
$readonly_attr = ! $is_extra_services_active || 'readonly' === $template_mode ? 'readonly' : '';
-$disabled_attr = ! $is_extra_services_active || 'readonly' === $template_mode ? 'disabled' : '';
/**
* Function to get extra service type by matching label
@@ -73,24 +72,24 @@
?>
<tr>
<td>
- <input type="text" name="line_items[extra_service][label][<?php echo $extra_service_index; ?>]" value="<?php echo esc_attr( $value['label'] ); ?>" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $disabled_attr; ?>>
+ <input type="text" name="line_items[extra_service][label][<?php echo $extra_service_index; ?>]" value="<?php echo esc_attr( $value['label'] ); ?>" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
<div class="wpte-field">
- <select name="line_items[extra_service][type][<?php echo $extra_service_index; ?>]" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $disabled_attr; ?>>
+ <select name="line_items[extra_service][type][<?php echo $extra_service_index; ?>]" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $readonly_attr; ?>>
<option value="default" <?php echo $service_type == 'default' ? 'selected' : ''; ?>><?php echo __( 'Default', 'wp-travel-engine' ); ?></option>
<option value="advanced" <?php echo $service_type == 'advanced' ? 'selected' : ''; ?>><?php echo __( 'Advanced', 'wp-travel-engine' ); ?></option>
</select>
</div>
</td>
<td style="text-align: center;">
- <input type="number" name="line_items[extra_service][quantity][<?php echo $extra_service_index; ?>]" value="<?php echo esc_attr( $value['quantity'] ); ?>" min="1" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $disabled_attr; ?>>
+ <input type="number" name="line_items[extra_service][quantity][<?php echo $extra_service_index; ?>]" value="<?php echo esc_attr( $value['quantity'] ); ?>" min="1" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
- <input type="number" name="line_items[extra_service][price][<?php echo $extra_service_index; ?>]" value="<?php echo esc_attr( $value['price'] ); ?>" min="0" step="0.01" class="wpte-checkout__input wpte-cost-input" data-parsley-required-message="This value is required" data-parsley-type="number" data-parsley-pattern="^d+(.d{1,2})?$" <?php echo $disabled_attr; ?>>
+ <input type="number" name="line_items[extra_service][price][<?php echo $extra_service_index; ?>]" value="<?php echo esc_attr( $value['price'] ); ?>" min="0" step="0.01" class="wpte-checkout__input wpte-cost-input" data-parsley-required-message="This value is required" data-parsley-type="number" data-parsley-pattern="^d+(.d{1,2})?$" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
- <button class="wpte-button wpte-delete-button wpte-table-delete-row" type="button" <?php echo $disabled_attr; ?>>
+ <button class="wpte-button wpte-delete-button wpte-table-delete-row" type="button" <?php echo $readonly_attr; ?>>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3333 5V4.33333C13.3333 3.39991 13.3333 2.9332 13.1517 2.57668C12.9919 2.26308 12.7369 2.00811 12.4233 1.84832C12.0668 1.66666 11.6001 1.66666 10.6667 1.66666H9.33333C8.39991 1.66666 7.9332 1.66666 7.57668 1.84832C7.26308 2.00811 7.00811 2.26308 6.84832 2.57668C6.66667 2.9332 6.66667 3.39991 6.66667 4.33333V5M8.33333 9.58333V13.75M11.6667 9.58333V13.75M2.5 5H17.5M15.8333 5V14.3333C15.8333 15.7335 15.8333 16.4335 15.5608 16.9683C15.3212 17.4387 14.9387 17.8212 14.4683 18.0608C13.9335 18.3333 13.2335 18.3333 11.8333 18.3333H8.16667C6.76654 18.3333 6.06647 18.3333 5.53169 18.0608C5.06129 17.8212 4.67883 17.4387 4.43915 16.9683C4.16667 16.4335 4.16667 15.7335 4.16667 14.3333V5" stroke="#E84B4B" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
--- a/wp-travel-engine/includes/backend/templates/booking/partials/edit/travel-insurance.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/edit/travel-insurance.php
@@ -14,7 +14,7 @@
// Check if travel insurance addon is active
$is_travel_insurance_active = wptravelengine_is_addon_active( 'travel-insurance' );
$readonly_attr = ! $is_travel_insurance_active || 'readonly' === $template_mode ? 'readonly' : '';
-$disabled_attr = ! $is_travel_insurance_active || 'readonly' === $template_mode ? 'disabled' : '';
+
?>
<div class="wpte-form-section" data-target-id="travel-insurance">
@@ -61,17 +61,17 @@
?>
<tr>
<td>
- <input type="text" name="line_items[travel_insurance][label][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['label'] ); ?>" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $disabled_attr; ?>>
+ <input type="text" name="line_items[travel_insurance][label][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['label'] ); ?>" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
- <input type="number" name="line_items[travel_insurance][quantity][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['quantity'] ); ?>" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $disabled_attr; ?>>
+ <input type="number" name="line_items[travel_insurance][quantity][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['quantity'] ); ?>" class="wpte-checkout__input" data-parsley-required-message="This value is required" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
- <input type="number" name="line_items[travel_insurance][price][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['price'] ); ?>" min="0" step="0.01" class="wpte-checkout__input wpte-cost-input" data-parsley-required-message="This value is required" data-parsley-type="number" data-parsley-pattern="^d+(.d{1,2})?$" <?php echo $readonly_attr; ?> <?php echo $disabled_attr; ?>>
+ <input type="number" name="line_items[travel_insurance][price][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['price'] ); ?>" min="0" step="0.01" class="wpte-checkout__input wpte-cost-input" data-parsley-required-message="This value is required" data-parsley-type="number" data-parsley-pattern="^d+(.d{1,2})?$" <?php echo $readonly_attr; ?>>
</td>
<td style="text-align: center;">
<input type="hidden" name="line_items[travel_insurance][total][<?php echo $travel_insurance_index; ?>]" value="<?php echo esc_attr( $travel_insurance_item['total'] ); ?>">
- <button class="wpte-button wpte-delete-button wpte-table-delete-row" type="button" <?php echo $disabled_attr; ?>>
+ <button class="wpte-button wpte-delete-button wpte-table-delete-row" type="button" <?php echo $readonly_attr; ?>>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3333 5V4.33333C13.3333 3.39991 13.3333 2.9332 13.1517 2.57668C12.9919 2.26308 12.7369 2.00811 12.4233 1.84832C12.0668 1.66666 11.6001 1.66666 10.6667 1.66666H9.33333C8.39991 1.66666 7.9332 1.66666 7.57668 1.84832C7.26308 2.00811 7.00811 2.26308 6.84832 2.57668C6.66667 2.9332 6.66667 3.39991 6.66667 4.33333V5M8.33333 9.58333V13.75M11.6667 9.58333V13.75M2.5 5H17.5M15.8333 5V14.3333C15.8333 15.7335 15.8333 16.4335 15.5608 16.9683C15.3212 17.4387 14.9387 17.8212 14.4683 18.0608C13.9335 18.3333 13.2335 18.3333 11.8333 18.3333H8.16667C6.76654 18.3333 6.06647 18.3333 5.53169 18.0608C5.06129 17.8212 4.67883 17.4387 4.43915 16.9683C4.16667 16.4335 4.16667 15.7335 4.16667 14.3333V5" stroke="#E84B4B" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
--- a/wp-travel-engine/includes/backend/templates/booking/partials/header.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/header.php
@@ -5,6 +5,7 @@
* @var WPTravelEngineCoreModelsPostBooking $booking
* @since 6.4.0
*/
+use WPTravelEngineCorePostTypesBooking;
$cart_line_items = $cart_info->get_item()->get_line_items();
@@ -47,16 +48,38 @@
<?php
endif;
- $status_tag = sprintf( '<span class="wpte-tag %1$s">%1$s</span>', $booking->get_booking_status() );
- $admin_edited_tag = wptravelengine_toggled( $booking->get_meta( '_user_edited' ) ) ? sprintf( '<span class="wpte-tag %1$s">%1$s</span>', __( 'Customized Reservation', 'wp-travel-engine' ) ) : '';
+ $status_tag = sprintf( '<span class="wpte-tag %1$s">%1$s</span>', $booking->get_booking_status_label() );
+ $migration_tag = absint( $booking->get_meta( '_migrated_from' ) ) ? sprintf(
+ '<span class="wpte-tag migrated">%s%s</span>',
+ Booking::BADGE_ICON_MIGRATED,
+ esc_html__( 'Migrated', 'wp-travel-engine' )
+ ) : '';
+
+ if ( wptravelengine_toggled( $booking->get_meta( '__is_manual' ) ) ) {
+ $admin_edited_tag = sprintf(
+ '<span class="wpte-tag manual">%s%s</span>',
+ Booking::BADGE_ICON_MANUAL,
+ esc_html__( 'Manual', 'wp-travel-engine' )
+ );
+ } elseif ( wptravelengine_toggled( $booking->get_meta( '_user_edited' ) ) ) {
+ $admin_edited_tag = sprintf(
+ '<span class="wpte-tag warning">%s%s</span>',
+ Booking::BADGE_ICON_MODIFIED,
+ esc_html__( 'Modified', 'wp-travel-engine' )
+ );
+ } else {
+ $admin_edited_tag = '';
+ }
+
if ( $booking->get_booking_status() !== 'auto-draft' ) {
?>
<div class="wpte-page-header-content">
<?php
printf(
- '<div class="wpte-tags-wrap">%1$s%2$s</div>',
+ '<div class="wpte-tags-wrap">%1$s%2$s%3$s</div>',
$admin_edited_tag,
$status_tag,
+ $migration_tag,
);
?>
</div>
--- a/wp-travel-engine/includes/backend/templates/booking/partials/migrate-button.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/migrate-button.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Migrate Button Partial.
+ *
+ * Renders a navigation link between an original booking and its migrated counterpart.
+ * - Migrated booking (has _migrated_from): shows "View Original Booking" link.
+ * - Original booking (has _migrated_to): shows "View Migrated Booking" link.
+ *
+ * @var WPTravelEngineCoreModelsPostBooking $booking
+ * @since 6.8.0
+ */
+
+$original_booking_id = absint( $booking->get_meta( '_migrated_from' ) );
+$migrated_booking_id = absint( $booking->get_meta( '_migrated_to' ) );
+$svg = '<svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path
+ d="M17.5 7.50001L17.5 2.50001M17.5 2.50001H12.5M17.5 2.50001L10 10M8.33333 2.5H6.5C5.09987 2.5 4.3998 2.5 3.86502 2.77248C3.39462 3.01217 3.01217 3.39462 2.77248 3.86502C2.5 4.3998 2.5 5.09987 2.5 6.5V13.5C2.5 14.9001 2.5 15.6002 2.77248 16.135C3.01217 16.6054 3.39462 16.9878 3.86502 17.2275C4.3998 17.5 5.09987 17.5 6.5 17.5H13.5C14.9001 17.5 15.6002 17.5 16.135 17.2275C16.6054 16.9878 16.9878 16.6054 17.2275 16.135C17.5 15.6002 17.5 14.9001 17.5 13.5V11.6667"
+ stroke="currentcolor" stroke-width="1.39" stroke-linecap="round" stroke-linejoin="round" />
+</svg>';
+
+$original_post = $original_booking_id ? get_post( $original_booking_id ) : null;
+$migrated_post = $migrated_booking_id ? get_post( $migrated_booking_id ) : null;
+
+if ( $original_post && 'trash' !== $original_post->post_status ) {
+ printf(
+ '<a href="%1$s" class="wpte-button wpte-outlined wpte-migrate-nav-button">%2$s%3$s</a>',
+ esc_url( admin_url( "post.php?post={$original_booking_id}&action=edit" ) ),
+ esc_html__( 'View Original Booking', 'wp-travel-engine' ),
+ $svg
+ );
+} elseif ( $migrated_post && 'trash' !== $migrated_post->post_status ) {
+ printf(
+ '<a href="%1$s" class="wpte-button wpte-outlined wpte-migrate-nav-button">%2$s%3$s</a>',
+ esc_url( admin_url( "post.php?post={$migrated_booking_id}&action=edit" ) ),
+ esc_html__( 'View Migrated Booking', 'wp-travel-engine' ),
+ $svg
+ );
+}
--- a/wp-travel-engine/includes/backend/templates/booking/partials/tab-title.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/tab-title.php
@@ -7,6 +7,10 @@
?>
<div class="wpte-tabs-container">
+ <button class="wpte-tabs-nav wpte-tabs-nav--prev" type="button" aria-label="<?php esc_attr_e( 'Previous tabs', 'wp-travel-engine' ); ?>" disabled>
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
+ </button>
+ <div class="wpte-tabs-scroll">
<!-- .wpte-tabs -->
<ul class="wpte-tabs horizontal">
<li class="wpte-tab-item is-active">
@@ -50,6 +54,10 @@
</li>
<?php endif; ?>
</ul> <!-- end .wpte-tabs -->
+ </div> <!-- end .wpte-tabs-scroll -->
+ <button class="wpte-tabs-nav wpte-tabs-nav--next" type="button" aria-label="<?php esc_attr_e( 'Next tabs', 'wp-travel-engine' ); ?>">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
+ </button>
<div class="wpte-button-group">
<?php if ( 'edit' === $template_mode || apply_filters( 'wptravelengine_booking_details_save_button', false, $booking ) ) : ?>
--- a/wp-travel-engine/includes/class-wte-ajax.php
+++ b/wp-travel-engine/includes/class-wte-ajax.php
@@ -32,6 +32,10 @@
* @since 6.4.0
*/
$ajax_registry->register( AjaxResendPurchaseReceipt::class );
+ /**
+ * @since 6.8.0
+ */
+ $ajax_registry->register( AjaxMigrateBooking::class );
/**
* Adds upcoming tours ajax actions
--- a/wp-travel-engine/includes/class-wte-session.php
+++ b/wp-travel-engine/includes/class-wte-session.php
@@ -54,18 +54,21 @@
* @return mixed session data.
*/
public function get( string $key ) {
- $key = sanitize_key( $key );
+ $key = sanitize_key( $key );
+ $value = $this->session[ $key ] ?? null;
- if ( isset( $this->session[ $key ] ) ) {
- // Check if JSON encoded.
- if ( is_string( $this->session[ $key ] ) && ( is_array( json_decode( $this->session[ $key ], true ) ) ) ) {
- return json_decode( $this->session[ $key ], true );
- }
+ if ( null === $value ) {
+ return false;
+ }
- return maybe_unserialize( $this->session[ $key ] );
+ if ( is_string( $value ) ) {
+ $decoded = json_decode( $value, true );
+ if ( JSON_ERROR_NONE === json_last_error() && is_array( $decoded ) ) {
+ return $decoded;
+ }
}
- return false;
+ return wptravelengine_maybe_unserialize( $value );
}
/**
--- a/wp-travel-engine/includes/classes/Abstracts/BookingEditFormFields.php
+++ b/wp-travel-engine/includes/classes/Abstracts/BookingEditFormFields.php
@@ -9,6 +9,7 @@
use WPTravelEngineBuildersFormFieldsFormField;
use WPTravelEngineCoreModelsPostBooking;
+use WPTravelEngineHelpersCountries;
abstract class BookingEditFormFields extends FormField {
@@ -49,5 +50,77 @@
return $this->defaults;
}
- abstract static function structure( string $mode = 'edit' );
+ /**
+ * Format date using WP date/time format.
+ *
+ * @param string $date Stored date string.
+ * @param bool $with_time Include time format.
+ * @return string
+ * @since 6.8.0
+ */
+ protected function format_date_for_view( string $date, bool $with_time = false ): string {
+ try {
+ $dt = new DateTimeImmutable( $date, wp_timezone() );
+ } catch ( Exception $e ) {
+ return $date;
+ }
+ $format = get_option( 'date_format' );
+ if ( $with_time ) {
+ $format .= ' ' . get_option( 'time_format' );
+ }
+ return wp_date( $format, $dt->getTimestamp() );
+ }
+
+ /**
+ * Format a stored HH:MM time value using WP time format for display.
+ *
+ * @param string $time Stored time string (HH:MM).
+ * @return string
+ * @since 6.8.0
+ */
+ protected function format_time_for_view( string $time ): string {
+ $timestamp = strtotime( $time );
+ if ( false === $timestamp ) {
+ return $time;
+ }
+ return wp_date( get_option( 'time_format' ), $timestamp );
+ }
+
+ /**
+ * Convert datepicker field: flatpickr in edit mode, formatted date in view mode.
+ *
+ * @param array $field Field array.
+ * @return array
+ * @since 6.8.0
+ */
+ protected function apply_datepicker_field( array $field ): array {
+ $field['type'] = 'text';
+ if ( static::$mode !== 'edit' ) {
+ if ( ! empty( $field['default'] ) ) {
+ $field['default'] = $this->format_date_for_view( $field['default'] );
+ }
+ } else {
+ $field['class'] = 'wpte-date-picker';
+ }
+ return $field;
+ }
+
+ /**
+ * Resolve country name to its country code for field default.
+ *
+ * @param array $field Field array.
+ * @return array
+ * @since 6.8.0
+ */
+ protected function resolve_country_field_default( array $field ): array {
+ foreach ( Countries::list() as $key => $value ) {
+ if ( $field['default'] === $value ) {
+ $field['default'] = $key;
+ break;
+ }
+ }
+ return $field;
+ }
+
+ abstract public static function structure( string $mode = 'edit' );
}
--- a/wp-travel-engine/includes/classes/Abstracts/PostModel.php
+++ b/wp-travel-engine/includes/classes/Abstracts/PostModel.php
@@ -12,6 +12,7 @@
use WP_Error;
use WP_Post;
use WPTravelEngineTraitsFactory;
+use WPTravelEngineUtilitiesArrayUtility;
/**
* Abstract class PostModel.
@@ -53,6 +54,21 @@
public int $ID;
/**
+ * Nested Data.
+ *
+ * @since 6.8.0
+ */
+ protected array $nested_data = array();
+
+ /**
+ * Depth counter for with_delayed_save() reentrancy. >0 means writes are queued.
+ *
+ * @var int
+ * @since 6.8.0
+ */
+ private int $hold_save = 0;
+
+ /**
* PostModel Constructor.
*
* @param WP_Post|int $post The post-object.
@@ -159,8 +175,7 @@
* @since 6.4.0
*/
public function has_meta( string $meta_key ): bool {
- $meta_value = get_post_meta( $this->ID, $meta_key );
- return count( $meta_value ) > 0;
+ return metadata_exists( 'post', $this->ID, $meta_key );
}
/**
@@ -178,11 +193,14 @@
/**
* Update the post-metadata.
*
- * This method is abstract and must be implemented in child classes.
- *
- * @return bool|int
+ * @return PostModel|bool|int
+ * @since 6.8.0 Queues write to __changes when delayed save is active.
*/
public function update_meta( $meta_key, $meta_value ) {
+ if ( $this->hold_save > 0 ) {
+ return $this->set_meta( $meta_key, $meta_value );
+ }
+
/**
* @since 6.3.3 Filter for meta_value before updating.
*/
@@ -215,14 +233,39 @@
public function save(): object {
foreach ( $this->data['__changes'] as $meta_key => $meta_value ) {
$this->update_meta( $meta_key, $meta_value );
- unset( $this->data[ $meta_key ] );
+ if ( $this->hold_save === 0 ) {
+ unset( $this->data[ $meta_key ] );
+ }
+ }
+ if ( $this->hold_save === 0 ) {
+ $this->data['__changes'] = array();
}
- $this->data['__changes'] = array();
return $this;
}
/**
+ * Executes $callback with all meta writes queued in memory,
+ * then flushes everything to the database in one save() call.
+ *
+ * @param callable $callback Receives $this as first argument.
+ * @return object
+ * @since 6.8.0
+ */
+ public function with_delayed_save( callable $callback ): object {
+ ++$this->hold_save;
+ try {
+ $callback( $this );
+ } finally {
+ --$this->hold_save;
+ }
+ if ( $this->hold_save === 0 ) {
+ return $this->save();
+ }
+ return $this;
+ }
+
+ /**
* Get all post-metadata.
*
* This method is not abstract and can be used directly in child classes.
@@ -283,4 +326,78 @@
update_post_meta( $this->ID, $key, $value );
}
}
+
+ /**
+ * Has changes.
+ *
+ * @since 6.8.0
+ */
+ public function has_changes(): bool {
+ return ! empty( $this->data['__changes'] );
+ }
+
+ /**
+ * Get changes.
+ *
+ * @since 6.8.0
+ */
+ public function get_changes(): array {
+ return $this->data['__changes'] ?? array();
+ }
+
+ /**
+ * Specially designed for array type meta values. It allows setting nested meta values using dot notation.
+ *
+ * @param string $meta_key Dot seperated key for nested meta value. E.g. "parent_key.child_key".
+ * @param mixed $meta_value The value to be set for the specified meta key.
+ * @param bool $force Whether to override the existing value if the meta key already exists. Default is true.
+ *
+ * @return $this
+ * @since 6.8.0
+ */
+ public function set_nested_meta( string $meta_key, $meta_value, bool $force = true ): PostModel {
+ $meta_key_arr = explode( '.', $meta_key );
+ $first_key = array_shift( $meta_key_arr );
+
+ if ( ! $force && null !== $this->get_nested_meta( $meta_key ) ) {
+ return $this;
+ }
+
+ if ( ! isset( $this->nested_data[ $first_key ] ) ) {
+ $value = $this->get_meta( $first_key );
+ $this->nested_data[ $first_key ] = ArrayUtility::make( is_array( $value ) ? $value : array() );
+ }
+
+ /** @var ArrayUtility $nested_data */
+ $nested_data = $this->nested_data[ $first_key ];
+
+ $nested_data->set( implode( '.', $meta_key_arr ), $meta_value );
+
+ return $this->set_meta( $first_key, $nested_data->value() );
+ }
+
+ /**
+ * Specially designed for array type meta values. It allows getting nested meta values using dot notation.
+ *
+ * @param string $meta_key Dot seperated key for nested meta value. E.g. "parent_key.child_key".
+ * @param mixed $default The default value to be returned if the specified meta key is not found.
+ *
+ * @return mixed The value of the specified meta key or the default value if the meta key is not found.
+ * @since 6.8.0
+ */
+ public function get_nested_meta( string $meta_key, $default = null ) {
+ $meta_key_arr = explode( '.', $meta_key );
+ $first_key = array_shift( $meta_key_arr );
+
+ $value = $this->get_meta( $first_key );
+
+ if ( ! is_array( $value ) ) {
+ return $default;
+ }
+
+ /** @var ArrayUtility $array_value */
+ $array_value = ArrayUtility::make( $value );
+
+ return empty( $meta_key_arr ) ? $array_value->value() : $array_value->get( implode( '.', $meta_key_arr ), $default );
+ }
}
--- a/wp-travel-engine/includes/classes/Assets.php
+++ b/wp-travel-engine/includes/classes/Assets.php
@@ -247,25 +247,7 @@
// Enqueue checkout template version 2.0 script and style.
global $post;
if ( wp_travel_engine_is_checkout_page() && isset( $wte_cart ) ) {
- $shortcode_present = has_shortcode( $post->post_content, 'WP_TRAVEL_ENGINE_PLACE_ORDER' );
- $version = '1.0';
-
- if ( $shortcode_present ) {
- $pattern = get_shortcode_regex();
- if ( preg_match_all( '/' . $pattern . '/', $post->post_content, $matches ) && array_key_exists( 2, $matches ) ) {
- foreach ( $matches[2] as $index => $shortcode_name ) {
- if ( 'WP_TRAVEL_ENGINE_PLACE_ORDER' === $shortcode_name ) {
- $shortcode_atts = shortcode_parse_atts( $matches[3][$index] );
- $version = $shortcode_atts['version'] ?? $version;
- }
- }
- }
- }
-
- $wptravelengine_settings = get_option( 'wp_travel_engine_settings', array() );
- $checkout_page_template = wptravelengine_get_checkout_template_version( $wptravelengine_settings );
-
- if ( $version === '2.0' || $checkout_page_template === '2.0' || has_shortcode( $post->post_content, 'WPTRAVELENGINE_CHECKOUT' ) ) {
+ if ( has_shortcode( $post->post_content, 'WP_TRAVEL_ENGINE_PLACE_ORDER' ) || has_shortcode( $post->post_content, 'WPTRAVELENGINE_CHECKOUT' ) ) {
$this->enqueue_script( 'wte-popper' );
$this->enqueue_script( 'wte-tippyjs' );
$this->enqueue_style( 'trip-checkout' );
--- a/wp-travel-engine/includes/classes/Booking/Email/Template_Tags.php
+++ b/wp-travel-engine/includes/classes/Booking/Email/Template_Tags.php
@@ -45,6 +45,14 @@
*/
public $called_from_payment_details = false;
+ /**
+ * Constructor.
+ *
+ * @since 6.8.0 Use get_post_meta() directly for traveller_details to bypass magic property caching.
+ *
+ * @param int $booking_id Booking post ID.
+ * @param int $payment_id Payment post ID.
+ */
public function __construct( $booking_id, $payment_id ) {
$this->booking = get_post( $booking_id );
$this->payment = get_post( $payment_id );
@@ -59,7 +67,7 @@
$this->billing_details = (array) ( $this->booking->wptravelengine_billing_details ?? array() );
- $this->traveller_details = (array) ( $this->booking->wptravelengine_travelers_details ?? array() );
+ $this->traveller_details = (array) ( get_post_meta( $booking_id, 'wptravelengine_travelers_details', true ) ?: array() );
$this->emergency_details = (array) ( $this->booking->wptravelengine_emergency_details ?? array() );
@@ -489,6 +497,7 @@
*
* @since 6.5.0
* @since 6.7.11 Skip empty line item groups; added wptravelengine_email_manual_trigger_{type} action.
+ * @since 6.8.0 Escape trip date output; end date reads from $trip->end_datetime instead of wptravelengine_format_trip_end_datetime().
*
* @return string
*/
@@ -533,15 +542,13 @@
</tr>
<tr>
<td style="color: #566267;"><?php esc_html_e( 'Trip Date:', 'wp-travel-engine' ); ?></td>
- <td style="width: 50%;text-align: right;"><strong><?php echo wptravelengine_format_trip_datetime( $trip->datetime ); ?></strong></td>
+ <td style="width: 50%;text-align: right;"><strong><?php echo esc_html( wptravelengine_format_trip_datetime( $trip->datetime ) ); ?></strong></td>
</tr>
<tr>
<td style="color: #566267;"><?php esc_html_e( 'Trip End Date:', 'wp-travel-engine' ); ?></td>
<td style="width: 50%;text-align: right;"><strong>
<?php
- echo esc_html(
- wptravelengine_format_trip_end_datetime( $trip->datetime, $trip_modal )
- );
+ echo esc_html( wptravelengine_format_trip_datetime( $trip->end_datetime ) );
?>
</strong>
</td>
@@ -1216,6 +1223,9 @@
return is_null( $trip ) ? '' : $trip->get_trip_code();
}
+ /**
+ * Build and register email template tags.
+ */
public function get_email_tags() {
$trip = $this->trip;
@@ -1282,8 +1292,8 @@
'{customer_email}' => $this->get_billing_email(),
'{trip_booked_date}' => $this->get_current_date(),
'{trip_start_date}' => wptravelengine_format_trip_datetime( $trip->datetime ),
- '{trip_end_date}' => wptravelengine_format_trip_datetime( isset( $order_trip['end_datetime'] ) ? $order_trip['end_datetime'] : '' ),
- '{no_of_travellers}' => $this->get_no_of_travellers(), // @depreacted next This is deprecated tags that need to remove after addon like ( automator, waitlist ) fixed typos.
+ '{trip_end_date}' => wptravelengine_format_trip_datetime( $this->cart_info_parser->get_item()->get_end_date() ),
+ '{no_of_travellers}' => $this->get_no_of_travellers(), // @deprecated 6.8.0 This is a deprecated tag; remove after addons (automator, waitlist) are updated.
'{no_of_travelers}' => $this->get_no_of_travellers(),
'{trip_total_price}' => $this->get_total_amount(),
'{trip_paid_amount}' => wptravelengine_the_price_with_decimal( $_booking->get_total_paid_amount(), false ),
--- a/wp-travel-engine/includes/classes/Builders/FormFields/BillingEditFormFields.php
+++ b/wp-travel-engine/includes/classes/Builders/FormFields/BillingEditFormFields.php
@@ -7,15 +7,11 @@
namespace WPTravelEngineBuildersFormFields;
use WPTravelEngineAbstractsBookingEditFormFields;
-use WPTravelEngineCoreModelsPostBooking;
-use WPTravelEngineHelpersCountries;
-class BillingEditFormFields extends BookingEditFormFields {
- protected ?Booking $booking = null;
+class BillingEditFormFields extends BookingEditFormFields {
public function __construct( array $defaults = array(), string $mode = 'edit' ) {
parent::__construct( $defaults, $mode );
- static::$mode = $mode;
$this->init( $this->map_fields( static::structure( $mode ) ) );
}
@@ -41,27 +37,14 @@
$field['default'] = $this->defaults[ $name ] ?? $field['default'] ?? '';
$field['validations']['required'] = false;
- // Convert country code to country name to show in the billing form.
- $countries_list = Countries::list();
if ( $field['type'] == 'country' ) {
- foreach ( $countries_list as $key => $value ) {
- if ( $field['default'] === $value ) {
- $field['default'] = $key;
- }
- }
+ $field = $this->resolve_country_field_default( $field );
}
}
// Convert datepicker to text input to fix styling issue and to enable time selection.
if ( $field['type'] === 'datepicker' ) {
- $field['type'] = 'text';
- $field['class'] = 'wpte-date-picker';
- $field['attributes'] = array(
- 'data-options' => array(
- 'enableTime' => true,
- 'dateFormat' => 'Y-m-d H:i',
- ),
- );
+ $field = $this->apply_datepicker_field( $field );
}
if ( $field['type'] == 'file' && ! empty( $field['default'] ) ) {
@@ -98,12 +81,10 @@
);
}
- if ( 'booking' === $current_screen->id && $current_screen->action == 'add' && 'edit' == static::$mode ) :
- if ( $field['type'] == 'file' && empty( $field['default'] ) ) {
- return;
- }
- return $field;
- endif;
+ if ( 'booking' === $current_screen->id && $current_screen->action == 'add' && 'edit' == static::$mode
+ && $field['type'] == 'file' && empty( $field['default'] ) ) {
+ return;
+ }
return $field;
}
--- a/wp-travel-engine/includes/classes/Builders/FormFields/BillingFormFields.php
+++ b/wp-travel-engine/includes/classes/Builders/FormFields/BillingFormFields.php
@@ -7,7 +7,6 @@
namespace WPTravelEngineBuildersFormFields;
-use WPTravelEngineHelpersCountries;
use WPTravelEngineCoreModelsPostCustomer;
@@ -38,37 +37,7 @@
}
protected function map_fields( $fields, $booking_ref ) {
- // Initialize billing form data with session data as fallback.
- $billing_form_data = WTE()->session->get( 'billing_form_data' ) ?? array();
-
- // Get logged in user data if available.
- $user_data = get_user_by( 'id', get_current_user_id() );
-
- if ( $user_data ) {
- $customer_id = Customer::is_exists( $user_data->user_email );
- if ( $customer_id ) {
- $customer_data = new Customer( $customer_id );
- $billing_form_data = array_merge(
- array(
- 'fname' => $customer_data->get_customer_fname(),
- 'lname' => $customer_data->get_customer_lname(),
- 'email' => $customer_data->get_customer_email(),
- ),
- $customer_data->get_customer_addresses()
- );
- }
- }
-
- // Override with booking data if available.
- if ( $booking_ref ) {
- $booking_data = get_post_meta( $booking_ref, 'wptravelengine_billing_details', true );
- if ( $booking_data ) {
- $billing_form_data = $booking_data;
- }
- }
- if ( ! $billing_form_data ) {
- $billing_form_data = array();
- }
+ $billing_form_data = $this->get_billing_form_data( $booking_ref );
return array_map(
function ( $field ) use ( $billing_form_data ) {
@@ -104,6 +73,50 @@
}
/**
+ * Resolves billing form data from booking, customer, or session.
+ *
+ * Priority: booking meta > session data (merged with customer defaults) > empty array.
+ *
+ * @param int|null $booking_ref Booking post ID.
+ *
+ * @return array
+ * @since 6.8.0
+ */
+ protected function get_billing_form_data( $booking_ref ): array {
+ if ( $booking_ref ) {
+ $booking_data = get_post_meta( $booking_ref, 'wptravelengine_billing_details', true );
+ if ( is_array( $booking_data ) && ! empty( $booking_data ) ) {
+ return $booking_data;
+ }
+ }
+
+ $session_data = WTE()->session->get( 'billing_form_data' );
+ $session_data = is_array( $session_data ) ? $session_data : array();
+
+ $user = get_user_by( 'id', get_current_user_id() );
+ if ( ! $user ) {
+ return $session_data;
+ }
+
+ $customer_id = Customer::is_exists( $user->user_email );
+ if ( ! $customer_id ) {
+ return $session_data;
+ }
+
+ $customer = new Customer( $customer_id );
+ $customer_defaults = array_merge(
+ array(
+ 'fname' => $customer->get_customer_fname(),
+ 'lname' => $customer->get_customer_lname(),
+ 'email' => $customer->get_customer_email(),
+ ),
+ $customer->get_customer_addresses()
+ );
+
+ return array_merge( $customer_defaults, array_filter( $session_data ) );
+ }
+
+ /**
* @param array $form_data
* @return array
*/
@@ -133,26 +146,9 @@
$field['id'] = sprintf( 'billing_%s', $name );
}
$field['field_label'] = isset( $field['placeholder'] ) && $field['placeholder'] !== '' ? $field['placeholder'] : $field['field_label'];
- $field['default'] = $billing_form_data[ $name ] ?? $field['default'] ?? '';
-
- $value = $form_data[ $name ] ?? $field['default'] ?? '';
- $field['value'] = is_array( $value ) ? implode( ',', $value ) : $value;
-
- if ( $field['type'] == 'country' ) {
- // Convert country code to country name to show in the billing form.
- $countries_list = Countries::list();
- if ( isset( $field['value'] ) && is_string( $field['value'] ) && isset( $countries_list[ $field['value'] ] ) ) {
- $field['value'] = $countries_list[ $field['value'] ];
- }
- }
-
- if ( $field['type'] === 'trips_list' ) {
- // Convert trip id to trip name to show in the billing form.
- $trips_list = wp_travel_engine_get_trips_array();
- if ( isset( $field['value'] ) && is_string( $field['value'] ) && isset( $trips_list[ $field['value'] ] ) ) {
- $field['value'] = $trips_list[ $field['value'] ];
- }
- }
+ $value = $form_data[ $name ] ?? $field['default'] ?? '';
+ $field['default'] = $value;
+ $field['value'] = self::resolve_display_value( $value, $field['type'] );
}
return $field;
--- a/wp-travel-engine/includes/classes/Builders/FormFields/EmergencyEditFormFields.php
+++ b/wp-travel-engine/includes/classes/Builders/FormFields/EmergencyEditFormFields.php
@@ -7,7 +7,6 @@
namespace WPTravelEngineBuildersFormFields;
use WPTravelEngineAbstractsBookingEditFormFields;
-use WPTravelEngineHelpersCountries;
use WTE_Default_Form_Fields;
/**
* Form field class to render billing form fields.
@@ -25,8 +24,7 @@
public function __construct( array $defaults = array(), string $mode = 'edit', $booking = null ) {
parent::__construct( $defaults, $mode );
- $this->count = $defaults['index'] ?? ( $defaults['total_count'] ?? 0 ) + 1;
- static::$mode = $mode;
+ $this->count = $defaults['index'] ?? ( $defaults['total_count'] ?? 0 ) + 1;
$this->init( $this->map_fields( static::structure( $mode, $booking ) ) );
}
@@ -53,27 +51,14 @@
$field['field_label'] = isset( $field['placeholder'] ) && $field['placeholder'] !== '' ? $field['placeholder'] : ( $field['field_label'] ?? '' );
$field['default'] = $this->defaults[ $name ] ?? $field['default'] ?? '';
$field['validations']['required'] = false;
- // Convert country code to country name to show in the emergency form.
- $countries_list = Countries::list();
if ( $field['type'] == 'country' ) {
- foreach ( $countries_list as $key => $value ) {
- if ( $field['default'] === $value ) {
- $field['default'] = $key;
- }
- }
+ $field = $this->resolve_country_field_default( $field );
}
}
// Convert datepicker to text input to fix styling issue and to enable time selection.
if ( $field['type'] === 'datepicker' ) {
- $field['type'] = 'text';
- $field['class'] = 'wpte-date-picker';
- $field['attributes'] = array(
- 'data-options' => array(
- 'enableTime' => true,
- 'dateFormat' => 'Y-m-d H:i',
- ),
- );
+ $field = $this->apply_datepicker_field( $field );
}
if ( static::$mode !== 'edit' && ! ( $field['skip_disabled'] ?? false ) ) {
@@ -98,7 +83,7 @@
* @param object $booking
* @return array
*/
- static function structure( string $mode = 'edit', $booking = null ): array {
+ public static function structure( string $mode = 'edit', $booking = null ): array {
if ( $booking && $booking->get_meta( 'traveller_page_type' ) == 'old' ) {
return WTE_Default_Form_Fields::emergency_contact();
} else {
--- a/wp-travel-engine/includes/classes/Builders/FormFields/EmergencyFormFields.php
+++ b/wp-travel-engine/includes/classes/Builders/FormFields/EmergencyFormFields.php
@@ -8,7 +8,6 @@
namespace WPTravelEngineBuildersFormFields;
use WTE_Default_Form_Fields;
-use WPTravelEngineHelpersCountries;
use WPTravelEngineBuildersFormFieldsDefaultFormFields;
/**
@@ -99,22 +98,8 @@
}
$field['field_label'] = isset( $field['placeholder'] ) && $field['placeholder'] !== '' ? $field['placeholder'] : $field['field_label'];
$value = $form_data[ $name ] ?? $field['default'] ?? '';
- $field['value'] = is_arra