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 +916 -813 13.3.3 → 16.3-a.7 View file →
@@ -9,27 +9,36 @@
9 9
10 10 use Automattic\Jetpack\Assets;
11 11 use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
12 12 use Automattic\Jetpack\Config;
13 +use Automattic\Jetpack\Connection\Authorize_Json_Api;
13 14 use Automattic\Jetpack\Connection\Client;
14 15 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
15 -use Automattic\Jetpack\Connection\Nonce_Handler;
16 16 use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
17 17 use Automattic\Jetpack\Connection\Secrets;
18 18 use Automattic\Jetpack\Connection\Tokens;
19 +use Automattic\Jetpack\Connection\Webhooks\Authorize_Redirect;
19 20 use Automattic\Jetpack\Constants;
20 21 use Automattic\Jetpack\CookieState;
21 22 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
22 23 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
23 24 use Automattic\Jetpack\Errors;
25 +use Automattic\Jetpack\Feature_Policy;
24 26 use Automattic\Jetpack\Files;
27 +use Automattic\Jetpack\Heartbeat;
25 28 use Automattic\Jetpack\Identity_Crisis;
29 +use Automattic\Jetpack\Import\Main as Import_Main;
26 30 use Automattic\Jetpack\Licensing;
27 31 use Automattic\Jetpack\Modules;
28 32 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
33 +use Automattic\Jetpack\Newsletter\Reader_Link;
29 34 use Automattic\Jetpack\Paths;
35 +use Automattic\Jetpack\Plugin\Deprecate;
30 36 use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
37 +use Automattic\Jetpack\Podcast\Podcast;
31 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;
32 41 use Automattic\Jetpack\Status;
33 42 use Automattic\Jetpack\Status\Host;
34 43 use Automattic\Jetpack\Status\Visitor;
35 44 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
@@ -36,9 +45,14 @@
36 45 use Automattic\Jetpack\Sync\Health;
37 46 use Automattic\Jetpack\Sync\Sender;
38 47 use Automattic\Jetpack\Terms_Of_Service;
39 48 use Automattic\Jetpack\Tracking;
49 +use Automattic\Woocommerce_Analytics;
40 50
51 +if ( ! defined( 'ABSPATH' ) ) {
52 + exit( 0 );
53 +}
54 +
41 55 /*
42 56 Options:
43 57 jetpack_options (array)
44 58 An array of options.
@@ -73,74 +87,8 @@
73 87 */
74 88 public $xmlrpc_server = null;
75 89
76 90 /**
77 - * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
78 - *
79 - * See $concatenated_style_handles for the list of handles,
80 - * and the implode_frontend_css method for more details.
81 - *
82 - * When updating this list, make sure to update $concatenated_style_handles as well.
83 - *
84 - * @var array List of Jetpack modules.
85 - */
86 - public $modules_with_concatenated_css = array(
87 - 'carousel',
88 - 'contact-form',
89 - 'infinite-scroll',
90 - 'likes',
91 - 'related-posts',
92 - 'sharedaddy',
93 - 'shortcodes',
94 - 'subscriptions',
95 - 'tiled-gallery',
96 - 'widgets',
97 - );
98 -
99 - /**
100 - * The handles of styles that are concatenated into jetpack.css.
101 - *
102 - * When making changes to that list,
103 - * you must also update concat_list in tools/webpack.config.css.js,
104 - * and to $modules_with_concatenated_css if necessary.
105 - *
106 - * @var array The handles of styles that are concatenated into jetpack.css.
107 - */
108 - public $concatenated_style_handles = array(
109 - 'jetpack-carousel-swiper-css',
110 - 'jetpack-carousel',
111 - 'grunion.css',
112 - 'the-neverending-homepage',
113 - 'jetpack_likes',
114 - 'jetpack_related-posts',
115 - 'sharedaddy',
116 - 'jetpack-slideshow',
117 - 'presentations',
118 - 'quiz',
119 - 'jetpack-subscriptions',
120 - 'jetpack-responsive-videos',
121 - 'jetpack-social-menu',
122 - 'tiled-gallery',
123 - 'jetpack_display_posts_widget',
124 - 'gravatar-profile-widget',
125 - 'goodreads-widget',
126 - 'jetpack_social_media_icons_widget',
127 - 'jetpack-top-posts-widget',
128 - 'jetpack_image_widget',
129 - 'jetpack-my-community-widget',
130 - 'jetpack-authors-widget',
131 - 'wordads',
132 - 'eu-cookie-law-style',
133 - 'flickr-widget-style',
134 - 'jetpack-search-widget',
135 - 'jetpack-simple-payments-widget-style',
136 - 'jetpack-widget-social-icons-styles',
137 - 'wpcom_instagram_widget',
138 - 'milestone-widget',
139 - 'subscribe-modal-css',
140 - );
141 -
142 - /**
143 91 * Contains all assets that have had their URL rewritten to minified versions.
144 92 *
145 93 * @var array
146 94 */
@@ -155,11 +103,8 @@
155 103 'contact-form' => array(
156 104 array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
157 105 array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
158 106 ),
159 - 'custom-css' => array(
160 - array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
161 - ),
162 107 'gravatar-hovercards' => array(
163 108 array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
164 109 ),
165 110 'latex' => array(
@@ -330,9 +275,8 @@
330 275 * Graph tags via filter when their Social Meta modules are active:
331 276 *
332 277 * - All in One SEO Pack, All in one SEO Pack Pro
333 278 * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
334 - * - SEOPress, SEOPress Pro
335 279 *
336 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:
337 281 * add_filter( 'jetpack_enable_open_graph', '__return_false' );
338 282 *
@@ -375,8 +319,10 @@
375 319 'wp-caregiver/wp-caregiver.php', // WP Caregiver.
376 320 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
377 321 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol.
378 322 'wp-ogp/wp-ogp.php', // WP-OGP.
323 + 'wp-seopress/seopress.php', // SEOPress.
324 + 'wp-seopress-pro/seopress-pro.php', // SEOPress Pro.
379 325 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin.
380 326 'wp-fb-share-like-button/wp_fb_share-like_widget.php', // WP Facebook Like Button.
381 327 'open-graph-metabox/open-graph-metabox.php', // Open Graph Metabox.
382 328 'seo-by-rank-math/rank-math.php', // Rank Math.
@@ -443,8 +389,10 @@
443 389
444 390 /**
445 391 * Verified data for JSON authorization request
446 392 *
393 + * @deprecated 13.4
394 + *
447 395 * @var array
448 396 */
449 397 public $json_api_authorization_request = array();
450 398
@@ -485,8 +433,16 @@
485 433 */
486 434 public static $instance = false;
487 435
488 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 + /**
489 445 * Singleton
490 446 *
491 447 * @static
492 448 */
@@ -526,9 +482,9 @@
526 482 if ( array_diff( $unfiltered_modules, $modules ) ) {
527 483 self::update_active_modules( $modules );
528 484 }
529 485
530 - add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
486 + self::register_upgrade_init_hooks();
531 487
532 488 // Upgrade to 4.3.0.
533 489 if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
534 490 Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
@@ -583,8 +539,16 @@
583 539 Jetpack_Options::delete_option( 'autoupdate_plugins' );
584 540 } // Should we have some type of fallback if something fails here?
585 541 }
586 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 +
587 551 if ( did_action( 'wp_loaded' ) ) {
588 552 self::upgrade_on_load();
589 553 } else {
590 554 add_action(
@@ -618,9 +582,8 @@
618 582 }
619 583
620 584 if (
621 585 class_exists( 'Jetpack_Sitemap_Manager' )
622 - && version_compare( JETPACK__VERSION, '5.3', '>=' )
623 586 ) {
624 587 do_action( 'jetpack_sitemaps_purge_data' );
625 588 }
626 589
@@ -635,9 +598,9 @@
635 598 * Also fires Action hooks for each newly activated and deactivated module.
636 599 *
637 600 * @param array $modules Array of active modules to be saved in options.
638 601 *
639 - * @return $success bool true for success, false for failure.
602 + * @return bool $success true for success, false for failure.
640 603 */
641 604 public static function update_active_modules( $modules ) {
642 605 return ( new Modules() )->update_active( $modules );
643 606 }
@@ -691,41 +654,17 @@
691 654 add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
692 655 add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
693 656 add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
694 657
695 - add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
696 -
697 - add_filter(
698 - 'jetpack_signature_check_token',
699 - array( __CLASS__, 'verify_onboarding_token' ),
700 - 10,
701 - 3
702 - );
703 -
704 658 /**
705 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.
706 663 */
707 664 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
708 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_independent_blocks' ) );
709 - add_action( 'plugins_loaded', array( 'Jetpack_Gutenberg', 'load_block_editor_extensions' ), 9 );
710 - /**
711 - * 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.
712 - *
713 - * 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.
714 - */
715 - if ( is_admin() ) {
716 - add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
717 - } else {
718 - add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
719 - }
720 - add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
721 -
722 665 add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
723 666
724 - // Unlink user before deleting the user from WP.com.
725 - add_action( 'deleted_user', array( $this, 'disconnect_user' ), 10, 1 );
726 - add_action( 'remove_user_from_blog', array( $this, 'disconnect_user' ), 10, 1 );
727 -
728 667 add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
729 668
730 669 add_filter( 'login_url', array( $this, 'login_url' ), 10, 2 );
731 670 add_action( 'login_init', array( $this, 'login_init' ) );
@@ -732,8 +671,13 @@
732 671
733 672 // Set up the REST authentication hooks.
734 673 Connection_Rest_Authentication::init();
735 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 +
736 680 add_action( 'admin_init', array( $this, 'admin_init' ) );
737 681 add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
738 682
739 683 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
@@ -766,25 +710,8 @@
766 710
767 711 add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
768 712 add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
769 713
770 - /*
771 - * If enabled, point edit post, page, and comment links to Calypso instead of WP-Admin.
772 - * We should make sure to only do this for front end links.
773 - */
774 - if ( self::get_option( 'edit_links_calypso_redirect' ) && ! is_admin() ) {
775 - add_filter( 'get_edit_post_link', array( $this, 'point_edit_post_links_to_calypso' ), 1, 2 );
776 - add_filter( 'get_edit_comment_link', array( $this, 'point_edit_comment_links_to_calypso' ), 1 );
777 -
778 - /*
779 - * We'll shortcircuit wp_notify_postauthor and wp_notify_moderator pluggable functions
780 - * so they point moderation links on emails to Calypso.
781 - */
782 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/functions.wp-notify.php';
783 - add_filter( 'comment_notification_recipients', 'jetpack_notify_postauthor', 1, 2 );
784 - add_filter( 'notify_moderator', 'jetpack_notify_moderator', 1, 2 );
785 - }
786 -
787 714 add_action(
788 715 'plugins_loaded',
789 716 function () {
790 717 if ( User_Agent_Info::is_mobile_app() ) {
@@ -795,18 +722,10 @@
795 722
796 723 // Update the site's Jetpack plan and products from API on heartbeats.
797 724 add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
798 725
799 - /**
800 - * This is the hack to concatenate all css files into one.
801 - * For description and reasoning see the implode_frontend_css method.
802 - *
803 - * Super late priority so we catch all the registered styles.
804 - */
805 - if ( ! is_admin() ) {
806 - add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
807 - add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
808 - }
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' ) );
809 728
810 729 // Actually push the stats on shutdown.
811 730 if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
812 731 add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -814,8 +733,10 @@
814 733
815 734 // After a successful connection.
816 735 add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
817 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' ) );
818 739
819 740 // Actions for Manager::authorize().
820 741 add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
821 742 add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
@@ -827,9 +748,9 @@
827 748 add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
828 749 add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
829 750
830 751 // Filters for the Manager::get_token() urls and request body.
831 - 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' ) );
832 753 add_filter( 'jetpack_token_request_body', array( __CLASS__, 'filter_token_request_body' ) );
833 754
834 755 // Filter for the `jetpack/v4/connection/data` API response.
835 756 add_filter( 'jetpack_current_user_connection_data', array( __CLASS__, 'filter_jetpack_current_user_connection_data' ) );
@@ -855,16 +776,180 @@
855 776
856 777 // Register product descriptions for partner coupon usage.
857 778 add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
858 779
859 - // Actions for conditional recommendations.
860 - add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
861 -
862 780 // Add 5-star
863 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();
864 791 }
865 792
866 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 + /**
867 952 * Before everything else starts getting initalized, we need to initialize Jetpack using the
868 953 * Config object.
869 954 */
870 955 public function configure() {
@@ -873,13 +958,10 @@
873 958 foreach (
874 959 array(
875 960 'jitm',
876 961 'sync',
962 + 'account_protection',
877 963 'waf',
878 - 'videopress',
879 - 'stats',
880 - 'stats_admin',
881 - 'import',
882 964 )
883 965 as $feature
884 966 ) {
885 967 $config->ensure( $feature );
@@ -884,8 +966,110 @@
884 966 ) {
885 967 $config->ensure( $feature );
886 968 }
887 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 +
888 1072 $config->ensure(
889 1073 'connection',
890 1074 array(
891 1075 'slug' => 'jetpack',
@@ -903,12 +1087,8 @@
903 1087 );
904 1088
905 1089 $config->ensure( 'search' );
906 1090
907 - if ( defined( 'ENABLE_WORDADS_SHARED_UI' ) && ENABLE_WORDADS_SHARED_UI ) {
908 - $config->ensure( 'wordads' );
909 - }
910 -
911 1091 if ( ! $this->connection_manager ) {
912 1092 $this->connection_manager = new Connection_Manager( 'jetpack' );
913 1093 }
914 1094
@@ -916,8 +1096,10 @@
916 1096 if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
917 1097 $config->ensure( 'publicize' );
918 1098 }
919 1099
1100 + add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
1101 +
920 1102 /*
921 1103 * Load things that should only be in Network Admin.
922 1104 *
923 1105 * For now blow away everything else until a more full
@@ -932,8 +1114,9 @@
932 1114 $is_connection_ready = self::is_connection_ready();
933 1115
934 1116 if ( $is_connection_ready ) {
935 1117 add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
1118 + $this->run_initialize_tracking_action();
936 1119
937 1120 Jetpack_Heartbeat::init();
938 1121 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
939 1122 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
@@ -938,8 +1121,19 @@
938 1121 if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
939 1122 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
940 1123 Jetpack_Search_Performance_Logger::init();
941 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 + );
942 1136 }
943 1137
944 1138 // Initialize remote file upload request handlers.
945 1139 $this->add_remote_request_handlers();
@@ -949,20 +1143,10 @@
949 1143 */
950 1144 if ( $is_connection_ready ) {
951 1145 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
952 1146 add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
953 - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php';
954 - add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 );
1147 + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
955 1148 }
956 -
957 - if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) ) {
958 - add_action( 'init', array( new Plugin_Tracking(), 'init' ) );
959 - } else {
960 - /**
961 - * Initialize tracking right after the user agrees to the terms of service.
962 - */
963 - add_action( 'jetpack_agreed_to_terms_of_service', array( new Plugin_Tracking(), 'init' ) );
964 - }
965 1149 }
966 1150
967 1151 /**
968 1152 * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
@@ -970,15 +1154,58 @@
970 1154 *
971 1155 * @action plugins_loaded
972 1156 */
973 1157 public function late_initialization() {
974 - add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
1158 + add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
975 1159
976 - 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 + }
977 1179
978 - // Initialize Boost Speed Score
979 - new Speed_Score( array(), 'jetpack-dashboard' );
1180 + Scan_Page_Init::initialize();
1181 + Jetpack_SEO_Initializer::init();
980 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 +
981 1208 /**
982 1209 * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
983 1210 * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
984 1211 *
@@ -1016,8 +1243,10 @@
1016 1243
1017 1244 /**
1018 1245 * Redirect edit post links to Calypso.
1019 1246 *
1247 + * @deprecated since 13.9
1248 + *
1020 1249 * @param string $default_url Post edit URL.
1021 1250 * @param int $post_id Post ID.
1022 1251 *
1023 1252 * @return string
@@ -1022,8 +1251,10 @@
1022 1251 *
1023 1252 * @return string
1024 1253 */
1025 1254 public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
1255 + _deprecated_function( __METHOD__, '13.9' );
1256 +
1026 1257 $post = get_post( $post_id );
1027 1258
1028 1259 if ( empty( $post ) ) {
1029 1260 return $default_url;
@@ -1054,13 +1285,17 @@
1054 1285
1055 1286 /**
1056 1287 * Redirect edit comment links to Calypso.
1057 1288 *
1289 + * @deprecated since 13.9
1290 + *
1058 1291 * @param string $url Comment edit URL.
1059 1292 *
1060 1293 * @return string
1061 1294 */
1062 1295 public function point_edit_comment_links_to_calypso( $url ) {
1296 + _deprecated_function( __METHOD__, '13.9' );
1297 +
1063 1298 // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
1064 1299 $query_args = null;
1065 1300 wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
1066 1301
@@ -1079,30 +1314,16 @@
1079 1314 *
1080 1315 * @return array list of callables.
1081 1316 */
1082 1317 public function filter_sync_callable_whitelist( $callables ) {
1083 -
1084 1318 // Jetpack Functions.
1085 1319 $jetpack_callables = array(
1086 1320 'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
1087 1321 'updates' => array( 'Jetpack', 'get_updates' ),
1088 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' ),
1089 1324 );
1090 - $callables = array_merge( $callables, $jetpack_callables );
1091 -
1092 - // Jetpack_SSO_Helpers.
1093 - if ( include_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ) {
1094 - $sso_helpers = array(
1095 - 'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
1096 - 'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
1097 - 'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
1098 - 'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
1099 - 'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
1100 - );
1101 - $callables = array_merge( $callables, $sso_helpers );
1102 - }
1103 -
1104 - return $callables;
1325 + return array_merge( $callables, $jetpack_callables );
1105 1326 }
1106 1327
1107 1328 /**
1108 1329 * Extend Sync multisite callables with Jetpack Plugin functions.
@@ -1143,16 +1364,8 @@
1143 1364 * @param string $cap Capability name.
1144 1365 */
1145 1366 public function jetpack_custom_caps( $caps, $cap ) {
1146 1367 switch ( $cap ) {
1147 - case 'jetpack_manage_modules':
1148 - case 'jetpack_activate_modules':
1149 - case 'jetpack_deactivate_modules':
1150 - $caps = array( 'manage_options' );
1151 - break;
1152 - case 'jetpack_configure_modules':
1153 - $caps = array( 'manage_options' );
1154 - break;
1155 1368 case 'jetpack_manage_autoupdates':
1156 1369 $caps = array(
1157 1370 'manage_options',
1158 1371 'update_plugins',
@@ -1170,9 +1383,9 @@
1170 1383 if ( $is_offline_mode ) {
1171 1384 $caps = array( 'manage_options' );
1172 1385 break;
1173 1386 } else {
1174 - $caps = array( 'read' );
1387 + $caps = array( 'edit_posts' );
1175 1388 }
1176 1389 break;
1177 1390 }
1178 1391 return $caps;
@@ -1331,9 +1544,9 @@
1331 1544 }
1332 1545 /**
1333 1546 * Does the network allow admins to add new users.
1334 1547 *
1335 - * @return boolian
1548 + * @return bool
1336 1549 */
1337 1550 public static function network_add_new_users() {
1338 1551 return (bool) get_site_option( 'add_new_users' );
1339 1552 }
@@ -1340,9 +1553,9 @@
1340 1553 /**
1341 1554 * File upload psace left per site in MB.
1342 1555 * -1 means NO LIMIT.
1343 1556 *
1344 - * @return number
1557 + * @return int
1345 1558 */
1346 1559 public static function network_site_upload_space() {
1347 1560 // value in MB.
1348 1561 return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
@@ -1359,9 +1572,9 @@
1359 1572
1360 1573 /**
1361 1574 * Maximum file upload size set by the network.
1362 1575 *
1363 - * @return number
1576 + * @return int
1364 1577 */
1365 1578 public static function network_max_upload_file_size() {
1366 1579 // value in KB.
1367 1580 return get_site_option( 'fileupload_maxk', 300 );
@@ -1615,29 +1828,8 @@
1615 1828 return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1616 1829 }
1617 1830
1618 1831 /**
1619 - * Whether the site is currently onboarding or not.
1620 - * A site is considered as being onboarded if it currently has an onboarding token.
1621 - *
1622 - * @since 5.8
1623 - * @deprecated Use \Automattic\Jetpack\Status()->is_onboarding()
1624 - *
1625 - * @access public
1626 - * @static
1627 - *
1628 - * @return bool True if the site is currently onboarding, false otherwise
1629 - */
1630 - public static function is_onboarding() {
1631 - _deprecated_function( __METHOD__, 'jetpack-10.9', 'Automattic\\Jetpack\\Status\\is_onboarding' );
1632 -
1633 - if ( ! method_exists( 'Automattic\Jetpack\Status', 'is_onboarding' ) ) {
1634 - return Jetpack_Options::get_option( 'onboarding' ) !== false;
1635 - }
1636 - return ( new Status() )->is_onboarding();
1637 - }
1638 -
1639 - /**
1640 1832 * Determines reason for Jetpack offline mode.
1641 1833 */
1642 1834 public static function development_mode_trigger_text() {
1643 1835 $status = new Status();
@@ -1651,11 +1843,10 @@
1651 1843 } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1652 1844 $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1653 1845 } elseif ( $status->is_local_site() ) {
1654 1846 $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1655 - /** This filter is documented in packages/status/src/class-status.php */
1656 - } elseif ( has_filter( 'jetpack_development_mode' ) && apply_filters( 'jetpack_development_mode', false ) ) { // This is a deprecated filter name.
1657 - $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' );
1658 1849 } else {
1659 1850 $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1660 1851 }
1661 1852
@@ -1685,15 +1876,8 @@
1685 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' ) ) );
1686 1877
1687 1878 echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1688 1879 }
1689 - // Throw up a notice if using staging mode.
1690 - if ( ( new Status() )->is_staging_site() ) {
1691 - /* translators: %s is a URL */
1692 - $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' ) ) );
1693 -
1694 - echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1695 - }
1696 1880 }
1697 1881
1698 1882 /**
1699 1883 * Whether Jetpack's version maps to a public release, or a development version.
@@ -1767,18 +1951,11 @@
1767 1951 */
1768 1952 public static function load_modules() {
1769 1953 $status = new Status();
1770 1954
1771 - if ( method_exists( $status, 'is_onboarding' ) ) {
1772 - $is_onboarding = $status->is_onboarding();
1773 - } else {
1774 - $is_onboarding = self::is_onboarding();
1775 - }
1776 -
1777 1955 if (
1778 1956 ! self::is_connection_ready()
1779 1957 && ! $status->is_offline_mode()
1780 - && ! $is_onboarding
1781 1958 && (
1782 1959 ! is_multisite()
1783 1960 || ! get_site_option( 'jetpack_protect_active' )
1784 1961 )
@@ -1899,22 +2076,14 @@
1899 2076 *
1900 2077 * @todo Store the result in core's object cache maybe?
1901 2078 */
1902 2079 public static function get_active_plugins() {
1903 - $active_plugins = (array) get_option( 'active_plugins', array() );
1904 -
1905 - if ( is_multisite() ) {
1906 - // Due to legacy code, active_sitewide_plugins stores them in the keys,
1907 - // whereas active_plugins stores them in the values.
1908 - $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1909 - if ( $network_plugins ) {
1910 - $active_plugins = array_merge( $active_plugins, $network_plugins );
1911 - }
2080 + // Older Connection copies can load first and lack this method.
2081 + if ( ! method_exists( Heartbeat::class, 'get_active_plugins' ) ) {
2082 + return array();
1912 2083 }
1913 2084
1914 - sort( $active_plugins );
1915 -
1916 - return array_unique( $active_plugins );
2085 + return Heartbeat::get_active_plugins();
1917 2086 }
1918 2087
1919 2088 /**
1920 2089 * Gets and parses additional plugin data to send with the heartbeat data
@@ -2052,8 +2221,10 @@
2052 2221 *
2053 2222 * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
2054 2223 */
2055 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).
2056 2227 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
2057 2228 }
2058 2229 }
2059 2230
@@ -2156,9 +2327,9 @@
2156 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.
2157 2328 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2158 2329 }
2159 2330 wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2160 - exit;
2331 + exit( 0 );
2161 2332 }
2162 2333 }
2163 2334
2164 2335 /**
@@ -2205,8 +2376,12 @@
2205 2376 default:
2206 2377 break;
2207 2378 }
2208 2379 }
2380 + if ( method_exists( Feature_Policy::class, 'ensure_hooks' ) ) {
2381 + Feature_Policy::ensure_hooks();
2382 + }
2383 +
2209 2384 /**
2210 2385 * Filters the array of default modules.
2211 2386 *
2212 2387 * @since 2.5.0
@@ -2299,8 +2474,17 @@
2299 2474 }
2300 2475 }
2301 2476 }
2302 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 +
2303 2487 return $modules;
2304 2488 }
2305 2489
2306 2490 /**
@@ -2357,23 +2541,30 @@
2357 2541
2358 2542 /**
2359 2543 * Return module name translation. Uses matching string created in modules/module-headings.php.
2360 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 + *
2361 2549 * @since 3.9.2
2362 2550 *
2363 2551 * @param array $modules Array of Jetpack modules.
2364 2552 *
2365 - * @return string|void
2553 + * @return array
2366 2554 */
2367 2555 public static function get_translated_modules( $modules ) {
2368 2556 foreach ( $modules as $index => $module ) {
2369 2557 $i18n_module = jetpack_get_module_i18n( $module['module'] );
2370 - if ( isset( $module['name'] ) ) {
2371 - $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;
2372 2563 }
2373 - if ( isset( $module['description'] ) ) {
2374 - $modules[ $index ]['description'] = $i18n_module['description'];
2375 - $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;
2376 2567 }
2377 2568 if ( isset( $module['module_tags'] ) ) {
2378 2569 $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2379 2570 }
@@ -2413,20 +2604,28 @@
2413 2604
2414 2605 /**
2415 2606 * Catches PHP errors. Must be used in conjunction with output buffering.
2416 2607 *
2608 + * @deprecated since 13.5
2417 2609 * @param bool $catch True to start catching, False to stop.
2418 2610 *
2419 2611 * @static
2612 + * @deprecated 13.5
2613 + * @see \Automattic\Jetpack\Errors
2420 2614 */
2421 2615 public static function catch_errors( $catch ) {
2616 + _deprecated_function( __METHOD__, '13.5' );
2617 + // @phan-suppress-next-line PhanDeprecatedClass
2422 2618 return ( new Errors() )->catch_errors( $catch );
2423 2619 }
2424 2620
2425 2621 /**
2426 2622 * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2623 + *
2624 + * @deprecated since 13.5
2427 2625 */
2428 2626 public static function catch_errors_on_shutdown() {
2627 + _deprecated_function( __METHOD__, '13.5' );
2429 2628 self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2430 2629 }
2431 2630
2432 2631 /**
@@ -2530,9 +2729,9 @@
2530 2729 ),
2531 2730 add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2532 2731 );
2533 2732 wp_safe_redirect( $url );
2534 - exit;
2733 + exit( 0 );
2535 2734 }
2536 2735 }
2537 2736
2538 2737 /**
@@ -2550,9 +2749,8 @@
2550 2749
2551 2750 // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating.
2552 2751 if ( $send_state_messages ) {
2553 2752 self::restate();
2554 - self::catch_errors( true );
2555 2753 }
2556 2754
2557 2755 $active = self::get_active_modules();
2558 2756
@@ -2620,10 +2818,8 @@
2620 2818 if ( $send_state_messages ) {
2621 2819 self::state( 'error', false );
2622 2820 self::state( 'module', false );
2623 2821 }
2624 -
2625 - self::catch_errors( false );
2626 2822 /**
2627 2823 * Fires when default modules are activated.
2628 2824 *
2629 2825 * @since 1.9.0
@@ -2681,9 +2877,9 @@
2681 2877 * @return string $url module configuration URL.
2682 2878 */
2683 2879 public static function module_configuration_url( $module ) {
2684 2880 $module = self::get_module_slug( $module );
2685 - $default_url = self::admin_url() . "#/settings?term=$module";
2881 + $default_url = self::admin_url( array( 'page' => 'jetpack-settings' ) ) . "#/settings?term=$module";
2686 2882 /**
2687 2883 * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2688 2884 *
2689 2885 * @since 6.9.0
@@ -2741,9 +2937,9 @@
2741 2937 if ( $update ) {
2742 2938 update_option( 'active_plugins', array_filter( $plugins ) );
2743 2939 }
2744 2940 }
2745 - exit;
2941 + exit( 0 );
2746 2942 }
2747 2943
2748 2944 /**
2749 2945 * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
@@ -2768,12 +2964,18 @@
2768 2964 update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2769 2965
2770 2966 Health::on_jetpack_activated();
2771 2967
2968 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
2969 +
2772 2970 if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2773 2971 Sync_Actions::do_only_first_initial_sync();
2774 2972 }
2775 2973
2974 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2975 + Woocommerce_Analytics::maybe_add_proxy_speed_module();
2976 + }
2977 +
2776 2978 self::plugin_initialize();
2777 2979 }
2778 2980
2779 2981 /**
@@ -2808,9 +3010,9 @@
2808 3010
2809 3011 if ( $plugins_path === $referer['path'] ) {
2810 3012 $source_type = 'list';
2811 3013 } elseif ( $plugins_install_path === $referer['path'] ) {
2812 - $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
3014 + $tab = $query_parts['tab'] ?? 'featured';
2813 3015 switch ( $tab ) {
2814 3016 case 'popular':
2815 3017 $source_type = 'popular';
2816 3018 break;
@@ -2820,10 +3022,10 @@
2820 3022 case 'favorites':
2821 3023 $source_type = 'favorites';
2822 3024 break;
2823 3025 case 'search':
2824 - $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2825 - $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;
2826 3028 break;
2827 3029 default:
2828 3030 $source_type = 'featured';
2829 3031 }
@@ -2832,29 +3034,171 @@
2832 3034 return array( $source_type, $source_query );
2833 3035 }
2834 3036
2835 3037 /**
2836 - * Runs before bumping version numbers up to a new version
3038 + * Runs before bumping version numbers up to a new version.
2837 3039 *
2838 - * @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.
2839 3046 * @param string $old_version Old Version:timestamp or false if not set yet.
2840 3047 */
2841 - public static function do_version_bump( $version, $old_version ) {
2842 - if ( $old_version ) { // For existing Jetpack installations.
2843 - 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 + }
2844 3051
2845 - // If a front end page is visited after the update, the 'wp' action will fire.
2846 - 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 + }
2847 3071
2848 - // If an admin page is visited after the update, the 'current_screen' action will fire.
2849 - 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;
2850 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 + }
2851 3088 }
2852 3089
2853 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 + /**
2854 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
2855 3198 */
2856 3199 public static function set_update_modal_display() {
3200 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2857 3201 self::state( 'display_update_modal', true );
2858 3202 }
2859 3203
2860 3204 /**
@@ -2859,11 +3203,16 @@
2859 3203
2860 3204 /**
2861 3205 * Enqueues the block library styles.
2862 3206 *
3207 + * Only ever used by the release post update modal, which has been removed.
3208 + *
3209 + * @deprecated 16.2
3210 + *
2863 3211 * @param string $hook The current admin page.
2864 3212 */
2865 3213 public static function enqueue_block_style( $hook ) {
3214 + _deprecated_function( __METHOD__, 'jetpack-16.2' );
2866 3215 if ( 'toplevel_page_jetpack' === $hook ) {
2867 3216 wp_enqueue_style( 'wp-block-library' );
2868 3217 }
2869 3218 }
@@ -2952,8 +3301,12 @@
2952 3301 add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2953 3302 self::disconnect();
2954 3303 Jetpack_Options::delete_option( 'version' );
2955 3304 }
3305 +
3306 + if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
3307 + Woocommerce_Analytics::maybe_remove_proxy_speed_module();
3308 + }
2956 3309 }
2957 3310
2958 3311 /**
2959 3312 * Set activated option to 4 on jetpack_idc_disconnect action.
@@ -2981,9 +3334,9 @@
2981 3334 $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2982 3335 }
2983 3336
2984 3337 /**
2985 - * Happens after a successfull disconnection.
3338 + * Happens after a successful disconnection.
2986 3339 *
2987 3340 * @static
2988 3341 */
2989 3342 public static function jetpack_site_disconnected() {
@@ -2988,8 +3341,10 @@
2988 3341 */
2989 3342 public static function jetpack_site_disconnected() {
2990 3343 Identity_Crisis::clear_all_idc_options();
2991 3344
3345 + \Automattic\Jetpack\Reprint_Export\Reprint_Exporter::discard_credentials();
3346 +
2992 3347 // Delete all the sync related data. Since it could be taking up space.
2993 3348 Sender::get_instance()->uninstall();
2994 3349
2995 3350 /**
@@ -3006,10 +3361,13 @@
3006 3361 }
3007 3362 }
3008 3363
3009 3364 /**
3010 - * Disconnects the user
3365 + * Disconnects the user.
3011 3366 *
3367 + * @deprecated 13.4
3368 + * @see \Automattic\Jetpack\Connection\Manager::disconnect_user()
3369 + *
3012 3370 * @param int $user_id The user ID to disconnect.
3013 3371 */
3014 3372 public function disconnect_user( $user_id ) {
3015 3373 $this->connection_manager->disconnect_user( $user_id );
@@ -3050,10 +3408,17 @@
3050 3408 * @param mixed $code Error code to log.
3051 3409 * @param mixed $data Data to log.
3052 3410 */
3053 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 +
3054 3419 // only grab the latest 200 entries.
3055 - $log = array_slice( Jetpack_Options::get_option( 'log', array() ), -199, 199 );
3420 + $log = array_slice( $raw_log, -199, 199 );
3056 3421
3057 3422 // Append our event to the log.
3058 3423 $log_entry = array(
3059 3424 'time' => time(),
@@ -3153,8 +3518,15 @@
3153 3518
3154 3519 /**
3155 3520 * Return stat data for WPCOM sync.
3156 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 + *
3157 3529 * @param bool $encode JSON encode the result.
3158 3530 * @param bool $extended Adds additional stats data.
3159 3531 *
3160 3532 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
@@ -3159,10 +3531,15 @@
3159 3531 *
3160 3532 * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3161 3533 */
3162 3534 public static function get_stat_data( $encode = true, $extended = true ) {
3163 - $data = Jetpack_Heartbeat::generate_stats_array();
3535 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Heartbeat::generate_stats_array' );
3164 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 +
3165 3542 if ( $extended ) {
3166 3543 $additional_data = self::get_additional_stat_data();
3167 3544 $data = array_merge( $data, $additional_data );
3168 3545 }
@@ -3167,9 +3544,9 @@
3167 3544 $data = array_merge( $data, $additional_data );
3168 3545 }
3169 3546
3170 3547 if ( $encode ) {
3171 - return wp_json_encode( $data );
3548 + return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3172 3549 }
3173 3550
3174 3551 return $data;
3175 3552 }
@@ -3248,12 +3625,17 @@
3248 3625 if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3249 3626 // Upgrade: 1.1 -> 1.1.1
3250 3627 // Check and see if host can verify the Jetpack servers' SSL certificate.
3251 3628 $args = array();
3629 + // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3252 3630 Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3253 3631 }
3254 3632
3255 - 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 + ) {
3256 3638 add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3257 3639 }
3258 3640
3259 3641 add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
@@ -3262,12 +3644,8 @@
3262 3644 if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3263 3645 add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3264 3646 }
3265 3647
3266 - if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3267 - add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3268 - }
3269 -
3270 3648 add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3271 3649
3272 3650 if ( self::is_connection_ready() || $is_offline_mode ) {
3273 3651 // Artificially throw errors in certain specific cases during plugin activation.
@@ -3306,8 +3684,9 @@
3306 3684 * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3307 3685 * This function artificially throws errors for such cases (per a specific list).
3308 3686 *
3309 3687 * @param string $plugin The activated plugin.
3688 + * @throws RuntimeException If a conflicting plugin is detected.
3310 3689 */
3311 3690 public function throw_error_on_activate_plugin( $plugin ) {
3312 3691 $active_modules = self::get_active_modules();
3313 3692
@@ -3320,8 +3699,9 @@
3320 3699 if ( 'stats.php' === basename( $plugin ) ) {
3321 3700 $throw = true;
3322 3701 }
3323 3702 } else {
3703 + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Checked above. See also https://github.com/phan/phan/issues/1204.
3324 3704 $reflection = new ReflectionFunction( 'stats_get_api_key' );
3325 3705 if ( basename( $plugin ) === basename( $reflection->getFileName() ) ) {
3326 3706 $throw = true;
3327 3707 }
@@ -3328,9 +3708,9 @@
3328 3708 }
3329 3709
3330 3710 if ( $throw ) {
3331 3711 /* translators: Plugin name to deactivate. */
3332 - 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' ) );
3333 3713 }
3334 3714 }
3335 3715 }
3336 3716
@@ -3441,18 +3821,17 @@
3441 3821 if ( ! is_int( $status_code ) ) {
3442 3822 $status_code = 400;
3443 3823 }
3444 3824
3445 - status_header( $status_code );
3446 - die( wp_json_encode( (object) compact( 'error', 'error_description' ) ) );
3825 + wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3447 3826 }
3448 3827
3449 - status_header( 200 );
3450 3828 if ( true === $response ) {
3451 - exit;
3829 + status_header( 200 );
3830 + exit( 0 );
3452 3831 }
3453 3832
3454 - die( wp_json_encode( (object) $response ) );
3833 + wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3455 3834 }
3456 3835
3457 3836 /**
3458 3837 * Uploads a file gotten from the global $_FILES.
@@ -3460,9 +3839,9 @@
3460 3839 * the attachment file of the media item (gotten through of the post_id)
3461 3840 * will be updated instead of add a new one.
3462 3841 *
3463 3842 * @param boolean $update_media_item - update media attachment.
3464 - * @return array - An array describing the uploadind files process.
3843 + * @return array|WP_Error - An array describing the uploading files process.
3465 3844 */
3466 3845 public function upload_handler( $update_media_item = false ) {
3467 3846 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
3468 3847 return new WP_Error( 405, get_status_header_desc( 405 ), 405 );
@@ -3513,9 +3892,9 @@
3513 3892 return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3514 3893 }
3515 3894
3516 3895 $uploaded_files = array();
3517 - $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3896 + $global_post = $GLOBALS['post'] ?? null;
3518 3897 unset( $GLOBALS['post'] );
3519 3898 if ( empty( $_FILES['media']['name'] ) ) {
3520 3899 // Nothing to process, just return.
3521 3900 return $uploaded_files;
@@ -3522,9 +3901,9 @@
3522 3901 }
3523 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.
3524 3903 $file = array();
3525 3904 foreach ( $media_keys as $media_key ) {
3526 - $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.
3527 3906 }
3528 3907
3529 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.
3530 3909
@@ -3570,9 +3949,9 @@
3570 3949 'type' => (string) $edited_media_item->post_mime_type,
3571 3950 'meta' => (array) wp_get_attachment_metadata( $post_id ),
3572 3951 );
3573 3952
3574 - return (array) array( $response );
3953 + return array( $response );
3575 3954 }
3576 3955
3577 3956 $attachment_id = media_handle_upload(
3578 3957 '.jetpack.upload.',
@@ -3611,58 +3990,8 @@
3611 3990 return $uploaded_files;
3612 3991 }
3613 3992
3614 3993 /**
3615 - * Add help to the Jetpack page
3616 - *
3617 - * @since Jetpack (1.2.3)
3618 - * @return void
3619 - */
3620 - public function admin_help() {
3621 - $current_screen = get_current_screen();
3622 -
3623 - // Overview.
3624 - $current_screen->add_help_tab(
3625 - array(
3626 - 'id' => 'home',
3627 - 'title' => __( 'Home', 'jetpack' ),
3628 - 'content' =>
3629 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3630 - '<p>' . __( 'Jetpack supercharges your self-hosted WordPress site with the awesome cloud power of WordPress.com.', 'jetpack' ) . '</p>' .
3631 - '<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>',
3632 - )
3633 - );
3634 -
3635 - // Screen Content.
3636 - if ( current_user_can( 'manage_options' ) ) {
3637 - $current_screen->add_help_tab(
3638 - array(
3639 - 'id' => 'settings',
3640 - 'title' => __( 'Settings', 'jetpack' ),
3641 - 'content' =>
3642 - '<p><strong>' . __( 'Jetpack', 'jetpack' ) . '</strong></p>' .
3643 - '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
3644 - '<ol>' .
3645 - '<li>' . __( 'Each module has an Activate or Deactivate link so you can toggle one individually.', 'jetpack' ) . '</li>' .
3646 - '<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>' .
3647 - '</ol>' .
3648 - '<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>',
3649 - )
3650 - );
3651 - }
3652 -
3653 - // Help Sidebar.
3654 - $support_url = Redirect::get_url( 'jetpack-support' );
3655 - $faq_url = Redirect::get_url( 'jetpack-faq' );
3656 - $current_screen->set_help_sidebar(
3657 - '<p><strong>' . __( 'For more information:', 'jetpack' ) . '</strong></p>' .
3658 - '<p><a href="' . esc_url( $faq_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack FAQ', 'jetpack' ) . '</a></p>' .
3659 - '<p><a href="' . esc_url( $support_url ) . '" rel="noopener noreferrer" target="_blank">' . __( 'Jetpack Support', 'jetpack' ) . '</a></p>' .
3660 - '<p><a href="' . esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ) . '">' . __( 'Jetpack Debugging Center', 'jetpack' ) . '</a></p>'
3661 - );
3662 - }
3663 -
3664 - /**
3665 3994 * Add action links for the Jetpack plugin.
3666 3995 *
3667 3996 * @param array $actions Plugin actions.
3668 3997 *
@@ -3670,9 +3999,9 @@
3670 3999 */
3671 4000 public function plugin_action_links( $actions ) {
3672 4001 if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3673 4002 return array_merge(
3674 - array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
4003 + array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack-settings#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3675 4004 $actions
3676 4005 );
3677 4006 }
3678 4007
@@ -3700,14 +4029,10 @@
3700 4029 'jetpack-plugins-page-js',
3701 4030 '_inc/build/plugins-page.js',
3702 4031 JETPACK__PLUGIN_FILE,
3703 4032 array(
3704 - 'in_footer' => true,
3705 - 'textdomain' => 'jetpack',
3706 - 'dependencies' => array(
3707 - 'wp-polyfill',
3708 - 'wp-components',
3709 - ),
4033 + 'in_footer' => true,
4034 + 'textdomain' => 'jetpack',
3710 4035 )
3711 4036 );
3712 4037 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3713 4038
@@ -3712,9 +4037,9 @@
3712 4037 Assets::enqueue_script( 'jetpack-plugins-page-js' );
3713 4038
3714 4039 // Add objects to be passed to the initial state of the app.
3715 4040 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3716 - 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' );
3717 4042
3718 4043 add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3719 4044 }
3720 4045 }
@@ -3763,9 +4088,9 @@
3763 4088 // @todo provide way to go to specific calypso env.
3764 4089 self::get_calypso_host() . 'jetpack/connect'
3765 4090 )
3766 4091 );
3767 - exit;
4092 + exit( 0 );
3768 4093 }
3769 4094 }
3770 4095
3771 4096 /*
@@ -3800,8 +4125,28 @@
3800 4125 * Done!
3801 4126 */
3802 4127
3803 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 + /**
3804 4149 * Handles the page load events for the Jetpack admin page
3805 4150 */
3806 4151 public function admin_page_load() {
3807 4152 $error = false;
@@ -3837,10 +4182,11 @@
3837 4182 $registered = static::connection()->try_registration();
3838 4183 if ( is_wp_error( $registered ) ) {
3839 4184 $error = $registered->get_error_code();
3840 4185 self::state( 'error', $error );
3841 - self::state( 'error', $registered->get_error_message() );
3842 4186
4187 + self::state( 'error_description', self::get_registration_error_description( $error, $registered->get_error_message() ) );
4188 +
3843 4189 /**
3844 4190 * Jetpack registration Error.
3845 4191 *
3846 4192 * @since 7.5.0
@@ -3864,12 +4210,8 @@
3864 4210 do_action( 'jetpack_connection_register_success', $from );
3865 4211
3866 4212 $url = $this->build_connect_url( true, $redirect, $from );
3867 4213
3868 - if ( ! empty( $_GET['onboarding'] ) ) {
3869 - $url = add_query_arg( 'onboarding', rawurlencode_deep( wp_unslash( $_GET['onboarding'] ) ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3870 - }
3871 -
3872 4214 if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3873 4215 $url = add_query_arg( 'auth_approved', 'true', $url );
3874 4216 }
3875 4217
@@ -3874,9 +4216,9 @@
3874 4216 }
3875 4217
3876 4218 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3877 4219 wp_safe_redirect( $url );
3878 - exit;
4220 + exit( 0 );
3879 4221 case 'activate':
3880 4222 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3881 4223 $error = 'cheatin';
3882 4224 break;
@@ -3890,9 +4232,9 @@
3890 4232 self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3891 4233 }
3892 4234 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3893 4235 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3894 - exit;
4236 + exit( 0 );
3895 4237 case 'activate_default_modules':
3896 4238 check_admin_referer( 'activate_default_modules' );
3897 4239 self::log( 'activate_default_modules' );
3898 4240 self::restate();
@@ -3900,9 +4242,9 @@
3900 4242 $max_version = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3901 4243 $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3902 4244 self::activate_default_modules( $min_version, $max_version, $other_modules );
3903 4245 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3904 - exit;
4246 + exit( 0 );
3905 4247 case 'disconnect':
3906 4248 if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3907 4249 $error = 'cheatin';
3908 4250 break;
@@ -3911,9 +4253,9 @@
3911 4253 check_admin_referer( 'jetpack-disconnect' );
3912 4254 self::log( 'disconnect' );
3913 4255 self::disconnect();
3914 4256 wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3915 - exit;
4257 + exit( 0 );
3916 4258 case 'reconnect':
3917 4259 if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3918 4260 $error = 'cheatin';
3919 4261 break;
@@ -3924,9 +4266,9 @@
3924 4266 self::disconnect();
3925 4267
3926 4268 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3927 4269 wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3928 - exit;
4270 + exit( 0 );
3929 4271 case 'deactivate':
3930 4272 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3931 4273 $error = 'cheatin';
3932 4274 break;
@@ -3940,9 +4282,9 @@
3940 4282 self::state( 'message', 'module_deactivated' );
3941 4283 }
3942 4284 self::state( 'module', $modules );
3943 4285 wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3944 - exit;
4286 + exit( 0 );
3945 4287 case 'unlink':
3946 4288 $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3947 4289 check_admin_referer( 'jetpack-unlink' );
3948 4290 self::log( 'unlink' );
@@ -3952,32 +4294,9 @@
3952 4294 wp_safe_redirect( admin_url() );
3953 4295 } else {
3954 4296 wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3955 4297 }
3956 - exit;
3957 - case 'onboard':
3958 - if ( ! current_user_can( 'manage_options' ) ) {
3959 - wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3960 - } else {
3961 - self::create_onboarding_token();
3962 - $url = $this->build_connect_url( true );
3963 -
3964 - $token = Jetpack_Options::get_option( 'onboarding' );
3965 -
3966 - if ( false !== ( $token ) ) {
3967 - $url = add_query_arg( 'onboarding', $token, $url );
3968 - }
3969 -
3970 - $calypso_env = ( new Host() )->get_calypso_env();
3971 - if ( ! empty( $calypso_env ) ) {
3972 - $url = add_query_arg( 'calypso_env', $calypso_env, $url );
3973 - }
3974 -
3975 - add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3976 - wp_safe_redirect( $url );
3977 - exit;
3978 - }
3979 - exit;
4298 + exit( 0 );
3980 4299 default:
3981 4300 /**
3982 4301 * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3983 4302 *
@@ -4200,37 +4519,8 @@
4200 4519 endif;
4201 4520 }
4202 4521
4203 4522 /**
4204 - * We can't always respond to a signed XML-RPC request with a
4205 - * helpful error message. In some circumstances, doing so could
4206 - * leak information.
4207 - *
4208 - * Instead, track that the error occurred via a Jetpack_Option,
4209 - * and send that data back in the heartbeat.
4210 - * All this does is increment a number, but it's enough to find
4211 - * trends.
4212 - *
4213 - * @param WP_Error $xmlrpc_error The error produced during
4214 - * signature validation.
4215 - */
4216 - public function track_xmlrpc_error( $xmlrpc_error ) {
4217 - $code = is_wp_error( $xmlrpc_error )
4218 - ? $xmlrpc_error->get_error_code()
4219 - : 'should-not-happen';
4220 -
4221 - $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4222 - if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4223 - // No need to update the option if we already have
4224 - // this code stored.
4225 - return;
4226 - }
4227 - $xmlrpc_errors[ $code ] = true;
4228 -
4229 - Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4230 - }
4231 -
4232 - /**
4233 4523 * Initialize the jetpack stats instance only when needed
4234 4524 *
4235 4525 * @return void
4236 4526 */
@@ -4353,9 +4643,9 @@
4353 4643 return $this->build_connect_url( $raw, $redirect, $from, true );
4354 4644 }
4355 4645 }
4356 4646
4357 - $url = static::build_authorize_url( $redirect );
4647 + $url = ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4358 4648 }
4359 4649
4360 4650 if ( $from ) {
4361 4651 $url = add_query_arg( 'from', $from, $url );
@@ -4380,66 +4670,30 @@
4380 4670 * @param null $deprecated Deprecated since Jetpack 10.9.
4381 4671 *
4382 4672 * @todo Update default value for redirect since the called function expects a string.
4383 4673 *
4674 + * @deprecated 13.4
4675 + *
4384 4676 * @return mixed|void
4385 4677 */
4386 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' );
4387 4680
4388 - add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4389 - add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4390 -
4391 - $c8n = self::connection();
4392 - $url = $c8n->get_authorization_url( wp_get_current_user(), $redirect );
4393 -
4394 - remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
4395 - remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
4396 -
4397 - /**
4398 - * Filter the URL used when authorizing a user to a WordPress.com account.
4399 - *
4400 - * @since 8.9.0
4401 - *
4402 - * @param string $url Connection URL.
4403 - */
4404 - return apply_filters( 'jetpack_build_authorize_url', $url );
4681 + return ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4405 4682 }
4406 4683
4407 4684 /**
4408 4685 * Filters the connection URL parameter array.
4409 4686 *
4687 + * @deprecated 13.4
4688 + *
4410 4689 * @param array $args default URL parameters used by the package.
4411 4690 * @return array the modified URL arguments array.
4412 4691 */
4413 4692 public static function filter_connect_request_body( $args ) {
4414 - if (
4415 - Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4416 - && include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
4417 - ) {
4418 - $gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
4419 - $args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
4420 - ? $gp_locale->slug
4421 - : '';
4422 - }
4693 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_request_body' );
4423 4694
4424 - $tracking = new Tracking();
4425 - $tracks_identity = $tracking->tracks_get_identity( $args['state'] );
4426 -
4427 - $args = array_merge(
4428 - $args,
4429 - array(
4430 - '_ui' => $tracks_identity['_ui'],
4431 - '_ut' => $tracks_identity['_ut'],
4432 - )
4433 - );
4434 -
4435 - $calypso_env = ( new Host() )->get_calypso_env();
4436 -
4437 - if ( ! empty( $calypso_env ) ) {
4438 - $args['calypso_env'] = $calypso_env;
4439 - }
4440 -
4441 - return $args;
4695 + return Authorize_Redirect::filter_connect_request_body( $args );
4442 4696 }
4443 4697
4444 4698 /**
4445 4699 * Filters the `jetpack/v4/connection/data` API response of the Connection package in order to
@@ -4479,22 +4733,16 @@
4479 4733 /**
4480 4734 * Filters the redirection URL that is used for connect requests. The redirect
4481 4735 * URL should return the user back to the Jetpack console.
4482 4736 *
4737 + * @deprecated 13.4
4738 + *
4483 4739 * @param String $redirect the default redirect URL used by the package.
4484 4740 * @return String the modified URL.
4485 4741 */
4486 4742 public static function filter_connect_redirect_url( $redirect ) {
4487 - $jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
4488 - $redirect = $redirect
4489 - ? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
4490 - : $jetpack_admin_page;
4491 -
4492 - if ( isset( $_REQUEST['is_multisite'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- not making a site change here.
4493 - $redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
4494 - }
4495 -
4496 - return $redirect;
4743 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_redirect_url' );
4744 + return Authorize_Redirect::filter_connect_redirect_url( $redirect );
4497 4745 }
4498 4746
4499 4747 /**
4500 4748 * This action fires at the beginning of the Manager::authorize method.
@@ -4558,11 +4806,8 @@
4558 4806 *
4559 4807 * @param array $data The request data.
4560 4808 */
4561 4809 public static function authorize_ending_authorized( $data ) {
4562 - // If this site has been through the Jetpack Onboarding flow, delete the onboarding token.
4563 - self::invalidate_onboarding_token();
4564 -
4565 4810 // If redirect_uri is SSO, ensure SSO module is enabled.
4566 4811 parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4567 4812
4568 4813 /** This filter is documented in class.jetpack-cli.php */
@@ -4702,10 +4947,12 @@
4702 4947 $result = self::permit_ssl( true );
4703 4948 wp_send_json(
4704 4949 array(
4705 4950 'enabled' => $result,
4706 - 'message' => get_transient( 'jetpack_https_test_message' ),
4707 - )
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
4708 4955 );
4709 4956 }
4710 4957
4711 4958 /* Client API */
@@ -4712,8 +4959,10 @@
4712 4959
4713 4960 /**
4714 4961 * Verify the onboarding token.
4715 4962 *
4963 + * @deprecated since 13.9
4964 + *
4716 4965 * @param array $token_data Token data.
4717 4966 * @param string $token Token value.
4718 4967 * @param string $request_data JSON-encoded request data.
4719 4968 *
@@ -4719,8 +4968,9 @@
4719 4968 *
4720 4969 * @return mixed
4721 4970 */
4722 4971 public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4972 + _deprecated_function( __METHOD__, '13.9' );
4723 4973 // Default to a blog token.
4724 4974 $token_type = 'blog';
4725 4975
4726 4976 // Let's see if this is onboarding. In such case, use user token type and the provided user id.
@@ -4748,9 +4998,9 @@
4748 4998 $jp_user = get_user_by( 'email', $jpo_user );
4749 4999 if ( is_a( $jp_user, 'WP_User' ) ) {
4750 5000 wp_set_current_user( $jp_user->ID );
4751 5001 $user_can = is_multisite()
4752 - ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' )
5002 + ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4753 5003 : current_user_can( 'manage_options' );
4754 5004 if ( $user_can ) {
4755 5005 $token_type = 'user';
4756 5006 $token->external_user_id = $jp_user->ID;
@@ -4767,11 +5017,13 @@
4767 5017
4768 5018 /**
4769 5019 * Create a random secret for validating onboarding payload
4770 5020 *
5021 + * @deprecated since 13.9
4771 5022 * @return string Secret token
4772 5023 */
4773 5024 public static function create_onboarding_token() {
5025 + _deprecated_function( __METHOD__, '13.9' );
4774 5026 $token = Jetpack_Options::get_option( 'onboarding' );
4775 5027 if ( false === ( $token ) ) {
4776 5028 $token = wp_generate_password( 32, false );
4777 5029 Jetpack_Options::update_option( 'onboarding', $token );
@@ -4782,11 +5034,13 @@
4782 5034
4783 5035 /**
4784 5036 * Remove the onboarding token
4785 5037 *
5038 + * @deprecated since 13.9
4786 5039 * @return bool True on success, false on failure
4787 5040 */
4788 5041 public static function invalidate_onboarding_token() {
5042 + _deprecated_function( __METHOD__, '13.9' );
4789 5043 return Jetpack_Options::delete_option( 'onboarding' );
4790 5044 }
4791 5045
4792 5046 /**
@@ -4791,8 +5045,10 @@
4791 5045
4792 5046 /**
4793 5047 * Validate an onboarding token for a specific action
4794 5048 *
5049 + * @deprecated since 13.9
5050 + *
4795 5051 * @param string $token Onboarding token.
4796 5052 * @param string $action Action name.
4797 5053 *
4798 5054 * @return boolean True if token/action pair is accepted, false if not
@@ -4797,8 +5053,9 @@
4797 5053 *
4798 5054 * @return boolean True if token/action pair is accepted, false if not
4799 5055 */
4800 5056 public static function validate_onboarding_token_action( $token, $action ) {
5057 + _deprecated_function( __METHOD__, '13.9' );
4801 5058 // Compare tokens, bail if tokens do not match.
4802 5059 if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4803 5060 return false;
4804 5061 }
@@ -4824,39 +5081,41 @@
4824 5081 * @return boolean
4825 5082 * @since 2.3.3
4826 5083 */
4827 5084 public static function permit_ssl( $force_recheck = false ) {
4828 - // Do some fancy tests to see if ssl is being supported.
4829 - if ( ! $force_recheck ) {
4830 - $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;
4831 5088 }
4832 5089
4833 - if ( $force_recheck || false === $ssl ) {
4834 - $message = '';
4835 - if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4836 - $ssl = 0;
4837 - } else {
4838 - $ssl = 1;
5090 + return Heartbeat::permit_ssl( $force_recheck );
5091 + }
4839 5092
4840 - if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4841 - $ssl = 0;
4842 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4843 - } else {
4844 - $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4845 - if ( is_wp_error( $response ) ) {
4846 - $ssl = 0;
4847 - $message = __( 'WordPress reports no SSL support', 'jetpack' );
4848 - } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4849 - $ssl = 0;
4850 - $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4851 - }
4852 - }
4853 - }
4854 - set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4855 - 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 '';
4856 5106 }
4857 5107
4858 - 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 + }
4859 5118 }
4860 5119
4861 5120 /**
4862 5121 * Displays an admin_notice, alerting the user that outbound SSL isn't working.
@@ -4875,9 +5134,9 @@
4875 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>
4876 5135 <p>
4877 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' ); ?>
4878 5137 <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4879 - <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>
4880 5139 </p>
4881 5140 <p>
4882 5141 <?php
4883 5142 printf(
@@ -4896,18 +5155,18 @@
4896 5155 <script type="text/javascript">
4897 5156 jQuery( document ).ready( function( $ ) {
4898 5157 $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4899 5158 var $this = $( this );
4900 - $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 ); ?> );
4901 5160 $( '#jetpack-recheck-ssl-output' ).html( '' );
4902 5161 e.preventDefault();
4903 - 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 ); ?> };
4904 5163 $.post( ajaxurl, data )
4905 5164 .done( function( response ) {
4906 5165 if ( response.enabled ) {
4907 5166 $( '#jetpack-ssl-warning' ).hide();
4908 5167 } else {
4909 - 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 ); ?> );
4910 5169 $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4911 5170 }
4912 5171 }.bind( $this ) );
4913 5172 } );
@@ -4977,9 +5236,9 @@
4977 5236
4978 5237 /**
4979 5238 * If the db version is showing something other that what we've got now, bump it to current.
4980 5239 *
4981 - * @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.
4982 5241 */
4983 5242 public static function maybe_set_version_option() {
4984 5243 list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
4985 5244 if ( JETPACK__VERSION !== $version ) {
@@ -5078,13 +5337,14 @@
5078 5337 /**
5079 5338 * Handles the login action for Authorizing the JSON API
5080 5339 */
5081 5340 public function login_form_json_api_authorization() {
5082 - $this->verify_json_api_authorization_request();
5341 + $authorize_json_api = new Authorize_Json_Api();
5342 + $authorize_json_api->verify_json_api_authorization_request();
5083 5343
5084 - 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 );
5085 5345
5086 - 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' ) );
5087 5347 add_action( 'login_form', array( $this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
5088 5348 add_filter( 'site_url', array( $this, 'post_login_form_to_signed_url' ), 10, 3 );
5089 5349 }
5090 5350
@@ -5122,16 +5382,17 @@
5122 5382
5123 5383 /**
5124 5384 * If someone logs in to approve API access, store the Access Code in usermeta.
5125 5385 *
5386 + * @deprecated 13.4
5387 + *
5126 5388 * @param string $user_login Unused.
5127 5389 * @param WP_User $user User logged in.
5128 5390 */
5129 5391 public function store_json_api_authorization_token( $user_login, $user ) {
5130 - add_filter( 'login_redirect', array( $this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
5131 - add_filter( 'allowed_redirect_hosts', array( $this, 'allow_wpcom_public_api_domain' ) );
5132 - $token = wp_generate_password( 32, false );
5133 - 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 );
5134 5395 }
5135 5396
5136 5397 /**
5137 5398 * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
@@ -5137,23 +5398,29 @@
5137 5398 * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
5138 5399 *
5139 5400 * To be used with a filter of allowed domains for a redirect.
5140 5401 *
5402 + * @deprecated 13.4
5403 + *
5141 5404 * @param array $domains Allowed WP.com Environments.
5142 5405 */
5143 5406 public function allow_wpcom_public_api_domain( $domains ) {
5144 - $domains[] = 'public-api.wordpress.com';
5145 - 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 );
5146 5410 }
5147 5411
5148 5412 /**
5149 5413 * Check if the redirect is encoded.
5150 5414 *
5415 + * @deprecated 13.4
5416 + *
5151 5417 * @param string $redirect_url Redirect URL.
5152 5418 *
5153 5419 * @return bool If redirect has been encoded.
5154 5420 */
5155 5421 public static function is_redirect_encoded( $redirect_url ) {
5422 + _deprecated_function( __METHOD__, 'jetpack-13.4' );
5156 5423 return preg_match( '/https?%3A%2F%2F/i', $redirect_url ) > 0;
5157 5424 }
5158 5425
5159 5426 /**
@@ -5171,8 +5438,10 @@
5171 5438
5172 5439 /**
5173 5440 * Add the Access Code details to the public-api.wordpress.com redirect.
5174 5441 *
5442 + * @deprecated 13.4
5443 + *
5175 5444 * @param string $redirect_to URL.
5176 5445 * @param string $original_redirect_to URL.
5177 5446 * @param WP_User $user WP_User for the redirect.
5178 5447 *
@@ -5178,23 +5447,18 @@
5178 5447 *
5179 5448 * @return string
5180 5449 */
5181 5450 public function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
5182 - return add_query_arg(
5183 - urlencode_deep(
5184 - array(
5185 - 'jetpack-code' => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
5186 - 'jetpack-user-id' => (int) $user->ID,
5187 - 'jetpack-state' => $this->json_api_authorization_request['state'],
5188 - )
5189 - ),
5190 - $redirect_to
5191 - );
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 );
5192 5454 }
5193 5455
5194 5456 /**
5195 5457 * Verifies the request by checking the signature
5196 5458 *
5459 + * @deprecated 13.4
5460 + *
5197 5461 * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
5198 5462 * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
5199 5463 *
5200 5464 * @param null|array $environment Value to override $_REQUEST.
@@ -5199,194 +5463,24 @@
5199 5463 *
5200 5464 * @param null|array $environment Value to override $_REQUEST.
5201 5465 */
5202 5466 public function verify_json_api_authorization_request( $environment = null ) {
5203 - $environment = $environment === null
5204 - ? $_REQUEST // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verification handled later in function.
5205 - : $environment;
5467 + _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::verify_json_api_authorization_request' );
5206 5468
5207 - list( $env_token,, $env_user_id ) = explode( ':', $environment['token'] );
5208 - $token = ( new Tokens() )->get_access_token( $env_user_id, $env_token );
5209 - if ( ! $token || empty( $token->secret ) ) {
5210 - wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack' ) );
5211 - }
5212 -
5213 - $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' );
5214 -
5215 - // Host has encoded the request URL, probably as a result of a bad http => https redirect.
5216 - 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.
5217 - /**
5218 - * Jetpack authorisation request Error.
5219 - *
5220 - * @since 7.5.0
5221 - */
5222 - do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
5223 - $die_error = sprintf(
5224 - /* translators: %s is a URL */
5225 - __( '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' ),
5226 - esc_url( Redirect::get_url( 'jetpack-support-double-encoding' ) )
5227 - );
5228 - }
5229 -
5230 - $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
5231 -
5232 - if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
5233 - $signature = $jetpack_signature->sign_request(
5234 - $environment['token'],
5235 - $environment['timestamp'],
5236 - $environment['nonce'],
5237 - '',
5238 - 'GET',
5239 - $environment['jetpack_json_api_original_query'],
5240 - null,
5241 - true
5242 - );
5243 - } else {
5244 - $signature = $jetpack_signature->sign_current_request(
5245 - array(
5246 - 'body' => null,
5247 - 'method' => 'GET',
5248 - )
5249 - );
5250 - }
5251 -
5252 - if ( ! $signature ) {
5253 - wp_die(
5254 - wp_kses(
5255 - $die_error,
5256 - array(
5257 - 'a' => array(
5258 - 'href' => array(),
5259 - ),
5260 - )
5261 - )
5262 - );
5263 - } elseif ( is_wp_error( $signature ) ) {
5264 - wp_die(
5265 - wp_kses(
5266 - $die_error,
5267 - array(
5268 - 'a' => array(
5269 - 'href' => array(),
5270 - ),
5271 - )
5272 - )
5273 - );
5274 - } elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
5275 - if ( is_ssl() ) {
5276 - // 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.
5277 - $signature = $jetpack_signature->sign_current_request(
5278 - array(
5279 - 'scheme' => 'http',
5280 - 'body' => null,
5281 - 'method' => 'GET',
5282 - )
5283 - );
5284 - if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
5285 - wp_die(
5286 - wp_kses(
5287 - $die_error,
5288 - array(
5289 - 'a' => array(
5290 - 'href' => array(),
5291 - ),
5292 - )
5293 - )
5294 - );
5295 - }
5296 - } else {
5297 - wp_die(
5298 - wp_kses(
5299 - $die_error,
5300 - array(
5301 - 'a' => array(
5302 - 'href' => array(),
5303 - ),
5304 - )
5305 - )
5306 - );
5307 - }
5308 - }
5309 -
5310 - $timestamp = (int) $environment['timestamp'];
5311 - $nonce = stripslashes( (string) $environment['nonce'] );
5312 -
5313 - if ( ! $this->connection_manager ) {
5314 - $this->connection_manager = new Connection_Manager();
5315 - }
5316 -
5317 - if ( ! ( new Nonce_Handler() )->add( $timestamp, $nonce ) ) {
5318 - // De-nonce the nonce, at least for 5 minutes.
5319 - // 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).
5320 - $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
5321 - if ( $old_nonce_time < time() - 300 ) {
5322 - wp_die( esc_html__( 'The authorization process expired. Please go back and try again.', 'jetpack' ) );
5323 - }
5324 - }
5325 -
5326 - $data = json_decode( base64_decode( stripslashes( $environment['data'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
5327 - $data_filters = array(
5328 - 'state' => 'opaque',
5329 - 'client_id' => 'int',
5330 - 'client_title' => 'string',
5331 - 'client_image' => 'url',
5332 - );
5333 -
5334 - foreach ( $data_filters as $key => $sanitation ) {
5335 - if ( ! isset( $data->$key ) ) {
5336 - wp_die(
5337 - wp_kses(
5338 - $die_error,
5339 - array(
5340 - 'a' => array(
5341 - 'href' => array(),
5342 - ),
5343 - )
5344 - )
5345 - );
5346 - }
5347 -
5348 - switch ( $sanitation ) {
5349 - case 'int':
5350 - $this->json_api_authorization_request[ $key ] = (int) $data->$key;
5351 - break;
5352 - case 'opaque':
5353 - $this->json_api_authorization_request[ $key ] = (string) $data->$key;
5354 - break;
5355 - case 'string':
5356 - $this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
5357 - break;
5358 - case 'url':
5359 - $this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
5360 - break;
5361 - }
5362 - }
5363 -
5364 - if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
5365 - wp_die(
5366 - wp_kses(
5367 - $die_error,
5368 - array(
5369 - 'a' => array(
5370 - 'href' => array(),
5371 - ),
5372 - )
5373 - )
5374 - );
5375 - }
5469 + return ( new Authorize_Json_Api() )->verify_json_api_authorization_request( $environment );
5376 5470 }
5377 5471
5378 5472 /**
5379 5473 * HTML for the JSON API authorization notice.
5380 5474 *
5475 + * @deprecated 13.4
5476 + *
5381 5477 * @return string
5382 5478 */
5383 5479 public function login_message_json_api_authorization() {
5384 - return '<p class="message">' . sprintf(
5385 - /* translators: Name/image of the client requesting authorization */
5386 - esc_html__( '%s wants to access your site’s data. Log in to authorize that access.', 'jetpack' ),
5387 - '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
5388 - ) . '<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();
5389 5483 }
5390 5484
5391 5485 /**
5392 5486 * Get $content_width, but with a <s>twist</s> filter.
@@ -5430,15 +5524,20 @@
5430 5524
5431 5525 /**
5432 5526 * Checks if the site is currently in an identity crisis.
5433 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 + *
5434 5534 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5435 5535 */
5436 5536 public static function check_identity_crisis() {
5437 - if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5438 - return false;
5439 - }
5440 - return Jetpack_Options::get_option( 'sync_error_idc' );
5537 + _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\\Jetpack\\Identity_Crisis::check_identity_crisis' );
5538 +
5539 + return Identity_Crisis::check_identity_crisis();
5441 5540 }
5442 5541
5443 5542 /**
5444 5543 * Normalizes a url by doing three things:
@@ -5526,9 +5625,9 @@
5526 5625 *
5527 5626 * @return mixed
5528 5627 */
5529 5628 public static function set_suffix_on_min( $src, $handle ) {
5530 - if ( ! str_contains( $src, '.min.css' ) ) {
5629 + if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5531 5630 return $src;
5532 5631 }
5533 5632
5534 5633 if ( ! empty( self::$min_assets ) ) {
@@ -5564,8 +5663,10 @@
5564 5663 *
5565 5664 * Data passed in with the $data parameter will be available in the
5566 5665 * template file as $data['value']
5567 5666 *
5667 + * @html-template-var array $data
5668 + *
5568 5669 * @param string $template - Template file to load.
5569 5670 * @param array $data - Any data to pass along to the template.
5570 5671 * @return boolean - If template file was found.
5571 5672 **/
@@ -5583,8 +5684,19 @@
5583 5684 return false;
5584 5685 }
5585 5686
5586 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 + /**
5587 5699 * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5588 5700 */
5589 5701 public function deprecated_hooks() {
5590 5702 $filter_deprecated_list = array(
@@ -5804,8 +5916,14 @@
5804 5916 'jetpack_pre_connection_prompt_helpers' => array(
5805 5917 'replacement' => null,
5806 5918 'version' => 'jetpack-13.2.0',
5807 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.
5808 5926 );
5809 5927
5810 5928 foreach ( $filter_deprecated_list as $tag => $args ) {
5811 5929 if ( has_filter( $tag ) ) {
@@ -5936,125 +6054,8 @@
5936 6054 return $css;
5937 6055 }
5938 6056
5939 6057 /**
5940 - * This methods removes all of the registered css files on the front end
5941 - * from Jetpack in favor of using a single file. In effect "imploding"
5942 - * all the files into one file.
5943 - *
5944 - * Pros:
5945 - * - Uses only ONE css asset connection instead of 15
5946 - * - Saves a minimum of 56k
5947 - * - Reduces server load
5948 - * - Reduces time to first painted byte
5949 - *
5950 - * Cons:
5951 - * - Loads css for ALL modules. However all selectors are prefixed so it
5952 - * should not cause any issues with themes.
5953 - * - Plugins/themes dequeuing styles no longer do anything. See
5954 - * jetpack_implode_frontend_css filter for a workaround
5955 - *
5956 - * For some situations developers may wish to disable css imploding and
5957 - * instead operate in legacy mode where each file loads seperately and
5958 - * can be edited individually or dequeued. This can be accomplished with
5959 - * the following line:
5960 - *
5961 - * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
5962 - *
5963 - * @param bool $travis_test Is this a test run.
5964 - *
5965 - * @since 3.2
5966 - */
5967 - public function implode_frontend_css( $travis_test = false ) {
5968 - $do_implode = true;
5969 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
5970 - $do_implode = false;
5971 - }
5972 -
5973 - // Do not implode CSS when the page loads via the AMP plugin.
5974 - if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
5975 - $do_implode = false;
5976 - }
5977 -
5978 - /*
5979 - * Only proceed if at least 2 modules with concatenated CSS are active.
5980 - * There is no point in serving a big concatenated CSS file
5981 - * if there are no features (or only one) that actually need some CSS loaded.
5982 - */
5983 - $active_modules = self::get_active_modules();
5984 - $modules_with_concatenated_css = $this->modules_with_concatenated_css;
5985 - $active_module_with_css_count = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
5986 - if ( $active_module_with_css_count < 2 ) {
5987 - $do_implode = false;
5988 - }
5989 -
5990 - /**
5991 - * Allow CSS to be concatenated into a single jetpack.css file.
5992 - *
5993 - * @since 3.2.0
5994 - *
5995 - * @param bool $do_implode Should CSS be concatenated? Default to true.
5996 - */
5997 - $do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
5998 -
5999 - // Do not use the imploded file when default behavior was altered through the filter.
6000 - if ( ! $do_implode ) {
6001 - return;
6002 - }
6003 -
6004 - // We do not want to use the imploded file in dev mode, or if not connected.
6005 - if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
6006 - if ( ! $travis_test ) {
6007 - return;
6008 - }
6009 - }
6010 -
6011 - // Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
6012 - if ( get_option( 'sharedaddy_disable_resources' ) ) {
6013 - return;
6014 - }
6015 -
6016 - /*
6017 - * Now we assume Jetpack is connected and able to serve the single
6018 - * file.
6019 - *
6020 - * In the future there will be a check here to serve the file locally
6021 - * or potentially from the Jetpack CDN
6022 - *
6023 - * For now:
6024 - * - Enqueue a single imploded css file
6025 - * - Zero out the style_loader_tag for the bundled ones
6026 - * - Be happy, drink scotch
6027 - */
6028 -
6029 - add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
6030 -
6031 - $version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
6032 -
6033 - wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
6034 - wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
6035 - }
6036 -
6037 - /**
6038 - * Removes styles that are part of concatenated group.
6039 - *
6040 - * @param string $tag Style tag.
6041 - * @param string $handle Style handle.
6042 - *
6043 - * @return string
6044 - */
6045 - public function concat_remove_style_loader_tag( $tag, $handle ) {
6046 - if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
6047 - $tag = '';
6048 - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
6049 - $tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
6050 - }
6051 - }
6052 -
6053 - return $tag;
6054 - }
6055 -
6056 - /**
6057 6058 * Check the heartbeat data
6058 6059 *
6059 6060 * Organizes the heartbeat data by severity. For example, if the site
6060 6061 * is in an ID crisis, it will be in the $filtered_data['bad'] array.
@@ -6066,9 +6067,23 @@
6066 6067 *
6067 6068 * $return array $filtered_data
6068 6069 */
6069 6070 public static function jetpack_check_heartbeat_data() {
6070 - $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 + );
6071 6086
6072 6087 $good = array();
6073 6088 $caution = array();
6074 6089 $bad = array();
@@ -6327,13 +6342,20 @@
6327 6342 }
6328 6343 }
6329 6344
6330 6345 /**
6331 - * Returns a boolean for whether backups UI should be displayed or not.
6346 + * Whether UI for backups should be displayed.
6332 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 + *
6333 6351 * @return bool Should backups UI be displayed?
6334 6352 */
6335 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 +
6336 6358 /**
6337 6359 * Whether UI for backups should be displayed.
6338 6360 *
6339 6361 * @since 6.5.0
@@ -6343,8 +6365,24 @@
6343 6365 return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
6344 6366 }
6345 6367
6346 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 + /**
6347 6385 * Clean leftoveruser meta.
6348 6386 *
6349 6387 * Delete Jetpack-related user meta when it is no longer needed.
6350 6388 *
@@ -6431,8 +6469,25 @@
6431 6469 _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
6432 6470 ),
6433 6471 );
6434 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 +
6435 6490 $products['scan'] = array(
6436 6491 'title' => __( 'Jetpack Scan', 'jetpack' ),
6437 6492 'slug' => 'jetpack_scan',
6438 6493 'description' => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
@@ -6579,8 +6634,56 @@
6579 6634 . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6580 6635 . '</a>';
6581 6636
6582 6637 return $plugin_meta;
6638 + }
6639 +
6640 + /**
6641 + * Lazy instantiation of the Plugin_Tracking object.
6642 + *
6643 + * @since 13.9
6644 + *
6645 + * @return void
6646 + */
6647 + public function initialize_tracking() {
6648 + if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6649 + // Only need to run once.
6650 + return;
6651 + }
6652 +
6653 + if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6654 + ( new Plugin_Tracking() )->init();
6655 + }
6656 + }
6657 +
6658 + /**
6659 + * Run the "initialize tracking" hook.
6660 + *
6661 + * @since 13.9
6662 + */
6663 + public function run_initialize_tracking_action() {
6664 + /**
6665 + * Fires when the tracking needs to be initialized.
6666 + * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6667 + *
6668 + * @since 13.9
6669 + */
6670 + do_action( 'jetpack_initialize_tracking' );
6671 + }
6672 +
6673 + /**
6674 + * Initialize REST jsonAPI if needed.
6675 + *
6676 + * @return void
6677 + */
6678 + public function maybe_initialize_rest_jsonapi() {
6679 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6680 + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6681 + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6682 +
6683 + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6684 + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6685 + }
6583 6686 }
6584 6687
6585 6688 /**
6586 6689 * Run plugin post-activation actions if we need to.