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
← All changes | class.jetpack.php +990 -1178 12.9.5 → 16.3-beta View file →
@@ -7,32 +7,38 @@
7 7 * @package automattic/jetpack
8 8 */
9 9
10 10 use Automattic\Jetpack\Assets;
11 -use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
12 11 use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
13 12 use Automattic\Jetpack\Config;
13 +use Automattic\Jetpack\Connection\Authorize_Json_Api;
14 14 use Automattic\Jetpack\Connection\Client;
15 -use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
16 15 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
17 -use Automattic\Jetpack\Connection\Nonce_Handler;
18 16 use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
19 17 use Automattic\Jetpack\Connection\Secrets;
20 18 use Automattic\Jetpack\Connection\Tokens;
21 -use Automattic\Jetpack\Connection\Utils as Connection_Utils;
19 +use Automattic\Jetpack\Connection\Webhooks\Authorize_Redirect;
22 20 use Automattic\Jetpack\Constants;
23 21 use Automattic\Jetpack\CookieState;
24 22 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
25 23 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
26 24 use Automattic\Jetpack\Errors;
25 +use Automattic\Jetpack\Feature_Policy;
27 26 use Automattic\Jetpack\Files;
27 +use Automattic\Jetpack\Heartbeat;
28 28 use Automattic\Jetpack\Identity_Crisis;
29 +use Automattic\Jetpack\Import\Main as Import_Main;
29 30 use Automattic\Jetpack\Licensing;
30 31 use Automattic\Jetpack\Modules;
31 32 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
33 +use Automattic\Jetpack\Newsletter\Reader_Link;
32 34 use Automattic\Jetpack\Paths;
35 +use Automattic\Jetpack\Plugin\Deprecate;
33 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
34 38 use Automattic\Jetpack\Redirect;
39 +use Automattic\Jetpack\Scan_Page\Jetpack_Scan as Scan_Page_Init;
40 +use Automattic\Jetpack\SEO\Initializer as Jetpack_SEO_Initializer;
35 41 use Automattic\Jetpack\Status;
36 42 use Automattic\Jetpack\Status\Host;
37 43 use Automattic\Jetpack\Status\Visitor;
38 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -39,9 +45,14 @@
39 45 use Automattic\Jetpack\Sync\Health;
40 46 use Automattic\Jetpack\Sync\Sender;
41 47 use Automattic\Jetpack\Terms_Of_Service;
42 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
43 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
44 55 /*
45 56 Options:
46 57 jetpack_options (array)
47 58 An array of options.
@@ -76,74 +87,8 @@
76 87 */
77 88 public $xmlrpc_server = null;
78 89
79 90 /**
80 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
81 - *
82 - * See $concatenated_style_handles for the list of handles,
83 - * and the implode_frontend_css method for more details.
84 - *
85 - * When updating this list, make sure to update $concatenated_style_handles as well.
86 - *
87 - * @var array List of Jetpack modules.
88 - */
89 - public $modules_with_concatenated_css = array(
90 - 'carousel',
91 - 'contact-form',
92 - 'infinite-scroll',
93 - 'likes',
94 - 'related-posts',
95 - 'sharedaddy',
96 - 'shortcodes',
97 - 'subscriptions',
98 - 'tiled-gallery',
99 - 'widgets',
100 - );
101 -
102 - /**
103 - * The handles of styles that are concatenated into jetpack.css.
104 - *
105 - * When making changes to that list,
106 - * you must also update concat_list in tools/webpack.config.css.js,
107 - * and to $modules_with_concatenated_css if necessary.
108 - *
109 - * @var array The handles of styles that are concatenated into jetpack.css.
110 - */
111 - public $concatenated_style_handles = array(
112 - 'jetpack-carousel-swiper-css',
113 - 'jetpack-carousel',
114 - 'grunion.css',
115 - 'the-neverending-homepage',
116 - 'jetpack_likes',
117 - 'jetpack_related-posts',
118 - 'sharedaddy',
119 - 'jetpack-slideshow',
120 - 'presentations',
121 - 'quiz',
122 - 'jetpack-subscriptions',
123 - 'jetpack-responsive-videos',
124 - 'jetpack-social-menu',
125 - 'tiled-gallery',
126 - 'jetpack_display_posts_widget',
127 - 'gravatar-profile-widget',
128 - 'goodreads-widget',
129 - 'jetpack_social_media_icons_widget',
130 - 'jetpack-top-posts-widget',
131 - 'jetpack_image_widget',
132 - 'jetpack-my-community-widget',
133 - 'jetpack-authors-widget',
134 - 'wordads',
135 - 'eu-cookie-law-style',
136 - 'flickr-widget-style',
137 - 'jetpack-search-widget',
138 - 'jetpack-simple-payments-widget-style',
139 - 'jetpack-widget-social-icons-styles',
140 - 'wpcom_instagram_widget',
141 - 'milestone-widget',
142 - 'subscribe-modal-css',
143 - );
144 -
145 - /**
146 91 * Contains all assets that have had their URL rewritten to minified versions.
147 92 *
148 93 * @var array
149 94 */
@@ -158,11 +103,8 @@
158 103 'contact-form' => array(
159 104 array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
160 105 array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
161 106 ),
162 - 'custom-css' => array(
163 - array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
164 - ),
165 107 'gravatar-hovercards' => array(
166 108 array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
167 109 ),
168 110 'latex' => array(
@@ -333,9 +275,8 @@
333 275 * Graph tags via filter when their Social Meta modules are active:
334 276 *
335 277 * - All in One SEO Pack, All in one SEO Pack Pro
336 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
337 - * - SEOPress, SEOPress Pro
338 279 *
339 280 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
340 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
341 282 *
@@ -378,8 +319,10 @@
378 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
379 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
380 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
381 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
382 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
383 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
384 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
385 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -446,8 +389,10 @@
446 389
447 390 /**
448 391 * Verified data for JSON authorization request
449 392 *
393 + * @deprecated 13.4
394 + *
450 395 * @var array
451 396 */
452 397 public $json_api_authorization_request = array();
453 398
@@ -488,8 +433,16 @@
488 433 */
489 434 public static $instance = false;
490 435
491 436 /**
437 + * Resolved answer for `is_premium_analytics_enabled()`, or null before the first call.
438 + *
439 + * @since 16.1
440 + * @var bool|null
441 + */
442 + private static $premium_analytics_enabled = null;
443 +
444 + /**
492 445 * Singleton
493 446 *
494 447 * @static
495 448 */
@@ -529,9 +482,9 @@
529 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
530 483 self::update_active_modules( $modules );
531 484 }
532 485
533 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
534 487
535 488 // Upgrade to 4.3.0.
536 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
537 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -586,8 +539,16 @@
586 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
587 540 } // Should we have some type of fallback if something fails here?
588 541 }
589 542
543 + // Set the newsletter send default option for existing sites.
544 + if ( false === get_option( 'wpcom_newsletter_send_default' ) ) {
545 + add_option( 'wpcom_newsletter_send_default', 1 );
546 + }
547 +
548 + // Its handler went with the Recommendations assistant.
549 + wp_clear_scheduled_hook( 'jetpack_recommend_videopress' );
550 +
590 551 if ( did_action( 'wp_loaded' ) ) {
591 552 self::upgrade_on_load();
592 553 } else {
593 554 add_action(
@@ -621,9 +582,8 @@
621 582 }
622 583
623 584 if (
624 585 class_exists( 'Jetpack_Sitemap_Manager' )
625 - && version_compare( JETPACK__VERSION, '5.3', '>=' )
626 586 ) {
627 587 do_action( 'jetpack_sitemaps_purge_data' );
628 588 }
629 589
@@ -638,9 +598,9 @@
638 598 * Also fires Action hooks for each newly activated and deactivated module.
639 599 *
640 600 * @param array $modules Array of active modules to be saved in options.
641 601 *
642 - * @return $success bool true for success, false for failure.
602 + * @return bool $success true for success, false for failure.
643 603 */
644 604 public static function update_active_modules( $modules ) {
645 605 return ( new Modules() )->update_active( $modules );
646 606 }
@@ -694,41 +654,17 @@
694 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
695 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
696 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
697 657
698 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
699 -
700 - add_filter(
701 - 'jetpack_signature_check_token',
702 - array( __CLASS__, 'verify_onboarding_token' ),
703 - 10,
704 - 3
705 - );
706 -
707 658 /**
708 659 * Prepare Gutenberg Editor functionality
660 + *
661 + * The hooks previously here have been moved to modules/blocks.php but leaving this here pending
662 + * a longer investigation to see if code is expecting the Gutenberg class to always be available.
709 663 */
710 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
711 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
712 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
713 - /**
714 - * We've switched from enqueue_block_editor_assets to enqueue_block_assets in WP-Admin because the assets with the former are loaded on the main site-editor.php.
715 - *
716 - * With the latter, the assets are now loaded in the SE iframe; the implementation is now faster because Gutenberg doesn't need to inject the assets in the iframe on client-side.
717 - */
718 - if ( is_admin() ) {
719 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
720 - } else {
721 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
722 - }
723 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
724 -
725 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
726 666
727 - // Unlink user before deleting the user from WP.com.
728 - add_action( 'deleted_user', array( $this, 'disconnect_user' ), 10, 1 );
729 - add_action( 'remove_user_from_blog', array( $this, 'disconnect_user' ), 10, 1 );
730 -
731 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
732 668
733 669 add_filter( 'login_url', array( $this, 'login_url' ), 10, 2 );
734 670 add_action( 'login_init', array( $this, 'login_init' ) );
@@ -735,8 +671,13 @@
735 671
736 672 // Set up the REST authentication hooks.
737 673 Connection_Rest_Authentication::init();
738 674
675 + // Register Jetpack-specific connection tests (sync health, etc.) with the connection
676 + // package's health test suite. This runs on all requests (not just admin), because
677 + // the connection/test REST endpoint can be called outside admin context.
678 + add_action( 'jetpack_connection_tests_loaded', array( $this, 'register_jetpack_connection_tests' ) );
679 +
739 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
740 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
741 682
742 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -750,12 +691,8 @@
750 691
751 692 // Returns HTTPS support status.
752 693 add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
753 694
754 - add_action( 'wp_ajax_jetpack_connection_banner', array( $this, 'jetpack_connection_banner_callback' ) );
755 -
756 - add_action( 'wp_ajax_jetpack_recommendations_banner', array( 'Jetpack_Recommendations_Banner', 'ajax_callback' ) );
757 -
758 695 add_action( 'wp_loaded', array( $this, 'register_assets' ) );
759 696
760 697 /**
761 698 * These actions run checks to load additional files.
@@ -773,29 +710,8 @@
773 710
774 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
775 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
776 713
777 - require_once JETPACK__PLUGIN_DIR . 'class-jetpack-pre-connection-jitms.php';
778 - $jetpack_jitm_messages = ( new Jetpack_Pre_Connection_JITMs() );
779 - add_filter( 'jetpack_pre_connection_jitms', array( $jetpack_jitm_messages, 'add_pre_connection_jitms' ) );
780 -
781 - /*
782 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
783 - * We should make sure to only do this for front end links.
784 - */
785 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
786 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
787 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
788 -
789 - /*
790 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
791 - * so they point moderation links on emails to Calypso.
792 - */
793 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
794 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
795 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
796 - }
797 -
798 714 add_action(
799 715 'plugins_loaded',
800 716 function () {
801 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -806,18 +722,10 @@
806 722
807 723 // Update the site's Jetpack plan and products from API on heartbeats.
808 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
809 725
810 - /**
811 - * This is the hack to concatenate all css files into one.
812 - * For description and reasoning see the implode_frontend_css method.
813 - *
814 - * Super late priority so we catch all the registered styles.
815 - */
816 - if ( ! is_admin() ) {
817 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
818 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
819 - }
726 + // The Connection package fetches the site record for `jetpack/v4/site`; reuse it to refresh the plan.
727 + add_action( 'jetpack_site_data_fetched', array( Jetpack_Plan::class, 'update_from_site_record' ) );
820 728
821 729 // Actually push the stats on shutdown.
822 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
823 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -825,8 +733,10 @@
825 733
826 734 // After a successful connection.
827 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
828 736 add_action( 'jetpack_site_registered', array( $this, 'handle_unique_registrations_stats' ) );
737 + add_action( 'jetpack_site_registered', array( Reader_Link::class, 'activate_on_connection' ), 9 );
738 + add_action( 'jetpack_site_registered', array( \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::class, 'discard_credentials' ) );
829 739
830 740 // Actions for Manager::authorize().
831 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
832 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -831,8 +741,9 @@
831 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
832 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
833 743 add_action( 'jetpack_authorize_ending_authorized', array( $this, 'authorize_ending_authorized' ) );
834 744
745 + Jetpack_Client_Server::init();
835 746 add_action( 'jetpack_client_authorize_error', array( Jetpack_Client_Server::class, 'client_authorize_error' ) );
836 747 add_filter( 'jetpack_client_authorize_already_authorized_url', array( Jetpack_Client_Server::class, 'client_authorize_already_authorized_url' ) );
837 748 add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
838 749 add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
@@ -837,9 +748,9 @@
837 748 add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
838 749 add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
839 750
840 751 // Filters for the Manager::get_token() urls and request body.
841 - add_filter( 'jetpack_token_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
752 + add_filter( 'jetpack_token_redirect_url', array( Authorize_Redirect::class, 'filter_connect_redirect_url' ) );
842 753 add_filter( 'jetpack_token_request_body', array( __CLASS__, 'filter_token_request_body' ) );
843 754
844 755 // Filter for the `jetpack/v4/connection/data` API response.
845 756 add_filter( 'jetpack_current_user_connection_data', array( __CLASS__, 'filter_jetpack_current_user_connection_data' ) );
@@ -865,13 +776,184 @@
865 776
866 777 // Register product descriptions for partner coupon usage.
867 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
868 779
869 - // Actions for conditional recommendations.
870 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
780 + // Add 5-star
781 + add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
782 + add_action( 'init', array( Deprecate::class, 'instance' ) );
783 +
784 + // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
785 + \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
786 +
787 + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the
788 + // Jetpack plugin for now: the Connection package no longer auto-wires these, so
789 + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet.
790 + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init();
871 791 }
872 792
873 793 /**
794 + * Whether the current request should eagerly initialize the admin/REST-only
795 + * packages (the Import package and My Jetpack) now, at `plugins_loaded` time.
796 + *
797 + * Returns true for admin, cron, POST, and WP-CLI requests — the contexts,
798 + * knowable this early, where those packages have work to do. Returns false
799 + * for a plain front-end GET *and* for a REST request: the two can't be told
800 + * apart yet (this runs before `rest_api_init`), so callers defer the REST
801 + * case by initializing the package on `rest_api_init` instead, while a plain
802 + * page view never fires that hook and so loads nothing. This keeps
803 + * admin/REST-only PHP out of opcache on the front-end GET hot path.
804 + *
805 + * No in-repo code depends on the deferral. The one externally observable
806 + * change is timing: the packages' documented init hooks
807 + * (`jetpack_import_initialized`, `jetpack_feature_import_enabled`, and
808 + * `my_jetpack_init`) no longer fire on a plain front-end GET — they fire on
809 + * the admin, cron, POST, WP-CLI, and REST requests where the packages load.
810 + *
811 + * @return bool
812 + */
813 + private static function should_eager_load_packages() {
814 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) );
815 + $is_wp_cli = Constants::is_true( 'WP_CLI' );
816 +
817 + return is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli;
818 + }
819 +
820 + /**
821 + * Configure the Import package from a deferred hook.
822 + *
823 + * The eager path uses Config::ensure( 'import' ), but the deferred REST path
824 + * runs after Config::on_plugins_loaded() has already processed its feature
825 + * flags, so it needs a hookable bootstrap callback. Preserve Config's
826 + * feature-enabled action for hook consumers.
827 + *
828 + * @since 16.0
829 + *
830 + * @return void
831 + */
832 + public static function configure_import_package() {
833 + if ( class_exists( Import_Main::class ) ) {
834 + Import_Main::configure();
835 +
836 + if ( ! did_action( 'jetpack_feature_import_enabled' ) ) {
837 + do_action( 'jetpack_feature_import_enabled' );
838 + }
839 + }
840 + }
841 +
842 + /**
843 + * Enable the bundled Backup dashboard.
844 + *
845 + * The standalone plugin initializes the package first, so with both active this is a no-op.
846 + *
847 + * @return void
848 + */
849 + public static function configure_backup_package() {
850 + // Not offered on multisite, which the Backup package does not support, or without a
851 + // connected owner, since buying and managing backups needs a linked account.
852 + if ( is_multisite() || ! self::is_connection_ready() || ! self::connection()->has_connected_owner() ) {
853 + return;
854 + }
855 +
856 + if ( ! self::is_module_active( 'backup' ) ) {
857 + return;
858 + }
859 +
860 + /**
861 + * Filters whether the Jetpack plugin offers its bundled Backup dashboard.
862 + *
863 + * Resolved at the earliest `plugins_loaded` priority, so hook it from a mu-plugin.
864 + *
865 + * @since 16.3
866 + *
867 + * @param bool $enabled Whether to initialize the bundled Backup dashboard. Default true.
868 + */
869 + if ( ! apply_filters( 'jetpack_backup_dashboard_enabled', true ) ) {
870 + return;
871 + }
872 +
873 + $backup = 'Automattic\\Jetpack\\Backup\\V0005\\Jetpack_Backup';
874 +
875 + // An older package would take over the connection (see Jetpack_Backup::DEFAULT_INIT_OPTIONS);
876 + // only the standalone plugin ships one that old, and it draws its own menu.
877 + if ( ! class_exists( $backup ) || ! defined( $backup . '::DEFAULT_INIT_OPTIONS' ) ) {
878 + return;
879 + }
880 +
881 + $backup::initialize( array( 'manage_connection' => false ) );
882 + }
883 +
884 + /**
885 + * Whether the bundled Stats v2 dashboard is enabled.
886 + *
887 + * Stats v2 (formerly "Premium Analytics") ships with the plugin behind this
888 + * flag while it rolls out (WOOA7S-1595). When enabled it adds its own admin
889 + * menu alongside the existing Stats UI; it never replaces or hides the
890 + * legacy Stats menu, admin-bar entries, post-list column, or WP dashboard
891 + * widget. The Stats module's tracking is unaffected either way, and Stats v2
892 + * reads what that module collects, so while the module is off the plugin
893 + * answers false here before even reading the flag.
894 + *
895 + * The package has to be loadable for this to be true, so a site with the
896 + * flag on but a missing package answers false here and never adds the
897 + * Stats v2 menu (a warning is logged instead).
898 + *
899 + * @since 16.1
900 + *
901 + * @return bool
902 + */
903 + public static function is_premium_analytics_enabled() {
904 + if ( null !== self::$premium_analytics_enabled ) {
905 + return self::$premium_analytics_enabled;
906 + }
907 +
908 + if ( ! self::is_module_active( 'stats' ) ) {
909 + self::$premium_analytics_enabled = false;
910 + return false;
911 + }
912 +
913 + /**
914 + * Filters whether the bundled Premium Analytics dashboard is enabled.
915 + *
916 + * Resolved once, from `Jetpack::configure()` on `plugins_loaded`, and only
917 + * while the Stats module is active. Register this from a mu-plugin or a
918 + * plugin's main file — a callback added on `plugins_loaded` or later runs
919 + * too late to be seen.
920 + *
921 + * @since 16.1
922 + *
923 + * @param bool $enabled Defaults to the `jetpack_premium_analytics_enabled` option (false).
924 + */
925 + $flag = (bool) apply_filters( 'jetpack_premium_analytics_enabled', (bool) get_option( 'jetpack_premium_analytics_enabled' ) );
926 +
927 + self::$premium_analytics_enabled = $flag && class_exists( 'Automattic\Jetpack\PremiumAnalytics\Analytics' );
928 +
929 + if ( $flag && ! self::$premium_analytics_enabled ) {
930 + wp_trigger_error(
931 + __METHOD__,
932 + 'The jetpack_premium_analytics_enabled flag is on but the Premium Analytics package is not loadable; keeping the Stats UI in place.'
933 + );
934 + }
935 +
936 + return self::$premium_analytics_enabled;
937 + }
938 +
939 + /**
940 + * Expose the setting that turns the Premium Analytics dashboard on and off.
941 + *
942 + * Deliberately not behind is_premium_analytics_enabled(): this is the setting that flips that
943 + * check, so it has to answer while the dashboard is still off.
944 + *
945 + * @since 16.2
946 + *
947 + * @return void
948 + */
949 + public static function register_premium_analytics_enablement_setting() {
950 + if ( class_exists( 'Automattic\Jetpack\PremiumAnalytics\Enablement_Setting' ) ) {
951 + \Automattic\Jetpack\PremiumAnalytics\Enablement_Setting::register();
952 + }
953 + }
954 +
955 + /**
874 956 * Before everything else starts getting initalized, we need to initialize Jetpack using the
875 957 * Config object.
876 958 */
877 959 public function configure() {
@@ -880,13 +962,10 @@
880 962 foreach (
881 963 array(
882 964 'jitm',
883 965 'sync',
966 + 'account_protection',
884 967 'waf',
885 - 'videopress',
886 - 'stats',
887 - 'stats_admin',
888 - 'import',
889 968 )
890 969 as $feature
891 970 ) {
892 971 $config->ensure( $feature );
@@ -891,8 +970,110 @@
891 970 ) {
892 971 $config->ensure( $feature );
893 972 }
894 973
974 + // Enable the VideoPress admin UI (the "Jetpack > VideoPress" dashboard) inside the
975 + // Jetpack plugin, mirroring the standalone Jetpack VideoPress plugin. The dashboard
976 + // only renders when the VideoPress module is active (Status::is_active()); when it
977 + // is not, the menu item links to the My Jetpack interstitial to activate it.
978 + $config->ensure( 'videopress', array( 'admin_ui' => true ) );
979 +
980 + // The Backup dashboard is only an admin menu and REST routes, so it is deferred like Import below.
981 + if ( self::should_eager_load_packages() ) {
982 + self::configure_backup_package();
983 + } else {
984 + add_action( 'rest_api_init', array( __CLASS__, 'configure_backup_package' ), 0 );
985 + }
986 +
987 + /*
988 + * The Import package only registers `jetpack/v4/import` REST routes — it
989 + * does nothing when rendering a front-end page — so gate its `ensure()`
990 + * to keep its PHP out of opcache on the front-end GET hot path. It still
991 + * loads on admin, cron, POST, and WP-CLI requests, and on `rest_api_init`
992 + * for REST: a REST request can't be identified yet at `plugins_loaded`
993 + * (this runs before `Config::on_plugins_loaded`, and `rest_api_init`
994 + * fires later), so it is initialized directly when that hook fires, while
995 + * a plain page view never fires it and so loads nothing.
996 + *
997 + * JITM stays eager (above): unlike Import, its `register()` adds a
998 + * `jetpack_sync_before_send_updated_option` filter that records the
999 + * `jetpack_last_plugin_sync` transient, and a Jetpack Sync send can fire
1000 + * on a plain front-end GET — including the dedicated-sync `spawn-sync`
1001 + * GET, which runs on `init` and exits before `rest_api_init`. Deferring
1002 + * JITM would skip that bookkeeping and leave its message cache stale after
1003 + * a plugin change, so it loads on every request as before.
1004 + */
1005 + if ( self::should_eager_load_packages() ) {
1006 + $config->ensure( 'import' );
1007 + } else {
1008 + add_action(
1009 + 'rest_api_init',
1010 + array( __CLASS__, 'configure_import_package' ),
1011 + 0
1012 + );
1013 + }
1014 +
1015 + /*
1016 + * The Stats and Stats Admin packages only do work when the Stats module
1017 + * is active (the front-end tracking pixel) or on wp-admin, REST, cron,
1018 + * POST, and WP-CLI requests: the Stats dashboard page, the stats /
1019 + * stats-app REST endpoints (which the block editor also calls for
1020 + * email-open rates), the transient-cleanup cron, the connection
1021 + * package's package-version tracker (which runs on POSTs and reads the
1022 + * `jetpack_package_versions` filter that Stats registers), and CLI
1023 + * introspection such as the heartbeat inspector. On a plain front-end
1024 + * GET page view with the module off they are inert: the pixel
1025 + * short-circuits on `Stats\Main::should_track()` and every other entry
1026 + * point only hooks rest_api_init, admin, cron, the POST-only tracker, or
1027 + * is reached through WP-CLI.
1028 + * Skip loading them — and eagerly constructing the Stats Admin REST
1029 + * controller — on that hot path to keep their PHP out of opcache, but
1030 + * keep loading them everywhere else exactly as before so the stats REST
1031 + * permission mapping (view_stats, registered by Stats\Main), the
1032 + * editor's stats-app calls, the cleanup cron, and the package-version
1033 + * tracker are all unchanged.
1034 + *
1035 + * REST requests are not yet identifiable here (REST_REQUEST is defined
1036 + * after plugins_loaded), so defer those to rest_api_init. Call the
1037 + * package initializers directly rather than `$config->ensure()`: ensure()
1038 + * only flags a feature for `Config::on_plugins_loaded()` (plugins_loaded
1039 + * priority 2), which has already run by the time rest_api_init fires.
1040 + * Priority 0 runs before each package's own priority-10 route
1041 + * registration, so their routes still register within the same dispatch.
1042 + * A plain page view never fires rest_api_init, so the packages stay
1043 + * unloaded there. See JETPACK-1747.
1044 + */
1045 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'];
1046 + $is_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
1047 +
1048 + if ( self::is_module_active( 'stats' ) || is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli ) {
1049 + $config->ensure( 'stats' );
1050 + $config->ensure( 'stats_admin' );
1051 + } else {
1052 + add_action(
1053 + 'rest_api_init',
1054 + static function () {
1055 + if ( class_exists( 'Automattic\Jetpack\Stats\Main' ) ) {
1056 + \Automattic\Jetpack\Stats\Main::init();
1057 + }
1058 + if ( class_exists( 'Automattic\Jetpack\Stats_Admin\Main' ) ) {
1059 + \Automattic\Jetpack\Stats_Admin\Main::init();
1060 + }
1061 + },
1062 + 0
1063 + );
1064 + }
1065 +
1066 + // Stats v2 (WOOA7S-1595). Unlike Stats above it cannot be deferred when enabled — see
1067 + // Analytics::init() for why, and for why it takes no menu_title here.
1068 + if ( self::is_premium_analytics_enabled() ) {
1069 + \Automattic\Jetpack\PremiumAnalytics\Analytics::init();
1070 + }
1071 +
1072 + // Outside the check above on purpose — see Enablement_Setting. Deferred like Stats, to keep
1073 + // the autoload off the front-end hot path.
1074 + add_action( 'rest_api_init', array( __CLASS__, 'register_premium_analytics_enablement_setting' ), 0 );
1075 +
895 1076 $config->ensure(
896 1077 'connection',
897 1078 array(
898 1079 'slug' => 'jetpack',
@@ -910,12 +1091,8 @@
910 1091 );
911 1092
912 1093 $config->ensure( 'search' );
913 1094
914 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
915 - $config->ensure( 'wordads' );
916 - }
917 -
918 1095 if ( ! $this->connection_manager ) {
919 1096 $this->connection_manager = new Connection_Manager( 'jetpack' );
920 1097 }
921 1098
@@ -923,8 +1100,10 @@
923 1100 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
924 1101 $config->ensure( 'publicize' );
925 1102 }
926 1103
1104 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1105 +
927 1106 /*
928 1107 * Load things that should only be in Network Admin.
929 1108 *
930 1109 * For now blow away everything else until a more full
@@ -939,8 +1118,9 @@
939 1118 $is_connection_ready = self::is_connection_ready();
940 1119
941 1120 if ( $is_connection_ready ) {
942 1121 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1122 + $this->run_initialize_tracking_action();
943 1123
944 1124 Jetpack_Heartbeat::init();
945 1125 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
946 1126 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -945,8 +1125,19 @@
945 1125 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
946 1126 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
947 1127 Jetpack_Search_Performance_Logger::init();
948 1128 }
1129 + } else {
1130 + add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
1131 + add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
1132 + add_filter(
1133 + 'xmlrpc_methods',
1134 + function ( $methods ) {
1135 + $this->run_initialize_tracking_action();
1136 + return $methods;
1137 + },
1138 + 1
1139 + );
949 1140 }
950 1141
951 1142 // Initialize remote file upload request handlers.
952 1143 $this->add_remote_request_handlers();
@@ -956,20 +1147,10 @@
956 1147 */
957 1148 if ( $is_connection_ready ) {
958 1149 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
959 1150 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
960 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
961 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1151 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
962 1152 }
963 -
964 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
965 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
966 - } else {
967 - /**
968 - * Initialize tracking right after the user agrees to the terms of service.
969 - */
970 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
971 - }
972 1153 }
973 1154
974 1155 /**
975 1156 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -977,16 +1158,58 @@
977 1158 *
978 1159 * @action plugins_loaded
979 1160 */
980 1161 public function late_initialization() {
981 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1162 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
982 1163
983 - My_Jetpack_Initializer::init();
1164 + /*
1165 + * My Jetpack is a wp-admin dashboard. Its Initializer::init() only wires
1166 + * up admin-menu, admin_init, and rest_api_init surfaces — and eagerly
1167 + * loads every product class (backup, boost, protect, …) just to register
1168 + * admin plugin-action links — so none of it is needed on a plain
1169 + * front-end GET page view. (The pieces that do immediate work, e.g.
1170 + * Connection REST authentication and Licensing, are already initialized
1171 + * unconditionally in Jetpack's constructor, so they are unaffected here.)
1172 + *
1173 + * Gate the call to the request types where My Jetpack actually does work.
1174 + * REST can't be detected yet at plugins_loaded, so initialize on
1175 + * rest_api_init for that branch; a plain page view never fires it, so My
1176 + * Jetpack stays unloaded there.
1177 + */
1178 + if ( self::should_eager_load_packages() ) {
1179 + My_Jetpack_Initializer::init();
1180 + } else {
1181 + add_action( 'rest_api_init', array( My_Jetpack_Initializer::class, 'init' ), 0 );
1182 + }
984 1183
985 - // Initialize Boost Speed Score
986 - $modules = Jetpack_Boost_Modules::init();
987 - new Speed_Score( $modules, 'jetpack-dashboard' );
1184 + Scan_Page_Init::initialize();
1185 + Jetpack_SEO_Initializer::init();
988 1186
1187 + if ( ( new Modules() )->is_active( 'podcast' ) ) {
1188 + Podcast::init();
1189 + }
1190 +
1191 + /*
1192 + * Initialize Boost Speed Score. It only does work on REST requests (the
1193 + * dashboard speed-score endpoints) and on a few Jetpack Boost lifecycle
1194 + * actions, so defer constructing it — and loading the boost-speed-score
1195 + * package classes — until one of those hooks actually fires instead of on
1196 + * every request. Priority 0 ensures the object's own callbacks (added in
1197 + * its constructor at the default priority) still run for the firing hook.
1198 + */
1199 + $initialize_speed_score = static function () {
1200 + static $initialized = false;
1201 + if ( $initialized ) {
1202 + return;
1203 + }
1204 + $initialized = true;
1205 + new Speed_Score( array(), 'jetpack-dashboard' );
1206 + };
1207 + add_action( 'rest_api_init', $initialize_speed_score, 0 );
1208 + add_action( 'jetpack_boost_deactivate', $initialize_speed_score, 0 );
1209 + add_action( 'jetpack_boost_environment_changed', $initialize_speed_score, 0 );
1210 + add_action( 'handle_environment_change', $initialize_speed_score, 0 );
1211 +
989 1212 /**
990 1213 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
991 1214 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
992 1215 *
@@ -1024,8 +1247,10 @@
1024 1247
1025 1248 /**
1026 1249 * Redirect edit post links to Calypso.
1027 1250 *
1251 + * @deprecated since 13.9
1252 + *
1028 1253 * @param string $default_url Post edit URL.
1029 1254 * @param int $post_id Post ID.
1030 1255 *
1031 1256 * @return string
@@ -1030,8 +1255,10 @@
1030 1255 *
1031 1256 * @return string
1032 1257 */
1033 1258 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1259 + _deprecated_function( __METHOD__, '13.9' );
1260 +
1034 1261 $post = get_post( $post_id );
1035 1262
1036 1263 if ( empty( $post ) ) {
1037 1264 return $default_url;
@@ -1062,13 +1289,17 @@
1062 1289
1063 1290 /**
1064 1291 * Redirect edit comment links to Calypso.
1065 1292 *
1293 + * @deprecated since 13.9
1294 + *
1066 1295 * @param string $url Comment edit URL.
1067 1296 *
1068 1297 * @return string
1069 1298 */
1070 1299 public function point_edit_comment_links_to_calypso( $url ) {
1300 + _deprecated_function( __METHOD__, '13.9' );
1301 +
1071 1302 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1072 1303 $query_args = null;
1073 1304 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1074 1305
@@ -1087,30 +1318,16 @@
1087 1318 *
1088 1319 * @return array list of callables.
1089 1320 */
1090 1321 public function filter_sync_callable_whitelist( $callables ) {
1091 -
1092 1322 // Jetpack Functions.
1093 1323 $jetpack_callables = array(
1094 1324 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1095 1325 'updates' => array( 'Jetpack', 'get_updates' ),
1096 1326 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1327 + 'theme_styles' => array( 'Automattic\\Jetpack\\Plugin\\Theme_Styles_Sync', 'get_theme_styles' ),
1097 1328 );
1098 - $callables = array_merge( $callables, $jetpack_callables );
1099 -
1100 - // Jetpack_SSO_Helpers.
1101 - if ( include_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ) {
1102 - $sso_helpers = array(
1103 - 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
1104 - 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
1105 - 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
1106 - 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
1107 - 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
1108 - );
1109 - $callables = array_merge( $callables, $sso_helpers );
1110 - }
1111 -
1112 - return $callables;
1329 + return array_merge( $callables, $jetpack_callables );
1113 1330 }
1114 1331
1115 1332 /**
1116 1333 * Extend Sync multisite callables with Jetpack Plugin functions.
@@ -1135,41 +1352,8 @@
1135 1352 return $callables;
1136 1353 }
1137 1354
1138 1355 /**
1139 - * Deprecated
1140 - * Please use Automattic\Jetpack\JITMS\JITM::jetpack_track_last_sync_callback instead.
1141 - *
1142 - * @param array $params The action parameters.
1143 - *
1144 - * @deprecated since 9.8.
1145 - */
1146 - public function jetpack_track_last_sync_callback( $params ) {
1147 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\Automattic\Jetpack\JITMS\JITM->jetpack_track_last_sync_callback' );
1148 - return Automattic\Jetpack\JITMS\JITM::get_instance()->jetpack_track_last_sync_callback( $params );
1149 - }
1150 -
1151 - /**
1152 - * Jetpack Connection banner callback function.
1153 - *
1154 - * @return void
1155 - */
1156 - public function jetpack_connection_banner_callback() {
1157 - check_ajax_referer( 'jp-connection-banner-nonce', 'nonce' );
1158 -
1159 - // Disable the banner dismiss functionality if the pre-connection prompt helpers filter is set.
1160 - if (
1161 - isset( $_REQUEST['dismissBanner'] ) &&
1162 - ! Jetpack_Connection_Banner::force_display()
1163 - ) {
1164 - Jetpack_Options::update_option( 'dismissed_connection_banner', 1 );
1165 - wp_send_json_success();
1166 - }
1167 -
1168 - wp_die();
1169 - }
1170 -
1171 - /**
1172 1356 * If there are any stats that need to be pushed, but haven't been, push them now.
1173 1357 */
1174 1358 public function push_stats() {
1175 1359 if ( ! empty( $this->stats ) ) {
@@ -1184,16 +1368,8 @@
1184 1368 * @param string $cap Capability name.
1185 1369 */
1186 1370 public function jetpack_custom_caps( $caps, $cap ) {
1187 1371 switch ( $cap ) {
1188 - case 'jetpack_manage_modules':
1189 - case 'jetpack_activate_modules':
1190 - case 'jetpack_deactivate_modules':
1191 - $caps = array( 'manage_options' );
1192 - break;
1193 - case 'jetpack_configure_modules':
1194 - $caps = array( 'manage_options' );
1195 - break;
1196 1372 case 'jetpack_manage_autoupdates':
1197 1373 $caps = array(
1198 1374 'manage_options',
1199 1375 'update_plugins',
@@ -1211,9 +1387,9 @@
1211 1387 if ( $is_offline_mode ) {
1212 1388 $caps = array( 'manage_options' );
1213 1389 break;
1214 1390 } else {
1215 - $caps = array( 'read' );
1391 + $caps = array( 'edit_posts' );
1216 1392 }
1217 1393 break;
1218 1394 }
1219 1395 return $caps;
@@ -1372,9 +1548,9 @@
1372 1548 }
1373 1549 /**
1374 1550 * Does the network allow admins to add new users.
1375 1551 *
1376 - * @return boolian
1552 + * @return bool
1377 1553 */
1378 1554 public static function network_add_new_users() {
1379 1555 return (bool) get_site_option( 'add_new_users' );
1380 1556 }
@@ -1381,9 +1557,9 @@
1381 1557 /**
1382 1558 * File upload psace left per site in MB.
1383 1559 * -1 means NO LIMIT.
1384 1560 *
1385 - * @return number
1561 + * @return int
1386 1562 */
1387 1563 public static function network_site_upload_space() {
1388 1564 // value in MB.
1389 1565 return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
@@ -1400,9 +1576,9 @@
1400 1576
1401 1577 /**
1402 1578 * Maximum file upload size set by the network.
1403 1579 *
1404 - * @return number
1580 + * @return int
1405 1581 */
1406 1582 public static function network_max_upload_file_size() {
1407 1583 // value in KB.
1408 1584 return get_site_option( 'fileupload_maxk', 300 );
@@ -1656,44 +1832,8 @@
1656 1832 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1657 1833 }
1658 1834
1659 1835 /**
1660 - * Deprecated: Is Jetpack in development (offline) mode?
1661 - *
1662 - * This static method is being left here intentionally without the use of _deprecated_function(), as other plugins
1663 - * and themes still use it, and we do not want to flood them with notices.
1664 - *
1665 - * Please use Automattic\Jetpack\Status()->is_offline_mode() instead.
1666 - *
1667 - * @deprecated since 8.0.
1668 - */
1669 - public static function is_development_mode() {
1670 - _deprecated_function( __METHOD__, 'jetpack-8.0', '\Automattic\Jetpack\Status->is_offline_mode' );
1671 - return ( new Status() )->is_offline_mode();
1672 - }
1673 -
1674 - /**
1675 - * Whether the site is currently onboarding or not.
1676 - * A site is considered as being onboarded if it currently has an onboarding token.
1677 - *
1678 - * @since 5.8
1679 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1680 - *
1681 - * @access public
1682 - * @static
1683 - *
1684 - * @return bool True if the site is currently onboarding, false otherwise
1685 - */
1686 - public static function is_onboarding() {
1687 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1688 -
1689 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1690 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1691 - }
1692 - return ( new Status() )->is_onboarding();
1693 - }
1694 -
1695 - /**
1696 1836 * Determines reason for Jetpack offline mode.
1697 1837 */
1698 1838 public static function development_mode_trigger_text() {
1699 1839 $status = new Status();
@@ -1707,11 +1847,10 @@
1707 1847 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1708 1848 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1709 1849 } elseif ( $status->is_local_site() ) {
1710 1850 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1711 - /** This filter is documented in packages/status/src/class-status.php */
1712 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1713 - $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1851 + } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1852 + $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1714 1853 } else {
1715 1854 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1716 1855 }
1717 1856
@@ -1741,15 +1880,8 @@
1741 1880 $notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), esc_url( Redirect::get_url( 'jetpack-contact-support-beta-group' ) ) );
1742 1881
1743 1882 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1744 1883 }
1745 - // Throw up a notice if using staging mode.
1746 - if ( ( new Status() )->is_staging_site() ) {
1747 - /* translators: %s is a URL */
1748 - $notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), esc_url( Redirect::get_url( 'jetpack-support-staging-sites' ) ) );
1749 -
1750 - echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1751 - }
1752 1884 }
1753 1885
1754 1886 /**
1755 1887 * Whether Jetpack's version maps to a public release, or a development version.
@@ -1770,28 +1902,8 @@
1770 1902 );
1771 1903 }
1772 1904
1773 1905 /**
1774 - * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1775 - *
1776 - * @param int $user_id User ID or will use get_current_user_id if false/not provided.
1777 - */
1778 - public static function is_user_connected( $user_id = false ) {
1779 - _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Manager\\is_user_connected' );
1780 - return self::connection()->is_user_connected( $user_id );
1781 - }
1782 -
1783 - /**
1784 - * Get the wpcom user data of the current|specified connected user.
1785 - *
1786 - * @param null|int $user_id User ID or will use get_current_user_id if null.
1787 - */
1788 - public static function get_connected_user_data( $user_id = null ) {
1789 - _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Manager\\get_connected_user_data' );
1790 - return self::connection()->get_connected_user_data( $user_id );
1791 - }
1792 -
1793 - /**
1794 1906 * Get the wpcom email of the current|specified connected user.
1795 1907 *
1796 1908 * @param null|int $user_id User ID or will use get_current_user_id if null.
1797 1909 */
@@ -1843,18 +1955,11 @@
1843 1955 */
1844 1956 public static function load_modules() {
1845 1957 $status = new Status();
1846 1958
1847 - if ( method_exists( $status, 'is_onboarding' ) ) {
1848 - $is_onboarding = $status->is_onboarding();
1849 - } else {
1850 - $is_onboarding = self::is_onboarding();
1851 - }
1852 -
1853 1959 if (
1854 1960 ! self::is_connection_ready()
1855 1961 && ! $status->is_offline_mode()
1856 - && ! $is_onboarding
1857 1962 && (
1858 1963 ! is_multisite()
1859 1964 || ! get_site_option( 'jetpack_protect_active' )
1860 1965 )
@@ -1975,22 +2080,14 @@
1975 2080 *
1976 2081 * @todo Store the result in core's object cache maybe?
1977 2082 */
1978 2083 public static function get_active_plugins() {
1979 - $active_plugins = (array) get_option( 'active_plugins', array() );
1980 -
1981 - if ( is_multisite() ) {
1982 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1983 - // whereas active_plugins stores them in the values.
1984 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1985 - if ( $network_plugins ) {
1986 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1987 - }
2084 + // Older Connection copies can load first and lack this method.
2085 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2086 + return array();
1988 2087 }
1989 2088
1990 - sort( $active_plugins );
1991 -
1992 - return array_unique( $active_plugins );
2089 + return Heartbeat::get_active_plugins();
1993 2090 }
1994 2091
1995 2092 /**
1996 2093 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2128,8 +2225,10 @@
2128 2225 *
2129 2226 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2130 2227 */
2131 2228 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2229 + // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
2230 + // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
2132 2231 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2133 2232 }
2134 2233 }
2135 2234
@@ -2232,9 +2331,9 @@
2232 2331 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2233 2332 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2234 2333 }
2235 2334 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2236 - exit;
2335 + exit( 0 );
2237 2336 }
2238 2337 }
2239 2338
2240 2339 /**
@@ -2281,8 +2380,12 @@
2281 2380 default:
2282 2381 break;
2283 2382 }
2284 2383 }
2384 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2385 + Feature_Policy::ensure_hooks();
2386 + }
2387 +
2285 2388 /**
2286 2389 * Filters the array of default modules.
2287 2390 *
2288 2391 * @since 2.5.0
@@ -2314,12 +2417,14 @@
2314 2417 * @return array
2315 2418 */
2316 2419 public function handle_deprecated_modules( $modules ) {
2317 2420 $deprecated_modules = array(
2318 - 'debug' => null, // Closed out and moved to the debugger library.
2319 - 'wpcc' => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2320 - 'gplus-authorship' => null, // Closed out in 3.2 -- Google dropped support.
2321 - 'minileven' => null, // Closed out in 8.3 -- Responsive themes are common now, and so is AMP.
2421 + 'debug' => null, // Closed out and moved to the debugger library.
2422 + 'wpcc' => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2423 + 'gplus-authorship' => null, // Closed out in 3.2 -- Google dropped support.
2424 + 'minileven' => null, // Closed out in 8.3 -- Responsive themes are common now, and so is AMP.
2425 + 'lazy-images' => null, // Closed out in 12.8 -- WordPress core now has native lazy loading.
2426 + 'enhanced-distribution' => null, // Closed out in 13.3 -- WP.com is winding down the firehose.
2322 2427 );
2323 2428
2324 2429 // Don't activate SSO if they never completed activating WPCC.
2325 2430 if ( self::is_module_active( 'wpcc' ) ) {
@@ -2373,8 +2478,17 @@
2373 2478 }
2374 2479 }
2375 2480 }
2376 2481
2482 + // Special case to convert block setting to a block module.
2483 + $block_key = array_search( 'blocks', $modules, true );
2484 + if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2485 + $block_option = get_option( 'jetpack_blocks_disabled', null );
2486 + if ( $block_option ) {
2487 + unset( $modules[ $block_key ] );
2488 + }
2489 + }
2490 +
2377 2491 return $modules;
2378 2492 }
2379 2493
2380 2494 /**
@@ -2431,23 +2545,30 @@
2431 2545
2432 2546 /**
2433 2547 * Return module name translation. Uses matching string created in modules/module-headings.php.
2434 2548 *
2549 + * The module list is globbed from `modules/` at runtime, so a module can be listed with no
2550 + * entry in that generated file. Fall back to the untranslated header rather than overwriting
2551 + * it with the null `jetpack_get_module_i18n()` returns for an unknown slug.
2552 + *
2435 2553 * @since 3.9.2
2436 2554 *
2437 2555 * @param array $modules Array of Jetpack modules.
2438 2556 *
2439 - * @return string|void
2557 + * @return array
2440 2558 */
2441 2559 public static function get_translated_modules( $modules ) {
2442 2560 foreach ( $modules as $index => $module ) {
2443 2561 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2444 - if ( isset( $module['name'] ) ) {
2445 - $modules[ $index ]['name'] = $i18n_module['name'];
2562 + $name = $i18n_module['name'] ?? null;
2563 + $description = $i18n_module['description'] ?? null;
2564 +
2565 + if ( null !== $name && isset( $module['name'] ) ) {
2566 + $modules[ $index ]['name'] = $name;
2446 2567 }
2447 - if ( isset( $module['description'] ) ) {
2448 - $modules[ $index ]['description'] = $i18n_module['description'];
2449 - $modules[ $index ]['short_description'] = $i18n_module['description'];
2568 + if ( null !== $description && isset( $module['description'] ) ) {
2569 + $modules[ $index ]['description'] = $description;
2570 + $modules[ $index ]['short_description'] = $description;
2450 2571 }
2451 2572 if ( isset( $module['module_tags'] ) ) {
2452 2573 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2453 2574 }
@@ -2487,20 +2608,28 @@
2487 2608
2488 2609 /**
2489 2610 * Catches PHP errors. Must be used in conjunction with output buffering.
2490 2611 *
2612 + * @deprecated since 13.5
2491 2613 * @param bool $catch True to start catching, False to stop.
2492 2614 *
2493 2615 * @static
2616 + * @deprecated 13.5
2617 + * @see \Automattic\Jetpack\Errors
2494 2618 */
2495 2619 public static function catch_errors( $catch ) {
2620 + _deprecated_function( __METHOD__, '13.5' );
2621 + // @phan-suppress-next-line PhanDeprecatedClass
2496 2622 return ( new Errors() )->catch_errors( $catch );
2497 2623 }
2498 2624
2499 2625 /**
2500 2626 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2627 + *
2628 + * @deprecated since 13.5
2501 2629 */
2502 2630 public static function catch_errors_on_shutdown() {
2631 + _deprecated_function( __METHOD__, '13.5' );
2503 2632 self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2504 2633 }
2505 2634
2506 2635 /**
@@ -2604,9 +2733,9 @@
2604 2733 ),
2605 2734 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2606 2735 );
2607 2736 wp_safe_redirect( $url );
2608 - exit;
2737 + exit( 0 );
2609 2738 }
2610 2739 }
2611 2740
2612 2741 /**
@@ -2624,9 +2753,8 @@
2624 2753
2625 2754 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating.
2626 2755 if ( $send_state_messages ) {
2627 2756 self::restate();
2628 - self::catch_errors( true );
2629 2757 }
2630 2758
2631 2759 $active = self::get_active_modules();
2632 2760
@@ -2694,10 +2822,8 @@
2694 2822 if ( $send_state_messages ) {
2695 2823 self::state( 'error', false );
2696 2824 self::state( 'module', false );
2697 2825 }
2698 -
2699 - self::catch_errors( false );
2700 2826 /**
2701 2827 * Fires when default modules are activated.
2702 2828 *
2703 2829 * @since 1.9.0
@@ -2755,9 +2881,9 @@
2755 2881 * @return string $url module configuration URL.
2756 2882 */
2757 2883 public static function module_configuration_url( $module ) {
2758 2884 $module = self::get_module_slug( $module );
2759 - $default_url = self::admin_url() . "#/settings?term=$module";
2885 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2760 2886 /**
2761 2887 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2762 2888 *
2763 2889 * @since 6.9.0
@@ -2775,9 +2901,9 @@
2775 2901 *
2776 2902 * @param string $message Error message.
2777 2903 * @param bool $deactivate Deactivate Jetpack or not.
2778 2904 *
2779 - * @return void
2905 + * @return never
2780 2906 */
2781 2907 public static function bail_on_activation( $message, $deactivate = true ) {
2782 2908 ?>
2783 2909 <!doctype html>
@@ -2815,9 +2941,9 @@
2815 2941 if ( $update ) {
2816 2942 update_option( 'active_plugins', array_filter( $plugins ) );
2817 2943 }
2818 2944 }
2819 - exit;
2945 + exit( 0 );
2820 2946 }
2821 2947
2822 2948 /**
2823 2949 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2842,12 +2968,18 @@
2842 2968 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2843 2969
2844 2970 Health::on_jetpack_activated();
2845 2971
2972 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2973 +
2846 2974 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2847 2975 Sync_Actions::do_only_first_initial_sync();
2848 2976 }
2849 2977
2978 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2979 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2980 + }
2981 +
2850 2982 self::plugin_initialize();
2851 2983 }
2852 2984
2853 2985 /**
@@ -2882,9 +3014,9 @@
2882 3014
2883 3015 if ( $plugins_path === $referer['path'] ) {
2884 3016 $source_type = 'list';
2885 3017 } elseif ( $plugins_install_path === $referer['path'] ) {
2886 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3018 + $tab = $query_parts['tab'] ?? 'featured';
2887 3019 switch ( $tab ) {
2888 3020 case 'popular':
2889 3021 $source_type = 'popular';
2890 3022 break;
@@ -2894,10 +3026,10 @@
2894 3026 case 'favorites':
2895 3027 $source_type = 'favorites';
2896 3028 break;
2897 3029 case 'search':
2898 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2899 - $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3030 + $source_type = 'search-' . ( $query_parts['type'] ?? 'term' );
3031 + $source_query = $query_parts['s'] ?? null;
2900 3032 break;
2901 3033 default:
2902 3034 $source_type = 'featured';
2903 3035 }
@@ -2906,29 +3038,171 @@
2906 3038 return array( $source_type, $source_query );
2907 3039 }
2908 3040
2909 3041 /**
2910 - * Runs before bumping version numbers up to a new version
3042 + * Runs before bumping version numbers up to a new version.
2911 3043 *
2912 - * @param string $version Version:timestamp.
3044 + * Only ever registered the hooks for the release post update modal, which has been removed.
3045 + * No longer hooked to `updating_jetpack_version`.
3046 + *
3047 + * @deprecated 16.2
3048 + *
3049 + * @param string $version Version:timestamp.
2913 3050 * @param string $old_version Old Version:timestamp or false if not set yet.
2914 3051 */
2915 - public static function do_version_bump( $version, $old_version ) {
2916 - if ( $old_version ) { // For existing Jetpack installations.
2917 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3052 + public static function do_version_bump( $version, $old_version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature preserved for the deprecation shim.
3053 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
3054 + }
2918 3055
2919 - // If a front end page is visited after the update, the 'wp' action will fire.
2920 - add_action( 'wp', 'Jetpack::set_update_modal_display' );
3056 + /**
3057 + * Enables the Newsletter (subscriptions) module for existing sites now that it is a default-on module.
3058 + *
3059 + * Fresh installs receive the module via its "Auto Activate: Yes" header, so this only handles sites
3060 + * upgrading from a version where the module defaulted off. It runs once per site (guarded by the
3061 + * subscriptions_default_on_migrated option). Fresh installs are marked as migrated immediately so the
3062 + * migration never runs for them. After it has run, the user's choice to deactivate the module again
3063 + * (for example from the My Jetpack Products page) is respected and never reverted.
3064 + *
3065 + * The module requires a connection, so on a disconnected site the migration is deferred without setting
3066 + * the guard, allowing a later version bump to retry once the site is connected.
3067 + *
3068 + * @param string $version New Jetpack version:timestamp.
3069 + * @param string|false $old_version Previous Jetpack version:timestamp, or false on a fresh install.
3070 + */
3071 + public static function activate_subscriptions_module_for_existing_sites( $version, $old_version ) {
3072 + if ( get_option( 'jetpack_subscriptions_default_on_migrated' ) ) {
3073 + return;
3074 + }
2921 3075
2922 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2923 - add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3076 + // Fresh installs get the module via its "Auto Activate: Yes" header. Mark them as migrated so a
3077 + // later opt-out is never reverted by the existing-site path on a subsequent version bump.
3078 + if ( ! $old_version ) {
3079 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3080 + return;
2924 3081 }
3082 +
3083 + if ( ! self::is_connection_ready() ) {
3084 + return;
3085 + }
3086 +
3087 + // Mark as migrated only once the module is active, so a transient activation failure is retried on
3088 + // a later version bump rather than being silently skipped.
3089 + if ( self::is_module_active( 'subscriptions' ) || self::activate_module( 'subscriptions', false, false ) ) {
3090 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3091 + }
2925 3092 }
2926 3093
2927 3094 /**
3095 + * Option flag that records the AI master-switch opt-out reconciliation has run,
3096 + * so it never runs twice.
3097 + *
3098 + * @var string
3099 + */
3100 + const AI_MASTER_OPTOUT_MIGRATED_OPTION = 'jetpack_ai_master_optout_migrated';
3101 +
3102 + /**
3103 + * Register the on-upgrade init hooks whose relative ORDER matters, extracted so
3104 + * the ordering can be asserted in tests without invoking plugin_upgrade() (whose
3105 + * guards make it unreliable to trigger under test). activate_new_modules()
3106 + * (init, default priority 10) auto-activates "Auto Activate: Yes" modules
3107 + * including the `ai` master; reconcile_ai_master_optout() must run at a LATER
3108 + * priority to honor an explicit AI opt-out.
3109 + *
3110 + * @return void
3111 + */
3112 + public static function register_upgrade_init_hooks() {
3113 + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
3114 + add_action( 'init', array( __CLASS__, 'reconcile_ai_master_optout' ), 20 );
3115 + }
3116 +
3117 + /**
3118 + * Preserves an explicit Jetpack AI opt-out when the `ai` module becomes the site-wide
3119 + * master switch off WordPress.com Simple (self-hosted and Atomic).
3120 + *
3121 + * The `ai` module is "Auto Activate: Yes", so on upgrade {@see self::activate_new_modules()}
3122 + * turns it on for connected sites — the desired default-on / auto-enable-on-connection
3123 + * behavior, which this method deliberately leaves alone. The one case it corrects is a site
3124 + * that had explicitly disabled Jetpack AI (the `jetpack_ai_enabled` option present and falsey)
3125 + * before the module shipped: that opt-out must survive the module becoming the master, so the
3126 + * module is deactivated for exactly those sites. An absent or truthy option is left untouched.
3127 + *
3128 + * Ordering is the whole point. `activate_new_modules()` is hooked on `init` at priority 10 and
3129 + * auto-activates the module there; this method is hooked on `init` at priority 20 (see
3130 + * {@see self::plugin_upgrade()}), so it runs AFTER the auto-activation and its deactivation is
3131 + * the final state. A version-guarded block that ran inline during `plugins_loaded` would be
3132 + * undone by the later auto-activation, which is why this is a late-init hook rather than an
3133 + * inline upgrade step.
3134 + *
3135 + * WordPress.com Simple never runs modules — the option stays the master there — so this is a
3136 + * no-op on Simple. The {@see self::AI_MASTER_OPTOUT_MIGRATED_OPTION} flag makes it run exactly
3137 + * once, which matters because off-Simple the option is no longer the master after this runs:
3138 + * a stale falsey option must not keep re-deactivating a module the user later turns back on.
3139 + *
3140 + * @return void
3141 + */
3142 + public static function reconcile_ai_master_optout() {
3143 + if ( get_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION ) ) {
3144 + return;
3145 + }
3146 +
3147 + // Simple keeps the `jetpack_ai_enabled` option as the master; modules don't run there.
3148 + if ( ( new Host() )->is_wpcom_simple() ) {
3149 + return;
3150 + }
3151 +
3152 + // A sentinel default distinguishes an absent option (leave auto-activation alone) from one
3153 + // explicitly stored falsey (an opt-out to preserve).
3154 + $stored = get_option( 'jetpack_ai_enabled', 'not-set' );
3155 + if ( 'not-set' !== $stored && ! (bool) $stored ) {
3156 + ( new Modules() )->deactivate( 'ai' );
3157 + }
3158 +
3159 + update_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION, true );
3160 + }
3161 +
3162 + /**
3163 + * Deletes obsolete SEO module-state options without changing module activation.
3164 + *
3165 + * @since 16.3
3166 + */
3167 + public static function cleanup_seo_module_state_options() {
3168 + delete_option( 'jetpack_seo_sitemap_enabled' );
3169 + delete_option( 'jetpack_seo_canonical_urls_enabled' );
3170 + delete_option( 'jetpack_seo_module_state_reconciled' );
3171 + }
3172 +
3173 + /**
3174 + * Seeds the Jetpack SEO discoverability cohort once, so the new SEO surface is
3175 + * auto-discoverable on fresh installs but opt-in on existing ones (JETPACK-1700).
3176 + *
3177 + * Hooked on `updating_jetpack_version`, which fires on every install including the
3178 + * first — with `$old_version === false` on a brand-new site (the same signal
3179 + * {@see self::activate_subscriptions_module_for_existing_sites()} keys off). Fresh
3180 + * installs are seeded visible; existing installs are seeded hidden and opt in later
3181 + * via the legacy Traffic page or My Jetpack. `add_option()` makes this seed-once: it
3182 + * never overrides a value a later opt-in (or opt-out) has set. WordPress.com sites
3183 + * ignore this option entirely (always visible) — see
3184 + * {@see \Automattic\Jetpack\SEO\Initializer::is_seo_surface_visible()}.
3185 + *
3186 + * @param string $version The new Jetpack version (unused).
3187 + * @param string|false $old_version The previous version, or false on a fresh install.
3188 + */
3189 + public static function seed_seo_visibility_cohort( $version, $old_version ) {
3190 + add_option( Jetpack_SEO_Initializer::VISIBILITY_OPTION, ! $old_version );
3191 + }
3192 +
3193 + /**
2928 3194 * Sets the display_update_modal state.
3195 + *
3196 + * The release post update modal that read this state has been removed. The write is kept so the
3197 + * method still behaves as documented for the deprecation window. When this is deleted, also drop
3198 + * the matching `display_update_modal` guard in Automattic\Jetpack\CookieState::should_set_cookie(),
3199 + * which exists only to keep this key out of the cookie on the Jetpack admin screen.
3200 + *
3201 + * @deprecated 16.2
2929 3202 */
2930 3203 public static function set_update_modal_display() {
3204 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2931 3205 self::state( 'display_update_modal', true );
2932 3206 }
2933 3207
2934 3208 /**
@@ -2933,11 +3207,16 @@
2933 3207
2934 3208 /**
2935 3209 * Enqueues the block library styles.
2936 3210 *
3211 + * Only ever used by the release post update modal, which has been removed.
3212 + *
3213 + * @deprecated 16.2
3214 + *
2937 3215 * @param string $hook The current admin page.
2938 3216 */
2939 3217 public static function enqueue_block_style( $hook ) {
3218 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2940 3219 if ( 'toplevel_page_jetpack' === $hook ) {
2941 3220 wp_enqueue_style( 'wp-block-library' );
2942 3221 }
2943 3222 }
@@ -2966,9 +3245,8 @@
2966 3245
2967 3246 self::load_modules();
2968 3247
2969 3248 Jetpack_Options::delete_option( 'do_activate' );
2970 - Jetpack_Options::delete_option( 'dismissed_connection_banner' );
2971 3249 }
2972 3250
2973 3251 /**
2974 3252 * Handles the activation of the default modules depending on the current state of the site:
@@ -3027,8 +3305,12 @@
3027 3305 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
3028 3306 self::disconnect();
3029 3307 Jetpack_Options::delete_option( 'version' );
3030 3308 }
3309 +
3310 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3311 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3312 + }
3031 3313 }
3032 3314
3033 3315 /**
3034 3316 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -3056,9 +3338,9 @@
3056 3338 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
3057 3339 }
3058 3340
3059 3341 /**
3060 - * Happens after a successfull disconnection.
3342 + * Happens after a successful disconnection.
3061 3343 *
3062 3344 * @static
3063 3345 */
3064 3346 public static function jetpack_site_disconnected() {
@@ -3063,8 +3345,10 @@
3063 3345 */
3064 3346 public static function jetpack_site_disconnected() {
3065 3347 Identity_Crisis::clear_all_idc_options();
3066 3348
3349 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3350 +
3067 3351 // Delete all the sync related data. Since it could be taking up space.
3068 3352 Sender::get_instance()->uninstall();
3069 3353
3070 3354 /**
@@ -3081,10 +3365,13 @@
3081 3365 }
3082 3366 }
3083 3367
3084 3368 /**
3085 - * Disconnects the user
3369 + * Disconnects the user.
3086 3370 *
3371 + * @deprecated 13.4
3372 + * @see \Automattic\Jetpack\Connection\Manager::disconnect_user()
3373 + *
3087 3374 * @param int $user_id The user ID to disconnect.
3088 3375 */
3089 3376 public function disconnect_user( $user_id ) {
3090 3377 $this->connection_manager->disconnect_user( $user_id );
@@ -3090,21 +3377,8 @@
3090 3377 $this->connection_manager->disconnect_user( $user_id );
3091 3378 }
3092 3379
3093 3380 /**
3094 - * Attempts Jetpack registration. If it fail, a state flag is set: @see ::admin_page_load()
3095 - *
3096 - * @deprecated since Jetpack 9.7.0
3097 - * @see Automattic\Jetpack\Connection\Manager::try_registration()
3098 - *
3099 - * @return bool|WP_Error
3100 - */
3101 - public static function try_registration() {
3102 - _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Manager::try_registration' );
3103 - return static::connection()->try_registration();
3104 - }
3105 -
3106 - /**
3107 3381 * Checking the domain names in beta versions.
3108 3382 * If this is a development version, before attempting to connect, let's make sure that the domains are viable.
3109 3383 *
3110 3384 * @param null|\WP_Error $error The domain validation error, or `null` if everything's fine.
@@ -3138,10 +3412,17 @@
3138 3412 * @param mixed $code Error code to log.
3139 3413 * @param mixed $data Data to log.
3140 3414 */
3141 3415 public static function log( $code, $data = null ) {
3416 +
3417 + $raw_log = Jetpack_Options::get_option( 'log', array() );
3418 + // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
3419 + if ( ! is_array( $raw_log ) ) {
3420 + return;
3421 + }
3422 +
3142 3423 // only grab the latest 200 entries.
3143 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3424 + $log = array_slice( $raw_log, -199, 199 );
3144 3425
3145 3426 // Append our event to the log.
3146 3427 $log_entry = array(
3147 3428 'time' => time(),
@@ -3241,8 +3522,15 @@
3241 3522
3242 3523 /**
3243 3524 * Return stat data for WPCOM sync.
3244 3525 *
3526 + * The Sync stats module was this method's last caller and moved to the Connection package's
3527 + * `Heartbeat::generate_stats_array()`, which assembles the heartbeat data through the
3528 + * `jetpack_heartbeat_stats_array` filter. Note the package method does not include the extended
3529 + * data from `get_additional_stat_data()`, so callers relying on `$extended` need to add it themselves.
3530 + *
3531 + * @deprecated 16.2
3532 + *
3245 3533 * @param bool $encode JSON encode the result.
3246 3534 * @param bool $extended Adds additional stats data.
3247 3535 *
3248 3536 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3247,10 +3535,15 @@
3247 3535 *
3248 3536 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3249 3537 */
3250 3538 public static function get_stat_data( $encode = true, $extended = true ) {
3251 - $data = Jetpack_Heartbeat::generate_stats_array();
3539 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3252 3540
3541 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
3542 + ? Heartbeat::get_environment_stats()
3543 + : array();
3544 + $data = array_merge( Jetpack_Heartbeat::generate_stats_array(), $env_stats );
3545 +
3253 3546 if ( $extended ) {
3254 3547 $additional_data = self::get_additional_stat_data();
3255 3548 $data = array_merge( $data, $additional_data );
3256 3549 }
@@ -3255,9 +3548,9 @@
3255 3548 $data = array_merge( $data, $additional_data );
3256 3549 }
3257 3550
3258 3551 if ( $encode ) {
3259 - return wp_json_encode( $data );
3552 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3260 3553 }
3261 3554
3262 3555 return $data;
3263 3556 }
@@ -3329,23 +3622,24 @@
3329 3622 $fallback_no_verify_ssl_certs = Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
3330 3623 /** Already documented in automattic/jetpack-connection::src/class-client.php */
3331 3624 $client_verify_ssl_certs = apply_filters( 'jetpack_client_verify_ssl_certs', false );
3332 3625
3333 - if ( ! $is_offline_mode ) {
3334 - Jetpack_Connection_Banner::init();
3335 - Jetpack_Connection_Widget::init();
3336 - }
3626 + // Run post-activation actions if needed.
3627 + $this->plugin_post_activation();
3337 3628
3338 3629 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3339 3630 // Upgrade: 1.1 -> 1.1.1
3340 3631 // Check and see if host can verify the Jetpack servers' SSL certificate.
3341 3632 $args = array();
3633 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3342 3634 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3343 3635 }
3344 3636
3345 - Jetpack_Recommendations_Banner::init();
3346 -
3347 - if ( current_user_can( 'manage_options' ) && ! self::permit_ssl() ) {
3637 + if (
3638 + current_user_can( 'manage_options' )
3639 + && ! self::permit_ssl()
3640 + && ! $is_offline_mode
3641 + ) {
3348 3642 add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3349 3643 }
3350 3644
3351 3645 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
@@ -3354,12 +3648,8 @@
3354 3648 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3355 3649 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3356 3650 }
3357 3651
3358 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3359 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3360 - }
3361 -
3362 3652 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3363 3653
3364 3654 if ( self::is_connection_ready() || $is_offline_mode ) {
3365 3655 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3364,13 +3654,8 @@
3364 3654 if ( self::is_connection_ready() || $is_offline_mode ) {
3365 3655 // Artificially throw errors in certain specific cases during plugin activation.
3366 3656 add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3367 3657 }
3368 -
3369 - // Add custom column in wp-admin/users.php to show whether user is linked.
3370 - add_filter( 'manage_users_columns', array( $this, 'jetpack_icon_user_connected' ) );
3371 - add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3372 - add_action( 'admin_print_styles', array( $this, 'jetpack_user_col_style' ) );
3373 3658 }
3374 3659
3375 3660 /**
3376 3661 * Adds body classes.
@@ -3403,8 +3688,9 @@
3403 3688 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3404 3689 * This function artificially throws errors for such cases (per a specific list).
3405 3690 *
3406 3691 * @param string $plugin The activated plugin.
3692 + * @throws RuntimeException If a conflicting plugin is detected.
3407 3693 */
3408 3694 public function throw_error_on_activate_plugin( $plugin ) {
3409 3695 $active_modules = self::get_active_modules();
3410 3696
@@ -3417,8 +3703,9 @@
3417 3703 if ( 'stats.php' === basename( $plugin ) ) {
3418 3704 $throw = true;
3419 3705 }
3420 3706 } else {
3707 + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Checked above. See also https://github.com/phan/phan/issues/1204.
3421 3708 $reflection = new ReflectionFunction( 'stats_get_api_key' );
3422 3709 if ( basename( $plugin ) === basename( $reflection->getFileName() ) ) {
3423 3710 $throw = true;
3424 3711 }
@@ -3425,9 +3712,9 @@
3425 3712 }
3426 3713
3427 3714 if ( $throw ) {
3428 3715 /* translators: Plugin name to deactivate. */
3429 - trigger_error( sprintf( esc_html__( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
3716 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3430 3717 }
3431 3718 }
3432 3719 }
3433 3720
@@ -3509,8 +3796,9 @@
3509 3796 /**
3510 3797 * Handler for Jetpack remote file uploads.
3511 3798 *
3512 3799 * @access public
3800 + * @return never
3513 3801 */
3514 3802 public function remote_request_handlers() {
3515 3803 switch ( current_filter() ) {
3516 3804 case 'wp_ajax_nopriv_jetpack_upload_file':
@@ -3537,18 +3825,17 @@
3537 3825 if ( ! is_int( $status_code ) ) {
3538 3826 $status_code = 400;
3539 3827 }
3540 3828
3541 - status_header( $status_code );
3542 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3829 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3543 3830 }
3544 3831
3545 - status_header( 200 );
3546 3832 if ( true === $response ) {
3547 - exit;
3833 + status_header( 200 );
3834 + exit( 0 );
3548 3835 }
3549 3836
3550 - die( wp_json_encode( (object) $response ) );
3837 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3551 3838 }
3552 3839
3553 3840 /**
3554 3841 * Uploads a file gotten from the global $_FILES.
@@ -3556,9 +3843,9 @@
3556 3843 * the attachment file of the media item (gotten through of the post_id)
3557 3844 * will be updated instead of add a new one.
3558 3845 *
3559 3846 * @param boolean $update_media_item - update media attachment.
3560 - * @return array - An array describing the uploadind files process.
3847 + * @return array|WP_Error - An array describing the uploading files process.
3561 3848 */
3562 3849 public function upload_handler( $update_media_item = false ) {
3563 3850 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
3564 3851 return new WP_Error( 405, get_status_header_desc( 405 ), 405 );
@@ -3609,9 +3896,9 @@
3609 3896 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3610 3897 }
3611 3898
3612 3899 $uploaded_files = array();
3613 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3900 + $global_post = $GLOBALS['post'] ?? null;
3614 3901 unset( $GLOBALS['post'] );
3615 3902 if ( empty( $_FILES['media']['name'] ) ) {
3616 3903 // Nothing to process, just return.
3617 3904 return $uploaded_files;
@@ -3618,9 +3905,9 @@
3618 3905 }
3619 3906 foreach ( $_FILES['media']['name'] as $index => $name ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, unslash sniff is wrong. Validation should happen below.
3620 3907 $file = array();
3621 3908 foreach ( $media_keys as $media_key ) {
3622 - $file[ $media_key ] = isset( $_FILES['media'][ $media_key ][ $index ] ) ? $_FILES['media'][ $media_key ][ $index ] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3909 + $file[ $media_key ] = $_FILES['media'][ $media_key ][ $index ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3623 3910 }
3624 3911
3625 3912 list( $hmac_provided, $salt ) = isset( $_POST['_jetpack_file_hmac_media'][ $index ] ) ? explode( ':', filter_var( wp_unslash( $_POST['_jetpack_file_hmac_media'][ $index ] ) ) ) : array( 'no', '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce should have been checked by the caller.
3626 3913
@@ -3666,9 +3953,9 @@
3666 3953 'type' => (string) $edited_media_item->post_mime_type,
3667 3954 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3668 3955 );
3669 3956
3670 - return (array) array( $response );
3957 + return array( $response );
3671 3958 }
3672 3959
3673 3960 $attachment_id = media_handle_upload(
3674 3961 '.jetpack.upload.',
@@ -3707,67 +3994,8 @@
3707 3994 return $uploaded_files;
3708 3995 }
3709 3996
3710 3997 /**
3711 - * Add help to the Jetpack page
3712 - *
3713 - * @since Jetpack (1.2.3)
3714 - * @return void
3715 - */
3716 - public function admin_help() {
3717 - $current_screen = get_current_screen();
3718 -
3719 - // Overview.
3720 - $current_screen->add_help_tab(
3721 - array(
3722 - 'id' => 'home',
3723 - 'title' => __( 'Home', 'jetpack' ),
3724 - 'content' =>
3725 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3726 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3727 - '<p>' . __( 'On this page, you are able to view the modules available within Jetpack, learn more about them, and activate or deactivate them as needed.', 'jetpack' ) . '</p>',
3728 - )
3729 - );
3730 -
3731 - // Screen Content.
3732 - if ( current_user_can( 'manage_options' ) ) {
3733 - $current_screen->add_help_tab(
3734 - array(
3735 - 'id' => 'settings',
3736 - 'title' => __( 'Settings', 'jetpack' ),
3737 - 'content' =>
3738 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3739 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3740 - '<ol>' .
3741 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3742 - '<li>' . __( 'Using the checkboxes next to each module, you can select multiple modules to toggle via the Bulk Actions menu at the top of the list.', 'jetpack' ) . '</li>' .
3743 - '</ol>' .
3744 - '<p>' . __( 'Using the tools on the right, you can search for specific modules, filter by module categories or which are active, or change the sorting order.', 'jetpack' ) . '</p>',
3745 - )
3746 - );
3747 - }
3748 -
3749 - // Help Sidebar.
3750 - $support_url = Redirect::get_url( 'jetpack-support' );
3751 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3752 - $current_screen->set_help_sidebar(
3753 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3754 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3755 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3756 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3757 - );
3758 - }
3759 -
3760 - /**
3761 - * Enqueues the jetpack-icons style.
3762 - *
3763 - * @return void
3764 - */
3765 - public function admin_menu_css() {
3766 - wp_enqueue_style( 'jetpack-icons' );
3767 - }
3768 -
3769 - /**
3770 3998 * Add action links for the Jetpack plugin.
3771 3999 *
3772 4000 * @param array $actions Plugin actions.
3773 4001 *
@@ -3773,14 +4001,11 @@
3773 4001 *
3774 4002 * @return array
3775 4003 */
3776 4004 public function plugin_action_links( $actions ) {
3777 - $support_link = ( new Host() )->is_woa_site() ? 'https://wordpress.com/help/contact/' : self::admin_url( 'page=jetpack-debugger' );
3778 -
3779 4005 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3780 4006 return array_merge(
3781 - array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3782 - array( 'support' => sprintf( '<a href="%s">%s</a>', esc_url( $support_link ), __( 'Support', 'jetpack' ) ) ),
4007 + array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack-settings#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3783 4008 $actions
3784 4009 );
3785 4010 }
3786 4011
@@ -3787,53 +4012,8 @@
3787 4012 return $actions;
3788 4013 }
3789 4014
3790 4015 /**
3791 - * Add an activation modal to the plugins page and the main dashboard.
3792 - *
3793 - * @param string $hook The current admin page.
3794 - *
3795 - * @return void
3796 - */
3797 - public function activate_dialog( $hook ) {
3798 - /*
3799 - * We do not need it on other plugin pages,
3800 - * or when Jetpack is already connected.
3801 - */
3802 - if (
3803 - ! in_array( $hook, array( 'plugins.php', 'index.php' ), true )
3804 - || self::is_connection_ready()
3805 - ) {
3806 - return;
3807 - }
3808 -
3809 - // add an activation script that will pick up deactivation actions for the Jetpack plugin.
3810 - Assets::register_script(
3811 - 'jetpack-full-activation-modal-js',
3812 - '_inc/build/activation-modal.js',
3813 - JETPACK__PLUGIN_FILE,
3814 - array(
3815 - 'enqueue' => true,
3816 - 'in_footer' => true,
3817 - 'textdomain' => 'jetpack',
3818 - 'dependencies' => array(
3819 - 'wp-polyfill',
3820 - 'wp-components',
3821 - ),
3822 - )
3823 - );
3824 -
3825 - // Add objects to be passed to the initial state of the app.
3826 - // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3827 - wp_add_inline_script( 'jetpack-full-activation-modal-js', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state() ) ) . '"));', 'before' );
3828 -
3829 - // Adds Connection package initial state.
3830 - Connection_Initial_State::render_script( 'jetpack-full-activation-modal-js' );
3831 -
3832 - add_action( 'admin_notices', array( $this, 'jetpack_plugin_portal_containers' ) );
3833 - }
3834 -
3835 - /**
3836 4016 * Adds the deactivation warning modal for Jetpack.
3837 4017 *
3838 4018 * @param string $hook The current admin page.
3839 4019 *
@@ -3853,14 +4033,10 @@
3853 4033 'jetpack-plugins-page-js',
3854 4034 '_inc/build/plugins-page.js',
3855 4035 JETPACK__PLUGIN_FILE,
3856 4036 array(
3857 - 'in_footer' => true,
3858 - 'textdomain' => 'jetpack',
3859 - 'dependencies' => array(
3860 - 'wp-polyfill',
3861 - 'wp-components',
3862 - ),
4037 + 'in_footer' => true,
4038 + 'textdomain' => 'jetpack',
3863 4039 )
3864 4040 );
3865 4041 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3866 4042
@@ -3865,9 +4041,9 @@
3865 4041 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3866 4042
3867 4043 // Add objects to be passed to the initial state of the app.
3868 4044 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3869 - wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state() ) ) . '"));', 'before' );
4045 + wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=' . wp_json_encode( Jetpack_Redux_State_Helper::get_plugins_page_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
3870 4046
3871 4047 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3872 4048 }
3873 4049 }
@@ -3916,9 +4092,9 @@
3916 4092 // @todo provide way to go to specific calypso env.
3917 4093 self::get_calypso_host() . 'jetpack/connect'
3918 4094 )
3919 4095 );
3920 - exit;
4096 + exit( 0 );
3921 4097 }
3922 4098 }
3923 4099
3924 4100 /*
@@ -3953,8 +4129,28 @@
3953 4129 * Done!
3954 4130 */
3955 4131
3956 4132 /**
4133 + * Build the user-facing description stored alongside a registration error code.
4134 + *
4135 + * @since 16.2
4136 + *
4137 + * @param string $error_code The WP_Error code.
4138 + * @param string $message The WP_Error message.
4139 + * @return string The description, empty when the message is not user-facing copy.
4140 + */
4141 + public static function get_registration_error_description( $error_code, $message ) {
4142 + // Manager::validate_remote_register_response() does not always put user-facing copy in the
4143 + // message slot: wpcom_5??, wpcom_408 and wpcom_bad_response store the HTTP status there,
4144 + // and jetpack_id stores the raw response body, which can also overflow the state cookie.
4145 + if ( 'jetpack_id' === $error_code || is_numeric( $message ) ) {
4146 + return '';
4147 + }
4148 +
4149 + return mb_substr( (string) $message, 0, 250 );
4150 + }
4151 +
4152 + /**
3957 4153 * Handles the page load events for the Jetpack admin page
3958 4154 */
3959 4155 public function admin_page_load() {
3960 4156 $error = false;
@@ -3990,10 +4186,11 @@
3990 4186 $registered = static::connection()->try_registration();
3991 4187 if ( is_wp_error( $registered ) ) {
3992 4188 $error = $registered->get_error_code();
3993 4189 self::state( 'error', $error );
3994 - self::state( 'error', $registered->get_error_message() );
3995 4190
4191 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4192 +
3996 4193 /**
3997 4194 * Jetpack registration Error.
3998 4195 *
3999 4196 * @since 7.5.0
@@ -4017,12 +4214,8 @@
4017 4214 do_action( 'jetpack_connection_register_success', $from );
4018 4215
4019 4216 $url = $this->build_connect_url( true, $redirect, $from );
4020 4217
4021 - if ( ! empty( $_GET['onboarding'] ) ) {
4022 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
4023 - }
4024 -
4025 4218 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
4026 4219 $url = add_query_arg( 'auth_approved', 'true', $url );
4027 4220 }
4028 4221
@@ -4027,9 +4220,9 @@
4027 4220 }
4028 4221
4029 4222 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
4030 4223 wp_safe_redirect( $url );
4031 - exit;
4224 + exit( 0 );
4032 4225 case 'activate':
4033 4226 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
4034 4227 $error = 'cheatin';
4035 4228 break;
@@ -4043,9 +4236,9 @@
4043 4236 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
4044 4237 }
4045 4238 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4046 4239 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4047 - exit;
4240 + exit( 0 );
4048 4241 case 'activate_default_modules':
4049 4242 check_admin_referer( 'activate_default_modules' );
4050 4243 self::log( 'activate_default_modules' );
4051 4244 self::restate();
@@ -4053,9 +4246,9 @@
4053 4246 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
4054 4247 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
4055 4248 self::activate_default_modules( $min_version, $max_version, $other_modules );
4056 4249 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4057 - exit;
4250 + exit( 0 );
4058 4251 case 'disconnect':
4059 4252 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4060 4253 $error = 'cheatin';
4061 4254 break;
@@ -4064,9 +4257,9 @@
4064 4257 check_admin_referer( 'jetpack-disconnect' );
4065 4258 self::log( 'disconnect' );
4066 4259 self::disconnect();
4067 4260 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
4068 - exit;
4261 + exit( 0 );
4069 4262 case 'reconnect':
4070 4263 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4071 4264 $error = 'cheatin';
4072 4265 break;
@@ -4077,9 +4270,9 @@
4077 4270 self::disconnect();
4078 4271
4079 4272 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
4080 4273 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4081 - exit;
4274 + exit( 0 );
4082 4275 case 'deactivate':
4083 4276 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4084 4277 $error = 'cheatin';
4085 4278 break;
@@ -4093,9 +4286,9 @@
4093 4286 self::state( 'message', 'module_deactivated' );
4094 4287 }
4095 4288 self::state( 'module', $modules );
4096 4289 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4097 - exit;
4290 + exit( 0 );
4098 4291 case 'unlink':
4099 4292 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
4100 4293 check_admin_referer( 'jetpack-unlink' );
4101 4294 self::log( 'unlink' );
@@ -4105,32 +4298,9 @@
4105 4298 wp_safe_redirect( admin_url() );
4106 4299 } else {
4107 4300 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
4108 4301 }
4109 - exit;
4110 - case 'onboard':
4111 - if ( ! current_user_can( 'manage_options' ) ) {
4112 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4113 - } else {
4114 - self::create_onboarding_token();
4115 - $url = $this->build_connect_url( true );
4116 -
4117 - $token = Jetpack_Options::get_option( 'onboarding' );
4118 -
4119 - if ( false !== ( $token ) ) {
4120 - $url = add_query_arg( 'onboarding', $token, $url );
4121 - }
4122 -
4123 - $calypso_env = ( new Host() )->get_calypso_env();
4124 - if ( ! empty( $calypso_env ) ) {
4125 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
4126 - }
4127 -
4128 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
4129 - wp_safe_redirect( $url );
4130 - exit;
4131 - }
4132 - exit;
4302 + exit( 0 );
4133 4303 default:
4134 4304 /**
4135 4305 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4136 4306 *
@@ -4353,37 +4523,8 @@
4353 4523 endif;
4354 4524 }
4355 4525
4356 4526 /**
4357 - * We can't always respond to a signed XML-RPC request with a
4358 - * helpful error message. In some circumstances, doing so could
4359 - * leak information.
4360 - *
4361 - * Instead, track that the error occurred via a Jetpack_Option,
4362 - * and send that data back in the heartbeat.
4363 - * All this does is increment a number, but it's enough to find
4364 - * trends.
4365 - *
4366 - * @param WP_Error $xmlrpc_error The error produced during
4367 - * signature validation.
4368 - */
4369 - public function track_xmlrpc_error( $xmlrpc_error ) {
4370 - $code = is_wp_error( $xmlrpc_error )
4371 - ? $xmlrpc_error->get_error_code()
4372 - : 'should-not-happen';
4373 -
4374 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4375 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4376 - // No need to update the option if we already have
4377 - // this code stored.
4378 - return;
4379 - }
4380 - $xmlrpc_errors[ $code ] = true;
4381 -
4382 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4383 - }
4384 -
4385 - /**
4386 4527 * Initialize the jetpack stats instance only when needed
4387 4528 *
4388 4529 * @return void
4389 4530 */
@@ -4506,9 +4647,9 @@
4506 4647 return $this->build_connect_url( $raw, $redirect, $from, true );
4507 4648 }
4508 4649 }
4509 4650
4510 - $url = static::build_authorize_url( $redirect );
4651 + $url = ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4511 4652 }
4512 4653
4513 4654 if ( $from ) {
4514 4655 $url = add_query_arg( 'from', $from, $url );
@@ -4533,66 +4674,30 @@
4533 4674 * @param null $deprecated Deprecated since Jetpack 10.9.
4534 4675 *
4535 4676 * @todo Update default value for redirect since the called function expects a string.
4536 4677 *
4678 + * @deprecated 13.4
4679 + *
4537 4680 * @return mixed|void
4538 4681 */
4539 4682 public static function build_authorize_url( $redirect = false, $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
4683 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::build_authorize_url' );
4540 4684
4541 - add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4542 - add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4543 -
4544 - $c8n = self::connection();
4545 - $url = $c8n->get_authorization_url( wp_get_current_user(), $redirect );
4546 -
4547 - remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4548 - remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4549 -
4550 - /**
4551 - * Filter the URL used when authorizing a user to a WordPress.com account.
4552 - *
4553 - * @since 8.9.0
4554 - *
4555 - * @param string $url Connection URL.
4556 - */
4557 - return apply_filters( 'jetpack_build_authorize_url', $url );
4685 + return ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4558 4686 }
4559 4687
4560 4688 /**
4561 4689 * Filters the connection URL parameter array.
4562 4690 *
4691 + * @deprecated 13.4
4692 + *
4563 4693 * @param array $args default URL parameters used by the package.
4564 4694 * @return array the modified URL arguments array.
4565 4695 */
4566 4696 public static function filter_connect_request_body( $args ) {
4567 - if (
4568 - Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4569 - && include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4570 - ) {
4571 - $gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4572 - $args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
4573 - ? $gp_locale->slug
4574 - : '';
4575 - }
4697 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_request_body' );
4576 4698
4577 - $tracking = new Tracking();
4578 - $tracks_identity = $tracking->tracks_get_identity( $args['state'] );
4579 -
4580 - $args = array_merge(
4581 - $args,
4582 - array(
4583 - '_ui' => $tracks_identity['_ui'],
4584 - '_ut' => $tracks_identity['_ut'],
4585 - )
4586 - );
4587 -
4588 - $calypso_env = ( new Host() )->get_calypso_env();
4589 -
4590 - if ( ! empty( $calypso_env ) ) {
4591 - $args['calypso_env'] = $calypso_env;
4592 - }
4593 -
4594 - return $args;
4699 + return Authorize_Redirect::filter_connect_request_body( $args );
4595 4700 }
4596 4701
4597 4702 /**
4598 4703 * Filters the `jetpack/v4/connection/data` API response of the Connection package in order to
@@ -4629,41 +4734,19 @@
4629 4734 return $current_user_connection_data;
4630 4735 }
4631 4736
4632 4737 /**
4633 - * Filters the URL that will process the connection data. It can be different from the URL
4634 - * that we send the user to after everything is done.
4635 - *
4636 - * @param String $processing_url the default redirect URL used by the package.
4637 - * @return String the modified URL.
4638 - *
4639 - * @deprecated since Jetpack 9.5.0
4640 - */
4641 - public static function filter_connect_processing_url( $processing_url ) {
4642 - _deprecated_function( __METHOD__, 'jetpack-9.5' );
4643 -
4644 - $processing_url = admin_url( 'admin.php?page=jetpack' ); // Making PHPCS happy.
4645 - return $processing_url;
4646 - }
4647 -
4648 - /**
4649 4738 * Filters the redirection URL that is used for connect requests. The redirect
4650 4739 * URL should return the user back to the Jetpack console.
4651 4740 *
4741 + * @deprecated 13.4
4742 + *
4652 4743 * @param String $redirect the default redirect URL used by the package.
4653 4744 * @return String the modified URL.
4654 4745 */
4655 4746 public static function filter_connect_redirect_url( $redirect ) {
4656 - $jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4657 - $redirect = $redirect
4658 - ? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4659 - : $jetpack_admin_page;
4660 -
4661 - if ( isset( $_REQUEST['is_multisite'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- not making a site change here.
4662 - $redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4663 - }
4664 -
4665 - return $redirect;
4747 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_redirect_url' );
4748 + return Authorize_Redirect::filter_connect_redirect_url( $redirect );
4666 4749 }
4667 4750
4668 4751 /**
4669 4752 * This action fires at the beginning of the Manager::authorize method.
@@ -4727,11 +4810,8 @@
4727 4810 *
4728 4811 * @param array $data The request data.
4729 4812 */
4730 4813 public static function authorize_ending_authorized( $data ) {
4731 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4732 - self::invalidate_onboarding_token();
4733 -
4734 4814 // If redirect_uri is SSO, ensure SSO module is enabled.
4735 4815 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4736 4816
4737 4817 /** This filter is documented in class.jetpack-cli.php */
@@ -4871,10 +4951,12 @@
4871 4951 $result = self::permit_ssl( true );
4872 4952 wp_send_json(
4873 4953 array(
4874 4954 'enabled' => $result,
4875 - 'message' => get_transient( 'jetpack_https_test_message' ),
4876 - )
4955 + 'message' => self::get_ssl_test_message(),
4956 + ),
4957 + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
4958 + JSON_UNESCAPED_SLASHES
4877 4959 );
4878 4960 }
4879 4961
4880 4962 /* Client API */
@@ -4881,8 +4963,10 @@
4881 4963
4882 4964 /**
4883 4965 * Verify the onboarding token.
4884 4966 *
4967 + * @deprecated since 13.9
4968 + *
4885 4969 * @param array $token_data Token data.
4886 4970 * @param string $token Token value.
4887 4971 * @param string $request_data JSON-encoded request data.
4888 4972 *
@@ -4888,8 +4972,9 @@
4888 4972 *
4889 4973 * @return mixed
4890 4974 */
4891 4975 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4976 + _deprecated_function( __METHOD__, '13.9' );
4892 4977 // Default to a blog token.
4893 4978 $token_type = 'blog';
4894 4979
4895 4980 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4917,9 +5002,9 @@
4917 5002 $jp_user = get_user_by( 'email', $jpo_user );
4918 5003 if ( is_a( $jp_user, 'WP_User' ) ) {
4919 5004 wp_set_current_user( $jp_user->ID );
4920 5005 $user_can = is_multisite()
4921 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5006 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4922 5007 : current_user_can( 'manage_options' );
4923 5008 if ( $user_can ) {
4924 5009 $token_type = 'user';
4925 5010 $token->external_user_id = $jp_user->ID;
@@ -4936,11 +5021,13 @@
4936 5021
4937 5022 /**
4938 5023 * Create a random secret for validating onboarding payload
4939 5024 *
5025 + * @deprecated since 13.9
4940 5026 * @return string Secret token
4941 5027 */
4942 5028 public static function create_onboarding_token() {
5029 + _deprecated_function( __METHOD__, '13.9' );
4943 5030 $token = Jetpack_Options::get_option( 'onboarding' );
4944 5031 if ( false === ( $token ) ) {
4945 5032 $token = wp_generate_password( 32, false );
4946 5033 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4951,11 +5038,13 @@
4951 5038
4952 5039 /**
4953 5040 * Remove the onboarding token
4954 5041 *
5042 + * @deprecated since 13.9
4955 5043 * @return bool True on success, false on failure
4956 5044 */
4957 5045 public static function invalidate_onboarding_token() {
5046 + _deprecated_function( __METHOD__, '13.9' );
4958 5047 return Jetpack_Options::delete_option( 'onboarding' );
4959 5048 }
4960 5049
4961 5050 /**
@@ -4960,8 +5049,10 @@
4960 5049
4961 5050 /**
4962 5051 * Validate an onboarding token for a specific action
4963 5052 *
5053 + * @deprecated since 13.9
5054 + *
4964 5055 * @param string $token Onboarding token.
4965 5056 * @param string $action Action name.
4966 5057 *
4967 5058 * @return boolean True if token/action pair is accepted, false if not
@@ -4966,8 +5057,9 @@
4966 5057 *
4967 5058 * @return boolean True if token/action pair is accepted, false if not
4968 5059 */
4969 5060 public static function validate_onboarding_token_action( $token, $action ) {
5061 + _deprecated_function( __METHOD__, '13.9' );
4970 5062 // Compare tokens, bail if tokens do not match.
4971 5063 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4972 5064 return false;
4973 5065 }
@@ -4993,39 +5085,41 @@
4993 5085 * @return boolean
4994 5086 * @since 2.3.3
4995 5087 */
4996 5088 public static function permit_ssl( $force_recheck = false ) {
4997 - // Do some fancy tests to see if ssl is being supported.
4998 - if ( ! $force_recheck ) {
4999 - $ssl = get_transient( 'jetpack_https_test' );
5089 + if ( ! method_exists( Heartbeat::class, 'permit_ssl' ) ) {
5090 + // Skip the SSL-fail notice when the check cannot run.
5091 + return true;
5000 5092 }
5001 5093
5002 - if ( $force_recheck || false === $ssl ) {
5003 - $message = '';
5004 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
5005 - $ssl = 0;
5006 - } else {
5007 - $ssl = 1;
5094 + return Heartbeat::permit_ssl( $force_recheck );
5095 + }
5008 5096
5009 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
5010 - $ssl = 0;
5011 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
5012 - } else {
5013 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
5014 - if ( is_wp_error( $response ) ) {
5015 - $ssl = 0;
5016 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
5017 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
5018 - $ssl = 0;
5019 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
5020 - }
5021 - }
5022 - }
5023 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
5024 - set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5097 + /**
5098 + * Returns a localized message describing the last SSL connectivity failure, if any.
5099 + *
5100 + * The Connection package's canonical SSL check stores a neutral reason code; this maps it to
5101 + * a translated, `jetpack`-domain message for display in the admin notice and AJAX recheck.
5102 + *
5103 + * @since 16.1
5104 + *
5105 + * @return string The localized message, or an empty string when there is no failure.
5106 + */
5107 + public static function get_ssl_test_message() {
5108 + if ( ! method_exists( Heartbeat::class, 'get_ssl_test_error' ) ) {
5109 + return '';
5025 5110 }
5026 5111
5027 - return (bool) $ssl;
5112 + $error = Heartbeat::get_ssl_test_error();
5113 +
5114 + switch ( $error['code'] ) {
5115 + case 'no_ssl_support':
5116 + return __( 'WordPress reports no SSL support', 'jetpack' );
5117 + case 'bad_response':
5118 + return __( 'Response was not OK: ', 'jetpack' ) . $error['detail'];
5119 + default:
5120 + return '';
5121 + }
5028 5122 }
5029 5123
5030 5124 /**
5031 5125 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -5044,9 +5138,9 @@
5044 5138 <p><?php esc_html_e( 'Your site could not connect to WordPress.com via HTTPS. This could be due to any number of reasons, including faulty SSL certificates, misconfigured or missing SSL libraries, or network issues.', 'jetpack' ); ?></p>
5045 5139 <p>
5046 5140 <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
5047 5141 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
5048 - <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
5142 + <span id="jetpack-recheck-ssl-output"><?php echo esc_html( self::get_ssl_test_message() ); ?></span>
5049 5143 </p>
5050 5144 <p>
5051 5145 <?php
5052 5146 printf(
@@ -5065,18 +5159,18 @@
5065 5159 <script type="text/javascript">
5066 5160 jQuery( document ).ready( function( $ ) {
5067 5161 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
5068 5162 var $this = $( this );
5069 - $this.html( <?php echo wp_json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5163 + $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
5070 5164 $( '#jetpack-recheck-ssl-output' ).html( '' );
5071 5165 e.preventDefault();
5072 - var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce ); ?> };
5166 + var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
5073 5167 $.post( ajaxurl, data )
5074 5168 .done( function( response ) {
5075 5169 if ( response.enabled ) {
5076 5170 $( '#jetpack-ssl-warning' ).hide();
5077 5171 } else {
5078 - this.html( <?php echo wp_json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5172 + this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
5079 5173 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
5080 5174 }
5081 5175 }.bind( $this ) );
5082 5176 } );
@@ -5102,26 +5196,8 @@
5102 5196 return $jetpack->connection_manager;
5103 5197 }
5104 5198
5105 5199 /**
5106 - * Creates two secret tokens and the end of life timestamp for them.
5107 - *
5108 - * Note these tokens are unique per call, NOT static per site for connecting.
5109 - *
5110 - * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->generate() instead.
5111 - *
5112 - * @since 2.6
5113 - * @param String $action The action name.
5114 - * @param Integer $user_id The user identifier.
5115 - * @param Integer $exp Expiration time in seconds.
5116 - * @return array
5117 - */
5118 - public static function generate_secrets( $action, $user_id = false, $exp = 600 ) {
5119 - _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->generate' );
5120 - return self::connection()->generate_secrets( $action, $user_id, $exp );
5121 - }
5122 -
5123 - /**
5124 5200 * Get verification secrets.
5125 5201 *
5126 5202 * @param string $action Action name.
5127 5203 * @param int $user_id User ID.
@@ -5142,35 +5218,8 @@
5142 5218 return $secrets;
5143 5219 }
5144 5220
5145 5221 /**
5146 - * Register a connection.
5147 - *
5148 - * @deprecated Jetpack 9.7.0
5149 - * @see Automattic\Jetpack\Connection\Manager::try_registration()
5150 - *
5151 - * @return bool|WP_Error
5152 - */
5153 - public static function register() {
5154 - _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Manager::try_registration' );
5155 - return static::connection()->try_registration( false );
5156 - }
5157 -
5158 - /**
5159 - * Filters the registration request body to include tracking properties.
5160 - *
5161 - * @deprecated Jetpack 9.7.0
5162 - * @see Automattic\Jetpack\Connection\Utils::filter_register_request_body()
5163 - *
5164 - * @param array $properties Token request properties.
5165 - * @return array amended properties.
5166 - */
5167 - public static function filter_register_request_body( $properties ) {
5168 - _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Utils::filter_register_request_body' );
5169 - return Connection_Utils::filter_register_request_body( $properties );
5170 - }
5171 -
5172 - /**
5173 5222 * Filters the token request body to include tracking properties.
5174 5223 *
5175 5224 * @param array $properties Token request properties.
5176 5225 *
@@ -5191,9 +5240,9 @@
5191 5240
5192 5241 /**
5193 5242 * If the db version is showing something other that what we've got now, bump it to current.
5194 5243 *
5195 - * @return bool: True if the option was incorrect and updated, false if nothing happened.
5244 + * @return bool True if the option was incorrect and updated, false if nothing happened.
5196 5245 */
5197 5246 public static function maybe_set_version_option() {
5198 5247 list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5199 5248 if ( JETPACK__VERSION !== $version ) {
@@ -5211,21 +5260,8 @@
5211 5260
5212 5261 /* Client Server API */
5213 5262
5214 5263 /**
5215 - * Loads the Jetpack XML-RPC client.
5216 - * No longer necessary, as the XML-RPC client will be automagically loaded.
5217 - *
5218 - * Note: we cannot remove this function yet as it is used in this plugin:
5219 - * https://wordpress.org/plugins/jetpack-subscription-form/
5220 - *
5221 - * @deprecated since 7.7.0
5222 - */
5223 - public static function load_xml_rpc_client() {
5224 - _deprecated_function( __METHOD__, 'jetpack-7.7' );
5225 - }
5226 -
5227 - /**
5228 5264 * State is passed via cookies from one request to the next, but never to subsequent requests.
5229 5265 * SET: state( $key, $value );
5230 5266 * GET: $value = state( $key );
5231 5267 *
@@ -5299,20 +5335,8 @@
5299 5335
5300 5336 self::state( 'privacy_checks', $privacy_checks );
5301 5337 }
5302 5338
5303 - /**
5304 - * Serve a WordPress.com static resource via a randomized wp.com subdomain.
5305 - *
5306 - * @deprecated 9.3.0 Use Assets::staticize_subdomain.
5307 - *
5308 - * @param string $url WordPress.com static resource URL.
5309 - */
5310 - public static function staticize_subdomain( $url ) {
5311 - _deprecated_function( __METHOD__, 'jetpack-9.3.0', 'Automattic\Jetpack\Assets::staticize_subdomain' );
5312 - return Assets::staticize_subdomain( $url );
5313 - }
5314 -
5315 5339 /* JSON API Authorization */
5316 5340
5317 5341 /**
5318 5342 * Handles the login action for Authorizing the JSON API
@@ -5317,13 +5341,14 @@
5317 5341 /**
5318 5342 * Handles the login action for Authorizing the JSON API
5319 5343 */
5320 5344 public function login_form_json_api_authorization() {
5321 - $this->verify_json_api_authorization_request();
5345 + $authorize_json_api = new Authorize_Json_Api();
5346 + $authorize_json_api->verify_json_api_authorization_request();
5322 5347
5323 - add_action( 'wp_login', array( $this, 'store_json_api_authorization_token' ), 10, 2 );
5348 + add_action( 'wp_login', array( $authorize_json_api, 'store_json_api_authorization_token' ), 10, 2 );
5324 5349
5325 - add_action( 'login_message', array( $this, 'login_message_json_api_authorization' ) );
5350 + add_action( 'login_message', array( $authorize_json_api, 'login_message_json_api_authorization' ) );
5326 5351 add_action( 'login_form', array( $this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5327 5352 add_filter( 'site_url', array( $this, 'post_login_form_to_signed_url' ), 10, 3 );
5328 5353 }
5329 5354
@@ -5361,16 +5386,17 @@
5361 5386
5362 5387 /**
5363 5388 * If someone logs in to approve API access, store the Access Code in usermeta.
5364 5389 *
5390 + * @deprecated 13.4
5391 + *
5365 5392 * @param string $user_login Unused.
5366 5393 * @param WP_User $user User logged in.
5367 5394 */
5368 5395 public function store_json_api_authorization_token( $user_login, $user ) {
5369 - add_filter( 'login_redirect', array( $this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5370 - add_filter( 'allowed_redirect_hosts', array( $this, 'allow_wpcom_public_api_domain' ) );
5371 - $token = wp_generate_password( 32, false );
5372 - update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5396 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::store_json_api_authorization_token' );
5397 +
5398 + return ( new Authorize_Json_Api() )->store_json_api_authorization_token( $user_login, $user );
5373 5399 }
5374 5400
5375 5401 /**
5376 5402 * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
@@ -5376,23 +5402,29 @@
5376 5402 * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
5377 5403 *
5378 5404 * To be used with a filter of allowed domains for a redirect.
5379 5405 *
5406 + * @deprecated 13.4
5407 + *
5380 5408 * @param array $domains Allowed WP.com Environments.
5381 5409 */
5382 5410 public function allow_wpcom_public_api_domain( $domains ) {
5383 - $domains[] = 'public-api.wordpress.com';
5384 - return $domains;
5411 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Status\\Host::allow_wpcom_public_api_domain' );
5412 +
5413 + return Host::allow_wpcom_public_api_domain( $domains );
5385 5414 }
5386 5415
5387 5416 /**
5388 5417 * Check if the redirect is encoded.
5389 5418 *
5419 + * @deprecated 13.4
5420 + *
5390 5421 * @param string $redirect_url Redirect URL.
5391 5422 *
5392 5423 * @return bool If redirect has been encoded.
5393 5424 */
5394 5425 public static function is_redirect_encoded( $redirect_url ) {
5426 + _deprecated_function( __METHOD__, 'jetpack-13.4' );
5395 5427 return preg_match( '/https?%3A%2F%2F/i', $redirect_url ) > 0;
5396 5428 }
5397 5429
5398 5430 /**
@@ -5410,8 +5442,10 @@
5410 5442
5411 5443 /**
5412 5444 * Add the Access Code details to the public-api.wordpress.com redirect.
5413 5445 *
5446 + * @deprecated 13.4
5447 + *
5414 5448 * @param string $redirect_to URL.
5415 5449 * @param string $original_redirect_to URL.
5416 5450 * @param WP_User $user WP_User for the redirect.
5417 5451 *
@@ -5417,23 +5451,18 @@
5417 5451 *
5418 5452 * @return string
5419 5453 */
5420 5454 public function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5421 - return add_query_arg(
5422 - urlencode_deep(
5423 - array(
5424 - 'jetpack-code' => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5425 - 'jetpack-user-id' => (int) $user->ID,
5426 - 'jetpack-state' => $this->json_api_authorization_request['state'],
5427 - )
5428 - ),
5429 - $redirect_to
5430 - );
5455 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::add_token_to_login_redirect_json_api_authorization' );
5456 +
5457 + return ( new Authorize_Json_Api() )->add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user );
5431 5458 }
5432 5459
5433 5460 /**
5434 5461 * Verifies the request by checking the signature
5435 5462 *
5463 + * @deprecated 13.4
5464 + *
5436 5465 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5437 5466 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5438 5467 *
5439 5468 * @param null|array $environment Value to override $_REQUEST.
@@ -5438,194 +5467,24 @@
5438 5467 *
5439 5468 * @param null|array $environment Value to override $_REQUEST.
5440 5469 */
5441 5470 public function verify_json_api_authorization_request( $environment = null ) {
5442 - $environment = $environment === null
5443 - ? $_REQUEST // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verification handled later in function.
5444 - : $environment;
5471 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::verify_json_api_authorization_request' );
5445 5472
5446 - list( $env_token,, $env_user_id ) = explode( ':', $environment['token'] );
5447 - $token = ( new Tokens() )->get_access_token( $env_user_id, $env_token );
5448 - if ( ! $token || empty( $token->secret ) ) {
5449 - wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack' ) );
5450 - }
5451 -
5452 - $die_error = __( 'Someone may be trying to trick you into giving them access to your site. Or it could be you just encountered a bug :). Either way, please close this window.', 'jetpack' );
5453 -
5454 - // Host has encoded the request URL, probably as a result of a bad http => https redirect.
5455 - if ( self::is_redirect_encoded( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- no site changes, we're erroring out.
5456 - /**
5457 - * Jetpack authorisation request Error.
5458 - *
5459 - * @since 7.5.0
5460 - */
5461 - do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
5462 - $die_error = sprintf(
5463 - /* translators: %s is a URL */
5464 - __( 'Your site is incorrectly double-encoding redirects from http to https. This is preventing Jetpack from authenticating your connection. Please visit our <a href="%s">support page</a> for details about how to resolve this.', 'jetpack' ),
5465 - esc_url( Redirect::get_url( 'jetpack-support-double-encoding' ) )
5466 - );
5467 - }
5468 -
5469 - $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5470 -
5471 - if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5472 - $signature = $jetpack_signature->sign_request(
5473 - $environment['token'],
5474 - $environment['timestamp'],
5475 - $environment['nonce'],
5476 - '',
5477 - 'GET',
5478 - $environment['jetpack_json_api_original_query'],
5479 - null,
5480 - true
5481 - );
5482 - } else {
5483 - $signature = $jetpack_signature->sign_current_request(
5484 - array(
5485 - 'body' => null,
5486 - 'method' => 'GET',
5487 - )
5488 - );
5489 - }
5490 -
5491 - if ( ! $signature ) {
5492 - wp_die(
5493 - wp_kses(
5494 - $die_error,
5495 - array(
5496 - 'a' => array(
5497 - 'href' => array(),
5498 - ),
5499 - )
5500 - )
5501 - );
5502 - } elseif ( is_wp_error( $signature ) ) {
5503 - wp_die(
5504 - wp_kses(
5505 - $die_error,
5506 - array(
5507 - 'a' => array(
5508 - 'href' => array(),
5509 - ),
5510 - )
5511 - )
5512 - );
5513 - } elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
5514 - if ( is_ssl() ) {
5515 - // If we signed an HTTP request on the Jetpack Servers, but got redirected to HTTPS by the local blog, check the HTTP signature as well.
5516 - $signature = $jetpack_signature->sign_current_request(
5517 - array(
5518 - 'scheme' => 'http',
5519 - 'body' => null,
5520 - 'method' => 'GET',
5521 - )
5522 - );
5523 - if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5524 - wp_die(
5525 - wp_kses(
5526 - $die_error,
5527 - array(
5528 - 'a' => array(
5529 - 'href' => array(),
5530 - ),
5531 - )
5532 - )
5533 - );
5534 - }
5535 - } else {
5536 - wp_die(
5537 - wp_kses(
5538 - $die_error,
5539 - array(
5540 - 'a' => array(
5541 - 'href' => array(),
5542 - ),
5543 - )
5544 - )
5545 - );
5546 - }
5547 - }
5548 -
5549 - $timestamp = (int) $environment['timestamp'];
5550 - $nonce = stripslashes( (string) $environment['nonce'] );
5551 -
5552 - if ( ! $this->connection_manager ) {
5553 - $this->connection_manager = new Connection_Manager();
5554 - }
5555 -
5556 - if ( ! ( new Nonce_Handler() )->add( $timestamp, $nonce ) ) {
5557 - // De-nonce the nonce, at least for 5 minutes.
5558 - // We have to reuse this nonce at least once (used the first time when the initial request is made, used a second time when the login form is POSTed).
5559 - $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5560 - if ( $old_nonce_time < time() - 300 ) {
5561 - wp_die( esc_html__( 'The authorization process expired. Please go back and try again.', 'jetpack' ) );
5562 - }
5563 - }
5564 -
5565 - $data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
5566 - $data_filters = array(
5567 - 'state' => 'opaque',
5568 - 'client_id' => 'int',
5569 - 'client_title' => 'string',
5570 - 'client_image' => 'url',
5571 - );
5572 -
5573 - foreach ( $data_filters as $key => $sanitation ) {
5574 - if ( ! isset( $data->$key ) ) {
5575 - wp_die(
5576 - wp_kses(
5577 - $die_error,
5578 - array(
5579 - 'a' => array(
5580 - 'href' => array(),
5581 - ),
5582 - )
5583 - )
5584 - );
5585 - }
5586 -
5587 - switch ( $sanitation ) {
5588 - case 'int':
5589 - $this->json_api_authorization_request[ $key ] = (int) $data->$key;
5590 - break;
5591 - case 'opaque':
5592 - $this->json_api_authorization_request[ $key ] = (string) $data->$key;
5593 - break;
5594 - case 'string':
5595 - $this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
5596 - break;
5597 - case 'url':
5598 - $this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
5599 - break;
5600 - }
5601 - }
5602 -
5603 - if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5604 - wp_die(
5605 - wp_kses(
5606 - $die_error,
5607 - array(
5608 - 'a' => array(
5609 - 'href' => array(),
5610 - ),
5611 - )
5612 - )
5613 - );
5614 - }
5473 + return ( new Authorize_Json_Api() )->verify_json_api_authorization_request( $environment );
5615 5474 }
5616 5475
5617 5476 /**
5618 5477 * HTML for the JSON API authorization notice.
5619 5478 *
5479 + * @deprecated 13.4
5480 + *
5620 5481 * @return string
5621 5482 */
5622 5483 public function login_message_json_api_authorization() {
5623 - return '<p class="message">' . sprintf(
5624 - /* translators: Name/image of the client requesting authorization */
5625 - esc_html__( '%s wants to access your site’s data. Log in to authorize that access.', 'jetpack' ),
5626 - '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5627 - ) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5484 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::login_message_json_api_authorization' );
5485 +
5486 + return ( new Authorize_Json_Api() )->login_message_json_api_authorization();
5628 5487 }
5629 5488
5630 5489 /**
5631 5490 * Get $content_width, but with a <s>twist</s> filter.
@@ -5669,28 +5528,20 @@
5669 5528
5670 5529 /**
5671 5530 * Checks if the site is currently in an identity crisis.
5672 5531 *
5532 + * Now delegates to the Connection package so this matches what the heartbeat itself reports.
5533 + * Note the package guards on `Connection\Manager::is_connected()` where this used to guard on
5534 + * `Jetpack::is_connection_ready()`, so the `jetpack_is_connection_ready` filter no longer applies.
5535 + *
5536 + * @deprecated 16.2
5537 + *
5673 5538 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5674 5539 */
5675 5540 public static function check_identity_crisis() {
5676 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5677 - return false;
5678 - }
5679 - return Jetpack_Options::get_option( 'sync_error_idc' );
5680 - }
5541 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5681 5542
5682 - /**
5683 - * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
5684 - *
5685 - * @since 4.4.0
5686 - * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
5687 - *
5688 - * @return bool
5689 - */
5690 - public static function validate_sync_error_idc_option() {
5691 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::validate_sync_error_idc_option' );
5692 - return Identity_Crisis::validate_sync_error_idc_option();
5543 + return Identity_Crisis::check_identity_crisis();
5693 5544 }
5694 5545
5695 5546 /**
5696 5547 * Normalizes a url by doing three things:
@@ -5714,35 +5565,8 @@
5714 5565 return $url;
5715 5566 }
5716 5567
5717 5568 /**
5718 - * Gets the value that is to be saved in the jetpack_sync_error_idc option.
5719 - *
5720 - * @since 4.4.0
5721 - * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB
5722 - * @deprecated 9.8.0 Use \\Automattic\\Jetpack\\Identity_Crisis::get_sync_error_idc_option
5723 - *
5724 - * @param array $response HTTP response.
5725 - * @return array Array of the local urls, wpcom urls, and error code
5726 - */
5727 - public static function get_sync_error_idc_option( $response = array() ) {
5728 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::get_sync_error_idc_option' );
5729 - return Identity_Crisis::get_sync_error_idc_option( $response );
5730 - }
5731 -
5732 - /**
5733 - * Returns the value of the jetpack_sync_idc_optin filter, or constant.
5734 - * If set to true, the site will be put into staging mode.
5735 - *
5736 - * @since 4.3.2
5737 - * @return bool
5738 - */
5739 - public static function sync_idc_optin() {
5740 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::sync_idc_optin' );
5741 - return Identity_Crisis::sync_idc_optin();
5742 - }
5743 -
5744 - /**
5745 5569 * Maybe Use a .min.css stylesheet, maybe not.
5746 5570 *
5747 5571 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5748 5572 *
@@ -5805,9 +5629,9 @@
5805 5629 *
5806 5630 * @return mixed
5807 5631 */
5808 5632 public static function set_suffix_on_min( $src, $handle ) {
5809 - if ( ! str_contains( $src, '.min.css' ) ) {
5633 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5810 5634 return $src;
5811 5635 }
5812 5636
5813 5637 if ( ! empty( self::$min_assets ) ) {
@@ -5843,8 +5667,10 @@
5843 5667 *
5844 5668 * Data passed in with the $data parameter will be available in the
5845 5669 * template file as $data['value']
5846 5670 *
5671 + * @html-template-var array $data
5672 + *
5847 5673 * @param string $template - Template file to load.
5848 5674 * @param array $data - Any data to pass along to the template.
5849 5675 * @return boolean - If template file was found.
5850 5676 **/
@@ -5862,8 +5688,19 @@
5862 5688 return false;
5863 5689 }
5864 5690
5865 5691 /**
5692 + * Register Jetpack-specific tests on the connection package's health test suite.
5693 + *
5694 + * @param \Automattic\Jetpack\Connection\Connection_Health_Tests $connection_tests The test suite instance.
5695 + */
5696 + public function register_jetpack_connection_tests( $connection_tests ) {
5697 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger/class-jetpack-cxn-tests.php';
5698 + $jetpack_tests = new Jetpack_Cxn_Tests();
5699 + $jetpack_tests->register_tests_on( $connection_tests );
5700 + }
5701 +
5702 + /**
5866 5703 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5867 5704 */
5868 5705 public function deprecated_hooks() {
5869 5706 $filter_deprecated_list = array(
@@ -6079,8 +5916,18 @@
6079 5916 'jetpack_subscriptions_modal_enabled' => array(
6080 5917 'replacement' => null,
6081 5918 'version' => 'jetpack-12.7.0',
6082 5919 ),
5920 + 'jetpack_pre_connection_prompt_helpers' => array(
5921 + 'replacement' => null,
5922 + 'version' => 'jetpack-13.2.0',
5923 + ),
5924 + 'jetpack_contact_form_use_package' => array(
5925 + 'replacement' => null,
5926 + 'version' => 'jetpack-13.4.0',
5927 + ),
5928 + // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
5929 + // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
6083 5930 );
6084 5931
6085 5932 foreach ( $filter_deprecated_list as $tag => $args ) {
6086 5933 if ( has_filter( $tag ) ) {
@@ -6211,125 +6058,8 @@
6211 6058 return $css;
6212 6059 }
6213 6060
6214 6061 /**
6215 - * This methods removes all of the registered css files on the front end
6216 - * from Jetpack in favor of using a single file. In effect "imploding"
6217 - * all the files into one file.
6218 - *
6219 - * Pros:
6220 - * - Uses only ONE css asset connection instead of 15
6221 - * - Saves a minimum of 56k
6222 - * - Reduces server load
6223 - * - Reduces time to first painted byte
6224 - *
6225 - * Cons:
6226 - * - Loads css for ALL modules. However all selectors are prefixed so it
6227 - * should not cause any issues with themes.
6228 - * - Plugins/themes dequeuing styles no longer do anything. See
6229 - * jetpack_implode_frontend_css filter for a workaround
6230 - *
6231 - * For some situations developers may wish to disable css imploding and
6232 - * instead operate in legacy mode where each file loads seperately and
6233 - * can be edited individually or dequeued. This can be accomplished with
6234 - * the following line:
6235 - *
6236 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6237 - *
6238 - * @param bool $travis_test Is this a test run.
6239 - *
6240 - * @since 3.2
6241 - */
6242 - public function implode_frontend_css( $travis_test = false ) {
6243 - $do_implode = true;
6244 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6245 - $do_implode = false;
6246 - }
6247 -
6248 - // Do not implode CSS when the page loads via the AMP plugin.
6249 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
6250 - $do_implode = false;
6251 - }
6252 -
6253 - /*
6254 - * Only proceed if at least 2 modules with concatenated CSS are active.
6255 - * There is no point in serving a big concatenated CSS file
6256 - * if there are no features (or only one) that actually need some CSS loaded.
6257 - */
6258 - $active_modules = self::get_active_modules();
6259 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
6260 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
6261 - if ( $active_module_with_css_count < 2 ) {
6262 - $do_implode = false;
6263 - }
6264 -
6265 - /**
6266 - * Allow CSS to be concatenated into a single jetpack.css file.
6267 - *
6268 - * @since 3.2.0
6269 - *
6270 - * @param bool $do_implode Should CSS be concatenated? Default to true.
6271 - */
6272 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6273 -
6274 - // Do not use the imploded file when default behavior was altered through the filter.
6275 - if ( ! $do_implode ) {
6276 - return;
6277 - }
6278 -
6279 - // We do not want to use the imploded file in dev mode, or if not connected.
6280 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
6281 - if ( ! $travis_test ) {
6282 - return;
6283 - }
6284 - }
6285 -
6286 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
6287 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
6288 - return;
6289 - }
6290 -
6291 - /*
6292 - * Now we assume Jetpack is connected and able to serve the single
6293 - * file.
6294 - *
6295 - * In the future there will be a check here to serve the file locally
6296 - * or potentially from the Jetpack CDN
6297 - *
6298 - * For now:
6299 - * - Enqueue a single imploded css file
6300 - * - Zero out the style_loader_tag for the bundled ones
6301 - * - Be happy, drink scotch
6302 - */
6303 -
6304 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6305 -
6306 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6307 -
6308 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6309 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6310 - }
6311 -
6312 - /**
6313 - * Removes styles that are part of concatenated group.
6314 - *
6315 - * @param string $tag Style tag.
6316 - * @param string $handle Style handle.
6317 - *
6318 - * @return string
6319 - */
6320 - public function concat_remove_style_loader_tag( $tag, $handle ) {
6321 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
6322 - $tag = '';
6323 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6324 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6325 - }
6326 - }
6327 -
6328 - return $tag;
6329 - }
6330 -
6331 - /**
6332 6062 * Check the heartbeat data
6333 6063 *
6334 6064 * Organizes the heartbeat data by severity. For example, if the site
6335 6065 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -6341,9 +6071,23 @@
6341 6071 *
6342 6072 * $return array $filtered_data
6343 6073 */
6344 6074 public static function jetpack_check_heartbeat_data() {
6345 - $raw_data = Jetpack_Heartbeat::generate_stats_array();
6075 + /*
6076 + * Site environment stats (incl. wp-version/php-version checked below) now live in the Connection package,
6077 + * and the IDC stat is contributed by the Connection package's `jetpack_heartbeat_stats_array` filter
6078 + * callback. We rebuild the stat here rather than running that filter: the filter's callbacks have side
6079 + * effects (Connection\Manager::add_stats_to_heartbeat() consumes and deletes the `xmlrpc_errors` option)
6080 + * and return non-scalar values, neither of which is appropriate for this read-only diagnostic.
6081 + */
6082 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
6083 + ? Heartbeat::get_environment_stats()
6084 + : array();
6085 + $raw_data = array_merge(
6086 + Jetpack_Heartbeat::generate_stats_array(),
6087 + $env_stats,
6088 + array( 'identitycrisis' => Identity_Crisis::check_identity_crisis() ? 'yes' : 'no' )
6089 + );
6346 6090
6347 6091 $good = array();
6348 6092 $caution = array();
6349 6093 $bad = array();
@@ -6409,23 +6153,8 @@
6409 6153 return Jetpack_Options::get_options_for_reset();
6410 6154 }
6411 6155
6412 6156 /**
6413 - * Strip http:// or https:// from a url, replaces forward slash with ::,
6414 - * so we can bring them directly to their site in calypso.
6415 - *
6416 - * @deprecated 9.2.0 Use Automattic\Jetpack\Status::get_site_suffix
6417 - *
6418 - * @param string $url URL.
6419 - * @return string url without the guff.
6420 - */
6421 - public static function build_raw_urls( $url ) {
6422 - _deprecated_function( __METHOD__, 'jetpack-9.2.0', 'Automattic\Jetpack\Status::get_site_suffix' );
6423 -
6424 - return ( new Status() )->get_site_suffix( $url );
6425 - }
6426 -
6427 - /**
6428 6157 * Get the user's meta box order.
6429 6158 *
6430 6159 * @param array $sorted Value for the user's option.
6431 6160 * @return mixed
@@ -6454,68 +6183,9 @@
6454 6183 }
6455 6184
6456 6185 return $sorted;
6457 6186 }
6458 -
6459 6187 /**
6460 - * Adds a "blank" column in the user admin table to display indication of user connection.
6461 - *
6462 - * @param array $columns User list table columns.
6463 - *
6464 - * @return array
6465 - */
6466 - public function jetpack_icon_user_connected( $columns ) {
6467 - $columns['user_jetpack'] = '';
6468 - return $columns;
6469 - }
6470 -
6471 - /**
6472 - * Show Jetpack icon if the user is linked.
6473 - *
6474 - * @param string $val HTML for the icon.
6475 - * @param string $col User list table column.
6476 - * @param int $user_id User ID.
6477 - *
6478 - * @return string
6479 - */
6480 - public function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6481 - if ( 'user_jetpack' === $col && self::connection()->is_user_connected( $user_id ) ) {
6482 - $jetpack_logo = new Jetpack_Logo();
6483 - $emblem_html = sprintf(
6484 - '<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6485 - esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6486 - $jetpack_logo->get_jp_emblem()
6487 - );
6488 - return $emblem_html;
6489 - }
6490 -
6491 - return $val;
6492 - }
6493 -
6494 - /**
6495 - * Style the Jetpack user column
6496 - */
6497 - public function jetpack_user_col_style() {
6498 - global $current_screen;
6499 - if ( ! empty( $current_screen->base ) && 'users' === $current_screen->base ) {
6500 - ?>
6501 - <style>
6502 - .fixed .column-user_jetpack {
6503 - width: 21px;
6504 - }
6505 - .jp-emblem-user-admin svg {
6506 - width: 20px;
6507 - height: 20px;
6508 - }
6509 - .jp-emblem-user-admin path {
6510 - fill: #069e08;
6511 - }
6512 - </style>
6513 - <?php
6514 - }
6515 - }
6516 -
6517 - /**
6518 6188 * Checks if Akismet is active and working.
6519 6189 *
6520 6190 * We dropped support for Akismet 3.0 with Jetpack 6.1.1 while introducing a check for an Akismet valid key
6521 6191 * that implied usage of methods present since more recent version.
@@ -6676,13 +6346,20 @@
6676 6346 }
6677 6347 }
6678 6348
6679 6349 /**
6680 - * Returns a boolean for whether backups UI should be displayed or not.
6350 + * Whether UI for backups should be displayed.
6681 6351 *
6352 + * On WPCom platforms this is gated on the backups-self-serve site feature.
6353 + * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
6354 + *
6682 6355 * @return bool Should backups UI be displayed?
6683 6356 */
6684 6357 public static function show_backups_ui() {
6358 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6359 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
6360 + }
6361 +
6685 6362 /**
6686 6363 * Whether UI for backups should be displayed.
6687 6364 *
6688 6365 * @since 6.5.0
@@ -6692,8 +6369,24 @@
6692 6369 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6693 6370 }
6694 6371
6695 6372 /**
6373 + * Whether UI for security scanning should be displayed.
6374 + *
6375 + * On WPCom platforms this is gated on the scan-self-serve site feature.
6376 + * On self-hosted Jetpack sites it always returns true.
6377 + *
6378 + * @return bool Should scan UI be displayed?
6379 + */
6380 + public static function show_scan_ui() {
6381 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6382 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
6383 + }
6384 +
6385 + return true;
6386 + }
6387 +
6388 + /**
6696 6389 * Clean leftoveruser meta.
6697 6390 *
6698 6391 * Delete Jetpack-related user meta when it is no longer needed.
6699 6392 *
@@ -6780,8 +6473,25 @@
6780 6473 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6781 6474 ),
6782 6475 );
6783 6476
6477 + $products['growth'] = array(
6478 + 'title' => __( 'Jetpack Growth', 'jetpack' ),
6479 + 'slug' => 'jetpack_growth_yearly',
6480 + 'description' => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
6481 + 'show_promotion' => true,
6482 + 'discount_percent' => 50,
6483 + 'included_in_plans' => array( 'complete' ),
6484 + 'features' => array(
6485 + _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
6486 + _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
6487 + _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
6488 + _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
6489 + _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
6490 + _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
6491 + ),
6492 + );
6493 +
6784 6494 $products['scan'] = array(
6785 6495 'title' => __( 'Jetpack Scan', 'jetpack' ),
6786 6496 'slug' => 'jetpack_scan',
6787 6497 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6905,6 +6615,108 @@
6905 6615 return false;
6906 6616 }
6907 6617
6908 6618 return true;
6619 + }
6620 +
6621 + /**
6622 + * Add 5-star review link on the Jetpack Plugin meta, in the plugins list table (on the plugins page).
6623 + *
6624 + * @param array $plugin_meta An array of the plugin's metadata.
6625 + * @param string $plugin_file Path to the plugin file.
6626 + *
6627 + * @return array $plugin_meta An array of the plugin's metadata.
6628 + */
6629 + public function add_5_star_review_link( $plugin_meta, $plugin_file ) {
6630 + if ( $plugin_file !== 'jetpack/jetpack.php' ) {
6631 + return $plugin_meta;
6632 + }
6633 +
6634 + $u = get_current_user_id();
6635 + $site = get_site_url();
6636 +
6637 + $plugin_meta[] = '<a href="https://jetpack.com/redirect?source=jetpack-plugin-review&site=' . esc_attr( $site ) . '&u=' . esc_attr( $u ) . '" target="_blank" rel="noopener noreferrer" title="' . esc_attr__( 'Rate Jetpack on WordPress.org', 'jetpack' ) . '" style="color: #ffb900">'
6638 + . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6639 + . '</a>';
6640 +
6641 + return $plugin_meta;
6642 + }
6643 +
6644 + /**
6645 + * Lazy instantiation of the Plugin_Tracking object.
6646 + *
6647 + * @since 13.9
6648 + *
6649 + * @return void
6650 + */
6651 + public function initialize_tracking() {
6652 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6653 + // Only need to run once.
6654 + return;
6655 + }
6656 +
6657 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6658 + ( new Plugin_Tracking() )->init();
6659 + }
6660 + }
6661 +
6662 + /**
6663 + * Run the "initialize tracking" hook.
6664 + *
6665 + * @since 13.9
6666 + */
6667 + public function run_initialize_tracking_action() {
6668 + /**
6669 + * Fires when the tracking needs to be initialized.
6670 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6671 + *
6672 + * @since 13.9
6673 + */
6674 + do_action( 'jetpack_initialize_tracking' );
6675 + }
6676 +
6677 + /**
6678 + * Initialize REST jsonAPI if needed.
6679 + *
6680 + * @return void
6681 + */
6682 + public function maybe_initialize_rest_jsonapi() {
6683 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6684 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6685 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6686 +
6687 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6688 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6689 + }
6690 + }
6691 +
6692 + /**
6693 + * Run plugin post-activation actions if we need to.
6694 + *
6695 + * @return void
6696 + */
6697 + private function plugin_post_activation() {
6698 + if ( ( new Status() )->is_offline_mode() ) {
6699 + return;
6700 + }
6701 +
6702 + if ( get_transient( 'activated_jetpack' ) ) {
6703 + delete_transient( 'activated_jetpack' );
6704 +
6705 + if ( ( new Host() )->is_woa_site() ) {
6706 + $redirect_url = static::admin_url( 'page=jetpack' );
6707 + } elseif ( is_network_admin() ) {
6708 + $redirect_url = admin_url( 'network/admin.php?page=jetpack' );
6709 + } elseif ( get_transient( 'my_jetpack_product_activated' ) ) {
6710 + // don't redirect if this is an activation that just came from My Jetpack
6711 + // My Jetpack has its own set of post-activation redirects
6712 + return;
6713 + } elseif ( My_Jetpack_Initializer::should_initialize() ) {
6714 + $redirect_url = static::admin_url( 'page=my-jetpack' );
6715 + } else {
6716 + $redirect_url = static::admin_url( 'page=jetpack' );
6717 + }
6718 +
6719 + wp_safe_redirect( $redirect_url );
6720 + }
6909 6721 }
6910 6722 }