PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
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 13.8.3 13.9.2 14.0.1 14.1.1 All 503 releases
← All changes | class.jetpack.php +962 -539 13.4.516.2 View file →
@@ -6,8 +6,9 @@
6 6 *
7 7 * @package automattic/jetpack
8 8 */
9 9
10 +use Automattic\Jetpack\Activity_Log\Jetpack_Activity_Log as Activity_Log_Init;
10 11 use Automattic\Jetpack\Assets;
11 12 use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
12 13 use Automattic\Jetpack\Config;
13 14 use Automattic\Jetpack\Connection\Authorize_Json_Api;
@@ -22,15 +23,22 @@
22 23 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
23 24 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
24 25 use Automattic\Jetpack\Errors;
25 26 use Automattic\Jetpack\Files;
27 +use Automattic\Jetpack\Heartbeat;
26 28 use Automattic\Jetpack\Identity_Crisis;
29 +use Automattic\Jetpack\Import\Main as Import_Main;
27 30 use Automattic\Jetpack\Licensing;
28 31 use Automattic\Jetpack\Modules;
29 32 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
33 +use Automattic\Jetpack\Newsletter\Reader_Link;
30 34 use Automattic\Jetpack\Paths;
35 +use Automattic\Jetpack\Plugin\Deprecate;
31 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
32 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;
33 41 use Automattic\Jetpack\Status;
34 42 use Automattic\Jetpack\Status\Host;
35 43 use Automattic\Jetpack\Status\Visitor;
36 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -37,9 +45,14 @@
37 45 use Automattic\Jetpack\Sync\Health;
38 46 use Automattic\Jetpack\Sync\Sender;
39 47 use Automattic\Jetpack\Terms_Of_Service;
40 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
41 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
42 55 /*
43 56 Options:
44 57 jetpack_options (array)
45 58 An array of options.
@@ -67,79 +80,25 @@
67 80 * The Jetpack class.
68 81 */
69 82 class Jetpack {
70 83 /**
71 - * XMLRPC server instance.
84 + * Marks that the durable Jetpack SEO module-state options have been reconciled against
85 + * the site's module configuration, so the repair runs at most once.
72 86 *
73 - * @var null|Jetpack_XMLRPC_Server XMLRPC server used by Jetpack.
74 - */
75 - public $xmlrpc_server = null;
76 -
77 - /**
78 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
87 + * {@see self::reconcile_seo_module_state_options()}
79 88 *
80 - * See $concatenated_style_handles for the list of handles,
81 - * and the implode_frontend_css method for more details.
89 + * @since 16.1
82 90 *
83 - * When updating this list, make sure to update $concatenated_style_handles as well.
84 - *
85 - * @var array List of Jetpack modules.
91 + * @var string
86 92 */
87 - public $modules_with_concatenated_css = array(
88 - 'carousel',
89 - 'contact-form',
90 - 'infinite-scroll',
91 - 'likes',
92 - 'related-posts',
93 - 'sharedaddy',
94 - 'shortcodes',
95 - 'subscriptions',
96 - 'tiled-gallery',
97 - 'widgets',
98 - );
93 + const SEO_MODULE_STATE_RECONCILED_OPTION = 'jetpack_seo_module_state_reconciled';
99 94
100 95 /**
101 - * The handles of styles that are concatenated into jetpack.css.
96 + * XMLRPC server instance.
102 97 *
103 - * When making changes to that list,
104 - * you must also update concat_list in tools/webpack.config.css.js,
105 - * and to $modules_with_concatenated_css if necessary.
106 - *
107 - * @var array The handles of styles that are concatenated into jetpack.css.
98 + * @var null|Jetpack_XMLRPC_Server XMLRPC server used by Jetpack.
108 99 */
109 - public $concatenated_style_handles = array(
110 - 'jetpack-carousel-swiper-css',
111 - 'jetpack-carousel',
112 - 'grunion.css',
113 - 'the-neverending-homepage',
114 - 'jetpack_likes',
115 - 'jetpack_related-posts',
116 - 'sharedaddy',
117 - 'jetpack-slideshow',
118 - 'presentations',
119 - 'quiz',
120 - 'jetpack-subscriptions',
121 - 'jetpack-responsive-videos',
122 - 'jetpack-social-menu',
123 - 'tiled-gallery',
124 - 'jetpack_display_posts_widget',
125 - 'gravatar-profile-widget',
126 - 'goodreads-widget',
127 - 'jetpack_social_media_icons_widget',
128 - 'jetpack-top-posts-widget',
129 - 'jetpack_image_widget',
130 - 'jetpack-my-community-widget',
131 - 'jetpack-authors-widget',
132 - 'wordads',
133 - 'eu-cookie-law-style',
134 - 'flickr-widget-style',
135 - 'jetpack-search-widget',
136 - 'jetpack-simple-payments-widget-style',
137 - 'jetpack-widget-social-icons-styles',
138 - 'wpcom_instagram_widget',
139 - 'milestone-widget',
140 - 'subscribe-modal-css',
141 - );
100 + public $xmlrpc_server = null;
142 101
143 102 /**
144 103 * Contains all assets that have had their URL rewritten to minified versions.
145 104 *
@@ -156,11 +115,8 @@
156 115 'contact-form' => array(
157 116 array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
158 117 array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
159 118 ),
160 - 'custom-css' => array(
161 - array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
162 - ),
163 119 'gravatar-hovercards' => array(
164 120 array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
165 121 ),
166 122 'latex' => array(
@@ -331,9 +287,8 @@
331 287 * Graph tags via filter when their Social Meta modules are active:
332 288 *
333 289 * - All in One SEO Pack, All in one SEO Pack Pro
334 290 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
335 - * - SEOPress, SEOPress Pro
336 291 *
337 292 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
338 293 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
339 294 *
@@ -376,8 +331,10 @@
376 331 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
377 332 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
378 333 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
379 334 'wp-ogp/wp-ogp.php', // WP-OGP.
335 + 'wp-seopress/seopress.php', // SEOPress.
336 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
380 337 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
381 338 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
382 339 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
383 340 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -488,8 +445,16 @@
488 445 */
489 446 public static $instance = false;
490 447
491 448 /**
449 + * Resolved answer for `is_premium_analytics_enabled()`, or null before the first call.
450 + *
451 + * @since 16.1
452 + * @var bool|null
453 + */
454 + private static $premium_analytics_enabled = null;
455 +
456 + /**
492 457 * Singleton
493 458 *
494 459 * @static
495 460 */
@@ -529,9 +494,9 @@
529 494 if ( array_diff( $unfiltered_modules, $modules ) ) {
530 495 self::update_active_modules( $modules );
531 496 }
532 497
533 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
498 + self::register_upgrade_init_hooks();
534 499
535 500 // Upgrade to 4.3.0.
536 501 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
537 502 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -586,8 +551,13 @@
586 551 Jetpack_Options::delete_option( 'autoupdate_plugins' );
587 552 } // Should we have some type of fallback if something fails here?
588 553 }
589 554
555 + // Set the newsletter send default option for existing sites.
556 + if ( false === get_option( 'wpcom_newsletter_send_default' ) ) {
557 + add_option( 'wpcom_newsletter_send_default', 1 );
558 + }
559 +
590 560 if ( did_action( 'wp_loaded' ) ) {
591 561 self::upgrade_on_load();
592 562 } else {
593 563 add_action(
@@ -621,9 +591,8 @@
621 591 }
622 592
623 593 if (
624 594 class_exists( 'Jetpack_Sitemap_Manager' )
625 - && version_compare( JETPACK__VERSION, '5.3', '>=' )
626 595 ) {
627 596 do_action( 'jetpack_sitemaps_purge_data' );
628 597 }
629 598
@@ -694,35 +663,15 @@
694 663 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
695 664 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
696 665 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
697 666
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 667 /**
708 668 * Prepare Gutenberg Editor functionality
669 + *
670 + * The hooks previously here have been moved to modules/blocks.php but leaving this here pending
671 + * a longer investigation to see if code is expecting the Gutenberg class to always be available.
709 672 */
710 673 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 674 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
726 675
727 676 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
728 677
@@ -731,8 +680,13 @@
731 680
732 681 // Set up the REST authentication hooks.
733 682 Connection_Rest_Authentication::init();
734 683
684 + // Register Jetpack-specific connection tests (sync health, etc.) with the connection
685 + // package's health test suite. This runs on all requests (not just admin), because
686 + // the connection/test REST endpoint can be called outside admin context.
687 + add_action( 'jetpack_connection_tests_loaded', array( $this, 'register_jetpack_connection_tests' ) );
688 +
735 689 add_action( 'admin_init', array( $this, 'admin_init' ) );
736 690 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
737 691
738 692 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -765,25 +719,8 @@
765 719
766 720 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
767 721 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
768 722
769 - /*
770 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
771 - * We should make sure to only do this for front end links.
772 - */
773 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
774 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
775 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
776 -
777 - /*
778 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
779 - * so they point moderation links on emails to Calypso.
780 - */
781 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
782 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
783 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
784 - }
785 -
786 723 add_action(
787 724 'plugins_loaded',
788 725 function () {
789 726 if ( User_Agent_Info::is_mobile_app() ) {
@@ -794,18 +731,10 @@
794 731
795 732 // Update the site's Jetpack plan and products from API on heartbeats.
796 733 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
797 734
798 - /**
799 - * This is the hack to concatenate all css files into one.
800 - * For description and reasoning see the implode_frontend_css method.
801 - *
802 - * Super late priority so we catch all the registered styles.
803 - */
804 - if ( ! is_admin() ) {
805 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
806 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
807 - }
735 + // The Connection package fetches the site record for `jetpack/v4/site`; reuse it to refresh the plan.
736 + add_action( 'jetpack_site_data_fetched', array( Jetpack_Plan::class, 'update_from_site_record' ) );
808 737
809 738 // Actually push the stats on shutdown.
810 739 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
811 740 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -813,8 +742,10 @@
813 742
814 743 // After a successful connection.
815 744 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
816 745 add_action( 'jetpack_site_registered', array( $this, 'handle_unique_registrations_stats' ) );
746 + add_action( 'jetpack_site_registered', array( Reader_Link::class, 'activate_on_connection' ), 9 );
747 + add_action( 'jetpack_site_registered', array( \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::class, 'discard_credentials' ) );
817 748
818 749 // Actions for Manager::authorize().
819 750 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
820 751 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -859,11 +790,140 @@
859 790 add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
860 791
861 792 // Add 5-star
862 793 add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
794 + add_action( 'init', array( Deprecate::class, 'instance' ) );
795 +
796 + // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
797 + \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
798 +
799 + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the
800 + // Jetpack plugin for now: the Connection package no longer auto-wires these, so
801 + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet.
802 + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init();
863 803 }
864 804
865 805 /**
806 + * Whether the current request should eagerly initialize the admin/REST-only
807 + * packages (the Import package and My Jetpack) now, at `plugins_loaded` time.
808 + *
809 + * Returns true for admin, cron, POST, and WP-CLI requests — the contexts,
810 + * knowable this early, where those packages have work to do. Returns false
811 + * for a plain front-end GET *and* for a REST request: the two can't be told
812 + * apart yet (this runs before `rest_api_init`), so callers defer the REST
813 + * case by initializing the package on `rest_api_init` instead, while a plain
814 + * page view never fires that hook and so loads nothing. This keeps
815 + * admin/REST-only PHP out of opcache on the front-end GET hot path.
816 + *
817 + * No in-repo code depends on the deferral. The one externally observable
818 + * change is timing: the packages' documented init hooks
819 + * (`jetpack_import_initialized`, `jetpack_feature_import_enabled`, and
820 + * `my_jetpack_init`) no longer fire on a plain front-end GET — they fire on
821 + * the admin, cron, POST, WP-CLI, and REST requests where the packages load.
822 + *
823 + * @return bool
824 + */
825 + private static function should_eager_load_packages() {
826 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) );
827 + $is_wp_cli = Constants::is_true( 'WP_CLI' );
828 +
829 + return is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli;
830 + }
831 +
832 + /**
833 + * Configure the Import package from a deferred hook.
834 + *
835 + * The eager path uses Config::ensure( 'import' ), but the deferred REST path
836 + * runs after Config::on_plugins_loaded() has already processed its feature
837 + * flags, so it needs a hookable bootstrap callback. Preserve Config's
838 + * feature-enabled action for hook consumers.
839 + *
840 + * @since 16.0
841 + *
842 + * @return void
843 + */
844 + public static function configure_import_package() {
845 + if ( class_exists( Import_Main::class ) ) {
846 + Import_Main::configure();
847 +
848 + if ( ! did_action( 'jetpack_feature_import_enabled' ) ) {
849 + do_action( 'jetpack_feature_import_enabled' );
850 + }
851 + }
852 + }
853 +
854 + /**
855 + * Whether the bundled Stats v2 dashboard is enabled.
856 + *
857 + * Stats v2 (formerly "Premium Analytics") ships with the plugin behind this
858 + * flag while it rolls out (WOOA7S-1595). When enabled it adds its own admin
859 + * menu alongside the existing Stats UI; it never replaces or hides the
860 + * legacy Stats menu, admin-bar entries, post-list column, or WP dashboard
861 + * widget. The Stats module's tracking is unaffected either way, and Stats v2
862 + * reads what that module collects, so while the module is off the plugin
863 + * answers false here before even reading the flag.
864 + *
865 + * The package has to be loadable for this to be true, so a site with the
866 + * flag on but a missing package answers false here and never adds the
867 + * Stats v2 menu (a warning is logged instead).
868 + *
869 + * @since 16.1
870 + *
871 + * @return bool
872 + */
873 + public static function is_premium_analytics_enabled() {
874 + if ( null !== self::$premium_analytics_enabled ) {
875 + return self::$premium_analytics_enabled;
876 + }
877 +
878 + if ( ! self::is_module_active( 'stats' ) ) {
879 + self::$premium_analytics_enabled = false;
880 + return false;
881 + }
882 +
883 + /**
884 + * Filters whether the bundled Premium Analytics dashboard is enabled.
885 + *
886 + * Resolved once, from `Jetpack::configure()` on `plugins_loaded`, and only
887 + * while the Stats module is active. Register this from a mu-plugin or a
888 + * plugin's main file — a callback added on `plugins_loaded` or later runs
889 + * too late to be seen.
890 + *
891 + * @since 16.1
892 + *
893 + * @param bool $enabled Defaults to the `jetpack_premium_analytics_enabled` option (false).
894 + */
895 + $flag = (bool) apply_filters( 'jetpack_premium_analytics_enabled', (bool) get_option( 'jetpack_premium_analytics_enabled' ) );
896 +
897 + self::$premium_analytics_enabled = $flag && class_exists( 'Automattic\Jetpack\PremiumAnalytics\Analytics' );
898 +
899 + if ( $flag && ! self::$premium_analytics_enabled ) {
900 + wp_trigger_error(
901 + __METHOD__,
902 + 'The jetpack_premium_analytics_enabled flag is on but the Premium Analytics package is not loadable; keeping the Stats UI in place.'
903 + );
904 + }
905 +
906 + return self::$premium_analytics_enabled;
907 + }
908 +
909 + /**
910 + * Expose the setting that turns the Premium Analytics dashboard on and off.
911 + *
912 + * Deliberately not behind is_premium_analytics_enabled(): this is the setting that flips that
913 + * check, so it has to answer while the dashboard is still off.
914 + *
915 + * @since 16.2
916 + *
917 + * @return void
918 + */
919 + public static function register_premium_analytics_enablement_setting() {
920 + if ( class_exists( 'Automattic\Jetpack\PremiumAnalytics\Enablement_Setting' ) ) {
921 + \Automattic\Jetpack\PremiumAnalytics\Enablement_Setting::register();
922 + }
923 + }
924 +
925 + /**
866 926 * Before everything else starts getting initalized, we need to initialize Jetpack using the
867 927 * Config object.
868 928 */
869 929 public function configure() {
@@ -872,13 +932,10 @@
872 932 foreach (
873 933 array(
874 934 'jitm',
875 935 'sync',
936 + 'account_protection',
876 937 'waf',
877 - 'videopress',
878 - 'stats',
879 - 'stats_admin',
880 - 'import',
881 938 )
882 939 as $feature
883 940 ) {
884 941 $config->ensure( $feature );
@@ -883,8 +940,103 @@
883 940 ) {
884 941 $config->ensure( $feature );
885 942 }
886 943
944 + // Enable the VideoPress admin UI (the "Jetpack > VideoPress" dashboard) inside the
945 + // Jetpack plugin, mirroring the standalone Jetpack VideoPress plugin. The dashboard
946 + // only renders when the VideoPress module is active (Status::is_active()); when it
947 + // is not, the menu item links to the My Jetpack interstitial to activate it.
948 + $config->ensure( 'videopress', array( 'admin_ui' => true ) );
949 +
950 + /*
951 + * The Import package only registers `jetpack/v4/import` REST routes — it
952 + * does nothing when rendering a front-end page — so gate its `ensure()`
953 + * to keep its PHP out of opcache on the front-end GET hot path. It still
954 + * loads on admin, cron, POST, and WP-CLI requests, and on `rest_api_init`
955 + * for REST: a REST request can't be identified yet at `plugins_loaded`
956 + * (this runs before `Config::on_plugins_loaded`, and `rest_api_init`
957 + * fires later), so it is initialized directly when that hook fires, while
958 + * a plain page view never fires it and so loads nothing.
959 + *
960 + * JITM stays eager (above): unlike Import, its `register()` adds a
961 + * `jetpack_sync_before_send_updated_option` filter that records the
962 + * `jetpack_last_plugin_sync` transient, and a Jetpack Sync send can fire
963 + * on a plain front-end GET — including the dedicated-sync `spawn-sync`
964 + * GET, which runs on `init` and exits before `rest_api_init`. Deferring
965 + * JITM would skip that bookkeeping and leave its message cache stale after
966 + * a plugin change, so it loads on every request as before.
967 + */
968 + if ( self::should_eager_load_packages() ) {
969 + $config->ensure( 'import' );
970 + } else {
971 + add_action(
972 + 'rest_api_init',
973 + array( __CLASS__, 'configure_import_package' ),
974 + 0
975 + );
976 + }
977 +
978 + /*
979 + * The Stats and Stats Admin packages only do work when the Stats module
980 + * is active (the front-end tracking pixel) or on wp-admin, REST, cron,
981 + * POST, and WP-CLI requests: the Stats dashboard page, the stats /
982 + * stats-app REST endpoints (which the block editor also calls for
983 + * email-open rates), the transient-cleanup cron, the connection
984 + * package's package-version tracker (which runs on POSTs and reads the
985 + * `jetpack_package_versions` filter that Stats registers), and CLI
986 + * introspection such as the heartbeat inspector. On a plain front-end
987 + * GET page view with the module off they are inert: the pixel
988 + * short-circuits on `Stats\Main::should_track()` and every other entry
989 + * point only hooks rest_api_init, admin, cron, the POST-only tracker, or
990 + * is reached through WP-CLI.
991 + * Skip loading them — and eagerly constructing the Stats Admin REST
992 + * controller — on that hot path to keep their PHP out of opcache, but
993 + * keep loading them everywhere else exactly as before so the stats REST
994 + * permission mapping (view_stats, registered by Stats\Main), the
995 + * editor's stats-app calls, the cleanup cron, and the package-version
996 + * tracker are all unchanged.
997 + *
998 + * REST requests are not yet identifiable here (REST_REQUEST is defined
999 + * after plugins_loaded), so defer those to rest_api_init. Call the
1000 + * package initializers directly rather than `$config->ensure()`: ensure()
1001 + * only flags a feature for `Config::on_plugins_loaded()` (plugins_loaded
1002 + * priority 2), which has already run by the time rest_api_init fires.
1003 + * Priority 0 runs before each package's own priority-10 route
1004 + * registration, so their routes still register within the same dispatch.
1005 + * A plain page view never fires rest_api_init, so the packages stay
1006 + * unloaded there. See JETPACK-1747.
1007 + */
1008 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'];
1009 + $is_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
1010 +
1011 + if ( self::is_module_active( 'stats' ) || is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli ) {
1012 + $config->ensure( 'stats' );
1013 + $config->ensure( 'stats_admin' );
1014 + } else {
1015 + add_action(
1016 + 'rest_api_init',
1017 + static function () {
1018 + if ( class_exists( 'Automattic\Jetpack\Stats\Main' ) ) {
1019 + \Automattic\Jetpack\Stats\Main::init();
1020 + }
1021 + if ( class_exists( 'Automattic\Jetpack\Stats_Admin\Main' ) ) {
1022 + \Automattic\Jetpack\Stats_Admin\Main::init();
1023 + }
1024 + },
1025 + 0
1026 + );
1027 + }
1028 +
1029 + // Stats v2 (WOOA7S-1595). Unlike Stats above it cannot be deferred when enabled — see
1030 + // Analytics::init() for why, and for why it takes no menu_title here.
1031 + if ( self::is_premium_analytics_enabled() ) {
1032 + \Automattic\Jetpack\PremiumAnalytics\Analytics::init();
1033 + }
1034 +
1035 + // Outside the check above on purpose — see Enablement_Setting. Deferred like Stats, to keep
1036 + // the autoload off the front-end hot path.
1037 + add_action( 'rest_api_init', array( __CLASS__, 'register_premium_analytics_enablement_setting' ), 0 );
1038 +
887 1039 $config->ensure(
888 1040 'connection',
889 1041 array(
890 1042 'slug' => 'jetpack',
@@ -902,12 +1054,8 @@
902 1054 );
903 1055
904 1056 $config->ensure( 'search' );
905 1057
906 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
907 - $config->ensure( 'wordads' );
908 - }
909 -
910 1058 if ( ! $this->connection_manager ) {
911 1059 $this->connection_manager = new Connection_Manager( 'jetpack' );
912 1060 }
913 1061
@@ -915,8 +1063,10 @@
915 1063 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
916 1064 $config->ensure( 'publicize' );
917 1065 }
918 1066
1067 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1068 +
919 1069 /*
920 1070 * Load things that should only be in Network Admin.
921 1071 *
922 1072 * For now blow away everything else until a more full
@@ -931,8 +1081,9 @@
931 1081 $is_connection_ready = self::is_connection_ready();
932 1082
933 1083 if ( $is_connection_ready ) {
934 1084 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1085 + $this->run_initialize_tracking_action();
935 1086
936 1087 Jetpack_Heartbeat::init();
937 1088 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
938 1089 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -937,8 +1088,19 @@
937 1088 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
938 1089 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
939 1090 Jetpack_Search_Performance_Logger::init();
940 1091 }
1092 + } else {
1093 + add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
1094 + add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
1095 + add_filter(
1096 + 'xmlrpc_methods',
1097 + function ( $methods ) {
1098 + $this->run_initialize_tracking_action();
1099 + return $methods;
1100 + },
1101 + 1
1102 + );
941 1103 }
942 1104
943 1105 // Initialize remote file upload request handlers.
944 1106 $this->add_remote_request_handlers();
@@ -948,20 +1110,10 @@
948 1110 */
949 1111 if ( $is_connection_ready ) {
950 1112 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
951 1113 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
952 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
953 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1114 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
954 1115 }
955 -
956 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
957 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
958 - } else {
959 - /**
960 - * Initialize tracking right after the user agrees to the terms of service.
961 - */
962 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
963 - }
964 1116 }
965 1117
966 1118 /**
967 1119 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -969,15 +1121,59 @@
969 1121 *
970 1122 * @action plugins_loaded
971 1123 */
972 1124 public function late_initialization() {
973 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1125 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
974 1126
975 - My_Jetpack_Initializer::init();
1127 + /*
1128 + * My Jetpack is a wp-admin dashboard. Its Initializer::init() only wires
1129 + * up admin-menu, admin_init, and rest_api_init surfaces — and eagerly
1130 + * loads every product class (backup, boost, protect, …) just to register
1131 + * admin plugin-action links — so none of it is needed on a plain
1132 + * front-end GET page view. (The pieces that do immediate work, e.g.
1133 + * Connection REST authentication and Licensing, are already initialized
1134 + * unconditionally in Jetpack's constructor, so they are unaffected here.)
1135 + *
1136 + * Gate the call to the request types where My Jetpack actually does work.
1137 + * REST can't be detected yet at plugins_loaded, so initialize on
1138 + * rest_api_init for that branch; a plain page view never fires it, so My
1139 + * Jetpack stays unloaded there.
1140 + */
1141 + if ( self::should_eager_load_packages() ) {
1142 + My_Jetpack_Initializer::init();
1143 + } else {
1144 + add_action( 'rest_api_init', array( My_Jetpack_Initializer::class, 'init' ), 0 );
1145 + }
976 1146
977 - // Initialize Boost Speed Score
978 - new Speed_Score( array(), 'jetpack-dashboard' );
1147 + Activity_Log_Init::initialize();
1148 + Scan_Page_Init::initialize();
1149 + Jetpack_SEO_Initializer::init();
979 1150
1151 + if ( ( new Modules() )->is_active( 'podcast' ) ) {
1152 + Podcast::init();
1153 + }
1154 +
1155 + /*
1156 + * Initialize Boost Speed Score. It only does work on REST requests (the
1157 + * dashboard speed-score endpoints) and on a few Jetpack Boost lifecycle
1158 + * actions, so defer constructing it — and loading the boost-speed-score
1159 + * package classes — until one of those hooks actually fires instead of on
1160 + * every request. Priority 0 ensures the object's own callbacks (added in
1161 + * its constructor at the default priority) still run for the firing hook.
1162 + */
1163 + $initialize_speed_score = static function () {
1164 + static $initialized = false;
1165 + if ( $initialized ) {
1166 + return;
1167 + }
1168 + $initialized = true;
1169 + new Speed_Score( array(), 'jetpack-dashboard' );
1170 + };
1171 + add_action( 'rest_api_init', $initialize_speed_score, 0 );
1172 + add_action( 'jetpack_boost_deactivate', $initialize_speed_score, 0 );
1173 + add_action( 'jetpack_boost_environment_changed', $initialize_speed_score, 0 );
1174 + add_action( 'handle_environment_change', $initialize_speed_score, 0 );
1175 +
980 1176 /**
981 1177 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
982 1178 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
983 1179 *
@@ -1015,8 +1211,10 @@
1015 1211
1016 1212 /**
1017 1213 * Redirect edit post links to Calypso.
1018 1214 *
1215 + * @deprecated since 13.9
1216 + *
1019 1217 * @param string $default_url Post edit URL.
1020 1218 * @param int $post_id Post ID.
1021 1219 *
1022 1220 * @return string
@@ -1021,8 +1219,10 @@
1021 1219 *
1022 1220 * @return string
1023 1221 */
1024 1222 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1223 + _deprecated_function( __METHOD__, '13.9' );
1224 +
1025 1225 $post = get_post( $post_id );
1026 1226
1027 1227 if ( empty( $post ) ) {
1028 1228 return $default_url;
@@ -1053,13 +1253,17 @@
1053 1253
1054 1254 /**
1055 1255 * Redirect edit comment links to Calypso.
1056 1256 *
1257 + * @deprecated since 13.9
1258 + *
1057 1259 * @param string $url Comment edit URL.
1058 1260 *
1059 1261 * @return string
1060 1262 */
1061 1263 public function point_edit_comment_links_to_calypso( $url ) {
1264 + _deprecated_function( __METHOD__, '13.9' );
1265 +
1062 1266 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1063 1267 $query_args = null;
1064 1268 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1065 1269
@@ -1078,9 +1282,8 @@
1078 1282 *
1079 1283 * @return array list of callables.
1080 1284 */
1081 1285 public function filter_sync_callable_whitelist( $callables ) {
1082 -
1083 1286 // Jetpack Functions.
1084 1287 $jetpack_callables = array(
1085 1288 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1086 1289 'updates' => array( 'Jetpack', 'get_updates' ),
@@ -1085,23 +1288,9 @@
1085 1288 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1086 1289 'updates' => array( 'Jetpack', 'get_updates' ),
1087 1290 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1088 1291 );
1089 - $callables = array_merge( $callables, $jetpack_callables );
1090 -
1091 - // Jetpack_SSO_Helpers.
1092 - if ( include_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ) {
1093 - $sso_helpers = array(
1094 - 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
1095 - 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
1096 - 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
1097 - 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
1098 - 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
1099 - );
1100 - $callables = array_merge( $callables, $sso_helpers );
1101 - }
1102 -
1103 - return $callables;
1292 + return array_merge( $callables, $jetpack_callables );
1104 1293 }
1105 1294
1106 1295 /**
1107 1296 * Extend Sync multisite callables with Jetpack Plugin functions.
@@ -1142,16 +1331,8 @@
1142 1331 * @param string $cap Capability name.
1143 1332 */
1144 1333 public function jetpack_custom_caps( $caps, $cap ) {
1145 1334 switch ( $cap ) {
1146 - case 'jetpack_manage_modules':
1147 - case 'jetpack_activate_modules':
1148 - case 'jetpack_deactivate_modules':
1149 - $caps = array( 'manage_options' );
1150 - break;
1151 - case 'jetpack_configure_modules':
1152 - $caps = array( 'manage_options' );
1153 - break;
1154 1335 case 'jetpack_manage_autoupdates':
1155 1336 $caps = array(
1156 1337 'manage_options',
1157 1338 'update_plugins',
@@ -1169,9 +1350,9 @@
1169 1350 if ( $is_offline_mode ) {
1170 1351 $caps = array( 'manage_options' );
1171 1352 break;
1172 1353 } else {
1173 - $caps = array( 'read' );
1354 + $caps = array( 'edit_posts' );
1174 1355 }
1175 1356 break;
1176 1357 }
1177 1358 return $caps;
@@ -1614,29 +1795,8 @@
1614 1795 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1615 1796 }
1616 1797
1617 1798 /**
1618 - * Whether the site is currently onboarding or not.
1619 - * A site is considered as being onboarded if it currently has an onboarding token.
1620 - *
1621 - * @since 5.8
1622 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1623 - *
1624 - * @access public
1625 - * @static
1626 - *
1627 - * @return bool True if the site is currently onboarding, false otherwise
1628 - */
1629 - public static function is_onboarding() {
1630 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1631 -
1632 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1633 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1634 - }
1635 - return ( new Status() )->is_onboarding();
1636 - }
1637 -
1638 - /**
1639 1799 * Determines reason for Jetpack offline mode.
1640 1800 */
1641 1801 public static function development_mode_trigger_text() {
1642 1802 $status = new Status();
@@ -1650,11 +1810,10 @@
1650 1810 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1651 1811 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1652 1812 } elseif ( $status->is_local_site() ) {
1653 1813 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1654 - /** This filter is documented in packages/status/src/class-status.php */
1655 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1656 - $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1814 + } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1815 + $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1657 1816 } else {
1658 1817 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1659 1818 }
1660 1819
@@ -1684,15 +1843,8 @@
1684 1843 $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' ) ) );
1685 1844
1686 1845 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1687 1846 }
1688 - // Throw up a notice if using staging mode.
1689 - if ( ( new Status() )->is_staging_site() ) {
1690 - /* translators: %s is a URL */
1691 - $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' ) ) );
1692 -
1693 - echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1694 - }
1695 1847 }
1696 1848
1697 1849 /**
1698 1850 * Whether Jetpack's version maps to a public release, or a development version.
@@ -1766,18 +1918,11 @@
1766 1918 */
1767 1919 public static function load_modules() {
1768 1920 $status = new Status();
1769 1921
1770 - if ( method_exists( $status, 'is_onboarding' ) ) {
1771 - $is_onboarding = $status->is_onboarding();
1772 - } else {
1773 - $is_onboarding = self::is_onboarding();
1774 - }
1775 -
1776 1922 if (
1777 1923 ! self::is_connection_ready()
1778 1924 && ! $status->is_offline_mode()
1779 - && ! $is_onboarding
1780 1925 && (
1781 1926 ! is_multisite()
1782 1927 || ! get_site_option( 'jetpack_protect_active' )
1783 1928 )
@@ -1898,22 +2043,10 @@
1898 2043 *
1899 2044 * @todo Store the result in core's object cache maybe?
1900 2045 */
1901 2046 public static function get_active_plugins() {
1902 - $active_plugins = (array) get_option( 'active_plugins', array() );
1903 -
1904 - if ( is_multisite() ) {
1905 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1906 - // whereas active_plugins stores them in the values.
1907 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1908 - if ( $network_plugins ) {
1909 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1910 - }
1911 - }
1912 -
1913 - sort( $active_plugins );
1914 -
1915 - return array_unique( $active_plugins );
2047 + // Delegates to the canonical implementation in the Connection package.
2048 + return Heartbeat::get_active_plugins();
1916 2049 }
1917 2050
1918 2051 /**
1919 2052 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2051,8 +2184,10 @@
2051 2184 *
2052 2185 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2053 2186 */
2054 2187 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2188 + // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
2189 + // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
2055 2190 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2056 2191 }
2057 2192 }
2058 2193
@@ -2155,9 +2290,9 @@
2155 2290 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.
2156 2291 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2157 2292 }
2158 2293 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2159 - exit;
2294 + exit( 0 );
2160 2295 }
2161 2296 }
2162 2297
2163 2298 /**
@@ -2298,8 +2433,17 @@
2298 2433 }
2299 2434 }
2300 2435 }
2301 2436
2437 + // Special case to convert block setting to a block module.
2438 + $block_key = array_search( 'blocks', $modules, true );
2439 + if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2440 + $block_option = get_option( 'jetpack_blocks_disabled', null );
2441 + if ( $block_option ) {
2442 + unset( $modules[ $block_key ] );
2443 + }
2444 + }
2445 +
2302 2446 return $modules;
2303 2447 }
2304 2448
2305 2449 /**
@@ -2412,20 +2556,28 @@
2412 2556
2413 2557 /**
2414 2558 * Catches PHP errors. Must be used in conjunction with output buffering.
2415 2559 *
2560 + * @deprecated since 13.5
2416 2561 * @param bool $catch True to start catching, False to stop.
2417 2562 *
2418 2563 * @static
2564 + * @deprecated 13.5
2565 + * @see \Automattic\Jetpack\Errors
2419 2566 */
2420 2567 public static function catch_errors( $catch ) {
2568 + _deprecated_function( __METHOD__, '13.5' );
2569 + // @phan-suppress-next-line PhanDeprecatedClass
2421 2570 return ( new Errors() )->catch_errors( $catch );
2422 2571 }
2423 2572
2424 2573 /**
2425 2574 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2575 + *
2576 + * @deprecated since 13.5
2426 2577 */
2427 2578 public static function catch_errors_on_shutdown() {
2579 + _deprecated_function( __METHOD__, '13.5' );
2428 2580 self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2429 2581 }
2430 2582
2431 2583 /**
@@ -2529,9 +2681,9 @@
2529 2681 ),
2530 2682 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2531 2683 );
2532 2684 wp_safe_redirect( $url );
2533 - exit;
2685 + exit( 0 );
2534 2686 }
2535 2687 }
2536 2688
2537 2689 /**
@@ -2549,9 +2701,8 @@
2549 2701
2550 2702 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating.
2551 2703 if ( $send_state_messages ) {
2552 2704 self::restate();
2553 - self::catch_errors( true );
2554 2705 }
2555 2706
2556 2707 $active = self::get_active_modules();
2557 2708
@@ -2619,10 +2770,8 @@
2619 2770 if ( $send_state_messages ) {
2620 2771 self::state( 'error', false );
2621 2772 self::state( 'module', false );
2622 2773 }
2623 -
2624 - self::catch_errors( false );
2625 2774 /**
2626 2775 * Fires when default modules are activated.
2627 2776 *
2628 2777 * @since 1.9.0
@@ -2740,9 +2889,9 @@
2740 2889 if ( $update ) {
2741 2890 update_option( 'active_plugins', array_filter( $plugins ) );
2742 2891 }
2743 2892 }
2744 - exit;
2893 + exit( 0 );
2745 2894 }
2746 2895
2747 2896 /**
2748 2897 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2767,12 +2916,18 @@
2767 2916 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2768 2917
2769 2918 Health::on_jetpack_activated();
2770 2919
2920 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2921 +
2771 2922 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2772 2923 Sync_Actions::do_only_first_initial_sync();
2773 2924 }
2774 2925
2926 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2927 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2928 + }
2929 +
2775 2930 self::plugin_initialize();
2776 2931 }
2777 2932
2778 2933 /**
@@ -2807,9 +2962,9 @@
2807 2962
2808 2963 if ( $plugins_path === $referer['path'] ) {
2809 2964 $source_type = 'list';
2810 2965 } elseif ( $plugins_install_path === $referer['path'] ) {
2811 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
2966 + $tab = $query_parts['tab'] ?? 'featured';
2812 2967 switch ( $tab ) {
2813 2968 case 'popular':
2814 2969 $source_type = 'popular';
2815 2970 break;
@@ -2819,10 +2974,10 @@
2819 2974 case 'favorites':
2820 2975 $source_type = 'favorites';
2821 2976 break;
2822 2977 case 'search':
2823 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2824 - $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
2978 + $source_type = 'search-' . ( $query_parts['type'] ?? 'term' );
2979 + $source_query = $query_parts['s'] ?? null;
2825 2980 break;
2826 2981 default:
2827 2982 $source_type = 'featured';
2828 2983 }
@@ -2831,29 +2986,351 @@
2831 2986 return array( $source_type, $source_query );
2832 2987 }
2833 2988
2834 2989 /**
2835 - * Runs before bumping version numbers up to a new version
2990 + * Runs before bumping version numbers up to a new version.
2836 2991 *
2837 - * @param string $version Version:timestamp.
2992 + * Only ever registered the hooks for the release post update modal, which has been removed.
2993 + * No longer hooked to `updating_jetpack_version`.
2994 + *
2995 + * @deprecated 16.2
2996 + *
2997 + * @param string $version Version:timestamp.
2838 2998 * @param string $old_version Old Version:timestamp or false if not set yet.
2839 2999 */
2840 - public static function do_version_bump( $version, $old_version ) {
2841 - if ( $old_version ) { // For existing Jetpack installations.
2842 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3000 + public static function do_version_bump( $version, $old_version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature preserved for the deprecation shim.
3001 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
3002 + }
2843 3003
2844 - // If a front end page is visited after the update, the 'wp' action will fire.
2845 - add_action( 'wp', 'Jetpack::set_update_modal_display' );
3004 + /**
3005 + * Enables the Newsletter (subscriptions) module for existing sites now that it is a default-on module.
3006 + *
3007 + * Fresh installs receive the module via its "Auto Activate: Yes" header, so this only handles sites
3008 + * upgrading from a version where the module defaulted off. It runs once per site (guarded by the
3009 + * subscriptions_default_on_migrated option). Fresh installs are marked as migrated immediately so the
3010 + * migration never runs for them. After it has run, the user's choice to deactivate the module again
3011 + * (for example from the My Jetpack Products page) is respected and never reverted.
3012 + *
3013 + * The module requires a connection, so on a disconnected site the migration is deferred without setting
3014 + * the guard, allowing a later version bump to retry once the site is connected.
3015 + *
3016 + * @param string $version New Jetpack version:timestamp.
3017 + * @param string|false $old_version Previous Jetpack version:timestamp, or false on a fresh install.
3018 + */
3019 + public static function activate_subscriptions_module_for_existing_sites( $version, $old_version ) {
3020 + if ( get_option( 'jetpack_subscriptions_default_on_migrated' ) ) {
3021 + return;
3022 + }
2846 3023
2847 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2848 - add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3024 + // Fresh installs get the module via its "Auto Activate: Yes" header. Mark them as migrated so a
3025 + // later opt-out is never reverted by the existing-site path on a subsequent version bump.
3026 + if ( ! $old_version ) {
3027 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3028 + return;
2849 3029 }
3030 +
3031 + if ( ! self::is_connection_ready() ) {
3032 + return;
3033 + }
3034 +
3035 + // Mark as migrated only once the module is active, so a transient activation failure is retried on
3036 + // a later version bump rather than being silently skipped.
3037 + if ( self::is_module_active( 'subscriptions' ) || self::activate_module( 'subscriptions', false, false ) ) {
3038 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3039 + }
2850 3040 }
2851 3041
2852 3042 /**
3043 + * Option flag that records the AI master-switch opt-out reconciliation has run,
3044 + * so it never runs twice.
3045 + *
3046 + * @var string
3047 + */
3048 + const AI_MASTER_OPTOUT_MIGRATED_OPTION = 'jetpack_ai_master_optout_migrated';
3049 +
3050 + /**
3051 + * Register the on-upgrade init hooks whose relative ORDER matters, extracted so
3052 + * the ordering can be asserted in tests without invoking plugin_upgrade() (whose
3053 + * guards make it unreliable to trigger under test). activate_new_modules()
3054 + * (init, default priority 10) auto-activates "Auto Activate: Yes" modules
3055 + * including the `ai` master; reconcile_ai_master_optout() must run at a LATER
3056 + * priority to honor an explicit AI opt-out.
3057 + *
3058 + * @return void
3059 + */
3060 + public static function register_upgrade_init_hooks() {
3061 + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
3062 + add_action( 'init', array( __CLASS__, 'reconcile_ai_master_optout' ), 20 );
3063 + }
3064 +
3065 + /**
3066 + * Preserves an explicit Jetpack AI opt-out when the `ai` module becomes the site-wide
3067 + * master switch off WordPress.com Simple (self-hosted and Atomic).
3068 + *
3069 + * The `ai` module is "Auto Activate: Yes", so on upgrade {@see self::activate_new_modules()}
3070 + * turns it on for connected sites — the desired default-on / auto-enable-on-connection
3071 + * behavior, which this method deliberately leaves alone. The one case it corrects is a site
3072 + * that had explicitly disabled Jetpack AI (the `jetpack_ai_enabled` option present and falsey)
3073 + * before the module shipped: that opt-out must survive the module becoming the master, so the
3074 + * module is deactivated for exactly those sites. An absent or truthy option is left untouched.
3075 + *
3076 + * Ordering is the whole point. `activate_new_modules()` is hooked on `init` at priority 10 and
3077 + * auto-activates the module there; this method is hooked on `init` at priority 20 (see
3078 + * {@see self::plugin_upgrade()}), so it runs AFTER the auto-activation and its deactivation is
3079 + * the final state. A version-guarded block that ran inline during `plugins_loaded` would be
3080 + * undone by the later auto-activation, which is why this is a late-init hook rather than an
3081 + * inline upgrade step.
3082 + *
3083 + * WordPress.com Simple never runs modules — the option stays the master there — so this is a
3084 + * no-op on Simple. The {@see self::AI_MASTER_OPTOUT_MIGRATED_OPTION} flag makes it run exactly
3085 + * once, which matters because off-Simple the option is no longer the master after this runs:
3086 + * a stale falsey option must not keep re-deactivating a module the user later turns back on.
3087 + *
3088 + * @return void
3089 + */
3090 + public static function reconcile_ai_master_optout() {
3091 + if ( get_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION ) ) {
3092 + return;
3093 + }
3094 +
3095 + // Simple keeps the `jetpack_ai_enabled` option as the master; modules don't run there.
3096 + if ( ( new Host() )->is_wpcom_simple() ) {
3097 + return;
3098 + }
3099 +
3100 + // A sentinel default distinguishes an absent option (leave auto-activation alone) from one
3101 + // explicitly stored falsey (an opt-out to preserve).
3102 + $stored = get_option( 'jetpack_ai_enabled', 'not-set' );
3103 + if ( 'not-set' !== $stored && ! (bool) $stored ) {
3104 + ( new Modules() )->deactivate( 'ai' );
3105 + }
3106 +
3107 + update_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION, true );
3108 + }
3109 +
3110 + /**
3111 + * Whether a module should be recorded as active in its durable Jetpack SEO option.
3112 + *
3113 + * Permanent module overrides are part of the site's configuration, so the normal
3114 + * filtered module state is authoritative. The exception is wpcomsh's private-site
3115 + * callback, which temporarily suppresses `sitemaps` on Atomic sites without changing
3116 + * the site's configured state. Evaluate the module state with only that callback
3117 + * disabled.
3118 + *
3119 + * The hook is cloned before removing the callback so its ordering remains unchanged for
3120 + * the rest of the request. `$available_only` is false because these migrations can run
3121 + * after the standalone module file has been removed.
3122 + *
3123 + * WordPress.com Simple keeps module state outside this site's options table, so its
3124 + * normal filtered read remains authoritative.
3125 + *
3126 + * @since 16.1
3127 + *
3128 + * @param string $module Module slug.
3129 + * @return bool Whether the module should be recorded as active.
3130 + */
3131 + private static function is_module_active_for_seo_option( $module ) {
3132 + $modules = new Modules();
3133 +
3134 + if ( ( new Host() )->is_wpcom_simple() ) {
3135 + return $modules->is_active( $module, false );
3136 + }
3137 +
3138 + global $wp_filter;
3139 +
3140 + $hook_name = 'jetpack_active_modules';
3141 + $private_site_callback = '\Private_Site\filter_jetpack_active_modules';
3142 + $callback_priority = has_filter( $hook_name, $private_site_callback );
3143 +
3144 + if ( false === $callback_priority ) {
3145 + return $modules->is_active( $module, false );
3146 + }
3147 +
3148 + $original_hook = $wp_filter[ $hook_name ];
3149 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Use an isolated hook copy without changing callback order.
3150 + $wp_filter[ $hook_name ] = clone $original_hook;
3151 + remove_filter( $hook_name, $private_site_callback, $callback_priority );
3152 +
3153 + try {
3154 + return $modules->is_active( $module, false );
3155 + } finally {
3156 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the untouched original hook.
3157 + $wp_filter[ $hook_name ] = $original_hook;
3158 + }
3159 + }
3160 +
3161 + /**
3162 + * Records whether the standalone Sitemaps module is active so the setting survives
3163 + * the module's removal.
3164 + *
3165 + * The Jetpack SEO product reads the {@see Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION}
3166 + * option instead of the `sitemaps` module's active state. Module-active state is
3167 + * filtered against the modules present on disk, so once the standalone module is
3168 + * removed it would read as inactive even for sites that had it on. This one-time
3169 + * migration captures the configured module state — which persists regardless of
3170 + * whether the module file is present — into the durable option.
3171 + *
3172 + * Deliberately non-destructive: it never touches generated sitemap data
3173 + * (`jp_sitemap*` posts), the `jetpack-sitemap-state` option, sitemap settings, or the
3174 + * `jp_sitemap_cron_hook` cron, so no regeneration is triggered. `add_option()` only
3175 + * seeds when the option is absent, so it is safe to run on every version bump and
3176 + * never reverts a value the user has since set.
3177 + *
3178 + * Hooked on `updating_jetpack_version`; the version arguments are not needed because
3179 + * `add_option()` provides the run-once guard.
3180 + */
3181 + public static function migrate_sitemaps_module_to_seo_option() {
3182 + // Ignore wpcomsh's temporary private-site suppression while preserving permanent
3183 + // module overrides. {@see self::is_module_active_for_seo_option()}.
3184 + $sitemaps_active = self::is_module_active_for_seo_option( 'sitemaps' );
3185 +
3186 + add_option( Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION, $sitemaps_active );
3187 + }
3188 +
3189 + /**
3190 + * Keeps the Jetpack SEO sitemap option in sync with the legacy `sitemaps` module
3191 + * while both still exist.
3192 + *
3193 + * Hooked to the module's activate/deactivate actions, so toggling sitemaps from any
3194 + * surface (the legacy Traffic settings, the SEO Settings tab, or WP-CLI) keeps the
3195 + * durable {@see Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION} option current. The
3196 + * actions fire after `active_modules` is updated, so the configured state already
3197 + * reflects the new choice. Ignore temporary private-site suppression without bypassing
3198 + * permanent module overrides. Removed alongside the module itself.
3199 + */
3200 + public static function sync_seo_sitemap_option() {
3201 + update_option( Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION, self::is_module_active_for_seo_option( 'sitemaps' ) );
3202 + }
3203 +
3204 + /**
3205 + * Records whether the standalone Canonical URLs module is active so the setting survives
3206 + * the module's removal.
3207 + *
3208 + * The Jetpack SEO product reads the {@see Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION}
3209 + * option instead of the `canonical-urls` module's active state. Module-active state is
3210 + * filtered against the modules present on disk, so once the standalone module is
3211 + * removed it would read as inactive even for sites that had it on. This one-time
3212 + * migration captures the configured module state — which persists regardless of
3213 + * whether the module file is present — into the durable option.
3214 + *
3215 + * Deliberately non-destructive: `add_option()` only seeds when the option is absent, so
3216 + * it is safe to run on every version bump and never reverts a value the user has since
3217 + * set.
3218 + *
3219 + * Hooked on `updating_jetpack_version`; the version arguments are not needed because
3220 + * `add_option()` provides the run-once guard.
3221 + */
3222 + public static function migrate_canonical_urls_module_to_seo_option() {
3223 + $canonical_active = self::is_module_active_for_seo_option( 'canonical-urls' );
3224 +
3225 + add_option( Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION, $canonical_active );
3226 + }
3227 +
3228 + /**
3229 + * Keeps the Jetpack SEO canonical-urls option in sync with the legacy `canonical-urls`
3230 + * module while both still exist.
3231 + *
3232 + * Hooked to the module's activate/deactivate actions, so toggling canonical URLs from any
3233 + * surface (the legacy Traffic settings, the SEO Settings tab, or WP-CLI) keeps the
3234 + * durable {@see Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION} option current. The
3235 + * actions fire after `active_modules` is updated, so the configured state already
3236 + * reflects the new choice. Permanent module overrides remain authoritative. Removed
3237 + * alongside the module itself.
3238 + */
3239 + public static function sync_seo_canonical_urls_option() {
3240 + update_option( Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION, self::is_module_active_for_seo_option( 'canonical-urls' ) );
3241 + }
3242 +
3243 + /**
3244 + * Repairs durable SEO module-state options that the first pass of the migration seeded
3245 + * from a filtered read.
3246 + *
3247 + * Jetpack 16.0 seeded {@see Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION} and
3248 + * {@see Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION} through
3249 + * {@see Modules::is_active()}, which passes the `jetpack_active_modules` filter. A site
3250 + * that was private at the time therefore recorded `sitemaps` as off even though the user
3251 + * had it on, and because the migration seeds with `add_option()` the value was never
3252 + * revisited — making the site public again did not restore the setting.
3253 + *
3254 + * This runs once and rewrites both options from the site's configured module state,
3255 + * including permanent module overrides. That is safe against a choice the user has made
3256 + * since: every surface that toggles these settings (the legacy Traffic page, the SEO
3257 + * Settings tab, WP-CLI) goes through {@see Modules::activate()}/{@see Modules::deactivate()},
3258 + * so the module state and durable option are already in agreement wherever the original
3259 + * migration got it right.
3260 + *
3261 + * Idempotent: the marker is written with `add_option()`, so reruns on later version bumps
3262 + * are no-ops. Like the migrations it seeds from, it touches no sitemap data or cron state.
3263 + *
3264 + * @since 16.1
3265 + */
3266 + public static function reconcile_seo_module_state_options() {
3267 + if ( get_option( self::SEO_MODULE_STATE_RECONCILED_OPTION ) ) {
3268 + return;
3269 + }
3270 +
3271 + update_option( Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION, self::is_module_active_for_seo_option( 'sitemaps' ) );
3272 + update_option( Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION, self::is_module_active_for_seo_option( 'canonical-urls' ) );
3273 +
3274 + add_option( self::SEO_MODULE_STATE_RECONCILED_OPTION, true );
3275 + }
3276 +
3277 + /**
3278 + * Wires up the migration + sync hooks that keep the durable Jetpack SEO module-state
3279 + * options ({@see Jetpack_SEO_Initializer::SITEMAP_ENABLED_OPTION} /
3280 + * {@see Jetpack_SEO_Initializer::CANONICAL_ENABLED_OPTION}) seeded and in sync with their
3281 + * legacy modules. Called once from `load-jetpack.php`.
3282 + *
3283 + * Extracted from file scope so the wiring is unit-testable (file-scope `add_action()`
3284 + * calls run during bootstrap and can't be exercised by a test). Removed alongside the
3285 + * modules in the deferred post-convergence follow-up that absorbs them into Jetpack SEO.
3286 + */
3287 + public static function register_seo_module_migration_hooks() {
3288 + add_action( 'updating_jetpack_version', array( 'Jetpack', 'migrate_sitemaps_module_to_seo_option' ) );
3289 + add_action( 'jetpack_activate_module_sitemaps', array( 'Jetpack', 'sync_seo_sitemap_option' ) );
3290 + add_action( 'jetpack_deactivate_module_sitemaps', array( 'Jetpack', 'sync_seo_sitemap_option' ) );
3291 +
3292 + add_action( 'updating_jetpack_version', array( 'Jetpack', 'migrate_canonical_urls_module_to_seo_option' ) );
3293 + add_action( 'jetpack_activate_module_canonical-urls', array( 'Jetpack', 'sync_seo_canonical_urls_option' ) );
3294 + add_action( 'jetpack_deactivate_module_canonical-urls', array( 'Jetpack', 'sync_seo_canonical_urls_option' ) );
3295 +
3296 + // Runs after both migrations above (default priority, registered last) so a freshly
3297 + // seeded site is already correct and the reconciliation is a no-op there.
3298 + add_action( 'updating_jetpack_version', array( 'Jetpack', 'reconcile_seo_module_state_options' ) );
3299 + }
3300 +
3301 + /**
3302 + * Seeds the Jetpack SEO discoverability cohort once, so the new SEO surface is
3303 + * auto-discoverable on fresh installs but opt-in on existing ones (JETPACK-1700).
3304 + *
3305 + * Hooked on `updating_jetpack_version`, which fires on every install including the
3306 + * first — with `$old_version === false` on a brand-new site (the same signal
3307 + * {@see self::activate_subscriptions_module_for_existing_sites()} keys off). Fresh
3308 + * installs are seeded visible; existing installs are seeded hidden and opt in later
3309 + * via the legacy Traffic page or My Jetpack. `add_option()` makes this seed-once: it
3310 + * never overrides a value a later opt-in (or opt-out) has set. WordPress.com sites
3311 + * ignore this option entirely (always visible) — see
3312 + * {@see \Automattic\Jetpack\SEO\Initializer::is_seo_surface_visible()}.
3313 + *
3314 + * @param string $version The new Jetpack version (unused).
3315 + * @param string|false $old_version The previous version, or false on a fresh install.
3316 + */
3317 + public static function seed_seo_visibility_cohort( $version, $old_version ) {
3318 + add_option( Jetpack_SEO_Initializer::VISIBILITY_OPTION, ! $old_version );
3319 + }
3320 +
3321 + /**
2853 3322 * Sets the display_update_modal state.
3323 + *
3324 + * The release post update modal that read this state has been removed. The write is kept so the
3325 + * method still behaves as documented for the deprecation window. When this is deleted, also drop
3326 + * the matching `display_update_modal` guard in Automattic\Jetpack\CookieState::should_set_cookie(),
3327 + * which exists only to keep this key out of the cookie on the Jetpack admin screen.
3328 + *
3329 + * @deprecated 16.2
2854 3330 */
2855 3331 public static function set_update_modal_display() {
3332 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2856 3333 self::state( 'display_update_modal', true );
2857 3334 }
2858 3335
2859 3336 /**
@@ -2858,11 +3335,16 @@
2858 3335
2859 3336 /**
2860 3337 * Enqueues the block library styles.
2861 3338 *
3339 + * Only ever used by the release post update modal, which has been removed.
3340 + *
3341 + * @deprecated 16.2
3342 + *
2862 3343 * @param string $hook The current admin page.
2863 3344 */
2864 3345 public static function enqueue_block_style( $hook ) {
3346 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2865 3347 if ( 'toplevel_page_jetpack' === $hook ) {
2866 3348 wp_enqueue_style( 'wp-block-library' );
2867 3349 }
2868 3350 }
@@ -2951,8 +3433,12 @@
2951 3433 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2952 3434 self::disconnect();
2953 3435 Jetpack_Options::delete_option( 'version' );
2954 3436 }
3437 +
3438 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3439 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3440 + }
2955 3441 }
2956 3442
2957 3443 /**
2958 3444 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -2980,9 +3466,9 @@
2980 3466 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2981 3467 }
2982 3468
2983 3469 /**
2984 - * Happens after a successfull disconnection.
3470 + * Happens after a successful disconnection.
2985 3471 *
2986 3472 * @static
2987 3473 */
2988 3474 public static function jetpack_site_disconnected() {
@@ -2987,8 +3473,10 @@
2987 3473 */
2988 3474 public static function jetpack_site_disconnected() {
2989 3475 Identity_Crisis::clear_all_idc_options();
2990 3476
3477 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3478 +
2991 3479 // Delete all the sync related data. Since it could be taking up space.
2992 3480 Sender::get_instance()->uninstall();
2993 3481
2994 3482 /**
@@ -3052,10 +3540,17 @@
3052 3540 * @param mixed $code Error code to log.
3053 3541 * @param mixed $data Data to log.
3054 3542 */
3055 3543 public static function log( $code, $data = null ) {
3544 +
3545 + $raw_log = Jetpack_Options::get_option( 'log', array() );
3546 + // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
3547 + if ( ! is_array( $raw_log ) ) {
3548 + return;
3549 + }
3550 +
3056 3551 // only grab the latest 200 entries.
3057 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3552 + $log = array_slice( $raw_log, -199, 199 );
3058 3553
3059 3554 // Append our event to the log.
3060 3555 $log_entry = array(
3061 3556 'time' => time(),
@@ -3155,8 +3650,15 @@
3155 3650
3156 3651 /**
3157 3652 * Return stat data for WPCOM sync.
3158 3653 *
3654 + * The Sync stats module was this method's last caller and moved to the Connection package's
3655 + * `Heartbeat::generate_stats_array()`, which assembles the heartbeat data through the
3656 + * `jetpack_heartbeat_stats_array` filter. Note the package method does not include the extended
3657 + * data from `get_additional_stat_data()`, so callers relying on `$extended` need to add it themselves.
3658 + *
3659 + * @deprecated 16.2
3660 + *
3159 3661 * @param bool $encode JSON encode the result.
3160 3662 * @param bool $extended Adds additional stats data.
3161 3663 *
3162 3664 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3161,10 +3663,13 @@
3161 3663 *
3162 3664 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3163 3665 */
3164 3666 public static function get_stat_data( $encode = true, $extended = true ) {
3165 - $data = Jetpack_Heartbeat::generate_stats_array();
3667 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3166 3668
3669 + // Site environment stats now live in the Connection package; merge them with the Jetpack-specific stats.
3670 + $data = array_merge( Jetpack_Heartbeat::generate_stats_array(), Heartbeat::get_environment_stats() );
3671 +
3167 3672 if ( $extended ) {
3168 3673 $additional_data = self::get_additional_stat_data();
3169 3674 $data = array_merge( $data, $additional_data );
3170 3675 }
@@ -3169,9 +3674,9 @@
3169 3674 $data = array_merge( $data, $additional_data );
3170 3675 }
3171 3676
3172 3677 if ( $encode ) {
3173 - return wp_json_encode( $data );
3678 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3174 3679 }
3175 3680
3176 3681 return $data;
3177 3682 }
@@ -3250,12 +3755,17 @@
3250 3755 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3251 3756 // Upgrade: 1.1 -> 1.1.1
3252 3757 // Check and see if host can verify the Jetpack servers' SSL certificate.
3253 3758 $args = array();
3759 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3254 3760 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3255 3761 }
3256 3762
3257 - if ( current_user_can( 'manage_options' ) && ! self::permit_ssl() ) {
3763 + if (
3764 + current_user_can( 'manage_options' )
3765 + && ! self::permit_ssl()
3766 + && ! $is_offline_mode
3767 + ) {
3258 3768 add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3259 3769 }
3260 3770
3261 3771 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
@@ -3264,12 +3774,8 @@
3264 3774 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3265 3775 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3266 3776 }
3267 3777
3268 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3269 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3270 - }
3271 -
3272 3778 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3273 3779
3274 3780 if ( self::is_connection_ready() || $is_offline_mode ) {
3275 3781 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3308,8 +3814,9 @@
3308 3814 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3309 3815 * This function artificially throws errors for such cases (per a specific list).
3310 3816 *
3311 3817 * @param string $plugin The activated plugin.
3818 + * @throws RuntimeException If a conflicting plugin is detected.
3312 3819 */
3313 3820 public function throw_error_on_activate_plugin( $plugin ) {
3314 3821 $active_modules = self::get_active_modules();
3315 3822
@@ -3322,8 +3829,9 @@
3322 3829 if ( 'stats.php' === basename( $plugin ) ) {
3323 3830 $throw = true;
3324 3831 }
3325 3832 } else {
3833 + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Checked above. See also https://github.com/phan/phan/issues/1204.
3326 3834 $reflection = new ReflectionFunction( 'stats_get_api_key' );
3327 3835 if ( basename( $plugin ) === basename( $reflection->getFileName() ) ) {
3328 3836 $throw = true;
3329 3837 }
@@ -3330,9 +3838,9 @@
3330 3838 }
3331 3839
3332 3840 if ( $throw ) {
3333 3841 /* translators: Plugin name to deactivate. */
3334 - 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
3842 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3335 3843 }
3336 3844 }
3337 3845 }
3338 3846
@@ -3443,18 +3951,17 @@
3443 3951 if ( ! is_int( $status_code ) ) {
3444 3952 $status_code = 400;
3445 3953 }
3446 3954
3447 - status_header( $status_code );
3448 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3955 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3449 3956 }
3450 3957
3451 - status_header( 200 );
3452 3958 if ( true === $response ) {
3453 - exit;
3959 + status_header( 200 );
3960 + exit( 0 );
3454 3961 }
3455 3962
3456 - die( wp_json_encode( (object) $response ) );
3963 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3457 3964 }
3458 3965
3459 3966 /**
3460 3967 * Uploads a file gotten from the global $_FILES.
@@ -3515,9 +4022,9 @@
3515 4022 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3516 4023 }
3517 4024
3518 4025 $uploaded_files = array();
3519 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
4026 + $global_post = $GLOBALS['post'] ?? null;
3520 4027 unset( $GLOBALS['post'] );
3521 4028 if ( empty( $_FILES['media']['name'] ) ) {
3522 4029 // Nothing to process, just return.
3523 4030 return $uploaded_files;
@@ -3524,9 +4031,9 @@
3524 4031 }
3525 4032 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.
3526 4033 $file = array();
3527 4034 foreach ( $media_keys as $media_key ) {
3528 - $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.
4035 + $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.
3529 4036 }
3530 4037
3531 4038 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.
3532 4039
@@ -3572,9 +4079,9 @@
3572 4079 'type' => (string) $edited_media_item->post_mime_type,
3573 4080 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3574 4081 );
3575 4082
3576 - return (array) array( $response );
4083 + return array( $response );
3577 4084 }
3578 4085
3579 4086 $attachment_id = media_handle_upload(
3580 4087 '.jetpack.upload.',
@@ -3613,58 +4120,8 @@
3613 4120 return $uploaded_files;
3614 4121 }
3615 4122
3616 4123 /**
3617 - * Add help to the Jetpack page
3618 - *
3619 - * @since Jetpack (1.2.3)
3620 - * @return void
3621 - */
3622 - public function admin_help() {
3623 - $current_screen = get_current_screen();
3624 -
3625 - // Overview.
3626 - $current_screen->add_help_tab(
3627 - array(
3628 - 'id' => 'home',
3629 - 'title' => __( 'Home', 'jetpack' ),
3630 - 'content' =>
3631 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3632 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3633 - '<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>',
3634 - )
3635 - );
3636 -
3637 - // Screen Content.
3638 - if ( current_user_can( 'manage_options' ) ) {
3639 - $current_screen->add_help_tab(
3640 - array(
3641 - 'id' => 'settings',
3642 - 'title' => __( 'Settings', 'jetpack' ),
3643 - 'content' =>
3644 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3645 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3646 - '<ol>' .
3647 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3648 - '<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>' .
3649 - '</ol>' .
3650 - '<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>',
3651 - )
3652 - );
3653 - }
3654 -
3655 - // Help Sidebar.
3656 - $support_url = Redirect::get_url( 'jetpack-support' );
3657 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3658 - $current_screen->set_help_sidebar(
3659 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3660 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3661 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3662 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3663 - );
3664 - }
3665 -
3666 - /**
3667 4124 * Add action links for the Jetpack plugin.
3668 4125 *
3669 4126 * @param array $actions Plugin actions.
3670 4127 *
@@ -3702,14 +4159,10 @@
3702 4159 'jetpack-plugins-page-js',
3703 4160 '_inc/build/plugins-page.js',
3704 4161 JETPACK__PLUGIN_FILE,
3705 4162 array(
3706 - 'in_footer' => true,
3707 - 'textdomain' => 'jetpack',
3708 - 'dependencies' => array(
3709 - 'wp-polyfill',
3710 - 'wp-components',
3711 - ),
4163 + 'in_footer' => true,
4164 + 'textdomain' => 'jetpack',
3712 4165 )
3713 4166 );
3714 4167 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3715 4168
@@ -3714,9 +4167,9 @@
3714 4167 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3715 4168
3716 4169 // Add objects to be passed to the initial state of the app.
3717 4170 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3718 - 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' );
4171 + wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=' . wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
3719 4172
3720 4173 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3721 4174 }
3722 4175 }
@@ -3765,9 +4218,9 @@
3765 4218 // @todo provide way to go to specific calypso env.
3766 4219 self::get_calypso_host() . 'jetpack/connect'
3767 4220 )
3768 4221 );
3769 - exit;
4222 + exit( 0 );
3770 4223 }
3771 4224 }
3772 4225
3773 4226 /*
@@ -3802,8 +4255,28 @@
3802 4255 * Done!
3803 4256 */
3804 4257
3805 4258 /**
4259 + * Build the user-facing description stored alongside a registration error code.
4260 + *
4261 + * @since 16.2
4262 + *
4263 + * @param string $error_code The WP_Error code.
4264 + * @param string $message The WP_Error message.
4265 + * @return string The description, empty when the message is not user-facing copy.
4266 + */
4267 + public static function get_registration_error_description( $error_code, $message ) {
4268 + // Manager::validate_remote_register_response() does not always put user-facing copy in the
4269 + // message slot: wpcom_5??, wpcom_408 and wpcom_bad_response store the HTTP status there,
4270 + // and jetpack_id stores the raw response body, which can also overflow the state cookie.
4271 + if ( 'jetpack_id' === $error_code || is_numeric( $message ) ) {
4272 + return '';
4273 + }
4274 +
4275 + return mb_substr( (string) $message, 0, 250 );
4276 + }
4277 +
4278 + /**
3806 4279 * Handles the page load events for the Jetpack admin page
3807 4280 */
3808 4281 public function admin_page_load() {
3809 4282 $error = false;
@@ -3839,10 +4312,11 @@
3839 4312 $registered = static::connection()->try_registration();
3840 4313 if ( is_wp_error( $registered ) ) {
3841 4314 $error = $registered->get_error_code();
3842 4315 self::state( 'error', $error );
3843 - self::state( 'error', $registered->get_error_message() );
3844 4316
4317 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4318 +
3845 4319 /**
3846 4320 * Jetpack registration Error.
3847 4321 *
3848 4322 * @since 7.5.0
@@ -3866,12 +4340,8 @@
3866 4340 do_action( 'jetpack_connection_register_success', $from );
3867 4341
3868 4342 $url = $this->build_connect_url( true, $redirect, $from );
3869 4343
3870 - if ( ! empty( $_GET['onboarding'] ) ) {
3871 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3872 - }
3873 -
3874 4344 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3875 4345 $url = add_query_arg( 'auth_approved', 'true', $url );
3876 4346 }
3877 4347
@@ -3876,9 +4346,9 @@
3876 4346 }
3877 4347
3878 4348 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3879 4349 wp_safe_redirect( $url );
3880 - exit;
4350 + exit( 0 );
3881 4351 case 'activate':
3882 4352 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3883 4353 $error = 'cheatin';
3884 4354 break;
@@ -3892,9 +4362,9 @@
3892 4362 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3893 4363 }
3894 4364 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3895 4365 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3896 - exit;
4366 + exit( 0 );
3897 4367 case 'activate_default_modules':
3898 4368 check_admin_referer( 'activate_default_modules' );
3899 4369 self::log( 'activate_default_modules' );
3900 4370 self::restate();
@@ -3902,9 +4372,9 @@
3902 4372 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3903 4373 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3904 4374 self::activate_default_modules( $min_version, $max_version, $other_modules );
3905 4375 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3906 - exit;
4376 + exit( 0 );
3907 4377 case 'disconnect':
3908 4378 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3909 4379 $error = 'cheatin';
3910 4380 break;
@@ -3913,9 +4383,9 @@
3913 4383 check_admin_referer( 'jetpack-disconnect' );
3914 4384 self::log( 'disconnect' );
3915 4385 self::disconnect();
3916 4386 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3917 - exit;
4387 + exit( 0 );
3918 4388 case 'reconnect':
3919 4389 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3920 4390 $error = 'cheatin';
3921 4391 break;
@@ -3926,9 +4396,9 @@
3926 4396 self::disconnect();
3927 4397
3928 4398 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3929 4399 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3930 - exit;
4400 + exit( 0 );
3931 4401 case 'deactivate':
3932 4402 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3933 4403 $error = 'cheatin';
3934 4404 break;
@@ -3942,9 +4412,9 @@
3942 4412 self::state( 'message', 'module_deactivated' );
3943 4413 }
3944 4414 self::state( 'module', $modules );
3945 4415 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3946 - exit;
4416 + exit( 0 );
3947 4417 case 'unlink':
3948 4418 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3949 4419 check_admin_referer( 'jetpack-unlink' );
3950 4420 self::log( 'unlink' );
@@ -3954,32 +4424,9 @@
3954 4424 wp_safe_redirect( admin_url() );
3955 4425 } else {
3956 4426 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3957 4427 }
3958 - exit;
3959 - case 'onboard':
3960 - if ( ! current_user_can( 'manage_options' ) ) {
3961 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3962 - } else {
3963 - self::create_onboarding_token();
3964 - $url = $this->build_connect_url( true );
3965 -
3966 - $token = Jetpack_Options::get_option( 'onboarding' );
3967 -
3968 - if ( false !== ( $token ) ) {
3969 - $url = add_query_arg( 'onboarding', $token, $url );
3970 - }
3971 -
3972 - $calypso_env = ( new Host() )->get_calypso_env();
3973 - if ( ! empty( $calypso_env ) ) {
3974 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
3975 - }
3976 -
3977 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3978 - wp_safe_redirect( $url );
3979 - exit;
3980 - }
3981 - exit;
4428 + exit( 0 );
3982 4429 default:
3983 4430 /**
3984 4431 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3985 4432 *
@@ -4202,37 +4649,8 @@
4202 4649 endif;
4203 4650 }
4204 4651
4205 4652 /**
4206 - * We can't always respond to a signed XML-RPC request with a
4207 - * helpful error message. In some circumstances, doing so could
4208 - * leak information.
4209 - *
4210 - * Instead, track that the error occurred via a Jetpack_Option,
4211 - * and send that data back in the heartbeat.
4212 - * All this does is increment a number, but it's enough to find
4213 - * trends.
4214 - *
4215 - * @param WP_Error $xmlrpc_error The error produced during
4216 - * signature validation.
4217 - */
4218 - public function track_xmlrpc_error( $xmlrpc_error ) {
4219 - $code = is_wp_error( $xmlrpc_error )
4220 - ? $xmlrpc_error->get_error_code()
4221 - : 'should-not-happen';
4222 -
4223 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4224 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4225 - // No need to update the option if we already have
4226 - // this code stored.
4227 - return;
4228 - }
4229 - $xmlrpc_errors[ $code ] = true;
4230 -
4231 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4232 - }
4233 -
4234 - /**
4235 4653 * Initialize the jetpack stats instance only when needed
4236 4654 *
4237 4655 * @return void
4238 4656 */
@@ -4518,11 +4936,8 @@
4518 4936 *
4519 4937 * @param array $data The request data.
4520 4938 */
4521 4939 public static function authorize_ending_authorized( $data ) {
4522 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4523 - self::invalidate_onboarding_token();
4524 -
4525 4940 // If redirect_uri is SSO, ensure SSO module is enabled.
4526 4941 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4527 4942
4528 4943 /** This filter is documented in class.jetpack-cli.php */
@@ -4662,10 +5077,12 @@
4662 5077 $result = self::permit_ssl( true );
4663 5078 wp_send_json(
4664 5079 array(
4665 5080 'enabled' => $result,
4666 - 'message' => get_transient( 'jetpack_https_test_message' ),
4667 - )
5081 + 'message' => self::get_ssl_test_message(),
5082 + ),
5083 + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
5084 + JSON_UNESCAPED_SLASHES
4668 5085 );
4669 5086 }
4670 5087
4671 5088 /* Client API */
@@ -4672,8 +5089,10 @@
4672 5089
4673 5090 /**
4674 5091 * Verify the onboarding token.
4675 5092 *
5093 + * @deprecated since 13.9
5094 + *
4676 5095 * @param array $token_data Token data.
4677 5096 * @param string $token Token value.
4678 5097 * @param string $request_data JSON-encoded request data.
4679 5098 *
@@ -4679,8 +5098,9 @@
4679 5098 *
4680 5099 * @return mixed
4681 5100 */
4682 5101 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
5102 + _deprecated_function( __METHOD__, '13.9' );
4683 5103 // Default to a blog token.
4684 5104 $token_type = 'blog';
4685 5105
4686 5106 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4708,9 +5128,9 @@
4708 5128 $jp_user = get_user_by( 'email', $jpo_user );
4709 5129 if ( is_a( $jp_user, 'WP_User' ) ) {
4710 5130 wp_set_current_user( $jp_user->ID );
4711 5131 $user_can = is_multisite()
4712 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5132 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4713 5133 : current_user_can( 'manage_options' );
4714 5134 if ( $user_can ) {
4715 5135 $token_type = 'user';
4716 5136 $token->external_user_id = $jp_user->ID;
@@ -4727,11 +5147,13 @@
4727 5147
4728 5148 /**
4729 5149 * Create a random secret for validating onboarding payload
4730 5150 *
5151 + * @deprecated since 13.9
4731 5152 * @return string Secret token
4732 5153 */
4733 5154 public static function create_onboarding_token() {
5155 + _deprecated_function( __METHOD__, '13.9' );
4734 5156 $token = Jetpack_Options::get_option( 'onboarding' );
4735 5157 if ( false === ( $token ) ) {
4736 5158 $token = wp_generate_password( 32, false );
4737 5159 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4742,11 +5164,13 @@
4742 5164
4743 5165 /**
4744 5166 * Remove the onboarding token
4745 5167 *
5168 + * @deprecated since 13.9
4746 5169 * @return bool True on success, false on failure
4747 5170 */
4748 5171 public static function invalidate_onboarding_token() {
5172 + _deprecated_function( __METHOD__, '13.9' );
4749 5173 return Jetpack_Options::delete_option( 'onboarding' );
4750 5174 }
4751 5175
4752 5176 /**
@@ -4751,8 +5175,10 @@
4751 5175
4752 5176 /**
4753 5177 * Validate an onboarding token for a specific action
4754 5178 *
5179 + * @deprecated since 13.9
5180 + *
4755 5181 * @param string $token Onboarding token.
4756 5182 * @param string $action Action name.
4757 5183 *
4758 5184 * @return boolean True if token/action pair is accepted, false if not
@@ -4757,8 +5183,9 @@
4757 5183 *
4758 5184 * @return boolean True if token/action pair is accepted, false if not
4759 5185 */
4760 5186 public static function validate_onboarding_token_action( $token, $action ) {
5187 + _deprecated_function( __METHOD__, '13.9' );
4761 5188 // Compare tokens, bail if tokens do not match.
4762 5189 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4763 5190 return false;
4764 5191 }
@@ -4784,39 +5211,33 @@
4784 5211 * @return boolean
4785 5212 * @since 2.3.3
4786 5213 */
4787 5214 public static function permit_ssl( $force_recheck = false ) {
4788 - // Do some fancy tests to see if ssl is being supported.
4789 - if ( ! $force_recheck ) {
4790 - $ssl = get_transient( 'jetpack_https_test' );
4791 - }
5215 + // Delegates to the canonical SSL check in the Connection package.
5216 + return Heartbeat::permit_ssl( $force_recheck );
5217 + }
4792 5218
4793 - if ( $force_recheck || false === $ssl ) {
4794 - $message = '';
4795 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4796 - $ssl = 0;
4797 - } else {
4798 - $ssl = 1;
5219 + /**
5220 + * Returns a localized message describing the last SSL connectivity failure, if any.
5221 + *
5222 + * The Connection package's canonical SSL check stores a neutral reason code; this maps it to
5223 + * a translated, `jetpack`-domain message for display in the admin notice and AJAX recheck.
5224 + *
5225 + * @since 16.1
5226 + *
5227 + * @return string The localized message, or an empty string when there is no failure.
5228 + */
5229 + public static function get_ssl_test_message() {
5230 + $error = Heartbeat::get_ssl_test_error();
4799 5231
4800 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4801 - $ssl = 0;
4802 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4803 - } else {
4804 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4805 - if ( is_wp_error( $response ) ) {
4806 - $ssl = 0;
4807 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4808 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4809 - $ssl = 0;
4810 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4811 - }
4812 - }
4813 - }
4814 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4815 - set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5232 + switch ( $error['code'] ) {
5233 + case 'no_ssl_support':
5234 + return __( 'WordPress reports no SSL support', 'jetpack' );
5235 + case 'bad_response':
5236 + return __( 'Response was not OK: ', 'jetpack' ) . $error['detail'];
5237 + default:
5238 + return '';
4816 5239 }
4817 -
4818 - return (bool) $ssl;
4819 5240 }
4820 5241
4821 5242 /**
4822 5243 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -4835,9 +5256,9 @@
4835 5256 <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>
4836 5257 <p>
4837 5258 <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4838 5259 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4839 - <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
5260 + <span id="jetpack-recheck-ssl-output"><?php echo esc_html( self::get_ssl_test_message() ); ?></span>
4840 5261 </p>
4841 5262 <p>
4842 5263 <?php
4843 5264 printf(
@@ -4856,18 +5277,18 @@
4856 5277 <script type="text/javascript">
4857 5278 jQuery( document ).ready( function( $ ) {
4858 5279 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4859 5280 var $this = $( this );
4860 - $this.html( <?php echo wp_json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5281 + $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4861 5282 $( '#jetpack-recheck-ssl-output' ).html( '' );
4862 5283 e.preventDefault();
4863 - var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce ); ?> };
5284 + var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
4864 5285 $.post( ajaxurl, data )
4865 5286 .done( function( response ) {
4866 5287 if ( response.enabled ) {
4867 5288 $( '#jetpack-ssl-warning' ).hide();
4868 5289 } else {
4869 - this.html( <?php echo wp_json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5290 + this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4870 5291 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4871 5292 }
4872 5293 }.bind( $this ) );
4873 5294 } );
@@ -5225,15 +5646,20 @@
5225 5646
5226 5647 /**
5227 5648 * Checks if the site is currently in an identity crisis.
5228 5649 *
5650 + * Now delegates to the Connection package so this matches what the heartbeat itself reports.
5651 + * Note the package guards on `Connection\Manager::is_connected()` where this used to guard on
5652 + * `Jetpack::is_connection_ready()`, so the `jetpack_is_connection_ready` filter no longer applies.
5653 + *
5654 + * @deprecated 16.2
5655 + *
5229 5656 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5230 5657 */
5231 5658 public static function check_identity_crisis() {
5232 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5233 - return false;
5234 - }
5235 - return Jetpack_Options::get_option( 'sync_error_idc' );
5659 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5660 +
5661 + return Identity_Crisis::check_identity_crisis();
5236 5662 }
5237 5663
5238 5664 /**
5239 5665 * Normalizes a url by doing three things:
@@ -5321,9 +5747,9 @@
5321 5747 *
5322 5748 * @return mixed
5323 5749 */
5324 5750 public static function set_suffix_on_min( $src, $handle ) {
5325 - if ( ! str_contains( $src, '.min.css' ) ) {
5751 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5326 5752 return $src;
5327 5753 }
5328 5754
5329 5755 if ( ! empty( self::$min_assets ) ) {
@@ -5359,8 +5785,10 @@
5359 5785 *
5360 5786 * Data passed in with the $data parameter will be available in the
5361 5787 * template file as $data['value']
5362 5788 *
5789 + * @html-template-var array $data
5790 + *
5363 5791 * @param string $template - Template file to load.
5364 5792 * @param array $data - Any data to pass along to the template.
5365 5793 * @return boolean - If template file was found.
5366 5794 **/
@@ -5378,8 +5806,19 @@
5378 5806 return false;
5379 5807 }
5380 5808
5381 5809 /**
5810 + * Register Jetpack-specific tests on the connection package's health test suite.
5811 + *
5812 + * @param \Automattic\Jetpack\Connection\Connection_Health_Tests $connection_tests The test suite instance.
5813 + */
5814 + public function register_jetpack_connection_tests( $connection_tests ) {
5815 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger/class-jetpack-cxn-tests.php';
5816 + $jetpack_tests = new Jetpack_Cxn_Tests();
5817 + $jetpack_tests->register_tests_on( $connection_tests );
5818 + }
5819 +
5820 + /**
5382 5821 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5383 5822 */
5384 5823 public function deprecated_hooks() {
5385 5824 $filter_deprecated_list = array(
@@ -5603,8 +6042,10 @@
5603 6042 'jetpack_contact_form_use_package' => array(
5604 6043 'replacement' => null,
5605 6044 'version' => 'jetpack-13.4.0',
5606 6045 ),
6046 + // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
6047 + // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
5607 6048 );
5608 6049
5609 6050 foreach ( $filter_deprecated_list as $tag => $args ) {
5610 6051 if ( has_filter( $tag ) ) {
@@ -5735,125 +6176,8 @@
5735 6176 return $css;
5736 6177 }
5737 6178
5738 6179 /**
5739 - * This methods removes all of the registered css files on the front end
5740 - * from Jetpack in favor of using a single file. In effect "imploding"
5741 - * all the files into one file.
5742 - *
5743 - * Pros:
5744 - * - Uses only ONE css asset connection instead of 15
5745 - * - Saves a minimum of 56k
5746 - * - Reduces server load
5747 - * - Reduces time to first painted byte
5748 - *
5749 - * Cons:
5750 - * - Loads css for ALL modules. However all selectors are prefixed so it
5751 - * should not cause any issues with themes.
5752 - * - Plugins/themes dequeuing styles no longer do anything. See
5753 - * jetpack_implode_frontend_css filter for a workaround
5754 - *
5755 - * For some situations developers may wish to disable css imploding and
5756 - * instead operate in legacy mode where each file loads seperately and
5757 - * can be edited individually or dequeued. This can be accomplished with
5758 - * the following line:
5759 - *
5760 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5761 - *
5762 - * @param bool $travis_test Is this a test run.
5763 - *
5764 - * @since 3.2
5765 - */
5766 - public function implode_frontend_css( $travis_test = false ) {
5767 - $do_implode = true;
5768 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5769 - $do_implode = false;
5770 - }
5771 -
5772 - // Do not implode CSS when the page loads via the AMP plugin.
5773 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
5774 - $do_implode = false;
5775 - }
5776 -
5777 - /*
5778 - * Only proceed if at least 2 modules with concatenated CSS are active.
5779 - * There is no point in serving a big concatenated CSS file
5780 - * if there are no features (or only one) that actually need some CSS loaded.
5781 - */
5782 - $active_modules = self::get_active_modules();
5783 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
5784 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
5785 - if ( $active_module_with_css_count < 2 ) {
5786 - $do_implode = false;
5787 - }
5788 -
5789 - /**
5790 - * Allow CSS to be concatenated into a single jetpack.css file.
5791 - *
5792 - * @since 3.2.0
5793 - *
5794 - * @param bool $do_implode Should CSS be concatenated? Default to true.
5795 - */
5796 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5797 -
5798 - // Do not use the imploded file when default behavior was altered through the filter.
5799 - if ( ! $do_implode ) {
5800 - return;
5801 - }
5802 -
5803 - // We do not want to use the imploded file in dev mode, or if not connected.
5804 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
5805 - if ( ! $travis_test ) {
5806 - return;
5807 - }
5808 - }
5809 -
5810 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
5811 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
5812 - return;
5813 - }
5814 -
5815 - /*
5816 - * Now we assume Jetpack is connected and able to serve the single
5817 - * file.
5818 - *
5819 - * In the future there will be a check here to serve the file locally
5820 - * or potentially from the Jetpack CDN
5821 - *
5822 - * For now:
5823 - * - Enqueue a single imploded css file
5824 - * - Zero out the style_loader_tag for the bundled ones
5825 - * - Be happy, drink scotch
5826 - */
5827 -
5828 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
5829 -
5830 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
5831 -
5832 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
5833 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
5834 - }
5835 -
5836 - /**
5837 - * Removes styles that are part of concatenated group.
5838 - *
5839 - * @param string $tag Style tag.
5840 - * @param string $handle Style handle.
5841 - *
5842 - * @return string
5843 - */
5844 - public function concat_remove_style_loader_tag( $tag, $handle ) {
5845 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
5846 - $tag = '';
5847 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5848 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
5849 - }
5850 - }
5851 -
5852 - return $tag;
5853 - }
5854 -
5855 - /**
5856 6180 * Check the heartbeat data
5857 6181 *
5858 6182 * Organizes the heartbeat data by severity. For example, if the site
5859 6183 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -5865,9 +6189,20 @@
5865 6189 *
5866 6190 * $return array $filtered_data
5867 6191 */
5868 6192 public static function jetpack_check_heartbeat_data() {
5869 - $raw_data = Jetpack_Heartbeat::generate_stats_array();
6193 + /*
6194 + * Site environment stats (incl. wp-version/php-version checked below) now live in the Connection package,
6195 + * and the IDC stat is contributed by the Connection package's `jetpack_heartbeat_stats_array` filter
6196 + * callback. We rebuild the stat here rather than running that filter: the filter's callbacks have side
6197 + * effects (Connection\Manager::add_stats_to_heartbeat() consumes and deletes the `xmlrpc_errors` option)
6198 + * and return non-scalar values, neither of which is appropriate for this read-only diagnostic.
6199 + */
6200 + $raw_data = array_merge(
6201 + Jetpack_Heartbeat::generate_stats_array(),
6202 + Heartbeat::get_environment_stats(),
6203 + array( 'identitycrisis' => Identity_Crisis::check_identity_crisis() ? 'yes' : 'no' )
6204 + );
5870 6205
5871 6206 $good = array();
5872 6207 $caution = array();
5873 6208 $bad = array();
@@ -6126,13 +6461,20 @@
6126 6461 }
6127 6462 }
6128 6463
6129 6464 /**
6130 - * Returns a boolean for whether backups UI should be displayed or not.
6465 + * Whether UI for backups should be displayed.
6131 6466 *
6467 + * On WPCom platforms this is gated on the backups-self-serve site feature.
6468 + * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
6469 + *
6132 6470 * @return bool Should backups UI be displayed?
6133 6471 */
6134 6472 public static function show_backups_ui() {
6473 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6474 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
6475 + }
6476 +
6135 6477 /**
6136 6478 * Whether UI for backups should be displayed.
6137 6479 *
6138 6480 * @since 6.5.0
@@ -6142,8 +6484,24 @@
6142 6484 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6143 6485 }
6144 6486
6145 6487 /**
6488 + * Whether UI for security scanning should be displayed.
6489 + *
6490 + * On WPCom platforms this is gated on the scan-self-serve site feature.
6491 + * On self-hosted Jetpack sites it always returns true.
6492 + *
6493 + * @return bool Should scan UI be displayed?
6494 + */
6495 + public static function show_scan_ui() {
6496 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6497 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
6498 + }
6499 +
6500 + return true;
6501 + }
6502 +
6503 + /**
6146 6504 * Clean leftoveruser meta.
6147 6505 *
6148 6506 * Delete Jetpack-related user meta when it is no longer needed.
6149 6507 *
@@ -6230,8 +6588,25 @@
6230 6588 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6231 6589 ),
6232 6590 );
6233 6591
6592 + $products['growth'] = array(
6593 + 'title' => __( 'Jetpack Growth', 'jetpack' ),
6594 + 'slug' => 'jetpack_growth_yearly',
6595 + 'description' => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
6596 + 'show_promotion' => true,
6597 + 'discount_percent' => 50,
6598 + 'included_in_plans' => array( 'complete' ),
6599 + 'features' => array(
6600 + _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
6601 + _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
6602 + _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
6603 + _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
6604 + _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
6605 + _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
6606 + ),
6607 + );
6608 +
6234 6609 $products['scan'] = array(
6235 6610 'title' => __( 'Jetpack Scan', 'jetpack' ),
6236 6611 'slug' => 'jetpack_scan',
6237 6612 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6378,8 +6753,56 @@
6378 6753 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6379 6754 . '</a>';
6380 6755
6381 6756 return $plugin_meta;
6757 + }
6758 +
6759 + /**
6760 + * Lazy instantiation of the Plugin_Tracking object.
6761 + *
6762 + * @since 13.9
6763 + *
6764 + * @return void
6765 + */
6766 + public function initialize_tracking() {
6767 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6768 + // Only need to run once.
6769 + return;
6770 + }
6771 +
6772 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6773 + ( new Plugin_Tracking() )->init();
6774 + }
6775 + }
6776 +
6777 + /**
6778 + * Run the "initialize tracking" hook.
6779 + *
6780 + * @since 13.9
6781 + */
6782 + public function run_initialize_tracking_action() {
6783 + /**
6784 + * Fires when the tracking needs to be initialized.
6785 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6786 + *
6787 + * @since 13.9
6788 + */
6789 + do_action( 'jetpack_initialize_tracking' );
6790 + }
6791 +
6792 + /**
6793 + * Initialize REST jsonAPI if needed.
6794 + *
6795 + * @return void
6796 + */
6797 + public function maybe_initialize_rest_jsonapi() {
6798 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6799 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6800 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6801 +
6802 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6803 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6804 + }
6382 6805 }
6383 6806
6384 6807 /**
6385 6808 * Run plugin post-activation actions if we need to.