PluginProbe
Polylang / 3.0.1
Polylang v3.0.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-base.php

admin-base.php in Polylang 3.0.1, at admin/admin-base.php

472 lines 16.2 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 * Setup features available on all admin pages.
8 *
9 * @since 1.8
10 */
11 abstract class PLL_Admin_Base extends PLL_Base {
12 /**
13 * Current language (used to filter the content).
14 *
15 * @var PLL_Language
16 */
17 public $curlang;
18
19 /**
20 * Language selected in the admin language filter.
21 *
22 * @var PLL_Language
23 */
24 public $filter_lang;
25
26 /**
27 * Preferred language to assign to new contents.
28 *
29 * @var PLL_Language
30 */
31 public $pref_lang;
32
33 /**
34 * @var PLL_Filters_Links
35 */
36 public $filters_links;
37
38 /**
39 * @var PLL_Admin_Links
40 */
41 public $links;
42
43 /**
44 * @var PLL_Admin_Notices
45 */
46 public $notices;
47
48 /**
49 * @var PLL_Admin_Static_Pages
50 */
51 public $static_pages;
52
53 /**
54 * Setups actions needed on all admin pages.
55 *
56 * @since 1.8
57 *
58 * @param PLL_Links_Model $links_model Reference to the links model.
59 */
60 public function __construct( &$links_model ) {
61 parent::__construct( $links_model );
62
63 // Adds the link to the languages panel in the WordPress admin menu
64 add_action( 'admin_menu', array( $this, 'add_menus' ) );
65
66 // Setup js scripts and css styles
67 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
68 add_action( 'admin_print_footer_scripts', array( $this, 'admin_print_footer_scripts' ), 0 ); // High priority in case an ajax request is sent by an immediately invoked function
69
70 add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) );
71 }
72
73 /**
74 * Setups filters and action needed on all admin pages and on plugins page
75 * Loads the settings pages or the filters base on the request
76 *
77 * @since 1.2
78 */
79 public function init() {
80 parent::init();
81
82 $this->notices = new PLL_Admin_Notices( $this );
83
84 if ( ! $this->model->get_languages_list() ) {
85 return;
86 }
87
88 $this->links = new PLL_Admin_Links( $this ); // FIXME needed here ?
89 $this->static_pages = new PLL_Admin_Static_Pages( $this ); // FIXME needed here ?
90 $this->filters_links = new PLL_Filters_Links( $this ); // FIXME needed here ?
91
92 // Filter admin language for users
93 // We must not call user info before WordPress defines user roles in wp-settings.php
94 add_action( 'setup_theme', array( $this, 'init_user' ) );
95 add_filter( 'request', array( $this, 'request' ) );
96
97 // Adds the languages in admin bar
98 add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); // 100 determines the position
99 }
100
101 /**
102 * Adds the link to the languages panel in the WordPress admin menu
103 *
104 * @since 0.1
105 *
106 * @return void
107 */
108 public function add_menus() {
109 global $admin_page_hooks;
110
111 // Prepare the list of tabs
112 $tabs = array( 'lang' => __( 'Languages', 'polylang' ) );
113
114 // Only if at least one language has been created
115 if ( $this->model->get_languages_list() ) {
116 $tabs['strings'] = __( 'Strings translations', 'polylang' );
117 }
118
119 $tabs['settings'] = __( 'Settings', 'polylang' );
120
121 /**
122 * Filter the list of tabs in Polylang settings
123 *
124 * @since 1.5.1
125 *
126 * @param array $tabs list of tab names
127 */
128 $tabs = apply_filters( 'pll_settings_tabs', $tabs );
129
130 $parent = '';
131
132 foreach ( $tabs as $tab => $title ) {
133 $page = 'lang' === $tab ? 'mlang' : "mlang_$tab";
134 if ( empty( $parent ) ) {
135 $parent = $page;
136 add_menu_page( $title, __( 'Languages', 'polylang' ), 'manage_options', $page, '__return_null', 'dashicons-translation' );
137 $admin_page_hooks[ $page ] = 'languages'; // Hack to avoid the localization of the hook name. See: https://core.trac.wordpress.org/ticket/18857
138 }
139
140 add_submenu_page( $parent, $title, $title, 'manage_options', $page, array( $this, 'languages_page' ) );
141 }
142 }
143
144 /**
145 * Setup js scripts & css styles ( only on the relevant pages )
146 *
147 * @since 0.6
148 *
149 * @return void
150 */
151 public function admin_enqueue_scripts() {
152 $screen = get_current_screen();
153 if ( empty( $screen ) ) {
154 return;
155 }
156
157 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
158
159 /*
160 * For each script:
161 * 0 => the pages on which to load the script
162 * 1 => the scripts it needs to work
163 * 2 => 1 if loaded even if languages have not been defined yet, 0 otherwise
164 * 3 => 1 if loaded in footer
165 */
166 $scripts = array(
167 'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), 0, 0 ),
168 'widgets' => array( array( 'widgets' ), array( 'jquery' ), 0, 0 ),
169 );
170
171 if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
172 $scripts['post'] = array( array( 'edit', 'upload' ), array( 'jquery', 'wp-ajax-response' ), 0, 1 );
173
174 // Classic editor.
175 if ( ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) {
176 $scripts['classic-editor'] = array( array( 'post', 'media', 'async-upload' ), array( 'jquery', 'wp-ajax-response', 'post', 'jquery-ui-dialog', 'wp-i18n' ), 0, 1 );
177 }
178
179 // Block editor with legacy metabox in WP 5.0+.
180 if ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin() ) {
181 $scripts['block-editor'] = array( array( 'post' ), array( 'jquery', 'wp-ajax-response', 'wp-api-fetch', 'jquery-ui-dialog', 'wp-i18n' ), 0, 1 );
182 }
183 }
184
185 if ( ! empty( $screen->taxonomy ) && $this->model->is_translated_taxonomy( $screen->taxonomy ) ) {
186 $scripts['term'] = array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), 0, 1 );
187 }
188
189 foreach ( $scripts as $script => $v ) {
190 if ( in_array( $screen->base, $v[0] ) && ( $v[2] || $this->model->get_languages_list() ) ) {
191 wp_enqueue_script( 'pll_' . $script, plugins_url( '/js/build/' . $script . $suffix . '.js', POLYLANG_BASENAME ), $v[1], POLYLANG_VERSION, $v[3] );
192 }
193 }
194
195 wp_register_style( 'polylang_admin', plugins_url( '/css/build/admin' . $suffix . '.css', POLYLANG_BASENAME ), array( 'wp-jquery-ui-dialog' ), POLYLANG_VERSION );
196 wp_enqueue_style( 'polylang_dialog', plugins_url( '/css/build/dialog' . $suffix . '.css', POLYLANG_BASENAME ), array( 'polylang_admin' ), POLYLANG_VERSION );
197
198 $this->localize_scripts();
199 }
200
201 /**
202 * Enqueue scripts to the WP Customizer.
203 *
204 * @since 2.4.0
205 *
206 * @return void
207 */
208 public function customize_controls_enqueue_scripts() {
209 if ( $this->model->get_languages_list() ) {
210 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
211 wp_enqueue_script( 'pll_widgets', plugins_url( '/js/build/widgets' . $suffix . '.js', POLYLANG_BASENAME ), array( 'jquery' ), POLYLANG_VERSION, true );
212 $this->localize_scripts();
213 }
214 }
215
216 /**
217 * Localize scripts.
218 *
219 * @since 2.4.0
220 *
221 * @return void
222 */
223 public function localize_scripts() {
224 if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
225 wp_localize_script(
226 'pll_widgets',
227 'pll_widgets',
228 array(
229 'flags' => wp_list_pluck( $this->model->get_languages_list(), 'flag', 'slug' ),
230 )
231 );
232 }
233 }
234
235 /**
236 * Sets pll_ajax_backend on all backend ajax request
237 * The final goal is to detect if an ajax request is made on admin or frontend
238 *
239 * Takes care to various situations:
240 * when the ajax request has no options.data thanks to ScreenfeedFr
241 * see: https://wordpress.org/support/topic/ajaxprefilter-may-not-work-as-expected
242 * when options.data is a json string
243 * see: https://wordpress.org/support/topic/polylang-breaking-third-party-ajax-requests-on-admin-panels
244 * when options.data is an empty string (GET request with the method 'load')
245 * see: https://wordpress.org/support/topic/invalid-url-during-wordpress-new-dashboard-widget-operation
246 *
247 * @since 1.4
248 *
249 * @return void
250 */
251 public function admin_print_footer_scripts() {
252 global $post_ID, $tag_ID;
253
254 $params = array( 'pll_ajax_backend' => 1 );
255 if ( ! empty( $post_ID ) ) {
256 $params = array_merge( $params, array( 'pll_post_id' => (int) $post_ID ) );
257 }
258
259 if ( ! empty( $tag_ID ) ) {
260 $params = array_merge( $params, array( 'pll_term_id' => (int) $tag_ID ) );
261 }
262
263 $str = http_build_query( $params );
264 $arr = wp_json_encode( $params );
265 ?>
266 <script type="text/javascript">
267 if (typeof jQuery != 'undefined') {
268 jQuery(
269 function( $ ){
270 $.ajaxPrefilter( function ( options, originalOptions, jqXHR ) {
271 if ( -1 != options.url.indexOf( ajaxurl ) || -1 != ajaxurl.indexOf( options.url ) ) {
272
273 function addStringParameters() {
274 if ( 'undefined' === typeof options.data || '' === options.data ) {
275 options.data = '<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>';
276 } else {
277 options.data = options.data + '&<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>';
278 }
279 }
280
281 /*
282 * options.processData set to true is the default jQuery process where the data is converted in a query string by using jQuery.param().
283 * This step is done before applying filters. Thus here the options.data is already a string in this case.
284 * @See https://github.com/jquery/jquery/blob/3.5.1/src/ajax.js#L563-L569 jQuery ajax function.
285 */
286 if ( options.processData ) {
287 addStringParameters();
288 } else {
289 /*
290 * If options.processData is set to false data could be undefined or pass as a string.
291 * So data as to be processed as if options.processData is set to true.
292 */
293 if ( 'undefined' === typeof options.data || 'string' === typeof options.data ) {
294 addStringParameters();
295 } else {
296 // Otherwise options.data is probably an object.
297 options.data = Object.assign( options.data, <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?> );
298 }
299 }
300 }
301 });
302 }
303 );
304 }
305 </script>
306 <?php
307 }
308
309 /**
310 * Sets the admin current language, used to filter the content
311 *
312 * @since 2.0
313 *
314 * @return void
315 */
316 public function set_current_language() {
317 $this->curlang = $this->filter_lang;
318
319 // Edit Post
320 if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
321 $this->curlang = $lang;
322 } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && $this->model->is_translated_post_type( get_post_type( (int) $_GET['post'] ) ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
323 $this->curlang = $lang;
324 } elseif ( 'post-new.php' === $GLOBALS['pagenow'] && ( empty( $_GET['post_type'] ) || $this->model->is_translated_post_type( sanitize_key( $_GET['post_type'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
325 $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
326 }
327
328 // Edit Term
329 elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
330 $this->curlang = $lang;
331 } elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
332 if ( isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
333 $this->curlang = $lang;
334 } elseif ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
335 $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
336 } elseif ( empty( $this->curlang ) ) {
337 $this->curlang = $this->pref_lang;
338 }
339 }
340
341 // Ajax
342 if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
343 $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
344 }
345
346 // Inform that the admin language has been set.
347 if ( $this->curlang ) {
348 /** This action is documented in frontend/choose-lang.php */
349 do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
350 } else {
351 /** This action is documented in include/class-polylang.php */
352 do_action( 'pll_no_language_defined' ); // To load overridden textdomains.
353 }
354 }
355
356 /**
357 * Defines the backend language and the admin language filter based on user preferences
358 *
359 * @since 1.2.3
360 *
361 * @return void
362 */
363 public function init_user() {
364 // Language for admin language filter: may be empty
365 // $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter
366 // We intentionally don't use a nonce to update the language filter
367 if ( ! wp_doing_ajax() && ! empty( $_GET['lang'] ) && ! is_numeric( sanitize_key( $_GET['lang'] ) ) && current_user_can( 'edit_user', $user_id = get_current_user_id() ) ) { // phpcs:ignore WordPress.Security.NonceVerification
368 update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( sanitize_key( $_GET['lang'] ) ) ) ? $lang->slug : '' ); // phpcs:ignore WordPress.Security.NonceVerification
369 }
370
371 $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
372
373 // Set preferred language for use when saving posts and terms: must not be empty
374 $this->pref_lang = empty( $this->filter_lang ) ? $this->model->get_language( $this->options['default_lang'] ) : $this->filter_lang;
375
376 /**
377 * Filters the preferred language on admin side.
378 * The preferred language is used for example to determine the language of a new post.
379 *
380 * @since 1.2.3
381 *
382 * @param PLL_Language $pref_lang Preferred language.
383 */
384 $this->pref_lang = apply_filters( 'pll_admin_preferred_language', $this->pref_lang );
385
386 $this->set_current_language();
387
388 // Plugin i18n, only needed for backend.
389 load_plugin_textdomain( 'polylang' );
390 }
391
392 /**
393 * Avoids parsing a tax query when all languages are requested
394 * Fixes https://wordpress.org/support/topic/notice-undefined-offset-0-in-wp-includesqueryphp-on-line-3877 introduced in WP 4.1
395 *
396 * @see https://core.trac.wordpress.org/ticket/31246 the suggestion of @boonebgorges.
397 *
398 * @since 1.6.5
399 *
400 * @param array $qvars
401 * @return array
402 */
403 public function request( $qvars ) {
404 if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
405 unset( $qvars['lang'] );
406 }
407
408 return $qvars;
409 }
410
411 /**
412 * Adds the languages list in admin bar for the admin languages filter.
413 *
414 * @since 0.9
415 *
416 * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar global object.
417 * @return void
418 */
419 public function admin_bar_menu( $wp_admin_bar ) {
420 $all_item = (object) array(
421 'slug' => 'all',
422 'name' => __( 'Show all languages', 'polylang' ),
423 'flag' => '<span class="ab-icon"></span>',
424 );
425
426 $selected = empty( $this->filter_lang ) ? $all_item : $this->filter_lang;
427
428 $title = sprintf(
429 '<span class="ab-label"%1$s><span class="screen-reader-text">%2$s</span>%3$s</span>',
430 'all' === $selected->slug ? '' : sprintf( ' lang="%s"', esc_attr( $selected->get_locale( 'display' ) ) ),
431 __( 'Filters content by language', 'polylang' ),
432 esc_html( $selected->name )
433 );
434
435 /**
436 * Filters the admin languages filter submenu items
437 *
438 * @since 2.6
439 *
440 * @param array $items The admin languages filter submenu items.
441 */
442 $items = apply_filters( 'pll_admin_languages_filter', array_merge( array( $all_item ), $this->model->get_languages_list() ) );
443
444 if ( ! empty( $items ) ) {
445 $wp_admin_bar->add_menu(
446 array(
447 'id' => 'languages',
448 'title' => $selected->flag . $title,
449 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ),
450 'meta' => array( 'title' => __( 'Filters content by language', 'polylang' ) ),
451 )
452 );
453 }
454
455 foreach ( $items as $lang ) {
456 if ( $selected->slug === $lang->slug ) {
457 continue;
458 }
459
460 $wp_admin_bar->add_menu(
461 array(
462 'parent' => 'languages',
463 'id' => $lang->slug,
464 'title' => $lang->flag . esc_html( $lang->name ),
465 'href' => esc_url( add_query_arg( 'lang', $lang->slug, remove_query_arg( 'paged' ) ) ),
466 'meta' => 'all' === $lang->slug ? array() : array( 'lang' => esc_attr( $lang->get_locale( 'display' ) ) ),
467 )
468 );
469 }
470 }
471 }
472