__( 'Set out on the journey.', 'viator-collector' ),
'eyebrow' => __( 'Tour & Travel', 'viator-collector' ),
'image' => '',
'image_id' => '', // WordPress media attachment ID (responsive).
),
$atts,
self::HERO
);
// Prefer a media-library image (responsive srcset): shortcode image_id,
// then a shortcode URL, then the plugin's Hero image setting. No bundled
// fallback — an empty source shows the CSS gradient.
if ( '' !== $atts['image_id'] ) {
$img = absint( $atts['image_id'] );
} elseif ( '' !== $atts['image'] ) {
$img = $atts['image'];
} else {
$img = vc_hero_image_source();
}
$archive = get_post_type_archive_link( 'viator_tour' );
$archive = $archive ? $archive : home_url( '/' );
$dests = get_terms( array( 'taxonomy' => 'viator_destination', 'hide_empty' => true, 'number' => 12, 'orderby' => 'count', 'order' => 'DESC' ) );
$dests = is_wp_error( $dests ) ? array() : $dests;
ob_start();
?>
with cards and then calls close_rail().
*/
private static function open_rail( $title, $lead, $view_all_url, $view_all_text ) {
$id = 'vcRail' . ( ++self::$rail_seq );
ob_start();
?>
$id, 'head' => ob_get_clean() );
}
private static function close_rail() {
return '
';
}
/**
* [viator_top_tours limit="8" title="Most Liked Tours"]
*/
public static function top_tours( $atts ) {
$atts = shortcode_atts(
array(
'ids' => '',
'codes' => '',
'destination' => '',
'tag' => '',
'limit' => 8,
'orderby' => 'rating',
'title' => __( 'Most Liked
Tours', 'viator-collector' ),
'lead' => __( 'Hand-picked tours travellers loved most.', 'viator-collector' ),
),
$atts,
self::TOP_TOURS
);
return self::tour_rail( $atts );
}
/**
* [viator_top_attractions limit="8"]
*/
public static function top_attractions( $atts ) {
$atts = shortcode_atts(
array(
'ids' => '',
'codes' => '',
'destination' => '',
'tag' => '',
'limit' => 8,
'orderby' => 'rating',
'title' => __( 'Top
Attractions', 'viator-collector' ),
'lead' => __( 'The must-see experiences everyone books.', 'viator-collector' ),
),
$atts,
self::TOP_ATTRS
);
return self::tour_rail( $atts );
}
/**
* [viator_browse_activities limit="8"]
*/
public static function browse_activities( $atts ) {
$atts = shortcode_atts(
array(
'ids' => '',
'codes' => '',
'destination' => '',
'tag' => '',
'limit' => 8,
'orderby' => 'rating',
'title' => __( 'Browse By
Activity', 'viator-collector' ),
'lead' => __( 'Find your style — from city sights to river cruises.', 'viator-collector' ),
),
$atts,
self::ACTIVITIES
);
return self::tour_rail( $atts );
}
/**
* Shared: a rail of tour cards from a curated/filtered query.
*/
private static function tour_rail( array $atts ) {
$query = self::tour_query( $atts );
if ( ! $query->have_posts() ) {
return '';
}
$archive = get_post_type_archive_link( 'viator_tour' );
$rail = self::open_rail( $atts['title'], $atts['lead'], $archive, __( 'See all', 'viator-collector' ) );
ob_start();
echo $rail['head']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
while ( $query->have_posts() ) {
$query->the_post();
echo vc_get_template( 'card-tour.php', array( 'post_id' => get_the_ID(), 'variant' => 'rail' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::close_rail(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
wp_reset_postdata();
return ob_get_clean();
}
/**
* [viator_top_destinations limit="8" title="Where you wanna go?"]
*
* A rail of destination-term cards; each links to the term archive.
*/
public static function top_destinations( $atts ) {
$atts = shortcode_atts(
array(
'ids' => '',
'slugs' => '',
'limit' => 8,
'orderby' => 'count',
'title' => __( 'Where you
wanna go?', 'viator-collector' ),
'lead' => __( 'Popular destinations our travellers explore.', 'viator-collector' ),
'link_text' => __( 'View tours', 'viator-collector' ),
),
$atts,
self::TOP_DESTS
);
$args = array( 'taxonomy' => 'viator_destination', 'hide_empty' => true );
if ( '' !== $atts['ids'] ) {
$args['include'] = array_filter( array_map( 'absint', explode( ',', $atts['ids'] ) ) );
$args['orderby'] = 'include';
} elseif ( '' !== $atts['slugs'] ) {
$args['slug'] = array_filter( array_map( 'sanitize_title', explode( ',', $atts['slugs'] ) ) );
$args['orderby'] = 'name';
} else {
$args['number'] = max( 1, absint( $atts['limit'] ) );
$args['orderby'] = ( 'name' === $atts['orderby'] ) ? 'name' : 'count';
$args['order'] = ( 'name' === $atts['orderby'] ) ? 'ASC' : 'DESC';
}
$terms = get_terms( $args );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return '';
}
$archive = get_post_type_archive_link( 'viator_tour' );
$rail = self::open_rail( $atts['title'], $atts['lead'], $archive, __( 'See all', 'viator-collector' ) );
ob_start();
echo $rail['head']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
foreach ( $terms as $term ) {
$link = get_term_link( $term );
if ( is_wp_error( $link ) ) {
continue;
}
$image = vc_term_image_url( $term->term_id, 'viator_destination' );
?>
__( "FAQ's", 'viator-collector' ),
'lead' => __( 'Everything you need to know before you book.', 'viator-collector' ),
'items' => '',
),
$atts,
self::FAQ
);
$items = array();
if ( '' !== $atts['items'] ) {
foreach ( explode( ';;', $atts['items'] ) as $pair ) {
$parts = array_map( 'trim', explode( '|', $pair, 2 ) );
if ( '' !== $parts[0] && isset( $parts[1] ) ) {
$items[] = array( 'q' => $parts[0], 'a' => $parts[1] );
}
}
}
if ( empty( $items ) ) {
$items = apply_filters(
'vc_faq_items',
array(
array( 'q' => __( 'Is the booking process fully online?', 'viator-collector' ), 'a' => __( 'Yes — you can browse, choose a date and complete your booking entirely online in a few minutes.', 'viator-collector' ) ),
array( 'q' => __( 'How can I book a tour?', 'viator-collector' ), 'a' => __( 'Pick a tour, choose your date and number of seats, then confirm. You will receive your tickets by email.', 'viator-collector' ) ),
array( 'q' => __( 'Can I cancel or change my booking?', 'viator-collector' ), 'a' => __( 'Most tours offer free cancellation up to 24 hours before the start time. Check each tour for details.', 'viator-collector' ) ),
array( 'q' => __( 'Do I need to print my ticket?', 'viator-collector' ), 'a' => __( 'No — a mobile ticket is accepted for the vast majority of tours.', 'viator-collector' ) ),
)
);
}
if ( empty( $items ) ) {
return '';
}
ob_start();
?>
__( 'Your Comments', 'viator-collector' ),
'lead' => __( 'What travellers say about their tours.', 'viator-collector' ),
),
$atts,
self::REVIEWS
);
// Avatars are optional; supply them via the vc_testimonials filter
// ('avatar' => attachment URL) — none are bundled with the plugin.
$items = apply_filters(
'vc_testimonials',
array(
array( 'name' => 'Malika Favre', 'role' => __( 'Paris Tour', 'viator-collector' ), 'text' => __( 'An unforgettable day — the open-top ride and the guide made the whole city feel new. Booking was effortless.', 'viator-collector' ), 'avatar' => '', 'size' => 'lg' ),
array( 'name' => 'James Carter', 'role' => __( 'London Tour', 'viator-collector' ), 'text' => __( 'Great value and flexible timing. We hopped on and off all day and saw everything on our list.', 'viator-collector' ), 'avatar' => '', 'size' => '' ),
array( 'name' => 'Sofia Rossi', 'role' => __( 'Sydney Tour', 'viator-collector' ), 'text' => __( 'Smooth from start to finish. Highly recommended.', 'viator-collector' ), 'avatar' => '', 'size' => 'sm' ),
array( 'name' => 'Liam Nguyen', 'role' => __( 'San Diego Tour', 'viator-collector' ), 'text' => __( 'Friendly crew, comfortable seats and the best views of the coast.', 'viator-collector' ), 'avatar' => '', 'size' => '' ),
)
);
if ( empty( $items ) ) {
return '';
}
ob_start();
?>
'viator_tour',
'post_status' => 'publish',
'posts_per_page' => max( 1, absint( $atts['limit'] ) ),
'no_found_rows' => true,
'ignore_sticky_posts' => true,
);
if ( '' !== $atts['ids'] ) {
$args['post__in'] = array_filter( array_map( 'absint', explode( ',', $atts['ids'] ) ) );
$args['orderby'] = 'post__in';
} elseif ( '' !== $atts['codes'] ) {
$args['meta_query'] = array(
'vc_product_codes' => array(
'key' => '_vc_viator_product_code',
'value' => array_filter( array_map( 'sanitize_text_field', explode( ',', $atts['codes'] ) ) ),
'compare' => 'IN',
),
);
}
$tax_query = array();
if ( '' !== $atts['destination'] ) {
$tax_query[] = array( 'taxonomy' => 'viator_destination', 'field' => 'slug', 'terms' => array_map( 'sanitize_title', explode( ',', $atts['destination'] ) ) );
}
if ( '' !== $atts['tag'] ) {
$tax_query[] = array( 'taxonomy' => 'viator_tag', 'field' => 'slug', 'terms' => array_map( 'sanitize_title', explode( ',', $atts['tag'] ) ) );
}
if ( $tax_query ) {
$args['tax_query'] = $tax_query;
}
if ( ! isset( $args['orderby'] ) ) {
switch ( $atts['orderby'] ) {
case 'price':
$args['meta_key'] = '_vc_price_from';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'ASC';
break;
case 'rand':
$args['orderby'] = 'rand';
break;
case 'date':
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
default:
$args['meta_key'] = '_vc_rating';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
}
}
$query = new WP_Query( $args );
// Ordering by a meta key silently drops posts that lack it; fall back to
// newest so the section never empties.
if ( ! $query->have_posts() && isset( $args['meta_key'] ) ) {
unset( $args['meta_key'] );
$args['orderby'] = 'date';
$args['order'] = 'DESC';
$query = new WP_Query( $args );
}
return $query;
}
}