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

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