diff --git a/viator-collector/includes/class-vc-plugin.php b/viator-collector/includes/class-vc-plugin.php index 21d4ca2..e97f148 100644 --- a/viator-collector/includes/class-vc-plugin.php +++ b/viator-collector/includes/class-vc-plugin.php @@ -20,6 +20,7 @@ final class VC_Plugin { VC_Taxonomies::init(); VC_Settings::init(); VC_Structure_Admin::init(); + VC_Routing::init(); VC_Cron::init(); VC_Display::init(); VC_Sections::init(); @@ -37,6 +38,7 @@ final class VC_Plugin { 'includes/class-vc-cron.php', 'includes/class-vc-settings.php', 'includes/class-vc-structure-admin.php', + 'includes/class-vc-routing.php', 'includes/class-vc-display.php', 'includes/class-vc-sections.php', ); diff --git a/viator-collector/includes/class-vc-routing.php b/viator-collector/includes/class-vc-routing.php new file mode 100644 index 0000000..67569dd --- /dev/null +++ b/viator-collector/includes/class-vc-routing.php @@ -0,0 +1,139 @@ + $query ) { + add_rewrite_rule( $regex, $query, 'top' ); + } + if ( get_option( 'vc_flush_rewrite' ) ) { + delete_option( 'vc_flush_rewrite' ); + flush_rewrite_rules( false ); + } + } + + public static function is_configured_term( $term ) { + if ( ! $term instanceof WP_Term || 'viator_destination' !== $term->taxonomy ) { + return false; + } + if ( get_term_meta( $term->term_id, '_vc_region', true ) ) { + return true; + } + $viator_id = (string) get_term_meta( $term->term_id, '_vc_viator_destination_id', true ); + if ( '' === $viator_id ) { + return false; + } + $cities = VC_Structure::cities( VC_Structure::get() ); + return isset( $cities[ $viator_id ] ); + } + + /** + * Slug of the city a tour's URL lives under, or '' (it stays at /tours/). + */ + public static function product_city( $post_id ) { + $post_id = (int) $post_id; + if ( isset( self::$product_city[ $post_id ] ) ) { + return self::$product_city[ $post_id ]; + } + $dest_ids = array(); + $destinations = json_decode( (string) get_post_meta( $post_id, '_vc_destinations', true ), true ); + foreach ( is_array( $destinations ) ? $destinations : array() as $destination ) { + $ref = is_array( $destination ) ? ( $destination['ref'] ?? ( $destination['destinationId'] ?? '' ) ) : $destination; + if ( '' !== (string) $ref ) { + $dest_ids[] = (string) $ref; + } + } + $primary = (string) get_post_meta( $post_id, '_vc_primary_destination', true ); + + self::$product_city[ $post_id ] = VC_Structure::canonical_city( VC_Structure::get(), $dest_ids, $primary ); + return self::$product_city[ $post_id ]; + } + + public static function term_link( $url, $term, $taxonomy ) { + if ( 'viator_destination' === $taxonomy && self::is_configured_term( $term ) ) { + return home_url( '/' . $term->slug . '/' ); + } + return $url; + } + + public static function post_type_link( $url, $post ) { + if ( 'viator_tour' !== $post->post_type || '' === $post->post_name ) { + return $url; + } + $city = self::product_city( $post->ID ); + if ( '' === $city ) { + return $url; + } + return home_url( '/' . $city . '/' . $post->post_name . '/' ); + } + + public static function redirect() { + if ( is_preview() ) { + return; + } + $canonical = ''; + if ( is_singular( 'viator_tour' ) ) { + $post = get_queried_object(); + if ( $post && 'publish' === $post->post_status ) { + $canonical = get_permalink( $post ); + } + } elseif ( is_tax( 'viator_destination' ) ) { + $term = get_queried_object(); + if ( self::is_configured_term( $term ) ) { + $canonical = get_term_link( $term ); + } + } + if ( ! $canonical || is_wp_error( $canonical ) ) { + return; + } + + $request = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $parts = explode( '?', $request, 2 ); + $current = untrailingslashit( rawurldecode( $parts[0] ) ); + $target = untrailingslashit( rawurldecode( (string) wp_parse_url( $canonical, PHP_URL_PATH ) ) ); + if ( $current === $target ) { + return; + } + if ( isset( $parts[1] ) && '' !== $parts[1] ) { + $canonical .= '?' . $parts[1]; + } + wp_safe_redirect( $canonical, 301 ); + exit; + } + + /** + * Core prints rel=canonical for single tours only; add it for region and + * city archives unless an SEO plugin already handles canonicals. + */ + public static function term_canonical() { + if ( defined( 'WPSEO_VERSION' ) || defined( 'RANK_MATH_VERSION' ) || ! is_tax( 'viator_destination' ) ) { + return; + } + $term = get_queried_object(); + if ( ! self::is_configured_term( $term ) ) { + return; + } + $link = get_term_link( $term ); + if ( ! is_wp_error( $link ) ) { + echo '' . "\n"; + } + } +}