PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-seo / src / class-initializer.php
jetpack / jetpack_vendor / automattic / jetpack-seo / src Last commit date
class-admin-page.php 3 weeks ago class-ai-crawlers.php 3 weeks ago class-ai-seo-enhancer.php 3 days ago class-author-schema-node.php 1 month ago class-breadcrumb-schema-node.php 1 month ago class-content-coverage.php 3 weeks ago class-dashboard-data.php 2 weeks ago class-initializer.php 3 days ago class-llms-txt.php 3 weeks ago class-local-business-schema-node.php 1 month ago class-organization-schema-node.php 1 month ago class-post-schema-node.php 1 month ago class-post-types.php 3 weeks ago class-schema-builder.php 1 month ago class-schema-graph.php 1 month ago class-schema-node-ids.php 1 month ago class-schema-settings-controller.php 1 month ago class-schema-settings.php 1 month ago class-surface-visibility.php 2 weeks ago class-website-schema-node.php 1 month ago
class-initializer.php
367 lines
1 <?php
2 /**
3 * Jetpack SEO — the visibility command center for WordPress sites.
4 *
5 * Gates the surface behind its feature flag and cohort, then wires the admin
6 * page ({@see Admin_Page}), the dashboard's REST reads ({@see Dashboard_Data}),
7 * the content-coverage cache invalidation ({@see Content_Coverage}), and the
8 * opt-in surface ({@see Surface_Visibility}).
9 *
10 * @package automattic/jetpack-seo-package
11 */
12
13 namespace Automattic\Jetpack\SEO;
14
15 use Automattic\Jetpack\Current_Plan;
16 use Automattic\Jetpack\Modules;
17 use Automattic\Jetpack\Status;
18 use Automattic\Jetpack\Status\Host;
19
20 /**
21 * Boots the package and carries its cross-plugin contract: the feature flag,
22 * the script-data key, and the option names / visibility reads other plugins consume.
23 */
24 class Initializer {
25
26 /**
27 * Jetpack SEO package version.
28 *
29 * @var string
30 */
31 const PACKAGE_VERSION = '0.8.2';
32
33 /**
34 * WordPress.com site feature that enables the Jetpack SEO surface.
35 *
36 * Kept separate from `advanced-seo`, which gates the paid parts of the
37 * dashboard after this product-level availability check has passed.
38 *
39 * @var string
40 */
41 const FEATURE_SLUG = 'seo-admin-ui';
42
43 /**
44 * Filter name that can enable the entire Jetpack SEO surface.
45 *
46 * The surface is available when this filter returns true or the current site's
47 * active features include {@see self::FEATURE_SLUG}. When neither is enabled,
48 * the package registers no admin menu or assets and changes nothing about the
49 * existing Jetpack UI.
50 *
51 * @var string
52 */
53 const FEATURE_FILTER = 'rsm_jetpack_seo';
54
55 /**
56 * Key under `window.JetpackScriptData` the React app reads its state from
57 * (`window.JetpackScriptData.seo`). Must match the JS-side reader in
58 * `_inc/data/get-overview.ts`.
59 */
60 const SCRIPT_DATA_KEY = 'seo';
61
62 /**
63 * Option recording whether sitemap generation is enabled.
64 *
65 * Read in place of the standalone `sitemaps` module's active state. Module-active
66 * state is filtered against the modules present on disk, so once that module is
67 * removed it would read as inactive even for sites that had it on. A one-time
68 * migration in the Jetpack plugin seeds this option from the site's existing module
69 * state and keeps it in sync while the legacy module still exists. See
70 * `Jetpack::migrate_sitemaps_module_to_seo_option()`.
71 *
72 * @var string
73 */
74 const SITEMAP_ENABLED_OPTION = 'jetpack_seo_sitemap_enabled';
75
76 /**
77 * Option recording that the user has deliberately turned the site's sitemap OFF,
78 * so WordPress core's own sitemap should be suppressed too ("off" means no sitemap
79 * at all, not a fallback to `/wp-sitemap.xml`).
80 *
81 * Set when the sitemaps module is switched off and cleared when it's switched on
82 * (see {@see self::flag_sitemap_user_disabled()} / {@see self::clear_sitemap_user_disabled()}),
83 * so it captures a deliberate off — a *transition* — rather than the ambient
84 * off-state. A site that simply never enabled the sitemap never fires the toggle,
85 * so the flag stays absent and its existing (e.g. WordPress-native) sitemap is left
86 * untouched.
87 *
88 * @var string
89 */
90 const SUPPRESS_WP_SITEMAP_OPTION = 'jetpack_seo_suppress_wp_sitemap';
91
92 /**
93 * Option recording whether canonical URLs are enabled.
94 *
95 * Read in place of the standalone `canonical-urls` module's active state. Module-active
96 * state is filtered against the modules present on disk, so once that module is
97 * removed it would read as inactive even for sites that had it on. A one-time
98 * migration in the Jetpack plugin seeds this option from the site's existing module
99 * state and keeps it in sync while the legacy module still exists. See
100 * `Jetpack::migrate_canonical_urls_module_to_seo_option()`.
101 *
102 * @var string
103 */
104 const CANONICAL_ENABLED_OPTION = 'jetpack_seo_canonical_urls_enabled';
105
106 /**
107 * Option recording whether the Jetpack SEO surface is discoverable on this site.
108 *
109 * Gates whether the SEO admin menu registers on self-hosted sites. Seeded once by the
110 * Jetpack plugin on install/upgrade: fresh installs default to visible, existing
111 * installs default to hidden and opt in via the legacy Traffic page or My Jetpack.
112 * WordPress.com (Simple + Atomic) bypasses this option entirely and is always visible.
113 * Absent until seeded, in which case self-hosted defaults to hidden (the non-disruptive
114 * default). See {@see Surface_Visibility::is_visible()}.
115 *
116 * @var string
117 */
118 const VISIBILITY_OPTION = 'jetpack_seo_surface_visible';
119
120 /**
121 * Whether the package has been initialized.
122 *
123 * @var bool
124 */
125 private static $initialized = false;
126
127 /**
128 * Initialize the package.
129 *
130 * Called from the Jetpack plugin's `late_initialization()` hook.
131 *
132 * @return void
133 */
134 public static function init() {
135 if ( self::$initialized ) {
136 return;
137 }
138 self::$initialized = true;
139
140 // Gate the entire SEO surface behind its legacy filter or per-site feature.
141 if ( ! self::is_available() ) {
142 return;
143 }
144
145 // The opt-in endpoint must be reachable even before the surface is visible, so
146 // existing self-hosted installs can switch to the new experience from the legacy
147 // Traffic page or My Jetpack (JETPACK-1700). Registered ahead of the cohort gate.
148 add_action( 'rest_api_init', array( Surface_Visibility::class, 'register_optin_route' ) );
149
150 // Expose opt-in availability to other admin surfaces (the legacy Traffic-page
151 // banner reads it via `@automattic/jetpack-script-data`). Hooked here — after the
152 // feature flag, before the cohort gate — so a still-hidden install gets the signal.
153 add_filter( 'jetpack_admin_js_script_data', array( Surface_Visibility::class, 'inject_optin_availability' ) );
154
155 // Sitemap output is a front-end concern tied to the SEO feature itself, not to
156 // whether the admin dashboard is visible — so register it here, ahead of the
157 // cohort gate. This keeps the deliberate-off behavior consistent in the two
158 // edges the surface gate would otherwise break: a site that turns the sitemap
159 // off while the dashboard is still hidden (an existing self-hosted install that
160 // hasn't opted in), and a flag set while the dashboard was visible that must
161 // stay honored if the dashboard is later hidden.
162 //
163 // Maintain the deliberate-off flag as the sitemap is toggled: these fire only on
164 // a genuine module toggle (not wpcomsh's private-site suppression, which is a
165 // filter, not a deactivation), and are registered before the toggle's REST write.
166 add_action( 'jetpack_deactivate_module_sitemaps', array( __CLASS__, 'flag_sitemap_user_disabled' ) );
167 add_action( 'jetpack_activate_module_sitemaps', array( __CLASS__, 'clear_sitemap_user_disabled' ) );
168
169 // When the user has deliberately turned the sitemap off, suppress WordPress
170 // core's own sitemap too — otherwise "off" silently falls back to core's
171 // `/wp-sitemap.xml` (and its `/sitemap.xml` → `/wp-sitemap.xml` redirect). Keyed
172 // on the deliberate-off flag, NOT the ambient off-state, so a site that never
173 // enabled the sitemap keeps whatever sitemap it already had. Runs on
174 // `plugins_loaded`, before core registers its sitemap server on `init`, so the
175 // filter is in place; with core sitemaps disabled, `/sitemap.xml` and
176 // `/wp-sitemap.xml` both return a proper 404. (When the sitemap is ON, the
177 // Jetpack sitemaps module already disables core's duplicate.)
178 if ( get_option( self::SUPPRESS_WP_SITEMAP_OPTION, false ) ) {
179 add_filter( 'wp_sitemaps_enabled', '__return_false' );
180 }
181
182 // Discoverability cohort gate: the SEO surface is auto-discoverable for fresh
183 // installs and all WordPress.com sites; existing self-hosted installs opt in via
184 // the legacy Traffic page or My Jetpack (JETPACK-1700). Until it's visible we
185 // register nothing else here and let those opt-in surfaces drive discovery.
186 if ( ! self::is_seo_surface_visible() ) {
187 return;
188 }
189
190 // The admin menu and app shell register whenever the surface is visible, even
191 // when the `seo-tools` module is inactive, so SEO stays discoverable and can be
192 // turned on from within the page itself (JETPACK-1700). When the module is off,
193 // the Overview renders only its "enable SEO tools" affordance.
194 //
195 // Priority 1: load the wp-build bundle (and define its render function)
196 // before `add_menu_item()` runs at the default priority and needs it.
197 add_action( 'admin_menu', array( Admin_Page::class, 'maybe_load_wp_build' ), 1 );
198 add_action( 'admin_menu', array( Admin_Page::class, 'add_menu_item' ), 10 );
199
200 // Read-only REST routes the dashboard hydrates its initial state from. Preloaded
201 // into the page (see Admin_Page::inject_script_data) so a normal load resolves
202 // them with no request, and fetched by the app when that preload is missing or
203 // stale — so the dashboard recovers its data instead of dead-ending. Registered
204 // whenever the surface is visible (independent of the seo-tools module, like the
205 // Overview).
206 add_action( 'rest_api_init', array( Dashboard_Data::class, 'register_rest_reads' ) );
207
208 // Keep the Overview's cached content-coverage counts honest. Hooked here rather than
209 // alongside the admin surface above because posts are written from everywhere — the
210 // block editor (REST), the classic editor, wp-cli, cron, other plugins — and the
211 // cache has to be dropped wherever that happens, not just where it's read.
212 Content_Coverage::register_invalidation();
213
214 // The settings surface only comes online once SEO tools are active — there's
215 // nothing to configure while the module is off, so we don't register its REST
216 // endpoints until then. Expose the core `blog_public` option to the REST settings
217 // endpoint so the Settings tab can save search-engine visibility via
218 // `/wp/v2/settings` (the Jetpack settings endpoint only accepts Jetpack options).
219 // Writes are still capability-gated by the core settings controller.
220 if ( self::is_seo_tools_module_active() ) {
221 // Front-end JSON-LD schema output and author profile schema fields.
222 // Intentionally NOT gated: every site keeps emitting its structured data —
223 // a plan-gated site loses the schema *settings* card (a paid control), but
224 // stripping the schema its pages already carry would hurt SEO it has today.
225 // (Finer per-type gating — e.g. sitewide LocalBusiness to paid plans on
226 // self-hosted — is a separate follow-up, tracked in the schema project.)
227 Schema_Builder::init();
228 Author_Schema_Node::init();
229
230 // GEO-tab front-end services. These are paid surfaces on WordPress.com: a
231 // plan-gated site has the GEO tab hidden from its dashboard, so it must not
232 // keep emitting their front-end output either — otherwise it would still
233 // serve /llms.txt and AI-crawler robots.txt directives it doesn't qualify
234 // for. Self-hosted is never gated, so it always registers both.
235 if ( ! self::is_gated() ) {
236 // The /llms.txt handler. Self-hooks a front-end action, so it no-ops off
237 // the front end and stays behind the same gates as the schema above.
238 Llms_Txt::init();
239 // robots.txt directives for blocked AI crawlers. Self-hooks the
240 // `robots_txt` filter, so it stays inert off the front end.
241 Ai_Crawlers::init();
242 }
243
244 add_action( 'rest_api_init', array( Dashboard_Data::class, 'register_rest_settings' ) );
245 // Package-owned route for the site-level Schema settings (see the controller).
246 add_action( 'rest_api_init', array( Schema_Settings_Controller::class, 'register_routes' ) );
247 }
248
249 /**
250 * Fires after the Jetpack SEO package is initialized.
251 *
252 * @since 0.1.0
253 */
254 do_action( 'jetpack_seo_init' );
255 }
256
257 /**
258 * Whether the Jetpack SEO product is available on this site.
259 *
260 * Keep the existing filter as an override while allowing WordPress.com to
261 * enable the product for individual sites through its feature registry.
262 *
263 * @return bool
264 */
265 public static function is_available() {
266 if ( (bool) apply_filters( self::FEATURE_FILTER, false ) ) {
267 return true;
268 }
269
270 $features = ( new Host() )->is_wpcom_simple()
271 ? Current_Plan::get_simple_site_specific_features()
272 : Current_Plan::get()['features'];
273
274 return in_array( self::FEATURE_SLUG, $features['active'] ?? array(), true );
275 }
276
277 /**
278 * Whether the Jetpack SEO surface should be discoverable (admin menu registered).
279 *
280 * @return bool
281 */
282 public static function is_seo_surface_visible() {
283 return Surface_Visibility::is_visible();
284 }
285
286 /**
287 * Whether to offer an existing install the chance to opt into the new SEO experience.
288 *
289 * @return bool
290 */
291 public static function is_optin_available() {
292 return Surface_Visibility::is_optin_available();
293 }
294
295 /**
296 * Whether the SEO dashboard is plan-gated for this site.
297 *
298 * Gating applies only on WordPress.com (Simple + Atomic): `advanced-seo` is in the
299 * FREE plan's supports list, so `Current_Plan::supports( 'advanced-seo' )` returns
300 * true on self-hosted (never gated) and hijacks to `wpcom_site_has_feature()` on
301 * WordPress.com, where it's false below the Premium plan. Mirrors the AI SEO
302 * Enhancer's plan check in {@see Dashboard_Data::get_ai_data()}.
303 *
304 * Public because {@see Admin_Page::inject_script_data()} reads it to build the
305 * dashboard's gating payload, and {@see self::init()} uses it to decide whether the
306 * GEO-tab front-end services register at all.
307 *
308 * @return bool
309 */
310 public static function is_gated() {
311 return ( new Host() )->is_wpcom_platform()
312 && ! Current_Plan::supports( 'advanced-seo' );
313 }
314
315 /**
316 * The WordPress.com Premium checkout URL for this site, used by the upsell banner
317 * shown to gated sites.
318 *
319 * Built server-side because the client doesn't have the site slug. `value_bundle`
320 * is the wpcom Premium plan slug (see the `premium` entry in
321 * `Automattic\Jetpack\Current_Plan`), and `Status::get_site_suffix()` resolves the
322 * Calypso site slug (via `WPCOM_Masterbar::get_calypso_site_slug()` on wpcom).
323 *
324 * @return string
325 */
326 public static function get_upsell_url() {
327 $site_slug = ( new Status() )->get_site_suffix();
328
329 return sprintf( 'https://wordpress.com/checkout/%s/value_bundle', $site_slug );
330 }
331
332 /**
333 * Whether the `seo-tools` Jetpack module is currently active.
334 *
335 * @return bool
336 */
337 private static function is_seo_tools_module_active() {
338 if ( ! class_exists( 'Automattic\\Jetpack\\Modules' ) ) {
339 return false;
340 }
341 return ( new Modules() )->is_active( 'seo-tools' );
342 }
343
344 /**
345 * Record that the user has turned the sitemap off, so WordPress core's own sitemap
346 * is suppressed too. Hooked to the sitemaps module's deactivation, which fires only
347 * on a real toggle from a surface (the SEO Settings tab, the legacy Traffic page, or
348 * WP-CLI) — not wpcomsh's private-site suppression, which is a filter on the
349 * active-modules read rather than a deactivation.
350 *
351 * @return void
352 */
353 public static function flag_sitemap_user_disabled() {
354 update_option( self::SUPPRESS_WP_SITEMAP_OPTION, true );
355 }
356
357 /**
358 * Clear the deliberate-off flag when the sitemap is turned back on — the Jetpack
359 * sitemaps module then serves `/sitemap.xml` and suppresses core's duplicate itself.
360 *
361 * @return void
362 */
363 public static function clear_sitemap_user_disabled() {
364 delete_option( self::SUPPRESS_WP_SITEMAP_OPTION );
365 }
366 }
367