PluginProbe
Polylang / trunk
Polylang vtrunk
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 / src / admin / admin-filters-post.php

admin-filters-post.php in Polylang trunk, at src/admin/admin-filters-post.php

371 lines 9.7 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 use WP_Syntex\Polylang\Capabilities\Capabilities;
7
8 /**
9 * Manages filters and actions related to posts on admin side
10 *
11 * @since 1.2
12 */
13 class PLL_Admin_Filters_Post {
14 /**
15 * @var PLL_Model
16 */
17 public $model;
18
19 /**
20 * Current language (used to filter the content).
21 *
22 * @var PLL_Language|null
23 */
24 public $curlang;
25
26 /**
27 * Constructor: setups filters and actions
28 *
29 * @since 1.2
30 *
31 * @param object $polylang The Polylang object.
32 */
33 public function __construct( &$polylang ) {
34 $this->model = &$polylang->model;
35 $this->curlang = &$polylang->curlang;
36
37 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
38
39 // Filters posts, pages and media by language
40 add_action( 'parse_query', array( $this, 'parse_query' ) );
41
42 // Adds actions and filters related to languages when creating, saving or deleting posts and pages
43 add_action( 'load-post.php', array( $this, 'edit_post' ) );
44 add_action( 'load-edit.php', array( $this, 'bulk_edit_posts' ) );
45 add_action( 'wp_ajax_inline-save', array( $this, 'inline_edit_post' ), 0 ); // Before WordPress
46
47 // Sets the language in Tiny MCE
48 add_filter( 'tiny_mce_before_init', array( $this, 'tiny_mce_before_init' ) );
49
50 // Filter untranslated items in the posts list table.
51 add_action( 'restrict_manage_posts', array( $this, 'untranslated_dropdown' ) );
52
53 // Languages views in post list tables.
54 foreach ( $this->model->get_translated_post_types() as $post_type ) {
55 add_filter( "views_edit-{$post_type}", array( $this, 'add_per_language_edit_views' ) );
56 }
57 }
58
59 /**
60 * Outputs a javascript list of terms ordered by language and hierarchical taxonomies
61 * to filter the category checklist per post language in quick edit
62 * Outputs a javascript list of pages ordered by language
63 * to filter the parent dropdown per post language in quick edit
64 *
65 * @since 1.7
66 *
67 * @return void
68 */
69 public function admin_enqueue_scripts() {
70 $screen = get_current_screen();
71
72 if ( empty( $screen ) ) {
73 return;
74 }
75
76 // Hierarchical taxonomies
77 if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'objects' ) ) {
78 // Get translated hierarchical taxonomies
79 $hierarchical_taxonomies = array();
80 foreach ( $taxonomies as $taxonomy ) {
81 if ( $taxonomy->hierarchical && $taxonomy->show_in_quick_edit && $this->model->is_translated_taxonomy( $taxonomy->name ) ) {
82 $hierarchical_taxonomies[] = $taxonomy->name;
83 }
84 }
85
86 if ( ! empty( $hierarchical_taxonomies ) ) {
87 $terms = get_terms( array( 'taxonomy' => $hierarchical_taxonomies, 'get' => 'all' ) );
88 $term_languages = array();
89
90 if ( is_array( $terms ) ) {
91 foreach ( $terms as $term ) {
92 if ( $lang = $this->model->term->get_language( $term->term_id ) ) {
93 $term_languages[ $lang->slug ][ $term->taxonomy ][] = $term->term_id;
94 }
95 }
96 }
97
98 // Send all these data to javascript
99 if ( ! empty( $term_languages ) ) {
100 wp_localize_script( 'pll_post', 'pll_term_languages', $term_languages );
101 }
102 }
103 }
104
105 // Hierarchical post types
106 if ( 'edit' == $screen->base && is_post_type_hierarchical( $screen->post_type ) && post_type_supports( $screen->post_type, 'page-attributes' ) ) {
107 $pages = get_pages(
108 array(
109 'sort_column' => 'menu_order, post_title',
110 'post_type' => $screen->post_type,
111 )
112 ); // Same arguments as the parent pages dropdown to avoid an extra query.
113
114 if ( ! is_array( $pages ) ) {
115 return;
116 }
117
118 update_object_term_cache( wp_list_pluck( $pages, 'ID' ), $screen->post_type );
119
120 $page_languages = array();
121
122 foreach ( $pages as $page ) {
123 if ( $lang = $this->model->post->get_language( $page->ID ) ) {
124 $page_languages[ $lang->slug ][] = $page->ID;
125 }
126 }
127
128 // Send all these data to javascript
129 if ( ! empty( $page_languages ) ) {
130 wp_localize_script( 'pll_post', 'pll_page_languages', $page_languages );
131 }
132 }
133 }
134
135 /**
136 * Filters posts, pages and media by language.
137 *
138 * @since 0.1
139 *
140 * @param WP_Query $query WP_Query object.
141 * @return void
142 */
143 public function parse_query( $query ) {
144 $pll_query = new PLL_Query( $query, $this->model );
145 $pll_query->filter_query( $this->curlang );
146 }
147
148 /**
149 * Save language and translation when editing a post (post.php).
150 *
151 * @since 2.3
152 *
153 * @return void
154 */
155 public function edit_post() {
156 if ( ! isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) ) {
157 return;
158 }
159
160 check_admin_referer( 'pll_language', '_pll_nonce' );
161
162 $post_id = (int) $_POST['post_ID'];
163 $post = get_post( $post_id );
164
165 if ( empty( $post ) ) {
166 return;
167 }
168
169 $post_type_object = get_post_type_object( $post->post_type );
170
171 if ( empty( $post_type_object ) ) {
172 return;
173 }
174
175 $user = Capabilities::get_user();
176 if ( ! $user->has_cap( $post_type_object->cap->edit_post, $post_id ) ) {
177 return;
178 }
179
180 $language = $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) );
181
182 if ( empty( $language ) ) {
183 return;
184 }
185
186 $user->can_translate_or_die( $language );
187
188 $this->model->post->set_language( $post_id, $language );
189
190 if ( ! isset( $_POST['post_tr_lang'] ) ) {
191 return;
192 }
193
194 $this->model->post->save_translations( $post_id, array_map( 'absint', $_POST['post_tr_lang'] ) );
195 }
196
197 /**
198 * Save language when bulk editing posts.
199 *
200 * @since 2.3
201 *
202 * @return void
203 */
204 public function bulk_edit_posts() {
205 if ( ! isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'], $_REQUEST['post'], $_REQUEST['_wpnonce'] ) ) {
206 return;
207 }
208
209 if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-posts' ) ) {
210 return;
211 }
212
213 if ( -1 === $_GET['inline_lang_choice'] ) {
214 return;
215 }
216
217 $language = $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) );
218
219 if ( empty( $language ) ) {
220 return;
221 }
222
223 $user = Capabilities::get_user();
224 $user->can_translate_or_die( $language );
225
226 $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
227 foreach ( $post_ids as $post_id ) {
228 if ( $user->has_cap( 'edit_post', $post_id ) ) {
229 $this->model->post->set_language( $post_id, $language );
230 }
231 }
232 }
233
234 /**
235 * Save language when inline editing a post.
236 *
237 * @since 2.3
238 *
239 * @return void
240 */
241 public function inline_edit_post() {
242 if ( ! isset( $_POST['post_ID'], $_POST['inline_lang_choice'], $_REQUEST['_inline_edit'] ) ) {
243 return;
244 }
245
246 if ( ! wp_verify_nonce( $_REQUEST['_inline_edit'], 'inlineeditnonce' ) ) {
247 return;
248 }
249
250 $language = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) );
251
252 if ( empty( $language ) ) {
253 return;
254 }
255
256 $user = Capabilities::get_user();
257 $user->can_translate_or_die( $language );
258
259 $post_id = (int) $_POST['post_ID'];
260
261 if ( ! $post_id || ! $user->has_cap( 'edit_post', $post_id ) ) {
262 return;
263 }
264
265 $this->model->post->set_language( $post_id, $language );
266 }
267
268 /**
269 * Sets the language attribute and text direction for Tiny MCE.
270 *
271 * @since 2.2
272 *
273 * @param array $mce_init TinyMCE config.
274 * @return array
275 */
276 public function tiny_mce_before_init( $mce_init ) {
277 if ( ! empty( $this->curlang ) ) {
278 $mce_init['wp_lang_attr'] = $this->curlang->get_locale( 'display' );
279 $mce_init['directionality'] = $this->curlang->is_rtl ? 'rtl' : 'ltr';
280 }
281 return $mce_init;
282 }
283
284 /**
285 * Displays a dropdown for filtering unstranslated items in the posts list table.
286 *
287 * @since 3.9
288 *
289 * @param string $post_type The post type.
290 * @return void
291 */
292 public function untranslated_dropdown( $post_type ) {
293 if ( ! $this->model->is_translated_post_type( $post_type ) ) {
294 return;
295 }
296
297 $languages = $this->model->get_languages_list();
298
299 foreach ( $languages as $k => $language ) {
300 $languages[ $k ] = $language->to_std_class(); // Important not to modify the language name everywhere.
301 /* translators: %s is a native language name */
302 $languages[ $k ]->name = sprintf( __( 'Untranslated in %s', 'polylang' ), $language->name );
303 }
304
305 $dropdown = new PLL_Walker_Dropdown();
306
307 $dropdown_html = $dropdown->walk(
308 array_merge(
309 array( (object) array( 'slug' => 0, 'name' => __( 'All translation statuses', 'polylang' ) ) ),
310 $languages
311 ),
312 -1,
313 array(
314 'id' => 'pll_untranslated_in',
315 'name' => 'untranslated_in',
316 'class' => 'postform',
317 'selected' => isset( $_GET['untranslated_in'] ) ? sanitize_key( $_GET['untranslated_in'] ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
318 )
319 );
320
321 printf(
322 '<label class="screen-reader-text" for="pll_untranslated_in">%s</label>',
323 esc_html__( 'Filter untranslated items', 'polylang' )
324 );
325
326 echo $dropdown_html; // phpcs:ignore WordPress.Security.EscapeOutput
327 }
328
329 /**
330 * Add languages filter views to the list of views available for the post list table.
331 *
332 * @since 3.9
333 *
334 * @param string[] $views Array of available list table views.
335 * @return string[]
336 */
337 public function add_per_language_edit_views( $views ) {
338 global $typenow;
339
340 if ( empty( $typenow ) ) {
341 return $views;
342 }
343
344 $q = array(
345 'post_type' => $typenow,
346 'post_status' => 'any',
347 );
348
349 foreach ( $this->model->languages->get_list() as $language ) {
350 $views[ $language->slug ] = sprintf(
351 '<a href="%s" %s>%s <span class="count">(%s)</span></a>',
352 esc_url( add_query_arg( 'lang', $language->slug, remove_query_arg( 'paged' ) ) ),
353 ! empty( $this->curlang ) && $this->curlang->slug === $language->slug ? 'class="current" aria-current="page"' : '',
354 esc_html( $language->name ),
355 number_format_i18n( (int) $this->model->count_posts( $language, $q ) )
356 );
357 }
358
359 if ( isset( $views['all'] ) ) { // In case a filter removes this key.
360 $replacement = 'lang=all&';
361
362 if ( ! str_contains( $views['all'], 'all_posts=1' ) ) {
363 $replacement .= 'all_posts=1&';
364 }
365 $views['all'] = str_replace( 'edit.php?', 'edit.php?' . $replacement, $views['all'] );
366 }
367
368 return $views;
369 }
370 }
371