init_hooks(); } } /** * Initialize the hooks. */ public function init_hooks() { self::$initialized = true; // Jetpack uses 998 and 'Admin_Menu' uses 1000. add_action( 'admin_menu', array( $this, 'add_wp_admin_menu' ), $this->menu_priority ); } /** * Add a "Stats" top-level admin menu. * * Declares no `product` gate: that resolves false without the Jetpack plugin, which is * exactly when the standalone Stats plugin registers this page. * * @return void */ public function add_wp_admin_menu() { /** * Disable this menu for dashboard.wordpress.com because older versions of Jetpack need to fetch the old Stats UI. * * If this menu is registered, it will conflict with the back-end and break non-odyssey Stats. */ if ( defined( 'IS_WPCOM' ) && IS_WPCOM && 120742 === get_current_blog_id() ) { return; } $page_title = __( 'Stats', 'jetpack-stats-admin' ); $menu_title = _x( 'Stats', 'product name shown in menu', 'jetpack-stats-admin' ); $capability = $this->get_capability(); $callback = array( $this, 'render' ); // An older admin-ui, loaded first by another plugin, may predate add_top_level_menu(). if ( method_exists( Admin_Menu::class, 'add_top_level_menu' ) ) { // The key the legacy Stats screen in the Jetpack plugin also declares, so hosts name Stats once. $page_suffix = Admin_Menu::add_top_level_menu( $page_title, $menu_title, $capability, 'stats', $callback, 'dashicons-chart-bar', 2, array( 'key' => 'jetpack-stats' ) ); } else { $page_suffix = add_menu_page( $page_title, $menu_title, $capability, 'stats', $callback, 'dashicons-chart-bar', 2 ); } if ( $page_suffix ) { add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) ); // The dashboard renders full bleed, so core notices stacked above it look broken. if ( method_exists( Admin_Menu::class, 'hide_core_admin_notices' ) ) { add_action( 'load-' . $page_suffix, array( Admin_Menu::class, 'hide_core_admin_notices' ) ); } } } /** * Capability a user needs to reach the dashboard. * * Until the site is connected the page exists to pick a plan and connect, which only a user * who can manage the connection can act on. Once connected it is a reporting page, open to * everyone allowed to view stats. * * Pre-connection that is `jetpack_connect`: it maps to `manage_options` on a normal site, * but is `do_not_allow` in offline mode and honours the same multisite/filter rules as the * register endpoint, so the menu is not offered where the connection cannot be completed. * * @return string */ protected function get_capability() { return Main::is_site_connected() ? 'view_stats' : 'jetpack_connect'; } /** * Override render funtion */ public function render() { // Record the number of views of the stats dashboard on the initial several loads for the // purpose of showing feedback notice. Views before the site is connected show the plan // choice rather than the dashboard, and there is nothing to give feedback on yet. $views = intval( Stats_Options::get_option( 'views' ) ) + 1; if ( $views <= Notices::VIEWS_TO_SHOW_FEEDBACK && Main::is_site_connected() ) { Stats_Options::set_option( 'views', $views ); } ?>
src="//en.wordpress.com/i/loading/loading-64.gif" />
maybe_refresh_plan(); add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) ); } /** * Fill an empty plan cache before the config data that reads it is printed. * * The app cannot refresh the plan it paywalls on, so a site that never stored one renders as * free. Throttled and time-boxed, because WordPress.com can keep answering without a plan. */ private function maybe_refresh_plan() { if ( ! Main::is_site_connected() ) { return; } // method_exists guard: an older plans package may win the autoloader on another plugin. if ( method_exists( Jetpack_Plan::class, 'get_wpcom_site_specific_features' ) && null !== Jetpack_Plan::get_wpcom_site_specific_features() ) { return; } $plan = Jetpack_Plan::get(); if ( ! empty( $plan['features']['active'] ) || get_transient( self::PLAN_REFRESH_TRANSIENT ) ) { return; } set_transient( self::PLAN_REFRESH_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS ); Jetpack_Plan::refresh_from_wpcom( array( 'timeout' => 5 ) ); } /** * Load the admin scripts. */ public function load_admin_scripts() { ( new Odyssey_Assets() )->load_admin_scripts( 'jp-stats-dashboard', 'build.min', array( 'config_variable_name' => 'jetpackStatsOdysseyAppConfigData' ) ); // The bootstrap runs on jQuery, which the Odyssey bundle does not depend on. It gets its own // handle rather than jQuery being added to that bundle, which the dashboard widget shares // and which has no use for it. wp_register_script( 'jp-stats-dashboard-bootstrap', false, array( 'jquery' ), Main::VERSION, true ); wp_enqueue_script( 'jp-stats-dashboard-bootstrap' ); wp_add_inline_script( 'jp-stats-dashboard-bootstrap', $this->get_bootstrap_script() ); // The app is served from our CDN and so cannot bundle the connection package. Print the // state Search and Protect print on their own pages, so it can read the connection status // and register the site through the connection REST API itself. Connected sites fetch // `jetpack/v4/connection` over REST instead, and must not receive registrationNonce and // the connected-plugin list on a page that `view_stats` users can open. if ( ! Main::is_site_connected() ) { Connection_Initial_State::render_script( 'jp-stats-dashboard' ); } } }