PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
← All changes | class.jetpack.php +936 -1173 13.1.5 → 16.3-a.7 View file →
@@ -7,32 +7,38 @@
7 7 * @package automattic/jetpack
8 8 */
9 9
10 10 use Automattic\Jetpack\Assets;
11 -use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
12 11 use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
13 12 use Automattic\Jetpack\Config;
13 +use Automattic\Jetpack\Connection\Authorize_Json_Api;
14 14 use Automattic\Jetpack\Connection\Client;
15 -use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
16 15 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
17 -use Automattic\Jetpack\Connection\Nonce_Handler;
18 16 use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
19 17 use Automattic\Jetpack\Connection\Secrets;
20 18 use Automattic\Jetpack\Connection\Tokens;
21 -use Automattic\Jetpack\Connection\Utils as Connection_Utils;
19 +use Automattic\Jetpack\Connection\Webhooks\Authorize_Redirect;
22 20 use Automattic\Jetpack\Constants;
23 21 use Automattic\Jetpack\CookieState;
24 22 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
25 23 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
26 24 use Automattic\Jetpack\Errors;
25 +use Automattic\Jetpack\Feature_Policy;
27 26 use Automattic\Jetpack\Files;
27 +use Automattic\Jetpack\Heartbeat;
28 28 use Automattic\Jetpack\Identity_Crisis;
29 +use Automattic\Jetpack\Import\Main as Import_Main;
29 30 use Automattic\Jetpack\Licensing;
30 31 use Automattic\Jetpack\Modules;
31 32 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
33 +use Automattic\Jetpack\Newsletter\Reader_Link;
32 34 use Automattic\Jetpack\Paths;
35 +use Automattic\Jetpack\Plugin\Deprecate;
33 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
34 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;
35 41 use Automattic\Jetpack\Status;
36 42 use Automattic\Jetpack\Status\Host;
37 43 use Automattic\Jetpack\Status\Visitor;
38 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -39,9 +45,14 @@
39 45 use Automattic\Jetpack\Sync\Health;
40 46 use Automattic\Jetpack\Sync\Sender;
41 47 use Automattic\Jetpack\Terms_Of_Service;
42 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
43 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
44 55 /*
45 56 Options:
46 57 jetpack_options (array)
47 58 An array of options.
@@ -76,74 +87,8 @@
76 87 */
77 88 public $xmlrpc_server = null;
78 89
79 90 /**
80 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
81 - *
82 - * See $concatenated_style_handles for the list of handles,
83 - * and the implode_frontend_css method for more details.
84 - *
85 - * When updating this list, make sure to update $concatenated_style_handles as well.
86 - *
87 - * @var array List of Jetpack modules.
88 - */
89 - public $modules_with_concatenated_css = array(
90 - 'carousel',
91 - 'contact-form',
92 - 'infinite-scroll',
93 - 'likes',
94 - 'related-posts',
95 - 'sharedaddy',
96 - 'shortcodes',
97 - 'subscriptions',
98 - 'tiled-gallery',
99 - 'widgets',
100 - );
101 -
102 - /**
103 - * The handles of styles that are concatenated into jetpack.css.
104 - *
105 - * When making changes to that list,
106 - * you must also update concat_list in tools/webpack.config.css.js,
107 - * and to $modules_with_concatenated_css if necessary.
108 - *
109 - * @var array The handles of styles that are concatenated into jetpack.css.
110 - */
111 - public $concatenated_style_handles = array(
112 - 'jetpack-carousel-swiper-css',
113 - 'jetpack-carousel',
114 - 'grunion.css',
115 - 'the-neverending-homepage',
116 - 'jetpack_likes',
117 - 'jetpack_related-posts',
118 - 'sharedaddy',
119 - 'jetpack-slideshow',
120 - 'presentations',
121 - 'quiz',
122 - 'jetpack-subscriptions',
123 - 'jetpack-responsive-videos',
124 - 'jetpack-social-menu',
125 - 'tiled-gallery',
126 - 'jetpack_display_posts_widget',
127 - 'gravatar-profile-widget',
128 - 'goodreads-widget',
129 - 'jetpack_social_media_icons_widget',
130 - 'jetpack-top-posts-widget',
131 - 'jetpack_image_widget',
132 - 'jetpack-my-community-widget',
133 - 'jetpack-authors-widget',
134 - 'wordads',
135 - 'eu-cookie-law-style',
136 - 'flickr-widget-style',
137 - 'jetpack-search-widget',
138 - 'jetpack-simple-payments-widget-style',
139 - 'jetpack-widget-social-icons-styles',
140 - 'wpcom_instagram_widget',
141 - 'milestone-widget',
142 - 'subscribe-modal-css',
143 - );
144 -
145 - /**
146 91 * Contains all assets that have had their URL rewritten to minified versions.
147 92 *
148 93 * @var array
149 94 */
@@ -158,11 +103,8 @@
158 103 'contact-form' => array(
159 104 array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
160 105 array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
161 106 ),
162 - 'custom-css' => array(
163 - array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
164 - ),
165 107 'gravatar-hovercards' => array(
166 108 array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
167 109 ),
168 110 'latex' => array(
@@ -333,9 +275,8 @@
333 275 * Graph tags via filter when their Social Meta modules are active:
334 276 *
335 277 * - All in One SEO Pack, All in one SEO Pack Pro
336 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
337 - * - SEOPress, SEOPress Pro
338 279 *
339 280 * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
340 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
341 282 *
@@ -378,8 +319,10 @@
378 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
379 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
380 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
381 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
382 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
383 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
384 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
385 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -446,8 +389,10 @@
446 389
447 390 /**
448 391 * Verified data for JSON authorization request
449 392 *
393 + * @deprecated 13.4
394 + *
450 395 * @var array
451 396 */
452 397 public $json_api_authorization_request = array();
453 398
@@ -488,8 +433,16 @@
488 433 */
489 434 public static $instance = false;
490 435
491 436 /**
437 + * Resolved answer for `is_premium_analytics_enabled()`, or null before the first call.
438 + *
439 + * @since 16.1
440 + * @var bool|null
441 + */
442 + private static $premium_analytics_enabled = null;
443 +
444 + /**
492 445 * Singleton
493 446 *
494 447 * @static
495 448 */
@@ -529,9 +482,9 @@
529 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
530 483 self::update_active_modules( $modules );
531 484 }
532 485
533 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
534 487
535 488 // Upgrade to 4.3.0.
536 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
537 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -586,8 +539,16 @@
586 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
587 540 } // Should we have some type of fallback if something fails here?
588 541 }
589 542
543 + // Set the newsletter send default option for existing sites.
544 + if ( false === get_option( 'wpcom_newsletter_send_default' ) ) {
545 + add_option( 'wpcom_newsletter_send_default', 1 );
546 + }
547 +
548 + // Its handler went with the Recommendations assistant.
549 + wp_clear_scheduled_hook( 'jetpack_recommend_videopress' );
550 +
590 551 if ( did_action( 'wp_loaded' ) ) {
591 552 self::upgrade_on_load();
592 553 } else {
593 554 add_action(
@@ -621,9 +582,8 @@
621 582 }
622 583
623 584 if (
624 585 class_exists( 'Jetpack_Sitemap_Manager' )
625 - && version_compare( JETPACK__VERSION, '5.3', '>=' )
626 586 ) {
627 587 do_action( 'jetpack_sitemaps_purge_data' );
628 588 }
629 589
@@ -638,9 +598,9 @@
638 598 * Also fires Action hooks for each newly activated and deactivated module.
639 599 *
640 600 * @param array $modules Array of active modules to be saved in options.
641 601 *
642 - * @return $success bool true for success, false for failure.
602 + * @return bool $success true for success, false for failure.
643 603 */
644 604 public static function update_active_modules( $modules ) {
645 605 return ( new Modules() )->update_active( $modules );
646 606 }
@@ -694,41 +654,17 @@
694 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
695 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
696 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
697 657
698 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
699 -
700 - add_filter(
701 - 'jetpack_signature_check_token',
702 - array( __CLASS__, 'verify_onboarding_token' ),
703 - 10,
704 - 3
705 - );
706 -
707 658 /**
708 659 * Prepare Gutenberg Editor functionality
660 + *
661 + * The hooks previously here have been moved to modules/blocks.php but leaving this here pending
662 + * a longer investigation to see if code is expecting the Gutenberg class to always be available.
709 663 */
710 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
711 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
712 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
713 - /**
714 - * We've switched from enqueue_block_editor_assets to enqueue_block_assets in WP-Admin because the assets with the former are loaded on the main site-editor.php.
715 - *
716 - * With the latter, the assets are now loaded in the SE iframe; the implementation is now faster because Gutenberg doesn't need to inject the assets in the iframe on client-side.
717 - */
718 - if ( is_admin() ) {
719 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
720 - } else {
721 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
722 - }
723 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
724 -
725 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
726 666
727 - // Unlink user before deleting the user from WP.com.
728 - add_action( 'deleted_user', array( $this, 'disconnect_user' ), 10, 1 );
729 - add_action( 'remove_user_from_blog', array( $this, 'disconnect_user' ), 10, 1 );
730 -
731 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
732 668
733 669 add_filter( 'login_url', array( $this, 'login_url' ), 10, 2 );
734 670 add_action( 'login_init', array( $this, 'login_init' ) );
@@ -735,8 +671,13 @@
735 671
736 672 // Set up the REST authentication hooks.
737 673 Connection_Rest_Authentication::init();
738 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 +
739 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
740 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
741 682
742 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -750,11 +691,8 @@
750 691
751 692 // Returns HTTPS support status.
752 693 add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
753 694
754 - // TODO: the conneciton banner has been deprecated - this can be removed in 13.1+
755 - add_action( 'wp_ajax_jetpack_connection_banner', array( $this, 'jetpack_connection_banner_callback' ) );
756 -
757 695 add_action( 'wp_loaded', array( $this, 'register_assets' ) );
758 696
759 697 /**
760 698 * These actions run checks to load additional files.
@@ -772,29 +710,8 @@
772 710
773 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
774 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
775 713
776 - require_once JETPACK__PLUGIN_DIR . 'class-jetpack-pre-connection-jitms.php';
777 - $jetpack_jitm_messages = ( new Jetpack_Pre_Connection_JITMs() );
778 - add_filter( 'jetpack_pre_connection_jitms', array( $jetpack_jitm_messages, 'add_pre_connection_jitms' ) );
779 -
780 - /*
781 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
782 - * We should make sure to only do this for front end links.
783 - */
784 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
785 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
786 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
787 -
788 - /*
789 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
790 - * so they point moderation links on emails to Calypso.
791 - */
792 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
793 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
794 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
795 - }
796 -
797 714 add_action(
798 715 'plugins_loaded',
799 716 function () {
800 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -805,18 +722,10 @@
805 722
806 723 // Update the site's Jetpack plan and products from API on heartbeats.
807 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
808 725
809 - /**
810 - * This is the hack to concatenate all css files into one.
811 - * For description and reasoning see the implode_frontend_css method.
812 - *
813 - * Super late priority so we catch all the registered styles.
814 - */
815 - if ( ! is_admin() ) {
816 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
817 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
818 - }
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' ) );
819 728
820 729 // Actually push the stats on shutdown.
821 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
822 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -824,8 +733,10 @@
824 733
825 734 // After a successful connection.
826 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
827 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' ) );
828 739
829 740 // Actions for Manager::authorize().
830 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
831 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -830,8 +741,9 @@
830 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
831 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
832 743 add_action( 'jetpack_authorize_ending_authorized', array( $this, 'authorize_ending_authorized' ) );
833 744
745 + Jetpack_Client_Server::init();
834 746 add_action( 'jetpack_client_authorize_error', array( Jetpack_Client_Server::class, 'client_authorize_error' ) );
835 747 add_filter( 'jetpack_client_authorize_already_authorized_url', array( Jetpack_Client_Server::class, 'client_authorize_already_authorized_url' ) );
836 748 add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
837 749 add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
@@ -836,9 +748,9 @@
836 748 add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
837 749 add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
838 750
839 751 // Filters for the Manager::get_token() urls and request body.
840 - add_filter( 'jetpack_token_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
752 + add_filter( 'jetpack_token_redirect_url', array( Authorize_Redirect::class, 'filter_connect_redirect_url' ) );
841 753 add_filter( 'jetpack_token_request_body', array( __CLASS__, 'filter_token_request_body' ) );
842 754
843 755 // Filter for the `jetpack/v4/connection/data` API response.
844 756 add_filter( 'jetpack_current_user_connection_data', array( __CLASS__, 'filter_jetpack_current_user_connection_data' ) );
@@ -864,16 +776,180 @@
864 776
865 777 // Register product descriptions for partner coupon usage.
866 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
867 779
868 - // Actions for conditional recommendations.
869 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
870 -
871 780 // Add 5-star
872 781 add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
782 + add_action( 'init', array( Deprecate::class, 'instance' ) );
783 +
784 + // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
785 + \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
786 +
787 + // Register Connection abilities (WordPress Abilities API, WP 6.9+). Scoped to the
788 + // Jetpack plugin for now: the Connection package no longer auto-wires these, so
789 + // connection-only consumers (Boost, Protect, Search, etc.) do not register them yet.
790 + \Automattic\Jetpack\Connection\Abilities\Connection_Abilities::init();
873 791 }
874 792
875 793 /**
794 + * Whether the current request should eagerly initialize the admin/REST-only
795 + * packages (the Import package and My Jetpack) now, at `plugins_loaded` time.
796 + *
797 + * Returns true for admin, cron, POST, and WP-CLI requests — the contexts,
798 + * knowable this early, where those packages have work to do. Returns false
799 + * for a plain front-end GET *and* for a REST request: the two can't be told
800 + * apart yet (this runs before `rest_api_init`), so callers defer the REST
801 + * case by initializing the package on `rest_api_init` instead, while a plain
802 + * page view never fires that hook and so loads nothing. This keeps
803 + * admin/REST-only PHP out of opcache on the front-end GET hot path.
804 + *
805 + * No in-repo code depends on the deferral. The one externally observable
806 + * change is timing: the packages' documented init hooks
807 + * (`jetpack_import_initialized`, `jetpack_feature_import_enabled`, and
808 + * `my_jetpack_init`) no longer fire on a plain front-end GET — they fire on
809 + * the admin, cron, POST, WP-CLI, and REST requests where the packages load.
810 + *
811 + * @return bool
812 + */
813 + private static function should_eager_load_packages() {
814 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) );
815 + $is_wp_cli = Constants::is_true( 'WP_CLI' );
816 +
817 + return is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli;
818 + }
819 +
820 + /**
821 + * Configure the Import package from a deferred hook.
822 + *
823 + * The eager path uses Config::ensure( 'import' ), but the deferred REST path
824 + * runs after Config::on_plugins_loaded() has already processed its feature
825 + * flags, so it needs a hookable bootstrap callback. Preserve Config's
826 + * feature-enabled action for hook consumers.
827 + *
828 + * @since 16.0
829 + *
830 + * @return void
831 + */
832 + public static function configure_import_package() {
833 + if ( class_exists( Import_Main::class ) ) {
834 + Import_Main::configure();
835 +
836 + if ( ! did_action( 'jetpack_feature_import_enabled' ) ) {
837 + do_action( 'jetpack_feature_import_enabled' );
838 + }
839 + }
840 + }
841 +
842 + /**
843 + * Enable the bundled Backup dashboard.
844 + *
845 + * The standalone plugin initializes the package first, so with both active this is a no-op.
846 + *
847 + * @return void
848 + */
849 + public static function configure_backup_package() {
850 + // Not offered on multisite, which the Backup package does not support, or without a
851 + // connected owner, since buying and managing backups needs a linked account.
852 + if ( is_multisite() || ! self::is_connection_ready() || ! self::connection()->has_connected_owner() ) {
853 + return;
854 + }
855 +
856 + /**
857 + * Filters whether the Jetpack plugin offers its bundled Backup dashboard.
858 + *
859 + * Resolved at the earliest `plugins_loaded` priority, so hook it from a mu-plugin.
860 + *
861 + * @since 16.3
862 + *
863 + * @param bool $enabled Whether to initialize the bundled Backup dashboard. Default true.
864 + */
865 + if ( ! apply_filters( 'jetpack_backup_dashboard_enabled', true ) ) {
866 + return;
867 + }
868 +
869 + $backup = 'Automattic\\Jetpack\\Backup\\V0005\\Jetpack_Backup';
870 +
871 + // An older package would take over the connection (see Jetpack_Backup::DEFAULT_INIT_OPTIONS);
872 + // only the standalone plugin ships one that old, and it draws its own menu.
873 + if ( ! class_exists( $backup ) || ! defined( $backup . '::DEFAULT_INIT_OPTIONS' ) ) {
874 + return;
875 + }
876 +
877 + $backup::initialize( array( 'manage_connection' => false ) );
878 + }
879 +
880 + /**
881 + * Whether the bundled Stats v2 dashboard is enabled.
882 + *
883 + * Stats v2 (formerly "Premium Analytics") ships with the plugin behind this
884 + * flag while it rolls out (WOOA7S-1595). When enabled it adds its own admin
885 + * menu alongside the existing Stats UI; it never replaces or hides the
886 + * legacy Stats menu, admin-bar entries, post-list column, or WP dashboard
887 + * widget. The Stats module's tracking is unaffected either way, and Stats v2
888 + * reads what that module collects, so while the module is off the plugin
889 + * answers false here before even reading the flag.
890 + *
891 + * The package has to be loadable for this to be true, so a site with the
892 + * flag on but a missing package answers false here and never adds the
893 + * Stats v2 menu (a warning is logged instead).
894 + *
895 + * @since 16.1
896 + *
897 + * @return bool
898 + */
899 + public static function is_premium_analytics_enabled() {
900 + if ( null !== self::$premium_analytics_enabled ) {
901 + return self::$premium_analytics_enabled;
902 + }
903 +
904 + if ( ! self::is_module_active( 'stats' ) ) {
905 + self::$premium_analytics_enabled = false;
906 + return false;
907 + }
908 +
909 + /**
910 + * Filters whether the bundled Premium Analytics dashboard is enabled.
911 + *
912 + * Resolved once, from `Jetpack::configure()` on `plugins_loaded`, and only
913 + * while the Stats module is active. Register this from a mu-plugin or a
914 + * plugin's main file — a callback added on `plugins_loaded` or later runs
915 + * too late to be seen.
916 + *
917 + * @since 16.1
918 + *
919 + * @param bool $enabled Defaults to the `jetpack_premium_analytics_enabled` option (false).
920 + */
921 + $flag = (bool) apply_filters( 'jetpack_premium_analytics_enabled', (bool) get_option( 'jetpack_premium_analytics_enabled' ) );
922 +
923 + self::$premium_analytics_enabled = $flag && class_exists( 'Automattic\Jetpack\PremiumAnalytics\Analytics' );
924 +
925 + if ( $flag && ! self::$premium_analytics_enabled ) {
926 + wp_trigger_error(
927 + __METHOD__,
928 + 'The jetpack_premium_analytics_enabled flag is on but the Premium Analytics package is not loadable; keeping the Stats UI in place.'
929 + );
930 + }
931 +
932 + return self::$premium_analytics_enabled;
933 + }
934 +
935 + /**
936 + * Expose the setting that turns the Premium Analytics dashboard on and off.
937 + *
938 + * Deliberately not behind is_premium_analytics_enabled(): this is the setting that flips that
939 + * check, so it has to answer while the dashboard is still off.
940 + *
941 + * @since 16.2
942 + *
943 + * @return void
944 + */
945 + public static function register_premium_analytics_enablement_setting() {
946 + if ( class_exists( 'Automattic\Jetpack\PremiumAnalytics\Enablement_Setting' ) ) {
947 + \Automattic\Jetpack\PremiumAnalytics\Enablement_Setting::register();
948 + }
949 + }
950 +
951 + /**
876 952 * Before everything else starts getting initalized, we need to initialize Jetpack using the
877 953 * Config object.
878 954 */
879 955 public function configure() {
@@ -882,13 +958,10 @@
882 958 foreach (
883 959 array(
884 960 'jitm',
885 961 'sync',
962 + 'account_protection',
886 963 'waf',
887 - 'videopress',
888 - 'stats',
889 - 'stats_admin',
890 - 'import',
891 964 )
892 965 as $feature
893 966 ) {
894 967 $config->ensure( $feature );
@@ -893,8 +966,110 @@
893 966 ) {
894 967 $config->ensure( $feature );
895 968 }
896 969
970 + // Enable the VideoPress admin UI (the "Jetpack > VideoPress" dashboard) inside the
971 + // Jetpack plugin, mirroring the standalone Jetpack VideoPress plugin. The dashboard
972 + // only renders when the VideoPress module is active (Status::is_active()); when it
973 + // is not, the menu item links to the My Jetpack interstitial to activate it.
974 + $config->ensure( 'videopress', array( 'admin_ui' => true ) );
975 +
976 + // The Backup dashboard is only an admin menu and REST routes, so it is deferred like Import below.
977 + if ( self::should_eager_load_packages() ) {
978 + self::configure_backup_package();
979 + } else {
980 + add_action( 'rest_api_init', array( __CLASS__, 'configure_backup_package' ), 0 );
981 + }
982 +
983 + /*
984 + * The Import package only registers `jetpack/v4/import` REST routes — it
985 + * does nothing when rendering a front-end page — so gate its `ensure()`
986 + * to keep its PHP out of opcache on the front-end GET hot path. It still
987 + * loads on admin, cron, POST, and WP-CLI requests, and on `rest_api_init`
988 + * for REST: a REST request can't be identified yet at `plugins_loaded`
989 + * (this runs before `Config::on_plugins_loaded`, and `rest_api_init`
990 + * fires later), so it is initialized directly when that hook fires, while
991 + * a plain page view never fires it and so loads nothing.
992 + *
993 + * JITM stays eager (above): unlike Import, its `register()` adds a
994 + * `jetpack_sync_before_send_updated_option` filter that records the
995 + * `jetpack_last_plugin_sync` transient, and a Jetpack Sync send can fire
996 + * on a plain front-end GET — including the dedicated-sync `spawn-sync`
997 + * GET, which runs on `init` and exits before `rest_api_init`. Deferring
998 + * JITM would skip that bookkeeping and leave its message cache stale after
999 + * a plugin change, so it loads on every request as before.
1000 + */
1001 + if ( self::should_eager_load_packages() ) {
1002 + $config->ensure( 'import' );
1003 + } else {
1004 + add_action(
1005 + 'rest_api_init',
1006 + array( __CLASS__, 'configure_import_package' ),
1007 + 0
1008 + );
1009 + }
1010 +
1011 + /*
1012 + * The Stats and Stats Admin packages only do work when the Stats module
1013 + * is active (the front-end tracking pixel) or on wp-admin, REST, cron,
1014 + * POST, and WP-CLI requests: the Stats dashboard page, the stats /
1015 + * stats-app REST endpoints (which the block editor also calls for
1016 + * email-open rates), the transient-cleanup cron, the connection
1017 + * package's package-version tracker (which runs on POSTs and reads the
1018 + * `jetpack_package_versions` filter that Stats registers), and CLI
1019 + * introspection such as the heartbeat inspector. On a plain front-end
1020 + * GET page view with the module off they are inert: the pixel
1021 + * short-circuits on `Stats\Main::should_track()` and every other entry
1022 + * point only hooks rest_api_init, admin, cron, the POST-only tracker, or
1023 + * is reached through WP-CLI.
1024 + * Skip loading them — and eagerly constructing the Stats Admin REST
1025 + * controller — on that hot path to keep their PHP out of opcache, but
1026 + * keep loading them everywhere else exactly as before so the stats REST
1027 + * permission mapping (view_stats, registered by Stats\Main), the
1028 + * editor's stats-app calls, the cleanup cron, and the package-version
1029 + * tracker are all unchanged.
1030 + *
1031 + * REST requests are not yet identifiable here (REST_REQUEST is defined
1032 + * after plugins_loaded), so defer those to rest_api_init. Call the
1033 + * package initializers directly rather than `$config->ensure()`: ensure()
1034 + * only flags a feature for `Config::on_plugins_loaded()` (plugins_loaded
1035 + * priority 2), which has already run by the time rest_api_init fires.
1036 + * Priority 0 runs before each package's own priority-10 route
1037 + * registration, so their routes still register within the same dispatch.
1038 + * A plain page view never fires rest_api_init, so the packages stay
1039 + * unloaded there. See JETPACK-1747.
1040 + */
1041 + $is_post_request = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'];
1042 + $is_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
1043 +
1044 + if ( self::is_module_active( 'stats' ) || is_admin() || wp_doing_cron() || $is_post_request || $is_wp_cli ) {
1045 + $config->ensure( 'stats' );
1046 + $config->ensure( 'stats_admin' );
1047 + } else {
1048 + add_action(
1049 + 'rest_api_init',
1050 + static function () {
1051 + if ( class_exists( 'Automattic\Jetpack\Stats\Main' ) ) {
1052 + \Automattic\Jetpack\Stats\Main::init();
1053 + }
1054 + if ( class_exists( 'Automattic\Jetpack\Stats_Admin\Main' ) ) {
1055 + \Automattic\Jetpack\Stats_Admin\Main::init();
1056 + }
1057 + },
1058 + 0
1059 + );
1060 + }
1061 +
1062 + // Stats v2 (WOOA7S-1595). Unlike Stats above it cannot be deferred when enabled — see
1063 + // Analytics::init() for why, and for why it takes no menu_title here.
1064 + if ( self::is_premium_analytics_enabled() ) {
1065 + \Automattic\Jetpack\PremiumAnalytics\Analytics::init();
1066 + }
1067 +
1068 + // Outside the check above on purpose — see Enablement_Setting. Deferred like Stats, to keep
1069 + // the autoload off the front-end hot path.
1070 + add_action( 'rest_api_init', array( __CLASS__, 'register_premium_analytics_enablement_setting' ), 0 );
1071 +
897 1072 $config->ensure(
898 1073 'connection',
899 1074 array(
900 1075 'slug' => 'jetpack',
@@ -912,12 +1087,8 @@
912 1087 );
913 1088
914 1089 $config->ensure( 'search' );
915 1090
916 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
917 - $config->ensure( 'wordads' );
918 - }
919 -
920 1091 if ( ! $this->connection_manager ) {
921 1092 $this->connection_manager = new Connection_Manager( 'jetpack' );
922 1093 }
923 1094
@@ -925,8 +1096,10 @@
925 1096 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
926 1097 $config->ensure( 'publicize' );
927 1098 }
928 1099
1100 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1101 +
929 1102 /*
930 1103 * Load things that should only be in Network Admin.
931 1104 *
932 1105 * For now blow away everything else until a more full
@@ -941,8 +1114,9 @@
941 1114 $is_connection_ready = self::is_connection_ready();
942 1115
943 1116 if ( $is_connection_ready ) {
944 1117 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1118 + $this->run_initialize_tracking_action();
945 1119
946 1120 Jetpack_Heartbeat::init();
947 1121 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
948 1122 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -947,8 +1121,19 @@
947 1121 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
948 1122 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
949 1123 Jetpack_Search_Performance_Logger::init();
950 1124 }
1125 + } else {
1126 + add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
1127 + add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
1128 + add_filter(
1129 + 'xmlrpc_methods',
1130 + function ( $methods ) {
1131 + $this->run_initialize_tracking_action();
1132 + return $methods;
1133 + },
1134 + 1
1135 + );
951 1136 }
952 1137
953 1138 // Initialize remote file upload request handlers.
954 1139 $this->add_remote_request_handlers();
@@ -958,20 +1143,10 @@
958 1143 */
959 1144 if ( $is_connection_ready ) {
960 1145 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
961 1146 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
962 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
963 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1147 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
964 1148 }
965 -
966 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
967 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
968 - } else {
969 - /**
970 - * Initialize tracking right after the user agrees to the terms of service.
971 - */
972 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
973 - }
974 1149 }
975 1150
976 1151 /**
977 1152 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -979,16 +1154,58 @@
979 1154 *
980 1155 * @action plugins_loaded
981 1156 */
982 1157 public function late_initialization() {
983 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1158 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
984 1159
985 - My_Jetpack_Initializer::init();
1160 + /*
1161 + * My Jetpack is a wp-admin dashboard. Its Initializer::init() only wires
1162 + * up admin-menu, admin_init, and rest_api_init surfaces — and eagerly
1163 + * loads every product class (backup, boost, protect, …) just to register
1164 + * admin plugin-action links — so none of it is needed on a plain
1165 + * front-end GET page view. (The pieces that do immediate work, e.g.
1166 + * Connection REST authentication and Licensing, are already initialized
1167 + * unconditionally in Jetpack's constructor, so they are unaffected here.)
1168 + *
1169 + * Gate the call to the request types where My Jetpack actually does work.
1170 + * REST can't be detected yet at plugins_loaded, so initialize on
1171 + * rest_api_init for that branch; a plain page view never fires it, so My
1172 + * Jetpack stays unloaded there.
1173 + */
1174 + if ( self::should_eager_load_packages() ) {
1175 + My_Jetpack_Initializer::init();
1176 + } else {
1177 + add_action( 'rest_api_init', array( My_Jetpack_Initializer::class, 'init' ), 0 );
1178 + }
986 1179
987 - // Initialize Boost Speed Score
988 - $modules = Jetpack_Boost_Modules::init();
989 - new Speed_Score( $modules, 'jetpack-dashboard' );
1180 + Scan_Page_Init::initialize();
1181 + Jetpack_SEO_Initializer::init();
990 1182
1183 + if ( ( new Modules() )->is_active( 'podcast' ) ) {
1184 + Podcast::init();
1185 + }
1186 +
1187 + /*
1188 + * Initialize Boost Speed Score. It only does work on REST requests (the
1189 + * dashboard speed-score endpoints) and on a few Jetpack Boost lifecycle
1190 + * actions, so defer constructing it — and loading the boost-speed-score
1191 + * package classes — until one of those hooks actually fires instead of on
1192 + * every request. Priority 0 ensures the object's own callbacks (added in
1193 + * its constructor at the default priority) still run for the firing hook.
1194 + */
1195 + $initialize_speed_score = static function () {
1196 + static $initialized = false;
1197 + if ( $initialized ) {
1198 + return;
1199 + }
1200 + $initialized = true;
1201 + new Speed_Score( array(), 'jetpack-dashboard' );
1202 + };
1203 + add_action( 'rest_api_init', $initialize_speed_score, 0 );
1204 + add_action( 'jetpack_boost_deactivate', $initialize_speed_score, 0 );
1205 + add_action( 'jetpack_boost_environment_changed', $initialize_speed_score, 0 );
1206 + add_action( 'handle_environment_change', $initialize_speed_score, 0 );
1207 +
991 1208 /**
992 1209 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
993 1210 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
994 1211 *
@@ -1026,8 +1243,10 @@
1026 1243
1027 1244 /**
1028 1245 * Redirect edit post links to Calypso.
1029 1246 *
1247 + * @deprecated since 13.9
1248 + *
1030 1249 * @param string $default_url Post edit URL.
1031 1250 * @param int $post_id Post ID.
1032 1251 *
1033 1252 * @return string
@@ -1032,8 +1251,10 @@
1032 1251 *
1033 1252 * @return string
1034 1253 */
1035 1254 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1255 + _deprecated_function( __METHOD__, '13.9' );
1256 +
1036 1257 $post = get_post( $post_id );
1037 1258
1038 1259 if ( empty( $post ) ) {
1039 1260 return $default_url;
@@ -1064,13 +1285,17 @@
1064 1285
1065 1286 /**
1066 1287 * Redirect edit comment links to Calypso.
1067 1288 *
1289 + * @deprecated since 13.9
1290 + *
1068 1291 * @param string $url Comment edit URL.
1069 1292 *
1070 1293 * @return string
1071 1294 */
1072 1295 public function point_edit_comment_links_to_calypso( $url ) {
1296 + _deprecated_function( __METHOD__, '13.9' );
1297 +
1073 1298 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1074 1299 $query_args = null;
1075 1300 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1076 1301
@@ -1089,30 +1314,16 @@
1089 1314 *
1090 1315 * @return array list of callables.
1091 1316 */
1092 1317 public function filter_sync_callable_whitelist( $callables ) {
1093 -
1094 1318 // Jetpack Functions.
1095 1319 $jetpack_callables = array(
1096 1320 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1097 1321 'updates' => array( 'Jetpack', 'get_updates' ),
1098 1322 'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
1323 + 'theme_styles' => array( 'Automattic\\Jetpack\\Plugin\\Theme_Styles_Sync', 'get_theme_styles' ),
1099 1324 );
1100 - $callables = array_merge( $callables, $jetpack_callables );
1101 -
1102 - // Jetpack_SSO_Helpers.
1103 - if ( include_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ) {
1104 - $sso_helpers = array(
1105 - 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
1106 - 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
1107 - 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
1108 - 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
1109 - 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
1110 - );
1111 - $callables = array_merge( $callables, $sso_helpers );
1112 - }
1113 -
1114 - return $callables;
1325 + return array_merge( $callables, $jetpack_callables );
1115 1326 }
1116 1327
1117 1328 /**
1118 1329 * Extend Sync multisite callables with Jetpack Plugin functions.
@@ -1137,41 +1348,8 @@
1137 1348 return $callables;
1138 1349 }
1139 1350
1140 1351 /**
1141 - * Deprecated
1142 - * Please use Automattic\Jetpack\JITMS\JITM::jetpack_track_last_sync_callback instead.
1143 - *
1144 - * @param array $params The action parameters.
1145 - *
1146 - * @deprecated since 9.8.
1147 - */
1148 - public function jetpack_track_last_sync_callback( $params ) {
1149 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\Automattic\Jetpack\JITMS\JITM->jetpack_track_last_sync_callback' );
1150 - return Automattic\Jetpack\JITMS\JITM::get_instance()->jetpack_track_last_sync_callback( $params );
1151 - }
1152 -
1153 - /**
1154 - * Jetpack Connection banner callback function.
1155 - *
1156 - * @return void
1157 - */
1158 - public function jetpack_connection_banner_callback() {
1159 - check_ajax_referer( 'jp-connection-banner-nonce', 'nonce' );
1160 -
1161 - // Disable the banner dismiss functionality if the pre-connection prompt helpers filter is set.
1162 - if (
1163 - isset( $_REQUEST['dismissBanner'] ) &&
1164 - ! Jetpack_Connection_Banner::force_display()
1165 - ) {
1166 - Jetpack_Options::update_option( 'dismissed_connection_banner', 1 );
1167 - wp_send_json_success();
1168 - }
1169 -
1170 - wp_die();
1171 - }
1172 -
1173 - /**
1174 1352 * If there are any stats that need to be pushed, but haven't been, push them now.
1175 1353 */
1176 1354 public function push_stats() {
1177 1355 if ( ! empty( $this->stats ) ) {
@@ -1186,16 +1364,8 @@
1186 1364 * @param string $cap Capability name.
1187 1365 */
1188 1366 public function jetpack_custom_caps( $caps, $cap ) {
1189 1367 switch ( $cap ) {
1190 - case 'jetpack_manage_modules':
1191 - case 'jetpack_activate_modules':
1192 - case 'jetpack_deactivate_modules':
1193 - $caps = array( 'manage_options' );
1194 - break;
1195 - case 'jetpack_configure_modules':
1196 - $caps = array( 'manage_options' );
1197 - break;
1198 1368 case 'jetpack_manage_autoupdates':
1199 1369 $caps = array(
1200 1370 'manage_options',
1201 1371 'update_plugins',
@@ -1213,9 +1383,9 @@
1213 1383 if ( $is_offline_mode ) {
1214 1384 $caps = array( 'manage_options' );
1215 1385 break;
1216 1386 } else {
1217 - $caps = array( 'read' );
1387 + $caps = array( 'edit_posts' );
1218 1388 }
1219 1389 break;
1220 1390 }
1221 1391 return $caps;
@@ -1374,9 +1544,9 @@
1374 1544 }
1375 1545 /**
1376 1546 * Does the network allow admins to add new users.
1377 1547 *
1378 - * @return boolian
1548 + * @return bool
1379 1549 */
1380 1550 public static function network_add_new_users() {
1381 1551 return (bool) get_site_option( 'add_new_users' );
1382 1552 }
@@ -1383,9 +1553,9 @@
1383 1553 /**
1384 1554 * File upload psace left per site in MB.
1385 1555 * -1 means NO LIMIT.
1386 1556 *
1387 - * @return number
1557 + * @return int
1388 1558 */
1389 1559 public static function network_site_upload_space() {
1390 1560 // value in MB.
1391 1561 return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
@@ -1402,9 +1572,9 @@
1402 1572
1403 1573 /**
1404 1574 * Maximum file upload size set by the network.
1405 1575 *
1406 - * @return number
1576 + * @return int
1407 1577 */
1408 1578 public static function network_max_upload_file_size() {
1409 1579 // value in KB.
1410 1580 return get_site_option( 'fileupload_maxk', 300 );
@@ -1658,44 +1828,8 @@
1658 1828 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1659 1829 }
1660 1830
1661 1831 /**
1662 - * Deprecated: Is Jetpack in development (offline) mode?
1663 - *
1664 - * This static method is being left here intentionally without the use of _deprecated_function(), as other plugins
1665 - * and themes still use it, and we do not want to flood them with notices.
1666 - *
1667 - * Please use Automattic\Jetpack\Status()->is_offline_mode() instead.
1668 - *
1669 - * @deprecated since 8.0.
1670 - */
1671 - public static function is_development_mode() {
1672 - _deprecated_function( __METHOD__, 'jetpack-8.0', '\Automattic\Jetpack\Status->is_offline_mode' );
1673 - return ( new Status() )->is_offline_mode();
1674 - }
1675 -
1676 - /**
1677 - * Whether the site is currently onboarding or not.
1678 - * A site is considered as being onboarded if it currently has an onboarding token.
1679 - *
1680 - * @since 5.8
1681 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1682 - *
1683 - * @access public
1684 - * @static
1685 - *
1686 - * @return bool True if the site is currently onboarding, false otherwise
1687 - */
1688 - public static function is_onboarding() {
1689 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1690 -
1691 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1692 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1693 - }
1694 - return ( new Status() )->is_onboarding();
1695 - }
1696 -
1697 - /**
1698 1832 * Determines reason for Jetpack offline mode.
1699 1833 */
1700 1834 public static function development_mode_trigger_text() {
1701 1835 $status = new Status();
@@ -1709,11 +1843,10 @@
1709 1843 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1710 1844 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1711 1845 } elseif ( $status->is_local_site() ) {
1712 1846 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1713 - /** This filter is documented in packages/status/src/class-status.php */
1714 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1715 - $notice = __( 'The jetpack_development_mode filter is set to true.', 'jetpack' );
1847 + } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1848 + $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1716 1849 } else {
1717 1850 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1718 1851 }
1719 1852
@@ -1743,15 +1876,8 @@
1743 1876 $notice = sprintf( __( 'You are currently running a development version of Jetpack. <a href="%s" target="_blank">Submit your feedback</a>', 'jetpack' ), esc_url( Redirect::get_url( 'jetpack-contact-support-beta-group' ) ) );
1744 1877
1745 1878 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1746 1879 }
1747 - // Throw up a notice if using staging mode.
1748 - if ( ( new Status() )->is_staging_site() ) {
1749 - /* translators: %s is a URL */
1750 - $notice = sprintf( __( 'You are running Jetpack on a <a href="%s" target="_blank">staging server</a>.', 'jetpack' ), esc_url( Redirect::get_url( 'jetpack-support-staging-sites' ) ) );
1751 -
1752 - echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1753 - }
1754 1880 }
1755 1881
1756 1882 /**
1757 1883 * Whether Jetpack's version maps to a public release, or a development version.
@@ -1772,28 +1898,8 @@
1772 1898 );
1773 1899 }
1774 1900
1775 1901 /**
1776 - * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
1777 - *
1778 - * @param int $user_id User ID or will use get_current_user_id if false/not provided.
1779 - */
1780 - public static function is_user_connected( $user_id = false ) {
1781 - _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Manager\\is_user_connected' );
1782 - return self::connection()->is_user_connected( $user_id );
1783 - }
1784 -
1785 - /**
1786 - * Get the wpcom user data of the current|specified connected user.
1787 - *
1788 - * @param null|int $user_id User ID or will use get_current_user_id if null.
1789 - */
1790 - public static function get_connected_user_data( $user_id = null ) {
1791 - _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Manager\\get_connected_user_data' );
1792 - return self::connection()->get_connected_user_data( $user_id );
1793 - }
1794 -
1795 - /**
1796 1902 * Get the wpcom email of the current|specified connected user.
1797 1903 *
1798 1904 * @param null|int $user_id User ID or will use get_current_user_id if null.
1799 1905 */
@@ -1845,18 +1951,11 @@
1845 1951 */
1846 1952 public static function load_modules() {
1847 1953 $status = new Status();
1848 1954
1849 - if ( method_exists( $status, 'is_onboarding' ) ) {
1850 - $is_onboarding = $status->is_onboarding();
1851 - } else {
1852 - $is_onboarding = self::is_onboarding();
1853 - }
1854 -
1855 1955 if (
1856 1956 ! self::is_connection_ready()
1857 1957 && ! $status->is_offline_mode()
1858 - && ! $is_onboarding
1859 1958 && (
1860 1959 ! is_multisite()
1861 1960 || ! get_site_option( 'jetpack_protect_active' )
1862 1961 )
@@ -1977,22 +2076,14 @@
1977 2076 *
1978 2077 * @todo Store the result in core's object cache maybe?
1979 2078 */
1980 2079 public static function get_active_plugins() {
1981 - $active_plugins = (array) get_option( 'active_plugins', array() );
1982 -
1983 - if ( is_multisite() ) {
1984 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1985 - // whereas active_plugins stores them in the values.
1986 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1987 - if ( $network_plugins ) {
1988 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1989 - }
2080 + // Older Connection copies can load first and lack this method.
2081 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2082 + return array();
1990 2083 }
1991 2084
1992 - sort( $active_plugins );
1993 -
1994 - return array_unique( $active_plugins );
2085 + return Heartbeat::get_active_plugins();
1995 2086 }
1996 2087
1997 2088 /**
1998 2089 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2130,8 +2221,10 @@
2130 2221 *
2131 2222 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2132 2223 */
2133 2224 if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
2225 + // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
2226 + // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
2134 2227 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2135 2228 }
2136 2229 }
2137 2230
@@ -2234,9 +2327,9 @@
2234 2327 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'jetpack', 'jetpack_modules' ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2235 2328 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2236 2329 }
2237 2330 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2238 - exit;
2331 + exit( 0 );
2239 2332 }
2240 2333 }
2241 2334
2242 2335 /**
@@ -2283,8 +2376,12 @@
2283 2376 default:
2284 2377 break;
2285 2378 }
2286 2379 }
2380 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2381 + Feature_Policy::ensure_hooks();
2382 + }
2383 +
2287 2384 /**
2288 2385 * Filters the array of default modules.
2289 2386 *
2290 2387 * @since 2.5.0
@@ -2316,12 +2413,14 @@
2316 2413 * @return array
2317 2414 */
2318 2415 public function handle_deprecated_modules( $modules ) {
2319 2416 $deprecated_modules = array(
2320 - 'debug' => null, // Closed out and moved to the debugger library.
2321 - 'wpcc' => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2322 - 'gplus-authorship' => null, // Closed out in 3.2 -- Google dropped support.
2323 - 'minileven' => null, // Closed out in 8.3 -- Responsive themes are common now, and so is AMP.
2417 + 'debug' => null, // Closed out and moved to the debugger library.
2418 + 'wpcc' => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2419 + 'gplus-authorship' => null, // Closed out in 3.2 -- Google dropped support.
2420 + 'minileven' => null, // Closed out in 8.3 -- Responsive themes are common now, and so is AMP.
2421 + 'lazy-images' => null, // Closed out in 12.8 -- WordPress core now has native lazy loading.
2422 + 'enhanced-distribution' => null, // Closed out in 13.3 -- WP.com is winding down the firehose.
2324 2423 );
2325 2424
2326 2425 // Don't activate SSO if they never completed activating WPCC.
2327 2426 if ( self::is_module_active( 'wpcc' ) ) {
@@ -2375,8 +2474,17 @@
2375 2474 }
2376 2475 }
2377 2476 }
2378 2477
2478 + // Special case to convert block setting to a block module.
2479 + $block_key = array_search( 'blocks', $modules, true );
2480 + if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2481 + $block_option = get_option( 'jetpack_blocks_disabled', null );
2482 + if ( $block_option ) {
2483 + unset( $modules[ $block_key ] );
2484 + }
2485 + }
2486 +
2379 2487 return $modules;
2380 2488 }
2381 2489
2382 2490 /**
@@ -2433,23 +2541,30 @@
2433 2541
2434 2542 /**
2435 2543 * Return module name translation. Uses matching string created in modules/module-headings.php.
2436 2544 *
2545 + * The module list is globbed from `modules/` at runtime, so a module can be listed with no
2546 + * entry in that generated file. Fall back to the untranslated header rather than overwriting
2547 + * it with the null `jetpack_get_module_i18n()` returns for an unknown slug.
2548 + *
2437 2549 * @since 3.9.2
2438 2550 *
2439 2551 * @param array $modules Array of Jetpack modules.
2440 2552 *
2441 - * @return string|void
2553 + * @return array
2442 2554 */
2443 2555 public static function get_translated_modules( $modules ) {
2444 2556 foreach ( $modules as $index => $module ) {
2445 2557 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2446 - if ( isset( $module['name'] ) ) {
2447 - $modules[ $index ]['name'] = $i18n_module['name'];
2558 + $name = $i18n_module['name'] ?? null;
2559 + $description = $i18n_module['description'] ?? null;
2560 +
2561 + if ( null !== $name && isset( $module['name'] ) ) {
2562 + $modules[ $index ]['name'] = $name;
2448 2563 }
2449 - if ( isset( $module['description'] ) ) {
2450 - $modules[ $index ]['description'] = $i18n_module['description'];
2451 - $modules[ $index ]['short_description'] = $i18n_module['description'];
2564 + if ( null !== $description && isset( $module['description'] ) ) {
2565 + $modules[ $index ]['description'] = $description;
2566 + $modules[ $index ]['short_description'] = $description;
2452 2567 }
2453 2568 if ( isset( $module['module_tags'] ) ) {
2454 2569 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2455 2570 }
@@ -2489,20 +2604,28 @@
2489 2604
2490 2605 /**
2491 2606 * Catches PHP errors. Must be used in conjunction with output buffering.
2492 2607 *
2608 + * @deprecated since 13.5
2493 2609 * @param bool $catch True to start catching, False to stop.
2494 2610 *
2495 2611 * @static
2612 + * @deprecated 13.5
2613 + * @see \Automattic\Jetpack\Errors
2496 2614 */
2497 2615 public static function catch_errors( $catch ) {
2616 + _deprecated_function( __METHOD__, '13.5' );
2617 + // @phan-suppress-next-line PhanDeprecatedClass
2498 2618 return ( new Errors() )->catch_errors( $catch );
2499 2619 }
2500 2620
2501 2621 /**
2502 2622 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2623 + *
2624 + * @deprecated since 13.5
2503 2625 */
2504 2626 public static function catch_errors_on_shutdown() {
2627 + _deprecated_function( __METHOD__, '13.5' );
2505 2628 self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2506 2629 }
2507 2630
2508 2631 /**
@@ -2606,9 +2729,9 @@
2606 2729 ),
2607 2730 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2608 2731 );
2609 2732 wp_safe_redirect( $url );
2610 - exit;
2733 + exit( 0 );
2611 2734 }
2612 2735 }
2613 2736
2614 2737 /**
@@ -2626,9 +2749,8 @@
2626 2749
2627 2750 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating.
2628 2751 if ( $send_state_messages ) {
2629 2752 self::restate();
2630 - self::catch_errors( true );
2631 2753 }
2632 2754
2633 2755 $active = self::get_active_modules();
2634 2756
@@ -2696,10 +2818,8 @@
2696 2818 if ( $send_state_messages ) {
2697 2819 self::state( 'error', false );
2698 2820 self::state( 'module', false );
2699 2821 }
2700 -
2701 - self::catch_errors( false );
2702 2822 /**
2703 2823 * Fires when default modules are activated.
2704 2824 *
2705 2825 * @since 1.9.0
@@ -2757,9 +2877,9 @@
2757 2877 * @return string $url module configuration URL.
2758 2878 */
2759 2879 public static function module_configuration_url( $module ) {
2760 2880 $module = self::get_module_slug( $module );
2761 - $default_url = self::admin_url() . "#/settings?term=$module";
2881 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2762 2882 /**
2763 2883 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2764 2884 *
2765 2885 * @since 6.9.0
@@ -2777,9 +2897,9 @@
2777 2897 *
2778 2898 * @param string $message Error message.
2779 2899 * @param bool $deactivate Deactivate Jetpack or not.
2780 2900 *
2781 - * @return void
2901 + * @return never
2782 2902 */
2783 2903 public static function bail_on_activation( $message, $deactivate = true ) {
2784 2904 ?>
2785 2905 <!doctype html>
@@ -2817,9 +2937,9 @@
2817 2937 if ( $update ) {
2818 2938 update_option( 'active_plugins', array_filter( $plugins ) );
2819 2939 }
2820 2940 }
2821 - exit;
2941 + exit( 0 );
2822 2942 }
2823 2943
2824 2944 /**
2825 2945 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2844,12 +2964,18 @@
2844 2964 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2845 2965
2846 2966 Health::on_jetpack_activated();
2847 2967
2968 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2969 +
2848 2970 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2849 2971 Sync_Actions::do_only_first_initial_sync();
2850 2972 }
2851 2973
2974 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2975 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2976 + }
2977 +
2852 2978 self::plugin_initialize();
2853 2979 }
2854 2980
2855 2981 /**
@@ -2884,9 +3010,9 @@
2884 3010
2885 3011 if ( $plugins_path === $referer['path'] ) {
2886 3012 $source_type = 'list';
2887 3013 } elseif ( $plugins_install_path === $referer['path'] ) {
2888 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3014 + $tab = $query_parts['tab'] ?? 'featured';
2889 3015 switch ( $tab ) {
2890 3016 case 'popular':
2891 3017 $source_type = 'popular';
2892 3018 break;
@@ -2896,10 +3022,10 @@
2896 3022 case 'favorites':
2897 3023 $source_type = 'favorites';
2898 3024 break;
2899 3025 case 'search':
2900 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2901 - $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
3026 + $source_type = 'search-' . ( $query_parts['type'] ?? 'term' );
3027 + $source_query = $query_parts['s'] ?? null;
2902 3028 break;
2903 3029 default:
2904 3030 $source_type = 'featured';
2905 3031 }
@@ -2908,29 +3034,171 @@
2908 3034 return array( $source_type, $source_query );
2909 3035 }
2910 3036
2911 3037 /**
2912 - * Runs before bumping version numbers up to a new version
3038 + * Runs before bumping version numbers up to a new version.
2913 3039 *
2914 - * @param string $version Version:timestamp.
3040 + * Only ever registered the hooks for the release post update modal, which has been removed.
3041 + * No longer hooked to `updating_jetpack_version`.
3042 + *
3043 + * @deprecated 16.2
3044 + *
3045 + * @param string $version Version:timestamp.
2915 3046 * @param string $old_version Old Version:timestamp or false if not set yet.
2916 3047 */
2917 - public static function do_version_bump( $version, $old_version ) {
2918 - if ( $old_version ) { // For existing Jetpack installations.
2919 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3048 + public static function do_version_bump( $version, $old_version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature preserved for the deprecation shim.
3049 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
3050 + }
2920 3051
2921 - // If a front end page is visited after the update, the 'wp' action will fire.
2922 - add_action( 'wp', 'Jetpack::set_update_modal_display' );
3052 + /**
3053 + * Enables the Newsletter (subscriptions) module for existing sites now that it is a default-on module.
3054 + *
3055 + * Fresh installs receive the module via its "Auto Activate: Yes" header, so this only handles sites
3056 + * upgrading from a version where the module defaulted off. It runs once per site (guarded by the
3057 + * subscriptions_default_on_migrated option). Fresh installs are marked as migrated immediately so the
3058 + * migration never runs for them. After it has run, the user's choice to deactivate the module again
3059 + * (for example from the My Jetpack Products page) is respected and never reverted.
3060 + *
3061 + * The module requires a connection, so on a disconnected site the migration is deferred without setting
3062 + * the guard, allowing a later version bump to retry once the site is connected.
3063 + *
3064 + * @param string $version New Jetpack version:timestamp.
3065 + * @param string|false $old_version Previous Jetpack version:timestamp, or false on a fresh install.
3066 + */
3067 + public static function activate_subscriptions_module_for_existing_sites( $version, $old_version ) {
3068 + if ( get_option( 'jetpack_subscriptions_default_on_migrated' ) ) {
3069 + return;
3070 + }
2923 3071
2924 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2925 - add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
3072 + // Fresh installs get the module via its "Auto Activate: Yes" header. Mark them as migrated so a
3073 + // later opt-out is never reverted by the existing-site path on a subsequent version bump.
3074 + if ( ! $old_version ) {
3075 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3076 + return;
2926 3077 }
3078 +
3079 + if ( ! self::is_connection_ready() ) {
3080 + return;
3081 + }
3082 +
3083 + // Mark as migrated only once the module is active, so a transient activation failure is retried on
3084 + // a later version bump rather than being silently skipped.
3085 + if ( self::is_module_active( 'subscriptions' ) || self::activate_module( 'subscriptions', false, false ) ) {
3086 + update_option( 'jetpack_subscriptions_default_on_migrated', true );
3087 + }
2927 3088 }
2928 3089
2929 3090 /**
3091 + * Option flag that records the AI master-switch opt-out reconciliation has run,
3092 + * so it never runs twice.
3093 + *
3094 + * @var string
3095 + */
3096 + const AI_MASTER_OPTOUT_MIGRATED_OPTION = 'jetpack_ai_master_optout_migrated';
3097 +
3098 + /**
3099 + * Register the on-upgrade init hooks whose relative ORDER matters, extracted so
3100 + * the ordering can be asserted in tests without invoking plugin_upgrade() (whose
3101 + * guards make it unreliable to trigger under test). activate_new_modules()
3102 + * (init, default priority 10) auto-activates "Auto Activate: Yes" modules
3103 + * including the `ai` master; reconcile_ai_master_optout() must run at a LATER
3104 + * priority to honor an explicit AI opt-out.
3105 + *
3106 + * @return void
3107 + */
3108 + public static function register_upgrade_init_hooks() {
3109 + add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
3110 + add_action( 'init', array( __CLASS__, 'reconcile_ai_master_optout' ), 20 );
3111 + }
3112 +
3113 + /**
3114 + * Preserves an explicit Jetpack AI opt-out when the `ai` module becomes the site-wide
3115 + * master switch off WordPress.com Simple (self-hosted and Atomic).
3116 + *
3117 + * The `ai` module is "Auto Activate: Yes", so on upgrade {@see self::activate_new_modules()}
3118 + * turns it on for connected sites — the desired default-on / auto-enable-on-connection
3119 + * behavior, which this method deliberately leaves alone. The one case it corrects is a site
3120 + * that had explicitly disabled Jetpack AI (the `jetpack_ai_enabled` option present and falsey)
3121 + * before the module shipped: that opt-out must survive the module becoming the master, so the
3122 + * module is deactivated for exactly those sites. An absent or truthy option is left untouched.
3123 + *
3124 + * Ordering is the whole point. `activate_new_modules()` is hooked on `init` at priority 10 and
3125 + * auto-activates the module there; this method is hooked on `init` at priority 20 (see
3126 + * {@see self::plugin_upgrade()}), so it runs AFTER the auto-activation and its deactivation is
3127 + * the final state. A version-guarded block that ran inline during `plugins_loaded` would be
3128 + * undone by the later auto-activation, which is why this is a late-init hook rather than an
3129 + * inline upgrade step.
3130 + *
3131 + * WordPress.com Simple never runs modules — the option stays the master there — so this is a
3132 + * no-op on Simple. The {@see self::AI_MASTER_OPTOUT_MIGRATED_OPTION} flag makes it run exactly
3133 + * once, which matters because off-Simple the option is no longer the master after this runs:
3134 + * a stale falsey option must not keep re-deactivating a module the user later turns back on.
3135 + *
3136 + * @return void
3137 + */
3138 + public static function reconcile_ai_master_optout() {
3139 + if ( get_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION ) ) {
3140 + return;
3141 + }
3142 +
3143 + // Simple keeps the `jetpack_ai_enabled` option as the master; modules don't run there.
3144 + if ( ( new Host() )->is_wpcom_simple() ) {
3145 + return;
3146 + }
3147 +
3148 + // A sentinel default distinguishes an absent option (leave auto-activation alone) from one
3149 + // explicitly stored falsey (an opt-out to preserve).
3150 + $stored = get_option( 'jetpack_ai_enabled', 'not-set' );
3151 + if ( 'not-set' !== $stored && ! (bool) $stored ) {
3152 + ( new Modules() )->deactivate( 'ai' );
3153 + }
3154 +
3155 + update_option( self::AI_MASTER_OPTOUT_MIGRATED_OPTION, true );
3156 + }
3157 +
3158 + /**
3159 + * Deletes obsolete SEO module-state options without changing module activation.
3160 + *
3161 + * @since 16.3
3162 + */
3163 + public static function cleanup_seo_module_state_options() {
3164 + delete_option( 'jetpack_seo_sitemap_enabled' );
3165 + delete_option( 'jetpack_seo_canonical_urls_enabled' );
3166 + delete_option( 'jetpack_seo_module_state_reconciled' );
3167 + }
3168 +
3169 + /**
3170 + * Seeds the Jetpack SEO discoverability cohort once, so the new SEO surface is
3171 + * auto-discoverable on fresh installs but opt-in on existing ones (JETPACK-1700).
3172 + *
3173 + * Hooked on `updating_jetpack_version`, which fires on every install including the
3174 + * first — with `$old_version === false` on a brand-new site (the same signal
3175 + * {@see self::activate_subscriptions_module_for_existing_sites()} keys off). Fresh
3176 + * installs are seeded visible; existing installs are seeded hidden and opt in later
3177 + * via the legacy Traffic page or My Jetpack. `add_option()` makes this seed-once: it
3178 + * never overrides a value a later opt-in (or opt-out) has set. WordPress.com sites
3179 + * ignore this option entirely (always visible) — see
3180 + * {@see \Automattic\Jetpack\SEO\Initializer::is_seo_surface_visible()}.
3181 + *
3182 + * @param string $version The new Jetpack version (unused).
3183 + * @param string|false $old_version The previous version, or false on a fresh install.
3184 + */
3185 + public static function seed_seo_visibility_cohort( $version, $old_version ) {
3186 + add_option( Jetpack_SEO_Initializer::VISIBILITY_OPTION, ! $old_version );
3187 + }
3188 +
3189 + /**
2930 3190 * Sets the display_update_modal state.
3191 + *
3192 + * The release post update modal that read this state has been removed. The write is kept so the
3193 + * method still behaves as documented for the deprecation window. When this is deleted, also drop
3194 + * the matching `display_update_modal` guard in Automattic\Jetpack\CookieState::should_set_cookie(),
3195 + * which exists only to keep this key out of the cookie on the Jetpack admin screen.
3196 + *
3197 + * @deprecated 16.2
2931 3198 */
2932 3199 public static function set_update_modal_display() {
3200 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2933 3201 self::state( 'display_update_modal', true );
2934 3202 }
2935 3203
2936 3204 /**
@@ -2935,11 +3203,16 @@
2935 3203
2936 3204 /**
2937 3205 * Enqueues the block library styles.
2938 3206 *
3207 + * Only ever used by the release post update modal, which has been removed.
3208 + *
3209 + * @deprecated 16.2
3210 + *
2939 3211 * @param string $hook The current admin page.
2940 3212 */
2941 3213 public static function enqueue_block_style( $hook ) {
3214 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2942 3215 if ( 'toplevel_page_jetpack' === $hook ) {
2943 3216 wp_enqueue_style( 'wp-block-library' );
2944 3217 }
2945 3218 }
@@ -2968,9 +3241,8 @@
2968 3241
2969 3242 self::load_modules();
2970 3243
2971 3244 Jetpack_Options::delete_option( 'do_activate' );
2972 - Jetpack_Options::delete_option( 'dismissed_connection_banner' );
2973 3245 }
2974 3246
2975 3247 /**
2976 3248 * Handles the activation of the default modules depending on the current state of the site:
@@ -3029,8 +3301,12 @@
3029 3301 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
3030 3302 self::disconnect();
3031 3303 Jetpack_Options::delete_option( 'version' );
3032 3304 }
3305 +
3306 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3307 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3308 + }
3033 3309 }
3034 3310
3035 3311 /**
3036 3312 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -3058,9 +3334,9 @@
3058 3334 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
3059 3335 }
3060 3336
3061 3337 /**
3062 - * Happens after a successfull disconnection.
3338 + * Happens after a successful disconnection.
3063 3339 *
3064 3340 * @static
3065 3341 */
3066 3342 public static function jetpack_site_disconnected() {
@@ -3065,8 +3341,10 @@
3065 3341 */
3066 3342 public static function jetpack_site_disconnected() {
3067 3343 Identity_Crisis::clear_all_idc_options();
3068 3344
3345 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3346 +
3069 3347 // Delete all the sync related data. Since it could be taking up space.
3070 3348 Sender::get_instance()->uninstall();
3071 3349
3072 3350 /**
@@ -3083,10 +3361,13 @@
3083 3361 }
3084 3362 }
3085 3363
3086 3364 /**
3087 - * Disconnects the user
3365 + * Disconnects the user.
3088 3366 *
3367 + * @deprecated 13.4
3368 + * @see \Automattic\Jetpack\Connection\Manager::disconnect_user()
3369 + *
3089 3370 * @param int $user_id The user ID to disconnect.
3090 3371 */
3091 3372 public function disconnect_user( $user_id ) {
3092 3373 $this->connection_manager->disconnect_user( $user_id );
@@ -3092,21 +3373,8 @@
3092 3373 $this->connection_manager->disconnect_user( $user_id );
3093 3374 }
3094 3375
3095 3376 /**
3096 - * Attempts Jetpack registration. If it fail, a state flag is set: @see ::admin_page_load()
3097 - *
3098 - * @deprecated since Jetpack 9.7.0
3099 - * @see Automattic\Jetpack\Connection\Manager::try_registration()
3100 - *
3101 - * @return bool|WP_Error
3102 - */
3103 - public static function try_registration() {
3104 - _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Manager::try_registration' );
3105 - return static::connection()->try_registration();
3106 - }
3107 -
3108 - /**
3109 3377 * Checking the domain names in beta versions.
3110 3378 * If this is a development version, before attempting to connect, let's make sure that the domains are viable.
3111 3379 *
3112 3380 * @param null|\WP_Error $error The domain validation error, or `null` if everything's fine.
@@ -3140,10 +3408,17 @@
3140 3408 * @param mixed $code Error code to log.
3141 3409 * @param mixed $data Data to log.
3142 3410 */
3143 3411 public static function log( $code, $data = null ) {
3412 +
3413 + $raw_log = Jetpack_Options::get_option( 'log', array() );
3414 + // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
3415 + if ( ! is_array( $raw_log ) ) {
3416 + return;
3417 + }
3418 +
3144 3419 // only grab the latest 200 entries.
3145 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3420 + $log = array_slice( $raw_log, -199, 199 );
3146 3421
3147 3422 // Append our event to the log.
3148 3423 $log_entry = array(
3149 3424 'time' => time(),
@@ -3243,8 +3518,15 @@
3243 3518
3244 3519 /**
3245 3520 * Return stat data for WPCOM sync.
3246 3521 *
3522 + * The Sync stats module was this method's last caller and moved to the Connection package's
3523 + * `Heartbeat::generate_stats_array()`, which assembles the heartbeat data through the
3524 + * `jetpack_heartbeat_stats_array` filter. Note the package method does not include the extended
3525 + * data from `get_additional_stat_data()`, so callers relying on `$extended` need to add it themselves.
3526 + *
3527 + * @deprecated 16.2
3528 + *
3247 3529 * @param bool $encode JSON encode the result.
3248 3530 * @param bool $extended Adds additional stats data.
3249 3531 *
3250 3532 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3249,10 +3531,15 @@
3249 3531 *
3250 3532 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3251 3533 */
3252 3534 public static function get_stat_data( $encode = true, $extended = true ) {
3253 - $data = Jetpack_Heartbeat::generate_stats_array();
3535 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3254 3536
3537 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
3538 + ? Heartbeat::get_environment_stats()
3539 + : array();
3540 + $data = array_merge( Jetpack_Heartbeat::generate_stats_array(), $env_stats );
3541 +
3255 3542 if ( $extended ) {
3256 3543 $additional_data = self::get_additional_stat_data();
3257 3544 $data = array_merge( $data, $additional_data );
3258 3545 }
@@ -3257,9 +3544,9 @@
3257 3544 $data = array_merge( $data, $additional_data );
3258 3545 }
3259 3546
3260 3547 if ( $encode ) {
3261 - return wp_json_encode( $data );
3548 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3262 3549 }
3263 3550
3264 3551 return $data;
3265 3552 }
@@ -3338,12 +3625,17 @@
3338 3625 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3339 3626 // Upgrade: 1.1 -> 1.1.1
3340 3627 // Check and see if host can verify the Jetpack servers' SSL certificate.
3341 3628 $args = array();
3629 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3342 3630 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3343 3631 }
3344 3632
3345 - if ( current_user_can( 'manage_options' ) && ! self::permit_ssl() ) {
3633 + if (
3634 + current_user_can( 'manage_options' )
3635 + && ! self::permit_ssl()
3636 + && ! $is_offline_mode
3637 + ) {
3346 3638 add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3347 3639 }
3348 3640
3349 3641 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
@@ -3352,12 +3644,8 @@
3352 3644 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3353 3645 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3354 3646 }
3355 3647
3356 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3357 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3358 - }
3359 -
3360 3648 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3361 3649
3362 3650 if ( self::is_connection_ready() || $is_offline_mode ) {
3363 3651 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3362,13 +3650,8 @@
3362 3650 if ( self::is_connection_ready() || $is_offline_mode ) {
3363 3651 // Artificially throw errors in certain specific cases during plugin activation.
3364 3652 add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3365 3653 }
3366 -
3367 - // Add custom column in wp-admin/users.php to show whether user is linked.
3368 - add_filter( 'manage_users_columns', array( $this, 'jetpack_icon_user_connected' ) );
3369 - add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_user_connected_icon' ), 10, 3 );
3370 - add_action( 'admin_print_styles', array( $this, 'jetpack_user_col_style' ) );
3371 3654 }
3372 3655
3373 3656 /**
3374 3657 * Adds body classes.
@@ -3401,8 +3684,9 @@
3401 3684 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3402 3685 * This function artificially throws errors for such cases (per a specific list).
3403 3686 *
3404 3687 * @param string $plugin The activated plugin.
3688 + * @throws RuntimeException If a conflicting plugin is detected.
3405 3689 */
3406 3690 public function throw_error_on_activate_plugin( $plugin ) {
3407 3691 $active_modules = self::get_active_modules();
3408 3692
@@ -3415,8 +3699,9 @@
3415 3699 if ( 'stats.php' === basename( $plugin ) ) {
3416 3700 $throw = true;
3417 3701 }
3418 3702 } else {
3703 + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Checked above. See also https://github.com/phan/phan/issues/1204.
3419 3704 $reflection = new ReflectionFunction( 'stats_get_api_key' );
3420 3705 if ( basename( $plugin ) === basename( $reflection->getFileName() ) ) {
3421 3706 $throw = true;
3422 3707 }
@@ -3423,9 +3708,9 @@
3423 3708 }
3424 3709
3425 3710 if ( $throw ) {
3426 3711 /* translators: Plugin name to deactivate. */
3427 - trigger_error( sprintf( esc_html__( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), 'WordPress.com Stats' ), E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
3712 + throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3428 3713 }
3429 3714 }
3430 3715 }
3431 3716
@@ -3507,8 +3792,9 @@
3507 3792 /**
3508 3793 * Handler for Jetpack remote file uploads.
3509 3794 *
3510 3795 * @access public
3796 + * @return never
3511 3797 */
3512 3798 public function remote_request_handlers() {
3513 3799 switch ( current_filter() ) {
3514 3800 case 'wp_ajax_nopriv_jetpack_upload_file':
@@ -3535,18 +3821,17 @@
3535 3821 if ( ! is_int( $status_code ) ) {
3536 3822 $status_code = 400;
3537 3823 }
3538 3824
3539 - status_header( $status_code );
3540 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3825 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3541 3826 }
3542 3827
3543 - status_header( 200 );
3544 3828 if ( true === $response ) {
3545 - exit;
3829 + status_header( 200 );
3830 + exit( 0 );
3546 3831 }
3547 3832
3548 - die( wp_json_encode( (object) $response ) );
3833 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3549 3834 }
3550 3835
3551 3836 /**
3552 3837 * Uploads a file gotten from the global $_FILES.
@@ -3554,9 +3839,9 @@
3554 3839 * the attachment file of the media item (gotten through of the post_id)
3555 3840 * will be updated instead of add a new one.
3556 3841 *
3557 3842 * @param boolean $update_media_item - update media attachment.
3558 - * @return array - An array describing the uploadind files process.
3843 + * @return array|WP_Error - An array describing the uploading files process.
3559 3844 */
3560 3845 public function upload_handler( $update_media_item = false ) {
3561 3846 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
3562 3847 return new WP_Error( 405, get_status_header_desc( 405 ), 405 );
@@ -3607,9 +3892,9 @@
3607 3892 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3608 3893 }
3609 3894
3610 3895 $uploaded_files = array();
3611 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3896 + $global_post = $GLOBALS['post'] ?? null;
3612 3897 unset( $GLOBALS['post'] );
3613 3898 if ( empty( $_FILES['media']['name'] ) ) {
3614 3899 // Nothing to process, just return.
3615 3900 return $uploaded_files;
@@ -3616,9 +3901,9 @@
3616 3901 }
3617 3902 foreach ( $_FILES['media']['name'] as $index => $name ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, unslash sniff is wrong. Validation should happen below.
3618 3903 $file = array();
3619 3904 foreach ( $media_keys as $media_key ) {
3620 - $file[ $media_key ] = isset( $_FILES['media'][ $media_key ][ $index ] ) ? $_FILES['media'][ $media_key ][ $index ] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3905 + $file[ $media_key ] = $_FILES['media'][ $media_key ][ $index ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- As above, the unslash sniff is wrong.
3621 3906 }
3622 3907
3623 3908 list( $hmac_provided, $salt ) = isset( $_POST['_jetpack_file_hmac_media'][ $index ] ) ? explode( ':', filter_var( wp_unslash( $_POST['_jetpack_file_hmac_media'][ $index ] ) ) ) : array( 'no', '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce should have been checked by the caller.
3624 3909
@@ -3664,9 +3949,9 @@
3664 3949 'type' => (string) $edited_media_item->post_mime_type,
3665 3950 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3666 3951 );
3667 3952
3668 - return (array) array( $response );
3953 + return array( $response );
3669 3954 }
3670 3955
3671 3956 $attachment_id = media_handle_upload(
3672 3957 '.jetpack.upload.',
@@ -3705,67 +3990,8 @@
3705 3990 return $uploaded_files;
3706 3991 }
3707 3992
3708 3993 /**
3709 - * Add help to the Jetpack page
3710 - *
3711 - * @since Jetpack (1.2.3)
3712 - * @return void
3713 - */
3714 - public function admin_help() {
3715 - $current_screen = get_current_screen();
3716 -
3717 - // Overview.
3718 - $current_screen->add_help_tab(
3719 - array(
3720 - 'id' => 'home',
3721 - 'title' => __( 'Home', 'jetpack' ),
3722 - 'content' =>
3723 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3724 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3725 - '<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>',
3726 - )
3727 - );
3728 -
3729 - // Screen Content.
3730 - if ( current_user_can( 'manage_options' ) ) {
3731 - $current_screen->add_help_tab(
3732 - array(
3733 - 'id' => 'settings',
3734 - 'title' => __( 'Settings', 'jetpack' ),
3735 - 'content' =>
3736 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3737 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3738 - '<ol>' .
3739 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3740 - '<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>' .
3741 - '</ol>' .
3742 - '<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>',
3743 - )
3744 - );
3745 - }
3746 -
3747 - // Help Sidebar.
3748 - $support_url = Redirect::get_url( 'jetpack-support' );
3749 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3750 - $current_screen->set_help_sidebar(
3751 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3752 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3753 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3754 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3755 - );
3756 - }
3757 -
3758 - /**
3759 - * Enqueues the jetpack-icons style.
3760 - *
3761 - * @return void
3762 - */
3763 - public function admin_menu_css() {
3764 - wp_enqueue_style( 'jetpack-icons' );
3765 - }
3766 -
3767 - /**
3768 3994 * Add action links for the Jetpack plugin.
3769 3995 *
3770 3996 * @param array $actions Plugin actions.
3771 3997 *
@@ -3771,14 +3997,11 @@
3771 3997 *
3772 3998 * @return array
3773 3999 */
3774 4000 public function plugin_action_links( $actions ) {
3775 - $support_link = ( new Host() )->is_woa_site() ? 'https://wordpress.com/help/contact/' : self::admin_url( 'page=jetpack-debugger' );
3776 -
3777 4001 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3778 4002 return array_merge(
3779 - array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3780 - array( 'support' => sprintf( '<a href="%s">%s</a>', esc_url( $support_link ), __( 'Support', 'jetpack' ) ) ),
4003 + array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack-settings#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3781 4004 $actions
3782 4005 );
3783 4006 }
3784 4007
@@ -3785,53 +4008,8 @@
3785 4008 return $actions;
3786 4009 }
3787 4010
3788 4011 /**
3789 - * Add an activation modal to the plugins page and the main dashboard.
3790 - *
3791 - * @param string $hook The current admin page.
3792 - *
3793 - * @return void
3794 - */
3795 - public function activate_dialog( $hook ) {
3796 - /*
3797 - * We do not need it on other plugin pages,
3798 - * or when Jetpack is already connected.
3799 - */
3800 - if (
3801 - ! in_array( $hook, array( 'plugins.php', 'index.php' ), true )
3802 - || self::is_connection_ready()
3803 - ) {
3804 - return;
3805 - }
3806 -
3807 - // add an activation script that will pick up deactivation actions for the Jetpack plugin.
3808 - Assets::register_script(
3809 - 'jetpack-full-activation-modal-js',
3810 - '_inc/build/activation-modal.js',
3811 - JETPACK__PLUGIN_FILE,
3812 - array(
3813 - 'enqueue' => true,
3814 - 'in_footer' => true,
3815 - 'textdomain' => 'jetpack',
3816 - 'dependencies' => array(
3817 - 'wp-polyfill',
3818 - 'wp-components',
3819 - ),
3820 - )
3821 - );
3822 -
3823 - // Add objects to be passed to the initial state of the app.
3824 - // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3825 - wp_add_inline_script( 'jetpack-full-activation-modal-js', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state() ) ) . '"));', 'before' );
3826 -
3827 - // Adds Connection package initial state.
3828 - Connection_Initial_State::render_script( 'jetpack-full-activation-modal-js' );
3829 -
3830 - add_action( 'admin_notices', array( $this, 'jetpack_plugin_portal_containers' ) );
3831 - }
3832 -
3833 - /**
3834 4012 * Adds the deactivation warning modal for Jetpack.
3835 4013 *
3836 4014 * @param string $hook The current admin page.
3837 4015 *
@@ -3851,14 +4029,10 @@
3851 4029 'jetpack-plugins-page-js',
3852 4030 '_inc/build/plugins-page.js',
3853 4031 JETPACK__PLUGIN_FILE,
3854 4032 array(
3855 - 'in_footer' => true,
3856 - 'textdomain' => 'jetpack',
3857 - 'dependencies' => array(
3858 - 'wp-polyfill',
3859 - 'wp-components',
3860 - ),
4033 + 'in_footer' => true,
4034 + 'textdomain' => 'jetpack',
3861 4035 )
3862 4036 );
3863 4037 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3864 4038
@@ -3863,9 +4037,9 @@
3863 4037 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3864 4038
3865 4039 // Add objects to be passed to the initial state of the app.
3866 4040 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3867 - wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state() ) ) . '"));', 'before' );
4041 + wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=' . wp_json_encode( Jetpack_Redux_State_Helper::get_plugins_page_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
3868 4042
3869 4043 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3870 4044 }
3871 4045 }
@@ -3914,9 +4088,9 @@
3914 4088 // @todo provide way to go to specific calypso env.
3915 4089 self::get_calypso_host() . 'jetpack/connect'
3916 4090 )
3917 4091 );
3918 - exit;
4092 + exit( 0 );
3919 4093 }
3920 4094 }
3921 4095
3922 4096 /*
@@ -3951,8 +4125,28 @@
3951 4125 * Done!
3952 4126 */
3953 4127
3954 4128 /**
4129 + * Build the user-facing description stored alongside a registration error code.
4130 + *
4131 + * @since 16.2
4132 + *
4133 + * @param string $error_code The WP_Error code.
4134 + * @param string $message The WP_Error message.
4135 + * @return string The description, empty when the message is not user-facing copy.
4136 + */
4137 + public static function get_registration_error_description( $error_code, $message ) {
4138 + // Manager::validate_remote_register_response() does not always put user-facing copy in the
4139 + // message slot: wpcom_5??, wpcom_408 and wpcom_bad_response store the HTTP status there,
4140 + // and jetpack_id stores the raw response body, which can also overflow the state cookie.
4141 + if ( 'jetpack_id' === $error_code || is_numeric( $message ) ) {
4142 + return '';
4143 + }
4144 +
4145 + return mb_substr( (string) $message, 0, 250 );
4146 + }
4147 +
4148 + /**
3955 4149 * Handles the page load events for the Jetpack admin page
3956 4150 */
3957 4151 public function admin_page_load() {
3958 4152 $error = false;
@@ -3988,10 +4182,11 @@
3988 4182 $registered = static::connection()->try_registration();
3989 4183 if ( is_wp_error( $registered ) ) {
3990 4184 $error = $registered->get_error_code();
3991 4185 self::state( 'error', $error );
3992 - self::state( 'error', $registered->get_error_message() );
3993 4186
4187 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4188 +
3994 4189 /**
3995 4190 * Jetpack registration Error.
3996 4191 *
3997 4192 * @since 7.5.0
@@ -4015,12 +4210,8 @@
4015 4210 do_action( 'jetpack_connection_register_success', $from );
4016 4211
4017 4212 $url = $this->build_connect_url( true, $redirect, $from );
4018 4213
4019 - if ( ! empty( $_GET['onboarding'] ) ) {
4020 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
4021 - }
4022 -
4023 4214 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
4024 4215 $url = add_query_arg( 'auth_approved', 'true', $url );
4025 4216 }
4026 4217
@@ -4025,9 +4216,9 @@
4025 4216 }
4026 4217
4027 4218 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
4028 4219 wp_safe_redirect( $url );
4029 - exit;
4220 + exit( 0 );
4030 4221 case 'activate':
4031 4222 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
4032 4223 $error = 'cheatin';
4033 4224 break;
@@ -4041,9 +4232,9 @@
4041 4232 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
4042 4233 }
4043 4234 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
4044 4235 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4045 - exit;
4236 + exit( 0 );
4046 4237 case 'activate_default_modules':
4047 4238 check_admin_referer( 'activate_default_modules' );
4048 4239 self::log( 'activate_default_modules' );
4049 4240 self::restate();
@@ -4051,9 +4242,9 @@
4051 4242 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
4052 4243 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
4053 4244 self::activate_default_modules( $min_version, $max_version, $other_modules );
4054 4245 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4055 - exit;
4246 + exit( 0 );
4056 4247 case 'disconnect':
4057 4248 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
4058 4249 $error = 'cheatin';
4059 4250 break;
@@ -4062,9 +4253,9 @@
4062 4253 check_admin_referer( 'jetpack-disconnect' );
4063 4254 self::log( 'disconnect' );
4064 4255 self::disconnect();
4065 4256 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
4066 - exit;
4257 + exit( 0 );
4067 4258 case 'reconnect':
4068 4259 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
4069 4260 $error = 'cheatin';
4070 4261 break;
@@ -4075,9 +4266,9 @@
4075 4266 self::disconnect();
4076 4267
4077 4268 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
4078 4269 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
4079 - exit;
4270 + exit( 0 );
4080 4271 case 'deactivate':
4081 4272 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
4082 4273 $error = 'cheatin';
4083 4274 break;
@@ -4091,9 +4282,9 @@
4091 4282 self::state( 'message', 'module_deactivated' );
4092 4283 }
4093 4284 self::state( 'module', $modules );
4094 4285 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4095 - exit;
4286 + exit( 0 );
4096 4287 case 'unlink':
4097 4288 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
4098 4289 check_admin_referer( 'jetpack-unlink' );
4099 4290 self::log( 'unlink' );
@@ -4103,32 +4294,9 @@
4103 4294 wp_safe_redirect( admin_url() );
4104 4295 } else {
4105 4296 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
4106 4297 }
4107 - exit;
4108 - case 'onboard':
4109 - if ( ! current_user_can( 'manage_options' ) ) {
4110 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
4111 - } else {
4112 - self::create_onboarding_token();
4113 - $url = $this->build_connect_url( true );
4114 -
4115 - $token = Jetpack_Options::get_option( 'onboarding' );
4116 -
4117 - if ( false !== ( $token ) ) {
4118 - $url = add_query_arg( 'onboarding', $token, $url );
4119 - }
4120 -
4121 - $calypso_env = ( new Host() )->get_calypso_env();
4122 - if ( ! empty( $calypso_env ) ) {
4123 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
4124 - }
4125 -
4126 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
4127 - wp_safe_redirect( $url );
4128 - exit;
4129 - }
4130 - exit;
4298 + exit( 0 );
4131 4299 default:
4132 4300 /**
4133 4301 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
4134 4302 *
@@ -4351,37 +4519,8 @@
4351 4519 endif;
4352 4520 }
4353 4521
4354 4522 /**
4355 - * We can't always respond to a signed XML-RPC request with a
4356 - * helpful error message. In some circumstances, doing so could
4357 - * leak information.
4358 - *
4359 - * Instead, track that the error occurred via a Jetpack_Option,
4360 - * and send that data back in the heartbeat.
4361 - * All this does is increment a number, but it's enough to find
4362 - * trends.
4363 - *
4364 - * @param WP_Error $xmlrpc_error The error produced during
4365 - * signature validation.
4366 - */
4367 - public function track_xmlrpc_error( $xmlrpc_error ) {
4368 - $code = is_wp_error( $xmlrpc_error )
4369 - ? $xmlrpc_error->get_error_code()
4370 - : 'should-not-happen';
4371 -
4372 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4373 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4374 - // No need to update the option if we already have
4375 - // this code stored.
4376 - return;
4377 - }
4378 - $xmlrpc_errors[ $code ] = true;
4379 -
4380 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4381 - }
4382 -
4383 - /**
4384 4523 * Initialize the jetpack stats instance only when needed
4385 4524 *
4386 4525 * @return void
4387 4526 */
@@ -4504,9 +4643,9 @@
4504 4643 return $this->build_connect_url( $raw, $redirect, $from, true );
4505 4644 }
4506 4645 }
4507 4646
4508 - $url = static::build_authorize_url( $redirect );
4647 + $url = ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4509 4648 }
4510 4649
4511 4650 if ( $from ) {
4512 4651 $url = add_query_arg( 'from', $from, $url );
@@ -4531,66 +4670,30 @@
4531 4670 * @param null $deprecated Deprecated since Jetpack 10.9.
4532 4671 *
4533 4672 * @todo Update default value for redirect since the called function expects a string.
4534 4673 *
4674 + * @deprecated 13.4
4675 + *
4535 4676 * @return mixed|void
4536 4677 */
4537 4678 public static function build_authorize_url( $redirect = false, $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
4679 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::build_authorize_url' );
4538 4680
4539 - add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4540 - add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4541 -
4542 - $c8n = self::connection();
4543 - $url = $c8n->get_authorization_url( wp_get_current_user(), $redirect );
4544 -
4545 - remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4546 - remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4547 -
4548 - /**
4549 - * Filter the URL used when authorizing a user to a WordPress.com account.
4550 - *
4551 - * @since 8.9.0
4552 - *
4553 - * @param string $url Connection URL.
4554 - */
4555 - return apply_filters( 'jetpack_build_authorize_url', $url );
4681 + return ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4556 4682 }
4557 4683
4558 4684 /**
4559 4685 * Filters the connection URL parameter array.
4560 4686 *
4687 + * @deprecated 13.4
4688 + *
4561 4689 * @param array $args default URL parameters used by the package.
4562 4690 * @return array the modified URL arguments array.
4563 4691 */
4564 4692 public static function filter_connect_request_body( $args ) {
4565 - if (
4566 - Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4567 - && include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4568 - ) {
4569 - $gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4570 - $args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
4571 - ? $gp_locale->slug
4572 - : '';
4573 - }
4693 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_request_body' );
4574 4694
4575 - $tracking = new Tracking();
4576 - $tracks_identity = $tracking->tracks_get_identity( $args['state'] );
4577 -
4578 - $args = array_merge(
4579 - $args,
4580 - array(
4581 - '_ui' => $tracks_identity['_ui'],
4582 - '_ut' => $tracks_identity['_ut'],
4583 - )
4584 - );
4585 -
4586 - $calypso_env = ( new Host() )->get_calypso_env();
4587 -
4588 - if ( ! empty( $calypso_env ) ) {
4589 - $args['calypso_env'] = $calypso_env;
4590 - }
4591 -
4592 - return $args;
4695 + return Authorize_Redirect::filter_connect_request_body( $args );
4593 4696 }
4594 4697
4595 4698 /**
4596 4699 * Filters the `jetpack/v4/connection/data` API response of the Connection package in order to
@@ -4627,41 +4730,19 @@
4627 4730 return $current_user_connection_data;
4628 4731 }
4629 4732
4630 4733 /**
4631 - * Filters the URL that will process the connection data. It can be different from the URL
4632 - * that we send the user to after everything is done.
4633 - *
4634 - * @param String $processing_url the default redirect URL used by the package.
4635 - * @return String the modified URL.
4636 - *
4637 - * @deprecated since Jetpack 9.5.0
4638 - */
4639 - public static function filter_connect_processing_url( $processing_url ) {
4640 - _deprecated_function( __METHOD__, 'jetpack-9.5' );
4641 -
4642 - $processing_url = admin_url( 'admin.php?page=jetpack' ); // Making PHPCS happy.
4643 - return $processing_url;
4644 - }
4645 -
4646 - /**
4647 4734 * Filters the redirection URL that is used for connect requests. The redirect
4648 4735 * URL should return the user back to the Jetpack console.
4649 4736 *
4737 + * @deprecated 13.4
4738 + *
4650 4739 * @param String $redirect the default redirect URL used by the package.
4651 4740 * @return String the modified URL.
4652 4741 */
4653 4742 public static function filter_connect_redirect_url( $redirect ) {
4654 - $jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4655 - $redirect = $redirect
4656 - ? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4657 - : $jetpack_admin_page;
4658 -
4659 - if ( isset( $_REQUEST['is_multisite'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- not making a site change here.
4660 - $redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4661 - }
4662 -
4663 - return $redirect;
4743 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_redirect_url' );
4744 + return Authorize_Redirect::filter_connect_redirect_url( $redirect );
4664 4745 }
4665 4746
4666 4747 /**
4667 4748 * This action fires at the beginning of the Manager::authorize method.
@@ -4725,11 +4806,8 @@
4725 4806 *
4726 4807 * @param array $data The request data.
4727 4808 */
4728 4809 public static function authorize_ending_authorized( $data ) {
4729 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4730 - self::invalidate_onboarding_token();
4731 -
4732 4810 // If redirect_uri is SSO, ensure SSO module is enabled.
4733 4811 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4734 4812
4735 4813 /** This filter is documented in class.jetpack-cli.php */
@@ -4869,10 +4947,12 @@
4869 4947 $result = self::permit_ssl( true );
4870 4948 wp_send_json(
4871 4949 array(
4872 4950 'enabled' => $result,
4873 - 'message' => get_transient( 'jetpack_https_test_message' ),
4874 - )
4951 + 'message' => self::get_ssl_test_message(),
4952 + ),
4953 + null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
4954 + JSON_UNESCAPED_SLASHES
4875 4955 );
4876 4956 }
4877 4957
4878 4958 /* Client API */
@@ -4879,8 +4959,10 @@
4879 4959
4880 4960 /**
4881 4961 * Verify the onboarding token.
4882 4962 *
4963 + * @deprecated since 13.9
4964 + *
4883 4965 * @param array $token_data Token data.
4884 4966 * @param string $token Token value.
4885 4967 * @param string $request_data JSON-encoded request data.
4886 4968 *
@@ -4886,8 +4968,9 @@
4886 4968 *
4887 4969 * @return mixed
4888 4970 */
4889 4971 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4972 + _deprecated_function( __METHOD__, '13.9' );
4890 4973 // Default to a blog token.
4891 4974 $token_type = 'blog';
4892 4975
4893 4976 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4915,9 +4998,9 @@
4915 4998 $jp_user = get_user_by( 'email', $jpo_user );
4916 4999 if ( is_a( $jp_user, 'WP_User' ) ) {
4917 5000 wp_set_current_user( $jp_user->ID );
4918 5001 $user_can = is_multisite()
4919 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5002 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4920 5003 : current_user_can( 'manage_options' );
4921 5004 if ( $user_can ) {
4922 5005 $token_type = 'user';
4923 5006 $token->external_user_id = $jp_user->ID;
@@ -4934,11 +5017,13 @@
4934 5017
4935 5018 /**
4936 5019 * Create a random secret for validating onboarding payload
4937 5020 *
5021 + * @deprecated since 13.9
4938 5022 * @return string Secret token
4939 5023 */
4940 5024 public static function create_onboarding_token() {
5025 + _deprecated_function( __METHOD__, '13.9' );
4941 5026 $token = Jetpack_Options::get_option( 'onboarding' );
4942 5027 if ( false === ( $token ) ) {
4943 5028 $token = wp_generate_password( 32, false );
4944 5029 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4949,11 +5034,13 @@
4949 5034
4950 5035 /**
4951 5036 * Remove the onboarding token
4952 5037 *
5038 + * @deprecated since 13.9
4953 5039 * @return bool True on success, false on failure
4954 5040 */
4955 5041 public static function invalidate_onboarding_token() {
5042 + _deprecated_function( __METHOD__, '13.9' );
4956 5043 return Jetpack_Options::delete_option( 'onboarding' );
4957 5044 }
4958 5045
4959 5046 /**
@@ -4958,8 +5045,10 @@
4958 5045
4959 5046 /**
4960 5047 * Validate an onboarding token for a specific action
4961 5048 *
5049 + * @deprecated since 13.9
5050 + *
4962 5051 * @param string $token Onboarding token.
4963 5052 * @param string $action Action name.
4964 5053 *
4965 5054 * @return boolean True if token/action pair is accepted, false if not
@@ -4964,8 +5053,9 @@
4964 5053 *
4965 5054 * @return boolean True if token/action pair is accepted, false if not
4966 5055 */
4967 5056 public static function validate_onboarding_token_action( $token, $action ) {
5057 + _deprecated_function( __METHOD__, '13.9' );
4968 5058 // Compare tokens, bail if tokens do not match.
4969 5059 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4970 5060 return false;
4971 5061 }
@@ -4991,39 +5081,41 @@
4991 5081 * @return boolean
4992 5082 * @since 2.3.3
4993 5083 */
4994 5084 public static function permit_ssl( $force_recheck = false ) {
4995 - // Do some fancy tests to see if ssl is being supported.
4996 - if ( ! $force_recheck ) {
4997 - $ssl = get_transient( 'jetpack_https_test' );
5085 + if ( ! method_exists( Heartbeat::class, 'permit_ssl' ) ) {
5086 + // Skip the SSL-fail notice when the check cannot run.
5087 + return true;
4998 5088 }
4999 5089
5000 - if ( $force_recheck || false === $ssl ) {
5001 - $message = '';
5002 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
5003 - $ssl = 0;
5004 - } else {
5005 - $ssl = 1;
5090 + return Heartbeat::permit_ssl( $force_recheck );
5091 + }
5006 5092
5007 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
5008 - $ssl = 0;
5009 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
5010 - } else {
5011 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
5012 - if ( is_wp_error( $response ) ) {
5013 - $ssl = 0;
5014 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
5015 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
5016 - $ssl = 0;
5017 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
5018 - }
5019 - }
5020 - }
5021 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
5022 - set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
5093 + /**
5094 + * Returns a localized message describing the last SSL connectivity failure, if any.
5095 + *
5096 + * The Connection package's canonical SSL check stores a neutral reason code; this maps it to
5097 + * a translated, `jetpack`-domain message for display in the admin notice and AJAX recheck.
5098 + *
5099 + * @since 16.1
5100 + *
5101 + * @return string The localized message, or an empty string when there is no failure.
5102 + */
5103 + public static function get_ssl_test_message() {
5104 + if ( ! method_exists( Heartbeat::class, 'get_ssl_test_error' ) ) {
5105 + return '';
5023 5106 }
5024 5107
5025 - return (bool) $ssl;
5108 + $error = Heartbeat::get_ssl_test_error();
5109 +
5110 + switch ( $error['code'] ) {
5111 + case 'no_ssl_support':
5112 + return __( 'WordPress reports no SSL support', 'jetpack' );
5113 + case 'bad_response':
5114 + return __( 'Response was not OK: ', 'jetpack' ) . $error['detail'];
5115 + default:
5116 + return '';
5117 + }
5026 5118 }
5027 5119
5028 5120 /**
5029 5121 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -5042,9 +5134,9 @@
5042 5134 <p><?php esc_html_e( 'Your site could not connect to WordPress.com via HTTPS. This could be due to any number of reasons, including faulty SSL certificates, misconfigured or missing SSL libraries, or network issues.', 'jetpack' ); ?></p>
5043 5135 <p>
5044 5136 <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
5045 5137 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
5046 - <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
5138 + <span id="jetpack-recheck-ssl-output"><?php echo esc_html( self::get_ssl_test_message() ); ?></span>
5047 5139 </p>
5048 5140 <p>
5049 5141 <?php
5050 5142 printf(
@@ -5063,18 +5155,18 @@
5063 5155 <script type="text/javascript">
5064 5156 jQuery( document ).ready( function( $ ) {
5065 5157 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
5066 5158 var $this = $( this );
5067 - $this.html( <?php echo wp_json_encode( __( 'Checking', 'jetpack' ) ); ?> );
5159 + $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
5068 5160 $( '#jetpack-recheck-ssl-output' ).html( '' );
5069 5161 e.preventDefault();
5070 - var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce ); ?> };
5162 + var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
5071 5163 $.post( ajaxurl, data )
5072 5164 .done( function( response ) {
5073 5165 if ( response.enabled ) {
5074 5166 $( '#jetpack-ssl-warning' ).hide();
5075 5167 } else {
5076 - this.html( <?php echo wp_json_encode( __( 'Try again', 'jetpack' ) ); ?> );
5168 + this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
5077 5169 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
5078 5170 }
5079 5171 }.bind( $this ) );
5080 5172 } );
@@ -5100,26 +5192,8 @@
5100 5192 return $jetpack->connection_manager;
5101 5193 }
5102 5194
5103 5195 /**
5104 - * Creates two secret tokens and the end of life timestamp for them.
5105 - *
5106 - * Note these tokens are unique per call, NOT static per site for connecting.
5107 - *
5108 - * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->generate() instead.
5109 - *
5110 - * @since 2.6
5111 - * @param String $action The action name.
5112 - * @param Integer $user_id The user identifier.
5113 - * @param Integer $exp Expiration time in seconds.
5114 - * @return array
5115 - */
5116 - public static function generate_secrets( $action, $user_id = false, $exp = 600 ) {
5117 - _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->generate' );
5118 - return self::connection()->generate_secrets( $action, $user_id, $exp );
5119 - }
5120 -
5121 - /**
5122 5196 * Get verification secrets.
5123 5197 *
5124 5198 * @param string $action Action name.
5125 5199 * @param int $user_id User ID.
@@ -5140,35 +5214,8 @@
5140 5214 return $secrets;
5141 5215 }
5142 5216
5143 5217 /**
5144 - * Register a connection.
5145 - *
5146 - * @deprecated Jetpack 9.7.0
5147 - * @see Automattic\Jetpack\Connection\Manager::try_registration()
5148 - *
5149 - * @return bool|WP_Error
5150 - */
5151 - public static function register() {
5152 - _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Manager::try_registration' );
5153 - return static::connection()->try_registration( false );
5154 - }
5155 -
5156 - /**
5157 - * Filters the registration request body to include tracking properties.
5158 - *
5159 - * @deprecated Jetpack 9.7.0
5160 - * @see Automattic\Jetpack\Connection\Utils::filter_register_request_body()
5161 - *
5162 - * @param array $properties Token request properties.
5163 - * @return array amended properties.
5164 - */
5165 - public static function filter_register_request_body( $properties ) {
5166 - _deprecated_function( __METHOD__, 'jetpack-9.7', 'Automattic\\Jetpack\\Connection\\Utils::filter_register_request_body' );
5167 - return Connection_Utils::filter_register_request_body( $properties );
5168 - }
5169 -
5170 - /**
5171 5218 * Filters the token request body to include tracking properties.
5172 5219 *
5173 5220 * @param array $properties Token request properties.
5174 5221 *
@@ -5189,9 +5236,9 @@
5189 5236
5190 5237 /**
5191 5238 * If the db version is showing something other that what we've got now, bump it to current.
5192 5239 *
5193 - * @return bool: True if the option was incorrect and updated, false if nothing happened.
5240 + * @return bool True if the option was incorrect and updated, false if nothing happened.
5194 5241 */
5195 5242 public static function maybe_set_version_option() {
5196 5243 list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
5197 5244 if ( JETPACK__VERSION !== $version ) {
@@ -5209,21 +5256,8 @@
5209 5256
5210 5257 /* Client Server API */
5211 5258
5212 5259 /**
5213 - * Loads the Jetpack XML-RPC client.
5214 - * No longer necessary, as the XML-RPC client will be automagically loaded.
5215 - *
5216 - * Note: we cannot remove this function yet as it is used in this plugin:
5217 - * https://wordpress.org/plugins/jetpack-subscription-form/
5218 - *
5219 - * @deprecated since 7.7.0
5220 - */
5221 - public static function load_xml_rpc_client() {
5222 - _deprecated_function( __METHOD__, 'jetpack-7.7' );
5223 - }
5224 -
5225 - /**
5226 5260 * State is passed via cookies from one request to the next, but never to subsequent requests.
5227 5261 * SET: state( $key, $value );
5228 5262 * GET: $value = state( $key );
5229 5263 *
@@ -5297,20 +5331,8 @@
5297 5331
5298 5332 self::state( 'privacy_checks', $privacy_checks );
5299 5333 }
5300 5334
5301 - /**
5302 - * Serve a WordPress.com static resource via a randomized wp.com subdomain.
5303 - *
5304 - * @deprecated 9.3.0 Use Assets::staticize_subdomain.
5305 - *
5306 - * @param string $url WordPress.com static resource URL.
5307 - */
5308 - public static function staticize_subdomain( $url ) {
5309 - _deprecated_function( __METHOD__, 'jetpack-9.3.0', 'Automattic\Jetpack\Assets::staticize_subdomain' );
5310 - return Assets::staticize_subdomain( $url );
5311 - }
5312 -
5313 5335 /* JSON API Authorization */
5314 5336
5315 5337 /**
5316 5338 * Handles the login action for Authorizing the JSON API
@@ -5315,13 +5337,14 @@
5315 5337 /**
5316 5338 * Handles the login action for Authorizing the JSON API
5317 5339 */
5318 5340 public function login_form_json_api_authorization() {
5319 - $this->verify_json_api_authorization_request();
5341 + $authorize_json_api = new Authorize_Json_Api();
5342 + $authorize_json_api->verify_json_api_authorization_request();
5320 5343
5321 - add_action( 'wp_login', array( $this, 'store_json_api_authorization_token' ), 10, 2 );
5344 + add_action( 'wp_login', array( $authorize_json_api, 'store_json_api_authorization_token' ), 10, 2 );
5322 5345
5323 - add_action( 'login_message', array( $this, 'login_message_json_api_authorization' ) );
5346 + add_action( 'login_message', array( $authorize_json_api, 'login_message_json_api_authorization' ) );
5324 5347 add_action( 'login_form', array( $this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5325 5348 add_filter( 'site_url', array( $this, 'post_login_form_to_signed_url' ), 10, 3 );
5326 5349 }
5327 5350
@@ -5359,16 +5382,17 @@
5359 5382
5360 5383 /**
5361 5384 * If someone logs in to approve API access, store the Access Code in usermeta.
5362 5385 *
5386 + * @deprecated 13.4
5387 + *
5363 5388 * @param string $user_login Unused.
5364 5389 * @param WP_User $user User logged in.
5365 5390 */
5366 5391 public function store_json_api_authorization_token( $user_login, $user ) {
5367 - add_filter( 'login_redirect', array( $this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5368 - add_filter( 'allowed_redirect_hosts', array( $this, 'allow_wpcom_public_api_domain' ) );
5369 - $token = wp_generate_password( 32, false );
5370 - update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
5392 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::store_json_api_authorization_token' );
5393 +
5394 + return ( new Authorize_Json_Api() )->store_json_api_authorization_token( $user_login, $user );
5371 5395 }
5372 5396
5373 5397 /**
5374 5398 * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
@@ -5374,23 +5398,29 @@
5374 5398 * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
5375 5399 *
5376 5400 * To be used with a filter of allowed domains for a redirect.
5377 5401 *
5402 + * @deprecated 13.4
5403 + *
5378 5404 * @param array $domains Allowed WP.com Environments.
5379 5405 */
5380 5406 public function allow_wpcom_public_api_domain( $domains ) {
5381 - $domains[] = 'public-api.wordpress.com';
5382 - return $domains;
5407 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Status\\Host::allow_wpcom_public_api_domain' );
5408 +
5409 + return Host::allow_wpcom_public_api_domain( $domains );
5383 5410 }
5384 5411
5385 5412 /**
5386 5413 * Check if the redirect is encoded.
5387 5414 *
5415 + * @deprecated 13.4
5416 + *
5388 5417 * @param string $redirect_url Redirect URL.
5389 5418 *
5390 5419 * @return bool If redirect has been encoded.
5391 5420 */
5392 5421 public static function is_redirect_encoded( $redirect_url ) {
5422 + _deprecated_function( __METHOD__, 'jetpack-13.4' );
5393 5423 return preg_match( '/https?%3A%2F%2F/i', $redirect_url ) > 0;
5394 5424 }
5395 5425
5396 5426 /**
@@ -5408,8 +5438,10 @@
5408 5438
5409 5439 /**
5410 5440 * Add the Access Code details to the public-api.wordpress.com redirect.
5411 5441 *
5442 + * @deprecated 13.4
5443 + *
5412 5444 * @param string $redirect_to URL.
5413 5445 * @param string $original_redirect_to URL.
5414 5446 * @param WP_User $user WP_User for the redirect.
5415 5447 *
@@ -5415,23 +5447,18 @@
5415 5447 *
5416 5448 * @return string
5417 5449 */
5418 5450 public function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5419 - return add_query_arg(
5420 - urlencode_deep(
5421 - array(
5422 - 'jetpack-code' => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5423 - 'jetpack-user-id' => (int) $user->ID,
5424 - 'jetpack-state' => $this->json_api_authorization_request['state'],
5425 - )
5426 - ),
5427 - $redirect_to
5428 - );
5451 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::add_token_to_login_redirect_json_api_authorization' );
5452 +
5453 + return ( new Authorize_Json_Api() )->add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user );
5429 5454 }
5430 5455
5431 5456 /**
5432 5457 * Verifies the request by checking the signature
5433 5458 *
5459 + * @deprecated 13.4
5460 + *
5434 5461 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5435 5462 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5436 5463 *
5437 5464 * @param null|array $environment Value to override $_REQUEST.
@@ -5436,194 +5463,24 @@
5436 5463 *
5437 5464 * @param null|array $environment Value to override $_REQUEST.
5438 5465 */
5439 5466 public function verify_json_api_authorization_request( $environment = null ) {
5440 - $environment = $environment === null
5441 - ? $_REQUEST // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verification handled later in function.
5442 - : $environment;
5467 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::verify_json_api_authorization_request' );
5443 5468
5444 - list( $env_token,, $env_user_id ) = explode( ':', $environment['token'] );
5445 - $token = ( new Tokens() )->get_access_token( $env_user_id, $env_token );
5446 - if ( ! $token || empty( $token->secret ) ) {
5447 - wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack' ) );
5448 - }
5449 -
5450 - $die_error = __( 'Someone may be trying to trick you into giving them access to your site. Or it could be you just encountered a bug :). Either way, please close this window.', 'jetpack' );
5451 -
5452 - // Host has encoded the request URL, probably as a result of a bad http => https redirect.
5453 - if ( self::is_redirect_encoded( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- no site changes, we're erroring out.
5454 - /**
5455 - * Jetpack authorisation request Error.
5456 - *
5457 - * @since 7.5.0
5458 - */
5459 - do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
5460 - $die_error = sprintf(
5461 - /* translators: %s is a URL */
5462 - __( 'Your site is incorrectly double-encoding redirects from http to https. This is preventing Jetpack from authenticating your connection. Please visit our <a href="%s">support page</a> for details about how to resolve this.', 'jetpack' ),
5463 - esc_url( Redirect::get_url( 'jetpack-support-double-encoding' ) )
5464 - );
5465 - }
5466 -
5467 - $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5468 -
5469 - if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5470 - $signature = $jetpack_signature->sign_request(
5471 - $environment['token'],
5472 - $environment['timestamp'],
5473 - $environment['nonce'],
5474 - '',
5475 - 'GET',
5476 - $environment['jetpack_json_api_original_query'],
5477 - null,
5478 - true
5479 - );
5480 - } else {
5481 - $signature = $jetpack_signature->sign_current_request(
5482 - array(
5483 - 'body' => null,
5484 - 'method' => 'GET',
5485 - )
5486 - );
5487 - }
5488 -
5489 - if ( ! $signature ) {
5490 - wp_die(
5491 - wp_kses(
5492 - $die_error,
5493 - array(
5494 - 'a' => array(
5495 - 'href' => array(),
5496 - ),
5497 - )
5498 - )
5499 - );
5500 - } elseif ( is_wp_error( $signature ) ) {
5501 - wp_die(
5502 - wp_kses(
5503 - $die_error,
5504 - array(
5505 - 'a' => array(
5506 - 'href' => array(),
5507 - ),
5508 - )
5509 - )
5510 - );
5511 - } elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
5512 - if ( is_ssl() ) {
5513 - // If we signed an HTTP request on the Jetpack Servers, but got redirected to HTTPS by the local blog, check the HTTP signature as well.
5514 - $signature = $jetpack_signature->sign_current_request(
5515 - array(
5516 - 'scheme' => 'http',
5517 - 'body' => null,
5518 - 'method' => 'GET',
5519 - )
5520 - );
5521 - if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5522 - wp_die(
5523 - wp_kses(
5524 - $die_error,
5525 - array(
5526 - 'a' => array(
5527 - 'href' => array(),
5528 - ),
5529 - )
5530 - )
5531 - );
5532 - }
5533 - } else {
5534 - wp_die(
5535 - wp_kses(
5536 - $die_error,
5537 - array(
5538 - 'a' => array(
5539 - 'href' => array(),
5540 - ),
5541 - )
5542 - )
5543 - );
5544 - }
5545 - }
5546 -
5547 - $timestamp = (int) $environment['timestamp'];
5548 - $nonce = stripslashes( (string) $environment['nonce'] );
5549 -
5550 - if ( ! $this->connection_manager ) {
5551 - $this->connection_manager = new Connection_Manager();
5552 - }
5553 -
5554 - if ( ! ( new Nonce_Handler() )->add( $timestamp, $nonce ) ) {
5555 - // De-nonce the nonce, at least for 5 minutes.
5556 - // We have to reuse this nonce at least once (used the first time when the initial request is made, used a second time when the login form is POSTed).
5557 - $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5558 - if ( $old_nonce_time < time() - 300 ) {
5559 - wp_die( esc_html__( 'The authorization process expired. Please go back and try again.', 'jetpack' ) );
5560 - }
5561 - }
5562 -
5563 - $data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
5564 - $data_filters = array(
5565 - 'state' => 'opaque',
5566 - 'client_id' => 'int',
5567 - 'client_title' => 'string',
5568 - 'client_image' => 'url',
5569 - );
5570 -
5571 - foreach ( $data_filters as $key => $sanitation ) {
5572 - if ( ! isset( $data->$key ) ) {
5573 - wp_die(
5574 - wp_kses(
5575 - $die_error,
5576 - array(
5577 - 'a' => array(
5578 - 'href' => array(),
5579 - ),
5580 - )
5581 - )
5582 - );
5583 - }
5584 -
5585 - switch ( $sanitation ) {
5586 - case 'int':
5587 - $this->json_api_authorization_request[ $key ] = (int) $data->$key;
5588 - break;
5589 - case 'opaque':
5590 - $this->json_api_authorization_request[ $key ] = (string) $data->$key;
5591 - break;
5592 - case 'string':
5593 - $this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
5594 - break;
5595 - case 'url':
5596 - $this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
5597 - break;
5598 - }
5599 - }
5600 -
5601 - if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5602 - wp_die(
5603 - wp_kses(
5604 - $die_error,
5605 - array(
5606 - 'a' => array(
5607 - 'href' => array(),
5608 - ),
5609 - )
5610 - )
5611 - );
5612 - }
5469 + return ( new Authorize_Json_Api() )->verify_json_api_authorization_request( $environment );
5613 5470 }
5614 5471
5615 5472 /**
5616 5473 * HTML for the JSON API authorization notice.
5617 5474 *
5475 + * @deprecated 13.4
5476 + *
5618 5477 * @return string
5619 5478 */
5620 5479 public function login_message_json_api_authorization() {
5621 - return '<p class="message">' . sprintf(
5622 - /* translators: Name/image of the client requesting authorization */
5623 - esc_html__( '%s wants to access your site’s data. Log in to authorize that access.', 'jetpack' ),
5624 - '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5625 - ) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
5480 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::login_message_json_api_authorization' );
5481 +
5482 + return ( new Authorize_Json_Api() )->login_message_json_api_authorization();
5626 5483 }
5627 5484
5628 5485 /**
5629 5486 * Get $content_width, but with a <s>twist</s> filter.
@@ -5667,28 +5524,20 @@
5667 5524
5668 5525 /**
5669 5526 * Checks if the site is currently in an identity crisis.
5670 5527 *
5528 + * Now delegates to the Connection package so this matches what the heartbeat itself reports.
5529 + * Note the package guards on `Connection\Manager::is_connected()` where this used to guard on
5530 + * `Jetpack::is_connection_ready()`, so the `jetpack_is_connection_ready` filter no longer applies.
5531 + *
5532 + * @deprecated 16.2
5533 + *
5671 5534 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5672 5535 */
5673 5536 public static function check_identity_crisis() {
5674 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5675 - return false;
5676 - }
5677 - return Jetpack_Options::get_option( 'sync_error_idc' );
5678 - }
5537 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5679 5538
5680 - /**
5681 - * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
5682 - *
5683 - * @since 4.4.0
5684 - * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
5685 - *
5686 - * @return bool
5687 - */
5688 - public static function validate_sync_error_idc_option() {
5689 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::validate_sync_error_idc_option' );
5690 - return Identity_Crisis::validate_sync_error_idc_option();
5539 + return Identity_Crisis::check_identity_crisis();
5691 5540 }
5692 5541
5693 5542 /**
5694 5543 * Normalizes a url by doing three things:
@@ -5712,35 +5561,8 @@
5712 5561 return $url;
5713 5562 }
5714 5563
5715 5564 /**
5716 - * Gets the value that is to be saved in the jetpack_sync_error_idc option.
5717 - *
5718 - * @since 4.4.0
5719 - * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB
5720 - * @deprecated 9.8.0 Use \\Automattic\\Jetpack\\Identity_Crisis::get_sync_error_idc_option
5721 - *
5722 - * @param array $response HTTP response.
5723 - * @return array Array of the local urls, wpcom urls, and error code
5724 - */
5725 - public static function get_sync_error_idc_option( $response = array() ) {
5726 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::get_sync_error_idc_option' );
5727 - return Identity_Crisis::get_sync_error_idc_option( $response );
5728 - }
5729 -
5730 - /**
5731 - * Returns the value of the jetpack_sync_idc_optin filter, or constant.
5732 - * If set to true, the site will be put into staging mode.
5733 - *
5734 - * @since 4.3.2
5735 - * @return bool
5736 - */
5737 - public static function sync_idc_optin() {
5738 - _deprecated_function( __METHOD__, 'jetpack-9.8', '\\Automattic\\Jetpack\\Identity_Crisis::sync_idc_optin' );
5739 - return Identity_Crisis::sync_idc_optin();
5740 - }
5741 -
5742 - /**
5743 5565 * Maybe Use a .min.css stylesheet, maybe not.
5744 5566 *
5745 5567 * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5746 5568 *
@@ -5803,9 +5625,9 @@
5803 5625 *
5804 5626 * @return mixed
5805 5627 */
5806 5628 public static function set_suffix_on_min( $src, $handle ) {
5807 - if ( ! str_contains( $src, '.min.css' ) ) {
5629 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5808 5630 return $src;
5809 5631 }
5810 5632
5811 5633 if ( ! empty( self::$min_assets ) ) {
@@ -5841,8 +5663,10 @@
5841 5663 *
5842 5664 * Data passed in with the $data parameter will be available in the
5843 5665 * template file as $data['value']
5844 5666 *
5667 + * @html-template-var array $data
5668 + *
5845 5669 * @param string $template - Template file to load.
5846 5670 * @param array $data - Any data to pass along to the template.
5847 5671 * @return boolean - If template file was found.
5848 5672 **/
@@ -5860,8 +5684,19 @@
5860 5684 return false;
5861 5685 }
5862 5686
5863 5687 /**
5688 + * Register Jetpack-specific tests on the connection package's health test suite.
5689 + *
5690 + * @param \Automattic\Jetpack\Connection\Connection_Health_Tests $connection_tests The test suite instance.
5691 + */
5692 + public function register_jetpack_connection_tests( $connection_tests ) {
5693 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger/class-jetpack-cxn-tests.php';
5694 + $jetpack_tests = new Jetpack_Cxn_Tests();
5695 + $jetpack_tests->register_tests_on( $connection_tests );
5696 + }
5697 +
5698 + /**
5864 5699 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5865 5700 */
5866 5701 public function deprecated_hooks() {
5867 5702 $filter_deprecated_list = array(
@@ -6077,8 +5912,18 @@
6077 5912 'jetpack_subscriptions_modal_enabled' => array(
6078 5913 'replacement' => null,
6079 5914 'version' => 'jetpack-12.7.0',
6080 5915 ),
5916 + 'jetpack_pre_connection_prompt_helpers' => array(
5917 + 'replacement' => null,
5918 + 'version' => 'jetpack-13.2.0',
5919 + ),
5920 + 'jetpack_contact_form_use_package' => array(
5921 + 'replacement' => null,
5922 + 'version' => 'jetpack-13.4.0',
5923 + ),
5924 + // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
5925 + // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
6081 5926 );
6082 5927
6083 5928 foreach ( $filter_deprecated_list as $tag => $args ) {
6084 5929 if ( has_filter( $tag ) ) {
@@ -6209,125 +6054,8 @@
6209 6054 return $css;
6210 6055 }
6211 6056
6212 6057 /**
6213 - * This methods removes all of the registered css files on the front end
6214 - * from Jetpack in favor of using a single file. In effect "imploding"
6215 - * all the files into one file.
6216 - *
6217 - * Pros:
6218 - * - Uses only ONE css asset connection instead of 15
6219 - * - Saves a minimum of 56k
6220 - * - Reduces server load
6221 - * - Reduces time to first painted byte
6222 - *
6223 - * Cons:
6224 - * - Loads css for ALL modules. However all selectors are prefixed so it
6225 - * should not cause any issues with themes.
6226 - * - Plugins/themes dequeuing styles no longer do anything. See
6227 - * jetpack_implode_frontend_css filter for a workaround
6228 - *
6229 - * For some situations developers may wish to disable css imploding and
6230 - * instead operate in legacy mode where each file loads seperately and
6231 - * can be edited individually or dequeued. This can be accomplished with
6232 - * the following line:
6233 - *
6234 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
6235 - *
6236 - * @param bool $travis_test Is this a test run.
6237 - *
6238 - * @since 3.2
6239 - */
6240 - public function implode_frontend_css( $travis_test = false ) {
6241 - $do_implode = true;
6242 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
6243 - $do_implode = false;
6244 - }
6245 -
6246 - // Do not implode CSS when the page loads via the AMP plugin.
6247 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
6248 - $do_implode = false;
6249 - }
6250 -
6251 - /*
6252 - * Only proceed if at least 2 modules with concatenated CSS are active.
6253 - * There is no point in serving a big concatenated CSS file
6254 - * if there are no features (or only one) that actually need some CSS loaded.
6255 - */
6256 - $active_modules = self::get_active_modules();
6257 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
6258 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
6259 - if ( $active_module_with_css_count < 2 ) {
6260 - $do_implode = false;
6261 - }
6262 -
6263 - /**
6264 - * Allow CSS to be concatenated into a single jetpack.css file.
6265 - *
6266 - * @since 3.2.0
6267 - *
6268 - * @param bool $do_implode Should CSS be concatenated? Default to true.
6269 - */
6270 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
6271 -
6272 - // Do not use the imploded file when default behavior was altered through the filter.
6273 - if ( ! $do_implode ) {
6274 - return;
6275 - }
6276 -
6277 - // We do not want to use the imploded file in dev mode, or if not connected.
6278 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
6279 - if ( ! $travis_test ) {
6280 - return;
6281 - }
6282 - }
6283 -
6284 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
6285 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
6286 - return;
6287 - }
6288 -
6289 - /*
6290 - * Now we assume Jetpack is connected and able to serve the single
6291 - * file.
6292 - *
6293 - * In the future there will be a check here to serve the file locally
6294 - * or potentially from the Jetpack CDN
6295 - *
6296 - * For now:
6297 - * - Enqueue a single imploded css file
6298 - * - Zero out the style_loader_tag for the bundled ones
6299 - * - Be happy, drink scotch
6300 - */
6301 -
6302 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6303 -
6304 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6305 -
6306 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6307 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6308 - }
6309 -
6310 - /**
6311 - * Removes styles that are part of concatenated group.
6312 - *
6313 - * @param string $tag Style tag.
6314 - * @param string $handle Style handle.
6315 - *
6316 - * @return string
6317 - */
6318 - public function concat_remove_style_loader_tag( $tag, $handle ) {
6319 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
6320 - $tag = '';
6321 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6322 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6323 - }
6324 - }
6325 -
6326 - return $tag;
6327 - }
6328 -
6329 - /**
6330 6058 * Check the heartbeat data
6331 6059 *
6332 6060 * Organizes the heartbeat data by severity. For example, if the site
6333 6061 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -6339,9 +6067,23 @@
6339 6067 *
6340 6068 * $return array $filtered_data
6341 6069 */
6342 6070 public static function jetpack_check_heartbeat_data() {
6343 - $raw_data = Jetpack_Heartbeat::generate_stats_array();
6071 + /*
6072 + * Site environment stats (incl. wp-version/php-version checked below) now live in the Connection package,
6073 + * and the IDC stat is contributed by the Connection package's `jetpack_heartbeat_stats_array` filter
6074 + * callback. We rebuild the stat here rather than running that filter: the filter's callbacks have side
6075 + * effects (Connection\Manager::add_stats_to_heartbeat() consumes and deletes the `xmlrpc_errors` option)
6076 + * and return non-scalar values, neither of which is appropriate for this read-only diagnostic.
6077 + */
6078 + $env_stats = method_exists( Heartbeat::class, 'get_environment_stats' )
6079 + ? Heartbeat::get_environment_stats()
6080 + : array();
6081 + $raw_data = array_merge(
6082 + Jetpack_Heartbeat::generate_stats_array(),
6083 + $env_stats,
6084 + array( 'identitycrisis' => Identity_Crisis::check_identity_crisis() ? 'yes' : 'no' )
6085 + );
6344 6086
6345 6087 $good = array();
6346 6088 $caution = array();
6347 6089 $bad = array();
@@ -6407,23 +6149,8 @@
6407 6149 return Jetpack_Options::get_options_for_reset();
6408 6150 }
6409 6151
6410 6152 /**
6411 - * Strip http:// or https:// from a url, replaces forward slash with ::,
6412 - * so we can bring them directly to their site in calypso.
6413 - *
6414 - * @deprecated 9.2.0 Use Automattic\Jetpack\Status::get_site_suffix
6415 - *
6416 - * @param string $url URL.
6417 - * @return string url without the guff.
6418 - */
6419 - public static function build_raw_urls( $url ) {
6420 - _deprecated_function( __METHOD__, 'jetpack-9.2.0', 'Automattic\Jetpack\Status::get_site_suffix' );
6421 -
6422 - return ( new Status() )->get_site_suffix( $url );
6423 - }
6424 -
6425 - /**
6426 6153 * Get the user's meta box order.
6427 6154 *
6428 6155 * @param array $sorted Value for the user's option.
6429 6156 * @return mixed
@@ -6452,68 +6179,9 @@
6452 6179 }
6453 6180
6454 6181 return $sorted;
6455 6182 }
6456 -
6457 6183 /**
6458 - * Adds a "blank" column in the user admin table to display indication of user connection.
6459 - *
6460 - * @param array $columns User list table columns.
6461 - *
6462 - * @return array
6463 - */
6464 - public function jetpack_icon_user_connected( $columns ) {
6465 - $columns['user_jetpack'] = '';
6466 - return $columns;
6467 - }
6468 -
6469 - /**
6470 - * Show Jetpack icon if the user is linked.
6471 - *
6472 - * @param string $val HTML for the icon.
6473 - * @param string $col User list table column.
6474 - * @param int $user_id User ID.
6475 - *
6476 - * @return string
6477 - */
6478 - public function jetpack_show_user_connected_icon( $val, $col, $user_id ) {
6479 - if ( 'user_jetpack' === $col && self::connection()->is_user_connected( $user_id ) ) {
6480 - $jetpack_logo = new Jetpack_Logo();
6481 - $emblem_html = sprintf(
6482 - '<a title="%1$s" class="jp-emblem-user-admin">%2$s</a>',
6483 - esc_attr__( 'This user is linked and ready to fly with Jetpack.', 'jetpack' ),
6484 - $jetpack_logo->get_jp_emblem()
6485 - );
6486 - return $emblem_html;
6487 - }
6488 -
6489 - return $val;
6490 - }
6491 -
6492 - /**
6493 - * Style the Jetpack user column
6494 - */
6495 - public function jetpack_user_col_style() {
6496 - global $current_screen;
6497 - if ( ! empty( $current_screen->base ) && 'users' === $current_screen->base ) {
6498 - ?>
6499 - <style>
6500 - .fixed .column-user_jetpack {
6501 - width: 21px;
6502 - }
6503 - .jp-emblem-user-admin svg {
6504 - width: 20px;
6505 - height: 20px;
6506 - }
6507 - .jp-emblem-user-admin path {
6508 - fill: #069e08;
6509 - }
6510 - </style>
6511 - <?php
6512 - }
6513 - }
6514 -
6515 - /**
6516 6184 * Checks if Akismet is active and working.
6517 6185 *
6518 6186 * We dropped support for Akismet 3.0 with Jetpack 6.1.1 while introducing a check for an Akismet valid key
6519 6187 * that implied usage of methods present since more recent version.
@@ -6674,13 +6342,20 @@
6674 6342 }
6675 6343 }
6676 6344
6677 6345 /**
6678 - * Returns a boolean for whether backups UI should be displayed or not.
6346 + * Whether UI for backups should be displayed.
6679 6347 *
6348 + * On WPCom platforms this is gated on the backups-self-serve site feature.
6349 + * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
6350 + *
6680 6351 * @return bool Should backups UI be displayed?
6681 6352 */
6682 6353 public static function show_backups_ui() {
6354 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6355 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
6356 + }
6357 +
6683 6358 /**
6684 6359 * Whether UI for backups should be displayed.
6685 6360 *
6686 6361 * @since 6.5.0
@@ -6690,8 +6365,24 @@
6690 6365 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6691 6366 }
6692 6367
6693 6368 /**
6369 + * Whether UI for security scanning should be displayed.
6370 + *
6371 + * On WPCom platforms this is gated on the scan-self-serve site feature.
6372 + * On self-hosted Jetpack sites it always returns true.
6373 + *
6374 + * @return bool Should scan UI be displayed?
6375 + */
6376 + public static function show_scan_ui() {
6377 + if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
6378 + return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
6379 + }
6380 +
6381 + return true;
6382 + }
6383 +
6384 + /**
6694 6385 * Clean leftoveruser meta.
6695 6386 *
6696 6387 * Delete Jetpack-related user meta when it is no longer needed.
6697 6388 *
@@ -6778,8 +6469,25 @@
6778 6469 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6779 6470 ),
6780 6471 );
6781 6472
6473 + $products['growth'] = array(
6474 + 'title' => __( 'Jetpack Growth', 'jetpack' ),
6475 + 'slug' => 'jetpack_growth_yearly',
6476 + 'description' => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
6477 + 'show_promotion' => true,
6478 + 'discount_percent' => 50,
6479 + 'included_in_plans' => array( 'complete' ),
6480 + 'features' => array(
6481 + _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
6482 + _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
6483 + _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
6484 + _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
6485 + _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
6486 + _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
6487 + ),
6488 + );
6489 +
6782 6490 $products['scan'] = array(
6783 6491 'title' => __( 'Jetpack Scan', 'jetpack' ),
6784 6492 'slug' => 'jetpack_scan',
6785 6493 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6918,9 +6626,12 @@
6918 6626 if ( $plugin_file !== 'jetpack/jetpack.php' ) {
6919 6627 return $plugin_meta;
6920 6628 }
6921 6629
6922 - $plugin_meta[] = '<a href="https://wordpress.org/support/plugin/jetpack/reviews/?filter=5" target="_blank" rel="noopener noreferrer" title="' . esc_attr__( 'Rate Jetpack on WordPress.org', 'jetpack' ) . '" style="color: #ffb900">'
6630 + $u = get_current_user_id();
6631 + $site = get_site_url();
6632 +
6633 + $plugin_meta[] = '<a href="https://jetpack.com/redirect?source=jetpack-plugin-review&site=' . esc_attr( $site ) . '&u=' . esc_attr( $u ) . '" target="_blank" rel="noopener noreferrer" title="' . esc_attr__( 'Rate Jetpack on WordPress.org', 'jetpack' ) . '" style="color: #ffb900">'
6923 6634 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6924 6635 . '</a>';
6925 6636
6926 6637 return $plugin_meta;
@@ -6926,8 +6637,56 @@
6926 6637 return $plugin_meta;
6927 6638 }
6928 6639
6929 6640 /**
6641 + * Lazy instantiation of the Plugin_Tracking object.
6642 + *
6643 + * @since 13.9
6644 + *
6645 + * @return void
6646 + */
6647 + public function initialize_tracking() {
6648 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6649 + // Only need to run once.
6650 + return;
6651 + }
6652 +
6653 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6654 + ( new Plugin_Tracking() )->init();
6655 + }
6656 + }
6657 +
6658 + /**
6659 + * Run the "initialize tracking" hook.
6660 + *
6661 + * @since 13.9
6662 + */
6663 + public function run_initialize_tracking_action() {
6664 + /**
6665 + * Fires when the tracking needs to be initialized.
6666 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6667 + *
6668 + * @since 13.9
6669 + */
6670 + do_action( 'jetpack_initialize_tracking' );
6671 + }
6672 +
6673 + /**
6674 + * Initialize REST jsonAPI if needed.
6675 + *
6676 + * @return void
6677 + */
6678 + public function maybe_initialize_rest_jsonapi() {
6679 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6680 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6681 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6682 +
6683 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6684 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6685 + }
6686 + }
6687 +
6688 + /**
6930 6689 * Run plugin post-activation actions if we need to.
6931 6690 *
6932 6691 * @return void
6933 6692 */
@@ -6942,8 +6701,12 @@
6942 6701 if ( ( new Host() )->is_woa_site() ) {
6943 6702 $redirect_url = static::admin_url( 'page=jetpack' );
6944 6703 } elseif ( is_network_admin() ) {
6945 6704 $redirect_url = admin_url( 'network/admin.php?page=jetpack' );
6705 + } elseif ( get_transient( 'my_jetpack_product_activated' ) ) {
6706 + // don't redirect if this is an activation that just came from My Jetpack
6707 + // My Jetpack has its own set of post-activation redirects
6708 + return;
6946 6709 } elseif ( My_Jetpack_Initializer::should_initialize() ) {
6947 6710 $redirect_url = static::admin_url( 'page=my-jetpack' );
6948 6711 } else {
6949 6712 $redirect_url = static::admin_url( 'page=jetpack' );