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 +795 -520 13.8.3 → 16.3-a.5 View file →
@@ -21,17 +21,24 @@
21 21 use Automattic\Jetpack\CookieState;
22 22 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
23 23 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
24 24 use Automattic\Jetpack\Errors;
25 +use Automattic\Jetpack\Feature_Policy;
25 26 use Automattic\Jetpack\Files;
27 +use Automattic\Jetpack\Heartbeat;
26 28 use Automattic\Jetpack\Identity_Crisis;
29 +use Automattic\Jetpack\Import\Main as Import_Main;
27 30 use Automattic\Jetpack\Licensing;
28 31 use Automattic\Jetpack\Modules;
29 32 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
33 +use Automattic\Jetpack\Newsletter\Reader_Link;
30 34 use Automattic\Jetpack\Paths;
31 35 use Automattic\Jetpack\Plugin\Deprecate;
32 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
33 38 use Automattic\Jetpack\Redirect;
39 +use Automattic\Jetpack\Scan_Page\Jetpack_Scan as Scan_Page_Init;
40 +use Automattic\Jetpack\SEO\Initializer as Jetpack_SEO_Initializer;
34 41 use Automattic\Jetpack\Status;
35 42 use Automattic\Jetpack\Status\Host;
36 43 use Automattic\Jetpack\Status\Visitor;
37 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -38,9 +45,14 @@
38 45 use Automattic\Jetpack\Sync\Health;
39 46 use Automattic\Jetpack\Sync\Sender;
40 47 use Automattic\Jetpack\Terms_Of_Service;
41 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
42 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
43 55 /*
44 56 Options:
45 57 jetpack_options (array)
46 58 An array of options.
@@ -75,75 +87,8 @@
75 87 */
76 88 public $xmlrpc_server = null;
77 89
78 90 /**
79 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
80 - *
81 - * See $concatenated_style_handles for the list of handles,
82 - * and the implode_frontend_css method for more details.
83 - *
84 - * When updating this list, make sure to update $concatenated_style_handles as well.
85 - *
86 - * @var array List of Jetpack modules.
87 - */
88 - public $modules_with_concatenated_css = array(
89 - 'carousel',
90 - 'contact-form',
91 - 'infinite-scroll',
92 - 'likes',
93 - 'related-posts',
94 - 'sharedaddy',
95 - 'shortcodes',
96 - 'subscriptions',
97 - 'tiled-gallery',
98 - 'widgets',
99 - );
100 -
101 - /**
102 - * The handles of styles that are concatenated into jetpack.css.
103 - *
104 - * When making changes to that list,
105 - * you must also update concat_list in tools/webpack.config.css.js,
106 - * and to $modules_with_concatenated_css if necessary.
107 - *
108 - * @var array The handles of styles that are concatenated into jetpack.css.
109 - */
110 - public $concatenated_style_handles = array(
111 - 'jetpack-carousel-swiper-css',
112 - 'jetpack-carousel',
113 - 'grunion.css',
114 - 'the-neverending-homepage',
115 - 'jetpack_likes',
116 - 'jetpack_related-posts',
117 - 'sharedaddy',
118 - 'jetpack-slideshow',
119 - 'presentations',
120 - 'quiz',
121 - 'jetpack-subscriptions',
122 - 'jetpack-responsive-videos',
123 - 'jetpack-social-menu',
124 - 'tiled-gallery',
125 - 'jetpack_display_posts_widget',
126 - 'gravatar-profile-widget',
127 - 'goodreads-widget',
128 - 'jetpack_social_media_icons_widget',
129 - 'jetpack-top-posts-widget',
130 - 'jetpack_image_widget',
131 - 'jetpack-my-community-widget',
132 - 'jetpack-authors-widget',
133 - 'wordads',
134 - 'eu-cookie-law-style',
135 - 'flickr-widget-style',
136 - 'jetpack-search-widget',
137 - 'jetpack-simple-payments-widget-style',
138 - 'jetpack-widget-social-icons-styles',
139 - 'wpcom_instagram_widget',
140 - 'milestone-widget',
141 - 'subscribe-modal-css',
142 - 'subscribe-overlay-css',
143 - );
144 -
145 - /**
146 91 * Contains all assets that have had their URL rewritten to minified versions.
147 92 *
148 93 * @var array
149 94 */
@@ -164,8 +109,11 @@
164 109 ),
165 110 'latex' => array(
166 111 array( 'wp-latex/wp-latex.php', 'WP LaTeX' ),
167 112 ),
113 + 'random-redirect' => array(
114 + array( 'random-redirect/random-redirect.php', 'Random Redirect' ),
115 + ),
168 116 'sharedaddy' => array(
169 117 array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
170 118 array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
171 119 ),
@@ -253,8 +201,11 @@
253 201 'Wordfence Security' => 'wordfence/wordfence.php',
254 202 'All In One WP Security & Firewall' => 'all-in-one-wp-security-and-firewall/wp-security.php',
255 203 'iThemes Security' => 'better-wp-security/better-wp-security.php',
256 204 ),
205 + 'random-redirect' => array(
206 + 'Random Redirect 2' => 'random-redirect-2/random-redirect.php',
207 + ),
257 208 'related-posts' => array(
258 209 'YARPP' => 'yet-another-related-posts-plugin/yarpp.php',
259 210 'WordPress Related Posts' => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
260 211 'nrelate Related Content' => 'nrelate-related-content/nrelate-related.php',
@@ -324,9 +275,8 @@
324 275 * Graph tags via filter when their Social Meta modules are active:
325 276 *
326 277 * - All in One SEO Pack, All in one SEO Pack Pro
327 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
328 - * - SEOPress, SEOPress Pro
329 279 *
330 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:
331 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
332 282 *
@@ -369,8 +319,10 @@
369 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
370 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
371 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
372 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
373 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
374 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
375 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
376 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -481,8 +433,16 @@
481 433 */
482 434 public static $instance = false;
483 435
484 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 + /**
485 445 * Singleton
486 446 *
487 447 * @static
488 448 */
@@ -522,9 +482,9 @@
522 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
523 483 self::update_active_modules( $modules );
524 484 }
525 485
526 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
527 487
528 488 // Upgrade to 4.3.0.
529 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
530 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -579,8 +539,16 @@
579 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
580 540 } // Should we have some type of fallback if something fails here?
581 541 }
582 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 +
583 551 if ( did_action( 'wp_loaded' ) ) {
584 552 self::upgrade_on_load();
585 553 } else {
586 554 add_action(
@@ -686,35 +654,15 @@
686 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
687 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
688 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
689 657
690 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
691 -
692 - add_filter(
693 - 'jetpack_signature_check_token',
694 - array( __CLASS__, 'verify_onboarding_token' ),
695 - 10,
696 - 3
697 - );
698 -
699 658 /**
700 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.
701 663 */
702 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
703 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
704 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
705 - /**
706 - * 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.
707 - *
708 - * 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.
709 - */
710 - if ( is_admin() ) {
711 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
712 - } else {
713 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
714 - }
715 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
716 -
717 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
718 666
719 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
720 668
@@ -723,8 +671,13 @@
723 671
724 672 // Set up the REST authentication hooks.
725 673 Connection_Rest_Authentication::init();
726 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 +
727 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
728 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
729 682
730 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -757,25 +710,8 @@
757 710
758 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
759 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
760 713
761 - /*
762 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
763 - * We should make sure to only do this for front end links.
764 - */
765 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
766 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
767 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
768 -
769 - /*
770 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
771 - * so they point moderation links on emails to Calypso.
772 - */
773 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
774 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
775 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
776 - }
777 -
778 714 add_action(
779 715 'plugins_loaded',
780 716 function () {
781 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -786,18 +722,10 @@
786 722
787 723 // Update the site's Jetpack plan and products from API on heartbeats.
788 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
789 725
790 - /**
791 - * This is the hack to concatenate all css files into one.
792 - * For description and reasoning see the implode_frontend_css method.
793 - *
794 - * Super late priority so we catch all the registered styles.
795 - */
796 - if ( ! is_admin() ) {
797 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
798 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
799 - }
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' ) );
800 728
801 729 // Actually push the stats on shutdown.
802 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
803 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -805,8 +733,10 @@
805 733
806 734 // After a successful connection.
807 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
808 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' ) );
809 739
810 740 // Actions for Manager::authorize().
811 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
812 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -846,18 +776,142 @@
846 776
847 777 // Register product descriptions for partner coupon usage.
848 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
849 779
850 - // Actions for conditional recommendations.
851 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
852 -
853 780 // Add 5-star
854 781 add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
782 + add_action( 'init', array( Deprecate::class, 'instance' ) );
855 783
856 - Deprecate::instance();
784 + // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
785 + \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
786 +
787 + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the
788 + // Jetpack plugin for now: the Connection package no longer auto-wires these, so
789 + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet.
790 + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init();
857 791 }
858 792
859 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 + /**
860 914 * Before everything else starts getting initalized, we need to initialize Jetpack using the
861 915 * Config object.
862 916 */
863 917 public function configure() {
@@ -866,13 +920,10 @@
866 920 foreach (
867 921 array(
868 922 'jitm',
869 923 'sync',
924 + 'account_protection',
870 925 'waf',
871 - 'videopress',
872 - 'stats',
873 - 'stats_admin',
874 - 'import',
875 926 )
876 927 as $feature
877 928 ) {
878 929 $config->ensure( $feature );
@@ -877,8 +928,103 @@
877 928 ) {
878 929 $config->ensure( $feature );
879 930 }
880 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 +
881 1027 $config->ensure(
882 1028 'connection',
883 1029 array(
884 1030 'slug' => 'jetpack',
@@ -896,12 +1042,8 @@
896 1042 );
897 1043
898 1044 $config->ensure( 'search' );
899 1045
900 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
901 - $config->ensure( 'wordads' );
902 - }
903 -
904 1046 if ( ! $this->connection_manager ) {
905 1047 $this->connection_manager = new Connection_Manager( 'jetpack' );
906 1048 }
907 1049
@@ -909,8 +1051,10 @@
909 1051 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
910 1052 $config->ensure( 'publicize' );
911 1053 }
912 1054
1055 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1056 +
913 1057 /*
914 1058 * Load things that should only be in Network Admin.
915 1059 *
916 1060 * For now blow away everything else until a more full
@@ -925,8 +1069,9 @@
925 1069 $is_connection_ready = self::is_connection_ready();
926 1070
927 1071 if ( $is_connection_ready ) {
928 1072 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1073 + $this->run_initialize_tracking_action();
929 1074
930 1075 Jetpack_Heartbeat::init();
931 1076 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
932 1077 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -931,8 +1076,19 @@
931 1076 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
932 1077 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
933 1078 Jetpack_Search_Performance_Logger::init();
934 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 + );
935 1091 }
936 1092
937 1093 // Initialize remote file upload request handlers.
938 1094 $this->add_remote_request_handlers();
@@ -942,20 +1098,10 @@
942 1098 */
943 1099 if ( $is_connection_ready ) {
944 1100 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
945 1101 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
946 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
947 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1102 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
948 1103 }
949 -
950 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
951 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
952 - } else {
953 - /**
954 - * Initialize tracking right after the user agrees to the terms of service.
955 - */
956 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
957 - }
958 1104 }
959 1105
960 1106 /**
961 1107 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -963,15 +1109,58 @@
963 1109 *
964 1110 * @action plugins_loaded
965 1111 */
966 1112 public function late_initialization() {
967 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1113 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
968 1114
969 - 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 + }
970 1134
971 - // Initialize Boost Speed Score
972 - new Speed_Score( array(), 'jetpack-dashboard' );
1135 + Scan_Page_Init::initialize();
1136 + Jetpack_SEO_Initializer::init();
973 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 +
974 1163 /**
975 1164 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
976 1165 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
977 1166 *
@@ -1009,8 +1198,10 @@
1009 1198
1010 1199 /**
1011 1200 * Redirect edit post links to Calypso.
1012 1201 *
1202 + * @deprecated since 13.9
1203 + *
1013 1204 * @param string $default_url Post edit URL.
1014 1205 * @param int $post_id Post ID.
1015 1206 *
1016 1207 * @return string
@@ -1015,8 +1206,10 @@
1015 1206 *
1016 1207 * @return string
1017 1208 */
1018 1209 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1210 + _deprecated_function( __METHOD__, '13.9' );
1211 +
1019 1212 $post = get_post( $post_id );
1020 1213
1021 1214 if ( empty( $post ) ) {
1022 1215 return $default_url;
@@ -1047,13 +1240,17 @@
1047 1240
1048 1241 /**
1049 1242 * Redirect edit comment links to Calypso.
1050 1243 *
1244 + * @deprecated since 13.9
1245 + *
1051 1246 * @param string $url Comment edit URL.
1052 1247 *
1053 1248 * @return string
1054 1249 */
1055 1250 public function point_edit_comment_links_to_calypso( $url ) {
1251 + _deprecated_function( __METHOD__, '13.9' );
1252 +
1056 1253 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1057 1254 $query_args = null;
1058 1255 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1059 1256
@@ -1077,8 +1274,9 @@
1077 1274 $jetpack_callables = array(
1078 1275 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1079 1276 'updates' => array( 'Jetpack', 'get_updates' ),
1080 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' ),
1081 1279 );
1082 1280 return array_merge( $callables, $jetpack_callables );
1083 1281 }
1084 1282
@@ -1121,16 +1319,8 @@
1121 1319 * @param string $cap Capability name.
1122 1320 */
1123 1321 public function jetpack_custom_caps( $caps, $cap ) {
1124 1322 switch ( $cap ) {
1125 - case 'jetpack_manage_modules':
1126 - case 'jetpack_activate_modules':
1127 - case 'jetpack_deactivate_modules':
1128 - $caps = array( 'manage_options' );
1129 - break;
1130 - case 'jetpack_configure_modules':
1131 - $caps = array( 'manage_options' );
1132 - break;
1133 1323 case 'jetpack_manage_autoupdates':
1134 1324 $caps = array(
1135 1325 'manage_options',
1136 1326 'update_plugins',
@@ -1148,9 +1338,9 @@
1148 1338 if ( $is_offline_mode ) {
1149 1339 $caps = array( 'manage_options' );
1150 1340 break;
1151 1341 } else {
1152 - $caps = array( 'read' );
1342 + $caps = array( 'edit_posts' );
1153 1343 }
1154 1344 break;
1155 1345 }
1156 1346 return $caps;
@@ -1593,29 +1783,8 @@
1593 1783 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1594 1784 }
1595 1785
1596 1786 /**
1597 - * Whether the site is currently onboarding or not.
1598 - * A site is considered as being onboarded if it currently has an onboarding token.
1599 - *
1600 - * @since 5.8
1601 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1602 - *
1603 - * @access public
1604 - * @static
1605 - *
1606 - * @return bool True if the site is currently onboarding, false otherwise
1607 - */
1608 - public static function is_onboarding() {
1609 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1610 -
1611 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1612 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1613 - }
1614 - return ( new Status() )->is_onboarding();
1615 - }
1616 -
1617 - /**
1618 1787 * Determines reason for Jetpack offline mode.
1619 1788 */
1620 1789 public static function development_mode_trigger_text() {
1621 1790 $status = new Status();
@@ -1629,11 +1798,10 @@
1629 1798 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1630 1799 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1631 1800 } elseif ( $status->is_local_site() ) {
1632 1801 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1633 - /** This filter is documented in packages/status/src/class-status.php */
1634 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1635 - $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' );
1636 1804 } else {
1637 1805 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1638 1806 }
1639 1807
@@ -1738,18 +1906,11 @@
1738 1906 */
1739 1907 public static function load_modules() {
1740 1908 $status = new Status();
1741 1909
1742 - if ( method_exists( $status, 'is_onboarding' ) ) {
1743 - $is_onboarding = $status->is_onboarding();
1744 - } else {
1745 - $is_onboarding = self::is_onboarding();
1746 - }
1747 -
1748 1910 if (
1749 1911 ! self::is_connection_ready()
1750 1912 && ! $status->is_offline_mode()
1751 - && ! $is_onboarding
1752 1913 && (
1753 1914 ! is_multisite()
1754 1915 || ! get_site_option( 'jetpack_protect_active' )
1755 1916 )
@@ -1870,22 +2031,14 @@
1870 2031 *
1871 2032 * @todo Store the result in core's object cache maybe?
1872 2033 */
1873 2034 public static function get_active_plugins() {
1874 - $active_plugins = (array) get_option( 'active_plugins', array() );
1875 -
1876 - if ( is_multisite() ) {
1877 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1878 - // whereas active_plugins stores them in the values.
1879 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1880 - if ( $network_plugins ) {
1881 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1882 - }
2035 + // Older Connection copies can load first and lack this method.
2036 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2037 + return array();
1883 2038 }
1884 2039
1885 - sort( $active_plugins );
1886 -
1887 - return array_unique( $active_plugins );
2040 + return Heartbeat::get_active_plugins();
1888 2041 }
1889 2042
1890 2043 /**
1891 2044 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2023,8 +2176,10 @@
2023 2176 *
2024 2177 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2025 2178 */
2026 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).
2027 2182 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2028 2183 }
2029 2184 }
2030 2185
@@ -2127,9 +2282,9 @@
2127 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.
2128 2283 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2129 2284 }
2130 2285 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2131 - exit;
2286 + exit( 0 );
2132 2287 }
2133 2288 }
2134 2289
2135 2290 /**
@@ -2176,8 +2331,12 @@
2176 2331 default:
2177 2332 break;
2178 2333 }
2179 2334 }
2335 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2336 + Feature_Policy::ensure_hooks();
2337 + }
2338 +
2180 2339 /**
2181 2340 * Filters the array of default modules.
2182 2341 *
2183 2342 * @since 2.5.0
@@ -2270,8 +2429,17 @@
2270 2429 }
2271 2430 }
2272 2431 }
2273 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 +
2274 2442 return $modules;
2275 2443 }
2276 2444
2277 2445 /**
@@ -2328,23 +2496,30 @@
2328 2496
2329 2497 /**
2330 2498 * Return module name translation. Uses matching string created in modules/module-headings.php.
2331 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 + *
2332 2504 * @since 3.9.2
2333 2505 *
2334 2506 * @param array $modules Array of Jetpack modules.
2335 2507 *
2336 - * @return string|void
2508 + * @return array
2337 2509 */
2338 2510 public static function get_translated_modules( $modules ) {
2339 2511 foreach ( $modules as $index => $module ) {
2340 2512 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2341 - if ( isset( $module['name'] ) ) {
2342 - $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;
2343 2518 }
2344 - if ( isset( $module['description'] ) ) {
2345 - $modules[ $index ]['description'] = $i18n_module['description'];
2346 - $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;
2347 2522 }
2348 2523 if ( isset( $module['module_tags'] ) ) {
2349 2524 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2350 2525 }
@@ -2509,9 +2684,9 @@
2509 2684 ),
2510 2685 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2511 2686 );
2512 2687 wp_safe_redirect( $url );
2513 - exit;
2688 + exit( 0 );
2514 2689 }
2515 2690 }
2516 2691
2517 2692 /**
@@ -2657,9 +2832,9 @@
2657 2832 * @return string $url module configuration URL.
2658 2833 */
2659 2834 public static function module_configuration_url( $module ) {
2660 2835 $module = self::get_module_slug( $module );
2661 - $default_url = self::admin_url() . "#/settings?term=$module";
2836 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2662 2837 /**
2663 2838 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2664 2839 *
2665 2840 * @since 6.9.0
@@ -2717,9 +2892,9 @@
2717 2892 if ( $update ) {
2718 2893 update_option( 'active_plugins', array_filter( $plugins ) );
2719 2894 }
2720 2895 }
2721 - exit;
2896 + exit( 0 );
2722 2897 }
2723 2898
2724 2899 /**
2725 2900 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2744,12 +2919,18 @@
2744 2919 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2745 2920
2746 2921 Health::on_jetpack_activated();
2747 2922
2923 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2924 +
2748 2925 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2749 2926 Sync_Actions::do_only_first_initial_sync();
2750 2927 }
2751 2928
2929 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2930 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2931 + }
2932 +
2752 2933 self::plugin_initialize();
2753 2934 }
2754 2935
2755 2936 /**
@@ -2784,9 +2965,9 @@
2784 2965
2785 2966 if ( $plugins_path === $referer['path'] ) {
2786 2967 $source_type = 'list';
2787 2968 } elseif ( $plugins_install_path === $referer['path'] ) {
2788 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
2969 + $tab = $query_parts['tab'] ?? 'featured';
2789 2970 switch ( $tab ) {
2790 2971 case 'popular':
2791 2972 $source_type = 'popular';
2792 2973 break;
@@ -2796,10 +2977,10 @@
2796 2977 case 'favorites':
2797 2978 $source_type = 'favorites';
2798 2979 break;
2799 2980 case 'search':
2800 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2801 - $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;
2802 2983 break;
2803 2984 default:
2804 2985 $source_type = 'featured';
2805 2986 }
@@ -2808,29 +2989,171 @@
2808 2989 return array( $source_type, $source_query );
2809 2990 }
2810 2991
2811 2992 /**
2812 - * Runs before bumping version numbers up to a new version
2993 + * Runs before bumping version numbers up to a new version.
2813 2994 *
2814 - * @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.
2815 3001 * @param string $old_version Old Version:timestamp or false if not set yet.
2816 3002 */
2817 - public static function do_version_bump( $version, $old_version ) {
2818 - if ( $old_version ) { // For existing Jetpack installations.
2819 - 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 + }
2820 3006
2821 - // If a front end page is visited after the update, the 'wp' action will fire.
2822 - 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 + }
2823 3026
2824 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2825 - 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;
2826 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 + }
2827 3043 }
2828 3044
2829 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 + /**
2830 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
2831 3153 */
2832 3154 public static function set_update_modal_display() {
3155 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2833 3156 self::state( 'display_update_modal', true );
2834 3157 }
2835 3158
2836 3159 /**
@@ -2835,11 +3158,16 @@
2835 3158
2836 3159 /**
2837 3160 * Enqueues the block library styles.
2838 3161 *
3162 + * Only ever used by the release post update modal, which has been removed.
3163 + *
3164 + * @deprecated 16.2
3165 + *
2839 3166 * @param string $hook The current admin page.
2840 3167 */
2841 3168 public static function enqueue_block_style( $hook ) {
3169 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2842 3170 if ( 'toplevel_page_jetpack' === $hook ) {
2843 3171 wp_enqueue_style( 'wp-block-library' );
2844 3172 }
2845 3173 }
@@ -2928,8 +3256,12 @@
2928 3256 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2929 3257 self::disconnect();
2930 3258 Jetpack_Options::delete_option( 'version' );
2931 3259 }
3260 +
3261 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3262 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3263 + }
2932 3264 }
2933 3265
2934 3266 /**
2935 3267 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -2957,9 +3289,9 @@
2957 3289 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2958 3290 }
2959 3291
2960 3292 /**
2961 - * Happens after a successfull disconnection.
3293 + * Happens after a successful disconnection.
2962 3294 *
2963 3295 * @static
2964 3296 */
2965 3297 public static function jetpack_site_disconnected() {
@@ -2964,8 +3296,10 @@
2964 3296 */
2965 3297 public static function jetpack_site_disconnected() {
2966 3298 Identity_Crisis::clear_all_idc_options();
2967 3299
3300 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3301 +
2968 3302 // Delete all the sync related data. Since it could be taking up space.
2969 3303 Sender::get_instance()->uninstall();
2970 3304
2971 3305 /**
@@ -3029,10 +3363,17 @@
3029 3363 * @param mixed $code Error code to log.
3030 3364 * @param mixed $data Data to log.
3031 3365 */
3032 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 +
3033 3374 // only grab the latest 200 entries.
3034 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3375 + $log = array_slice( $raw_log, -199, 199 );
3035 3376
3036 3377 // Append our event to the log.
3037 3378 $log_entry = array(
3038 3379 'time' => time(),
@@ -3132,8 +3473,15 @@
3132 3473
3133 3474 /**
3134 3475 * Return stat data for WPCOM sync.
3135 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 + *
3136 3484 * @param bool $encode JSON encode the result.
3137 3485 * @param bool $extended Adds additional stats data.
3138 3486 *
3139 3487 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3138,10 +3486,15 @@
3138 3486 *
3139 3487 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3140 3488 */
3141 3489 public static function get_stat_data( $encode = true, $extended = true ) {
3142 - $data = Jetpack_Heartbeat::generate_stats_array();
3490 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3143 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 +
3144 3497 if ( $extended ) {
3145 3498 $additional_data = self::get_additional_stat_data();
3146 3499 $data = array_merge( $data, $additional_data );
3147 3500 }
@@ -3146,9 +3499,9 @@
3146 3499 $data = array_merge( $data, $additional_data );
3147 3500 }
3148 3501
3149 3502 if ( $encode ) {
3150 - return wp_json_encode( $data );
3503 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3151 3504 }
3152 3505
3153 3506 return $data;
3154 3507 }
@@ -3227,8 +3580,9 @@
3227 3580 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3228 3581 // Upgrade: 1.1 -> 1.1.1
3229 3582 // Check and see if host can verify the Jetpack servers' SSL certificate.
3230 3583 $args = array();
3584 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3231 3585 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3232 3586 }
3233 3587
3234 3588 if (
@@ -3245,12 +3599,8 @@
3245 3599 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3246 3600 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3247 3601 }
3248 3602
3249 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3250 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3251 - }
3252 -
3253 3603 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3254 3604
3255 3605 if ( self::is_connection_ready() || $is_offline_mode ) {
3256 3606 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3289,8 +3639,9 @@
3289 3639 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3290 3640 * This function artificially throws errors for such cases (per a specific list).
3291 3641 *
3292 3642 * @param string $plugin The activated plugin.
3643 + * @throws RuntimeException If a conflicting plugin is detected.
3293 3644 */
3294 3645 public function throw_error_on_activate_plugin( $plugin ) {
3295 3646 $active_modules = self::get_active_modules();
3296 3647
@@ -3312,9 +3663,9 @@
3312 3663 }
3313 3664
3314 3665 if ( $throw ) {
3315 3666 /* translators: Plugin name to deactivate. */
3316 - trigger_error( sprintf( esc_html__( 'Jetpack contains the most recent version of the old “%1$s” plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
3667 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3317 3668 }
3318 3669 }
3319 3670 }
3320 3671
@@ -3425,18 +3776,17 @@
3425 3776 if ( ! is_int( $status_code ) ) {
3426 3777 $status_code = 400;
3427 3778 }
3428 3779
3429 - status_header( $status_code );
3430 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3780 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3431 3781 }
3432 3782
3433 - status_header( 200 );
3434 3783 if ( true === $response ) {
3435 - exit;
3784 + status_header( 200 );
3785 + exit( 0 );
3436 3786 }
3437 3787
3438 - die( wp_json_encode( (object) $response ) );
3788 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3439 3789 }
3440 3790
3441 3791 /**
3442 3792 * Uploads a file gotten from the global $_FILES.
@@ -3497,9 +3847,9 @@
3497 3847 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3498 3848 }
3499 3849
3500 3850 $uploaded_files = array();
3501 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3851 + $global_post = $GLOBALS['post'] ?? null;
3502 3852 unset( $GLOBALS['post'] );
3503 3853 if ( empty( $_FILES['media']['name'] ) ) {
3504 3854 // Nothing to process, just return.
3505 3855 return $uploaded_files;
@@ -3506,9 +3856,9 @@
3506 3856 }
3507 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.
3508 3858 $file = array();
3509 3859 foreach ( $media_keys as $media_key ) {
3510 - $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.
3511 3861 }
3512 3862
3513 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.
3514 3864
@@ -3554,9 +3904,9 @@
3554 3904 'type' => (string) $edited_media_item->post_mime_type,
3555 3905 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3556 3906 );
3557 3907
3558 - return (array) array( $response );
3908 + return array( $response );
3559 3909 }
3560 3910
3561 3911 $attachment_id = media_handle_upload(
3562 3912 '.jetpack.upload.',
@@ -3595,58 +3945,8 @@
3595 3945 return $uploaded_files;
3596 3946 }
3597 3947
3598 3948 /**
3599 - * Add help to the Jetpack page
3600 - *
3601 - * @since Jetpack (1.2.3)
3602 - * @return void
3603 - */
3604 - public function admin_help() {
3605 - $current_screen = get_current_screen();
3606 -
3607 - // Overview.
3608 - $current_screen->add_help_tab(
3609 - array(
3610 - 'id' => 'home',
3611 - 'title' => __( 'Home', 'jetpack' ),
3612 - 'content' =>
3613 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3614 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3615 - '<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>',
3616 - )
3617 - );
3618 -
3619 - // Screen Content.
3620 - if ( current_user_can( 'manage_options' ) ) {
3621 - $current_screen->add_help_tab(
3622 - array(
3623 - 'id' => 'settings',
3624 - 'title' => __( 'Settings', 'jetpack' ),
3625 - 'content' =>
3626 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3627 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3628 - '<ol>' .
3629 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3630 - '<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>' .
3631 - '</ol>' .
3632 - '<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>',
3633 - )
3634 - );
3635 - }
3636 -
3637 - // Help Sidebar.
3638 - $support_url = Redirect::get_url( 'jetpack-support' );
3639 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3640 - $current_screen->set_help_sidebar(
3641 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3642 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3643 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3644 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3645 - );
3646 - }
3647 -
3648 - /**
3649 3949 * Add action links for the Jetpack plugin.
3650 3950 *
3651 3951 * @param array $actions Plugin actions.
3652 3952 *
@@ -3654,9 +3954,9 @@
3654 3954 */
3655 3955 public function plugin_action_links( $actions ) {
3656 3956 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3657 3957 return array_merge(
3658 - 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' ) ) ),
3659 3959 $actions
3660 3960 );
3661 3961 }
3662 3962
@@ -3684,14 +3984,10 @@
3684 3984 'jetpack-plugins-page-js',
3685 3985 '_inc/build/plugins-page.js',
3686 3986 JETPACK__PLUGIN_FILE,
3687 3987 array(
3688 - 'in_footer' => true,
3689 - 'textdomain' => 'jetpack',
3690 - 'dependencies' => array(
3691 - 'wp-polyfill',
3692 - 'wp-components',
3693 - ),
3988 + 'in_footer' => true,
3989 + 'textdomain' => 'jetpack',
3694 3990 )
3695 3991 );
3696 3992 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3697 3993
@@ -3696,9 +3992,9 @@
3696 3992 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3697 3993
3698 3994 // Add objects to be passed to the initial state of the app.
3699 3995 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3700 - 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' );
3701 3997
3702 3998 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3703 3999 }
3704 4000 }
@@ -3747,9 +4043,9 @@
3747 4043 // @todo provide way to go to specific calypso env.
3748 4044 self::get_calypso_host() . 'jetpack/connect'
3749 4045 )
3750 4046 );
3751 - exit;
4047 + exit( 0 );
3752 4048 }
3753 4049 }
3754 4050
3755 4051 /*
@@ -3784,8 +4080,28 @@
3784 4080 * Done!
3785 4081 */
3786 4082
3787 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 + /**
3788 4104 * Handles the page load events for the Jetpack admin page
3789 4105 */
3790 4106 public function admin_page_load() {
3791 4107 $error = false;
@@ -3821,10 +4137,11 @@
3821 4137 $registered = static::connection()->try_registration();
3822 4138 if ( is_wp_error( $registered ) ) {
3823 4139 $error = $registered->get_error_code();
3824 4140 self::state( 'error', $error );
3825 - self::state( 'error', $registered->get_error_message() );
3826 4141
4142 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4143 +
3827 4144 /**
3828 4145 * Jetpack registration Error.
3829 4146 *
3830 4147 * @since 7.5.0
@@ -3848,12 +4165,8 @@
3848 4165 do_action( 'jetpack_connection_register_success', $from );
3849 4166
3850 4167 $url = $this->build_connect_url( true, $redirect, $from );
3851 4168
3852 - if ( ! empty( $_GET['onboarding'] ) ) {
3853 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3854 - }
3855 -
3856 4169 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3857 4170 $url = add_query_arg( 'auth_approved', 'true', $url );
3858 4171 }
3859 4172
@@ -3858,9 +4171,9 @@
3858 4171 }
3859 4172
3860 4173 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3861 4174 wp_safe_redirect( $url );
3862 - exit;
4175 + exit( 0 );
3863 4176 case 'activate':
3864 4177 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3865 4178 $error = 'cheatin';
3866 4179 break;
@@ -3874,9 +4187,9 @@
3874 4187 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3875 4188 }
3876 4189 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3877 4190 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3878 - exit;
4191 + exit( 0 );
3879 4192 case 'activate_default_modules':
3880 4193 check_admin_referer( 'activate_default_modules' );
3881 4194 self::log( 'activate_default_modules' );
3882 4195 self::restate();
@@ -3884,9 +4197,9 @@
3884 4197 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3885 4198 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3886 4199 self::activate_default_modules( $min_version, $max_version, $other_modules );
3887 4200 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3888 - exit;
4201 + exit( 0 );
3889 4202 case 'disconnect':
3890 4203 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3891 4204 $error = 'cheatin';
3892 4205 break;
@@ -3895,9 +4208,9 @@
3895 4208 check_admin_referer( 'jetpack-disconnect' );
3896 4209 self::log( 'disconnect' );
3897 4210 self::disconnect();
3898 4211 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3899 - exit;
4212 + exit( 0 );
3900 4213 case 'reconnect':
3901 4214 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3902 4215 $error = 'cheatin';
3903 4216 break;
@@ -3908,9 +4221,9 @@
3908 4221 self::disconnect();
3909 4222
3910 4223 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3911 4224 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3912 - exit;
4225 + exit( 0 );
3913 4226 case 'deactivate':
3914 4227 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3915 4228 $error = 'cheatin';
3916 4229 break;
@@ -3924,9 +4237,9 @@
3924 4237 self::state( 'message', 'module_deactivated' );
3925 4238 }
3926 4239 self::state( 'module', $modules );
3927 4240 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3928 - exit;
4241 + exit( 0 );
3929 4242 case 'unlink':
3930 4243 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3931 4244 check_admin_referer( 'jetpack-unlink' );
3932 4245 self::log( 'unlink' );
@@ -3936,32 +4249,9 @@
3936 4249 wp_safe_redirect( admin_url() );
3937 4250 } else {
3938 4251 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3939 4252 }
3940 - exit;
3941 - case 'onboard':
3942 - if ( ! current_user_can( 'manage_options' ) ) {
3943 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3944 - } else {
3945 - self::create_onboarding_token();
3946 - $url = $this->build_connect_url( true );
3947 -
3948 - $token = Jetpack_Options::get_option( 'onboarding' );
3949 -
3950 - if ( false !== ( $token ) ) {
3951 - $url = add_query_arg( 'onboarding', $token, $url );
3952 - }
3953 -
3954 - $calypso_env = ( new Host() )->get_calypso_env();
3955 - if ( ! empty( $calypso_env ) ) {
3956 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
3957 - }
3958 -
3959 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3960 - wp_safe_redirect( $url );
3961 - exit;
3962 - }
3963 - exit;
4253 + exit( 0 );
3964 4254 default:
3965 4255 /**
3966 4256 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3967 4257 *
@@ -4184,37 +4474,8 @@
4184 4474 endif;
4185 4475 }
4186 4476
4187 4477 /**
4188 - * We can't always respond to a signed XML-RPC request with a
4189 - * helpful error message. In some circumstances, doing so could
4190 - * leak information.
4191 - *
4192 - * Instead, track that the error occurred via a Jetpack_Option,
4193 - * and send that data back in the heartbeat.
4194 - * All this does is increment a number, but it's enough to find
4195 - * trends.
4196 - *
4197 - * @param WP_Error $xmlrpc_error The error produced during
4198 - * signature validation.
4199 - */
4200 - public function track_xmlrpc_error( $xmlrpc_error ) {
4201 - $code = is_wp_error( $xmlrpc_error )
4202 - ? $xmlrpc_error->get_error_code()
4203 - : 'should-not-happen';
4204 -
4205 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4206 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4207 - // No need to update the option if we already have
4208 - // this code stored.
4209 - return;
4210 - }
4211 - $xmlrpc_errors[ $code ] = true;
4212 -
4213 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4214 - }
4215 -
4216 - /**
4217 4478 * Initialize the jetpack stats instance only when needed
4218 4479 *
4219 4480 * @return void
4220 4481 */
@@ -4500,11 +4761,8 @@
4500 4761 *
4501 4762 * @param array $data The request data.
4502 4763 */
4503 4764 public static function authorize_ending_authorized( $data ) {
4504 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4505 - self::invalidate_onboarding_token();
4506 -
4507 4765 // If redirect_uri is SSO, ensure SSO module is enabled.
4508 4766 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4509 4767
4510 4768 /** This filter is documented in class.jetpack-cli.php */
@@ -4644,10 +4902,12 @@
4644 4902 $result = self::permit_ssl( true );
4645 4903 wp_send_json(
4646 4904 array(
4647 4905 'enabled' => $result,
4648 - 'message' => get_transient( 'jetpack_https_test_message' ),
4649 - )
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
4650 4910 );
4651 4911 }
4652 4912
4653 4913 /* Client API */
@@ -4654,8 +4914,10 @@
4654 4914
4655 4915 /**
4656 4916 * Verify the onboarding token.
4657 4917 *
4918 + * @deprecated since 13.9
4919 + *
4658 4920 * @param array $token_data Token data.
4659 4921 * @param string $token Token value.
4660 4922 * @param string $request_data JSON-encoded request data.
4661 4923 *
@@ -4661,8 +4923,9 @@
4661 4923 *
4662 4924 * @return mixed
4663 4925 */
4664 4926 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4927 + _deprecated_function( __METHOD__, '13.9' );
4665 4928 // Default to a blog token.
4666 4929 $token_type = 'blog';
4667 4930
4668 4931 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4690,9 +4953,9 @@
4690 4953 $jp_user = get_user_by( 'email', $jpo_user );
4691 4954 if ( is_a( $jp_user, 'WP_User' ) ) {
4692 4955 wp_set_current_user( $jp_user->ID );
4693 4956 $user_can = is_multisite()
4694 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
4957 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4695 4958 : current_user_can( 'manage_options' );
4696 4959 if ( $user_can ) {
4697 4960 $token_type = 'user';
4698 4961 $token->external_user_id = $jp_user->ID;
@@ -4709,11 +4972,13 @@
4709 4972
4710 4973 /**
4711 4974 * Create a random secret for validating onboarding payload
4712 4975 *
4976 + * @deprecated since 13.9
4713 4977 * @return string Secret token
4714 4978 */
4715 4979 public static function create_onboarding_token() {
4980 + _deprecated_function( __METHOD__, '13.9' );
4716 4981 $token = Jetpack_Options::get_option( 'onboarding' );
4717 4982 if ( false === ( $token ) ) {
4718 4983 $token = wp_generate_password( 32, false );
4719 4984 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4724,11 +4989,13 @@
4724 4989
4725 4990 /**
4726 4991 * Remove the onboarding token
4727 4992 *
4993 + * @deprecated since 13.9
4728 4994 * @return bool True on success, false on failure
4729 4995 */
4730 4996 public static function invalidate_onboarding_token() {
4997 + _deprecated_function( __METHOD__, '13.9' );
4731 4998 return Jetpack_Options::delete_option( 'onboarding' );
4732 4999 }
4733 5000
4734 5001 /**
@@ -4733,8 +5000,10 @@
4733 5000
4734 5001 /**
4735 5002 * Validate an onboarding token for a specific action
4736 5003 *
5004 + * @deprecated since 13.9
5005 + *
4737 5006 * @param string $token Onboarding token.
4738 5007 * @param string $action Action name.
4739 5008 *
4740 5009 * @return boolean True if token/action pair is accepted, false if not
@@ -4739,8 +5008,9 @@
4739 5008 *
4740 5009 * @return boolean True if token/action pair is accepted, false if not
4741 5010 */
4742 5011 public static function validate_onboarding_token_action( $token, $action ) {
5012 + _deprecated_function( __METHOD__, '13.9' );
4743 5013 // Compare tokens, bail if tokens do not match.
4744 5014 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4745 5015 return false;
4746 5016 }
@@ -4766,39 +5036,41 @@
4766 5036 * @return boolean
4767 5037 * @since 2.3.3
4768 5038 */
4769 5039 public static function permit_ssl( $force_recheck = false ) {
4770 - // Do some fancy tests to see if ssl is being supported.
4771 - if ( ! $force_recheck ) {
4772 - $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;
4773 5043 }
4774 5044
4775 - if ( $force_recheck || false === $ssl ) {
4776 - $message = '';
4777 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4778 - $ssl = 0;
4779 - } else {
4780 - $ssl = 1;
5045 + return Heartbeat::permit_ssl( $force_recheck );
5046 + }
4781 5047
4782 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4783 - $ssl = 0;
4784 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4785 - } else {
4786 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4787 - if ( is_wp_error( $response ) ) {
4788 - $ssl = 0;
4789 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4790 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4791 - $ssl = 0;
4792 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4793 - }
4794 - }
4795 - }
4796 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4797 - 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 '';
4798 5061 }
4799 5062
4800 - 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 + }
4801 5073 }
4802 5074
4803 5075 /**
4804 5076 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -4817,9 +5089,9 @@
4817 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>
4818 5090 <p>
4819 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' ); ?>
4820 5092 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4821 - <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>
4822 5094 </p>
4823 5095 <p>
4824 5096 <?php
4825 5097 printf(
@@ -4838,18 +5110,18 @@
4838 5110 <script type="text/javascript">
4839 5111 jQuery( document ).ready( function( $ ) {
4840 5112 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4841 5113 var $this = $( this );
4842 - $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 ); ?> );
4843 5115 $( '#jetpack-recheck-ssl-output' ).html( '' );
4844 5116 e.preventDefault();
4845 - 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 ); ?> };
4846 5118 $.post( ajaxurl, data )
4847 5119 .done( function( response ) {
4848 5120 if ( response.enabled ) {
4849 5121 $( '#jetpack-ssl-warning' ).hide();
4850 5122 } else {
4851 - 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 ); ?> );
4852 5124 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4853 5125 }
4854 5126 }.bind( $this ) );
4855 5127 } );
@@ -5207,15 +5479,20 @@
5207 5479
5208 5480 /**
5209 5481 * Checks if the site is currently in an identity crisis.
5210 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 + *
5211 5489 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5212 5490 */
5213 5491 public static function check_identity_crisis() {
5214 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5215 - return false;
5216 - }
5217 - 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();
5218 5495 }
5219 5496
5220 5497 /**
5221 5498 * Normalizes a url by doing three things:
@@ -5303,9 +5580,9 @@
5303 5580 *
5304 5581 * @return mixed
5305 5582 */
5306 5583 public static function set_suffix_on_min( $src, $handle ) {
5307 - if ( ! str_contains( $src, '.min.css' ) ) {
5584 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5308 5585 return $src;
5309 5586 }
5310 5587
5311 5588 if ( ! empty( self::$min_assets ) ) {
@@ -5362,8 +5639,19 @@
5362 5639 return false;
5363 5640 }
5364 5641
5365 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 + /**
5366 5654 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5367 5655 */
5368 5656 public function deprecated_hooks() {
5369 5657 $filter_deprecated_list = array(
@@ -5587,8 +5875,10 @@
5587 5875 'jetpack_contact_form_use_package' => array(
5588 5876 'replacement' => null,
5589 5877 'version' => 'jetpack-13.4.0',
5590 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.
5591 5881 );
5592 5882
5593 5883 foreach ( $filter_deprecated_list as $tag => $args ) {
5594 5884 if ( has_filter( $tag ) ) {
@@ -5719,125 +6009,8 @@
5719 6009 return $css;
5720 6010 }
5721 6011
5722 6012 /**
5723 - * This methods removes all of the registered css files on the front end
5724 - * from Jetpack in favor of using a single file. In effect "imploding"
5725 - * all the files into one file.
5726 - *
5727 - * Pros:
5728 - * - Uses only ONE css asset connection instead of 15
5729 - * - Saves a minimum of 56k
5730 - * - Reduces server load
5731 - * - Reduces time to first painted byte
5732 - *
5733 - * Cons:
5734 - * - Loads css for ALL modules. However all selectors are prefixed so it
5735 - * should not cause any issues with themes.
5736 - * - Plugins/themes dequeuing styles no longer do anything. See
5737 - * jetpack_implode_frontend_css filter for a workaround
5738 - *
5739 - * For some situations developers may wish to disable css imploding and
5740 - * instead operate in legacy mode where each file loads seperately and
5741 - * can be edited individually or dequeued. This can be accomplished with
5742 - * the following line:
5743 - *
5744 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5745 - *
5746 - * @param bool $travis_test Is this a test run.
5747 - *
5748 - * @since 3.2
5749 - */
5750 - public function implode_frontend_css( $travis_test = false ) {
5751 - $do_implode = true;
5752 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5753 - $do_implode = false;
5754 - }
5755 -
5756 - // Do not implode CSS when the page loads via the AMP plugin.
5757 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
5758 - $do_implode = false;
5759 - }
5760 -
5761 - /*
5762 - * Only proceed if at least 2 modules with concatenated CSS are active.
5763 - * There is no point in serving a big concatenated CSS file
5764 - * if there are no features (or only one) that actually need some CSS loaded.
5765 - */
5766 - $active_modules = self::get_active_modules();
5767 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
5768 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
5769 - if ( $active_module_with_css_count < 2 ) {
5770 - $do_implode = false;
5771 - }
5772 -
5773 - /**
5774 - * Allow CSS to be concatenated into a single jetpack.css file.
5775 - *
5776 - * @since 3.2.0
5777 - *
5778 - * @param bool $do_implode Should CSS be concatenated? Default to true.
5779 - */
5780 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5781 -
5782 - // Do not use the imploded file when default behavior was altered through the filter.
5783 - if ( ! $do_implode ) {
5784 - return;
5785 - }
5786 -
5787 - // We do not want to use the imploded file in dev mode, or if not connected.
5788 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
5789 - if ( ! $travis_test ) {
5790 - return;
5791 - }
5792 - }
5793 -
5794 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
5795 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
5796 - return;
5797 - }
5798 -
5799 - /*
5800 - * Now we assume Jetpack is connected and able to serve the single
5801 - * file.
5802 - *
5803 - * In the future there will be a check here to serve the file locally
5804 - * or potentially from the Jetpack CDN
5805 - *
5806 - * For now:
5807 - * - Enqueue a single imploded css file
5808 - * - Zero out the style_loader_tag for the bundled ones
5809 - * - Be happy, drink scotch
5810 - */
5811 -
5812 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
5813 -
5814 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
5815 -
5816 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
5817 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
5818 - }
5819 -
5820 - /**
5821 - * Removes styles that are part of concatenated group.
5822 - *
5823 - * @param string $tag Style tag.
5824 - * @param string $handle Style handle.
5825 - *
5826 - * @return string
5827 - */
5828 - public function concat_remove_style_loader_tag( $tag, $handle ) {
5829 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
5830 - $tag = '';
5831 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5832 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
5833 - }
5834 - }
5835 -
5836 - return $tag;
5837 - }
5838 -
5839 - /**
5840 6013 * Check the heartbeat data
5841 6014 *
5842 6015 * Organizes the heartbeat data by severity. For example, if the site
5843 6016 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -5849,9 +6022,23 @@
5849 6022 *
5850 6023 * $return array $filtered_data
5851 6024 */
5852 6025 public static function jetpack_check_heartbeat_data() {
5853 - $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 + );
5854 6041
5855 6042 $good = array();
5856 6043 $caution = array();
5857 6044 $bad = array();
@@ -6110,13 +6297,20 @@
6110 6297 }
6111 6298 }
6112 6299
6113 6300 /**
6114 - * Returns a boolean for whether backups UI should be displayed or not.
6301 + * Whether UI for backups should be displayed.
6115 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 + *
6116 6306 * @return bool Should backups UI be displayed?
6117 6307 */
6118 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 +
6119 6313 /**
6120 6314 * Whether UI for backups should be displayed.
6121 6315 *
6122 6316 * @since 6.5.0
@@ -6126,8 +6320,24 @@
6126 6320 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6127 6321 }
6128 6322
6129 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 + /**
6130 6340 * Clean leftoveruser meta.
6131 6341 *
6132 6342 * Delete Jetpack-related user meta when it is no longer needed.
6133 6343 *
@@ -6214,8 +6424,25 @@
6214 6424 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6215 6425 ),
6216 6426 );
6217 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 +
6218 6445 $products['scan'] = array(
6219 6446 'title' => __( 'Jetpack Scan', 'jetpack' ),
6220 6447 'slug' => 'jetpack_scan',
6221 6448 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6362,8 +6589,56 @@
6362 6589 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6363 6590 . '</a>';
6364 6591
6365 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 + }
6366 6641 }
6367 6642
6368 6643 /**
6369 6644 * Run plugin post-activation actions if we need to.