PluginProbe
Polylang / 2.8.3
Polylang v2.8.3
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 2.8.3, at admin/admin-base.php

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