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-newsletter / src / class-settings.php

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

708 lines 24.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A class that adds a newsletter settings screen to wp-admin.
4 *
5 * @package automattic/jetpack-newsletter
6 */
7
8 namespace Automattic\Jetpack\Newsletter;
9
10 use Automattic\Jetpack\Admin_UI\Admin_Menu;
11 use Automattic\Jetpack\Assets;
12 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
13 use Automattic\Jetpack\Feature_Flags\Feature_Flags;
14 use Automattic\Jetpack\Modules;
15 use Automattic\Jetpack\Redirect;
16 use Automattic\Jetpack\Status;
17 use Automattic\Jetpack\Status\Host;
18 use Jetpack_Tracks_Client;
19
20 /**
21 * A class responsible for adding a newsletter settings screen to wp-admin.
22 */
23 class Settings {
24
25 const PACKAGE_VERSION = '0.17.2';
26
27 const ADMIN_PAGE_SLUG = 'jetpack-newsletter';
28
29 /**
30 * Slug of the retired Subscribers page, kept only to redirect stale bookmarks.
31 */
32 const RETIRED_SUBSCRIBERS_PAGE_SLUG = 'jetpack-subscribers';
33
34 /**
35 * Filter name that gates the wp-build–based dashboard.
36 *
37 * When this filter returns true, "Jetpack > Newsletter" renders the new
38 * wp-build dashboard instead of the legacy Newsletter Settings React app.
39 */
40 const MODERNIZATION_FILTER = 'rsm_jetpack_ui_modernization_newsletter';
41
42 /**
43 * Feature flag for the Newsletter Overview tab.
44 *
45 * Also gates the Stats tab and its REST endpoints: Stats is a temporary,
46 * standalone page that eases development of the Overview dashboard's
47 * eventual stats section -- it ships and retires with the same flag rather
48 * than getting an independent one.
49 */
50 const OVERVIEW_FEATURE_FLAG = 'newsletter-overview';
51
52 /**
53 * Whether the class has been initialized
54 *
55 * @var boolean
56 */
57 private static $initialized = false;
58
59 /**
60 * The screen ID alias_screen_id_for_wp_build() replaced, until it is restored.
61 *
62 * @var string|null
63 */
64 private static $wp_build_original_screen_id = null;
65
66 /**
67 * Register Newsletter feature flags.
68 *
69 * @return void
70 */
71 public static function register_feature_flags() {
72 Feature_Flags::register(
73 self::OVERVIEW_FEATURE_FLAG,
74 array(
75 'default' => false,
76 'description' => 'Enable the Newsletter Overview and Stats tabs.',
77 'owner' => 'jetpack-newsletter',
78 )
79 );
80
81 Subscriber_Stats_Controller::register();
82 }
83
84 /**
85 * Init Newsletter Settings if it wasn't already.
86 */
87 public static function init() {
88 self::register_feature_flags();
89
90 if ( ! self::$initialized ) {
91 self::$initialized = true;
92 ( new self() )->init_hooks();
93 }
94 }
95
96 /**
97 * Check if the subscriptions module is active.
98 *
99 * @return bool
100 */
101 private function is_subscriptions_active() {
102 return ( new Modules() )->is_active( 'subscriptions' );
103 }
104
105 /**
106 * Determine whether to show the Newsletter menu item.
107 * When true, shown regardless of subscriptions module state.
108 *
109 * @return bool
110 */
111 private function should_show_menu_item() {
112 /**
113 * Filter to control Newsletter menu item visibility.
114 * Defaults to true.
115 *
116 * @since 0.6.0
117 * @param bool $show Whether to show the menu item.
118 */
119 return apply_filters(
120 'jetpack_show_newsletter_menu_item',
121 true
122 );
123 }
124
125 /**
126 * Subscribe to necessary hooks.
127 */
128 public function init_hooks() {
129 // Priority 1 so this runs before the menu is built and the request is denied.
130 add_action( 'admin_menu', array( __CLASS__, 'redirect_retired_subscribers_page' ), 1 );
131
132 // Add the Reading settings notice as long as subscriptions are active.
133 if ( $this->is_subscriptions_active() ) {
134 add_action( 'admin_init', array( $this, 'add_reading_page_notice' ) );
135 }
136
137 // Hijack the config URLs to point to our settings page.
138 // Priority 20 to override the default URL set in subscriptions.php.
139 add_filter(
140 'jetpack_module_configuration_url_subscriptions',
141 function () {
142 return Urls::get_newsletter_settings_url();
143 },
144 20
145 );
146
147 // Defer wp-build loading to admin_menu (priority 1) on every host. The
148 // modernization filter — which third parties typically register from a
149 // plugins_loaded callback — needs to have been applied before we read it,
150 // and the wp-build render function needs to be defined before any menu
151 // callback runs (priority 999 on standalone Jetpack, priority 999999 on
152 // wpcom Simple via wpcom-admin-menu.php's call to add_wp_admin_submenu).
153 // Settings::init() runs synchronously from load-jetpack.php at
154 // plugin-file-include time — before any plugins_loaded callback fires —
155 // so an inline check here would always see the unfiltered default.
156 add_action( 'admin_menu', array( __CLASS__, 'maybe_load_wp_build' ), 1 );
157
158 // Priority 20 runs after add_script_data(), which replaces the whole `newsletter` key on the Newsletter page.
159 add_filter( 'jetpack_admin_js_script_data', array( __CLASS__, 'add_subscribers_url_script_data' ), 20 );
160
161 $host = new Host();
162
163 // On wpcom Simple, the Jetpack menu is created at priority 999999 by wpcom-admin-menu.php,
164 // which will call add_wp_admin_submenu() directly. Skip adding the menu here to avoid
165 // trying to add a submenu before the parent menu exists.
166 if ( $host->is_wpcom_simple() ) {
167 return;
168 }
169
170 // Add admin menu item.
171 // Use priority 999 to ensure menu items are queued BEFORE Admin_Menu::admin_menu_hook_callback
172 // runs at priority 1000 to process all queued items.
173 add_action( 'admin_menu', array( $this, 'add_wp_admin_menu' ), 999 );
174 }
175
176 /**
177 * Send the retired Subscribers page to Newsletter, which absorbed it.
178 *
179 * The slug was live for about three months, so it is still in browser histories,
180 * where it would otherwise hit WordPress's generic "not allowed to access this
181 * page" and read as a permissions error rather than a move.
182 *
183 * @return void
184 */
185 public static function redirect_retired_subscribers_page() {
186 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
187 $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
188
189 if ( self::RETIRED_SUBSCRIBERS_PAGE_SLUG !== $page || ! current_user_can( 'manage_options' ) ) {
190 return;
191 }
192
193 wp_safe_redirect( admin_url( 'admin.php?page=' . self::ADMIN_PAGE_SLUG ) );
194 exit( 0 );
195 }
196
197 /**
198 * Load wp-build for the Newsletter admin page when modernization is enabled.
199 *
200 * Hooked to `admin_menu` priority 1 so the modernization filter has been
201 * registered by any opt-in code (mu-plugins, snippets, themes) before we
202 * read it, and so the wp-build render function and enqueue hook are in
203 * place before `add_wp_admin_menu` runs at priority 999.
204 *
205 * @return void
206 */
207 public static function maybe_load_wp_build() {
208 if ( ! self::is_modernized() || ! self::is_newsletter_admin_request() ) {
209 return;
210 }
211
212 self::load_wp_build_with_screen_alias();
213
214 // wp-build registers standalone modules (e.g. the init module) on
215 // wp_default_scripts, which has already fired by admin_menu. Register them
216 // directly so the init module makes it into the import map.
217 if ( function_exists( 'jetpack_newsletter_register_script_modules' ) ) {
218 jetpack_newsletter_register_script_modules(); // @phan-suppress-current-line PhanUndeclaredFunction -- Checked with function_exists(); defined in the generated build/modules.php, which Phan excludes.
219 }
220 }
221
222 /**
223 * Add the newsletter settings submenu to the Jetpack menu.
224 *
225 * Note: This method is NOT called on wpcom Simple sites. Simple sites use
226 * add_wp_admin_submenu() called from wpcom-admin-menu.php instead.
227 */
228 public function add_wp_admin_menu() {
229 // On sites using Jetpack, only show the menu if the site is connected.
230 if ( ! ( new Connection_Manager() )->is_connected() ) {
231 return;
232 }
233
234 // On the modernized dashboard, the Newsletter screen is only useful when the
235 // subscriptions module is active, so skip registering the menu entirely when it
236 // is off. Gated on the modernization flag to leave legacy behavior unchanged.
237 if ( self::is_modernized() && ! $this->is_subscriptions_active() ) {
238 return;
239 }
240
241 $host = new Host();
242
243 // should_show_menu_item() controls visibility of the menu item.
244 $show_menu = $this->should_show_menu_item();
245 $parent_slug = $show_menu ? 'jetpack' : '';
246
247 // On Atomic, use add_submenu_page. On standalone Jetpack, use Admin_Menu when showing in menu.
248 $use_jetpack_menu = ! $host->is_woa_site() && $show_menu;
249
250 $callback = self::is_modernized() && function_exists( 'jetpack_newsletter_jetpack_newsletter_dashboard_wp_admin_render_page' )
251 ? 'jetpack_newsletter_jetpack_newsletter_dashboard_wp_admin_render_page'
252 : array( $this, 'render' );
253
254 // Register menu item.
255 if ( $use_jetpack_menu ) {
256 $page_suffix = Admin_Menu::add_menu(
257 /** "Newsletter" is a product name, do not translate. */
258 'Newsletter',
259 'Newsletter',
260 'manage_options',
261 'jetpack-newsletter',
262 $callback,
263 null,
264 array(
265 'product' => 'newsletter',
266 'key' => 'jetpack-newsletter',
267 )
268 );
269 } else {
270 $page_suffix = add_submenu_page(
271 $parent_slug,
272 /** "Newsletter" is a product name, do not translate. */
273 'Newsletter',
274 'Newsletter',
275 'manage_options',
276 'jetpack-newsletter',
277 $callback
278 );
279 }
280
281 if ( $page_suffix ) {
282 add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
283 }
284 }
285
286 /**
287 * Add the newsletter settings submenu directly under the Jetpack menu.
288 *
289 * This method is called from wpcom-admin-menu.php on Simple sites at late priority
290 * (999999) when the Jetpack menu already exists.
291 */
292 public function add_wp_admin_submenu() {
293 // On the modernized dashboard, the Newsletter screen is only useful when the
294 // subscriptions module is active, so skip registering the menu entirely when it
295 // is off. Gated on the modernization flag to leave legacy behavior unchanged.
296 if ( self::is_modernized() && ! $this->is_subscriptions_active() ) {
297 return;
298 }
299
300 $parent_slug = $this->should_show_menu_item() ? 'jetpack' : '';
301 $callback = self::is_modernized() && function_exists( 'jetpack_newsletter_jetpack_newsletter_dashboard_wp_admin_render_page' )
302 ? 'jetpack_newsletter_jetpack_newsletter_dashboard_wp_admin_render_page'
303 : array( $this, 'render' );
304 $page_suffix = add_submenu_page(
305 $parent_slug,
306 /** "Newsletter" is a product name, do not translate. */
307 'Newsletter',
308 'Newsletter',
309 'manage_options',
310 'jetpack-newsletter',
311 $callback
312 );
313
314 if ( $page_suffix ) {
315 add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
316 }
317 }
318
319 /**
320 * Admin init actions.
321 */
322 public function admin_init() {
323 add_filter( 'jetpack_admin_js_script_data', array( $this, 'add_script_data' ) );
324 add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) );
325 }
326
327 /**
328 * Add newsletter-specific data to the global JetpackScriptData object.
329 *
330 * @param array $data The existing script data.
331 * @return array The modified script data.
332 */
333 public function add_script_data( $data ) {
334 $current_user = wp_get_current_user();
335 $theme = wp_get_theme();
336
337 $host = new Host();
338 $status = new Status();
339 $site_suffix = $status->get_site_suffix();
340 $blog_id = (int) $host->get_wpcom_site_id();
341 $is_wpcom = $host->is_wpcom_platform();
342 $is_block_theme = wp_is_block_theme();
343 $setup_payment_plan_url = ( $is_wpcom ? 'https://wordpress.com/earn/payments/' : 'https://cloud.jetpack.com/monetize/payments/' ) . $site_suffix;
344
345 $wp_admin_subscriber_management_enabled = self::is_wp_admin_subscriber_management_enabled();
346
347 // Populate blog_id which is needed for API calls on Simple sites.
348 $data['site']['wpcom']['blog_id'] = $blog_id;
349
350 // Add newsletter-specific data.
351 // Note: Common data like admin_url, rest_nonce, rest_root, title, is_wpcom_platform,
352 // and user.current_user.display_name are already provided by Script_Data.
353 $data['newsletter'] = array(
354 'isBlockTheme' => $is_block_theme,
355 'themeStylesheet' => $theme->get_stylesheet(),
356 'email' => $current_user->user_email,
357 'gravatar' => get_avatar_url( $current_user->ID ),
358 'dateExample' => gmdate( get_option( 'date_format' ), time() ),
359 'subscriberManagementUrl' => $this->get_subscriber_management_url( $wp_admin_subscriber_management_enabled, $is_wpcom, $site_suffix, $blog_id ),
360 'subscriberManagementEnabled' => (bool) $wp_admin_subscriber_management_enabled,
361 'overviewEnabled' => Feature_Flags::is_enabled( self::OVERVIEW_FEATURE_FLAG ),
362 'isSubscriptionSiteEditSupported' => $is_block_theme,
363 'setupPaymentPlansUrl' => $setup_payment_plan_url,
364 'isSitePublic' => ! $status->is_private_site() && ! $status->is_coming_soon(),
365 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
366 );
367
368 return $data;
369 }
370
371 /**
372 * Load the admin scripts.
373 */
374 public function load_admin_scripts() {
375 // This callback is registered via `admin_enqueue_scripts` from `admin_init`,
376 // which itself fires on `load-{$page_suffix}` in `add_wp_admin_menu()` — so it
377 // only fires on the Newsletter admin page; no need to re-check the page here.
378 // The Tracks transport is required on both surfaces — `analytics.initialize`
379 // only queues events into `window._tkq`; without `jp-tracks` loaded, no
380 // pixel.gif requests fire and the queue grows forever.
381 wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
382
383 if ( self::is_modernized() ) {
384 // The i18n loader is registered on every admin page by jetpack-assets but
385 // only enqueued when depended on; the esbuild bundles don't pull it in.
386 // Enqueue it so the wp-build dashboard's init module can download its JS
387 // translation catalogs.
388 if ( wp_script_is( 'wp-jp-i18n-loader', 'registered' ) ) {
389 wp_enqueue_script( 'wp-jp-i18n-loader' );
390 }
391
392 // wp-build manages the rest of its enqueue pipeline. The legacy
393 // newsletter script and JetpackScriptData are intentionally skipped
394 // for the wp-build dashboard.
395 return;
396 }
397
398 // The legacy bundle imports `@wordpress/ui`, which reaches
399 // `@wordpress/theme` and so lists `wp-theme` in its generated asset file.
400 // Core does not register that handle, and `load_wp_build()` — the only
401 // other caller — runs on the modernized path alone. Without this,
402 // WordPress silently drops the script over the unregistered dependency
403 // and the page renders blank. Request only the handles this bundle needs:
404 // `wp-theme` (Core never registers it), `wp-private-apis` (so the polyfill
405 // can replace Core's incomplete allowlist on older WP) and `wp-rich-text`
406 // (`@wordpress/ui` also reaches `@wordpress/dataviews`, whose dataform
407 // controls unlock rich-text's `privateApis` at module scope; WP 6.9 exports
408 // none, which throws "Cannot unlock an undefined object"). We leave out
409 // `wp-notices` so the polyfill's force-replacement never touches it.
410 if ( class_exists( \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::class ) ) {
411 \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::register(
412 'jetpack-newsletter',
413 array( 'wp-theme', 'wp-private-apis', 'wp-rich-text' )
414 );
415 }
416
417 Assets::register_script(
418 'jetpack-newsletter',
419 '../build/newsletter.js',
420 __FILE__,
421 array(
422 'in_footer' => true,
423 'textdomain' => 'jetpack-newsletter',
424 'enqueue' => true,
425 'dependencies' => array( 'jetpack-script-data' ),
426 )
427 );
428 }
429
430 /**
431 * Get the subscriber management URL based on site type and filter settings.
432 *
433 * - If jetpack_wp_admin_subscriber_management_enabled filter is true: wp-admin subscribers page
434 * - If filter is false AND wpcom site: wordpress.com/subscribers/$domain
435 * - If filter is false AND Jetpack site: jetpack.com redirect URL
436 *
437 * @param bool $wp_admin_enabled Whether wp-admin subscriber management is enabled.
438 * @param bool $is_wpcom Whether this is a WordPress.com site.
439 * @param string $site_suffix The Calypso site suffix (home host, slashes as `::`).
440 * @param int $blog_id The blog ID.
441 * @return string The subscriber management URL.
442 */
443 private function get_subscriber_management_url( $wp_admin_enabled, $is_wpcom, $site_suffix, $blog_id ) {
444 // If wp-admin subscriber management is enabled, use the wp-admin page.
445 if ( $wp_admin_enabled ) {
446 return admin_url( 'admin.php?page=subscribers' );
447 }
448
449 // For wpcom sites, use the wordpress.com URL.
450 if ( $is_wpcom ) {
451 return 'https://wordpress.com/subscribers/' . $site_suffix;
452 }
453
454 // For Jetpack sites, use the jetpack.com redirect URL.
455 $site_id = $blog_id ? (int) $blog_id : Connection_Manager::get_site_id( true );
456 $args = ( ! empty( $site_id ) )
457 ? array( 'site' => $site_id )
458 : array();
459
460 return Redirect::get_url(
461 'jetpack-settings-jetpack-manage-subscribers',
462 $args
463 );
464 }
465
466 /**
467 * Render the newsletter settings page.
468 */
469 public function render() {
470 ?>
471 <div id="newsletter-settings-root"></div>
472 <?php
473 }
474
475 /**
476 * Register a notice on the Reading settings page to clarify that the RSS
477 * excerpt setting does not control newsletter emails.
478 *
479 * @since 0.5.1
480 */
481 public function add_reading_page_notice() {
482 add_settings_field(
483 'jetpack_newsletter_reading_notice',
484 '',
485 array( $this, 'render_reading_page_notice' ),
486 'reading',
487 'default'
488 );
489 }
490
491 /**
492 * Render the clarifying notice on the Reading settings page.
493 *
494 * Uses JavaScript to relocate the notice next to the "For each post in a feed"
495 * (rss_use_excerpt) setting.
496 *
497 * @since 0.5.1
498 */
499 public function render_reading_page_notice() {
500 $newsletter_url = Urls::get_newsletter_settings_url();
501
502 printf(
503 '<p class="description" id="jetpack-newsletter-reading-notice">%s</p>',
504 sprintf(
505 wp_kses(
506 /* translators: %s is a link to the Newsletter settings page. */
507 __( 'To control what’s included in newsletter emails, visit your <a href="%s">Newsletter settings</a>.', 'jetpack-newsletter' ),
508 array(
509 'a' => array(
510 'href' => array(),
511 ),
512 )
513 ),
514 esc_url( $newsletter_url )
515 )
516 );
517 ?>
518 <script type="text/javascript">
519 document.addEventListener( 'DOMContentLoaded', function() {
520 var notice = document.getElementById( 'jetpack-newsletter-reading-notice' );
521 var excerptInput = document.querySelector( 'input[name="rss_use_excerpt"]' );
522 var excerptRow = excerptInput ? excerptInput.closest( 'tr' ) : null;
523
524 if ( ! notice || ! excerptRow ) {
525 return;
526 }
527
528 // Remember the original parent before moving the notice.
529 var originalTable = notice.closest( 'table' );
530 var excerptTable = excerptRow.closest( 'table' );
531
532 // Move the notice into the rss_use_excerpt row's fieldset.
533 excerptRow.querySelector( 'td' ).appendChild( notice );
534
535 // Remove the now-empty original table (if it's different from the excerpt's table).
536 if ( originalTable && originalTable !== excerptTable ) {
537 originalTable.remove();
538 }
539 } );
540 </script>
541 <?php
542 }
543
544 /**
545 * Load the wp-build entry file and register its polyfills.
546 *
547 * Only called on `?page=jetpack-newsletter` admin requests when the
548 * modernization filter is enabled. Keeps wp-build off every other request.
549 *
550 * @return void
551 */
552 private static function load_wp_build() {
553 $build_index = dirname( __DIR__ ) . '/build/build.php';
554
555 if ( ! file_exists( $build_index ) ) {
556 return;
557 }
558
559 require_once $build_index;
560
561 \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::register(
562 'jetpack-newsletter',
563 array_merge(
564 \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::SCRIPT_HANDLES,
565 \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills::MODULE_IDS
566 )
567 );
568 }
569
570 /**
571 * Load wp-build with the screen ID aliased across its generated enqueue check.
572 *
573 * @see WP_Build_Screen_Id::load_with_alias()
574 * @return void
575 */
576 private static function load_wp_build_with_screen_alias() {
577 // Fallback: an older wp-build-polyfills under the jetpack-autoloader may predate load_with_alias().
578 if ( method_exists( \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Screen_Id::class, 'load_with_alias' ) ) {
579 \Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Screen_Id::load_with_alias(
580 array( __CLASS__, 'alias_screen_id_for_wp_build' ),
581 array( __CLASS__, 'restore_screen_id_after_wp_build' ),
582 function () {
583 self::load_wp_build();
584 }
585 );
586 return;
587 }
588
589 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'alias_screen_id_for_wp_build' ) );
590 self::load_wp_build();
591 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'restore_screen_id_after_wp_build' ) );
592 }
593
594 /**
595 * Alias the current screen ID to satisfy wp-build's auto-generated enqueue check.
596 *
597 * Wp-build's `<page>-wp-admin` enqueue callback enqueues only when the screen ID
598 * matches the wp-build page slug (`jetpack-newsletter-dashboard`). Our wp-admin
599 * menu slug stays `jetpack-newsletter`, so we mutate the screen object in place
600 * to make the check pass without changing the user-facing URL.
601 *
602 * Hooked only when modernization is on AND we're on the Newsletter admin page,
603 * so this never affects any other request.
604 *
605 * @since 0.16.0 Takes no argument; hooked on `admin_enqueue_scripts`.
606 *
607 * @return void
608 */
609 public static function alias_screen_id_for_wp_build() {
610 $screen = get_current_screen();
611 if ( ! $screen ) {
612 return;
613 }
614
615 self::$wp_build_original_screen_id = $screen->id;
616 $screen->id = 'jetpack-newsletter-dashboard';
617 }
618
619 /**
620 * Undo alias_screen_id_for_wp_build(), so code after the generated check sees the real screen ID.
621 *
622 * @since 0.16.0
623 *
624 * @return void
625 */
626 public static function restore_screen_id_after_wp_build() {
627 $screen = get_current_screen();
628 if ( ! $screen || null === self::$wp_build_original_screen_id ) {
629 return;
630 }
631
632 $screen->id = self::$wp_build_original_screen_id;
633 self::$wp_build_original_screen_id = null;
634 }
635
636 /**
637 * Returns true when the wp-build modernization filter is enabled.
638 *
639 * The modernized Newsletter dashboard, wp-admin subscriber management, and the
640 * retired Calypso Subscribers submenu now default on for every site. Hosts (and
641 * a11ns who want the legacy view back) can still force the legacy experience with
642 * `add_filter( self::MODERNIZATION_FILTER, '__return_false' );`.
643 *
644 * @return bool
645 */
646 private static function is_modernized() {
647 return (bool) apply_filters( self::MODERNIZATION_FILTER, true );
648 }
649
650 /**
651 * Returns true when subscribers are managed in wp-admin rather than on WordPress.com or Jetpack Cloud.
652 *
653 * @return bool
654 */
655 private static function is_wp_admin_subscriber_management_enabled() {
656 /** This filter is documented in projects/plugins/jetpack/modules/subscriptions.php */
657 return (bool) apply_filters( 'jetpack_wp_admin_subscriber_management_enabled', true );
658 }
659
660 /**
661 * Publish the Subscribers tab URL so other dashboards can link to it.
662 *
663 * @since 0.17.0
664 *
665 * @param array $data The existing script data.
666 * @return array The script data, with `newsletter.subscribersUrl` set to null when the current user cannot open the tab.
667 */
668 public static function add_subscribers_url_script_data( $data ) {
669 $data['newsletter']['subscribersUrl'] = self::is_subscribers_tab_available() ? Urls::get_subscribers_url() : null;
670
671 return $data;
672 }
673
674 /**
675 * Whether the current user can open the Subscribers tab of the Newsletter page.
676 *
677 * Reads the admin menu, so it returns false until `admin_menu` has run.
678 *
679 * @return bool
680 */
681 private static function is_subscribers_tab_available() {
682 // The legacy page and a host that manages subscribers elsewhere both leave the page Settings-only.
683 if ( ! self::is_modernized() || ! self::is_wp_admin_subscriber_management_enabled() ) {
684 return false;
685 }
686
687 // Registration applies the page's own gates: a connected site, the subscriptions module, and `manage_options`.
688 return function_exists( 'menu_page_url' ) && '' !== menu_page_url( self::ADMIN_PAGE_SLUG, false );
689 }
690
691 /**
692 * Returns true when the current request targets the Newsletter admin page.
693 *
694 * Used to scope wp-build loading to the one page that needs it. The
695 * `$_GET['page']` value is populated by wp-admin/admin.php before any of
696 * our hooks fire, so this check is reliable from `init_hooks()` onwards.
697 *
698 * @return bool
699 */
700 private static function is_newsletter_admin_request() {
701 if ( ! is_admin() || ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
702 return false;
703 }
704
705 return sanitize_text_field( wp_unslash( $_GET['page'] ) ) === self::ADMIN_PAGE_SLUG; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
706 }
707 }
708