__( '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, $rail_class = 'rail--dest' ) { $id = 'vcRail' . ( ++self::$rail_seq ); ob_start(); ?>

array() ) ); ?>

    $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' ); ?>
  • <?php echo esc_attr( $term->name ); ?>

    name ); ?>

    count ) ); ?>
  • __( "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(); ?>

    $item ) : ?>

    __( '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(); ?>

    '', 'codes' => '', 'destination' => '', 'tag' => '', 'limit' => 4, 'orderby' => 'rating', 'title' => __( 'Open Top
    Bus Tours…', 'viator-collector' ), 'note' => __( 'An open-top hop on hop off bus is the best way to discover the historic and cultural sights of any city.', 'viator-collector' ), 'chip' => __( 'Very Fun!', 'viator-collector' ), 'image_id' => '', // WordPress media attachment ID for the bus illustration. 'image' => '', // …or a direct image URL. ), $atts, self::BUS_TOURS ); $query = self::tour_query( $atts ); if ( ! $query->have_posts() ) { return ''; } $archive = get_post_type_archive_link( 'viator_tour' ); $archive = $archive ? $archive : home_url( '/' ); $bus_img = ''; if ( '' !== $atts['image_id'] ) { // 'full' (uncropped original) — some sites register a hard-cropped // square 'large', which slices decorative illustrations in half. $bus_img = wp_get_attachment_image( absint( $atts['image_id'] ), 'full', false, array( 'alt' => '', 'loading' => 'lazy' ) ); } elseif ( '' !== $atts['image'] ) { $bus_img = ''; } ob_start(); ?>

    array() ) ); ?>

      have_posts() ) : $query->the_post(); ++$i; $pid = get_the_ID(); $price = get_post_meta( $pid, '_vc_price_from', true ); $currency = get_post_meta( $pid, '_vc_currency', true ); $terms = get_the_terms( $pid, 'viator_destination' ); $kicker = ( $terms && ! is_wp_error( $terms ) ) ? $terms[0]->name : ''; $active = ( 1 === $i ); $badge = vc_ring_badge( 'BOOK NOW · BOOK NOW · ', get_permalink( $pid ), 'card' ); if ( $active ) { $badge = str_replace( 'ring-badge--card', 'ring-badge--card ring-badge--accent', $badge ); } ?>
    __( 'Hop On Hop Off', 'viator-collector' ), 'highlight' => __( 'Memorable', 'viator-collector' ), 'text' => __( 'Discover cities the memorable way — hop on and off our open-top buses at every major sight and explore each place at your own pace, with expert local commentary along the way.', 'viator-collector' ), 'button_text' => __( 'About us', 'viator-collector' ), 'button_url' => '', 'image_id' => '', 'image' => '', 'stats' => '', // "25+|Years Experience;;500+|Tours Conducted;;430+|Tours this Year;;15+|Different Tours" ), $atts, self::ABOUT ); $stats = array(); if ( '' !== $atts['stats'] ) { foreach ( explode( ';;', $atts['stats'] ) as $pair ) { $p = array_map( 'trim', explode( '|', $pair, 2 ) ); if ( '' !== $p[0] && isset( $p[1] ) ) { $stats[] = array( 'num' => $p[0], 'label' => $p[1] ); } } } if ( empty( $stats ) ) { $stats = array( array( 'num' => '25+', 'label' => __( 'Years Experience', 'viator-collector' ) ), array( 'num' => '500+', 'label' => __( 'Tours Conducted', 'viator-collector' ) ), array( 'num' => '430+', 'label' => __( 'Tours this Year', 'viator-collector' ) ), array( 'num' => '15+', 'label' => __( 'Different Tours', 'viator-collector' ) ), ); } $button_url = $atts['button_url']; if ( '' === $button_url ) { $button_url = get_post_type_archive_link( 'viator_tour' ); $button_url = $button_url ? $button_url : home_url( '/' ); } $bus_img = ''; if ( '' !== $atts['image_id'] ) { // 'full' (uncropped original) — some sites register a hard-cropped // square 'large', which slices decorative illustrations in half. $bus_img = wp_get_attachment_image( absint( $atts['image_id'] ), 'full', false, array( 'alt' => '', 'loading' => 'lazy' ) ); } elseif ( '' !== $atts['image'] ) { $bus_img = ''; } ob_start(); ?>

    __( 'Latest
    Articles', 'viator-collector' ), 'lead' => __( 'News, tips and stories from our tours.', 'viator-collector' ), 'limit' => 8, 'category' => '', 'view_all' => '', ), $atts, self::ARTICLES ); $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => max( 1, absint( $atts['limit'] ) ), 'ignore_sticky_posts' => true, 'no_found_rows' => true, ); if ( '' !== $atts['category'] ) { $args['category_name'] = sanitize_title( $atts['category'] ); } $query = new WP_Query( $args ); if ( ! $query->have_posts() ) { return ''; } $view_all = $atts['view_all']; if ( '' === $view_all ) { $posts_page = (int) get_option( 'page_for_posts' ); $view_all = $posts_page ? get_permalink( $posts_page ) : home_url( '/' ); } $rail = self::open_rail( $atts['title'], $atts['lead'], $view_all, __( 'See all', 'viator-collector' ), 'rail--art' ); ob_start(); echo $rail['head']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $i = 0; while ( $query->have_posts() ) : $query->the_post(); ++$i; $pid = get_the_ID(); $active = ( 1 === $i ); $img = has_post_thumbnail( $pid ) ? get_the_post_thumbnail( $pid, 'large', array( 'class' => 'acard__img', 'loading' => 'lazy', 'alt' => '' ) ) : ''; $time = human_time_diff( (int) get_post_time( 'U', true, $pid ), (int) current_time( 'timestamp', true ) ); $excerpt = get_the_excerpt( $pid ); $badge = vc_ring_badge( 'READ MORE · READ MORE · ', get_permalink( $pid ), 'card' ); if ( $active ) { $badge = str_replace( 'ring-badge--card', 'ring-badge--card ring-badge--accent', $badge ); } ?>
  • '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; } }