PluginProbe
Polylang / 2.1.6
Polylang v2.1.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.1.6, at admin/admin-filters-post.php

590 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',
120 'post_parent',
121 'attachment',
122 'attachment_id',
123 'name',
124 'pagename',
125 'page_id',
126 'category_name',
127 'tag',
128 'cat',
129 'tag_id',
130 'category__in',
131 'category__and',
132 'post__in',
133 'post_name__in',
134 'tag__in',
135 'tag__and',
136 'tag_slug__in',
137 'tag_slug__and',
138 'post_parent__in',
139 );
140
141 foreach ( $excludes as $k ) {
142 if ( ! empty( $qvars[ $k ] ) ) {
143 return;
144 }
145 }
146
147 $taxonomies = array_intersect( $this->model->get_translated_taxonomies(), get_taxonomies( array( '_builtin' => false ) ) );
148
149 foreach ( $taxonomies as $tax ) {
150 $tax = get_taxonomy( $tax );
151 if ( ! empty( $qv[ $tax->query_var ] ) ) {
152 return;
153 }
154 }
155
156 if ( ! empty( $qvars['tax_query'] ) && is_array( $qvars['tax_query'] ) && $this->model->have_translated_taxonomy( $qvars['tax_query'] ) ) {
157 return;
158 }
159
160 // Filter queries according to the current language
161 if ( isset( $qvars['post_type'] ) && ! empty( $this->curlang ) ) {
162 $qvars['lang'] = $this->curlang->slug;
163 }
164 }
165
166 if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
167 unset( $qvars['lang'] );
168 }
169 }
170
171 /**
172 * Adds the Language box in the 'Edit Post' and 'Edit Page' panels ( as well as in custom post types panels )
173 * Removes the editor for translations of the pages for posts
174 *
175 * @since 0.1
176 *
177 * @param string $post_type
178 */
179 public function add_meta_boxes( $post_type, $post ) {
180 if ( $this->model->is_translated_post_type( $post_type ) ) {
181 add_meta_box( 'ml_box', __( 'Languages','polylang' ), array( $this, 'post_language' ), $post_type, 'side', 'high' );
182 }
183
184 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 ) ) {
185 add_action( 'edit_form_after_title', '_wp_posts_page_notice' );
186 remove_post_type_support( $post_type, 'editor' );
187 }
188 }
189
190 /**
191 * Displays the Languages metabox in the 'Edit Post' and 'Edit Page' panels
192 *
193 * @since 0.1
194 */
195 public function post_language() {
196 global $post_ID;
197 $post_id = $post_ID;
198 $post_type = get_post_type( $post_ID );
199
200 $lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg :
201 ( isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) :
202 $this->pref_lang );
203
204 $dropdown = new PLL_Walker_Dropdown();
205
206 wp_nonce_field( 'pll_language', '_pll_nonce' );
207
208 // NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js )
209 printf( '
210 <p><strong>%1$s</strong></p>
211 <label class="screen-reader-text" for="%2$s">%1$s</label>
212 <div id="select-%3$s-language">%4$s</div>',
213 esc_html__( 'Language', 'polylang' ),
214 $id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', $post_ID ) : 'post_lang_choice',
215 'attachment' === $post_type ? 'media' : 'post',
216 $dropdown->walk( $this->model->get_languages_list(), array(
217 'name' => $id,
218 'class' => 'post_lang_choice tags-input',
219 'selected' => $lang ? $lang->slug : '',
220 'flag' => true,
221 ) )
222 );
223
224 /**
225 * Fires before displaying the list of translations in the Languages metabox for posts
226 *
227 * @since 1.8
228 */
229 do_action( 'pll_before_post_translations', $post_type );
230
231 echo '<div id="post-translations" class="translations">';
232 if ( $lang ) {
233 include PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php';
234 }
235 echo '</div>' . "\n";
236 }
237
238 /**
239 * Ajax response for changing the language in the post metabox
240 *
241 * @since 0.2
242 */
243 public function post_lang_choice() {
244 check_ajax_referer( 'pll_language', '_pll_nonce' );
245
246 global $post_ID; // Obliged to use the global variable for wp_popular_terms_checklist
247 $post_id = $post_ID = (int) $_POST['post_id'];
248 $lang = $this->model->get_language( $_POST['lang'] );
249
250 $post_type = $_POST['post_type'];
251 $post_type_object = get_post_type_object( $post_type );
252 if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
253 wp_die( -1 );
254 }
255
256 $this->model->post->set_language( $post_ID, $lang ); // Save language, useful to set the language when uploading media from post
257
258 ob_start();
259 if ( $lang ) {
260 include PLL_ADMIN_INC . '/view-translations-' . ( 'attachment' == $post_type ? 'media' : 'post' ) . '.php';
261 }
262 $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
263 ob_end_clean();
264
265 // Categories
266 if ( isset( $_POST['taxonomies'] ) ) {
267 // Not set for pages
268 foreach ( $_POST['taxonomies'] as $taxname ) {
269 $taxonomy = get_taxonomy( $taxname );
270
271 ob_start();
272 $popular_ids = wp_popular_terms_checklist( $taxonomy->name );
273 $supplemental['populars'] = ob_get_contents();
274 ob_end_clean();
275
276 ob_start();
277 // Use $post_ID to remember ckecked terms in case we come back to the original language
278 wp_terms_checklist( $post_ID, array( 'taxonomy' => $taxonomy->name, 'popular_cats' => $popular_ids ) );
279 $supplemental['all'] = ob_get_contents();
280 ob_end_clean();
281
282 $supplemental['dropdown'] = wp_dropdown_categories( array(
283 'taxonomy' => $taxonomy->name,
284 'hide_empty' => 0,
285 'name' => 'new'.$taxonomy->name.'_parent',
286 'orderby' => 'name',
287 'hierarchical' => 1,
288 'show_option_none' => '&mdash; '.$taxonomy->labels->parent_item.' &mdash;',
289 'echo' => 0,
290 ) );
291
292 $x->Add( array( 'what' => 'taxonomy', 'data' => $taxonomy->name, 'supplemental' => $supplemental ) );
293 }
294 }
295
296 // Parent dropdown list ( only for hierarchical post types )
297 if ( in_array( $post_type, get_post_types( array( 'hierarchical' => true ) ) ) ) {
298 $post = get_post( $post_ID );
299
300 // Args and filter from 'page_attributes_meta_box' in wp-admin/includes/meta-boxes.php of WP 4.2.1
301 $dropdown_args = array(
302 'post_type' => $post->post_type,
303 'exclude_tree' => $post->ID,
304 'selected' => $post->post_parent,
305 'name' => 'parent_id',
306 'show_option_none' => __( '(no parent)' ),
307 'sort_column' => 'menu_order, post_title',
308 'echo' => 0,
309 );
310
311 /** This filter is documented in wp-admin/includes/meta-boxes.php */
312 $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // Since WP 3.3
313
314 $x->Add( array( 'what' => 'pages', 'data' => wp_dropdown_pages( $dropdown_args ) ) );
315 }
316
317 // Flag
318 $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
319
320 // Sample permalink
321 $x->Add( array( 'what' => 'permalink', 'data' => get_sample_permalink_html( $post_ID ) ) );
322
323 $x->send();
324 }
325
326 /**
327 * Ajax response for input in translation autocomplete input box
328 *
329 * @since 1.5
330 */
331 public function ajax_posts_not_translated() {
332 check_ajax_referer( 'pll_language', '_pll_nonce' );
333
334 if ( ! post_type_exists( $_GET['post_type'] ) ) {
335 die( 0 );
336 }
337
338 $post_language = $this->model->get_language( $_GET['post_language'] );
339 $translation_language = $this->model->get_language( $_GET['translation_language'] );
340
341 // Don't order by title: see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
342 $args = array(
343 's' => wp_unslash( $_GET['term'] ),
344 'suppress_filters' => 0, // To make the post_fields filter work
345 'lang' => 0, // Avoid admin language filter
346 'numberposts' => 20, // Limit to 20 posts
347 'post_status' => 'any',
348 'post_type' => $_GET['post_type'],
349 'tax_query' => array( array(
350 'taxonomy' => 'language',
351 'field' => 'term_taxonomy_id', // WP 3.5+
352 'terms' => $translation_language->term_taxonomy_id,
353 ) ),
354 );
355
356 /**
357 * Filter the query args when auto suggesting untranslated posts in the Languages metabox
358 * This should help plugins to fix some edge cases
359 *
360 * @see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
361 *
362 * @since 1.7
363 *
364 * @param array $args WP_Query arguments
365 */
366 $args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
367 $posts = get_posts( $args );
368
369 $return = array();
370
371 foreach ( $posts as $key => $post ) {
372 if ( ! $this->model->post->get_translation( $post->ID, $post_language ) ) {
373 $return[] = array(
374 'id' => $post->ID,
375 'value' => $post->post_title,
376 'link' => $this->links->edit_post_translation_link( $post->ID ),
377 );
378 }
379 }
380
381 // Add current translation in list
382 if ( $post_id = $this->model->post->get_translation( (int) $_GET['pll_post_id'], $translation_language ) ) {
383 $post = get_post( $post_id );
384 array_unshift( $return, array(
385 'id' => $post_id,
386 'value' => $post->post_title,
387 'link' => $this->links->edit_post_translation_link( $post_id ),
388 ) );
389 }
390
391 wp_die( json_encode( $return ) );
392 }
393
394 /**
395 * Saves language
396 * Checks the terms saved are in the right language
397 *
398 * @since 1.5
399 *
400 * @param int $post_id
401 * @param array $post
402 */
403 protected function save_language( $post_id, $post ) {
404 // Security checks are necessary to accept language modifications as 'wp_insert_post' can be called from outside WP admin
405
406 // Edit post
407 if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $_POST['post_ID'] == $post_id ) {
408 check_admin_referer( 'pll_language', '_pll_nonce' );
409 $this->model->post->set_language( $post_id, $lang = $this->model->get_language( $_POST['post_lang_choice'] ) );
410 }
411
412 // Quick edit and bulk edit
413 // POST for quick edit, GET for bulk edit
414 elseif ( isset( $_REQUEST['inline_lang_choice'] ) ) {
415 // Bulk edit does not modify the language
416 if ( isset( $_GET['bulk_edit'] ) && -1 == $_REQUEST['inline_lang_choice'] ) {
417 check_admin_referer( 'bulk-posts' );
418 $lang = $this->model->post->get_language( $post_id ); // Get the post language for later use when saving terms
419 }
420 // A language is set in the language dropdown
421 else {
422 isset( $_GET['bulk_edit'] ) ? check_admin_referer( 'bulk-posts' ) : check_admin_referer( 'inlineeditnonce', '_inline_edit' );
423
424 $old_lang = $this->model->post->get_language( $post_id ); // Stores the old language
425 $this->model->post->set_language( $post_id, $lang = $this->model->get_language( $_REQUEST['inline_lang_choice'] ) ); // set new language
426
427 // Checks if the new language already exists in the translation group
428 if ( $old_lang && $old_lang->slug != $lang->slug ) {
429 $translations = $this->model->post->get_translations( $post_id );
430
431 // If yes, separate this post from the translation group
432 if ( array_key_exists( $lang->slug, $translations ) ) {
433 $this->model->post->delete_translation( $post_id );
434 }
435
436 elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
437 unset( $translations[ $old_lang->slug ] );
438 $this->model->post->save_translations( $post_id, $translations );
439 }
440 }
441 }
442 }
443
444 // Quick press
445 // 'post-quickpress-save', 'post-quickpress-publish' = backward compatibility WP < 3.8
446 elseif ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], array( 'post-quickpress-save', 'post-quickpress-publish', 'post-quickdraft-save' ) ) ) {
447 check_admin_referer( 'add-' . $post->post_type );
448 $this->model->post->set_language( $post_id, $lang = $this->pref_lang ); // Default language for Quick draft
449 }
450
451 else {
452 $this->set_default_language( $post_id );
453 }
454
455 // Make sure we get save terms in the right language (especially tags with same name in different languages)
456 if ( ! empty( $lang ) ) {
457 // FIXME quite a lot of queries in foreach
458 foreach ( $this->model->get_translated_taxonomies() as $tax ) {
459 $terms = get_the_terms( $post_id, $tax );
460
461 if ( is_array( $terms ) ) {
462 $newterms = array();
463 foreach ( $terms as $term ) {
464 // Check if the term is in the correct language or if a translation exist ( mainly for default category )
465 if ( $newterm = $this->model->term->get( $term->term_id, $lang ) ) {
466 $newterms[] = (int) $newterm;
467 }
468
469 // Or choose the correct language for tags ( initially defined by name )
470 elseif ( $newterm = $this->model->term_exists( $term->name, $tax, $term->parent, $lang ) ) {
471 $newterms[] = (int) $newterm; // Cast is important otherwise we get 'numeric' tags
472 }
473
474 // Or create the term in the correct language
475 elseif ( ! is_wp_error( $term_info = wp_insert_term( $term->name, $tax ) ) ) {
476 $newterms[] = (int) $term_info['term_id'];
477 }
478 }
479
480 wp_set_object_terms( $post_id, $newterms, $tax );
481 }
482 }
483 }
484 }
485
486 /**
487 * Called when a post ( or page ) is saved, published or updated
488 * saves languages and translations
489 *
490 * @since 0.1
491 *
492 * @param int $post_id
493 * @param object $post
494 * @param bool $update Whether it is an update or not
495 */
496 public function save_post( $post_id, $post, $update ) {
497 // Does nothing except on post types which are filterable
498 if ( ! $this->model->is_translated_post_type( $post->post_type ) ) {
499 return;
500 }
501
502 if ( $id = wp_is_post_revision( $post_id ) ) {
503 $post_id = $id;
504 }
505
506 // Capability check
507 // As 'wp_insert_post' can be called from outside WP admin
508 $post_type_object = get_post_type_object( $post->post_type );
509 if ( ( $update && current_user_can( $post_type_object->cap->edit_post, $post_id ) ) || ( ! $update && current_user_can( $post_type_object->cap->create_posts ) ) ) {
510 $this->save_language( $post_id, $post );
511
512 // Make sure we are saving translations only for the main post currently being edited
513 if ( isset( $_POST['post_tr_lang'], $_POST['post_ID'] ) && $_POST['post_ID'] == $post_id ) {
514 $translations = $this->save_translations( $post_id, $_POST['post_tr_lang'] );
515 }
516
517 /**
518 * Fires after the post language and translations are saved
519 *
520 * @since 1.2
521 *
522 * @param int $post_id Post id
523 * @param object $post Post object
524 * @param array $translations The list of translations post ids
525 */
526 do_action( 'pll_save_post', $post_id, $post, empty( $translations ) ? $this->model->post->get_translations( $post_id ) : $translations );
527 }
528
529 // Attempts to set a default language even if no capability
530 else {
531 $this->set_default_language( $post_id );
532 }
533 }
534
535 /**
536 * Make sure that the post parent is in the correct language when using bulk edit
537 *
538 * @since 1.8
539 *
540 * @param int $post_parent Post parent ID.
541 * @param int $post_id Post ID.
542 * @param array $new_postarr Array of parsed post data.
543 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
544 * @return int
545 */
546 public function wp_insert_post_parent( $post_parent, $post_id, $new_postarr, $postarr ) {
547 if ( isset( $postarr['bulk_edit'] ) ) {
548 check_admin_referer( 'bulk-posts' );
549 $lang = -1 == $postarr['inline_lang_choice'] ?
550 $this->model->post->get_language( $post_id ) :
551 $this->model->get_language( $postarr['inline_lang_choice'] );
552 $post_parent = $this->model->post->get_translation( $post_parent, $lang );
553 }
554 return $post_parent;
555 }
556
557 /**
558 * Called when a post, page or media is deleted
559 * Don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
560 * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
561 *
562 * @since 0.1
563 *
564 * @param int $post_id
565 */
566 public function delete_post( $post_id ) {
567 if ( ! wp_is_post_revision( $post_id ) ) {
568 $this->model->post->delete_translation( $post_id );
569 }
570 }
571
572 /**
573 * Filters the pages by language in the parent dropdown list in the page attributes metabox
574 *
575 * @since 0.6
576 *
577 * @param array $dropdown_args Arguments passed to wp_dropdown_pages
578 * @param object $post
579 * @return array Modified arguments
580 */
581 public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) {
582 $dropdown_args['lang'] = isset( $_POST['lang'] ) ? $this->model->get_language( $_POST['lang'] ) : $this->model->post->get_language( $post->ID ); // ajax or not ?
583 if ( ! $dropdown_args['lang'] ) {
584 $dropdown_args['lang'] = $this->pref_lang;
585 }
586
587 return $dropdown_args;
588 }
589 }
590