PluginProbe
Polylang / 3.7.1
Polylang v3.7.1
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-columns.php

admin-filters-columns.php in Polylang 3.7.1, at admin/admin-filters-columns.php

461 lines 15.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 /**
7 * Adds the language column in posts and terms list tables
8 * Manages quick edit and bulk edit as well
9 *
10 * @since 1.2
11 */
12 class PLL_Admin_Filters_Columns {
13 /**
14 * @var PLL_Model
15 */
16 public $model;
17
18 /**
19 * @var PLL_Admin_Links|null
20 */
21 public $links;
22
23 /**
24 * Language selected in the admin language filter.
25 *
26 * @var PLL_Language|null
27 */
28 public $filter_lang;
29
30 /**
31 * Constructor: setups filters and actions
32 *
33 * @since 1.2
34 *
35 * @param object $polylang The Polylang object.
36 */
37 public function __construct( &$polylang ) {
38 $this->links = &$polylang->links;
39 $this->model = &$polylang->model;
40 $this->filter_lang = &$polylang->filter_lang;
41
42 // Hide the column of the filtered language.
43 add_filter( 'hidden_columns', array( $this, 'hidden_columns' ) ); // Since WP 4.4.
44
45 // Add the language and translations columns in 'All Posts', 'All Pages' and 'Media library' panels.
46 foreach ( $this->model->get_translated_post_types() as $type ) {
47 // Use the latest filter late as some plugins purely overwrite what's done by others :(
48 // Specific case for media.
49 add_filter( 'manage_' . ( 'attachment' == $type ? 'upload' : 'edit-' . $type ) . '_columns', array( $this, 'add_post_column' ), 100 );
50 add_action( 'manage_' . ( 'attachment' == $type ? 'media' : $type . '_posts' ) . '_custom_column', array( $this, 'post_column' ), 10, 2 );
51 }
52
53 // Quick edit and bulk edit.
54 add_filter( 'quick_edit_custom_box', array( $this, 'quick_edit_custom_box' ) );
55 add_filter( 'bulk_edit_custom_box', array( $this, 'quick_edit_custom_box' ) );
56
57 // Adds the language column in the 'Categories' and 'Post Tags' tables.
58 foreach ( $this->model->get_translated_taxonomies() as $tax ) {
59 add_filter( 'manage_edit-' . $tax . '_columns', array( $this, 'add_term_column' ) );
60 add_filter( 'manage_' . $tax . '_custom_column', array( $this, 'term_column' ), 10, 3 );
61 }
62
63 // Ajax responses to update list table rows.
64 add_action( 'wp_ajax_pll_update_post_rows', array( $this, 'ajax_update_post_rows' ) );
65 add_action( 'wp_ajax_pll_update_term_rows', array( $this, 'ajax_update_term_rows' ) );
66 }
67
68 /**
69 * Adds languages and translations columns in posts, pages, media, categories and tags tables.
70 *
71 * @since 0.8.2
72 *
73 * @param string[] $columns List of table columns.
74 * @param string $before The column before which we want to add our languages.
75 * @return string[] Modified list of columns.
76 */
77 protected function add_column( $columns, $before ) {
78 if ( $n = array_search( $before, array_keys( $columns ) ) ) {
79 $end = array_slice( $columns, $n );
80 $columns = array_slice( $columns, 0, $n );
81 }
82
83 foreach ( $this->model->get_languages_list() as $language ) {
84 $columns[ 'language_' . $language->slug ] = $this->get_flag_html( $language ) . '<span class="screen-reader-text">' . esc_html( $language->name ) . '</span>';
85 }
86
87 return isset( $end ) ? array_merge( $columns, $end ) : $columns;
88 }
89
90 /**
91 * Returns the first language column in posts, pages, media, categories and tags tables.
92 *
93 * @since 0.9
94 *
95 * @return string first language column name.
96 */
97 protected function get_first_language_column() {
98 foreach ( $this->model->get_languages_list() as $language ) {
99 return 'language_' . $language->slug;
100 }
101
102 return '';
103 }
104
105 /**
106 * Hides the column for the filtered language.
107 *
108 * @since 2.7
109 *
110 * @param string[] $hidden Array of hidden columns.
111 * @return string[]
112 */
113 public function hidden_columns( $hidden ) {
114 if ( ! empty( $this->filter_lang ) ) {
115 $hidden[] = 'language_' . $this->filter_lang->slug;
116 }
117 return $hidden;
118 }
119
120 /**
121 * Adds the language and translations columns ( before the comments column ) in the posts, pages and media library tables.
122 *
123 * @since 0.1
124 *
125 * @param string[] $columns List of posts table columns.
126 * @return string[] Modified list of columns.
127 */
128 public function add_post_column( $columns ) {
129 return $this->add_column( $columns, 'comments' );
130 }
131
132 /**
133 * Fills the language and translations columns in the posts, pages and media library tables
134 * take care that when doing ajax inline edit, the post may not be updated in database yet
135 *
136 * @since 0.1
137 *
138 * @param string $column Column name.
139 * @param int $post_id Post ID.
140 * @return void
141 */
142 public function post_column( $column, $post_id ) {
143 $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save' === $_REQUEST['action']; // phpcs:ignore WordPress.Security.NonceVerification
144 $lang = $inline ? $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) : $this->model->post->get_language( $post_id ); // phpcs:ignore WordPress.Security.NonceVerification
145
146 if ( false === strpos( $column, 'language_' ) || ! $lang ) {
147 return;
148 }
149
150 $language = $this->model->get_language( substr( $column, 9 ) );
151
152 if ( empty( $language ) ) {
153 return;
154 }
155
156 // Hidden field containing the post language for quick edit
157 if ( $column == $this->get_first_language_column() ) {
158 printf( '<div class="hidden" id="lang_%d">%s</div>', intval( $post_id ), esc_html( $lang->slug ) );
159 }
160
161 // Link to edit post ( or a translation )
162 if ( $id = $this->model->post->get( $post_id, $language ) ) {
163 // get_edit_post_link returns nothing if the user cannot edit the post
164 // Thanks to Solinx. See http://wordpress.org/support/topic/feature-request-incl-code-check-for-capabilities-in-admin-screens
165 if ( $link = get_edit_post_link( $id ) ) {
166 $flag = '';
167 if ( $id === $post_id ) {
168 $flag = $this->get_flag_html( $language );
169 $class = 'pll_column_flag';
170 /* translators: accessibility text, %s is a native language name */
171 $s = sprintf( __( 'Edit this item in %s', 'polylang' ), $language->name );
172 } else {
173 $class = esc_attr( 'pll_icon_edit translation_' . $id );
174 /* translators: accessibility text, %s is a native language name */
175 $s = sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name );
176 }
177
178 $post = get_post( $id );
179
180 if ( ! empty( $post ) ) {
181 printf(
182 '<a class="%1$s" title="%2$s" href="%3$s"><span class="screen-reader-text">%4$s</span>%5$s</a>',
183 esc_attr( $class ),
184 esc_attr( $post->post_title ),
185 esc_url( $link ),
186 esc_html( $s ),
187 $flag // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
188 );
189 }
190 } elseif ( $id === $post_id ) {
191 printf(
192 '<span class="pll_column_flag" style=""><span class="screen-reader-text">%1$s</span>%2$s</span>',
193 /* translators: accessibility text, %s is a native language name */
194 esc_html( sprintf( __( 'This item is in %s', 'polylang' ), $language->name ) ),
195 $this->get_flag_html( $language ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
196 );
197 }
198 }
199 // Link to add a new translation
200 else {
201 echo $this->links->new_post_translation_link( $post_id, $language ); // phpcs:ignore WordPress.Security.EscapeOutput
202 }
203 }
204
205 /**
206 * Quick edit & bulk edit
207 *
208 * @since 0.9
209 *
210 * @param string $column column name
211 * @return string unmodified $column
212 */
213 public function quick_edit_custom_box( $column ) {
214 if ( $column == $this->get_first_language_column() ) {
215
216 $elements = $this->model->get_languages_list();
217 if ( current_filter() == 'bulk_edit_custom_box' ) {
218 array_unshift( $elements, (object) array( 'slug' => -1, 'name' => __( '&mdash; No Change &mdash;', 'polylang' ) ) );
219 }
220
221 $dropdown = new PLL_Walker_Dropdown();
222 // The hidden field 'old_lang' allows to pass the old language to ajax request
223 printf(
224 '<fieldset class="inline-edit-col-left">
225 <div class="inline-edit-col">
226 <label class="alignleft">
227 <span class="title">%s</span>
228 %s
229 </label>
230 </div>
231 </fieldset>',
232 esc_html__( 'Language', 'polylang' ),
233 $dropdown->walk( $elements, -1, array( 'name' => 'inline_lang_choice', 'id' => '' ) ) // phpcs:ignore WordPress.Security.EscapeOutput
234 );
235 }
236 return $column;
237 }
238
239 /**
240 * Adds the language column ( before the posts column ) in the 'Categories' or 'Post Tags' table.
241 *
242 * @since 0.1
243 *
244 * @param string[] $columns List of terms table columns.
245 * @return string[] modified List of columns.
246 */
247 public function add_term_column( $columns ) {
248 $screen = get_current_screen();
249
250 // Avoid displaying languages in screen options when editing a term.
251 if ( $screen instanceof WP_Screen && 'term' === $screen->base ) {
252 return $columns;
253 }
254
255 return $this->add_column( $columns, 'posts' );
256 }
257
258 /**
259 * Fills the language column in the taxonomy terms list table.
260 *
261 * @since 0.1
262 *
263 * @param string $out Column output.
264 * @param string $column Column name.
265 * @param int $term_id Term ID.
266 * @return string
267 */
268 public function term_column( $out, $column, $term_id ) {
269 $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save-tax' === $_REQUEST['action']; // phpcs:ignore WordPress.Security.NonceVerification
270 if ( false === strpos( $column, 'language_' ) || ! ( $lang = $inline ? $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) : $this->model->term->get_language( $term_id ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
271 return $out;
272 }
273
274 if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
275 $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification
276 }
277
278 if ( isset( $GLOBALS['post_type'] ) ) {
279 $post_type = $GLOBALS['post_type'];
280 }
281
282 if ( isset( $_REQUEST['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
283 $taxonomy = sanitize_key( $_REQUEST['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification
284 }
285
286 if ( isset( $GLOBALS['taxonomy'] ) ) {
287 $taxonomy = $GLOBALS['taxonomy'];
288 }
289
290 if ( ! isset( $taxonomy, $post_type ) || ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
291 return $out;
292 }
293
294 $term_id = (int) $term_id;
295 $language = $this->model->get_language( substr( $column, 9 ) );
296
297 if ( empty( $language ) ) {
298 return $out;
299 }
300
301 // Link to edit term ( or a translation )
302 if ( ( $id = $this->model->term->get( $term_id, $language ) ) && $term = get_term( $id, $taxonomy ) ) {
303 if ( $term instanceof WP_Term && $link = get_edit_term_link( $id, $taxonomy, $post_type ) ) {
304 $flag = '';
305 if ( $id === $term_id ) {
306 $flag = $this->get_flag_html( $language );
307 $class = 'pll_column_flag';
308 /* translators: accessibility text, %s is a native language name */
309 $s = sprintf( __( 'Edit this item in %s', 'polylang' ), $language->name );
310 } else {
311 $class = esc_attr( 'pll_icon_edit translation_' . $id );
312 /* translators: accessibility text, %s is a native language name */
313 $s = sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name );
314 }
315 $out .= sprintf(
316 '<a class="%1$s" title="%2$s" href="%3$s"><span class="screen-reader-text">%4$s</span>%5$s</a>',
317 $class,
318 esc_attr( $term->name ),
319 esc_url( $link ),
320 esc_html( $s ),
321 $flag
322 );
323 } elseif ( $id === $term_id ) {
324 $out .= sprintf(
325 '<span class="pll_column_flag"><span class="screen-reader-text">%1$s</span>%2$s</span>',
326 /* translators: accessibility text, %s is a native language name */
327 esc_html( sprintf( __( 'This item is in %s', 'polylang' ), $language->name ) ),
328 $this->get_flag_html( $language )
329 );
330 }
331 }
332
333 // Link to add a new translation
334 else {
335 $out .= $this->links->new_term_translation_link( $term_id, $taxonomy, $post_type, $language );
336 }
337
338 if ( $this->get_first_language_column() === $column ) {
339 $out .= sprintf( '<div class="hidden" id="lang_%d">%s</div>', intval( $term_id ), esc_html( $lang->slug ) );
340
341 /**
342 * Filters the output of the first language column in the terms list table.
343 *
344 * @since 3.7
345 *
346 * @param string $output First language column output.
347 * @param int $term_id Term ID.
348 * @param string $lang Language code.
349 */
350 $out = apply_filters( 'pll_first_language_term_column', $out, $term_id, $lang->slug );
351 }
352
353 return $out;
354 }
355
356 /**
357 * Update rows of translated posts when the language is modified in quick edit
358 *
359 * @since 1.7
360 *
361 * @return void
362 */
363 public function ajax_update_post_rows() {
364 check_ajax_referer( 'inlineeditnonce', '_pll_nonce' );
365
366 if ( ! isset( $_POST['post_type'], $_POST['post_id'], $_POST['screen'] ) ) {
367 wp_die( 0 );
368 }
369
370 $post_type = sanitize_key( $_POST['post_type'] );
371
372 if ( ! post_type_exists( $post_type ) || ! $this->model->is_translated_post_type( $post_type ) ) {
373 wp_die( 0 );
374 }
375
376 /** @var WP_Posts_List_Table $wp_list_table */
377 $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => sanitize_key( $_POST['screen'] ) ) );
378
379 $x = new WP_Ajax_Response();
380
381 // Collect old translations
382 $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
383 $translations = array_map( 'intval', $translations );
384
385 $translations = array_merge( $translations, array( (int) $_POST['post_id'] ) ); // Add current post
386
387 foreach ( $translations as $post_id ) {
388 $level = is_post_type_hierarchical( $post_type ) ? count( get_ancestors( $post_id, $post_type ) ) : 0;
389 if ( $post = get_post( $post_id ) ) {
390 ob_start();
391 $wp_list_table->single_row( $post, $level );
392 $data = ob_get_clean();
393 $x->add( array( 'what' => 'row', 'data' => $data, 'supplemental' => array( 'post_id' => $post_id ) ) );
394 }
395 }
396
397 $x->send();
398 }
399
400 /**
401 * Update rows of translated terms when adding / deleting a translation or when the language is modified in quick edit
402 *
403 * @since 1.7
404 *
405 * @return void
406 */
407 public function ajax_update_term_rows() {
408 check_ajax_referer( 'pll_language', '_pll_nonce' );
409
410 if ( ! isset( $_POST['taxonomy'], $_POST['term_id'], $_POST['screen'] ) ) {
411 wp_die( 0 );
412 }
413
414 $taxonomy = sanitize_key( $_POST['taxonomy'] );
415
416 if ( ! taxonomy_exists( $taxonomy ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
417 wp_die( 0 );
418 }
419
420 /** @var WP_Terms_List_Table $wp_list_table */
421 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => sanitize_key( $_POST['screen'] ) ) );
422
423 $x = new WP_Ajax_Response();
424
425 // Collect old translations
426 $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
427 $translations = array_map( 'intval', $translations );
428
429 $translations = array_merge( $translations, $this->model->term->get_translations( (int) $_POST['term_id'] ) ); // Add current translations
430 $translations = array_unique( $translations ); // Remove duplicates
431
432 foreach ( $translations as $term_id ) {
433 $level = is_taxonomy_hierarchical( $taxonomy ) ? count( get_ancestors( $term_id, $taxonomy ) ) : 0;
434 $tag = get_term( $term_id, $taxonomy );
435
436 if ( ! $tag instanceof WP_Term ) {
437 continue;
438 }
439
440 ob_start();
441 $wp_list_table->single_row( $tag, $level );
442 $data = ob_get_clean();
443 $x->add( array( 'what' => 'row', 'data' => $data, 'supplemental' => array( 'term_id' => $term_id ) ) );
444 }
445
446 $x->send();
447 }
448
449 /**
450 * Returns the language flag or the language slug if there is no flag.
451 *
452 * @since 2.8
453 *
454 * @param PLL_Language $language PLL_Language object.
455 * @return string
456 */
457 protected function get_flag_html( $language ) {
458 return $language->flag ?: sprintf( '<abbr>%s</abbr>', esc_html( $language->slug ) );
459 }
460 }
461