PluginProbe
Polylang / 2.0.12
Polylang v2.0.12
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
polylang / admin / admin-filters-post.php

admin-filters-post.php in Polylang 2.0.12, at admin/admin-filters-post.php

576 lines 20.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * manages filters and actions related to posts on admin side
5 *
6 * @since 1.2
7 */
8 class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
9 public $options, $curlang;
10
11 /**
12 * constructor: setups filters and actions
13 *
14 * @since 1.2
15 *
16 * @param object $polylang
17 */
18 public function __construct( &$polylang ) {
19 parent::__construct( $polylang );
20 $this->options = &$polylang->options;
21 $this->curlang = &$polylang->curlang;
22
23 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
24
25 // filters posts, pages and media by language
26 add_action( 'parse_query', array( $this, 'parse_query' ) );
27
28 // adds the Languages box in the 'Edit Post' and 'Edit Page' panels
29 add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
30
31 // ajax response for changing the language in the post metabox
32 add_action( 'wp_ajax_post_lang_choice', array( $this, 'post_lang_choice' ) );
33 add_action( 'wp_ajax_pll_posts_not_translated', array( $this, 'ajax_posts_not_translated' ) );
34
35 // adds actions and filters related to languages when creating, saving or deleting posts and pages
36 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
37 add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 4 );
38 add_action( 'before_delete_post', array( $this, 'delete_post' ) );
39 if ( $this->options['media_support'] ) {
40 add_action( 'delete_attachment', array( $this, 'delete_post' ) ); // action shared with media
41 }
42
43 // filters the pages by language in the parent dropdown list in the page attributes metabox
44 add_filter( 'page_attributes_dropdown_pages_args', array( $this, 'page_attributes_dropdown_pages_args' ), 10, 2 );
45 }
46
47 /**
48 * outputs a javascript list of terms ordered by language and hierarchical taxonomies
49 * to filter the category checklist per post language in quick edit
50 * outputs a javascript list of pages ordered by language
51 * to filter the parent dropdown per post language in quick edit
52 *
53 * @since 1.7
54 */
55 public function admin_enqueue_scripts() {
56 $screen = get_current_screen();
57
58 //hierarchical taxonomies
59 if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'object' ) ) {
60 // get translated hierarchical taxonomies
61 foreach ( $taxonomies as $taxonomy ) {
62 if ( $taxonomy->hierarchical && $taxonomy->show_in_quick_edit && $this->model->is_translated_taxonomy( $taxonomy->name ) ) {
63 $hierarchical_taxonomies[] = $taxonomy->name;
64 }
65 }
66
67 if ( ! empty( $hierarchical_taxonomies ) ) {
68 $terms = get_terms( $hierarchical_taxonomies, array( 'get' => 'all' ) );
69
70 foreach ( $terms as $term ) {
71 if ( $lang = $this->model->term->get_language( $term->term_id ) ) {
72 $term_languages[ $lang->slug ][ $term->taxonomy ][] = $term->term_id;
73 }
74 }
75
76 // send all these data to javascript
77 if ( ! empty( $term_languages ) ) {
78 wp_localize_script( 'pll_post', 'pll_term_languages', $term_languages );
79 }
80 }
81 }
82
83 // hierarchical post types
84 if ( 'edit' == $screen->base && is_post_type_hierarchical( $screen->post_type ) ) {
85 $pages = get_pages();
86
87 foreach ( $pages as $page ) {
88 if ( $lang = $this->model->post->get_language( $page->ID ) ) {
89 $page_languages[ $lang->slug ][] = $page->ID;
90 }
91 }
92
93 // send all these data to javascript
94 if ( ! empty( $page_languages ) ) {
95 wp_localize_script( 'pll_post', 'pll_page_languages', $page_languages );
96 }
97 }
98 }
99
100 /**
101 * filters posts, pages and media by language
102 *
103 * @since 0.1
104 *
105 * @param object $query a WP_Query object
106 */
107 public function parse_query( $query ) {
108 $qvars = &$query->query_vars;
109
110 // do not filter post types such as nav_menu_item
111 if ( isset( $qvars['post_type'] ) && ! $this->model->is_translated_post_type( $qvars['post_type'] ) ) {
112 unset( $qvars['lang'] );
113 return;
114 }
115
116 // Do not filter the query if the language is already specified in another way
117 if ( ! isset( $qvars['lang'] ) ) {
118 $excludes = array(
119 'p', 'post_parent', 'attachment', 'attachment_id', 'name', 'pagename', 'page_id',
120 'category_name', 'tag', 'cat', 'tag_id', 'category__in', 'category__not_in', 'category__and',
121 'post__in', 'post__not_in', 'post_name__in', 'tag__in', 'tag__not_in', 'tag__and',
122 'tag_slug__in', 'tag_slug__and', 'post_parent__in', 'post_parent__not_in',
123 );
124
125 foreach ( $excludes as $k ) {
126 if ( ! empty( $qvars[ $k ] ) ) {
127 return;
128 }
129 }
130
131 $taxonomies = array_intersect( $this->model->get_translated_taxonomies(), get_taxonomies( array( '_builtin' => false ) ) );
132
133 foreach ( $taxonomies as $tax ) {
134 $tax = get_taxonomy( $tax );
135 if ( ! empty( $qv[ $tax->query_var ] ) ) {
136 return;
137 }
138 }
139
140 if ( ! empty( $qvars['tax_query'] ) && is_array( $qvars['tax_query'] ) && $this->model->have_translated_taxonomy( $qvars['tax_query'] ) ) {
141 return;
142 }
143
144 // Filter queries according to the current language
145 if ( isset( $qvars['post_type'] ) && ! empty( $this->curlang ) ) {
146 $qvars['lang'] = $this->curlang->slug;
147 }
148 }
149
150
151 if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
152 unset( $qvars['lang'] );
153 }
154 }
155
156 /**
157 * Adds the Language box in the 'Edit Post' and 'Edit Page' panels ( as well as in custom post types panels )
158 * Removes the editor for translations of the pages for posts
159 *
160 * @since 0.1
161 *
162 * @param string $post_type
163 */
164 public function add_meta_boxes( $post_type, $post ) {
165 if ( $this->model->is_translated_post_type( $post_type ) ) {
166 add_meta_box( 'ml_box', __( 'Languages','polylang' ), array( $this, 'post_language' ), $post_type, 'side', 'high' );
167 }
168
169 if ( ( $page_for_posts = get_option( 'page_for_posts' ) ) && ( $translations = $this->model->post->get_translations( $page_for_posts ) ) && in_array( $post->ID, $translations ) && empty( $post->post_content ) ) {
170 add_action( 'edit_form_after_title', '_wp_posts_page_notice' );
171 remove_post_type_support( $post_type, 'editor' );
172 }
173 }
174
175 /**
176 * displays the Languages metabox in the 'Edit Post' and 'Edit Page' panels
177 *
178 * @since 0.1
179 */
180 public function post_language() {
181 global $post_ID;
182 $post_id = $post_ID;
183 $post_type = get_post_type( $post_ID );
184
185 $lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg :
186 ( isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) :
187 $this->pref_lang );
188
189 $dropdown = new PLL_Walker_Dropdown();
190
191 wp_nonce_field( 'pll_language', '_pll_nonce' );
192
193 // NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js )
194 printf( '
195 <p><strong>%1$s</strong></p>
196 <label class="screen-reader-text" for="%2$s">%1$s</label>
197 <div id="select-%3$s-language">%4$s</div>',
198 esc_html__( 'Language', 'polylang' ),
199 $id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', $post_ID ) : 'post_lang_choice',
200 'attachment' === $post_type ? 'media' : 'post',
201 $dropdown->walk( $this->model->get_languages_list(), array(
202 'name' => $id,
203 'class' => 'post_lang_choice tags-input',
204 'selected' => $lang ? $lang->slug : '',
205 'flag' => true,
206 ) )
207 );
208
209 /**
210 * Fires before displaying the list of translations in the Languages metabox for posts
211 *
212 * @since 1.8
213 */
214 do_action( 'pll_before_post_translations', $post_type );
215
216 echo '<div id="post-translations" class="translations">';
217 if ( $lang ) {
218 include( PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php' );
219 }
220 echo '</div>' . "\n";
221 }
222
223 /**
224 * ajax response for changing the language in the post metabox
225 *
226 * @since 0.2
227 */
228 public function post_lang_choice() {
229 check_ajax_referer( 'pll_language', '_pll_nonce' );
230
231 global $post_ID; // obliged to use the global variable for wp_popular_terms_checklist
232 $post_id = $post_ID = (int) $_POST['post_id'];
233 $lang = $this->model->get_language( $_POST['lang'] );
234
235 $post_type = $_POST['post_type'];
236 $post_type_object = get_post_type_object( $post_type );
237 if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
238 wp_die( -1 );
239 }
240
241 $this->model->post->set_language( $post_ID, $lang ); // save language, useful to set the language when uploading media from post
242
243 ob_start();
244 if ( $lang ) {
245 include( PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php' );
246 }
247 $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
248 ob_end_clean();
249
250 // categories
251 if ( isset( $_POST['taxonomies'] ) ) {
252 // not set for pages
253 foreach ( $_POST['taxonomies'] as $taxname ) {
254 $taxonomy = get_taxonomy( $taxname );
255
256 ob_start();
257 $popular_ids = wp_popular_terms_checklist( $taxonomy->name );
258 $supplemental['populars'] = ob_get_contents();
259 ob_end_clean();
260
261 ob_start();
262 // use $post_ID to remember ckecked terms in case we come back to the original language
263 wp_terms_checklist( $post_ID, array( 'taxonomy' => $taxonomy->name, 'popular_cats' => $popular_ids ) );
264 $supplemental['all'] = ob_get_contents();
265 ob_end_clean();
266
267 $supplemental['dropdown'] = wp_dropdown_categories( array(
268 'taxonomy' => $taxonomy->name,
269 'hide_empty' => 0,
270 'name' => 'new'.$taxonomy->name.'_parent',
271 'orderby' => 'name',
272 'hierarchical' => 1,
273 'show_option_none' => '&mdash; '.$taxonomy->labels->parent_item.' &mdash;',
274 'echo' => 0,
275 ) );
276
277 $x->Add( array( 'what' => 'taxonomy', 'data' => $taxonomy->name, 'supplemental' => $supplemental ) );
278 }
279 }
280
281 // parent dropdown list ( only for hierarchical post types )
282 if ( in_array( $post_type, get_post_types( array( 'hierarchical' => true ) ) ) ) {
283 $post = get_post( $post_ID );
284
285 // args and filter from 'page_attributes_meta_box' in wp-admin/includes/meta-boxes.php of WP 4.2.1
286 $dropdown_args = array(
287 'post_type' => $post->post_type,
288 'exclude_tree' => $post->ID,
289 'selected' => $post->post_parent,
290 'name' => 'parent_id',
291 'show_option_none' => __( '(no parent)' ),
292 'sort_column' => 'menu_order, post_title',
293 'echo' => 0,
294 );
295
296 /** This filter is documented in wp-admin/includes/meta-boxes.php */
297 $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // since WP 3.3
298
299 $x->Add( array( 'what' => 'pages', 'data' => wp_dropdown_pages( $dropdown_args ) ) );
300 }
301
302 // flag
303 $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
304
305 // Sample permalink
306 $x->Add( array( 'what' => 'permalink', 'data' => get_sample_permalink_html( $post_ID ) ) );
307
308 $x->send();
309 }
310
311 /**
312 * ajax response for input in translation autocomplete input box
313 *
314 * @since 1.5
315 */
316 public function ajax_posts_not_translated() {
317 check_ajax_referer( 'pll_language', '_pll_nonce' );
318
319 if ( ! post_type_exists( $_GET['post_type'] ) ) {
320 die( 0 );
321 }
322
323 $post_language = $this->model->get_language( $_GET['post_language'] );
324 $translation_language = $this->model->get_language( $_GET['translation_language'] );
325
326 // don't order by title: see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
327 $args = array(
328 's' => wp_unslash( $_GET['term'] ),
329 'suppress_filters' => 0, // to make the post_fields filter work
330 'lang' => 0, // avoid admin language filter
331 'numberposts' => 20, // limit to 20 posts
332 'post_status' => 'any',
333 'post_type' => $_GET['post_type'],
334 'tax_query' => array( array(
335 'taxonomy' => 'language',
336 'field' => 'term_taxonomy_id', // WP 3.5+
337 'terms' => $translation_language->term_taxonomy_id,
338 ) ),
339 );
340
341 /**
342 * Filter the query args when auto suggesting untranslated posts in the Languages metabox
343 * This should help plugins to fix some edge cases
344 *
345 * @see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
346 *
347 * @since 1.7
348 *
349 * @param array $args WP_Query arguments
350 */
351 $args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
352 $posts = get_posts( $args );
353
354 $return = array();
355
356 foreach ( $posts as $key => $post ) {
357 if ( ! $this->model->post->get_translation( $post->ID, $post_language ) ) {
358 $return[] = array(
359 'id' => $post->ID,
360 'value' => $post->post_title,
361 'link' => $this->links->edit_post_translation_link( $post->ID ),
362 );
363 }
364 }
365
366 // add current translation in list
367 if ( $post_id = $this->model->post->get_translation( (int) $_GET['pll_post_id'], $translation_language ) ) {
368 $post = get_post( $post_id );
369 array_unshift( $return, array(
370 'id' => $post_id,
371 'value' => $post->post_title,
372 'link' => $this->links->edit_post_translation_link( $post_id ),
373 ) );
374 }
375
376 wp_die( json_encode( $return ) );
377 }
378
379 /**
380 * saves language
381 * checks the terms saved are in the right language
382 *
383 * @since 1.5
384 *
385 * @param int $post_id
386 * @param array $post
387 */
388 protected function save_language( $post_id, $post ) {
389 // security checks are necessary to accept language modifications
390 // as 'wp_insert_post' can be called from outside WP admin
391
392 // edit post
393 if ( isset( $_POST['post_lang_choice'] ) ) {
394 check_admin_referer( 'pll_language', '_pll_nonce' );
395 $this->model->post->set_language( $post_id, $lang = $this->model->get_language( $_POST['post_lang_choice'] ) );
396 }
397
398 // quick edit and bulk edit
399 // POST for quick edit, GET for bulk edit
400 elseif ( isset( $_REQUEST['inline_lang_choice'] ) ) {
401 // bulk edit does not modify the language
402 if ( isset( $_GET['bulk_edit'] ) && -1 == $_REQUEST['inline_lang_choice'] ) {
403 check_admin_referer( 'bulk-posts' );
404 $lang = $this->model->post->get_language( $post_id ); // get the post language for later use when saving terms
405 }
406 // a language is set in the language dropdown
407 else {
408 isset( $_GET['bulk_edit'] ) ? check_admin_referer( 'bulk-posts' ) : check_admin_referer( 'inlineeditnonce', '_inline_edit' );
409
410 $old_lang = $this->model->post->get_language( $post_id ); // stores the old language
411 $this->model->post->set_language( $post_id, $lang = $this->model->get_language( $_REQUEST['inline_lang_choice'] ) ); // set new language
412
413 // checks if the new language already exists in the translation group
414 if ( $old_lang && $old_lang->slug != $lang->slug ) {
415 $translations = $this->model->post->get_translations( $post_id );
416
417 // if yes, separate this post from the translation group
418 if ( array_key_exists( $lang->slug, $translations ) ) {
419 $this->model->post->delete_translation( $post_id );
420 }
421
422 elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
423 unset( $translations[ $old_lang->slug ] );
424 $this->model->post->save_translations( $post_id, $translations );
425 }
426 }
427 }
428 }
429
430 // quick press
431 // 'post-quickpress-save', 'post-quickpress-publish' = backward compatibility WP < 3.8
432 elseif ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], array( 'post-quickpress-save', 'post-quickpress-publish', 'post-quickdraft-save' ) ) ) {
433 check_admin_referer( 'add-' . $post->post_type );
434 $this->model->post->set_language( $post_id, $lang = $this->pref_lang ); // default language for Quick draft
435 }
436
437 else {
438 $this->set_default_language( $post_id );
439 }
440
441 // make sure we get save terms in the right language (especially tags with same name in different languages)
442 if ( ! empty( $lang ) ) {
443 // FIXME quite a lot of queries in foreach
444 foreach ( $this->model->get_translated_taxonomies() as $tax ) {
445 $terms = get_the_terms( $post_id, $tax );
446
447 if ( is_array( $terms ) ) {
448 $newterms = array();
449 foreach ( $terms as $term ) {
450 // check if the term is in the correct language or if a translation exist ( mainly for default category )
451 if ( $newterm = $this->model->term->get( $term->term_id, $lang ) ) {
452 $newterms[] = (int) $newterm;
453 }
454
455 // or choose the correct language for tags ( initially defined by name )
456 elseif ( $newterm = $this->model->term_exists( $term->name, $tax, $term->parent, $lang ) ) {
457 $newterms[] = (int) $newterm; // cast is important otherwise we get 'numeric' tags
458 }
459
460 // or create the term in the correct language
461 elseif ( ! is_wp_error( $term_info = wp_insert_term( $term->name, $tax ) ) ) {
462 $newterms[] = (int) $term_info['term_id'];
463 }
464 }
465
466 wp_set_object_terms( $post_id, $newterms, $tax );
467 }
468 }
469 }
470 }
471
472 /**
473 * called when a post ( or page ) is saved, published or updated
474 * saves languages and translations
475 *
476 * @since 0.1
477 *
478 * @param int $post_id
479 * @param object $post
480 * @param bool $update whether it is an update or not
481 */
482 public function save_post( $post_id, $post, $update ) {
483 // does nothing except on post types which are filterable
484 if ( ! $this->model->is_translated_post_type( $post->post_type ) ) {
485 return;
486 }
487
488 if ( $id = wp_is_post_revision( $post_id ) ) {
489 $post_id = $id;
490 }
491
492 // capability check
493 // as 'wp_insert_post' can be called from outside WP admin
494 $post_type_object = get_post_type_object( $post->post_type );
495 if ( ( $update && current_user_can( $post_type_object->cap->edit_post, $post_id ) ) || ( ! $update && current_user_can( $post_type_object->cap->create_posts ) ) ) {
496 $this->save_language( $post_id, $post );
497
498 // Make sure we are saving translations only for the main post currently being edited and not for other possible post types
499 if ( ! empty( $GLOBALS['post_type'] ) && $post->post_type === $GLOBALS['post_type'] && isset( $_POST['post_tr_lang'] ) ) {
500 $translations = $this->save_translations( $post_id, $_POST['post_tr_lang'] );
501 }
502
503 /**
504 * Fires after the post language and translations are saved
505 *
506 * @since 1.2
507 *
508 * @param int $post_id post id
509 * @param object $post post object
510 * @param array $translations the list of translations post ids
511 */
512 do_action( 'pll_save_post', $post_id, $post, empty( $translations ) ? $this->model->post->get_translations( $post_id ) : $translations );
513 }
514
515 // attempts to set a default language even if no capability
516 else {
517 $this->set_default_language( $post_id );
518 }
519 }
520
521 /**
522 * make sure that the post parent is in the correct language when using bulk edit
523 *
524 * @since 1.8
525 *
526 * @param int $post_parent Post parent ID.
527 * @param int $post_id Post ID.
528 * @param array $new_postarr Array of parsed post data.
529 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
530 * @return int
531 */
532 public function wp_insert_post_parent( $post_parent, $post_id, $new_postarr, $postarr ) {
533 if ( isset( $postarr['bulk_edit'] ) ) {
534 check_admin_referer( 'bulk-posts' );
535 $lang = -1 == $postarr['inline_lang_choice'] ?
536 $this->model->post->get_language( $post_id ) :
537 $this->model->get_language( $postarr['inline_lang_choice'] );
538 $post_parent = $this->model->post->get_translation( $post_parent, $lang );
539 }
540 return $post_parent;
541 }
542
543 /**
544 * called when a post, page or media is deleted
545 * don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
546 * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
547 *
548 * @since 0.1
549 *
550 * @param int $post_id
551 */
552 public function delete_post( $post_id ) {
553 if ( ! wp_is_post_revision( $post_id ) ) {
554 $this->model->post->delete_translation( $post_id );
555 }
556 }
557
558 /**
559 * filters the pages by language in the parent dropdown list in the page attributes metabox
560 *
561 * @since 0.6
562 *
563 * @param array $dropdown_args arguments passed to wp_dropdown_pages
564 * @param object $post
565 * @return array modified arguments
566 */
567 public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) {
568 $dropdown_args['lang'] = isset( $_POST['lang'] ) ? $this->model->get_language( $_POST['lang'] ) : $this->model->post->get_language( $post->ID ); // ajax or not ?
569 if ( ! $dropdown_args['lang'] ) {
570 $dropdown_args['lang'] = $this->pref_lang;
571 }
572
573 return $dropdown_args;
574 }
575 }
576