PluginProbe
Polylang / 3.8
Polylang v3.8
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 / src / admin / admin-base.php

admin-base.php in Polylang 3.8, at src/admin/admin-base.php

708 lines 22.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 use WP_Syntex\Polylang\Capabilities\Capabilities;
7
8 /**
9 * Setup features available on all admin pages.
10 *
11 * @since 1.8
12 */
13 abstract class PLL_Admin_Base extends PLL_Base {
14 /**
15 * @since 3.8
16 */
17 public const SCREEN_PREFIX = 'languages';
18
19 /**
20 * Current language (used to filter the content).
21 *
22 * @var PLL_Language|null
23 */
24 public $curlang;
25
26 /**
27 * Language selected in the admin language filter.
28 *
29 * @var PLL_Language|null
30 */
31 public $filter_lang;
32
33 /**
34 * Preferred language to assign to new contents.
35 *
36 * @var PLL_Language|null
37 */
38 public $pref_lang;
39
40 /**
41 * @var PLL_Filters_Links|null
42 */
43 public $filters_links;
44
45 /**
46 * @var PLL_Admin_Links|null
47 */
48 public $links;
49
50 /**
51 * @var PLL_Admin_Notices|null
52 */
53 public $notices;
54
55 /**
56 * @var PLL_Admin_Static_Pages|null
57 */
58 public $static_pages;
59
60 /**
61 * @var PLL_Admin_Default_Term|null
62 */
63 public $default_term;
64
65 /**
66 * Setups actions needed on all admin pages.
67 *
68 * @since 1.8
69 *
70 * @param PLL_Links_Model $links_model Reference to the links model.
71 */
72 public function __construct( &$links_model ) {
73 parent::__construct( $links_model );
74
75 // Adds the link to the languages panel in the WordPress admin menu
76 add_action( 'admin_menu', array( $this, 'add_menus' ) );
77
78 add_action( 'admin_menu', array( $this, 'remove_customize_submenu' ) );
79
80 // Setup js scripts and css styles
81 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 0 ); // High priority in case an ajax request is sent by an immediately invoked function
82
83 add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) );
84
85 // Early instantiated to be able to correctly initialize language properties.
86 $this->static_pages = new PLL_Admin_Static_Pages( $this );
87 $this->model->set_languages_ready();
88 }
89
90 /**
91 * Setups filters and action needed on all admin pages and on plugins page
92 * Loads the settings pages or the filters base on the request
93 *
94 * @since 1.2
95 */
96 public function init() {
97 parent::init();
98
99 $this->notices = new PLL_Admin_Notices( $this );
100
101 $this->default_term = new PLL_Admin_Default_Term( $this );
102 $this->default_term->add_hooks();
103
104 if ( ! $this->model->has_languages() ) {
105 return;
106 }
107
108 $this->links = new PLL_Admin_Links( $this ); // FIXME needed here ?
109 $this->filters_links = new PLL_Filters_Links( $this ); // FIXME needed here ?
110
111 // Filter admin language for users
112 // We must not call user info before WordPress defines user roles in wp-settings.php
113 add_action( 'setup_theme', array( $this, 'init_user' ) );
114 add_filter( 'request', array( $this, 'request' ) );
115
116 // Adds the languages in admin bar
117 add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); // 100 determines the position
118 }
119
120 /**
121 * Adds links to Polylang's admin panels to the WordPress admin menu.
122 *
123 * @since 0.1
124 *
125 * @return void
126 */
127 public function add_menus(): void {
128 global $admin_page_hooks;
129
130 $parent = '';
131 $first_tab = '';
132
133 foreach ( $this->get_menu_items() as $tab => $title ) {
134 $page = self::get_screen_slug( $tab );
135 $capa = $this->get_menu_capability( $tab );
136
137 if ( empty( $parent ) ) {
138 $parent = $page;
139 $first_tab = $tab;
140
141 /*
142 * WP actually doesn't care about the user capability used here, as long as it has sub-menus: it will
143 * use the ones from the sub-menus. See `_wp_menu_output()`.
144 * Ex: a user with `manage_translations` will still be able to access the Translations page, even if the
145 * main menu has `manage_options`.
146 */
147 add_menu_page( $title, __( 'Languages', 'polylang' ), $capa, $parent, '__return_null', 'dashicons-translation' );
148 $admin_page_hooks[ $parent ] = self::SCREEN_PREFIX; // Hack to avoid the localization of the hook name. See: https://core.trac.wordpress.org/ticket/18857
149 }
150
151 add_submenu_page( $parent, $title, $title, $capa, $page, array( $this, 'languages_page' ) );
152 }
153
154 /*
155 * Get rid of the `toplevel` prefix in hook names.
156 *
157 * In the WP admin, if an admin screen is the first of its menu (like the PLL's "Languages" screen), the hooks
158 * fired in the screen get a `toplevel` prefix (ex: `toplevel_page_mlang`) while all the other screens get a
159 * slug based on the parent screen title (ex: `languages_page_mlang_strings`, where `languages` is the parent
160 * screen's slug). This will not prevent the `toplevel` hooks to fire, but it will fire the `languages` hooks in
161 * addition: this way, screens can be removed or moved around without the need of hooking both prefixes: using
162 * the hooks with the `languages` prefix will work in both cases.
163 *
164 * @see get_plugin_page_hookname()
165 */
166 foreach ( array( 'load-', 'admin_print_styles-', 'admin_print_scripts-', 'admin_head-', '', 'admin_print_footer_scripts-', 'admin_footer-' ) as $prefix ) {
167 add_action(
168 "{$prefix}toplevel_page_{$parent}",
169 static function () use ( $prefix, $first_tab ) {
170 do_action( $prefix . self::get_screen_id( $first_tab ) );
171 }
172 );
173 }
174
175 /*
176 * Ensure a common CSS class to the `<body>` tag.
177 *
178 * Due to the `toplevel` "issue" described earlier, the CSS class `toplevel_page_mlang` (for example) is added
179 * to the body. This adds a class with the `languages` prefix. This ensures we have a common CSS class, even if
180 * the screen is moved to the 1st position in the menu.
181 */
182 add_action(
183 // Target the screen in 1st position only.
184 "admin_head-toplevel_page_{$parent}",
185 static function () use ( $first_tab ) {
186 add_filter(
187 'admin_body_class',
188 static function ( $admin_body_classes ) use ( $first_tab ) {
189 return $admin_body_classes . ' ' . self::get_screen_id( $first_tab );
190 }
191 );
192 }
193 );
194
195 /**
196 * Also modify the screen ID and base.
197 *
198 * Note: the global variables `$page_hook` and `$hook_suffix` are not changed, their value is still
199 * `toplevel_page_mlang`. Changing them breaks things because we can't filter `get_plugin_page_hookname()`.
200 * This is why the above hooks are still needed.
201 */
202 add_action(
203 'current_screen',
204 static function ( $current_screen ) use ( $parent, $first_tab ) {
205 if ( "toplevel_page_{$parent}" !== $current_screen->id ) {
206 return;
207 }
208
209 $current_screen->id = self::get_screen_id( $first_tab );
210 $current_screen->base = self::get_screen_id( $first_tab );
211 }
212 );
213 }
214
215 /**
216 * Dummy method to display the 3 tabs pages: languages, strings translations, settings.
217 * Overwritten in `PLL_Settings`.
218 *
219 * @since 3.7
220 *
221 * @return void
222 */
223 public function languages_page() {}
224
225 /**
226 * Setup js scripts & css styles ( only on the relevant pages )
227 *
228 * @since 0.6
229 *
230 * @return void
231 */
232 public function admin_enqueue_scripts() {
233 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
234
235 wp_enqueue_script( 'pll_admin', plugins_url( "/js/build/admin{$suffix}.js", POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true );
236 $inline_script = sprintf( 'let pll_admin = %s;', wp_json_encode( array( 'ajax_filter' => $this->get_ajax_filter_data() ) ) );
237 wp_add_inline_script( 'pll_admin', $inline_script, 'before' );
238
239 $screen = get_current_screen();
240 if ( empty( $screen ) ) {
241 return;
242 }
243
244 /*
245 * For each script:
246 * 0 => the pages on which to load the script
247 * 1 => the scripts it needs to work
248 * 2 => true if loaded even if languages have not been defined yet, false otherwise
249 * 3 => true if loaded in footer
250 */
251 $scripts = array(
252 'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), false, false ),
253 'widgets' => array( array( 'widgets' ), array( 'jquery' ), false, false ),
254 );
255
256 $block_screens = array( 'widgets', 'site-editor' );
257
258 if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
259 $scripts['post'] = array( array( 'edit' ), array( 'jquery', 'wp-ajax-response' ), false, true );
260
261 // Classic editor.
262 if ( ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) {
263 $scripts['classic-editor'] = array( array( 'post', 'media', 'async-upload' ), array( 'jquery', 'wp-ajax-response', 'post', 'jquery-ui-dialog', 'wp-i18n' ), false, true );
264 }
265
266 // Block editor with legacy metabox in WP 5.0+.
267 $block_screens[] = 'post';
268 }
269
270 if ( $this->options['media_support'] ) {
271 $scripts['media'] = array( array( 'upload' ), array( 'jquery' ), false, true );
272 }
273
274 if ( $this->is_block_editor( $screen ) ) {
275 $scripts['block-editor'] = array( $block_screens, array( 'jquery', 'wp-ajax-response', 'wp-api-fetch', 'jquery-ui-dialog', 'wp-i18n' ), false, true );
276 }
277
278 if ( ! empty( $screen->taxonomy ) && $this->model->is_translated_taxonomy( $screen->taxonomy ) ) {
279 $scripts['term'] = array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), false, true );
280 }
281
282 foreach ( $scripts as $script => $v ) {
283 if ( in_array( $screen->base, $v[0] ) && ( $v[2] || $this->model->has_languages() ) ) {
284 wp_enqueue_script( "pll_{$script}", plugins_url( "/js/build/{$script}{$suffix}.js", POLYLANG_ROOT_FILE ), $v[1], POLYLANG_VERSION, $v[3] );
285 if ( 'classic-editor' === $script || 'block-editor' === $script ) {
286 wp_set_script_translations( "pll_{$script}", 'polylang' );
287 }
288 }
289 }
290
291 wp_register_style( 'polylang_admin', plugins_url( "/css/build/admin{$suffix}.css", POLYLANG_ROOT_FILE ), array( 'wp-jquery-ui-dialog' ), POLYLANG_VERSION );
292 wp_enqueue_style( 'polylang_dialog', plugins_url( "/css/build/dialog{$suffix}.css", POLYLANG_ROOT_FILE ), array( 'polylang_admin' ), POLYLANG_VERSION );
293
294 $this->add_inline_scripts();
295 }
296
297 /**
298 * Tells whether or not the given screen is block editor kind.
299 * e.g. widget, site or post editor.
300 *
301 * @since 3.3
302 *
303 * @param WP_Screen $screen Screen object.
304 * @return bool True if the screen is a block editor, false otherwise.
305 */
306 protected function is_block_editor( $screen ) {
307 return method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin();
308 }
309
310 /**
311 * Enqueue scripts to the WP Customizer.
312 *
313 * @since 2.4.0
314 *
315 * @return void
316 */
317 public function customize_controls_enqueue_scripts() {
318 if ( $this->model->has_languages() ) {
319 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
320 wp_enqueue_script( 'pll_widgets', plugins_url( '/js/build/widgets' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true );
321 $this->add_inline_scripts();
322 }
323 }
324
325 /**
326 * Adds inline scripts to set the default language in JS
327 * and localizes scripts.
328 *
329 * @since 3.3
330 *
331 * @return void
332 */
333 private function add_inline_scripts() {
334 if ( wp_script_is( 'pll_block-editor', 'enqueued' ) ) {
335 $default_lang_script = 'const pllDefaultLanguage = "' . $this->options['default_lang'] . '";';
336 wp_add_inline_script(
337 'pll_block-editor',
338 $default_lang_script,
339 'before'
340 );
341 }
342 if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
343 wp_localize_script(
344 'pll_widgets',
345 'pll_widgets',
346 array(
347 'flags' => wp_list_pluck( $this->model->get_languages_list(), 'flag', 'slug' ),
348 )
349 );
350 }
351 }
352
353 /**
354 * Returns the data to use with the AJAX filter.
355 * The final goal is to detect if an ajax request is made on admin or frontend.
356 *
357 * Takes care to various situations:
358 * - When the AJAX request has no `options.data` thanks to ScreenfeedFr.
359 * See: https://wordpress.org/support/topic/ajaxprefilter-may-not-work-as-expected.
360 * - When `options.data` is a JSON string.
361 * See: https://wordpress.org/support/topic/polylang-breaking-third-party-ajax-requests-on-admin-panels.
362 * - When `options.data` is an empty string (GET request with the method 'load').
363 * See: https://wordpress.org/support/topic/invalid-url-during-wordpress-new-dashboard-widget-operation.
364 *
365 * @since 3.7
366 *
367 * @return array
368 */
369 public function get_ajax_filter_data(): array {
370 global $post, $tag;
371
372 $params = array( 'pll_ajax_backend' => 1 );
373 if ( $post instanceof WP_Post && $this->model->post_types->is_translated( $post->post_type ) ) {
374 $params['pll_post_id'] = $post->ID;
375 }
376
377 if ( $tag instanceof WP_Term && $this->model->taxonomies->is_translated( $tag->taxonomy ) ) {
378 $params['pll_term_id'] = $tag->term_id;
379 }
380
381 /**
382 * Filters the list of parameters to add to the admin ajax request.
383 *
384 * @since 3.4.5
385 *
386 * @param array $params List of parameters to add to the admin ajax request.
387 */
388 return (array) apply_filters( 'pll_admin_ajax_params', $params );
389 }
390
391 /**
392 * Sets the admin current language, used to filter the content
393 *
394 * @since 2.0
395 *
396 * @return void
397 */
398 public function set_current_language() {
399 $this->curlang = $this->filter_lang;
400
401 // Edit Post
402 if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
403 $this->curlang = $lang;
404 } 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
405 $this->curlang = $lang;
406 } 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
407 $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
408 }
409
410 // Edit Term
411 elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
412 $this->curlang = $lang;
413 } 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
414 if ( isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
415 $this->curlang = $lang;
416 } elseif ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
417 $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
418 } elseif ( empty( $this->curlang ) ) {
419 $this->curlang = $this->pref_lang;
420 }
421 }
422
423 // Ajax
424 if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
425 $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
426 }
427
428 /**
429 * Filters the current language used by Polylang in the admin context.
430 *
431 * @since 3.2
432 *
433 * @param PLL_Language|false|null $curlang Instance of the current language.
434 * @param PLL_Admin_Base $polylang Instance of the main Polylang's object.
435 */
436 $this->curlang = apply_filters( 'pll_admin_current_language', $this->curlang, $this );
437
438 // Inform that the admin language has been set.
439 if ( $this->curlang instanceof PLL_Language ) {
440 /** This action is documented in src/frontend/choose-lang.php */
441 do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
442 } else {
443 /** This action is documented in src/class-polylang.php */
444 do_action( 'pll_no_language_defined' ); // To load overridden textdomains.
445 }
446 }
447
448 /**
449 * Defines the backend language and the admin language filter based on user preferences.
450 *
451 * @since 1.2.3
452 *
453 * @return void
454 */
455 public function init_user() {
456 /*
457 * $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter.
458 * We intentionally don't use a nonce to update the language filter.
459 */
460 if ( ! wp_doing_ajax() && ! empty( $_GET['lang'] ) && ! is_numeric( sanitize_key( $_GET['lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
461 $user_id = get_current_user_id();
462 if ( current_user_can( 'edit_user', $user_id ) ) {
463 $lang = $this->model->get_language( sanitize_key( $_GET['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
464 update_user_meta( $user_id, 'pll_filter_content', $lang ? $lang->slug : '' );
465 }
466 }
467
468 $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
469
470 // Set preferred language for use when saving posts and terms: must not be empty.
471 $this->pref_lang = empty( $this->filter_lang ) ? $this->model->get_default_language() : $this->filter_lang;
472
473 /**
474 * Filters the preferred language on admin side.
475 * The preferred language is used for example to determine the language of a new post.
476 *
477 * @since 1.2.3
478 *
479 * @param PLL_Language $pref_lang Preferred language.
480 */
481 $this->pref_lang = apply_filters( 'pll_admin_preferred_language', $this->pref_lang );
482
483 $this->set_current_language();
484 }
485
486 /**
487 * Avoids parsing a tax query when all languages are requested
488 * Fixes https://wordpress.org/support/topic/notice-undefined-offset-0-in-wp-includesqueryphp-on-line-3877 introduced in WP 4.1
489 *
490 * @see https://core.trac.wordpress.org/ticket/31246 the suggestion of @boonebgorges.
491 *
492 * @since 1.6.5
493 *
494 * @param array $qvars The array of requested query variables.
495 * @return array
496 */
497 public function request( $qvars ) {
498 if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) {
499 unset( $qvars['lang'] );
500 }
501
502 return $qvars;
503 }
504
505 /**
506 * Adds the languages list in admin bar for the admin languages filter.
507 *
508 * @since 0.9
509 *
510 * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar global object.
511 * @return void
512 */
513 public function admin_bar_menu( $wp_admin_bar ) {
514 $all_item = (object) array(
515 'slug' => 'all',
516 'name' => __( 'Show all languages', 'polylang' ),
517 'flag' => '<span class="ab-icon"></span>',
518 );
519
520 $selected = empty( $this->filter_lang ) ? $all_item : $this->filter_lang;
521
522 $title = sprintf(
523 '<span class="ab-label"%1$s><span class="screen-reader-text">%2$s</span>%3$s</span>',
524 $selected instanceof PLL_Language ? sprintf( ' lang="%s"', esc_attr( $selected->get_locale( 'display' ) ) ) : '',
525 __( 'Filters content by language', 'polylang' ),
526 esc_html( $selected->name )
527 );
528
529 $all_items = array_merge( array( $all_item ), $this->model->get_languages_list() );
530 $items = $all_items;
531
532 if ( $this->should_hide_admin_bar_menu() ) {
533 $items = array();
534 }
535
536 /**
537 * Filters the admin bar language filter submenu items.
538 *
539 * @since 2.6
540 * @since 3.8 Added `$all_items` parameter.
541 *
542 * @param array $items The items of the admin languages filter to display (may be empty if menu hidden).
543 * @param array $all_items Complete unfiltered list of all available language items.
544 */
545 $items = apply_filters( 'pll_admin_languages_filter', $items, $all_items );
546
547 if ( empty( $items ) ) {
548 return;
549 }
550
551 $wp_admin_bar->add_menu(
552 array(
553 'id' => 'languages',
554 'title' => $selected->flag . $title,
555 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ),
556 'meta' => array(
557 'title' => __( 'Filters content by language', 'polylang' ),
558 'class' => 'all' === $selected->slug ? '' : 'pll-filtered-languages',
559 ),
560 )
561 );
562
563 foreach ( $items as $lang ) {
564 if ( $selected->slug === $lang->slug ) {
565 continue;
566 }
567
568 $wp_admin_bar->add_menu(
569 array(
570 'parent' => 'languages',
571 'id' => $lang->slug,
572 'title' => $lang->flag . esc_html( $lang->name ),
573 'href' => esc_url( add_query_arg( 'lang', $lang->slug, remove_query_arg( 'paged' ) ) ),
574 'meta' => 'all' === $lang->slug ? array() : array( 'lang' => esc_attr( $lang->get_locale( 'display' ) ) ),
575 )
576 );
577 }
578 }
579
580 /**
581 * Remove the customize submenu when using a block theme.
582 *
583 * WordPress removes the Customizer menu if a block theme is activated and no other plugins interact with it.
584 * As Polylang interacts with the Customizer, we have to delete this menu ourselves in the case of a block theme,
585 * unless another plugin than Polylang interacts with the Customizer.
586 *
587 * @since 3.2
588 *
589 * @return void
590 */
591 public function remove_customize_submenu() {
592 if ( ! $this->should_customize_menu_be_removed() ) {
593 return;
594 }
595
596 global $submenu;
597
598 if ( ! empty( $submenu['themes.php'] ) ) {
599 foreach ( $submenu['themes.php'] as $submenu_item ) {
600 if ( 'customize' === $submenu_item[1] ) {
601 remove_submenu_page( 'themes.php', $submenu_item[2] );
602 }
603 }
604 }
605 }
606
607 /**
608 * Tells if the Polylang's admin bar menu should be hidden for the current page.
609 * Conventionally, it should be hidden on edition pages.
610 *
611 * @since 3.8
612 *
613 * @return bool
614 */
615 public function should_hide_admin_bar_menu(): bool {
616 global $pagenow, $typenow, $taxnow;
617
618 if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) {
619 return ! empty( $typenow );
620 }
621
622 if ( 'term.php' === $pagenow ) {
623 return ! empty( $taxnow );
624 }
625
626 return false;
627 }
628
629 /**
630 * Returns the ID of a Polylang's settings screen.
631 *
632 * @since 3.8
633 *
634 * @param string $tab The name of the screen (`lang`, `strings`, `settings`).
635 * @return string
636 *
637 * @phpstan-return non-empty-string
638 */
639 public static function get_screen_id( string $tab ): string {
640 return sprintf( '%s_page_%s', self::SCREEN_PREFIX, self::get_screen_slug( $tab ) );
641 }
642
643 /**
644 * Returns the slug of a Polylang's settings screen, as seen in the URL.
645 *
646 * @since 3.8
647 *
648 * @param string $tab The name of the screen (`lang`, `strings`, `settings`).
649 * @return string
650 *
651 * @phpstan-return non-empty-string
652 */
653 public static function get_screen_slug( string $tab ): string {
654 return 'lang' === $tab ? 'mlang' : "mlang_$tab";
655 }
656
657 /**
658 * Returns the list of sub-menu items.
659 *
660 * @since 3.8
661 *
662 * @return string[] List of sub-menu items with page slugs as array keys, and sub-menu titles as array values.
663 *
664 * @phpstan-return array<non-empty-string, string>
665 */
666 protected function get_menu_items(): array {
667 $tabs = array(
668 'lang' => __( 'Languages', 'polylang' ),
669 );
670
671 // Only if at least one language has been created.
672 if ( ! empty( $this->model->languages->filter( 'translator' )->get_list() ) ) {
673 $tabs['strings'] = __( 'Translations', 'polylang' );
674 }
675
676 $tabs['settings'] = __( 'Settings', 'polylang' );
677
678 /**
679 * Filter the list of sub-menu items in Polylang settings.
680 *
681 * @since 1.5.1
682 *
683 * @param string[] $tabs List of sub-menu items with page slugs as array keys and titles as array values.
684 */
685 return (array) apply_filters( 'pll_settings_tabs', $tabs );
686 }
687
688 /**
689 * Returns the user capability required to access the given menu page.
690 *
691 * @since 3.8
692 *
693 * @param string $menu Menu slug.
694 * @return string
695 */
696 protected function get_menu_capability( string $menu ): string {
697 switch ( $menu ) {
698 case 'lang':
699 return Capabilities::LANGUAGES;
700
701 case 'strings':
702 return Capabilities::TRANSLATIONS;
703 }
704
705 return 'manage_options';
706 }
707 }
708