feat: Enhance gallery functionality with lightbox feature and improve icon sprite accessibility
- Added a lightbox feature for gallery images, allowing users to view photos in a modal with navigation controls. - Updated the SVG sprite to include inline styles for better accessibility. - Increased CSS specificity by doubling the `.vc-app` class to ensure styles override Astra theme defaults. - Introduced new shortcodes for bus tours, about section, and articles in the VC_Sections class. - Improved the handling of featured images in the single tour template for sharper display. - Updated version number to 1.8.15.
This commit is contained in:
@@ -11,6 +11,9 @@ class VC_Display {
|
||||
'viator_top_destinations',
|
||||
'viator_top_attractions',
|
||||
'viator_browse_activities',
|
||||
'viator_bus_tours',
|
||||
'viator_about',
|
||||
'viator_articles',
|
||||
'viator_faq',
|
||||
'viator_testimonials',
|
||||
'viator_tour_card',
|
||||
@@ -24,9 +27,12 @@ class VC_Display {
|
||||
);
|
||||
|
||||
public static function init() {
|
||||
// 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 );
|
||||
// Priority 100 so the theme (Astra + child) has already enqueued ALL of
|
||||
// its stylesheets — including the parent style.css and the child's chrome
|
||||
// CSS, which are enqueued by the theme at priority 20 and would otherwise
|
||||
// print AFTER us (plugins run before themes at equal priority) and win
|
||||
// equal-specificity ties. Loading last makes our design win those ties.
|
||||
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ), 100 );
|
||||
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' ) );
|
||||
@@ -146,11 +152,15 @@ class VC_Display {
|
||||
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. Depend on the Astra
|
||||
// theme stylesheet (when present) so our rules load AFTER it and win ties.
|
||||
// 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' );
|
||||
if ( wp_style_is( 'astra-theme-css', 'registered' ) ) {
|
||||
$deps[] = 'astra-theme-css';
|
||||
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() );
|
||||
@@ -180,42 +190,89 @@ class VC_Display {
|
||||
. '.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;}',
|
||||
. '{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 .hero__media,.vc-app .hero-tour{background:linear-gradient(120deg,#101317,#2cafc2);}',
|
||||
'.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`). 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;}',
|
||||
// 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 (<ul class="rail">) whose vertical padding gives the hover glow
|
||||
// room. list-style stays !important (purely cosmetic; prose lists
|
||||
// re-enable bullets via a higher-specificity rule).
|
||||
'.vc-app.vc-app :is(ul,ol){list-style:none!important;margin:0;padding:0;}',
|
||||
'.vc-app.vc-app li{margin:0;}',
|
||||
// …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;}',
|
||||
'.vc-app.vc-app .tour__text a{text-decoration:underline;}',
|
||||
'.vc-app.vc-app .tour__text ul{list-style:disc!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app.vc-app .tour__text ol{list-style:decimal!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app.vc-app .tour__text li{margin:.25em 0!important;}',
|
||||
// Links must inherit the design colour (Astra colours links with its
|
||||
// theme colour, which turned white card titles/destination names into
|
||||
// coloured text). Buttons keep their own colour.
|
||||
'.vc-app a:not(.btn){color:inherit;}',
|
||||
'.vc-app .tour__text a{color:var(--c-teal);}',
|
||||
// …but not ring-badges: they set their own colour on the <a>, and this
|
||||
// inherit rule out-specifies .ring-badge--top/--scroll, which turned
|
||||
// the badge text white (inherited) on light backgrounds.
|
||||
'.vc-app.vc-app a:not(.btn):not(.ring-badge){color:inherit;}',
|
||||
'.vc-app.vc-app .tour__text a{color:var(--c-teal);}',
|
||||
// Real tour titles are far longer than the mockup\'s city names — clamp
|
||||
// them and stop price/duration values wrapping mid-number.
|
||||
'.vc-app .dcard__title{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app .titem__desc{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app .dcard__meta .meta__v{white-space:nowrap;}',
|
||||
'.vc-app.vc-app .dcard__title{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app.vc-app .titem__desc{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
// Real tour descriptions run long; the on-hover card blurb clamps to
|
||||
// three lines with an ellipsis instead of spilling over the card.
|
||||
'.vc-app.vc-app .dcard__desc p{display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app.vc-app .dcard__meta .meta__v{white-space:nowrap;}',
|
||||
// Astra's page-builder layout centres/limits every entry-content child
|
||||
// (max-width:none; margin:auto), which flattened the hero inset. Our
|
||||
// sections are full-bleed, so re-assert 0 side-margins; !important
|
||||
// beats Astra's 4-class selector. The hero keeps its small inset — the
|
||||
// only section with an intentional side margin.
|
||||
'.vc-app.vc-app>*{max-width:none!important;margin-inline:0!important;}',
|
||||
// Heroes keep their small inset (rounded card look) — .hero on the
|
||||
// home/archive pages and .hero-tour on the single-tour page. Without
|
||||
// this they went full-bleed after the margin-inline:0 reset above.
|
||||
'.vc-app.vc-app>.hero,.vc-app.vc-app>.hero-tour{margin:var(--page-inset)!important;}',
|
||||
// Let vertical decorations (rotating badges, card glow, promo bus, FAQ
|
||||
// watermark) escape, but keep the horizontal axis clipped so a
|
||||
// full-bleed section or decorative element can't cause a mobile
|
||||
// sideways scroll. overflow-x:clip (unlike hidden) does NOT force
|
||||
// overflow-y to auto, so the y-axis stays truly visible.
|
||||
'.vc-app.vc-app{overflow-x:clip;overflow-y:visible;}',
|
||||
// Astra wraps the content in article/.entry-content/.ast-container,
|
||||
// some of which clip overflow — that cut the card hover glow at the
|
||||
// rail edges. Open them up on our pages only (page scroll stays
|
||||
// contained by body.vc-fullwidth{overflow-x:hidden}).
|
||||
'.vc-fullwidth .site-main,.vc-fullwidth #primary,.vc-fullwidth #content,'
|
||||
. '.vc-fullwidth .site-content,.vc-fullwidth .ast-container,'
|
||||
. '.vc-fullwidth .entry-content,.vc-fullwidth .site-main>article'
|
||||
. '{overflow:visible!important;}',
|
||||
// FAQ question buttons must stay flat — Astra gives <button> a shadow.
|
||||
'.vc-app.vc-app .acc__q{box-shadow:none!important;}',
|
||||
// Astra colours every heading on the element itself (h1–h6 and
|
||||
// .entry-content :where(h1..h6)), beating our container-inherited
|
||||
// colours. Make headings inherit again — no !important, so design
|
||||
// classes (.hero__title, the white .tcard titles) keep their own.
|
||||
'.vc-app.vc-app :is(h1,h2,h3,h4,h5,h6){color:inherit;}',
|
||||
// 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,'
|
||||
. '.vc-app select,.vc-app textarea,.vc-app blockquote'
|
||||
'.vc-app.vc-app,.vc-app.vc-app h1,.vc-app.vc-app h2,.vc-app.vc-app h3,.vc-app.vc-app h4,.vc-app.vc-app h5,.vc-app.vc-app h6,'
|
||||
. '.vc-app.vc-app p,.vc-app.vc-app a,.vc-app.vc-app span,.vc-app.vc-app li,.vc-app.vc-app button,.vc-app.vc-app input,'
|
||||
. '.vc-app.vc-app select,.vc-app.vc-app textarea,.vc-app.vc-app blockquote'
|
||||
. '{font-family:"Plus Jakarta Sans",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}',
|
||||
)
|
||||
);
|
||||
|
||||
@@ -15,6 +15,9 @@ class VC_Sections {
|
||||
const TOP_ATTRS = 'viator_top_attractions';
|
||||
const ACTIVITIES = 'viator_browse_activities';
|
||||
const TOP_TOURS = 'viator_top_tours';
|
||||
const BUS_TOURS = 'viator_bus_tours';
|
||||
const ABOUT = 'viator_about';
|
||||
const ARTICLES = 'viator_articles';
|
||||
const FAQ = 'viator_faq';
|
||||
const REVIEWS = 'viator_testimonials';
|
||||
|
||||
@@ -28,6 +31,9 @@ class VC_Sections {
|
||||
add_shortcode( self::TOP_ATTRS, array( __CLASS__, 'top_attractions' ) );
|
||||
add_shortcode( self::ACTIVITIES, array( __CLASS__, 'browse_activities' ) );
|
||||
add_shortcode( self::TOP_TOURS, array( __CLASS__, 'top_tours' ) );
|
||||
add_shortcode( self::BUS_TOURS, array( __CLASS__, 'bus_tours' ) );
|
||||
add_shortcode( self::ABOUT, array( __CLASS__, 'about' ) );
|
||||
add_shortcode( self::ARTICLES, array( __CLASS__, 'articles' ) );
|
||||
add_shortcode( self::FAQ, array( __CLASS__, 'faq' ) );
|
||||
add_shortcode( self::REVIEWS, array( __CLASS__, 'testimonials' ) );
|
||||
}
|
||||
@@ -130,7 +136,7 @@ class VC_Sections {
|
||||
* Open a titled rail section and return its unique rail id. Caller fills the
|
||||
* <ul> with cards and then calls close_rail().
|
||||
*/
|
||||
private static function open_rail( $title, $lead, $view_all_url, $view_all_text ) {
|
||||
private static function open_rail( $title, $lead, $view_all_url, $view_all_text, $rail_class = 'rail--dest' ) {
|
||||
$id = 'vcRail' . ( ++self::$rail_seq );
|
||||
ob_start();
|
||||
?>
|
||||
@@ -150,7 +156,7 @@ class VC_Sections {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="rail rail--dest" id="<?php echo esc_attr( $id ); ?>">
|
||||
<ul class="rail <?php echo esc_attr( $rail_class ); ?>" id="<?php echo esc_attr( $id ); ?>">
|
||||
<?php
|
||||
return array( 'id' => $id, 'head' => ob_get_clean() );
|
||||
}
|
||||
@@ -450,6 +456,299 @@ class VC_Sections {
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* [viator_bus_tours limit="4" tag="hop-on-hop-off" image_id="123"]
|
||||
*
|
||||
* The "Open Top Bus Tours…" promo block + a 4-up grid of tour cards. Backed
|
||||
* by the same curated/filtered tour query as the rails. The bus illustration
|
||||
* is a heavy image, so it comes from the Media Library via image_id (or a
|
||||
* direct image URL) — nothing is bundled with the plugin.
|
||||
*/
|
||||
public static function bus_tours( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'ids' => '',
|
||||
'codes' => '',
|
||||
'destination' => '',
|
||||
'tag' => '',
|
||||
'limit' => 4,
|
||||
'orderby' => 'rating',
|
||||
'title' => __( 'Open Top<br>Bus Tours…', 'viator-collector' ),
|
||||
'note' => __( 'An open-top hop on hop off bus is the best way to discover the historic and cultural sights of any city.', 'viator-collector' ),
|
||||
'chip' => __( 'Very Fun!', 'viator-collector' ),
|
||||
'image_id' => '', // WordPress media attachment ID for the bus illustration.
|
||||
'image' => '', // …or a direct image URL.
|
||||
),
|
||||
$atts,
|
||||
self::BUS_TOURS
|
||||
);
|
||||
|
||||
$query = self::tour_query( $atts );
|
||||
if ( ! $query->have_posts() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$archive = get_post_type_archive_link( 'viator_tour' );
|
||||
$archive = $archive ? $archive : home_url( '/' );
|
||||
|
||||
$bus_img = '';
|
||||
if ( '' !== $atts['image_id'] ) {
|
||||
// 'full' (uncropped original) — some sites register a hard-cropped
|
||||
// square 'large', which slices decorative illustrations in half.
|
||||
$bus_img = wp_get_attachment_image( absint( $atts['image_id'] ), 'full', false, array( 'alt' => '', 'loading' => 'lazy' ) );
|
||||
} elseif ( '' !== $atts['image'] ) {
|
||||
$bus_img = '<img src="' . esc_url( $atts['image'] ) . '" alt="" loading="lazy">';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="vc-app vc-sections">
|
||||
<section class="section section--tight">
|
||||
<div class="container bus">
|
||||
<div class="promo">
|
||||
<svg class="promo__route" viewBox="0 0 416 526" fill="none" aria-hidden="true" preserveAspectRatio="none">
|
||||
<path d="M-20 470 C 90 470, 60 350, 150 330 S 300 300, 300 210 S 210 120, 300 60 S 430 40, 440 -10" stroke="#2CAFC2" stroke-dasharray="3 3" />
|
||||
</svg>
|
||||
<div class="promo__head">
|
||||
<h2 class="promo__title"><?php echo wp_kses( $atts['title'], array( 'br' => array() ) ); ?></h2>
|
||||
<a class="btn btn--white" href="<?php echo esc_url( $archive ); ?>"><span><?php echo esc_html__( 'See all', 'viator-collector' ); ?></span><?php echo vc_icon( 'arrow-ne' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
|
||||
</div>
|
||||
<?php if ( $atts['chip'] ) : ?><span class="chip chip--accent promo__chip"><?php echo esc_html( $atts['chip'] ); ?></span><?php endif; ?>
|
||||
<?php if ( $bus_img ) : ?><div class="promo__bus" aria-hidden="true"><?php echo $bus_img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div><?php endif; ?>
|
||||
<?php if ( $atts['note'] ) : ?><p class="promo__note"><?php echo esc_html( $atts['note'] ); ?></p><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<ul class="bus__grid">
|
||||
<?php
|
||||
$i = 0;
|
||||
while ( $query->have_posts() ) :
|
||||
$query->the_post();
|
||||
++$i;
|
||||
$pid = get_the_ID();
|
||||
$price = get_post_meta( $pid, '_vc_price_from', true );
|
||||
$currency = get_post_meta( $pid, '_vc_currency', true );
|
||||
$terms = get_the_terms( $pid, 'viator_destination' );
|
||||
$kicker = ( $terms && ! is_wp_error( $terms ) ) ? $terms[0]->name : '';
|
||||
$active = ( 1 === $i );
|
||||
$badge = vc_ring_badge( 'BOOK NOW · BOOK NOW · ', get_permalink( $pid ), 'card' );
|
||||
if ( $active ) {
|
||||
$badge = str_replace( 'ring-badge--card', 'ring-badge--card ring-badge--accent', $badge );
|
||||
}
|
||||
?>
|
||||
<li class="tcard<?php echo $active ? ' is-active' : ''; ?>">
|
||||
<?php echo vc_tour_image_html( $pid, 'tcard__img' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div class="tcard__top">
|
||||
<?php if ( $kicker ) : ?><span class="tcard__k"><?php echo esc_html( $kicker ); ?></span><?php endif; ?>
|
||||
<h3 class="tcard__title"><a href="<?php echo esc_url( get_permalink( $pid ) ); ?>"><?php echo esc_html( get_the_title( $pid ) ); ?></a></h3>
|
||||
</div>
|
||||
<div class="tcard__bottom">
|
||||
<?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 echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php
|
||||
wp_reset_postdata();
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* [viator_about image_id="123" title="Hop On Hop Off" highlight="Memorable"
|
||||
* stats="25+|Years Experience;;500+|Tours Conducted"]
|
||||
*
|
||||
* A static "About" marketing block (headline + chip, paragraph, stat counters
|
||||
* and a decorative bus). All copy is editable via attributes; the isometric
|
||||
* bus is a heavy image supplied from the Media Library (image_id / image).
|
||||
*/
|
||||
public static function about( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'title' => __( 'Hop On Hop Off', 'viator-collector' ),
|
||||
'highlight' => __( 'Memorable', 'viator-collector' ),
|
||||
'text' => __( 'Discover cities the memorable way — hop on and off our open-top buses at every major sight and explore each place at your own pace, with expert local commentary along the way.', 'viator-collector' ),
|
||||
'button_text' => __( 'About us', 'viator-collector' ),
|
||||
'button_url' => '',
|
||||
'image_id' => '',
|
||||
'image' => '',
|
||||
'stats' => '', // "25+|Years Experience;;500+|Tours Conducted;;430+|Tours this Year;;15+|Different Tours"
|
||||
),
|
||||
$atts,
|
||||
self::ABOUT
|
||||
);
|
||||
|
||||
$stats = array();
|
||||
if ( '' !== $atts['stats'] ) {
|
||||
foreach ( explode( ';;', $atts['stats'] ) as $pair ) {
|
||||
$p = array_map( 'trim', explode( '|', $pair, 2 ) );
|
||||
if ( '' !== $p[0] && isset( $p[1] ) ) {
|
||||
$stats[] = array( 'num' => $p[0], 'label' => $p[1] );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( empty( $stats ) ) {
|
||||
$stats = array(
|
||||
array( 'num' => '25+', 'label' => __( 'Years Experience', 'viator-collector' ) ),
|
||||
array( 'num' => '500+', 'label' => __( 'Tours Conducted', 'viator-collector' ) ),
|
||||
array( 'num' => '430+', 'label' => __( 'Tours this Year', 'viator-collector' ) ),
|
||||
array( 'num' => '15+', 'label' => __( 'Different Tours', 'viator-collector' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
$button_url = $atts['button_url'];
|
||||
if ( '' === $button_url ) {
|
||||
$button_url = get_post_type_archive_link( 'viator_tour' );
|
||||
$button_url = $button_url ? $button_url : home_url( '/' );
|
||||
}
|
||||
|
||||
$bus_img = '';
|
||||
if ( '' !== $atts['image_id'] ) {
|
||||
// 'full' (uncropped original) — some sites register a hard-cropped
|
||||
// square 'large', which slices decorative illustrations in half.
|
||||
$bus_img = wp_get_attachment_image( absint( $atts['image_id'] ), 'full', false, array( 'alt' => '', 'loading' => 'lazy' ) );
|
||||
} elseif ( '' !== $atts['image'] ) {
|
||||
$bus_img = '<img src="' . esc_url( $atts['image'] ) . '" alt="" loading="lazy">';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="vc-app vc-sections">
|
||||
<section class="section">
|
||||
<div class="container about">
|
||||
<div class="about__art" aria-hidden="true">
|
||||
<span class="about__glow"></span>
|
||||
<span class="about__trail" aria-hidden="true"></span>
|
||||
<?php if ( $bus_img ) : ?><span class="about__bus"><?php echo $bus_img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span><?php endif; ?>
|
||||
<svg class="about__arcs" viewBox="0 0 480 440" fill="none" aria-hidden="true">
|
||||
<defs>
|
||||
<path id="busArc" pathLength="100" d="M20 336.6V221.6C20 96.6 170 23.4 170 148.4V263.4" />
|
||||
</defs>
|
||||
<g stroke="#FFCC00" stroke-width="9" stroke-linecap="round" fill="none" transform="translate(16 24)">
|
||||
<use href="#busArc" opacity="0.1" stroke-dasharray="62 100" />
|
||||
<use href="#busArc" opacity="0.25" stroke-dasharray="69 100" transform="translate(48 22)" />
|
||||
<use href="#busArc" opacity="0.45" stroke-dasharray="76 100" transform="translate(96 44)" />
|
||||
<use href="#busArc" opacity="0.8" stroke-dasharray="83 100" transform="translate(144 66)" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="about__body">
|
||||
<div class="about__head">
|
||||
<h2 class="about__title"><?php echo esc_html( $atts['title'] ); ?><?php if ( $atts['highlight'] ) : ?><span class="chip chip--accent about__chip"><?php echo esc_html( $atts['highlight'] ); ?></span><?php endif; ?></h2>
|
||||
<a class="btn btn--dark" href="<?php echo esc_url( $button_url ); ?>"><span><?php echo esc_html( $atts['button_text'] ); ?></span><?php echo vc_icon( 'arrow-right' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
|
||||
</div>
|
||||
|
||||
<?php if ( $atts['text'] ) : ?><p class="about__text"><?php echo esc_html( $atts['text'] ); ?></p><?php endif; ?>
|
||||
|
||||
<dl class="stats">
|
||||
<?php
|
||||
foreach ( $stats as $s ) :
|
||||
$num = $s['num'];
|
||||
$suffix = '';
|
||||
if ( preg_match( '/^(.*?)(\+)$/', $num, $m ) ) {
|
||||
$num = $m[1];
|
||||
$suffix = $m[2];
|
||||
}
|
||||
?>
|
||||
<div class="stat">
|
||||
<dt class="stat__num"><?php echo esc_html( $num ); ?><?php if ( $suffix ) : ?><span><?php echo esc_html( $suffix ); ?></span><?php endif; ?></dt>
|
||||
<dd class="stat__label"><?php echo esc_html( $s['label'] ); ?></dd>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* [viator_articles limit="8" category="news"]
|
||||
*
|
||||
* A rail of the latest blog posts, styled as the design's "Latest Articles"
|
||||
* cards. Uses each post's featured image, time and excerpt; links to the post.
|
||||
*/
|
||||
public static function articles( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'title' => __( 'Latest<br>Articles', 'viator-collector' ),
|
||||
'lead' => __( 'News, tips and stories from our tours.', 'viator-collector' ),
|
||||
'limit' => 8,
|
||||
'category' => '',
|
||||
'view_all' => '',
|
||||
),
|
||||
$atts,
|
||||
self::ARTICLES
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => max( 1, absint( $atts['limit'] ) ),
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
);
|
||||
if ( '' !== $atts['category'] ) {
|
||||
$args['category_name'] = sanitize_title( $atts['category'] );
|
||||
}
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
if ( ! $query->have_posts() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$view_all = $atts['view_all'];
|
||||
if ( '' === $view_all ) {
|
||||
$posts_page = (int) get_option( 'page_for_posts' );
|
||||
$view_all = $posts_page ? get_permalink( $posts_page ) : home_url( '/' );
|
||||
}
|
||||
|
||||
$rail = self::open_rail( $atts['title'], $atts['lead'], $view_all, __( 'See all', 'viator-collector' ), 'rail--art' );
|
||||
|
||||
ob_start();
|
||||
echo $rail['head']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$i = 0;
|
||||
while ( $query->have_posts() ) :
|
||||
$query->the_post();
|
||||
++$i;
|
||||
$pid = get_the_ID();
|
||||
$active = ( 1 === $i );
|
||||
$img = has_post_thumbnail( $pid ) ? get_the_post_thumbnail( $pid, 'large', array( 'class' => 'acard__img', 'loading' => 'lazy', 'alt' => '' ) ) : '';
|
||||
$time = human_time_diff( (int) get_post_time( 'U', true, $pid ), (int) current_time( 'timestamp', true ) );
|
||||
$excerpt = get_the_excerpt( $pid );
|
||||
$badge = vc_ring_badge( 'READ MORE · READ MORE · ', get_permalink( $pid ), 'card' );
|
||||
if ( $active ) {
|
||||
$badge = str_replace( 'ring-badge--card', 'ring-badge--card ring-badge--accent', $badge );
|
||||
}
|
||||
?>
|
||||
<li class="acard<?php echo $active ? ' is-active' : ''; ?>">
|
||||
<div class="acard__media">
|
||||
<?php echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<span class="chip chip--glass acard__time"><?php printf( /* translators: %s: human-readable time difference, e.g. "3 hours". */ esc_html__( '%s ago', 'viator-collector' ), esc_html( $time ) ); ?></span>
|
||||
<div class="acard__overlay">
|
||||
<h3 class="acard__title"><a href="<?php echo esc_url( get_permalink( $pid ) ); ?>"><?php echo esc_html( get_the_title( $pid ) ); ?></a></h3>
|
||||
<?php echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ( $excerpt ) : ?><p class="acard__excerpt"><?php echo esc_html( $excerpt ); ?></p><?php endif; ?>
|
||||
</li>
|
||||
<?php
|
||||
endwhile;
|
||||
echo self::close_rail(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
wp_reset_postdata();
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared tour query for the landing sections. Supports curation (ids /
|
||||
* product codes) and filtering (destination / tag), with a sane fallback.
|
||||
|
||||
@@ -595,7 +595,10 @@ function vc_print_icon_sprite() {
|
||||
* e.g. vc_icon( 'star', 'rating__star' ) => <svg class="icon rating__star">…</svg>
|
||||
*/
|
||||
function vc_icon( $name, $extra_class = '' ) {
|
||||
$name = preg_replace( '/[^a-z0-9\-]/', '', 'i-' . ltrim( (string) $name, 'i-' ) );
|
||||
// Strip an existing "i-" prefix (once), then re-add it. ltrim() with a
|
||||
// charlist would eat any leading i/-, turning "instagram" into "nstagram".
|
||||
$slug = preg_replace( '/^i-/', '', (string) $name );
|
||||
$name = preg_replace( '/[^a-z0-9\-]/', '', 'i-' . $slug );
|
||||
$class = 'icon' . ( $extra_class ? ' ' . $extra_class : '' );
|
||||
return '<svg class="' . esc_attr( $class ) . '" aria-hidden="true"><use href="#' . esc_attr( $name ) . '"></use></svg>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user