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
jetpack / jetpack_vendor / automattic / jetpack-scan-page / src / class-jetpack-scan.php

class-jetpack-scan.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-scan-page/src/class-jetpack-scan.php

270 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Primary class for the Jetpack Scan package.
4 *
5 * @package automattic/jetpack-scan-page
6 */
7
8 namespace Automattic\Jetpack\Scan_Page;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit( 0 );
12 }
13
14 use Automattic\Jetpack\Admin_UI\Admin_Menu;
15 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
16 use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
17 use function add_action;
18 use function add_filter;
19 use function apply_filters;
20 use function call_user_func;
21 use function current_user_can;
22 use function did_action;
23 use function do_action;
24 use function function_exists;
25 use function is_admin;
26 use function is_multisite;
27 use function remove_action;
28 use function remove_all_actions;
29 use function sanitize_text_field;
30 use function wp_unslash;
31
32 /**
33 * Class Jetpack_Scan
34 *
35 * Registers the Scan admin page and its REST routes inside the main
36 * Jetpack plugin. The page bundle is built by `@wordpress/build`
37 * (mirroring Newsletter / Forms); this class wires the wp-admin menu
38 * + the bridges that route our user-facing slug to wp-build's
39 * auto-generated enqueue / render functions.
40 */
41 class Jetpack_Scan {
42
43 /**
44 * URL-facing menu slug.
45 *
46 * @var string
47 */
48 const PAGE_SLUG = 'jetpack-scan';
49
50 /**
51 * Internal slug emitted by `@wordpress/build` (`wpPlugin.pages[0]`
52 * plus the `-wp-admin` suffix the build template appends). Used to
53 * find the auto-generated render / enqueue functions.
54 *
55 * @var string
56 */
57 const WP_BUILD_SLUG = 'jetpack-scan-wp-admin';
58
59 /**
60 * Filter name that gates the wp-build–based Scan dashboard.
61 *
62 * When this filter returns true, the new wp-admin Scan page is
63 * registered and rendered. Default false during the modernization
64 * roll-out — the package registers no admin menu and changes
65 * nothing about the existing Jetpack UI when this filter is off.
66 *
67 * @var string
68 */
69 const MODERNIZATION_FILTER = 'rsm_jetpack_ui_modernization_scan';
70
71 /**
72 * Entry point. Idempotent: safe to call from multiple bootstraps.
73 */
74 public static function initialize() {
75 if ( did_action( 'jetpack_scan_page_initialized' ) ) {
76 return;
77 }
78
79 if ( ! (bool) apply_filters( self::MODERNIZATION_FILTER, false ) ) {
80 return;
81 }
82
83 self::load_wp_build();
84 self::bridge_wp_build_enqueue();
85
86 add_action( 'admin_menu', array( __CLASS__, 'add_wp_admin_submenu' ) );
87 add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
88 add_filter( 'jetpack_package_versions', array( Package_Version::class, 'send_package_version_to_tracker' ) );
89
90 /**
91 * Fires once the Jetpack Scan package has wired its hooks.
92 *
93 * @since 0.1.0
94 */
95 do_action( 'jetpack_scan_page_initialized' );
96 }
97
98 /**
99 * Load wp-build generated registration files. Mirrors Newsletter / Forms.
100 */
101 public static function load_wp_build() {
102 // The polyfills force-replace core script handles (notably
103 // `wp-private-apis`, a stateful singleton shared by every @wordpress
104 // package on the page) during `wp_default_scripts`. Scope registration
105 // to the Scan admin page so it never runs on other admin pages such as
106 // the block editor. `$_GET['page']` is reliable this early — it's the
107 // raw query param, available well before `current_screen` exists.
108 if ( self::is_scan_admin_request() ) {
109 WP_Build_Polyfills::register(
110 'jetpack-scan',
111 array_merge( WP_Build_Polyfills::SCRIPT_HANDLES, WP_Build_Polyfills::MODULE_IDS )
112 );
113 }
114
115 $wp_build_index = dirname( __DIR__ ) . '/build/build.php';
116 if ( file_exists( $wp_build_index ) ) {
117 require_once $wp_build_index;
118 }
119
120 // `page.php` ships an `admin_init` interceptor that takes over our
121 // slug with a standalone (non-wp-admin) render. We want the
122 // wp-admin integrated experience, so unregister it as soon as it's
123 // loaded.
124 remove_action(
125 'admin_init',
126 'jetpack_scan_jetpack_scan_intercept_render'
127 );
128 }
129
130 /**
131 * Whether the current request targets the Scan admin page.
132 *
133 * Used to scope the wp-build polyfill registration (which force-replaces
134 * core script handles) to this one page, so it never affects other admin
135 * pages. Reads the menu page slug directly so it is cheap and safe to call
136 * at plugin-load time, before `current_screen` exists.
137 *
138 * @return bool True when serving the Scan page in wp-admin.
139 */
140 public static function is_scan_admin_request() {
141 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
142 if ( ! is_admin() || ! isset( $_GET['page'] ) ) {
143 return false;
144 }
145
146 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
147 return self::PAGE_SLUG === sanitize_text_field( wp_unslash( $_GET['page'] ) );
148 }
149
150 /**
151 * Bridge wp-build's auto-generated enqueue function — which checks for
152 * `?page=jetpack-scan-wp-admin` — to our user-facing slug
153 * `?page=jetpack-scan`. Hooked at priority 9 so the wp-build copy
154 * (registered at priority 10) sees the original `$_GET['page']` and
155 * skips its own enqueue.
156 */
157 public static function bridge_wp_build_enqueue() {
158 add_action(
159 'admin_enqueue_scripts',
160 static function ( $hook_suffix ) {
161 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
162 if ( ! isset( $_GET['page'] ) || self::PAGE_SLUG !== $_GET['page'] ) {
163 return;
164 }
165
166 $enqueue_fn = 'jetpack_scan_jetpack_scan_wp_admin_enqueue_scripts';
167 if ( ! function_exists( $enqueue_fn ) ) {
168 return;
169 }
170
171 // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
172 $original = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : null;
173 $_GET['page'] = self::WP_BUILD_SLUG;
174 // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Function is generated by @wordpress/build into build/pages/jetpack-scan/page-wp-admin.php, which is outside Phan's analysis scope. The function_exists() guard above protects the call at runtime.
175 call_user_func( $enqueue_fn, $hook_suffix );
176 if ( null === $original ) {
177 unset( $_GET['page'] );
178 } else {
179 $_GET['page'] = $original;
180 }
181 // phpcs:enable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
182 },
183 9
184 );
185 }
186
187 /**
188 * Register the Scan submenu under Jetpack.
189 *
190 * @return string|null The resulting page's hook suffix, if registered.
191 */
192 public static function add_wp_admin_submenu() {
193 if ( ! self::is_available() ) {
194 return null;
195 }
196
197 $render_fn = 'jetpack_scan_jetpack_scan_wp_admin_render_page';
198 $render = function_exists( $render_fn )
199 ? $render_fn
200 : array( __CLASS__, 'render_page_fallback' );
201
202 $page_suffix = Admin_Menu::add_menu(
203 /** "Scan" is a product name, do not translate. */
204 'Scan',
205 'Scan',
206 'manage_options',
207 self::PAGE_SLUG,
208 $render,
209 null,
210 array(
211 'product' => 'scan',
212 'key' => 'jetpack-scan',
213 )
214 );
215
216 if ( $page_suffix ) {
217 add_action( 'load-' . $page_suffix, array( __CLASS__, 'admin_init' ) );
218 }
219
220 return $page_suffix;
221 }
222
223 /**
224 * Whether the Scan page should be shown to the current user.
225 *
226 * @return bool
227 */
228 public static function is_available() {
229 if ( is_multisite() ) {
230 return false;
231 }
232
233 if ( ! current_user_can( 'manage_options' ) ) {
234 return false;
235 }
236
237 return ( new Connection_Manager() )->is_user_connected();
238 }
239
240 /**
241 * Fires when the admin page is loaded.
242 *
243 * Silences the standard wp-admin notice channels so JITMs and
244 * plugin-update messages don't reflow the focused Scan layout
245 * mid-scan or while a fix modal is open.
246 */
247 public static function admin_init() {
248 remove_all_actions( 'admin_notices' );
249 remove_all_actions( 'all_admin_notices' );
250 }
251
252 /**
253 * Fallback render — used only if the wp-build registration file
254 * isn't loaded (e.g. the package wasn't built yet). Renders a bare
255 * mount node so the page doesn't 500 in dev.
256 */
257 public static function render_page_fallback() {
258 ?>
259 <div id="jetpack-scan-page-root"></div>
260 <?php
261 }
262
263 /**
264 * Register the REST routes backing the Scan UI.
265 */
266 public static function register_rest_routes() {
267 REST_Controller::register_rest_routes();
268 }
269 }
270