post_content, $shortcode ) ) {
return true;
}
}
return false;
}
/**
* Marker class so the full-width CSS can be gated to our pages only.
*/
public static function body_class( $classes ) {
if ( self::is_vc_page() ) {
$classes[] = 'vc-fullwidth';
}
return $classes;
}
/**
* Astra: drop the sidebar on Viator pages.
*/
public static function astra_page_layout( $layout ) {
return self::is_vc_page() ? 'no-sidebar' : $layout;
}
/**
* Astra: use the "page-builder" content layout, which removes the
* .ast-container max-width/padding for a true full-width canvas.
*/
public static function astra_content_layout( $layout ) {
return self::is_vc_page() ? 'page-builder' : $layout;
}
/**
* Lazily fetch and cache full product detail the first time a tour page is
* viewed, so itinerary/inclusions/etc. are available without crawling
* detail for the whole catalogue up front.
*/
public static function maybe_enrich_single() {
if ( is_admin() || ! is_singular( 'viator_tour' ) ) {
return;
}
$post_id = get_queried_object_id();
if ( ! $post_id || get_post_meta( $post_id, '_vc_detail_fetched', true ) ) {
return;
}
$code = get_post_meta( $post_id, '_vc_viator_product_code', true );
if ( ! $code ) {
return;
}
// Avoid repeated calls (and hammering on errors) for the same product.
$lock = 'vc_enrich_' . $post_id;
if ( get_transient( $lock ) ) {
return;
}
set_transient( $lock, 1, 10 * MINUTE_IN_SECONDS );
( new VC_Importer() )->enrich_from_detail( $post_id, $code );
}
public static function enqueue_assets() {
$is_single = is_singular( 'viator_tour' );
$is_archive = is_post_type_archive( 'viator_tour' ) || is_tax( array( 'viator_destination', 'viator_tag', 'viator_category' ) );
$has_short = self::post_has_shortcode();
if ( ! $is_single && ! $is_archive && ! $has_short ) {
return;
}
// Design typeface.
wp_enqueue_style( 'vc-fonts', 'https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;700;800&display=swap', array(), null );
// One scoped stylesheet (.vc-app) covers listing, single, and homepage
// sections — ported from the "Hop On Hop Off" design. Declare every theme
// stylesheet that could fight ours as a dependency (when present) so our
// rules always print AFTER them and win equal-specificity ties. We enqueue
// at priority 100, so by now the theme (priority 20) has registered them.
$deps = array( 'vc-fonts' );
foreach ( array( 'astra-theme-css', 'astra-parent', 'hoho-chrome' ) as $handle ) {
if ( wp_style_is( $handle, 'registered' ) ) {
$deps[] = $handle;
}
}
wp_enqueue_style( 'vc-design', VC_PLUGIN_URL . 'assets/css/vc-design.css', $deps, VC_VERSION );
wp_add_inline_style( 'vc-design', self::astra_overrides() );
if ( $is_single ) {
// Tour page top section + mobile booking bar (after the overrides above).
wp_enqueue_style( 'vc-tour', VC_PLUGIN_URL . 'assets/css/vc-tour.css', array( 'vc-design' ), VC_VERSION );
}
if ( is_tax( 'viator_destination' ) ) {
// City guide: tagline, facts bar, About and Seasons (see VC_City_Guide).
wp_enqueue_style( 'vc-city', VC_PLUGIN_URL . 'assets/css/vc-city.css', array( 'vc-design' ), VC_VERSION );
}
// Front-end interactions: card rails, FAQ accordion, testimonials
// marquee, date field, and auto-submit on quick filter toggles.
wp_enqueue_script( 'vc-design', VC_PLUGIN_URL . 'assets/js/vc-design.js', array(), VC_VERSION, true );
// The SVG icon sprite (+ ring-badge path) is printed once in the footer.
add_action( 'wp_footer', 'vc_print_icon_sprite' );
}
/**
* CSS that neutralises Astra's content spacing/width on Viator pages and
* makes the design typography win over the theme. Injected after vc-design.css.
*/
private static function astra_overrides() {
return implode(
'',
array(
// Full-bleed: strip Astra's page-builder article padding/width so
// 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;}',
// display:block undoes Astra's `.site-content .ast-container{display:flex}`
// (≥922px), which otherwise sizes our content to its contents instead
// of filling the full-width container.
'.vc-fullwidth .site-content .ast-container,.vc-fullwidth .ast-container'
. '{max-width:none!important;padding-left:0!important;padding-right:0!important;display:block!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.vc-app .hero__media,.vc-app.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`). Every selector matches
// the doubled `.vc-app.vc-app` scope of the main stylesheet so these
// patches keep winning against it, not just against the theme.
'.vc-app.vc-app a{text-decoration:none;box-shadow:none;}',
// No !important on margin/padding: the doubled .vc-app.vc-app scope
// already out-specifies Astra's list rules, and !important here was
// steamrolling design lists that need their own spacing — e.g. the
// rail (