PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-stats-admin/src/class-dashboard.php +136 -32 12.6.4 → 16.3-beta View file →
@@ -7,8 +7,10 @@
7 7
8 8 namespace Automattic\Jetpack\Stats_Admin;
9 9
10 10 use Automattic\Jetpack\Admin_UI\Admin_Menu;
11 +use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
12 +use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
11 13 use Automattic\Jetpack\Stats\Options as Stats_Options;
12 14
13 15 /**
14 16 * Responsible for adding a stats dashboard to wp-admin.
@@ -23,12 +25,20 @@
23 25 */
24 26 private static $initialized = false;
25 27
26 28 /**
29 + * Transient that throttles the plan refresh below.
30 + *
31 + * @var string
32 + */
33 + private const PLAN_REFRESH_TRANSIENT = 'jetpack_stats_admin_plan_refresh';
34 +
35 + /**
27 36 * Priority for the dashboard menu
28 37 * For Jetpack sites: Jetpack uses 998 and 'Admin_Menu' uses 1000, so we need to use 999.
29 - * For simple site: the value is overriden in a child class with value 100000 to wait for all menus to be registered.
30 38 *
39 + * Admin_Menu registers what it has queued at priority 1000, so this has to stay below it.
40 + *
31 41 * @var int
32 42 */
33 43 protected $menu_priority = 999;
34 44
@@ -47,35 +57,77 @@
47 57 */
48 58 public function init_hooks() {
49 59 self::$initialized = true;
50 60 // Jetpack uses 998 and 'Admin_Menu' uses 1000.
51 - add_action( 'admin_menu', array( $this, 'add_wp_admin_submenu' ), $this->menu_priority );
61 + add_action( 'admin_menu', array( $this, 'add_wp_admin_menu' ), $this->menu_priority );
52 62 }
53 63
54 64 /**
55 - * The page to be added to submenu
65 + * Add a "Stats" top-level admin menu.
66 + *
67 + * Declares no `product` gate: that resolves false without the Jetpack plugin, which is
68 + * exactly when the standalone Stats plugin registers this page.
69 + *
70 + * @return void
56 71 */
57 - public function add_wp_admin_submenu() {
58 - $page_suffix = Admin_Menu::add_menu(
59 - __( 'Stats App', 'jetpack-stats-admin' ),
60 - _x( 'Stats App', 'product name shown in menu', 'jetpack-stats-admin' ),
61 - 'manage_options',
62 - 'jetpack-stats-app',
63 - array( $this, 'render' )
64 - );
72 + public function add_wp_admin_menu() {
73 + /**
74 + * Disable this menu for dashboard.wordpress.com because older versions of Jetpack need to fetch the old Stats UI.
75 + *
76 + * If this menu is registered, it will conflict with the back-end and break non-odyssey Stats.
77 + */
78 + if ( defined( 'IS_WPCOM' ) && IS_WPCOM && 120742 === get_current_blog_id() ) {
79 + return;
80 + }
65 81
82 + $page_title = __( 'Stats', 'jetpack-stats-admin' );
83 + $menu_title = _x( 'Stats', 'product name shown in menu', 'jetpack-stats-admin' );
84 + $capability = $this->get_capability();
85 + $callback = array( $this, 'render' );
86 +
87 + // An older admin-ui, loaded first by another plugin, may predate add_top_level_menu().
88 + if ( method_exists( Admin_Menu::class, 'add_top_level_menu' ) ) {
89 + // The key the legacy Stats screen in the Jetpack plugin also declares, so hosts name Stats once.
90 + $page_suffix = Admin_Menu::add_top_level_menu( $page_title, $menu_title, $capability, 'stats', $callback, 'dashicons-chart-bar', 2, array( 'key' => 'jetpack-stats' ) );
91 + } else {
92 + $page_suffix = add_menu_page( $page_title, $menu_title, $capability, 'stats', $callback, 'dashicons-chart-bar', 2 );
93 + }
94 +
66 95 if ( $page_suffix ) {
67 96 add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
97 + // The dashboard renders full bleed, so core notices stacked above it look broken.
98 + if ( method_exists( Admin_Menu::class, 'hide_core_admin_notices' ) ) {
99 + add_action( 'load-' . $page_suffix, array( Admin_Menu::class, 'hide_core_admin_notices' ) );
100 + }
68 101 }
69 102 }
70 103
71 104 /**
105 + * Capability a user needs to reach the dashboard.
106 + *
107 + * Until the site is connected the page exists to pick a plan and connect, which only a user
108 + * who can manage the connection can act on. Once connected it is a reporting page, open to
109 + * everyone allowed to view stats.
110 + *
111 + * Pre-connection that is `jetpack_connect`: it maps to `manage_options` on a normal site,
112 + * but is `do_not_allow` in offline mode and honours the same multisite/filter rules as the
113 + * register endpoint, so the menu is not offered where the connection cannot be completed.
114 + *
115 + * @return string
116 + */
117 + protected function get_capability() {
118 + return Main::is_site_connected() ? 'view_stats' : 'jetpack_connect';
119 + }
120 +
121 + /**
72 122 * Override render funtion
73 123 */
74 124 public function render() {
75 - // Record the number of views of the stats dashboard on the initial several loads for the purpose of showing feedback notice.
125 + // Record the number of views of the stats dashboard on the initial several loads for the
126 + // purpose of showing feedback notice. Views before the site is connected show the plan
127 + // choice rather than the dashboard, and there is nothing to give feedback on yet.
76 128 $views = intval( Stats_Options::get_option( 'views' ) ) + 1;
77 - if ( $views <= Notices::VIEWS_TO_SHOW_FEEDBACK ) {
129 + if ( $views <= Notices::VIEWS_TO_SHOW_FEEDBACK && Main::is_site_connected() ) {
78 130 Stats_Options::set_option( 'views', $views );
79 131 }
80 132
81 133 ?>
@@ -91,40 +143,92 @@
91 143 src="//en.wordpress.com/i/loading/loading-64.gif"
92 144 />
93 145 </div>
94 146 </div>
95 - <script>
96 - jQuery(document).ready(function($) {
97 - // Load SVG sprite.
98 - $.get("https://widgets.wp.com/odyssey-stats/common/gridicons-506499ddac13811fee8e.svg", function(data) {
99 - var div = document.createElement("div");
100 - div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
101 - div.style = 'display: none';
102 - document.body.insertBefore(div, document.body.childNodes[0]);
103 - });
104 - // we intercept on all anchor tags and change it to hashbang style.
105 - $("#wpcom").on('click', 'a', function (e) {
106 - const link = e && e.currentTarget && e.currentTarget.attributes && e.currentTarget.attributes.href && e.currentTarget.attributes.href.value;
107 - if( link && link.startsWith( '/stats' ) ) {
108 - location.hash = `#!${link}`;
109 - return false;
110 - }
111 - });
112 - });
113 - </script>
114 147 <?php
115 148 }
116 149
117 150 /**
151 + * The dashboard bootstrap: load the icon sprite, and keep in-app links inside the dashboard.
152 + *
153 + * @return string
154 + */
155 + private function get_bootstrap_script() {
156 + return <<<'JS'
157 +jQuery(document).ready(function($) {
158 + // Load SVG sprite.
159 + $.get("https://widgets.wp.com/odyssey-stats/common/gridicons-506499ddac13811fee8e.svg", function(data) {
160 + var div = document.createElement("div");
161 + div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
162 + div.style = 'display: none';
163 + document.body.insertBefore(div, document.body.childNodes[0]);
164 + });
165 + // we intercept on all anchor tags and change it to hashbang style.
166 + $("#wpcom").on('click', 'a', function (e) {
167 + const link = e && e.currentTarget && e.currentTarget.attributes && e.currentTarget.attributes.href && e.currentTarget.attributes.href.value;
168 + if( link && link.startsWith( '/stats' ) ) {
169 + location.hash = `#!${link}`;
170 + return false;
171 + }
172 + });
173 +});
174 +JS;
175 + }
176 +
177 + /**
118 178 * Initialize the admin resources.
119 179 */
120 180 public function admin_init() {
181 + $this->maybe_refresh_plan();
121 182 add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) );
122 183 }
123 184
124 185 /**
186 + * Fill an empty plan cache before the config data that reads it is printed.
187 + *
188 + * The app cannot refresh the plan it paywalls on, so a site that never stored one renders as
189 + * free. Throttled and time-boxed, because WordPress.com can keep answering without a plan.
190 + */
191 + private function maybe_refresh_plan() {
192 + if ( ! Main::is_site_connected() ) {
193 + return;
194 + }
195 +
196 + // method_exists guard: an older plans package may win the autoloader on another plugin.
197 + if ( method_exists( Jetpack_Plan::class, 'get_wpcom_site_specific_features' )
198 + && null !== Jetpack_Plan::get_wpcom_site_specific_features() ) {
199 + return;
200 + }
201 +
202 + $plan = Jetpack_Plan::get();
203 + if ( ! empty( $plan['features']['active'] ) || get_transient( self::PLAN_REFRESH_TRANSIENT ) ) {
204 + return;
205 + }
206 +
207 + set_transient( self::PLAN_REFRESH_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS );
208 +
209 + Jetpack_Plan::refresh_from_wpcom( array( 'timeout' => 5 ) );
210 + }
211 +
212 + /**
125 213 * Load the admin scripts.
126 214 */
127 215 public function load_admin_scripts() {
128 216 ( new Odyssey_Assets() )->load_admin_scripts( 'jp-stats-dashboard', 'build.min', array( 'config_variable_name' => 'jetpackStatsOdysseyAppConfigData' ) );
217 +
218 + // The bootstrap runs on jQuery, which the Odyssey bundle does not depend on. It gets its own
219 + // handle rather than jQuery being added to that bundle, which the dashboard widget shares
220 + // and which has no use for it.
221 + wp_register_script( 'jp-stats-dashboard-bootstrap', false, array( 'jquery' ), Main::VERSION, true );
222 + wp_enqueue_script( 'jp-stats-dashboard-bootstrap' );
223 + wp_add_inline_script( 'jp-stats-dashboard-bootstrap', $this->get_bootstrap_script() );
224 +
225 + // The app is served from our CDN and so cannot bundle the connection package. Print the
226 + // state Search and Protect print on their own pages, so it can read the connection status
227 + // and register the site through the connection REST API itself. Connected sites fetch
228 + // `jetpack/v4/connection` over REST instead, and must not receive registrationNonce and
229 + // the connected-plugin list on a page that `view_stats` users can open.
230 + if ( ! Main::is_site_connected() ) {
231 + Connection_Initial_State::render_script( 'jp-stats-dashboard' );
232 + }
129 233 }
130 234 }