post_type, duolangue_post_types(), true ) ) { return $actions; } if ( duolangue_get_post_translation( $post->ID ) && current_user_can( 'edit_post', $post->ID ) ) { $actions['duolangue_unlink'] = sprintf( '%s', esc_url( Duolangue_Linker::unlink_action_url( $post->ID ) ), esc_js( __( 'Unlink the two versions? Neither page is deleted.', 'duolangue' ) ), esc_html__( 'Unlink translation', 'duolangue' ) ); } return $actions; } /* ------------------------------------------------------------------ * Sticky language — the list screens always show ONE language: the * tab you clicked, otherwise the language of the last content you * opened/saved, otherwise the site default. * ---------------------------------------------------------------- */ public static function effective_list_lang() { $codes = wp_list_pluck( duolangue_get_languages(), 'code' ); if ( isset( $_GET['duolangue_lang_filter'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $lang = sanitize_key( wp_unslash( $_GET['duolangue_lang_filter'] ) ); // phpcs:ignore WordPress.Security.NonceVerification if ( in_array( $lang, $codes, true ) ) { return $lang; } } $saved = get_user_meta( get_current_user_id(), self::USER_META, true ); if ( in_array( $saved, $codes, true ) ) { return $saved; } return duolangue_default_lang()['code']; } public static function remember( $lang ) { $codes = wp_list_pluck( duolangue_get_languages(), 'code' ); if ( in_array( $lang, $codes, true ) && get_current_user_id() ) { update_user_meta( get_current_user_id(), self::USER_META, $lang ); } } /** Nonce action protecting the "remember this language" write. */ const NONCE = 'duolangue_list_lang'; /** Clicking a language tab persists it (nonce-protected: it writes user meta). */ public function remember_tab_choice() { if ( ! isset( $_GET['duolangue_lang_filter'], $_GET['_duolangue_nonce'] ) ) { return; } if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_duolangue_nonce'] ) ), self::NONCE ) ) { return; } self::remember( sanitize_key( wp_unslash( $_GET['duolangue_lang_filter'] ) ) ); } public function remember_from_save( $post_id, $post ) { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } if ( 'auto-draft' === $post->post_status || ! in_array( $post->post_type, duolangue_post_types(), true ) ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } self::remember( duolangue_get_post_lang( $post_id ) ); } public function register_columns() { foreach ( duolangue_post_types() as $post_type ) { add_filter( "manage_{$post_type}_posts_columns", array( $this, 'add_columns' ) ); add_action( "manage_{$post_type}_posts_custom_column", array( $this, 'render_column' ), 10, 2 ); add_filter( "views_edit-{$post_type}", array( $this, 'add_language_views' ) ); } } /** * Language tabs on the list screens. One language is always active * (sticky context); the mixed-language "All" view is removed on purpose. * Native status views (Published, Draft…) keep the active language. */ public function add_language_views( $views ) { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; $post_type = ( $screen && $screen->post_type ) ? $screen->post_type : ( isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : 'post' ); // phpcs:ignore WordPress.Security.NonceVerification $current = self::effective_list_lang(); $default = duolangue_default_lang()['code']; // No mixed-language view. unset( $views['all'] ); // Status views keep the active language. foreach ( $views as $key => $view ) { $views[ $key ] = preg_replace( '/href="([^"]+)"/', 'href="$1&duolangue_lang_filter=' . esc_attr( $current ) . '&_duolangue_nonce=' . esc_attr( wp_create_nonce( self::NONCE ) ) . '"', $view, 1 ); } $lang_views = array(); foreach ( duolangue_get_languages() as $lang ) { $count = Duolangue_Settings::count_posts_in_lang( $post_type, $lang['code'], $lang['code'] === $default ); $url = add_query_arg( array( 'post_type' => $post_type, 'duolangue_lang_filter' => $lang['code'], '_duolangue_nonce' => wp_create_nonce( self::NONCE ), ), admin_url( 'edit.php' ) ); $lang_views[ 'duolangue_' . $lang['code'] ] = sprintf( '%s (%d)', esc_url( $url ), $current === $lang['code'] ? ' class="current" aria-current="page"' : '', esc_html( strtoupper( $lang['code'] ) ), (int) $count ); } // Language tabs first, then the status views. return array_merge( $lang_views, $views ); } public function add_columns( $columns ) { $new = array(); foreach ( $columns as $key => $label ) { $new[ $key ] = $label; if ( 'title' === $key ) { $new['duolangue_lang'] = __( 'Lang', 'duolangue' ); $new['duolangue_translation'] = __( 'Translation', 'duolangue' ); } } return $new; } public function render_column( $column, $post_id ) { if ( 'duolangue_lang' === $column ) { $lang = duolangue_get_post_lang( $post_id ); $is_default = $lang === duolangue_default_lang()['code']; printf( '%s', $is_default ? 'duolangue-badge-default' : 'duolangue-badge-secondary', esc_html( strtoupper( $lang ) ) ); return; } if ( 'duolangue_translation' !== $column ) { return; } $lang = duolangue_get_post_lang( $post_id ); $other = $lang === duolangue_default_lang()['code'] ? duolangue_secondary_lang() : duolangue_default_lang(); $linked = duolangue_get_post_translation( $post_id ); if ( $linked && get_post( $linked ) ) { printf( ' %s', esc_url( get_edit_post_link( $linked ) ), /* translators: %s: language code. */ esc_html( sprintf( __( '%s linked', 'duolangue' ), strtoupper( $other['code'] ) ) ) ); return; } printf( '— %s', /* translators: %s: language code. */ esc_html( sprintf( __( 'no %s', 'duolangue' ), strtoupper( $other['code'] ) ) ) ); /** * Extra HTML after the "no translation" status in the list column. * Output is passed through wp_kses_post(). * * @param string $html Default empty. * @param int $post_id Post shown in the row. * @param array $other Language definition of the missing version. */ $extra = (string) apply_filters( 'duolangue_missing_translation_column', '', $post_id, $other ); if ( '' !== $extra ) { echo ' ' . wp_kses_post( $extra ); } } /* ------------------------------------------------------------------ * Filter dropdown * ---------------------------------------------------------------- */ public function apply_filter( $query ) { if ( ! is_admin() || ! $query->is_main_query() ) { return; } global $pagenow; if ( 'edit.php' !== $pagenow ) { return; } $post_type = $query->get( 'post_type' ) ?: 'post'; if ( is_array( $post_type ) || ! in_array( $post_type, duolangue_post_types(), true ) ) { return; } // One language is always active on the list screens. $lang = self::effective_list_lang(); $clause = ( $lang === duolangue_default_lang()['code'] ) ? array( 'relation' => 'OR', array( 'key' => '_duolangue_lang', 'value' => $lang ), array( 'key' => '_duolangue_lang', 'compare' => 'NOT EXISTS' ), ) : array( array( 'key' => '_duolangue_lang', 'value' => $lang ) ); $meta_query = (array) $query->get( 'meta_query' ); $meta_query[] = $clause; $query->set( 'meta_query', $meta_query ); } public function enqueue_styles( $hook ) { if ( 'edit.php' !== $hook ) { return; } wp_enqueue_style( 'duolangue-admin-columns', DUOLANGUE_URL . 'assets/css/admin-columns.css', array(), DUOLANGUE_VERSION ); } }