Files
hoponhopofftickets/viator-collector/templates/listing.php
amir 35368fb5f9 perf/fix: externalize images to WP media; normalize Astra leaks; full-width
Plugin (1.8.2):
- remove all bundled raster images (~9.3MB) from assets/img; hero + single
  hero figure now come from the WP Media Library (Settings), shortcode args
  ([viator_hero image_id]), or the vc_testimonials filter — rendered responsive
- vc_hero_image_source() prefers an attachment ID (srcset); listing/hero use it
- settings: store hero_image_id + single_hero_figure_id (media pickers)
- enqueue at priority 20 + depend on astra-theme-css so our CSS wins ties
- stronger Astra overrides (!important): kill page-builder article top padding
  (gap above hero), force full-bleed containers, guard horizontal scroll
- normalize theme leaks inside .vc-app: no link underline, no list bullets
  (with prose exceptions for the tour Overview); gradient hero fallback

Theme: normalize .hoho link/list leaks; rebuild installable ZIP.
Also: move optimized originals to /removed-images/; ship viator-collector.zip.

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

251 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_source();
?>
<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>