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";
}
}
}
}
}