summaryrefslogtreecommitdiff
blob: 140b40f6e5ea2acc427806d48be1d7bb1e049e3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<?php
/**
 * Admin Menu Registration
 *
 * @package automattic/jetpack-admin-ui
 */

namespace Automattic\Jetpack\Admin_UI;

/**
 * This class offers a wrapper to add_submenu_page and makes sure stand-alone plugin's menu items are always added under the Jetpack top level menu.
 * If the Jetpack top level was not previously registered by other plugin, it will be registered here.
 */
class Admin_Menu {

	const PACKAGE_VERSION = '0.2.1';

	/**
	 * Whether this class has been initialized
	 *
	 * @var boolean
	 */
	private static $initialized = false;

	/**
	 * List of menu items enqueued to be added
	 *
	 * @var array
	 */
	private static $menu_items = array();

	/**
	 * Initialize the class and set up the main hook
	 *
	 * @return void
	 */
	public static function init() {
		if ( ! self::$initialized ) {
			self::$initialized = true;
			add_action( 'admin_menu', array( __CLASS__, 'admin_menu_hook_callback' ), 1000 ); // Jetpack uses 998.
		}
	}

	/**
	 * Enqueue styles for the top level menu
	 *
	 * @return void
	 */
	public static function enqueue_style() {
		wp_enqueue_style(
			'jetpack-admin-ui',
			plugin_dir_url( __FILE__ ) . 'css/jetpack-icon.css',
			array(),
			self::PACKAGE_VERSION
		);
	}

	/**
	 * Callback to the admin_menu hook that will register the enqueued menu items
	 *
	 * @return void
	 */
	public static function admin_menu_hook_callback() {
		$can_see_toplevel_menu  = true;
		$jetpack_plugin_present = class_exists( 'Jetpack_React_Page' );

		if ( ! $jetpack_plugin_present ) {
			add_action( 'admin_print_scripts', array( __CLASS__, 'enqueue_style' ) );
			add_menu_page(
				'Jetpack',
				'Jetpack',
				'read',
				'jetpack',
				'__return_null',
				'div',
				3
			);

			// If Jetpack plugin is not present, user will only be able to see this menu if they have enough capability to at least one of the sub menus being added.
			$can_see_toplevel_menu = false;
		}

		foreach ( self::$menu_items as $menu_item ) {
			if ( ! current_user_can( $menu_item['capability'] ) ) {
				continue;
			}

			$can_see_toplevel_menu = true;

			add_submenu_page(
				'jetpack',
				$menu_item['page_title'],
				$menu_item['menu_title'],
				$menu_item['capability'],
				$menu_item['menu_slug'],
				$menu_item['function'],
				$menu_item['position']
			);
		}

		if ( ! $jetpack_plugin_present ) {
			remove_submenu_page( 'jetpack', 'jetpack' );
		}

		if ( ! $can_see_toplevel_menu ) {
			remove_menu_page( 'jetpack' );
		}
	}

	/**
	 * Adds a new submenu to the Jetpack Top level menu
	 *
	 * The parameters this method accepts are the same as @see add_submenu_page. This class will
	 * aggreagate all menu items registered by stand-alone plugins and make sure they all go under the same
	 * Jetpack top level menu. It will also handle the top level menu registration in case the Jetpack plugin is not present.
	 *
	 * @param string   $page_title  The text to be displayed in the title tags of the page when the menu
	 *                              is selected.
	 * @param string   $menu_title  The text to be used for the menu.
	 * @param string   $capability  The capability required for this menu to be displayed to the user.
	 * @param string   $menu_slug   The slug name to refer to this menu by. Should be unique for this menu
	 *                              and only include lowercase alphanumeric, dashes, and underscores characters
	 *                              to be compatible with sanitize_key().
	 * @param callable $function    The function to be called to output the content for this page.
	 * @param int      $position    The position in the menu order this item should appear.
	 *
	 * @return string The resulting page's hook_suffix
	 */
	public static function add_menu( $page_title, $menu_title, $capability, $menu_slug, $function, $position = null ) {
		self::init();
		self::$menu_items[] = compact( 'page_title', 'menu_title', 'capability', 'menu_slug', 'function', 'position' );

		/**
		 * Let's return the page hook so consumers can use.
		 * We know all pages will be under Jetpack top level menu page, so we can hardcode the first part of the string.
		 * Using get_plugin_page_hookname here won't work because the top level page is not registered yet.
		 */
		return 'jetpack_page_' . $menu_slug;
	}

	/**
	 * Gets the slug for the first item under the Jetpack top level menu
	 *
	 * @return string|null
	 */
	public static function get_top_level_menu_item_slug() {
		global $submenu;
		if ( ! empty( $submenu['jetpack'] ) ) {
			$item = reset( $submenu['jetpack'] );
			if ( isset( $item[2] ) ) {
				return $item[2];
			}
		}
	}

	/**
	 * Gets the URL for the first item under the Jetpack top level menu
	 *
	 * @param string $fallback If Jetpack menu is not there or no children is found, return this fallback instead. Default to admin_url().
	 * @return string
	 */
	public static function get_top_level_menu_item_url( $fallback = false ) {
		$slug = self::get_top_level_menu_item_slug();

		if ( $slug ) {
			$url = menu_page_url( $slug, false );
			return $url;
		}

		$url = $fallback ? $fallback : admin_url();
		return $url;
	}

}