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

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