PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.1-a.5
16.2-a.3 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-dashboard-data.php
jetpack / jetpack_vendor / automattic / jetpack-seo / src Last commit date
class-admin-page.php 4 weeks ago class-ai-crawlers.php 4 weeks ago class-author-schema-node.php 1 month ago class-breadcrumb-schema-node.php 1 month ago class-content-coverage.php 4 weeks ago class-dashboard-data.php 4 weeks ago class-initializer.php 4 weeks ago class-llms-txt.php 4 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 4 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 4 weeks ago class-website-schema-node.php 1 month ago
class-dashboard-data.php
415 lines
1 <?php
2 /**
3 * The SEO dashboard's read-only REST routes and the payload builders behind
4 * them — one route per data-backed tab, preloaded onto the page so a normal
5 * load resolves them with no request.
6 *
7 * @package automattic/jetpack-seo-package
8 */
9
10 namespace Automattic\Jetpack\SEO;
11
12 use Automattic\Jetpack\Modules;
13 use Jetpack_SEO_Titles;
14 use Jetpack_SEO_Utils;
15 use Jetpack_Sitemap_Librarian;
16
17 /**
18 * Registers the dashboard's read routes and builds their payloads.
19 */
20 class Dashboard_Data {
21
22 /**
23 * Map of read-only dashboard routes: tab slug => data-builder callable. The
24 * single source of truth for both the registered routes and the paths
25 * preloaded onto the page, so the two can't drift.
26 *
27 * @return array<string, callable>
28 */
29 private static function rest_reads() {
30 return array(
31 'overview' => array( __CLASS__, 'get_overview_data' ),
32 'settings' => array( __CLASS__, 'get_settings_data' ),
33 'ai' => array( __CLASS__, 'get_ai_data' ),
34 'content' => array( __CLASS__, 'get_content_data' ),
35 );
36 }
37
38 /**
39 * REST paths the dashboard reads its initial state from, preloaded into the
40 * page (see {@see Admin_Page::inject_script_data()}) and fetched by the app.
41 *
42 * @return string[]
43 */
44 public static function rest_read_paths() {
45 return array_map(
46 static function ( $slug ) {
47 return '/jetpack/v4/seo/' . $slug;
48 },
49 array_keys( self::rest_reads() )
50 );
51 }
52
53 /**
54 * Register the read-only REST routes the dashboard hydrates from — one per
55 * data-backed tab, each returning the same builder payload previously injected
56 * synchronously onto the page. Read-only and gated to the page's own
57 * `manage_options`; writes still go through their existing endpoints.
58 *
59 * @return void
60 */
61 public static function register_rest_reads() {
62 foreach ( self::rest_reads() as $slug => $builder ) {
63 register_rest_route(
64 'jetpack/v4',
65 '/seo/' . $slug,
66 array(
67 'methods' => \WP_REST_Server::READABLE,
68 'callback' => static function () use ( $builder ) {
69 return rest_ensure_response( call_user_func( $builder ) );
70 },
71 'permission_callback' => array( __CLASS__, 'reads_permission_check' ),
72 )
73 );
74 }
75 }
76
77 /**
78 * Capability gate for the dashboard's read routes — the same `manage_options`
79 * the SEO admin page itself requires.
80 *
81 * @return bool
82 */
83 public static function reads_permission_check() {
84 return current_user_can( 'manage_options' );
85 }
86
87 /**
88 * Expose the core `blog_public` option to the REST settings endpoint.
89 *
90 * Search-engine visibility is a WordPress core option, not a Jetpack one,
91 * so the Settings tab saves it through `/wp/v2/settings` — which only
92 * round-trips settings registered with `show_in_rest`. The core settings
93 * controller enforces the `manage_options` capability on writes.
94 *
95 * @return void
96 */
97 public static function register_rest_settings() {
98 register_setting(
99 'reading',
100 'blog_public',
101 array(
102 'show_in_rest' => true,
103 'type' => 'integer',
104 'default' => 1,
105 )
106 );
107 }
108
109 /**
110 * Build the aggregated Overview state the dashboard renders.
111 *
112 * @return array
113 */
114 public static function get_overview_data() {
115 $modules = new Modules();
116 // @phan-suppress-next-line PhanUndeclaredClassMethod -- Jetpack_SEO_Utils lives in plugins/jetpack and is guarded by class_exists.
117 $seo_enabled = class_exists( 'Jetpack_SEO_Utils' ) && Jetpack_SEO_Utils::is_enabled_jetpack_seo();
118
119 $codes = get_option( 'verification_services_codes', array() );
120 if ( ! is_array( $codes ) ) {
121 $codes = array();
122 }
123
124 return array(
125 'site_visibility' => array(
126 'search_engines_visible' => (int) get_option( 'blog_public', 1 ) === 1,
127 // Read the durable SEO option (seeded/synced from the `sitemaps` module
128 // by the Jetpack plugin) so the state survives the module's removal. The
129 // reachable sitemap URL + "View" link live on the Settings tab.
130 'sitemap_active' => self::is_sitemap_enabled( $modules ),
131 'seo_tools_active' => $modules->is_active( 'seo-tools' ),
132 ),
133 // Per-service booleans (a code is set or not) for the Overview's
134 // Site verification card.
135 'site_verification' => array(
136 'google' => ! empty( $codes['google'] ),
137 'bing' => ! empty( $codes['bing'] ),
138 'pinterest' => ! empty( $codes['pinterest'] ),
139 'yandex' => ! empty( $codes['yandex'] ),
140 'facebook' => ! empty( $codes['facebook'] ),
141 ),
142 'content_coverage' => Content_Coverage::get(),
143 'plan' => array(
144 'seo_enabled_for_site' => $seo_enabled,
145 ),
146 );
147 }
148
149 /**
150 * Build the editable Settings state the Settings tab hydrates from.
151 *
152 * Read-only bootstrap only. Most writes go through the existing
153 * `/jetpack/v4/settings` REST endpoint, which already validates and
154 * sanitizes those flat fields. Nested Schema writes use the package's
155 * schema-settings route; bootstrapping them here keeps the Settings UI
156 * hydrated without a second request.
157 *
158 * @return array
159 */
160 public static function get_settings_data() {
161 $modules = new Modules();
162
163 // @phan-suppress-next-line PhanUndeclaredClassMethod -- Jetpack_SEO_Titles lives in plugins/jetpack and is guarded by class_exists.
164 $title_formats = class_exists( 'Jetpack_SEO_Titles' ) ? Jetpack_SEO_Titles::get_custom_title_formats() : array();
165 // @phan-suppress-next-line PhanUndeclaredClassMethod -- Jetpack_SEO_Utils lives in plugins/jetpack and is guarded by class_exists.
166 $front_page_desc = class_exists( 'Jetpack_SEO_Utils' ) ? Jetpack_SEO_Utils::get_front_page_meta_description() : '';
167
168 // A site that set a front-page description back when it was free for all
169 // WordPress.com Simple sites keeps editing it, even when otherwise plan-gated:
170 // the value stays live and `Jetpack_SEO_Utils` still reads/writes it via the
171 // legacy option. The gated Settings uses this to keep that one field editable.
172 // @phan-suppress-next-line PhanUndeclaredClassMethod -- Jetpack_SEO_Utils lives in plugins/jetpack and is guarded by class_exists.
173 $has_legacy_front_page_meta = class_exists( 'Jetpack_SEO_Utils' ) && (bool) Jetpack_SEO_Utils::has_legacy_front_page_meta();
174
175 $codes = get_option( 'verification_services_codes', array() );
176 if ( ! is_array( $codes ) ) {
177 $codes = array();
178 }
179
180 $sitemap_active = self::is_sitemap_enabled( $modules );
181
182 return array(
183 'search_engines_visible' => (int) get_option( 'blog_public', 1 ) === 1,
184 // Read the durable SEO option (seeded/synced from the `sitemaps` module
185 // by the Jetpack plugin) so the state survives the module's removal.
186 'sitemap_active' => $sitemap_active,
187 // Empty until the sitemap is genuinely reachable, so the Settings tab can
188 // link to it only once it won't 404 (it's built by cron after activation).
189 'sitemap_url' => self::get_reachable_sitemap_url( $sitemap_active ),
190 // Read the durable SEO option (seeded/synced from the `canonical-urls` module
191 // by the Jetpack plugin) so the state survives the module's removal.
192 'canonical_active' => self::is_canonical_enabled( $modules ),
193 // Cast to object so an empty format set serializes as `{}`, not `[]`.
194 'title_formats' => (object) $title_formats,
195 'front_page_description' => (string) $front_page_desc,
196 'has_legacy_front_page_meta' => $has_legacy_front_page_meta,
197 'verification' => array(
198 'google' => isset( $codes['google'] ) ? (string) $codes['google'] : '',
199 'bing' => isset( $codes['bing'] ) ? (string) $codes['bing'] : '',
200 'pinterest' => isset( $codes['pinterest'] ) ? (string) $codes['pinterest'] : '',
201 'yandex' => isset( $codes['yandex'] ) ? (string) $codes['yandex'] : '',
202 'facebook' => isset( $codes['facebook'] ) ? (string) $codes['facebook'] : '',
203 ),
204 'schema' => Schema_Settings::get_editable(),
205 );
206 }
207
208 /**
209 * Build the Google site-verification state for the Settings tab.
210 *
211 * The Settings verification card lets a connected user verify with Google via a
212 * WordPress.com keyring OAuth popup (in addition to pasting a meta-tag code). This
213 * bootstraps the keyring connect URL and whether the current user is connected —
214 * the live verified status is fetched client-side from `/jetpack/v4/verify-site/google`
215 * (a wpcom round-trip we don't want to make on every page load).
216 *
217 * Both `Keyring_Helper` (Publicize package) and the connection `Manager` are provided
218 * by the host Jetpack plugin, so they're guarded with `class_exists` like the
219 * `Jetpack_SEO_*` helpers. On a disconnected self-hosted site `is_connected` is false
220 * and the UI falls back to manual code entry only.
221 *
222 * @return array
223 */
224 public static function get_google_verify_data() {
225 $connect_url = '';
226 if ( class_exists( 'Automattic\\Jetpack\\Publicize\\Keyring_Helper' ) ) {
227 // @phan-suppress-next-line PhanUndeclaredClassMethod -- guarded; Publicize package is provided by the host plugin.
228 $connect_url = (string) \Automattic\Jetpack\Publicize\Keyring_Helper::connect_url( 'google_site_verification', 'other' );
229 }
230
231 $is_connected = false;
232 if ( class_exists( 'Automattic\\Jetpack\\Connection\\Manager' ) ) {
233 $is_connected = ( new \Automattic\Jetpack\Connection\Manager() )->is_user_connected();
234 }
235
236 return array(
237 'connect_url' => $connect_url,
238 'is_connected' => (bool) $is_connected,
239 );
240 }
241
242 /**
243 * Build the AI tab's initial state.
244 *
245 * The AI SEO Enhancer auto-generates SEO titles/descriptions/alt-text in the
246 * editor (the generation itself is wpcom/AI-Assistant side); this exposes only
247 * its persisted on/off toggle and whether it's available. Availability mirrors
248 * the legacy Traffic page: the `ai_seo_enhancer_enabled` feature filter must be
249 * on (it still depends on AI being available) AND the site's plan must support
250 * the `ai-seo-enhancer` feature. The toggle writes through the existing
251 * `/jetpack/v4/settings` endpoint (`ai_seo_enhancer_enabled`).
252 *
253 * @return array
254 */
255 public static function get_ai_data() {
256 $filter_on = (bool) apply_filters( 'ai_seo_enhancer_enabled', true );
257
258 // Current_Plan comes from the jetpack-plans package (a dependency of this
259 // package since the plan-gating work), so it's always available here; the
260 // class_exists guard is kept as belt-and-suspenders for older bundled snapshots.
261 $plan_supports = class_exists( 'Automattic\\Jetpack\\Current_Plan' )
262 && \Automattic\Jetpack\Current_Plan::supports( 'ai-seo-enhancer' );
263
264 return array(
265 'enhancer' => array(
266 'available' => $filter_on && $plan_supports,
267 'enabled' => (bool) get_option( 'ai_seo_enhancer_enabled', false ),
268 ),
269 'llmsTxt' => array(
270 'enabled' => Llms_Txt::is_enabled(),
271 'url' => home_url( '/llms.txt' ),
272 'canServe' => Llms_Txt::can_serve(),
273 ),
274 'crawlers' => Ai_Crawlers::get_bootstrap_data(),
275 );
276 }
277
278 /**
279 * Build the supported post type options for the Content tab.
280 *
281 * @return array{post_types:array<int,array{slug:string,label:string}>}
282 */
283 public static function get_content_data() {
284 return array(
285 'post_types' => Post_Types::get_supported_content_type_options(),
286 );
287 }
288
289 /**
290 * Site identity used to render the homepage search/social previews on the
291 * Settings tab: title, tagline, URL, and representative images. The front-page
292 * description that completes the preview is read from the Settings form
293 * (it's editable there), not bootstrapped here.
294 *
295 * @return array
296 */
297 public static function get_site_data() {
298 $icon_url = (string) get_site_icon_url();
299
300 $logo_id = (int) get_theme_mod( 'custom_logo' );
301 $logo_url = $logo_id ? (string) wp_get_attachment_image_url( $logo_id, 'full' ) : '';
302
303 return array(
304 'title' => (string) get_bloginfo( 'name' ),
305 'tagline' => (string) get_bloginfo( 'description' ),
306 'url' => (string) home_url(),
307 'icon' => $icon_url,
308 'image' => $logo_url ? $logo_url : $icon_url,
309 );
310 }
311
312 /**
313 * Whether sitemap generation is enabled.
314 *
315 * Reads the durable {@see Initializer::SITEMAP_ENABLED_OPTION} flag. The default is only
316 * used when the option is absent (for example before the Jetpack plugin's migration
317 * has run on a freshly upgraded site), in which case it falls back to the live
318 * `sitemaps` module state so behavior is unchanged in that gap.
319 *
320 * @param Modules $modules Modules instance to read live module state from.
321 * @return bool
322 */
323 private static function is_sitemap_enabled( Modules $modules ) {
324 $enabled = get_option( Initializer::SITEMAP_ENABLED_OPTION, null );
325
326 // Only fall back to the live module state when the durable option is absent.
327 // Passing it as get_option()'s default would evaluate it on every call, since
328 // PHP resolves function arguments eagerly even when the option exists.
329 if ( null === $enabled ) {
330 $enabled = $modules->is_active( 'sitemaps' );
331 }
332
333 return (bool) $enabled;
334 }
335
336 /**
337 * Whether canonical URLs are enabled.
338 *
339 * Reads the durable {@see Initializer::CANONICAL_ENABLED_OPTION} flag. The default is only
340 * used when the option is absent (for example before the Jetpack plugin's migration
341 * has run on a freshly upgraded site), in which case it falls back to the live
342 * `canonical-urls` module state so behavior is unchanged in that gap.
343 *
344 * @param Modules $modules Modules instance to read live module state from.
345 * @return bool
346 */
347 private static function is_canonical_enabled( Modules $modules ) {
348 $enabled = get_option( Initializer::CANONICAL_ENABLED_OPTION, null );
349
350 // Only fall back to the live module state when the durable option is absent.
351 // Passing it as get_option()'s default would evaluate it on every call, since
352 // PHP resolves function arguments eagerly even when the option exists.
353 if ( null === $enabled ) {
354 $enabled = $modules->is_active( 'canonical-urls' );
355 }
356
357 return (bool) $enabled;
358 }
359
360 /**
361 * The public URL of the generated XML sitemap, or an empty string when none
362 * is currently reachable.
363 *
364 * A sitemap is only reachable when generation is enabled, the site is public
365 * (Jetpack does not load the Sitemaps module on sites that discourage search
366 * engines), and the master sitemap has actually been generated — the Jetpack
367 * plugin builds it via cron 1–15 minutes after activation, so the URL 404s
368 * until then. Callers treat an empty string as "not yet reachable" and skip
369 * linking to it.
370 *
371 * {@see Jetpack_Sitemap_Librarian} and jetpack_sitemap_uri() live in the
372 * Jetpack plugin's Sitemaps module (loaded only for an active module on a
373 * public site), so both are guarded; in the package-only context they are
374 * absent and the sitemap is reported as not reachable.
375 *
376 * @param bool $sitemap_active Whether sitemap generation is enabled.
377 * @return string The sitemap URL, or '' when not reachable.
378 */
379 private static function get_reachable_sitemap_url( $sitemap_active ) {
380 // Jetpack only serves sitemaps when generation is on and the site is public.
381 if ( ! $sitemap_active || (int) get_option( 'blog_public', 1 ) !== 1 ) {
382 return '';
383 }
384
385 // The Sitemaps module (the librarian class, the `JP_MASTER_SITEMAP_TYPE`
386 // constant, and the `jp_sitemap_filename()` / `jetpack_sitemap_uri()`
387 // helpers) all live together in plugins/jetpack and load as a unit, so this
388 // single guard covers every symbol used below.
389 if (
390 ! class_exists( 'Jetpack_Sitemap_Librarian' )
391 || ! defined( 'JP_MASTER_SITEMAP_TYPE' )
392 || ! function_exists( 'jp_sitemap_filename' )
393 || ! function_exists( 'jetpack_sitemap_uri' )
394 ) {
395 return '';
396 }
397
398 // The master sitemap is stored as a post once the cron generation run
399 // completes; until then there is nothing to link to.
400 // `jp_sitemap_filename( JP_MASTER_SITEMAP_TYPE )` is the master file name
401 // ('sitemap.xml'); inlined so this stays one (untestable-in-package) line.
402 // @phan-suppress-next-line PhanUndeclaredFunction,PhanUndeclaredClassMethod -- guarded above; symbols live in plugins/jetpack.
403 $master = ( new Jetpack_Sitemap_Librarian() )->read_sitemap_data( jp_sitemap_filename( JP_MASTER_SITEMAP_TYPE ), JP_MASTER_SITEMAP_TYPE );
404 if ( null === $master ) {
405 return '';
406 }
407
408 // esc_url_raw (not esc_url): the value is transported via script data and
409 // rendered by React, so it must not be HTML-entity-encoded (e.g. the
410 // plain-permalink `?jetpack-sitemap=` form keeps its raw `&`).
411 // @phan-suppress-next-line PhanUndeclaredFunction -- jp_sitemap_filename()/jetpack_sitemap_uri() live in plugins/jetpack, guarded by function_exists.
412 return esc_url_raw( (string) jetpack_sitemap_uri( jp_sitemap_filename( JP_MASTER_SITEMAP_TYPE ) ) );
413 }
414 }
415