PluginProbe
Polylang / 1.5
Polylang v1.5
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
← All changes | admin/admin-filters-post.php +302 -145 2.81.5 View file →
@@ -1,215 +1,372 @@
1 1 <?php
2 -/**
3 - * @package Polylang
4 - */
5 2
6 -/**
7 - * Manages filters and actions related to posts on admin side
3 +/*
4 + * manages filters and actions related to posts on admin side
8 5 *
9 6 * @since 1.2
10 7 */
11 8 class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
12 - public $curlang;
9 + public $options, $curlang;
13 10
14 - /**
15 - * Constructor: setups filters and actions
11 + /*
12 + * constructor: setups filters and actions
16 13 *
17 14 * @since 1.2
18 15 *
19 16 * @param object $polylang
20 17 */
21 - public function __construct( &$polylang ) {
22 - parent::__construct( $polylang );
18 + public function __construct(&$polylang) {
19 + parent::__construct($polylang);
20 + $this->options = &$polylang->options;
23 21 $this->curlang = &$polylang->curlang;
24 22
25 - add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
23 + // filters posts, pages and media by language
24 + add_filter('parse_query',array(&$this,'parse_query'));
26 25
27 - // Filters posts, pages and media by language
28 - add_action( 'parse_query', array( $this, 'parse_query' ) );
26 + // adds the Languages box in the 'Edit Post' and 'Edit Page' panels
27 + add_action('add_meta_boxes', array(&$this, 'add_meta_boxes'));
29 28
30 - // Adds actions and filters related to languages when creating, saving or deleting posts and pages
31 - add_action( 'load-post.php', array( $this, 'edit_post' ) );
32 - add_action( 'load-edit.php', array( $this, 'bulk_edit_posts' ) );
33 - add_action( 'wp_ajax_inline-save', array( $this, 'inline_edit_post' ), 0 ); // Before WordPress
29 + // ajax response for changing the language in the post metabox
30 + add_action('wp_ajax_post_lang_choice', array(&$this,'post_lang_choice'));
31 + add_action('wp_ajax_pll_posts_not_translated', array(&$this,'ajax_posts_not_translated'));
34 32
35 - // Sets the language in Tiny MCE
36 - add_filter( 'tiny_mce_before_init', array( $this, 'tiny_mce_before_init' ) );
33 + // adds actions and filters related to languages when creating, saving or deleting posts and pages
34 + add_action('save_post', array(&$this, 'save_post'), 21, 2); // priority 21 to come after advanced custom fields (20) and before the event calendar which breaks everything after 25
35 + add_action('before_delete_post', array(&$this, 'delete_post'));
36 + if ($this->options['media_support'])
37 + add_action('delete_attachment', array(&$this, 'delete_post')); // action shared with media
38 +
39 + // filters the pages by language in the parent dropdown list in the page attributes metabox
40 + add_filter('page_attributes_dropdown_pages_args', array(&$this, 'page_attributes_dropdown_pages_args'), 10, 2);
37 41 }
38 42
39 - /**
40 - * Outputs a javascript list of terms ordered by language and hierarchical taxonomies
41 - * to filter the category checklist per post language in quick edit
42 - * Outputs a javascript list of pages ordered by language
43 - * to filter the parent dropdown per post language in quick edit
43 + /*
44 + * filters posts, pages and media by language
44 45 *
45 - * @since 1.7
46 + * @since 0.1
47 + *
48 + * @param object $query a WP_Query object
46 49 */
47 - public function admin_enqueue_scripts() {
48 - $screen = get_current_screen();
50 + public function parse_query($query) {
51 + $qvars = &$query->query_vars;
49 52
50 - // Hierarchical taxonomies
51 - if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'object' ) ) {
52 - // Get translated hierarchical taxonomies
53 - $hierarchical_taxonomies = array();
54 - foreach ( $taxonomies as $taxonomy ) {
55 - if ( $taxonomy->hierarchical && $taxonomy->show_in_quick_edit && $this->model->is_translated_taxonomy( $taxonomy->name ) ) {
56 - $hierarchical_taxonomies[] = $taxonomy->name;
57 - }
58 - }
53 + // do not filter post types such as nav_menu_item
54 + if (isset($qvars['post_type']) && !$this->model->is_translated_post_type($qvars['post_type'])) {
55 + unset ($qvars['lang']);
56 + return;
57 + }
59 58
60 - if ( ! empty( $hierarchical_taxonomies ) ) {
61 - $terms = get_terms( $hierarchical_taxonomies, array( 'get' => 'all' ) );
62 - $term_languages = array();
59 + if (isset($qvars['post_type']) && !isset($qvars['lang'])) {
60 + // filters the list of media (or wp-links) by language when uploading from post
61 + if (isset($_REQUEST['pll_post_id']) && $lang = $this->model->get_post_language($_REQUEST['pll_post_id']))
62 + $query->set('lang', $lang->slug);
63 63
64 - foreach ( $terms as $term ) {
65 - if ( $lang = $this->model->term->get_language( $term->term_id ) ) {
66 - $term_languages[ $lang->slug ][ $term->taxonomy ][] = $term->term_id;
67 - }
68 - }
64 + elseif (!empty($this->curlang))
65 + $qvars['lang'] = $this->curlang->slug;
66 + }
69 67
70 - // Send all these data to javascript
71 - if ( ! empty( $term_languages ) ) {
72 - wp_localize_script( 'pll_post', 'pll_term_languages', $term_languages );
73 - }
74 - }
75 - }
68 + if (isset($qvars['lang']) && 'all' === $qvars['lang'])
69 + unset ($qvars['lang']);
70 + }
76 71
77 - // Hierarchical post types
78 - if ( 'edit' == $screen->base && is_post_type_hierarchical( $screen->post_type ) ) {
79 - $pages = get_pages( array( 'sort_column' => 'menu_order, post_title' ) ); // Same arguments as the parent pages dropdown to avoid an extra query.
80 - update_post_caches( $pages, $screen->post_type );
72 + /*
73 + * adds the Language box in the 'Edit Post' and 'Edit Page' panels (as well as in custom post types panels)
74 + *
75 + * @since 0.1
76 + *
77 + * @param string $post_type
78 + */
79 + public function add_meta_boxes($post_type) {
80 + if ($this->model->is_translated_post_type($post_type))
81 + add_meta_box('ml_box', __('Languages','polylang'), array(&$this, 'post_language'), $post_type, 'side', 'high');
82 + }
81 83
82 - $page_languages = array();
84 + /*
85 + * displays the Languages metabox in the 'Edit Post' and 'Edit Page' panels
86 + *
87 + * @since 0.1
88 + */
89 + public function post_language() {
90 + global $post_ID;
91 + $post_id = $post_ID;
92 + $post_type = get_post_type($post_ID);
83 93
84 - foreach ( $pages as $page ) {
85 - if ( $lang = $this->model->post->get_language( $page->ID ) ) {
86 - $page_languages[ $lang->slug ][] = $page->ID;
87 - }
94 + $lang = ($lg = $this->model->get_post_language($post_ID)) ? $lg :
95 + (isset($_GET['new_lang']) ? $this->model->get_language($_GET['new_lang']) :
96 + $this->pref_lang);
97 +
98 + $dropdown = new PLL_Walker_Dropdown();
99 +
100 + wp_nonce_field('pll_language', '_pll_nonce');
101 +
102 + // NOTE: the class "tags-input" allows to include the field in the autosave $_POST (see autosave.js)
103 + printf('
104 + <p><strong>%s</strong></p>
105 + %s
106 + <div id="post-translations" class="translations">',
107 + __('Language', 'polylang'),
108 + $dropdown->walk($this->model->get_languages_list(), array(
109 + 'name' => $post_type == 'attachment' ? sprintf('attachments[%d][language]', $post_ID) : 'post_lang_choice',
110 + 'class' => $post_type == 'attachment' ? 'media_lang_choice' : 'tags-input',
111 + 'selected' => $lang ? $lang->slug : ''
112 + ))
113 + );
114 + if ($lang)
115 + include(PLL_ADMIN_INC.'/view-translations-'.($post_type == 'attachment' ? 'media' : 'post').'.php');
116 + echo '</div>'."\n";
117 + }
118 +
119 + /*
120 + * ajax response for changing the language in the post metabox
121 + *
122 + * @since 0.2
123 + */
124 + public function post_lang_choice() {
125 + check_ajax_referer('pll_language', '_pll_nonce');
126 +
127 + global $post_ID; // obliged to use the global variable for wp_popular_terms_checklist
128 + $post_ID = $_POST['post_id'];
129 + $post_type = get_post_type($post_ID);
130 + $lang = $this->model->get_language($_POST['lang']);
131 +
132 + $post_type_object = get_post_type_object($post_type);
133 + if (!current_user_can($post_type_object->cap->edit_posts) || !current_user_can($post_type_object->cap->create_posts))
134 + wp_die(-1);
135 +
136 + $this->model->set_post_language($post_ID, $lang); // save language, useful to set the language when uploading media from post
137 +
138 + ob_start();
139 + if ($lang)
140 + include(PLL_ADMIN_INC.'/view-translations-post.php');
141 + $x = new WP_Ajax_Response(array('what' => 'translations', 'data' => ob_get_contents()));
142 + ob_end_clean();
143 +
144 + // categories
145 + if (isset($_POST['taxonomies'])) {
146 + // not set for pages
147 + foreach ($_POST['taxonomies'] as $taxname) {
148 + $taxonomy = get_taxonomy($taxname);
149 +
150 + ob_start();
151 + $popular_ids = wp_popular_terms_checklist($taxonomy->name);
152 + $supplemental['populars'] = ob_get_contents();
153 + ob_end_clean();
154 +
155 + ob_start();
156 + // use $post_ID to remember ckecked terms in case we come back to the original language
157 + wp_terms_checklist( $post_ID, array( 'taxonomy' => $taxonomy->name, 'popular_cats' => $popular_ids ));
158 + $supplemental['all'] = ob_get_contents();
159 + ob_end_clean();
160 +
161 + $supplemental['dropdown'] = wp_dropdown_categories(array(
162 + 'taxonomy' => $taxonomy->name,
163 + 'hide_empty' => 0,
164 + 'name' => 'new'.$taxonomy->name.'_parent',
165 + 'orderby' => 'name',
166 + 'hierarchical' => 1,
167 + 'show_option_none' => '&mdash; '.$taxonomy->labels->parent_item.' &mdash;',
168 + 'echo' => 0
169 + ));
170 +
171 + $x->Add(array('what' => 'taxonomy', 'data' => $taxonomy->name, 'supplemental' => $supplemental));
88 172 }
173 + }
89 174
90 - // Send all these data to javascript
91 - if ( ! empty( $page_languages ) ) {
92 - wp_localize_script( 'pll_post', 'pll_page_languages', $page_languages );
93 - }
175 + // parent dropdown list (only for hierarchical post types)
176 + if (in_array($post_type, get_post_types(array('hierarchical' => true)))) {
177 + require_once( ABSPATH . 'wp-admin/includes/meta-boxes.php' );
178 + ob_start();
179 + page_attributes_meta_box(get_post($post_ID));
180 + $x->Add(array('what' => 'pages', 'data' => ob_get_contents()));
181 + ob_end_clean();
94 182 }
183 +
184 + $x->send();
95 185 }
96 186
97 - /**
98 - * Filters posts, pages and media by language
187 + /*
188 + * ajax response for input in translation autocomplete input box
99 189 *
100 - * @since 0.1
101 - *
102 - * @param object $query a WP_Query object
190 + * @since 1.5
103 191 */
104 - public function parse_query( $query ) {
105 - $pll_query = new PLL_Query( $query, $this->model );
106 - $pll_query->filter_query( $this->curlang );
192 + public function ajax_posts_not_translated() {
193 + check_ajax_referer('pll_language', '_pll_nonce');
194 +
195 + $posts = get_posts(array(
196 + 's' => $_REQUEST['term'],
197 + 'suppress_filters' => 0, // to make the post_fields filter work
198 + 'lang' => 0, // avoid admin language filter
199 + 'numberposts' => 10, // limit to 10 posts
200 + 'post_status' => 'any',
201 + 'post_type' => $_REQUEST['post_type'],
202 + 'orderby' => 'title',
203 + 'order' => 'ASC',
204 + 'tax_query' => array(array(
205 + 'taxonomy' => 'language',
206 + 'field' => 'term_taxonomy_id', // WP 3.5+
207 + 'terms' => $this->model->get_language($_REQUEST['translation_language'])->term_taxonomy_id
208 + ))
209 + ));
210 +
211 + $return = array();
212 +
213 + foreach ($posts as $key => $post) {
214 + if (!$this->model->get_translation('post', $post->ID, $_REQUEST['post_language']))
215 + $return[] = array('id' => $post->ID, 'value' => $post->post_title, 'link' => $this->edit_translation_link($post->ID));
216 + }
217 +
218 + // add current translation in list
219 + if ($post_id = $this->model->get_translation('post', $_REQUEST['pll_post_id'],$_REQUEST['translation_language'])) {
220 + $post = get_post($post_id);
221 + array_unshift($return, array(
222 + 'id' => $post_id,
223 + 'value' => $post->post_title,
224 + 'link' => $this->edit_translation_link($post_id)
225 + ));
226 + }
227 +
228 + wp_die(json_encode($return));
107 229 }
108 230
109 - /**
110 - * Save language and translation when editing a post (post.php)
231 + /*
232 + * saves language
233 + * checks the terms saved are in the right language
111 234 *
112 - * @since 2.3
235 + * @since 1.5
236 + *
237 + * @param int $post_id
238 + * @param array $post
113 239 */
114 - public function edit_post() {
115 - if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $post_id = (int) $_POST['post_ID'] ) { // phpcs:ignore WordPress.Security.NonceVerification
116 - check_admin_referer( 'pll_language', '_pll_nonce' );
240 + protected function save_language($post_id, $post) {
241 + // security checks are necessary to accept language modifications
242 + // as 'wp_insert_post' can be called from outside WP admin
117 243
118 - $post = get_post( $post_id );
119 - $post_type_object = get_post_type_object( $post->post_type );
244 + // edit post
245 + if (isset($_REQUEST['post_lang_choice'])) {
246 + check_admin_referer('pll_language', '_pll_nonce');
247 + $this->model->set_post_language($post_id, $lang = $_REQUEST['post_lang_choice']);
248 + }
120 249
121 - if ( current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
122 - $this->model->post->set_language( $post_id, $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ) );
250 + // quick edit and bulk edit
251 + elseif (isset($_REQUEST['inline_lang_choice'])) {
252 + // bulk edit does not modify the language
253 + if (isset($_REQUEST['bulk_edit']) && $_REQUEST['inline_lang_choice'] == -1)
254 + continue;
123 255
124 - if ( isset( $_POST['post_tr_lang'] ) ) {
125 - $this->save_translations( $post_id, array_map( 'absint', $_POST['post_tr_lang'] ) );
256 + isset($_REQUEST['bulk_edit']) ? check_admin_referer('bulk-posts') : check_admin_referer('inlineeditnonce', '_inline_edit');
257 +
258 + if (($old_lang = $this->model->get_post_language($post_id)) && $old_lang->slug != $_REQUEST['inline_lang_choice'])
259 + $this->model->delete_translation('post', $post_id);
260 + $this->model->set_post_language($post_id, $lang = $_REQUEST['inline_lang_choice']);
261 + }
262 +
263 + // quick press
264 + // 'post-quickpress-save', 'post-quickpress-publish' = backward compatibility WP < 3.8
265 + elseif (isset($_REQUEST['action']) && in_array($_REQUEST['action'], array('post-quickpress-save', 'post-quickpress-publish', 'post-quickdraft-save'))) {
266 + check_admin_referer('add-' . $post->post_type);
267 + $this->model->set_post_language($post_id, $this->pref_lang); // default language for Quick draft
268 + }
269 +
270 + else
271 + $this->set_default_language($post_id);
272 +
273 + // make sure we get save terms in the right language (especially tags with same name in different languages)
274 + if (!empty($lang)) {
275 + // FIXME quite a lot of queries in foreach
276 + foreach ($this->model->get_translated_taxonomies() as $tax) {
277 + $terms = get_the_terms($post_id, $tax);
278 +
279 + if (is_array($terms)) {
280 + $newterms = array();
281 + foreach ($terms as $term) {
282 + if ($newterm = $this->model->term_exists($term->name, $tax, $term->parent, $lang))
283 + $newterms[] = (int) $newterm; // cast is important otherwise we get 'numeric' tags
284 +
285 + elseif (!is_wp_error($term_info = wp_insert_term($term->name, $tax))) // create the term in the correct language
286 + $newterms[] = (int) $term_info['term_id'];
287 + }
288 +
289 + wp_set_object_terms($post_id, $newterms, $tax);
126 290 }
127 291 }
128 292 }
129 293 }
130 294
131 - /**
132 - * Save language when inline editing or bulk editing a post
133 - * Fix translations if necessary
295 + /*
296 + * called when a post (or page) is saved, published or updated
297 + * saves languages and translations
134 298 *
135 - * @since 2.3
299 + * @since 0.1
136 300 *
137 - * @param int $post_id Post ID
138 - * @param object $lang Language
301 + * @param int $post_id
302 + * @param object $post
139 303 */
140 - protected function inline_save_language( $post_id, $lang ) {
141 - $post = get_post( $post_id );
142 - $post_type_object = get_post_type_object( $post->post_type );
304 + public function save_post($post_id, $post) {
305 + // does nothing except on post types which are filterable
306 + if (!$this->model->is_translated_post_type($post->post_type))
307 + return;
143 308
144 - if ( current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
145 - $old_lang = $this->model->post->get_language( $post_id ); // Stores the old language
146 - $this->model->post->set_language( $post_id, $lang ); // set new language
309 + // capability check
310 + // as 'wp_insert_post' can be called from outside WP admin
311 + $post_type_object = get_post_type_object($post->post_type);
312 + if (!current_user_can($post_type_object->cap->edit_posts) || !current_user_can($post_type_object->cap->create_posts))
313 + wp_die( __( 'Cheatin&#8217; uh?' ) );
147 314
148 - // Checks if the new language already exists in the translation group
149 - if ( $old_lang && $old_lang->slug != $lang->slug ) {
150 - $translations = $this->model->post->get_translations( $post_id );
315 + if ($id = wp_is_post_revision($post_id))
316 + $post_id = $id;
151 317
152 - // If yes, separate this post from the translation group
153 - if ( array_key_exists( $lang->slug, $translations ) ) {
154 - $this->model->post->delete_translation( $post_id );
155 - }
318 + $this->save_language($post_id, $post);
156 319
157 - elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
158 - unset( $translations[ $old_lang->slug ] );
159 - $this->model->post->save_translations( $post_id, $translations );
160 - }
161 - }
162 - }
320 + if (isset($_POST['post_tr_lang']))
321 + $translations = $this->save_translations($post_id, $_POST['post_tr_lang']);
322 +
323 + do_action('pll_save_post', $post_id, $post, empty($translations) ? $this->model->get_translations('post', $post_id) : $translations);
163 324 }
164 325
165 - /**
166 - * Save language when bulk editing a post
326 + /*
327 + * called when a post, page or media is deleted
328 + * don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
329 + * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
167 330 *
168 - * @since 2.3
331 + * @since 0.1
332 + *
333 + * @param int $post_id
169 334 */
170 - public function bulk_edit_posts() {
171 - if ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'], $_REQUEST['post'] ) && -1 !== $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification
172 - check_admin_referer( 'bulk-posts' );
173 -
174 - if ( $lang = $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ) ) {
175 - $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
176 - foreach ( $post_ids as $post_id ) {
177 - $this->inline_save_language( $post_id, $lang );
178 - }
179 - }
180 - }
335 + public function delete_post($post_id) {
336 + if (!wp_is_post_revision($post_id))
337 + $this->model->delete_translation('post', $post_id);
181 338 }
182 339
183 - /**
184 - * Save language when inline editing a post
340 + /*
341 + * filters the pages by language in the parent dropdown list in the page attributes metabox
185 342 *
186 - * @since 2.3
343 + * @since 0.6
344 + *
345 + * @param array $dropdown_args arguments passed to wp_dropdown_pages
346 + * @param object $post
347 + * @return array modified arguments
187 348 */
188 - public function inline_edit_post() {
189 - check_admin_referer( 'inlineeditnonce', '_inline_edit' );
349 + public function page_attributes_dropdown_pages_args($dropdown_args, $post) {
350 + $dropdown_args['lang'] = isset($_POST['lang']) ? $this->model->get_language($_POST['lang']) : $this->model->get_post_language($post->ID); // ajax or not ?
351 + if (!$dropdown_args['lang'])
352 + $dropdown_args['lang'] = $this->pref_lang;
190 353
191 - if ( isset( $_POST['post_ID'], $_POST['inline_lang_choice'] ) ) {
192 - $post_id = (int) $_POST['post_ID'];
193 - $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) );
194 - if ( $post_id && $lang ) {
195 - $this->inline_save_language( $post_id, $lang );
196 - }
197 - }
354 + return $dropdown_args;
198 355 }
199 356
200 - /**
201 - * Sets the language attribute and text direction for Tiny MCE
357 + /*
358 + * returns html markup for a translation link
202 359 *
203 - * @since 2.2
360 + * @since 1.4
204 361 *
205 - * @param array $mce_init TinyMCE config
206 - * @return array
362 + * @param int $post_id translation post id
363 + * @return string
207 364 */
208 - public function tiny_mce_before_init( $mce_init ) {
209 - if ( ! empty( $this->curlang ) ) {
210 - $mce_init['wp_lang_attr'] = $this->curlang->get_locale( 'display' );
211 - $mce_init['directionality'] = $this->curlang->is_rtl ? 'rtl' : 'ltr';
212 - }
213 - return $mce_init;
365 + public function edit_translation_link($post_id) {
366 + return sprintf(
367 + '<a href="%1$s" class="pll_icon_edit" title="%2$s"></a>',
368 + esc_url(get_edit_post_link($post_id)),
369 + __('Edit', 'polylang')
370 + );
214 371 }
215 372 }