PluginProbe
Polylang / 3.1.1
Polylang v3.1.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.1.1, at admin/admin-filters-columns.php

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