'uk', or for every language with 'lang' => 'all'. Single posts are * not filtered: a post in another language is redirected to its own URL. * The site-wide comments feed (/uk/comments/feed/) follows the same rule: * only comments on posts in its language. */ declare( strict_types = 1 ); namespace AddBlack\Lang\Frontend; defined( 'ABSPATH' ) || exit; use AddBlack\Lang\Hookable; use AddBlack\Lang\Hooks; use AddBlack\Lang\Install\Schema; use AddBlack\Lang\Routing\CurrentLanguage; use AddBlack\Lang\Settings\SettingsRepository; use WP_Query; final class QueryFilter implements Hookable { public function __construct( private readonly CurrentLanguage $current, private readonly SettingsRepository $settings, private readonly Schema $schema, private readonly \wpdb $db ) {} public function register(): void { Hooks::action( 'pre_get_posts', array( $this, 'pre_get_posts' ) ); Hooks::filter( 'posts_clauses', array( $this, 'posts_clauses' ), 10, 2 ); Hooks::filter( 'comment_feed_join', array( $this, 'comment_feed_join' ), 10, 2 ); Hooks::filter( 'comment_feed_where', array( $this, 'comment_feed_where' ), 10, 2 ); } /** Decide the language of a query: its 'lang' var, else the page language for lists. */ public function pre_get_posts( WP_Query $q ): void { $lang = $q->get( 'lang' ); if ( '' === $lang || null === $lang ) { if ( is_admin() || '' === $this->current->get() ) { return; } // The posts page (Settings > Reading) keeps its pagename/page_id vars, but it is a list. $one_post = $q->is_singular() || $q->get( 'post__in' ) || $q->get( 'p' ) || $q->get( 'page_id' ) || $q->get( 'name' ) || $q->get( 'pagename' ); if ( $one_post && ! $q->is_posts_page ) { return; } $lang = $this->current->get(); } $q->set( 'blocklingo_lang', 'all' === $lang ? '' : $lang ); } /** * Join the translations table. Tagged posts: their language must match. * Untagged: the default language, or a post type that is not translated. * * @param array $clauses * @return array */ public function posts_clauses( array $clauses, WP_Query $q ): array { $lang = $this->query_language( $q ); if ( '' === $lang ) { return $clauses; } $clauses['join'] .= $this->join_sql(); $clauses['where'] .= $this->where_sql( $lang ); return $clauses; } /** Comment feeds of lists (not of one post): join the language of the commented post. */ public function comment_feed_join( mixed $join, mixed $q ): mixed { if ( ! is_string( $join ) || ! $q instanceof WP_Query || $q->is_singular() || '' === $this->query_language( $q ) ) { return $join; } return $join . $this->join_sql(); } public function comment_feed_where( mixed $where, mixed $q ): mixed { if ( ! is_string( $where ) || ! $q instanceof WP_Query || $q->is_singular() ) { return $where; } $lang = $this->query_language( $q ); return '' === $lang ? $where : $where . $this->where_sql( $lang ); } /** Language a query is limited to ('' = not limited). */ private function query_language( WP_Query $q ): string { $lang = (string) $q->get( 'blocklingo_lang' ); $settings = $this->settings->get(); return '' !== $lang && $settings->is_language( $lang ) && $settings->post_types() ? $lang : ''; } private function join_sql(): string { $posts = $this->db->posts; return (string) $this->db->prepare( " LEFT JOIN %i AS blocklingo_l ON blocklingo_l.object_type = 'post' AND blocklingo_l.object_id = {$posts}.ID", $this->schema->translations() ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- core table name. } /** Tagged posts: their language must match. Untagged: the default language, or a post type that is not translated. */ private function where_sql( string $lang ): string { $settings = $this->settings->get(); $posts = $this->db->posts; $in = implode( ',', array_fill( 0, count( $settings->post_types() ), '%s' ) ); $is_def = $settings->default_code() === $lang ? 1 : 0; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- core table name; $in holds %s placeholders. return (string) $this->db->prepare( " AND ( blocklingo_l.lang = %s OR ( blocklingo_l.lang IS NULL AND ( {$is_def} = 1 OR {$posts}.post_type NOT IN ({$in}) ) ) )", array_merge( array( $lang ), $settings->post_types() ) ); } }