Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wp-travel-engine/admin/class-wp-travel-engine-admin.php
+++ b/wp-travel-engine/admin/class-wp-travel-engine-admin.php
@@ -42,9 +42,10 @@
* @param string $version The version of this plugin.
*
* @since 1.0.0
+ * @since 6.7.11 Changed trip-thumb-size dimensions from 374×226 to 500×500.
*/
public function __construct( $plugin_name, $version ) {
- add_image_size( 'trip-thumb-size', 374, 226, true ); // 260 pixels wide by 210 pixels tall, hard crop mode
+ add_image_size( 'trip-thumb-size', 500, 500, true ); // 500 pixels wide by 500 pixels tall, hard crop mode
add_image_size( 'destination-thumb-size', 600, 810, true ); // 260 pixels wide by 210 pixels tall, hard crop mode
add_image_size( 'destination-thumb-trip-size', 410, 250, true );
add_image_size( 'activities-thumb-size', 600, 810, true ); // 260 pixels wide by 210 pixels tall, hard crop mode
--- a/wp-travel-engine/admin/meta-parts/trip-metas.php
+++ b/wp-travel-engine/admin/meta-parts/trip-metas.php
@@ -648,17 +648,8 @@
'icon' => 'message',
'fields' => array(
array(
- 'label' => __( 'Section Title', 'wp-travel-engine' ),
- 'divider' => true,
- 'field' => array(
- 'name' => 'faqs_title',
- 'type' => 'TEXT',
- 'placeholder' => __( 'Enter FAQs Title', 'wp-travel-engine' ),
- ),
- ),
- array(
'field' => array(
- 'name' => 'faqs',
+ 'name' => 'faqs_data',
'type' => 'FAQS',
),
),
--- 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' => '1397249661879674fa1e');
+<?php return array('dependencies' => array('wp-i18n'), 'version' => 'c30929729bcdea7303bf');
--- a/wp-travel-engine/dist/admin/dashboard-analytics/index.asset.php
+++ b/wp-travel-engine/dist/admin/dashboard-analytics/index.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-element', 'wp-hooks', 'wp-i18n'), 'version' => 'ad3ebb81ad261d5470f6');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '316d56a7adad669e59cf');
--- a/wp-travel-engine/dist/admin/exports.asset.php
+++ b/wp-travel-engine/dist/admin/exports.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-element', 'wp-hooks', 'wp-i18n'), 'version' => '0e26e691e9efad012b86');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '954c9611afc6777b54d1');
--- a/wp-travel-engine/dist/admin/global-settings.asset.php
+++ b/wp-travel-engine/dist/admin/global-settings.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-element', 'wp-hooks', 'wp-i18n'), 'version' => 'b435c87d77e4adf30dae');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '06f77d7f5207ef0b97be');
--- a/wp-travel-engine/dist/admin/plugins-php.asset.php
+++ b/wp-travel-engine/dist/admin/plugins-php.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '9fc9f721ee9f3a23b141');
+<?php return array('dependencies' => array(), 'version' => 'a84c7a3463105e63d518');
--- 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' => '296da90aeb903902b5c1');
+<?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' => 'a085103cc19144c6c57c');
--- 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' => '66a5ed2449862a9d8fed');
+<?php return array('dependencies' => array(), 'version' => 'fb5532390c924eabb31f');
--- a/wp-travel-engine/dist/blocks/editor/editor.asset.php
+++ b/wp-travel-engine/dist/blocks/editor/editor.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '913f926c6659619467f2');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '725428162943badc7489');
--- a/wp-travel-engine/dist/blocks/single-trip/faqs/block.php
+++ b/wp-travel-engine/dist/blocks/single-trip/faqs/block.php
@@ -18,20 +18,58 @@
} else {
$post_meta = get_post_meta( $wtetrip->post->ID, 'wp_travel_engine_setting', true );
- $trip_faqs = ( $post_meta['faq'] ?? false );
+ $faqs = array();
- if ( ! $trip_faqs || ! is_array( $trip_faqs['faq_title'] ) ) {
- return;
- }
+ // Check for new category-based format first.
+ $faqs_data = ( $post_meta['faqs_data'] ?? false );
+ if ( $faqs_data && isset( $faqs_data['categories'] ) && is_array( $faqs_data['categories'] ) ) {
+ $global_faq_map = wptravelengine_get_global_faq_map();
+ $global_faq_ids = array_keys( $global_faq_map );
+
+ foreach ( $faqs_data['categories'] as $category ) {
+ if ( isset( $category['faqs'] ) && is_array( $category['faqs'] ) ) {
+ foreach ( $category['faqs'] as $faq ) {
+ $source_id = (string) ( $faq['sourceId'] ?? '' );
+ $added_in_bulk = isset( $faq['addedInBulk'] ) ? (bool) $faq['addedInBulk'] : false;
+
+ if ( $added_in_bulk && '' !== $source_id && ! in_array( $source_id, $global_faq_ids, true ) ) {
+ continue;
+ }
+
+ $question = $faq['question'] ?? '';
+ $answer = $faq['answer'] ?? '';
+
+ if ( $added_in_bulk && '' !== $source_id && isset( $global_faq_map[ $source_id ] ) ) {
+ $question = $global_faq_map[ $source_id ]['question'];
+ $answer = $global_faq_map[ $source_id ]['answer'];
+ }
- $faqs = array();
- foreach ( $trip_faqs['faq_title'] as $key => $faq_title ) {
- if ( isset( $trip_faqs['faq_content'][ $key ] ) ) {
- $faqs[] = array(
- 'question' => $faq_title,
- 'answer' => $trip_faqs['faq_content'][ $key ],
- );
+ $faqs[] = array(
+ 'question' => $question,
+ 'answer' => $answer,
+ );
+ }
+ }
}
+ } else {
+ // Fallback to legacy format.
+ $trip_faqs = ( $post_meta['faq'] ?? false );
+
+ if ( $trip_faqs && is_array( $trip_faqs['faq_title'] ) ) {
+ foreach ( $trip_faqs['faq_title'] as $key => $faq_title ) {
+ if ( isset( $trip_faqs['faq_content'][ $key ] ) ) {
+ $faqs[] = array(
+ 'question' => $faq_title,
+ 'answer' => $trip_faqs['faq_content'][ $key ],
+ );
+ }
+ }
+ }
+ }
+
+ // Return if no FAQs found.
+ if ( empty( $faqs ) ) {
+ return;
}
}
--- a/wp-travel-engine/dist/public/components/trip-booking-modal.asset.php
+++ b/wp-travel-engine/dist/public/components/trip-booking-modal.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'c4869223f5d9bc4452fa');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '5f2ab3f9966d6acf96fd');
--- a/wp-travel-engine/dist/public/exports.asset.php
+++ b/wp-travel-engine/dist/public/exports.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '63bb4c5043633af52d76');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'f1ba771697b8fd37ef76');
--- a/wp-travel-engine/dist/public/my-account.asset.php
+++ b/wp-travel-engine/dist/public/my-account.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '20019e0c77f90fdc2842');
+<?php return array('dependencies' => array(), 'version' => 'e06a485dfe90218a1795');
--- a/wp-travel-engine/dist/public/single-trip.asset.php
+++ b/wp-travel-engine/dist/public/single-trip.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-dom-ready', 'wp-i18n'), 'version' => 'a28db25ced1240d7959e');
+<?php return array('dependencies' => array('wp-dom-ready', 'wp-i18n'), 'version' => '40204043ac6b4e30e0e8');
--- a/wp-travel-engine/dist/public/trip-archive.asset.php
+++ b/wp-travel-engine/dist/public/trip-archive.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '5608538f6b256647d192');
+<?php return array('dependencies' => array(), 'version' => 'ac2154eb7fa1ebca344e');
--- a/wp-travel-engine/dist/public/trip-checkout.asset.php
+++ b/wp-travel-engine/dist/public/trip-checkout.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash'), 'version' => '2ff40bf8ce5199181cec');
+<?php return array('dependencies' => array('lodash'), 'version' => 'cfa87e8cd3e5ecdb86bc');
--- a/wp-travel-engine/dist/public/trip-faqs.asset.php
+++ b/wp-travel-engine/dist/public/trip-faqs.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array(), 'version' => '34925113df9160be1a00');
--- a/wp-travel-engine/dist/public/trip-search/index.asset.php
+++ b/wp-travel-engine/dist/public/trip-search/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '5f120fd208cef13353fd');
+<?php return array('dependencies' => array(), 'version' => '144716b7a35abc94bbda');
--- a/wp-travel-engine/dist/public/trip-search/widgets-slider.asset.php
+++ b/wp-travel-engine/dist/public/trip-search/widgets-slider.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '6ead055093010adc5990');
+<?php return array('dependencies' => array(), 'version' => 'b993433282e271669541');
--- a/wp-travel-engine/dist/public/trip-wishlist.asset.php
+++ b/wp-travel-engine/dist/public/trip-wishlist.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '596035bf457b9dfa9a8c');
+<?php return array('dependencies' => array(), 'version' => 'fabfe7ae15e9bb20a480');
--- a/wp-travel-engine/dist/public/wte-public.asset.php
+++ b/wp-travel-engine/dist/public/wte-public.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-i18n'), 'version' => '82adf0143af51dea302b');
+<?php return array('dependencies' => array('wp-i18n'), 'version' => '376c0b0510bc27194616');
--- a/wp-travel-engine/includes/backend/templates/booking/partials/payment-details.php
+++ b/wp-travel-engine/includes/backend/templates/booking/partials/payment-details.php
@@ -236,7 +236,7 @@
<div class="wpte-payment-summary-card">
<h3 class="wpte-payment-summary-title"><?php esc_html_e( 'Payment Summary', 'wp-travel-engine' ); ?></h3>
<table class="wpte-payment-summary-table">
- <tr>
+ <tr class="wpte-payment-deposit">
<td><?php esc_html_e( 'Total Deposit Amount', 'wp-travel-engine' ); ?></td>
<td><?php wptravelengine_the_price( '', true, $pricing_arguments ); ?></td>
</tr>
--- a/wp-travel-engine/includes/class-wp-travel-engine-custom-shortcodes.php
+++ b/wp-travel-engine/includes/class-wp-travel-engine-custom-shortcodes.php
@@ -59,6 +59,7 @@
/*
** @since 5.5.7
+ ** @since 6.7.11 Skip WP_Query when wishlist is empty to prevent fatal error.
*/
public function wishlist_shortcode( $attr ) {
$attr = shortcode_atts(
@@ -67,24 +68,26 @@
'wte_wishlist'
);
- ob_start();
+ $query = null;
$current_user_wishlist = wptravelengine_user_wishlists();
- $current_user_wishlist = is_array( $current_user_wishlist ) ? $current_user_wishlist : array( 0 );
+ if ( ! empty( $current_user_wishlist ) && is_array( $current_user_wishlist ) ) {
+ $query = new WP_Query(
+ array(
+ 'post_type' => WP_TRAVEL_ENGINE_POST_TYPE,
+ 'post_status' => 'publish',
+ 'post__in' => $current_user_wishlist,
+ 'orderby' => 'post__in',
+ 'paged' => get_query_var( 'paged' ),
+ )
+ );
+ }
- $query = new WP_Query(
- array(
- 'post_type' => WP_TRAVEL_ENGINE_POST_TYPE,
- 'post_status' => 'publish',
- 'post__in' => $current_user_wishlist,
- 'orderby' => 'post__in',
- 'paged' => get_query_var( 'paged' ),
- )
- );
+ ob_start();
?>
<div>
<div class="wte-category-outer-wrap wte-user-wishlists">
<?php
- if ( 0 === $query->post_count ) {
+ if ( null === $query || 0 === $query->post_count ) {
$button_txt = __( 'Explore Trips', 'wp-travel-engine' );
$site_url = get_site_url();
$trip_page_url = $site_url . '/trip';
--- a/wp-travel-engine/includes/classes/Assets.php
+++ b/wp-travel-engine/includes/classes/Assets.php
@@ -165,11 +165,15 @@
/**
* Register Plugin Scripts.
+ *
+ * @since 6.7.11 Added wte-trip-faqs style and script registration.
*/
protected function register_plugin_scripts() {
/* @var PluginSettings $plugin_settings */
$this->register_style( Asset::register( 'style-trip-booking-modal', 'public/components/style-trip-booking-modal.css' ) );
$this->register_style( Asset::register( 'single-trip', 'public/single-trip.css' ) );
+ $this->register_style( Asset::register( 'wte-trip-faqs', 'public/trip-faqs.css' ) );
+ $this->register_script( Asset::register( 'wte-trip-faqs', 'public/trip-faqs.js' ) );
$this->register_style( Asset::register( 'wte-blocks-index', 'blocks/index.css' ) );
$this->register_style( Asset::register( 'wptravelengine-rtl', 'public/wte-rtl.css' ) );
@@ -712,6 +716,8 @@
/**
* Localize Scripts.
+ *
+ * @since 6.7.11 Added faq_settings_url to admin localize data.
*/
public function get_global_localize_data() {
$settings = get_option( 'wp_travel_engine_settings', array() );
@@ -806,7 +812,15 @@
}
if ( is_admin() ) {
- $l10n[ 'admin_url' ] = admin_url( '/' );
+ $l10n[ 'admin_url' ] = admin_url( '/' );
+ $l10n[ 'faq_settings_url' ] = add_query_arg(
+ array(
+ 'post_type' => 'booking',
+ 'page' => 'class-wp-travel-engine-admin.php',
+ 'wpte-tab' => 'faqs',
+ ),
+ admin_url( 'edit.php' )
+ ) . '#display-single-trip';
}
global $post;
@@ -1243,4 +1257,5 @@
wp_set_script_translations( 'wptravelengine-customer-edit', 'wp-travel-engine', $languages_path );
wp_set_script_translations( 'wptravelengine-upcoming-tours', 'wp-travel-engine', $languages_path );
}
+
}
--- a/wp-travel-engine/includes/classes/Blocks/Util/WP_Kses.php
+++ b/wp-travel-engine/includes/classes/Blocks/Util/WP_Kses.php
@@ -14,11 +14,19 @@
/**
* @return array
* @since 6.3.4
+ * @since 6.7.11 Added style tag support; use local copy instead of mutating global $allowedposttags.
*/
public function wptravelengine_post() {
global $allowedposttags;
- $allowedposttags['iframe'] = array(
+ $tags = $allowedposttags;
+
+ $tags['style'] = array(
+ 'type' => true,
+ 'media' => true,
+ );
+
+ $tags['iframe'] = array(
'src' => true,
'width' => true,
'height' => true,
@@ -29,7 +37,7 @@
'referrerpolicy' => true,
);
- return $allowedposttags;
+ return $tags;
}
public function allowed_html( $allowed_tags, $context ) {
--- a/wp-travel-engine/includes/classes/Booking/Email/Template_Tags.php
+++ b/wp-travel-engine/includes/classes/Booking/Email/Template_Tags.php
@@ -486,6 +486,7 @@
* Get trip booking Summary details.
*
* @since 6.5.0
+ * @since 6.7.11 Skip empty line item groups; added wptravelengine_email_manual_trigger_{type} action.
*
* @return string
*/
@@ -602,20 +603,30 @@
} else {
foreach ( $line_items as $line_item => $_items ) {
- $label = $this->get_line_item_label( $line_item );
- ?>
- <tr>
- <td colspan="2"><strong><?php echo esc_html( $label ); ?></strong></td>
- </tr>
- <?php
- foreach ( $_items as $item ) {
- ?>
- <tr>
- <td style="color: #566267;"><?php echo esc_html( $item['label'] ?? '' ) . ': ' . $item['quantity'] . ' x ' . wptravelengine_the_price( $item['price'], false ); ?></td>
- <td style="width: 50%;text-align: right;"><strong><?php echo wptravelengine_the_price( $item['total'], false ); ?></strong></td>
- </tr>
- <?php
+ if ( empty( $_items ) ) {
+ continue;
}
+
+ if ( ! has_action( 'wptravelengine_email_manual_trigger_' . $line_item ) ) {
+ $label = $this->get_line_item_label( $line_item );
+ if ( $label ) {
+ ?>
+ <tr>
+ <td colspan="2"><strong><?php echo esc_html( $label ); ?></strong></td>
+ </tr>
+ <?php
+ }
+ foreach ( $_items as $item ) {
+ ?>
+ <tr>
+ <td style="color: #566267;"><?php echo esc_html( $item['label'] ?? '' ) . ': ' . esc_html( $item['quantity'] ) . ' x ' . wptravelengine_the_price( $item['price'], false ); ?></td>
+ <td style="width: 50%;text-align: right;"><strong><?php echo wptravelengine_the_price( $item['total'], false ); ?></strong></td>
+ </tr>
+ <?php
+ }
+ }
+
+ do_action( 'wptravelengine_email_manual_trigger_' . $line_item, $_items, (array) $cart_info );
}
}
endforeach;
@@ -1630,8 +1641,9 @@
* This function returns label for line items of cart.
*
* @since 6.7.9
+ * @since 6.7.11 Changed visibility from public to protected.
*/
- public function get_line_item_label( $line_item ) {
+ protected function get_line_item_label( $line_item ) {
switch ( $line_item ) :
case 'pricing_category':
--- a/wp-travel-engine/includes/classes/Builders/admin-settings/display/single-trip.php
+++ b/wp-travel-engine/includes/classes/Builders/admin-settings/display/single-trip.php
@@ -511,6 +511,22 @@
),
),
),
+ array(
+ 'title' => __( 'FAQs', 'wp-travel-engine' ),
+ 'id' => 'faqs',
+ 'fields' => array(
+ array(
+ 'field_type' => 'ALERT',
+ 'status' => 'info',
+ 'content' => __( 'Create reusable FAQs that can be used across multiple trips.', 'wp-travel-engine' ),
+ ),
+ array(
+ 'field_type' => 'FAQ_LIST',
+ 'name' => 'faqs.items',
+ 'divider' => true,
+ ),
+ ),
+ ),
),
),
),
--- a/wp-travel-engine/includes/classes/Builders/admin-settings/display/trip-archive.php
+++ b/wp-travel-engine/includes/classes/Builders/admin-settings/display/trip-archive.php
@@ -287,6 +287,16 @@
'field_type' => 'DIVIDER',
),
array(
+ 'label' => __( 'Show Original Size Images', 'wp-travel-engine' ),
+ 'description' => __( 'Enable to display trip listing images at their original uploaded size instead of the cropped thumbnail.', 'wp-travel-engine' ),
+ 'field_type' => 'SWITCH',
+ 'name' => 'card_new_layout.enable_original_size_image',
+ 'isNew' => version_compare( WP_TRAVEL_ENGINE_VERSION, '6.7.13', '<' ),
+ ),
+ array(
+ 'field_type' => 'DIVIDER',
+ ),
+ array(
'label' => __( 'Trip Duration', 'wp-travel-engine' ),
'description' => __( 'Choose how the trip duration should be displayed, not applicable for hourly trips.', 'wp-travel-engine' ),
'field_type' => 'SELECT_BUTTON',
--- a/wp-travel-engine/includes/classes/Core/Booking/BookingProcess.php
+++ b/wp-travel-engine/includes/classes/Core/Booking/BookingProcess.php
@@ -316,10 +316,11 @@
* Triggers Payment Gateway.
*
* @return void
+ * @since 6.7.11 Increased transient expiry to 24 hours; added payment_key meta storage.
*/
protected function payment_gateway_process() {
global $wte_cart;
- set_transient( "payment_key_{$this->payment->get_payment_key()}", $this->payment->get_id(), 60 * 10 );
+ set_transient( "payment_key_{$this->payment->get_payment_key()}", $this->payment->get_id(), 24 * HOUR_IN_SECONDS );
$payment_gateway = PaymentGateways::instance()->get_payment_gateway( $this->payment_method );
@@ -554,7 +555,8 @@
->set_meta( 'payment_status', 'pending' )
->set_meta( 'billing_info', $this->billing_info )
->set_meta( 'payment_source', 'checkout' )
- ->set_meta( 'is_due_payment', $this->payment_type === 'due' ? 'yes' : 'no' );
+ ->set_meta( 'is_due_payment', $this->payment_type === 'due' ? 'yes' : 'no' )
+ ->set_meta( 'payment_key', $payment_model->get_payment_key() );
$payment_types = apply_filters(
'wte_payment_modes_and_titles',
@@ -765,7 +767,7 @@
* @return bool
*/
public static function is_traveler_information_save_request(): bool {
- return check_ajax_referer( 'wp_travel_engine_final_confirmation_nonce', 'nonce', false );
+ return isset( $_REQUEST['nonce'] ) && check_ajax_referer( 'wp_travel_engine_final_confirmation_nonce', 'nonce', false );
}
/**
--- a/wp-travel-engine/includes/classes/Core/Controllers/RestAPI/V2/Settings.php
+++ b/wp-travel-engine/includes/classes/Core/Controllers/RestAPI/V2/Settings.php
@@ -493,22 +493,24 @@
*
* @return array
* @since 6.2.0
+ * @since 6.7.11 Added enable_original_size_image setting.
*/
protected function prepare_trip_card_details( WP_REST_Request $request ): array {
$settings = array();
$settings['card_new_layout'] = array(
// 'enable' => wptravelengine_toggled( $this->plugin_settings->get( 'display_new_trip_listing' ) ),
- 'enable_slider' => wptravelengine_toggled( $this->plugin_settings->get( 'display_slider_layout', '1' ) ),
- 'enable_featured_tag' => wptravelengine_toggled( $this->plugin_settings->get( 'show_featured_tag', '1' ) ),
- 'enable_wishlist' => wptravelengine_toggled( $this->plugin_settings->get( 'show_wishlist', '1' ) ),
- 'enable_map' => wptravelengine_toggled( $this->plugin_settings->get( 'show_map_on_card', '1' ) ),
- 'enable_excerpt' => wptravelengine_toggled( $this->plugin_settings->get( 'show_excerpt', '1' ) ),
- 'enable_difficulty' => wptravelengine_toggled( $this->plugin_settings->get( 'show_difficulty_tax', '1' ) ),
- 'enable_tags' => wptravelengine_toggled( $this->plugin_settings->get( 'show_trips_tag', '1' ) ),
- 'enable_fsd' => wptravelengine_toggled( $this->plugin_settings->get( 'show_date_layout', '1' ) ),
- 'enable_available_months' => wptravelengine_toggled( $this->plugin_settings->get( 'show_available_months', '1' ) ),
- 'enable_available_dates' => wptravelengine_toggled( $this->plugin_settings->get( 'show_available_dates', '1' ) ?? false ),
+ 'enable_slider' => wptravelengine_toggled( $this->plugin_settings->get( 'display_slider_layout', '1' ) ),
+ 'enable_featured_tag' => wptravelengine_toggled( $this->plugin_settings->get( 'show_featured_tag', '1' ) ),
+ 'enable_wishlist' => wptravelengine_toggled( $this->plugin_settings->get( 'show_wishlist', '1' ) ),
+ 'enable_map' => wptravelengine_toggled( $this->plugin_settings->get( 'show_map_on_card', '1' ) ),
+ 'enable_excerpt' => wptravelengine_toggled( $this->plugin_settings->get( 'show_excerpt', '1' ) ),
+ 'enable_difficulty' => wptravelengine_toggled( $this->plugin_settings->get( 'show_difficulty_tax', '1' ) ),
+ 'enable_tags' => wptravelengine_toggled( $this->plugin_settings->get( 'show_trips_tag', '1' ) ),
+ 'enable_fsd' => wptravelengine_toggled( $this->plugin_settings->get( 'show_date_layout', '1' ) ),
+ 'enable_available_months' => wptravelengine_toggled( $this->plugin_settings->get( 'show_available_months', '1' ) ),
+ 'enable_available_dates' => wptravelengine_toggled( $this->plugin_settings->get( 'show_available_dates', '1' ) ?? false ),
+ 'enable_original_size_image' => wptravelengine_toggled( $this->plugin_settings->get( 'show_original_size_image', '0' ) ),
);
$settings['trip_duration_label_on_card'] = (string) $this->plugin_settings->get( 'set_duration_type', 'days' );
@@ -1612,6 +1614,7 @@
*
* @return void
* @since 6.2.0
+ * @since 6.7.11 Added enable_original_size_image setting persistence.
*/
protected function set_trip_card( WP_REST_Request $request ) {
@@ -1668,6 +1671,10 @@
if ( isset( $request['card_new_layout']['enable_available_dates'] ) ) {
$plugin_settings->set( 'show_available_dates', wptravelengine_replace( $request['card_new_layout']['enable_available_dates'], true, '1', '0' ) );
}
+
+ if ( isset( $request['card_new_layout']['enable_original_size_image'] ) ) {
+ $plugin_settings->set( 'show_original_size_image', wptravelengine_replace( $request['card_new_layout']['enable_original_size_image'], true, '1', '0' ) );
+ }
}
/**
@@ -3189,50 +3196,54 @@
'description' => __( 'New Trip Layout', 'wp-travel-engine' ),
'type' => 'object',
'properties' => array(
- 'enable' => array(
+ 'enable' => array(
'description' => __( 'New Trip Layout Enabled or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_slider' => array(
+ 'enable_slider' => array(
'description' => __( 'Display Slider or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_featured_tag' => array(
+ 'enable_featured_tag' => array(
'description' => __( 'Display Featured or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_wishlist' => array(
+ 'enable_wishlist' => array(
'description' => __( 'Display Wishlist or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_map' => array(
+ 'enable_map' => array(
'description' => __( 'Display Map or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_excerpt' => array(
+ 'enable_excerpt' => array(
'description' => __( 'Display Trip Archive Excerpt or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_difficulty' => array(
+ 'enable_difficulty' => array(
'description' => __( 'Display Difficulty or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_tags' => array(
+ 'enable_tags' => array(
'description' => __( 'Display Tags or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_fsd' => array(
+ 'enable_fsd' => array(
'description' => __( 'Display Next Departure Dates or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_available_months' => array(
+ 'enable_available_months' => array(
'description' => __( 'Display Available Months or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
- 'enable_available_dates' => array(
+ 'enable_available_dates' => array(
'description' => __( 'Display Available Dates or Not', 'wp-travel-engine' ),
'type' => 'boolean',
),
+ 'enable_original_size_image' => array(
+ 'description' => __( 'Display Original Size Image or Not', 'wp-travel-engine' ),
+ 'type' => 'boolean',
+ ),
),
),
'trip_duration_label_on_card' => array(
--- a/wp-travel-engine/includes/classes/Core/Models/Post/Booking.php
+++ b/wp-travel-engine/includes/classes/Core/Models/Post/Booking.php
@@ -1454,6 +1454,7 @@
* @return void
* @since 6.7.0
* @since 6.7.1 Added support for send_booking_emails, send_payment_emails arguments and payment event trigger.
+ * @since 6.7.11 Added wptravelengine_verify_payment_success_status filter to allow bypassing completed-status guard.
*/
public function sync_payment_success_metas( int $payment_id, float $paid_amount, array $args = array() ): void {
$args = wp_parse_args(
@@ -1496,7 +1497,7 @@
$previous_payment_status = $_payment->get_payment_status();
- if ( 'completed' === $previous_payment_status ) {
+ if ( apply_filters( 'wptravelengine_verify_payment_success_status', true, $this ) && 'completed' === $previous_payment_status ) {
return;
}
--- a/wp-travel-engine/includes/classes/Core/Models/Post/Payment.php
+++ b/wp-travel-engine/includes/classes/Core/Models/Post/Payment.php
@@ -219,6 +219,7 @@
*
* @return ?Payment
* @throws InvalidArgumentException
+ * @since 6.7.11 Fixed transient lookup failure by adding meta-query fallback when transient is missing (e.g. object-cache issues).
*/
public static function from_payment_key( string $payment_key ): ?Payment {
if ( empty( $payment_key ) ) {
@@ -227,6 +228,25 @@
$payment_id = get_transient( 'payment_key_' . $payment_key );
+ // Fallback: query payment by meta if transient not found (e.g., object cache issues).
+ if ( ! $payment_id ) {
+ $query = new WP_Query(
+ array(
+ 'post_type' => 'wte-payments',
+ 'posts_per_page' => 1,
+ 'meta_key' => 'payment_key',
+ 'meta_value' => $payment_key,
+ 'fields' => 'ids',
+ 'no_found_rows' => true,
+ 'suppress_filters' => true,
+ )
+ );
+ if ( ! empty( $query->posts ) ) {
+ $payment_id = $query->posts[0];
+ set_transient( 'payment_key_' . $payment_key, $payment_id, 24 * HOUR_IN_SECONDS );
+ }
+ }
+
if ( ! $payment_id ) {
throw new InvalidArgumentException( 'Invalid Payment Key' );
}
--- a/wp-travel-engine/includes/classes/Core/Models/Post/Trip.php
+++ b/wp-travel-engine/includes/classes/Core/Models/Post/Trip.php
@@ -174,6 +174,7 @@
* Get the services.
*
* @return array
+ * @since 6.7.11 Added wptravelengine_trip_services filter.
*/
public function get_services(): array {
@@ -261,7 +262,7 @@
);
}
- return $data;
+ return apply_filters( 'wptravelengine_trip_services', $data, $this->ID );
}
/**
--- a/wp-travel-engine/includes/classes/Core/SEO.php
+++ b/wp-travel-engine/includes/classes/Core/SEO.php
@@ -179,28 +179,81 @@
* @param array $settings
* @param int $post_id
* @return array|null
+ * @since 6.7.11 Added category-based FAQ structure support with legacy flat format fallback.
*/
private static function build_faq_schema( $settings, $post_id ) {
- if ( empty( $settings['faq']['faq_title'] ) || ! is_array( $settings['faq']['faq_title'] ) ) {
- return null;
+ $faqs = array();
+
+ // New category-based structure takes priority.
+ $faqs_data = $settings['faqs_data'] ?? array();
+ if ( ! empty( $faqs_data['categories'] ) && is_array( $faqs_data['categories'] ) ) {
+ $global_faq_map = wptravelengine_get_global_faq_map();
+ foreach ( $faqs_data['categories'] as $category ) {
+ if ( empty( $category['faqs'] ) || ! is_array( $category['faqs'] ) ) {
+ continue;
+ }
+ $category_name = strip_tags( $category['name'] ?? '' );
+ foreach ( $category['faqs'] as $faq ) {
+ $question = (string) ( $faq['question'] ?? '' );
+ $answer = (string) ( $faq['answer'] ?? '' );
+ $source_id = (string) ( $faq['sourceId'] ?? '' );
+
+ if ( ! empty( $faq['addedInBulk'] ) && '' !== $source_id ) {
+ if ( ! isset( $global_faq_map[ $source_id ] ) ) {
+ continue;
+ }
+ $question = $global_faq_map[ $source_id ]['question'];
+ $answer = $global_faq_map[ $source_id ]['answer'];
+ }
+
+ if ( empty( trim( $question ) ) ) {
+ continue;
+ }
+
+ $question = strip_tags( preg_replace( '/<pb[^>]*>(.*?)</p>/i', '$1', $question ) );
+ $answer = strip_tags( preg_replace( '/<pb[^>]*>(.*?)</p>/i', '$1', $answer ) );
+
+ $entry = array(
+ '@type' => 'Question',
+ 'name' => esc_attr( $question ),
+ 'acceptedAnswer' => array(
+ '@type' => 'Answer',
+ 'text' => esc_attr( $answer ),
+ ),
+ );
+
+ if ( ! empty( $category_name ) ) {
+ $entry['about'] = array(
+ '@type' => 'Thing',
+ 'name' => esc_attr( $category_name ),
+ );
+ }
+
+ $faqs[] = $entry;
+ }
+ }
+ } elseif ( ! empty( $settings['faq']['faq_title'] ) && is_array( $settings['faq']['faq_title'] ) ) {
+ // Legacy flat format fallback.
+ foreach ( array_keys( $settings['faq']['faq_title'] ) as $value ) {
+ $question = $settings['faq']['faq_title'][ $value ] ?? '';
+ $answer = $settings['faq']['faq_content'][ $value ] ?? '';
+
+ $question = strip_tags( preg_replace( '/<pb[^>]*>(.*?)</p>/i', '$1', $question ) );
+ $answer = strip_tags( preg_replace( '/<pb[^>]*>(.*?)</p>/i', '$1', $answer ) );
+
+ $faqs[] = array(
+ '@type' => 'Question',
+ 'name' => esc_attr( $question ),
+ 'acceptedAnswer' => array(
+ '@type' => 'Answer',
+ 'text' => esc_attr( $answer ),
+ ),
+ );
+ }
}
- $faqs = array();
- foreach ( array_keys( $settings['faq']['faq_title'] ) as $value ) {
- $question = $settings['faq']['faq_title'][ $value ] ?? '';
- $answer = $settings['faq']['faq_content'][ $value ] ?? '';
-
- $question = preg_replace( '/<pb[^>]*>(.*?)</p>/i', '', strip_tags( $question ) );
- $answer = preg_replace( '/<pb[^>]*>(.*?)</p>/i', '', strip_tags( $answer ) );
-
- $faqs[] = array(
- '@type' => 'Question',
- 'name' => esc_attr( $question ),
- 'acceptedAnswer' => array(
- '@type' => 'Answer',
- 'text' => esc_attr( $answer ),
- ),
- );
+ if ( empty( $faqs ) ) {
+ return null;
}
return apply_filters(
--- a/wp-travel-engine/includes/classes/Core/Shortcodes/TripFaq.php
+++ b/wp-travel-engine/includes/classes/Core/Shortcodes/TripFaq.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * ShortCode TripFaq.
+ *
+ * @package WPTravelEngine
+ * @subpackage WPTravelEngineCoreShortcodes
+ */
+
+namespace WPTravelEngineCoreShortcodes;
+
+use WPTravelEngineAbstractsShortcode;
+
+/**
+ * Class TripFaq.
+ *
+ * Renders the FAQ section for a trip.
+ * Usage: [wte_trip_faqs id="123"]
+ *
+ * @since 6.7.11
+ */
+class TripFaq extends Shortcode {
+
+ /**
+ * Shortcode tag.
+ *
+ * @var string
+ */
+ const TAG = 'wte_trip_faqs';
+
+ /**
+ * Default attributes.
+ *
+ * @return array
+ */
+ protected function default_attributes(): array {
+ global $post;
+ return array(
+ 'id' => isset( $post->ID ) ? $post->ID : 0,
+ );
+ }
+
+ /**
+ * Renders the FAQ section for the given trip ID.
+ *
+ * @param array $atts Shortcode attributes.
+ * @return string
+ */
+ public function output( $atts ): string {
+ $trip_id = absint( $atts['id'] );
+
+ if ( ! $trip_id ) {
+ return '';
+ }
+
+ $wp_travel_engine_setting = get_post_meta( $trip_id, 'wp_travel_engine_setting', true );
+ $faqs_data = $wp_travel_engine_setting['faqs_data'] ?? array();
+
+ $settings_available = function_exists( 'wptravelengine_settings' );
+
+ $global_faq_map = wptravelengine_get_global_faq_map();
+ $global_faq_ids = array_keys( $global_faq_map );
+
+ // Filter out orphaned bulk-imported FAQs.
+ if ( ! empty( $faqs_data['categories'] ) && $settings_available ) {
+ $faqs_data['categories'] = wptravelengine_filter_orphaned_faqs( $faqs_data['categories'], $global_faq_ids );
+ }
+
+ $has_faqs = false;
+ if ( ! empty( $faqs_data['categories'] ) ) {
+ foreach ( $faqs_data['categories'] as $category ) {
+ if ( ! empty( $category['faqs'] ) ) {
+ $has_faqs = true;
+ break;
+ }
+ }
+ }
+
+ if ( ! $has_faqs ) {
+ return '';
+ }
+
+ $section_title = ! empty( $faqs_data['sectionTitle'] )
+ ? $faqs_data['sectionTitle']
+ : ( $wp_travel_engine_setting['faq_section_title']
+ ?? $wp_travel_engine_setting['faqs_title']
+ ?? __( 'Frequently Asked Questions', 'wp-travel-engine' ) );
+
+ if ( ! is_singular( 'trip' ) ) {
+ wp_enqueue_style( 'wte-trip-faqs' );
+ wp_enqueue_script( 'wte-trip-faqs' );
+ }
+
+ ob_start();
+ wte_get_template(
+ 'single-trip/trip-tabs/_faqs.php',
+ array(
+ 'faqs_data' => $faqs_data,
+ 'section_title' => $section_title,
+ 'global_faq_map' => $global_faq_map,
+ )
+ );
+ return ob_get_clean();
+ }
+}
--- a/wp-travel-engine/includes/classes/Filters/SettingsAPISchema.php
+++ b/wp-travel-engine/includes/classes/Filters/SettingsAPISchema.php
@@ -551,6 +551,27 @@
}
/**
+ * Prepare FAQs Settings.
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return array
+ * @since 6.7.11
+ */
+ protected function prepare_faqs_settings( WP_REST_Request $request ): array {
+
+ $settings = array();
+
+ $faq_items = $this->plugin_settings->get( 'faqs.items', array() );
+
+ $settings['faqs'] = array(
+ 'items' => is_array( $faq_items ) ? $faq_items : array(),
+ );
+
+ return $settings;
+ }
+
+ /**
* Process zaps data.
*
* @param array $zaps Zaps data.
@@ -599,6 +620,7 @@
$this->prepare_zapier_settings( $request ),
$this->prepare_wetravel_settings( $request ),
$this->prepare_user_history_settings( $request ),
+ $this->prepare_faqs_settings( $request ),
);
}
@@ -1307,6 +1329,54 @@
}
/**
+ * Process FAQs Settings.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @param Settings $settings_controller Settings controller instance.
+ *
+ * @return void
+ * @since 6.7.11
+ */
+ protected function set_faqs_details( WP_REST_Request $request, Settings $settings_controller ) {
+
+ if ( ! isset( $request['faqs'] ) ) {
+ return;
+ }
+
+ $faqs = $request['faqs'];
+
+ if ( isset( $faqs['items'] ) && is_array( $faqs['items'] ) ) {
+ // Validate: every FAQ must have a non-empty question.
+ foreach ( $faqs['items'] as $faq ) {
+ if ( is_array( $faq ) && empty( trim( $faq['question'] ?? '' ) ) ) {
+ $settings_controller->set_bad_request(
+ 'faq_question_required',
+ sprintf( __( '%1$sFAQ Title%2$s must not be empty.', 'wp-travel-engine' ), '<strong>', '</strong>' ),
+ 'faqs.items'
+ );
+ return;
+ }
+ }
+
+ $faq_items = array();
+ foreach ( $faqs['items'] as $faq ) {
+ if ( ! is_array( $faq ) ) {
+ continue;
+ }
+ if ( ! empty( $faq['question'] ) || ! empty( $faq['answer'] ) ) {
+ $faq_items[] = array(
+ 'id' => $faq['id'] ?? '',
+ 'question' => $faq['question'] ?? '',
+ 'answer' => $faq['answer'] ?? '',
+ );
+ }
+ }
+ $this->plugin_settings->set( 'faqs.items', $faq_items );
+ wptravelengine_get_global_faq_map( true );
+ }
+ }
+
+ /**
* Update Addons Settings.
*
* @param WP_REST_Request $request Request object.
@@ -1332,5 +1402,6 @@
$this->set_zapier_details( $request );
$this->set_we_travel_details( $request );
$this->set_user_history_details( $request );
+ $this->set_faqs_details( $request, $settings_controller );
}
}
--- a/wp-travel-engine/includes/classes/Filters/TripAPISchema.php
+++ b/wp-travel-engine/includes/classes/Filters/TripAPISchema.php
@@ -191,6 +191,64 @@
'type' => 'boolean',
);
+ // FAQs schema with categories support.
+ $schema['faqs_data'] = array(
+ 'description' => __( 'Trip FAQs with categories.', 'wp-travel-engine' ),
+ 'type' => 'object',
+ 'properties' => array(
+ 'sectionTitle' => array(
+ 'description' => __( 'FAQs section title.', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'categories' => array(
+ 'description' => __( 'FAQ categories.', 'wp-travel-engine' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'Category ID.', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'name' => array(
+ 'description' => __( 'Category name.', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'faqs' => array(
+ 'description' => __( 'FAQs in this category.', 'wp-travel-engine' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'FAQ ID.', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'question' => array(
+ 'description' => __( 'FAQ question.', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'answer' => array(
+ 'description' => __( 'FAQ answer.', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'addedInBulk' => array(
+ 'description' => __( 'FAQ added in bulk.', 'wp-travel-engine' ),
+ 'type' => 'boolean',
+ ),
+ 'sourceId' => array(
+ 'description' => __( 'Global FAQ source ID (when added in bulk).', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
return $schema;
}
@@ -356,6 +414,7 @@
* @param TripController $controller
*
* @return array
+ * @since 6.7.11 Added prepare_faqs call.
*/
public function trip_edit_api_prepare( array $data, WP_REST_Request $request, TripController $controller ): array {
@@ -365,6 +424,7 @@
$this->prepare_extra_services( $data, $request );
$this->prepare_file_downloads( $data, $request );
$this->prepare_partial_payment( $data, $request );
+ $this->prepare_faqs( $data, $request );
return $data;
}
@@ -376,6 +436,7 @@
* @param TripController $controller
*
* @return void
+ * @since 6.7.11 Added update_faqs call.
*/
public function trip_edit_api_update( WP_REST_Request $request, TripController $controller ): void {
@@ -386,6 +447,7 @@
$this->update_extra_services( $request );
$this->update_file_downloads( $request );
$this->update_partial_payment( $request, $controller );
+ $this->update_faqs( $request, $controller );
}
/**
@@ -498,4 +560,187 @@
$this->trip_settings->set( 'trip_full_payment_enabled', $request['full_payment_enable'] ? 'yes' : 'no' );
}
}
+
+ /**
+ * Prepares the FAQs data.
+ *
+ * @param array $data
+ * @param WP_REST_Request $request
+ *
+ * @since 6.7.11
+ * @return void
+ */
+ protected function prepare_faqs( array &$data, WP_REST_Request $request ): void {
+
+ $settings_available = function_exists( 'wptravelengine_settings' );
+ $global_faq_map = wptravelengine_get_global_faq_map();
+ $global_faq_ids = array_keys( $global_faq_map );
+
+ $faqs_data = $this->trip->get_setting( 'faqs_data', array() );
+
+ // If new structure exists, use it.
+ if ( ! empty( $faqs_data ) && is_array( $faqs_data ) ) {
+ $data['faqs_data'] = array(
+ 'sectionTitle' => (string) ( $faqs_data['sectionTitle'] ?? '' ),
+ 'categories' => array(),
+ );
+
+ if ( ! empty( $faqs_data['categories'] ) && is_array( $faqs_data['categories'] ) ) {
+ foreach ( $faqs_data['categories'] as $category ) {
+ $faqs_array = array();
+ if ( ! empty( $category['faqs'] ) && is_array( $category['faqs'] ) ) {
+ foreach ( $category['faqs'] as $faq ) {
+ $source_id = (string) ( $faq['sourceId'] ?? '' );
+ $added_in_bulk = isset( $faq['addedInBulk'] ) ? (bool) $faq['addedInBulk'] : false;
+ if (
+ $added_in_bulk
+ && '' !== $source_id
+ && $settings_available
+ && ! in_array( $source_id, $global_faq_ids, true )
+ ) {
+ continue;
+ }
+
+ $faq_item = array(
+ 'id' => (string) ( $faq['id'] ?? '' ),
+ 'question' => (string) ( $faq['question'] ?? '' ),
+ 'answer' => (string) ( $faq['answer'] ?? '' ),
+ );
+
+ // For bulk-added FAQs, use global question/answer only as fallback when no trip-level override has been saved.
+ if ( $added_in_bulk && '' !== $source_id && isset( $global_faq_map[ $source_id ] ) ) {
+ if ( '' === $faq_item['question'] ) {
+ $faq_item['question'] = $global_faq_map[ $source_id ]['question'];
+ }
+ if ( '' === $faq_item['answer'] ) {
+ $faq_item['answer'] = $global_faq_map[ $source_id ]['answer'];
+ }
+ }
+
+ if ( isset( $faq['addedInBulk'] ) ) {
+ $faq_item['addedInBulk'] = (bool) $faq['addedInBulk'];
+ }
+ if ( isset( $faq['sourceId'] ) ) {
+ $faq_item['sourceId'] = $source_id;
+ }
+ $faqs_array[] = $faq_item;
+ }
+ }
+
+ $data['faqs_data']['categories'][] = array(
+ 'id' => (string) ( $category['id'] ?? '' ),
+ 'name' => (string) ( $category['name'] ?? '' ),
+ 'faqs' => $faqs_array,
+ );
+ }
+ }
+ } else {
+ // Transform legacy format for backward compatibility.
+ $legacy_faq = $this->trip->get_setting( 'faq', array() );
+ if ( ! empty( $legacy_faq['faq_title'] ) && is_array( $legacy_faq['faq_title'] ) ) {
+ $faqs_array = array();
+ foreach ( $legacy_faq['faq_title'] as $key => $question ) {
+ $faqs_array[] = array(
+ 'id' => 'faq-legacy-' . $key,
+ 'question' => (string) $question,
+ 'answer' => (string) ( $legacy_faq['faq_content'][ $key ] ?? '' ),
+ );
+ }
+
+ // Get legacy section title.
+ $legacy_section_title = $this->trip->get_setting( 'faq_section_title', '' );
+
+ $data['faqs_data'] = array(
+ 'sectionTitle' => (string) $legacy_section_title,
+ 'categories' => array(
+ array(
+ 'id' => 'category-legacy-general',
+ 'name' => __( 'General', 'wp-travel-engine' ),
+ 'faqs' => $faqs_array,
+ ),
+ ),
+ );
+ }
+ }
+ }
+
+ /**
+ * Updates the FAQs data.
+ *
+ * @param WP_REST_Request $request
+ *
+ * @since 6.7.11
+ * @return void
+ */
+ protected function update_faqs( WP_REST_Request $request, TripController $controller ): void {
+
+ if ( ! isset( $request['faqs_data'] ) ) {
+ return;
+ }
+
+ $faqs_data = $request['faqs_data'];
+
+ // Validate: every FAQ must have a non-empty question.
+ if ( ! empty( $faqs_data['categories'] ) && is_array( $faqs_data['categories'] ) ) {
+ foreach ( $faqs_data['categories'] as $category ) {
+ if ( ! is_array( $category ) || empty( $category['faqs'] ) ) {
+ continue;
+ }
+ foreach ( $category['faqs'] as $faq ) {
+ if ( is_array( $faq ) && empty( trim( $faq['question'] ?? '' ) ) ) {
+ $controller->set_bad_request(
+ 'faq_title_required',
+ sprintf( __( '%1$sFAQ Title%2$s must not be empty.', 'wp-travel-engine' ), '<strong>', '</strong>' ),
+ 'faqs_data'
+ );
+ return;
+ }
+ }
+ }
+ }
+
+ $categories = array();
+
+ foreach ( $faqs_data['categories'] ?? array() as $category ) {
+ if ( ! is_array( $category ) ) {
+ continue;
+ }
+
+ $faqs_array = array();
+ foreach ( $category['faqs'] ?? array() as $faq ) {
+ if ( ! is_array( $faq ) ) {
+ continue;
+ }
+
+ $faq_item = array(
+ 'id' => $faq['id'] ?? '',
+ 'question' => $faq['question'] ?? '',
+ 'answer' => $faq['answer'] ?? '',
+ );
+
+ if ( isset( $faq['addedInBulk'] ) ) {
+ $faq_item['addedInBulk'] = (bool) $faq['addedInBulk'];
+ }
+ if ( isset( $faq['sourceId'] ) ) {
+ $faq_item['sourceId'] = $faq['sourceId'];
+ }
+
+ $faqs_array[] = $faq_item;
+ }
+
+ $categories[] = array(
+ 'id' => $category['id'] ?? '',
+ 'name' => $category['name'] ?? '',
+ 'faqs' => $faqs_array,
+ );
+ }
+
+ $this->trip_settings->set(
+ 'faqs_data',
+ array(
+ 'sectionTitle' => $faqs_data['sectionTitle'] ?? '',
+ 'categories' => $categories,
+ )
+ );
+ }
}
--- a/wp-travel-engine/includes/classes/Filters/TripMetaTabs.php
+++ b/wp-travel-engine/includes/classes/Filters/TripMetaTabs.php
@@ -442,6 +442,22 @@
),
),
array(
+ 'divider' => true,
+ 'field' => array(
+ 'type' => 'SHORTCODE',
+ 'title' => __( 'To display FAQs of this tour in posts/pages/tabs/widgets use the following <strong>Shortcode.</strong>', 'wp-travel-engine' ),
+ 'code' => "[wte_trip_faqs id='{$post->ID}']",
+ ),
+ ),
+ array(
+ 'divider' => false,
+ 'field' => array(
+ 'type' => 'SHORTCODE',
+ 'title' => __( 'To display FAQs of this tour in posts/pages/tabs/widgets, please use below <strong>PHP Function.</strong>', 'wp-travel-engine' ),
+ 'code' => "<?php echo do_shortcode('[wte_trip_faqs id={$post->ID}]'); ?>",
+ ),
+ ),
+ array(
'field' => array(
'type' => 'SHORTCODE',
'title' => __( 'To display downloadable file list in posts/pages/tabs and widget, please use following <strong>Shortcode.</strong>', 'wp-travel-engine' ),
--- a/wp-travel-engine/includes/classes/Filters/schemas/faqs.php
+++ b/wp-travel-engine/includes/classes/Filters/schemas/faqs.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * FAQs Schema.
+ *
+ * @package WPTravelEngine
+ * @since 6.7.11
+ */
+
+return array(
+ 'faqs' => array(
+ 'description' => __( 'FAQs Settings', 'wp-travel-engine' ),
+ 'type' => 'object',
+ 'properties' => array(
+ 'items' => array(
+ 'description' => __( 'FAQ Items', 'wp-travel-engine' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'FAQ ID', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'question' => array(
+ 'description' => __( 'FAQ Question', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ 'answer' => array(
+ 'description' => __( 'FAQ Answer', 'wp-travel-engine' ),
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+);
--- a/wp-travel-engine/includes/classes/Modules/Filters.php
+++ b/wp-travel-engine/includes/classes/Modules/Filters.php
@@ -41,6 +41,11 @@
$this->register_taxonomies();
}
+ /**
+ * Register custom filter taxonomies.
+ *
+ * @since 6.7.11 Added rest_base to custom taxonomy registration.
+ */
private function register_taxonomies() {
$filters = get_option( 'wte_custom_filters', array() );
@@ -53,6 +58,7 @@
'name' => $filter['label'],
),
'show_in_rest' => true,
+ 'rest_base' => 'wptravelengine_custom_taxonomy_' . $filter['slug'],
'hierarchical' => $filter['hierarchical'],
'sort' => true,
)
--- a/wp-travel-engine/includes/classes/Modules/TripSearch.php
+++ b/wp-travel-engine/includes/classes/Modules/TripSearch.php
@@ -63,9 +63,11 @@
has_shortcode( $post->post_content, 'WTE_Trip_Search' ) ||
has_block( 'wptravelenginepagesblocks/archive-trip', $post ) ||
has_block( 'wptravelenginepagesblocks/trip-search', $post ) ||
- has_block( 'wptravelengine/trip-search', $post )
+ has_block( 'wptravelengine/trip-search', $post ) ||
+ self::has_elementor_shortcode( $post->ID, 'WTE_Trip_Search' )
) {
self::enqueue_styles();
+ self::enqueue_scripts();
self::enqueue_trip_search_scripts();
}
}
@@ -129,6 +131,29 @@
10,
2
);
+
+ /**
+ * Enqueue scripts and styles for Elementor preview mode.
+ *
+ * This ensures that when the WTE_Trip_Search shortcode is added or edited
+ * in the Elementor Editor, all necessary JS and CSS assets are loaded.
+ */
+ if ( defined( 'ELEMENTOR_VERSION' ) ) {
+ add_action(
+ 'elementor/preview/enqueue_scripts',
+ function () {
+ $post_id = get_the_ID();
+ if ( ! $post_id ) {
+ return;
+ }
+ if ( self::has_elementor_shortcode( $post_id, 'WTE_Trip_Search' ) ) {
+ self::enqueue_assets();
+ self::enqueue_trip_search_scripts();
+ }
+ },
+ 99
+ );
+ }
}
/**
@@ -1131,6 +1156,23 @@
}
/**
+ * Checks if a given shortcode exists in Elementor's stored page data.
+ *
+ * @param int $post_id Post ID to check.
+ * @param string $shortcode Shortcode tag to look for.
+ * @static
+ * @return boolean True if shortcode found in Elementor data.
+ * @since 6.7.11
+ */
+ private static function has_elementor_shortcode( int $post_id, string $shortcode ): bool {
+ if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
+ return false;
+ }
+ $elementor_data = get_post_meta( $post_id, '_elementor_data', true );
+ return ! empty( $elementor_data ) && false !== strpos( $elementor_data, '"' . $shortcode . '"' );
+ }
+
+ /**
* Checks if current page is the search results page.
*
* @static
--- a/wp-travel-engine/includes/classes/Plugin.php
+++ b/wp-travel-engine/includes/classes/Plugin.php
@@ -45,6 +45,7 @@
use WPTravelEngineCoreModelsPostTripPackages;
use WPTravelEngineCoreControllersRestAPIV2Booking as BookingController;
use WPTravelEngineCoreModelsPostPayment;
+use WPTravelEngineCoreShortcodesTripFaq;
use WPTravelEnginePagesAdminUpcomingTours;
/**
@@ -1631,6 +1632,7 @@
* Init shortcodes.
*
* @since 1.0.0
+ * @since 6.7.11 Added TripFaq shortcode registration.
*/
public function init_shortcodes() {
ShortcodeRegistry::make()
@@ -1640,7 +1642,8 @@
->register( General::class )
->register( TripCheckout::class )
->register( UserAccount::class )
- ->register( TripsList::class );
+ ->register( TripsList::class )
+ ->register( TripFaq::class );
}
/**
--- a/wp-travel-engine/includes/helpers/functions.php
+++ b/wp-travel-engine/includes/helpers/functions.php
@@ -2611,4 +2611,69 @@
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
-}
No newline at end of file
+}
+
+/**
+ * Build a map of active global FAQ items keyed by their ID.
+ *
+ * @since 6.7.11
+ * @return array<string, array{question: string, answer: string}>
+ */
+function wptravelengine_get_global_faq_map( bool $force_refresh = false ): array {
+ static $map = null;
+ if ( null !== $map && ! $force_refresh ) {
+ return $map;
+ }
+
+ if ( ! function_exists( 'wptravelengine_settings' ) ) {
+ return array();
+ }
+
+ $global_settings = wptravelengine_settings()->get();
+ $global_faq_items = $global_settings['faqs']['items'] ?? array();
+ $map = array();
+
+ if ( is_array( $global_faq_items ) ) {
+ foreach ( $global_faq_items as $global_faq ) {
+ $global_id = (string) ( $global_faq['id'] ?? '' );
+ if ( '' !== $global_id ) {
+ $map[ $global_id ] = array(
+ 'question' => (string) ( $global_faq['question'] ?? $global_faq['faq_title'] ?? '' ),
+ 'answer' => (string) ( $global_faq['answer'] ?? $global_faq['faq_content'] ?? '' ),
+ );
+ }
+ }
+ }
+
+ return $map;
+}
+
+/**
+ * Remove orphaned bulk-imported FAQs from a categories array.
+ *
+ * @since 6.7.11
+ * @param array<int, array> $categories FAQ categories array from faqs_data['categories'].
+ * @param string[] $global_faq_ids IDs that currently exist in Global Settings.
+ * @return array<int, array>
+ */
+function wptravelengine_filter_orphaned_faqs( array $categories, array $global_faq_ids ): array {
+ foreach ( $categories as $cat_index => $category ) {
+ if ( empty( $category['faqs'] ) || ! is_array( $category['faqs'] ) ) {
+ continue;
+ }
+
+ $filtered_faqs = array();
+ foreach ( $category['faqs'] as $faq ) {
+ $added_in_bulk = isset( $faq['addedInBulk'] ) ? (bool) $faq['addedInBulk'] : false;
+ $source_id = (string) ( $faq['sourceId'] ?? $faq['globalFaqId'] ?? '' );
+
+ if ( $added_in_bulk && '' !== $source_id && ! in_array( $source_id, $global_faq_ids, true ) ) {
+ continue;
+ }
+ $filtered_faqs[] = $faq;
+ }
+ $categories[ $cat_index ]['faqs'] = $filtered_faqs;
+ }
+
+ return $categories;
+}
--- a/wp-travel-engine/includes/helpers/helpers.php
+++ b/wp-travel-engine/includes/helpers/helpers.php
@@ -1648,7 +1648,17 @@
}
break;
case 'faqs':
- if ( empty( $post_meta['faq']['faq_title'] ) ) {
+ $has_old_faqs = ! empty( $post_meta['faq']['faq_title'] );
+ $has_new_faqs = false;
+ if ( ! empty( $post_meta['faqs_data']['categories'] ) ) {
+ foreach ( $post_meta['faqs_data']['categories'] as $category ) {
+ if ( ! empty( $category['faqs'] ) ) {
+ $has_new_faqs = true;
+ break;
+ }
+ }
+ }
+ if ( ! $has_old_faqs && ! $has_new_faqs ) {
unset( $settings['trip_tabs']['id'][ $value ] );
}
break;
--- a/wp-travel-engine/includes/templates/single-trip/main-gallery.php
+++ b/wp-travel-engine/includes/templates/single-trip/main-gallery.php
@@ -13,11 +13,12 @@
$enable_video_gallery = $wptravelengine_trip_settings['enable_video_gallery'] ?? false;
$banner_layout = $wptravelengine_settings['trip_banner_layout'] ?? 'banner-default';
$default_banner_class = ! $related_query && 'banner-default' === $banner_layout ? ' banner-layout-default' : ''; // Add default class for banner only.
+$is_archive = is_archive() || ( isset( $_POST['action'] ) && 'wte_show_ajax_result' === $_POST['action'] );
// Determine appropriate image size based on context.
-if ( $related_query || is_archive() ) {
- // Archive/related trips use smaller images (374x226).
- $default_image_size = 'trip-thumb-size';
+if ( $related_query || $is_archive ) {
+ // Archive/related trips use smaller images (500x500) if show_original_size_image is disabled.
+ $default_image_size = wptravelengine_toggled( $wptravelengine_settings['show_original_size_image'] ?? false ) ? 'full' : 'trip-thumb-size';
} elseif ( 'banner-default' === $banner_layout ) {
$default_image_size = 'full';
} else {
@@ -196,4 +197,4 @@
endif;
endif;
?>
-</div>
No newline at end of file
+</div>
--- a/wp-travel-engine/includes/templates/single-trip/trip-tabs/_faqs.php
+++ b/wp-travel-engine/includes/templates/single-trip/trip-tabs/_faqs.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Partial template: FAQ list (new category-based structure).
+ *
+ * Included by faqs.php after it has prepared the data. The following
+ * variables must be set in the including scope before this file is loaded:
+ *
+ * @var array $faqs_data Structured FAQ data with 'categories' key.
+ * @var string $section_title Section heading (may be empty).
+ * @var array $global_faq_map Map of global FAQ IDs to ['question', 'answer'].
+ * Built via wptravelengine_get_global_faq_map().
+ * @since 6.7.11
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+?>
+<?php do_action( 'wte_before_faq_content' ); ?>
+
+<div class="wpte-faq-section">
+ <?php if ( ! empty( $section_title ) ) : ?>
+ <h2 class="wpte-faq-section-title"><?php echo esc_html( $section_title ); ?></h2>
+ <?php endif; ?>
+
+ <?php
+ // Display new structure FAQs with categories
+ if ( ! empty( $faqs_data['categories'] ) ) :
+ for