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