PluginProbe
Polylang / 2.8.1
Polylang v2.8.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 2.8.1, at admin/admin-filters-columns.php

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