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
This commit is contained in:
@@ -24,7 +24,9 @@ class VC_Display {
|
||||
);
|
||||
|
||||
public static function init() {
|
||||
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
|
||||
// Priority 20 so Astra has already registered/enqueued its stylesheet;
|
||||
// our CSS then loads after it and wins equal-specificity ties.
|
||||
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ), 20 );
|
||||
add_action( 'template_redirect', array( __CLASS__, 'maybe_enrich_single' ) );
|
||||
add_action( 'pre_get_posts', array( __CLASS__, 'filter_archive_query' ) );
|
||||
add_filter( 'single_template', array( __CLASS__, 'single_template' ) );
|
||||
@@ -170,14 +172,36 @@ class VC_Display {
|
||||
'',
|
||||
array(
|
||||
// Full-bleed: strip Astra's page-builder article padding/width so
|
||||
// the hero and sections reach the viewport edges (fixes the extra
|
||||
// top gap above the hero and the non-full-width sections).
|
||||
'.vc-fullwidth .site-main>article,.vc-fullwidth .ast-page-builder-template .site-main>article{padding:0;margin:0;border:0;}',
|
||||
'.vc-fullwidth #primary{margin:0;padding:0;}',
|
||||
'.vc-fullwidth .site-content .ast-container{max-width:none;padding-inline:0;}',
|
||||
'.vc-fullwidth .ast-container{max-width:none;}',
|
||||
'.vc-fullwidth .entry-content{margin-block:0;}',
|
||||
'.vc-fullwidth .site-content{padding:0;}',
|
||||
// the hero and sections reach the viewport edges. !important beats
|
||||
// Astra's own high-specificity rules (e.g. the 2em article padding
|
||||
// that leaves a gap above the hero).
|
||||
'.vc-fullwidth .site-main>article,'
|
||||
. '.vc-fullwidth .ast-page-builder-template .site-main>article,'
|
||||
. '.vc-fullwidth.ast-page-builder-template .site-main>article'
|
||||
. '{padding:0!important;margin:0!important;border:0!important;max-width:none!important;}',
|
||||
'.vc-fullwidth #primary,.vc-fullwidth #content{margin:0!important;padding:0!important;}',
|
||||
'.vc-fullwidth .site-content .ast-container,.vc-fullwidth .ast-container'
|
||||
. '{max-width:none!important;padding-left:0!important;padding-right:0!important;}',
|
||||
'.vc-fullwidth .entry-content{margin-block:0!important;}',
|
||||
'.vc-fullwidth .site-content{padding:0!important;}',
|
||||
// Guard against a stray horizontal scrollbar from any full-bleed child.
|
||||
'body.vc-fullwidth{overflow-x:hidden;}',
|
||||
// A gradient stands in when no hero image is set, so the banner is
|
||||
// never blank.
|
||||
'.vc-app .hero__media,.vc-app .hero-tour{background:linear-gradient(120deg,#101317,#2cafc2);}',
|
||||
// Normalize: stop Astra from decorating our components. Its
|
||||
// .entry-content rules (underlined links, list bullets/indent)
|
||||
// out-specify our scoped base, so reset them here (!important beats
|
||||
// e.g. `.ast-single-post .entry-content a`). The .vc-app.vc-app
|
||||
// doubling lifts specificity without touching design class rules.
|
||||
'.vc-app a,.vc-app.vc-app a{text-decoration:none;box-shadow:none;}',
|
||||
'.vc-app :is(ul,ol),.vc-app.vc-app :is(ul,ol){list-style:none!important;margin:0!important;padding:0!important;}',
|
||||
'.vc-app li{margin:0!important;}',
|
||||
// …but keep the tour "Overview" prose (the_content) readable.
|
||||
'.vc-app .tour__text a{text-decoration:underline;}',
|
||||
'.vc-app .tour__text ul{list-style:disc!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app .tour__text ol{list-style:decimal!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app .tour__text li{margin:.25em 0!important;}',
|
||||
// Typography: force the design font over Astra inside our scopes.
|
||||
'.vc-app,.vc-app h1,.vc-app h2,.vc-app h3,.vc-app h4,.vc-app h5,.vc-app h6,'
|
||||
. '.vc-app p,.vc-app a,.vc-app span,.vc-app li,.vc-app button,.vc-app input,'
|
||||
|
||||
@@ -50,17 +50,15 @@ class VC_Sections {
|
||||
self::HERO
|
||||
);
|
||||
|
||||
// Prefer a media-library image (responsive srcset); fall back to a URL,
|
||||
// the plugin's Hero image setting, then the bundled image.
|
||||
$img = '';
|
||||
// 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'];
|
||||
} elseif ( vc_hero_image_url() ) {
|
||||
$img = vc_hero_image_url();
|
||||
} else {
|
||||
$img = vc_asset_url( 'img/hero.png' );
|
||||
$img = vc_hero_image_source();
|
||||
}
|
||||
$archive = get_post_type_archive_link( 'viator_tour' );
|
||||
$archive = $archive ? $archive : home_url( '/' );
|
||||
@@ -405,15 +403,15 @@ class VC_Sections {
|
||||
self::REVIEWS
|
||||
);
|
||||
|
||||
$a1 = vc_asset_url( 'img/ellipse-17.png' );
|
||||
$a2 = vc_asset_url( 'img/ellipse-17-2.png' );
|
||||
// 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' => $a1, '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' => $a2, 'size' => '' ),
|
||||
array( 'name' => 'Sofia Rossi', 'role' => __( 'Sydney Tour', 'viator-collector' ), 'text' => __( 'Smooth from start to finish. Highly recommended.', 'viator-collector' ), 'avatar' => $a1, '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' => $a2, 'size' => '' ),
|
||||
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 ) ) {
|
||||
|
||||
@@ -75,6 +75,8 @@ class VC_Settings {
|
||||
'timeout' => 20,
|
||||
'default_post_status' => 'publish',
|
||||
'hero_image' => '',
|
||||
'hero_image_id' => 0,
|
||||
'single_hero_figure_id' => 0,
|
||||
'hero_slides' => '',
|
||||
'crawl_destinations' => '',
|
||||
'rate_limit_per_minute' => 25,
|
||||
@@ -122,6 +124,8 @@ class VC_Settings {
|
||||
'timeout' => max( 5, absint( $input['timeout'] ?? 20 ) ),
|
||||
'default_post_status' => sanitize_key( $input['default_post_status'] ?? 'publish' ),
|
||||
'hero_image' => esc_url_raw( $input['hero_image'] ?? '' ),
|
||||
'hero_image_id' => absint( $input['hero_image_id'] ?? 0 ),
|
||||
'single_hero_figure_id' => absint( $input['single_hero_figure_id'] ?? 0 ),
|
||||
'hero_slides' => self::sanitize_id_list( $input['hero_slides'] ?? '' ),
|
||||
'crawl_destinations' => self::sanitize_id_list( $input['crawl_destinations'] ?? '' ),
|
||||
'rate_limit_per_minute' => max( 1, min( 60, absint( $input['rate_limit_per_minute'] ?? 25 ) ) ),
|
||||
@@ -198,9 +202,10 @@ class VC_Settings {
|
||||
<th scope="row"><label for="vc_hero_image"><?php echo esc_html__( 'Hero image', 'viator-collector' ); ?></label></th>
|
||||
<td>
|
||||
<input id="vc_hero_image" type="url" class="regular-text" name="vc_settings[hero_image]" value="<?php echo esc_attr( $options['hero_image'] ); ?>" placeholder="https://…">
|
||||
<input id="vc_hero_image_id" type="hidden" name="vc_settings[hero_image_id]" value="<?php echo esc_attr( $options['hero_image_id'] ); ?>">
|
||||
<button type="button" class="button" id="vc-hero-pick"><?php echo esc_html__( 'Select image', 'viator-collector' ); ?></button>
|
||||
<button type="button" class="button" id="vc-hero-clear"><?php echo esc_html__( 'Clear', 'viator-collector' ); ?></button>
|
||||
<p class="description"><?php echo esc_html__( 'Banner shown at the top of the tours listing. Leave empty for a gradient. Destination pages use their own image when one is set.', 'viator-collector' ); ?></p>
|
||||
<p class="description"><?php echo esc_html__( 'Banner at the top of the tours listing / home hero. Pick from the Media Library so the browser gets responsive, device-sized images. Leave empty for a gradient. Destination pages use their own image when one is set.', 'viator-collector' ); ?></p>
|
||||
<div id="vc-hero-preview" style="margin-top:8px;">
|
||||
<?php if ( $options['hero_image'] ) : ?>
|
||||
<img src="<?php echo esc_url( $options['hero_image'] ); ?>" alt="" style="max-width:320px;height:auto;border-radius:8px;">
|
||||
@@ -221,6 +226,7 @@ class VC_Settings {
|
||||
frame.on('select', function () {
|
||||
var img = frame.state().get('selection').first().toJSON();
|
||||
$('#vc_hero_image').val(img.url);
|
||||
$('#vc_hero_image_id').val(img.id);
|
||||
$('#vc-hero-preview').html($('<img>', { src: img.url, alt: '' }).css({ maxWidth: '320px', height: 'auto', borderRadius: '8px' }));
|
||||
});
|
||||
frame.open();
|
||||
@@ -228,12 +234,56 @@ class VC_Settings {
|
||||
$('#vc-hero-clear').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#vc_hero_image').val('');
|
||||
$('#vc_hero_image_id').val('');
|
||||
$('#vc-hero-preview').empty();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label><?php echo esc_html__( 'Single tour hero figure', 'viator-collector' ); ?></label></th>
|
||||
<td>
|
||||
<input id="vc_single_figure_id" type="hidden" name="vc_settings[single_hero_figure_id]" value="<?php echo esc_attr( $options['single_hero_figure_id'] ); ?>">
|
||||
<button type="button" class="button" id="vc-figure-pick"><?php echo esc_html__( 'Select image', 'viator-collector' ); ?></button>
|
||||
<button type="button" class="button" id="vc-figure-clear"><?php echo esc_html__( 'Clear', 'viator-collector' ); ?></button>
|
||||
<p class="description"><?php echo esc_html__( 'Optional decorative cut-out shown over the single-tour hero (e.g. a landmark PNG with a transparent background). Leave empty to hide it.', 'viator-collector' ); ?></p>
|
||||
<div id="vc-figure-preview" style="margin-top:8px;">
|
||||
<?php
|
||||
if ( $options['single_hero_figure_id'] ) {
|
||||
echo wp_get_attachment_image( (int) $options['single_hero_figure_id'], 'medium', false, array( 'style' => 'max-width:200px;height:auto;' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<script>
|
||||
jQuery(function ($) {
|
||||
var frame;
|
||||
$('#vc-figure-pick').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
if (frame) { frame.open(); return; }
|
||||
frame = wp.media({
|
||||
title: <?php echo wp_json_encode( __( 'Select hero figure', 'viator-collector' ) ); ?>,
|
||||
button: { text: <?php echo wp_json_encode( __( 'Use this image', 'viator-collector' ) ); ?> },
|
||||
library: { type: 'image' },
|
||||
multiple: false
|
||||
});
|
||||
frame.on('select', function () {
|
||||
var img = frame.state().get('selection').first().toJSON();
|
||||
$('#vc_single_figure_id').val(img.id);
|
||||
var src = (img.sizes && img.sizes.medium) ? img.sizes.medium.url : img.url;
|
||||
$('#vc-figure-preview').html($('<img>', { src: src, alt: '' }).css({ maxWidth: '200px', height: 'auto' }));
|
||||
});
|
||||
frame.open();
|
||||
});
|
||||
$('#vc-figure-clear').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#vc_single_figure_id').val('');
|
||||
$('#vc-figure-preview').empty();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="vc_hero_slides"><?php echo esc_html__( 'Hero slider images', 'viator-collector' ); ?></label></th>
|
||||
<td>
|
||||
|
||||
@@ -138,6 +138,34 @@ function vc_hero_image_url() {
|
||||
return apply_filters( 'vc_hero_image_url', $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hero image *source* for the current view: an attachment ID when available
|
||||
* (so it can be rendered responsively), otherwise a URL. Resolution order:
|
||||
* 1. On a term archive, that term's own image.
|
||||
* 2. The "Hero image" chosen in Settings (attachment ID preferred).
|
||||
* 3. Nothing.
|
||||
*
|
||||
* @return int|string Attachment ID, image URL, or '' .
|
||||
*/
|
||||
function vc_hero_image_source() {
|
||||
if ( is_tax( array( 'viator_destination', 'viator_tag', 'viator_category' ) ) ) {
|
||||
$term = get_queried_object();
|
||||
if ( $term && ! is_wp_error( $term ) ) {
|
||||
$url = vc_term_image_url( $term->term_id, $term->taxonomy );
|
||||
if ( $url ) {
|
||||
return apply_filters( 'vc_hero_image_source', $url );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$id = (int) vc_get_option( 'hero_image_id', 0 );
|
||||
if ( $id ) {
|
||||
return apply_filters( 'vc_hero_image_source', $id );
|
||||
}
|
||||
|
||||
return apply_filters( 'vc_hero_image_source', (string) vc_get_option( 'hero_image', '' ) );
|
||||
}
|
||||
|
||||
function vc_term_image_url( $term_id, $taxonomy = '' ) {
|
||||
$term_id = absint( $term_id );
|
||||
if ( ! $term_id ) {
|
||||
|
||||
Reference in New Issue
Block a user