content; } private function languages() { $langs = Settings::get_languages_option(); $items = $langs['items']; uasort( $items, function( $a, $b ) { return (int) $a['order'] <=> (int) $b['order']; } ); $langs['items'] = $items; return $langs; } private function is_prefix_seo_forced() { $settings = Settings::get_settings_option(); return ( ( $settings['url_mode'] ?? 'none' ) === 'prefix' ) && ! empty( $settings['prefix_force_canonical'] ); } private function normalize_html_lang( $code ) { $code = (string) $code; if ( $code === '' ) { return $code; } if ( strpos( $code, '-' ) !== false || strpos( $code, '_' ) !== false ) { return str_replace( '_', '-', $code ); } $map = [ 'en' => 'en-US', 'es' => 'es-ES', 'fr' => 'fr-FR', 'pt' => 'pt-PT', 'de' => 'de-DE', 'it' => 'it-IT', 'nl' => 'nl-NL', ]; $tag = $map[ $code ] ?? $code; return apply_filters( 'abdiel_amm_html_lang', $tag, $code ); } private function normalize_locale_for_yoast( $code ) { $tag = ''; if ( ! empty( $row['locale'] ) ) { $tag = str_replace( '_', '-', sanitize_text_field( $row['locale'] ) ); } if ( empty( $tag ) ) { $tag = $this->normalize_html_lang( $code ); } // Yoast typically expects underscore locale like en_US. $loc = str_replace( '-', '_', $tag ); return apply_filters( 'abdiel_amm_yoast_locale', $loc, $code, $tag ); } private function build_lang_url( $target_post_id, $lang_code ) { $settings = Settings::get_settings_option(); $mode = $settings['url_mode'] ?? 'none'; $default = $this->content()->get_default_language_code(); $url = $target_post_id ? get_permalink( $target_post_id ) : home_url( '/' ); if ( $mode === 'query' ) { if ( $default && $lang_code === $default ) { return $url; } return add_query_arg( 'lang', $lang_code, $url ); } if ( $mode === 'prefix' ) { if ( $default && $lang_code === $default ) { return $url; } $parsed = wp_parse_url( $url ); $host = ( isset( $parsed['scheme'] ) ? $parsed['scheme'] . '://' : '' ) . ( $parsed['host'] ?? '' ); if ( isset( $parsed['port'] ) ) { $host .= ':' . $parsed['port']; } $path = $parsed['path'] ?? '/'; $path = '/' . ltrim( $path, '/' ); // Remove existing /xx/ if present $path = preg_replace( '#^/([a-z]{2})(/|$)#i', '/', $path, 1 ); $path = '/' . $lang_code . rtrim( $path, '/' ); if ( substr( $path, -1 ) !== '/' ) { $path .= '/'; } return $host . $path; } return $url; } private function get_prefixed_canonical_url( $post_id ) { if ( ! $post_id || get_post_status( $post_id ) !== 'publish' ) { return ''; } $code = $this->content()->get_current_language_code( $post_id ); $default = $this->content()->get_default_language_code(); if ( ! $code || ! $default || $code === $default ) { return ''; } $canonical = $this->build_lang_url( $post_id, $code ); return $canonical ? $canonical : ''; } public function filter_wpseo_canonical( $canonical ) { if ( ! is_singular() || ! $this->is_prefix_seo_forced() ) { return $canonical; } $pref = $this->get_prefixed_canonical_url( get_queried_object_id() ); return $pref ? $pref : $canonical; } public function filter_wp_core_canonical( $canonical, $post ) { if ( ! is_singular() || ! $this->is_prefix_seo_forced() ) { return $canonical; } $post_id = ( is_object( $post ) && ! empty( $post->ID ) ) ? (int) $post->ID : (int) get_queried_object_id(); $pref = $this->get_prefixed_canonical_url( $post_id ); return $pref ? $pref : $canonical; } public function filter_wpseo_sitemap_post_url( $url, $post ) { if ( ! $post || empty( $post->ID ) ) { return $url; } $settings = Settings::get_settings_option(); if ( ( $settings['url_mode'] ?? 'none' ) !== 'prefix' ) { return $url; } $code = $this->content()->get_current_language_code( (int) $post->ID ); $default = $this->content()->get_default_language_code(); if ( ! $code || ! $default || $code === $default ) { return $url; } $pref = $this->build_lang_url( (int) $post->ID, $code ); return $pref ? $pref : $url; } public function filter_wpseo_sitemap_entry( $entry, $type, $obj ) { if ( $type !== 'post' || ! is_array( $entry ) ) { return $entry; } if ( empty( $entry['loc'] ) || ! is_object( $obj ) || empty( $obj->ID ) ) { return $entry; } $settings = Settings::get_settings_option(); if ( ( $settings['url_mode'] ?? 'none' ) !== 'prefix' ) { return $entry; } $code = $this->content()->get_current_language_code( (int) $obj->ID ); $default = $this->content()->get_default_language_code(); if ( ! $code || ! $default || $code === $default ) { return $entry; } $pref = $this->build_lang_url( (int) $obj->ID, $code ); if ( $pref ) { $entry['loc'] = $pref; } return $entry; } public function maybe_print_canonical() { if ( ! is_singular() ) { return; } $settings = Settings::get_settings_option(); if ( ( $settings['url_mode'] ?? 'none' ) !== 'prefix' ) { return; } if ( empty( $settings['prefix_force_canonical'] ) ) { return; } // If Yoast is present, it will output canonical. We still print as a fallback, // but the main source of truth is our wpseo_canonical filter. $post_id = get_queried_object_id(); $canonical = $this->get_prefixed_canonical_url( $post_id ); if ( ! $canonical ) { return; } echo "\n" . '' . "\n"; } public function maybe_redirect_to_prefixed() { if ( ! is_singular() ) { return; } $settings = Settings::get_settings_option(); if ( ( $settings['url_mode'] ?? 'none' ) !== 'prefix' ) { return; } if ( empty( $settings['prefix_force_redirect_301'] ) ) { return; } $post_id = get_queried_object_id(); if ( ! $post_id || get_post_status( $post_id ) !== 'publish' ) { return; } $code = $this->content()->get_current_language_code( $post_id ); $default = $this->content()->get_default_language_code(); if ( ! $code || ! $default || $code === $default ) { return; } $req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; $req_path = wp_parse_url( ( is_ssl() ? 'https://' : 'http://' ) . $host . $req_uri, PHP_URL_PATH ); $req_path = is_string( $req_path ) ? $req_path : ''; if ( preg_match( '#^/' . preg_quote( $code, '#' ) . '(/|$)#i', $req_path ) ) { return; } $target = $this->build_lang_url( $post_id, $code ); if ( ! $target ) { return; } $q = wp_parse_url( $req_uri, PHP_URL_QUERY ); if ( is_string( $q ) && $q !== '' ) { $target = $target . ( strpos( $target, '?' ) === false ? '?' : '&' ) . $q; } wp_safe_redirect( $target, 301 ); exit; } public function html_lang_attribute( $output ) { if ( ! is_singular() ) { return $output; } $post_id = get_queried_object_id(); if ( ! $post_id ) { return $output; } $code = $this->content()->get_current_language_code( $post_id ); if ( ! $code ) { return $output; } $tag = ''; if ( ! empty( $row['locale'] ) ) { $tag = str_replace( '_', '-', sanitize_text_field( $row['locale'] ) ); } if ( empty( $tag ) ) { $tag = $this->normalize_html_lang( $code ); } if ( ! $tag ) { return $output; } if ( preg_match( '/\blang\s*=\s*\"[^\"]*\"/i', $output ) ) { $output = preg_replace( '/\blang\s*=\s*\"[^\"]*\"/i', 'lang="' . esc_attr( $tag ) . '"', $output ); } else { $output .= ' lang="' . esc_attr( $tag ) . '"'; } return $output; } public function print_hreflang() { if ( ! is_singular() ) { return; } $post_id = get_queried_object_id(); if ( ! $post_id ) { return; } if ( get_post_status( $post_id ) !== 'publish' ) { return; } $langs = $this->languages(); $active = []; foreach ( (array) $langs['items'] as $code => $row ) { if ( ! empty( $row['active'] ) ) { $active[ $code ] = $row; } } $map = $this->content()->get_translation_map( $post_id ); if ( empty( $map ) ) { return; } $default = isset( $langs['default'] ) ? sanitize_key( $langs['default'] ) : ''; $printed = 0; foreach ( $active as $code => $row ) { if ( empty( $map[ $code ] ) ) { continue; } $pid = absint( $map[ $code ] ); if ( $pid <= 0 ) { continue; } if ( get_post_status( $pid ) !== 'publish' ) { continue; } $url = $this->build_lang_url( $pid, $code ); if ( ! $url ) { continue; } $tag = ''; if ( ! empty( $row['locale'] ) ) { $tag = str_replace( '_', '-', sanitize_text_field( $row['locale'] ) ); } if ( empty( $tag ) ) { $tag = $this->normalize_html_lang( $code ); } echo "\n" . '' . "\n"; $printed++; } if ( $printed > 1 && $default && ! empty( $map[ $default ] ) ) { $pid = absint( $map[ $default ] ); if ( $pid && get_post_status( $pid ) === 'publish' ) { $url = $this->build_lang_url( $pid, $default ); if ( $url ) { echo "\n" . '' . "\n"; } } } } }