Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.50% covered (warning)
72.50%
58 / 80
33.33% covered (danger)
33.33%
4 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Dashboard
74.36% covered (warning)
74.36%
58 / 78
33.33% covered (danger)
33.33%
4 / 12
33.71
0.00% covered (danger)
0.00%
0 / 1
 register_feature_flag
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 is_enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 init
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 maybe_load_wp_build
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 is_dashboard_request
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 load_wp_build
83.33% covered (warning)
83.33%
20 / 24
0.00% covered (danger)
0.00%
0 / 1
5.12
 alias_screen_id
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 restore_screen_id
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 enqueue_i18n_loader
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 add_menu
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 add_wp_admin_submenu
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 render
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
2.04
1<?php
2/**
3 * Monetize dashboard: a wp-build page in the Jetpack sidebar.
4 *
5 * @package automattic/jetpack-monetize
6 */
7
8namespace Automattic\Jetpack\Monetize;
9
10use Automattic\Jetpack\Admin_UI\Admin_Menu;
11use Automattic\Jetpack\Feature_Flags\Feature_Flags;
12use Automattic\Jetpack\Status\Host;
13use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
14use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Screen_Id;
15
16if ( ! defined( 'ABSPATH' ) ) {
17    exit( 0 );
18}
19
20/**
21 * Registers the Monetize sidebar item and renders its wp-build route.
22 */
23class Dashboard {
24
25    /**
26     * Package version, bumped at release through composer.json `version-constants`.
27     *
28     * @var string
29     */
30    const PACKAGE_VERSION = '0.1.0-alpha';
31
32    /**
33     * Feature flag that makes the dashboard available while it is built.
34     *
35     * @var string
36     */
37    const FEATURE_FLAG = 'jetpack-monetize-dashboard';
38
39    /**
40     * Sidebar menu slug.
41     *
42     * @var string
43     */
44    const MENU_SLUG = 'jetpack-monetize';
45
46    /**
47     * The wp-build route's page id. It must not be the menu slug: the generated
48     * standalone page.php intercepts `admin_init` for its own id and exits.
49     *
50     * @var string
51     */
52    const WP_BUILD_PAGE_ID = 'jetpack-monetize-dashboard';
53
54    /**
55     * Name of the wp-build render function generated for WP_BUILD_PAGE_ID.
56     *
57     * @var string
58     */
59    const RENDER_FUNCTION = 'jetpack_monetize_jetpack_monetize_dashboard_wp_admin_render_page';
60
61    /**
62     * Extra Admin_Menu item options, such as the module that owns the item.
63     *
64     * @var array
65     */
66    private static $menu_options = array();
67
68    /**
69     * The screen ID alias_screen_id() replaced, until it is restored.
70     *
71     * @var string|null
72     */
73    private static $original_screen_id = null;
74
75    /**
76     * Register the flag. Hosts call this before they decide whether to load the dashboard.
77     *
78     * @return void
79     */
80    public static function register_feature_flag() {
81        Feature_Flags::register(
82            self::FEATURE_FLAG,
83            array(
84                'default'     => false,
85                'description' => 'Make the Monetize dashboard available while it is built.',
86                'owner'       => 'jetpack',
87            )
88        );
89    }
90
91    /**
92     * Whether the dashboard's feature flag is on.
93     *
94     * @return bool
95     */
96    public static function is_enabled() {
97        return Feature_Flags::is_enabled( self::FEATURE_FLAG );
98    }
99
100    /**
101     * Wire the hooks.
102     *
103     * @param array $menu_options Admin_Menu item options merged over the defaults, e.g. `module`.
104     * @return void
105     */
106    public static function init( array $menu_options = array() ) {
107        self::$menu_options = $menu_options;
108
109        add_action( 'admin_menu', array( __CLASS__, 'maybe_load_wp_build' ), 1 );
110
111        // A wpcom host adds the item itself: see README.
112        if ( ! ( new Host() )->is_wpcom_platform() ) {
113            // Before Admin_Menu registers its items at 1000.
114            add_action( 'admin_menu', array( __CLASS__, 'add_menu' ), 999 );
115        }
116    }
117
118    /**
119     * Load wp-build output, only on this page's request.
120     *
121     * Scoped to the page so WP_Build_Polyfills does not replace core scripts everywhere else.
122     *
123     * @return void
124     */
125    public static function maybe_load_wp_build() {
126        if ( self::is_dashboard_request() ) {
127            self::load_wp_build( dirname( __DIR__ ) . '/build/build.php' );
128        }
129    }
130
131    /**
132     * Whether the request is for the dashboard's page.
133     *
134     * @return bool
135     */
136    public static function is_dashboard_request() {
137        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading the page slug only.
138        return isset( $_GET['page'] ) && self::MENU_SLUG === sanitize_text_field( wp_unslash( $_GET['page'] ) );
139    }
140
141    /**
142     * Load a wp-build index and, if it can render the page, its scripts and polyfills.
143     *
144     * @param string $build_index Path to the generated build.php.
145     * @return bool Whether the build can render the page.
146     */
147    public static function load_wp_build( $build_index ) {
148        if ( ! file_exists( $build_index ) ) {
149            return false;
150        }
151
152        $load_wp_build = static function () use ( $build_index ) {
153            require_once $build_index;
154        };
155
156        // Fallback: an older wp-build-polyfills under the jetpack-autoloader may predate load_with_alias().
157        if ( method_exists( WP_Build_Screen_Id::class, 'load_with_alias' ) ) {
158            WP_Build_Screen_Id::load_with_alias(
159                array( __CLASS__, 'alias_screen_id' ),
160                array( __CLASS__, 'restore_screen_id' ),
161                $load_wp_build
162            );
163        } else {
164            add_action( 'admin_enqueue_scripts', array( __CLASS__, 'alias_screen_id' ) );
165            $load_wp_build();
166            add_action( 'admin_enqueue_scripts', array( __CLASS__, 'restore_screen_id' ) );
167        }
168
169        // A stale or partial build can't render the page, so don't swap core's scripts for it.
170        if ( ! function_exists( self::RENDER_FUNCTION ) ) {
171            return false;
172        }
173
174        add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_i18n_loader' ) );
175
176        // wp-build hooks module registration to wp_default_scripts, which has already fired by
177        // admin_menu — call it directly or the init module never reaches the import map.
178        if ( function_exists( 'jetpack_monetize_register_script_modules' ) ) {
179            jetpack_monetize_register_script_modules(); // @phan-suppress-current-line PhanUndeclaredFunction -- Checked with function_exists(); defined in the generated build/modules.php, which Phan excludes.
180        }
181
182        WP_Build_Polyfills::register(
183            'jetpack-monetize',
184            array_merge( WP_Build_Polyfills::SCRIPT_HANDLES, WP_Build_Polyfills::MODULE_IDS )
185        );
186
187        return true;
188    }
189
190    /**
191     * Point the screen ID at the wp-build page while its generated enqueue check runs.
192     *
193     * @return void
194     */
195    public static function alias_screen_id() {
196        $screen = get_current_screen();
197        if ( ! $screen ) {
198            return;
199        }
200
201        self::$original_screen_id = $screen->id;
202        $screen->id               = self::WP_BUILD_PAGE_ID;
203    }
204
205    /**
206     * Undo alias_screen_id(), since JITM builds its message path from the screen ID.
207     *
208     * @return void
209     */
210    public static function restore_screen_id() {
211        $screen = get_current_screen();
212        if ( ! $screen || null === self::$original_screen_id ) {
213            return;
214        }
215
216        $screen->id               = self::$original_screen_id;
217        self::$original_screen_id = null;
218    }
219
220    /**
221     * Enqueue the JS translation loader, which the esbuild route bundles don't pull in.
222     *
223     * @return void
224     */
225    public static function enqueue_i18n_loader() {
226        if ( wp_script_is( 'wp-jp-i18n-loader', 'registered' ) ) {
227            wp_enqueue_script( 'wp-jp-i18n-loader' );
228        }
229    }
230
231    /**
232     * Add the "Monetize" item to the Jetpack sidebar.
233     *
234     * @return void
235     */
236    public static function add_menu() {
237        Admin_Menu::add_menu(
238            __( 'Monetize', 'jetpack-monetize' ),
239            __( 'Monetize', 'jetpack-monetize' ),
240            'manage_options',
241            self::MENU_SLUG,
242            array( __CLASS__, 'render' ),
243            null,
244            array_merge( array( 'key' => self::MENU_SLUG ), self::$menu_options )
245        );
246    }
247
248    /**
249     * Add the "Monetize" item under the Jetpack menu on Simple, once wpcom-admin-menu.php has built that menu.
250     *
251     * @return void
252     */
253    public static function add_wp_admin_submenu() {
254        add_submenu_page(
255            'jetpack',
256            __( 'Monetize', 'jetpack-monetize' ),
257            __( 'Monetize', 'jetpack-monetize' ),
258            'manage_options',
259            self::MENU_SLUG,
260            array( __CLASS__, 'render' )
261        );
262    }
263
264    /**
265     * Render the wp-build page, or say why it could not be rendered.
266     *
267     * @return void
268     */
269    public static function render() {
270        $render_fn = self::RENDER_FUNCTION;
271        if ( function_exists( $render_fn ) ) {
272            $render_fn();
273            return;
274        }
275
276        printf(
277            '<div class="wrap"><h1>%s</h1><div class="notice notice-error"><p>%s</p></div></div>',
278            esc_html__( 'Monetize', 'jetpack-monetize' ),
279            esc_html__( 'The Monetize dashboard could not be loaded because its assets are missing.', 'jetpack-monetize' )
280        );
281    }
282}