Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.16% covered (success)
93.16%
109 / 117
66.67% covered (warning)
66.67%
8 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Admin
93.16% covered (success)
93.16%
109 / 117
66.67% covered (warning)
66.67%
8 / 12
29.27
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 handle_admin_menu
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
3
 is_modern_dashboard
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 maybe_load_wp_build
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 dashboard_build_is_available
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 alias_screen_id_for_wp_build
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 restore_screen_id_after_wp_build
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 admin_init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_scripts
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
5
 plugin_page_settings_link
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 localize_api_settings
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 render_settings
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * The admin-specific functionality of the plugin.
4 *
5 * @since      1.0.0
6 * @package    automattic/jetpack-boost
7 */
8
9namespace Automattic\Jetpack_Boost\Admin;
10
11use Automattic\Jetpack\Admin_UI\Admin_Menu;
12use Automattic\Jetpack\Assets;
13use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
14use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
15use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
16use Automattic\Jetpack_Boost\Lib\Analytics;
17use Automattic\Jetpack_Boost\Lib\Debug;
18use Automattic\Jetpack_Boost\Lib\Environment_Change_Detector;
19use Automattic\Jetpack_Boost\Lib\Premium_Features;
20use Automattic\Jetpack_Boost\Modules\Modules_Setup;
21
22class Admin {
23    /**
24     * Menu slug.
25     */
26    const MENU_SLUG = 'jetpack-boost';
27
28    /**
29     * Filter enabling the modern dashboard.
30     */
31    const MODERNIZATION_FILTER = 'rsm_jetpack_ui_modernization_boost';
32
33    /**
34     * Whether this request loaded the modern dashboard.
35     *
36     * @var bool
37     */
38    private $modern_dashboard_loaded = false;
39
40    /**
41     * The screen ID alias_screen_id_for_wp_build() replaced, until it is restored.
42     *
43     * @var string|null
44     */
45    private $wp_build_original_screen_id = null;
46
47    public function init( Modules_Setup $modules ) {
48        Environment_Change_Detector::init();
49
50        // Initiate speed scores.
51        new Speed_Score( $modules->get_ready_active_optimization_modules(), 'boost-plugin' );
52
53        add_action( 'init', array( new Analytics(), 'init' ) );
54        add_filter( 'plugin_action_links_' . JETPACK_BOOST_PLUGIN_BASE, array( $this, 'plugin_page_settings_link' ) );
55        add_action( 'admin_menu', array( $this, 'handle_admin_menu' ), 1 ); // Akismet uses 4, so we use 1 to ensure both menus are added when only they exist.
56    }
57
58    public function handle_admin_menu() {
59        $this->maybe_load_wp_build();
60
61        /**
62         * Filters the number of problems shown in the Boost sidebar menu
63         *
64         * @param int $count the number of problems shown.
65         *
66         * @since   1.0.0
67         */
68        // Only report the count to users who can actually reach the Boost menu
69        // (added below with the 'manage_options' cap). Otherwise the central
70        // menu-badges total would include problems the current user can't see.
71        if ( current_user_can( 'manage_options' ) ) {
72            $total_problems = apply_filters( 'jetpack_boost_total_problem_count', 0 );
73            \Automattic\Jetpack\Menu_Badges\Menu_Badges::init(); // idempotent; wires the renderer.
74            \Automattic\Jetpack\Menu_Badges\Notification_Counts::register(
75                'jetpack-boost',
76                array(
77                    'menu_slug' => JETPACK_BOOST_SLUG,
78                    'count'     => (int) $total_problems,
79                    'type'      => 'count',
80                )
81            );
82        }
83
84        $page_suffix = Admin_Menu::add_menu(
85            __( 'Jetpack Boost - Settings', 'jetpack-boost' ),
86            'Boost', // "Boost" is a product name, do not translate.
87            'manage_options',
88            JETPACK_BOOST_SLUG,
89            $this->modern_dashboard_loaded ? 'jetpack_boost_jetpack_boost_dashboard_wp_admin_render_page' : array( $this, 'render_settings' ),
90            null,
91            array(
92                'product' => 'boost',
93                'key'     => 'jetpack-boost',
94            )
95        );
96        add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
97    }
98
99    /**
100     * Whether this request opts into the modern admin dashboard.
101     *
102     * @return bool Whether modernization is enabled for this admin request.
103     */
104    private static function is_modern_dashboard() {
105        /**
106         * Enable the modern Boost dashboard.
107         *
108         * @since 4.7.1
109         * @param bool $enabled Whether to enable the modern dashboard. Default false.
110         */
111        return apply_filters( self::MODERNIZATION_FILTER, false ) && is_admin();
112    }
113
114    /**
115     * Load the modern dashboard only on an opted-in Boost admin request.
116     */
117    private function maybe_load_wp_build() {
118        if ( ! self::is_modern_dashboard() ) {
119            return;
120        }
121
122        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
123        $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
124        if ( JETPACK_BOOST_SLUG !== $page || ! $this->dashboard_build_is_available() ) {
125            return;
126        }
127
128        WP_Build_Polyfills::register(
129            'jetpack-boost',
130            array_merge( WP_Build_Polyfills::SCRIPT_HANDLES, WP_Build_Polyfills::MODULE_IDS )
131        );
132
133        // wp_default_scripts has already fired by admin_menu, so register the init module now.
134        jetpack_boost_register_script_modules(); // @phan-suppress-current-line PhanUndeclaredFunction -- Defined by the generated build and checked in dashboard_build_is_available().
135        $this->modern_dashboard_loaded = true;
136    }
137
138    /**
139     * Load the generated dashboard, retaining the legacy page when assets are absent.
140     *
141     * @return bool Whether the generated dashboard is available.
142     */
143    private function dashboard_build_is_available() {
144        $build_file = JETPACK_BOOST_DIR_PATH . '/build/build.php';
145        if ( ! file_exists( $build_file ) ) {
146            Debug::log( 'Modern dashboard build is missing; loading the legacy dashboard.' );
147            return false;
148        }
149
150        // Hooked around the require, so only the generated enqueue callback, with its init action and REST preloading, sees the alias.
151        add_action( 'admin_enqueue_scripts', array( $this, 'alias_screen_id_for_wp_build' ) );
152        require_once $build_file;
153        add_action( 'admin_enqueue_scripts', array( $this, 'restore_screen_id_after_wp_build' ) );
154
155        if ( function_exists( 'jetpack_boost_register_script_modules' )
156            && function_exists( 'jetpack_boost_jetpack_boost_dashboard_wp_admin_render_page' ) ) {
157            return true;
158        }
159
160        remove_action( 'admin_enqueue_scripts', array( $this, 'alias_screen_id_for_wp_build' ) );
161        remove_action( 'admin_enqueue_scripts', array( $this, 'restore_screen_id_after_wp_build' ) );
162        return false;
163    }
164
165    /**
166     * Match wp-build's enqueue screen without changing the Boost menu URL.
167     *
168     * @since 4.7.1
169     * @since $$next-version$$ Reads the current screen itself and records the ID it replaces.
170     */
171    public function alias_screen_id_for_wp_build() {
172        $screen = get_current_screen();
173        if ( ! $screen ) {
174            return;
175        }
176
177        $this->wp_build_original_screen_id = $screen->id;
178        $screen->id                        = 'jetpack-boost-dashboard';
179    }
180
181    /**
182     * Undo alias_screen_id_for_wp_build(), since JITM builds its message path from the screen ID.
183     *
184     * @since $$next-version$$
185     */
186    public function restore_screen_id_after_wp_build() {
187        $screen = get_current_screen();
188        if ( ! $screen || null === $this->wp_build_original_screen_id ) {
189            return;
190        }
191
192        $screen->id                        = $this->wp_build_original_screen_id;
193        $this->wp_build_original_screen_id = null;
194    }
195
196    /**
197     * Enqueue scripts and styles for the admin page.
198     */
199    public function admin_init() {
200        // Clear premium features cache when the plugin settings page is loaded.
201        Premium_Features::clear_cache();
202
203        add_action( 'admin_enqueue_scripts', array( My_Jetpack_Initializer::class, 'enqueue_scripts' ) );
204        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
205    }
206
207    /**
208     * Register the JavaScript for the admin area.
209     *
210     * @since    1.0.0
211     */
212    public function enqueue_scripts() {
213        /**
214         * Filters the internal path to the distributed assets used by the plugin
215         *
216         * @param string $path the path to the assets
217         */
218        $internal_path = apply_filters( 'jetpack_boost_asset_internal_path', 'app/assets/dist/' );
219
220        $admin_js_handle = 'jetpack-boost-admin';
221
222        $admin_js_dependencies = array(
223            'wp-i18n',
224            'wp-components',
225            'my_jetpack_main_app',
226        );
227
228        Assets::register_script(
229            $admin_js_handle,
230            $internal_path . 'jetpack-boost.js',
231            JETPACK_BOOST_PATH,
232            array(
233                'dependencies' => $admin_js_dependencies,
234                'in_footer'    => true,
235                'textdomain'   => 'jetpack-boost',
236                'css_path'     => $internal_path . 'jetpack-boost.css',
237            )
238        );
239
240        wp_localize_script(
241            $admin_js_handle,
242            'Jetpack_Boost',
243            ( new Config() )->constants()
244        );
245
246        Assets::enqueue_script( $admin_js_handle );
247
248        if ( $this->modern_dashboard_loaded ) {
249            $this->localize_api_settings( $admin_js_handle );
250            $i18n_loader_registered = wp_script_is( 'wp-jp-i18n-loader', 'registered' );
251            if ( $i18n_loader_registered ) {
252                wp_enqueue_script( 'wp-jp-i18n-loader' );
253            }
254
255            // The webpack handle carries Boost constants and DataSync bootstrap needed before modules run.
256            $prerequisites = wp_scripts()->query( 'jetpack-boost-dashboard-wp-admin-prerequisites', 'registered' );
257            if ( $prerequisites ) {
258                $prerequisites->deps[] = $admin_js_handle;
259                if ( $i18n_loader_registered ) {
260                    $prerequisites->deps[] = 'wp-jp-i18n-loader';
261                }
262            } else {
263                Debug::log( 'Modern dashboard prerequisites are not registered; bootstrap dependencies could not be attached.' );
264            }
265        }
266    }
267
268    /**
269     * Get settings link.
270     *
271     * @param array $links the array of links.
272     */
273    public function plugin_page_settings_link( $links ) {
274        $settings_link = '<a href="' . admin_url( 'admin.php?page=jetpack-boost' ) . '">' . esc_html__( 'Settings', 'jetpack-boost' ) . '</a>';
275        array_unshift( $links, $settings_link );
276
277        return $links;
278    }
279
280    /**
281     * Localize the REST API settings for a script.
282     *
283     * @param string $handle Script handle.
284     */
285    private function localize_api_settings( $handle ) {
286        wp_localize_script(
287            $handle,
288            'wpApiSettings',
289            array(
290                'root'  => esc_url_raw( rest_url() ),
291                'nonce' => wp_create_nonce( 'wp_rest' ),
292            )
293        );
294    }
295
296    /**
297     * Generate the settings page.
298     */
299    public function render_settings() {
300        $this->localize_api_settings( 'jetpack-boost-admin' );
301        ?>
302        <div id="jb-admin-settings"></div>
303        <?php
304    }
305}