PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
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 +808 -549 13.4.5 → 16.3-a.5 View file →
@@ -21,16 +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;
35 +use Automattic\Jetpack\Plugin\Deprecate;
31 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
32 38 use Automattic\Jetpack\Redirect;
39 +use Automattic\Jetpack\Scan_Page\Jetpack_Scan as Scan_Page_Init;
40 +use Automattic\Jetpack\SEO\Initializer as Jetpack_SEO_Initializer;
33 41 use Automattic\Jetpack\Status;
34 42 use Automattic\Jetpack\Status\Host;
35 43 use Automattic\Jetpack\Status\Visitor;
36 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -37,9 +45,14 @@
37 45 use Automattic\Jetpack\Sync\Health;
38 46 use Automattic\Jetpack\Sync\Sender;
39 47 use Automattic\Jetpack\Terms_Of_Service;
40 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
41 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
42 55 /*
43 56 Options:
44 57 jetpack_options (array)
45 58 An array of options.
@@ -74,74 +87,8 @@
74 87 */
75 88 public $xmlrpc_server = null;
76 89
77 90 /**
78 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
79 - *
80 - * See $concatenated_style_handles for the list of handles,
81 - * and the implode_frontend_css method for more details.
82 - *
83 - * When updating this list, make sure to update $concatenated_style_handles as well.
84 - *
85 - * @var array List of Jetpack modules.
86 - */
87 - public $modules_with_concatenated_css = array(
88 - 'carousel',
89 - 'contact-form',
90 - 'infinite-scroll',
91 - 'likes',
92 - 'related-posts',
93 - 'sharedaddy',
94 - 'shortcodes',
95 - 'subscriptions',
96 - 'tiled-gallery',
97 - 'widgets',
98 - );
99 -
100 - /**
101 - * The handles of styles that are concatenated into jetpack.css.
102 - *
103 - * When making changes to that list,
104 - * you must also update concat_list in tools/webpack.config.css.js,
105 - * and to $modules_with_concatenated_css if necessary.
106 - *
107 - * @var array The handles of styles that are concatenated into jetpack.css.
108 - */
109 - public $concatenated_style_handles = array(
110 - 'jetpack-carousel-swiper-css',
111 - 'jetpack-carousel',
112 - 'grunion.css',
113 - 'the-neverending-homepage',
114 - 'jetpack_likes',
115 - 'jetpack_related-posts',
116 - 'sharedaddy',
117 - 'jetpack-slideshow',
118 - 'presentations',
119 - 'quiz',
120 - 'jetpack-subscriptions',
121 - 'jetpack-responsive-videos',
122 - 'jetpack-social-menu',
123 - 'tiled-gallery',
124 - 'jetpack_display_posts_widget',
125 - 'gravatar-profile-widget',
126 - 'goodreads-widget',
127 - 'jetpack_social_media_icons_widget',
128 - 'jetpack-top-posts-widget',
129 - 'jetpack_image_widget',
130 - 'jetpack-my-community-widget',
131 - 'jetpack-authors-widget',
132 - 'wordads',
133 - 'eu-cookie-law-style',
134 - 'flickr-widget-style',
135 - 'jetpack-search-widget',
136 - 'jetpack-simple-payments-widget-style',
137 - 'jetpack-widget-social-icons-styles',
138 - 'wpcom_instagram_widget',
139 - 'milestone-widget',
140 - 'subscribe-modal-css',
141 - );
142 -
143 - /**
144 91 * Contains all assets that have had their URL rewritten to minified versions.
145 92 *
146 93 * @var array
147 94 */
@@ -156,11 +103,8 @@
156 103 'contact-form' => array(
157 104 array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
158 105 array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
159 106 ),
160 - 'custom-css' => array(
161 - array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
162 - ),
163 107 'gravatar-hovercards' => array(
164 108 array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
165 109 ),
166 110 'latex' => array(
@@ -331,9 +275,8 @@
331 275 * Graph tags via filter when their Social Meta modules are active:
332 276 *
333 277 * - All in One SEO Pack, All in one SEO Pack Pro
334 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
335 - * - SEOPress, SEOPress Pro
336 279 *
337 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:
338 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
339 282 *
@@ -376,8 +319,10 @@
376 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
377 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
378 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
379 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
380 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
381 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
382 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
383 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -488,8 +433,16 @@
488 433 */
489 434 public static $instance = false;
490 435
491 436 /**
437 + * Resolved answer for `is_premium_analytics_enabled()`, or null before the first call.
438 + *
439 + * @since 16.1
440 + * @var bool|null
441 + */
442 + private static $premium_analytics_enabled = null;
443 +
444 + /**
492 445 * Singleton
493 446 *
494 447 * @static
495 448 */
@@ -529,9 +482,9 @@
529 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
530 483 self::update_active_modules( $modules );
531 484 }
532 485
533 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
534 487
535 488 // Upgrade to 4.3.0.
536 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
537 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -586,8 +539,16 @@
586 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
587 540 } // Should we have some type of fallback if something fails here?
588 541 }
589 542
543 + // Set the newsletter send default option for existing sites.
544 + if ( false === get_option( 'wpcom_newsletter_send_default' ) ) {
545 + add_option( 'wpcom_newsletter_send_default', 1 );
546 + }
547 +
548 + // Its handler went with the Recommendations assistant.
549 + wp_clear_scheduled_hook( 'jetpack_recommend_videopress' );
550 +
590 551 if ( did_action( 'wp_loaded' ) ) {
591 552 self::upgrade_on_load();
592 553 } else {
593 554 add_action(
@@ -621,9 +582,8 @@
621 582 }
622 583
623 584 if (
624 585 class_exists( 'Jetpack_Sitemap_Manager' )
625 - && version_compare( JETPACK__VERSION, '5.3', '>=' )
626 586 ) {
627 587 do_action( 'jetpack_sitemaps_purge_data' );
628 588 }
629 589
@@ -694,35 +654,15 @@
694 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
695 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
696 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
697 657
698 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
699 -
700 - add_filter(
701 - 'jetpack_signature_check_token',
702 - array( __CLASS__, 'verify_onboarding_token' ),
703 - 10,
704 - 3
705 - );
706 -
707 658 /**
708 659 * Prepare Gutenberg Editor functionality
660 + *
661 + * The hooks previously here have been moved to modules/blocks.php but leaving this here pending
662 + * a longer investigation to see if code is expecting the Gutenberg class to always be available.
709 663 */
710 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
711 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
712 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
713 - /**
714 - * We've switched from enqueue_block_editor_assets to enqueue_block_assets in WP-Admin because the assets with the former are loaded on the main site-editor.php.
715 - *
716 - * With the latter, the assets are now loaded in the SE iframe; the implementation is now faster because Gutenberg doesn't need to inject the assets in the iframe on client-side.
717 - */
718 - if ( is_admin() ) {
719 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
720 - } else {
721 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
722 - }
723 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
724 -
725 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
726 666
727 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
728 668
@@ -731,8 +671,13 @@
731 671
732 672 // Set up the REST authentication hooks.
733 673 Connection_Rest_Authentication::init();
734 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 +
735 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
736 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
737 682
738 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -765,25 +710,8 @@
765 710
766 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
767 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
768 713
769 - /*
770 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
771 - * We should make sure to only do this for front end links.
772 - */
773 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
774 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
775 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
776 -
777 - /*
778 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
779 - * so they point moderation links on emails to Calypso.
780 - */
781 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
782 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
783 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
784 - }
785 -
786 714 add_action(
787 715 'plugins_loaded',
788 716 function () {
789 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -794,18 +722,10 @@
794 722
795 723 // Update the site's Jetpack plan and products from API on heartbeats.
796 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
797 725
798 - /**
799 - * This is the hack to concatenate all css files into one.
800 - * For description and reasoning see the implode_frontend_css method.
801 - *
802 - * Super late priority so we catch all the registered styles.
803 - */
804 - if ( ! is_admin() ) {
805 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
806 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
807 - }
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' ) );
808 728
809 729 // Actually push the stats on shutdown.
810 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
811 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -813,8 +733,10 @@
813 733
814 734 // After a successful connection.
815 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
816 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' ) );
817 739
818 740 // Actions for Manager::authorize().
819 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
820 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -854,16 +776,142 @@
854 776
855 777 // Register product descriptions for partner coupon usage.
856 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
857 779
858 - // Actions for conditional recommendations.
859 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
860 -
861 780 // Add 5-star
862 781 add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
782 + add_action( 'init', array( Deprecate::class, 'instance' ) );
783 +
784 + // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
785 + \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
786 +
787 + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the
788 + // Jetpack plugin for now: the Connection package no longer auto-wires these, so
789 + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet.
790 + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init();
863 791 }
864 792
865 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 + * Whether the bundled Stats v2 dashboard is enabled.
844 + *
845 + * Stats v2 (formerly "Premium Analytics") ships with the plugin behind this
846 + * flag while it rolls out (WOOA7S-1595). When enabled it adds its own admin
847 + * menu alongside the existing Stats UI; it never replaces or hides the
848 + * legacy Stats menu, admin-bar entries, post-list column, or WP dashboard
849 + * widget. The Stats module's tracking is unaffected either way, and Stats v2
850 + * reads what that module collects, so while the module is off the plugin
851 + * answers false here before even reading the flag.
852 + *
853 + * The package has to be loadable for this to be true, so a site with the
854 + * flag on but a missing package answers false here and never adds the
855 + * Stats v2 menu (a warning is logged instead).
856 + *
857 + * @since 16.1
858 + *
859 + * @return bool
860 + */
861 + public static function is_premium_analytics_enabled() {
862 + if ( null !== self::$premium_analytics_enabled ) {
863 + return self::$premium_analytics_enabled;
864 + }
865 +
866 + if ( ! self::is_module_active( 'stats' ) ) {
867 + self::$premium_analytics_enabled = false;
868 + return false;
869 + }
870 +
871 + /**
872 + * Filters whether the bundled Premium Analytics dashboard is enabled.
873 + *
874 + * Resolved once, from `Jetpack::configure()` on `plugins_loaded`, and only
875 + * while the Stats module is active. Register this from a mu-plugin or a
876 + * plugin's main file — a callback added on `plugins_loaded` or later runs
877 + * too late to be seen.
878 + *
879 + * @since 16.1
880 + *
881 + * @param bool $enabled Defaults to the `jetpack_premium_analytics_enabled` option (false).
882 + */
883 + $flag = (bool) apply_filters( 'jetpack_premium_analytics_enabled', (bool) get_option( 'jetpack_premium_analytics_enabled' ) );
884 +
885 + self::$premium_analytics_enabled = $flag && class_exists( 'Automattic\Jetpack\PremiumAnalytics\Analytics' );
886 +
887 + if ( $flag && ! self::$premium_analytics_enabled ) {
888 + wp_trigger_error(
889 + __METHOD__,
890 + 'The jetpack_premium_analytics_enabled flag is on but the Premium Analytics package is not loadable; keeping the Stats UI in place.'
891 + );
892 + }
893 +
894 + return self::$premium_analytics_enabled;
895 + }
896 +
897 + /**
898 + * Expose the setting that turns the Premium Analytics dashboard on and off.
899 + *
900 + * Deliberately not behind is_premium_analytics_enabled(): this is the setting that flips that
901 + * check, so it has to answer while the dashboard is still off.
902 + *
903 + * @since 16.2
904 + *
905 + * @return void
906 + */
907 + public static function register_premium_analytics_enablement_setting() {
908 + if ( class_exists( 'Automattic\Jetpack\PremiumAnalytics\Enablement_Setting' ) ) {
909 + \Automattic\Jetpack\PremiumAnalytics\Enablement_Setting::register();
910 + }
911 + }
912 +
913 + /**
866 914 * Before everything else starts getting initalized, we need to initialize Jetpack using the
867 915 * Config object.
868 916 */
869 917 public function configure() {
@@ -872,13 +920,10 @@
872 920 foreach (
873 921 array(
874 922 'jitm',
875 923 'sync',
924 + 'account_protection',
876 925 'waf',
877 - 'videopress',
878 - 'stats',
879 - 'stats_admin',
880 - 'import',
881 926 )
882 927 as $feature
883 928 ) {
884 929 $config->ensure( $feature );
@@ -883,8 +928,103 @@
883 928 ) {
884 929 $config->ensure( $feature );
885 930 }
886 931
932 + // Enable the VideoPress admin UI (the "Jetpack > VideoPress" dashboard) inside the
933 + // Jetpack plugin, mirroring the standalone Jetpack VideoPress plugin. The dashboard
934 + // only renders when the VideoPress module is active (Status::is_active()); when it
935 + // is not, the menu item links to the My Jetpack interstitial to activate it.
936 + $config->ensure( 'videopress', array( 'admin_ui' => true ) );
937 +
938 + /*
939 + * The Import package only registers `jetpack/v4/import` REST routes — it
940 + * does nothing when rendering a front-end page — so gate its `ensure()`
941 + * to keep its PHP out of opcache on the front-end GET hot path. It still
942 + * loads on admin, cron, POST, and WP-CLI requests, and on `rest_api_init`
943 + * for REST: a REST request can't be identified yet at `plugins_loaded`
944 + * (this runs before `Config::on_plugins_loaded`, and `rest_api_init`
945 + * fires later), so it is initialized directly when that hook fires, while
946 + * a plain page view never fires it and so loads nothing.
947 + *
948 + * JITM stays eager (above): unlike Import, its `register()` adds a
949 + * `jetpack_sync_before_send_updated_option` filter that records the
950 + * `jetpack_last_plugin_sync` transient, and a Jetpack Sync send can fire
951 + * on a plain front-end GET — including the dedicated-sync `spawn-sync`
952 + * GET, which runs on `init` and exits before `rest_api_init`. Deferring
953 + * JITM would skip that bookkeeping and leave its message cache stale after
954 + * a plugin change, so it loads on every request as before.
955 + */
956 + if ( self::should_eager_load_packages() ) {
957 + $config->ensure( 'import' );
958 + } else {
959 + add_action(
960 + 'rest_api_init',
961 + array( __CLASS__, 'configure_import_package' ),
962 + 0
963 + );
964 + }
965 +
966 + /*
967 + * The Stats and Stats Admin packages only do work when the Stats module
968 + * is active (the front-end tracking pixel) or on wp-admin, REST, cron,
969 + * POST, and WP-CLI requests: the Stats dashboard page, the stats /
970 + * stats-app REST endpoints (which the block editor also calls for
971 + * email-open rates), the transient-cleanup cron, the connection
972 + * package's package-version tracker (which runs on POSTs and reads the
973 + * `jetpack_package_versions` filter that Stats registers), and CLI
974 + * introspection such as the heartbeat inspector. On a plain front-end
975 + * GET page view with the module off they are inert: the pixel
976 + * short-circuits on `Stats\Main::should_track()` and every other entry
977 + * point only hooks rest_api_init, admin, cron, the POST-only tracker, or
978 + * is reached through WP-CLI.
979 + * Skip loading them — and eagerly constructing the Stats Admin REST
980 + * controller — on that hot path to keep their PHP out of opcache, but
981 + * keep loading them everywhere else exactly as before so the stats REST
982 + * permission mapping (view_stats, registered by Stats\Main), the
983 + * editor's stats-app calls, the cleanup cron, and the package-version
984 + * tracker are all unchanged.
985 + *
986 + * REST requests are not yet identifiable here (REST_REQUEST is defined
987 + * after plugins_loaded), so defer those to rest_api_init. Call the
988 + * package initializers directly rather than `$config->ensure()`: ensure()
989 + * only flags a feature for `Config::on_plugins_loaded()` (plugins_loaded
990 + * priority 2), which has already run by the time rest_api_init fires.
991 + * Priority 0 runs before each package's own priority-10 route
992 + * registration, so their routes still register within the same dispatch.
993 + * A plain page view never fires rest_api_init, so the packages stay
994 + * unloaded there. See JETPACK-1747.
995 + */
996 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'];
997 + $is_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
998 +
999 + if ( self::is_module_active( 'stats' ) || is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli ) {
1000 + $config->ensure( 'stats' );
1001 + $config->ensure( 'stats_admin' );
1002 + } else {
1003 + add_action(
1004 + 'rest_api_init',
1005 + static function () {
1006 + if ( class_exists( 'Automattic\Jetpack\Stats\Main' ) ) {
1007 + \Automattic\Jetpack\Stats\Main::init();
1008 + }
1009 + if ( class_exists( 'Automattic\Jetpack\Stats_Admin\Main' ) ) {
1010 + \Automattic\Jetpack\Stats_Admin\Main::init();
1011 + }
1012 + },
1013 + 0
1014 + );
1015 + }
1016 +
1017 + // Stats v2 (WOOA7S-1595). Unlike Stats above it cannot be deferred when enabled — see
1018 + // Analytics::init() for why, and for why it takes no menu_title here.
1019 + if ( self::is_premium_analytics_enabled() ) {
1020 + \Automattic\Jetpack\PremiumAnalytics\Analytics::init();
1021 + }
1022 +
1023 + // Outside the check above on purpose — see Enablement_Setting. Deferred like Stats, to keep
1024 + // the autoload off the front-end hot path.
1025 + add_action( 'rest_api_init', array( __CLASS__, 'register_premium_analytics_enablement_setting' ), 0 );
1026 +
887 1027 $config->ensure(
888 1028 'connection',
889 1029 array(
890 1030 'slug' => 'jetpack',
@@ -902,12 +1042,8 @@
902 1042 );
903 1043
904 1044 $config->ensure( 'search' );
905 1045
906 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
907 - $config->ensure( 'wordads' );
908 - }
909 -
910 1046 if ( ! $this->connection_manager ) {
911 1047 $this->connection_manager = new Connection_Manager( 'jetpack' );
912 1048 }
913 1049
@@ -915,8 +1051,10 @@
915 1051 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
916 1052 $config->ensure( 'publicize' );
917 1053 }
918 1054
1055 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1056 +
919 1057 /*
920 1058 * Load things that should only be in Network Admin.
921 1059 *
922 1060 * For now blow away everything else until a more full
@@ -931,8 +1069,9 @@
931 1069 $is_connection_ready = self::is_connection_ready();
932 1070
933 1071 if ( $is_connection_ready ) {
934 1072 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1073 + $this->run_initialize_tracking_action();
935 1074
936 1075 Jetpack_Heartbeat::init();
937 1076 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
938 1077 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -937,8 +1076,19 @@
937 1076 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
938 1077 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
939 1078 Jetpack_Search_Performance_Logger::init();
940 1079 }
1080 + } else {
1081 + add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
1082 + add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
1083 + add_filter(
1084 + 'xmlrpc_methods',
1085 + function ( $methods ) {
1086 + $this->run_initialize_tracking_action();
1087 + return $methods;
1088 + },
1089 + 1
1090 + );
941 1091 }
942 1092
943 1093 // Initialize remote file upload request handlers.
944 1094 $this->add_remote_request_handlers();
@@ -948,20 +1098,10 @@
948 1098 */
949 1099 if ( $is_connection_ready ) {
950 1100 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
951 1101 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
952 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
953 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1102 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
954 1103 }
955 -
956 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
957 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
958 - } else {
959 - /**
960 - * Initialize tracking right after the user agrees to the terms of service.
961 - */
962 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
963 - }
964 1104 }
965 1105
966 1106 /**
967 1107 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -969,15 +1109,58 @@
969 1109 *
970 1110 * @action plugins_loaded
971 1111 */
972 1112 public function late_initialization() {
973 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1113 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
974 1114
975 - My_Jetpack_Initializer::init();
1115 + /*
1116 + * My Jetpack is a wp-admin dashboard. Its Initializer::init() only wires
1117 + * up admin-menu, admin_init, and rest_api_init surfaces — and eagerly
1118 + * loads every product class (backup, boost, protect, …) just to register
1119 + * admin plugin-action links — so none of it is needed on a plain
1120 + * front-end GET page view. (The pieces that do immediate work, e.g.
1121 + * Connection REST authentication and Licensing, are already initialized
1122 + * unconditionally in Jetpack's constructor, so they are unaffected here.)
1123 + *
1124 + * Gate the call to the request types where My Jetpack actually does work.
1125 + * REST can't be detected yet at plugins_loaded, so initialize on
1126 + * rest_api_init for that branch; a plain page view never fires it, so My
1127 + * Jetpack stays unloaded there.
1128 + */
1129 + if ( self::should_eager_load_packages() ) {
1130 + My_Jetpack_Initializer::init();
1131 + } else {
1132 + add_action( 'rest_api_init', array( My_Jetpack_Initializer::class, 'init' ), 0 );
1133 + }
976 1134
977 - // Initialize Boost Speed Score
978 - new Speed_Score( array(), 'jetpack-dashboard' );
1135 + Scan_Page_Init::initialize();
1136 + Jetpack_SEO_Initializer::init();
979 1137
1138 + if ( ( new Modules() )->is_active( 'podcast' ) ) {
1139 + Podcast::init();
1140 + }
1141 +
1142 + /*
1143 + * Initialize Boost Speed Score. It only does work on REST requests (the
1144 + * dashboard speed-score endpoints) and on a few Jetpack Boost lifecycle
1145 + * actions, so defer constructing it — and loading the boost-speed-score
1146 + * package classes — until one of those hooks actually fires instead of on
1147 + * every request. Priority 0 ensures the object's own callbacks (added in
1148 + * its constructor at the default priority) still run for the firing hook.
1149 + */
1150 + $initialize_speed_score = static function () {
1151 + static $initialized = false;
1152 + if ( $initialized ) {
1153 + return;
1154 + }
1155 + $initialized = true;
1156 + new Speed_Score( array(), 'jetpack-dashboard' );
1157 + };
1158 + add_action( 'rest_api_init', $initialize_speed_score, 0 );
1159 + add_action( 'jetpack_boost_deactivate', $initialize_speed_score, 0 );
1160 + add_action( 'jetpack_boost_environment_changed', $initialize_speed_score, 0 );
1161 + add_action( 'handle_environment_change', $initialize_speed_score, 0 );
1162 +
980 1163 /**
981 1164 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
982 1165 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
983 1166 *
@@ -1015,8 +1198,10 @@
1015 1198
1016 1199 /**
1017 1200 * Redirect edit post links to Calypso.
1018 1201 *
1202 + * @deprecated since 13.9
1203 + *
1019 1204 * @param string $default_url Post edit URL.
1020 1205 * @param int $post_id Post ID.
1021 1206 *
1022 1207 * @return string
@@ -1021,8 +1206,10 @@
1021 1206 *
1022 1207 * @return string
1023 1208 */
1024 1209 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1210 + _deprecated_function( __METHOD__, '13.9' );
1211 +
1025 1212 $post = get_post( $post_id );
1026 1213
1027 1214 if ( empty( $post ) ) {
1028 1215 return $default_url;
@@ -1053,13 +1240,17 @@
1053 1240
1054 1241 /**
1055 1242 * Redirect edit comment links to Calypso.
1056 1243 *
1244 + * @deprecated since 13.9
1245 + *
1057 1246 * @param string $url Comment edit URL.
1058 1247 *
1059 1248 * @return string
1060 1249 */
1061 1250 public function point_edit_comment_links_to_calypso( $url ) {
1251 + _deprecated_function( __METHOD__, '13.9' );
1252 +
1062 1253 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1063 1254 $query_args = null;
1064 1255 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1065 1256
@@ -1078,30 +1269,16 @@
1078 1269 *
1079 1270 * @return array list of callables.
1080 1271 */
1081 1272 public function filter_sync_callable_whitelist( $callables ) {
1082 -
1083 1273 // Jetpack Functions.
1084 1274 $jetpack_callables = array(
1085 1275 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1086 1276 'updates' => array( 'Jetpack', 'get_updates' ),
1087 1277 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1278 + 'theme_styles' => array( 'Automattic\\Jetpack\\Plugin\\Theme_Styles_Sync', 'get_theme_styles' ),
1088 1279 );
1089 - $callables = array_merge( $callables, $jetpack_callables );
1090 -
1091 - // Jetpack_SSO_Helpers.
1092 - if ( include_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ) {
1093 - $sso_helpers = array(
1094 - 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
1095 - 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
1096 - 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
1097 - 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
1098 - 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
1099 - );
1100 - $callables = array_merge( $callables, $sso_helpers );
1101 - }
1102 -
1103 - return $callables;
1280 + return array_merge( $callables, $jetpack_callables );
1104 1281 }
1105 1282
1106 1283 /**
1107 1284 * Extend Sync multisite callables with Jetpack Plugin functions.
@@ -1142,16 +1319,8 @@
1142 1319 * @param string $cap Capability name.
1143 1320 */
1144 1321 public function jetpack_custom_caps( $caps, $cap ) {
1145 1322 switch ( $cap ) {
1146 - case 'jetpack_manage_modules':
1147 - case 'jetpack_activate_modules':
1148 - case 'jetpack_deactivate_modules':
1149 - $caps = array( 'manage_options' );
1150 - break;
1151 - case 'jetpack_configure_modules':
1152 - $caps = array( 'manage_options' );
1153 - break;
1154 1323 case 'jetpack_manage_autoupdates':
1155 1324 $caps = array(
1156 1325 'manage_options',
1157 1326 'update_plugins',
@@ -1169,9 +1338,9 @@
1169 1338 if ( $is_offline_mode ) {
1170 1339 $caps = array( 'manage_options' );
1171 1340 break;
1172 1341 } else {
1173 - $caps = array( 'read' );
1342 + $caps = array( 'edit_posts' );
1174 1343 }
1175 1344 break;
1176 1345 }
1177 1346 return $caps;
@@ -1614,29 +1783,8 @@
1614 1783 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1615 1784 }
1616 1785
1617 1786 /**
1618 - * Whether the site is currently onboarding or not.
1619 - * A site is considered as being onboarded if it currently has an onboarding token.
1620 - *
1621 - * @since 5.8
1622 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1623 - *
1624 - * @access public
1625 - * @static
1626 - *
1627 - * @return bool True if the site is currently onboarding, false otherwise
1628 - */
1629 - public static function is_onboarding() {
1630 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1631 -
1632 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1633 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1634 - }
1635 - return ( new Status() )->is_onboarding();
1636 - }
1637 -
1638 - /**
1639 1787 * Determines reason for Jetpack offline mode.
1640 1788 */
1641 1789 public static function development_mode_trigger_text() {
1642 1790 $status = new Status();
@@ -1650,11 +1798,10 @@
1650 1798 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1651 1799 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1652 1800 } elseif ( $status->is_local_site() ) {
1653 1801 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1654 - /** This filter is documented in packages/status/src/class-status.php */
1655 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1656 - $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1802 + } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1803 + $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1657 1804 } else {
1658 1805 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1659 1806 }
1660 1807
@@ -1684,15 +1831,8 @@
1684 1831 $notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), esc_url( Redirect::get_url( 'jetpack-contact-support-beta-group' ) ) );
1685 1832
1686 1833 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1687 1834 }
1688 - // Throw up a notice if using staging mode.
1689 - if ( ( new Status() )->is_staging_site() ) {
1690 - /* translators: %s is a URL */
1691 - $notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), esc_url( Redirect::get_url( 'jetpack-support-staging-sites' ) ) );
1692 -
1693 - echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1694 - }
1695 1835 }
1696 1836
1697 1837 /**
1698 1838 * Whether Jetpack's version maps to a public release, or a development version.
@@ -1766,18 +1906,11 @@
1766 1906 */
1767 1907 public static function load_modules() {
1768 1908 $status = new Status();
1769 1909
1770 - if ( method_exists( $status, 'is_onboarding' ) ) {
1771 - $is_onboarding = $status->is_onboarding();
1772 - } else {
1773 - $is_onboarding = self::is_onboarding();
1774 - }
1775 -
1776 1910 if (
1777 1911 ! self::is_connection_ready()
1778 1912 && ! $status->is_offline_mode()
1779 - && ! $is_onboarding
1780 1913 && (
1781 1914 ! is_multisite()
1782 1915 || ! get_site_option( 'jetpack_protect_active' )
1783 1916 )
@@ -1898,22 +2031,14 @@
1898 2031 *
1899 2032 * @todo Store the result in core's object cache maybe?
1900 2033 */
1901 2034 public static function get_active_plugins() {
1902 - $active_plugins = (array) get_option( 'active_plugins', array() );
1903 -
1904 - if ( is_multisite() ) {
1905 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1906 - // whereas active_plugins stores them in the values.
1907 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1908 - if ( $network_plugins ) {
1909 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1910 - }
2035 + // Older Connection copies can load first and lack this method.
2036 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2037 + return array();
1911 2038 }
1912 2039
1913 - sort( $active_plugins );
1914 -
1915 - return array_unique( $active_plugins );
2040 + return Heartbeat::get_active_plugins();
1916 2041 }
1917 2042
1918 2043 /**
1919 2044 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2051,8 +2176,10 @@
2051 2176 *
2052 2177 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2053 2178 */
2054 2179 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2180 + // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
2181 + // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
2055 2182 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2056 2183 }
2057 2184 }
2058 2185
@@ -2155,9 +2282,9 @@
2155 2282 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2156 2283 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2157 2284 }
2158 2285 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2159 - exit;
2286 + exit( 0 );
2160 2287 }
2161 2288 }
2162 2289
2163 2290 /**
@@ -2204,8 +2331,12 @@
2204 2331 default:
2205 2332 break;
2206 2333 }
2207 2334 }
2335 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2336 + Feature_Policy::ensure_hooks();
2337 + }
2338 +
2208 2339 /**
2209 2340 * Filters the array of default modules.
2210 2341 *
2211 2342 * @since 2.5.0
@@ -2298,8 +2429,17 @@
2298 2429 }
2299 2430 }
2300 2431 }
2301 2432
2433 + // Special case to convert block setting to a block module.
2434 + $block_key = array_search( 'blocks', $modules, true );
2435 + if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2436 + $block_option = get_option( 'jetpack_blocks_disabled', null );
2437 + if ( $block_option ) {
2438 + unset( $modules[ $block_key ] );
2439 + }
2440 + }
2441 +
2302 2442 return $modules;
2303 2443 }
2304 2444
2305 2445 /**
@@ -2356,23 +2496,30 @@
2356 2496
2357 2497 /**
2358 2498 * Return module name translation. Uses matching string created in modules/module-headings.php.
2359 2499 *
2500 + * The module list is globbed from `modules/` at runtime, so a module can be listed with no
2501 + * entry in that generated file. Fall back to the untranslated header rather than overwriting
2502 + * it with the null `jetpack_get_module_i18n()` returns for an unknown slug.
2503 + *
2360 2504 * @since 3.9.2
2361 2505 *
2362 2506 * @param array $modules Array of Jetpack modules.
2363 2507 *
2364 - * @return string|void
2508 + * @return array
2365 2509 */
2366 2510 public static function get_translated_modules( $modules ) {
2367 2511 foreach ( $modules as $index => $module ) {
2368 2512 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2369 - if ( isset( $module['name'] ) ) {
2370 - $modules[ $index ]['name'] = $i18n_module['name'];
2513 + $name = $i18n_module['name'] ?? null;
2514 + $description = $i18n_module['description'] ?? null;
2515 +
2516 + if ( null !== $name && isset( $module['name'] ) ) {
2517 + $modules[ $index ]['name'] = $name;
2371 2518 }
2372 - if ( isset( $module['description'] ) ) {
2373 - $modules[ $index ]['description'] = $i18n_module['description'];
2374 - $modules[ $index ]['short_description'] = $i18n_module['description'];
2519 + if ( null !== $description && isset( $module['description'] ) ) {
2520 + $modules[ $index ]['description'] = $description;
2521 + $modules[ $index ]['short_description'] = $description;
2375 2522 }
2376 2523 if ( isset( $module['module_tags'] ) ) {
2377 2524 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2378 2525 }
@@ -2412,20 +2559,28 @@
2412 2559
2413 2560 /**
2414 2561 * Catches PHP errors. Must be used in conjunction with output buffering.
2415 2562 *
2563 + * @deprecated since 13.5
2416 2564 * @param bool $catch True to start catching, False to stop.
2417 2565 *
2418 2566 * @static
2567 + * @deprecated 13.5
2568 + * @see \Automattic\Jetpack\Errors
2419 2569 */
2420 2570 public static function catch_errors( $catch ) {
2571 + _deprecated_function( __METHOD__, '13.5' );
2572 + // @phan-suppress-next-line PhanDeprecatedClass
2421 2573 return ( new Errors() )->catch_errors( $catch );
2422 2574 }
2423 2575
2424 2576 /**
2425 2577 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2578 + *
2579 + * @deprecated since 13.5
2426 2580 */
2427 2581 public static function catch_errors_on_shutdown() {
2582 + _deprecated_function( __METHOD__, '13.5' );
2428 2583 self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2429 2584 }
2430 2585
2431 2586 /**
@@ -2529,9 +2684,9 @@
2529 2684 ),
2530 2685 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2531 2686 );
2532 2687 wp_safe_redirect( $url );
2533 - exit;
2688 + exit( 0 );
2534 2689 }
2535 2690 }
2536 2691
2537 2692 /**
@@ -2549,9 +2704,8 @@
2549 2704
2550 2705 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating.
2551 2706 if ( $send_state_messages ) {
2552 2707 self::restate();
2553 - self::catch_errors( true );
2554 2708 }
2555 2709
2556 2710 $active = self::get_active_modules();
2557 2711
@@ -2619,10 +2773,8 @@
2619 2773 if ( $send_state_messages ) {
2620 2774 self::state( 'error', false );
2621 2775 self::state( 'module', false );
2622 2776 }
2623 -
2624 - self::catch_errors( false );
2625 2777 /**
2626 2778 * Fires when default modules are activated.
2627 2779 *
2628 2780 * @since 1.9.0
@@ -2680,9 +2832,9 @@
2680 2832 * @return string $url module configuration URL.
2681 2833 */
2682 2834 public static function module_configuration_url( $module ) {
2683 2835 $module = self::get_module_slug( $module );
2684 - $default_url = self::admin_url() . "#/settings?term=$module";
2836 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2685 2837 /**
2686 2838 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2687 2839 *
2688 2840 * @since 6.9.0
@@ -2740,9 +2892,9 @@
2740 2892 if ( $update ) {
2741 2893 update_option( 'active_plugins', array_filter( $plugins ) );
2742 2894 }
2743 2895 }
2744 - exit;
2896 + exit( 0 );
2745 2897 }
2746 2898
2747 2899 /**
2748 2900 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2767,12 +2919,18 @@
2767 2919 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2768 2920
2769 2921 Health::on_jetpack_activated();
2770 2922
2923 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2924 +
2771 2925 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2772 2926 Sync_Actions::do_only_first_initial_sync();
2773 2927 }
2774 2928
2929 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2930 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2931 + }
2932 +
2775 2933 self::plugin_initialize();
2776 2934 }
2777 2935
2778 2936 /**
@@ -2807,9 +2965,9 @@
2807 2965
2808 2966 if ( $plugins_path === $referer['path'] ) {
2809 2967 $source_type = 'list';
2810 2968 } elseif ( $plugins_install_path === $referer['path'] ) {
2811 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
2969 + $tab = $query_parts['tab'] ?? 'featured';
2812 2970 switch ( $tab ) {
2813 2971 case 'popular':
2814 2972 $source_type = 'popular';
2815 2973 break;
@@ -2819,10 +2977,10 @@
2819 2977 case 'favorites':
2820 2978 $source_type = 'favorites';
2821 2979 break;
2822 2980 case 'search':
2823 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2824 - $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
2981 + $source_type = 'search-' . ( $query_parts['type'] ?? 'term' );
2982 + $source_query = $query_parts['s'] ?? null;
2825 2983 break;
2826 2984 default:
2827 2985 $source_type = 'featured';
2828 2986 }
@@ -2831,29 +2989,171 @@
2831 2989 return array( $source_type, $source_query );
2832 2990 }
2833 2991
2834 2992 /**
2835 - * Runs before bumping version numbers up to a new version
2993 + * Runs before bumping version numbers up to a new version.
2836 2994 *
2837 - * @param string $version Version:timestamp.
2995 + * Only ever registered the hooks for the release post update modal, which has been removed.
2996 + * No longer hooked to `updating_jetpack_version`.
2997 + *
2998 + * @deprecated 16.2
2999 + *
3000 + * @param string $version Version:timestamp.
2838 3001 * @param string $old_version Old Version:timestamp or false if not set yet.
2839 3002 */
2840 - public static function do_version_bump( $version, $old_version ) {
2841 - if ( $old_version ) { // For existing Jetpack installations.
2842 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3003 + public static function do_version_bump( $version, $old_version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature preserved for the deprecation shim.
3004 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
3005 + }
2843 3006
2844 - // If a front end page is visited after the update, the 'wp' action will fire.
2845 - add_action( 'wp', 'Jetpack::set_update_modal_display' );
3007 + /**
3008 + * Enables the Newsletter (subscriptions) module for existing sites now that it is a default-on module.
3009 + *
3010 + * Fresh installs receive the module via its "Auto Activate: Yes" header, so this only handles sites
3011 + * upgrading from a version where the module defaulted off. It runs once per site (guarded by the
3012 + * subscriptions_default_on_migrated option). Fresh installs are marked as migrated immediately so the
3013 + * migration never runs for them. After it has run, the user's choice to deactivate the module again
3014 + * (for example from the My Jetpack Products page) is respected and never reverted.
3015 + *
3016 + * The module requires a connection, so on a disconnected site the migration is deferred without setting
3017 + * the guard, allowing a later version bump to retry once the site is connected.
3018 + *
3019 + * @param string $version New Jetpack version:timestamp.
3020 + * @param string|false $old_version Previous Jetpack version:timestamp, or false on a fresh install.
3021 + */
3022 + public static function activate_subscriptions_module_for_existing_sites( $version, $old_version ) {
3023 + if ( get_option( 'jetpack_subscriptions_default_on_migrated' ) ) {
3024 + return;
3025 + }
2846 3026
2847 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2848 - add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3027 + // Fresh installs get the module via its "Auto Activate: Yes" header. Mark them as migrated so a
3028 + // later opt-out is never reverted by the existing-site path on a subsequent version bump.
3029 + if ( ! $old_version ) {
3030 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3031 + return;
2849 3032 }
3033 +
3034 + if ( ! self::is_connection_ready() ) {
3035 + return;
3036 + }
3037 +
3038 + // Mark as migrated only once the module is active, so a transient activation failure is retried on
3039 + // a later version bump rather than being silently skipped.
3040 + if ( self::is_module_active( 'subscriptions' ) || self::activate_module( 'subscriptions', false, false ) ) {
3041 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3042 + }
2850 3043 }
2851 3044
2852 3045 /**
3046 + * Option flag that records the AI master-switch opt-out reconciliation has run,
3047 + * so it never runs twice.
3048 + *
3049 + * @var string
3050 + */
3051 + const AI_MASTER_OPTOUT_MIGRATED_OPTION = 'jetpack_ai_master_optout_migrated';
3052 +
3053 + /**
3054 + * Register the on-upgrade init hooks whose relative ORDER matters, extracted so
3055 + * the ordering can be asserted in tests without invoking plugin_upgrade() (whose
3056 + * guards make it unreliable to trigger under test). activate_new_modules()
3057 + * (init, default priority 10) auto-activates "Auto Activate: Yes" modules
3058 + * including the `ai` master; reconcile_ai_master_optout() must run at a LATER
3059 + * priority to honor an explicit AI opt-out.
3060 + *
3061 + * @return void
3062 + */
3063 + public static function register_upgrade_init_hooks() {
3064 + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
3065 + add_action( 'init', array( __CLASS__, 'reconcile_ai_master_optout' ), 20 );
3066 + }
3067 +
3068 + /**
3069 + * Preserves an explicit Jetpack AI opt-out when the `ai` module becomes the site-wide
3070 + * master switch off WordPress.com Simple (self-hosted and Atomic).
3071 + *
3072 + * The `ai` module is "Auto Activate: Yes", so on upgrade {@see self::activate_new_modules()}
3073 + * turns it on for connected sites — the desired default-on / auto-enable-on-connection
3074 + * behavior, which this method deliberately leaves alone. The one case it corrects is a site
3075 + * that had explicitly disabled Jetpack AI (the `jetpack_ai_enabled` option present and falsey)
3076 + * before the module shipped: that opt-out must survive the module becoming the master, so the
3077 + * module is deactivated for exactly those sites. An absent or truthy option is left untouched.
3078 + *
3079 + * Ordering is the whole point. `activate_new_modules()` is hooked on `init` at priority 10 and
3080 + * auto-activates the module there; this method is hooked on `init` at priority 20 (see
3081 + * {@see self::plugin_upgrade()}), so it runs AFTER the auto-activation and its deactivation is
3082 + * the final state. A version-guarded block that ran inline during `plugins_loaded` would be
3083 + * undone by the later auto-activation, which is why this is a late-init hook rather than an
3084 + * inline upgrade step.
3085 + *
3086 + * WordPress.com Simple never runs modules — the option stays the master there — so this is a
3087 + * no-op on Simple. The {@see self::AI_MASTER_OPTOUT_MIGRATED_OPTION} flag makes it run exactly
3088 + * once, which matters because off-Simple the option is no longer the master after this runs:
3089 + * a stale falsey option must not keep re-deactivating a module the user later turns back on.
3090 + *
3091 + * @return void
3092 + */
3093 + public static function reconcile_ai_master_optout() {
3094 + if ( get_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION ) ) {
3095 + return;
3096 + }
3097 +
3098 + // Simple keeps the `jetpack_ai_enabled` option as the master; modules don't run there.
3099 + if ( ( new Host() )->is_wpcom_simple() ) {
3100 + return;
3101 + }
3102 +
3103 + // A sentinel default distinguishes an absent option (leave auto-activation alone) from one
3104 + // explicitly stored falsey (an opt-out to preserve).
3105 + $stored = get_option( 'jetpack_ai_enabled', 'not-set' );
3106 + if ( 'not-set' !== $stored && ! (bool) $stored ) {
3107 + ( new Modules() )->deactivate( 'ai' );
3108 + }
3109 +
3110 + update_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION, true );
3111 + }
3112 +
3113 + /**
3114 + * Deletes obsolete SEO module-state options without changing module activation.
3115 + *
3116 + * @since 16.3
3117 + */
3118 + public static function cleanup_seo_module_state_options() {
3119 + delete_option( 'jetpack_seo_sitemap_enabled' );
3120 + delete_option( 'jetpack_seo_canonical_urls_enabled' );
3121 + delete_option( 'jetpack_seo_module_state_reconciled' );
3122 + }
3123 +
3124 + /**
3125 + * Seeds the Jetpack SEO discoverability cohort once, so the new SEO surface is
3126 + * auto-discoverable on fresh installs but opt-in on existing ones (JETPACK-1700).
3127 + *
3128 + * Hooked on `updating_jetpack_version`, which fires on every install including the
3129 + * first — with `$old_version === false` on a brand-new site (the same signal
3130 + * {@see self::activate_subscriptions_module_for_existing_sites()} keys off). Fresh
3131 + * installs are seeded visible; existing installs are seeded hidden and opt in later
3132 + * via the legacy Traffic page or My Jetpack. `add_option()` makes this seed-once: it
3133 + * never overrides a value a later opt-in (or opt-out) has set. WordPress.com sites
3134 + * ignore this option entirely (always visible) — see
3135 + * {@see \Automattic\Jetpack\SEO\Initializer::is_seo_surface_visible()}.
3136 + *
3137 + * @param string $version The new Jetpack version (unused).
3138 + * @param string|false $old_version The previous version, or false on a fresh install.
3139 + */
3140 + public static function seed_seo_visibility_cohort( $version, $old_version ) {
3141 + add_option( Jetpack_SEO_Initializer::VISIBILITY_OPTION, ! $old_version );
3142 + }
3143 +
3144 + /**
2853 3145 * Sets the display_update_modal state.
3146 + *
3147 + * The release post update modal that read this state has been removed. The write is kept so the
3148 + * method still behaves as documented for the deprecation window. When this is deleted, also drop
3149 + * the matching `display_update_modal` guard in Automattic\Jetpack\CookieState::should_set_cookie(),
3150 + * which exists only to keep this key out of the cookie on the Jetpack admin screen.
3151 + *
3152 + * @deprecated 16.2
2854 3153 */
2855 3154 public static function set_update_modal_display() {
3155 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2856 3156 self::state( 'display_update_modal', true );
2857 3157 }
2858 3158
2859 3159 /**
@@ -2858,11 +3158,16 @@
2858 3158
2859 3159 /**
2860 3160 * Enqueues the block library styles.
2861 3161 *
3162 + * Only ever used by the release post update modal, which has been removed.
3163 + *
3164 + * @deprecated 16.2
3165 + *
2862 3166 * @param string $hook The current admin page.
2863 3167 */
2864 3168 public static function enqueue_block_style( $hook ) {
3169 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2865 3170 if ( 'toplevel_page_jetpack' === $hook ) {
2866 3171 wp_enqueue_style( 'wp-block-library' );
2867 3172 }
2868 3173 }
@@ -2951,8 +3256,12 @@
2951 3256 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2952 3257 self::disconnect();
2953 3258 Jetpack_Options::delete_option( 'version' );
2954 3259 }
3260 +
3261 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3262 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3263 + }
2955 3264 }
2956 3265
2957 3266 /**
2958 3267 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -2980,9 +3289,9 @@
2980 3289 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2981 3290 }
2982 3291
2983 3292 /**
2984 - * Happens after a successfull disconnection.
3293 + * Happens after a successful disconnection.
2985 3294 *
2986 3295 * @static
2987 3296 */
2988 3297 public static function jetpack_site_disconnected() {
@@ -2987,8 +3296,10 @@
2987 3296 */
2988 3297 public static function jetpack_site_disconnected() {
2989 3298 Identity_Crisis::clear_all_idc_options();
2990 3299
3300 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3301 +
2991 3302 // Delete all the sync related data. Since it could be taking up space.
2992 3303 Sender::get_instance()->uninstall();
2993 3304
2994 3305 /**
@@ -3052,10 +3363,17 @@
3052 3363 * @param mixed $code Error code to log.
3053 3364 * @param mixed $data Data to log.
3054 3365 */
3055 3366 public static function log( $code, $data = null ) {
3367 +
3368 + $raw_log = Jetpack_Options::get_option( 'log', array() );
3369 + // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
3370 + if ( ! is_array( $raw_log ) ) {
3371 + return;
3372 + }
3373 +
3056 3374 // only grab the latest 200 entries.
3057 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3375 + $log = array_slice( $raw_log, -199, 199 );
3058 3376
3059 3377 // Append our event to the log.
3060 3378 $log_entry = array(
3061 3379 'time' => time(),
@@ -3155,8 +3473,15 @@
3155 3473
3156 3474 /**
3157 3475 * Return stat data for WPCOM sync.
3158 3476 *
3477 + * The Sync stats module was this method's last caller and moved to the Connection package's
3478 + * `Heartbeat::generate_stats_array()`, which assembles the heartbeat data through the
3479 + * `jetpack_heartbeat_stats_array` filter. Note the package method does not include the extended
3480 + * data from `get_additional_stat_data()`, so callers relying on `$extended` need to add it themselves.
3481 + *
3482 + * @deprecated 16.2
3483 + *
3159 3484 * @param bool $encode JSON encode the result.
3160 3485 * @param bool $extended Adds additional stats data.
3161 3486 *
3162 3487 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3161,10 +3486,15 @@
3161 3486 *
3162 3487 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3163 3488 */
3164 3489 public static function get_stat_data( $encode = true, $extended = true ) {
3165 - $data = Jetpack_Heartbeat::generate_stats_array();
3490 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3166 3491
3492 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
3493 + ? Heartbeat::get_environment_stats()
3494 + : array();
3495 + $data = array_merge( Jetpack_Heartbeat::generate_stats_array(), $env_stats );
3496 +
3167 3497 if ( $extended ) {
3168 3498 $additional_data = self::get_additional_stat_data();
3169 3499 $data = array_merge( $data, $additional_data );
3170 3500 }
@@ -3169,9 +3499,9 @@
3169 3499 $data = array_merge( $data, $additional_data );
3170 3500 }
3171 3501
3172 3502 if ( $encode ) {
3173 - return wp_json_encode( $data );
3503 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3174 3504 }
3175 3505
3176 3506 return $data;
3177 3507 }
@@ -3250,12 +3580,17 @@
3250 3580 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3251 3581 // Upgrade: 1.1 -> 1.1.1
3252 3582 // Check and see if host can verify the Jetpack servers' SSL certificate.
3253 3583 $args = array();
3584 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3254 3585 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3255 3586 }
3256 3587
3257 - if ( current_user_can( 'manage_options' ) && ! self::permit_ssl() ) {
3588 + if (
3589 + current_user_can( 'manage_options' )
3590 + && ! self::permit_ssl()
3591 + && ! $is_offline_mode
3592 + ) {
3258 3593 add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3259 3594 }
3260 3595
3261 3596 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
@@ -3264,12 +3599,8 @@
3264 3599 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3265 3600 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3266 3601 }
3267 3602
3268 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3269 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3270 - }
3271 -
3272 3603 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3273 3604
3274 3605 if ( self::is_connection_ready() || $is_offline_mode ) {
3275 3606 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3308,8 +3639,9 @@
3308 3639 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3309 3640 * This function artificially throws errors for such cases (per a specific list).
3310 3641 *
3311 3642 * @param string $plugin The activated plugin.
3643 + * @throws RuntimeException If a conflicting plugin is detected.
3312 3644 */
3313 3645 public function throw_error_on_activate_plugin( $plugin ) {
3314 3646 $active_modules = self::get_active_modules();
3315 3647
@@ -3322,8 +3654,9 @@
3322 3654 if ( 'stats.php' === basename( $plugin ) ) {
3323 3655 $throw = true;
3324 3656 }
3325 3657 } else {
3658 + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Checked above. See also https://github.com/phan/phan/issues/1204.
3326 3659 $reflection = new ReflectionFunction( 'stats_get_api_key' );
3327 3660 if ( basename( $plugin ) === basename( $reflection->getFileName() ) ) {
3328 3661 $throw = true;
3329 3662 }
@@ -3330,9 +3663,9 @@
3330 3663 }
3331 3664
3332 3665 if ( $throw ) {
3333 3666 /* translators: Plugin name to deactivate. */
3334 - trigger_error( sprintf( esc_html__( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
3667 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3335 3668 }
3336 3669 }
3337 3670 }
3338 3671
@@ -3443,18 +3776,17 @@
3443 3776 if ( ! is_int( $status_code ) ) {
3444 3777 $status_code = 400;
3445 3778 }
3446 3779
3447 - status_header( $status_code );
3448 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3780 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3449 3781 }
3450 3782
3451 - status_header( 200 );
3452 3783 if ( true === $response ) {
3453 - exit;
3784 + status_header( 200 );
3785 + exit( 0 );
3454 3786 }
3455 3787
3456 - die( wp_json_encode( (object) $response ) );
3788 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3457 3789 }
3458 3790
3459 3791 /**
3460 3792 * Uploads a file gotten from the global $_FILES.
@@ -3515,9 +3847,9 @@
3515 3847 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3516 3848 }
3517 3849
3518 3850 $uploaded_files = array();
3519 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3851 + $global_post = $GLOBALS['post'] ?? null;
3520 3852 unset( $GLOBALS['post'] );
3521 3853 if ( empty( $_FILES['media']['name'] ) ) {
3522 3854 // Nothing to process, just return.
3523 3855 return $uploaded_files;
@@ -3524,9 +3856,9 @@
3524 3856 }
3525 3857 foreach ( $_FILES['media']['name'] as $index => $name ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, unslash sniff is wrong. Validation should happen below.
3526 3858 $file = array();
3527 3859 foreach ( $media_keys as $media_key ) {
3528 - $file[ $media_key ] = isset( $_FILES['media'][ $media_key ][ $index ] ) ? $_FILES['media'][ $media_key ][ $index ] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3860 + $file[ $media_key ] = $_FILES['media'][ $media_key ][ $index ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3529 3861 }
3530 3862
3531 3863 list( $hmac_provided, $salt ) = isset( $_POST['_jetpack_file_hmac_media'][ $index ] ) ? explode( ':', filter_var( wp_unslash( $_POST['_jetpack_file_hmac_media'][ $index ] ) ) ) : array( 'no', '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce should have been checked by the caller.
3532 3864
@@ -3572,9 +3904,9 @@
3572 3904 'type' => (string) $edited_media_item->post_mime_type,
3573 3905 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3574 3906 );
3575 3907
3576 - return (array) array( $response );
3908 + return array( $response );
3577 3909 }
3578 3910
3579 3911 $attachment_id = media_handle_upload(
3580 3912 '.jetpack.upload.',
@@ -3613,58 +3945,8 @@
3613 3945 return $uploaded_files;
3614 3946 }
3615 3947
3616 3948 /**
3617 - * Add help to the Jetpack page
3618 - *
3619 - * @since Jetpack (1.2.3)
3620 - * @return void
3621 - */
3622 - public function admin_help() {
3623 - $current_screen = get_current_screen();
3624 -
3625 - // Overview.
3626 - $current_screen->add_help_tab(
3627 - array(
3628 - 'id' => 'home',
3629 - 'title' => __( 'Home', 'jetpack' ),
3630 - 'content' =>
3631 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3632 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3633 - '<p>' . __( 'On this page, you are able to view the modules available within Jetpack, learn more about them, and activate or deactivate them as needed.', 'jetpack' ) . '</p>',
3634 - )
3635 - );
3636 -
3637 - // Screen Content.
3638 - if ( current_user_can( 'manage_options' ) ) {
3639 - $current_screen->add_help_tab(
3640 - array(
3641 - 'id' => 'settings',
3642 - 'title' => __( 'Settings', 'jetpack' ),
3643 - 'content' =>
3644 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3645 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3646 - '<ol>' .
3647 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3648 - '<li>' . __( 'Using the checkboxes next to each module, you can select multiple modules to toggle via the Bulk Actions menu at the top of the list.', 'jetpack' ) . '</li>' .
3649 - '</ol>' .
3650 - '<p>' . __( 'Using the tools on the right, you can search for specific modules, filter by module categories or which are active, or change the sorting order.', 'jetpack' ) . '</p>',
3651 - )
3652 - );
3653 - }
3654 -
3655 - // Help Sidebar.
3656 - $support_url = Redirect::get_url( 'jetpack-support' );
3657 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3658 - $current_screen->set_help_sidebar(
3659 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3660 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3661 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3662 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3663 - );
3664 - }
3665 -
3666 - /**
3667 3949 * Add action links for the Jetpack plugin.
3668 3950 *
3669 3951 * @param array $actions Plugin actions.
3670 3952 *
@@ -3672,9 +3954,9 @@
3672 3954 */
3673 3955 public function plugin_action_links( $actions ) {
3674 3956 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3675 3957 return array_merge(
3676 - array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3958 + array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack-settings#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3677 3959 $actions
3678 3960 );
3679 3961 }
3680 3962
@@ -3702,14 +3984,10 @@
3702 3984 'jetpack-plugins-page-js',
3703 3985 '_inc/build/plugins-page.js',
3704 3986 JETPACK__PLUGIN_FILE,
3705 3987 array(
3706 - 'in_footer' => true,
3707 - 'textdomain' => 'jetpack',
3708 - 'dependencies' => array(
3709 - 'wp-polyfill',
3710 - 'wp-components',
3711 - ),
3988 + 'in_footer' => true,
3989 + 'textdomain' => 'jetpack',
3712 3990 )
3713 3991 );
3714 3992 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3715 3993
@@ -3714,9 +3992,9 @@
3714 3992 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3715 3993
3716 3994 // Add objects to be passed to the initial state of the app.
3717 3995 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3718 - wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state() ) ) . '"));', 'before' );
3996 + 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' );
3719 3997
3720 3998 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3721 3999 }
3722 4000 }
@@ -3765,9 +4043,9 @@
3765 4043 // @todo provide way to go to specific calypso env.
3766 4044 self::get_calypso_host() . 'jetpack/connect'
3767 4045 )
3768 4046 );
3769 - exit;
4047 + exit( 0 );
3770 4048 }
3771 4049 }
3772 4050
3773 4051 /*
@@ -3802,8 +4080,28 @@
3802 4080 * Done!
3803 4081 */
3804 4082
3805 4083 /**
4084 + * Build the user-facing description stored alongside a registration error code.
4085 + *
4086 + * @since 16.2
4087 + *
4088 + * @param string $error_code The WP_Error code.
4089 + * @param string $message The WP_Error message.
4090 + * @return string The description, empty when the message is not user-facing copy.
4091 + */
4092 + public static function get_registration_error_description( $error_code, $message ) {
4093 + // Manager::validate_remote_register_response() does not always put user-facing copy in the
4094 + // message slot: wpcom_5??, wpcom_408 and wpcom_bad_response store the HTTP status there,
4095 + // and jetpack_id stores the raw response body, which can also overflow the state cookie.
4096 + if ( 'jetpack_id' === $error_code || is_numeric( $message ) ) {
4097 + return '';
4098 + }
4099 +
4100 + return mb_substr( (string) $message, 0, 250 );
4101 + }
4102 +
4103 + /**
3806 4104 * Handles the page load events for the Jetpack admin page
3807 4105 */
3808 4106 public function admin_page_load() {
3809 4107 $error = false;
@@ -3839,10 +4137,11 @@
3839 4137 $registered = static::connection()->try_registration();
3840 4138 if ( is_wp_error( $registered ) ) {
3841 4139 $error = $registered->get_error_code();
3842 4140 self::state( 'error', $error );
3843 - self::state( 'error', $registered->get_error_message() );
3844 4141
4142 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4143 +
3845 4144 /**
3846 4145 * Jetpack registration Error.
3847 4146 *
3848 4147 * @since 7.5.0
@@ -3866,12 +4165,8 @@
3866 4165 do_action( 'jetpack_connection_register_success', $from );
3867 4166
3868 4167 $url = $this->build_connect_url( true, $redirect, $from );
3869 4168
3870 - if ( ! empty( $_GET['onboarding'] ) ) {
3871 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3872 - }
3873 -
3874 4169 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3875 4170 $url = add_query_arg( 'auth_approved', 'true', $url );
3876 4171 }
3877 4172
@@ -3876,9 +4171,9 @@
3876 4171 }
3877 4172
3878 4173 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3879 4174 wp_safe_redirect( $url );
3880 - exit;
4175 + exit( 0 );
3881 4176 case 'activate':
3882 4177 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3883 4178 $error = 'cheatin';
3884 4179 break;
@@ -3892,9 +4187,9 @@
3892 4187 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3893 4188 }
3894 4189 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3895 4190 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3896 - exit;
4191 + exit( 0 );
3897 4192 case 'activate_default_modules':
3898 4193 check_admin_referer( 'activate_default_modules' );
3899 4194 self::log( 'activate_default_modules' );
3900 4195 self::restate();
@@ -3902,9 +4197,9 @@
3902 4197 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3903 4198 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3904 4199 self::activate_default_modules( $min_version, $max_version, $other_modules );
3905 4200 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3906 - exit;
4201 + exit( 0 );
3907 4202 case 'disconnect':
3908 4203 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3909 4204 $error = 'cheatin';
3910 4205 break;
@@ -3913,9 +4208,9 @@
3913 4208 check_admin_referer( 'jetpack-disconnect' );
3914 4209 self::log( 'disconnect' );
3915 4210 self::disconnect();
3916 4211 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3917 - exit;
4212 + exit( 0 );
3918 4213 case 'reconnect':
3919 4214 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3920 4215 $error = 'cheatin';
3921 4216 break;
@@ -3926,9 +4221,9 @@
3926 4221 self::disconnect();
3927 4222
3928 4223 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3929 4224 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3930 - exit;
4225 + exit( 0 );
3931 4226 case 'deactivate':
3932 4227 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3933 4228 $error = 'cheatin';
3934 4229 break;
@@ -3942,9 +4237,9 @@
3942 4237 self::state( 'message', 'module_deactivated' );
3943 4238 }
3944 4239 self::state( 'module', $modules );
3945 4240 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3946 - exit;
4241 + exit( 0 );
3947 4242 case 'unlink':
3948 4243 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3949 4244 check_admin_referer( 'jetpack-unlink' );
3950 4245 self::log( 'unlink' );
@@ -3954,32 +4249,9 @@
3954 4249 wp_safe_redirect( admin_url() );
3955 4250 } else {
3956 4251 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3957 4252 }
3958 - exit;
3959 - case 'onboard':
3960 - if ( ! current_user_can( 'manage_options' ) ) {
3961 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3962 - } else {
3963 - self::create_onboarding_token();
3964 - $url = $this->build_connect_url( true );
3965 -
3966 - $token = Jetpack_Options::get_option( 'onboarding' );
3967 -
3968 - if ( false !== ( $token ) ) {
3969 - $url = add_query_arg( 'onboarding', $token, $url );
3970 - }
3971 -
3972 - $calypso_env = ( new Host() )->get_calypso_env();
3973 - if ( ! empty( $calypso_env ) ) {
3974 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
3975 - }
3976 -
3977 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3978 - wp_safe_redirect( $url );
3979 - exit;
3980 - }
3981 - exit;
4253 + exit( 0 );
3982 4254 default:
3983 4255 /**
3984 4256 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3985 4257 *
@@ -4202,37 +4474,8 @@
4202 4474 endif;
4203 4475 }
4204 4476
4205 4477 /**
4206 - * We can't always respond to a signed XML-RPC request with a
4207 - * helpful error message. In some circumstances, doing so could
4208 - * leak information.
4209 - *
4210 - * Instead, track that the error occurred via a Jetpack_Option,
4211 - * and send that data back in the heartbeat.
4212 - * All this does is increment a number, but it's enough to find
4213 - * trends.
4214 - *
4215 - * @param WP_Error $xmlrpc_error The error produced during
4216 - * signature validation.
4217 - */
4218 - public function track_xmlrpc_error( $xmlrpc_error ) {
4219 - $code = is_wp_error( $xmlrpc_error )
4220 - ? $xmlrpc_error->get_error_code()
4221 - : 'should-not-happen';
4222 -
4223 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4224 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4225 - // No need to update the option if we already have
4226 - // this code stored.
4227 - return;
4228 - }
4229 - $xmlrpc_errors[ $code ] = true;
4230 -
4231 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4232 - }
4233 -
4234 - /**
4235 4478 * Initialize the jetpack stats instance only when needed
4236 4479 *
4237 4480 * @return void
4238 4481 */
@@ -4518,11 +4761,8 @@
4518 4761 *
4519 4762 * @param array $data The request data.
4520 4763 */
4521 4764 public static function authorize_ending_authorized( $data ) {
4522 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4523 - self::invalidate_onboarding_token();
4524 -
4525 4765 // If redirect_uri is SSO, ensure SSO module is enabled.
4526 4766 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4527 4767
4528 4768 /** This filter is documented in class.jetpack-cli.php */
@@ -4662,10 +4902,12 @@
4662 4902 $result = self::permit_ssl( true );
4663 4903 wp_send_json(
4664 4904 array(
4665 4905 'enabled' => $result,
4666 - 'message' => get_transient( 'jetpack_https_test_message' ),
4667 - )
4906 + 'message' => self::get_ssl_test_message(),
4907 + ),
4908 + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
4909 + JSON_UNESCAPED_SLASHES
4668 4910 );
4669 4911 }
4670 4912
4671 4913 /* Client API */
@@ -4672,8 +4914,10 @@
4672 4914
4673 4915 /**
4674 4916 * Verify the onboarding token.
4675 4917 *
4918 + * @deprecated since 13.9
4919 + *
4676 4920 * @param array $token_data Token data.
4677 4921 * @param string $token Token value.
4678 4922 * @param string $request_data JSON-encoded request data.
4679 4923 *
@@ -4679,8 +4923,9 @@
4679 4923 *
4680 4924 * @return mixed
4681 4925 */
4682 4926 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4927 + _deprecated_function( __METHOD__, '13.9' );
4683 4928 // Default to a blog token.
4684 4929 $token_type = 'blog';
4685 4930
4686 4931 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4708,9 +4953,9 @@
4708 4953 $jp_user = get_user_by( 'email', $jpo_user );
4709 4954 if ( is_a( $jp_user, 'WP_User' ) ) {
4710 4955 wp_set_current_user( $jp_user->ID );
4711 4956 $user_can = is_multisite()
4712 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
4957 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4713 4958 : current_user_can( 'manage_options' );
4714 4959 if ( $user_can ) {
4715 4960 $token_type = 'user';
4716 4961 $token->external_user_id = $jp_user->ID;
@@ -4727,11 +4972,13 @@
4727 4972
4728 4973 /**
4729 4974 * Create a random secret for validating onboarding payload
4730 4975 *
4976 + * @deprecated since 13.9
4731 4977 * @return string Secret token
4732 4978 */
4733 4979 public static function create_onboarding_token() {
4980 + _deprecated_function( __METHOD__, '13.9' );
4734 4981 $token = Jetpack_Options::get_option( 'onboarding' );
4735 4982 if ( false === ( $token ) ) {
4736 4983 $token = wp_generate_password( 32, false );
4737 4984 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4742,11 +4989,13 @@
4742 4989
4743 4990 /**
4744 4991 * Remove the onboarding token
4745 4992 *
4993 + * @deprecated since 13.9
4746 4994 * @return bool True on success, false on failure
4747 4995 */
4748 4996 public static function invalidate_onboarding_token() {
4997 + _deprecated_function( __METHOD__, '13.9' );
4749 4998 return Jetpack_Options::delete_option( 'onboarding' );
4750 4999 }
4751 5000
4752 5001 /**
@@ -4751,8 +5000,10 @@
4751 5000
4752 5001 /**
4753 5002 * Validate an onboarding token for a specific action
4754 5003 *
5004 + * @deprecated since 13.9
5005 + *
4755 5006 * @param string $token Onboarding token.
4756 5007 * @param string $action Action name.
4757 5008 *
4758 5009 * @return boolean True if token/action pair is accepted, false if not
@@ -4757,8 +5008,9 @@
4757 5008 *
4758 5009 * @return boolean True if token/action pair is accepted, false if not
4759 5010 */
4760 5011 public static function validate_onboarding_token_action( $token, $action ) {
5012 + _deprecated_function( __METHOD__, '13.9' );
4761 5013 // Compare tokens, bail if tokens do not match.
4762 5014 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4763 5015 return false;
4764 5016 }
@@ -4784,39 +5036,41 @@
4784 5036 * @return boolean
4785 5037 * @since 2.3.3
4786 5038 */
4787 5039 public static function permit_ssl( $force_recheck = false ) {
4788 - // Do some fancy tests to see if ssl is being supported.
4789 - if ( ! $force_recheck ) {
4790 - $ssl = get_transient( 'jetpack_https_test' );
5040 + if ( ! method_exists( Heartbeat::class, 'permit_ssl' ) ) {
5041 + // Skip the SSL-fail notice when the check cannot run.
5042 + return true;
4791 5043 }
4792 5044
4793 - if ( $force_recheck || false === $ssl ) {
4794 - $message = '';
4795 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4796 - $ssl = 0;
4797 - } else {
4798 - $ssl = 1;
5045 + return Heartbeat::permit_ssl( $force_recheck );
5046 + }
4799 5047
4800 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4801 - $ssl = 0;
4802 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4803 - } else {
4804 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4805 - if ( is_wp_error( $response ) ) {
4806 - $ssl = 0;
4807 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4808 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4809 - $ssl = 0;
4810 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4811 - }
4812 - }
4813 - }
4814 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4815 - set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5048 + /**
5049 + * Returns a localized message describing the last SSL connectivity failure, if any.
5050 + *
5051 + * The Connection package's canonical SSL check stores a neutral reason code; this maps it to
5052 + * a translated, `jetpack`-domain message for display in the admin notice and AJAX recheck.
5053 + *
5054 + * @since 16.1
5055 + *
5056 + * @return string The localized message, or an empty string when there is no failure.
5057 + */
5058 + public static function get_ssl_test_message() {
5059 + if ( ! method_exists( Heartbeat::class, 'get_ssl_test_error' ) ) {
5060 + return '';
4816 5061 }
4817 5062
4818 - return (bool) $ssl;
5063 + $error = Heartbeat::get_ssl_test_error();
5064 +
5065 + switch ( $error['code'] ) {
5066 + case 'no_ssl_support':
5067 + return __( 'WordPress reports no SSL support', 'jetpack' );
5068 + case 'bad_response':
5069 + return __( 'Response was not OK: ', 'jetpack' ) . $error['detail'];
5070 + default:
5071 + return '';
5072 + }
4819 5073 }
4820 5074
4821 5075 /**
4822 5076 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -4835,9 +5089,9 @@
4835 5089 <p><?php esc_html_e( 'Your site could not connect to WordPress.com via HTTPS. This could be due to any number of reasons, including faulty SSL certificates, misconfigured or missing SSL libraries, or network issues.', 'jetpack' ); ?></p>
4836 5090 <p>
4837 5091 <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4838 5092 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4839 - <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
5093 + <span id="jetpack-recheck-ssl-output"><?php echo esc_html( self::get_ssl_test_message() ); ?></span>
4840 5094 </p>
4841 5095 <p>
4842 5096 <?php
4843 5097 printf(
@@ -4856,18 +5110,18 @@
4856 5110 <script type="text/javascript">
4857 5111 jQuery( document ).ready( function( $ ) {
4858 5112 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4859 5113 var $this = $( this );
4860 - $this.html( <?php echo wp_json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5114 + $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4861 5115 $( '#jetpack-recheck-ssl-output' ).html( '' );
4862 5116 e.preventDefault();
4863 - var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce ); ?> };
5117 + var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
4864 5118 $.post( ajaxurl, data )
4865 5119 .done( function( response ) {
4866 5120 if ( response.enabled ) {
4867 5121 $( '#jetpack-ssl-warning' ).hide();
4868 5122 } else {
4869 - this.html( <?php echo wp_json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5123 + this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4870 5124 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4871 5125 }
4872 5126 }.bind( $this ) );
4873 5127 } );
@@ -5225,15 +5479,20 @@
5225 5479
5226 5480 /**
5227 5481 * Checks if the site is currently in an identity crisis.
5228 5482 *
5483 + * Now delegates to the Connection package so this matches what the heartbeat itself reports.
5484 + * Note the package guards on `Connection\Manager::is_connected()` where this used to guard on
5485 + * `Jetpack::is_connection_ready()`, so the `jetpack_is_connection_ready` filter no longer applies.
5486 + *
5487 + * @deprecated 16.2
5488 + *
5229 5489 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5230 5490 */
5231 5491 public static function check_identity_crisis() {
5232 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5233 - return false;
5234 - }
5235 - return Jetpack_Options::get_option( 'sync_error_idc' );
5492 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5493 +
5494 + return Identity_Crisis::check_identity_crisis();
5236 5495 }
5237 5496
5238 5497 /**
5239 5498 * Normalizes a url by doing three things:
@@ -5321,9 +5580,9 @@
5321 5580 *
5322 5581 * @return mixed
5323 5582 */
5324 5583 public static function set_suffix_on_min( $src, $handle ) {
5325 - if ( ! str_contains( $src, '.min.css' ) ) {
5584 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5326 5585 return $src;
5327 5586 }
5328 5587
5329 5588 if ( ! empty( self::$min_assets ) ) {
@@ -5359,8 +5618,10 @@
5359 5618 *
5360 5619 * Data passed in with the $data parameter will be available in the
5361 5620 * template file as $data['value']
5362 5621 *
5622 + * @html-template-var array $data
5623 + *
5363 5624 * @param string $template - Template file to load.
5364 5625 * @param array $data - Any data to pass along to the template.
5365 5626 * @return boolean - If template file was found.
5366 5627 **/
@@ -5378,8 +5639,19 @@
5378 5639 return false;
5379 5640 }
5380 5641
5381 5642 /**
5643 + * Register Jetpack-specific tests on the connection package's health test suite.
5644 + *
5645 + * @param \Automattic\Jetpack\Connection\Connection_Health_Tests $connection_tests The test suite instance.
5646 + */
5647 + public function register_jetpack_connection_tests( $connection_tests ) {
5648 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger/class-jetpack-cxn-tests.php';
5649 + $jetpack_tests = new Jetpack_Cxn_Tests();
5650 + $jetpack_tests->register_tests_on( $connection_tests );
5651 + }
5652 +
5653 + /**
5382 5654 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5383 5655 */
5384 5656 public function deprecated_hooks() {
5385 5657 $filter_deprecated_list = array(
@@ -5603,8 +5875,10 @@
5603 5875 'jetpack_contact_form_use_package' => array(
5604 5876 'replacement' => null,
5605 5877 'version' => 'jetpack-13.4.0',
5606 5878 ),
5879 + // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
5880 + // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
5607 5881 );
5608 5882
5609 5883 foreach ( $filter_deprecated_list as $tag => $args ) {
5610 5884 if ( has_filter( $tag ) ) {
@@ -5735,125 +6009,8 @@
5735 6009 return $css;
5736 6010 }
5737 6011
5738 6012 /**
5739 - * This methods removes all of the registered css files on the front end
5740 - * from Jetpack in favor of using a single file. In effect "imploding"
5741 - * all the files into one file.
5742 - *
5743 - * Pros:
5744 - * - Uses only ONE css asset connection instead of 15
5745 - * - Saves a minimum of 56k
5746 - * - Reduces server load
5747 - * - Reduces time to first painted byte
5748 - *
5749 - * Cons:
5750 - * - Loads css for ALL modules. However all selectors are prefixed so it
5751 - * should not cause any issues with themes.
5752 - * - Plugins/themes dequeuing styles no longer do anything. See
5753 - * jetpack_implode_frontend_css filter for a workaround
5754 - *
5755 - * For some situations developers may wish to disable css imploding and
5756 - * instead operate in legacy mode where each file loads seperately and
5757 - * can be edited individually or dequeued. This can be accomplished with
5758 - * the following line:
5759 - *
5760 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5761 - *
5762 - * @param bool $travis_test Is this a test run.
5763 - *
5764 - * @since 3.2
5765 - */
5766 - public function implode_frontend_css( $travis_test = false ) {
5767 - $do_implode = true;
5768 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5769 - $do_implode = false;
5770 - }
5771 -
5772 - // Do not implode CSS when the page loads via the AMP plugin.
5773 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
5774 - $do_implode = false;
5775 - }
5776 -
5777 - /*
5778 - * Only proceed if at least 2 modules with concatenated CSS are active.
5779 - * There is no point in serving a big concatenated CSS file
5780 - * if there are no features (or only one) that actually need some CSS loaded.
5781 - */
5782 - $active_modules = self::get_active_modules();
5783 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
5784 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
5785 - if ( $active_module_with_css_count < 2 ) {
5786 - $do_implode = false;
5787 - }
5788 -
5789 - /**
5790 - * Allow CSS to be concatenated into a single jetpack.css file.
5791 - *
5792 - * @since 3.2.0
5793 - *
5794 - * @param bool $do_implode Should CSS be concatenated? Default to true.
5795 - */
5796 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5797 -
5798 - // Do not use the imploded file when default behavior was altered through the filter.
5799 - if ( ! $do_implode ) {
5800 - return;
5801 - }
5802 -
5803 - // We do not want to use the imploded file in dev mode, or if not connected.
5804 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
5805 - if ( ! $travis_test ) {
5806 - return;
5807 - }
5808 - }
5809 -
5810 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
5811 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
5812 - return;
5813 - }
5814 -
5815 - /*
5816 - * Now we assume Jetpack is connected and able to serve the single
5817 - * file.
5818 - *
5819 - * In the future there will be a check here to serve the file locally
5820 - * or potentially from the Jetpack CDN
5821 - *
5822 - * For now:
5823 - * - Enqueue a single imploded css file
5824 - * - Zero out the style_loader_tag for the bundled ones
5825 - * - Be happy, drink scotch
5826 - */
5827 -
5828 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
5829 -
5830 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
5831 -
5832 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
5833 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
5834 - }
5835 -
5836 - /**
5837 - * Removes styles that are part of concatenated group.
5838 - *
5839 - * @param string $tag Style tag.
5840 - * @param string $handle Style handle.
5841 - *
5842 - * @return string
5843 - */
5844 - public function concat_remove_style_loader_tag( $tag, $handle ) {
5845 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
5846 - $tag = '';
5847 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5848 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
5849 - }
5850 - }
5851 -
5852 - return $tag;
5853 - }
5854 -
5855 - /**
5856 6013 * Check the heartbeat data
5857 6014 *
5858 6015 * Organizes the heartbeat data by severity. For example, if the site
5859 6016 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -5865,9 +6022,23 @@
5865 6022 *
5866 6023 * $return array $filtered_data
5867 6024 */
5868 6025 public static function jetpack_check_heartbeat_data() {
5869 - $raw_data = Jetpack_Heartbeat::generate_stats_array();
6026 + /*
6027 + * Site environment stats (incl. wp-version/php-version checked below) now live in the Connection package,
6028 + * and the IDC stat is contributed by the Connection package's `jetpack_heartbeat_stats_array` filter
6029 + * callback. We rebuild the stat here rather than running that filter: the filter's callbacks have side
6030 + * effects (Connection\Manager::add_stats_to_heartbeat() consumes and deletes the `xmlrpc_errors` option)
6031 + * and return non-scalar values, neither of which is appropriate for this read-only diagnostic.
6032 + */
6033 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
6034 + ? Heartbeat::get_environment_stats()
6035 + : array();
6036 + $raw_data = array_merge(
6037 + Jetpack_Heartbeat::generate_stats_array(),
6038 + $env_stats,
6039 + array( 'identitycrisis' => Identity_Crisis::check_identity_crisis() ? 'yes' : 'no' )
6040 + );
5870 6041
5871 6042 $good = array();
5872 6043 $caution = array();
5873 6044 $bad = array();
@@ -6126,13 +6297,20 @@
6126 6297 }
6127 6298 }
6128 6299
6129 6300 /**
6130 - * Returns a boolean for whether backups UI should be displayed or not.
6301 + * Whether UI for backups should be displayed.
6131 6302 *
6303 + * On WPCom platforms this is gated on the backups-self-serve site feature.
6304 + * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
6305 + *
6132 6306 * @return bool Should backups UI be displayed?
6133 6307 */
6134 6308 public static function show_backups_ui() {
6309 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6310 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
6311 + }
6312 +
6135 6313 /**
6136 6314 * Whether UI for backups should be displayed.
6137 6315 *
6138 6316 * @since 6.5.0
@@ -6142,8 +6320,24 @@
6142 6320 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6143 6321 }
6144 6322
6145 6323 /**
6324 + * Whether UI for security scanning should be displayed.
6325 + *
6326 + * On WPCom platforms this is gated on the scan-self-serve site feature.
6327 + * On self-hosted Jetpack sites it always returns true.
6328 + *
6329 + * @return bool Should scan UI be displayed?
6330 + */
6331 + public static function show_scan_ui() {
6332 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6333 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
6334 + }
6335 +
6336 + return true;
6337 + }
6338 +
6339 + /**
6146 6340 * Clean leftoveruser meta.
6147 6341 *
6148 6342 * Delete Jetpack-related user meta when it is no longer needed.
6149 6343 *
@@ -6230,8 +6424,25 @@
6230 6424 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6231 6425 ),
6232 6426 );
6233 6427
6428 + $products['growth'] = array(
6429 + 'title' => __( 'Jetpack Growth', 'jetpack' ),
6430 + 'slug' => 'jetpack_growth_yearly',
6431 + 'description' => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
6432 + 'show_promotion' => true,
6433 + 'discount_percent' => 50,
6434 + 'included_in_plans' => array( 'complete' ),
6435 + 'features' => array(
6436 + _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
6437 + _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
6438 + _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
6439 + _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
6440 + _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
6441 + _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
6442 + ),
6443 + );
6444 +
6234 6445 $products['scan'] = array(
6235 6446 'title' => __( 'Jetpack Scan', 'jetpack' ),
6236 6447 'slug' => 'jetpack_scan',
6237 6448 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6378,8 +6589,56 @@
6378 6589 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6379 6590 . '</a>';
6380 6591
6381 6592 return $plugin_meta;
6593 + }
6594 +
6595 + /**
6596 + * Lazy instantiation of the Plugin_Tracking object.
6597 + *
6598 + * @since 13.9
6599 + *
6600 + * @return void
6601 + */
6602 + public function initialize_tracking() {
6603 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6604 + // Only need to run once.
6605 + return;
6606 + }
6607 +
6608 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6609 + ( new Plugin_Tracking() )->init();
6610 + }
6611 + }
6612 +
6613 + /**
6614 + * Run the "initialize tracking" hook.
6615 + *
6616 + * @since 13.9
6617 + */
6618 + public function run_initialize_tracking_action() {
6619 + /**
6620 + * Fires when the tracking needs to be initialized.
6621 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6622 + *
6623 + * @since 13.9
6624 + */
6625 + do_action( 'jetpack_initialize_tracking' );
6626 + }
6627 +
6628 + /**
6629 + * Initialize REST jsonAPI if needed.
6630 + *
6631 + * @return void
6632 + */
6633 + public function maybe_initialize_rest_jsonapi() {
6634 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6635 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6636 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6637 +
6638 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6639 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6640 + }
6382 6641 }
6383 6642
6384 6643 /**
6385 6644 * Run plugin post-activation actions if we need to.