PluginProbe
Polylang / 2.8.2
Polylang v2.8.2
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.2, at admin/admin-base.php

410 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'] ) && $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 // FIXME 'edit-tags.php' for backward compatibility with WP < 4.5
272 elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
273 $this->curlang = $lang;
274 } elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
275 $this->curlang = $lang;
276 } elseif ( 'edit-tags.php' === $GLOBALS['pagenow'] && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
277 if ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
278 $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
279 } elseif ( empty( $this->curlang ) ) {
280 $this->curlang = $this->pref_lang;
281 }
282 }
283
284 // Ajax
285 if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
286 $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
287 }
288 }
289
290 /**
291 * Defines the backend language and the admin language filter based on user preferences
292 *
293 * @since 1.2.3
294 */
295 public function init_user() {
296 // Language for admin language filter: may be empty
297 // $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter
298 // We intentionally don't use a nonce to update the language filter
299 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
300 update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( sanitize_key( $_GET['lang'] ) ) ) ? $lang->slug : '' ); // phpcs:ignore WordPress.Security.NonceVerification
301 }
302
303 $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
304
305 // Set preferred language for use when saving posts and terms: must not be empty
306 $this->pref_lang = empty( $this->filter_lang ) ? $this->model->get_language( $this->options['default_lang'] ) : $this->filter_lang;
307
308 /**
309 * Filter the preferred language on admin side
310 * The preferred language is used for example to determine the language of a new post
311 *
312 * @since 1.2.3
313 *
314 * @param object $pref_lang preferred language
315 */
316 $this->pref_lang = apply_filters( 'pll_admin_preferred_language', $this->pref_lang );
317
318 $this->set_current_language();
319
320 // Inform that the admin language has been set
321 // Only if the admin language is one of the Polylang defined language
322 if ( $curlang = $this->model->get_language( get_user_locale() ) ) {
323 /** This action is documented in frontend/choose-lang.php */
324 do_action( 'pll_language_defined', $curlang->slug, $curlang );
325 } else {
326 /** This action is documented in include/class-polylang.php */
327 do_action( 'pll_no_language_defined' ); // to load overridden textdomains
328 }
329 }
330
331 /**
332 * Avoids parsing a tax query when all languages are requested
333 * Fixes https://wordpress.org/support/topic/notice-undefined-offset-0-in-wp-includesqueryphp-on-line-3877 introduced in WP 4.1
334 *
335 * @see the suggestion of @boonebgorges, https://core.trac.wordpress.org/ticket/31246
336 *
337 * @since 1.6.5
338 *
339 * @param array $qvars
340 * @return array
341 */
342 public function request( $qvars ) {
343 if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
344 unset( $qvars['lang'] );
345 }
346
347 return $qvars;
348 }
349
350 /**
351 * Adds the languages list in admin bar for the admin languages filter
352 *
353 * @since 0.9
354 *
355 * @param object $wp_admin_bar
356 */
357 public function admin_bar_menu( $wp_admin_bar ) {
358 $all_item = (object) array(
359 'slug' => 'all',
360 'name' => __( 'Show all languages', 'polylang' ),
361 'flag' => '<span class="ab-icon"></span>',
362 );
363
364 $selected = empty( $this->filter_lang ) ? $all_item : $this->filter_lang;
365
366 $title = sprintf(
367 '<span class="ab-label"%1$s><span class="screen-reader-text">%2$s</span>%3$s</span>',
368 'all' === $selected->slug ? '' : sprintf( ' lang="%s"', esc_attr( $selected->get_locale( 'display' ) ) ),
369 __( 'Filters content by language', 'polylang' ),
370 esc_html( $selected->name )
371 );
372
373 /**
374 * Filters the admin languages filter submenu items
375 *
376 * @since 2.6
377 *
378 * @param array $items The admin languages filter submenu items.
379 */
380 $items = apply_filters( 'pll_admin_languages_filter', array_merge( array( $all_item ), $this->model->get_languages_list() ) );
381
382 if ( ! empty( $items ) ) {
383 $wp_admin_bar->add_menu(
384 array(
385 'id' => 'languages',
386 'title' => $selected->flag . $title,
387 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ),
388 'meta' => array( 'title' => __( 'Filters content by language', 'polylang' ) ),
389 )
390 );
391 }
392
393 foreach ( $items as $lang ) {
394 if ( $selected->slug === $lang->slug ) {
395 continue;
396 }
397
398 $wp_admin_bar->add_menu(
399 array(
400 'parent' => 'languages',
401 'id' => $lang->slug,
402 'title' => $lang->flag . esc_html( $lang->name ),
403 'href' => esc_url( add_query_arg( 'lang', $lang->slug, remove_query_arg( 'paged' ) ) ),
404 'meta' => 'all' === $lang->slug ? array() : array( 'lang' => esc_attr( $lang->get_locale( 'display' ) ) ),
405 )
406 );
407 }
408 }
409 }
410