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

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