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. Depend on the Astra // theme stylesheet (when present) so our rules load AFTER it and win ties. $deps = array( 'vc-fonts' ); if ( wp_style_is( 'astra-theme-css', 'registered' ) ) { $deps[] = 'astra-theme-css'; } 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() ); // 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;}', '.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;}', // 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);}', // 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;}', // 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' . '{font-family:"Plus Jakarta Sans",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}', ) ); } /** * Read + sanitize the current filter state from the query string. Shared by * the query builder and the archive template so both stay in sync. */ public static function get_filter_state() { $slug_list = function ( $key ) { $raw = isset( $_GET[ $key ] ) ? wp_unslash( $_GET[ $key ] ) : array(); // phpcs:ignore WordPress.Security.NonceVerification $raw = is_array( $raw ) ? $raw : array( $raw ); return array_values( array_filter( array_map( 'sanitize_title', $raw ) ) ); }; $pmin = isset( $_GET['pmin'] ) ? max( 0, absint( $_GET['pmin'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification $pmax = isset( $_GET['pmax'] ) ? absint( $_GET['pmax'] ) : 1000; // phpcs:ignore WordPress.Security.NonceVerification // A coarse "price" range select (value like "50-150") maps to pmin/pmax. if ( isset( $_GET['price'] ) && '' !== $_GET['price'] ) { // phpcs:ignore WordPress.Security.NonceVerification $parts = array_pad( explode( '-', sanitize_text_field( wp_unslash( $_GET['price'] ) ) ), 2, '' ); // phpcs:ignore WordPress.Security.NonceVerification $pmin = absint( $parts[0] ); $pmax = ( '' === $parts[1] ) ? 1000 : absint( $parts[1] ); } return array( 'q' => isset( $_GET['vc_q'] ) ? sanitize_text_field( wp_unslash( $_GET['vc_q'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification 'dest' => $slug_list( 'dest' ), 'cat' => $slug_list( 'cat' ), 'dur' => array_values( array_intersect( array( 'half', 'full', 'multi' ), $slug_list( 'dur' ) ) ), 'pmin' => $pmin, 'pmax' => $pmax, 'rating' => isset( $_GET['rating'] ) ? floatval( $_GET['rating'] ) : 0, // phpcs:ignore WordPress.Security.NonceVerification 'freecancel' => ! empty( $_GET['freecancel'] ) ? 1 : 0, // phpcs:ignore WordPress.Security.NonceVerification 'bestdeal' => ! empty( $_GET['bestdeal'] ) ? 1 : 0, // phpcs:ignore WordPress.Security.NonceVerification 'sellout' => ! empty( $_GET['sellout'] ) ? 1 : 0, // phpcs:ignore WordPress.Security.NonceVerification 'sort' => isset( $_GET['sort'] ) ? sanitize_key( $_GET['sort'] ) : 'newest', // phpcs:ignore WordPress.Security.NonceVerification ); } /** * Apply the filter state to the main tours query (archive + taxonomy pages). */ public static function filter_archive_query( $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } $is_archive = $query->is_post_type_archive( 'viator_tour' ); $is_tax = $query->is_tax( array( 'viator_destination', 'viator_tag', 'viator_category' ) ); if ( ! $is_archive && ! $is_tax ) { return; } $base_tax = array(); foreach ( (array) $query->get( 'tax_query' ) as $k => $clause ) { if ( 'relation' !== $k && is_array( $clause ) ) { $base_tax[] = $clause; } } foreach ( self::build_query_vars( $base_tax ) as $key => $value ) { $query->set( $key, $value ); } } /** * Build WP_Query vars from the current filter state (query string) plus any * locked base tax constraints. Shared by the archive query and the * [viator_listing] shortcode so both filter/sort identically. */ public static function build_query_vars( array $base_tax = array() ) { $f = self::get_filter_state(); $vars = array( 'posts_per_page' => 12, 'ignore_sticky_posts' => true ); if ( '' !== $f['q'] ) { $vars['s'] = $f['q']; } $tax_query = array(); foreach ( $base_tax as $clause ) { if ( is_array( $clause ) ) { $tax_query[] = $clause; } } if ( ! empty( $f['dest'] ) ) { $tax_query[] = array( 'taxonomy' => 'viator_destination', 'field' => 'slug', 'terms' => $f['dest'] ); } if ( ! empty( $f['cat'] ) ) { $tax_query[] = array( 'taxonomy' => 'viator_tag', 'field' => 'slug', 'terms' => $f['cat'] ); } if ( count( $tax_query ) > 1 ) { $tax_query['relation'] = 'AND'; } if ( ! empty( $tax_query ) ) { $vars['tax_query'] = $tax_query; } $meta_query = array(); if ( $f['pmin'] > 0 || $f['pmax'] < 1000 ) { $meta_query[] = array( 'key' => '_vc_price_from', 'value' => array( $f['pmin'], $f['pmax'] ), 'type' => 'DECIMAL', 'compare' => 'BETWEEN' ); } if ( ! empty( $f['dur'] ) ) { $dur = array( 'relation' => 'OR' ); foreach ( $f['dur'] as $bucket ) { if ( 'half' === $bucket ) { $dur[] = array( 'key' => '_vc_duration_minutes', 'value' => array( 1, 300 ), 'type' => 'NUMERIC', 'compare' => 'BETWEEN' ); } elseif ( 'full' === $bucket ) { $dur[] = array( 'key' => '_vc_duration_minutes', 'value' => array( 301, 1439 ), 'type' => 'NUMERIC', 'compare' => 'BETWEEN' ); } elseif ( 'multi' === $bucket ) { $dur[] = array( 'key' => '_vc_duration_minutes', 'value' => 1440, 'type' => 'NUMERIC', 'compare' => '>=' ); } } if ( count( $dur ) > 1 ) { $meta_query[] = $dur; } } if ( $f['rating'] > 0 ) { $meta_query[] = array( 'key' => '_vc_rating', 'value' => $f['rating'], 'type' => 'DECIMAL', 'compare' => '>=' ); } if ( $f['freecancel'] ) { $meta_query[] = array( 'key' => '_vc_free_cancellation', 'value' => '1' ); } // _vc_flags is a JSON array, so match the flag name inside it. if ( $f['bestdeal'] ) { $meta_query[] = array( 'key' => '_vc_flags', 'value' => 'SPECIAL_OFFER', 'compare' => 'LIKE' ); } if ( $f['sellout'] ) { $meta_query[] = array( 'key' => '_vc_flags', 'value' => 'LIKELY_TO_SELL_OUT', 'compare' => 'LIKE' ); } // Sort. Performance: the default (newest) orders by post date only — no // postmeta JOIN and no featured/review tiebreak — so the unfiltered // archive stays fast even with a very large catalogue (tens of thousands // of tours). Price/rating sorts add a single meta_key JOIN, on demand. switch ( $f['sort'] ) { case 'price_asc': $vars['meta_key'] = '_vc_price_from'; $vars['orderby'] = 'meta_value_num'; $vars['order'] = 'ASC'; break; case 'price_desc': $vars['meta_key'] = '_vc_price_from'; $vars['orderby'] = 'meta_value_num'; $vars['order'] = 'DESC'; break; case 'rating': $vars['meta_key'] = '_vc_rating'; $vars['orderby'] = 'meta_value_num'; $vars['order'] = 'DESC'; break; default: $vars['orderby'] = 'date'; $vars['order'] = 'DESC'; } // Only attach a meta_query when a filter is actually active, so the plain // archive query carries no postmeta joins at all. if ( ! empty( $meta_query ) ) { if ( count( $meta_query ) > 1 ) { $meta_query['relation'] = 'AND'; } $vars['meta_query'] = $meta_query; } return $vars; } /** * [viator_listing tag="airport-hotel-transfers,private-transfers" title="Transportation"] * * Full listing UI (filters, sort, pagination) locked to a tag/destination. * Use on a normal WordPress Page to build e.g. a "Transportation" page. */ public static function shortcode_listing( $atts ) { $atts = shortcode_atts( array( 'tag' => '', 'destination' => '', 'title' => __( 'Tours', 'viator-collector' ), 'subtitle' => '', ), $atts, 'viator_listing' ); $base_tax = array(); if ( '' !== $atts['tag'] ) { $base_tax[] = array( 'taxonomy' => 'viator_tag', 'field' => 'slug', 'terms' => array_filter( array_map( 'sanitize_title', explode( ',', $atts['tag'] ) ) ) ); } if ( '' !== $atts['destination'] ) { $base_tax[] = array( 'taxonomy' => 'viator_destination', 'field' => 'slug', 'terms' => array_filter( array_map( 'sanitize_title', explode( ',', $atts['destination'] ) ) ) ); } $vars = self::build_query_vars( $base_tax ); $vars['post_type'] = 'viator_tour'; $vars['post_status'] = 'publish'; $vars['paged'] = max( 1, (int) get_query_var( 'paged' ), (int) get_query_var( 'page' ), isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1 ); // phpcs:ignore WordPress.Security.NonceVerification $query = new WP_Query( $vars ); $inner = vc_get_template( 'listing.php', array( 'vc_title' => $atts['title'], 'vc_subtitle' => $atts['subtitle'], 'vc_query' => $query, 'vc_form_action' => get_permalink(), ) ); // .vc-app is the scope every rule in vc-design.css hangs off. return '
' . $inner . '
'; } private static function post_has_shortcode() { if ( ! is_singular() ) { return false; } $post = get_post(); if ( ! $post ) { return false; } foreach ( self::SHORTCODES as $shortcode ) { if ( has_shortcode( $post->post_content, $shortcode ) ) { return true; } } return false; } public static function single_template( $template ) { if ( is_singular( 'viator_tour' ) ) { $path = get_stylesheet_directory() . '/viator-collector/single-viator_tour.php'; if ( file_exists( $path ) ) { return $path; } $plugin = VC_PLUGIN_DIR . 'templates/single-viator_tour.php'; if ( file_exists( $plugin ) ) { return $plugin; } } return $template; } public static function archive_template( $template ) { if ( is_post_type_archive( 'viator_tour' ) ) { $path = get_stylesheet_directory() . '/viator-collector/archive-viator_tour.php'; if ( file_exists( $path ) ) { return $path; } $plugin = VC_PLUGIN_DIR . 'templates/archive-viator_tour.php'; if ( file_exists( $plugin ) ) { return $plugin; } } return $template; } public static function taxonomy_template( $template ) { if ( is_tax( array( 'viator_destination', 'viator_tag', 'viator_category' ) ) ) { $path = get_stylesheet_directory() . '/viator-collector/taxonomy-viator.php'; if ( file_exists( $path ) ) { return $path; } $plugin = VC_PLUGIN_DIR . 'templates/taxonomy-viator.php'; if ( file_exists( $plugin ) ) { return $plugin; } } return $template; } public static function shortcode_tours( $atts ) { $atts = shortcode_atts( array( 'limit' => 12, 'destination' => '', 'tag' => '', 'category' => '', 'orderby' => 'date', 'order' => 'DESC', ), $atts, 'viator_tours' ); $primary_orderby = sanitize_key( $atts['orderby'] ); $primary_order = 'ASC' === strtoupper( $atts['order'] ) ? 'ASC' : 'DESC'; $args = array( 'post_type' => 'viator_tour', 'post_status' => 'publish', 'posts_per_page' => max( 1, absint( $atts['limit'] ) ), ); if ( 'rand' === $primary_orderby ) { $args['orderby'] = 'rand'; } else { $args['meta_query'] = array( 'vc_reviews_sort' => array( 'key' => '_vc_review_count', 'type' => 'NUMERIC', 'compare' => 'EXISTS', ), ); $args['orderby'] = array( $primary_orderby => $primary_order, 'vc_reviews_sort' => 'DESC', ); if ( 'date' !== $primary_orderby ) { $args['orderby']['date'] = 'DESC'; } } $tax_query = array(); $tax_map = array( 'destination' => 'viator_destination', 'tag' => 'viator_tag', 'category' => 'viator_category', ); foreach ( $tax_map as $att => $taxonomy ) { if ( ! empty( $atts[ $att ] ) ) { $tax_query[] = array( 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => array_map( 'sanitize_title', array_map( 'trim', explode( ',', $atts[ $att ] ) ) ), ); } } if ( $tax_query ) { $args['tax_query'] = $tax_query; } return self::render_tour_grid( $args ); } /** * "Top Tours" section: a titled grid of tours, highest rated first. */ public static function shortcode_top_tours( $atts ) { $atts = shortcode_atts( array( 'limit' => 8, 'title' => __( 'Top Tours', 'viator-collector' ), 'eyebrow' => __( 'Hand-picked', 'viator-collector' ), 'view_all' => '', 'orderby' => 'rating', ), $atts, 'viator_top_tours' ); $args = array( 'post_type' => 'viator_tour', 'post_status' => 'publish', 'posts_per_page' => max( 1, absint( $atts['limit'] ) ), ); if ( 'rating' === $atts['orderby'] ) { $args['meta_key'] = '_vc_rating'; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; } else { $args['orderby'] = 'date'; $args['order'] = 'DESC'; } $grid = self::render_tour_grid( $args ); if ( '' === $grid ) { return ''; } return self::render_section( 'vc-section--tours', $atts['eyebrow'], $atts['title'], $atts['view_all'], $grid ); } /** * "Top Destinations" section: a grid of destination term cards. */ public static function shortcode_top_destinations( $atts ) { $atts = shortcode_atts( array( 'count' => 8, 'ids' => '', 'title' => __( 'Top Destinations', 'viator-collector' ), 'eyebrow' => __( 'Explore', 'viator-collector' ), 'view_all' => '', ), $atts, 'viator_top_destinations' ); $terms = self::get_section_terms( 'viator_destination', $atts['count'], $atts['ids'] ); if ( empty( $terms ) ) { return ''; } ob_start(); echo '
'; foreach ( $terms as $term ) { echo vc_get_template( 'card-destination.php', array( 'term' => $term ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } echo '
'; $grid = ob_get_clean(); return self::render_section( 'vc-section--destinations', $atts['eyebrow'], $atts['title'], $atts['view_all'], $grid ); } /** * "Top Attractions" section. Backed by top-rated tours rendered as large * feature cards (repoint to a dedicated attraction taxonomy once ingested). */ public static function shortcode_top_attractions( $atts ) { $atts = shortcode_atts( array( 'limit' => 3, 'title' => __( 'Top Attractions', 'viator-collector' ), 'eyebrow' => __( 'Must see', 'viator-collector' ), 'view_all' => '', ), $atts, 'viator_top_attractions' ); $query = new WP_Query( array( 'post_type' => 'viator_tour', 'post_status' => 'publish', 'posts_per_page' => max( 1, absint( $atts['limit'] ) ), 'meta_key' => '_vc_rating', 'orderby' => 'meta_value_num', 'order' => 'DESC', ) ); if ( ! $query->have_posts() ) { return ''; } ob_start(); echo '
'; while ( $query->have_posts() ) { $query->the_post(); echo vc_get_template( 'card-attraction.php', array( 'post_id' => get_the_ID() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } echo '
'; wp_reset_postdata(); $grid = ob_get_clean(); return self::render_section( 'vc-section--attractions', $atts['eyebrow'], $atts['title'], $atts['view_all'], $grid ); } /** * "Browse By Activity" section: a list of activity (tag) rows. */ public static function shortcode_browse_activities( $atts ) { $atts = shortcode_atts( array( 'count' => 8, 'ids' => '', 'title' => __( 'Browse By Activity', 'viator-collector' ), 'eyebrow' => __( 'Find your style', 'viator-collector' ), 'view_all' => '', ), $atts, 'viator_browse_activities' ); $terms = self::get_section_terms( 'viator_tag', $atts['count'], $atts['ids'] ); if ( empty( $terms ) ) { return ''; } ob_start(); echo '
'; foreach ( $terms as $term ) { echo vc_get_template( 'card-activity.php', array( 'term' => $term ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } echo '
'; $grid = ob_get_clean(); return self::render_section( 'vc-section--activities', $atts['eyebrow'], $atts['title'], $atts['view_all'], $grid ); } /** * Shared: render a WP_Query of tours into the card grid markup. */ private static function render_tour_grid( array $args ) { $query = new WP_Query( $args ); if ( ! $query->have_posts() ) { wp_reset_postdata(); return ''; } ob_start(); echo '
'; wp_reset_postdata(); return ob_get_clean(); } /** * Shared: fetch terms for a section, either an explicit id list or the * most-used terms in the taxonomy. */ private static function get_section_terms( $taxonomy, $count, $ids ) { $args = array( 'taxonomy' => $taxonomy, 'hide_empty' => true, 'number' => max( 1, absint( $count ) ), ); $ids = array_filter( array_map( 'absint', array_map( 'trim', explode( ',', (string) $ids ) ) ) ); if ( ! empty( $ids ) ) { $args['include'] = $ids; $args['orderby'] = 'include'; unset( $args['number'] ); } else { $args['orderby'] = 'count'; $args['order'] = 'DESC'; } $terms = get_terms( $args ); return ( is_wp_error( $terms ) || empty( $terms ) ) ? array() : $terms; } /** * Shared: wrap inner markup in a titled, reveal-animated section. */ private static function render_section( $modifier, $eyebrow, $title, $view_all, $inner ) { ob_start(); ?>

$post_id, 'variant' => 'wide' ) ); return '
'; } public static function shortcode_itinerary( $atts ) { return vc_get_template( 'itinerary.php', array( 'post_id' => self::post_id_from_atts( $atts ) ) ); } public static function shortcode_inclusions( $atts ) { return vc_get_template( 'inclusions.php', array( 'post_id' => self::post_id_from_atts( $atts ) ) ); } public static function shortcode_exclusions( $atts ) { return vc_get_template( 'exclusions.php', array( 'post_id' => self::post_id_from_atts( $atts ) ) ); } public static function shortcode_booking_button( $atts ) { return vc_get_template( 'booking-button.php', array( 'post_id' => self::post_id_from_atts( $atts ) ) ); } public static function shortcode_duration( $atts ) { return vc_get_template( 'duration.php', array( 'post_id' => self::post_id_from_atts( $atts ) ) ); } public static function shortcode_rating( $atts ) { return vc_get_template( 'rating.php', array( 'post_id' => self::post_id_from_atts( $atts ) ) ); } public static function shortcode_gallery( $atts ) { $post_id = self::post_id_from_atts( $atts ); // Legacy installs may still have downloaded attachments. $ids = vc_json_decode_meta( get_post_meta( $post_id, '_vc_gallery_image_ids', true ), array() ); if ( ! empty( $ids ) ) { $out = ''; return $out; } $urls = vc_json_decode_meta( get_post_meta( $post_id, '_vc_gallery_image_urls', true ), array() ); if ( empty( $urls ) ) { return ''; } $title = esc_attr( get_the_title( $post_id ) ); $out = ''; return $out; } private static function post_id_from_atts( $atts ) { $atts = shortcode_atts( array( 'post_id' => get_the_ID() ), (array) $atts ); $post_id = absint( $atts['post_id'] ); return 'viator_tour' === get_post_type( $post_id ) ? $post_id : 0; } }