Traduction

This commit is contained in:
Thibault Richy 2017-11-17 16:22:31 +01:00
parent 50ce9c25d5
commit 46330c5d4d
451 changed files with 44887 additions and 42 deletions

View file

@ -0,0 +1,370 @@
<?php
/**
* Base class for both admin
*
* @since 1.8
*/
class PLL_Admin_Base extends PLL_Base {
public $filter_lang, $curlang, $pref_lang;
/**
* Loads the polylang text domain
* Setups actions needed on all admin pages
*
* @since 1.8
*
* @param object $links_model
*/
public function __construct( &$links_model ) {
parent::__construct( $links_model );
// Plugin i18n, only needed for backend
load_plugin_textdomain( 'polylang', false, basename( POLYLANG_DIR ) . '/languages' );
// Adds the link to the languages panel in the WordPress admin menu
add_action( 'admin_menu', array( $this, 'add_menus' ) );
// Setup js scripts and css styles
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
add_action( 'admin_print_footer_scripts', array( $this, 'admin_print_footer_scripts' ), 0 ); // High priority in case an ajax request is sent by an immediately invoked function
// Lingotek
if ( ! defined( 'PLL_LINGOTEK_AD' ) || PLL_LINGOTEK_AD ) {
require_once POLYLANG_DIR . '/lingotek/lingotek.php';
}
}
/**
* Setups filters and action needed on all admin pages and on plugins page
* Loads the settings pages or the filters base on the request
*
* @since 1.2
*/
public function init() {
if ( ! $this->model->get_languages_list() ) {
return;
}
$this->links = new PLL_Admin_Links( $this ); // FIXME needed here ?
$this->static_pages = new PLL_Admin_Static_Pages( $this ); // FIXME needed here ?
$this->filters_links = new PLL_Filters_Links( $this ); // FIXME needed here ?
// Filter admin language for users
// We must not call user info before WordPress defines user roles in wp-settings.php
add_filter( 'setup_theme', array( $this, 'init_user' ) );
add_filter( 'request', array( $this, 'request' ) );
// Adds the languages in admin bar
add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); // 100 determines the position
}
/**
* Adds the link to the languages panel in the WordPress admin menu
*
* @since 0.1
*/
public function add_menus() {
// Prepare the list of tabs
$tabs = array( 'lang' => __( 'Languages','polylang' ) );
// Only if at least one language has been created
if ( $this->model->get_languages_list() ) {
$tabs['strings'] = __( 'Strings translations', 'polylang' );
}
$tabs['settings'] = __( 'Settings', 'polylang' );
/**
* Filter the list of tabs in Polylang settings
*
* @since 1.5.1
*
* @param array $tabs list of tab names
*/
$tabs = apply_filters( 'pll_settings_tabs', $tabs );
foreach ( $tabs as $tab => $title ) {
$page = 'lang' === $tab ? 'mlang' : "mlang_$tab";
if ( empty( $parent ) ) {
$parent = $page;
add_menu_page( $title, __( 'Languages','polylang' ), 'manage_options', $page, null , 'dashicons-translation' );
}
add_submenu_page( $parent, $title, $title, 'manage_options', $page , array( $this, 'languages_page' ) );
}
}
/**
* Setup js scripts & css styles ( only on the relevant pages )
*
* @since 0.6
*/
public function admin_enqueue_scripts() {
$screen = get_current_screen();
if ( empty( $screen ) ) {
return;
}
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
// For each script:
// 0 => the pages on which to load the script
// 1 => the scripts it needs to work
// 2 => 1 if loaded even if languages have not been defined yet, 0 otherwise
// 3 => 1 if loaded in footer
// FIXME: check if I can load more scripts in footer
$scripts = array(
'post' => array( array( 'post', 'media', 'async-upload', 'edit' ), array( 'jquery', 'wp-ajax-response', 'post', 'jquery-ui-autocomplete' ), 0, 1 ),
'media' => array( array( 'upload' ), array( 'jquery' ), 0, 1 ),
'term' => array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), 0, 1 ),
'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), 0, 0 ),
);
foreach ( $scripts as $script => $v ) {
if ( in_array( $screen->base, $v[0] ) && ( $v[2] || $this->model->get_languages_list() ) ) {
wp_enqueue_script( 'pll_' . $script, plugins_url( '/js/' . $script . $suffix . '.js', POLYLANG_FILE ), $v[1], POLYLANG_VERSION, $v[3] );
}
}
wp_enqueue_style( 'polylang_admin', plugins_url( '/css/admin' . $suffix . '.css', POLYLANG_FILE ), array(), POLYLANG_VERSION );
}
/**
* Sets pll_ajax_backend on all backend ajax request
* The final goal is to detect if an ajax request is made on admin or frontend
*
* Takes care to various situations:
* when the ajax request has no options.data thanks to ScreenfeedFr
* see: https://wordpress.org/support/topic/ajaxprefilter-may-not-work-as-expected
* when options.data is a json string
* see: https://wordpress.org/support/topic/polylang-breaking-third-party-ajax-requests-on-admin-panels
* when options.data is an empty string (GET request with the method 'load')
* see: https://wordpress.org/support/topic/invalid-url-during-wordpress-new-dashboard-widget-operation
*
* @since 1.4
*/
public function admin_print_footer_scripts() {
global $post_ID, $tag_ID;
$params = array( 'pll_ajax_backend' => 1 );
if ( ! empty( $post_ID ) ) {
$params = array_merge( $params, array( 'pll_post_id' => (int) $post_ID ) );
}
if ( ! empty( $tag_ID ) ) {
$params = array_merge( $params, array( 'pll_term_id' => (int) $tag_ID ) );
}
$str = http_build_query( $params );
$arr = json_encode( $params );
?>
<script type="text/javascript">
if (typeof jQuery != 'undefined') {
(function($){
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if ( -1 != options.url.indexOf( ajaxurl ) || -1 != ajaxurl.indexOf( options.url ) ) {
if ( 'undefined' === typeof options.data ) {
options.data = ( 'get' === options.type.toLowerCase() ) ? '<?php echo $str; ?>' : <?php echo $arr; ?>;
} else {
if ( 'string' === typeof options.data ) {
if ( '' === options.data && 'get' === options.type.toLowerCase() ) {
options.url = options.url+'&<?php echo $str; ?>';
} else {
try {
o = $.parseJSON(options.data);
o = $.extend(o, <?php echo $arr; ?>);
options.data = JSON.stringify(o);
}
catch(e) {
options.data = '<?php echo $str; ?>&'+options.data;
}
}
} else {
options.data = $.extend(options.data, <?php echo $arr; ?>);
}
}
}
});
})(jQuery)
}
</script>
<?php
}
/**
* Sets the admin current language, used to filter the content
*
* @since 2.0
*/
public function set_current_language() {
$this->curlang = $this->filter_lang;
// POST
if ( isset( $_POST['post_lang_choice'] ) && $lang = $this->model->get_language( $_POST['post_lang_choice'] ) ) {
$this->curlang = $lang;
} elseif ( isset( $_POST['term_lang_choice'] ) && $lang = $this->model->get_language( $_POST['term_lang_choice'] ) ) {
$this->curlang = $lang;
} elseif ( isset( $_POST['inline_lang_choice'] ) && $lang = $this->model->get_language( $_POST['inline_lang_choice'] ) ) {
$this->curlang = $lang;
}
// Edit Post
if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) {
$this->curlang = $lang;
} elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && is_numeric( $_GET['post'] ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) {
$this->curlang = $lang;
} elseif ( 'post-new.php' === $GLOBALS['pagenow'] && ( empty( $_GET['post_type'] ) || $this->model->is_translated_post_type( $_GET['post_type'] ) ) ) {
$this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( $_GET['new_lang'] );
}
// Edit Term
// FIXME 'edit-tags.php' for backward compatibility with WP < 4.5
elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) {
$this->curlang = $lang;
} elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) {
$this->curlang = $lang;
} elseif ( 'edit-tags.php' === $GLOBALS['pagenow'] && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( $_GET['taxonomy'] ) ) {
if ( ! empty( $_GET['new_lang'] ) ) {
$this->curlang = $this->model->get_language( $_GET['new_lang'] );
} elseif ( empty( $this->curlang ) ) {
$this->curlang = $this->pref_lang;
}
}
// Ajax
if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) {
$this->curlang = $this->model->get_language( $_REQUEST['lang'] );
}
}
/**
* Defines the backend language and the admin language filter based on user preferences
*
* @since 1.2.3
*/
public function init_user() {
// Backend locale
// FIXME: Backward compatibility with WP < 4.7
if ( version_compare( $GLOBALS['wp_version'], '4.7alpha', '<' ) ) {
add_filter( 'locale', array( $this, 'get_locale' ) );
}
// Language for admin language filter: may be empty
// $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter
if ( ! wp_doing_ajax() && ! empty( $_GET['lang'] ) && ! is_numeric( $_GET['lang'] ) && current_user_can( 'edit_user', $user_id = get_current_user_id() ) ) {
update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( $_GET['lang'] ) ) ? $lang->slug : '' );
}
$this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
// Set preferred language for use when saving posts and terms: must not be empty
$this->pref_lang = empty( $this->filter_lang ) ? $this->model->get_language( $this->options['default_lang'] ) : $this->filter_lang;
/**
* Filter the preferred language on amin side
* The preferred language is used for example to determine the language of a new post
*
* @since 1.2.3
*
* @param object $pref_lang preferred language
*/
$this->pref_lang = apply_filters( 'pll_admin_preferred_language', $this->pref_lang );
$this->set_current_language();
// Inform that the admin language has been set
// Only if the admin language is one of the Polylang defined language
// FIXME test get_user_locale for backward compatibility with WP 4.7
if ( $curlang = $this->model->get_language( function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale() ) ) {
// FIXME: Backward compatibility with WP < 4.7
if ( version_compare( $GLOBALS['wp_version'], '4.7alpha', '<' ) ) {
$GLOBALS['text_direction'] = $curlang->is_rtl ? 'rtl' : 'ltr'; // force text direction according to language setting
}
/** This action is documented in frontend/choose-lang.php */
do_action( 'pll_language_defined', $curlang->slug, $curlang );
} else {
/** This action is documented in include/class-polylang.php */
do_action( 'pll_no_language_defined' ); // to load overriden textdomains
}
}
/**
* Avoids parsing a tax query when all languages are requested
* Fixes https://wordpress.org/support/topic/notice-undefined-offset-0-in-wp-includesqueryphp-on-line-3877 introduced in WP 4.1
* @see the suggestion of @boonebgorges, https://core.trac.wordpress.org/ticket/31246
*
* @since 1.6.5
*
* @param array $qvars
* @return array
*/
public function request( $qvars ) {
if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
unset( $qvars['lang'] );
}
return $qvars;
}
/**
* Get the locale based on user preference
* FIXME: Backward compatibility with WP < 4.7
*
* @since 0.4
*
* @param string $locale
* @return string modified locale
*/
public function get_locale( $locale ) {
return ( $loc = get_user_meta( get_current_user_id(), 'locale', 'true' ) ) ? $loc : $locale;
}
/**
* Adds the languages list in admin bar for the admin languages filter
*
* @since 0.9
*
* @param object $wp_admin_bar
*/
public function admin_bar_menu( $wp_admin_bar ) {
$url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$all_item = (object) array(
'slug' => 'all',
'name' => __( 'Show all languages', 'polylang' ),
'flag' => '<span class="ab-icon"></span>',
);
$selected = empty( $this->filter_lang ) ? $all_item : $this->filter_lang;
$title = sprintf(
'<span class="ab-label"%1$s><span class="screen-reader-text">%2$s</span>%3$s</span>',
'all' === $selected->slug ? '' : sprintf( ' lang="%s"', esc_attr( $selected->get_locale( 'display' ) ) ),
__( 'Filters content by language', 'polylang' ),
esc_html( $selected->name )
);
$wp_admin_bar->add_menu( array(
'id' => 'languages',
'title' => $selected->flag . $title,
'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged', $url ) ) ),
'meta' => array( 'title' => __( 'Filters content by language', 'polylang' ) ),
) );
foreach ( array_merge( array( $all_item ), $this->model->get_languages_list() ) as $lang ) {
if ( $selected->slug === $lang->slug ) {
continue;
}
$wp_admin_bar->add_menu( array(
'parent' => 'languages',
'id' => $lang->slug,
'title' => $lang->flag . esc_html( $lang->name ),
'href' => esc_url( add_query_arg( 'lang', $lang->slug, remove_query_arg( 'paged', $url ) ) ),
'meta' => 'all' === $lang->slug ? array() : array( 'lang' => esc_attr( $lang->get_locale( 'display' ) ) ),
) );
}
}
}

View file

@ -0,0 +1,341 @@
<?php
/**
* adds the language column in posts and terms list tables
* manages quick edit and bulk edit as well
*
* @since 1.2
*/
class PLL_Admin_Filters_Columns {
public $links, $model, $filter_lang;
/**
* constructor: setups filters and actions
*
* @since 1.2
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
$this->links = &$polylang->links;
$this->model = &$polylang->model;
$this->filter_lang = &$polylang->filter_lang;
// add the language and translations columns in 'All Posts', 'All Pages' and 'Media library' panels
foreach ( $this->model->get_translated_post_types() as $type ) {
// use the latest filter late as some plugins purely overwrite what's done by others :(
// specific case for media
add_filter( 'manage_' . ( 'attachment' == $type ? 'upload' : 'edit-' . $type ) . '_columns', array( $this, 'add_post_column' ), 100 );
add_action( 'manage_' . ( 'attachment' == $type ? 'media' : $type . '_posts' ) . '_custom_column', array( $this, 'post_column' ), 10, 2 );
}
// quick edit and bulk edit
add_filter( 'quick_edit_custom_box', array( $this, 'quick_edit_custom_box' ), 10, 2 );
add_filter( 'bulk_edit_custom_box', array( $this, 'quick_edit_custom_box' ), 10, 2 );
// adds the language column in the 'Categories' and 'Post Tags' tables
foreach ( $this->model->get_translated_taxonomies() as $tax ) {
add_filter( 'manage_edit-' . $tax . '_columns', array( $this, 'add_term_column' ) );
add_filter( 'manage_' . $tax . '_custom_column', array( $this, 'term_column' ), 10, 3 );
}
// ajax responses to update list table rows
add_action( 'wp_ajax_pll_update_post_rows', array( $this, 'ajax_update_post_rows' ) );
add_action( 'wp_ajax_pll_update_term_rows', array( $this, 'ajax_update_term_rows' ) );
}
/**
* adds languages and translations columns in posts, pages, media, categories and tags tables
*
* @since 0.8.2
*
* @param array $columns List of table columns
* @param string $before The column before which we want to add our languages
* @return array modified list of columns
*/
protected function add_column( $columns, $before ) {
if ( $n = array_search( $before, array_keys( $columns ) ) ) {
$end = array_slice( $columns, $n );
$columns = array_slice( $columns, 0, $n );
}
foreach ( $this->model->get_languages_list() as $language ) {
// don't add the column for the filtered language
if ( empty( $this->filter_lang ) || $language->slug != $this->filter_lang->slug ) {
$columns[ 'language_' . $language->slug ] = $language->flag ? $language->flag . '<span class="screen-reader-text">' . esc_html( $language->name ) . '</span>' : esc_html( $language->slug );
}
}
return isset( $end ) ? array_merge( $columns, $end ) : $columns;
}
/**
* returns the first language column in the posts, pages and media library tables
*
* @since 0.9
*
* @return string first language column name
*/
protected function get_first_language_column() {
foreach ( $this->model->get_languages_list() as $language ) {
if ( empty( $this->filter_lang ) || $language->slug != $this->filter_lang->slug ) {
$columns[] = 'language_' . $language->slug;
}
}
return empty( $columns ) ? '' : reset( $columns );
}
/**
* adds the language and translations columns ( before the comments column ) in the posts, pages and media library tables
*
* @since 0.1
*
* @param array $columns list of posts table columns
* @return array modified list of columns
*/
function add_post_column( $columns ) {
return $this->add_column( $columns, 'comments' );
}
/**
* fills the language and translations columns in the posts, pages and media library tables
* take care that when doing ajax inline edit, the post may not be updated in database yet
*
* @since 0.1
*
* @param string $column Column name
* @param int $post_id
*/
public function post_column( $column, $post_id ) {
$inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save' === $_REQUEST['action'];
$lang = $inline ? $this->model->get_language( $_POST['inline_lang_choice'] ) : $this->model->post->get_language( $post_id );
if ( false === strpos( $column, 'language_' ) || ! $lang ) {
return;
}
$language = $this->model->get_language( substr( $column, 9 ) );
// hidden field containing the post language for quick edit
if ( $column == $this->get_first_language_column() ) {
printf( '<div class="hidden" id="lang_%d">%s</div>', intval( $post_id ), esc_html( $lang->slug ) );
}
$post_type_object = get_post_type_object( get_post_type( $post_id ) );
// link to edit post ( or a translation )
if ( $id = $this->model->post->get( $post_id, $language ) ) {
// get_edit_post_link returns nothing if the user cannot edit the post
// thanks to Solinx. See http://wordpress.org/support/topic/feature-request-incl-code-check-for-capabilities-in-admin-screens
if ( $link = get_edit_post_link( $id ) ) {
if ( $id === $post_id ) {
$class = 'pll_icon_tick';
/* translators: accessibility text, %s is a native language name */
$s = sprintf( __( 'Edit this item in %s', 'polylang' ), $language->name );
} else {
$class = esc_attr( 'pll_icon_edit translation_' . $id );
/* translators: accessibility text, %s is a native language name */
$s = sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name );
}
printf(
'<a class="%1$s" title="%2$s" href="%3$s"><span class="screen-reader-text">%4$s</span></a>',
esc_attr( $class ), esc_attr( get_post( $id )->post_title ), esc_url( $link ), esc_html( $s )
);
} elseif ( $id === $post_id ) {
printf(
'<span class="pll_icon_tick"><span class="screen-reader-text">%s</span></span>',
/* translators: accessibility text, %s is a native language name */
esc_html( sprintf( __( 'This item is in %s', 'polylang' ), $language->name ) )
);
}
}
// link to add a new translation
else {
echo $this->links->new_post_translation_link( $post_id, $language );
}
}
/**
* quick edit & bulk edit
*
* @since 0.9
*
* @param string $column column name
* @param string $type either 'edit-tags' for terms list table or post type for posts list table
* @return string unmodified $column
*/
public function quick_edit_custom_box( $column, $type ) {
if ( $column == $this->get_first_language_column() ) {
$elements = $this->model->get_languages_list();
if ( current_filter() == 'bulk_edit_custom_box' ) {
array_unshift( $elements, (object) array( 'slug' => -1, 'name' => __( '&mdash; No Change &mdash;' ) ) );
}
$dropdown = new PLL_Walker_Dropdown();
// the hidden field 'old_lang' allows to pass the old language to ajax request
printf(
'<fieldset class="inline-edit-col-left">
<div class="inline-edit-col">
<label class="alignleft">
<span class="title">%s</span>
%s
</label>
</div>
</fieldset>',
esc_html__( 'Language', 'polylang' ),
$dropdown->walk( $elements, array( 'name' => 'inline_lang_choice', 'id' => '' ) )
);
}
return $column;
}
/**
* adds the language column ( before the posts column ) in the 'Categories' or 'Post Tags' table
*
* @since 0.1
*
* @param array $columns list of terms table columns
* @return array modified list of columns
*/
public function add_term_column( $columns ) {
return $this->add_column( $columns, 'posts' );
}
/**
* fills the language column in the 'Categories' or 'Post Tags' table
*
* @since 0.1
*
* @param string $out
* @param string $column Column name
* @param int $term_id
*/
public function term_column( $out, $column, $term_id ) {
$inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save-tax' === $_REQUEST['action'];
if ( false === strpos( $column, 'language_' ) || ! ( $lang = $inline ? $this->model->get_language( $_POST['inline_lang_choice'] ) : $this->model->term->get_language( $term_id ) ) ) {
return $out;
}
$post_type = isset( $GLOBALS['post_type'] ) ? $GLOBALS['post_type'] : $_REQUEST['post_type']; // 2nd case for quick edit
$taxonomy = isset( $GLOBALS['taxonomy'] ) ? $GLOBALS['taxonomy'] : $_REQUEST['taxonomy'];
if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
return $out;
}
$term_id = (int) $term_id;
$language = $this->model->get_language( substr( $column, 9 ) );
if ( $column == $this->get_first_language_column() ) {
$out = sprintf( '<div class="hidden" id="lang_%d">%s</div>', intval( $term_id ), esc_html( $lang->slug ) );
// identify the default categories to disable the language dropdown in js
if ( in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) ) ) {
$out .= sprintf( '<div class="hidden" id="default_cat_%1$d">%1$d</div>', intval( $term_id ) );
}
}
// link to edit term ( or a translation )
if ( ( $id = $this->model->term->get( $term_id, $language ) ) && $term = get_term( $id, $taxonomy ) ) {
if ( $link = get_edit_term_link( $id, $taxonomy, $post_type ) ) {
if ( $id === $term_id ) {
$class = 'pll_icon_tick';
/* translators: accessibility text, %s is a native language name */
$s = sprintf( __( 'Edit this item in %s', 'polylang' ), $language->name );
} else {
$class = esc_attr( 'pll_icon_edit translation_' . $id );
/* translators: accessibility text, %s is a native language name */
$s = sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name );
}
$out .= sprintf(
'<a class="%1$s" title="%2$s" href="%3$s"><span class="screen-reader-text">%4$s</span></a>',
$class, esc_attr( $term->name ), esc_url( $link ), esc_html( $s )
);
} elseif ( $id === $term_id ) {
$out .= printf(
'<span class="pll_icon_tick"><span class="screen-reader-text">%s</span></span>',
/* translators: accessibility text, %s is a native language name */
esc_html( sprintf( __( 'This item is in %s', 'polylang' ), $language->name ) )
);
}
}
// link to add a new translation
else {
$out .= $this->links->new_term_translation_link( $term_id, $taxonomy, $post_type, $language );
}
return $out;
}
/**
* update rows of translated posts when the language is modified in quick edit
*
* @since 1.7
*/
public function ajax_update_post_rows() {
global $wp_list_table;
if ( ! post_type_exists( $post_type = $_POST['post_type'] ) || ! $this->model->is_translated_post_type( $post_type ) ) {
die( 0 );
}
check_ajax_referer( 'inlineeditnonce', '_pll_nonce' );
$x = new WP_Ajax_Response();
$wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
$translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // collect old translations
$translations = array_merge( $translations, array( $_POST['post_id'] ) ); // add current post
$translations = array_map( 'intval', $translations );
foreach ( $translations as $post_id ) {
$level = is_post_type_hierarchical( $post_type ) ? count( get_ancestors( $post_id, $post_type ) ) : 0;
if ( $post = get_post( $post_id ) ) {
ob_start();
$wp_list_table->single_row( $post, $level );
$data = ob_get_clean();
$x->add( array( 'what' => 'row', 'data' => $data, 'supplemental' => array( 'post_id' => $post_id ) ) );
}
}
$x->send();
}
/**
* update rows of translated terms when adding / deleting a translation or when the language is modified in quick edit
*
* @since 1.7
*/
public function ajax_update_term_rows() {
global $wp_list_table;
if ( ! taxonomy_exists( $taxonomy = $_POST['taxonomy'] ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
die( 0 );
}
check_ajax_referer( 'pll_language', '_pll_nonce' );
$x = new WP_Ajax_Response();
$wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
$translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // collect old translations
$translations = array_merge( $translations, $this->model->term->get_translations( (int) $_POST['term_id'] ) ); // add current translations
$translations = array_unique( $translations ); // remove doublons
$translations = array_map( 'intval', $translations );
foreach ( $translations as $term_id ) {
$level = is_taxonomy_hierarchical( $taxonomy ) ? count( get_ancestors( $term_id, $taxonomy ) ) : 0;
if ( $tag = get_term( $term_id, $taxonomy ) ) {
ob_start();
$wp_list_table->single_row( $tag, $level );
$data = ob_get_clean();
$x->add( array( 'what' => 'row', 'data' => $data, 'supplemental' => array( 'term_id' => $term_id ) ) );
}
}
$x->send();
}
}

View file

@ -0,0 +1,195 @@
<?php
/**
* Manages filters and actions related to media on admin side
* Capability to edit / create media is checked before loading this class
*
* @since 1.2
*/
class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
/**
* Constructor: setups filters and actions
*
* @since 1.2
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
parent::__construct( $polylang );
// Adds the language field and translations tables in the 'Edit Media' panel
add_filter( 'attachment_fields_to_edit', array( $this, 'attachment_fields_to_edit' ), 10, 2 );
// Adds actions related to languages when creating, saving or deleting media
add_action( 'add_attachment', array( $this, 'set_default_language' ) );
add_filter( 'attachment_fields_to_save', array( $this, 'save_media' ), 10, 2 );
add_filter( 'wp_delete_file', array( $this, 'wp_delete_file' ) );
// Creates a media translation
if ( isset( $_GET['action'], $_GET['new_lang'], $_GET['from_media'] ) && 'translate_media' === $_GET['action'] ) {
add_action( 'admin_init', array( $this, 'translate_media' ) );
}
}
/**
* Adds the language field and translations tables in the 'Edit Media' panel
* Needs WP 3.5+
*
* @since 0.9
*
* @param array $fields list of form fields
* @param object $post
* @return array modified list of form fields
*/
public function attachment_fields_to_edit( $fields, $post ) {
if ( 'post.php' == $GLOBALS['pagenow'] ) {
return $fields; // Don't add anything on edit media panel for WP 3.5+ since we have the metabox
}
$post_id = $post->ID;
$lang = $this->model->post->get_language( $post_id );
$dropdown = new PLL_Walker_Dropdown();
$fields['language'] = array(
'label' => __( 'Language', 'polylang' ),
'input' => 'html',
'html' => $dropdown->walk( $this->model->get_languages_list(), array(
'name' => sprintf( 'attachments[%d][language]', $post_id ),
'class' => 'media_lang_choice',
'selected' => $lang ? $lang->slug : '',
) ),
);
return $fields;
}
/**
* Creates a media translation
*
* @since 1.8
*
* @param int $post_id
* @param string|object $lang
* @return int id of the translated media
*/
public function create_media_translation( $post_id, $lang ) {
$post = get_post( $post_id );
if ( empty( $post ) ) {
return $post;
}
$lang = $this->model->get_language( $lang ); // Make sure we get a valid language slug
// Create a new attachment ( translate attachment parent if exists )
$post->ID = null; // Will force the creation
$post->post_parent = ( $post->post_parent && $tr_parent = $this->model->post->get_translation( $post->post_parent, $lang->slug ) ) ? $tr_parent : 0;
$post->tax_input = array( 'language' => array( $lang->slug ) ); // Assigns the language
$tr_id = wp_insert_attachment( $post );
// Copy metadata, attached file and alternative text
foreach ( array( '_wp_attachment_metadata', '_wp_attached_file', '_wp_attachment_image_alt' ) as $key ) {
if ( $meta = get_post_meta( $post_id, $key , true ) ) {
add_post_meta( $tr_id, $key, $meta );
}
}
$this->model->post->set_language( $tr_id, $lang );
$translations = $this->model->post->get_translations( $post_id );
if ( ! $translations && $src_lang = $this->model->post->get_language( $post_id ) ) {
$translations[ $src_lang->slug ] = $post_id;
}
$translations[ $lang->slug ] = $tr_id;
$this->model->post->save_translations( $tr_id, $translations );
/**
* Fires after a media translation is created
*
* @since 1.6.4
*
* @param int $post_id post id of the source media
* @param int $tr_id post id of the new media translation
* @param string $slug language code of the new translation
*/
do_action( 'pll_translate_media', $post_id, $tr_id, $lang->slug );
return $tr_id;
}
/**
* Creates a media translation
*
* @since 0.9
*/
public function translate_media() {
// Security check
check_admin_referer( 'translate_media' );
$post_id = (int) $_GET['from_media'];
// Bails if the translations already exists
// See https://wordpress.org/support/topic/edit-translation-in-media-attachments?#post-7322303
// Or if the source media does not exist
if ( $this->model->post->get_translation( $post_id, $_GET['new_lang'] ) || ! get_post( $post_id ) ) {
wp_safe_redirect( wp_get_referer() );
exit;
}
$tr_id = $this->create_media_translation( $post_id, $_GET['new_lang'] );
wp_safe_redirect( admin_url( sprintf( 'post.php?post=%d&action=edit', $tr_id ) ) ); // WP 3.5+
exit;
}
/**
* Called when a media is saved
* Saves language and translations
*
* @since 0.9
*
* @param array $post
* @param array $attachment
* @return array unmodified $post
*/
public function save_media( $post, $attachment ) {
// Language is filled in attachment by the function applying the filter 'attachment_fields_to_save'
// All security checks have been done by functions applying this filter
if ( ! empty( $attachment['language'] ) ) {
$this->model->post->set_language( $post['ID'], $attachment['language'] );
}
if ( isset( $_POST['media_tr_lang'] ) ) {
$this->save_translations( $post['ID'], $_POST['media_tr_lang'] );
}
return $post;
}
/**
* Prevents WP deleting files when there are still media using them
* Thanks to Bruno "Aesqe" Babic and its plugin file gallery in which I took all the ideas for this function
*
* @since 0.9
*
* @param string $file
* @return string unmodified $file
*/
public function wp_delete_file( $file ) {
global $wpdb;
$uploadpath = wp_upload_dir();
$ids = $wpdb->get_col( $wpdb->prepare( "
SELECT post_id FROM $wpdb->postmeta
WHERE meta_key = '_wp_attached_file' AND meta_value = '%s'",
substr_replace( $file, '', 0, strlen( trailingslashit( $uploadpath['basedir'] ) ) )
) );
if ( ! empty( $ids ) ) {
// Regenerate intermediate sizes if it's an image ( since we could not prevent WP deleting them before )
wp_update_attachment_metadata( $ids[0], wp_generate_attachment_metadata( $ids[0], $file ) );
return ''; // Prevent deleting the main file
}
return $file;
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* some common code for PLL_Admin_Filters_Post and PLL_Admin_Filters_Media
*
* @since 1.5
*/
abstract class PLL_Admin_Filters_Post_Base {
public $links, $model, $pref_lang;
/**
* constructor: setups filters and actions
*
* @since 1.2
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
$this->links = &$polylang->links;
$this->model = &$polylang->model;
$this->pref_lang = &$polylang->pref_lang;
}
/**
* allows to set a language by default for posts if it has no language yet
*
* @since 1.5
*
* @param int $post_id
*/
public function set_default_language( $post_id ) {
if ( ! $this->model->post->get_language( $post_id ) ) {
if ( isset( $_GET['new_lang'] ) && $lang = $this->model->get_language( $_GET['new_lang'] ) ) {
$this->model->post->set_language( $post_id, $lang );
}
elseif ( ( $parent_id = wp_get_post_parent_id( $post_id ) ) && $parent_lang = $this->model->post->get_language( $parent_id ) ) {
$this->model->post->set_language( $post_id, $parent_lang );
}
else {
$this->model->post->set_language( $post_id, $this->pref_lang );
}
}
}
/**
* save translations from language metabox
*
* @since 1.5
*
* @param int $post_id
* @param array $arr
* @return array
*/
protected function save_translations( $post_id, $arr ) {
// security check
// as 'wp_insert_post' can be called from outside WP admin
check_admin_referer( 'pll_language', '_pll_nonce' );
// save translations after checking the translated post is in the right language
foreach ( $arr as $lang => $tr_id ) {
$translations[ $lang ] = ( $tr_id && $this->model->post->get_language( (int) $tr_id )->slug == $lang ) ? (int) $tr_id : 0;
}
$this->model->post->save_translations( $post_id, $translations );
return $translations;
}
}

View file

@ -0,0 +1,546 @@
<?php
/**
* Manages filters and actions related to posts on admin side
*
* @since 1.2
*/
class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
public $options, $curlang;
/**
* Constructor: setups filters and actions
*
* @since 1.2
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
parent::__construct( $polylang );
$this->options = &$polylang->options;
$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 the Languages box in the 'Edit Post' and 'Edit Page' panels
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
// Ajax response for changing the language in the post metabox
add_action( 'wp_ajax_post_lang_choice', array( $this, 'post_lang_choice' ) );
add_action( 'wp_ajax_pll_posts_not_translated', array( $this, 'ajax_posts_not_translated' ) );
// Adds actions and filters related to languages when creating, saving or deleting posts and pages
add_action( 'save_post', array( $this, 'save_post' ), 21, 3 ); // Priority 21 to come after advanced custom fields ( 20 ) and before the event calendar which breaks everything after 25
add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 4 );
add_action( 'before_delete_post', array( $this, 'delete_post' ) );
if ( $this->options['media_support'] ) {
add_action( 'delete_attachment', array( $this, 'delete_post' ) ); // Action shared with media
}
// Filters the pages by language in the parent dropdown list in the page attributes metabox
add_filter( 'page_attributes_dropdown_pages_args', array( $this, 'page_attributes_dropdown_pages_args' ), 10, 2 );
// Sets the language in Tiny MCE
add_filter( 'tiny_mce_before_init', array( $this, 'tiny_mce_before_init' ) );
}
/**
* 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
*/
public function admin_enqueue_scripts() {
$screen = get_current_screen();
// Hierarchical taxonomies
if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'object' ) ) {
// Get translated hierarchical taxonomies
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( $hierarchical_taxonomies, array( 'get' => 'all' ) );
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 ) ) {
$pages = get_pages();
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 object $query a WP_Query object
*/
public function parse_query( $query ) {
$pll_query = new PLL_Query( $query, $this->model );
$pll_query->filter_query( $this->curlang );
}
/**
* Adds the Language box in the 'Edit Post' and 'Edit Page' panels ( as well as in custom post types panels )
*
* @since 0.1
*
* @param string $post_type Current post type
* @param object $post Current post
*/
public function add_meta_boxes( $post_type, $post ) {
if ( $this->model->is_translated_post_type( $post_type ) ) {
add_meta_box( 'ml_box', __( 'Languages','polylang' ), array( $this, 'post_language' ), $post_type, 'side', 'high' );
}
}
/**
* Displays the Languages metabox in the 'Edit Post' and 'Edit Page' panels
*
* @since 0.1
*/
public function post_language() {
global $post_ID;
$post_id = $post_ID;
$post_type = get_post_type( $post_ID );
$lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg :
( isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) :
$this->pref_lang );
$dropdown = new PLL_Walker_Dropdown();
wp_nonce_field( 'pll_language', '_pll_nonce' );
// NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js )
printf( '
<p><strong>%1$s</strong></p>
<label class="screen-reader-text" for="%2$s">%1$s</label>
<div id="select-%3$s-language">%4$s</div>',
esc_html__( 'Language', 'polylang' ),
$id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', $post_ID ) : 'post_lang_choice',
'attachment' === $post_type ? 'media' : 'post',
$dropdown->walk( $this->model->get_languages_list(), array(
'name' => $id,
'class' => 'post_lang_choice tags-input',
'selected' => $lang ? $lang->slug : '',
'flag' => true,
) )
);
/**
* Fires before displaying the list of translations in the Languages metabox for posts
*
* @since 1.8
*/
do_action( 'pll_before_post_translations', $post_type );
echo '<div id="post-translations" class="translations">';
if ( $lang ) {
include PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php';
}
echo '</div>' . "\n";
}
/**
* Ajax response for changing the language in the post metabox
*
* @since 0.2
*/
public function post_lang_choice() {
check_ajax_referer( 'pll_language', '_pll_nonce' );
global $post_ID; // Obliged to use the global variable for wp_popular_terms_checklist
$post_id = $post_ID = (int) $_POST['post_id'];
$lang = $this->model->get_language( $_POST['lang'] );
$post_type = $_POST['post_type'];
$post_type_object = get_post_type_object( $post_type );
if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
wp_die( -1 );
}
$this->model->post->set_language( $post_ID, $lang ); // Save language, useful to set the language when uploading media from post
ob_start();
if ( $lang ) {
include PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php';
}
$x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
ob_end_clean();
// Categories
if ( isset( $_POST['taxonomies'] ) ) {
// Not set for pages
foreach ( $_POST['taxonomies'] as $taxname ) {
$taxonomy = get_taxonomy( $taxname );
ob_start();
$popular_ids = wp_popular_terms_checklist( $taxonomy->name );
$supplemental['populars'] = ob_get_contents();
ob_end_clean();
ob_start();
// Use $post_ID to remember ckecked terms in case we come back to the original language
wp_terms_checklist( $post_ID, array( 'taxonomy' => $taxonomy->name, 'popular_cats' => $popular_ids ) );
$supplemental['all'] = ob_get_contents();
ob_end_clean();
$supplemental['dropdown'] = wp_dropdown_categories( array(
'taxonomy' => $taxonomy->name,
'hide_empty' => 0,
'name' => 'new' . $taxonomy->name . '_parent',
'orderby' => 'name',
'hierarchical' => 1,
'show_option_none' => '&mdash; ' . $taxonomy->labels->parent_item . ' &mdash;',
'echo' => 0,
) );
$x->Add( array( 'what' => 'taxonomy', 'data' => $taxonomy->name, 'supplemental' => $supplemental ) );
}
}
// Parent dropdown list ( only for hierarchical post types )
if ( in_array( $post_type, get_post_types( array( 'hierarchical' => true ) ) ) ) {
$post = get_post( $post_ID );
// Args and filter from 'page_attributes_meta_box' in wp-admin/includes/meta-boxes.php of WP 4.2.1
$dropdown_args = array(
'post_type' => $post->post_type,
'exclude_tree' => $post->ID,
'selected' => $post->post_parent,
'name' => 'parent_id',
'show_option_none' => __( '(no parent)' ),
'sort_column' => 'menu_order, post_title',
'echo' => 0,
);
/** This filter is documented in wp-admin/includes/meta-boxes.php */
$dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // Since WP 3.3
$x->Add( array( 'what' => 'pages', 'data' => wp_dropdown_pages( $dropdown_args ) ) );
}
// Flag
$x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
// Sample permalink
$x->Add( array( 'what' => 'permalink', 'data' => get_sample_permalink_html( $post_ID ) ) );
$x->send();
}
/**
* Ajax response for input in translation autocomplete input box
*
* @since 1.5
*/
public function ajax_posts_not_translated() {
check_ajax_referer( 'pll_language', '_pll_nonce' );
if ( ! post_type_exists( $_GET['post_type'] ) ) {
die( 0 );
}
$post_language = $this->model->get_language( $_GET['post_language'] );
$translation_language = $this->model->get_language( $_GET['translation_language'] );
// Don't order by title: see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
$args = array(
's' => wp_unslash( $_GET['term'] ),
'suppress_filters' => 0, // To make the post_fields filter work
'lang' => 0, // Avoid admin language filter
'numberposts' => 20, // Limit to 20 posts
'post_status' => 'any',
'post_type' => $_GET['post_type'],
'tax_query' => array(
array(
'taxonomy' => 'language',
'field' => 'term_taxonomy_id', // WP 3.5+
'terms' => $translation_language->term_taxonomy_id,
),
),
);
/**
* Filter the query args when auto suggesting untranslated posts in the Languages metabox
* This should help plugins to fix some edge cases
*
* @see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
*
* @since 1.7
*
* @param array $args WP_Query arguments
*/
$args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
$posts = get_posts( $args );
$return = array();
foreach ( $posts as $key => $post ) {
if ( ! $this->model->post->get_translation( $post->ID, $post_language ) ) {
$return[] = array(
'id' => $post->ID,
'value' => $post->post_title,
'link' => $this->links->edit_post_translation_link( $post->ID ),
);
}
}
// Add current translation in list
if ( $post_id = $this->model->post->get_translation( (int) $_GET['pll_post_id'], $translation_language ) ) {
$post = get_post( $post_id );
array_unshift( $return, array(
'id' => $post_id,
'value' => $post->post_title,
'link' => $this->links->edit_post_translation_link( $post_id ),
) );
}
wp_die( json_encode( $return ) );
}
/**
* Saves language
* Checks the terms saved are in the right language
*
* @since 1.5
*
* @param int $post_id
* @param array $post
*/
protected function save_language( $post_id, $post ) {
// Security checks are necessary to accept language modifications as 'wp_insert_post' can be called from outside WP admin
// Edit post
if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $_POST['post_ID'] == $post_id ) {
check_admin_referer( 'pll_language', '_pll_nonce' );
$this->model->post->set_language( $post_id, $lang = $this->model->get_language( $_POST['post_lang_choice'] ) );
}
// Quick edit and bulk edit
// POST for quick edit, GET for bulk edit
elseif ( isset( $_REQUEST['inline_lang_choice'] ) ) {
// Bulk edit does not modify the language
if ( isset( $_GET['bulk_edit'] ) && -1 == $_REQUEST['inline_lang_choice'] ) {
check_admin_referer( 'bulk-posts' );
$lang = $this->model->post->get_language( $post_id ); // Get the post language for later use when saving terms
}
// A language is set in the language dropdown
else {
isset( $_GET['bulk_edit'] ) ? check_admin_referer( 'bulk-posts' ) : check_admin_referer( 'inlineeditnonce', '_inline_edit' );
$old_lang = $this->model->post->get_language( $post_id ); // Stores the old language
$this->model->post->set_language( $post_id, $lang = $this->model->get_language( $_REQUEST['inline_lang_choice'] ) ); // set new language
// Checks if the new language already exists in the translation group
if ( $old_lang && $old_lang->slug != $lang->slug ) {
$translations = $this->model->post->get_translations( $post_id );
// If yes, separate this post from the translation group
if ( array_key_exists( $lang->slug, $translations ) ) {
$this->model->post->delete_translation( $post_id );
}
elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
unset( $translations[ $old_lang->slug ] );
$this->model->post->save_translations( $post_id, $translations );
}
}
}
}
// Quick press
// 'post-quickpress-save', 'post-quickpress-publish' = backward compatibility WP < 3.8
elseif ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], array( 'post-quickpress-save', 'post-quickpress-publish', 'post-quickdraft-save' ) ) ) {
check_admin_referer( 'add-' . $post->post_type );
$this->model->post->set_language( $post_id, $lang = $this->pref_lang ); // Default language for Quick draft
}
else {
$this->set_default_language( $post_id );
}
// Make sure we get save terms in the right language (especially tags with same name in different languages)
if ( ! empty( $lang ) ) {
// FIXME quite a lot of queries in foreach
foreach ( $this->model->get_translated_taxonomies() as $tax ) {
$terms = get_the_terms( $post_id, $tax );
if ( is_array( $terms ) ) {
$newterms = array();
foreach ( $terms as $term ) {
// Check if the term is in the correct language or if a translation exist ( mainly for default category )
if ( $newterm = $this->model->term->get( $term->term_id, $lang ) ) {
$newterms[] = (int) $newterm;
}
// Or choose the correct language for tags ( initially defined by name )
elseif ( $newterm = $this->model->term_exists( $term->name, $tax, $term->parent, $lang ) ) {
$newterms[] = (int) $newterm; // Cast is important otherwise we get 'numeric' tags
}
// Or create the term in the correct language
elseif ( ! is_wp_error( $term_info = wp_insert_term( $term->name, $tax ) ) ) {
$newterms[] = (int) $term_info['term_id'];
}
}
wp_set_object_terms( $post_id, $newterms, $tax );
}
}
}
}
/**
* Called when a post ( or page ) is saved, published or updated
* saves languages and translations
*
* @since 0.1
*
* @param int $post_id
* @param object $post
* @param bool $update Whether it is an update or not
*/
public function save_post( $post_id, $post, $update ) {
// Does nothing except on post types which are filterable
if ( ! $this->model->is_translated_post_type( $post->post_type ) ) {
return;
}
if ( $id = wp_is_post_revision( $post_id ) ) {
$post_id = $id;
}
// Capability check
// As 'wp_insert_post' can be called from outside WP admin
$post_type_object = get_post_type_object( $post->post_type );
if ( ( $update && current_user_can( $post_type_object->cap->edit_post, $post_id ) ) || ( ! $update && current_user_can( $post_type_object->cap->create_posts ) ) ) {
$this->save_language( $post_id, $post );
// Make sure we are saving translations only for the main post currently being edited
if ( isset( $_POST['post_tr_lang'], $_POST['post_ID'] ) && $_POST['post_ID'] == $post_id ) {
$translations = $this->save_translations( $post_id, $_POST['post_tr_lang'] );
}
/**
* Fires after the post language and translations are saved
*
* @since 1.2
*
* @param int $post_id Post id
* @param object $post Post object
* @param array $translations The list of translations post ids
*/
do_action( 'pll_save_post', $post_id, $post, empty( $translations ) ? $this->model->post->get_translations( $post_id ) : $translations );
}
// Attempts to set a default language even if no capability
else {
$this->set_default_language( $post_id );
}
}
/**
* Make sure that the post parent is in the correct language when using bulk edit
*
* @since 1.8
*
* @param int $post_parent Post parent ID.
* @param int $post_id Post ID.
* @param array $new_postarr Array of parsed post data.
* @param array $postarr Array of sanitized, but otherwise unmodified post data.
* @return int
*/
public function wp_insert_post_parent( $post_parent, $post_id, $new_postarr, $postarr ) {
if ( isset( $postarr['bulk_edit'] ) ) {
check_admin_referer( 'bulk-posts' );
$lang = -1 == $postarr['inline_lang_choice'] ?
$this->model->post->get_language( $post_id ) :
$this->model->get_language( $postarr['inline_lang_choice'] );
$post_parent = $this->model->post->get_translation( $post_parent, $lang );
}
return $post_parent;
}
/**
* Called when a post, page or media is deleted
* Don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
* http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
*
* @since 0.1
*
* @param int $post_id
*/
public function delete_post( $post_id ) {
if ( ! wp_is_post_revision( $post_id ) ) {
$this->model->post->delete_translation( $post_id );
}
}
/**
* Filters the pages by language in the parent dropdown list in the page attributes metabox
*
* @since 0.6
*
* @param array $dropdown_args Arguments passed to wp_dropdown_pages
* @param object $post
* @return array Modified arguments
*/
public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) {
$dropdown_args['lang'] = isset( $_POST['lang'] ) ? $this->model->get_language( $_POST['lang'] ) : $this->model->post->get_language( $post->ID ); // ajax or not ?
if ( ! $dropdown_args['lang'] ) {
$dropdown_args['lang'] = $this->pref_lang;
}
return $dropdown_args;
}
/**
* 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;
}
}

View file

@ -0,0 +1,729 @@
<?php
/**
* Manages filters and actions related to terms on admin side
*
* @since 1.2
*/
class PLL_Admin_Filters_Term {
public $links, $model, $options, $curlang, $filter_lang, $pref_lang;
protected $pre_term_name; // Used to store the term name before creating a slug if needed
protected $post_id; // Used to store the current post_id when bulk editing posts
/**
* Constructor: setups filters and actions
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
$this->links = &$polylang->links;
$this->model = &$polylang->model;
$this->options = &$polylang->options;
$this->curlang = &$polylang->curlang;
$this->filter_lang = &$polylang->filter_lang;
$this->pref_lang = &$polylang->pref_lang;
foreach ( $this->model->get_translated_taxonomies() as $tax ) {
// Adds the language field in the 'Categories' and 'Post Tags' panels
add_action( $tax . '_add_form_fields', array( $this, 'add_term_form' ) );
// Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
add_action( $tax . '_edit_form_fields', array( $this, 'edit_term_form' ) );
// Adds action related to languages when deleting categories and post tags
add_action( 'delete_' . $tax, array( $this, 'delete_term' ) );
}
// Adds actions related to languages when creating or saving categories and post tags
add_filter( 'wp_dropdown_cats', array( $this, 'wp_dropdown_cats' ) );
add_action( 'create_term', array( $this, 'save_term' ), 999, 3 );
add_action( 'edit_term', array( $this, 'save_term' ), 999, 3 ); // late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast
add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
add_filter( 'pre_term_name', array( $this, 'pre_term_name' ) );
add_filter( 'pre_term_slug', array( $this, 'pre_term_slug' ), 10, 2 );
// Ajax response for edit term form
add_action( 'wp_ajax_term_lang_choice', array( $this, 'term_lang_choice' ) );
add_action( 'wp_ajax_pll_terms_not_translated', array( $this, 'ajax_terms_not_translated' ) );
// Adds cache domain when querying terms
add_filter( 'get_terms_args', array( $this, 'get_terms_args' ), 10, 2 );
// Filters categories and post tags by language
add_filter( 'terms_clauses', array( $this, 'terms_clauses' ), 10, 3 );
// Allows to get the default categories in all languages
add_filter( 'option_default_category', array( $this, 'option_default_category' ) );
add_action( 'update_option_default_category', array( $this, 'update_option_default_category' ), 10, 2 );
// Updates the translations term ids when splitting a shared term
add_action( 'split_shared_term', array( $this, 'split_shared_term' ), 10, 4 ); // WP 4.2
}
/**
* Adds the language field in the 'Categories' and 'Post Tags' panels
*
* @since 0.1
*/
public function add_term_form() {
$taxonomy = $_GET['taxonomy'];
$post_type = isset( $GLOBALS['post_type'] ) ? $GLOBALS['post_type'] : $_REQUEST['post_type'];
if ( ! taxonomy_exists( $taxonomy ) || ! post_type_exists( $post_type ) ) {
return;
}
$lang = isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) : $this->pref_lang;
$dropdown = new PLL_Walker_Dropdown();
wp_nonce_field( 'pll_language', '_pll_nonce' );
printf( '
<div class="form-field">
<label for="term_lang_choice">%s</label>
<div id="select-add-term-language">%s</div>
<p>%s</p>
</div>',
esc_html__( 'Language', 'polylang' ),
$dropdown->walk( $this->model->get_languages_list(), array(
'name' => 'term_lang_choice',
'value' => 'term_id',
'selected' => $lang ? $lang->term_id : '',
'flag' => true,
) ),
esc_html__( 'Sets the language', 'polylang' )
);
if ( ! empty( $_GET['from_tag'] ) ) {
printf( '<input type="hidden" name="from_tag" value="%d" />', (int) $_GET['from_tag'] );
}
// Adds translation fields
echo '<div id="term-translations" class="form-field">';
if ( $lang ) {
include PLL_ADMIN_INC . '/view-translations-term.php';
}
echo '</div>' . "\n";
}
/**
* Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
*
* @since 0.1
*
* @param object $tag
*/
public function edit_term_form( $tag ) {
$post_type = isset( $GLOBALS['post_type'] ) ? $GLOBALS['post_type'] : $_REQUEST['post_type'];
if ( ! post_type_exists( $post_type ) ) {
return;
}
$term_id = $tag->term_id;
$taxonomy = $tag->taxonomy;
$lang = $this->model->term->get_language( $term_id );
$lang = empty( $lang ) ? $this->pref_lang : $lang;
$dropdown = new PLL_Walker_Dropdown();
// Disable the language dropdown and the translations input fields for default categories to prevent removal
$disabled = in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) );
printf( '
<tr class="form-field">
<th scope="row">
%s
<label for="term_lang_choice">%s</label>
</th>
<td id="select-edit-term-language">
%s<br />
<p class="description">%s</p>
</td>
</tr>',
wp_nonce_field( 'pll_language', '_pll_nonce', true , false ),
esc_html__( 'Language', 'polylang' ),
$dropdown->walk( $this->model->get_languages_list(), array(
'name' => 'term_lang_choice',
'value' => 'term_id',
'selected' => $lang ? $lang->term_id : '',
'disabled' => $disabled,
'flag' => true,
) ),
esc_html__( 'Sets the language', 'polylang' )
);
echo '<tr id="term-translations" class="form-field">';
if ( $lang ) {
include PLL_ADMIN_INC . '/view-translations-term.php';
}
echo '</tr>' . "\n";
}
/**
* Translates term parent if exists when using "Add new" ( translation )
*
* @since 0.7
*
* @param string $output html markup for dropdown list of categories
* @return string modified html
*/
public function wp_dropdown_cats( $output ) {
if ( isset( $_GET['taxonomy'], $_GET['from_tag'], $_GET['new_lang'] ) && taxonomy_exists( $_GET['taxonomy'] ) ) {
$term = get_term( (int) $_GET['from_tag'], $_GET['taxonomy'] );
if ( $term && $id = $term->parent ) {
$lang = $this->model->get_language( $_GET['new_lang'] );
if ( $parent = $this->model->term->get_translation( $id, $lang ) ) {
return str_replace( '"' . $parent . '"', '"' . $parent . '" selected="selected"', $output );
}
}
}
return $output;
}
/**
* stores the current post_id when bulk editing posts for use in save_language and pre_term_slug
*
* @since 1.7
*
* @param int $post_id
*/
public function pre_post_update( $post_id ) {
if ( isset( $_GET['bulk_edit'] ) ) {
$this->post_id = $post_id;
}
}
/**
* Allows to set a language by default for terms if it has no language yet
*
* @since 1.5.4
*
* @param int $term_id
* @param string $taxonomy
*/
protected function set_default_language( $term_id, $taxonomy ) {
if ( ! $this->model->term->get_language( $term_id ) ) {
// Sets language from term parent if exists thanks to Scott Kingsley Clark
if ( ( $term = get_term( $term_id, $taxonomy ) ) && ! empty( $term->parent ) && $parent_lang = $this->model->term->get_language( $term->parent ) ) {
$this->model->term->set_language( $term_id, $parent_lang );
}
else {
$this->model->term->set_language( $term_id, $this->pref_lang );
}
}
}
/**
* Saves language
*
* @since 1.5
*
* @param int $term_id
* @param string $taxonomy
*/
protected function save_language( $term_id, $taxonomy ) {
global $wpdb;
// Security checks are necessary to accept language modifications
// as 'wp_update_term' can be called from outside WP admin
// Edit tags
if ( isset( $_POST['term_lang_choice'] ) ) {
if ( 'add-' . $taxonomy == $_POST['action'] ) {
check_ajax_referer( $_POST['action'], '_ajax_nonce-add-' . $taxonomy ); // category metabox
}
else {
check_admin_referer( 'pll_language', '_pll_nonce' ); // edit tags or tags metabox
}
$this->model->term->set_language( $term_id, $this->model->get_language( $_POST['term_lang_choice'] ) );
}
// *Post* bulk edit, in case a new term is created
elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
check_admin_referer( 'bulk-posts' );
// Bulk edit does not modify the language
// So we possibly create a tag in several languages
if ( -1 == $_GET['inline_lang_choice'] ) {
// The language of the current term is set a according to the language of the current post
$this->model->term->set_language( $term_id, $this->model->post->get_language( $this->post_id ) );
$term = get_term( $term_id, $taxonomy );
// Get all terms with the same name
// FIXME backward compatibility WP < 4.2
// No WP function to get all terms with the exact same name so let's use a custom query
// $terms = get_terms( $taxonomy, array( 'name' => $term->name, 'hide_empty' => false, 'fields' => 'ids' ) ); should be OK in 4.2
// I may need to rework the loop below
$terms = $wpdb->get_results( $wpdb->prepare( "
SELECT t.term_id FROM $wpdb->terms AS t
INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
WHERE tt.taxonomy = %s AND t.name = %s",
$taxonomy, $term->name
) );
// If we have several terms with the same name, they are translations of each other
if ( count( $terms ) > 1 ) {
foreach ( $terms as $term ) {
$translations[ $this->model->term->get_language( $term->term_id )->slug ] = $term->term_id;
}
$this->model->term->save_translations( $term_id, $translations );
}
}
else {
$this->model->term->set_language( $term_id, $this->model->get_language( $_GET['inline_lang_choice'] ) );
}
}
// Quick edit
elseif ( isset( $_POST['inline_lang_choice'] ) ) {
check_ajax_referer(
isset( $_POST['action'] ) && 'inline-save' == $_POST['action'] ? 'inlineeditnonce' : 'taxinlineeditnonce', // Post quick edit or tag quick edit ?
'_inline_edit'
);
$old_lang = $this->model->term->get_language( $term_id ); // Stores the old language
$lang = $this->model->get_language( $_POST['inline_lang_choice'] ); // New language
$translations = $this->model->term->get_translations( $term_id );
// Checks if the new language already exists in the translation group
if ( $old_lang && $old_lang->slug != $lang->slug ) {
if ( array_key_exists( $lang->slug, $translations ) ) {
$this->model->term->delete_translation( $term_id );
}
elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
unset( $translations[ $old_lang->slug ] );
$this->model->term->save_translations( $term_id, $translations );
}
}
$this->model->term->set_language( $term_id, $lang ); // Set new language
}
// Edit post
elseif ( isset( $_POST['post_lang_choice'] ) ) { // FIXME should be useless now
check_admin_referer( 'pll_language', '_pll_nonce' );
$this->model->term->set_language( $term_id, $this->model->get_language( $_POST['post_lang_choice'] ) );
}
else {
$this->set_default_language( $term_id, $taxonomy );
}
}
/**
* Save translations from our form
*
* @since 1.5
*
* @param int $term_id
* @return array
*/
protected function save_translations( $term_id ) {
// Security check as 'wp_update_term' can be called from outside WP admin
check_admin_referer( 'pll_language', '_pll_nonce' );
// Save translations after checking the translated term is in the right language ( as well as cast id to int )
foreach ( $_POST['term_tr_lang'] as $lang => $tr_id ) {
$tr_lang = $this->model->term->get_language( (int) $tr_id );
$translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? (int) $tr_id : 0;
}
$this->model->term->save_translations( $term_id, $translations );
return $translations;
}
/**
* Called when a category or post tag is created or edited
* Saves language and translations
*
* @since 0.1
*
* @param int $term_id
* @param int $tt_id term taxononomy id
* @param string $taxonomy
*/
public function save_term( $term_id, $tt_id, $taxonomy ) {
// Does nothing except on taxonomies which are filterable
if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
return;
}
// Capability check
// As 'wp_update_term' can be called from outside WP admin
// 2nd test for creating tags when creating / editing a post
$tax = get_taxonomy( $taxonomy );
if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) {
$this->save_language( $term_id, $taxonomy );
if ( isset( $_POST['term_tr_lang'] ) ) {
$translations = $this->save_translations( $term_id );
}
/**
* Fires after the term language and translations are saved
*
* @since 1.2
*
* @param int $term_id term id
* @param string $taxonomy taxonomy name
* @param array $translations the list of translations term ids
*/
do_action( 'pll_save_term', $term_id, $taxonomy, empty( $translations ) ? $this->model->term->get_translations( $term_id ) : $translations );
}
// Attempts to set a default language even if no capability
else {
$this->set_default_language( $term_id, $taxonomy );
}
}
/**
* Stores the term name for use in pre_term_slug
*
* @since 0.9.5
*
* @param string $name term name
* @return string unmodified term name
*/
public function pre_term_name( $name ) {
return $this->pre_term_name = $name;
}
/**
* Creates the term slug in case the term already exists in another language
*
* @since 0.9.5
*
* @param string $slug
* @param string $taxonomy
* @return string
*/
public function pre_term_slug( $slug, $taxonomy ) {
$name = sanitize_title( $this->pre_term_name );
// If the term already exists in another language
if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
if ( isset( $_POST['term_lang_choice'] ) ) {
$slug = $name . '-' . $this->model->get_language( $_POST['term_lang_choice'] )->slug;
}
elseif ( isset( $_POST['inline_lang_choice'] ) ) {
$slug = $name . '-' . $this->model->get_language( $_POST['inline_lang_choice'] )->slug;
}
// *Post* bulk edit, in case a new term is created
elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
// Bulk edit does not modify the language
if ( -1 == $_GET['inline_lang_choice'] ) {
$slug = $name . '-' . $this->model->post->get_language( $this->post_id )->slug;
} else {
$slug = $name . '-' . $this->model->get_language( $_GET['inline_lang_choice'] )->slug;
}
}
}
return $slug;
}
/**
* Called when a category or post tag is deleted
* Deletes language and translations
*
* @since 0.1
*
* @param int $term_id
*/
public function delete_term( $term_id ) {
$this->model->term->delete_translation( $term_id );
$this->model->term->delete_language( $term_id );
}
/**
* Ajax response for edit term form
*
* @since 0.2
*/
public function term_lang_choice() {
check_ajax_referer( 'pll_language', '_pll_nonce' );
$lang = $this->model->get_language( $_POST['lang'] );
$term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : null;
$taxonomy = $_POST['taxonomy'];
$post_type = $_POST['post_type'];
if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
die( 0 );
}
ob_start();
if ( $lang ) {
include PLL_ADMIN_INC . '/view-translations-term.php';
}
$x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
ob_end_clean();
// Parent dropdown list ( only for hierarchical taxonomies )
// $args copied from edit_tags.php except echo
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
$args = array(
'hide_empty' => 0,
'hide_if_empty' => false,
'taxonomy' => $taxonomy,
'name' => 'parent',
'orderby' => 'name',
'hierarchical' => true,
'show_option_none' => __( 'None' ),
'echo' => 0,
);
$x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) );
}
// Tag cloud
// Tests copied from edit_tags.php
else {
$tax = get_taxonomy( $taxonomy );
if ( ! is_null( $tax->labels->popular_items ) ) {
$args = array( 'taxonomy' => $taxonomy, 'echo' => false );
if ( current_user_can( $tax->cap->edit_terms ) ) {
$args = array_merge( $args, array( 'link' => 'edit' ) );
}
if ( $tag_cloud = wp_tag_cloud( $args ) ) {
$html = sprintf( '<div class="tagcloud"><h2>%1$s</h2>%2$s</div>', esc_html( $tax->labels->popular_items ), $tag_cloud );
$x->Add( array( 'what' => 'tag_cloud', 'data' => $html ) );
}
}
}
// Flag
$x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
$x->send();
}
/**
* Ajax response for input in translation autocomplete input box
*
* @since 1.5
*/
public function ajax_terms_not_translated() {
check_ajax_referer( 'pll_language', '_pll_nonce' );
$s = wp_unslash( $_GET['term'] );
$post_type = $_GET['post_type'];
$taxonomy = $_GET['taxonomy'];
if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
die( 0 );
}
$term_language = $this->model->get_language( $_GET['term_language'] );
$translation_language = $this->model->get_language( $_GET['translation_language'] );
$return = array();
// It is more efficient to use one common query for all languages as soon as there are more than 2
foreach ( get_terms( $taxonomy, 'hide_empty=0&lang=0&name__like=' . $s ) as $term ) {
$lang = $this->model->term->get_language( $term->term_id );
if ( $lang && $lang->slug == $translation_language->slug && ! $this->model->term->get_translation( $term->term_id, $term_language ) ) {
$return[] = array(
'id' => $term->term_id,
'value' => $term->name,
'link' => $this->links->edit_term_translation_link( $term->term_id, $taxonomy, $post_type ),
);
}
}
// Add current translation in list
// Not in add term for as term_id is not set
if ( 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) {
$term = get_term( $term_id, $taxonomy );
array_unshift( $return, array(
'id' => $term_id,
'value' => $term->name,
'link' => $this->links->edit_term_translation_link( $term->term_id, $taxonomy, $post_type ),
) );
}
wp_die( json_encode( $return ) );
}
/**
* Get the language(s) to filter get_terms
*
* @since 1.7.6
*
* @param array $taxonomies queried taxonomies
* @param array $args get_terms arguments
* @return object|string|bool the language(s) to use in the filter, false otherwise
*/
protected function get_queried_language( $taxonomies, $args ) {
// Does nothing except on taxonomies which are filterable
// Since WP 4.7, make sure not to filter wp_get_object_terms()
if ( ! $this->model->is_translated_taxonomy( $taxonomies ) || ! empty( $args['object_ids'] ) ) {
return false;
}
// If get_terms is queried with a 'lang' parameter
if ( isset( $args['lang'] ) ) {
return $args['lang'];
}
// On tags page, everything should be filtered according to the admin language filter except the parent dropdown
if ( 'edit-tags.php' === $GLOBALS['pagenow'] && empty( $args['class'] ) ) {
return $this->filter_lang;
}
return $this->curlang;
}
/**
* Adds language dependent cache domain when querying terms
* Useful as the 'lang' parameter is not included in cache key by WordPress
*
* @since 1.3
*
* @param array $args
* @param array $taxonomies
* @return array modified arguments
*/
public function get_terms_args( $args, $taxonomies ) {
// don't break _get_term_hierarchy()
if ( 'all' === $args['get'] && 'id' === $args['orderby'] && 'id=>parent' === $args['fields'] ) {
$args['lang'] = '';
}
if ( $lang = $this->get_queried_language( $taxonomies, $args ) ) {
$key = '_' . ( is_array( $lang ) ? implode( ',', $lang ) : $this->model->get_language( $lang )->slug );
$args['cache_domain'] = empty( $args['cache_domain'] ) ? 'pll' . $key : $args['cache_domain'] . $key;
}
return $args;
}
/**
* Filters categories and post tags by language(s) when needed on admin side
*
* @since 0.5
*
* @param array $clauses list of sql clauses
* @param array $taxonomies list of taxonomies
* @param array $args get_terms arguments
* @return array modified sql clauses
*/
public function terms_clauses( $clauses, $taxonomies, $args ) {
$lang = $this->get_queried_language( $taxonomies, $args );
return ! empty( $lang ) ? $this->model->terms_clauses( $clauses, $lang ) : $clauses; // adds our clauses to filter by current language
}
/**
* Hack to avoid displaying delete link for the default category in all languages
* Also returns the default category in the right language when called from wp_delete_term
*
* @since 1.2
*
* @param int $value
* @return int
*/
public function option_default_category( $value ) {
// Filters the default category in note below the category list table and in settings->writing dropdown
if ( isset( $this->pref_lang ) && $tr = $this->model->term->get( $value, $this->pref_lang ) ) {
$value = $tr;
}
// FIXME backward compatibility with WP < 4.7
if ( version_compare( $GLOBALS['wp_version'], '4.7alpha', '<' ) ) {
$traces = debug_backtrace();
$n = version_compare( PHP_VERSION, '7', '>=' ) ? 3 : 4; // PHP 7 does not include call_user_func_array
if ( isset( $traces[ $n ] ) ) {
// FIXME 'column_name' for backward compatibility with WP < 4.3
if ( in_array( $traces[ $n ]['function'], array( 'column_cb', 'column_name', 'handle_row_actions' ) ) && in_array( $traces[ $n ]['args'][0]->term_id, $this->model->term->get_translations( $value ) ) ) {
return $traces[ $n ]['args'][0]->term_id;
}
if ( 'wp_delete_term' == $traces[ $n ]['function'] ) {
return $this->model->term->get( $value, $this->model->term->get_language( $traces[ $n ]['args'][0] ) );
}
}
}
return $value;
}
/**
* Checks if the new default category is translated in all languages
* If not, create the translations
*
* @since 1.7
*
* @param int $old_value
* @param int $value
*/
public function update_option_default_category( $old_value, $value ) {
$default_cat_lang = $this->model->term->get_language( $value );
// Assign a default language to default category
if ( ! $default_cat_lang ) {
$default_cat_lang = $this->model->get_language( $this->options['default_lang'] );
$this->model->term->set_language( (int) $value, $default_cat_lang );
}
foreach ( $this->model->get_languages_list() as $language ) {
if ( $language->slug != $default_cat_lang->slug && ! $this->model->term->get_translation( $value, $language ) ) {
$this->model->create_default_category( $language );
}
}
}
/**
* Updates the translations term ids when splitting a shared term
* Splits translations if these are shared terms too
*
* @since 1.7
*
* @param int $term_id Shared term_id
* @param int $new_term_id
* @param int $term_taxonomy_id
* @param string $taxonomy
*/
public function split_shared_term( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
return;
}
// Avoid recursion
static $avoid_recursion = false;
if ( $avoid_recursion ) {
return;
}
$avoid_recursion = true;
$lang = $this->model->term->get_language( $term_id );
foreach ( $this->model->term->get_translations( $term_id ) as $key => $tr_id ) {
if ( $lang->slug == $key ) {
$translations[ $key ] = $new_term_id;
}
else {
$tr_term = get_term( $tr_id, $taxonomy );
$translations[ $key ] = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id );
// Hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) {
$_POST['term_tr_lang'][ $key ] = $translations[ $key ];
}
}
$this->model->term->set_language( $translations[ $key ], $key );
}
$this->model->term->save_translations( $new_term_id, $translations );
$avoid_recursion = false;
}
}

View file

@ -0,0 +1,281 @@
<?php
/**
* Setup miscellaneous admin filters as well as filters common to admin and frontend
*
* @since 1.2
*/
class PLL_Admin_Filters extends PLL_Filters {
/**
* Constructor: setups filters and actions
*
* @since 1.2
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
parent::__construct( $polylang );
// Widgets languages filter
add_action( 'in_widget_form', array( $this, 'in_widget_form' ), 10, 3 );
add_filter( 'widget_update_callback', array( $this, 'widget_update_callback' ), 10, 4 );
// Language management for users
add_action( 'personal_options_update', array( $this, 'personal_options_update' ) );
add_action( 'edit_user_profile_update', array( $this, 'personal_options_update' ) );
add_action( 'personal_options', array( $this, 'personal_options' ) );
// Ugrades languages files after a core upgrade ( timing is important )
// Backward compatibility WP < 4.0 *AND* Polylang < 1.6
add_action( '_core_updated_successfully', array( $this, 'upgrade_languages' ), 1 ); // since WP 3.3
// Upgrades plugins and themes translations files
add_filter( 'themes_update_check_locales', array( $this, 'update_check_locales' ) );
add_filter( 'plugins_update_check_locales', array( $this, 'update_check_locales' ) );
// We need specific filters for German and Danish
$specific_locales = array( 'da_DK', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'ca', 'sr_RS', 'bs_BA' );
if ( array_intersect( $this->model->get_languages_list( array( 'fields' => 'locale' ) ), $specific_locales ) ) {
add_filter( 'sanitize_title', array( $this, 'sanitize_title' ), 10, 3 );
add_filter( 'sanitize_user', array( $this, 'sanitize_user' ), 10, 3 );
}
add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
}
/**
* Modifies the widgets forms to add our language dropdwown list
*
* @since 0.3
*
* @param object $widget Widget instance
* @param null $return Not used
* @param array $instance Widget settings
*/
public function in_widget_form( $widget, $return, $instance ) {
$screen = get_current_screen();
// Test the Widgets screen and the Customizer to avoid displaying the option in page builders
// Saving the widget reloads the form. And curiously the action is in $_REQUEST but neither in $_POST, not in $_GET.
if ( ( isset( $screen ) && 'widgets' === $screen->base ) || ( isset( $_REQUEST['action'] ) && 'save-widget' === $_REQUEST['action'] ) || isset( $GLOBALS['wp_customize'] ) ) {
$dropdown = new PLL_Walker_Dropdown();
printf( '<p><label for="%1$s">%2$s %3$s</label></p>',
esc_attr( $widget->id . '_lang_choice' ),
esc_html__( 'The widget is displayed for:', 'polylang' ),
$dropdown->walk(
array_merge(
array( (object) array( 'slug' => 0, 'name' => __( 'All languages', 'polylang' ) ) ),
$this->model->get_languages_list()
),
array(
'name' => $widget->id . '_lang_choice',
'class' => 'tags-input',
'selected' => empty( $instance['pll_lang'] ) ? '' : $instance['pll_lang'],
)
)
);
}
}
/**
* Called when widget options are saved
* saves the language associated to the widget
*
* @since 0.3
*
* @param array $instance Widget options
* @param array $new_instance Not used
* @param array $old_instance Not used
* @param object $widget WP_Widget object
* @return array Widget options
*/
public function widget_update_callback( $instance, $new_instance, $old_instance, $widget ) {
if ( ! empty( $_POST[ $key = $widget->id . '_lang_choice' ] ) && in_array( $_POST[ $key ], $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) ) {
$instance['pll_lang'] = $_POST[ $key ];
} else {
unset( $instance['pll_lang'] );
}
return $instance;
}
/**
* Updates language user preference set in user profile
*
* @since 0.4
*
* @param int $user_id
*/
public function personal_options_update( $user_id ) {
// Admin language
// FIXME Backward compatibility with WP < 4.7
if ( version_compare( $GLOBALS['wp_version'], '4.7alpha', '<' ) ) {
$user_lang = in_array( $_POST['user_lang'], $this->model->get_languages_list( array( 'fields' => 'locale' ) ) ) ? $_POST['user_lang'] : 0;
update_user_meta( $user_id, 'locale', $user_lang );
}
// Biography translations
foreach ( $this->model->get_languages_list() as $lang ) {
$meta = $lang->slug == $this->options['default_lang'] ? 'description' : 'description_' . $lang->slug;
$description = empty( $_POST[ 'description_' . $lang->slug ] ) ? '' : trim( $_POST[ 'description_' . $lang->slug ] );
/** This filter is documented in wp-includes/user.php */
$description = apply_filters( 'pre_user_description', $description ); // Applies WP default filter wp_filter_kses
update_user_meta( $user_id, $meta, $description );
}
}
/**
* Form for language user preference in user profile
*
* @since 0.4
*
* @param object $profileuser
*/
public function personal_options( $profileuser ) {
// FIXME: Backward compatibility with WP < 4.7
if ( version_compare( $GLOBALS['wp_version'], '4.7alpha', '<' ) ) {
$dropdown = new PLL_Walker_Dropdown();
printf( '
<tr>
<th><label for="user_lang">%s</label></th>
<td>%s</td>
</tr>',
esc_html__( 'Admin language', 'polylang' ),
$dropdown->walk(
array_merge(
array( (object) array( 'locale' => 0, 'name' => __( 'WordPress default', 'polylang' ) ) ),
$this->model->get_languages_list()
),
array(
'name' => 'user_lang',
'value' => 'locale',
'selected' => get_user_meta( $profileuser->ID, 'locale', true ),
)
)
);
}
// Hidden informations to modify the biography form with js
foreach ( $this->model->get_languages_list() as $lang ) {
$meta = $lang->slug == $this->options['default_lang'] ? 'description' : 'description_' . $lang->slug;
/** This filter is documented in wp-includes/user.php */
$description = apply_filters( 'user_description', get_user_meta( $profileuser->ID, $meta, true ) ); // Applies WP default filter wp_kses_data
printf( '<input type="hidden" class="biography" name="%s___%s" value="%s" />',
esc_attr( $lang->slug ),
esc_attr( $lang->name ),
esc_attr( $description )
);
}
}
/**
* Ugprades languages files after a core upgrade
* only for backward compatibility WP < 4.0 *AND* Polylang < 1.6
*
* @since 0.6
*
* @param string $version new WP version
*/
public function upgrade_languages( $version ) {
// $GLOBALS['wp_version'] is the old WP version
if ( version_compare( $version, '4.0', '>=' ) && version_compare( $GLOBALS['wp_version'], '4.0', '<' ) ) {
/** This filter is documented in wp-admin/includes/update-core.php */
apply_filters( 'update_feedback', __( 'Upgrading language files&#8230;', 'polylang' ) );
PLL_Upgrade::download_language_packs();
}
}
/**
* Allows to update translations files for plugins and themes
*
* @since 1.6
*
* @param array $locales Not used
* @return array list of locales to update
*/
function update_check_locales( $locales ) {
return $this->model->get_languages_list( array( 'fields' => 'locale' ) );
}
/**
* Filters the locale according to the current language instead of the language
* of the admin interface
*
* @since 2.0
*
* @param string $locale
* @return string
*/
public function get_locale( $locale ) {
return $this->curlang->locale;
}
/**
* Maybe fix the result of sanitize_title() in case the languages include German or Danish
* Without this filter, if language of the title being sanitized is different from the language
* used for the admin interface and if one this language is German or Danish, some specific
* characters such as ä, ö, ü, ß are incorrectly sanitized.
*
* @since 2.0
*
* @param string $title Sanitized title.
* @param string $raw_title The title prior to sanitization.
* @param string $context The context for which the title is being sanitized.
* @return string
*/
public function sanitize_title( $title, $raw_title, $context ) {
static $once = false;
if ( ! $once && 'save' == $context && ! empty( $this->curlang ) && ! empty( $title ) ) {
$once = true;
add_filter( 'locale', array( $this, 'get_locale' ), 20 ); // After the filter for the admin interface
$title = sanitize_title( $raw_title, '', $context );
remove_filter( 'locale', array( $this, 'get_locale' ), 20 );
$once = false;
}
return $title;
}
/**
* Maybe fix the result of sanitize_user() in case the languages include German or Danish
*
* @since 2.0
*
* @param string $username Sanitized username.
* @param string $raw_username The username prior to sanitization.
* @param bool $strict Whether to limit the sanitization to specific characters. Default false.
* @return string
*/
public function sanitize_user( $username, $raw_username, $strict ) {
static $once = false;
if ( ! $once && ! empty( $this->curlang ) ) {
$once = true;
add_filter( 'locale', array( $this, 'get_locale' ), 20 ); // After the filter for the admin interface
$username = sanitize_user( $raw_username, '', $strict );
remove_filter( 'locale', array( $this, 'get_locale' ), 20 );
$once = false;
}
return $username;
}
/**
* Adds a text direction dependent class to the body
*
* @since 2.2
*
* @param string $classes Space-separated list of CSS classes.
* @return string
*/
public function admin_body_class( $classes ) {
if ( ! empty( $this->curlang ) ) {
$classes .= ' pll-dir-' . ( $this->curlang->is_rtl ? 'rtl' : 'ltr' );
}
return $classes;
}
}

View file

@ -0,0 +1,177 @@
<?php
/**
* manages links related functions
*
* @since 1.8
*/
class PLL_Admin_Links extends PLL_Links {
/**
* get the link to create a new post translation
*
* @since 1.5
*
* @param int $post_id the source post id
* @param object $language the language of the new translation
* @return string
*/
public function get_new_post_translation_link( $post_id, $language ) {
$post_type = get_post_type( $post_id );
$post_type_object = get_post_type_object( get_post_type( $post_id ) );
if ( ! current_user_can( $post_type_object->cap->create_posts ) ) {
return '';
}
if ( 'attachment' == $post_type ) {
$args = array(
'action' => 'translate_media',
'from_media' => $post_id,
'new_lang' => $language->slug,
);
// add nonce for media as we will directly publish a new attachment from a clic on this link
$link = wp_nonce_url( add_query_arg( $args, admin_url( 'admin.php' ) ), 'translate_media' );
} else {
$args = array(
'post_type' => $post_type,
'from_post' => $post_id,
'new_lang' => $language->slug,
);
$link = add_query_arg( $args, admin_url( 'post-new.php' ) );
}
/**
* Filter the new post translation link
*
* @since 1.8
*
* @param string $link the new post translation link
* @param object $language the language of the new translation
* @param int $post_id the source post id
*/
return apply_filters( 'pll_get_new_post_translation_link', $link, $language, $post_id );
}
/**
* returns html markup for a new post translation link
*
* @since 1.8
*
* @param int $post_id
* @param object $language
* @return string
*/
public function new_post_translation_link( $post_id, $language ) {
$link = $this->get_new_post_translation_link( $post_id, $language );
return $link ? sprintf(
'<a href="%1$s" class="pll_icon_add"><span class="screen-reader-text">%2$s</span></a>',
esc_url( $link ),
/* translators: accessibility text, %s is a native language name */
esc_html( sprintf( __( 'Add a translation in %s', 'polylang' ), $language->name ) )
) : '';
}
/**
* returns html markup for a translation link
*
* @since 1.4
*
* @param int $post_id translation post id
* @return string
*/
public function edit_post_translation_link( $post_id ) {
$link = get_edit_post_link( $post_id );
$language = $this->model->post->get_language( $post_id );
return $link ? sprintf(
'<a href="%1$s" class="pll_icon_edit"><span class="screen-reader-text">%2$s</span></a>',
esc_url( $link ),
/* translators: accessibility text, %s is a native language name */
esc_html( sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ) )
) : '';
}
/**
* get the link to create a new term translation
*
* @since 1.5
*
* @param int $term_id
* @param string $taxonomy
* @param string $post_type
* @param object $language
* @return string
*/
public function get_new_term_translation_link( $term_id, $taxonomy, $post_type, $language ) {
$tax = get_taxonomy( $taxonomy );
if ( ! $tax || ! current_user_can( $tax->cap->edit_terms ) ) {
return '';
}
$args = array(
'taxonomy' => $taxonomy,
'post_type' => $post_type,
'from_tag' => $term_id,
'new_lang' => $language->slug,
);
$link = add_query_arg( $args, admin_url( 'edit-tags.php' ) );
/**
* Filter the new term translation link
*
* @since 1.8
*
* @param string $link the new term translation link
* @param object $language the language of the new translation
* @param int $term_id the source term id
* @param string $taxonomy
* @param string $post_type
*/
return apply_filters( 'pll_get_new_term_translation_link', $link, $language, $term_id, $taxonomy, $post_type );
}
/**
* returns html markup for a new term translation
*
* @since 1.8
*
* @param int $term_id
* @param string $taxonomy
* @param string $post_type
* @param object $language
* @return string
*/
public function new_term_translation_link( $term_id, $taxonomy, $post_type, $language ) {
$link = $this->get_new_term_translation_link( $term_id, $taxonomy, $post_type, $language );
return $link ? sprintf(
'<a href="%1$s" class="pll_icon_add"><span class="screen-reader-text">%2$s</span></a>',
esc_url( $link ),
/* translators: accessibility text, %s is a native language name */
esc_html( sprintf( __( 'Add a translation in %s', 'polylang' ), $language->name ) )
) : '';
}
/**
* returns html markup for a term translation link
*
* @since 1.4
*
* @param object $term_id translation term id
* @param string $taxonomy
* @param string $post_type
* @return string
*/
public function edit_term_translation_link( $term_id, $taxonomy, $post_type ) {
$link = get_edit_term_link( $term_id, $taxonomy, $post_type );
$language = $this->model->term->get_language( $term_id );
return $link ? sprintf(
'<a href="%1$s" class="pll_icon_edit"><span class="screen-reader-text">%2$s</span></a>',
esc_url( $link ),
/* translators: accessibility text, %s is a native language name */
esc_html( sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ) )
) : '';
}
}

View file

@ -0,0 +1,540 @@
<?php
/**
* Extends the PLL_Model class with methods needed only in Polylang settings pages
*
* @since 1.2
*/
class PLL_Admin_Model extends PLL_Model {
/**
* Adds a new language
* Creates a default category for this language
*
* List of arguments that $args must contain:
* name -> language name ( used only for display )
* slug -> language code ( ideally 2-letters ISO 639-1 language code )
* locale -> WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded...
* rtl -> 1 if rtl language, 0 otherwise
* term_group -> language order when displayed
*
* Optional arguments that $args can contain:
* no_default_cat -> if set, no default category will be created for this language
* flag -> country code, see flags.php
*
* @since 1.2
*
* @param array $args
* @return bool true if success / false if failed
*/
public function add_language( $args ) {
if ( ! $this->validate_lang( $args ) ) {
return false;
}
// First the language taxonomy
$description = serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
$r = wp_insert_term( $args['name'], 'language', array( 'slug' => $args['slug'], 'description' => $description ) );
if ( is_wp_error( $r ) ) {
// Avoid an ugly fatal error if something went wrong ( reported once in the forum )
add_settings_error( 'general', 'pll_add_language', __( 'Impossible to add the language.', 'polylang' ) );
return false;
}
wp_update_term( (int) $r['term_id'], 'language', array( 'term_group' => (int) $args['term_group'] ) ); // can't set the term group directly in wp_insert_term
// The term_language taxonomy
// Don't want shared terms so use a different slug
wp_insert_term( $args['name'], 'term_language', array( 'slug' => 'pll_' . $args['slug'] ) );
$this->clean_languages_cache(); // Udpate the languages list now !
if ( ! isset( $this->options['default_lang'] ) ) {
// If this is the first language created, set it as default language
$this->options['default_lang'] = $args['slug'];
update_option( 'polylang', $this->options );
// And assign default language to default category
$this->term->set_language( (int) get_option( 'default_category' ), (int) $r['term_id'] );
} elseif ( empty( $args['no_default_cat'] ) ) {
$this->create_default_category( $args['slug'] );
}
// Init a mo_id for this language
$mo = new PLL_MO();
$mo->export_to_db( $this->get_language( $args['slug'] ) );
/**
* Fires when a language is added
*
* @since 1.9
*
* @param array $args arguments used to create the language
*/
do_action( 'pll_add_language', $args );
$this->clean_languages_cache(); // Again to set add mo_id in the cached languages list
flush_rewrite_rules(); // Refresh rewrite rules
add_settings_error( 'general', 'pll_languages_created', __( 'Language added.', 'polylang' ), 'updated' );
return true;
}
/**
* Delete a language
*
* @since 1.2
*
* @param int $lang_id language term_id
*/
public function delete_language( $lang_id ) {
$lang = $this->get_language( (int) $lang_id );
if ( empty( $lang ) ) {
return;
}
// Oops ! we are deleting the default language...
// Need to do this before loosing the information for default category translations
if ( $this->options['default_lang'] == $lang->slug ) {
$slugs = $this->get_languages_list( array( 'fields' => 'slug' ) );
$slugs = array_diff( $slugs, array( $lang->slug ) );
if ( ! empty( $slugs ) ) {
$this->update_default_lang( reset( $slugs ) ); // Arbitrary choice...
} else {
unset( $this->options['default_lang'] );
}
}
// Delete the translations
$this->update_translations( $lang->slug );
// Delete language option in widgets
foreach ( $GLOBALS['wp_registered_widgets'] as $widget ) {
if ( ! empty( $widget['callback'][0] ) && ! empty( $widget['params'][0]['number'] ) ) {
$obj = $widget['callback'][0];
$number = $widget['params'][0]['number'];
if ( is_object( $obj ) && method_exists( $obj, 'get_settings' ) && method_exists( $obj, 'save_settings' ) ) {
$settings = $obj->get_settings();
if ( isset( $settings[ $number ]['pll_lang'] ) && $settings[ $number ]['pll_lang'] == $lang->slug ) {
unset( $settings[ $number ]['pll_lang'] );
$obj->save_settings( $settings );
}
}
}
}
// Delete menus locations
if ( ! empty( $this->options['nav_menus'] ) ) {
foreach ( $this->options['nav_menus'] as $theme => $locations ) {
foreach ( $locations as $location => $languages ) {
unset( $this->options['nav_menus'][ $theme ][ $location ][ $lang->slug ] );
}
}
}
// Delete users options
foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
delete_user_meta( $user_id, 'pll_filter_content', $lang->slug );
delete_user_meta( $user_id, 'description_' . $lang->slug );
}
// Delete the string translations
$post = wpcom_vip_get_page_by_title( 'polylang_mo_' . $lang->term_id, OBJECT, 'polylang_mo' );
if ( ! empty( $post ) ) {
wp_delete_post( $post->ID );
}
// Delete domain
unset( $this->options['domains'][ $lang->slug ] );
// Delete the language itself
wp_delete_term( $lang->term_id, 'language' );
wp_delete_term( $lang->tl_term_id, 'term_language' );
// Update languages list
$this->clean_languages_cache();
update_option( 'polylang', $this->options );
flush_rewrite_rules(); // refresh rewrite rules
add_settings_error( 'general', 'pll_languages_deleted', __( 'Language deleted.', 'polylang' ), 'updated' );
}
/**
* Update language properties
*
* List of arguments that $args must contain:
* lang_id -> term_id of the language to modify
* name -> language name ( used only for display )
* slug -> language code ( ideally 2-letters ISO 639-1 language code
* locale -> WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded...
* rtl -> 1 if rtl language, 0 otherwise
* term_group -> language order when displayed
*
* Optional arguments that $args can contain:
* flag -> country code, see flags.php
*
* @since 1.2
*
* @param array $args
* @return bool true if success / false if failed
*/
public function update_language( $args ) {
$lang = $this->get_language( (int) $args['lang_id'] );
if ( ! $this->validate_lang( $args, $lang ) ) {
return false;
}
// Update links to this language in posts and terms in case the slug has been modified
$slug = $args['slug'];
$old_slug = $lang->slug;
if ( $old_slug != $slug ) {
// Update the language slug in translations
$this->update_translations( $old_slug, $slug );
// Update language option in widgets
foreach ( $GLOBALS['wp_registered_widgets'] as $widget ) {
if ( ! empty( $widget['callback'][0] ) && ! empty( $widget['params'][0]['number'] ) ) {
$obj = $widget['callback'][0];
$number = $widget['params'][0]['number'];
if ( is_object( $obj ) && method_exists( $obj, 'get_settings' ) && method_exists( $obj, 'save_settings' ) ) {
$settings = $obj->get_settings();
if ( isset( $settings[ $number ]['pll_lang'] ) && $settings[ $number ]['pll_lang'] == $old_slug ) {
$settings[ $number ]['pll_lang'] = $slug;
$obj->save_settings( $settings );
}
}
}
}
// Update menus locations
if ( ! empty( $this->options['nav_menus'] ) ) {
foreach ( $this->options['nav_menus'] as $theme => $locations ) {
foreach ( $locations as $location => $languages ) {
if ( ! empty( $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ] ) ) {
$this->options['nav_menus'][ $theme ][ $location ][ $slug ] = $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ];
unset( $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ] );
}
}
}
}
// Update domains
if ( ! empty( $this->options['domains'][ $old_slug ] ) ) {
$this->options['domains'][ $slug ] = $this->options['domains'][ $old_slug ];
unset( $this->options['domains'][ $old_slug ] );
}
// Update the default language option if necessary
if ( $this->options['default_lang'] == $old_slug ) {
$this->options['default_lang'] = $slug;
}
}
update_option( 'polylang', $this->options );
// And finally update the language itself
$description = serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
wp_update_term( (int) $lang->term_id, 'language', array( 'slug' => $slug, 'name' => $args['name'], 'description' => $description, 'term_group' => (int) $args['term_group'] ) );
wp_update_term( (int) $lang->tl_term_id, 'term_language', array( 'slug' => 'pll_' . $slug, 'name' => $args['name'] ) );
/**
* Fires when a language is added
*
* @since 1.9
*
* @param array $args arguments used to modify the language
*/
do_action( 'pll_update_language', $args );
$this->clean_languages_cache();
flush_rewrite_rules(); // Refresh rewrite rules
add_settings_error( 'general', 'pll_languages_updated', __( 'Language updated.', 'polylang' ), 'updated' );
return true;
}
/**
* Validates data entered when creating or updating a language
* @see PLL_Admin_Model::add_language
*
* @since 0.4
*
* @param array $args
* @param object $lang optional the language currently updated, the language is created if not set
* @return bool true if success / false if failed
*/
protected function validate_lang( $args, $lang = null ) {
// Validate locale with the same pattern as WP 4.3. See #28303
if ( ! preg_match( '#^[a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?$#', $args['locale'], $matches ) ) {
add_settings_error( 'general', 'pll_invalid_locale', __( 'Enter a valid WordPress locale', 'polylang' ) );
}
// Validate slug characters
if ( ! preg_match( '#^[a-z_-]+$#', $args['slug'] ) ) {
add_settings_error( 'general', 'pll_invalid_slug', __( 'The language code contains invalid characters', 'polylang' ) );
}
// Validate slug is unique
if ( $this->get_language( $args['slug'] ) && ( null === $lang || ( isset( $lang ) && $lang->slug != $args['slug'] ) ) ) {
add_settings_error( 'general', 'pll_non_unique_slug', __( 'The language code must be unique', 'polylang' ) );
}
// Validate name
// No need to sanitize it as wp_insert_term will do it for us
if ( empty( $args['name'] ) ) {
add_settings_error( 'general', 'pll_invalid_name', __( 'The language must have a name', 'polylang' ) );
}
// Validate flag
if ( ! empty( $args['flag'] ) && ! file_exists( POLYLANG_DIR . '/flags/' . $args['flag'] . '.png' ) ) {
add_settings_error( 'general', 'pll_invalid_flag', __( 'The flag does not exist', 'polylang' ) );
}
return get_settings_errors() ? false : true;
}
/**
* Used to set the language of posts or terms in mass
*
* @since 1.2
*
* @param string $type either 'post' or 'term'
* @param array $ids array of post ids or term ids
* @param object|string $lang object or slug
*/
public function set_language_in_mass( $type, $ids, $lang ) {
global $wpdb;
$ids = array_map( 'intval', $ids );
$lang = $this->get_language( $lang );
$tt_id = 'term' === $type ? $lang->tl_term_taxonomy_id : $lang->term_taxonomy_id;
foreach ( $ids as $id ) {
$values[] = $wpdb->prepare( '( %d, %d )', $id, $tt_id );
}
if ( ! empty( $values ) ) {
$values = array_unique( $values );
$wpdb->query( "INSERT INTO $wpdb->term_relationships ( object_id, term_taxonomy_id ) VALUES " . implode( ',', $values ) );
$lang->update_count(); // Updating term count is mandatory ( thanks to AndyDeGroo )
}
if ( 'term' === $type ) {
clean_term_cache( $ids, 'term_language' );
foreach ( $ids as $id ) {
$translations[] = array( $lang->slug => $id );
}
if ( ! empty( $translations ) ) {
$this->set_translation_in_mass( 'term', $translations );
}
} else {
clean_term_cache( $ids, 'language' );
}
}
/**
* Used to create a translations groups in mass
*
* @since 1.6.3
*
* @param string $type either 'post' or 'term'
* @param array $translations array of translations arrays
*/
public function set_translation_in_mass( $type, $translations ) {
global $wpdb;
$taxonomy = $type . '_translations';
foreach ( $translations as $t ) {
$term = uniqid( 'pll_' ); // the term name
$terms[] = $wpdb->prepare( '( "%s", "%s" )', $term, $term );
$slugs[] = $wpdb->prepare( '"%s"', $term );
$description[ $term ] = serialize( $t );
$count[ $term ] = count( $t );
}
// Insert terms
if ( ! empty( $terms ) ) {
$terms = array_unique( $terms );
$wpdb->query( "INSERT INTO $wpdb->terms ( slug, name ) VALUES " . implode( ',', $terms ) );
}
// Get all terms with their term_id
$terms = $wpdb->get_results( "SELECT term_id, slug FROM $wpdb->terms WHERE slug IN ( " . implode( ',', $slugs ) . ' )' );
// Prepare terms taxonomy relationship
foreach ( $terms as $term ) {
$term_ids[] = $term->term_id;
$tts[] = $wpdb->prepare( '( %d, "%s", "%s", %d )', $term->term_id, $taxonomy, $description[ $term->slug ], $count[ $term->slug ] );
}
// Insert term_taxonomy
if ( ! empty( $tts ) ) {
$tts = array_unique( $tts );
$wpdb->query( "INSERT INTO $wpdb->term_taxonomy ( term_id, taxonomy, description, count ) VALUES " . implode( ',', $tts ) );
}
// Get all terms with term_taxonomy_id
$terms = get_terms( $taxonomy, array( 'hide_empty' => false ) );
// Prepare objects relationships
foreach ( $terms as $term ) {
$t = unserialize( $term->description );
if ( in_array( $t, $translations ) ) {
foreach ( $t as $object_id ) {
if ( ! empty( $object_id ) ) {
$trs[] = $wpdb->prepare( '( %d, %d )', $object_id, $term->term_taxonomy_id );
}
}
}
}
// Insert term_relationships
if ( ! empty( $trs ) ) {
$wpdb->query( "INSERT INTO $wpdb->term_relationships ( object_id, term_taxonomy_id ) VALUES " . implode( ',', $trs ) );
$trs = array_unique( $trs );
}
clean_term_cache( $term_ids, $taxonomy );
}
/**
* Returns unstranslated posts and terms ids ( used in settings )
*
* @since 0.9
*
* @return array array made of an array of post ids and an array of term ids
*/
public function get_objects_with_no_lang() {
$posts = get_posts( array(
'numberposts' => -1,
'nopaging' => true,
'post_type' => $this->get_translated_post_types(),
'post_status' => 'any',
'fields' => 'ids',
'tax_query' => array(
array(
'taxonomy' => 'language',
'terms' => $this->get_languages_list( array( 'fields' => 'term_id' ) ),
'operator' => 'NOT IN',
),
),
) );
$terms = get_terms( $this->get_translated_taxonomies(), array( 'get' => 'all', 'fields' => 'ids' ) );
$groups = $this->get_languages_list( array( 'fields' => 'tl_term_id' ) );
$tr_terms = get_objects_in_term( $groups, 'term_language' );
$terms = array_unique( array_diff( $terms, $tr_terms ) ); // array_unique to avoid duplicates if a term is in more than one taxonomy
$terms = array_map( 'intval', $terms );
/**
* Filter the list of untranslated posts ids and terms ids
*
* @since 0.9
*
* @param bool|array $objects false if no ids found, list of post and/or term ids otherwise
*/
return apply_filters( 'pll_get_objects_with_no_lang', empty( $posts ) && empty( $terms ) ? false : array( 'posts' => $posts, 'terms' => $terms ) );
}
/**
* Used to delete translations or update the translations when a language slug has been modified in settings
*
* @since 0.5
*
* @param string $old_slug the old language slug
* @param string $new_slug optional, the new language slug, if not set it means the correspondant has been deleted
*/
public function update_translations( $old_slug, $new_slug = '' ) {
global $wpdb;
$terms = get_terms( array( 'post_translations', 'term_translations' ) );
foreach ( $terms as $term ) {
$term_ids[ $term->taxonomy ][] = $term->term_id;
$tr = unserialize( $term->description );
if ( ! empty( $tr[ $old_slug ] ) ) {
if ( $new_slug ) {
$tr[ $new_slug ] = $tr[ $old_slug ]; // Suppress this for delete
} else {
$dr['id'][] = (int) $tr[ $old_slug ];
$dr['tt'][] = (int) $term->term_taxonomy_id;
}
unset( $tr[ $old_slug ] );
if ( empty( $tr ) || 1 == count( $tr ) ) {
$dt['t'][] = (int) $term->term_id;
$dt['tt'][] = (int) $term->term_taxonomy_id;
} else {
$ut['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $term->term_id, serialize( $tr ) );
$ut['in'][] = (int) $term->term_id;
}
}
}
// Delete relationships
if ( ! empty( $dr ) ) {
$wpdb->query( "
DELETE FROM $wpdb->term_relationships
WHERE object_id IN ( " . implode( ',', $dr['id'] ) . ' )
AND term_taxonomy_id IN ( ' . implode( ',', $dr['tt'] ) . ' )
' );
}
// Delete terms
if ( ! empty( $dt ) ) {
$wpdb->query( "DELETE FROM $wpdb->terms WHERE term_id IN ( " . implode( ',', $dt['t'] ) . ' )' );
$wpdb->query( "DELETE FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ( " . implode( ',', $dt['tt'] ) . ' )' );
}
// Update terms
if ( ! empty( $ut ) ) {
$wpdb->query( "
UPDATE $wpdb->term_taxonomy
SET description = ( CASE term_id " . implode( ' ', $ut['case'] ) . ' END )
WHERE term_id IN ( ' . implode( ',', $ut['in'] ) . ' )
' );
}
if ( ! empty( $term_ids ) ) {
foreach ( $term_ids as $taxonomy => $ids ) {
clean_term_cache( $ids, $taxonomy );
}
}
}
/**
* Updates the default language
* taking care to update the default category & the nav menu locations
*
* @since 1.8
*
* @param string $slug new language slug
*/
public function update_default_lang( $slug ) {
// The nav menus stored in theme locations should be in the default language
$theme = get_stylesheet();
if ( ! empty( $this->options['nav_menus'][ $theme ] ) ) {
foreach ( $this->options['nav_menus'][ $theme ] as $key => $loc ) {
$menus[ $key ] = empty( $loc[ $slug ] ) ? 0 : $loc[ $slug ];
}
set_theme_mod( 'nav_menu_locations', $menus );
}
// The default category should be in the default language
$default_cats = $this->term->get_translations( get_option( 'default_category' ) );
if ( isset( $default_cats[ $slug ] ) ) {
update_option( 'default_category', $default_cats[ $slug ] );
}
// Update options
$this->options['default_lang'] = $slug;
update_option( 'polylang', $this->options );
$this->clean_languages_cache();
flush_rewrite_rules();
}
}

View file

@ -0,0 +1,331 @@
<?php
/**
* Manages custom menus translations as well as the language switcher menu item on admin side
*
* @since 1.2
*/
class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
/**
* Constructor: setups filters and actions
*
* @since 1.2
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
parent::__construct( $polylang );
$this->theme = get_option( 'stylesheet' );
// Populates nav menus locations
// Since WP 4.4, must be done before customize_register is fired
add_filter( 'theme_mod_nav_menu_locations', array( $this, 'theme_mod_nav_menu_locations' ), 20 );
// Integration in the WP menu interface
add_action( 'admin_init', array( $this, 'admin_init' ) ); // after Polylang upgrade
}
/**
* Setups filters and terms
* adds the language switcher metabox and create new nav menu locations
*
* @since 1.1
*/
public function admin_init() {
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
add_action( 'wp_update_nav_menu_item', array( $this, 'wp_update_nav_menu_item' ), 10, 2 );
add_filter( 'wp_get_nav_menu_items', array( $this, 'translate_switcher_title' ) );
// Translation of menus based on chosen locations
add_filter( 'pre_update_option_theme_mods_' . $this->theme, array( $this, 'pre_update_option_theme_mods' ) );
add_action( 'delete_nav_menu', array( $this, 'delete_nav_menu' ) );
// Filter _wp_auto_add_pages_to_menu by language
add_action( 'transition_post_status', array( $this, 'auto_add_pages_to_menu' ), 5, 3 ); // before _wp_auto_add_pages_to_menu
// FIXME is it possible to choose the order ( after theme locations in WP3.5 and older ) ?
// FIXME not displayed if Polylang is activated before the first time the user goes to nav menus http://core.trac.wordpress.org/ticket/16828
add_meta_box( 'pll_lang_switch_box', __( 'Language switcher', 'polylang' ), array( $this, 'lang_switch' ), 'nav-menus', 'side', 'high' );
$this->create_nav_menu_locations();
}
/**
* Language switcher metabox
* The checkbox and all hidden fields are important
* Thanks to John Morris for his very interesting post http://www.johnmorrisonline.com/how-to-add-a-fully-functional-custom-meta-box-to-wordpress-navigation-menus/
*
* @since 1.1
*/
public function lang_switch() {
global $_nav_menu_placeholder, $nav_menu_selected_id;
$_nav_menu_placeholder = 0 > $_nav_menu_placeholder ? $_nav_menu_placeholder - 1 : -1;
?>
<div id="posttype-lang-switch" class="posttypediv">
<div id="tabs-panel-lang-switch" class="tabs-panel tabs-panel-active">
<ul id="lang-switch-checklist" class="categorychecklist form-no-clear">
<li>
<label class="menu-item-title">
<input type="checkbox" class="menu-item-checkbox" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-object-id]" value="-1"> <?php esc_html_e( 'Language switcher', 'polylang' ); ?>
</label>
<input type="hidden" class="menu-item-type" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-type]" value="custom">
<input type="hidden" class="menu-item-title" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-title]" value="<?php esc_html_e( 'Language switcher', 'polylang' ); ?>">
<input type="hidden" class="menu-item-url" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-url]" value="#pll_switcher">
</li>
</ul>
</div>
<p class="button-controls">
<span class="add-to-menu">
<input type="submit" <?php disabled( $nav_menu_selected_id, 0 ); ?> class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-post-type-menu-item" id="submit-posttype-lang-switch">
<span class="spinner"></span>
</span>
</p>
</div>
<?php
}
/**
* Prepares javascript to modify the language switcher menu item
*
* @since 1.1
*/
public function admin_enqueue_scripts() {
$screen = get_current_screen();
if ( 'nav-menus' != $screen->base ) {
return;
}
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_enqueue_script( 'pll_nav_menu', plugins_url( '/js/nav-menu' . $suffix . '.js', POLYLANG_FILE ), array( 'jquery' ), POLYLANG_VERSION );
$data['strings'] = PLL_Switcher::get_switcher_options( 'menu', 'string' ); // The strings for the options
$data['title'] = __( 'Language switcher', 'polylang' ); // The title
// Get all language switcher menu items
$items = get_posts( array(
'numberposts' => -1,
'nopaging' => true,
'post_type' => 'nav_menu_item',
'fields' => 'ids',
'meta_key' => '_pll_menu_item',
) );
// The options values for the language switcher
$data['val'] = array();
foreach ( $items as $item ) {
$data['val'][ $item ] = get_post_meta( $item, '_pll_menu_item', true );
}
// Send all these data to javascript
wp_localize_script( 'pll_nav_menu', 'pll_data', $data );
}
/**
* Save our menu item options
*
* @since 1.1
*
* @param int $menu_id not used
* @param int $menu_item_db_id
*/
public function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0 ) {
if ( empty( $_POST['menu-item-url'][ $menu_item_db_id ] ) || '#pll_switcher' != $_POST['menu-item-url'][ $menu_item_db_id ] ) {
return;
}
// Security check as 'wp_update_nav_menu_item' can be called from outside WP admin
if ( current_user_can( 'edit_theme_options' ) ) {
check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' );
$options = array( 'hide_if_no_translation' => 0, 'hide_current' => 0, 'force_home' => 0, 'show_flags' => 0, 'show_names' => 1, 'dropdown' => 0 ); // Default values
// Our jQuery form has not been displayed
if ( empty( $_POST['menu-item-pll-detect'][ $menu_item_db_id ] ) ) {
if ( ! get_post_meta( $menu_item_db_id, '_pll_menu_item', true ) ) { // Our options were never saved
update_post_meta( $menu_item_db_id, '_pll_menu_item', $options );
}
}
else {
foreach ( $options as $opt => $v ) {
$options[ $opt ] = empty( $_POST[ 'menu-item-' . $opt ][ $menu_item_db_id ] ) ? 0 : 1;
}
update_post_meta( $menu_item_db_id, '_pll_menu_item', $options ); // Allow us to easily identify our nav menu item
}
}
}
/**
* Translates the language switcher menu items title in case the user switches the admin language
*
* @since 1.1.1
*
* @param array $items
* @return array modified $items
*/
public function translate_switcher_title( $items ) {
foreach ( $items as $item ) {
if ( '#pll_switcher' == $item->url ) {
$item->post_title = __( 'Language switcher', 'polylang' );
}
}
return $items;
}
/**
* Assign menu languages and translations based on ( temporary ) locations
*
* @since 1.8
*
* @param array $locations nav menu locations
* @return array
*/
public function update_nav_menu_locations( $locations ) {
$default = $this->options['default_lang'];
// Extract language and menu from locations
foreach ( $locations as $loc => $menu ) {
$infos = $this->explode_location( $loc );
$this->options['nav_menus'][ $this->theme ][ $infos['location'] ][ $infos['lang'] ] = $menu;
if ( $this->options['default_lang'] != $infos['lang'] ) {
unset( $locations[ $loc ] ); // Remove temporary locations before database update
}
}
update_option( 'polylang', $this->options );
return $locations;
}
/**
* Assign menu languages and translations based on ( temporary ) locations
*
* @since 1.1
*
* @param array $mods theme mods
* @return unmodified $mods
*/
public function pre_update_option_theme_mods( $mods ) {
if ( current_user_can( 'edit_theme_options' ) && isset( $mods['nav_menu_locations'] ) ) {
// Manage Locations tab in Appearance -> Menus
if ( isset( $_GET['action'] ) && 'locations' == $_GET['action'] ) {
check_admin_referer( 'save-menu-locations' );
$this->options['nav_menus'][ $this->theme ] = array();
}
// Edit Menus tab in Appearance -> Menus
// Add the test of $_POST['update-nav-menu-nonce'] to avoid conflict with Vantage theme
elseif ( isset( $_POST['action'], $_POST['update-nav-menu-nonce'] ) && 'update' == $_POST['action'] ) {
check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' );
$this->options['nav_menus'][ $this->theme ] = array();
}
// Customizer
// Don't reset locations in this case.
// see http://wordpress.org/support/topic/menus-doesnt-show-and-not-saved-in-theme-settings-multilingual-site
elseif ( isset( $_POST['action'] ) && 'customize_save' == $_POST['action'] ) {
check_ajax_referer( 'save-customize_' . $GLOBALS['wp_customize']->get_stylesheet(), 'nonce' );
}
else {
return $mods; // No modification for nav menu locations
}
$mods['nav_menu_locations'] = $this->update_nav_menu_locations( $mods['nav_menu_locations'] );
}
return $mods;
}
/**
* Fills temporary menu locations based on menus translations
*
* @since 1.2
*
* @param bool|array $menus
* @return bool|array modified list of menu locations
*/
public function theme_mod_nav_menu_locations( $menus ) {
// Prefill locations with 0 value in case a location does not exist in $menus
$locations = get_registered_nav_menus();
if ( is_array( $locations ) ) {
$locations = array_fill_keys( array_keys( $locations ), 0 );
$menus = is_array( $menus ) ? array_merge( $locations , $menus ) : $locations;
}
if ( is_array( $menus ) ) {
foreach ( $menus as $loc => $menu ) {
foreach ( $this->model->get_languages_list() as $lang ) {
if ( ! empty( $this->options['nav_menus'][ $this->theme ][ $loc ][ $lang->slug ] ) && term_exists( $this->options['nav_menus'][ $this->theme ][ $loc ][ $lang->slug ], 'nav_menu' ) ) {
$menus[ $this->combine_location( $loc, $lang ) ] = $this->options['nav_menus'][ $this->theme ][ $loc ][ $lang->slug ];
}
}
}
}
return $menus;
}
/**
* Removes the nav menu term_id from the locations stored in Polylang options when a nav menu is deleted
*
* @since 1.7.3
*
* @param int $term_id nav menu id
*/
function delete_nav_menu( $term_id ) {
if ( isset( $this->options['nav_menus'] ) ) {
foreach ( $this->options['nav_menus'] as $theme => $locations ) {
foreach ( $locations as $loc => $languages ) {
foreach ( $languages as $lang => $menu_id ) {
if ( $menu_id === $term_id ) {
unset( $this->options['nav_menus'][ $theme ][ $loc ][ $lang ] );
}
}
}
}
update_option( 'polylang', $this->options );
}
}
/**
* Filters the option nav_menu_options for auto added pages to menu
*
* @since 0.9.4
*
* @param array $options
* @return array Modified options
*/
public function nav_menu_options( $options ) {
$options['auto_add'] = array_intersect( $options['auto_add'], $this->auto_add_menus );
return $options;
}
/**
* Filters _wp_auto_add_pages_to_menu by language
*
* @since 0.9.4
*
* @param string $new_status Transition to this post status.
* @param string $old_status Previous post status.
* @param object $post Post data.
*/
public function auto_add_pages_to_menu( $new_status, $old_status, $post ) {
if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type || ! empty( $post->post_parent ) || ! ( $lang = $this->model->post->get_language( $post->ID ) ) ) {
return;
}
if ( ! empty( $this->options['nav_menus'][ $this->theme ] ) ) {
$this->auto_add_menus = array();
// Get all the menus in the page language
foreach ( $this->options['nav_menus'][ $this->theme ] as $loc ) {
if ( ! empty( $loc[ $lang->slug ] ) ) {
$this->auto_add_menus[] = $loc[ $lang->slug ];
}
}
add_filter( 'option_nav_menu_options', array( $this, 'nav_menu_options' ) );
}
}
}

View file

@ -0,0 +1,165 @@
<?php
/**
* Manages the static front page and the page for posts on admin side
*
* @since 1.8
*/
class PLL_Admin_Static_Pages extends PLL_Static_Pages {
/**
* Constructor: setups filters and actions
*
* @since 1.8
*
* @param object $polylang
*/
public function __construct( &$polylang ) {
parent::__construct( $polylang );
// Removes the editor and the template select dropdown for pages for posts
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
// Add post state for translations of the front page and posts page
add_filter( 'display_post_states', array( $this, 'display_post_states' ), 10, 2 );
// Refresh language cache when a static front page has been translated
add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
// Checks if chosen page on front is translated
add_filter( 'pre_update_option_page_on_front', array( $this, 'update_page_on_front' ), 10, 2 );
add_filter( 'customize_validate_page_on_front', array( $this, 'customize_validate_page_on_front' ), 10, 2 );
// Prevents WP resetting the option
add_filter( 'pre_update_option_show_on_front', array( $this, 'update_show_on_front' ), 10, 2 );
}
/**
* Removes the editor for translations of the pages for posts
* Removes the page template select dropdown in page attributes metabox too
*
* @since 2.2.2
*
* @param string $post_type Current post type
* @param object $post Current post
*/
function add_meta_boxes( $post_type, $post ) {
if ( 'page' === $post_type ) {
add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
if ( ( get_option( 'page_for_posts' ) == $post->ID ) && empty( $post->post_content ) ) {
add_action( 'edit_form_after_title', '_wp_posts_page_notice' );
remove_post_type_support( $post_type, 'editor' );
}
}
}
/**
* Add post state for translations of the front page and posts page
*
* @since 1.8
*
* @param array $post_states
* @param object $post
* @return array
*/
public function display_post_states( $post_states, $post ) {
if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_on_front' ) ) ) ) {
$post_states['page_on_front'] = __( 'Front Page' );
}
if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ) ) ) {
$post_states['page_for_posts'] = __( 'Posts Page' );
}
return $post_states;
}
/**
* Refresh language cache when a static front page has been translated
*
* @since 1.8
*
* @param int $post_id Not used
* @param object $post Not used
* @param array $translations
*/
public function pll_save_post( $post_id, $post, $translations ) {
if ( in_array( $this->page_on_front, $translations ) ) {
$this->model->clean_languages_cache();
}
}
/**
* Checks if a page is translted in all languages
*
* @since 2.2
*
* @param int $page_id
* @return bool
*/
protected function is_page_translated( $page_id ) {
if ( $page_id ) {
$translations = count( $this->model->post->get_translations( $page_id ) );
$languages = count( $this->model->get_languages_list() );
if ( $languages > 1 && $translations != $languages ) {
return false;
}
}
return true;
}
/**
* Prevents choosing an untranslated static front page
* Displays an error message
*
* @since 1.6
*
* @param int $page_id New page on front page id
* @param int $old_id Old page on front page_id
* @return int
*/
public function update_page_on_front( $page_id, $old_id ) {
if ( ! $this->is_page_translated( $page_id ) ) {
$page_id = $old_id;
add_settings_error( 'reading', 'pll_page_on_front_error', __( 'The chosen static front page must be translated in all languages.', 'polylang' ) );
}
return $page_id;
}
/**
* Displays an error message in the customizer when choosing an untranslated static front page
*
* @since 2.2
*
* @param object $validity WP_Error object
* @param int $page_id New page on front page id
* @return object
*/
public function customize_validate_page_on_front( $validity, $page_id ) {
if ( ! $this->is_page_translated( $page_id ) ) {
return new WP_Error( 'pll_page_on_front_error', __( 'The chosen static front page must be translated in all languages.', 'polylang' ) );
}
return $validity;
}
/**
* Prevents WP resetting the option if the admin language filter is active for a language with no pages
*
* @since 1.9.3
*
* @param string $value
* @param string $old_value
* @return string
*/
public function update_show_on_front( $value, $old_value ) {
if ( ! empty( $GLOBALS['pagenow'] ) && 'options-reading.php' === $GLOBALS['pagenow'] && 'posts' === $value && ! get_pages() && get_pages( array( 'lang' => '' ) ) ) {
$value = $old_value;
}
return $value;
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* a fully static class to manage strings translations on admin side
*
* @since 1.6
*/
class PLL_Admin_Strings {
static protected $strings = array(); // strings to translate
static protected $default_strings; // default strings to register
/**
* init: add filters
*
* @since 1.6
*/
static public function init() {
// default strings translations sanitization
add_filter( 'pll_sanitize_string_translation', array( __CLASS__, 'sanitize_string_translation' ), 10, 2 );
}
/**
* register strings for translation making sure it is not duplicate or empty
*
* @since 0.6
*
* @param string $name A unique name for the string
* @param string $string The string to register
* @param string $context Optional, the group in which the string is registered, defaults to 'polylang'
* @param bool $multiline Optional, whether the string table should display a multiline textarea or a single line input, defaults to single line
*/
static public function register_string( $name, $string, $context = 'Polylang', $multiline = false ) {
// backward compatibility with Polylang older than 1.1
if ( is_bool( $context ) ) {
$multiline = $context;
$context = 'Polylang';
}
if ( $string && is_scalar( $string ) ) {
self::$strings[ md5( $string ) ] = compact( 'name', 'string', 'context', 'multiline' );
}
}
/**
* get registered strings
*
* @since 0.6.1
*
* @return array list of all registered strings
*/
static public function &get_strings() {
self::$default_strings = array(
'options' => array(
'blogname' => __( 'Site Title' ),
'blogdescription' => __( 'Tagline' ),
'date_format' => __( 'Date Format' ),
'time_format' => __( 'Time Format' ),
),
'widget_title' => __( 'Widget title', 'polylang' ),
'widget_text' => __( 'Widget text', 'polylang' ),
);
// WP strings
foreach ( self::$default_strings['options'] as $option => $string ) {
self::register_string( $string, get_option( $option ), 'WordPress' );
}
// widgets titles
global $wp_registered_widgets;
$sidebars = wp_get_sidebars_widgets();
foreach ( $sidebars as $sidebar => $widgets ) {
if ( 'wp_inactive_widgets' == $sidebar || empty( $widgets ) ) {
continue;
}
foreach ( $widgets as $widget ) {
// nothing can be done if the widget is created using pre WP2.8 API :(
// there is no object, so we can't access it to get the widget options
if ( ! isset( $wp_registered_widgets[ $widget ]['callback'][0] ) || ! is_object( $wp_registered_widgets[ $widget ]['callback'][0] ) || ! method_exists( $wp_registered_widgets[ $widget ]['callback'][0], 'get_settings' ) ) {
continue;
}
$widget_settings = $wp_registered_widgets[ $widget ]['callback'][0]->get_settings();
$number = $wp_registered_widgets[ $widget ]['params'][0]['number'];
// don't enable widget translation if the widget is visible in only one language or if there is no title
if ( empty( $widget_settings[ $number ]['pll_lang'] ) ) {
if ( isset( $widget_settings[ $number ]['title'] ) && $title = $widget_settings[ $number ]['title'] ) {
self::register_string( self::$default_strings['widget_title'], $title, 'Widget' );
}
if ( isset( $widget_settings[ $number ]['text'] ) && $text = $widget_settings[ $number ]['text'] ) {
self::register_string( self::$default_strings['widget_text'], $text, 'Widget', true );
}
}
}
}
/**
* Filter the list of strings registered for translation
* Mainly for use by our PLL_WPML_Compat class
*
* @since 1.0.2
*
* @param array $strings list of strings
*/
self::$strings = apply_filters( 'pll_get_strings', self::$strings );
return self::$strings;
}
/**
* performs the sanitization ( before saving in DB ) of default strings translations
*
* @since 1.6
*
* @param string $translation translation to sanitize
* @param string $name unique name for the string
* @return string
*/
static public function sanitize_string_translation( $translation, $name ) {
$translation = wp_unslash( trim( $translation ) );
if ( false !== ( $option = array_search( $name, self::$default_strings['options'], true ) ) ) {
$translation = sanitize_option( $option, $translation );
}
if ( $name == self::$default_strings['widget_title'] ) {
$translation = strip_tags( $translation );
}
if ( $name == self::$default_strings['widget_text'] && ! current_user_can( 'unfiltered_html' ) ) {
$translation = wp_unslash( wp_filter_post_kses( addslashes( $translation ) ) ); // wp_filter_post_kses() expects slashed
}
return $translation;
}
}

View file

@ -0,0 +1,116 @@
<?php
/**
* admin side controller
* accessible in $polylang global object
*
* properties:
* options => inherited, reference to Polylang options array
* model => inherited, reference to PLL_Model object
* links_model => inherited, reference to PLL_Links_Model object
* links => inherited, reference to PLL_Admin_Links object
* static_pages => inherited, reference to PLL_Admin_Static_Pages object
* filters_links => inherited, reference to PLL_Filters_Links object
* curlang => inherited, optional, current language used to filter the content (language of the post or term being edited, equal to filter_lang otherwise)
* filter_lang => inherited, optional, current status of the admin languages filter (in the admin bar)
* pref_lang => inherited, preferred language used as default when saving posts or terms
* filters => reference to PLL_Filters object
* filters_columns => reference to PLL_Admin_Filters_Columns object
* filters_post => reference to PLL_Admin_Filters_Post object
* filters_term => reference to PLL_Admin_filters_Term object
* nav_menu => reference to PLL_Admin_Nav_Menu object
* filters_media => optional, reference to PLL_Admin_Filters_Media object
*
* @since 1.2
*/
class PLL_Admin extends PLL_Admin_Base {
public $filters, $filters_columns, $filters_post, $filters_term, $nav_menu, $sync, $filters_media;
/**
* loads the polylang text domain
* setups filters and action needed on all admin pages and on plugins page
*
* @since 1.2
*
* @param object $links_model
*/
public function __construct( &$links_model ) {
parent::__construct( $links_model );
// adds a 'settings' link in the plugins table
add_filter( 'plugin_action_links_' . POLYLANG_BASENAME, array( $this, 'plugin_action_links' ) );
add_action( 'in_plugin_update_message-' . POLYLANG_BASENAME, array( $this, 'plugin_update_message' ), 10, 2 );
}
/**
* setups filters and action needed on all admin pages and on plugins page
* loads the settings pages or the filters base on the request
*
* @since 1.2
*/
public function init() {
parent::init();
// setup filters for admin pages
// priority 5 to make sure filters are there before customize_register is fired
if ( $this->model->get_languages_list() ) {
add_action( 'wp_loaded', array( $this, 'add_filters' ), 5 );
}
}
/**
* adds a 'settings' link in the plugins table
*
* @since 0.1
*
* @param array $links list of links associated to the plugin
* @return array modified list of links
*/
public function plugin_action_links( $links ) {
array_unshift( $links, '<a href="admin.php?page=mlang">' . __( 'Settings', 'polylang' ) . '</a>' );
return $links;
}
/**
* adds the upgrade notice in plugins table
*
* @since 1.1.6
*
* @param array $plugin_data Not used
* @param object $r Plugin update data
*/
function plugin_update_message( $plugin_data, $r ) {
if ( isset( $r->upgrade_notice ) ) {
printf( '<p style="margin: 3px 0 0 0; border-top: 1px solid #ddd; padding-top: 3px">%s</p>', esc_html( $r->upgrade_notice ) );
}
}
/**
* setup filters for admin pages
*
* @since 1.2
*/
public function add_filters() {
// all these are separated just for convenience and maintainability
$classes = array( 'Filters', 'Filters_Columns', 'Filters_Post', 'Filters_Term', 'Nav_Menu', 'Sync' );
// don't load media filters if option is disabled or if user has no right
if ( $this->options['media_support'] && ( $obj = get_post_type_object( 'attachment' ) ) && ( current_user_can( $obj->cap->edit_posts ) || current_user_can( $obj->cap->create_posts ) ) ) {
$classes[] = 'Filters_Media';
}
foreach ( $classes as $class ) {
$obj = strtolower( $class );
/**
* Filter the class to instantiate when loding admin filters
*
* @since 1.5
*
* @param string $class class name
*/
$class = apply_filters( 'pll_' . $obj, 'PLL_Admin_' . $class );
$this->$obj = new $class( $this );
}
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* Displays the translations fields for media
* Needs WP 3.5+
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Don't access directly
};
?>
<p><strong><?php esc_html_e( 'Translations', 'polylang' ); ?></strong></p>
<table>
<?php
foreach ( $this->model->get_languages_list() as $language ) {
if ( $language->term_id == $lang->term_id ) {
continue;
}
?>
<tr>
<td class = "pll-media-language-column"><span class = "pll-translation-flag"><?php echo $language->flag; ?></span><?php echo esc_html( $language->name ); ?></td>
<td class = "pll-media-edit-column">
<?php
// The translation exists
if ( ( $translation_id = $this->model->post->get_translation( $post_id, $language ) ) && $translation_id !== $post_id ) {
printf(
'<input type="hidden" name="media_tr_lang[%s]" value="%d" />%s',
esc_attr( $language->slug ),
esc_attr( $translation_id ),
$this->links->edit_post_translation_link( $translation_id )
);
}
// No translation
else {
echo $this->links->new_post_translation_link( $post_id, $language );
}
?>
</td>
</tr>
<?php
} // End foreach
?>
</table>

View file

@ -0,0 +1,70 @@
<?php
/**
* displays the translations fields for posts
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // don't access directly
};
?>
<p><strong><?php esc_html_e( 'Translations', 'polylang' ); ?></strong></p>
<table>
<?php
foreach ( $this->model->get_languages_list() as $language ) {
if ( $language->term_id == $lang->term_id ) {
continue;
}
$value = $this->model->post->get_translation( $post_ID, $language );
if ( ! $value || $value == $post_ID ) { // $value == $post_ID happens if the post has been ( auto )saved before changing the language
$value = '';
}
if ( isset( $_GET['from_post'] ) ) {
$value = $this->model->post->get( (int) $_GET['from_post'], $language );
}
$link = $add_link = $this->links->new_post_translation_link( $post_ID, $language );
if ( $value ) {
$selected = get_post( $value );
$link = $this->links->edit_post_translation_link( $value );
}
?>
<tr>
<th class = "pll-language-column"><?php echo $language->flag ? $language->flag : esc_html( $language->slug ); ?></th>
<td class = "hidden"><?php echo $add_link; ?></td>
<td class = "pll-edit-column pll-column-icon"><?php echo $link; ?></td>
<?php
/**
* Fires before the translation colummn is outputed in the language metabox
* The dynamic portion of the hook name, `$lang`, refers to the language code
*
* @since 2.1
*/
do_action( 'pll_before_post_translation_' . $language->slug );
?>
<td class = "pll-translation-column">
<?php
printf( '
<label class="screen-reader-text" for="tr_lang_%1$s">%2$s</label>
<input type="hidden" name="post_tr_lang[%1$s]" id="htr_lang_%1$s" value="%3$s" />
<span lang="%6$s" dir="%7$s"><input type="text" class="tr_lang" id="tr_lang_%1$s" value="%4$s"%5$s /></span>',
esc_attr( $language->slug ),
/* translators: accessibility text */
esc_html__( 'Translation', 'polylang' ),
empty( $value ) ? 0 : esc_attr( $selected->ID ),
empty( $value ) ? '' : esc_attr( $selected->post_title ),
empty( $link ) ? ' disabled="disabled"' : '',
esc_attr( $language->get_locale( 'display' ) ),
$language->is_rtl ? 'rtl' : 'ltr'
);
?>
</td>
</tr>
<?php
}
?>
</table>

View file

@ -0,0 +1,97 @@
<?php
/**
* Displays the translations fields for terms
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Don't access directly
};
if ( isset( $term_id ) ) {
// Edit term form ?>
<th scope="row"><?php esc_html_e( 'Translations', 'polylang' ); ?></th>
<td>
<?php
}
else {
// Add term form
?>
<p><?php esc_html_e( 'Translations', 'polylang' ); ?></p>
<?php
}
?>
<table class="widefat term-translations" id="<?php echo isset( $term_id ) ? 'edit' : 'add'; ?>-term-translations">
<?php
foreach ( $this->model->get_languages_list() as $language ) {
if ( $language->term_id == $lang->term_id ) {
continue;
}
// Look for any existing translation in this language
// Take care not to propose a self link
$translation = 0;
if ( isset( $term_id ) && ( $translation_id = $this->model->term->get_translation( $term_id, $language ) ) && $translation_id != $term_id ) {
$translation = get_term( $translation_id, $taxonomy );
}
if ( isset( $_GET['from_tag'] ) && ( $translation_id = $this->model->term->get( (int) $_GET['from_tag'], $language ) ) && ! $this->model->term->get_translation( $translation_id, $lang ) ) {
$translation = get_term( $translation_id, $taxonomy );
}
if ( isset( $term_id ) ) { // Do not display the add new link in add term form ( $term_id not set !!! )
$link = $add_link = $this->links->new_term_translation_link( $term_id, $taxonomy, $post_type, $language );
}
if ( $translation ) {
$link = $this->links->edit_term_translation_link( $translation->term_id, $taxonomy, $post_type );
}
?>
<tr>
<th class = "pll-language-column">
<span class = "pll-translation-flag"><?php echo $language->flag; ?></span>
<?php
printf(
'<span class="pll-language-name%1$s">%2$s</span>',
isset( $term_id ) ? '' : ' screen-reader-text',
esc_html( $language->name )
);
?>
</th>
<?php
if ( isset( $term_id ) ) {
?>
<td class = "hidden"><?php echo $add_link; ?></td>
<td class = "pll-edit-column"><?php echo $link; ?></td>
<?php
}
?>
<td class = "pll-translation-column">
<?php
printf( '
<label class="screen-reader-text" for="tr_lang_%1$s">%2$s</label>
<input type="hidden" class="htr_lang" name="term_tr_lang[%1$s]" id="htr_lang_%1$s" value="%3$s" />
<span lang="%6$s" dir="%7$s"><input type="text" class="tr_lang" id="tr_lang_%1$s" value="%4$s"%5$s /></span>',
esc_attr( $language->slug ),
/* translators: accessibility text */
esc_html__( 'Translation', 'polylang' ),
empty( $translation ) ? 0 : esc_attr( $translation->term_id ),
empty( $translation ) ? '' : esc_attr( $translation->name ),
empty( $disabled ) ? '' : ' disabled="disabled"',
esc_attr( $language->get_locale( 'display' ) ),
$language->is_rtl ? 'rtl' : 'ltr'
);
?>
</td>
</tr>
<?php
} // End foreach
?>
</table>
<?php
if ( isset( $term_id ) ) {
// Edit term form
?>
</td>
<?php
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,400 @@
/* languages admin panel */
#add-lang select {
width: 95%;
}
.column-locale,
.languages .column-slug {
width : 15%
}
.column-default_lang {
width : 5%;
}
.column-term_group,
.column-flag, .column-count {
width : 10%;
}
.icon-default-lang:before {
font-family: 'dashicons';
content: "\f155";
}
.form-field input[type="radio"] {
width: auto;
margin-right: 2px;
}
/* about Polylang metabox */
#pll-about-box p,
#pll-recommended p {
text-align: justify;
}
#pll-about-box input {
margin: 0;
padding: 0;
float: right;
}
/* strings translation table */
.stringstranslations .column-name,
.stringstranslations .column-context {
width: 10%;
}
.stringstranslations .column-string {
width: 33%;
}
.translation label {
display: inline-block;
width: 23%;
vertical-align: top;
}
.translation input,
.translation textarea {
width: 72%;
}
/* settings */
.pll-settings {
margin-top: 20px;
}
.pll-settings .plugin-title {
width: 25%;
}
#wpbody-content .pll-settings .pll-configure tr {
display: table-row;
}
#wpbody-content .pll-settings .pll-configure td {
display: table-cell;
}
#wpbody-content .pll-settings .pll-configure > td {
padding: 20px 20px 20px 40px;
}
.pll-configure legend {
font-size: 14px;
font-weight: 600;
margin-bottom: 0.5em;
}
.pll-configure td .description {
margin-top: 2px;
margin-bottom: 0.5em;
}
.pll-configure p.submit {
margin-top: 20px;
}
.pll-configure .button {
margin-right: 20px;
}
.pll-configure fieldset {
margin-bottom: 1.5em;
}
.pll-inline-block-list {
margin: 0;
}
.pll-inline-block-list li {
display: inline-block;
margin: 0;
width: 250px;
}
/* settings URL modifications */
#pll-domains-table td {
padding: 2px 2px 2px 1.5em;
-webkit-box-shadow: none;
box-shadow: none;
border: none;
}
.pll-settings-url-col {
display: inline-block;
width: 49%;
vertical-align: top;
}
/* settings Activation keys */
#pll-licenses-table td {
vertical-align: top;
}
#pll-licenses-table label {
font-size: 1em;
font-weight: 600;
}
.pll-configure .pll-deactivate-license {
margin: 0 0 0 20px;
}
/* language columns in edit.php and edit-tags.php */
th[class*='column-language_'],
td[class*='column-language_'] {
width: 1.5em;
}
/* Text direcction in post.php and edit-tags.php */
.pll-dir-rtl textarea,
.pll-dir-rtl input[type="text"] {
direction: rtl;
}
.pll-dir-ltr textarea,
.pll-dir-ltr input[type="text"] {
direction: ltr;
}
.pll-dir-ltr .tr_lang,
.pll-dir-rtl .tr_lang {
direction: inherit;
}
/* languages metabox in post.php */
#post-translations p {
float: left;
}
#post-translations table {
table-layout: fixed;
width: 100%;
clear: both;
}
#post-translations a {
text-decoration: none;
}
#post-translations .pll-language-column,
#post-translations .pll-column-icon {
width: 20px;
}
#post-translations .tr_lang {
width: 100%;
}
#post-translations td {
padding: 2px;
}
#post-translations .spinner,
#term-translations .spinner {
float: none;
margin: 0;
background-position: center;
width: auto;
}
.pll-column-icon {
text-align: center;
}
#select-post-language .pll-select-flag {
padding: 4px;
margin-right: 32px;
}
/* specific cases for media */
#select-media-language .pll-select-flag {
padding: 4px;
margin-right: 10px;
}
.pll-media-edit-column {
float: right;
}
/* language and translations in edit-tags.php */
.pll-translation-flag { /* also for media */
margin-right: 14px;
}
#select-add-term-language .pll-select-flag {
padding: 11px;
margin-right: 13px;
}
#select-edit-term-language .pll-select-flag {
padding: 11px;
margin-right: 4px;
}
#term-translations p {
/* same style as label */
font-size: 12px;
font-style: normal;
padding: 2px;
color: #23282d;
}
#add-term-translations,
#edit-term-translations {
width: 95%;
}
#term-translations .pll-language-column {
line-height: 28px;
width: 20%;
}
#term-translations .pll-edit-column,
#add-term-translations .pll-language-column {
width: 20px;
}
#edit-term-translations .pll-language-column {
padding: 15px 10px;
font-weight: normal;
}
/* icon fonts */
.pll_icon_tick:before {
content: "\f147";
}
.pll_icon_add:before {
content: "\f132";
}
.pll_icon_edit:before {
content: "\f464";
}
[class^="pll_icon_"] {
font: 20px/1 'dashicons';
vertical-align: middle;
}
/* admin bar */
#wpadminbar #wp-admin-bar-languages .ab-item img {
margin: 0 8px 0 2px;
}
#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon {
float: none;
top: 4px;
}
#wpadminbar #wp-admin-bar-languages .ab-icon:before {
content: "\f326";
top: 1px;
}
@media screen and ( max-width: 782px ) {
/* settings */
#wpbody-content .pll-settings .pll-configure > td {
padding: 20px;
}
#wpbody-content .pll-settings #cb {
padding: 20px 9px;
}
/* settings URL modifications */
.pll-inline-block {
width: auto;
}
.pll-settings-url-col {
display: block;
width: 100%;
}
/* settings licenses */
#wpbody-content .pll-settings #pll-licenses-table td {
display: block;
}
.pll-configure .pll-deactivate-license {
margin: 10px 0 5px;
}
/* strings translations table */
.stringstranslations .column-context,
.stringstranslations .column-name {
display: none; /* backward compatibility WP < 4.3 */
}
.translation label {
display: block;
width: 95%;
padding-left: 0;
}
.translation input {
width: 95%;
}
/* hide selected language flag and translations language name */
#select-add-term-language .pll-select-flag,
#select-edit-term-language .pll-select-flag,
#edit-term-translations .pll-language-name {
display: none;
}
#edit-term-translations {
width: 100%;
}
#add-term-translations .pll-language-column {
line-height: 38px;
}
#edit-term-translations td {
padding: 8px 10px;
}
#edit-term-translations .pll-language-column,
#edit-term-translations .pll-edit-column {
width: 20px;
}
/* translations tables should be kept as table */
.term-translations .pll-language-column,
.term-translations .pll-edit-column,
.term-translations .pll-translation-column {
display: table-cell;
}
.term-translations .hidden {
display: none;
}
/* admin bar */
#wpadminbar #wp-admin-bar-languages {
display: block; /*shows our menu on mobile devices */
}
#wpadminbar #wp-admin-bar-languages > .ab-item {
width: 50px;
text-align: center;
}
#wpadminbar #wp-admin-bar-languages > .ab-item .ab-icon:before {
font: 32px/1 'dashicons';
top: -1px;
}
#wpadminbar #wp-admin-bar-languages > .ab-item img {
margin: 19px 0;
}
#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon {
margin-right: 6px;
font-size: 20px !important;
line-height: 20px !important;
}
}

View file

@ -0,0 +1 @@
#pll-licenses-table td,.translation label{vertical-align:top}#add-lang select{width:95%}.column-locale,.languages .column-slug{width:15%}.column-default_lang{width:5%}.column-count,.column-flag,.column-term_group{width:10%}.icon-default-lang:before{font-family:dashicons;content:"\f155"}.form-field input[type=radio]{width:auto;margin-right:2px}#pll-about-box p,#pll-recommended p{text-align:justify}#pll-about-box input{margin:0;padding:0;float:right}.stringstranslations .column-context,.stringstranslations .column-name{width:10%}.stringstranslations .column-string{width:33%}.translation label{display:inline-block;width:23%}.translation input,.translation textarea{width:72%}.pll-settings{margin-top:20px}.pll-settings .plugin-title{width:25%}#wpbody-content .pll-settings .pll-configure tr{display:table-row}#wpbody-content .pll-settings .pll-configure td{display:table-cell}#wpbody-content .pll-settings .pll-configure>td{padding:20px 20px 20px 40px}.pll-configure legend{font-size:14px;font-weight:600;margin-bottom:.5em}.pll-configure td .description{margin-top:2px;margin-bottom:.5em}.pll-configure p.submit{margin-top:20px}.pll-configure .button{margin-right:20px}.pll-configure fieldset{margin-bottom:1.5em}.pll-inline-block-list{margin:0}.pll-inline-block-list li{display:inline-block;margin:0;width:250px}#pll-domains-table td{padding:2px 2px 2px 1.5em;-webkit-box-shadow:none;box-shadow:none;border:none}.pll-settings-url-col{display:inline-block;width:49%;vertical-align:top}#pll-licenses-table label{font-size:1em;font-weight:600}.pll-configure .pll-deactivate-license{margin:0 0 0 20px}td[class*=column-language_],th[class*=column-language_]{width:1.5em}.pll-dir-rtl input[type=text],.pll-dir-rtl textarea{direction:rtl}.pll-dir-ltr input[type=text],.pll-dir-ltr textarea{direction:ltr}.pll-dir-ltr .tr_lang,.pll-dir-rtl .tr_lang{direction:inherit}#post-translations p{float:left}#post-translations table{table-layout:fixed;width:100%;clear:both}#post-translations a{text-decoration:none}#post-translations .pll-column-icon,#post-translations .pll-language-column{width:20px}#post-translations .tr_lang{width:100%}#post-translations td{padding:2px}#post-translations .spinner,#term-translations .spinner{float:none;margin:0;background-position:center;width:auto}.pll-column-icon{text-align:center}#select-post-language .pll-select-flag{padding:4px;margin-right:32px}#select-media-language .pll-select-flag{padding:4px;margin-right:10px}.pll-media-edit-column{float:right}.pll-translation-flag{margin-right:14px}#select-add-term-language .pll-select-flag{padding:11px;margin-right:13px}#select-edit-term-language .pll-select-flag{padding:11px;margin-right:4px}#term-translations p{font-size:12px;font-style:normal;padding:2px;color:#23282d}#add-term-translations,#edit-term-translations{width:95%}#term-translations .pll-language-column{line-height:28px;width:20%}#add-term-translations .pll-language-column,#term-translations .pll-edit-column{width:20px}#edit-term-translations .pll-language-column{padding:15px 10px;font-weight:400}.pll_icon_tick:before{content:"\f147"}.pll_icon_add:before{content:"\f132"}.pll_icon_edit:before{content:"\f464"}[class^=pll_icon_]{font:20px/1 dashicons;vertical-align:middle}#wpadminbar #wp-admin-bar-languages .ab-item img{margin:0 8px 0 2px}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{float:none;top:4px}#wpadminbar #wp-admin-bar-languages .ab-icon:before{content:"\f326";top:1px}@media screen and (max-width:782px){#wpbody-content .pll-settings .pll-configure>td{padding:20px}#wpbody-content .pll-settings #cb{padding:20px 9px}.pll-inline-block{width:auto}.pll-settings-url-col{display:block;width:100%}#wpbody-content .pll-settings #pll-licenses-table td{display:block}.pll-configure .pll-deactivate-license{margin:10px 0 5px}.stringstranslations .column-context,.stringstranslations .column-name{display:none}.translation label{display:block;width:95%;padding-left:0}.translation input{width:95%}#edit-term-translations .pll-language-name,#select-add-term-language .pll-select-flag,#select-edit-term-language .pll-select-flag{display:none}#edit-term-translations{width:100%}#add-term-translations .pll-language-column{line-height:38px}#edit-term-translations td{padding:8px 10px}#edit-term-translations .pll-edit-column,#edit-term-translations .pll-language-column{width:20px}.term-translations .pll-edit-column,.term-translations .pll-language-column,.term-translations .pll-translation-column{display:table-cell}.term-translations .hidden{display:none}#wpadminbar #wp-admin-bar-languages{display:block}#wpadminbar #wp-admin-bar-languages>.ab-item{width:50px;text-align:center}#wpadminbar #wp-admin-bar-languages>.ab-item .ab-icon:before{font:32px/1 dashicons;top:-1px}#wpadminbar #wp-admin-bar-languages>.ab-item img{margin:19px 0}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{margin-right:6px;font-size:20px!important;line-height:20px!important}}

View file

@ -0,0 +1,140 @@
/* Greatly modified version of the jquery-ui.css */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: none;
}
.ui-menu .ui-menu {
position: absolute;
}
.ui-menu .ui-menu-item {
position: relative;
margin: 0;
padding: 3px 1em 3px .4em;
min-height: 0; /* support: IE7 */
/* support: IE10, see #8844 */
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item {
padding-left: 2em;
}
/* left-aligned */
.ui-selectmenu-text .ui-icon,
.ui-menu .ui-icon {
position: absolute;
top: 0;
bottom: 0;
left: .2em;
margin: auto 0;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
left: auto;
right: 0;
}
.ui-selectmenu-menu {
padding: 0;
margin: 0;
position: absolute;
top: 0;
left: 0;
display: none;
}
.ui-selectmenu-menu .ui-menu {
overflow: auto;
/* Support: IE7 */
overflow-x: hidden;
padding-bottom: 1px;
}
.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
font-size: 1em;
font-weight: bold;
line-height: 23px;
padding: 2px 0.4em;
margin: 0.5em 0 0 0;
height: auto;
border: 0;
}
.ui-selectmenu-open {
display: block;
}
.ui-selectmenu-button {
display: inline-block;
overflow: hidden;
position: relative;
text-decoration: none;
}
.ui-selectmenu-button span.ui-icon {
right: 0.5em;
left: auto;
margin-top: -10px;
position: absolute;
top: 50%;
}
.ui-selectmenu-button span.ui-selectmenu-text {
text-align: left;
padding: 0.4em 2.1em 0.4em 2em;
display: block;
line-height: 23px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ui-widget-content,
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
background: #fff;
border: 1px solid #ddd;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07) inset;
color: #32373c;
}
.ui-widget-content .ui-state-hover,
.ui-widget-content .ui-state-focus {
background: #f5f5f5;
}
.ui-selectmenu-button.ui-state-focus {
border: 1px solid #5b9dd9;
box-shadow: 0 0 2px rgba(30, 140, 190, 0.8);
}
.ui-icon-triangle-1-s:before {
content: "\f140";
font: 20px/1 'dashicons';
}
.ui-widget-content {
max-height: 231px;
box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3);
}

View file

@ -0,0 +1 @@
.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;min-height:0;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon,.ui-selectmenu-text .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:700;line-height:23px;padding:2px .4em;margin:.5em 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none}.ui-selectmenu-button span.ui-icon{right:.5em;left:auto;margin-top:-10px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:.4em 2.1em .4em 2em;display:block;line-height:23px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-state-default,.ui-widget-content,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{background:#fff;border:1px solid #ddd;box-shadow:0 1px 2px rgba(0,0,0,.07) inset;color:#32373c}.ui-widget-content .ui-state-focus,.ui-widget-content .ui-state-hover{background:#f5f5f5}.ui-selectmenu-button.ui-state-focus{border:1px solid #5b9dd9;box-shadow:0 0 2px rgba(30,140,190,.8)}.ui-icon-triangle-1-s:before{content:"\f140";font:20px/1 dashicons}.ui-widget-content{max-height:231px;box-shadow:0 2px 6px rgba(100,100,100,.3)}

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Some files were not shown because too many files have changed in this diff Show more