get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND post_parent = %d AND ID != %d", $original_slug, $post_type, $post_parent, $post_id ) ); if ( ! $conflicts ) { return $slug; } foreach ( $conflicts as $conflict_id ) { if ( duolangue_get_post_lang( (int) $conflict_id ) === $lang ) { return $slug; // Real conflict within the same language. } } return $original_slug; // Conflicts only across languages: allowed. } /* ------------------------------------------------------------------ * Bases * ---------------------------------------------------------------- */ /** * Configured base for an object in a language, or ''. */ public static function get_base( $name, $kind, $lang_code ) { $settings = duolangue_get_settings(); return isset( $settings['bases'][ $kind ][ $name ][ $lang_code ] ) ? (string) $settings['bases'][ $kind ][ $name ][ $lang_code ] : ''; } /** * The base actually used in rewrite rules for the default language: * the configured default base, otherwise the originally registered slug. */ public static function effective_default_base( $name, $kind ) { $configured = self::get_base( $name, $kind, duolangue_default_lang()['code'] ); if ( $configured ) { return $configured; } if ( 'post_type' === $kind ) { $obj = get_post_type_object( $name ); if ( $obj && ! empty( $obj->rewrite['slug'] ) ) { return $obj->rewrite['slug']; } } else { $obj = get_taxonomy( $name ); if ( $obj && ! empty( $obj->rewrite['slug'] ) ) { return $obj->rewrite['slug']; } } return ''; } /** * Inject the default-language base as the registered rewrite slug. */ public function filter_post_type_args( $args, $post_type ) { $base = self::get_base( $post_type, 'post_type', duolangue_default_lang()['code'] ); if ( $base && ! empty( $args['rewrite'] ) ) { $rewrite = is_array( $args['rewrite'] ) ? $args['rewrite'] : array(); $rewrite['slug'] = $base; $args['rewrite'] = $rewrite; } return $args; } public function filter_taxonomy_args( $args, $taxonomy ) { $base = self::get_base( $taxonomy, 'taxonomy', duolangue_default_lang()['code'] ); if ( $base && ! empty( $args['rewrite'] ) ) { $rewrite = is_array( $args['rewrite'] ) ? $args['rewrite'] : array(); $rewrite['slug'] = $base; $args['rewrite'] = $rewrite; } return $args; } /* ------------------------------------------------------------------ * Rewrite rules * ---------------------------------------------------------------- */ /** * Build the secondary-language rule set: * 1. Clone every rule whose regex starts with a translated base, * swapping in the secondary base. * 2. Prefix those clones + all original rules with `{lang}/`, * appending &lang={lang}. * Original rules stay untouched at the root (default language). */ public function filter_rewrite_rules( $rules ) { $settings = duolangue_get_settings(); if ( 'directory' !== $settings['url_mode'] || ! is_array( $rules ) ) { return $rules; } $secondary = duolangue_secondary_lang()['code']; // Base pairs: effective default base => secondary base. $pairs = array(); foreach ( duolangue_post_types() as $post_type ) { $sec = self::get_base( $post_type, 'post_type', $secondary ); $def = self::effective_default_base( $post_type, 'post_type' ); if ( $sec && $def && $sec !== $def ) { $pairs[ $def ] = $sec; } } foreach ( duolangue_taxonomies() as $taxonomy ) { $sec = self::get_base( $taxonomy, 'taxonomy', $secondary ); $def = self::effective_default_base( $taxonomy, 'taxonomy' ); if ( $sec && $def && $sec !== $def ) { $pairs[ $def ] = $sec; } } // 1. Clones with the secondary base (only served under the prefix). $clones = array(); foreach ( $pairs as $def_base => $sec_base ) { foreach ( $rules as $regex => $query ) { if ( 0 === strpos( $regex, $def_base . '/' ) || $regex === $def_base . '/?$' ) { $clones[ $sec_base . substr( $regex, strlen( $def_base ) ) ] = $query; } } } // 2. Prefixed rules for the secondary language. $prefixed = array( $secondary . '/?$' => 'index.php?lang=' . $secondary, ); foreach ( $clones + $rules as $regex => $query ) { $prefixed[ $secondary . '/' . $regex ] = $query . '&lang=' . $secondary; } // Prefixed (more specific) first, then the original root rules. return $prefixed + $rules; } /** * Swap the default base for the secondary base inside a permalink. */ public static function swap_base_in_url( $url, $name, $kind ) { $secondary = duolangue_secondary_lang()['code']; $sec_base = self::get_base( $name, $kind, $secondary ); $def_base = self::effective_default_base( $name, $kind ); if ( ! $sec_base || ! $def_base || $sec_base === $def_base ) { return $url; } $pos = strpos( $url, '/' . $def_base . '/' ); if ( false === $pos ) { return $url; } return substr_replace( $url, '/' . $sec_base . '/', $pos, strlen( '/' . $def_base . '/' ) ); } /* ------------------------------------------------------------------ * Screen * ---------------------------------------------------------------- */ public static function render_screen() { $default = duolangue_default_lang(); $secondary = duolangue_secondary_lang(); // Rows MUST follow the same order as the headers: default first. $langs = array( $default, $secondary ); ?>