Add viator-collector plugin (Hop On Hop Off front-end reskin)

Consolidate the plugin into the TourWebsite repository (its separate
nested git repo has been removed). Includes the full new-design reskin:
scoped design stylesheet, SVG icon sprite, rebuilt listing/single/section
templates, and homepage hero/FAQ/testimonials shortcodes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J4hNL1PSHxXbu94SCenpKj
This commit is contained in:
2026-09-12 21:11:20 +03:30
parent 99e5413fee
commit 7ada8860f8
42 changed files with 9019 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<?php
/**
* Post-type archive for viator_tour — renders the shared listing UI.
*/
defined( 'ABSPATH' ) || exit;
get_header();
?>
<main class="vc-app vc-listing">
<?php
echo vc_get_template( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'listing.php',
array(
// Deliberately location-agnostic; override with the vc_listing_title filter.
'vc_title' => __( 'Explore Tours & Experiences', 'viator-collector' ),
'vc_subtitle' => __( 'Find the best tours, activities and unforgettable experiences.', 'viator-collector' ),
'vc_crumb_label' => __( 'Tours', 'viator-collector' ),
)
);
?>
</main>
<?php
get_footer();

View File

@@ -0,0 +1,9 @@
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
$url = get_post_meta( $post_id, '_vc_booking_url', true );
if ( ! $url ) {
return;
}
?>
<a class="vc-booking-button" href="<?php echo esc_url( $url ); ?>" target="_blank" rel="nofollow sponsored noopener"><?php echo esc_html__( 'Book this tour', 'viator-collector' ); ?></a>

View File

@@ -0,0 +1,25 @@
<?php
/**
* Activity row (Browse By Activity). Args: $term (WP_Term).
*/
defined( 'ABSPATH' ) || exit;
if ( empty( $term ) || ! is_object( $term ) ) {
return;
}
$link = get_term_link( $term );
$link = is_wp_error( $link ) ? '#' : $link;
$image = vc_term_image_url( $term->term_id, $term->taxonomy );
$count = vc_term_tour_count( $term );
?>
<a class="vc-activity vc-reveal" href="<?php echo esc_url( $link ); ?>">
<span class="vc-activity__thumb">
<?php if ( $image ) : ?>
<img src="<?php echo esc_url( $image ); ?>" alt="<?php echo esc_attr( $term->name ); ?>" loading="lazy">
<?php else : ?>
<span class="vc-activity__thumb-ph" aria-hidden="true"></span>
<?php endif; ?>
</span>
<span class="vc-activity__name"><?php echo esc_html( $term->name ); ?></span>
<?php if ( $count ) : ?><span class="vc-activity__count"><?php echo esc_html( number_format_i18n( $count ) ); ?></span><?php endif; ?>
<span class="vc-activity__arrow" aria-hidden="true">&rsaquo;</span>
</a>

View File

@@ -0,0 +1,32 @@
<?php
/**
* Large attraction feature card. Args: $post_id.
*/
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
if ( ! $post_id ) {
return;
}
$permalink = get_permalink( $post_id );
$price = get_post_meta( $post_id, '_vc_price_from', true );
$currency = get_post_meta( $post_id, '_vc_currency', true );
$rating = get_post_meta( $post_id, '_vc_rating', true );
$destinations = get_the_terms( $post_id, 'viator_destination' );
$destination = ( ! empty( $destinations ) && ! is_wp_error( $destinations ) ) ? $destinations[0]->name : '';
?>
<a class="vc-attraction vc-reveal" href="<?php echo esc_url( $permalink ); ?>">
<span class="vc-attraction__media">
<?php echo vc_tour_image_html( $post_id, 'vc-attraction__img' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<span class="vc-attraction__scrim"></span>
</span>
<?php if ( '' !== $price ) : ?>
<span class="vc-attraction__badge"><span><?php echo esc_html__( 'From', 'viator-collector' ); ?></span> <?php echo esc_html( vc_money( $price, $currency ) ); ?></span>
<?php endif; ?>
<span class="vc-attraction__body">
<?php if ( $destination ) : ?><span class="vc-attraction__place"><?php echo esc_html( $destination ); ?></span><?php endif; ?>
<span class="vc-attraction__title"><?php echo esc_html( get_the_title( $post_id ) ); ?></span>
<?php if ( '' !== $rating ) : ?>
<span class="vc-attraction__rating"><?php echo vc_render_stars( $rating ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <strong><?php echo esc_html( $rating ); ?></strong></span>
<?php endif; ?>
</span>
</a>

View File

@@ -0,0 +1,27 @@
<?php
/**
* Destination card. Args: $term (WP_Term).
*/
defined( 'ABSPATH' ) || exit;
if ( empty( $term ) || ! is_object( $term ) ) {
return;
}
$link = get_term_link( $term );
$link = is_wp_error( $link ) ? '#' : $link;
$image = vc_term_image_url( $term->term_id, $term->taxonomy );
$count = vc_term_tour_count( $term );
?>
<a class="vc-dest-card vc-reveal" href="<?php echo esc_url( $link ); ?>">
<span class="vc-dest-card__media">
<?php if ( $image ) : ?>
<img class="vc-dest-card__img" src="<?php echo esc_url( $image ); ?>" alt="<?php echo esc_attr( $term->name ); ?>" loading="lazy">
<?php else : ?>
<span class="vc-dest-card__placeholder" aria-hidden="true"></span>
<?php endif; ?>
<span class="vc-dest-card__overlay"></span>
</span>
<span class="vc-dest-card__body">
<span class="vc-dest-card__name"><?php echo esc_html( $term->name ); ?></span>
<?php if ( $count ) : ?><span class="vc-dest-card__count"><?php echo esc_html( sprintf( _n( '%d tour', '%d tours', $count, 'viator-collector' ), $count ) ); ?></span><?php endif; ?>
</span>
</a>

View File

@@ -0,0 +1,97 @@
<?php
/**
* Tour card — "Hop On Hop Off" design.
*
* Args:
* $post_id int Tour post ID (defaults to the loop post).
* $variant string 'wide' → listing item (<li class="titem">),
* 'rail' → homepage rail card (<li class="dcard">).
*
* Markup mirrors the .dcard rules in assets/css/vc-design.css (ported from
* new front/tours.html and index.html).
*/
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
if ( ! $post_id ) {
return;
}
$variant = isset( $variant ) ? $variant : 'wide';
$permalink = get_permalink( $post_id );
$title = get_the_title( $post_id );
$price = get_post_meta( $post_id, '_vc_price_from', true );
$currency = get_post_meta( $post_id, '_vc_currency', true );
$rating = get_post_meta( $post_id, '_vc_rating', true );
$duration = get_post_meta( $post_id, '_vc_duration_text', true );
$minutes = absint( get_post_meta( $post_id, '_vc_duration_minutes', true ) );
if ( '' === (string) $duration && $minutes ) {
$duration = vc_duration_human( $minutes );
}
$excerpt = has_excerpt( $post_id )
? get_the_excerpt( $post_id )
: wp_trim_words( wp_strip_all_tags( (string) get_post_field( 'post_content', $post_id ) ), 22 );
$rating_html = '';
if ( '' !== (string) $rating ) {
$rating_html = '<span class="rating">' . vc_icon( 'star', 'icon--12 rating__star' )
. '<b>' . esc_html( number_format_i18n( (float) $rating, 1 ) ) . '</b><i>/5</i></span>';
}
$img_html = vc_tour_image_html( $post_id, 'dcard__img' );
$title_link = '<a href="' . esc_url( $permalink ) . '">' . esc_html( $title ) . '</a>';
ob_start();
?>
<div class="dcard__meta">
<?php if ( '' !== (string) $price ) : ?>
<div class="meta">
<span class="meta__k"><?php echo esc_html__( 'Start at', 'viator-collector' ); ?></span>
<span class="meta__v"><?php echo esc_html( vc_money( $price, $currency ) ); ?></span>
</div>
<?php endif; ?>
<?php if ( $duration ) : ?>
<div class="meta">
<span class="meta__k"><?php echo esc_html__( 'Duration', 'viator-collector' ); ?></span>
<span class="meta__v"><?php echo esc_html( $duration ); ?></span>
</div>
<?php endif; ?>
<?php echo vc_ring_badge( 'BOOK NOW · BOOK NOW · ', $permalink, 'card' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php
$meta_html = ob_get_clean();
if ( 'rail' === $variant ) :
?>
<li class="dcard">
<?php echo $img_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo $rating_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if ( $excerpt ) : ?>
<div class="dcard__desc">
<span class="dcard__desc-k"><?php echo esc_html__( 'Tour Description', 'viator-collector' ); ?></span>
<p><?php echo esc_html( $excerpt ); ?></p>
</div>
<?php endif; ?>
<div class="dcard__body">
<h3 class="dcard__title"><?php echo $title_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></h3>
<?php echo $meta_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
</li>
<?php
else :
?>
<li class="titem">
<article class="dcard dcard--wide">
<?php echo $img_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo $rating_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class="dcard__body">
<h2 class="dcard__title"><?php echo $title_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></h2>
<?php echo $meta_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
</article>
<?php if ( $excerpt ) : ?>
<p class="titem__desc"><?php echo esc_html( $excerpt ); ?></p>
<?php endif; ?>
</li>
<?php
endif;

View File

@@ -0,0 +1,13 @@
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
$text = get_post_meta( $post_id, '_vc_duration_text', true );
$minutes = absint( get_post_meta( $post_id, '_vc_duration_minutes', true ) );
if ( ! $text && ! $minutes ) {
return;
}
if ( ! $text && $minutes ) {
$text = sprintf( _n( '%d minute', '%d minutes', $minutes, 'viator-collector' ), $minutes );
}
?>
<p class="vc-duration"><?php echo esc_html__( 'Duration: ', 'viator-collector' ) . esc_html( $text ); ?></p>

View File

@@ -0,0 +1,23 @@
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
$items = vc_json_decode_meta( get_post_meta( $post_id, '_vc_exclusions', true ), array() );
$rows = array();
foreach ( (array) $items as $item ) {
$text = vc_feature_text( $item );
if ( '' !== $text ) {
$rows[] = $text;
}
}
if ( empty( $rows ) ) {
return;
}
?>
<section class="vc-block vc-reveal vc-exclusions">
<h2><?php echo esc_html__( "What's not included", 'viator-collector' ); ?></h2>
<ul class="vc-features">
<?php foreach ( $rows as $row ) : ?>
<li class="vc-feature vc-feature--exc"><span class="vc-feature__icon" aria-hidden="true"></span><span><?php echo esc_html( $row ); ?></span></li>
<?php endforeach; ?>
</ul>
</section>

View File

@@ -0,0 +1,23 @@
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
$items = vc_json_decode_meta( get_post_meta( $post_id, '_vc_inclusions', true ), array() );
$rows = array();
foreach ( (array) $items as $item ) {
$text = vc_feature_text( $item );
if ( '' !== $text ) {
$rows[] = $text;
}
}
if ( empty( $rows ) ) {
return;
}
?>
<section class="vc-block vc-reveal vc-inclusions">
<h2><?php echo esc_html__( "What's included", 'viator-collector' ); ?></h2>
<ul class="vc-features">
<?php foreach ( $rows as $row ) : ?>
<li class="vc-feature vc-feature--inc"><span class="vc-feature__icon" aria-hidden="true"></span><span><?php echo esc_html( $row ); ?></span></li>
<?php endforeach; ?>
</ul>
</section>

View File

@@ -0,0 +1,35 @@
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
$itinerary = vc_json_decode_meta( get_post_meta( $post_id, '_vc_itinerary', true ), array() );
$steps = vc_itinerary_steps( $itinerary );
if ( empty( $steps ) ) {
return;
}
?>
<section class="vc-block vc-reveal vc-itinerary">
<h2><?php echo esc_html__( 'Itinerary', 'viator-collector' ); ?></h2>
<ol class="vc-itin">
<?php foreach ( $steps as $i => $step ) : ?>
<li class="vc-itin__step">
<span class="vc-itin__marker"><?php echo esc_html( $i + 1 ); ?></span>
<div class="vc-itin__content">
<p class="vc-itin__desc"><?php echo esc_html( $step['description'] ); ?></p>
<?php if ( ! empty( $step['duration'] ) || ! empty( $step['admission'] ) || ! empty( $step['passby'] ) ) : ?>
<div class="vc-itin__meta">
<?php if ( ! empty( $step['duration'] ) ) : ?>
<span class="vc-tag"><?php echo esc_html( vc_duration_human( $step['duration'] ) ); ?></span>
<?php endif; ?>
<?php if ( ! empty( $step['admission'] ) ) : ?>
<span class="vc-tag vc-tag--ok"><?php echo esc_html__( 'Admission included', 'viator-collector' ); ?></span>
<?php endif; ?>
<?php if ( ! empty( $step['passby'] ) ) : ?>
<span class="vc-tag vc-tag--muted"><?php echo esc_html__( 'Pass by', 'viator-collector' ); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</li>
<?php endforeach; ?>
</ol>
</section>

View File

@@ -0,0 +1,253 @@
<?php
/**
* Tours listing UI — "Hop On Hop Off" design (post-type archive, taxonomy
* archives, and the [viator_listing] shortcode).
*
* Filtering stays server-side: every control lives inside #vc-filter-form and
* submits via GET. VC_Display::filter_archive_query() reads the same state.
*
* Args: $vc_title, $vc_subtitle, $vc_query, $vc_form_action.
*/
defined( 'ABSPATH' ) || exit;
$f = VC_Display::get_filter_state();
$vc_title = isset( $vc_title ) ? $vc_title : __( 'Explore Tours', 'viator-collector' );
$vc_subtitle = isset( $vc_subtitle ) ? $vc_subtitle : '';
$vc_title = apply_filters( 'vc_listing_title', $vc_title );
$vc_subtitle = apply_filters( 'vc_listing_subtitle', $vc_subtitle );
$vc_query = ( isset( $vc_query ) && $vc_query instanceof WP_Query ) ? $vc_query : $GLOBALS['wp_query'];
$vc_is_shortcode = ( $vc_query !== $GLOBALS['wp_query'] );
$vc_term = is_tax() ? get_queried_object() : null;
if ( isset( $vc_form_action ) && $vc_form_action ) {
$vc_action = $vc_form_action;
} else {
$vc_action = $vc_term ? get_term_link( $vc_term ) : get_post_type_archive_link( 'viator_tour' );
}
if ( is_wp_error( $vc_action ) || ! $vc_action ) {
$vc_action = home_url( '/' );
}
$vc_destinations = get_terms( array( 'taxonomy' => 'viator_destination', 'hide_empty' => true, 'number' => 20, 'orderby' => 'count', 'order' => 'DESC' ) );
$vc_categories = get_terms( array( 'taxonomy' => 'viator_tag', 'hide_empty' => true, 'number' => 20, 'orderby' => 'count', 'order' => 'DESC' ) );
$vc_destinations = is_wp_error( $vc_destinations ) ? array() : $vc_destinations;
$vc_categories = is_wp_error( $vc_categories ) ? array() : $vc_categories;
$vc_total = (int) $vc_query->found_posts;
$vc_per = 12;
$vc_paged = max( 1, (int) $vc_query->get( 'paged' ), (int) get_query_var( 'paged' ), isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1 ); // phpcs:ignore WordPress.Security.NonceVerification
$vc_first = $vc_total ? ( ( $vc_paged - 1 ) * $vc_per ) + 1 : 0;
$vc_last = min( $vc_total, $vc_paged * $vc_per );
$vc_max = (int) $vc_query->max_num_pages;
$vc_price_val = ( $f['pmin'] > 0 || $f['pmax'] < 1000 ) ? $f['pmin'] . '-' . $f['pmax'] : '';
$vc_dur_labels = array(
'half' => __( 'Half Day (≤5h)', 'viator-collector' ),
'full' => __( 'Full Day (5–24h)', 'viator-collector' ),
'multi' => __( 'Multi-Day', 'viator-collector' ),
);
// Whether any filter is active (drives the "Clear filters" control).
$vc_active = ( '' !== $f['q'] || ! empty( $f['dest'] ) || ! empty( $f['cat'] ) || ! empty( $f['dur'] )
|| $f['pmin'] > 0 || $f['pmax'] < 1000 || $f['rating'] > 0
|| $f['freecancel'] || $f['bestdeal'] || $f['sellout'] );
// Current filters as a base URL, so pagination links keep the query string.
$vc_base_args = array();
if ( '' !== $f['q'] ) { $vc_base_args['vc_q'] = $f['q']; }
if ( ! empty( $f['dest'] ) ) { $vc_base_args['dest'] = $f['dest']; }
if ( ! empty( $f['cat'] ) ) { $vc_base_args['cat'] = $f['cat']; }
if ( ! empty( $f['dur'] ) ) { $vc_base_args['dur'] = $f['dur']; }
if ( '' !== $vc_price_val ) { $vc_base_args['price'] = $vc_price_val; }
if ( $f['rating'] > 0 ) { $vc_base_args['rating'] = $f['rating']; }
if ( $f['freecancel'] ) { $vc_base_args['freecancel'] = 1; }
if ( $f['bestdeal'] ) { $vc_base_args['bestdeal'] = 1; }
if ( $f['sellout'] ) { $vc_base_args['sellout'] = 1; }
if ( 'newest' !== $f['sort'] ) { $vc_base_args['sort'] = $f['sort']; }
$vc_page_url = function ( $n ) use ( $vc_action, $vc_base_args ) {
$args = $vc_base_args;
if ( $n > 1 ) {
$args['paged'] = $n;
}
return empty( $args ) ? $vc_action : add_query_arg( $args, $vc_action );
};
$vc_hero_img = vc_hero_image_url();
if ( ! $vc_hero_img ) {
$vc_hero_img = vc_asset_url( 'img/hero2.png' );
}
?>
<section class="hero hero--archive">
<div class="hero__media">
<img class="hero__img" src="<?php echo esc_url( $vc_hero_img ); ?>" alt="" fetchpriority="high">
<div class="hero__scrim" aria-hidden="true"></div>
</div>
<div class="container hero__content">
<p class="pill hero__eyebrow"><?php echo esc_html__( 'Tour & Travel', 'viator-collector' ); ?></p>
<h1 class="hero__title"><?php echo esc_html( $vc_title ); ?></h1>
<form class="filters" id="vc-filter-form" method="get" action="<?php echo esc_url( $vc_action ); ?>" novalidate>
<div class="filters__row">
<div class="fsearch">
<?php echo vc_icon( 'search', 'fsearch__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<input class="fsearch__input" name="vc_q" type="search" value="<?php echo esc_attr( $f['q'] ); ?>" placeholder="<?php echo esc_attr__( 'Search tours, cities …', 'viator-collector' ); ?>" aria-label="<?php echo esc_attr__( 'Search tours and cities', 'viator-collector' ); ?>">
</div>
<div class="fsel">
<?php echo vc_icon( 'location', 'fsel__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<select class="fsel__input" name="dest[]" aria-label="<?php echo esc_attr__( 'Destination', 'viator-collector' ); ?>">
<option value=""><?php echo esc_html__( 'Destination', 'viator-collector' ); ?></option>
<?php foreach ( $vc_destinations as $t ) : ?>
<option value="<?php echo esc_attr( $t->slug ); ?>" <?php selected( in_array( $t->slug, $f['dest'], true ) ); ?>><?php echo esc_html( $t->name ); ?></option>
<?php endforeach; ?>
</select>
<?php echo vc_icon( 'chevron', 'fsel__chevron' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<div class="fsel">
<?php echo vc_icon( 'grid', 'fsel__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<select class="fsel__input" name="cat[]" aria-label="<?php echo esc_attr__( 'Category', 'viator-collector' ); ?>">
<option value=""><?php echo esc_html__( 'Categorize', 'viator-collector' ); ?></option>
<?php foreach ( $vc_categories as $t ) : ?>
<option value="<?php echo esc_attr( $t->slug ); ?>" <?php selected( in_array( $t->slug, $f['cat'], true ) ); ?>><?php echo esc_html( $t->name ); ?></option>
<?php endforeach; ?>
</select>
<?php echo vc_icon( 'chevron', 'fsel__chevron' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<div class="fsel">
<?php echo vc_icon( 'price', 'fsel__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<select class="fsel__input" name="price" aria-label="<?php echo esc_attr__( 'Price range', 'viator-collector' ); ?>">
<option value=""><?php echo esc_html__( 'Price Range', 'viator-collector' ); ?></option>
<option value="0-50" <?php selected( $vc_price_val, '0-50' ); ?>><?php echo esc_html__( 'Up to $50', 'viator-collector' ); ?></option>
<option value="50-150" <?php selected( $vc_price_val, '50-150' ); ?>>$50 &ndash; $150</option>
<option value="150-1000" <?php selected( $vc_price_val, '150-1000' ); ?>><?php echo esc_html__( '$150 and up', 'viator-collector' ); ?></option>
</select>
<?php echo vc_icon( 'chevron', 'fsel__chevron' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<div class="fsel">
<?php echo vc_icon( 'duration', 'fsel__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<select class="fsel__input" name="dur[]" aria-label="<?php echo esc_attr__( 'Duration', 'viator-collector' ); ?>">
<option value=""><?php echo esc_html__( 'Duration', 'viator-collector' ); ?></option>
<?php foreach ( $vc_dur_labels as $key => $label ) : ?>
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( in_array( $key, $f['dur'], true ) ); ?>><?php echo esc_html( $label ); ?></option>
<?php endforeach; ?>
</select>
<?php echo vc_icon( 'chevron', 'fsel__chevron' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<button class="btn btn--dark filters__apply" type="submit"><?php echo esc_html__( 'Apply Filters', 'viator-collector' ); ?></button>
</div>
<div class="filters__bar">
<div class="filters__checks">
<label class="check">
<input class="check__input vc-autosubmit" type="checkbox" name="freecancel" value="1" <?php checked( $f['freecancel'], 1 ); ?>>
<span class="check__box" aria-hidden="true"><?php echo vc_icon( 'check', 'icon--12' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
<span class="check__text"><?php echo esc_html__( 'Free Cancellation', 'viator-collector' ); ?></span>
</label>
<label class="check">
<input class="check__input vc-autosubmit" type="checkbox" name="bestdeal" value="1" <?php checked( $f['bestdeal'], 1 ); ?>>
<span class="check__box" aria-hidden="true"><?php echo vc_icon( 'check', 'icon--12' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
<span class="check__text"><?php echo esc_html__( 'Best Deal', 'viator-collector' ); ?></span>
</label>
<label class="check">
<input class="check__input vc-autosubmit" type="checkbox" name="sellout" value="1" <?php checked( $f['sellout'], 1 ); ?>>
<span class="check__box" aria-hidden="true"><?php echo vc_icon( 'check', 'icon--12' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
<span class="check__text"><?php echo esc_html__( 'Likely to Sell Out', 'viator-collector' ); ?></span>
</label>
</div>
<div class="filters__sort">
<span class="filters__sort-label"><?php echo vc_icon( 'sort' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><label for="f-sort"><?php echo esc_html__( 'Sort by', 'viator-collector' ); ?></label></span>
<div class="fsel fsel--bare">
<select class="fsel__input vc-autosubmit" id="f-sort" name="sort">
<option value="newest" <?php selected( $f['sort'], 'newest' ); ?>><?php echo esc_html__( 'Newest', 'viator-collector' ); ?></option>
<option value="price_asc" <?php selected( $f['sort'], 'price_asc' ); ?>><?php echo esc_html__( 'Price: low to high', 'viator-collector' ); ?></option>
<option value="price_desc" <?php selected( $f['sort'], 'price_desc' ); ?>><?php echo esc_html__( 'Price: high to low', 'viator-collector' ); ?></option>
<option value="rating" <?php selected( $f['sort'], 'rating' ); ?>><?php echo esc_html__( 'Top rated', 'viator-collector' ); ?></option>
</select>
<?php echo vc_icon( 'chevron', 'fsel__chevron' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
</div>
</div>
</form>
</div>
<?php echo vc_ring_badge( 'SCROLL DOWN · SCROLL DOWN · ', '#tour-list', 'scroll' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</section>
<section class="section" id="tour-list">
<div class="container">
<div class="results">
<p class="results__count" role="status">
<?php if ( $vc_total ) : ?>
<?php
printf(
/* translators: 1: first result, 2: last result, 3: total. */
wp_kses( __( 'Showing <b>%1$d&ndash;%2$d</b> of <b>%3$d</b> tours', 'viator-collector' ), array( 'b' => array() ) ),
(int) $vc_first,
(int) $vc_last,
(int) $vc_total
);
?>
<?php else : ?>
<?php echo esc_html__( 'No tours found', 'viator-collector' ); ?>
<?php endif; ?>
</p>
<?php if ( $vc_active ) : ?>
<a class="btn btn--ghost results__reset" href="<?php echo esc_url( $vc_action ); ?>"><?php echo esc_html__( 'Clear filters', 'viator-collector' ); ?></a>
<?php endif; ?>
</div>
<?php if ( $vc_query->have_posts() ) : ?>
<ul class="tgrid" id="tourGrid">
<?php
while ( $vc_query->have_posts() ) :
$vc_query->the_post();
echo vc_get_template( 'card-tour.php', array( 'post_id' => get_the_ID(), 'variant' => 'wide' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
endwhile;
wp_reset_postdata();
?>
</ul>
<?php if ( $vc_max > 1 ) : ?>
<nav class="pager" aria-label="<?php echo esc_attr__( 'Tour list pages', 'viator-collector' ); ?>">
<?php if ( $vc_paged > 1 ) : ?>
<a class="pager__btn pager__btn--icon" href="<?php echo esc_url( $vc_page_url( $vc_paged - 1 ) ); ?>" aria-label="<?php echo esc_attr__( 'Previous page', 'viator-collector' ); ?>"><?php echo vc_icon( 'arrow-left' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
<?php endif; ?>
<span class="pager__pages">
<?php
$vc_range = vc_pager_range( $vc_paged, $vc_max );
foreach ( $vc_range as $n ) :
if ( null === $n ) :
?>
<span class="pager__gap" aria-hidden="true">&hellip;</span>
<?php
elseif ( $n === $vc_paged ) :
?>
<span class="pager__num is-current" aria-current="page"><?php echo esc_html( $n ); ?></span>
<?php
else :
?>
<a class="pager__num" href="<?php echo esc_url( $vc_page_url( $n ) ); ?>"><?php echo esc_html( $n ); ?></a>
<?php
endif;
endforeach;
?>
</span>
<?php if ( $vc_paged < $vc_max ) : ?>
<a class="pager__btn" href="<?php echo esc_url( $vc_page_url( $vc_paged + 1 ) ); ?>"><span><?php echo esc_html__( 'Next', 'viator-collector' ); ?></span><?php echo vc_icon( 'arrow-right' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
<?php endif; ?>
</nav>
<?php endif; ?>
<?php else : ?>
<p class="tgrid__empty"><?php echo esc_html__( 'No tours match these filters. Try widening your search or', 'viator-collector' ); ?> <a class="linkbtn" href="<?php echo esc_url( $vc_action ); ?>"><?php echo esc_html__( 'clear the filters', 'viator-collector' ); ?></a>.</p>
<?php endif; ?>
</div>
</section>

View File

@@ -0,0 +1,10 @@
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $post_id ) ? absint( $post_id ) : get_the_ID();
$rating = get_post_meta( $post_id, '_vc_rating', true );
$count = absint( get_post_meta( $post_id, '_vc_review_count', true ) );
if ( '' === $rating && ! $count ) {
return;
}
?>
<p class="vc-rating"><?php echo esc_html( $rating ); ?><?php if ( $count ) : ?> <span><?php echo esc_html( sprintf( _n( '(%d review)', '(%d reviews)', $count, 'viator-collector' ), $count ) ); ?></span><?php endif; ?></p>

View File

@@ -0,0 +1,255 @@
<?php
/**
* Single Viator tour — "Hop On Hop Off" design (ported from new front/tour.html).
* Itinerary and Reviews are not in the mockup; they reuse the design's own
* primitives (.notes, .tmeta) so they sit naturally alongside the rest.
*/
defined( 'ABSPATH' ) || exit;
get_header();
while ( have_posts() ) :
the_post();
$post_id = get_the_ID();
$rating = get_post_meta( $post_id, '_vc_rating', true );
$reviews = (int) get_post_meta( $post_id, '_vc_review_count', true );
$price = get_post_meta( $post_id, '_vc_price_from', true );
$currency = get_post_meta( $post_id, '_vc_currency', true );
$supplier = get_post_meta( $post_id, '_vc_supplier', true );
$duration_min = (int) get_post_meta( $post_id, '_vc_duration_minutes', true );
$max_trav = (int) get_post_meta( $post_id, '_vc_max_travelers', true );
$freecancel = (int) get_post_meta( $post_id, '_vc_free_cancellation', true );
$booking_url = get_post_meta( $post_id, '_vc_booking_url', true );
$dest_terms = get_the_terms( $post_id, 'viator_destination' );
$dest_names = ( $dest_terms && ! is_wp_error( $dest_terms ) ) ? wp_list_pluck( $dest_terms, 'name' ) : array();
$cat_terms = get_the_terms( $post_id, 'viator_tag' );
$cat_names = ( $cat_terms && ! is_wp_error( $cat_terms ) ) ? array_slice( wp_list_pluck( $cat_terms, 'name' ), 0, 2 ) : array();
$gallery = vc_json_decode_meta( get_post_meta( $post_id, '_vc_gallery_image_urls', true ), array() );
$gallery = array_values( array_filter( (array) $gallery ) );
$primary = get_post_meta( $post_id, '_vc_primary_image_url', true );
if ( ! $primary && ! empty( $gallery ) ) {
$primary = $gallery[0];
}
$more = array_slice( array_values( array_diff( $gallery, array( $primary ) ) ), 0, 5 );
$big_word = ! empty( $dest_names ) ? $dest_names[0] : get_the_title();
?>
<div class="vc-app vc-single">
<section class="hero-tour">
<?php if ( $primary ) : ?>
<img class="hero-tour__photo" src="<?php echo esc_url( $primary ); ?>" alt="" fetchpriority="high">
<?php endif; ?>
<div class="hero-tour__sky" aria-hidden="true"></div>
<div class="container">
<div class="hero-tour__block">
<p class="hero-tour__kicker"><?php echo esc_html__( 'Welcome to', 'viator-collector' ); ?></p>
<div class="hero-tour__stage">
<h1 class="hero-tour__word"><?php echo esc_html( $big_word ); ?></h1>
<img class="hero-tour__figure" src="<?php echo esc_url( vc_asset_url( 'img/tower.png' ) ); ?>" alt="" loading="eager">
</div>
</div>
</div>
</section>
<section class="section tour">
<div class="container tour__layout">
<div class="tour__main">
<header class="tour-head">
<h2 class="tour-head__title"><?php the_title(); ?></h2>
<div class="tmeta">
<?php if ( '' !== (string) $rating ) : ?>
<span class="tmeta__item"><?php echo vc_icon( 'star', 'icon--12 tmeta__star' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><b><?php echo esc_html( number_format_i18n( (float) $rating, 1 ) ); ?></b><i>/5</i></span>
<?php endif; ?>
<?php if ( ! empty( $dest_names ) ) : ?>
<span class="tmeta__rule" aria-hidden="true"></span>
<span class="tmeta__item"><?php echo vc_icon( 'location', 'tmeta__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php echo esc_html( implode( ', ', array_slice( $dest_names, 0, 2 ) ) ); ?></span>
<?php endif; ?>
<?php if ( $supplier ) : ?>
<span class="tmeta__rule" aria-hidden="true"></span>
<span class="tmeta__item"><?php echo vc_icon( 'person', 'tmeta__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php echo esc_html__( 'from', 'viator-collector' ) . ' ' . esc_html( $supplier ); ?></span>
<?php endif; ?>
</div>
</header>
<ul class="facts">
<?php if ( $duration_min ) : ?>
<li class="fact"><?php echo vc_icon( 'duration', 'fact__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><div class="fact__text"><span class="fact__k"><?php echo esc_html__( 'Duration', 'viator-collector' ); ?></span><span class="fact__v"><?php echo esc_html( vc_duration_human( $duration_min ) ); ?></span></div></li>
<?php endif; ?>
<?php if ( $max_trav ) : ?>
<li class="fact"><?php echo vc_icon( 'person', 'fact__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><div class="fact__text"><span class="fact__k"><?php echo esc_html__( 'Max Travelers', 'viator-collector' ); ?></span><span class="fact__v"><?php echo esc_html( sprintf( __( '%d people', 'viator-collector' ), $max_trav ) ); ?></span></div></li>
<?php endif; ?>
<?php if ( $freecancel ) : ?>
<li class="fact"><?php echo vc_icon( 'shield-check', 'fact__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><div class="fact__text"><span class="fact__k"><?php echo esc_html__( 'Free Cancellation', 'viator-collector' ); ?></span><span class="fact__v"><?php echo esc_html__( 'Up to 24 hrs', 'viator-collector' ); ?></span></div></li>
<?php endif; ?>
<?php if ( ! empty( $cat_names ) ) : ?>
<li class="fact"><?php echo vc_icon( 'grid', 'fact__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><div class="fact__text"><span class="fact__k"><?php echo esc_html__( 'Category', 'viator-collector' ); ?></span><span class="fact__v"><?php echo esc_html( implode( ', ', $cat_names ) ); ?></span></div></li>
<?php endif; ?>
</ul>
<?php if ( get_the_content() ) : ?>
<section class="tour__block">
<h3 class="tour__ct"><?php echo esc_html__( 'Overview', 'viator-collector' ); ?></h3>
<div class="tour__text"><?php the_content(); ?></div>
</section>
<?php endif; ?>
<?php if ( $primary ) : ?>
<section class="tour__block">
<h3 class="tour__ct"><?php echo esc_html__( 'Gallery', 'viator-collector' ); ?></h3>
<div class="gallery">
<img class="gallery__hero" src="<?php echo esc_url( $primary ); ?>" alt="<?php echo esc_attr( get_the_title() ); ?>" loading="lazy">
<?php foreach ( $more as $url ) : ?>
<img src="<?php echo esc_url( $url ); ?>" alt="<?php echo esc_attr( get_the_title() ); ?>" loading="lazy">
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php
$inc = array_filter( array_map( 'vc_feature_text', (array) vc_json_decode_meta( get_post_meta( $post_id, '_vc_inclusions', true ), array() ) ) );
$exc = array_filter( array_map( 'vc_feature_text', (array) vc_json_decode_meta( get_post_meta( $post_id, '_vc_exclusions', true ), array() ) ) );
if ( $inc || $exc ) :
?>
<div class="lists">
<?php if ( $inc ) : ?>
<section class="ilist">
<h3 class="tour__ct"><?php echo esc_html__( "What's included", 'viator-collector' ); ?></h3>
<ul class="ilist__items">
<?php foreach ( $inc as $row ) : ?>
<li><span class="tick tick--yes" aria-hidden="true"><?php echo vc_icon( 'check', 'icon--12' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span><p><?php echo esc_html( $row ); ?></p></li>
<?php endforeach; ?>
</ul>
</section>
<?php endif; ?>
<?php if ( $exc ) : ?>
<section class="ilist">
<h3 class="tour__ct"><?php echo esc_html__( "What's Not included", 'viator-collector' ); ?></h3>
<ul class="ilist__items">
<?php foreach ( $exc as $row ) : ?>
<li><span class="tick tick--no" aria-hidden="true"><?php echo vc_icon( 'x', 'icon--12' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span><p><?php echo esc_html( $row ); ?></p></li>
<?php endforeach; ?>
</ul>
</section>
<?php endif; ?>
</div>
<?php endif; ?>
<?php
$steps = vc_itinerary_steps( vc_json_decode_meta( get_post_meta( $post_id, '_vc_itinerary', true ), array() ) );
if ( ! empty( $steps ) ) :
?>
<section class="tour__block">
<h3 class="tour__ct"><?php echo esc_html__( 'Itinerary', 'viator-collector' ); ?></h3>
<ul class="notes">
<?php foreach ( $steps as $step ) : ?>
<li class="note"><?php echo vc_icon( 'location', 'note__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><p><?php
$name = ! empty( $step['name'] ) ? '<strong>' . esc_html( $step['name'] ) . '</strong> ' : '';
echo $name . esc_html( ! empty( $step['description'] ) ? $step['description'] : '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?></p></li>
<?php endforeach; ?>
</ul>
</section>
<?php endif; ?>
<?php
$info = (array) vc_json_decode_meta( get_post_meta( $post_id, '_vc_additional_info', true ), array() );
$info_rows = array_filter( array_map( 'vc_feature_text', $info ) );
if ( ! empty( $info_rows ) ) :
?>
<section class="tour__block">
<h3 class="tour__ct"><?php echo esc_html__( 'Good to know', 'viator-collector' ); ?></h3>
<ul class="notes">
<?php foreach ( $info_rows as $row ) : ?>
<li class="note"><?php echo vc_icon( 'info', 'note__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><p><?php echo esc_html( $row ); ?></p></li>
<?php endforeach; ?>
</ul>
</section>
<?php endif; ?>
<?php
$policy = vc_json_decode_meta( get_post_meta( $post_id, '_vc_cancellation_policy', true ), array() );
$policy_text = is_scalar( $policy ) ? (string) $policy : (string) vc_array_get( $policy, 'description' );
$tiers = is_array( $policy ) ? (array) vc_array_get( $policy, 'refundEligibility', array() ) : array();
if ( $policy_text || $tiers ) :
?>
<section class="tour__block">
<h3 class="tour__ct"><?php echo esc_html__( 'Cancellation Policy', 'viator-collector' ); ?></h3>
<?php if ( $policy_text ) : ?><p class="tour__text"><?php echo esc_html( $policy_text ); ?></p><?php endif; ?>
<?php if ( $tiers ) : ?>
<div class="policy">
<?php
foreach ( $tiers as $tier ) :
$pct = (int) vc_array_get( $tier, 'percentageRefundable', 0 );
$max = vc_array_get( $tier, 'dayRangeMax', null );
$min = vc_array_get( $tier, 'dayRangeMin', null );
$yes = $pct > 0;
$when = ( null !== $max )
? sprintf( __( 'Less than %d hours before the start time', 'viator-collector' ), (int) $max * 24 )
: sprintf( __( '%d hours or more before the start time', 'viator-collector' ), (int) $min * 24 );
?>
<div class="statcard statcard--<?php echo $yes ? 'yes' : 'no'; ?>">
<?php echo vc_icon( $yes ? 'check' : 'x', 'icon--12 statcard__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class="statcard__text">
<span class="statcard__k"><?php echo esc_html( sprintf( __( '%d%% Refund', 'viator-collector' ), $pct ) ); ?></span>
<span class="statcard__v"><?php echo esc_html( $when ); ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<?php endif; ?>
<?php if ( '' !== (string) $rating && $reviews ) : ?>
<section class="tour__block" id="reviews">
<h3 class="tour__ct"><?php echo esc_html__( 'Reviews', 'viator-collector' ); ?></h3>
<div class="tmeta">
<span class="tmeta__item"><?php echo vc_icon( 'star', 'icon--12 tmeta__star' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><b><?php echo esc_html( number_format_i18n( (float) $rating, 1 ) ); ?></b><i>/5</i></span>
<span class="tmeta__rule" aria-hidden="true"></span>
<span class="tmeta__item"><?php echo esc_html( sprintf( _n( '%s review', '%s reviews', $reviews, 'viator-collector' ), number_format_i18n( $reviews ) ) ); ?></span>
</div>
</section>
<?php endif; ?>
</div>
<aside class="tour__aside">
<div class="bookcard">
<h2 class="bookcard__title"><?php echo vc_icon( 'info' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php echo esc_html__( 'Tour Details', 'viator-collector' ); ?></h2>
<div class="bookcard__price">
<?php if ( '' !== (string) $price ) : ?>
<p class="price">
<span class="price__k"><?php echo esc_html__( 'From', 'viator-collector' ); ?></span>
<span class="price__v"><?php echo esc_html( vc_money( $price, $currency ) ); ?></span>
<span class="price__unit"><?php echo esc_html( ( $currency ? strtoupper( $currency ) . ' ' : '' ) . __( '/ Per person', 'viator-collector' ) ); ?></span>
</p>
<?php endif; ?>
<a class="btn btn--dark bookcard__cta" href="<?php echo esc_url( $booking_url ? $booking_url : '#' ); ?>"<?php echo $booking_url ? ' target="_blank" rel="nofollow noopener"' : ''; ?>><?php echo esc_html__( 'Check Availability', 'viator-collector' ); ?></a>
</div>
<div class="statcard statcard--teal">
<?php echo vc_icon( 'shield-check', 'statcard__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class="statcard__text">
<span class="statcard__k"><?php echo esc_html__( 'Free Cancellation', 'viator-collector' ); ?></span>
<span class="statcard__v"><?php echo esc_html__( 'Up to 24 hrs', 'viator-collector' ); ?></span>
</div>
</div>
<div class="statcard statcard--yes">
<?php echo vc_icon( 'shield-check', 'statcard__icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class="statcard__text">
<span class="statcard__k"><?php echo esc_html__( 'Reserve now, pay later!', 'viator-collector' ); ?></span>
<span class="statcard__v"><?php echo esc_html__( 'Secure your spot today', 'viator-collector' ); ?></span>
</div>
</div>
</div>
</aside>
</div>
</section>
</div><!-- #vc-app -->
<?php
endwhile;
get_footer();

View File

@@ -0,0 +1,32 @@
<?php
/**
* Shared archive for Viator destination / tag / category terms.
* Reuses the same listing UI (filters, sort, pagination) as the main archive.
*/
defined( 'ABSPATH' ) || exit;
get_header();
$vc_term = get_queried_object();
$vc_name = ( $vc_term && ! is_wp_error( $vc_term ) ) ? $vc_term->name : __( 'Tours', 'viator-collector' );
$vc_desc = term_description();
?>
<main class="vc-app vc-listing vc-listing--term">
<?php
echo vc_get_template( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'listing.php',
array(
'vc_title' => $vc_name,
/* translators: %s: term name. */
'vc_subtitle' => $vc_desc ? wp_strip_all_tags( $vc_desc ) : sprintf( __( 'Explore the best tours in %s.', 'viator-collector' ), $vc_name ),
'vc_crumb' => array(
array(
'label' => __( 'Tours', 'viator-collector' ),
'url' => get_post_type_archive_link( 'viator_tour' ),
),
),
)
);
?>
</main>
<?php
get_footer();