Files
hoponhopofftickets/viator-collector/templates/listing.php
amir 67c88c795b fix: Astra style conflicts, full-width, responsive hero images, header search
Plugin (viator-collector 1.8.1):
- is_vc_page() now matches any Viator shortcode, so shortcode Pages (home)
  get the full-width no-sidebar canvas
- load vc-design.css after Astra + inline overrides: strip Astra page-builder
  article padding (removes extra gap above hero), full-bleed containers,
  enforce design font inside .vc-app
- vc_hero_img_html(): render hero from the WP media library with responsive
  srcset when the image is an attachment; used in listing + [viator_hero]
  ([viator_hero image_id=".."])

Theme (astra child):
- remove the user/profile header item
- working header search (expands, submits to the tours archive ?vc_q=)
- load chrome.css after Astra + enforce design font inside .hoho
- rebuild installable ZIP

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J4hNL1PSHxXbu94SCenpKj
2026-09-15 17:32:07 +03:30

254 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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">
<?php echo vc_hero_img_html( $vc_hero_img, 'hero__img', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<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>