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

447 lines 16.4 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 // For each script:
139 // 0 => the pages on which to load the script
140 // 1 => the scripts it needs to work
141 // 2 => 1 if loaded even if languages have not been defined yet, 0 otherwise
142 // 3 => 1 if loaded in footer
143 // FIXME: check if I can load more scripts in footer
144 $scripts = array(
145 'post' => array( array( 'edit', 'upload' ), array( 'jquery', 'wp-ajax-response' ), 0, 1 ),
146 'term' => array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), 0, 1 ),
147 'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), 0, 0 ),
148 'widgets' => array( array( 'widgets' ), array( 'jquery' ), 0, 0 ),
149 );
150
151 if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
152 // Classic editor.
153 if ( ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) {
154 $scripts['classic-editor'] = array( array( 'post', 'media', 'async-upload' ), array( 'jquery', 'wp-ajax-response', 'post' ), 0, 1 );
155 }
156
157 // Block editor with legacy metabox in WP 5.0+.
158 if ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin() ) {
159 $scripts['block-editor'] = array( array( 'post' ), array( 'jquery', 'wp-ajax-response', 'wp-api-fetch' ), 0, 1 );
160 }
161 }
162
163 foreach ( $scripts as $script => $v ) {
164 if ( in_array( $screen->base, $v[0] ) && ( $v[2] || $this->model->get_languages_list() ) ) {
165 wp_enqueue_script( 'pll_' . $script, plugins_url( '/js/' . $script . $suffix . '.js', POLYLANG_FILE ), $v[1], POLYLANG_VERSION, $v[3] );
166 }
167 }
168
169 wp_enqueue_style( 'polylang_admin', plugins_url( '/css/admin' . $suffix . '.css', POLYLANG_FILE ), array(), POLYLANG_VERSION );
170
171 $this->localize_scripts();
172 }
173
174 /**
175 * Enqueue scripts to the WP Customizer.
176 *
177 * @since 2.4.0
178 */
179 public function customize_controls_enqueue_scripts() {
180 if ( $this->model->get_languages_list() ) {
181 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
182 wp_enqueue_script( 'pll_widgets', plugins_url( '/js/widgets' . $suffix . '.js', POLYLANG_FILE ), array( 'jquery' ), POLYLANG_VERSION, true );
183 $this->localize_scripts();
184 }
185 }
186
187 /**
188 * Localize scripts.
189 *
190 * @since 2.4.0
191 */
192 public function localize_scripts() {
193 if ( wp_script_is( 'pll_classic-editor', 'enqueued' ) ) {
194 wp_localize_script(
195 'pll_classic-editor',
196 'confirm_text',
197 __( 'You are about to overwrite an existing translation. Are you sure you want to proceed?', 'polylang' )
198 );
199 }
200
201 if ( wp_script_is( 'pll_block-editor', 'enqueued' ) ) {
202 wp_localize_script(
203 'pll_block-editor',
204 'confirm_text',
205 __( 'You are about to overwrite an existing translation. Are you sure you want to proceed?', 'polylang' )
206 );
207 }
208
209 if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
210 wp_localize_script(
211 'pll_widgets',
212 'pll_widgets',
213 array(
214 'flags' => wp_list_pluck( $this->model->get_languages_list(), 'flag', 'slug' ),
215 )
216 );
217 }
218 }
219
220 /**
221 * Sets pll_ajax_backend on all backend ajax request
222 * The final goal is to detect if an ajax request is made on admin or frontend
223 *
224 * Takes care to various situations:
225 * when the ajax request has no options.data thanks to ScreenfeedFr
226 * see: https://wordpress.org/support/topic/ajaxprefilter-may-not-work-as-expected
227 * when options.data is a json string
228 * see: https://wordpress.org/support/topic/polylang-breaking-third-party-ajax-requests-on-admin-panels
229 * when options.data is an empty string (GET request with the method 'load')
230 * see: https://wordpress.org/support/topic/invalid-url-during-wordpress-new-dashboard-widget-operation
231 *
232 * @since 1.4
233 */
234 public function admin_print_footer_scripts() {
235 global $post_ID, $tag_ID;
236
237 $params = array( 'pll_ajax_backend' => 1 );
238 if ( ! empty( $post_ID ) ) {
239 $params = array_merge( $params, array( 'pll_post_id' => (int) $post_ID ) );
240 }
241
242 if ( ! empty( $tag_ID ) ) {
243 $params = array_merge( $params, array( 'pll_term_id' => (int) $tag_ID ) );
244 }
245
246 $str = http_build_query( $params );
247 $arr = wp_json_encode( $params );
248 ?>
249 <script type="text/javascript">
250 if (typeof jQuery != 'undefined') {
251 (function($){
252 $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
253 if ( -1 != options.url.indexOf( ajaxurl ) || -1 != ajaxurl.indexOf( options.url ) ) {
254 if ( 'undefined' === typeof options.data ) {
255 options.data = ( 'get' === options.type.toLowerCase() ) ? '<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>' : <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?>;
256 } else {
257 if ( 'string' === typeof options.data ) {
258 if ( '' === options.data && 'get' === options.type.toLowerCase() ) {
259 options.url = options.url+'&<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>';
260 } else {
261 try {
262 var o = $.parseJSON(options.data);
263 o = $.extend(o, <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?>);
264 options.data = JSON.stringify(o);
265 }
266 catch(e) {
267 options.data = '<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>&'+options.data;
268 }
269 }
270 } else {
271 options.data = $.extend(options.data, <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?>);
272 }
273 }
274 }
275 });
276 })(jQuery)
277 }
278 </script>
279 <?php
280 }
281
282 /**
283 * Sets the admin current language, used to filter the content
284 *
285 * @since 2.0
286 */
287 public function set_current_language() {
288 $this->curlang = $this->filter_lang;
289
290 // Edit Post
291 if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
292 $this->curlang = $lang;
293 } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
294 $this->curlang = $lang;
295 } 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
296 $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
297 }
298
299 // Edit Term
300 // FIXME 'edit-tags.php' for backward compatibility with WP < 4.5
301 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
302 $this->curlang = $lang;
303 } elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
304 $this->curlang = $lang;
305 } elseif ( 'edit-tags.php' === $GLOBALS['pagenow'] && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
306 if ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
307 $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
308 } elseif ( empty( $this->curlang ) ) {
309 $this->curlang = $this->pref_lang;
310 }
311 }
312
313 // Ajax
314 if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
315 $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
316 }
317 }
318
319 /**
320 * Defines the backend language and the admin language filter based on user preferences
321 *
322 * @since 1.2.3
323 */
324 public function init_user() {
325 // Language for admin language filter: may be empty
326 // $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter
327 // We intentionally don't use a nonce to update the language filter
328 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
329 update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( sanitize_key( $_GET['lang'] ) ) ) ? $lang->slug : '' ); // phpcs:ignore WordPress.Security.NonceVerification
330 }
331
332 $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
333
334 // Set preferred language for use when saving posts and terms: must not be empty
335 $this->pref_lang = empty( $this->filter_lang ) ? $this->model->get_language( $this->options['default_lang'] ) : $this->filter_lang;
336
337 /**
338 * Filter the preferred language on admin side
339 * The preferred language is used for example to determine the language of a new post
340 *
341 * @since 1.2.3
342 *
343 * @param object $pref_lang preferred language
344 */
345 $this->pref_lang = apply_filters( 'pll_admin_preferred_language', $this->pref_lang );
346
347 $this->set_current_language();
348
349 // Inform that the admin language has been set
350 // Only if the admin language is one of the Polylang defined language
351 if ( $curlang = $this->model->get_language( get_user_locale() ) ) {
352 /** This action is documented in frontend/choose-lang.php */
353 do_action( 'pll_language_defined', $curlang->slug, $curlang );
354 } else {
355 /** This action is documented in include/class-polylang.php */
356 do_action( 'pll_no_language_defined' ); // to load overridden textdomains
357 }
358 }
359
360 /**
361 * Avoids parsing a tax query when all languages are requested
362 * Fixes https://wordpress.org/support/topic/notice-undefined-offset-0-in-wp-includesqueryphp-on-line-3877 introduced in WP 4.1
363 *
364 * @see the suggestion of @boonebgorges, https://core.trac.wordpress.org/ticket/31246
365 *
366 * @since 1.6.5
367 *
368 * @param array $qvars
369 * @return array
370 */
371 public function request( $qvars ) {
372 if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
373 unset( $qvars['lang'] );
374 }
375
376 return $qvars;
377 }
378
379 /**
380 * Adds the languages list in admin bar for the admin languages filter
381 *
382 * @since 0.9
383 *
384 * @param object $wp_admin_bar
385 */
386 public function admin_bar_menu( $wp_admin_bar ) {
387 $all_item = (object) array(
388 'slug' => 'all',
389 'name' => __( 'Show all languages', 'polylang' ),
390 'flag' => '<span class="ab-icon"></span>',
391 );
392
393 $selected = empty( $this->filter_lang ) ? $all_item : $this->filter_lang;
394
395 $title = sprintf(
396 '<span class="ab-label"%1$s><span class="screen-reader-text">%2$s</span>%3$s</span>',
397 'all' === $selected->slug ? '' : sprintf( ' lang="%s"', esc_attr( $selected->get_locale( 'display' ) ) ),
398 __( 'Filters content by language', 'polylang' ),
399 esc_html( $selected->name )
400 );
401
402 /**
403 * Filters the admin languages filter submenu items
404 *
405 * @since 2.6
406 *
407 * @param array $items The admin languages filter submenu items.
408 */
409 $items = apply_filters( 'pll_admin_languages_filter', array_merge( array( $all_item ), $this->model->get_languages_list() ) );
410
411 if ( ! empty( $items ) ) {
412 $wp_admin_bar->add_menu(
413 array(
414 'id' => 'languages',
415 'title' => $selected->flag . $title,
416 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ),
417 'meta' => array( 'title' => __( 'Filters content by language', 'polylang' ) ),
418 )
419 );
420 }
421
422 foreach ( $items as $lang ) {
423 if ( $selected->slug === $lang->slug ) {
424 continue;
425 }
426
427 $wp_admin_bar->add_menu(
428 array(
429 'parent' => 'languages',
430 'id' => $lang->slug,
431 'title' => $lang->flag . esc_html( $lang->name ),
432 'href' => esc_url( add_query_arg( 'lang', $lang->slug, remove_query_arg( 'paged' ) ) ),
433 'meta' => 'all' === $lang->slug ? array() : array( 'lang' => esc_attr( $lang->get_locale( 'display' ) ) ),
434 )
435 );
436 }
437 }
438 /**
439 * Instantiate PLL_Wizard_Pro class.
440 *
441 * @since 2.7
442 */
443 public function instantiate_wizard_pro() {
444 $this->wizard_pro = new PLL_Wizard_Pro( $this );
445 }
446 }
447