PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | class.jetpack.php +838 -524 13.6.2 → 16.3-beta View file →
@@ -21,17 +21,24 @@
21 21 use Automattic\Jetpack\CookieState;
22 22 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
23 23 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
24 24 use Automattic\Jetpack\Errors;
25 +use Automattic\Jetpack\Feature_Policy;
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;
31 35 use Automattic\Jetpack\Plugin\Deprecate;
32 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
33 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;
34 41 use Automattic\Jetpack\Status;
35 42 use Automattic\Jetpack\Status\Host;
36 43 use Automattic\Jetpack\Status\Visitor;
37 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -38,9 +45,14 @@
38 45 use Automattic\Jetpack\Sync\Health;
39 46 use Automattic\Jetpack\Sync\Sender;
40 47 use Automattic\Jetpack\Terms_Of_Service;
41 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
42 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
43 55 /*
44 56 Options:
45 57 jetpack_options (array)
46 58 An array of options.
@@ -75,75 +87,8 @@
75 87 */
76 88 public $xmlrpc_server = null;
77 89
78 90 /**
79 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
80 - *
81 - * See $concatenated_style_handles for the list of handles,
82 - * and the implode_frontend_css method for more details.
83 - *
84 - * When updating this list, make sure to update $concatenated_style_handles as well.
85 - *
86 - * @var array List of Jetpack modules.
87 - */
88 - public $modules_with_concatenated_css = array(
89 - 'carousel',
90 - 'contact-form',
91 - 'infinite-scroll',
92 - 'likes',
93 - 'related-posts',
94 - 'sharedaddy',
95 - 'shortcodes',
96 - 'subscriptions',
97 - 'tiled-gallery',
98 - 'widgets',
99 - );
100 -
101 - /**
102 - * The handles of styles that are concatenated into jetpack.css.
103 - *
104 - * When making changes to that list,
105 - * you must also update concat_list in tools/webpack.config.css.js,
106 - * and to $modules_with_concatenated_css if necessary.
107 - *
108 - * @var array The handles of styles that are concatenated into jetpack.css.
109 - */
110 - public $concatenated_style_handles = array(
111 - 'jetpack-carousel-swiper-css',
112 - 'jetpack-carousel',
113 - 'grunion.css',
114 - 'the-neverending-homepage',
115 - 'jetpack_likes',
116 - 'jetpack_related-posts',
117 - 'sharedaddy',
118 - 'jetpack-slideshow',
119 - 'presentations',
120 - 'quiz',
121 - 'jetpack-subscriptions',
122 - 'jetpack-responsive-videos',
123 - 'jetpack-social-menu',
124 - 'tiled-gallery',
125 - 'jetpack_display_posts_widget',
126 - 'gravatar-profile-widget',
127 - 'goodreads-widget',
128 - 'jetpack_social_media_icons_widget',
129 - 'jetpack-top-posts-widget',
130 - 'jetpack_image_widget',
131 - 'jetpack-my-community-widget',
132 - 'jetpack-authors-widget',
133 - 'wordads',
134 - 'eu-cookie-law-style',
135 - 'flickr-widget-style',
136 - 'jetpack-search-widget',
137 - 'jetpack-simple-payments-widget-style',
138 - 'jetpack-widget-social-icons-styles',
139 - 'wpcom_instagram_widget',
140 - 'milestone-widget',
141 - 'subscribe-modal-css',
142 - 'subscribe-overlay-css',
143 - );
144 -
145 - /**
146 91 * Contains all assets that have had their URL rewritten to minified versions.
147 92 *
148 93 * @var array
149 94 */
@@ -158,11 +103,8 @@
158 103 'contact-form' => array(
159 104 array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
160 105 array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
161 106 ),
162 - 'custom-css' => array(
163 - array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
164 - ),
165 107 'gravatar-hovercards' => array(
166 108 array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
167 109 ),
168 110 'latex' => array(
@@ -333,9 +275,8 @@
333 275 * Graph tags via filter when their Social Meta modules are active:
334 276 *
335 277 * - All in One SEO Pack, All in one SEO Pack Pro
336 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
337 - * - SEOPress, SEOPress Pro
338 279 *
339 280 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
340 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
341 282 *
@@ -378,8 +319,10 @@
378 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
379 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
380 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
381 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
382 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
383 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
384 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
385 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -490,8 +433,16 @@
490 433 */
491 434 public static $instance = false;
492 435
493 436 /**
437 + * Resolved answer for `is_premium_analytics_enabled()`, or null before the first call.
438 + *
439 + * @since 16.1
440 + * @var bool|null
441 + */
442 + private static $premium_analytics_enabled = null;
443 +
444 + /**
494 445 * Singleton
495 446 *
496 447 * @static
497 448 */
@@ -531,9 +482,9 @@
531 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
532 483 self::update_active_modules( $modules );
533 484 }
534 485
535 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
536 487
537 488 // Upgrade to 4.3.0.
538 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
539 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -588,8 +539,16 @@
588 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
589 540 } // Should we have some type of fallback if something fails here?
590 541 }
591 542
543 + // Set the newsletter send default option for existing sites.
544 + if ( false === get_option( 'wpcom_newsletter_send_default' ) ) {
545 + add_option( 'wpcom_newsletter_send_default', 1 );
546 + }
547 +
548 + // Its handler went with the Recommendations assistant.
549 + wp_clear_scheduled_hook( 'jetpack_recommend_videopress' );
550 +
592 551 if ( did_action( 'wp_loaded' ) ) {
593 552 self::upgrade_on_load();
594 553 } else {
595 554 add_action(
@@ -623,9 +582,8 @@
623 582 }
624 583
625 584 if (
626 585 class_exists( 'Jetpack_Sitemap_Manager' )
627 - && version_compare( JETPACK__VERSION, '5.3', '>=' )
628 586 ) {
629 587 do_action( 'jetpack_sitemaps_purge_data' );
630 588 }
631 589
@@ -696,35 +654,15 @@
696 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
697 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
698 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
699 657
700 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
701 -
702 - add_filter(
703 - 'jetpack_signature_check_token',
704 - array( __CLASS__, 'verify_onboarding_token' ),
705 - 10,
706 - 3
707 - );
708 -
709 658 /**
710 659 * Prepare Gutenberg Editor functionality
660 + *
661 + * The hooks previously here have been moved to modules/blocks.php but leaving this here pending
662 + * a longer investigation to see if code is expecting the Gutenberg class to always be available.
711 663 */
712 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
713 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
714 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
715 - /**
716 - * 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.
717 - *
718 - * 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.
719 - */
720 - if ( is_admin() ) {
721 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
722 - } else {
723 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
724 - }
725 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
726 -
727 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
728 666
729 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
730 668
@@ -733,8 +671,13 @@
733 671
734 672 // Set up the REST authentication hooks.
735 673 Connection_Rest_Authentication::init();
736 674
675 + // Register Jetpack-specific connection tests (sync health, etc.) with the connection
676 + // package's health test suite. This runs on all requests (not just admin), because
677 + // the connection/test REST endpoint can be called outside admin context.
678 + add_action( 'jetpack_connection_tests_loaded', array( $this, 'register_jetpack_connection_tests' ) );
679 +
737 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
738 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
739 682
740 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -767,25 +710,8 @@
767 710
768 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
769 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
770 713
771 - /*
772 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
773 - * We should make sure to only do this for front end links.
774 - */
775 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
776 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
777 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
778 -
779 - /*
780 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
781 - * so they point moderation links on emails to Calypso.
782 - */
783 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
784 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
785 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
786 - }
787 -
788 714 add_action(
789 715 'plugins_loaded',
790 716 function () {
791 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -796,18 +722,10 @@
796 722
797 723 // Update the site's Jetpack plan and products from API on heartbeats.
798 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
799 725
800 - /**
801 - * This is the hack to concatenate all css files into one.
802 - * For description and reasoning see the implode_frontend_css method.
803 - *
804 - * Super late priority so we catch all the registered styles.
805 - */
806 - if ( ! is_admin() ) {
807 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
808 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
809 - }
726 + // The Connection package fetches the site record for `jetpack/v4/site`; reuse it to refresh the plan.
727 + add_action( 'jetpack_site_data_fetched', array( Jetpack_Plan::class, 'update_from_site_record' ) );
810 728
811 729 // Actually push the stats on shutdown.
812 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
813 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -815,8 +733,10 @@
815 733
816 734 // After a successful connection.
817 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
818 736 add_action( 'jetpack_site_registered', array( $this, 'handle_unique_registrations_stats' ) );
737 + add_action( 'jetpack_site_registered', array( Reader_Link::class, 'activate_on_connection' ), 9 );
738 + add_action( 'jetpack_site_registered', array( \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::class, 'discard_credentials' ) );
819 739
820 740 // Actions for Manager::authorize().
821 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
822 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -856,18 +776,184 @@
856 776
857 777 // Register product descriptions for partner coupon usage.
858 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
859 779
860 - // Actions for conditional recommendations.
861 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
862 -
863 780 // Add 5-star
864 781 add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
782 + add_action( 'init', array( Deprecate::class, 'instance' ) );
865 783
866 - Deprecate::instance();
784 + // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
785 + \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
786 +
787 + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the
788 + // Jetpack plugin for now: the Connection package no longer auto-wires these, so
789 + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet.
790 + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init();
867 791 }
868 792
869 793 /**
794 + * Whether the current request should eagerly initialize the admin/REST-only
795 + * packages (the Import package and My Jetpack) now, at `plugins_loaded` time.
796 + *
797 + * Returns true for admin, cron, POST, and WP-CLI requests — the contexts,
798 + * knowable this early, where those packages have work to do. Returns false
799 + * for a plain front-end GET *and* for a REST request: the two can't be told
800 + * apart yet (this runs before `rest_api_init`), so callers defer the REST
801 + * case by initializing the package on `rest_api_init` instead, while a plain
802 + * page view never fires that hook and so loads nothing. This keeps
803 + * admin/REST-only PHP out of opcache on the front-end GET hot path.
804 + *
805 + * No in-repo code depends on the deferral. The one externally observable
806 + * change is timing: the packages' documented init hooks
807 + * (`jetpack_import_initialized`, `jetpack_feature_import_enabled`, and
808 + * `my_jetpack_init`) no longer fire on a plain front-end GET — they fire on
809 + * the admin, cron, POST, WP-CLI, and REST requests where the packages load.
810 + *
811 + * @return bool
812 + */
813 + private static function should_eager_load_packages() {
814 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) );
815 + $is_wp_cli = Constants::is_true( 'WP_CLI' );
816 +
817 + return is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli;
818 + }
819 +
820 + /**
821 + * Configure the Import package from a deferred hook.
822 + *
823 + * The eager path uses Config::ensure( 'import' ), but the deferred REST path
824 + * runs after Config::on_plugins_loaded() has already processed its feature
825 + * flags, so it needs a hookable bootstrap callback. Preserve Config's
826 + * feature-enabled action for hook consumers.
827 + *
828 + * @since 16.0
829 + *
830 + * @return void
831 + */
832 + public static function configure_import_package() {
833 + if ( class_exists( Import_Main::class ) ) {
834 + Import_Main::configure();
835 +
836 + if ( ! did_action( 'jetpack_feature_import_enabled' ) ) {
837 + do_action( 'jetpack_feature_import_enabled' );
838 + }
839 + }
840 + }
841 +
842 + /**
843 + * Enable the bundled Backup dashboard.
844 + *
845 + * The standalone plugin initializes the package first, so with both active this is a no-op.
846 + *
847 + * @return void
848 + */
849 + public static function configure_backup_package() {
850 + // Not offered on multisite, which the Backup package does not support, or without a
851 + // connected owner, since buying and managing backups needs a linked account.
852 + if ( is_multisite() || ! self::is_connection_ready() || ! self::connection()->has_connected_owner() ) {
853 + return;
854 + }
855 +
856 + if ( ! self::is_module_active( 'backup' ) ) {
857 + return;
858 + }
859 +
860 + /**
861 + * Filters whether the Jetpack plugin offers its bundled Backup dashboard.
862 + *
863 + * Resolved at the earliest `plugins_loaded` priority, so hook it from a mu-plugin.
864 + *
865 + * @since 16.3
866 + *
867 + * @param bool $enabled Whether to initialize the bundled Backup dashboard. Default true.
868 + */
869 + if ( ! apply_filters( 'jetpack_backup_dashboard_enabled', true ) ) {
870 + return;
871 + }
872 +
873 + $backup = 'Automattic\\Jetpack\\Backup\\V0005\\Jetpack_Backup';
874 +
875 + // An older package would take over the connection (see Jetpack_Backup::DEFAULT_INIT_OPTIONS);
876 + // only the standalone plugin ships one that old, and it draws its own menu.
877 + if ( ! class_exists( $backup ) || ! defined( $backup . '::DEFAULT_INIT_OPTIONS' ) ) {
878 + return;
879 + }
880 +
881 + $backup::initialize( array( 'manage_connection' => false ) );
882 + }
883 +
884 + /**
885 + * Whether the bundled Stats v2 dashboard is enabled.
886 + *
887 + * Stats v2 (formerly "Premium Analytics") ships with the plugin behind this
888 + * flag while it rolls out (WOOA7S-1595). When enabled it adds its own admin
889 + * menu alongside the existing Stats UI; it never replaces or hides the
890 + * legacy Stats menu, admin-bar entries, post-list column, or WP dashboard
891 + * widget. The Stats module's tracking is unaffected either way, and Stats v2
892 + * reads what that module collects, so while the module is off the plugin
893 + * answers false here before even reading the flag.
894 + *
895 + * The package has to be loadable for this to be true, so a site with the
896 + * flag on but a missing package answers false here and never adds the
897 + * Stats v2 menu (a warning is logged instead).
898 + *
899 + * @since 16.1
900 + *
901 + * @return bool
902 + */
903 + public static function is_premium_analytics_enabled() {
904 + if ( null !== self::$premium_analytics_enabled ) {
905 + return self::$premium_analytics_enabled;
906 + }
907 +
908 + if ( ! self::is_module_active( 'stats' ) ) {
909 + self::$premium_analytics_enabled = false;
910 + return false;
911 + }
912 +
913 + /**
914 + * Filters whether the bundled Premium Analytics dashboard is enabled.
915 + *
916 + * Resolved once, from `Jetpack::configure()` on `plugins_loaded`, and only
917 + * while the Stats module is active. Register this from a mu-plugin or a
918 + * plugin's main file — a callback added on `plugins_loaded` or later runs
919 + * too late to be seen.
920 + *
921 + * @since 16.1
922 + *
923 + * @param bool $enabled Defaults to the `jetpack_premium_analytics_enabled` option (false).
924 + */
925 + $flag = (bool) apply_filters( 'jetpack_premium_analytics_enabled', (bool) get_option( 'jetpack_premium_analytics_enabled' ) );
926 +
927 + self::$premium_analytics_enabled = $flag && class_exists( 'Automattic\Jetpack\PremiumAnalytics\Analytics' );
928 +
929 + if ( $flag && ! self::$premium_analytics_enabled ) {
930 + wp_trigger_error(
931 + __METHOD__,
932 + 'The jetpack_premium_analytics_enabled flag is on but the Premium Analytics package is not loadable; keeping the Stats UI in place.'
933 + );
934 + }
935 +
936 + return self::$premium_analytics_enabled;
937 + }
938 +
939 + /**
940 + * Expose the setting that turns the Premium Analytics dashboard on and off.
941 + *
942 + * Deliberately not behind is_premium_analytics_enabled(): this is the setting that flips that
943 + * check, so it has to answer while the dashboard is still off.
944 + *
945 + * @since 16.2
946 + *
947 + * @return void
948 + */
949 + public static function register_premium_analytics_enablement_setting() {
950 + if ( class_exists( 'Automattic\Jetpack\PremiumAnalytics\Enablement_Setting' ) ) {
951 + \Automattic\Jetpack\PremiumAnalytics\Enablement_Setting::register();
952 + }
953 + }
954 +
955 + /**
870 956 * Before everything else starts getting initalized, we need to initialize Jetpack using the
871 957 * Config object.
872 958 */
873 959 public function configure() {
@@ -876,13 +962,10 @@
876 962 foreach (
877 963 array(
878 964 'jitm',
879 965 'sync',
966 + 'account_protection',
880 967 'waf',
881 - 'videopress',
882 - 'stats',
883 - 'stats_admin',
884 - 'import',
885 968 )
886 969 as $feature
887 970 ) {
888 971 $config->ensure( $feature );
@@ -887,8 +970,110 @@
887 970 ) {
888 971 $config->ensure( $feature );
889 972 }
890 973
974 + // Enable the VideoPress admin UI (the "Jetpack > VideoPress" dashboard) inside the
975 + // Jetpack plugin, mirroring the standalone Jetpack VideoPress plugin. The dashboard
976 + // only renders when the VideoPress module is active (Status::is_active()); when it
977 + // is not, the menu item links to the My Jetpack interstitial to activate it.
978 + $config->ensure( 'videopress', array( 'admin_ui' => true ) );
979 +
980 + // The Backup dashboard is only an admin menu and REST routes, so it is deferred like Import below.
981 + if ( self::should_eager_load_packages() ) {
982 + self::configure_backup_package();
983 + } else {
984 + add_action( 'rest_api_init', array( __CLASS__, 'configure_backup_package' ), 0 );
985 + }
986 +
987 + /*
988 + * The Import package only registers `jetpack/v4/import` REST routes — it
989 + * does nothing when rendering a front-end page — so gate its `ensure()`
990 + * to keep its PHP out of opcache on the front-end GET hot path. It still
991 + * loads on admin, cron, POST, and WP-CLI requests, and on `rest_api_init`
992 + * for REST: a REST request can't be identified yet at `plugins_loaded`
993 + * (this runs before `Config::on_plugins_loaded`, and `rest_api_init`
994 + * fires later), so it is initialized directly when that hook fires, while
995 + * a plain page view never fires it and so loads nothing.
996 + *
997 + * JITM stays eager (above): unlike Import, its `register()` adds a
998 + * `jetpack_sync_before_send_updated_option` filter that records the
999 + * `jetpack_last_plugin_sync` transient, and a Jetpack Sync send can fire
1000 + * on a plain front-end GET — including the dedicated-sync `spawn-sync`
1001 + * GET, which runs on `init` and exits before `rest_api_init`. Deferring
1002 + * JITM would skip that bookkeeping and leave its message cache stale after
1003 + * a plugin change, so it loads on every request as before.
1004 + */
1005 + if ( self::should_eager_load_packages() ) {
1006 + $config->ensure( 'import' );
1007 + } else {
1008 + add_action(
1009 + 'rest_api_init',
1010 + array( __CLASS__, 'configure_import_package' ),
1011 + 0
1012 + );
1013 + }
1014 +
1015 + /*
1016 + * The Stats and Stats Admin packages only do work when the Stats module
1017 + * is active (the front-end tracking pixel) or on wp-admin, REST, cron,
1018 + * POST, and WP-CLI requests: the Stats dashboard page, the stats /
1019 + * stats-app REST endpoints (which the block editor also calls for
1020 + * email-open rates), the transient-cleanup cron, the connection
1021 + * package's package-version tracker (which runs on POSTs and reads the
1022 + * `jetpack_package_versions` filter that Stats registers), and CLI
1023 + * introspection such as the heartbeat inspector. On a plain front-end
1024 + * GET page view with the module off they are inert: the pixel
1025 + * short-circuits on `Stats\Main::should_track()` and every other entry
1026 + * point only hooks rest_api_init, admin, cron, the POST-only tracker, or
1027 + * is reached through WP-CLI.
1028 + * Skip loading them — and eagerly constructing the Stats Admin REST
1029 + * controller — on that hot path to keep their PHP out of opcache, but
1030 + * keep loading them everywhere else exactly as before so the stats REST
1031 + * permission mapping (view_stats, registered by Stats\Main), the
1032 + * editor's stats-app calls, the cleanup cron, and the package-version
1033 + * tracker are all unchanged.
1034 + *
1035 + * REST requests are not yet identifiable here (REST_REQUEST is defined
1036 + * after plugins_loaded), so defer those to rest_api_init. Call the
1037 + * package initializers directly rather than `$config->ensure()`: ensure()
1038 + * only flags a feature for `Config::on_plugins_loaded()` (plugins_loaded
1039 + * priority 2), which has already run by the time rest_api_init fires.
1040 + * Priority 0 runs before each package's own priority-10 route
1041 + * registration, so their routes still register within the same dispatch.
1042 + * A plain page view never fires rest_api_init, so the packages stay
1043 + * unloaded there. See JETPACK-1747.
1044 + */
1045 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'];
1046 + $is_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
1047 +
1048 + if ( self::is_module_active( 'stats' ) || is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli ) {
1049 + $config->ensure( 'stats' );
1050 + $config->ensure( 'stats_admin' );
1051 + } else {
1052 + add_action(
1053 + 'rest_api_init',
1054 + static function () {
1055 + if ( class_exists( 'Automattic\Jetpack\Stats\Main' ) ) {
1056 + \Automattic\Jetpack\Stats\Main::init();
1057 + }
1058 + if ( class_exists( 'Automattic\Jetpack\Stats_Admin\Main' ) ) {
1059 + \Automattic\Jetpack\Stats_Admin\Main::init();
1060 + }
1061 + },
1062 + 0
1063 + );
1064 + }
1065 +
1066 + // Stats v2 (WOOA7S-1595). Unlike Stats above it cannot be deferred when enabled — see
1067 + // Analytics::init() for why, and for why it takes no menu_title here.
1068 + if ( self::is_premium_analytics_enabled() ) {
1069 + \Automattic\Jetpack\PremiumAnalytics\Analytics::init();
1070 + }
1071 +
1072 + // Outside the check above on purpose — see Enablement_Setting. Deferred like Stats, to keep
1073 + // the autoload off the front-end hot path.
1074 + add_action( 'rest_api_init', array( __CLASS__, 'register_premium_analytics_enablement_setting' ), 0 );
1075 +
891 1076 $config->ensure(
892 1077 'connection',
893 1078 array(
894 1079 'slug' => 'jetpack',
@@ -906,12 +1091,8 @@
906 1091 );
907 1092
908 1093 $config->ensure( 'search' );
909 1094
910 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
911 - $config->ensure( 'wordads' );
912 - }
913 -
914 1095 if ( ! $this->connection_manager ) {
915 1096 $this->connection_manager = new Connection_Manager( 'jetpack' );
916 1097 }
917 1098
@@ -919,8 +1100,10 @@
919 1100 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
920 1101 $config->ensure( 'publicize' );
921 1102 }
922 1103
1104 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1105 +
923 1106 /*
924 1107 * Load things that should only be in Network Admin.
925 1108 *
926 1109 * For now blow away everything else until a more full
@@ -935,8 +1118,9 @@
935 1118 $is_connection_ready = self::is_connection_ready();
936 1119
937 1120 if ( $is_connection_ready ) {
938 1121 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1122 + $this->run_initialize_tracking_action();
939 1123
940 1124 Jetpack_Heartbeat::init();
941 1125 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
942 1126 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -941,8 +1125,19 @@
941 1125 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
942 1126 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
943 1127 Jetpack_Search_Performance_Logger::init();
944 1128 }
1129 + } else {
1130 + add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
1131 + add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
1132 + add_filter(
1133 + 'xmlrpc_methods',
1134 + function ( $methods ) {
1135 + $this->run_initialize_tracking_action();
1136 + return $methods;
1137 + },
1138 + 1
1139 + );
945 1140 }
946 1141
947 1142 // Initialize remote file upload request handlers.
948 1143 $this->add_remote_request_handlers();
@@ -952,20 +1147,10 @@
952 1147 */
953 1148 if ( $is_connection_ready ) {
954 1149 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
955 1150 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
956 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
957 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1151 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
958 1152 }
959 -
960 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
961 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
962 - } else {
963 - /**
964 - * Initialize tracking right after the user agrees to the terms of service.
965 - */
966 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
967 - }
968 1153 }
969 1154
970 1155 /**
971 1156 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -973,15 +1158,58 @@
973 1158 *
974 1159 * @action plugins_loaded
975 1160 */
976 1161 public function late_initialization() {
977 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1162 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
978 1163
979 - My_Jetpack_Initializer::init();
1164 + /*
1165 + * My Jetpack is a wp-admin dashboard. Its Initializer::init() only wires
1166 + * up admin-menu, admin_init, and rest_api_init surfaces — and eagerly
1167 + * loads every product class (backup, boost, protect, …) just to register
1168 + * admin plugin-action links — so none of it is needed on a plain
1169 + * front-end GET page view. (The pieces that do immediate work, e.g.
1170 + * Connection REST authentication and Licensing, are already initialized
1171 + * unconditionally in Jetpack's constructor, so they are unaffected here.)
1172 + *
1173 + * Gate the call to the request types where My Jetpack actually does work.
1174 + * REST can't be detected yet at plugins_loaded, so initialize on
1175 + * rest_api_init for that branch; a plain page view never fires it, so My
1176 + * Jetpack stays unloaded there.
1177 + */
1178 + if ( self::should_eager_load_packages() ) {
1179 + My_Jetpack_Initializer::init();
1180 + } else {
1181 + add_action( 'rest_api_init', array( My_Jetpack_Initializer::class, 'init' ), 0 );
1182 + }
980 1183
981 - // Initialize Boost Speed Score
982 - new Speed_Score( array(), 'jetpack-dashboard' );
1184 + Scan_Page_Init::initialize();
1185 + Jetpack_SEO_Initializer::init();
983 1186
1187 + if ( ( new Modules() )->is_active( 'podcast' ) ) {
1188 + Podcast::init();
1189 + }
1190 +
1191 + /*
1192 + * Initialize Boost Speed Score. It only does work on REST requests (the
1193 + * dashboard speed-score endpoints) and on a few Jetpack Boost lifecycle
1194 + * actions, so defer constructing it — and loading the boost-speed-score
1195 + * package classes — until one of those hooks actually fires instead of on
1196 + * every request. Priority 0 ensures the object's own callbacks (added in
1197 + * its constructor at the default priority) still run for the firing hook.
1198 + */
1199 + $initialize_speed_score = static function () {
1200 + static $initialized = false;
1201 + if ( $initialized ) {
1202 + return;
1203 + }
1204 + $initialized = true;
1205 + new Speed_Score( array(), 'jetpack-dashboard' );
1206 + };
1207 + add_action( 'rest_api_init', $initialize_speed_score, 0 );
1208 + add_action( 'jetpack_boost_deactivate', $initialize_speed_score, 0 );
1209 + add_action( 'jetpack_boost_environment_changed', $initialize_speed_score, 0 );
1210 + add_action( 'handle_environment_change', $initialize_speed_score, 0 );
1211 +
984 1212 /**
985 1213 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
986 1214 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
987 1215 *
@@ -1019,8 +1247,10 @@
1019 1247
1020 1248 /**
1021 1249 * Redirect edit post links to Calypso.
1022 1250 *
1251 + * @deprecated since 13.9
1252 + *
1023 1253 * @param string $default_url Post edit URL.
1024 1254 * @param int $post_id Post ID.
1025 1255 *
1026 1256 * @return string
@@ -1025,8 +1255,10 @@
1025 1255 *
1026 1256 * @return string
1027 1257 */
1028 1258 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1259 + _deprecated_function( __METHOD__, '13.9' );
1260 +
1029 1261 $post = get_post( $post_id );
1030 1262
1031 1263 if ( empty( $post ) ) {
1032 1264 return $default_url;
@@ -1057,13 +1289,17 @@
1057 1289
1058 1290 /**
1059 1291 * Redirect edit comment links to Calypso.
1060 1292 *
1293 + * @deprecated since 13.9
1294 + *
1061 1295 * @param string $url Comment edit URL.
1062 1296 *
1063 1297 * @return string
1064 1298 */
1065 1299 public function point_edit_comment_links_to_calypso( $url ) {
1300 + _deprecated_function( __METHOD__, '13.9' );
1301 +
1066 1302 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1067 1303 $query_args = null;
1068 1304 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1069 1305
@@ -1087,8 +1323,9 @@
1087 1323 $jetpack_callables = array(
1088 1324 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1089 1325 'updates' => array( 'Jetpack', 'get_updates' ),
1090 1326 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1327 + 'theme_styles' => array( 'Automattic\\Jetpack\\Plugin\\Theme_Styles_Sync', 'get_theme_styles' ),
1091 1328 );
1092 1329 return array_merge( $callables, $jetpack_callables );
1093 1330 }
1094 1331
@@ -1131,16 +1368,8 @@
1131 1368 * @param string $cap Capability name.
1132 1369 */
1133 1370 public function jetpack_custom_caps( $caps, $cap ) {
1134 1371 switch ( $cap ) {
1135 - case 'jetpack_manage_modules':
1136 - case 'jetpack_activate_modules':
1137 - case 'jetpack_deactivate_modules':
1138 - $caps = array( 'manage_options' );
1139 - break;
1140 - case 'jetpack_configure_modules':
1141 - $caps = array( 'manage_options' );
1142 - break;
1143 1372 case 'jetpack_manage_autoupdates':
1144 1373 $caps = array(
1145 1374 'manage_options',
1146 1375 'update_plugins',
@@ -1158,9 +1387,9 @@
1158 1387 if ( $is_offline_mode ) {
1159 1388 $caps = array( 'manage_options' );
1160 1389 break;
1161 1390 } else {
1162 - $caps = array( 'read' );
1391 + $caps = array( 'edit_posts' );
1163 1392 }
1164 1393 break;
1165 1394 }
1166 1395 return $caps;
@@ -1603,29 +1832,8 @@
1603 1832 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1604 1833 }
1605 1834
1606 1835 /**
1607 - * Whether the site is currently onboarding or not.
1608 - * A site is considered as being onboarded if it currently has an onboarding token.
1609 - *
1610 - * @since 5.8
1611 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1612 - *
1613 - * @access public
1614 - * @static
1615 - *
1616 - * @return bool True if the site is currently onboarding, false otherwise
1617 - */
1618 - public static function is_onboarding() {
1619 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1620 -
1621 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1622 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1623 - }
1624 - return ( new Status() )->is_onboarding();
1625 - }
1626 -
1627 - /**
1628 1836 * Determines reason for Jetpack offline mode.
1629 1837 */
1630 1838 public static function development_mode_trigger_text() {
1631 1839 $status = new Status();
@@ -1639,11 +1847,10 @@
1639 1847 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1640 1848 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1641 1849 } elseif ( $status->is_local_site() ) {
1642 1850 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1643 - /** This filter is documented in packages/status/src/class-status.php */
1644 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1645 - $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1851 + } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1852 + $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1646 1853 } else {
1647 1854 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1648 1855 }
1649 1856
@@ -1748,18 +1955,11 @@
1748 1955 */
1749 1956 public static function load_modules() {
1750 1957 $status = new Status();
1751 1958
1752 - if ( method_exists( $status, 'is_onboarding' ) ) {
1753 - $is_onboarding = $status->is_onboarding();
1754 - } else {
1755 - $is_onboarding = self::is_onboarding();
1756 - }
1757 -
1758 1959 if (
1759 1960 ! self::is_connection_ready()
1760 1961 && ! $status->is_offline_mode()
1761 - && ! $is_onboarding
1762 1962 && (
1763 1963 ! is_multisite()
1764 1964 || ! get_site_option( 'jetpack_protect_active' )
1765 1965 )
@@ -1880,22 +2080,14 @@
1880 2080 *
1881 2081 * @todo Store the result in core's object cache maybe?
1882 2082 */
1883 2083 public static function get_active_plugins() {
1884 - $active_plugins = (array) get_option( 'active_plugins', array() );
1885 -
1886 - if ( is_multisite() ) {
1887 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1888 - // whereas active_plugins stores them in the values.
1889 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1890 - if ( $network_plugins ) {
1891 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1892 - }
2084 + // Older Connection copies can load first and lack this method.
2085 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2086 + return array();
1893 2087 }
1894 2088
1895 - sort( $active_plugins );
1896 -
1897 - return array_unique( $active_plugins );
2089 + return Heartbeat::get_active_plugins();
1898 2090 }
1899 2091
1900 2092 /**
1901 2093 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2033,8 +2225,10 @@
2033 2225 *
2034 2226 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2035 2227 */
2036 2228 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2229 + // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
2230 + // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
2037 2231 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2038 2232 }
2039 2233 }
2040 2234
@@ -2137,9 +2331,9 @@
2137 2331 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2138 2332 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2139 2333 }
2140 2334 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2141 - exit;
2335 + exit( 0 );
2142 2336 }
2143 2337 }
2144 2338
2145 2339 /**
@@ -2186,8 +2380,12 @@
2186 2380 default:
2187 2381 break;
2188 2382 }
2189 2383 }
2384 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2385 + Feature_Policy::ensure_hooks();
2386 + }
2387 +
2190 2388 /**
2191 2389 * Filters the array of default modules.
2192 2390 *
2193 2391 * @since 2.5.0
@@ -2280,8 +2478,17 @@
2280 2478 }
2281 2479 }
2282 2480 }
2283 2481
2482 + // Special case to convert block setting to a block module.
2483 + $block_key = array_search( 'blocks', $modules, true );
2484 + if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2485 + $block_option = get_option( 'jetpack_blocks_disabled', null );
2486 + if ( $block_option ) {
2487 + unset( $modules[ $block_key ] );
2488 + }
2489 + }
2490 +
2284 2491 return $modules;
2285 2492 }
2286 2493
2287 2494 /**
@@ -2338,23 +2545,30 @@
2338 2545
2339 2546 /**
2340 2547 * Return module name translation. Uses matching string created in modules/module-headings.php.
2341 2548 *
2549 + * The module list is globbed from `modules/` at runtime, so a module can be listed with no
2550 + * entry in that generated file. Fall back to the untranslated header rather than overwriting
2551 + * it with the null `jetpack_get_module_i18n()` returns for an unknown slug.
2552 + *
2342 2553 * @since 3.9.2
2343 2554 *
2344 2555 * @param array $modules Array of Jetpack modules.
2345 2556 *
2346 - * @return string|void
2557 + * @return array
2347 2558 */
2348 2559 public static function get_translated_modules( $modules ) {
2349 2560 foreach ( $modules as $index => $module ) {
2350 2561 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2351 - if ( isset( $module['name'] ) ) {
2352 - $modules[ $index ]['name'] = $i18n_module['name'];
2562 + $name = $i18n_module['name'] ?? null;
2563 + $description = $i18n_module['description'] ?? null;
2564 +
2565 + if ( null !== $name && isset( $module['name'] ) ) {
2566 + $modules[ $index ]['name'] = $name;
2353 2567 }
2354 - if ( isset( $module['description'] ) ) {
2355 - $modules[ $index ]['description'] = $i18n_module['description'];
2356 - $modules[ $index ]['short_description'] = $i18n_module['description'];
2568 + if ( null !== $description && isset( $module['description'] ) ) {
2569 + $modules[ $index ]['description'] = $description;
2570 + $modules[ $index ]['short_description'] = $description;
2357 2571 }
2358 2572 if ( isset( $module['module_tags'] ) ) {
2359 2573 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2360 2574 }
@@ -2519,9 +2733,9 @@
2519 2733 ),
2520 2734 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2521 2735 );
2522 2736 wp_safe_redirect( $url );
2523 - exit;
2737 + exit( 0 );
2524 2738 }
2525 2739 }
2526 2740
2527 2741 /**
@@ -2667,9 +2881,9 @@
2667 2881 * @return string $url module configuration URL.
2668 2882 */
2669 2883 public static function module_configuration_url( $module ) {
2670 2884 $module = self::get_module_slug( $module );
2671 - $default_url = self::admin_url() . "#/settings?term=$module";
2885 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2672 2886 /**
2673 2887 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2674 2888 *
2675 2889 * @since 6.9.0
@@ -2727,9 +2941,9 @@
2727 2941 if ( $update ) {
2728 2942 update_option( 'active_plugins', array_filter( $plugins ) );
2729 2943 }
2730 2944 }
2731 - exit;
2945 + exit( 0 );
2732 2946 }
2733 2947
2734 2948 /**
2735 2949 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2754,12 +2968,18 @@
2754 2968 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2755 2969
2756 2970 Health::on_jetpack_activated();
2757 2971
2972 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2973 +
2758 2974 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2759 2975 Sync_Actions::do_only_first_initial_sync();
2760 2976 }
2761 2977
2978 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2979 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2980 + }
2981 +
2762 2982 self::plugin_initialize();
2763 2983 }
2764 2984
2765 2985 /**
@@ -2794,9 +3014,9 @@
2794 3014
2795 3015 if ( $plugins_path === $referer['path'] ) {
2796 3016 $source_type = 'list';
2797 3017 } elseif ( $plugins_install_path === $referer['path'] ) {
2798 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3018 + $tab = $query_parts['tab'] ?? 'featured';
2799 3019 switch ( $tab ) {
2800 3020 case 'popular':
2801 3021 $source_type = 'popular';
2802 3022 break;
@@ -2806,10 +3026,10 @@
2806 3026 case 'favorites':
2807 3027 $source_type = 'favorites';
2808 3028 break;
2809 3029 case 'search':
2810 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2811 - $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3030 + $source_type = 'search-' . ( $query_parts['type'] ?? 'term' );
3031 + $source_query = $query_parts['s'] ?? null;
2812 3032 break;
2813 3033 default:
2814 3034 $source_type = 'featured';
2815 3035 }
@@ -2818,29 +3038,171 @@
2818 3038 return array( $source_type, $source_query );
2819 3039 }
2820 3040
2821 3041 /**
2822 - * Runs before bumping version numbers up to a new version
3042 + * Runs before bumping version numbers up to a new version.
2823 3043 *
2824 - * @param string $version Version:timestamp.
3044 + * Only ever registered the hooks for the release post update modal, which has been removed.
3045 + * No longer hooked to `updating_jetpack_version`.
3046 + *
3047 + * @deprecated 16.2
3048 + *
3049 + * @param string $version Version:timestamp.
2825 3050 * @param string $old_version Old Version:timestamp or false if not set yet.
2826 3051 */
2827 - public static function do_version_bump( $version, $old_version ) {
2828 - if ( $old_version ) { // For existing Jetpack installations.
2829 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3052 + public static function do_version_bump( $version, $old_version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature preserved for the deprecation shim.
3053 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
3054 + }
2830 3055
2831 - // If a front end page is visited after the update, the 'wp' action will fire.
2832 - add_action( 'wp', 'Jetpack::set_update_modal_display' );
3056 + /**
3057 + * Enables the Newsletter (subscriptions) module for existing sites now that it is a default-on module.
3058 + *
3059 + * Fresh installs receive the module via its "Auto Activate: Yes" header, so this only handles sites
3060 + * upgrading from a version where the module defaulted off. It runs once per site (guarded by the
3061 + * subscriptions_default_on_migrated option). Fresh installs are marked as migrated immediately so the
3062 + * migration never runs for them. After it has run, the user's choice to deactivate the module again
3063 + * (for example from the My Jetpack Products page) is respected and never reverted.
3064 + *
3065 + * The module requires a connection, so on a disconnected site the migration is deferred without setting
3066 + * the guard, allowing a later version bump to retry once the site is connected.
3067 + *
3068 + * @param string $version New Jetpack version:timestamp.
3069 + * @param string|false $old_version Previous Jetpack version:timestamp, or false on a fresh install.
3070 + */
3071 + public static function activate_subscriptions_module_for_existing_sites( $version, $old_version ) {
3072 + if ( get_option( 'jetpack_subscriptions_default_on_migrated' ) ) {
3073 + return;
3074 + }
2833 3075
2834 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2835 - add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3076 + // Fresh installs get the module via its "Auto Activate: Yes" header. Mark them as migrated so a
3077 + // later opt-out is never reverted by the existing-site path on a subsequent version bump.
3078 + if ( ! $old_version ) {
3079 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3080 + return;
2836 3081 }
3082 +
3083 + if ( ! self::is_connection_ready() ) {
3084 + return;
3085 + }
3086 +
3087 + // Mark as migrated only once the module is active, so a transient activation failure is retried on
3088 + // a later version bump rather than being silently skipped.
3089 + if ( self::is_module_active( 'subscriptions' ) || self::activate_module( 'subscriptions', false, false ) ) {
3090 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3091 + }
2837 3092 }
2838 3093
2839 3094 /**
3095 + * Option flag that records the AI master-switch opt-out reconciliation has run,
3096 + * so it never runs twice.
3097 + *
3098 + * @var string
3099 + */
3100 + const AI_MASTER_OPTOUT_MIGRATED_OPTION = 'jetpack_ai_master_optout_migrated';
3101 +
3102 + /**
3103 + * Register the on-upgrade init hooks whose relative ORDER matters, extracted so
3104 + * the ordering can be asserted in tests without invoking plugin_upgrade() (whose
3105 + * guards make it unreliable to trigger under test). activate_new_modules()
3106 + * (init, default priority 10) auto-activates "Auto Activate: Yes" modules
3107 + * including the `ai` master; reconcile_ai_master_optout() must run at a LATER
3108 + * priority to honor an explicit AI opt-out.
3109 + *
3110 + * @return void
3111 + */
3112 + public static function register_upgrade_init_hooks() {
3113 + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
3114 + add_action( 'init', array( __CLASS__, 'reconcile_ai_master_optout' ), 20 );
3115 + }
3116 +
3117 + /**
3118 + * Preserves an explicit Jetpack AI opt-out when the `ai` module becomes the site-wide
3119 + * master switch off WordPress.com Simple (self-hosted and Atomic).
3120 + *
3121 + * The `ai` module is "Auto Activate: Yes", so on upgrade {@see self::activate_new_modules()}
3122 + * turns it on for connected sites — the desired default-on / auto-enable-on-connection
3123 + * behavior, which this method deliberately leaves alone. The one case it corrects is a site
3124 + * that had explicitly disabled Jetpack AI (the `jetpack_ai_enabled` option present and falsey)
3125 + * before the module shipped: that opt-out must survive the module becoming the master, so the
3126 + * module is deactivated for exactly those sites. An absent or truthy option is left untouched.
3127 + *
3128 + * Ordering is the whole point. `activate_new_modules()` is hooked on `init` at priority 10 and
3129 + * auto-activates the module there; this method is hooked on `init` at priority 20 (see
3130 + * {@see self::plugin_upgrade()}), so it runs AFTER the auto-activation and its deactivation is
3131 + * the final state. A version-guarded block that ran inline during `plugins_loaded` would be
3132 + * undone by the later auto-activation, which is why this is a late-init hook rather than an
3133 + * inline upgrade step.
3134 + *
3135 + * WordPress.com Simple never runs modules — the option stays the master there — so this is a
3136 + * no-op on Simple. The {@see self::AI_MASTER_OPTOUT_MIGRATED_OPTION} flag makes it run exactly
3137 + * once, which matters because off-Simple the option is no longer the master after this runs:
3138 + * a stale falsey option must not keep re-deactivating a module the user later turns back on.
3139 + *
3140 + * @return void
3141 + */
3142 + public static function reconcile_ai_master_optout() {
3143 + if ( get_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION ) ) {
3144 + return;
3145 + }
3146 +
3147 + // Simple keeps the `jetpack_ai_enabled` option as the master; modules don't run there.
3148 + if ( ( new Host() )->is_wpcom_simple() ) {
3149 + return;
3150 + }
3151 +
3152 + // A sentinel default distinguishes an absent option (leave auto-activation alone) from one
3153 + // explicitly stored falsey (an opt-out to preserve).
3154 + $stored = get_option( 'jetpack_ai_enabled', 'not-set' );
3155 + if ( 'not-set' !== $stored && ! (bool) $stored ) {
3156 + ( new Modules() )->deactivate( 'ai' );
3157 + }
3158 +
3159 + update_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION, true );
3160 + }
3161 +
3162 + /**
3163 + * Deletes obsolete SEO module-state options without changing module activation.
3164 + *
3165 + * @since 16.3
3166 + */
3167 + public static function cleanup_seo_module_state_options() {
3168 + delete_option( 'jetpack_seo_sitemap_enabled' );
3169 + delete_option( 'jetpack_seo_canonical_urls_enabled' );
3170 + delete_option( 'jetpack_seo_module_state_reconciled' );
3171 + }
3172 +
3173 + /**
3174 + * Seeds the Jetpack SEO discoverability cohort once, so the new SEO surface is
3175 + * auto-discoverable on fresh installs but opt-in on existing ones (JETPACK-1700).
3176 + *
3177 + * Hooked on `updating_jetpack_version`, which fires on every install including the
3178 + * first — with `$old_version === false` on a brand-new site (the same signal
3179 + * {@see self::activate_subscriptions_module_for_existing_sites()} keys off). Fresh
3180 + * installs are seeded visible; existing installs are seeded hidden and opt in later
3181 + * via the legacy Traffic page or My Jetpack. `add_option()` makes this seed-once: it
3182 + * never overrides a value a later opt-in (or opt-out) has set. WordPress.com sites
3183 + * ignore this option entirely (always visible) — see
3184 + * {@see \Automattic\Jetpack\SEO\Initializer::is_seo_surface_visible()}.
3185 + *
3186 + * @param string $version The new Jetpack version (unused).
3187 + * @param string|false $old_version The previous version, or false on a fresh install.
3188 + */
3189 + public static function seed_seo_visibility_cohort( $version, $old_version ) {
3190 + add_option( Jetpack_SEO_Initializer::VISIBILITY_OPTION, ! $old_version );
3191 + }
3192 +
3193 + /**
2840 3194 * Sets the display_update_modal state.
3195 + *
3196 + * The release post update modal that read this state has been removed. The write is kept so the
3197 + * method still behaves as documented for the deprecation window. When this is deleted, also drop
3198 + * the matching `display_update_modal` guard in Automattic\Jetpack\CookieState::should_set_cookie(),
3199 + * which exists only to keep this key out of the cookie on the Jetpack admin screen.
3200 + *
3201 + * @deprecated 16.2
2841 3202 */
2842 3203 public static function set_update_modal_display() {
3204 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2843 3205 self::state( 'display_update_modal', true );
2844 3206 }
2845 3207
2846 3208 /**
@@ -2845,11 +3207,16 @@
2845 3207
2846 3208 /**
2847 3209 * Enqueues the block library styles.
2848 3210 *
3211 + * Only ever used by the release post update modal, which has been removed.
3212 + *
3213 + * @deprecated 16.2
3214 + *
2849 3215 * @param string $hook The current admin page.
2850 3216 */
2851 3217 public static function enqueue_block_style( $hook ) {
3218 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2852 3219 if ( 'toplevel_page_jetpack' === $hook ) {
2853 3220 wp_enqueue_style( 'wp-block-library' );
2854 3221 }
2855 3222 }
@@ -2938,8 +3305,12 @@
2938 3305 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2939 3306 self::disconnect();
2940 3307 Jetpack_Options::delete_option( 'version' );
2941 3308 }
3309 +
3310 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3311 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3312 + }
2942 3313 }
2943 3314
2944 3315 /**
2945 3316 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -2967,9 +3338,9 @@
2967 3338 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2968 3339 }
2969 3340
2970 3341 /**
2971 - * Happens after a successfull disconnection.
3342 + * Happens after a successful disconnection.
2972 3343 *
2973 3344 * @static
2974 3345 */
2975 3346 public static function jetpack_site_disconnected() {
@@ -2974,8 +3345,10 @@
2974 3345 */
2975 3346 public static function jetpack_site_disconnected() {
2976 3347 Identity_Crisis::clear_all_idc_options();
2977 3348
3349 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3350 +
2978 3351 // Delete all the sync related data. Since it could be taking up space.
2979 3352 Sender::get_instance()->uninstall();
2980 3353
2981 3354 /**
@@ -3039,10 +3412,17 @@
3039 3412 * @param mixed $code Error code to log.
3040 3413 * @param mixed $data Data to log.
3041 3414 */
3042 3415 public static function log( $code, $data = null ) {
3416 +
3417 + $raw_log = Jetpack_Options::get_option( 'log', array() );
3418 + // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
3419 + if ( ! is_array( $raw_log ) ) {
3420 + return;
3421 + }
3422 +
3043 3423 // only grab the latest 200 entries.
3044 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3424 + $log = array_slice( $raw_log, -199, 199 );
3045 3425
3046 3426 // Append our event to the log.
3047 3427 $log_entry = array(
3048 3428 'time' => time(),
@@ -3142,8 +3522,15 @@
3142 3522
3143 3523 /**
3144 3524 * Return stat data for WPCOM sync.
3145 3525 *
3526 + * The Sync stats module was this method's last caller and moved to the Connection package's
3527 + * `Heartbeat::generate_stats_array()`, which assembles the heartbeat data through the
3528 + * `jetpack_heartbeat_stats_array` filter. Note the package method does not include the extended
3529 + * data from `get_additional_stat_data()`, so callers relying on `$extended` need to add it themselves.
3530 + *
3531 + * @deprecated 16.2
3532 + *
3146 3533 * @param bool $encode JSON encode the result.
3147 3534 * @param bool $extended Adds additional stats data.
3148 3535 *
3149 3536 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3148,10 +3535,15 @@
3148 3535 *
3149 3536 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3150 3537 */
3151 3538 public static function get_stat_data( $encode = true, $extended = true ) {
3152 - $data = Jetpack_Heartbeat::generate_stats_array();
3539 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3153 3540
3541 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
3542 + ? Heartbeat::get_environment_stats()
3543 + : array();
3544 + $data = array_merge( Jetpack_Heartbeat::generate_stats_array(), $env_stats );
3545 +
3154 3546 if ( $extended ) {
3155 3547 $additional_data = self::get_additional_stat_data();
3156 3548 $data = array_merge( $data, $additional_data );
3157 3549 }
@@ -3156,9 +3548,9 @@
3156 3548 $data = array_merge( $data, $additional_data );
3157 3549 }
3158 3550
3159 3551 if ( $encode ) {
3160 - return wp_json_encode( $data );
3552 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3161 3553 }
3162 3554
3163 3555 return $data;
3164 3556 }
@@ -3237,8 +3629,9 @@
3237 3629 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3238 3630 // Upgrade: 1.1 -> 1.1.1
3239 3631 // Check and see if host can verify the Jetpack servers' SSL certificate.
3240 3632 $args = array();
3633 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3241 3634 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3242 3635 }
3243 3636
3244 3637 if (
@@ -3255,12 +3648,8 @@
3255 3648 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3256 3649 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3257 3650 }
3258 3651
3259 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3260 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3261 - }
3262 -
3263 3652 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3264 3653
3265 3654 if ( self::is_connection_ready() || $is_offline_mode ) {
3266 3655 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3299,8 +3688,9 @@
3299 3688 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3300 3689 * This function artificially throws errors for such cases (per a specific list).
3301 3690 *
3302 3691 * @param string $plugin The activated plugin.
3692 + * @throws RuntimeException If a conflicting plugin is detected.
3303 3693 */
3304 3694 public function throw_error_on_activate_plugin( $plugin ) {
3305 3695 $active_modules = self::get_active_modules();
3306 3696
@@ -3322,9 +3712,9 @@
3322 3712 }
3323 3713
3324 3714 if ( $throw ) {
3325 3715 /* translators: Plugin name to deactivate. */
3326 - trigger_error( sprintf( esc_html__( 'Jetpack contains the most recent version of the old “%1$s” plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
3716 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3327 3717 }
3328 3718 }
3329 3719 }
3330 3720
@@ -3435,18 +3825,17 @@
3435 3825 if ( ! is_int( $status_code ) ) {
3436 3826 $status_code = 400;
3437 3827 }
3438 3828
3439 - status_header( $status_code );
3440 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3829 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3441 3830 }
3442 3831
3443 - status_header( 200 );
3444 3832 if ( true === $response ) {
3445 - exit;
3833 + status_header( 200 );
3834 + exit( 0 );
3446 3835 }
3447 3836
3448 - die( wp_json_encode( (object) $response ) );
3837 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3449 3838 }
3450 3839
3451 3840 /**
3452 3841 * Uploads a file gotten from the global $_FILES.
@@ -3507,9 +3896,9 @@
3507 3896 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3508 3897 }
3509 3898
3510 3899 $uploaded_files = array();
3511 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3900 + $global_post = $GLOBALS['post'] ?? null;
3512 3901 unset( $GLOBALS['post'] );
3513 3902 if ( empty( $_FILES['media']['name'] ) ) {
3514 3903 // Nothing to process, just return.
3515 3904 return $uploaded_files;
@@ -3516,9 +3905,9 @@
3516 3905 }
3517 3906 foreach ( $_FILES['media']['name'] as $index => $name ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, unslash sniff is wrong. Validation should happen below.
3518 3907 $file = array();
3519 3908 foreach ( $media_keys as $media_key ) {
3520 - $file[ $media_key ] = isset( $_FILES['media'][ $media_key ][ $index ] ) ? $_FILES['media'][ $media_key ][ $index ] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3909 + $file[ $media_key ] = $_FILES['media'][ $media_key ][ $index ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3521 3910 }
3522 3911
3523 3912 list( $hmac_provided, $salt ) = isset( $_POST['_jetpack_file_hmac_media'][ $index ] ) ? explode( ':', filter_var( wp_unslash( $_POST['_jetpack_file_hmac_media'][ $index ] ) ) ) : array( 'no', '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce should have been checked by the caller.
3524 3913
@@ -3564,9 +3953,9 @@
3564 3953 'type' => (string) $edited_media_item->post_mime_type,
3565 3954 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3566 3955 );
3567 3956
3568 - return (array) array( $response );
3957 + return array( $response );
3569 3958 }
3570 3959
3571 3960 $attachment_id = media_handle_upload(
3572 3961 '.jetpack.upload.',
@@ -3605,58 +3994,8 @@
3605 3994 return $uploaded_files;
3606 3995 }
3607 3996
3608 3997 /**
3609 - * Add help to the Jetpack page
3610 - *
3611 - * @since Jetpack (1.2.3)
3612 - * @return void
3613 - */
3614 - public function admin_help() {
3615 - $current_screen = get_current_screen();
3616 -
3617 - // Overview.
3618 - $current_screen->add_help_tab(
3619 - array(
3620 - 'id' => 'home',
3621 - 'title' => __( 'Home', 'jetpack' ),
3622 - 'content' =>
3623 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3624 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3625 - '<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>',
3626 - )
3627 - );
3628 -
3629 - // Screen Content.
3630 - if ( current_user_can( 'manage_options' ) ) {
3631 - $current_screen->add_help_tab(
3632 - array(
3633 - 'id' => 'settings',
3634 - 'title' => __( 'Settings', 'jetpack' ),
3635 - 'content' =>
3636 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3637 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3638 - '<ol>' .
3639 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3640 - '<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>' .
3641 - '</ol>' .
3642 - '<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>',
3643 - )
3644 - );
3645 - }
3646 -
3647 - // Help Sidebar.
3648 - $support_url = Redirect::get_url( 'jetpack-support' );
3649 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3650 - $current_screen->set_help_sidebar(
3651 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3652 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3653 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3654 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3655 - );
3656 - }
3657 -
3658 - /**
3659 3998 * Add action links for the Jetpack plugin.
3660 3999 *
3661 4000 * @param array $actions Plugin actions.
3662 4001 *
@@ -3664,9 +4003,9 @@
3664 4003 */
3665 4004 public function plugin_action_links( $actions ) {
3666 4005 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3667 4006 return array_merge(
3668 - array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
4007 + array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack-settings#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3669 4008 $actions
3670 4009 );
3671 4010 }
3672 4011
@@ -3694,14 +4033,10 @@
3694 4033 'jetpack-plugins-page-js',
3695 4034 '_inc/build/plugins-page.js',
3696 4035 JETPACK__PLUGIN_FILE,
3697 4036 array(
3698 - 'in_footer' => true,
3699 - 'textdomain' => 'jetpack',
3700 - 'dependencies' => array(
3701 - 'wp-polyfill',
3702 - 'wp-components',
3703 - ),
4037 + 'in_footer' => true,
4038 + 'textdomain' => 'jetpack',
3704 4039 )
3705 4040 );
3706 4041 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3707 4042
@@ -3706,9 +4041,9 @@
3706 4041 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3707 4042
3708 4043 // Add objects to be passed to the initial state of the app.
3709 4044 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3710 - wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state() ) ) . '"));', 'before' );
4045 + wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=' . wp_json_encode( Jetpack_Redux_State_Helper::get_plugins_page_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
3711 4046
3712 4047 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3713 4048 }
3714 4049 }
@@ -3757,9 +4092,9 @@
3757 4092 // @todo provide way to go to specific calypso env.
3758 4093 self::get_calypso_host() . 'jetpack/connect'
3759 4094 )
3760 4095 );
3761 - exit;
4096 + exit( 0 );
3762 4097 }
3763 4098 }
3764 4099
3765 4100 /*
@@ -3794,8 +4129,28 @@
3794 4129 * Done!
3795 4130 */
3796 4131
3797 4132 /**
4133 + * Build the user-facing description stored alongside a registration error code.
4134 + *
4135 + * @since 16.2
4136 + *
4137 + * @param string $error_code The WP_Error code.
4138 + * @param string $message The WP_Error message.
4139 + * @return string The description, empty when the message is not user-facing copy.
4140 + */
4141 + public static function get_registration_error_description( $error_code, $message ) {
4142 + // Manager::validate_remote_register_response() does not always put user-facing copy in the
4143 + // message slot: wpcom_5??, wpcom_408 and wpcom_bad_response store the HTTP status there,
4144 + // and jetpack_id stores the raw response body, which can also overflow the state cookie.
4145 + if ( 'jetpack_id' === $error_code || is_numeric( $message ) ) {
4146 + return '';
4147 + }
4148 +
4149 + return mb_substr( (string) $message, 0, 250 );
4150 + }
4151 +
4152 + /**
3798 4153 * Handles the page load events for the Jetpack admin page
3799 4154 */
3800 4155 public function admin_page_load() {
3801 4156 $error = false;
@@ -3831,10 +4186,11 @@
3831 4186 $registered = static::connection()->try_registration();
3832 4187 if ( is_wp_error( $registered ) ) {
3833 4188 $error = $registered->get_error_code();
3834 4189 self::state( 'error', $error );
3835 - self::state( 'error', $registered->get_error_message() );
3836 4190
4191 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4192 +
3837 4193 /**
3838 4194 * Jetpack registration Error.
3839 4195 *
3840 4196 * @since 7.5.0
@@ -3858,12 +4214,8 @@
3858 4214 do_action( 'jetpack_connection_register_success', $from );
3859 4215
3860 4216 $url = $this->build_connect_url( true, $redirect, $from );
3861 4217
3862 - if ( ! empty( $_GET['onboarding'] ) ) {
3863 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3864 - }
3865 -
3866 4218 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3867 4219 $url = add_query_arg( 'auth_approved', 'true', $url );
3868 4220 }
3869 4221
@@ -3868,9 +4220,9 @@
3868 4220 }
3869 4221
3870 4222 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3871 4223 wp_safe_redirect( $url );
3872 - exit;
4224 + exit( 0 );
3873 4225 case 'activate':
3874 4226 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3875 4227 $error = 'cheatin';
3876 4228 break;
@@ -3884,9 +4236,9 @@
3884 4236 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3885 4237 }
3886 4238 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3887 4239 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3888 - exit;
4240 + exit( 0 );
3889 4241 case 'activate_default_modules':
3890 4242 check_admin_referer( 'activate_default_modules' );
3891 4243 self::log( 'activate_default_modules' );
3892 4244 self::restate();
@@ -3894,9 +4246,9 @@
3894 4246 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3895 4247 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3896 4248 self::activate_default_modules( $min_version, $max_version, $other_modules );
3897 4249 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3898 - exit;
4250 + exit( 0 );
3899 4251 case 'disconnect':
3900 4252 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3901 4253 $error = 'cheatin';
3902 4254 break;
@@ -3905,9 +4257,9 @@
3905 4257 check_admin_referer( 'jetpack-disconnect' );
3906 4258 self::log( 'disconnect' );
3907 4259 self::disconnect();
3908 4260 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3909 - exit;
4261 + exit( 0 );
3910 4262 case 'reconnect':
3911 4263 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3912 4264 $error = 'cheatin';
3913 4265 break;
@@ -3918,9 +4270,9 @@
3918 4270 self::disconnect();
3919 4271
3920 4272 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3921 4273 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3922 - exit;
4274 + exit( 0 );
3923 4275 case 'deactivate':
3924 4276 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3925 4277 $error = 'cheatin';
3926 4278 break;
@@ -3934,9 +4286,9 @@
3934 4286 self::state( 'message', 'module_deactivated' );
3935 4287 }
3936 4288 self::state( 'module', $modules );
3937 4289 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3938 - exit;
4290 + exit( 0 );
3939 4291 case 'unlink':
3940 4292 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3941 4293 check_admin_referer( 'jetpack-unlink' );
3942 4294 self::log( 'unlink' );
@@ -3946,32 +4298,9 @@
3946 4298 wp_safe_redirect( admin_url() );
3947 4299 } else {
3948 4300 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3949 4301 }
3950 - exit;
3951 - case 'onboard':
3952 - if ( ! current_user_can( 'manage_options' ) ) {
3953 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3954 - } else {
3955 - self::create_onboarding_token();
3956 - $url = $this->build_connect_url( true );
3957 -
3958 - $token = Jetpack_Options::get_option( 'onboarding' );
3959 -
3960 - if ( false !== ( $token ) ) {
3961 - $url = add_query_arg( 'onboarding', $token, $url );
3962 - }
3963 -
3964 - $calypso_env = ( new Host() )->get_calypso_env();
3965 - if ( ! empty( $calypso_env ) ) {
3966 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
3967 - }
3968 -
3969 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3970 - wp_safe_redirect( $url );
3971 - exit;
3972 - }
3973 - exit;
4302 + exit( 0 );
3974 4303 default:
3975 4304 /**
3976 4305 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3977 4306 *
@@ -4194,37 +4523,8 @@
4194 4523 endif;
4195 4524 }
4196 4525
4197 4526 /**
4198 - * We can't always respond to a signed XML-RPC request with a
4199 - * helpful error message. In some circumstances, doing so could
4200 - * leak information.
4201 - *
4202 - * Instead, track that the error occurred via a Jetpack_Option,
4203 - * and send that data back in the heartbeat.
4204 - * All this does is increment a number, but it's enough to find
4205 - * trends.
4206 - *
4207 - * @param WP_Error $xmlrpc_error The error produced during
4208 - * signature validation.
4209 - */
4210 - public function track_xmlrpc_error( $xmlrpc_error ) {
4211 - $code = is_wp_error( $xmlrpc_error )
4212 - ? $xmlrpc_error->get_error_code()
4213 - : 'should-not-happen';
4214 -
4215 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4216 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4217 - // No need to update the option if we already have
4218 - // this code stored.
4219 - return;
4220 - }
4221 - $xmlrpc_errors[ $code ] = true;
4222 -
4223 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4224 - }
4225 -
4226 - /**
4227 4527 * Initialize the jetpack stats instance only when needed
4228 4528 *
4229 4529 * @return void
4230 4530 */
@@ -4510,11 +4810,8 @@
4510 4810 *
4511 4811 * @param array $data The request data.
4512 4812 */
4513 4813 public static function authorize_ending_authorized( $data ) {
4514 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4515 - self::invalidate_onboarding_token();
4516 -
4517 4814 // If redirect_uri is SSO, ensure SSO module is enabled.
4518 4815 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4519 4816
4520 4817 /** This filter is documented in class.jetpack-cli.php */
@@ -4654,10 +4951,12 @@
4654 4951 $result = self::permit_ssl( true );
4655 4952 wp_send_json(
4656 4953 array(
4657 4954 'enabled' => $result,
4658 - 'message' => get_transient( 'jetpack_https_test_message' ),
4659 - )
4955 + 'message' => self::get_ssl_test_message(),
4956 + ),
4957 + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
4958 + JSON_UNESCAPED_SLASHES
4660 4959 );
4661 4960 }
4662 4961
4663 4962 /* Client API */
@@ -4664,8 +4963,10 @@
4664 4963
4665 4964 /**
4666 4965 * Verify the onboarding token.
4667 4966 *
4967 + * @deprecated since 13.9
4968 + *
4668 4969 * @param array $token_data Token data.
4669 4970 * @param string $token Token value.
4670 4971 * @param string $request_data JSON-encoded request data.
4671 4972 *
@@ -4671,8 +4972,9 @@
4671 4972 *
4672 4973 * @return mixed
4673 4974 */
4674 4975 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4976 + _deprecated_function( __METHOD__, '13.9' );
4675 4977 // Default to a blog token.
4676 4978 $token_type = 'blog';
4677 4979
4678 4980 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4700,9 +5002,9 @@
4700 5002 $jp_user = get_user_by( 'email', $jpo_user );
4701 5003 if ( is_a( $jp_user, 'WP_User' ) ) {
4702 5004 wp_set_current_user( $jp_user->ID );
4703 5005 $user_can = is_multisite()
4704 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5006 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4705 5007 : current_user_can( 'manage_options' );
4706 5008 if ( $user_can ) {
4707 5009 $token_type = 'user';
4708 5010 $token->external_user_id = $jp_user->ID;
@@ -4719,11 +5021,13 @@
4719 5021
4720 5022 /**
4721 5023 * Create a random secret for validating onboarding payload
4722 5024 *
5025 + * @deprecated since 13.9
4723 5026 * @return string Secret token
4724 5027 */
4725 5028 public static function create_onboarding_token() {
5029 + _deprecated_function( __METHOD__, '13.9' );
4726 5030 $token = Jetpack_Options::get_option( 'onboarding' );
4727 5031 if ( false === ( $token ) ) {
4728 5032 $token = wp_generate_password( 32, false );
4729 5033 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4734,11 +5038,13 @@
4734 5038
4735 5039 /**
4736 5040 * Remove the onboarding token
4737 5041 *
5042 + * @deprecated since 13.9
4738 5043 * @return bool True on success, false on failure
4739 5044 */
4740 5045 public static function invalidate_onboarding_token() {
5046 + _deprecated_function( __METHOD__, '13.9' );
4741 5047 return Jetpack_Options::delete_option( 'onboarding' );
4742 5048 }
4743 5049
4744 5050 /**
@@ -4743,8 +5049,10 @@
4743 5049
4744 5050 /**
4745 5051 * Validate an onboarding token for a specific action
4746 5052 *
5053 + * @deprecated since 13.9
5054 + *
4747 5055 * @param string $token Onboarding token.
4748 5056 * @param string $action Action name.
4749 5057 *
4750 5058 * @return boolean True if token/action pair is accepted, false if not
@@ -4749,8 +5057,9 @@
4749 5057 *
4750 5058 * @return boolean True if token/action pair is accepted, false if not
4751 5059 */
4752 5060 public static function validate_onboarding_token_action( $token, $action ) {
5061 + _deprecated_function( __METHOD__, '13.9' );
4753 5062 // Compare tokens, bail if tokens do not match.
4754 5063 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4755 5064 return false;
4756 5065 }
@@ -4776,39 +5085,41 @@
4776 5085 * @return boolean
4777 5086 * @since 2.3.3
4778 5087 */
4779 5088 public static function permit_ssl( $force_recheck = false ) {
4780 - // Do some fancy tests to see if ssl is being supported.
4781 - if ( ! $force_recheck ) {
4782 - $ssl = get_transient( 'jetpack_https_test' );
5089 + if ( ! method_exists( Heartbeat::class, 'permit_ssl' ) ) {
5090 + // Skip the SSL-fail notice when the check cannot run.
5091 + return true;
4783 5092 }
4784 5093
4785 - if ( $force_recheck || false === $ssl ) {
4786 - $message = '';
4787 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4788 - $ssl = 0;
4789 - } else {
4790 - $ssl = 1;
5094 + return Heartbeat::permit_ssl( $force_recheck );
5095 + }
4791 5096
4792 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4793 - $ssl = 0;
4794 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4795 - } else {
4796 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4797 - if ( is_wp_error( $response ) ) {
4798 - $ssl = 0;
4799 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4800 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4801 - $ssl = 0;
4802 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4803 - }
4804 - }
4805 - }
4806 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4807 - set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5097 + /**
5098 + * Returns a localized message describing the last SSL connectivity failure, if any.
5099 + *
5100 + * The Connection package's canonical SSL check stores a neutral reason code; this maps it to
5101 + * a translated, `jetpack`-domain message for display in the admin notice and AJAX recheck.
5102 + *
5103 + * @since 16.1
5104 + *
5105 + * @return string The localized message, or an empty string when there is no failure.
5106 + */
5107 + public static function get_ssl_test_message() {
5108 + if ( ! method_exists( Heartbeat::class, 'get_ssl_test_error' ) ) {
5109 + return '';
4808 5110 }
4809 5111
4810 - return (bool) $ssl;
5112 + $error = Heartbeat::get_ssl_test_error();
5113 +
5114 + switch ( $error['code'] ) {
5115 + case 'no_ssl_support':
5116 + return __( 'WordPress reports no SSL support', 'jetpack' );
5117 + case 'bad_response':
5118 + return __( 'Response was not OK: ', 'jetpack' ) . $error['detail'];
5119 + default:
5120 + return '';
5121 + }
4811 5122 }
4812 5123
4813 5124 /**
4814 5125 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -4827,9 +5138,9 @@
4827 5138 <p><?php esc_html_e( 'Your site could not connect to WordPress.com via HTTPS. This could be due to any number of reasons, including faulty SSL certificates, misconfigured or missing SSL libraries, or network issues.', 'jetpack' ); ?></p>
4828 5139 <p>
4829 5140 <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4830 5141 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4831 - <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
5142 + <span id="jetpack-recheck-ssl-output"><?php echo esc_html( self::get_ssl_test_message() ); ?></span>
4832 5143 </p>
4833 5144 <p>
4834 5145 <?php
4835 5146 printf(
@@ -4848,18 +5159,18 @@
4848 5159 <script type="text/javascript">
4849 5160 jQuery( document ).ready( function( $ ) {
4850 5161 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4851 5162 var $this = $( this );
4852 - $this.html( <?php echo wp_json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5163 + $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4853 5164 $( '#jetpack-recheck-ssl-output' ).html( '' );
4854 5165 e.preventDefault();
4855 - var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce ); ?> };
5166 + var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
4856 5167 $.post( ajaxurl, data )
4857 5168 .done( function( response ) {
4858 5169 if ( response.enabled ) {
4859 5170 $( '#jetpack-ssl-warning' ).hide();
4860 5171 } else {
4861 - this.html( <?php echo wp_json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5172 + this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4862 5173 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4863 5174 }
4864 5175 }.bind( $this ) );
4865 5176 } );
@@ -5217,15 +5528,20 @@
5217 5528
5218 5529 /**
5219 5530 * Checks if the site is currently in an identity crisis.
5220 5531 *
5532 + * Now delegates to the Connection package so this matches what the heartbeat itself reports.
5533 + * Note the package guards on `Connection\Manager::is_connected()` where this used to guard on
5534 + * `Jetpack::is_connection_ready()`, so the `jetpack_is_connection_ready` filter no longer applies.
5535 + *
5536 + * @deprecated 16.2
5537 + *
5221 5538 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5222 5539 */
5223 5540 public static function check_identity_crisis() {
5224 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5225 - return false;
5226 - }
5227 - return Jetpack_Options::get_option( 'sync_error_idc' );
5541 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5542 +
5543 + return Identity_Crisis::check_identity_crisis();
5228 5544 }
5229 5545
5230 5546 /**
5231 5547 * Normalizes a url by doing three things:
@@ -5313,9 +5629,9 @@
5313 5629 *
5314 5630 * @return mixed
5315 5631 */
5316 5632 public static function set_suffix_on_min( $src, $handle ) {
5317 - if ( ! str_contains( $src, '.min.css' ) ) {
5633 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5318 5634 return $src;
5319 5635 }
5320 5636
5321 5637 if ( ! empty( self::$min_assets ) ) {
@@ -5372,8 +5688,19 @@
5372 5688 return false;
5373 5689 }
5374 5690
5375 5691 /**
5692 + * Register Jetpack-specific tests on the connection package's health test suite.
5693 + *
5694 + * @param \Automattic\Jetpack\Connection\Connection_Health_Tests $connection_tests The test suite instance.
5695 + */
5696 + public function register_jetpack_connection_tests( $connection_tests ) {
5697 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger/class-jetpack-cxn-tests.php';
5698 + $jetpack_tests = new Jetpack_Cxn_Tests();
5699 + $jetpack_tests->register_tests_on( $connection_tests );
5700 + }
5701 +
5702 + /**
5376 5703 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5377 5704 */
5378 5705 public function deprecated_hooks() {
5379 5706 $filter_deprecated_list = array(
@@ -5597,8 +5924,10 @@
5597 5924 'jetpack_contact_form_use_package' => array(
5598 5925 'replacement' => null,
5599 5926 'version' => 'jetpack-13.4.0',
5600 5927 ),
5928 + // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
5929 + // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
5601 5930 );
5602 5931
5603 5932 foreach ( $filter_deprecated_list as $tag => $args ) {
5604 5933 if ( has_filter( $tag ) ) {
@@ -5729,125 +6058,8 @@
5729 6058 return $css;
5730 6059 }
5731 6060
5732 6061 /**
5733 - * This methods removes all of the registered css files on the front end
5734 - * from Jetpack in favor of using a single file. In effect "imploding"
5735 - * all the files into one file.
5736 - *
5737 - * Pros:
5738 - * - Uses only ONE css asset connection instead of 15
5739 - * - Saves a minimum of 56k
5740 - * - Reduces server load
5741 - * - Reduces time to first painted byte
5742 - *
5743 - * Cons:
5744 - * - Loads css for ALL modules. However all selectors are prefixed so it
5745 - * should not cause any issues with themes.
5746 - * - Plugins/themes dequeuing styles no longer do anything. See
5747 - * jetpack_implode_frontend_css filter for a workaround
5748 - *
5749 - * For some situations developers may wish to disable css imploding and
5750 - * instead operate in legacy mode where each file loads seperately and
5751 - * can be edited individually or dequeued. This can be accomplished with
5752 - * the following line:
5753 - *
5754 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5755 - *
5756 - * @param bool $travis_test Is this a test run.
5757 - *
5758 - * @since 3.2
5759 - */
5760 - public function implode_frontend_css( $travis_test = false ) {
5761 - $do_implode = true;
5762 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5763 - $do_implode = false;
5764 - }
5765 -
5766 - // Do not implode CSS when the page loads via the AMP plugin.
5767 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
5768 - $do_implode = false;
5769 - }
5770 -
5771 - /*
5772 - * Only proceed if at least 2 modules with concatenated CSS are active.
5773 - * There is no point in serving a big concatenated CSS file
5774 - * if there are no features (or only one) that actually need some CSS loaded.
5775 - */
5776 - $active_modules = self::get_active_modules();
5777 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
5778 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
5779 - if ( $active_module_with_css_count < 2 ) {
5780 - $do_implode = false;
5781 - }
5782 -
5783 - /**
5784 - * Allow CSS to be concatenated into a single jetpack.css file.
5785 - *
5786 - * @since 3.2.0
5787 - *
5788 - * @param bool $do_implode Should CSS be concatenated? Default to true.
5789 - */
5790 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5791 -
5792 - // Do not use the imploded file when default behavior was altered through the filter.
5793 - if ( ! $do_implode ) {
5794 - return;
5795 - }
5796 -
5797 - // We do not want to use the imploded file in dev mode, or if not connected.
5798 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
5799 - if ( ! $travis_test ) {
5800 - return;
5801 - }
5802 - }
5803 -
5804 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
5805 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
5806 - return;
5807 - }
5808 -
5809 - /*
5810 - * Now we assume Jetpack is connected and able to serve the single
5811 - * file.
5812 - *
5813 - * In the future there will be a check here to serve the file locally
5814 - * or potentially from the Jetpack CDN
5815 - *
5816 - * For now:
5817 - * - Enqueue a single imploded css file
5818 - * - Zero out the style_loader_tag for the bundled ones
5819 - * - Be happy, drink scotch
5820 - */
5821 -
5822 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
5823 -
5824 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
5825 -
5826 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
5827 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
5828 - }
5829 -
5830 - /**
5831 - * Removes styles that are part of concatenated group.
5832 - *
5833 - * @param string $tag Style tag.
5834 - * @param string $handle Style handle.
5835 - *
5836 - * @return string
5837 - */
5838 - public function concat_remove_style_loader_tag( $tag, $handle ) {
5839 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
5840 - $tag = '';
5841 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5842 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
5843 - }
5844 - }
5845 -
5846 - return $tag;
5847 - }
5848 -
5849 - /**
5850 6062 * Check the heartbeat data
5851 6063 *
5852 6064 * Organizes the heartbeat data by severity. For example, if the site
5853 6065 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -5859,9 +6071,23 @@
5859 6071 *
5860 6072 * $return array $filtered_data
5861 6073 */
5862 6074 public static function jetpack_check_heartbeat_data() {
5863 - $raw_data = Jetpack_Heartbeat::generate_stats_array();
6075 + /*
6076 + * Site environment stats (incl. wp-version/php-version checked below) now live in the Connection package,
6077 + * and the IDC stat is contributed by the Connection package's `jetpack_heartbeat_stats_array` filter
6078 + * callback. We rebuild the stat here rather than running that filter: the filter's callbacks have side
6079 + * effects (Connection\Manager::add_stats_to_heartbeat() consumes and deletes the `xmlrpc_errors` option)
6080 + * and return non-scalar values, neither of which is appropriate for this read-only diagnostic.
6081 + */
6082 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
6083 + ? Heartbeat::get_environment_stats()
6084 + : array();
6085 + $raw_data = array_merge(
6086 + Jetpack_Heartbeat::generate_stats_array(),
6087 + $env_stats,
6088 + array( 'identitycrisis' => Identity_Crisis::check_identity_crisis() ? 'yes' : 'no' )
6089 + );
5864 6090
5865 6091 $good = array();
5866 6092 $caution = array();
5867 6093 $bad = array();
@@ -6120,13 +6346,20 @@
6120 6346 }
6121 6347 }
6122 6348
6123 6349 /**
6124 - * Returns a boolean for whether backups UI should be displayed or not.
6350 + * Whether UI for backups should be displayed.
6125 6351 *
6352 + * On WPCom platforms this is gated on the backups-self-serve site feature.
6353 + * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
6354 + *
6126 6355 * @return bool Should backups UI be displayed?
6127 6356 */
6128 6357 public static function show_backups_ui() {
6358 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6359 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
6360 + }
6361 +
6129 6362 /**
6130 6363 * Whether UI for backups should be displayed.
6131 6364 *
6132 6365 * @since 6.5.0
@@ -6136,8 +6369,24 @@
6136 6369 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6137 6370 }
6138 6371
6139 6372 /**
6373 + * Whether UI for security scanning should be displayed.
6374 + *
6375 + * On WPCom platforms this is gated on the scan-self-serve site feature.
6376 + * On self-hosted Jetpack sites it always returns true.
6377 + *
6378 + * @return bool Should scan UI be displayed?
6379 + */
6380 + public static function show_scan_ui() {
6381 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6382 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
6383 + }
6384 +
6385 + return true;
6386 + }
6387 +
6388 + /**
6140 6389 * Clean leftoveruser meta.
6141 6390 *
6142 6391 * Delete Jetpack-related user meta when it is no longer needed.
6143 6392 *
@@ -6224,8 +6473,25 @@
6224 6473 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6225 6474 ),
6226 6475 );
6227 6476
6477 + $products['growth'] = array(
6478 + 'title' => __( 'Jetpack Growth', 'jetpack' ),
6479 + 'slug' => 'jetpack_growth_yearly',
6480 + 'description' => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
6481 + 'show_promotion' => true,
6482 + 'discount_percent' => 50,
6483 + 'included_in_plans' => array( 'complete' ),
6484 + 'features' => array(
6485 + _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
6486 + _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
6487 + _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
6488 + _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
6489 + _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
6490 + _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
6491 + ),
6492 + );
6493 +
6228 6494 $products['scan'] = array(
6229 6495 'title' => __( 'Jetpack Scan', 'jetpack' ),
6230 6496 'slug' => 'jetpack_scan',
6231 6497 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6372,8 +6638,56 @@
6372 6638 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6373 6639 . '</a>';
6374 6640
6375 6641 return $plugin_meta;
6642 + }
6643 +
6644 + /**
6645 + * Lazy instantiation of the Plugin_Tracking object.
6646 + *
6647 + * @since 13.9
6648 + *
6649 + * @return void
6650 + */
6651 + public function initialize_tracking() {
6652 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6653 + // Only need to run once.
6654 + return;
6655 + }
6656 +
6657 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6658 + ( new Plugin_Tracking() )->init();
6659 + }
6660 + }
6661 +
6662 + /**
6663 + * Run the "initialize tracking" hook.
6664 + *
6665 + * @since 13.9
6666 + */
6667 + public function run_initialize_tracking_action() {
6668 + /**
6669 + * Fires when the tracking needs to be initialized.
6670 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6671 + *
6672 + * @since 13.9
6673 + */
6674 + do_action( 'jetpack_initialize_tracking' );
6675 + }
6676 +
6677 + /**
6678 + * Initialize REST jsonAPI if needed.
6679 + *
6680 + * @return void
6681 + */
6682 + public function maybe_initialize_rest_jsonapi() {
6683 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6684 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6685 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6686 +
6687 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6688 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6689 + }
6376 6690 }
6377 6691
6378 6692 /**
6379 6693 * Run plugin post-activation actions if we need to.