PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
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 13.8.3 All 506 releases
← All changes | class.jetpack.php +840 -523 13.7.2 → 16.3-a.7 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(
@@ -167,8 +109,11 @@
167 109 ),
168 110 'latex' => array(
169 111 array( 'wp-latex/wp-latex.php', 'WP LaTeX' ),
170 112 ),
113 + 'random-redirect' => array(
114 + array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
115 + ),
171 116 'sharedaddy' => array(
172 117 array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
173 118 array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
174 119 ),
@@ -256,8 +201,11 @@
256 201 'Wordfence Security' => 'wordfence/wordfence.php',
257 202 'All In One WP Security & Firewall' => 'all-in-one-wp-security-and-firewall/wp-security.php',
258 203 'iThemes Security' => 'better-wp-security/better-wp-security.php',
259 204 ),
205 + 'random-redirect' => array(
206 + 'Random Redirect 2' => 'random-redirect-2/random-redirect.php',
207 + ),
260 208 'related-posts' => array(
261 209 'YARPP' => 'yet-another-related-posts-plugin/yarpp.php',
262 210 'WordPress Related Posts' => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
263 211 'nrelate Related Content' => 'nrelate-related-content/nrelate-related.php',
@@ -327,9 +275,8 @@
327 275 * Graph tags via filter when their Social Meta modules are active:
328 276 *
329 277 * - All in One SEO Pack, All in one SEO Pack Pro
330 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
331 - * - SEOPress, SEOPress Pro
332 279 *
333 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:
334 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
335 282 *
@@ -372,8 +319,10 @@
372 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
373 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
374 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
375 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
376 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
377 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
378 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
379 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -484,8 +433,16 @@
484 433 */
485 434 public static $instance = false;
486 435
487 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 + /**
488 445 * Singleton
489 446 *
490 447 * @static
491 448 */
@@ -525,9 +482,9 @@
525 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
526 483 self::update_active_modules( $modules );
527 484 }
528 485
529 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
530 487
531 488 // Upgrade to 4.3.0.
532 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
533 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -582,8 +539,16 @@
582 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
583 540 } // Should we have some type of fallback if something fails here?
584 541 }
585 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 +
586 551 if ( did_action( 'wp_loaded' ) ) {
587 552 self::upgrade_on_load();
588 553 } else {
589 554 add_action(
@@ -689,35 +654,15 @@
689 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
690 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
691 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
692 657
693 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
694 -
695 - add_filter(
696 - 'jetpack_signature_check_token',
697 - array( __CLASS__, 'verify_onboarding_token' ),
698 - 10,
699 - 3
700 - );
701 -
702 658 /**
703 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.
704 663 */
705 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
706 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
707 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
708 - /**
709 - * 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.
710 - *
711 - * 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.
712 - */
713 - if ( is_admin() ) {
714 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
715 - } else {
716 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
717 - }
718 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
719 -
720 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
721 666
722 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
723 668
@@ -726,8 +671,13 @@
726 671
727 672 // Set up the REST authentication hooks.
728 673 Connection_Rest_Authentication::init();
729 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 +
730 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
731 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
732 682
733 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -760,25 +710,8 @@
760 710
761 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
762 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
763 713
764 - /*
765 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
766 - * We should make sure to only do this for front end links.
767 - */
768 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
769 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
770 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
771 -
772 - /*
773 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
774 - * so they point moderation links on emails to Calypso.
775 - */
776 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
777 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
778 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
779 - }
780 -
781 714 add_action(
782 715 'plugins_loaded',
783 716 function () {
784 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -789,18 +722,10 @@
789 722
790 723 // Update the site's Jetpack plan and products from API on heartbeats.
791 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
792 725
793 - /**
794 - * This is the hack to concatenate all css files into one.
795 - * For description and reasoning see the implode_frontend_css method.
796 - *
797 - * Super late priority so we catch all the registered styles.
798 - */
799 - if ( ! is_admin() ) {
800 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
801 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
802 - }
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' ) );
803 728
804 729 // Actually push the stats on shutdown.
805 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
806 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -808,8 +733,10 @@
808 733
809 734 // After a successful connection.
810 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
811 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' ) );
812 739
813 740 // Actions for Manager::authorize().
814 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
815 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -849,18 +776,180 @@
849 776
850 777 // Register product descriptions for partner coupon usage.
851 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
852 779
853 - // Actions for conditional recommendations.
854 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
855 -
856 780 // Add 5-star
857 781 add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
782 + add_action( 'init', array( Deprecate::class, 'instance' ) );
858 783
859 - 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();
860 791 }
861 792
862 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 + /**
857 + * Filters whether the Jetpack plugin offers its bundled Backup dashboard.
858 + *
859 + * Resolved at the earliest `plugins_loaded` priority, so hook it from a mu-plugin.
860 + *
861 + * @since 16.3
862 + *
863 + * @param bool $enabled Whether to initialize the bundled Backup dashboard. Default true.
864 + */
865 + if ( ! apply_filters( 'jetpack_backup_dashboard_enabled', true ) ) {
866 + return;
867 + }
868 +
869 + $backup = 'Automattic\\Jetpack\\Backup\\V0005\\Jetpack_Backup';
870 +
871 + // An older package would take over the connection (see Jetpack_Backup::DEFAULT_INIT_OPTIONS);
872 + // only the standalone plugin ships one that old, and it draws its own menu.
873 + if ( ! class_exists( $backup ) || ! defined( $backup . '::DEFAULT_INIT_OPTIONS' ) ) {
874 + return;
875 + }
876 +
877 + $backup::initialize( array( 'manage_connection' => false ) );
878 + }
879 +
880 + /**
881 + * Whether the bundled Stats v2 dashboard is enabled.
882 + *
883 + * Stats v2 (formerly "Premium Analytics") ships with the plugin behind this
884 + * flag while it rolls out (WOOA7S-1595). When enabled it adds its own admin
885 + * menu alongside the existing Stats UI; it never replaces or hides the
886 + * legacy Stats menu, admin-bar entries, post-list column, or WP dashboard
887 + * widget. The Stats module's tracking is unaffected either way, and Stats v2
888 + * reads what that module collects, so while the module is off the plugin
889 + * answers false here before even reading the flag.
890 + *
891 + * The package has to be loadable for this to be true, so a site with the
892 + * flag on but a missing package answers false here and never adds the
893 + * Stats v2 menu (a warning is logged instead).
894 + *
895 + * @since 16.1
896 + *
897 + * @return bool
898 + */
899 + public static function is_premium_analytics_enabled() {
900 + if ( null !== self::$premium_analytics_enabled ) {
901 + return self::$premium_analytics_enabled;
902 + }
903 +
904 + if ( ! self::is_module_active( 'stats' ) ) {
905 + self::$premium_analytics_enabled = false;
906 + return false;
907 + }
908 +
909 + /**
910 + * Filters whether the bundled Premium Analytics dashboard is enabled.
911 + *
912 + * Resolved once, from `Jetpack::configure()` on `plugins_loaded`, and only
913 + * while the Stats module is active. Register this from a mu-plugin or a
914 + * plugin's main file — a callback added on `plugins_loaded` or later runs
915 + * too late to be seen.
916 + *
917 + * @since 16.1
918 + *
919 + * @param bool $enabled Defaults to the `jetpack_premium_analytics_enabled` option (false).
920 + */
921 + $flag = (bool) apply_filters( 'jetpack_premium_analytics_enabled', (bool) get_option( 'jetpack_premium_analytics_enabled' ) );
922 +
923 + self::$premium_analytics_enabled = $flag && class_exists( 'Automattic\Jetpack\PremiumAnalytics\Analytics' );
924 +
925 + if ( $flag && ! self::$premium_analytics_enabled ) {
926 + wp_trigger_error(
927 + __METHOD__,
928 + 'The jetpack_premium_analytics_enabled flag is on but the Premium Analytics package is not loadable; keeping the Stats UI in place.'
929 + );
930 + }
931 +
932 + return self::$premium_analytics_enabled;
933 + }
934 +
935 + /**
936 + * Expose the setting that turns the Premium Analytics dashboard on and off.
937 + *
938 + * Deliberately not behind is_premium_analytics_enabled(): this is the setting that flips that
939 + * check, so it has to answer while the dashboard is still off.
940 + *
941 + * @since 16.2
942 + *
943 + * @return void
944 + */
945 + public static function register_premium_analytics_enablement_setting() {
946 + if ( class_exists( 'Automattic\Jetpack\PremiumAnalytics\Enablement_Setting' ) ) {
947 + \Automattic\Jetpack\PremiumAnalytics\Enablement_Setting::register();
948 + }
949 + }
950 +
951 + /**
863 952 * Before everything else starts getting initalized, we need to initialize Jetpack using the
864 953 * Config object.
865 954 */
866 955 public function configure() {
@@ -869,13 +958,10 @@
869 958 foreach (
870 959 array(
871 960 'jitm',
872 961 'sync',
962 + 'account_protection',
873 963 'waf',
874 - 'videopress',
875 - 'stats',
876 - 'stats_admin',
877 - 'import',
878 964 )
879 965 as $feature
880 966 ) {
881 967 $config->ensure( $feature );
@@ -880,8 +966,110 @@
880 966 ) {
881 967 $config->ensure( $feature );
882 968 }
883 969
970 + // Enable the VideoPress admin UI (the "Jetpack > VideoPress" dashboard) inside the
971 + // Jetpack plugin, mirroring the standalone Jetpack VideoPress plugin. The dashboard
972 + // only renders when the VideoPress module is active (Status::is_active()); when it
973 + // is not, the menu item links to the My Jetpack interstitial to activate it.
974 + $config->ensure( 'videopress', array( 'admin_ui' => true ) );
975 +
976 + // The Backup dashboard is only an admin menu and REST routes, so it is deferred like Import below.
977 + if ( self::should_eager_load_packages() ) {
978 + self::configure_backup_package();
979 + } else {
980 + add_action( 'rest_api_init', array( __CLASS__, 'configure_backup_package' ), 0 );
981 + }
982 +
983 + /*
984 + * The Import package only registers `jetpack/v4/import` REST routes — it
985 + * does nothing when rendering a front-end page — so gate its `ensure()`
986 + * to keep its PHP out of opcache on the front-end GET hot path. It still
987 + * loads on admin, cron, POST, and WP-CLI requests, and on `rest_api_init`
988 + * for REST: a REST request can't be identified yet at `plugins_loaded`
989 + * (this runs before `Config::on_plugins_loaded`, and `rest_api_init`
990 + * fires later), so it is initialized directly when that hook fires, while
991 + * a plain page view never fires it and so loads nothing.
992 + *
993 + * JITM stays eager (above): unlike Import, its `register()` adds a
994 + * `jetpack_sync_before_send_updated_option` filter that records the
995 + * `jetpack_last_plugin_sync` transient, and a Jetpack Sync send can fire
996 + * on a plain front-end GET — including the dedicated-sync `spawn-sync`
997 + * GET, which runs on `init` and exits before `rest_api_init`. Deferring
998 + * JITM would skip that bookkeeping and leave its message cache stale after
999 + * a plugin change, so it loads on every request as before.
1000 + */
1001 + if ( self::should_eager_load_packages() ) {
1002 + $config->ensure( 'import' );
1003 + } else {
1004 + add_action(
1005 + 'rest_api_init',
1006 + array( __CLASS__, 'configure_import_package' ),
1007 + 0
1008 + );
1009 + }
1010 +
1011 + /*
1012 + * The Stats and Stats Admin packages only do work when the Stats module
1013 + * is active (the front-end tracking pixel) or on wp-admin, REST, cron,
1014 + * POST, and WP-CLI requests: the Stats dashboard page, the stats /
1015 + * stats-app REST endpoints (which the block editor also calls for
1016 + * email-open rates), the transient-cleanup cron, the connection
1017 + * package's package-version tracker (which runs on POSTs and reads the
1018 + * `jetpack_package_versions` filter that Stats registers), and CLI
1019 + * introspection such as the heartbeat inspector. On a plain front-end
1020 + * GET page view with the module off they are inert: the pixel
1021 + * short-circuits on `Stats\Main::should_track()` and every other entry
1022 + * point only hooks rest_api_init, admin, cron, the POST-only tracker, or
1023 + * is reached through WP-CLI.
1024 + * Skip loading them — and eagerly constructing the Stats Admin REST
1025 + * controller — on that hot path to keep their PHP out of opcache, but
1026 + * keep loading them everywhere else exactly as before so the stats REST
1027 + * permission mapping (view_stats, registered by Stats\Main), the
1028 + * editor's stats-app calls, the cleanup cron, and the package-version
1029 + * tracker are all unchanged.
1030 + *
1031 + * REST requests are not yet identifiable here (REST_REQUEST is defined
1032 + * after plugins_loaded), so defer those to rest_api_init. Call the
1033 + * package initializers directly rather than `$config->ensure()`: ensure()
1034 + * only flags a feature for `Config::on_plugins_loaded()` (plugins_loaded
1035 + * priority 2), which has already run by the time rest_api_init fires.
1036 + * Priority 0 runs before each package's own priority-10 route
1037 + * registration, so their routes still register within the same dispatch.
1038 + * A plain page view never fires rest_api_init, so the packages stay
1039 + * unloaded there. See JETPACK-1747.
1040 + */
1041 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'];
1042 + $is_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
1043 +
1044 + if ( self::is_module_active( 'stats' ) || is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli ) {
1045 + $config->ensure( 'stats' );
1046 + $config->ensure( 'stats_admin' );
1047 + } else {
1048 + add_action(
1049 + 'rest_api_init',
1050 + static function () {
1051 + if ( class_exists( 'Automattic\Jetpack\Stats\Main' ) ) {
1052 + \Automattic\Jetpack\Stats\Main::init();
1053 + }
1054 + if ( class_exists( 'Automattic\Jetpack\Stats_Admin\Main' ) ) {
1055 + \Automattic\Jetpack\Stats_Admin\Main::init();
1056 + }
1057 + },
1058 + 0
1059 + );
1060 + }
1061 +
1062 + // Stats v2 (WOOA7S-1595). Unlike Stats above it cannot be deferred when enabled — see
1063 + // Analytics::init() for why, and for why it takes no menu_title here.
1064 + if ( self::is_premium_analytics_enabled() ) {
1065 + \Automattic\Jetpack\PremiumAnalytics\Analytics::init();
1066 + }
1067 +
1068 + // Outside the check above on purpose — see Enablement_Setting. Deferred like Stats, to keep
1069 + // the autoload off the front-end hot path.
1070 + add_action( 'rest_api_init', array( __CLASS__, 'register_premium_analytics_enablement_setting' ), 0 );
1071 +
884 1072 $config->ensure(
885 1073 'connection',
886 1074 array(
887 1075 'slug' => 'jetpack',
@@ -899,12 +1087,8 @@
899 1087 );
900 1088
901 1089 $config->ensure( 'search' );
902 1090
903 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
904 - $config->ensure( 'wordads' );
905 - }
906 -
907 1091 if ( ! $this->connection_manager ) {
908 1092 $this->connection_manager = new Connection_Manager( 'jetpack' );
909 1093 }
910 1094
@@ -912,8 +1096,10 @@
912 1096 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
913 1097 $config->ensure( 'publicize' );
914 1098 }
915 1099
1100 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1101 +
916 1102 /*
917 1103 * Load things that should only be in Network Admin.
918 1104 *
919 1105 * For now blow away everything else until a more full
@@ -928,8 +1114,9 @@
928 1114 $is_connection_ready = self::is_connection_ready();
929 1115
930 1116 if ( $is_connection_ready ) {
931 1117 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1118 + $this->run_initialize_tracking_action();
932 1119
933 1120 Jetpack_Heartbeat::init();
934 1121 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
935 1122 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -934,8 +1121,19 @@
934 1121 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
935 1122 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
936 1123 Jetpack_Search_Performance_Logger::init();
937 1124 }
1125 + } else {
1126 + add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
1127 + add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
1128 + add_filter(
1129 + 'xmlrpc_methods',
1130 + function ( $methods ) {
1131 + $this->run_initialize_tracking_action();
1132 + return $methods;
1133 + },
1134 + 1
1135 + );
938 1136 }
939 1137
940 1138 // Initialize remote file upload request handlers.
941 1139 $this->add_remote_request_handlers();
@@ -945,20 +1143,10 @@
945 1143 */
946 1144 if ( $is_connection_ready ) {
947 1145 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
948 1146 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
949 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
950 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1147 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
951 1148 }
952 -
953 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
954 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
955 - } else {
956 - /**
957 - * Initialize tracking right after the user agrees to the terms of service.
958 - */
959 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
960 - }
961 1149 }
962 1150
963 1151 /**
964 1152 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -966,15 +1154,58 @@
966 1154 *
967 1155 * @action plugins_loaded
968 1156 */
969 1157 public function late_initialization() {
970 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1158 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
971 1159
972 - My_Jetpack_Initializer::init();
1160 + /*
1161 + * My Jetpack is a wp-admin dashboard. Its Initializer::init() only wires
1162 + * up admin-menu, admin_init, and rest_api_init surfaces — and eagerly
1163 + * loads every product class (backup, boost, protect, …) just to register
1164 + * admin plugin-action links — so none of it is needed on a plain
1165 + * front-end GET page view. (The pieces that do immediate work, e.g.
1166 + * Connection REST authentication and Licensing, are already initialized
1167 + * unconditionally in Jetpack's constructor, so they are unaffected here.)
1168 + *
1169 + * Gate the call to the request types where My Jetpack actually does work.
1170 + * REST can't be detected yet at plugins_loaded, so initialize on
1171 + * rest_api_init for that branch; a plain page view never fires it, so My
1172 + * Jetpack stays unloaded there.
1173 + */
1174 + if ( self::should_eager_load_packages() ) {
1175 + My_Jetpack_Initializer::init();
1176 + } else {
1177 + add_action( 'rest_api_init', array( My_Jetpack_Initializer::class, 'init' ), 0 );
1178 + }
973 1179
974 - // Initialize Boost Speed Score
975 - new Speed_Score( array(), 'jetpack-dashboard' );
1180 + Scan_Page_Init::initialize();
1181 + Jetpack_SEO_Initializer::init();
976 1182
1183 + if ( ( new Modules() )->is_active( 'podcast' ) ) {
1184 + Podcast::init();
1185 + }
1186 +
1187 + /*
1188 + * Initialize Boost Speed Score. It only does work on REST requests (the
1189 + * dashboard speed-score endpoints) and on a few Jetpack Boost lifecycle
1190 + * actions, so defer constructing it — and loading the boost-speed-score
1191 + * package classes — until one of those hooks actually fires instead of on
1192 + * every request. Priority 0 ensures the object's own callbacks (added in
1193 + * its constructor at the default priority) still run for the firing hook.
1194 + */
1195 + $initialize_speed_score = static function () {
1196 + static $initialized = false;
1197 + if ( $initialized ) {
1198 + return;
1199 + }
1200 + $initialized = true;
1201 + new Speed_Score( array(), 'jetpack-dashboard' );
1202 + };
1203 + add_action( 'rest_api_init', $initialize_speed_score, 0 );
1204 + add_action( 'jetpack_boost_deactivate', $initialize_speed_score, 0 );
1205 + add_action( 'jetpack_boost_environment_changed', $initialize_speed_score, 0 );
1206 + add_action( 'handle_environment_change', $initialize_speed_score, 0 );
1207 +
977 1208 /**
978 1209 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
979 1210 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
980 1211 *
@@ -1012,8 +1243,10 @@
1012 1243
1013 1244 /**
1014 1245 * Redirect edit post links to Calypso.
1015 1246 *
1247 + * @deprecated since 13.9
1248 + *
1016 1249 * @param string $default_url Post edit URL.
1017 1250 * @param int $post_id Post ID.
1018 1251 *
1019 1252 * @return string
@@ -1018,8 +1251,10 @@
1018 1251 *
1019 1252 * @return string
1020 1253 */
1021 1254 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1255 + _deprecated_function( __METHOD__, '13.9' );
1256 +
1022 1257 $post = get_post( $post_id );
1023 1258
1024 1259 if ( empty( $post ) ) {
1025 1260 return $default_url;
@@ -1050,13 +1285,17 @@
1050 1285
1051 1286 /**
1052 1287 * Redirect edit comment links to Calypso.
1053 1288 *
1289 + * @deprecated since 13.9
1290 + *
1054 1291 * @param string $url Comment edit URL.
1055 1292 *
1056 1293 * @return string
1057 1294 */
1058 1295 public function point_edit_comment_links_to_calypso( $url ) {
1296 + _deprecated_function( __METHOD__, '13.9' );
1297 +
1059 1298 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1060 1299 $query_args = null;
1061 1300 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1062 1301
@@ -1080,8 +1319,9 @@
1080 1319 $jetpack_callables = array(
1081 1320 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1082 1321 'updates' => array( 'Jetpack', 'get_updates' ),
1083 1322 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1323 + 'theme_styles' => array( 'Automattic\\Jetpack\\Plugin\\Theme_Styles_Sync', 'get_theme_styles' ),
1084 1324 );
1085 1325 return array_merge( $callables, $jetpack_callables );
1086 1326 }
1087 1327
@@ -1124,16 +1364,8 @@
1124 1364 * @param string $cap Capability name.
1125 1365 */
1126 1366 public function jetpack_custom_caps( $caps, $cap ) {
1127 1367 switch ( $cap ) {
1128 - case 'jetpack_manage_modules':
1129 - case 'jetpack_activate_modules':
1130 - case 'jetpack_deactivate_modules':
1131 - $caps = array( 'manage_options' );
1132 - break;
1133 - case 'jetpack_configure_modules':
1134 - $caps = array( 'manage_options' );
1135 - break;
1136 1368 case 'jetpack_manage_autoupdates':
1137 1369 $caps = array(
1138 1370 'manage_options',
1139 1371 'update_plugins',
@@ -1151,9 +1383,9 @@
1151 1383 if ( $is_offline_mode ) {
1152 1384 $caps = array( 'manage_options' );
1153 1385 break;
1154 1386 } else {
1155 - $caps = array( 'read' );
1387 + $caps = array( 'edit_posts' );
1156 1388 }
1157 1389 break;
1158 1390 }
1159 1391 return $caps;
@@ -1596,29 +1828,8 @@
1596 1828 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1597 1829 }
1598 1830
1599 1831 /**
1600 - * Whether the site is currently onboarding or not.
1601 - * A site is considered as being onboarded if it currently has an onboarding token.
1602 - *
1603 - * @since 5.8
1604 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1605 - *
1606 - * @access public
1607 - * @static
1608 - *
1609 - * @return bool True if the site is currently onboarding, false otherwise
1610 - */
1611 - public static function is_onboarding() {
1612 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1613 -
1614 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1615 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1616 - }
1617 - return ( new Status() )->is_onboarding();
1618 - }
1619 -
1620 - /**
1621 1832 * Determines reason for Jetpack offline mode.
1622 1833 */
1623 1834 public static function development_mode_trigger_text() {
1624 1835 $status = new Status();
@@ -1632,11 +1843,10 @@
1632 1843 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1633 1844 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1634 1845 } elseif ( $status->is_local_site() ) {
1635 1846 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1636 - /** This filter is documented in packages/status/src/class-status.php */
1637 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1638 - $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1847 + } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1848 + $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1639 1849 } else {
1640 1850 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1641 1851 }
1642 1852
@@ -1741,18 +1951,11 @@
1741 1951 */
1742 1952 public static function load_modules() {
1743 1953 $status = new Status();
1744 1954
1745 - if ( method_exists( $status, 'is_onboarding' ) ) {
1746 - $is_onboarding = $status->is_onboarding();
1747 - } else {
1748 - $is_onboarding = self::is_onboarding();
1749 - }
1750 -
1751 1955 if (
1752 1956 ! self::is_connection_ready()
1753 1957 && ! $status->is_offline_mode()
1754 - && ! $is_onboarding
1755 1958 && (
1756 1959 ! is_multisite()
1757 1960 || ! get_site_option( 'jetpack_protect_active' )
1758 1961 )
@@ -1873,22 +2076,14 @@
1873 2076 *
1874 2077 * @todo Store the result in core's object cache maybe?
1875 2078 */
1876 2079 public static function get_active_plugins() {
1877 - $active_plugins = (array) get_option( 'active_plugins', array() );
1878 -
1879 - if ( is_multisite() ) {
1880 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1881 - // whereas active_plugins stores them in the values.
1882 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1883 - if ( $network_plugins ) {
1884 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1885 - }
2080 + // Older Connection copies can load first and lack this method.
2081 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2082 + return array();
1886 2083 }
1887 2084
1888 - sort( $active_plugins );
1889 -
1890 - return array_unique( $active_plugins );
2085 + return Heartbeat::get_active_plugins();
1891 2086 }
1892 2087
1893 2088 /**
1894 2089 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2026,8 +2221,10 @@
2026 2221 *
2027 2222 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2028 2223 */
2029 2224 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2225 + // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
2226 + // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
2030 2227 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2031 2228 }
2032 2229 }
2033 2230
@@ -2130,9 +2327,9 @@
2130 2327 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.
2131 2328 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2132 2329 }
2133 2330 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2134 - exit;
2331 + exit( 0 );
2135 2332 }
2136 2333 }
2137 2334
2138 2335 /**
@@ -2179,8 +2376,12 @@
2179 2376 default:
2180 2377 break;
2181 2378 }
2182 2379 }
2380 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2381 + Feature_Policy::ensure_hooks();
2382 + }
2383 +
2183 2384 /**
2184 2385 * Filters the array of default modules.
2185 2386 *
2186 2387 * @since 2.5.0
@@ -2273,8 +2474,17 @@
2273 2474 }
2274 2475 }
2275 2476 }
2276 2477
2478 + // Special case to convert block setting to a block module.
2479 + $block_key = array_search( 'blocks', $modules, true );
2480 + if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2481 + $block_option = get_option( 'jetpack_blocks_disabled', null );
2482 + if ( $block_option ) {
2483 + unset( $modules[ $block_key ] );
2484 + }
2485 + }
2486 +
2277 2487 return $modules;
2278 2488 }
2279 2489
2280 2490 /**
@@ -2331,23 +2541,30 @@
2331 2541
2332 2542 /**
2333 2543 * Return module name translation. Uses matching string created in modules/module-headings.php.
2334 2544 *
2545 + * The module list is globbed from `modules/` at runtime, so a module can be listed with no
2546 + * entry in that generated file. Fall back to the untranslated header rather than overwriting
2547 + * it with the null `jetpack_get_module_i18n()` returns for an unknown slug.
2548 + *
2335 2549 * @since 3.9.2
2336 2550 *
2337 2551 * @param array $modules Array of Jetpack modules.
2338 2552 *
2339 - * @return string|void
2553 + * @return array
2340 2554 */
2341 2555 public static function get_translated_modules( $modules ) {
2342 2556 foreach ( $modules as $index => $module ) {
2343 2557 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2344 - if ( isset( $module['name'] ) ) {
2345 - $modules[ $index ]['name'] = $i18n_module['name'];
2558 + $name = $i18n_module['name'] ?? null;
2559 + $description = $i18n_module['description'] ?? null;
2560 +
2561 + if ( null !== $name && isset( $module['name'] ) ) {
2562 + $modules[ $index ]['name'] = $name;
2346 2563 }
2347 - if ( isset( $module['description'] ) ) {
2348 - $modules[ $index ]['description'] = $i18n_module['description'];
2349 - $modules[ $index ]['short_description'] = $i18n_module['description'];
2564 + if ( null !== $description && isset( $module['description'] ) ) {
2565 + $modules[ $index ]['description'] = $description;
2566 + $modules[ $index ]['short_description'] = $description;
2350 2567 }
2351 2568 if ( isset( $module['module_tags'] ) ) {
2352 2569 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2353 2570 }
@@ -2512,9 +2729,9 @@
2512 2729 ),
2513 2730 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2514 2731 );
2515 2732 wp_safe_redirect( $url );
2516 - exit;
2733 + exit( 0 );
2517 2734 }
2518 2735 }
2519 2736
2520 2737 /**
@@ -2660,9 +2877,9 @@
2660 2877 * @return string $url module configuration URL.
2661 2878 */
2662 2879 public static function module_configuration_url( $module ) {
2663 2880 $module = self::get_module_slug( $module );
2664 - $default_url = self::admin_url() . "#/settings?term=$module";
2881 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2665 2882 /**
2666 2883 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2667 2884 *
2668 2885 * @since 6.9.0
@@ -2720,9 +2937,9 @@
2720 2937 if ( $update ) {
2721 2938 update_option( 'active_plugins', array_filter( $plugins ) );
2722 2939 }
2723 2940 }
2724 - exit;
2941 + exit( 0 );
2725 2942 }
2726 2943
2727 2944 /**
2728 2945 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2747,12 +2964,18 @@
2747 2964 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2748 2965
2749 2966 Health::on_jetpack_activated();
2750 2967
2968 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2969 +
2751 2970 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2752 2971 Sync_Actions::do_only_first_initial_sync();
2753 2972 }
2754 2973
2974 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2975 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2976 + }
2977 +
2755 2978 self::plugin_initialize();
2756 2979 }
2757 2980
2758 2981 /**
@@ -2787,9 +3010,9 @@
2787 3010
2788 3011 if ( $plugins_path === $referer['path'] ) {
2789 3012 $source_type = 'list';
2790 3013 } elseif ( $plugins_install_path === $referer['path'] ) {
2791 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3014 + $tab = $query_parts['tab'] ?? 'featured';
2792 3015 switch ( $tab ) {
2793 3016 case 'popular':
2794 3017 $source_type = 'popular';
2795 3018 break;
@@ -2799,10 +3022,10 @@
2799 3022 case 'favorites':
2800 3023 $source_type = 'favorites';
2801 3024 break;
2802 3025 case 'search':
2803 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2804 - $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3026 + $source_type = 'search-' . ( $query_parts['type'] ?? 'term' );
3027 + $source_query = $query_parts['s'] ?? null;
2805 3028 break;
2806 3029 default:
2807 3030 $source_type = 'featured';
2808 3031 }
@@ -2811,29 +3034,171 @@
2811 3034 return array( $source_type, $source_query );
2812 3035 }
2813 3036
2814 3037 /**
2815 - * Runs before bumping version numbers up to a new version
3038 + * Runs before bumping version numbers up to a new version.
2816 3039 *
2817 - * @param string $version Version:timestamp.
3040 + * Only ever registered the hooks for the release post update modal, which has been removed.
3041 + * No longer hooked to `updating_jetpack_version`.
3042 + *
3043 + * @deprecated 16.2
3044 + *
3045 + * @param string $version Version:timestamp.
2818 3046 * @param string $old_version Old Version:timestamp or false if not set yet.
2819 3047 */
2820 - public static function do_version_bump( $version, $old_version ) {
2821 - if ( $old_version ) { // For existing Jetpack installations.
2822 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3048 + public static function do_version_bump( $version, $old_version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature preserved for the deprecation shim.
3049 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
3050 + }
2823 3051
2824 - // If a front end page is visited after the update, the 'wp' action will fire.
2825 - add_action( 'wp', 'Jetpack::set_update_modal_display' );
3052 + /**
3053 + * Enables the Newsletter (subscriptions) module for existing sites now that it is a default-on module.
3054 + *
3055 + * Fresh installs receive the module via its "Auto Activate: Yes" header, so this only handles sites
3056 + * upgrading from a version where the module defaulted off. It runs once per site (guarded by the
3057 + * subscriptions_default_on_migrated option). Fresh installs are marked as migrated immediately so the
3058 + * migration never runs for them. After it has run, the user's choice to deactivate the module again
3059 + * (for example from the My Jetpack Products page) is respected and never reverted.
3060 + *
3061 + * The module requires a connection, so on a disconnected site the migration is deferred without setting
3062 + * the guard, allowing a later version bump to retry once the site is connected.
3063 + *
3064 + * @param string $version New Jetpack version:timestamp.
3065 + * @param string|false $old_version Previous Jetpack version:timestamp, or false on a fresh install.
3066 + */
3067 + public static function activate_subscriptions_module_for_existing_sites( $version, $old_version ) {
3068 + if ( get_option( 'jetpack_subscriptions_default_on_migrated' ) ) {
3069 + return;
3070 + }
2826 3071
2827 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2828 - add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3072 + // Fresh installs get the module via its "Auto Activate: Yes" header. Mark them as migrated so a
3073 + // later opt-out is never reverted by the existing-site path on a subsequent version bump.
3074 + if ( ! $old_version ) {
3075 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3076 + return;
2829 3077 }
3078 +
3079 + if ( ! self::is_connection_ready() ) {
3080 + return;
3081 + }
3082 +
3083 + // Mark as migrated only once the module is active, so a transient activation failure is retried on
3084 + // a later version bump rather than being silently skipped.
3085 + if ( self::is_module_active( 'subscriptions' ) || self::activate_module( 'subscriptions', false, false ) ) {
3086 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3087 + }
2830 3088 }
2831 3089
2832 3090 /**
3091 + * Option flag that records the AI master-switch opt-out reconciliation has run,
3092 + * so it never runs twice.
3093 + *
3094 + * @var string
3095 + */
3096 + const AI_MASTER_OPTOUT_MIGRATED_OPTION = 'jetpack_ai_master_optout_migrated';
3097 +
3098 + /**
3099 + * Register the on-upgrade init hooks whose relative ORDER matters, extracted so
3100 + * the ordering can be asserted in tests without invoking plugin_upgrade() (whose
3101 + * guards make it unreliable to trigger under test). activate_new_modules()
3102 + * (init, default priority 10) auto-activates "Auto Activate: Yes" modules
3103 + * including the `ai` master; reconcile_ai_master_optout() must run at a LATER
3104 + * priority to honor an explicit AI opt-out.
3105 + *
3106 + * @return void
3107 + */
3108 + public static function register_upgrade_init_hooks() {
3109 + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
3110 + add_action( 'init', array( __CLASS__, 'reconcile_ai_master_optout' ), 20 );
3111 + }
3112 +
3113 + /**
3114 + * Preserves an explicit Jetpack AI opt-out when the `ai` module becomes the site-wide
3115 + * master switch off WordPress.com Simple (self-hosted and Atomic).
3116 + *
3117 + * The `ai` module is "Auto Activate: Yes", so on upgrade {@see self::activate_new_modules()}
3118 + * turns it on for connected sites — the desired default-on / auto-enable-on-connection
3119 + * behavior, which this method deliberately leaves alone. The one case it corrects is a site
3120 + * that had explicitly disabled Jetpack AI (the `jetpack_ai_enabled` option present and falsey)
3121 + * before the module shipped: that opt-out must survive the module becoming the master, so the
3122 + * module is deactivated for exactly those sites. An absent or truthy option is left untouched.
3123 + *
3124 + * Ordering is the whole point. `activate_new_modules()` is hooked on `init` at priority 10 and
3125 + * auto-activates the module there; this method is hooked on `init` at priority 20 (see
3126 + * {@see self::plugin_upgrade()}), so it runs AFTER the auto-activation and its deactivation is
3127 + * the final state. A version-guarded block that ran inline during `plugins_loaded` would be
3128 + * undone by the later auto-activation, which is why this is a late-init hook rather than an
3129 + * inline upgrade step.
3130 + *
3131 + * WordPress.com Simple never runs modules — the option stays the master there — so this is a
3132 + * no-op on Simple. The {@see self::AI_MASTER_OPTOUT_MIGRATED_OPTION} flag makes it run exactly
3133 + * once, which matters because off-Simple the option is no longer the master after this runs:
3134 + * a stale falsey option must not keep re-deactivating a module the user later turns back on.
3135 + *
3136 + * @return void
3137 + */
3138 + public static function reconcile_ai_master_optout() {
3139 + if ( get_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION ) ) {
3140 + return;
3141 + }
3142 +
3143 + // Simple keeps the `jetpack_ai_enabled` option as the master; modules don't run there.
3144 + if ( ( new Host() )->is_wpcom_simple() ) {
3145 + return;
3146 + }
3147 +
3148 + // A sentinel default distinguishes an absent option (leave auto-activation alone) from one
3149 + // explicitly stored falsey (an opt-out to preserve).
3150 + $stored = get_option( 'jetpack_ai_enabled', 'not-set' );
3151 + if ( 'not-set' !== $stored && ! (bool) $stored ) {
3152 + ( new Modules() )->deactivate( 'ai' );
3153 + }
3154 +
3155 + update_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION, true );
3156 + }
3157 +
3158 + /**
3159 + * Deletes obsolete SEO module-state options without changing module activation.
3160 + *
3161 + * @since 16.3
3162 + */
3163 + public static function cleanup_seo_module_state_options() {
3164 + delete_option( 'jetpack_seo_sitemap_enabled' );
3165 + delete_option( 'jetpack_seo_canonical_urls_enabled' );
3166 + delete_option( 'jetpack_seo_module_state_reconciled' );
3167 + }
3168 +
3169 + /**
3170 + * Seeds the Jetpack SEO discoverability cohort once, so the new SEO surface is
3171 + * auto-discoverable on fresh installs but opt-in on existing ones (JETPACK-1700).
3172 + *
3173 + * Hooked on `updating_jetpack_version`, which fires on every install including the
3174 + * first — with `$old_version === false` on a brand-new site (the same signal
3175 + * {@see self::activate_subscriptions_module_for_existing_sites()} keys off). Fresh
3176 + * installs are seeded visible; existing installs are seeded hidden and opt in later
3177 + * via the legacy Traffic page or My Jetpack. `add_option()` makes this seed-once: it
3178 + * never overrides a value a later opt-in (or opt-out) has set. WordPress.com sites
3179 + * ignore this option entirely (always visible) — see
3180 + * {@see \Automattic\Jetpack\SEO\Initializer::is_seo_surface_visible()}.
3181 + *
3182 + * @param string $version The new Jetpack version (unused).
3183 + * @param string|false $old_version The previous version, or false on a fresh install.
3184 + */
3185 + public static function seed_seo_visibility_cohort( $version, $old_version ) {
3186 + add_option( Jetpack_SEO_Initializer::VISIBILITY_OPTION, ! $old_version );
3187 + }
3188 +
3189 + /**
2833 3190 * Sets the display_update_modal state.
3191 + *
3192 + * The release post update modal that read this state has been removed. The write is kept so the
3193 + * method still behaves as documented for the deprecation window. When this is deleted, also drop
3194 + * the matching `display_update_modal` guard in Automattic\Jetpack\CookieState::should_set_cookie(),
3195 + * which exists only to keep this key out of the cookie on the Jetpack admin screen.
3196 + *
3197 + * @deprecated 16.2
2834 3198 */
2835 3199 public static function set_update_modal_display() {
3200 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2836 3201 self::state( 'display_update_modal', true );
2837 3202 }
2838 3203
2839 3204 /**
@@ -2838,11 +3203,16 @@
2838 3203
2839 3204 /**
2840 3205 * Enqueues the block library styles.
2841 3206 *
3207 + * Only ever used by the release post update modal, which has been removed.
3208 + *
3209 + * @deprecated 16.2
3210 + *
2842 3211 * @param string $hook The current admin page.
2843 3212 */
2844 3213 public static function enqueue_block_style( $hook ) {
3214 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2845 3215 if ( 'toplevel_page_jetpack' === $hook ) {
2846 3216 wp_enqueue_style( 'wp-block-library' );
2847 3217 }
2848 3218 }
@@ -2931,8 +3301,12 @@
2931 3301 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2932 3302 self::disconnect();
2933 3303 Jetpack_Options::delete_option( 'version' );
2934 3304 }
3305 +
3306 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3307 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3308 + }
2935 3309 }
2936 3310
2937 3311 /**
2938 3312 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -2960,9 +3334,9 @@
2960 3334 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2961 3335 }
2962 3336
2963 3337 /**
2964 - * Happens after a successfull disconnection.
3338 + * Happens after a successful disconnection.
2965 3339 *
2966 3340 * @static
2967 3341 */
2968 3342 public static function jetpack_site_disconnected() {
@@ -2967,8 +3341,10 @@
2967 3341 */
2968 3342 public static function jetpack_site_disconnected() {
2969 3343 Identity_Crisis::clear_all_idc_options();
2970 3344
3345 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3346 +
2971 3347 // Delete all the sync related data. Since it could be taking up space.
2972 3348 Sender::get_instance()->uninstall();
2973 3349
2974 3350 /**
@@ -3032,10 +3408,17 @@
3032 3408 * @param mixed $code Error code to log.
3033 3409 * @param mixed $data Data to log.
3034 3410 */
3035 3411 public static function log( $code, $data = null ) {
3412 +
3413 + $raw_log = Jetpack_Options::get_option( 'log', array() );
3414 + // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
3415 + if ( ! is_array( $raw_log ) ) {
3416 + return;
3417 + }
3418 +
3036 3419 // only grab the latest 200 entries.
3037 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3420 + $log = array_slice( $raw_log, -199, 199 );
3038 3421
3039 3422 // Append our event to the log.
3040 3423 $log_entry = array(
3041 3424 'time' => time(),
@@ -3135,8 +3518,15 @@
3135 3518
3136 3519 /**
3137 3520 * Return stat data for WPCOM sync.
3138 3521 *
3522 + * The Sync stats module was this method's last caller and moved to the Connection package's
3523 + * `Heartbeat::generate_stats_array()`, which assembles the heartbeat data through the
3524 + * `jetpack_heartbeat_stats_array` filter. Note the package method does not include the extended
3525 + * data from `get_additional_stat_data()`, so callers relying on `$extended` need to add it themselves.
3526 + *
3527 + * @deprecated 16.2
3528 + *
3139 3529 * @param bool $encode JSON encode the result.
3140 3530 * @param bool $extended Adds additional stats data.
3141 3531 *
3142 3532 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3141,10 +3531,15 @@
3141 3531 *
3142 3532 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3143 3533 */
3144 3534 public static function get_stat_data( $encode = true, $extended = true ) {
3145 - $data = Jetpack_Heartbeat::generate_stats_array();
3535 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3146 3536
3537 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
3538 + ? Heartbeat::get_environment_stats()
3539 + : array();
3540 + $data = array_merge( Jetpack_Heartbeat::generate_stats_array(), $env_stats );
3541 +
3147 3542 if ( $extended ) {
3148 3543 $additional_data = self::get_additional_stat_data();
3149 3544 $data = array_merge( $data, $additional_data );
3150 3545 }
@@ -3149,9 +3544,9 @@
3149 3544 $data = array_merge( $data, $additional_data );
3150 3545 }
3151 3546
3152 3547 if ( $encode ) {
3153 - return wp_json_encode( $data );
3548 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3154 3549 }
3155 3550
3156 3551 return $data;
3157 3552 }
@@ -3230,8 +3625,9 @@
3230 3625 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3231 3626 // Upgrade: 1.1 -> 1.1.1
3232 3627 // Check and see if host can verify the Jetpack servers' SSL certificate.
3233 3628 $args = array();
3629 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3234 3630 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3235 3631 }
3236 3632
3237 3633 if (
@@ -3248,12 +3644,8 @@
3248 3644 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3249 3645 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3250 3646 }
3251 3647
3252 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3253 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3254 - }
3255 -
3256 3648 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3257 3649
3258 3650 if ( self::is_connection_ready() || $is_offline_mode ) {
3259 3651 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3292,8 +3684,9 @@
3292 3684 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3293 3685 * This function artificially throws errors for such cases (per a specific list).
3294 3686 *
3295 3687 * @param string $plugin The activated plugin.
3688 + * @throws RuntimeException If a conflicting plugin is detected.
3296 3689 */
3297 3690 public function throw_error_on_activate_plugin( $plugin ) {
3298 3691 $active_modules = self::get_active_modules();
3299 3692
@@ -3315,9 +3708,9 @@
3315 3708 }
3316 3709
3317 3710 if ( $throw ) {
3318 3711 /* translators: Plugin name to deactivate. */
3319 - 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
3712 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3320 3713 }
3321 3714 }
3322 3715 }
3323 3716
@@ -3428,18 +3821,17 @@
3428 3821 if ( ! is_int( $status_code ) ) {
3429 3822 $status_code = 400;
3430 3823 }
3431 3824
3432 - status_header( $status_code );
3433 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3825 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3434 3826 }
3435 3827
3436 - status_header( 200 );
3437 3828 if ( true === $response ) {
3438 - exit;
3829 + status_header( 200 );
3830 + exit( 0 );
3439 3831 }
3440 3832
3441 - die( wp_json_encode( (object) $response ) );
3833 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3442 3834 }
3443 3835
3444 3836 /**
3445 3837 * Uploads a file gotten from the global $_FILES.
@@ -3500,9 +3892,9 @@
3500 3892 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3501 3893 }
3502 3894
3503 3895 $uploaded_files = array();
3504 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3896 + $global_post = $GLOBALS['post'] ?? null;
3505 3897 unset( $GLOBALS['post'] );
3506 3898 if ( empty( $_FILES['media']['name'] ) ) {
3507 3899 // Nothing to process, just return.
3508 3900 return $uploaded_files;
@@ -3509,9 +3901,9 @@
3509 3901 }
3510 3902 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.
3511 3903 $file = array();
3512 3904 foreach ( $media_keys as $media_key ) {
3513 - $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.
3905 + $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.
3514 3906 }
3515 3907
3516 3908 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.
3517 3909
@@ -3557,9 +3949,9 @@
3557 3949 'type' => (string) $edited_media_item->post_mime_type,
3558 3950 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3559 3951 );
3560 3952
3561 - return (array) array( $response );
3953 + return array( $response );
3562 3954 }
3563 3955
3564 3956 $attachment_id = media_handle_upload(
3565 3957 '.jetpack.upload.',
@@ -3598,58 +3990,8 @@
3598 3990 return $uploaded_files;
3599 3991 }
3600 3992
3601 3993 /**
3602 - * Add help to the Jetpack page
3603 - *
3604 - * @since Jetpack (1.2.3)
3605 - * @return void
3606 - */
3607 - public function admin_help() {
3608 - $current_screen = get_current_screen();
3609 -
3610 - // Overview.
3611 - $current_screen->add_help_tab(
3612 - array(
3613 - 'id' => 'home',
3614 - 'title' => __( 'Home', 'jetpack' ),
3615 - 'content' =>
3616 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3617 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3618 - '<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>',
3619 - )
3620 - );
3621 -
3622 - // Screen Content.
3623 - if ( current_user_can( 'manage_options' ) ) {
3624 - $current_screen->add_help_tab(
3625 - array(
3626 - 'id' => 'settings',
3627 - 'title' => __( 'Settings', 'jetpack' ),
3628 - 'content' =>
3629 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3630 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3631 - '<ol>' .
3632 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3633 - '<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>' .
3634 - '</ol>' .
3635 - '<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>',
3636 - )
3637 - );
3638 - }
3639 -
3640 - // Help Sidebar.
3641 - $support_url = Redirect::get_url( 'jetpack-support' );
3642 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3643 - $current_screen->set_help_sidebar(
3644 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3645 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3646 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3647 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3648 - );
3649 - }
3650 -
3651 - /**
3652 3994 * Add action links for the Jetpack plugin.
3653 3995 *
3654 3996 * @param array $actions Plugin actions.
3655 3997 *
@@ -3657,9 +3999,9 @@
3657 3999 */
3658 4000 public function plugin_action_links( $actions ) {
3659 4001 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3660 4002 return array_merge(
3661 - array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
4003 + array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack-settings#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3662 4004 $actions
3663 4005 );
3664 4006 }
3665 4007
@@ -3687,14 +4029,10 @@
3687 4029 'jetpack-plugins-page-js',
3688 4030 '_inc/build/plugins-page.js',
3689 4031 JETPACK__PLUGIN_FILE,
3690 4032 array(
3691 - 'in_footer' => true,
3692 - 'textdomain' => 'jetpack',
3693 - 'dependencies' => array(
3694 - 'wp-polyfill',
3695 - 'wp-components',
3696 - ),
4033 + 'in_footer' => true,
4034 + 'textdomain' => 'jetpack',
3697 4035 )
3698 4036 );
3699 4037 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3700 4038
@@ -3699,9 +4037,9 @@
3699 4037 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3700 4038
3701 4039 // Add objects to be passed to the initial state of the app.
3702 4040 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3703 - 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' );
4041 + 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' );
3704 4042
3705 4043 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3706 4044 }
3707 4045 }
@@ -3750,9 +4088,9 @@
3750 4088 // @todo provide way to go to specific calypso env.
3751 4089 self::get_calypso_host() . 'jetpack/connect'
3752 4090 )
3753 4091 );
3754 - exit;
4092 + exit( 0 );
3755 4093 }
3756 4094 }
3757 4095
3758 4096 /*
@@ -3787,8 +4125,28 @@
3787 4125 * Done!
3788 4126 */
3789 4127
3790 4128 /**
4129 + * Build the user-facing description stored alongside a registration error code.
4130 + *
4131 + * @since 16.2
4132 + *
4133 + * @param string $error_code The WP_Error code.
4134 + * @param string $message The WP_Error message.
4135 + * @return string The description, empty when the message is not user-facing copy.
4136 + */
4137 + public static function get_registration_error_description( $error_code, $message ) {
4138 + // Manager::validate_remote_register_response() does not always put user-facing copy in the
4139 + // message slot: wpcom_5??, wpcom_408 and wpcom_bad_response store the HTTP status there,
4140 + // and jetpack_id stores the raw response body, which can also overflow the state cookie.
4141 + if ( 'jetpack_id' === $error_code || is_numeric( $message ) ) {
4142 + return '';
4143 + }
4144 +
4145 + return mb_substr( (string) $message, 0, 250 );
4146 + }
4147 +
4148 + /**
3791 4149 * Handles the page load events for the Jetpack admin page
3792 4150 */
3793 4151 public function admin_page_load() {
3794 4152 $error = false;
@@ -3824,10 +4182,11 @@
3824 4182 $registered = static::connection()->try_registration();
3825 4183 if ( is_wp_error( $registered ) ) {
3826 4184 $error = $registered->get_error_code();
3827 4185 self::state( 'error', $error );
3828 - self::state( 'error', $registered->get_error_message() );
3829 4186
4187 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4188 +
3830 4189 /**
3831 4190 * Jetpack registration Error.
3832 4191 *
3833 4192 * @since 7.5.0
@@ -3851,12 +4210,8 @@
3851 4210 do_action( 'jetpack_connection_register_success', $from );
3852 4211
3853 4212 $url = $this->build_connect_url( true, $redirect, $from );
3854 4213
3855 - if ( ! empty( $_GET['onboarding'] ) ) {
3856 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3857 - }
3858 -
3859 4214 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3860 4215 $url = add_query_arg( 'auth_approved', 'true', $url );
3861 4216 }
3862 4217
@@ -3861,9 +4216,9 @@
3861 4216 }
3862 4217
3863 4218 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3864 4219 wp_safe_redirect( $url );
3865 - exit;
4220 + exit( 0 );
3866 4221 case 'activate':
3867 4222 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3868 4223 $error = 'cheatin';
3869 4224 break;
@@ -3877,9 +4232,9 @@
3877 4232 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3878 4233 }
3879 4234 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3880 4235 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3881 - exit;
4236 + exit( 0 );
3882 4237 case 'activate_default_modules':
3883 4238 check_admin_referer( 'activate_default_modules' );
3884 4239 self::log( 'activate_default_modules' );
3885 4240 self::restate();
@@ -3887,9 +4242,9 @@
3887 4242 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3888 4243 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3889 4244 self::activate_default_modules( $min_version, $max_version, $other_modules );
3890 4245 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3891 - exit;
4246 + exit( 0 );
3892 4247 case 'disconnect':
3893 4248 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3894 4249 $error = 'cheatin';
3895 4250 break;
@@ -3898,9 +4253,9 @@
3898 4253 check_admin_referer( 'jetpack-disconnect' );
3899 4254 self::log( 'disconnect' );
3900 4255 self::disconnect();
3901 4256 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3902 - exit;
4257 + exit( 0 );
3903 4258 case 'reconnect':
3904 4259 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3905 4260 $error = 'cheatin';
3906 4261 break;
@@ -3911,9 +4266,9 @@
3911 4266 self::disconnect();
3912 4267
3913 4268 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3914 4269 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3915 - exit;
4270 + exit( 0 );
3916 4271 case 'deactivate':
3917 4272 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3918 4273 $error = 'cheatin';
3919 4274 break;
@@ -3927,9 +4282,9 @@
3927 4282 self::state( 'message', 'module_deactivated' );
3928 4283 }
3929 4284 self::state( 'module', $modules );
3930 4285 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3931 - exit;
4286 + exit( 0 );
3932 4287 case 'unlink':
3933 4288 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3934 4289 check_admin_referer( 'jetpack-unlink' );
3935 4290 self::log( 'unlink' );
@@ -3939,32 +4294,9 @@
3939 4294 wp_safe_redirect( admin_url() );
3940 4295 } else {
3941 4296 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3942 4297 }
3943 - exit;
3944 - case 'onboard':
3945 - if ( ! current_user_can( 'manage_options' ) ) {
3946 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3947 - } else {
3948 - self::create_onboarding_token();
3949 - $url = $this->build_connect_url( true );
3950 -
3951 - $token = Jetpack_Options::get_option( 'onboarding' );
3952 -
3953 - if ( false !== ( $token ) ) {
3954 - $url = add_query_arg( 'onboarding', $token, $url );
3955 - }
3956 -
3957 - $calypso_env = ( new Host() )->get_calypso_env();
3958 - if ( ! empty( $calypso_env ) ) {
3959 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
3960 - }
3961 -
3962 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3963 - wp_safe_redirect( $url );
3964 - exit;
3965 - }
3966 - exit;
4298 + exit( 0 );
3967 4299 default:
3968 4300 /**
3969 4301 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3970 4302 *
@@ -4187,37 +4519,8 @@
4187 4519 endif;
4188 4520 }
4189 4521
4190 4522 /**
4191 - * We can't always respond to a signed XML-RPC request with a
4192 - * helpful error message. In some circumstances, doing so could
4193 - * leak information.
4194 - *
4195 - * Instead, track that the error occurred via a Jetpack_Option,
4196 - * and send that data back in the heartbeat.
4197 - * All this does is increment a number, but it's enough to find
4198 - * trends.
4199 - *
4200 - * @param WP_Error $xmlrpc_error The error produced during
4201 - * signature validation.
4202 - */
4203 - public function track_xmlrpc_error( $xmlrpc_error ) {
4204 - $code = is_wp_error( $xmlrpc_error )
4205 - ? $xmlrpc_error->get_error_code()
4206 - : 'should-not-happen';
4207 -
4208 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4209 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4210 - // No need to update the option if we already have
4211 - // this code stored.
4212 - return;
4213 - }
4214 - $xmlrpc_errors[ $code ] = true;
4215 -
4216 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4217 - }
4218 -
4219 - /**
4220 4523 * Initialize the jetpack stats instance only when needed
4221 4524 *
4222 4525 * @return void
4223 4526 */
@@ -4503,11 +4806,8 @@
4503 4806 *
4504 4807 * @param array $data The request data.
4505 4808 */
4506 4809 public static function authorize_ending_authorized( $data ) {
4507 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4508 - self::invalidate_onboarding_token();
4509 -
4510 4810 // If redirect_uri is SSO, ensure SSO module is enabled.
4511 4811 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4512 4812
4513 4813 /** This filter is documented in class.jetpack-cli.php */
@@ -4647,10 +4947,12 @@
4647 4947 $result = self::permit_ssl( true );
4648 4948 wp_send_json(
4649 4949 array(
4650 4950 'enabled' => $result,
4651 - 'message' => get_transient( 'jetpack_https_test_message' ),
4652 - )
4951 + 'message' => self::get_ssl_test_message(),
4952 + ),
4953 + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
4954 + JSON_UNESCAPED_SLASHES
4653 4955 );
4654 4956 }
4655 4957
4656 4958 /* Client API */
@@ -4657,8 +4959,10 @@
4657 4959
4658 4960 /**
4659 4961 * Verify the onboarding token.
4660 4962 *
4963 + * @deprecated since 13.9
4964 + *
4661 4965 * @param array $token_data Token data.
4662 4966 * @param string $token Token value.
4663 4967 * @param string $request_data JSON-encoded request data.
4664 4968 *
@@ -4664,8 +4968,9 @@
4664 4968 *
4665 4969 * @return mixed
4666 4970 */
4667 4971 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4972 + _deprecated_function( __METHOD__, '13.9' );
4668 4973 // Default to a blog token.
4669 4974 $token_type = 'blog';
4670 4975
4671 4976 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4693,9 +4998,9 @@
4693 4998 $jp_user = get_user_by( 'email', $jpo_user );
4694 4999 if ( is_a( $jp_user, 'WP_User' ) ) {
4695 5000 wp_set_current_user( $jp_user->ID );
4696 5001 $user_can = is_multisite()
4697 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5002 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4698 5003 : current_user_can( 'manage_options' );
4699 5004 if ( $user_can ) {
4700 5005 $token_type = 'user';
4701 5006 $token->external_user_id = $jp_user->ID;
@@ -4712,11 +5017,13 @@
4712 5017
4713 5018 /**
4714 5019 * Create a random secret for validating onboarding payload
4715 5020 *
5021 + * @deprecated since 13.9
4716 5022 * @return string Secret token
4717 5023 */
4718 5024 public static function create_onboarding_token() {
5025 + _deprecated_function( __METHOD__, '13.9' );
4719 5026 $token = Jetpack_Options::get_option( 'onboarding' );
4720 5027 if ( false === ( $token ) ) {
4721 5028 $token = wp_generate_password( 32, false );
4722 5029 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4727,11 +5034,13 @@
4727 5034
4728 5035 /**
4729 5036 * Remove the onboarding token
4730 5037 *
5038 + * @deprecated since 13.9
4731 5039 * @return bool True on success, false on failure
4732 5040 */
4733 5041 public static function invalidate_onboarding_token() {
5042 + _deprecated_function( __METHOD__, '13.9' );
4734 5043 return Jetpack_Options::delete_option( 'onboarding' );
4735 5044 }
4736 5045
4737 5046 /**
@@ -4736,8 +5045,10 @@
4736 5045
4737 5046 /**
4738 5047 * Validate an onboarding token for a specific action
4739 5048 *
5049 + * @deprecated since 13.9
5050 + *
4740 5051 * @param string $token Onboarding token.
4741 5052 * @param string $action Action name.
4742 5053 *
4743 5054 * @return boolean True if token/action pair is accepted, false if not
@@ -4742,8 +5053,9 @@
4742 5053 *
4743 5054 * @return boolean True if token/action pair is accepted, false if not
4744 5055 */
4745 5056 public static function validate_onboarding_token_action( $token, $action ) {
5057 + _deprecated_function( __METHOD__, '13.9' );
4746 5058 // Compare tokens, bail if tokens do not match.
4747 5059 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4748 5060 return false;
4749 5061 }
@@ -4769,39 +5081,41 @@
4769 5081 * @return boolean
4770 5082 * @since 2.3.3
4771 5083 */
4772 5084 public static function permit_ssl( $force_recheck = false ) {
4773 - // Do some fancy tests to see if ssl is being supported.
4774 - if ( ! $force_recheck ) {
4775 - $ssl = get_transient( 'jetpack_https_test' );
5085 + if ( ! method_exists( Heartbeat::class, 'permit_ssl' ) ) {
5086 + // Skip the SSL-fail notice when the check cannot run.
5087 + return true;
4776 5088 }
4777 5089
4778 - if ( $force_recheck || false === $ssl ) {
4779 - $message = '';
4780 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4781 - $ssl = 0;
4782 - } else {
4783 - $ssl = 1;
5090 + return Heartbeat::permit_ssl( $force_recheck );
5091 + }
4784 5092
4785 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4786 - $ssl = 0;
4787 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4788 - } else {
4789 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4790 - if ( is_wp_error( $response ) ) {
4791 - $ssl = 0;
4792 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4793 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4794 - $ssl = 0;
4795 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4796 - }
4797 - }
4798 - }
4799 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4800 - set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5093 + /**
5094 + * Returns a localized message describing the last SSL connectivity failure, if any.
5095 + *
5096 + * The Connection package's canonical SSL check stores a neutral reason code; this maps it to
5097 + * a translated, `jetpack`-domain message for display in the admin notice and AJAX recheck.
5098 + *
5099 + * @since 16.1
5100 + *
5101 + * @return string The localized message, or an empty string when there is no failure.
5102 + */
5103 + public static function get_ssl_test_message() {
5104 + if ( ! method_exists( Heartbeat::class, 'get_ssl_test_error' ) ) {
5105 + return '';
4801 5106 }
4802 5107
4803 - return (bool) $ssl;
5108 + $error = Heartbeat::get_ssl_test_error();
5109 +
5110 + switch ( $error['code'] ) {
5111 + case 'no_ssl_support':
5112 + return __( 'WordPress reports no SSL support', 'jetpack' );
5113 + case 'bad_response':
5114 + return __( 'Response was not OK: ', 'jetpack' ) . $error['detail'];
5115 + default:
5116 + return '';
5117 + }
4804 5118 }
4805 5119
4806 5120 /**
4807 5121 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -4820,9 +5134,9 @@
4820 5134 <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>
4821 5135 <p>
4822 5136 <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4823 5137 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4824 - <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
5138 + <span id="jetpack-recheck-ssl-output"><?php echo esc_html( self::get_ssl_test_message() ); ?></span>
4825 5139 </p>
4826 5140 <p>
4827 5141 <?php
4828 5142 printf(
@@ -4841,18 +5155,18 @@
4841 5155 <script type="text/javascript">
4842 5156 jQuery( document ).ready( function( $ ) {
4843 5157 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4844 5158 var $this = $( this );
4845 - $this.html( <?php echo wp_json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5159 + $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4846 5160 $( '#jetpack-recheck-ssl-output' ).html( '' );
4847 5161 e.preventDefault();
4848 - var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce ); ?> };
5162 + var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
4849 5163 $.post( ajaxurl, data )
4850 5164 .done( function( response ) {
4851 5165 if ( response.enabled ) {
4852 5166 $( '#jetpack-ssl-warning' ).hide();
4853 5167 } else {
4854 - this.html( <?php echo wp_json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5168 + this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4855 5169 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4856 5170 }
4857 5171 }.bind( $this ) );
4858 5172 } );
@@ -5210,15 +5524,20 @@
5210 5524
5211 5525 /**
5212 5526 * Checks if the site is currently in an identity crisis.
5213 5527 *
5528 + * Now delegates to the Connection package so this matches what the heartbeat itself reports.
5529 + * Note the package guards on `Connection\Manager::is_connected()` where this used to guard on
5530 + * `Jetpack::is_connection_ready()`, so the `jetpack_is_connection_ready` filter no longer applies.
5531 + *
5532 + * @deprecated 16.2
5533 + *
5214 5534 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5215 5535 */
5216 5536 public static function check_identity_crisis() {
5217 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5218 - return false;
5219 - }
5220 - return Jetpack_Options::get_option( 'sync_error_idc' );
5537 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5538 +
5539 + return Identity_Crisis::check_identity_crisis();
5221 5540 }
5222 5541
5223 5542 /**
5224 5543 * Normalizes a url by doing three things:
@@ -5306,9 +5625,9 @@
5306 5625 *
5307 5626 * @return mixed
5308 5627 */
5309 5628 public static function set_suffix_on_min( $src, $handle ) {
5310 - if ( ! str_contains( $src, '.min.css' ) ) {
5629 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5311 5630 return $src;
5312 5631 }
5313 5632
5314 5633 if ( ! empty( self::$min_assets ) ) {
@@ -5365,8 +5684,19 @@
5365 5684 return false;
5366 5685 }
5367 5686
5368 5687 /**
5688 + * Register Jetpack-specific tests on the connection package's health test suite.
5689 + *
5690 + * @param \Automattic\Jetpack\Connection\Connection_Health_Tests $connection_tests The test suite instance.
5691 + */
5692 + public function register_jetpack_connection_tests( $connection_tests ) {
5693 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger/class-jetpack-cxn-tests.php';
5694 + $jetpack_tests = new Jetpack_Cxn_Tests();
5695 + $jetpack_tests->register_tests_on( $connection_tests );
5696 + }
5697 +
5698 + /**
5369 5699 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5370 5700 */
5371 5701 public function deprecated_hooks() {
5372 5702 $filter_deprecated_list = array(
@@ -5590,8 +5920,10 @@
5590 5920 'jetpack_contact_form_use_package' => array(
5591 5921 'replacement' => null,
5592 5922 'version' => 'jetpack-13.4.0',
5593 5923 ),
5924 + // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
5925 + // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
5594 5926 );
5595 5927
5596 5928 foreach ( $filter_deprecated_list as $tag => $args ) {
5597 5929 if ( has_filter( $tag ) ) {
@@ -5722,125 +6054,8 @@
5722 6054 return $css;
5723 6055 }
5724 6056
5725 6057 /**
5726 - * This methods removes all of the registered css files on the front end
5727 - * from Jetpack in favor of using a single file. In effect "imploding"
5728 - * all the files into one file.
5729 - *
5730 - * Pros:
5731 - * - Uses only ONE css asset connection instead of 15
5732 - * - Saves a minimum of 56k
5733 - * - Reduces server load
5734 - * - Reduces time to first painted byte
5735 - *
5736 - * Cons:
5737 - * - Loads css for ALL modules. However all selectors are prefixed so it
5738 - * should not cause any issues with themes.
5739 - * - Plugins/themes dequeuing styles no longer do anything. See
5740 - * jetpack_implode_frontend_css filter for a workaround
5741 - *
5742 - * For some situations developers may wish to disable css imploding and
5743 - * instead operate in legacy mode where each file loads seperately and
5744 - * can be edited individually or dequeued. This can be accomplished with
5745 - * the following line:
5746 - *
5747 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5748 - *
5749 - * @param bool $travis_test Is this a test run.
5750 - *
5751 - * @since 3.2
5752 - */
5753 - public function implode_frontend_css( $travis_test = false ) {
5754 - $do_implode = true;
5755 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5756 - $do_implode = false;
5757 - }
5758 -
5759 - // Do not implode CSS when the page loads via the AMP plugin.
5760 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
5761 - $do_implode = false;
5762 - }
5763 -
5764 - /*
5765 - * Only proceed if at least 2 modules with concatenated CSS are active.
5766 - * There is no point in serving a big concatenated CSS file
5767 - * if there are no features (or only one) that actually need some CSS loaded.
5768 - */
5769 - $active_modules = self::get_active_modules();
5770 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
5771 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
5772 - if ( $active_module_with_css_count < 2 ) {
5773 - $do_implode = false;
5774 - }
5775 -
5776 - /**
5777 - * Allow CSS to be concatenated into a single jetpack.css file.
5778 - *
5779 - * @since 3.2.0
5780 - *
5781 - * @param bool $do_implode Should CSS be concatenated? Default to true.
5782 - */
5783 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5784 -
5785 - // Do not use the imploded file when default behavior was altered through the filter.
5786 - if ( ! $do_implode ) {
5787 - return;
5788 - }
5789 -
5790 - // We do not want to use the imploded file in dev mode, or if not connected.
5791 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
5792 - if ( ! $travis_test ) {
5793 - return;
5794 - }
5795 - }
5796 -
5797 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
5798 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
5799 - return;
5800 - }
5801 -
5802 - /*
5803 - * Now we assume Jetpack is connected and able to serve the single
5804 - * file.
5805 - *
5806 - * In the future there will be a check here to serve the file locally
5807 - * or potentially from the Jetpack CDN
5808 - *
5809 - * For now:
5810 - * - Enqueue a single imploded css file
5811 - * - Zero out the style_loader_tag for the bundled ones
5812 - * - Be happy, drink scotch
5813 - */
5814 -
5815 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
5816 -
5817 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
5818 -
5819 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
5820 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
5821 - }
5822 -
5823 - /**
5824 - * Removes styles that are part of concatenated group.
5825 - *
5826 - * @param string $tag Style tag.
5827 - * @param string $handle Style handle.
5828 - *
5829 - * @return string
5830 - */
5831 - public function concat_remove_style_loader_tag( $tag, $handle ) {
5832 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
5833 - $tag = '';
5834 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5835 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
5836 - }
5837 - }
5838 -
5839 - return $tag;
5840 - }
5841 -
5842 - /**
5843 6058 * Check the heartbeat data
5844 6059 *
5845 6060 * Organizes the heartbeat data by severity. For example, if the site
5846 6061 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -5852,9 +6067,23 @@
5852 6067 *
5853 6068 * $return array $filtered_data
5854 6069 */
5855 6070 public static function jetpack_check_heartbeat_data() {
5856 - $raw_data = Jetpack_Heartbeat::generate_stats_array();
6071 + /*
6072 + * Site environment stats (incl. wp-version/php-version checked below) now live in the Connection package,
6073 + * and the IDC stat is contributed by the Connection package's `jetpack_heartbeat_stats_array` filter
6074 + * callback. We rebuild the stat here rather than running that filter: the filter's callbacks have side
6075 + * effects (Connection\Manager::add_stats_to_heartbeat() consumes and deletes the `xmlrpc_errors` option)
6076 + * and return non-scalar values, neither of which is appropriate for this read-only diagnostic.
6077 + */
6078 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
6079 + ? Heartbeat::get_environment_stats()
6080 + : array();
6081 + $raw_data = array_merge(
6082 + Jetpack_Heartbeat::generate_stats_array(),
6083 + $env_stats,
6084 + array( 'identitycrisis' => Identity_Crisis::check_identity_crisis() ? 'yes' : 'no' )
6085 + );
5857 6086
5858 6087 $good = array();
5859 6088 $caution = array();
5860 6089 $bad = array();
@@ -6113,13 +6342,20 @@
6113 6342 }
6114 6343 }
6115 6344
6116 6345 /**
6117 - * Returns a boolean for whether backups UI should be displayed or not.
6346 + * Whether UI for backups should be displayed.
6118 6347 *
6348 + * On WPCom platforms this is gated on the backups-self-serve site feature.
6349 + * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
6350 + *
6119 6351 * @return bool Should backups UI be displayed?
6120 6352 */
6121 6353 public static function show_backups_ui() {
6354 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6355 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
6356 + }
6357 +
6122 6358 /**
6123 6359 * Whether UI for backups should be displayed.
6124 6360 *
6125 6361 * @since 6.5.0
@@ -6129,8 +6365,24 @@
6129 6365 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6130 6366 }
6131 6367
6132 6368 /**
6369 + * Whether UI for security scanning should be displayed.
6370 + *
6371 + * On WPCom platforms this is gated on the scan-self-serve site feature.
6372 + * On self-hosted Jetpack sites it always returns true.
6373 + *
6374 + * @return bool Should scan UI be displayed?
6375 + */
6376 + public static function show_scan_ui() {
6377 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6378 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
6379 + }
6380 +
6381 + return true;
6382 + }
6383 +
6384 + /**
6133 6385 * Clean leftoveruser meta.
6134 6386 *
6135 6387 * Delete Jetpack-related user meta when it is no longer needed.
6136 6388 *
@@ -6217,8 +6469,25 @@
6217 6469 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6218 6470 ),
6219 6471 );
6220 6472
6473 + $products['growth'] = array(
6474 + 'title' => __( 'Jetpack Growth', 'jetpack' ),
6475 + 'slug' => 'jetpack_growth_yearly',
6476 + 'description' => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
6477 + 'show_promotion' => true,
6478 + 'discount_percent' => 50,
6479 + 'included_in_plans' => array( 'complete' ),
6480 + 'features' => array(
6481 + _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
6482 + _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
6483 + _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
6484 + _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
6485 + _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
6486 + _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
6487 + ),
6488 + );
6489 +
6221 6490 $products['scan'] = array(
6222 6491 'title' => __( 'Jetpack Scan', 'jetpack' ),
6223 6492 'slug' => 'jetpack_scan',
6224 6493 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6365,8 +6634,56 @@
6365 6634 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6366 6635 . '</a>';
6367 6636
6368 6637 return $plugin_meta;
6638 + }
6639 +
6640 + /**
6641 + * Lazy instantiation of the Plugin_Tracking object.
6642 + *
6643 + * @since 13.9
6644 + *
6645 + * @return void
6646 + */
6647 + public function initialize_tracking() {
6648 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6649 + // Only need to run once.
6650 + return;
6651 + }
6652 +
6653 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6654 + ( new Plugin_Tracking() )->init();
6655 + }
6656 + }
6657 +
6658 + /**
6659 + * Run the "initialize tracking" hook.
6660 + *
6661 + * @since 13.9
6662 + */
6663 + public function run_initialize_tracking_action() {
6664 + /**
6665 + * Fires when the tracking needs to be initialized.
6666 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6667 + *
6668 + * @since 13.9
6669 + */
6670 + do_action( 'jetpack_initialize_tracking' );
6671 + }
6672 +
6673 + /**
6674 + * Initialize REST jsonAPI if needed.
6675 + *
6676 + * @return void
6677 + */
6678 + public function maybe_initialize_rest_jsonapi() {
6679 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6680 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6681 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6682 +
6683 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6684 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6685 + }
6369 6686 }
6370 6687
6371 6688 /**
6372 6689 * Run plugin post-activation actions if we need to.