PluginProbe
Polylang / 2.3.6
Polylang v2.3.6
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.3.6, at admin/admin-filters-post.php

603 lines 20.2 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( 'load-post.php', array( $this, 'edit_post' ) );
37 add_action( 'load-edit.php', array( $this, 'bulk_edit_posts' ) );
38 add_action( 'wp_ajax_inline-save', array( $this, 'inline_edit_post' ), 0 ); // Before WordPress
39 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
40 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 4 );
41 add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 4 );
42 add_action( 'before_delete_post', array( $this, 'delete_post' ) );
43 if ( $this->options['media_support'] ) {
44 add_action( 'delete_attachment', array( $this, 'delete_post' ) ); // Action shared with media
45 }
46
47 // Filters the pages by language in the parent dropdown list in the page attributes metabox
48 add_filter( 'page_attributes_dropdown_pages_args', array( $this, 'page_attributes_dropdown_pages_args' ), 10, 2 );
49
50 // Sets the language in Tiny MCE
51 add_filter( 'tiny_mce_before_init', array( $this, 'tiny_mce_before_init' ) );
52 }
53
54 /**
55 * Outputs a javascript list of terms ordered by language and hierarchical taxonomies
56 * to filter the category checklist per post language in quick edit
57 * Outputs a javascript list of pages ordered by language
58 * to filter the parent dropdown per post language in quick edit
59 *
60 * @since 1.7
61 */
62 public function admin_enqueue_scripts() {
63 $screen = get_current_screen();
64
65 // Hierarchical taxonomies
66 if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'object' ) ) {
67 // Get translated hierarchical taxonomies
68 foreach ( $taxonomies as $taxonomy ) {
69 if ( $taxonomy->hierarchical && $taxonomy->show_in_quick_edit && $this->model->is_translated_taxonomy( $taxonomy->name ) ) {
70 $hierarchical_taxonomies[] = $taxonomy->name;
71 }
72 }
73
74 if ( ! empty( $hierarchical_taxonomies ) ) {
75 $terms = get_terms( $hierarchical_taxonomies, array( 'get' => 'all' ) );
76
77 foreach ( $terms as $term ) {
78 if ( $lang = $this->model->term->get_language( $term->term_id ) ) {
79 $term_languages[ $lang->slug ][ $term->taxonomy ][] = $term->term_id;
80 }
81 }
82
83 // Send all these data to javascript
84 if ( ! empty( $term_languages ) ) {
85 wp_localize_script( 'pll_post', 'pll_term_languages', $term_languages );
86 }
87 }
88 }
89
90 // Hierarchical post types
91 if ( 'edit' == $screen->base && is_post_type_hierarchical( $screen->post_type ) ) {
92 $pages = get_pages();
93
94 foreach ( $pages as $page ) {
95 if ( $lang = $this->model->post->get_language( $page->ID ) ) {
96 $page_languages[ $lang->slug ][] = $page->ID;
97 }
98 }
99
100 // Send all these data to javascript
101 if ( ! empty( $page_languages ) ) {
102 wp_localize_script( 'pll_post', 'pll_page_languages', $page_languages );
103 }
104 }
105 }
106
107 /**
108 * Filters posts, pages and media by language
109 *
110 * @since 0.1
111 *
112 * @param object $query a WP_Query object
113 */
114 public function parse_query( $query ) {
115 $pll_query = new PLL_Query( $query, $this->model );
116 $pll_query->filter_query( $this->curlang );
117 }
118
119 /**
120 * Adds the Language box in the 'Edit Post' and 'Edit Page' panels ( as well as in custom post types panels )
121 *
122 * @since 0.1
123 *
124 * @param string $post_type Current post type
125 * @param object $post Current post
126 */
127 public function add_meta_boxes( $post_type, $post ) {
128 if ( $this->model->is_translated_post_type( $post_type ) ) {
129 add_meta_box( 'ml_box', __( 'Languages', 'polylang' ), array( $this, 'post_language' ), $post_type, 'side', 'high' );
130 }
131 }
132
133 /**
134 * Displays the Languages metabox in the 'Edit Post' and 'Edit Page' panels
135 *
136 * @since 0.1
137 */
138 public function post_language() {
139 global $post_ID;
140 $post_id = $post_ID;
141 $post_type = get_post_type( $post_ID );
142
143 $lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg :
144 ( isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) :
145 $this->pref_lang );
146
147 $dropdown = new PLL_Walker_Dropdown();
148
149 wp_nonce_field( 'pll_language', '_pll_nonce' );
150
151 // NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js )
152 printf( '
153 <p><strong>%1$s</strong></p>
154 <label class="screen-reader-text" for="%2$s">%1$s</label>
155 <div id="select-%3$s-language">%4$s</div>',
156 esc_html__( 'Language', 'polylang' ),
157 $id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', $post_ID ) : 'post_lang_choice',
158 'attachment' === $post_type ? 'media' : 'post',
159 $dropdown->walk( $this->model->get_languages_list(), array(
160 'name' => $id,
161 'class' => 'post_lang_choice tags-input',
162 'selected' => $lang ? $lang->slug : '',
163 'flag' => true,
164 ) )
165 );
166
167 /**
168 * Fires before displaying the list of translations in the Languages metabox for posts
169 *
170 * @since 1.8
171 */
172 do_action( 'pll_before_post_translations', $post_type );
173
174 echo '<div id="post-translations" class="translations">';
175 if ( $lang ) {
176 include PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php';
177 }
178 echo '</div>' . "\n";
179 }
180
181 /**
182 * Ajax response for changing the language in the post metabox
183 *
184 * @since 0.2
185 */
186 public function post_lang_choice() {
187 check_ajax_referer( 'pll_language', '_pll_nonce' );
188
189 global $post_ID; // Obliged to use the global variable for wp_popular_terms_checklist
190 $post_id = $post_ID = (int) $_POST['post_id'];
191 $lang = $this->model->get_language( $_POST['lang'] );
192
193 $post_type = $_POST['post_type'];
194 $post_type_object = get_post_type_object( $post_type );
195 if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
196 wp_die( -1 );
197 }
198
199 $this->model->post->set_language( $post_ID, $lang ); // Save language, useful to set the language when uploading media from post
200
201 ob_start();
202 if ( $lang ) {
203 include PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php';
204 }
205 $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
206 ob_end_clean();
207
208 // Categories
209 if ( isset( $_POST['taxonomies'] ) ) {
210 // Not set for pages
211 foreach ( $_POST['taxonomies'] as $taxname ) {
212 $taxonomy = get_taxonomy( $taxname );
213
214 ob_start();
215 $popular_ids = wp_popular_terms_checklist( $taxonomy->name );
216 $supplemental['populars'] = ob_get_contents();
217 ob_end_clean();
218
219 ob_start();
220 // Use $post_ID to remember checked terms in case we come back to the original language
221 wp_terms_checklist( $post_ID, array( 'taxonomy' => $taxonomy->name, 'popular_cats' => $popular_ids ) );
222 $supplemental['all'] = ob_get_contents();
223 ob_end_clean();
224
225 $supplemental['dropdown'] = wp_dropdown_categories( array(
226 'taxonomy' => $taxonomy->name,
227 'hide_empty' => 0,
228 'name' => 'new' . $taxonomy->name . '_parent',
229 'orderby' => 'name',
230 'hierarchical' => 1,
231 'show_option_none' => '&mdash; ' . $taxonomy->labels->parent_item . ' &mdash;',
232 'echo' => 0,
233 ) );
234
235 $x->Add( array( 'what' => 'taxonomy', 'data' => $taxonomy->name, 'supplemental' => $supplemental ) );
236 }
237 }
238
239 // Parent dropdown list ( only for hierarchical post types )
240 if ( in_array( $post_type, get_post_types( array( 'hierarchical' => true ) ) ) ) {
241 $post = get_post( $post_ID );
242
243 // Args and filter from 'page_attributes_meta_box' in wp-admin/includes/meta-boxes.php of WP 4.2.1
244 $dropdown_args = array(
245 'post_type' => $post->post_type,
246 'exclude_tree' => $post->ID,
247 'selected' => $post->post_parent,
248 'name' => 'parent_id',
249 'show_option_none' => __( '(no parent)' ),
250 'sort_column' => 'menu_order, post_title',
251 'echo' => 0,
252 );
253
254 /** This filter is documented in wp-admin/includes/meta-boxes.php */
255 $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // Since WP 3.3
256
257 $x->Add( array( 'what' => 'pages', 'data' => wp_dropdown_pages( $dropdown_args ) ) );
258 }
259
260 // Flag
261 $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
262
263 // Sample permalink
264 $x->Add( array( 'what' => 'permalink', 'data' => get_sample_permalink_html( $post_ID ) ) );
265
266 $x->send();
267 }
268
269 /**
270 * Ajax response for input in translation autocomplete input box
271 *
272 * @since 1.5
273 */
274 public function ajax_posts_not_translated() {
275 check_ajax_referer( 'pll_language', '_pll_nonce' );
276
277 if ( ! post_type_exists( $_GET['post_type'] ) ) {
278 die( 0 );
279 }
280
281 $post_language = $this->model->get_language( $_GET['post_language'] );
282 $translation_language = $this->model->get_language( $_GET['translation_language'] );
283
284 // Don't order by title: see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
285 $args = array(
286 's' => wp_unslash( $_GET['term'] ),
287 'suppress_filters' => 0, // To make the post_fields filter work
288 'lang' => 0, // Avoid admin language filter
289 'numberposts' => 20, // Limit to 20 posts
290 'post_status' => 'any',
291 'post_type' => $_GET['post_type'],
292 'tax_query' => array(
293 array(
294 'taxonomy' => 'language',
295 'field' => 'term_taxonomy_id', // WP 3.5+
296 'terms' => $translation_language->term_taxonomy_id,
297 ),
298 ),
299 );
300
301 /**
302 * Filter the query args when auto suggesting untranslated posts in the Languages metabox
303 * This should help plugins to fix some edge cases
304 *
305 * @see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
306 *
307 * @since 1.7
308 *
309 * @param array $args WP_Query arguments
310 */
311 $args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
312 $posts = get_posts( $args );
313
314 $return = array();
315
316 foreach ( $posts as $key => $post ) {
317 if ( ! $this->model->post->get_translation( $post->ID, $post_language ) ) {
318 $return[] = array(
319 'id' => $post->ID,
320 'value' => $post->post_title,
321 'link' => $this->links->edit_post_translation_link( $post->ID ),
322 );
323 }
324 }
325
326 // Add current translation in list
327 if ( $post_id = $this->model->post->get_translation( (int) $_GET['pll_post_id'], $translation_language ) ) {
328 $post = get_post( $post_id );
329 array_unshift( $return, array(
330 'id' => $post_id,
331 'value' => $post->post_title,
332 'link' => $this->links->edit_post_translation_link( $post_id ),
333 ) );
334 }
335
336 wp_die( json_encode( $return ) );
337 }
338
339 /**
340 * Save language and translation when editing a post (post.php)
341 *
342 * @since 2.3
343 */
344 public function edit_post() {
345 if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $post_id = (int) $_POST['post_ID'] ) {
346 check_admin_referer( 'pll_language', '_pll_nonce' );
347
348 $post = get_post( $post_id );
349 $post_type_object = get_post_type_object( $post->post_type );
350
351 if ( current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
352 $this->model->post->set_language( $post_id, $this->model->get_language( $_POST['post_lang_choice'] ) );
353
354 if ( isset( $_POST['post_tr_lang'] ) ) {
355 $this->save_translations( $post_id, $_POST['post_tr_lang'] );
356 }
357 }
358 }
359 }
360
361 /**
362 * Save language when inline editing or bulk editing a post
363 * Fix translations if necessary
364 *
365 * @since 2.3
366 *
367 * @param int $post_id Post ID
368 * @param object $lang Language
369 */
370 protected function inline_save_language( $post_id, $lang ) {
371 $post = get_post( $post_id );
372 $post_type_object = get_post_type_object( $post->post_type );
373
374 if ( current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
375 $old_lang = $this->model->post->get_language( $post_id ); // Stores the old language
376 $this->model->post->set_language( $post_id, $lang ); // set new language
377
378 // Checks if the new language already exists in the translation group
379 if ( $old_lang && $old_lang->slug != $lang->slug ) {
380 $translations = $this->model->post->get_translations( $post_id );
381
382 // If yes, separate this post from the translation group
383 if ( array_key_exists( $lang->slug, $translations ) ) {
384 $this->model->post->delete_translation( $post_id );
385 }
386
387 elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
388 unset( $translations[ $old_lang->slug ] );
389 $this->model->post->save_translations( $post_id, $translations );
390 }
391 }
392 }
393 }
394
395 /**
396 * Save language when bulk editing a post
397 *
398 * @since 2.3
399 */
400 public function bulk_edit_posts() {
401 if ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) && -1 !== $_GET['inline_lang_choice'] ) {
402 check_admin_referer( 'bulk-posts' );
403
404 if ( $lang = $this->model->get_language( $_GET['inline_lang_choice'] ) ) {
405 $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
406 foreach ( $post_ids as $post_id ) {
407 $this->inline_save_language( $post_id, $lang );
408 }
409 }
410 }
411 }
412
413 /**
414 * Save language when inline editing a post
415 *
416 * @since 2.3
417 */
418 public function inline_edit_post() {
419 check_admin_referer( 'inlineeditnonce', '_inline_edit' );
420
421 if ( isset( $_POST['post_ID'], $_POST['inline_lang_choice'] ) ) {
422 $post_id = (int) $_POST['post_ID'];
423 $lang = $this->model->get_language( $_POST['inline_lang_choice'] );
424 if ( $post_id && $lang ) {
425 $this->inline_save_language( $post_id, $lang );
426 }
427 }
428 }
429
430 /**
431 * Called when a post ( or page ) is saved, published or updated
432 *
433 * @since 0.1
434 * @since 2.3 Does not save the language and translations anymore, unless the post has no language yet
435 *
436 * @param int $post_id
437 * @param object $post
438 * @param bool $update Whether it is an update or not
439 */
440 public function save_post( $post_id, $post, $update ) {
441 // Does nothing except on post types which are filterable
442 if ( $this->model->is_translated_post_type( $post->post_type ) ) {
443 if ( $id = wp_is_post_revision( $post_id ) ) {
444 $post_id = $id;
445 }
446
447 $lang = $this->model->post->get_language( $post_id );
448
449 if ( empty( $lang ) ) {
450 $this->set_default_language( $post_id );
451 }
452
453 /**
454 * Fires after the post language and translations are saved
455 *
456 * @since 1.2
457 *
458 * @param int $post_id Post id
459 * @param object $post Post object
460 * @param array $translations The list of translations post ids
461 */
462 do_action( 'pll_save_post', $post_id, $post, $this->model->post->get_translations( $post_id ) );
463 }
464 }
465
466 /**
467 * Make sure saved terms are in the right language (especially tags with same name in different languages)
468 *
469 * @since 2.3
470 *
471 * @param int $object_id Object ID.
472 * @param array $terms An array of object terms.
473 * @param array $tt_ids An array of term taxonomy IDs.
474 * @param string $taxonomy Taxonomy slug.
475 */
476 public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy ) {
477 static $avoid_recursion;
478
479 if ( ! $avoid_recursion && $this->model->is_translated_taxonomy( $taxonomy ) && ! empty( $terms ) ) {
480 $lang = $this->model->post->get_language( $object_id );
481
482 if ( ! empty( $lang ) && is_array( $terms ) ) {
483 // Convert to term ids if we got tag names
484 $strings = array_filter( $terms, 'is_string' );
485 if ( ! empty( $strings ) ) {
486 $_terms = get_terms( $taxonomy, array( 'name' => $strings, 'object_ids' => $object_id, 'fields' => 'ids' ) );
487 $terms = array_merge( array_diff( $terms, $strings ), $_terms );
488 }
489
490 $term_ids = array_combine( $terms, $terms );
491 $languages = array_map( array( $this->model->term, 'get_language' ), $term_ids );
492 $languages = wp_list_pluck( $languages, 'slug' );
493 $wrong_terms = array_diff( $languages, array( $lang->slug ) );
494
495 if ( ! empty( $wrong_terms ) ) {
496 // We got terms in a wrong language
497 $wrong_term_ids = array_keys( $wrong_terms );
498 $terms = get_the_terms( $object_id, $taxonomy );
499 wp_remove_object_terms( $object_id, $wrong_term_ids, $taxonomy );
500
501 if ( is_array( $terms ) ) {
502 $newterms = array();
503
504 foreach ( $terms as $term ) {
505 if ( in_array( $term->term_id, $wrong_term_ids ) ) {
506 // Check if the term is in the correct language or if a translation exist ( mainly for default category )
507 if ( $newterm = $this->model->term->get( $term->term_id, $lang ) ) {
508 $newterms[] = (int) $newterm;
509 }
510
511 // Or choose the correct language for tags ( initially defined by name )
512 elseif ( $newterm = $this->model->term_exists( $term->name, $taxonomy, $term->parent, $lang ) ) {
513 $newterms[] = (int) $newterm; // Cast is important otherwise we get 'numeric' tags
514 }
515
516 // Or create the term in the correct language
517 elseif ( ! is_wp_error( $term_info = wp_insert_term( $term->name, $taxonomy ) ) ) {
518 $newterms[] = (int) $term_info['term_id'];
519 }
520 }
521 }
522
523 $avoid_recursion = true;
524 wp_set_object_terms( $object_id, array_unique( $newterms ), $taxonomy, true ); // Append
525 $avoid_recursion = false;
526 }
527 }
528 }
529 }
530 }
531
532 /**
533 * Make sure that the post parent is in the correct language when using bulk edit
534 *
535 * @since 1.8
536 *
537 * @param int $post_parent Post parent ID.
538 * @param int $post_id Post ID.
539 * @param array $new_postarr Array of parsed post data.
540 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
541 * @return int
542 */
543 public function wp_insert_post_parent( $post_parent, $post_id, $new_postarr, $postarr ) {
544 if ( isset( $postarr['bulk_edit'] ) ) {
545 check_admin_referer( 'bulk-posts' );
546 $lang = -1 == $postarr['inline_lang_choice'] ?
547 $this->model->post->get_language( $post_id ) :
548 $this->model->get_language( $postarr['inline_lang_choice'] );
549 $post_parent = $this->model->post->get_translation( $post_parent, $lang );
550 }
551 return $post_parent;
552 }
553
554 /**
555 * Called when a post, page or media is deleted
556 * Don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
557 * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
558 *
559 * @since 0.1
560 *
561 * @param int $post_id
562 */
563 public function delete_post( $post_id ) {
564 if ( ! wp_is_post_revision( $post_id ) ) {
565 $this->model->post->delete_translation( $post_id );
566 }
567 }
568
569 /**
570 * Filters the pages by language in the parent dropdown list in the page attributes metabox
571 *
572 * @since 0.6
573 *
574 * @param array $dropdown_args Arguments passed to wp_dropdown_pages
575 * @param object $post
576 * @return array Modified arguments
577 */
578 public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) {
579 $dropdown_args['lang'] = isset( $_POST['lang'] ) ? $this->model->get_language( $_POST['lang'] ) : $this->model->post->get_language( $post->ID ); // ajax or not ?
580 if ( ! $dropdown_args['lang'] ) {
581 $dropdown_args['lang'] = $this->pref_lang;
582 }
583
584 return $dropdown_args;
585 }
586
587 /**
588 * Sets the language attribute and text direction for Tiny MCE
589 *
590 * @since 2.2
591 *
592 * @param array $mce_init TinyMCE config
593 * @return array
594 */
595 public function tiny_mce_before_init( $mce_init ) {
596 if ( ! empty( $this->curlang ) ) {
597 $mce_init['wp_lang_attr'] = $this->curlang->get_locale( 'display' );
598 $mce_init['directionality'] = $this->curlang->is_rtl ? 'rtl' : 'ltr';
599 }
600 return $mce_init;
601 }
602 }
603