model = &$polylang->model;
$this->curlang = &$polylang->curlang;
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
// Filters posts, pages and media by language
add_action( 'parse_query', array( $this, 'parse_query' ) );
// Adds actions and filters related to languages when creating, saving or deleting posts and pages
add_action( 'load-post.php', array( $this, 'edit_post' ) );
add_action( 'load-edit.php', array( $this, 'bulk_edit_posts' ) );
add_action( 'wp_ajax_inline-save', array( $this, 'inline_edit_post' ), 0 ); // Before WordPress
// Sets the language in Tiny MCE
add_filter( 'tiny_mce_before_init', array( $this, 'tiny_mce_before_init' ) );
// Filter untranslated items in the posts list table.
add_action( 'restrict_manage_posts', array( $this, 'untranslated_dropdown' ) );
// Languages views in post list tables.
foreach ( $this->model->get_translated_post_types() as $post_type ) {
add_filter( "views_edit-{$post_type}", array( $this, 'add_per_language_edit_views' ) );
}
}
/**
* Outputs a javascript list of terms ordered by language and hierarchical taxonomies
* to filter the category checklist per post language in quick edit
* Outputs a javascript list of pages ordered by language
* to filter the parent dropdown per post language in quick edit
*
* @since 1.7
*
* @return void
*/
public function admin_enqueue_scripts() {
$screen = get_current_screen();
if ( empty( $screen ) ) {
return;
}
// Hierarchical taxonomies
if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'objects' ) ) {
// Get translated hierarchical taxonomies
$hierarchical_taxonomies = array();
foreach ( $taxonomies as $taxonomy ) {
if ( $taxonomy->hierarchical && $taxonomy->show_in_quick_edit && $this->model->is_translated_taxonomy( $taxonomy->name ) ) {
$hierarchical_taxonomies[] = $taxonomy->name;
}
}
if ( ! empty( $hierarchical_taxonomies ) ) {
$terms = get_terms( array( 'taxonomy' => $hierarchical_taxonomies, 'get' => 'all' ) );
$term_languages = array();
if ( is_array( $terms ) ) {
foreach ( $terms as $term ) {
if ( $lang = $this->model->term->get_language( $term->term_id ) ) {
$term_languages[ $lang->slug ][ $term->taxonomy ][] = $term->term_id;
}
}
}
// Send all these data to javascript
if ( ! empty( $term_languages ) ) {
wp_localize_script( 'pll_post', 'pll_term_languages', $term_languages );
}
}
}
// Hierarchical post types
if ( 'edit' == $screen->base && is_post_type_hierarchical( $screen->post_type ) && post_type_supports( $screen->post_type, 'page-attributes' ) ) {
$pages = get_pages(
array(
'sort_column' => 'menu_order, post_title',
'post_type' => $screen->post_type,
)
); // Same arguments as the parent pages dropdown to avoid an extra query.
if ( ! is_array( $pages ) ) {
return;
}
update_object_term_cache( wp_list_pluck( $pages, 'ID' ), $screen->post_type );
$page_languages = array();
foreach ( $pages as $page ) {
if ( $lang = $this->model->post->get_language( $page->ID ) ) {
$page_languages[ $lang->slug ][] = $page->ID;
}
}
// Send all these data to javascript
if ( ! empty( $page_languages ) ) {
wp_localize_script( 'pll_post', 'pll_page_languages', $page_languages );
}
}
}
/**
* Filters posts, pages and media by language.
*
* @since 0.1
*
* @param WP_Query $query WP_Query object.
* @return void
*/
public function parse_query( $query ) {
$pll_query = new PLL_Query( $query, $this->model );
$pll_query->filter_query( $this->curlang );
}
/**
* Save language and translation when editing a post (post.php).
*
* @since 2.3
*
* @return void
*/
public function edit_post() {
if ( ! isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) ) {
return;
}
check_admin_referer( 'pll_language', '_pll_nonce' );
$post_id = (int) $_POST['post_ID'];
$post = get_post( $post_id );
if ( empty( $post ) ) {
return;
}
$post_type_object = get_post_type_object( $post->post_type );
if ( empty( $post_type_object ) ) {
return;
}
$user = Capabilities::get_user();
if ( ! $user->has_cap( $post_type_object->cap->edit_post, $post_id ) ) {
return;
}
$language = $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) );
if ( empty( $language ) ) {
return;
}
$user->can_translate_or_die( $language );
$this->model->post->set_language( $post_id, $language );
if ( ! isset( $_POST['post_tr_lang'] ) ) {
return;
}
$this->model->post->save_translations( $post_id, array_map( 'absint', $_POST['post_tr_lang'] ) );
}
/**
* Save language when bulk editing posts.
*
* @since 2.3
*
* @return void
*/
public function bulk_edit_posts() {
if ( ! isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'], $_REQUEST['post'], $_REQUEST['_wpnonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-posts' ) ) {
return;
}
if ( -1 === $_GET['inline_lang_choice'] ) {
return;
}
$language = $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) );
if ( empty( $language ) ) {
return;
}
$user = Capabilities::get_user();
$user->can_translate_or_die( $language );
$post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
foreach ( $post_ids as $post_id ) {
if ( $user->has_cap( 'edit_post', $post_id ) ) {
$this->model->post->set_language( $post_id, $language );
}
}
}
/**
* Save language when inline editing a post.
*
* @since 2.3
*
* @return void
*/
public function inline_edit_post() {
if ( ! isset( $_POST['post_ID'], $_POST['inline_lang_choice'], $_REQUEST['_inline_edit'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_REQUEST['_inline_edit'], 'inlineeditnonce' ) ) {
return;
}
$language = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) );
if ( empty( $language ) ) {
return;
}
$user = Capabilities::get_user();
$user->can_translate_or_die( $language );
$post_id = (int) $_POST['post_ID'];
if ( ! $post_id || ! $user->has_cap( 'edit_post', $post_id ) ) {
return;
}
$this->model->post->set_language( $post_id, $language );
}
/**
* Sets the language attribute and text direction for Tiny MCE.
*
* @since 2.2
*
* @param array $mce_init TinyMCE config.
* @return array
*/
public function tiny_mce_before_init( $mce_init ) {
if ( ! empty( $this->curlang ) ) {
$mce_init['wp_lang_attr'] = $this->curlang->get_locale( 'display' );
$mce_init['directionality'] = $this->curlang->is_rtl ? 'rtl' : 'ltr';
}
return $mce_init;
}
/**
* Displays a dropdown for filtering unstranslated items in the posts list table.
*
* @since 3.9
*
* @param string $post_type The post type.
* @return void
*/
public function untranslated_dropdown( $post_type ) {
if ( ! $this->model->is_translated_post_type( $post_type ) ) {
return;
}
$languages = $this->model->get_languages_list();
foreach ( $languages as $k => $language ) {
$languages[ $k ] = $language->to_std_class(); // Important not to modify the language name everywhere.
/* translators: %s is a native language name */
$languages[ $k ]->name = sprintf( __( 'Untranslated in %s', 'polylang' ), $language->name );
}
$dropdown = new PLL_Walker_Dropdown();
$dropdown_html = $dropdown->walk(
array_merge(
array( (object) array( 'slug' => 0, 'name' => __( 'All translation statuses', 'polylang' ) ) ),
$languages
),
-1,
array(
'id' => 'pll_untranslated_in',
'name' => 'untranslated_in',
'class' => 'postform',
'selected' => isset( $_GET['untranslated_in'] ) ? sanitize_key( $_GET['untranslated_in'] ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
)
);
printf(
'',
esc_html__( 'Filter untranslated items', 'polylang' )
);
echo $dropdown_html; // phpcs:ignore WordPress.Security.EscapeOutput
}
/**
* Add languages filter views to the list of views available for the post list table.
*
* @since 3.9
*
* @param string[] $views Array of available list table views.
* @return string[]
*/
public function add_per_language_edit_views( $views ) {
global $typenow;
if ( empty( $typenow ) ) {
return $views;
}
$q = array(
'post_type' => $typenow,
'post_status' => 'any',
);
foreach ( $this->model->languages->get_list() as $language ) {
$views[ $language->slug ] = sprintf(
'%s (%s)',
esc_url( add_query_arg( 'lang', $language->slug, remove_query_arg( 'paged' ) ) ),
! empty( $this->curlang ) && $this->curlang->slug === $language->slug ? 'class="current" aria-current="page"' : '',
esc_html( $language->name ),
number_format_i18n( (int) $this->model->count_posts( $language, $q ) )
);
}
if ( isset( $views['all'] ) ) { // In case a filter removes this key.
$replacement = 'lang=all&';
if ( ! str_contains( $views['all'], 'all_posts=1' ) ) {
$replacement .= 'all_posts=1&';
}
$views['all'] = str_replace( 'edit.php?', 'edit.php?' . $replacement, $views['all'] );
}
return $views;
}
}