[label, type, help]. Pure (no WordPress calls) so tests can * check the bundled data against it. */ public static function fields() { return array( 'tagline' => array( 'Tagline', 'text', 'One short line under the city name in the banner, e.g. "Where is America’s tropical star?"' ), 'language' => array( 'Language', 'text', 'Facts bar. E.g. "English, Spanish".' ), 'currency' => array( 'Currency', 'text', 'Facts bar. E.g. "US Dollar (USD)".' ), 'timezone' => array( 'Time zone', 'text', 'Facts bar. E.g. "GMT-5".' ), 'best_time' => array( 'Best time to visit', 'text', 'Facts bar. E.g. "March – May".' ), 'about_title' => array( 'About heading', 'text', 'Heading of the About section. Empty uses "Discover {city}".' ), 'about' => array( 'About text', 'html', 'History and introduction. Long text folds behind a "Read more" button.' ), 'about_image' => array( 'About photo', 'image', 'Photo beside the About text. Empty uses one of the city\'s tour photos.' ), 'spring' => array( 'Spring', 'textarea', 'Things to do in spring (2–3 sentences).' ), 'summer' => array( 'Summer', 'textarea', 'Things to do in summer.' ), 'fall' => array( 'Fall', 'textarea', 'Things to do in fall.' ), 'winter' => array( 'Winter', 'textarea', 'Things to do in winter.' ), 'southern' => array( 'Southern hemisphere', 'checkbox', 'Tick for cities south of the equator (e.g. Sydney, Cape Town) so the season months are flipped.' ), ); } public static function get( $term_id ) { $saved = get_term_meta( (int) $term_id, self::META, true ); $saved = is_array( $saved ) ? $saved : array(); $guide = array(); foreach ( self::fields() as $key => $field ) { $guide[ $key ] = isset( $saved[ $key ] ) ? $saved[ $key ] : ( 'checkbox' === $field[1] ? false : '' ); } return $guide; } public static function sanitize( array $raw ) { $clean = array(); foreach ( self::fields() as $key => $field ) { $value = isset( $raw[ $key ] ) ? $raw[ $key ] : ''; switch ( $field[1] ) { case 'html': $clean[ $key ] = trim( wp_kses_post( (string) $value ) ); break; case 'textarea': $clean[ $key ] = trim( sanitize_textarea_field( (string) $value ) ); break; case 'image': $clean[ $key ] = esc_url_raw( (string) $value ); break; case 'checkbox': $clean[ $key ] = ! empty( $value ); break; default: $clean[ $key ] = trim( sanitize_text_field( (string) $value ) ); } } return $clean; } /** True when no text field is filled (the hemisphere flag alone doesn't count). */ public static function is_empty( array $guide ) { foreach ( self::fields() as $key => $field ) { if ( 'checkbox' !== $field[1] && 'about_image' !== $key && ! empty( $guide[ $key ] ) ) { return false; } } return true; } /** * $guide with its empty fields taken from $defaults. Pure. */ public static function fill_empty( array $guide, array $defaults ) { foreach ( $defaults as $key => $value ) { if ( empty( $guide[ $key ] ) ) { $guide[ $key ] = $value; } } return $guide; } /** * Months for each season. Pure. */ public static function season_months( $southern ) { $north = array( 'spring' => 'March – May', 'summer' => 'June – August', 'fall' => 'September – November', 'winter' => 'December – February', ); if ( ! $southern ) { return $north; } return array( 'spring' => $north['fall'], 'summer' => $north['winter'], 'fall' => $north['spring'], 'winter' => $north['summer'], ); } /* ------------------------------------------------------------------ admin */ public static function edit_fields( $term ) { if ( get_term_meta( $term->term_id, '_vc_region', true ) ) { return; // Regions use their own page; the guide is for cities. } $guide = self::get( $term->term_id ); wp_nonce_field( 'vc_guide_save', 'vc_guide_nonce' ); ?>

$field ) { list( $label, $type, $help ) = $field; $name = 'vc_guide[' . $key . ']'; $id = 'vc-guide-' . $key; $value = $guide[ $key ]; ?> $name, 'textarea_rows' => 8, 'media_buttons' => false, 'teeny' => true, ) ); } elseif ( 'textarea' === $type ) { echo ''; } elseif ( 'checkbox' === $type ) { echo ''; } elseif ( 'image' === $type ) { self::image_field( $id, $name, (string) $value ); } else { echo ''; } ?>

term_id, self::META, self::fill_empty( self::get( $term->term_id ), self::sanitize( $data[ $slug ] ) ) ); $done[] = $slug; } update_option( self::IMPORTED_OPTION, array_values( array_unique( $done ) ), false ); if ( array_diff( array_keys( $data ), $done ) ) { set_transient( 'vc_city_guides_check', 1, HOUR_IN_SECONDS ); } } /* --------------------------------------------------------------- front end */ /** The guide is shown on page 1 of an unfiltered city listing only. */ public static function is_visible_here() { $paged = max( (int) get_query_var( 'paged' ), isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1 ); // phpcs:ignore WordPress.Security.NonceVerification return $paged <= 1 && ! VC_Display::filters_active( VC_Display::get_filter_state() ); } /** * Facts bar, About and Seasons for $term, or '' when the guide is empty. */ public static function render( $term ) { $guide = self::get( $term->term_id ); if ( self::is_empty( $guide ) ) { return ''; } $city = $term->name; $out = ''; $facts = array( 'language' => array( __( 'Language', 'viator-collector' ), 'lang' ), 'currency' => array( __( 'Currency', 'viator-collector' ), 'cur' ), 'timezone' => array( __( 'Time zone', 'viator-collector' ), 'time' ), 'best_time' => array( __( 'Best time to visit', 'viator-collector' ), 'sun' ), ); $items = ''; foreach ( $facts as $key => $fact ) { if ( '' !== $guide[ $key ] ) { $items .= '
  • ' . self::icon( $fact[1] ) . '' . esc_html( $fact[0] ) . '' . esc_html( $guide[ $key ] ) . '
  • '; } } if ( $items ) { // Wrapped: direct children of .vc-app are forced full width. $out .= '
    '; } if ( '' !== $guide['about'] ) { $img = $guide['about_image'] ? $guide['about_image'] : self::fallback_image( $term ); /* translators: %s: city name. */ $title = '' !== $guide['about_title'] ? $guide['about_title'] : sprintf( __( 'Discover %s', 'viator-collector' ), $city ); $out .= '
    '; if ( $img ) { $out .= '' . esc_attr( $city ) . ''; } /* translators: %s: city name. */ $out .= '

    ' . esc_html( sprintf( __( 'About %s', 'viator-collector' ), $city ) ) . '

    ' . '

    ' . esc_html( $title ) . '

    ' . '
    ' . wpautop( $guide['about'] ) . '
    ' . '' . '
    '; } $months = self::season_months( $guide['southern'] ); $seasons = array( 'spring' => array( __( 'Spring', 'viator-collector' ), 'flower' ), 'summer' => array( __( 'Summer', 'viator-collector' ), 'sun' ), 'fall' => array( __( 'Fall', 'viator-collector' ), 'leaf' ), 'winter' => array( __( 'Winter', 'viator-collector' ), 'snow' ), ); $cards = ''; foreach ( $seasons as $key => $season ) { if ( '' !== $guide[ $key ] ) { $cards .= '
  • ' . self::icon( $season[1] ) . '' . '

    ' . esc_html( $season[0] ) . '

    ' . esc_html( $months[ $key ] ) . '' . '

    ' . esc_html( $guide[ $key ] ) . '

  • '; } } if ( $cards ) { /* translators: %s: city name. */ $out .= '

    ' . esc_html__( 'Seasonal activities', 'viator-collector' ) . '

    ' . '

    ' . esc_html( sprintf( __( 'What to do in %s, all year round', 'viator-collector' ), $city ) ) . '

    ' . '
    '; } return $out; } public static function tagline( $term ) { return self::get( $term->term_id )['tagline']; } /** * A tour photo of the city that isn't the banner photo, cached for a day. */ private static function fallback_image( $term ) { $key = 'vc_guide_img_' . $term->term_id; $cached = get_transient( $key ); if ( false !== $cached ) { return $cached; } $hero = vc_term_image_url( $term->term_id, $term->taxonomy ); $query = new WP_Query( array( 'post_type' => 'viator_tour', 'post_status' => 'publish', 'posts_per_page' => 10, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, 'tax_query' => array( array( 'taxonomy' => $term->taxonomy, 'field' => 'term_id', 'terms' => $term->term_id, ), ), ) ); $url = ''; foreach ( $query->posts as $post_id ) { $candidate = (string) get_post_meta( (int) $post_id, '_vc_primary_image_url', true ); if ( '' !== $candidate && $candidate !== $hero ) { $url = $candidate; break; } } set_transient( $key, $url, $url ? DAY_IN_SECONDS : HOUR_IN_SECONDS ); return $url; } private static function icon( $name ) { $paths = array( 'lang' => '', 'cur' => '', 'time' => '', 'sun' => '', 'flower' => '', 'leaf' => '', 'snow' => '', ); return ''; } }