Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
32.85% covered (danger)
32.85%
748 / 2277
26.32% covered (danger)
26.32%
45 / 171
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack
32.89% covered (danger)
32.89%
748 / 2274
26.32% covered (danger)
26.32%
45 / 171
174390.35
0.00% covered (danger)
0.00%
0 / 1
 init
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
2.86
 plugin_upgrade
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
420
 upgrade_on_load
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 update_active_modules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete_active_modules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add_configure_hook
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 __construct
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
12
 configure
13.33% covered (danger)
13.33%
8 / 60
0.00% covered (danger)
0.00%
0 / 1
75.10
 late_initialization
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 add_wpcom_to_allowed_redirect_hosts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 allow_wpcom_domain
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 point_edit_post_links_to_calypso
n/a
0 / 0
n/a
0 / 0
3
 point_edit_comment_links_to_calypso
n/a
0 / 0
n/a
0 / 0
1
 filter_sync_callable_whitelist
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 filter_sync_multisite_callable_whitelist
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 push_stats
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 jetpack_custom_caps
76.19% covered (warning)
76.19%
16 / 21
0.00% covered (danger)
0.00%
0 / 1
7.66
 register_assets
17.95% covered (danger)
17.95%
7 / 39
0.00% covered (danger)
0.00%
0 / 1
18.81
 guess_locale_from_lang
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
156
 get_locale
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 jetpack_main_network_site_option
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 network_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 network_allow_new_registrations
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 network_add_new_users
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 network_site_upload_space
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 network_upload_file_types
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 network_max_upload_file_size
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 network_enable_administration_menus
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 maybe_clear_other_linked_admins_transient
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
 get_other_linked_admins
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
7.01
 is_main_network_option
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_multisite
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_multi_network
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 is_single_user_site
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 file_system_write_access
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 get_updates
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
5.93
 get_update_details
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 is_active
n/a
0 / 0
n/a
0 / 0
1
 is_connection_ready
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 development_mode_trigger_text
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 show_development_mode_notice
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 is_development_version
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 get_connected_user_email
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 get_master_user_email
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 current_user_ip
n/a
0 / 0
n/a
0 / 0
1
 load_modules
55.56% covered (warning)
55.56%
25 / 45
0.00% covered (danger)
0.00%
0 / 1
59.72
 check_rest_api_compat
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_active_plugins
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 get_parsed_plugin_data
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 get_parsed_theme_data
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 is_plugin_active
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 check_open_graph
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 check_twitter_tags
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 get_option_names
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_option
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 glob_php
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 activate_new_modules
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
132
 get_available_modules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_default_modules
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
 handle_deprecated_modules
66.67% covered (warning)
66.67%
12 / 18
0.00% covered (danger)
0.00%
0 / 1
10.37
 filter_default_modules
53.85% covered (warning)
53.85%
7 / 13
0.00% covered (danger)
0.00%
0 / 1
14.29
 get_module_slug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_module_path
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_module
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_file_data
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 translate_module_tag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_translated_modules
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 get_active_modules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_module_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_module
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 catch_errors
n/a
0 / 0
n/a
0 / 0
1
 catch_errors_on_shutdown
n/a
0 / 0
n/a
0 / 0
1
 alias_directories
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 activate_default_modules
40.96% covered (danger)
40.96%
34 / 83
0.00% covered (danger)
0.00%
0 / 1
318.68
 activate_module
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deactivate_module
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enable_module_configurable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 module_configuration_url
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 bail_on_activation
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 plugin_activation
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
7.18
 get_activation_source
93.55% covered (success)
93.55%
29 / 31
0.00% covered (danger)
0.00%
0 / 1
16.07
 do_version_bump
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 set_update_modal_display
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enqueue_block_style
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 plugin_initialize
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 handle_default_module_activation
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
5.04
 plugin_deactivation
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 on_idc_disconnect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 disconnect
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 jetpack_site_disconnected
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 disconnect_user
n/a
0 / 0
n/a
0 / 0
1
 registration_check_domains
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 log
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 get_log
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
132
 log_settings_change
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_stat_data
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 get_additional_stat_data
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 get_site_user_count
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 admin_init
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
272
 admin_body_class
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 add_jetpack_pagestyles
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 throw_error_on_activate_plugin
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 plugins_page_init_jetpack_state
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 intercept_plugin_error_scrape_init
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 intercept_plugin_error_scrape
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 add_remote_request_handlers
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
 remote_request_handlers
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
72
 upload_handler
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 1
992
 plugin_action_links
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 deactivate_dialog
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 jetpack_plugin_portal_containers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 login_url
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 login_init
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
2.00
 admin_page_load
0.00% covered (danger)
0.00%
0 / 144
0.00% covered (danger)
0.00%
0 / 1
2162
 admin_notices
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 1
56
 track_xmlrpc_error
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
4.25
 initialize_stats
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 stat
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 do_stats
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 do_server_side_stat
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 build_stats_url
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 build_connect_url
68.97% covered (warning)
68.97%
20 / 29
0.00% covered (danger)
0.00%
0 / 1
16.30
 build_authorize_url
n/a
0 / 0
n/a
0 / 0
1
 filter_connect_request_body
n/a
0 / 0
n/a
0 / 0
1
 filter_jetpack_current_user_connection_data
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
6.01
 filter_connect_redirect_url
n/a
0 / 0
n/a
0 / 0
1
 authorize_starting
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 handle_unique_registrations_stats
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 authorize_ending_linked
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 authorize_ending_authorized
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 activate_default_modules_on_site_register
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 reconnection_completed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 apply_activation_source_to_args
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 build_reconnect_url
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 admin_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 nonce_url_no_esc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 dismiss_jetpack_notice
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 sort_modules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ajax_recheck_ssl
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 verify_onboarding_token
n/a
0 / 0
n/a
0 / 0
15
 create_onboarding_token
n/a
0 / 0
n/a
0 / 0
2
 invalidate_onboarding_token
n/a
0 / 0
n/a
0 / 0
1
 validate_onboarding_token_action
n/a
0 / 0
n/a
0 / 0
3
 permit_ssl
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
 alert_auto_ssl_fail
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
6
 connection
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 get_secrets
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 filter_token_request_body
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_set_version_option
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 state
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 restate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 should_set_cookie
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 check_privacy
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 login_form_json_api_authorization
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 post_login_form_to_signed_url
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 preserve_action_in_login_form_for_json_api_authorization
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 store_json_api_authorization_token
n/a
0 / 0
n/a
0 / 0
1
 allow_wpcom_public_api_domain
n/a
0 / 0
n/a
0 / 0
1
 is_redirect_encoded
n/a
0 / 0
n/a
0 / 0
1
 allow_wpcom_environments
n/a
0 / 0
n/a
0 / 0
1
 add_token_to_login_redirect_json_api_authorization
n/a
0 / 0
n/a
0 / 0
1
 verify_json_api_authorization_request
n/a
0 / 0
n/a
0 / 0
1
 login_message_json_api_authorization
n/a
0 / 0
n/a
0 / 0
1
 get_content_width
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 get_cloud_site_options
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 check_identity_crisis
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
 normalize_url_protocol_agnostic
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 maybe_min_asset
28.57% covered (danger)
28.57%
6 / 21
0.00% covered (danger)
0.00%
0 / 1
31.32
 set_suffix_on_min
50.00% covered (danger)
50.00%
4 / 8
0.00% covered (danger)
0.00%
0 / 1
10.50
 maybe_inline_style
n/a
0 / 0
n/a
0 / 0
1
 load_view
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 deprecated_hooks
100.00% covered (success)
100.00%
284 / 284
100.00% covered (success)
100.00%
1 / 1
5
 absolutize_css_urls
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
10
 jetpack_check_heartbeat_data
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
110
 get_jetpack_options_for_reset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_user_option_meta_box_order_dashboard
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 is_akismet_active
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
90
 get_file_url_for_environment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_rewind_enabled
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
110
 get_calypso_env
n/a
0 / 0
n/a
0 / 0
1
 get_calypso_host
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 handle_post_authorization_actions
58.33% covered (warning)
58.33%
7 / 12
0.00% covered (danger)
0.00%
0 / 1
5.16
 show_backups_ui
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
 show_scan_ui
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 user_meta_cleanup
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 is_active_and_not_offline_mode
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 get_products_for_purchase
0.00% covered (danger)
0.00%
0 / 134
0.00% covered (danger)
0.00%
0 / 1
6
 get_partner_coupon_product_descriptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 current_user_can_purchase
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 add_5_star_review_link
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 initialize_tracking
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
6.00
 run_initialize_tracking_action
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 maybe_initialize_rest_jsonapi
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 plugin_post_activation
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * The mega-class.
4 *
5 * This contains too much, so please think twice before adding more.
6 *
7 * @package automattic/jetpack
8 */
9
10use Automattic\Jetpack\Activity_Log\Jetpack_Activity_Log as Activity_Log_Init;
11use Automattic\Jetpack\Assets;
12use Automattic\Jetpack\Boost_Speed_Score\Speed_Score;
13use Automattic\Jetpack\Config;
14use Automattic\Jetpack\Connection\Authorize_Json_Api;
15use Automattic\Jetpack\Connection\Client;
16use Automattic\Jetpack\Connection\Manager as Connection_Manager;
17use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
18use Automattic\Jetpack\Connection\Secrets;
19use Automattic\Jetpack\Connection\Tokens;
20use Automattic\Jetpack\Connection\Webhooks\Authorize_Redirect;
21use Automattic\Jetpack\Constants;
22use Automattic\Jetpack\CookieState;
23use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
24use Automattic\Jetpack\Device_Detection\User_Agent_Info;
25use Automattic\Jetpack\Errors;
26use Automattic\Jetpack\Files;
27use Automattic\Jetpack\Identity_Crisis;
28use Automattic\Jetpack\Licensing;
29use Automattic\Jetpack\Modules;
30use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
31use Automattic\Jetpack\Newsletter\Reader_Link;
32use Automattic\Jetpack\Paths;
33use Automattic\Jetpack\Plugin\Deprecate;
34use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
35use Automattic\Jetpack\Podcast\Podcast as Podcast_Init;
36use Automattic\Jetpack\Redirect;
37use Automattic\Jetpack\Scan_Page\Jetpack_Scan as Scan_Page_Init;
38use Automattic\Jetpack\Status;
39use Automattic\Jetpack\Status\Host;
40use Automattic\Jetpack\Status\Visitor;
41use Automattic\Jetpack\Sync\Actions as Sync_Actions;
42use Automattic\Jetpack\Sync\Health;
43use Automattic\Jetpack\Sync\Sender;
44use Automattic\Jetpack\Terms_Of_Service;
45use Automattic\Jetpack\Tracking;
46use Automattic\Woocommerce_Analytics;
47
48if ( ! defined( 'ABSPATH' ) ) {
49    exit( 0 );
50}
51
52/*
53Options:
54jetpack_options (array)
55    An array of options.
56    @see Jetpack_Options::get_option_names()
57
58jetpack_register (string)
59    Temporary verification secrets.
60
61jetpack_activated (int)
62    1: the plugin was activated normally
63    2: the plugin was activated on this site because of a network-wide activation
64    3: the plugin was auto-installed
65    4: the plugin was manually disconnected (but is still installed)
66
67jetpack_active_modules (array)
68    Array of active module slugs.
69
70jetpack_do_activate (bool)
71    Flag for "activating" the plugin on sites where the activation hook never fired (auto-installs)
72*/
73
74require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media.php';
75
76/**
77 * The Jetpack class.
78 */
79class Jetpack {
80    /**
81     * XMLRPC server instance.
82     *
83     * @var null|Jetpack_XMLRPC_Server XMLRPC server used by Jetpack.
84     */
85    public $xmlrpc_server = null;
86
87    /**
88     * Contains all assets that have had their URL rewritten to minified versions.
89     *
90     * @var array
91     */
92    public static $min_assets = array();
93
94    /**
95     * Plugins to deactivate.
96     *
97     * @var array Plugins to deactivate by module.
98     */
99    public $plugins_to_deactivate = array(
100        'contact-form'        => array(
101            array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
102            array( 'mullet/mullet-contact-form.php', 'Mullet Contact Form' ),
103        ),
104        'gravatar-hovercards' => array(
105            array( 'jetpack-gravatar-hovercards/gravatar-hovercards.php', 'Jetpack Gravatar Hovercards' ),
106        ),
107        'latex'               => array(
108            array( 'wp-latex/wp-latex.php', 'WP LaTeX' ),
109        ),
110        'sharedaddy'          => array(
111            array( 'sharedaddy/sharedaddy.php', 'Sharedaddy' ),
112            array( 'jetpack-sharing/sharedaddy.php', 'Jetpack Sharing' ),
113        ),
114        'shortlinks'          => array(
115            array( 'stats/stats.php', 'WordPress.com Stats' ),
116        ),
117        'stats'               => array(
118            array( 'stats/stats.php', 'WordPress.com Stats' ),
119        ),
120        'twitter-widget'      => array(
121            array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
122        ),
123        'videopress'          => array(
124            array( 'video/video.php', 'VideoPress' ),
125        ),
126        'widget-visibility'   => array(
127            array( 'jetpack-widget-visibility/widget-visibility.php', 'Jetpack Widget Visibility' ),
128            array( 'widget-visibility-without-jetpack/widget-visibility-without-jetpack.php', 'Widget Visibility Without Jetpack' ),
129        ),
130    );
131
132    /**
133     * Map of roles we care about, and their corresponding minimum capabilities.
134     *
135     * @deprecated 7.6 Use Automattic\Jetpack\Roles::$capability_translations instead.
136     *
137     * @access public
138     * @static
139     *
140     * @var array
141     */
142    public static $capability_translations = array(
143        'administrator' => 'manage_options',
144        'editor'        => 'edit_others_posts',
145        'author'        => 'publish_posts',
146        'contributor'   => 'edit_posts',
147        'subscriber'    => 'read',
148    );
149
150    /**
151     * Map of modules that have conflicts with plugins and should not be auto-activated
152     * if the plugins are active.  Used by filter_default_modules
153     *
154     * Plugin Authors: If you'd like to prevent a single module from auto-activating,
155     * change `module-slug` and add this to your plugin:
156     *
157     * add_filter( 'jetpack_get_default_modules', 'my_jetpack_get_default_modules' );
158     * function my_jetpack_get_default_modules( $modules ) {
159     *     return array_diff( $modules, array( 'module-slug' ) );
160     * }
161     *
162     * @var array
163     */
164    private $conflicting_plugins = array(
165        'comments'           => array(
166            'Intense Debate'                 => 'intensedebate/intensedebate.php',
167            'Disqus'                         => 'disqus-comment-system/disqus.php',
168            'Livefyre'                       => 'livefyre-comments/livefyre.php',
169            'Comments Evolved for WordPress' => 'gplus-comments/comments-evolved.php',
170            'Google+ Comments'               => 'google-plus-comments/google-plus-comments.php',
171            'WP-SpamShield Anti-Spam'        => 'wp-spamshield/wp-spamshield.php',
172        ),
173        'comment-likes'      => array(
174            'Epoch' => 'epoch/plugincore.php',
175        ),
176        'latex'              => array(
177            'LaTeX for WordPress'     => 'latex/latex.php',
178            'Youngwhans Simple Latex' => 'youngwhans-simple-latex/yw-latex.php',
179            'Easy WP LaTeX'           => 'easy-wp-latex-lite/easy-wp-latex-lite.php',
180            'MathJax-LaTeX'           => 'mathjax-latex/mathjax-latex.php',
181            'Enable Latex'            => 'enable-latex/enable-latex.php',
182            'WP QuickLaTeX'           => 'wp-quicklatex/wp-quicklatex.php',
183        ),
184        'protect'            => array(
185            'Limit Login Attempts'              => 'limit-login-attempts/limit-login-attempts.php',
186            'Captcha'                           => 'captcha/captcha.php',
187            'Brute Force Login Protection'      => 'brute-force-login-protection/brute-force-login-protection.php',
188            'Login Security Solution'           => 'login-security-solution/login-security-solution.php',
189            'WPSecureOps Brute Force Protect'   => 'wpsecureops-bruteforce-protect/wpsecureops-bruteforce-protect.php',
190            'BulletProof Security'              => 'bulletproof-security/bulletproof-security.php',
191            'SiteGuard WP Plugin'               => 'siteguard/siteguard.php',
192            'Security-protection'               => 'security-protection/security-protection.php',
193            'Login Security'                    => 'login-security/login-security.php',
194            'Botnet Attack Blocker'             => 'botnet-attack-blocker/botnet-attack-blocker.php',
195            'Wordfence Security'                => 'wordfence/wordfence.php',
196            'All In One WP Security & Firewall' => 'all-in-one-wp-security-and-firewall/wp-security.php',
197            'iThemes Security'                  => 'better-wp-security/better-wp-security.php',
198        ),
199        'related-posts'      => array(
200            'YARPP'                       => 'yet-another-related-posts-plugin/yarpp.php',
201            'WordPress Related Posts'     => 'wordpress-23-related-posts-plugin/wp_related_posts.php',
202            'nrelate Related Content'     => 'nrelate-related-content/nrelate-related.php',
203            'Contextual Related Posts'    => 'contextual-related-posts/contextual-related-posts.php',
204            'Related Posts for WordPress' => 'microkids-related-posts/microkids-related-posts.php',
205            'outbrain'                    => 'outbrain/outbrain.php',
206            'Shareaholic'                 => 'shareaholic/shareaholic.php',
207            'Sexybookmarks'               => 'sexybookmarks/shareaholic.php',
208        ),
209        'sharedaddy'         => array(
210            'AddThis'     => 'addthis/addthis_social_widget.php',
211            'Add To Any'  => 'add-to-any/add-to-any.php',
212            'ShareThis'   => 'share-this/sharethis.php',
213            'Shareaholic' => 'shareaholic/shareaholic.php',
214        ),
215        'seo-tools'          => array(
216            'WordPress SEO by Yoast'         => 'wordpress-seo/wp-seo.php',
217            'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
218            'All in One SEO Pack'            => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
219            'All in One SEO Pack Pro'        => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
220            'The SEO Framework'              => 'autodescription/autodescription.php',
221            'Rank Math'                      => 'seo-by-rank-math/rank-math.php',
222            'Slim SEO'                       => 'slim-seo/slim-seo.php',
223            'SEOKEY'                         => 'seo-key/seo-key.php',
224            'SEOKEY Pro'                     => 'seo-key-pro/seo-key.php',
225            'SEOPress'                       => 'wp-seopress/seopress.php',
226            'SEOPress Pro'                   => 'wp-seopress-pro/seopress-pro.php',
227        ),
228        'verification-tools' => array(
229            'WordPress SEO by Yoast'         => 'wordpress-seo/wp-seo.php',
230            'WordPress SEO Premium by Yoast' => 'wordpress-seo-premium/wp-seo-premium.php',
231            'All in One SEO Pack'            => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
232            'All in One SEO Pack Pro'        => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
233            'The SEO Framework'              => 'autodescription/autodescription.php',
234            'Rank Math'                      => 'seo-by-rank-math/rank-math.php',
235            'Slim SEO'                       => 'slim-seo/slim-seo.php',
236        ),
237        'widget-visibility'  => array(
238            'Widget Logic'    => 'widget-logic/widget_logic.php',
239            'Dynamic Widgets' => 'dynamic-widgets/dynamic-widgets.php',
240        ),
241        'sitemaps'           => array(
242            'Google XML Sitemaps'                  => 'google-sitemap-generator/sitemap.php',
243            'Better WordPress Google XML Sitemaps' => 'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
244            'Google XML Sitemaps for qTranslate'   => 'google-xml-sitemaps-v3-for-qtranslate/sitemap.php',
245            'XML Sitemap & Google News feeds'      => 'xml-sitemap-feed/xml-sitemap.php',
246            'Google Sitemap by BestWebSoft'        => 'google-sitemap-plugin/google-sitemap-plugin.php',
247            'WordPress SEO by Yoast'               => 'wordpress-seo/wp-seo.php',
248            'WordPress SEO Premium by Yoast'       => 'wordpress-seo-premium/wp-seo-premium.php',
249            'All in One SEO Pack'                  => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
250            'All in One SEO Pack Pro'              => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
251            'The SEO Framework'                    => 'autodescription/autodescription.php',
252            'Sitemap'                              => 'sitemap/sitemap.php',
253            'Simple Wp Sitemap'                    => 'simple-wp-sitemap/simple-wp-sitemap.php',
254            'Simple Sitemap'                       => 'simple-sitemap/simple-sitemap.php',
255            'XML Sitemaps'                         => 'xml-sitemaps/xml-sitemaps.php',
256            'MSM Sitemaps'                         => 'msm-sitemap/msm-sitemap.php',
257            'Rank Math'                            => 'seo-by-rank-math/rank-math.php',
258            'Slim SEO'                             => 'slim-seo/slim-seo.php',
259        ),
260    );
261
262    /**
263     * Plugins for which we turn off our Facebook OG Tags implementation.
264     *
265     * Note: the following plugins automatically deactivate Jetpack's Open
266     * Graph tags via filter when their Social Meta modules are active:
267     *
268     * - All in One SEO Pack, All in one SEO Pack Pro
269     * - WordPress SEO by Yoast, WordPress SEO Premium by Yoast
270     *
271     * Plugin authors: If you'd like to prevent Jetpack's Open Graph tag generation in your plugin, you can do so via this filter:
272     * add_filter( 'jetpack_enable_open_graph', '__return_false' );
273     *
274     * @var array Array of plugin slugs.
275     */
276    private $open_graph_conflicting_plugins = array(
277        '2-click-socialmedia-buttons/2-click-socialmedia-buttons.php', // 2 Click Social Media Buttons.
278        'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook.
279        'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags.
280        'complete-open-graph/complete-open-graph.php',           // Complete Open Graph.
281        'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail.
282        'heateor-open-graph-meta-tags/heateor-open-graph-meta-tags.php', // Open Graph Meta Tags by Heateor.
283        'facebook/facebook.php',                                 // Facebook (official plugin).
284        'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one.
285        'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php', // Facebook Featured Image & OG Meta Tags.
286        'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags.
287        'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php', // Facebook Open Graph Meta Tags for WordPress.
288        'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag.
289        'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer.
290        'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php', // Fedmich's Facebook Open Graph Meta.
291        'network-publisher/networkpub.php',                      // Network Publisher.
292        'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG.
293        'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php', // NextScripts SNAP.
294        'og-tags/og-tags.php',                                   // OG Tags.
295        'opengraph/opengraph.php',                               // Open Graph.
296        'open-graph-protocol-framework/open-graph-protocol-framework.php', // Open Graph Protocol Framework.
297        'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments.
298        'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate.
299        'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic.
300        'shareaholic/sexy-bookmarks.php',                        // Shareaholic.
301        'sharepress/sharepress.php',                             // SharePress.
302        'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect.
303        'social-discussions/social-discussions.php',             // Social Discussions.
304        'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit.
305        'socialize/socialize.php',                               // Socialize.
306        'squirrly-seo/squirrly.php',                             // SEO by SQUIRRLYâ„¢.
307        'only-tweet-like-share-and-google-1/tweet-like-plusone.php', // Tweet, Like, Google +1 and Share.
308        'wordbooker/wordbooker.php',                             // Wordbooker.
309        'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization.
310        'wp-caregiver/wp-caregiver.php',                         // WP Caregiver.
311        'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta.
312        'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol.
313        'wp-ogp/wp-ogp.php',                                     // WP-OGP.
314        'wp-seopress/seopress.php',                              // SEOPress.
315        'wp-seopress-pro/seopress-pro.php',                      // SEOPress Pro.
316        'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin.
317        'wp-fb-share-like-button/wp_fb_share-like_widget.php',   // WP Facebook Like Button.
318        'open-graph-metabox/open-graph-metabox.php',              // Open Graph Metabox.
319        'seo-by-rank-math/rank-math.php',                        // Rank Math.
320        'slim-seo/slim-seo.php',                                 // Slim SEO.
321    );
322
323    /**
324     * Plugins for which we turn off our Twitter Cards Tags implementation.
325     *
326     * @var array Plugins that conflict with Twitter cards.
327     */
328    private $twitter_cards_conflicting_plugins = array(
329        // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
330        // 'twitter/twitter.php',                       // The official one handles this on its own.
331        // https://github.com/twitter/wordpress/blob/master/src/Twitter/WordPress/Cards/Compatibility.php
332            'eewee-twitter-card/index.php',              // Eewee Twitter Card.
333        'ig-twitter-cards/ig-twitter-cards.php',     // IG:Twitter Cards.
334        'jm-twitter-cards/jm-twitter-cards.php',     // JM Twitter Cards.
335        'kevinjohn-gallagher-pure-web-brilliants-social-graph-twitter-cards-extention/kevinjohn_gallagher___social_graph_twitter_output.php',  // Pure Web Brilliant's Social Graph Twitter Cards Extension.
336        'twitter-cards/twitter-cards.php',           // Twitter Cards.
337        'twitter-cards-meta/twitter-cards-meta.php', // Twitter Cards Meta.
338        'wp-to-twitter/wp-to-twitter.php',           // WP to Twitter.
339        'wp-twitter-cards/twitter_cards.php',        // WP Twitter Cards.
340        'seo-by-rank-math/rank-math.php',            // Rank Math.
341        'slim-seo/slim-seo.php',                     // Slim SEO.
342    );
343
344    /**
345     * Message to display in admin_notice
346     *
347     * @var string
348     */
349    public $message = '';
350
351    /**
352     * Error to display in admin_notice
353     *
354     * @var string
355     */
356    public $error = '';
357
358    /**
359     * Modules that need more privacy description.
360     *
361     * @var string
362     */
363    public $privacy_checks = '';
364
365    /**
366     * Stats to record once the page loads
367     *
368     * @var array
369     */
370    public $stats = array();
371
372    /**
373     * Jetpack_Sync object
374     *
375     * @todo This is also seemingly unused.
376     *
377     * @var object
378     */
379    public $sync;
380
381    /**
382     * Verified data for JSON authorization request
383     *
384     * @deprecated 13.4
385     *
386     * @var array
387     */
388    public $json_api_authorization_request = array();
389
390    /**
391     * Connection manager.
392     *
393     * @var Automattic\Jetpack\Connection\Manager
394     */
395    protected $connection_manager;
396
397    /**
398     * Plugin lock key.
399     *
400     * @var string Transient key used to prevent multiple simultaneous plugin upgrades
401     */
402    public static $plugin_upgrade_lock_key = 'jetpack_upgrade_lock';
403
404    /**
405     * Holds an instance of Automattic\Jetpack\A8c_Mc_Stats
406     *
407     * @var Automattic\Jetpack\A8c_Mc_Stats
408     */
409    public $a8c_mc_stats_instance;
410
411    /**
412     * Constant for login redirect key.
413     *
414     * @var string
415     * @since 8.4.0
416     */
417    public static $jetpack_redirect_login = 'jetpack_connect_login_redirect';
418
419    /**
420     * Holds the singleton instance of this class
421     *
422     * @since 2.3.3
423     * @var Jetpack
424     */
425    public static $instance = false;
426
427    /**
428     * Singleton
429     *
430     * @static
431     */
432    public static function init() {
433        if ( ! self::$instance ) {
434            self::$instance = new Jetpack();
435            add_action( 'plugins_loaded', array( self::$instance, 'plugin_upgrade' ) );
436            add_action( 'jetpack_idc_disconnect', array( __CLASS__, 'on_idc_disconnect' ) );
437        }
438
439        return self::$instance;
440    }
441
442    /**
443     * Must never be called statically
444     */
445    public function plugin_upgrade() {
446        if ( self::is_connection_ready() ) {
447            list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
448            if ( JETPACK__VERSION !== $version ) {
449                // Prevent multiple upgrades at once - only a single process should trigger an upgrade to avoid stampedes.
450                if ( wp_using_ext_object_cache() ) {
451                    if ( true !== wp_cache_add( self::$plugin_upgrade_lock_key, 1, 'transient', 10 ) ) {
452                        return;
453                    }
454                } else {
455                    if ( false !== get_transient( self::$plugin_upgrade_lock_key ) ) {
456                        return;
457                    }
458
459                    set_transient( self::$plugin_upgrade_lock_key, 1, 10 );
460                }
461
462                // check which active modules actually exist and remove others from active_modules list.
463                $unfiltered_modules = self::get_active_modules();
464                $modules            = array_filter( $unfiltered_modules, array( 'Jetpack', 'is_module' ) );
465                if ( array_diff( $unfiltered_modules, $modules ) ) {
466                    self::update_active_modules( $modules );
467                }
468
469                add_action( 'init', array( __CLASS__, 'activate_new_modules' ) );
470
471                // Upgrade to 4.3.0.
472                if ( Jetpack_Options::get_option( 'identity_crisis_whitelist' ) ) {
473                    Jetpack_Options::delete_option( 'identity_crisis_whitelist' );
474                }
475
476                // Make sure Markdown for posts gets turned back on.
477                if ( ! get_option( 'wpcom_publish_posts_with_markdown' ) ) {
478                    update_option( 'wpcom_publish_posts_with_markdown', true );
479                }
480
481                /*
482                 * Minileven deprecation. 8.3.0.
483                 * Only delete options if not using
484                 * the replacement standalone Minileven plugin.
485                 */
486                if (
487                    ! self::is_plugin_active( 'minileven-master/minileven.php' )
488                    && ! self::is_plugin_active( 'minileven/minileven.php' )
489                ) {
490                    if ( get_option( 'wp_mobile_custom_css' ) ) {
491                        delete_option( 'wp_mobile_custom_css' );
492                    }
493                    if ( get_option( 'wp_mobile_excerpt' ) ) {
494                        delete_option( 'wp_mobile_excerpt' );
495                    }
496                    if ( get_option( 'wp_mobile_featured_images' ) ) {
497                        delete_option( 'wp_mobile_featured_images' );
498                    }
499                    if ( get_option( 'wp_mobile_app_promos' ) ) {
500                        delete_option( 'wp_mobile_app_promos' );
501                    }
502                }
503
504                // Upgrade to 8.4.0.
505                if ( Jetpack_Options::get_option( 'ab_connect_banner_green_bar' ) ) {
506                    Jetpack_Options::delete_option( 'ab_connect_banner_green_bar' );
507                }
508
509                // Update to 8.8.x (WordPress 5.5 Compatibility).
510                if ( Jetpack_Options::get_option( 'autoupdate_plugins' ) ) {
511                    $updated = update_site_option(
512                        'auto_update_plugins',
513                        array_unique(
514                            array_merge(
515                                (array) Jetpack_Options::get_option( 'autoupdate_plugins', array() ),
516                                (array) get_site_option( 'auto_update_plugins', array() )
517                            )
518                        )
519                    );
520
521                    if ( $updated ) {
522                        Jetpack_Options::delete_option( 'autoupdate_plugins' );
523                    } // Should we have some type of fallback if something fails here?
524                }
525
526                // Set the newsletter send default option for existing sites.
527                if ( false === get_option( 'wpcom_newsletter_send_default' ) ) {
528                    add_option( 'wpcom_newsletter_send_default', 1 );
529                }
530
531                if ( did_action( 'wp_loaded' ) ) {
532                    self::upgrade_on_load();
533                } else {
534                    add_action(
535                        'wp_loaded',
536                        array( __CLASS__, 'upgrade_on_load' )
537                    );
538                }
539            }
540        }
541    }
542
543    /**
544     * Runs upgrade routines that need to have modules loaded.
545     */
546    public static function upgrade_on_load() {
547
548        // Not attempting any upgrades if jetpack_modules_loaded did not fire.
549        // This can happen in case Jetpack has been just upgraded and is
550        // being initialized late during the page load. In this case we wait
551        // until the next proper admin page load with Jetpack active.
552        if ( ! did_action( 'jetpack_modules_loaded' ) ) {
553            delete_transient( self::$plugin_upgrade_lock_key );
554
555            return;
556        }
557
558        self::maybe_set_version_option();
559
560        if ( method_exists( 'Jetpack_Widget_Conditions', 'migrate_post_type_rules' ) ) {
561            Jetpack_Widget_Conditions::migrate_post_type_rules();
562        }
563
564        if (
565            class_exists( 'Jetpack_Sitemap_Manager' )
566        ) {
567            do_action( 'jetpack_sitemaps_purge_data' );
568        }
569
570        // Delete old stats cache.
571        delete_option( 'jetpack_restapi_stats_cache' );
572
573        delete_transient( self::$plugin_upgrade_lock_key );
574    }
575
576    /**
577     * Saves all the currently active modules to options.
578     * Also fires Action hooks for each newly activated and deactivated module.
579     *
580     * @param array $modules Array of active modules to be saved in options.
581     *
582     * @return bool $success true for success, false for failure.
583     */
584    public static function update_active_modules( $modules ) {
585        return ( new Modules() )->update_active( $modules );
586    }
587
588    /**
589     * Remove all active modules.
590     *
591     * @return void
592     */
593    public static function delete_active_modules() {
594        self::update_active_modules( array() );
595    }
596
597    /**
598     * Adds a hook to plugins_loaded at a priority that's currently the earliest
599     * available.
600     */
601    public function add_configure_hook() {
602        global $wp_filter;
603
604        $current_priority = has_filter( 'plugins_loaded', array( $this, 'configure' ) );
605        if ( false !== $current_priority ) {
606            remove_action( 'plugins_loaded', array( $this, 'configure' ), $current_priority );
607        }
608
609        $taken_priorities = array_map( 'intval', array_keys( $wp_filter['plugins_loaded']->callbacks ) );
610        sort( $taken_priorities );
611
612        $first_priority = array_shift( $taken_priorities );
613
614        if ( $first_priority <= PHP_INT_MIN ) {
615            $new_priority = PHP_INT_MIN;
616        } else {
617            $new_priority = $first_priority - 1;
618        }
619
620        add_action( 'plugins_loaded', array( $this, 'configure' ), $new_priority );
621    }
622
623    /**
624     * Constructor.  Initializes WordPress hooks
625     */
626    private function __construct() {
627        /*
628         * Check for and alert any deprecated hooks
629         */
630        add_action( 'init', array( $this, 'deprecated_hooks' ) );
631
632        // Note how this runs at an earlier plugin_loaded hook intentionally to accomodate for other plugins.
633        add_action( 'plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
634        add_action( 'network_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
635        add_action( 'mu_plugin_loaded', array( $this, 'add_configure_hook' ), 90 );
636        add_action( 'plugins_loaded', array( $this, 'late_initialization' ), 90 );
637
638        add_action( 'jetpack_verify_signature_error', array( $this, 'track_xmlrpc_error' ) );
639
640        /**
641         * Prepare Gutenberg Editor functionality
642         *
643         * The hooks previously here have been moved to modules/blocks.php but leaving this here pending
644         * a longer investigation to see if code is expecting the Gutenberg class to always be available.
645         */
646        require_once JETPACK__PLUGIN_DIR . 'class.jetpack-gutenberg.php';
647        add_action( 'set_user_role', array( $this, 'maybe_clear_other_linked_admins_transient' ), 10, 3 );
648
649        add_action( 'jetpack_event_log', array( 'Jetpack', 'log' ), 10, 2 );
650
651        add_filter( 'login_url', array( $this, 'login_url' ), 10, 2 );
652        add_action( 'login_init', array( $this, 'login_init' ) );
653
654        // Set up the REST authentication hooks.
655        Connection_Rest_Authentication::init();
656
657        add_action( 'admin_init', array( $this, 'admin_init' ) );
658        add_action( 'admin_init', array( $this, 'dismiss_jetpack_notice' ) );
659
660        add_filter( 'admin_body_class', array( $this, 'admin_body_class' ), 20 );
661
662        // Filter the dashboard meta box order to swap the new one in in place of the old one.
663        add_filter( 'get_user_option_meta-box-order_dashboard', array( $this, 'get_user_option_meta_box_order_dashboard' ) );
664
665        // WordPress dashboard widget.
666        require_once JETPACK__PLUGIN_DIR . 'class-jetpack-stats-dashboard-widget.php';
667        add_action( 'wp_dashboard_setup', array( new Jetpack_Stats_Dashboard_Widget(), 'init' ) );
668
669        // Returns HTTPS support status.
670        add_action( 'wp_ajax_jetpack-recheck-ssl', array( $this, 'ajax_recheck_ssl' ) );
671
672        add_action( 'wp_loaded', array( $this, 'register_assets' ) );
673
674        /**
675         * These actions run checks to load additional files.
676         * They check for external files or plugins, so they need to run as late as possible.
677         */
678        add_action( 'wp_head', array( $this, 'check_open_graph' ), 1 );
679        add_action( 'web_stories_story_head', array( $this, 'check_open_graph' ), 1 );
680        add_action( 'plugins_loaded', array( $this, 'check_twitter_tags' ), 999 );
681        add_action( 'plugins_loaded', array( $this, 'check_rest_api_compat' ), 1000 );
682
683        add_filter( 'plugins_url', array( 'Jetpack', 'maybe_min_asset' ), 1, 3 );
684        add_action( 'style_loader_src', array( 'Jetpack', 'set_suffix_on_min' ), 10, 2 );
685
686        add_filter( 'profile_update', array( 'Jetpack', 'user_meta_cleanup' ) );
687
688        add_filter( 'jetpack_get_default_modules', array( $this, 'filter_default_modules' ) );
689        add_filter( 'jetpack_get_default_modules', array( $this, 'handle_deprecated_modules' ), 99 );
690
691        add_action(
692            'plugins_loaded',
693            function () {
694                if ( User_Agent_Info::is_mobile_app() ) {
695                    add_filter( 'get_edit_post_link', '__return_empty_string' );
696                }
697            }
698        );
699
700        // Update the site's Jetpack plan and products from API on heartbeats.
701        add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
702
703        // Actually push the stats on shutdown.
704        if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
705            add_action( 'shutdown', array( $this, 'push_stats' ) );
706        }
707
708        // After a successful connection.
709        add_action( 'jetpack_site_registered', array( $this, 'activate_default_modules_on_site_register' ) );
710        add_action( 'jetpack_site_registered', array( $this, 'handle_unique_registrations_stats' ) );
711        add_action( 'jetpack_site_registered', array( Reader_Link::class, 'activate_on_connection' ), 9 );
712
713        // Actions for Manager::authorize().
714        add_action( 'jetpack_authorize_starting', array( $this, 'authorize_starting' ) );
715        add_action( 'jetpack_authorize_ending_linked', array( $this, 'authorize_ending_linked' ) );
716        add_action( 'jetpack_authorize_ending_authorized', array( $this, 'authorize_ending_authorized' ) );
717
718        Jetpack_Client_Server::init();
719        add_action( 'jetpack_client_authorize_error', array( Jetpack_Client_Server::class, 'client_authorize_error' ) );
720        add_filter( 'jetpack_client_authorize_already_authorized_url', array( Jetpack_Client_Server::class, 'client_authorize_already_authorized_url' ) );
721        add_action( 'jetpack_client_authorize_processing', array( Jetpack_Client_Server::class, 'client_authorize_processing' ) );
722        add_filter( 'jetpack_client_authorize_fallback_url', array( Jetpack_Client_Server::class, 'client_authorize_fallback_url' ) );
723
724        // Filters for the Manager::get_token() urls and request body.
725        add_filter( 'jetpack_token_redirect_url', array( Authorize_Redirect::class, 'filter_connect_redirect_url' ) );
726        add_filter( 'jetpack_token_request_body', array( __CLASS__, 'filter_token_request_body' ) );
727
728        // Filter for the `jetpack/v4/connection/data` API response.
729        add_filter( 'jetpack_current_user_connection_data', array( __CLASS__, 'filter_jetpack_current_user_connection_data' ) );
730
731        // Actions for successful reconnect.
732        add_action( 'jetpack_reconnection_completed', array( $this, 'reconnection_completed' ) );
733
734        // Actions for successful disconnect.
735        add_action( 'jetpack_site_disconnected', array( $this, 'jetpack_site_disconnected' ) );
736
737        // Actions for licensing.
738        Licensing::instance()->initialize();
739
740        // Filters for Sync Callables.
741        add_filter( 'jetpack_sync_callable_whitelist', array( $this, 'filter_sync_callable_whitelist' ), 10, 1 );
742        add_filter( 'jetpack_sync_multisite_callable_whitelist', array( $this, 'filter_sync_multisite_callable_whitelist' ), 10, 1 );
743
744        // Make resources use static domain when possible.
745        add_filter( 'jetpack_static_url', array( 'Automattic\\Jetpack\\Assets', 'staticize_subdomain' ) );
746
747        // Validate the domain names in Jetpack development versions.
748        add_action( 'jetpack_pre_register', array( static::class, 'registration_check_domains' ) );
749
750        // Register product descriptions for partner coupon usage.
751        add_filter( 'jetpack_partner_coupon_products', array( $this, 'get_partner_coupon_product_descriptions' ) );
752
753        // Actions for conditional recommendations.
754        add_action( 'plugins_loaded', array( 'Jetpack_Recommendations', 'init_conditional_recommendation_actions' ) );
755
756        // Add 5-star
757        add_filter( 'plugin_row_meta', array( $this, 'add_5_star_review_link' ), 10, 2 );
758        add_action( 'init', array( Deprecate::class, 'instance' ) );
759
760        // Register Jetpack module management abilities (WordPress Abilities API, WP 6.9+).
761        \Automattic\Jetpack\Plugin\Abilities\Modules_Abilities::init();
762    }
763
764    /**
765     * Before everything else starts getting initalized, we need to initialize Jetpack using the
766     * Config object.
767     */
768    public function configure() {
769        $config = new Config();
770
771        foreach (
772            array(
773                'jitm',
774                'sync',
775                'account_protection',
776                'waf',
777                'videopress',
778                'stats',
779                'stats_admin',
780                'import',
781            )
782            as $feature
783        ) {
784            $config->ensure( $feature );
785        }
786
787        $config->ensure(
788            'connection',
789            array(
790                'slug' => 'jetpack',
791                'name' => 'Jetpack',
792            )
793        );
794
795        // Identity crisis package.
796        $config->ensure(
797            'identity_crisis',
798            array(
799                'slug'       => 'jetpack',
800                'admin_page' => '/wp-admin/admin.php?page=jetpack',
801            )
802        );
803
804        $config->ensure( 'search' );
805
806        if ( ! $this->connection_manager ) {
807            $this->connection_manager = new Connection_Manager( 'jetpack' );
808        }
809
810        $modules = new Automattic\Jetpack\Modules();
811        if ( $modules->is_active( 'publicize' ) && $this->connection_manager->has_connected_user() ) {
812            $config->ensure( 'publicize' );
813        }
814
815        add_action( 'jetpack_initialize_tracking', array( $this, 'initialize_tracking' ) );
816
817        /*
818         * Load things that should only be in Network Admin.
819         *
820         * For now blow away everything else until a more full
821         * understanding of what is needed at the network level is
822         * available
823         */
824        if ( is_multisite() ) {
825            $network = Jetpack_Network::init();
826            $network->set_connection( $this->connection_manager );
827        }
828
829        $is_connection_ready = self::is_connection_ready();
830
831        if ( $is_connection_ready ) {
832            add_action( 'login_form_jetpack_json_api_authorization', array( $this, 'login_form_json_api_authorization' ) );
833            $this->run_initialize_tracking_action();
834
835            Jetpack_Heartbeat::init();
836            if ( self::is_module_active( 'stats' ) && self::is_module_active( 'search' ) ) {
837                require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-search-performance-logger.php';
838                Jetpack_Search_Performance_Logger::init();
839            }
840        } else {
841            add_action( 'jetpack_agreed_to_terms_of_service', array( $this, 'run_initialize_tracking_action' ) );
842            add_action( 'rest_api_init', array( $this, 'run_initialize_tracking_action' ) );
843            add_filter(
844                'xmlrpc_methods',
845                function ( $methods ) {
846                    $this->run_initialize_tracking_action();
847                    return $methods;
848                },
849                1
850            );
851        }
852
853        // Initialize remote file upload request handlers.
854        $this->add_remote_request_handlers();
855
856        /*
857         * Enable enhanced handling of previewing sites in Calypso
858         */
859        if ( $is_connection_ready ) {
860            require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php';
861            add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 );
862            add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) );
863        }
864    }
865
866    /**
867     * Runs on plugins_loaded. Use this to add code that needs to be executed later than other
868     * initialization code.
869     *
870     * @action plugins_loaded
871     */
872    public function late_initialization() {
873        add_action( 'after_setup_theme', array( 'Jetpack', 'load_modules' ), -2 );
874        My_Jetpack_Initializer::init();
875        Activity_Log_Init::initialize();
876        Scan_Page_Init::initialize();
877        Podcast_Init::init();
878
879        // Initialize Boost Speed Score
880        new Speed_Score( array(), 'jetpack-dashboard' );
881
882        /**
883         * Fires when Jetpack is fully loaded and ready. This is the point where it's safe
884         * to instantiate classes from packages and namespaces that are managed by the Jetpack Autoloader.
885         *
886         * @since 8.1.0
887         *
888         * @param Jetpack $jetpack the main plugin class object.
889         */
890        do_action( 'jetpack_loaded', $this );
891
892        add_filter( 'map_meta_cap', array( $this, 'jetpack_custom_caps' ), 1, 2 );
893    }
894
895    /**
896     * This is ported over from the manage module, which has been deprecated and baked in here.
897     */
898    public function add_wpcom_to_allowed_redirect_hosts() {
899        add_filter( 'allowed_redirect_hosts', array( $this, 'allow_wpcom_domain' ) );
900    }
901
902    /**
903     * Return $domains, with 'wordpress.com' appended.
904     * This is ported over from the manage module, which has been deprecated and baked in here.
905     *
906     * @param array $domains Array of domains allowed for redirect.
907     *
908     * @return array
909     */
910    public function allow_wpcom_domain( $domains ) {
911        if ( empty( $domains ) ) {
912            $domains = array();
913        }
914        $domains[] = 'wordpress.com';
915        return array_unique( $domains );
916    }
917
918    /**
919     * Redirect edit post links to Calypso.
920     *
921     * @deprecated since 13.9
922     *
923     * @param string $default_url Post edit URL.
924     * @param int    $post_id Post ID.
925     *
926     * @return string
927     */
928    public function point_edit_post_links_to_calypso( $default_url, $post_id ) {
929        _deprecated_function( __METHOD__, '13.9' );
930
931        $post = get_post( $post_id );
932
933        if ( empty( $post ) ) {
934            return $default_url;
935        }
936
937        $post_type = $post->post_type;
938
939        // Mapping the allowed CPTs on WordPress.com to corresponding paths in Calypso.
940        // https://en.support.wordpress.com/custom-post-types/.
941        $allowed_post_types = array(
942            'post',
943            'page',
944            'jetpack-portfolio',
945            'jetpack-testimonial',
946        );
947
948        if ( ! in_array( $post_type, $allowed_post_types, true ) ) {
949            return $default_url;
950        }
951
952        return Redirect::get_url(
953            'calypso-edit-' . $post_type,
954            array(
955                'path' => $post_id,
956            )
957        );
958    }
959
960    /**
961     * Redirect edit comment links to Calypso.
962     *
963     * @deprecated since 13.9
964     *
965     * @param string $url Comment edit URL.
966     *
967     * @return string
968     */
969    public function point_edit_comment_links_to_calypso( $url ) {
970        _deprecated_function( __METHOD__, '13.9' );
971
972        // Take the `query` key value from the URL, and parse its parts to the $query_args. `amp;c` matches the comment ID.
973        $query_args = null;
974        wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query_args );
975
976        return Redirect::get_url(
977            'calypso-edit-comment',
978            array(
979                'path' => $query_args['amp;c'],
980            )
981        );
982    }
983
984    /**
985     * Extend Sync callables with Jetpack Plugin functions.
986     *
987     * @param array $callables list of callables.
988     *
989     * @return array list of callables.
990     */
991    public function filter_sync_callable_whitelist( $callables ) {
992        // Jetpack Functions.
993        $jetpack_callables = array(
994            'single_user_site'         => array( 'Jetpack', 'is_single_user_site' ),
995            'updates'                  => array( 'Jetpack', 'get_updates' ),
996            'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins.
997        );
998        return array_merge( $callables, $jetpack_callables );
999    }
1000
1001    /**
1002     * Extend Sync multisite callables with Jetpack Plugin functions.
1003     *
1004     * @param array $callables list of callables.
1005     *
1006     * @return array list of callables.
1007     */
1008    public function filter_sync_multisite_callable_whitelist( $callables ) {
1009
1010        // Jetpack Funtions.
1011        $jetpack_multisite_callables = array(
1012            'network_name'                        => array( 'Jetpack', 'network_name' ),
1013            'network_allow_new_registrations'     => array( 'Jetpack', 'network_allow_new_registrations' ),
1014            'network_add_new_users'               => array( 'Jetpack', 'network_add_new_users' ),
1015            'network_site_upload_space'           => array( 'Jetpack', 'network_site_upload_space' ),
1016            'network_upload_file_types'           => array( 'Jetpack', 'network_upload_file_types' ),
1017            'network_enable_administration_menus' => array( 'Jetpack', 'network_enable_administration_menus' ),
1018        );
1019        $callables                   = array_merge( $callables, $jetpack_multisite_callables );
1020
1021        return $callables;
1022    }
1023
1024    /**
1025     * If there are any stats that need to be pushed, but haven't been, push them now.
1026     */
1027    public function push_stats() {
1028        if ( ! empty( $this->stats ) ) {
1029            $this->do_stats( 'server_side' );
1030        }
1031    }
1032
1033    /**
1034     * Sets the Jetpack custom capabilities.
1035     *
1036     * @param string[] $caps    Array of the user's capabilities.
1037     * @param string   $cap     Capability name.
1038     */
1039    public function jetpack_custom_caps( $caps, $cap ) {
1040        switch ( $cap ) {
1041            case 'jetpack_manage_autoupdates':
1042                $caps = array(
1043                    'manage_options',
1044                    'update_plugins',
1045                );
1046                break;
1047            case 'jetpack_network_admin_page':
1048            case 'jetpack_network_settings_page':
1049                $caps = array( 'manage_network_plugins' );
1050                break;
1051            case 'jetpack_network_sites_page':
1052                $caps = array( 'manage_sites' );
1053                break;
1054            case 'jetpack_admin_page':
1055                $is_offline_mode = ( new Status() )->is_offline_mode();
1056                if ( $is_offline_mode ) {
1057                    $caps = array( 'manage_options' );
1058                    break;
1059                } else {
1060                    $caps = array( 'edit_posts' );
1061                }
1062                break;
1063        }
1064        return $caps;
1065    }
1066
1067    /**
1068     * Register assets for use in various modules and the Jetpack admin page.
1069     *
1070     * @uses wp_script_is, wp_register_script, plugins_url
1071     * @action wp_loaded
1072     * @return void
1073     */
1074    public function register_assets() {
1075        if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) ) {
1076            wp_register_script(
1077                'jetpack-gallery-settings',
1078                Assets::get_file_url_for_environment( '_inc/build/gallery-settings.min.js', '_inc/gallery-settings.js' ),
1079                array( 'media-views' ),
1080                '20121225',
1081                true
1082            );
1083        }
1084
1085        if ( ! wp_script_is( 'jetpack-twitter-timeline', 'registered' ) ) {
1086            wp_register_script(
1087                'jetpack-twitter-timeline',
1088                Assets::get_file_url_for_environment( '_inc/build/twitter-timeline.min.js', '_inc/twitter-timeline.js' ),
1089                array(),
1090                '4.0.0',
1091                true
1092            );
1093        }
1094
1095        if ( ! wp_script_is( 'jetpack-facebook-embed', 'registered' ) ) {
1096            wp_register_script(
1097                'jetpack-facebook-embed',
1098                Assets::get_file_url_for_environment( '_inc/build/facebook-embed.min.js', '_inc/facebook-embed.js' ),
1099                array(),
1100                JETPACK__VERSION,
1101                true
1102            );
1103
1104            /** This filter is documented in modules/sharedaddy/sharing-sources.php */
1105            $fb_app_id = apply_filters( 'jetpack_sharing_facebook_app_id', '249643311490' );
1106            if ( ! is_numeric( $fb_app_id ) ) {
1107                $fb_app_id = '';
1108            }
1109            wp_localize_script(
1110                'jetpack-facebook-embed',
1111                'jpfbembed',
1112                array(
1113                    'appid'  => $fb_app_id,
1114                    'locale' => $this->get_locale(),
1115                )
1116            );
1117        }
1118
1119        /**
1120         * As jetpack_register_genericons is by default fired off a hook,
1121         * the hook may have already fired by this point.
1122         * So, let's just trigger it manually.
1123         */
1124        require_once JETPACK__PLUGIN_DIR . '_inc/genericons.php';
1125        jetpack_register_genericons();
1126
1127        /**
1128         * Register the social logos
1129         */
1130        require_once JETPACK__PLUGIN_DIR . '_inc/social-logos.php';
1131        jetpack_register_social_logos();
1132    }
1133
1134    /**
1135     * Guess locale from language code.
1136     *
1137     * @param string $lang Language code.
1138     * @return string|bool
1139     */
1140    public function guess_locale_from_lang( $lang ) {
1141        if ( 'en' === $lang || 'en_US' === $lang || ! $lang ) {
1142            return 'en_US';
1143        }
1144
1145        if ( ! class_exists( 'GP_Locales' ) ) {
1146            if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
1147                return false;
1148            }
1149
1150            require JETPACK__GLOTPRESS_LOCALES_PATH;
1151        }
1152
1153        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1154            // WP.com: get_locale() returns 'it'.
1155            $locale = GP_Locales::by_slug( $lang );
1156        } else {
1157            // Jetpack: get_locale() returns 'it_IT';.
1158            $locale = GP_Locales::by_field( 'facebook_locale', $lang );
1159        }
1160
1161        if ( ! $locale ) {
1162            return false;
1163        }
1164
1165        if ( empty( $locale->facebook_locale ) ) {
1166            if ( empty( $locale->wp_locale ) ) {
1167                return false;
1168            } else {
1169                // Facebook SDK is smart enough to fall back to en_US if a
1170                // locale isn't supported. Since supported Facebook locales
1171                // can fall out of sync, we'll attempt to use the known
1172                // wp_locale value and rely on said fallback.
1173                return $locale->wp_locale;
1174            }
1175        }
1176
1177        return $locale->facebook_locale;
1178    }
1179
1180    /**
1181     * Get the locale.
1182     *
1183     * @return string|bool
1184     */
1185    public function get_locale() {
1186        $locale = $this->guess_locale_from_lang( get_locale() );
1187
1188        if ( ! $locale ) {
1189            $locale = 'en_US';
1190        }
1191
1192        return $locale;
1193    }
1194
1195    /**
1196     * Return the network_site_url so that .com knows what network this site is a part of.
1197     *
1198     * @return string
1199     */
1200    public function jetpack_main_network_site_option() {
1201        return network_site_url();
1202    }
1203    /**
1204     * Network Name.
1205     */
1206    public static function network_name() {
1207        global $current_site;
1208        return $current_site->site_name;
1209    }
1210    /**
1211     * Does the network allow new user and site registrations.
1212     *
1213     * @return string
1214     */
1215    public static function network_allow_new_registrations() {
1216        return ( in_array( get_site_option( 'registration' ), array( 'none', 'user', 'blog', 'all' ), true ) ? get_site_option( 'registration' ) : 'none' );
1217    }
1218    /**
1219     * Does the network allow admins to add new users.
1220     *
1221     * @return bool
1222     */
1223    public static function network_add_new_users() {
1224        return (bool) get_site_option( 'add_new_users' );
1225    }
1226    /**
1227     * File upload psace left per site in MB.
1228     *  -1 means NO LIMIT.
1229     *
1230     * @return int
1231     */
1232    public static function network_site_upload_space() {
1233        // value in MB.
1234        return ( get_site_option( 'upload_space_check_disabled' ) ? -1 : get_space_allowed() );
1235    }
1236
1237    /**
1238     * Network allowed file types.
1239     *
1240     * @return string
1241     */
1242    public static function network_upload_file_types() {
1243        return get_site_option( 'upload_filetypes', 'jpg jpeg png gif' );
1244    }
1245
1246    /**
1247     * Maximum file upload size set by the network.
1248     *
1249     * @return int
1250     */
1251    public static function network_max_upload_file_size() {
1252        // value in KB.
1253        return get_site_option( 'fileupload_maxk', 300 );
1254    }
1255
1256    /**
1257     * Lets us know if a site allows admins to manage the network.
1258     *
1259     * @return array
1260     */
1261    public static function network_enable_administration_menus() {
1262        return get_site_option( 'menu_items' );
1263    }
1264
1265    /**
1266     * If a user has been promoted to or demoted from admin, we need to clear the
1267     * jetpack_other_linked_admins transient.
1268     *
1269     * @since 4.3.2
1270     * @since 4.4.0  $old_roles is null by default and if it's not passed, the transient is cleared.
1271     *
1272     * @param int    $user_id   The user ID whose role changed.
1273     * @param string $role      The new role.
1274     * @param array  $old_roles An array of the user's previous roles.
1275     */
1276    public function maybe_clear_other_linked_admins_transient( $user_id, $role, $old_roles = null ) {
1277        if ( 'administrator' === $role
1278            || ( is_array( $old_roles ) && in_array( 'administrator', $old_roles, true ) )
1279            || $old_roles === null
1280        ) {
1281            delete_transient( 'jetpack_other_linked_admins' );
1282        }
1283    }
1284
1285    /**
1286     * Checks to see if there are any other users available to become primary
1287     * Users must both:
1288     * - Be linked to wpcom
1289     * - Be an admin
1290     *
1291     * @return mixed False if no other users are linked, Int if there are.
1292     */
1293    public static function get_other_linked_admins() {
1294        $other_linked_users = get_transient( 'jetpack_other_linked_admins' );
1295
1296        if ( false === $other_linked_users ) {
1297            $admins = get_users( array( 'role' => 'administrator' ) );
1298            if ( count( $admins ) > 1 ) {
1299                $available = array();
1300                foreach ( $admins as $admin ) {
1301                    if ( self::connection()->is_user_connected( $admin->ID ) ) {
1302                        $available[] = $admin->ID;
1303                    }
1304                }
1305
1306                $count_connected_admins = count( $available );
1307                if ( count( $available ) > 1 ) {
1308                    $other_linked_users = $count_connected_admins;
1309                } else {
1310                    $other_linked_users = 0;
1311                }
1312            } else {
1313                $other_linked_users = 0;
1314            }
1315
1316            set_transient( 'jetpack_other_linked_admins', $other_linked_users, HOUR_IN_SECONDS );
1317        }
1318
1319        return ( 0 === $other_linked_users ) ? false : $other_linked_users;
1320    }
1321
1322    /**
1323     * Return whether we are dealing with a multi network setup or not.
1324     * The reason we are type casting this is because we want to avoid the situation where
1325     * the result is false since when is_main_network_option return false it cases
1326     * the rest the get_option( 'jetpack_is_multi_network' ); to return the value that is set in the
1327     * database which could be set to anything as opposed to what this function returns.
1328     *
1329     * @return boolean
1330     */
1331    public function is_main_network_option() {
1332        // returns either an '1' or an empty string.
1333        return (string) (bool) self::is_multi_network();
1334    }
1335
1336    /**
1337     * Return true if we are with multi-site or multi-network false if we are dealing with single site.
1338     *
1339     * @return string
1340     */
1341    public function is_multisite() {
1342        return (string) (bool) is_multisite();
1343    }
1344
1345    /**
1346     * Implemented since there is no core is multi network function
1347     * Right now there is no way to tell if we which network is the dominant network on the system
1348     *
1349     * @since  3.3
1350     * @return boolean
1351     */
1352    public static function is_multi_network() {
1353        global  $wpdb;
1354
1355        // if we don't have a multi site setup no need to do any more.
1356        if ( ! is_multisite() ) {
1357            return false;
1358        }
1359
1360        $num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
1361        if ( $num_sites > 1 ) {
1362            return true;
1363        } else {
1364            return false;
1365        }
1366    }
1367
1368    /**
1369     * Get back if the current site is single user site.
1370     *
1371     * @return bool
1372     */
1373    public static function is_single_user_site() {
1374        global $wpdb;
1375
1376        $some_users = get_transient( 'jetpack_is_single_user' );
1377        if ( false === $some_users ) {
1378            $some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
1379            set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
1380        }
1381        return 1 === (int) $some_users;
1382    }
1383
1384    /**
1385     * Returns true if the site has file write access false otherwise.
1386     *
1387     * @return string ( '1' | '0' )
1388     **/
1389    public static function file_system_write_access() {
1390        if ( ! function_exists( 'get_filesystem_method' ) ) {
1391            require_once ABSPATH . 'wp-admin/includes/file.php';
1392        }
1393
1394        require_once ABSPATH . 'wp-admin/includes/template.php';
1395
1396        $filesystem_method = get_filesystem_method();
1397        if ( 'direct' === $filesystem_method ) {
1398            return 1;
1399        }
1400
1401        ob_start();
1402        $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1403        ob_end_clean();
1404        if ( $filesystem_credentials_are_stored ) {
1405            return 1;
1406        }
1407        return 0;
1408    }
1409
1410    // phpcs:disable WordPress.WP.CapitalPDangit.MisspelledInComment
1411    /**
1412     * Gets updates and stores in jetpack_updates.
1413     *
1414     * The jetpack_updates option is saved in the following schema:
1415     *
1416     * array (
1417     *      'plugins'                       => (int) Number of plugin updates available.
1418     *      'themes'                        => (int) Number of theme updates available.
1419     *      'wordpress'                     => (int) Number of WordPress core updates available.
1420     *      'translations'                  => (int) Number of translation updates available.
1421     *      'total'                         => (int) Total of all available updates.
1422     *      'wp_update_version'             => (string) The latest available version of WordPress, only present if a WordPress update is needed.
1423     * )
1424     *
1425     * @return array
1426     */
1427    public static function get_updates() {
1428        $updates     = array();
1429        $update_data = wp_get_update_data();
1430
1431        // Stores the individual update counts as well as the total count.
1432        if ( isset( $update_data['counts'] ) ) {
1433            $updates = $update_data['counts'];
1434        }
1435
1436        // If we need to update WordPress core, let's find the latest version number.
1437        if ( ! empty( $updates['wordpress'] ) ) {
1438            $cur = get_preferred_from_update_core();
1439            if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
1440                $updates['wp_update_version'] = $cur->current;
1441            }
1442        }
1443        return $updates;
1444    }
1445    // phpcs:enable WordPress.WP.CapitalPDangit.MisspelledInComment
1446
1447    /**
1448     * Get update details for core, plugins, and themes.
1449     *
1450     * @return array
1451     */
1452    public static function get_update_details() {
1453        $update_details = array(
1454            'update_core'    => get_site_transient( 'update_core' ),
1455            'update_plugins' => get_site_transient( 'update_plugins' ),
1456            'update_themes'  => get_site_transient( 'update_themes' ),
1457        );
1458        return $update_details;
1459    }
1460
1461    /**
1462     * Is Jetpack active?
1463     * The method only checks if there's an existing token for the master user. It doesn't validate the token.
1464     *
1465     * This method is deprecated since 9.6.0. Please use one of the methods provided by the Manager class in the Connection package,
1466     * or Jetpack::is_connection_ready if you want to know when the Jetpack plugin starts considering the connection ready to be used.
1467     *
1468     * Since this method has a wide spread use, we decided not to throw any deprecation warnings for now.
1469     *
1470     * @deprecated 9.6.0
1471     *
1472     * @return bool
1473     */
1474    public static function is_active() {
1475        return self::connection()->has_connected_owner();
1476    }
1477
1478    /**
1479     * Returns true if the current site is connected to WordPress.com and has the minimum requirements to enable Jetpack UI
1480     *
1481     * This method was introduced just before the release of the possibility to use Jetpack without a user connection, while
1482     * it was available only when no_user_testing_mode was enabled. In the near future, this will return is_connected for all
1483     * users and this option will be available by default for everybody.
1484     *
1485     * @since 9.6.0
1486     * @since 9.7.0 returns is_connected in all cases and adds filter to the returned value
1487     *
1488     * @return bool is the site connection ready to be used?
1489     */
1490    public static function is_connection_ready() {
1491        /**
1492         * Allows filtering whether the connection is ready to be used. If true, this will enable the Jetpack UI and modules
1493         *
1494         * Modules will be enabled depending on the connection status and if the module requires a connection or user connection.
1495         *
1496         * @since 9.7.0
1497         *
1498         * @param bool                                  $is_connection_ready Is the connection ready?
1499         * @param Automattic\Jetpack\Connection\Manager $connection_manager Instance of the Manager class, can be used to check the connection status.
1500         */
1501        return apply_filters( 'jetpack_is_connection_ready', self::connection()->is_connected(), self::connection() );
1502    }
1503
1504    /**
1505     * Determines reason for Jetpack offline mode.
1506     */
1507    public static function development_mode_trigger_text() {
1508        $status = new Status();
1509
1510        if ( ! $status->is_offline_mode() ) {
1511            return __( 'Jetpack is not in Offline Mode.', 'jetpack' );
1512        }
1513
1514        if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
1515            $notice = __( 'The JETPACK_DEV_DEBUG constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1516        } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
1517            $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack' );
1518        } elseif ( $status->is_local_site() ) {
1519            $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack' );
1520        } elseif ( get_option( 'jetpack_offline_mode' ) ) {
1521            $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack' );
1522        } else {
1523            $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack' );
1524        }
1525
1526        return $notice;
1527    }
1528    /**
1529     * Get Jetpack offline mode notice text and notice class.
1530     *
1531     * Mirrors the checks made in Automattic\Jetpack\Status->is_offline_mode
1532     */
1533    public static function show_development_mode_notice() {
1534        if ( ( new Status() )->is_offline_mode() ) {
1535            $notice = sprintf(
1536                /* translators: %s is a URL */
1537                __( 'In <a href="%s" target="_blank">Offline Mode</a>:', 'jetpack' ),
1538                esc_url( Redirect::get_url( 'jetpack-support-development-mode' ) )
1539            );
1540
1541            $notice .= ' ' . self::development_mode_trigger_text();
1542
1543            echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1544        }
1545
1546        // Throw up a notice if using a development version and as for feedback.
1547        if ( self::is_development_version() ) {
1548            /* translators: %s is a URL */
1549            $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' ) ) );
1550
1551            echo '<div class="updated" style="border-color: #f0821e;"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All provided text.
1552        }
1553    }
1554
1555    /**
1556     * Whether Jetpack's version maps to a public release, or a development version.
1557     */
1558    public static function is_development_version() {
1559        /**
1560         * Allows filtering whether this is a development version of Jetpack.
1561         *
1562         * This filter is especially useful for tests.
1563         *
1564         * @since 4.3.0
1565         *
1566         * @param bool $development_version Is this a develoment version of Jetpack?
1567         */
1568        return (bool) apply_filters(
1569            'jetpack_development_version',
1570            ! preg_match( '/^\d+(\.\d+)+$/', Constants::get_constant( 'JETPACK__VERSION' ) )
1571        );
1572    }
1573
1574    /**
1575     * Get the wpcom email of the current|specified connected user.
1576     *
1577     * @param null|int $user_id User ID or will use get_current_user_id if null.
1578     */
1579    public static function get_connected_user_email( $user_id = null ) {
1580        if ( ! $user_id ) {
1581            $user_id = get_current_user_id();
1582        }
1583
1584        $xml = new Jetpack_IXR_Client(
1585            array(
1586                'user_id' => $user_id,
1587            )
1588        );
1589        $xml->query( 'wpcom.getUserEmail' );
1590        if ( ! $xml->isError() ) {
1591            return $xml->getResponse();
1592        }
1593        return false;
1594    }
1595
1596    /**
1597     * Get the wpcom email of the master user.
1598     */
1599    public static function get_master_user_email() {
1600        $master_user_id = Jetpack_Options::get_option( 'master_user' );
1601        if ( $master_user_id ) {
1602            return self::get_connected_user_email( $master_user_id );
1603        }
1604        return '';
1605    }
1606
1607    /**
1608     * Gets current user IP address.
1609     *
1610     * @param  bool $check_all_headers Check all headers? Default is `false`.
1611     *
1612     * @deprecated Jetpack 10.6
1613     *
1614     * @return string                  Current user IP address.
1615     */
1616    public static function current_user_ip( $check_all_headers = false ) {
1617        _deprecated_function( __METHOD__, 'jetpack-10.6', 'Automattic\\Jetpack\\Status\\Visitor::get_ip' );
1618
1619        return ( new Visitor() )->get_ip( $check_all_headers );
1620    }
1621
1622    /**
1623     * Loads the currently active modules.
1624     */
1625    public static function load_modules() {
1626        $status = new Status();
1627
1628        if (
1629            ! self::is_connection_ready()
1630            && ! $status->is_offline_mode()
1631            && (
1632                ! is_multisite()
1633                || ! get_site_option( 'jetpack_protect_active' )
1634            )
1635        ) {
1636            return;
1637        }
1638
1639        $version = Jetpack_Options::get_option( 'version' );
1640        if ( ! $version ) {
1641            $version     = JETPACK__VERSION . ':' . time();
1642            $old_version = $version;
1643            /** This action is documented in class.jetpack.php */
1644            do_action( 'updating_jetpack_version', $version, false );
1645            Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1646        }
1647        list( $version ) = explode( ':', $version );
1648
1649        $modules = array_filter( self::get_active_modules(), array( 'Jetpack', 'is_module' ) );
1650
1651        $modules_data = array();
1652
1653        // Don't load modules that have had "Major" changes since the stored version until they have been deactivated/reactivated through the lint check.
1654        if ( version_compare( $version, JETPACK__VERSION, '<' ) ) {
1655            $updated_modules = array();
1656            foreach ( $modules as $module ) {
1657                $modules_data[ $module ] = self::get_module( $module );
1658                if ( ! isset( $modules_data[ $module ]['changed'] ) ) {
1659                    continue;
1660                }
1661
1662                if ( version_compare( $modules_data[ $module ]['changed'], $version, '<=' ) ) {
1663                    continue;
1664                }
1665
1666                $updated_modules[] = $module;
1667            }
1668
1669            $modules = array_diff( $modules, $updated_modules );
1670        }
1671
1672        $is_site_connection = false;
1673
1674        if ( method_exists( self::connection(), 'is_site_connection' ) ) {
1675            $is_site_connection = self::connection()->is_site_connection();
1676        }
1677
1678        foreach ( $modules as $index => $module ) {
1679            // If we're in offline/site-connection mode, disable modules requiring a connection/user connection.
1680            if ( $status->is_offline_mode() || $is_site_connection ) {
1681                // Prime the pump if we need to.
1682                if ( empty( $modules_data[ $module ] ) ) {
1683                    $modules_data[ $module ] = self::get_module( $module );
1684                }
1685                // If the module requires a connection, but we're in local mode, don't include it.
1686                if ( $status->is_offline_mode() && $modules_data[ $module ]['requires_connection'] ) {
1687                    continue;
1688                }
1689
1690                if ( $is_site_connection && $modules_data[ $module ]['requires_user_connection'] ) {
1691                    continue;
1692                }
1693            }
1694
1695            if ( did_action( 'jetpack_module_loaded_' . $module ) ) {
1696                continue;
1697            }
1698
1699            if ( ! include_once self::get_module_path( $module ) ) { // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath
1700                unset( $modules[ $index ] );
1701                self::update_active_modules( array_values( $modules ) );
1702                continue;
1703            }
1704
1705            /**
1706             * Fires when a specific module is loaded.
1707             * The dynamic part of the hook, $module, is the module slug.
1708             *
1709             * @since 1.1.0
1710             */
1711            do_action( 'jetpack_module_loaded_' . $module );
1712        }
1713
1714        /**
1715         * Fires when all the modules are loaded.
1716         *
1717         * @since 1.1.0
1718         */
1719        do_action( 'jetpack_modules_loaded' );
1720
1721        // Load module-specific code that is needed even when a module isn't active. Loaded here because code contained therein may need actions such as setup_theme.
1722        require_once JETPACK__PLUGIN_DIR . 'modules/module-extras.php';
1723    }
1724
1725    /**
1726     * Check if Jetpack's REST API compat file should be included
1727     *
1728     * @action plugins_loaded
1729     * @return void
1730     */
1731    public function check_rest_api_compat() {
1732        /**
1733         * Filters the list of REST API compat files to be included.
1734         *
1735         * @since 2.2.5
1736         *
1737         * @param array $args Array of REST API compat files to include.
1738         */
1739        $_jetpack_rest_api_compat_includes = apply_filters( 'jetpack_rest_api_compat', array() );
1740
1741        foreach ( $_jetpack_rest_api_compat_includes as $_jetpack_rest_api_compat_include ) {
1742            require_once $_jetpack_rest_api_compat_include;
1743        }
1744    }
1745
1746    /**
1747     * Gets all plugins currently active in values, regardless of whether they're
1748     * traditionally activated or network activated.
1749     *
1750     * @todo Store the result in core's object cache maybe?
1751     */
1752    public static function get_active_plugins() {
1753        $active_plugins = (array) get_option( 'active_plugins', array() );
1754
1755        if ( is_multisite() ) {
1756            // Due to legacy code, active_sitewide_plugins stores them in the keys,
1757            // whereas active_plugins stores them in the values.
1758            $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
1759            if ( $network_plugins ) {
1760                $active_plugins = array_merge( $active_plugins, $network_plugins );
1761            }
1762        }
1763
1764        sort( $active_plugins );
1765
1766        return array_unique( $active_plugins );
1767    }
1768
1769    /**
1770     * Gets and parses additional plugin data to send with the heartbeat data
1771     *
1772     * @since 3.8.1
1773     *
1774     * @return array Array of plugin data
1775     */
1776    public static function get_parsed_plugin_data() {
1777        if ( ! function_exists( 'get_plugins' ) ) {
1778            require_once ABSPATH . 'wp-admin/includes/plugin.php';
1779        }
1780        /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
1781        $all_plugins    = apply_filters( 'all_plugins', get_plugins() );
1782        $active_plugins = self::get_active_plugins();
1783
1784        $plugins = array();
1785        foreach ( $all_plugins as $path => $plugin_data ) {
1786            $plugins[ $path ] = array(
1787                'is_active' => in_array( $path, $active_plugins, true ),
1788                'file'      => $path,
1789                'name'      => $plugin_data['Name'],
1790                'version'   => $plugin_data['Version'],
1791                'author'    => $plugin_data['Author'],
1792            );
1793        }
1794
1795        return $plugins;
1796    }
1797
1798    /**
1799     * Gets and parses theme data to send with the heartbeat data
1800     *
1801     * @since 3.8.1
1802     *
1803     * @return array Array of theme data
1804     */
1805    public static function get_parsed_theme_data() {
1806        $all_themes  = wp_get_themes( array( 'allowed' => true ) );
1807        $header_keys = array( 'Name', 'Author', 'Version', 'ThemeURI', 'AuthorURI', 'Status', 'Tags' );
1808
1809        $themes = array();
1810        foreach ( $all_themes as $slug => $theme_data ) {
1811            $theme_headers = array();
1812            foreach ( $header_keys as $header_key ) {
1813                $theme_headers[ $header_key ] = $theme_data->get( $header_key );
1814            }
1815
1816            $themes[ $slug ] = array(
1817                'is_active_theme' => wp_get_theme()->get_template() === $slug,
1818                'slug'            => $slug,
1819                'theme_root'      => $theme_data->get_theme_root_uri(),
1820                'parent'          => $theme_data->parent(),
1821                'headers'         => $theme_headers,
1822            );
1823        }
1824
1825        return $themes;
1826    }
1827
1828    /**
1829     * Checks whether a specific plugin is active.
1830     *
1831     * We don't want to store these in a static variable, in case
1832     * there are switch_to_blog() calls involved.
1833     *
1834     * @param string $plugin Plugin to check in 'folder/file.php` format.
1835     */
1836    public static function is_plugin_active( $plugin = 'jetpack/jetpack.php' ) {
1837        return in_array( $plugin, self::get_active_plugins(), true );
1838    }
1839
1840    /**
1841     * Check if Jetpack's Open Graph tags should be used.
1842     * If certain plugins are active, Jetpack's og tags are suppressed.
1843     *
1844     * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1845     * @action plugins_loaded
1846     * @return void
1847     */
1848    public function check_open_graph() {
1849        if ( in_array( 'publicize', self::get_active_modules(), true ) || in_array( 'sharedaddy', self::get_active_modules(), true ) ) {
1850            include_once JETPACK__PLUGIN_DIR . 'enhanced-open-graph.php';
1851            add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
1852        }
1853
1854        $active_plugins = self::get_active_plugins();
1855
1856        if ( ! empty( $active_plugins ) ) {
1857            foreach ( $this->open_graph_conflicting_plugins as $plugin ) {
1858                if ( in_array( $plugin, $active_plugins, true ) ) {
1859                    add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
1860                    break;
1861                }
1862            }
1863        }
1864
1865        /**
1866         * Allow the addition of Open Graph Meta Tags to all pages.
1867         *
1868         * @since 2.0.3
1869         *
1870         * @param bool false Should Open Graph Meta tags be added. Default to false.
1871         */
1872        if ( apply_filters( 'jetpack_enable_open_graph', false ) ) {
1873            require_once JETPACK__PLUGIN_DIR . 'functions.opengraph.php';
1874        }
1875    }
1876
1877    /**
1878     * Check if Jetpack's Twitter tags should be used.
1879     * If certain plugins are active, Jetpack's twitter tags are suppressed.
1880     *
1881     * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
1882     * @action plugins_loaded
1883     * @return void
1884     */
1885    public function check_twitter_tags() {
1886
1887        $active_plugins = self::get_active_plugins();
1888
1889        if ( ! empty( $active_plugins ) ) {
1890            foreach ( $this->twitter_cards_conflicting_plugins as $plugin ) {
1891                if ( in_array( $plugin, $active_plugins, true ) ) {
1892                    add_filter( 'jetpack_disable_twitter_cards', '__return_true', 99 );
1893                    break;
1894                }
1895            }
1896        }
1897
1898        /**
1899         * Allow Twitter Card Meta tags to be disabled.
1900         *
1901         * @since 2.6.0
1902         *
1903         * @param bool true Should Twitter Card Meta tags be disabled. Default to true.
1904         */
1905        if ( ! apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
1906            // @todo Remove this require once the deprecated Jetpack_Twitter_Cards wrapper has been removed.
1907            // Twitter Cards functionality now lives in the jetpack-post-media package (Automattic\Jetpack\Post_Media\Twitter_Cards).
1908            require_once JETPACK__PLUGIN_DIR . 'class.jetpack-twitter-cards.php';
1909        }
1910    }
1911
1912    /* Jetpack Options API */
1913
1914    /**
1915     * Gets the option names from Jetpack_Options.
1916     *
1917     * @param string $type Jetpack option type.
1918     *
1919     * @return array
1920     */
1921    public static function get_option_names( $type = 'compact' ) {
1922        return Jetpack_Options::get_option_names( $type );
1923    }
1924
1925    /**
1926     * Returns the requested option.
1927     *
1928     * Looks in jetpack_options or jetpack_$name as appropriate.
1929     *
1930     * @param string $name    Option name.
1931     * @param mixed  $default Default value.
1932     */
1933    public static function get_option( $name, $default = false ) {
1934        return Jetpack_Options::get_option( $name, $default );
1935    }
1936
1937    /**
1938     * Returns an array of all PHP files in the specified absolute path.
1939     * Equivalent to glob( "$absolute_path/*.php" ).
1940     *
1941     * @param string $absolute_path The absolute path of the directory to search.
1942     * @return array Array of absolute paths to the PHP files.
1943     */
1944    public static function glob_php( $absolute_path ) {
1945        return ( new Files() )->glob_php( $absolute_path );
1946    }
1947
1948    /**
1949     * Activate new modules.
1950     *
1951     * @param bool $redirect Should this function redirect after activation.
1952     *
1953     * @return void
1954     */
1955    public static function activate_new_modules( $redirect = false ) {
1956        if ( ! self::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
1957            return;
1958        }
1959
1960        $jetpack_old_version = Jetpack_Options::get_option( 'version' );
1961        if ( ! $jetpack_old_version ) {
1962            $old_version         = '1.1:' . time();
1963            $version             = $old_version;
1964            $jetpack_old_version = $version;
1965            /** This action is documented in class.jetpack.php */
1966            do_action( 'updating_jetpack_version', $version, false );
1967            Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
1968        }
1969
1970        list( $jetpack_version ) = explode( ':', $jetpack_old_version );
1971
1972        if ( version_compare( JETPACK__VERSION, $jetpack_version, '<=' ) ) {
1973            return;
1974        }
1975
1976        $active_modules     = self::get_active_modules();
1977        $reactivate_modules = array();
1978        foreach ( $active_modules as $active_module ) {
1979            $module = self::get_module( $active_module );
1980            if ( ! isset( $module['changed'] ) ) {
1981                continue;
1982            }
1983
1984            if ( version_compare( $module['changed'], $jetpack_version, '<=' ) ) {
1985                continue;
1986            }
1987
1988            $reactivate_modules[] = $active_module;
1989            self::deactivate_module( $active_module );
1990        }
1991
1992        $new_version = JETPACK__VERSION . ':' . time();
1993        /** This action is documented in class.jetpack.php */
1994        do_action( 'updating_jetpack_version', $new_version, $jetpack_old_version );
1995        Jetpack_Options::update_options(
1996            array(
1997                'version'     => $new_version,
1998                'old_version' => $jetpack_old_version,
1999            )
2000        );
2001
2002        self::state( 'message', 'modules_activated' );
2003
2004        self::activate_default_modules( $jetpack_version, JETPACK__VERSION, $reactivate_modules, $redirect );
2005
2006        if ( $redirect ) {
2007            $page = 'jetpack'; // make sure we redirect to either settings or the jetpack page.
2008            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.
2009                $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're not changing the site.
2010            }
2011            wp_safe_redirect( self::admin_url( 'page=' . rawurlencode( $page ) ) );
2012            exit( 0 );
2013        }
2014    }
2015
2016    /**
2017     * List available Jetpack modules. Simply lists .php files in /modules/.
2018     * Make sure to tuck away module "library" files in a sub-directory.
2019     *
2020     * @param bool|string $min_version Only return modules introduced in this version or later. Default is false, do not filter.
2021     * @param bool|string $max_version Only return modules introduced before this version. Default is false, do not filter.
2022     * @param bool|null   $requires_connection Pass a boolean value to only return modules that require (or do not require) a connection.
2023     * @param bool|null   $requires_user_connection Pass a boolean value to only return modules that require (or do not require) a user connection.
2024     *
2025     * @return array $modules Array of module slugs
2026     */
2027    public static function get_available_modules( $min_version = false, $max_version = false, $requires_connection = null, $requires_user_connection = null ) {
2028        return ( new Modules() )->get_available( $min_version, $max_version, $requires_connection, $requires_user_connection );
2029    }
2030
2031    /**
2032     * Get default modules loaded on activation.
2033     *
2034     * @param bool|string $min_version Only return modules introduced in this version or later. Default is false, do not filter.
2035     * @param bool|string $max_version Only return modules introduced before this version. Default is false, do not filter.
2036     * @param bool|null   $requires_connection Pass a boolean value to only return modules that require (or do not require) a connection.
2037     * @param bool|null   $requires_user_connection Pass a boolean value to only return modules that require (or do not require) a user connection.
2038     *
2039     * @return array $modules Array of module slugs
2040     */
2041    public static function get_default_modules( $min_version = false, $max_version = false, $requires_connection = null, $requires_user_connection = null ) {
2042        $return = array();
2043
2044        foreach ( self::get_available_modules( $min_version, $max_version, $requires_connection, $requires_user_connection ) as $module ) {
2045            $module_data = self::get_module( $module );
2046
2047            switch ( strtolower( $module_data['auto_activate'] ) ) {
2048                case 'yes':
2049                    $return[] = $module;
2050                    break;
2051                case 'public':
2052                    if ( Jetpack_Options::get_option( 'public' ) ) {
2053                        $return[] = $module;
2054                    }
2055                    break;
2056                case 'no':
2057                default:
2058                    break;
2059            }
2060        }
2061        /**
2062         * Filters the array of default modules.
2063         *
2064         * @since 2.5.0
2065         *
2066         * @param array $return Array of default modules.
2067         * @param string $min_version Minimum version number required to use modules.
2068         * @param string $max_version Maximum version number required to use modules.
2069         * @param bool|null $requires_connection Value of the Requires Connection filter.
2070         * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2071         */
2072        return apply_filters( 'jetpack_get_default_modules', $return, $min_version, $max_version, $requires_connection, $requires_user_connection );
2073    }
2074
2075    /**
2076     * Checks activated modules during auto-activation to determine
2077     * if any of those modules are being deprecated.  If so, close
2078     * them out, and add any replacement modules.
2079     *
2080     * Runs at priority 99 by default.
2081     *
2082     * This is run late, so that it can still activate a module if
2083     * the new module is a replacement for another that the user
2084     * currently has active, even if something at the normal priority
2085     * would kibosh everything.
2086     *
2087     * @since 2.6
2088     * @uses jetpack_get_default_modules filter
2089     * @param array $modules Array of Jetpack modules.
2090     * @return array
2091     */
2092    public function handle_deprecated_modules( $modules ) {
2093        $deprecated_modules = array(
2094            'debug'                 => null,  // Closed out and moved to the debugger library.
2095            'wpcc'                  => 'sso', // Closed out in 2.6 -- SSO provides the same functionality.
2096            'gplus-authorship'      => null,  // Closed out in 3.2 -- Google dropped support.
2097            'minileven'             => null,  // Closed out in 8.3 -- Responsive themes are common now, and so is AMP.
2098            'lazy-images'           => null, // Closed out in 12.8 -- WordPress core now has native lazy loading.
2099            'enhanced-distribution' => null, // Closed out in 13.3 -- WP.com is winding down the firehose.
2100        );
2101
2102        // Don't activate SSO if they never completed activating WPCC.
2103        if ( self::is_module_active( 'wpcc' ) ) {
2104            $wpcc_options = Jetpack_Options::get_option( 'wpcc_options' );
2105            if ( empty( $wpcc_options ) || empty( $wpcc_options['client_id'] ) || empty( $wpcc_options['client_id'] ) ) {
2106                $deprecated_modules['wpcc'] = null;
2107            }
2108        }
2109
2110        foreach ( $deprecated_modules as $module => $replacement ) {
2111            if ( self::is_module_active( $module ) ) {
2112                self::deactivate_module( $module );
2113                if ( $replacement ) {
2114                    $modules[] = $replacement;
2115                }
2116            }
2117        }
2118
2119        return array_unique( $modules );
2120    }
2121
2122    /**
2123     * Checks activated plugins during auto-activation to determine
2124     * if any of those plugins are in the list with a corresponding module
2125     * that is not compatible with the plugin. The module will not be allowed
2126     * to auto-activate.
2127     *
2128     * @since 2.6
2129     * @uses jetpack_get_default_modules filter
2130     * @param array $modules Array of Jetpack modules.
2131     * @return array
2132     */
2133    public function filter_default_modules( $modules ) {
2134
2135        $active_plugins = self::get_active_plugins();
2136
2137        if ( ! empty( $active_plugins ) ) {
2138
2139            // For each module we'd like to auto-activate...
2140            foreach ( $modules as $key => $module ) {
2141                // If there are potential conflicts for it...
2142                if ( ! empty( $this->conflicting_plugins[ $module ] ) ) {
2143                    // For each potential conflict...
2144                    foreach ( $this->conflicting_plugins[ $module ] as $plugin ) {
2145                        // If that conflicting plugin is active...
2146                        if ( in_array( $plugin, $active_plugins, true ) ) {
2147                            // Remove that item from being auto-activated.
2148                            unset( $modules[ $key ] );
2149                        }
2150                    }
2151                }
2152            }
2153        }
2154
2155        // Special case to convert block setting to a block module.
2156        $block_key = array_search( 'blocks', $modules, true );
2157        if ( $block_key !== false ) { // Only care if 'blocks' made it through the previous filters.
2158            $block_option = get_option( 'jetpack_blocks_disabled', null );
2159            if ( $block_option ) {
2160                unset( $modules[ $block_key ] );
2161            }
2162        }
2163
2164        return $modules;
2165    }
2166
2167    /**
2168     * Extract a module's slug from its full path.
2169     *
2170     * @param string $file Full path to a file.
2171     *
2172     * @return string Module slug.
2173     */
2174    public static function get_module_slug( $file ) {
2175        return ( new Modules() )->get_slug( $file );
2176    }
2177
2178    /**
2179     * Generate a module's path from its slug.
2180     *
2181     * @param string $slug Module slug.
2182     */
2183    public static function get_module_path( $slug ) {
2184        return ( new Modules() )->get_path( $slug );
2185    }
2186
2187    /**
2188     * Load module data from module file. Headers differ from WordPress
2189     * plugin headers to avoid them being identified as standalone
2190     * plugins on the WordPress plugins page.
2191     *
2192     * @param string $module The module slug.
2193     */
2194    public static function get_module( $module ) {
2195        return ( new Modules() )->get( $module );
2196    }
2197
2198    /**
2199     * Like core's get_file_data implementation, but caches the result.
2200     *
2201     * @param string $file Absolute path to the file.
2202     * @param array  $headers List of headers, in the format array( 'HeaderKey' => 'Header Name' ).
2203     */
2204    public static function get_file_data( $file, $headers ) {
2205        return ( new Modules() )->get_file_data( $file, $headers );
2206    }
2207
2208    /**
2209     * Return translated module tag.
2210     *
2211     * @param string $tag Tag as it appears in each module heading.
2212     *
2213     * @return mixed
2214     */
2215    public static function translate_module_tag( $tag ) {
2216        return jetpack_get_module_i18n_tag( $tag );
2217    }
2218
2219    /**
2220     * Return module name translation. Uses matching string created in modules/module-headings.php.
2221     *
2222     * @since 3.9.2
2223     *
2224     * @param array $modules Array of Jetpack modules.
2225     *
2226     * @return string|void
2227     */
2228    public static function get_translated_modules( $modules ) {
2229        foreach ( $modules as $index => $module ) {
2230            $i18n_module = jetpack_get_module_i18n( $module['module'] );
2231            if ( isset( $module['name'] ) ) {
2232                $modules[ $index ]['name'] = $i18n_module['name'];
2233            }
2234            if ( isset( $module['description'] ) ) {
2235                $modules[ $index ]['description']       = $i18n_module['description'];
2236                $modules[ $index ]['short_description'] = $i18n_module['description'];
2237            }
2238            if ( isset( $module['module_tags'] ) ) {
2239                $modules[ $index ]['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
2240            }
2241        }
2242        return $modules;
2243    }
2244
2245    /**
2246     * Get a list of activated modules as an array of module slugs.
2247     */
2248    public static function get_active_modules() {
2249        return ( new Modules() )->get_active();
2250    }
2251
2252    /**
2253     * Check whether or not a Jetpack module is active.
2254     *
2255     * @param string $module The slug of a Jetpack module.
2256     * @return bool
2257     *
2258     * @static
2259     */
2260    public static function is_module_active( $module ) {
2261        return ( new Modules() )->is_active( $module );
2262    }
2263
2264    /**
2265     * Is slug a valid module.
2266     *
2267     * @param string $module Module slug.
2268     *
2269     * @return bool
2270     */
2271    public static function is_module( $module ) {
2272        return ( new Modules() )->is_module( $module );
2273    }
2274
2275    /**
2276     * Catches PHP errors.  Must be used in conjunction with output buffering.
2277     *
2278     * @deprecated since 13.5
2279     * @param bool $catch True to start catching, False to stop.
2280     *
2281     * @static
2282     * @deprecated 13.5
2283     * @see \Automattic\Jetpack\Errors
2284     */
2285    public static function catch_errors( $catch ) {
2286        _deprecated_function( __METHOD__, '13.5' );
2287        // @phan-suppress-next-line PhanDeprecatedClass
2288        return ( new Errors() )->catch_errors( $catch );
2289    }
2290
2291    /**
2292     * Saves any generated PHP errors in ::state( 'php_errors', {errors} )
2293     *
2294     * @deprecated since 13.5
2295     */
2296    public static function catch_errors_on_shutdown() {
2297        _deprecated_function( __METHOD__, '13.5' );
2298        self::state( 'php_errors', self::alias_directories( ob_get_clean() ) );
2299    }
2300
2301    /**
2302     * Rewrite any string to make paths easier to read.
2303     *
2304     * Rewrites ABSPATH (eg `/home/jetpack/wordpress/`) to ABSPATH, and if WP_CONTENT_DIR
2305     * is located outside of ABSPATH, rewrites that to WP_CONTENT_DIR.
2306     *
2307     * @param string $string String to attempt rewrite.
2308     * @return mixed
2309     */
2310    public static function alias_directories( $string ) {
2311        // ABSPATH has a trailing slash.
2312        $string = str_replace( ABSPATH, 'ABSPATH/', $string );
2313        // WP_CONTENT_DIR does not have a trailing slash.
2314        $string = str_replace( WP_CONTENT_DIR, 'WP_CONTENT_DIR', $string );
2315
2316        return $string;
2317    }
2318
2319    /**
2320     * Activates default Jetpack modules.
2321     *
2322     * @param null|string $min_version Only return modules introduced in this version or later. Default is false, do not filter.
2323     * @param null|string $max_version Only return modules introduced before this version. Default is false, do not filter.
2324     * @param array       $other_modules Other modules to activate.
2325     * @param null|bool   $redirect Should there be a redirection after activation.
2326     * @param bool        $send_state_messages If a state message should be sent.
2327     * @param bool|null   $requires_connection Pass a boolean value to only return modules that require (or do not require) a connection.
2328     * @param bool|null   $requires_user_connection Pass a boolean value to only return modules that require (or do not require) a user connection.
2329     *
2330     * @return void
2331     */
2332    public static function activate_default_modules(
2333        $min_version = false,
2334        $max_version = false,
2335        $other_modules = array(),
2336        $redirect = null,
2337        $send_state_messages = null,
2338        $requires_connection = null,
2339        $requires_user_connection = null
2340    ) {
2341        $jetpack = self::init();
2342
2343        if ( $redirect === null ) {
2344            if (
2345                ( defined( 'REST_REQUEST' ) && REST_REQUEST )
2346            ||
2347                ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
2348            ||
2349                ( defined( 'WP_CLI' ) && WP_CLI )
2350            ||
2351                ( defined( 'DOING_CRON' ) && DOING_CRON )
2352            ||
2353                ( defined( 'DOING_AJAX' ) && DOING_AJAX )
2354            ) {
2355                $redirect = false;
2356            } elseif ( is_admin() ) {
2357                $redirect = true;
2358            } else {
2359                $redirect = false;
2360            }
2361        }
2362
2363        if ( $send_state_messages === null ) {
2364            $send_state_messages = current_user_can( 'jetpack_activate_modules' );
2365        }
2366
2367        $modules = self::get_default_modules( $min_version, $max_version, $requires_connection, $requires_user_connection );
2368        $modules = array_merge( $other_modules, $modules );
2369
2370        // Look for standalone plugins and disable if active.
2371
2372        $to_deactivate = array();
2373        foreach ( $modules as $module ) {
2374            if ( isset( $jetpack->plugins_to_deactivate[ $module ] ) ) {
2375                $to_deactivate[ $module ] = $jetpack->plugins_to_deactivate[ $module ];
2376            }
2377        }
2378
2379        $deactivated = array();
2380        foreach ( $to_deactivate as $module => $deactivate_us ) {
2381            foreach ( $deactivate_us as $i => $deactivate_me ) {
2382                list( $probable_file, $probable_title ) = $deactivate_me;
2383                if ( Jetpack_Client_Server::deactivate_plugin( $probable_file, $probable_title ) ) {
2384                    $deactivated[] = "$module:$i";
2385                }
2386            }
2387        }
2388
2389        if ( $deactivated ) {
2390            if ( $send_state_messages ) {
2391                self::state( 'deactivated_plugins', implode( ',', $deactivated ) );
2392            }
2393
2394            if ( $redirect ) {
2395                $url = add_query_arg(
2396                    array(
2397                        'action'   => 'activate_default_modules',
2398                        '_wpnonce' => wp_create_nonce( 'activate_default_modules' ),
2399                    ),
2400                    add_query_arg( compact( 'min_version', 'max_version', 'other_modules' ), self::admin_url( 'page=jetpack' ) )
2401                );
2402                wp_safe_redirect( $url );
2403                exit( 0 );
2404            }
2405        }
2406
2407        /**
2408         * Fires before default modules are activated.
2409         *
2410         * @since 1.9.0
2411         *
2412         * @param string    $min_version Minimum version number required to use modules.
2413         * @param string    $max_version Maximum version number required to use modules.
2414         * @param array     $other_modules Array of other modules to activate alongside the default modules.
2415         * @param bool|null $requires_connection Value of the Requires Connection filter.
2416         * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2417         */
2418        do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules, $requires_connection, $requires_user_connection );
2419
2420        // Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating.
2421        if ( $send_state_messages ) {
2422            self::restate();
2423        }
2424
2425        $active = self::get_active_modules();
2426
2427        foreach ( $modules as $module ) {
2428            if ( did_action( "jetpack_module_loaded_$module" ) ) {
2429                $active[] = $module;
2430                self::update_active_modules( $active );
2431                continue;
2432            }
2433
2434            if ( $send_state_messages && in_array( $module, $active, true ) ) {
2435                $module_info = self::get_module( $module );
2436                if ( ! $module_info['deactivate'] ) {
2437                    $state        = in_array( $module, $other_modules, true ) ? 'reactivated_modules' : 'activated_modules';
2438                    $active_state = self::state( $state );
2439                    if ( $active_state ) {
2440                        $active_state = explode( ',', $active_state );
2441                    } else {
2442                        $active_state = array();
2443                    }
2444                    $active_state[] = $module;
2445                    self::state( $state, implode( ',', $active_state ) );
2446                }
2447                continue;
2448            }
2449
2450            $file = self::get_module_path( $module );
2451            if ( ! file_exists( $file ) ) {
2452                continue;
2453            }
2454
2455            // we'll override this later if the plugin can be included without fatal error.
2456            if ( $redirect ) {
2457                wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
2458            }
2459
2460            if ( $send_state_messages ) {
2461                self::state( 'error', 'module_activation_failed' );
2462                self::state( 'module', $module );
2463            }
2464
2465            ob_start();
2466            require_once $file;
2467
2468            $active[] = $module;
2469
2470            if ( $send_state_messages ) {
2471
2472                $state        = in_array( $module, $other_modules, true ) ? 'reactivated_modules' : 'activated_modules';
2473                $active_state = self::state( $state );
2474                if ( $active_state ) {
2475                    $active_state = explode( ',', $active_state );
2476                } else {
2477                    $active_state = array();
2478                }
2479                $active_state[] = $module;
2480                self::state( $state, implode( ',', $active_state ) );
2481            }
2482
2483            self::update_active_modules( $active );
2484
2485            ob_end_clean();
2486        }
2487
2488        if ( $send_state_messages ) {
2489            self::state( 'error', false );
2490            self::state( 'module', false );
2491        }
2492        /**
2493         * Fires when default modules are activated.
2494         *
2495         * @since 1.9.0
2496         *
2497         * @param string    $min_version Minimum version number required to use modules.
2498         * @param string    $max_version Maximum version number required to use modules.
2499         * @param array     $other_modules Array of other modules to activate alongside the default modules.
2500         * @param bool|null $requires_connection Value of the Requires Connection filter.
2501         * @param bool|null $requires_user_connection Value of the Requires User Connection filter.
2502         */
2503        do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules, $requires_connection, $requires_user_connection );
2504    }
2505
2506    /**
2507     * Activate a module.
2508     *
2509     * @param string $module Module slug.
2510     * @param bool   $exit Should exit be called after deactivation.
2511     * @param bool   $redirect Should there be a redirection after activation.
2512     *
2513     * @return bool|void
2514     */
2515    public static function activate_module( $module, $exit = true, $redirect = true ) {
2516        return ( new Modules() )->activate( $module, $exit, $redirect );
2517    }
2518
2519    /**
2520     * Deactivate module.
2521     *
2522     * @param string $module Module slug.
2523     *
2524     * @return bool
2525     */
2526    public static function deactivate_module( $module ) {
2527        return ( new Modules() )->deactivate( $module );
2528    }
2529
2530    /**
2531     * Enable a configuable module.
2532     *
2533     * @param string $module Module slug.
2534     *
2535     * @return void
2536     */
2537    public static function enable_module_configurable( $module ) {
2538        $module = self::get_module_slug( $module );
2539        add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
2540    }
2541
2542    /**
2543     * Composes a module configure URL. It uses Jetpack settings search as default value
2544     * It is possible to redefine resulting URL by using "jetpack_module_configuration_url_$module" filter
2545     *
2546     * @param string $module Module slug.
2547     * @return string $url module configuration URL.
2548     */
2549    public static function module_configuration_url( $module ) {
2550        $module      = self::get_module_slug( $module );
2551        $default_url = self::admin_url() . "#/settings?term=$module";
2552        /**
2553         * Allows to modify configure_url of specific module to be able to redirect to some custom location.
2554         *
2555         * @since 6.9.0
2556         *
2557         * @param string $default_url Default url, which redirects to jetpack settings page.
2558         */
2559        $url = apply_filters( 'jetpack_module_configuration_url_' . $module, $default_url );
2560
2561        return $url;
2562    }
2563
2564    /* Installation */
2565    /**
2566     * Bail on activation if there is an issue.
2567     *
2568     * @param string $message Error message.
2569     * @param bool   $deactivate Deactivate Jetpack or not.
2570     *
2571     * @return never
2572     */
2573    public static function bail_on_activation( $message, $deactivate = true ) {
2574        ?>
2575<!doctype html>
2576<html>
2577<head>
2578<meta charset="<?php bloginfo( 'charset' ); ?>">
2579<style>
2580* {
2581    text-align: center;
2582    margin: 0;
2583    padding: 0;
2584    font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
2585}
2586p {
2587    margin-top: 1em;
2588    font-size: 18px;
2589}
2590</style>
2591<body>
2592<p><?php echo esc_html( $message ); ?></p>
2593</body>
2594</html>
2595        <?php
2596        if ( $deactivate ) {
2597            $plugins = get_option( 'active_plugins' );
2598            $jetpack = plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' );
2599            $update  = false;
2600            foreach ( $plugins as $i => $plugin ) {
2601                if ( $plugin === $jetpack ) {
2602                    $plugins[ $i ] = false;
2603                    $update        = true;
2604                }
2605            }
2606
2607            if ( $update ) {
2608                update_option( 'active_plugins', array_filter( $plugins ) );
2609            }
2610        }
2611        exit( 0 );
2612    }
2613
2614    /**
2615     * Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
2616     *
2617     * @param bool $network_wide Network-wide activation.
2618     */
2619    public static function plugin_activation( $network_wide ) {
2620        Jetpack_Options::update_option( 'activated', 1 );
2621
2622        if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
2623            /* translators: Jetpack version number. */
2624            self::bail_on_activation( sprintf( __( 'Jetpack requires WordPress version %s or later.', 'jetpack' ), JETPACK__MINIMUM_WP_VERSION ) );
2625        }
2626
2627        if ( $network_wide ) {
2628            self::state( 'network_nag', true );
2629        }
2630
2631        // For firing one-off events (notices) immediately after activation.
2632        set_transient( 'activated_jetpack', true, 0.1 * MINUTE_IN_SECONDS );
2633
2634        update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) );
2635
2636        Health::on_jetpack_activated();
2637
2638        if ( self::is_connection_ready() && method_exists( 'Automattic\Jetpack\Sync\Actions', 'do_only_first_initial_sync' ) ) {
2639            Sync_Actions::do_only_first_initial_sync();
2640        }
2641
2642        if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2643            Woocommerce_Analytics::maybe_add_proxy_speed_module();
2644        }
2645
2646        self::plugin_initialize();
2647    }
2648
2649    /**
2650     * Returns the activation source.
2651     *
2652     * @param string $referer_url URL.
2653     *
2654     * @return array source_type, source_query.
2655     */
2656    public static function get_activation_source( $referer_url ) {
2657        if ( defined( 'WP_CLI' ) && WP_CLI ) {
2658            return array( 'wp-cli', null );
2659        }
2660
2661        $referer = wp_parse_url( $referer_url );
2662
2663        $source_type  = 'unknown';
2664        $source_query = null;
2665
2666        if ( ! is_array( $referer ) || ! isset( $referer['path'] ) ) {
2667            return array( $source_type, $source_query );
2668        }
2669
2670        $plugins_path         = wp_parse_url( admin_url( 'plugins.php' ), PHP_URL_PATH );
2671        $plugins_install_path = wp_parse_url( admin_url( 'plugin-install.php' ), PHP_URL_PATH );// /wp-admin/plugin-install.php
2672
2673        if ( isset( $referer['query'] ) ) {
2674            parse_str( $referer['query'], $query_parts );
2675        } else {
2676            $query_parts = array();
2677        }
2678
2679        if ( $plugins_path === $referer['path'] ) {
2680            $source_type = 'list';
2681        } elseif ( $plugins_install_path === $referer['path'] ) {
2682            $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured';
2683            switch ( $tab ) {
2684                case 'popular':
2685                    $source_type = 'popular';
2686                    break;
2687                case 'recommended':
2688                    $source_type = 'recommended';
2689                    break;
2690                case 'favorites':
2691                    $source_type = 'favorites';
2692                    break;
2693                case 'search':
2694                    $source_type  = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' );
2695                    $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null;
2696                    break;
2697                default:
2698                    $source_type = 'featured';
2699            }
2700        }
2701
2702        return array( $source_type, $source_query );
2703    }
2704
2705    /**
2706     * Runs before bumping version numbers up to a new version
2707     *
2708     * @param string $version    Version:timestamp.
2709     * @param string $old_version Old Version:timestamp or false if not set yet.
2710     */
2711    public static function do_version_bump( $version, $old_version ) {
2712        if ( $old_version ) { // For existing Jetpack installations.
2713            add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
2714
2715            // If a front end page is visited after the update, the 'wp' action will fire.
2716            add_action( 'wp', 'Jetpack::set_update_modal_display' );
2717
2718            // If an admin page is visited after the update, the 'current_screen' action will fire.
2719            add_action( 'current_screen', 'Jetpack::set_update_modal_display' );
2720        }
2721    }
2722
2723    /**
2724     * Sets the display_update_modal state.
2725     */
2726    public static function set_update_modal_display() {
2727        self::state( 'display_update_modal', true );
2728    }
2729
2730    /**
2731     * Enqueues the block library styles.
2732     *
2733     * @param string $hook The current admin page.
2734     */
2735    public static function enqueue_block_style( $hook ) {
2736        if ( 'toplevel_page_jetpack' === $hook ) {
2737            wp_enqueue_style( 'wp-block-library' );
2738        }
2739    }
2740
2741    /**
2742     * Sets the internal version number and activation state.
2743     *
2744     * @static
2745     */
2746    public static function plugin_initialize() {
2747        if ( ! Jetpack_Options::get_option( 'activated' ) ) {
2748            Jetpack_Options::update_option( 'activated', 2 );
2749        }
2750
2751        if ( ! Jetpack_Options::get_option( 'version' ) ) {
2752            $old_version = JETPACK__VERSION . ':' . time();
2753            $version     = $old_version;
2754            /** This action is documented in class.jetpack.php */
2755            do_action( 'updating_jetpack_version', $version, false );
2756            Jetpack_Options::update_options( compact( 'version', 'old_version' ) );
2757        }
2758
2759        if ( self::is_connection_ready() ) {
2760            self::handle_default_module_activation( true );
2761        }
2762
2763        self::load_modules();
2764
2765        Jetpack_Options::delete_option( 'do_activate' );
2766    }
2767
2768    /**
2769     * Handles the activation of the default modules depending on the current state of the site:
2770     *  - If the site already has the jetpack_active_modules option, activate those.
2771     *  - If the site has a site-only connection, only activate the default modules that require only a site connection.
2772     *  - If the site has a user connection, activate the default modules that require a user connection.
2773     *
2774     * @param bool $should_activate_user_modules Whether the status of the user connection should be checked and the default modules that
2775     *                                           require a user connection activated.
2776     */
2777    private static function handle_default_module_activation( $should_activate_user_modules ) {
2778        $active_modules = Jetpack_Options::get_option( 'active_modules' );
2779        if ( $active_modules ) {
2780            self::delete_active_modules();
2781
2782            /**
2783             * Previously active modules could mean two things. First, it could mean
2784             * that Jetpack was previously active on the site. In this case we would like
2785             * to only activate the modules that were set to active.
2786             * Another case could be that the module option was set by a standalone
2787             * plugin. In that case the `active_modules_initalized` option will not
2788             * be set, so we need to enable default Jetpack modules as well.
2789             */
2790            if ( ! Jetpack_Options::get_option( 'active_modules_initialized' ) ) {
2791                $default_modules = self::get_default_modules();
2792                $active_modules  = array_merge( $active_modules, $default_modules );
2793                Jetpack_Options::update_option( 'active_modules_initialized', true );
2794            }
2795
2796            self::activate_default_modules(
2797                999, // This version trick basically excludes every default module.
2798                1,
2799                $active_modules,
2800                false
2801            );
2802        } elseif ( $should_activate_user_modules && ( new Connection_Manager() )->get_connection_owner_id() ) { // Check for a user connection.
2803            self::activate_default_modules( false, false, array(), false, null, null, null );
2804            Jetpack_Options::update_option( 'active_modules_initialized', true );
2805        } else {
2806            self::activate_default_modules( false, false, array(), false, null, null, false );
2807        }
2808    }
2809
2810    /**
2811     * Removes all connection options
2812     *
2813     * @static
2814     */
2815    public static function plugin_deactivation() {
2816        require_once ABSPATH . '/wp-admin/includes/plugin.php';
2817        $tracking = new Tracking();
2818        $tracking->record_user_event( 'deactivate_plugin', array() );
2819        if ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
2820            Jetpack_Network::init()->deactivate();
2821        } else {
2822            add_filter( 'jetpack_update_activated_state_on_disconnect', '__return_false' );
2823            self::disconnect();
2824            Jetpack_Options::delete_option( 'version' );
2825        }
2826
2827        if ( ! defined( 'WC_ANALYTICS' ) && class_exists( 'Automattic\Woocommerce_Analytics' ) ) {
2828            Woocommerce_Analytics::maybe_remove_proxy_speed_module();
2829        }
2830    }
2831
2832    /**
2833     * Set activated option to 4 on jetpack_idc_disconnect action.
2834     */
2835    public static function on_idc_disconnect() {
2836        \Jetpack_Options::update_option( 'activated', 4 );
2837    }
2838
2839    /**
2840     * Disconnects from the Jetpack servers.
2841     * Forgets all connection details and tells the Jetpack servers to do the same.
2842     *
2843     * Will not disconnect if there are other plugins using the connection.
2844     *
2845     * @since 11.0 Do not disconnect if other plugins are using the connection.
2846     *
2847     * @static
2848     */
2849    public static function disconnect() {
2850
2851        $connection = self::connection();
2852
2853        // If the site is in an IDC because sync is not allowed,
2854        // let's make sure to not disconnect the production site.
2855        $connection->remove_connection( ! Identity_Crisis::validate_sync_error_idc_option() );
2856    }
2857
2858    /**
2859     * Happens after a successful disconnection.
2860     *
2861     * @static
2862     */
2863    public static function jetpack_site_disconnected() {
2864        Identity_Crisis::clear_all_idc_options();
2865
2866        // Delete all the sync related data. Since it could be taking up space.
2867        Sender::get_instance()->uninstall();
2868
2869        /**
2870         * Filters whether the Jetpack activated state should be updated after disconnecting.
2871         *
2872         * @since 10.0.0
2873         *
2874         * @param bool $update_activated_state Whether activated state should be updated after disconnecting, defaults to true.
2875         */
2876        $update_activated_state = apply_filters( 'jetpack_update_activated_state_on_disconnect', true );
2877
2878        if ( $update_activated_state ) {
2879            Jetpack_Options::update_option( 'activated', 4 );
2880        }
2881    }
2882
2883    /**
2884     * Disconnects the user.
2885     *
2886     * @deprecated 13.4
2887     * @see \Automattic\Jetpack\Connection\Manager::disconnect_user()
2888     *
2889     * @param int $user_id The user ID to disconnect.
2890     */
2891    public function disconnect_user( $user_id ) {
2892        $this->connection_manager->disconnect_user( $user_id );
2893    }
2894
2895    /**
2896     * Checking the domain names in beta versions.
2897     * If this is a development version, before attempting to connect, let's make sure that the domains are viable.
2898     *
2899     * @param null|\WP_Error $error The domain validation error, or `null` if everything's fine.
2900     *
2901     * @return null|\WP_Error The domain validation error, or `null` if everything's fine.
2902     */
2903    public static function registration_check_domains( $error ) {
2904        if ( static::is_development_version() ) {
2905            $domains_to_check = array_unique(
2906                array(
2907                    'siteurl' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
2908                    'homeurl' => wp_parse_url( get_home_url(), PHP_URL_HOST ),
2909                )
2910            );
2911            foreach ( $domains_to_check as $domain ) {
2912                $result = static::connection()->is_usable_domain( $domain );
2913                if ( is_wp_error( $result ) ) {
2914                    return $result;
2915                }
2916            }
2917        }
2918
2919        return $error;
2920    }
2921
2922    /**
2923     * Tracking an internal event log. Try not to put too much chaff in here.
2924     *
2925     * [Everyone Loves a Log!](https://www.youtube.com/watch?v=2C7mNr5WMjA)
2926     *
2927     * @param mixed $code Error code to log.
2928     * @param mixed $data Data to log.
2929     */
2930    public static function log( $code, $data = null ) {
2931
2932        $raw_log = Jetpack_Options::get_option( 'log', array() );
2933        // This can be modified by the `jetpack_options` filter, so abort if we don't have an array.
2934        if ( ! is_array( $raw_log ) ) {
2935            return;
2936        }
2937
2938        // only grab the latest 200 entries.
2939        $log = array_slice( $raw_log, -199, 199 );
2940
2941        // Append our event to the log.
2942        $log_entry = array(
2943            'time'    => time(),
2944            'user_id' => get_current_user_id(),
2945            'blog_id' => Jetpack_Options::get_option( 'id' ),
2946            'code'    => $code,
2947        );
2948        // Don't bother storing it unless we've got some.
2949        if ( $data !== null ) {
2950            $log_entry['data'] = $data;
2951        }
2952        $log[] = $log_entry;
2953
2954        // Try add_option first, to make sure it's not autoloaded.
2955        // @todo: Add an add_option method to Jetpack_Options.
2956        if ( ! add_option( 'jetpack_log', $log, '', 'no' ) ) {
2957            Jetpack_Options::update_option( 'log', $log );
2958        }
2959
2960        /**
2961         * Fires when Jetpack logs an internal event.
2962         *
2963         * @since 3.0.0
2964         *
2965         * @param array $log_entry {
2966         *  Array of details about the log entry.
2967         *
2968         *  @param string time Time of the event.
2969         *  @param int user_id ID of the user who trigerred the event.
2970         *  @param int blog_id Jetpack Blog ID.
2971         *  @param string code Unique name for the event.
2972         *  @param string data Data about the event.
2973         * }
2974         */
2975        do_action( 'jetpack_log_entry', $log_entry );
2976    }
2977
2978    /**
2979     * Get the internal event log.
2980     *
2981     * @param string $event only return the specific log events.
2982     * @param int    $num - get specific number of latest results, limited to 200.
2983     *
2984     * @return array of log events || WP_Error for invalid params
2985     */
2986    public static function get_log( $event = false, $num = false ) {
2987        if ( $event && ! is_string( $event ) ) {
2988            return new WP_Error( __( 'First param must be string or empty', 'jetpack' ) );
2989        }
2990
2991        if ( $num && ! is_numeric( $num ) ) {
2992            return new WP_Error( __( 'Second param must be numeric or empty', 'jetpack' ) );
2993        }
2994
2995        $entire_log = Jetpack_Options::get_option( 'log', array() );
2996
2997        // If nothing set - act as it did before, otherwise let's start customizing the output.
2998        if ( ! $num && ! $event ) {
2999            return $entire_log;
3000        } else {
3001            $entire_log = array_reverse( $entire_log );
3002        }
3003
3004        $custom_log_output = array();
3005
3006        if ( $event ) {
3007            foreach ( $entire_log as $log_event ) {
3008                if ( $event === $log_event['code'] ) {
3009                    $custom_log_output[] = $log_event;
3010                }
3011            }
3012        } else {
3013            $custom_log_output = $entire_log;
3014        }
3015
3016        if ( $num ) {
3017            $custom_log_output = array_slice( $custom_log_output, 0, $num );
3018        }
3019
3020        return $custom_log_output;
3021    }
3022
3023    /**
3024     * Log modification of important settings.
3025     *
3026     * @param string $option Option name.
3027     * @param string $old_value Old value of option.
3028     * @param string $value New value of option.
3029     */
3030    public static function log_settings_change( $option, $old_value, $value ) {
3031        switch ( $option ) {
3032            case 'jetpack_sync_non_public_post_stati':
3033                self::log( $option, $value );
3034                break;
3035        }
3036    }
3037
3038    /**
3039     * Return stat data for WPCOM sync.
3040     *
3041     * @param bool $encode JSON encode the result.
3042     * @param bool $extended Adds additional stats data.
3043     *
3044     * @return array|string Stats data. Array if $encode is false. JSON-encoded string is $encode is true.
3045     */
3046    public static function get_stat_data( $encode = true, $extended = true ) {
3047        $data = Jetpack_Heartbeat::generate_stats_array();
3048
3049        if ( $extended ) {
3050            $additional_data = self::get_additional_stat_data();
3051            $data            = array_merge( $data, $additional_data );
3052        }
3053
3054        if ( $encode ) {
3055            return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
3056        }
3057
3058        return $data;
3059    }
3060
3061    /**
3062     * Get additional stat data to sync to WPCOM
3063     *
3064     * @param string $prefix Stats prefix.
3065     *
3066     * @return array stats values.
3067     */
3068    public static function get_additional_stat_data( $prefix = '' ) {
3069        $return                             = array();
3070        $return[ "{$prefix}themes" ]        = self::get_parsed_theme_data();
3071        $return[ "{$prefix}plugins-extra" ] = self::get_parsed_plugin_data();
3072        $return[ "{$prefix}users" ]         = (int) self::get_site_user_count();
3073        $return[ "{$prefix}site-count" ]    = 0;
3074
3075        if ( function_exists( 'get_blog_count' ) ) {
3076            $return[ "{$prefix}site-count" ] = get_blog_count();
3077        }
3078        return $return;
3079    }
3080
3081    /**
3082     * Get current site's user count.
3083     *
3084     * @return int|string|null Number of users on the site. -1 for a large network.
3085     */
3086    private static function get_site_user_count() {
3087        global $wpdb;
3088
3089        if ( function_exists( 'wp_is_large_network' ) ) {
3090            if ( wp_is_large_network( 'users' ) ) {
3091                return -1; // Not a real value but should tell us that we are dealing with a large network.
3092            }
3093        }
3094        $user_count = get_transient( 'jetpack_site_user_count' );
3095        if ( false === ( $user_count ) ) {
3096            // It wasn't there, so regenerate the data and save the transient.
3097            $user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities'" );
3098            set_transient( 'jetpack_site_user_count', $user_count, DAY_IN_SECONDS );
3099        }
3100        return $user_count;
3101    }
3102
3103    /* Admin Pages */
3104
3105    /**
3106     * Admin init function.
3107     *
3108     * Runs on admin_init hook.
3109     *
3110     * @return void
3111     */
3112    public function admin_init() {
3113        // If the plugin is not connected, display a connect message.
3114        if (
3115            // the plugin was auto-activated and needs its candy.
3116            Jetpack_Options::get_option_and_ensure_autoload( 'do_activate', '0' )
3117        ||
3118            // the plugin is active, but was never activated.  Probably came from a site-wide network activation.
3119            ! Jetpack_Options::get_option( 'activated' )
3120        ) {
3121            self::plugin_initialize();
3122        }
3123
3124        $is_offline_mode              = ( new Status() )->is_offline_mode();
3125        $fallback_no_verify_ssl_certs = Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
3126        /** Already documented in automattic/jetpack-connection::src/class-client.php */
3127        $client_verify_ssl_certs = apply_filters( 'jetpack_client_verify_ssl_certs', false );
3128
3129        // Run post-activation actions if needed.
3130        $this->plugin_post_activation();
3131
3132        if ( ( self::is_connection_ready() || $is_offline_mode ) && false === $fallback_no_verify_ssl_certs && ! $client_verify_ssl_certs ) {
3133            // Upgrade: 1.1 -> 1.1.1
3134            // Check and see if host can verify the Jetpack servers' SSL certificate.
3135            $args = array();
3136            // @phan-suppress-next-line PhanAccessMethodInternal -- Phan is correct, but the usage is intentional.
3137            Client::_wp_remote_request( self::connection()->api_url( 'test' ), $args, true );
3138        }
3139
3140        if (
3141            current_user_can( 'manage_options' )
3142            && ! self::permit_ssl()
3143            && ! $is_offline_mode
3144        ) {
3145            add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) );
3146        }
3147
3148        add_action( 'load-plugins.php', array( $this, 'intercept_plugin_error_scrape_init' ) );
3149        add_action( 'load-plugins.php', array( $this, 'plugins_page_init_jetpack_state' ) );
3150
3151        if ( ! ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! is_network_admin() ) ) {
3152            add_action( 'admin_enqueue_scripts', array( $this, 'deactivate_dialog' ) );
3153        }
3154
3155        if ( isset( $_COOKIE['jetpackState']['display_update_modal'] ) ) {
3156            add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_block_style' );
3157        }
3158
3159        add_filter( 'plugin_action_links_' . plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ), array( $this, 'plugin_action_links' ) );
3160
3161        if ( self::is_connection_ready() || $is_offline_mode ) {
3162            // Artificially throw errors in certain specific cases during plugin activation.
3163            add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
3164        }
3165    }
3166
3167    /**
3168     * Adds body classes.
3169     *
3170     * @param string $admin_body_class Body classes.
3171     *
3172     * @return string
3173     */
3174    public function admin_body_class( $admin_body_class = '' ) {
3175        $classes = explode( ' ', trim( $admin_body_class ) );
3176
3177        $classes[] = self::is_connection_ready() ? 'jetpack-connected' : 'jetpack-disconnected';
3178
3179        $admin_body_class = implode( ' ', array_unique( $classes ) );
3180        return " $admin_body_class ";
3181    }
3182
3183    /**
3184     * Adds Jetpack Page styles by appending class to the admin body class.
3185     *
3186     * @param string $admin_body_class Existing admin body class string.
3187     *
3188     * @return string
3189     */
3190    public static function add_jetpack_pagestyles( $admin_body_class = '' ) {
3191        return $admin_body_class . ' jetpack-pagestyles ';
3192    }
3193
3194    /**
3195     * Sometimes a plugin can activate without causing errors, but it will cause errors on the next page load.
3196     * This function artificially throws errors for such cases (per a specific list).
3197     *
3198     * @param string $plugin The activated plugin.
3199     * @throws RuntimeException If a conflicting plugin is detected.
3200     */
3201    public function throw_error_on_activate_plugin( $plugin ) {
3202        $active_modules = self::get_active_modules();
3203
3204        // The Shortlinks module and the Stats plugin conflict, but won't cause errors on activation because of some function_exists() checks.
3205        if ( function_exists( 'stats_get_api_key' ) && in_array( 'shortlinks', $active_modules, true ) ) {
3206            $throw = false;
3207
3208            // Try and make sure it really was the stats plugin.
3209            if ( ! class_exists( 'ReflectionFunction' ) ) {
3210                if ( 'stats.php' === basename( $plugin ) ) {
3211                    $throw = true;
3212                }
3213            } else {
3214                // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Checked above. See also https://github.com/phan/phan/issues/1204.
3215                $reflection = new ReflectionFunction( 'stats_get_api_key' );
3216                if ( basename( $plugin ) === basename( $reflection->getFileName() ) ) {
3217                    $throw = true;
3218                }
3219            }
3220
3221            if ( $throw ) {
3222                /* translators: Plugin name to deactivate. */
3223                throw new RuntimeException( sprintf( __( 'Jetpack contains the most recent version of the old "%1$s" plugin.', 'jetpack' ), 'WordPress.com Stats' ) );
3224            }
3225        }
3226    }
3227
3228    /**
3229     * Call to Jetpack::state on the load-plugins.php hook.
3230     * In case the jetpackState cookie is populated, this call will read and re-set the cookie before HTTP headers are sent.
3231     */
3232    public function plugins_page_init_jetpack_state() {
3233        self::state( 'message' );
3234    }
3235
3236    /**
3237     * Adds the intercept action to the check_admin_referer hook.
3238     *
3239     * @return void
3240     */
3241    public function intercept_plugin_error_scrape_init() {
3242        add_action( 'check_admin_referer', array( $this, 'intercept_plugin_error_scrape' ), 10, 2 );
3243    }
3244
3245    /**
3246     * Detect if conflicting plugin is being deactivated.
3247     *
3248     * @param string   $action The nonce action.
3249     * @param bool|int $result False if the nonce is invalid, 1 if the nonce is valid and generated between 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago.
3250     *
3251     * @return void
3252     */
3253    public function intercept_plugin_error_scrape( $action, $result ) {
3254        if ( ! $result ) {
3255            return;
3256        }
3257
3258        foreach ( $this->plugins_to_deactivate as $deactivate_us ) {
3259            foreach ( $deactivate_us as $deactivate_me ) {
3260                if ( "plugin-activation-error_{$deactivate_me[0]}" === $action ) {
3261                    /* translators: Plugin name to deactivate. */
3262                    self::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
3263                }
3264            }
3265        }
3266    }
3267
3268    /**
3269     * Register the remote file upload request handlers, if needed.
3270     *
3271     * @access public
3272     */
3273    public function add_remote_request_handlers() {
3274        // Remote file uploads are allowed only via AJAX requests.
3275        if ( ! is_admin() || ! Constants::get_constant( 'DOING_AJAX' ) ) {
3276            return;
3277        }
3278
3279        // Remote file uploads are allowed only for a set of specific AJAX actions.
3280        $remote_request_actions = array(
3281            'jetpack_upload_file',
3282            'jetpack_update_file',
3283        );
3284
3285        // phpcs:ignore WordPress.Security.NonceVerification
3286        if ( ! isset( $_POST['action'] ) || ! in_array( $_POST['action'], $remote_request_actions, true ) ) {
3287            return;
3288        }
3289
3290        // Require Jetpack authentication for the remote file upload AJAX requests.
3291        if ( ! $this->connection_manager ) {
3292            $this->connection_manager = new Connection_Manager();
3293        }
3294
3295        $this->connection_manager->require_jetpack_authentication();
3296
3297        // Register the remote file upload AJAX handlers.
3298        foreach ( $remote_request_actions as $action ) {
3299            add_action( "wp_ajax_nopriv_{$action}", array( $this, 'remote_request_handlers' ) );
3300        }
3301    }
3302
3303    /**
3304     * Handler for Jetpack remote file uploads.
3305     *
3306     * @access public
3307     * @return never
3308     */
3309    public function remote_request_handlers() {
3310        switch ( current_filter() ) {
3311            case 'wp_ajax_nopriv_jetpack_upload_file':
3312                $response = $this->upload_handler();
3313                break;
3314
3315            case 'wp_ajax_nopriv_jetpack_update_file':
3316                $response = $this->upload_handler( true );
3317                break;
3318            default:
3319                $response = new WP_Error( 'unknown_handler', 'Unknown Handler', 400 );
3320                break;
3321        }
3322
3323        if ( ! $response ) {
3324            $response = new WP_Error( 'unknown_error', 'Unknown Error', 400 );
3325        }
3326
3327        if ( is_wp_error( $response ) ) {
3328            $status_code       = $response->get_error_data();
3329            $error             = $response->get_error_code();
3330            $error_description = $response->get_error_message();
3331
3332            if ( ! is_int( $status_code ) ) {
3333                $status_code = 400;
3334            }
3335
3336            wp_send_json( (object) compact( 'error', 'error_description' ), $status_code, JSON_UNESCAPED_SLASHES );
3337        }
3338
3339        if ( true === $response ) {
3340            status_header( 200 );
3341            exit( 0 );
3342        }
3343
3344        wp_send_json( (object) $response, 200, JSON_UNESCAPED_SLASHES );
3345    }
3346
3347    /**
3348     * Uploads a file gotten from the global $_FILES.
3349     * If `$update_media_item` is true and `post_id` is defined
3350     * the attachment file of the media item (gotten through of the post_id)
3351     * will be updated instead of add a new one.
3352     *
3353     * @param  boolean $update_media_item - update media attachment.
3354     * @return array|WP_Error - An array describing the uploading files process.
3355     */
3356    public function upload_handler( $update_media_item = false ) {
3357        if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
3358            return new WP_Error( 405, get_status_header_desc( 405 ), 405 );
3359        }
3360
3361        $user = wp_authenticate( '', '' );
3362        if ( ! $user || is_wp_error( $user ) ) {
3363            return new WP_Error( 403, get_status_header_desc( 403 ), 403 );
3364        }
3365
3366        wp_set_current_user( $user->ID );
3367
3368        if ( ! current_user_can( 'upload_files' ) ) {
3369            return new WP_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
3370        }
3371
3372        if ( empty( $_FILES ) ) {
3373            return new WP_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
3374        }
3375
3376        foreach ( array_keys( $_FILES ) as $files_key ) {
3377            if ( ! isset( $_POST[ "_jetpack_file_hmac_{$files_key}" ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- no site changes here.
3378                return new WP_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
3379            }
3380        }
3381
3382        $media_keys = isset( $_FILES['media'] ) ? array_keys( $_FILES['media'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Unslash is not needed for `$_FILES`, the sniff is wrong. Sanitization should happen below.
3383
3384        $token = ( new Tokens() )->get_access_token( get_current_user_id() );
3385        if ( ! $token || is_wp_error( $token ) ) {
3386            return new WP_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
3387        }
3388
3389        /**
3390         * Optionally block uploads processed through Jetpack's upload_handler().
3391         * The filter may return false or WP_Error to block this particular upload.
3392         *
3393         * @since 10.8
3394         *
3395         * @param bool|WP_Error $allowed If false or WP_Error, block the upload. If true, allow the upload.
3396         * @param mixed $_FILES The $_FILES attempting to be uploaded.
3397         */
3398        $can_upload = apply_filters( 'jetpack_upload_handler_can_upload', true, $_FILES );
3399        if ( ! $can_upload || is_wp_error( $can_upload ) ) {
3400            if ( is_wp_error( $can_upload ) ) {
3401                return $can_upload;
3402            }
3403            return new WP_Error( 'handler_cannot_upload', __( 'The upload handler cannot upload files', 'jetpack' ), 400 );
3404        }
3405
3406        $uploaded_files = array();
3407        $global_post    = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
3408        unset( $GLOBALS['post'] );
3409        if ( empty( $_FILES['media']['name'] ) ) {
3410            // Nothing to process, just return.
3411            return $uploaded_files;
3412        }
3413        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.
3414            $file = array();
3415            foreach ( $media_keys as $media_key ) {
3416                $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.
3417            }
3418
3419            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.
3420
3421            $hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
3422            if ( $hmac_provided !== $hmac_file ) {
3423                $uploaded_files[ $index ] = (object) array(
3424                    'error'             => 'invalid_hmac',
3425                    'error_description' => 'The corresponding HMAC for this file does not match',
3426                );
3427                continue;
3428            }
3429
3430            $_FILES['.jetpack.upload.'] = $file;
3431            $post_id                    = isset( $_POST['post_id'][ $index ] ) ? absint( $_POST['post_id'][ $index ] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- caller should have checked a nonce.
3432            if ( ! current_user_can( 'edit_post', $post_id ) ) {
3433                $post_id = 0;
3434            }
3435
3436            if ( $update_media_item ) {
3437                if ( ! isset( $post_id ) || 0 === $post_id ) {
3438                    return new WP_Error( 'invalid_input', 'Media ID must be defined.', 400 );
3439                }
3440
3441                $media_array = $_FILES['media']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3442
3443                $file_array             = array();
3444                $file_array['name']     = $media_array['name'][0];
3445                $file_array['type']     = $media_array['type'][0];
3446                $file_array['tmp_name'] = $media_array['tmp_name'][0];
3447                $file_array['error']    = $media_array['error'][0];
3448                $file_array['size']     = $media_array['size'][0];
3449
3450                $edited_media_item = Jetpack_Media::edit_media_file( $post_id, $file_array );
3451
3452                if ( is_wp_error( $edited_media_item ) ) {
3453                    return $edited_media_item;
3454                }
3455
3456                $response = (object) array(
3457                    'id'   => (string) $post_id,
3458                    'file' => (string) $edited_media_item->post_title,
3459                    'url'  => (string) wp_get_attachment_url( $post_id ),
3460                    'type' => (string) $edited_media_item->post_mime_type,
3461                    'meta' => (array) wp_get_attachment_metadata( $post_id ),
3462                );
3463
3464                return array( $response );
3465            }
3466
3467            $attachment_id = media_handle_upload(
3468                '.jetpack.upload.',
3469                $post_id,
3470                array(),
3471                array(
3472                    'action' => 'jetpack_upload_file',
3473                )
3474            );
3475
3476            if ( ! $attachment_id ) {
3477                $uploaded_files[ $index ] = (object) array(
3478                    'error'             => 'unknown',
3479                    'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site',
3480                );
3481            } elseif ( is_wp_error( $attachment_id ) ) {
3482                $uploaded_files[ $index ] = (object) array(
3483                    'error'             => 'attachment_' . $attachment_id->get_error_code(),
3484                    'error_description' => $attachment_id->get_error_message(),
3485                );
3486            } else {
3487                $attachment               = get_post( $attachment_id );
3488                $uploaded_files[ $index ] = (object) array(
3489                    'id'   => (string) $attachment_id,
3490                    'file' => $attachment->post_title,
3491                    'url'  => wp_get_attachment_url( $attachment_id ),
3492                    'type' => $attachment->post_mime_type,
3493                    'meta' => wp_get_attachment_metadata( $attachment_id ),
3494                );
3495            }
3496        }
3497        if ( $global_post !== null ) {
3498            $GLOBALS['post'] = $global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
3499        }
3500
3501        return $uploaded_files;
3502    }
3503
3504    /**
3505     * Add action links for the Jetpack plugin.
3506     *
3507     * @param array $actions Plugin actions.
3508     *
3509     * @return array
3510     */
3511    public function plugin_action_links( $actions ) {
3512        if ( current_user_can( 'jetpack_manage_modules' ) && ( self::is_connection_ready() || ( new Status() )->is_offline_mode() ) ) {
3513            return array_merge(
3514                array( 'settings' => sprintf( '<a href="%s">%s</a>', esc_url( self::admin_url( 'page=jetpack#/settings' ) ), __( 'Settings', 'jetpack' ) ) ),
3515                $actions
3516            );
3517        }
3518
3519        return $actions;
3520    }
3521
3522    /**
3523     * Adds the deactivation warning modal for Jetpack.
3524     *
3525     * @param string $hook The current admin page.
3526     *
3527     * @return void
3528     */
3529    public function deactivate_dialog( $hook ) {
3530        if (
3531            'plugins.php' === $hook
3532            && self::is_connection_ready()
3533        ) {
3534
3535            // Register jp-tracks-functions dependency.
3536            Tracking::register_tracks_functions_scripts( true );
3537
3538            // add a deactivation script that will pick up deactivation actions for the Jetpack plugin.
3539            Assets::register_script(
3540                'jetpack-plugins-page-js',
3541                '_inc/build/plugins-page.js',
3542                JETPACK__PLUGIN_FILE,
3543                array(
3544                    'in_footer'  => true,
3545                    'textdomain' => 'jetpack',
3546                )
3547            );
3548            Assets::enqueue_script( 'jetpack-plugins-page-js' );
3549
3550            // Add objects to be passed to the initial state of the app.
3551            // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
3552            wp_add_inline_script( 'jetpack-plugins-page-js', 'var Initial_State=' . wp_json_encode( Jetpack_Redux_State_Helper::get_minimal_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
3553
3554            add_action( 'admin_footer', array( $this, 'jetpack_plugin_portal_containers' ) );
3555        }
3556    }
3557
3558    /**
3559     * Outputs the wrapper for the plugin modal
3560     * Contents are loaded by React script
3561     *
3562     * @return void
3563     */
3564    public function jetpack_plugin_portal_containers() {
3565        $this->load_view( 'admin/jetpack-plugin-portal-containers.php' );
3566    }
3567
3568    /**
3569     * Filters the login URL to include the registration flow in case the user isn't logged in.
3570     *
3571     * @param string $login_url The wp-login URL.
3572     * @param string $redirect  URL to redirect users after logging in.
3573     * @since Jetpack 8.4
3574     * @return string
3575     */
3576    public function login_url( $login_url, $redirect ) {
3577        parse_str( (string) wp_parse_url( $redirect, PHP_URL_QUERY ), $redirect_parts );
3578        if ( ! empty( $redirect_parts[ self::$jetpack_redirect_login ] ) ) {
3579            $login_url = add_query_arg( self::$jetpack_redirect_login, 'true', $login_url );
3580        }
3581        return $login_url;
3582    }
3583
3584    /**
3585     * Redirects non-authenticated users to authenticate with Calypso if redirect flag is set.
3586     *
3587     * @since Jetpack 8.4
3588     */
3589    public function login_init() {
3590        // phpcs:ignore WordPress.Security.NonceVerification
3591        if ( ! empty( $_GET[ self::$jetpack_redirect_login ] ) ) {
3592            add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3593            wp_safe_redirect(
3594                add_query_arg(
3595                    array(
3596                        'forceInstall' => 1,
3597                        'url'          => rawurlencode( get_site_url() ),
3598                    ),
3599                    // @todo provide way to go to specific calypso env.
3600                    self::get_calypso_host() . 'jetpack/connect'
3601                )
3602            );
3603            exit( 0 );
3604        }
3605    }
3606
3607    /*
3608     * Registration flow:
3609     * 1 - ::admin_page_load() action=register
3610     * 2 - ::try_registration()
3611     * 3 - ::register()
3612     *     - Creates jetpack_register option containing two secrets and a timestamp
3613     *     - Calls https://jetpack.wordpress.com/jetpack.register/1/ with
3614     *       siteurl, home, gmt_offset, timezone_string, site_name, secret_1, secret_2, site_lang, timeout, stats_id
3615     *     - That request to jetpack.wordpress.com does not immediately respond.  It first makes a request BACK to this site's
3616     *       xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
3617     *     - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
3618     *     - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
3619     *       jetpack_id, jetpack_secret, jetpack_public
3620     *     - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
3621     * 4 - redirect to https://wordpress.com/start/jetpack-connect
3622     * 5 - user logs in with WP.com account
3623     * 6 - remote request to this site's xmlrpc.php with action remoteAuthorize, Jetpack_XMLRPC_Server->remote_authorize
3624     *      - Manager::authorize()
3625     *      - Manager::get_token()
3626     *      - GET https://jetpack.wordpress.com/jetpack.token/1/ with
3627     *        client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
3628     *          - which responds with access_token, token_type, scope
3629     *      - Manager::authorize() stores jetpack_options: user_token => access_token.$user_id
3630     *      - Jetpack::activate_default_modules()
3631     *          - Deactivates deprecated plugins
3632     *          - Activates all default modules
3633     *      - Responds with either error, or 'connected' for new connection, or 'linked' for additional linked users
3634     * 7 - For a new connection, user selects a Jetpack plan on wordpress.com
3635     * 8 - User is redirected back to wp-admin/index.php?page=jetpack with state:message=authorized
3636     *     Done!
3637     */
3638
3639    /**
3640     * Handles the page load events for the Jetpack admin page
3641     */
3642    public function admin_page_load() {
3643        $error = false;
3644
3645        // Make sure we have the right body class to hook stylings for subpages off of.
3646        add_filter( 'admin_body_class', array( __CLASS__, 'add_jetpack_pagestyles' ), 20 );
3647
3648        if ( ! empty( $_GET['jetpack_restate'] ) ) {
3649            // Should only be used in intermediate redirects to preserve state across redirects.
3650            self::restate();
3651        }
3652
3653        if ( isset( $_GET['action'] ) ) {
3654            switch ( $_GET['action'] ) {
3655                /**
3656                 * Cases authorize and authorize_redirect are now handled by Connection package Webhooks
3657                 */
3658                case 'authorize_redirect':
3659                case 'authorize':
3660                    break;
3661                case 'register':
3662                    if ( ! current_user_can( 'jetpack_connect' ) ) {
3663                        $error = 'cheatin';
3664                        break;
3665                    }
3666                    check_admin_referer( 'jetpack-register' );
3667                    self::log( 'register' );
3668                    self::maybe_set_version_option();
3669                    $from = isset( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : false;
3670                    if ( $from ) {
3671                        static::connection()->add_register_request_param( 'from', (string) $from );
3672                    }
3673                    $registered = static::connection()->try_registration();
3674                    if ( is_wp_error( $registered ) ) {
3675                        $error = $registered->get_error_code();
3676                        self::state( 'error', $error );
3677                        self::state( 'error', $registered->get_error_message() );
3678
3679                        /**
3680                         * Jetpack registration Error.
3681                         *
3682                         * @since 7.5.0
3683                         *
3684                         * @param string|int $error The error code.
3685                         * @param \WP_Error $registered The error object.
3686                         */
3687                        do_action( 'jetpack_connection_register_fail', $error, $registered );
3688                        break;
3689                    }
3690
3691                    $redirect = isset( $_GET['redirect'] ) ? wp_unslash( $_GET['redirect'] ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3692
3693                    /**
3694                     * Jetpack registration Success.
3695                     *
3696                     * @since 7.5.0
3697                     *
3698                     * @param string $from 'from' GET parameter;
3699                     */
3700                    do_action( 'jetpack_connection_register_success', $from );
3701
3702                    $url = $this->build_connect_url( true, $redirect, $from );
3703
3704                    if ( ! empty( $_GET['auth_approved'] ) && 'true' === $_GET['auth_approved'] ) {
3705                        $url = add_query_arg( 'auth_approved', 'true', $url );
3706                    }
3707
3708                    add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3709                    wp_safe_redirect( $url );
3710                    exit( 0 );
3711                case 'activate':
3712                    if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
3713                        $error = 'cheatin';
3714                        break;
3715                    }
3716
3717                    $module = isset( $_GET['module'] ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : '';
3718                    check_admin_referer( "jetpack_activate-$module" );
3719                    self::log( 'activate', $module );
3720                    if ( ! self::activate_module( $module ) ) {
3721                        /* translators: module/feature name */
3722                        self::state( 'error', sprintf( __( 'Could not activate %s', 'jetpack' ), $module ) );
3723                    }
3724                    // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
3725                    wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3726                    exit( 0 );
3727                case 'activate_default_modules':
3728                    check_admin_referer( 'activate_default_modules' );
3729                    self::log( 'activate_default_modules' );
3730                    self::restate();
3731                    $min_version   = isset( $_GET['min_version'] ) ? sanitize_text_field( wp_unslash( $_GET['min_version'] ) ) : false;
3732                    $max_version   = isset( $_GET['max_version'] ) ? sanitize_text_field( wp_unslash( $_GET['max_version'] ) ) : false;
3733                    $other_modules = isset( $_GET['other_modules'] ) && is_array( $_GET['other_modules'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['other_modules'] ) ) : array();
3734                    self::activate_default_modules( $min_version, $max_version, $other_modules );
3735                    wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3736                    exit( 0 );
3737                case 'disconnect':
3738                    if ( ! current_user_can( 'jetpack_disconnect' ) ) {
3739                        $error = 'cheatin';
3740                        break;
3741                    }
3742
3743                    check_admin_referer( 'jetpack-disconnect' );
3744                    self::log( 'disconnect' );
3745                    self::disconnect();
3746                    wp_safe_redirect( self::admin_url( 'disconnected=true' ) );
3747                    exit( 0 );
3748                case 'reconnect':
3749                    if ( ! current_user_can( 'jetpack_reconnect' ) ) {
3750                        $error = 'cheatin';
3751                        break;
3752                    }
3753
3754                    check_admin_referer( 'jetpack-reconnect' );
3755                    self::log( 'reconnect' );
3756                    self::disconnect();
3757
3758                    add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
3759                    wp_safe_redirect( $this->build_connect_url( true, false, 'reconnect' ) );
3760                    exit( 0 );
3761                case 'deactivate':
3762                    if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
3763                        $error = 'cheatin';
3764                        break;
3765                    }
3766
3767                    $modules = sanitize_text_field( wp_unslash( $_GET['module'] ) );
3768                    check_admin_referer( "jetpack_deactivate-$modules" );
3769                    foreach ( explode( ',', $modules ) as $module ) {
3770                        self::log( 'deactivate', $module );
3771                        self::deactivate_module( $module );
3772                        self::state( 'message', 'module_deactivated' );
3773                    }
3774                    self::state( 'module', $modules );
3775                    wp_safe_redirect( self::admin_url( 'page=jetpack' ) );
3776                    exit( 0 );
3777                case 'unlink':
3778                    $redirect = isset( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
3779                    check_admin_referer( 'jetpack-unlink' );
3780                    self::log( 'unlink' );
3781                    $this->connection_manager->disconnect_user();
3782                    self::state( 'message', 'unlinked' );
3783                    if ( 'sub-unlink' === $redirect ) {
3784                        wp_safe_redirect( admin_url() );
3785                    } else {
3786                        wp_safe_redirect( self::admin_url( array( 'page' => rawurlencode( $redirect ) ) ) );
3787                    }
3788                    exit( 0 );
3789                default:
3790                    /**
3791                     * Fires when a Jetpack admin page is loaded with an unrecognized parameter.
3792                     *
3793                     * @since 2.6.0
3794                     *
3795                     * @param string sanitize_key( $_GET['action'] ) Unrecognized URL parameter.
3796                     */
3797                    do_action( 'jetpack_unrecognized_action', sanitize_key( $_GET['action'] ) );
3798            }
3799        }
3800
3801        $error = $error ? $error : self::state( 'error' );
3802        if ( ! $error ) {
3803            self::activate_new_modules( true );
3804        }
3805
3806        $activated_manage = false;
3807        $message_code     = self::state( 'message' );
3808        if ( self::state( 'optin-manage' ) ) {
3809            $activated_manage = $message_code;
3810            $message_code     = 'jetpack-manage';
3811        }
3812
3813        switch ( $message_code ) {
3814            case 'jetpack-manage':
3815                $sites_url = esc_url( Redirect::get_url( 'calypso-sites' ) );
3816                // translators: %s is the URL to the "Sites" panel on wordpress.com.
3817                $this->message = '<strong>' . sprintf( __( 'You are all set! Your site can now be managed from <a href="%s" target="_blank">wordpress.com/sites</a>.', 'jetpack' ), $sites_url ) . '</strong>';
3818                if ( $activated_manage ) {
3819                    $this->message .= '<br /><strong>' . __( 'Manage has been activated for you!', 'jetpack' ) . '</strong>';
3820                }
3821                break;
3822
3823        }
3824
3825        $deactivated_plugins = self::state( 'deactivated_plugins' );
3826
3827        if ( ! empty( $deactivated_plugins ) ) {
3828            $deactivated_plugins = explode( ',', $deactivated_plugins );
3829            $deactivated_titles  = array();
3830            foreach ( $deactivated_plugins as $deactivated_plugin ) {
3831                list( $module, $idx ) = explode( ':', $deactivated_plugin );
3832                if ( ! isset( $this->plugins_to_deactivate[ $module ][ $idx ] ) ) {
3833                    continue;
3834                }
3835
3836                $deactivated_titles[] = '<strong>' . str_replace( ' ', '&nbsp;', $this->plugins_to_deactivate[ $module ][ $idx ][1] ) . '</strong>';
3837            }
3838
3839            if ( $deactivated_titles ) {
3840                if ( $this->message ) {
3841                    $this->message .= "<br /><br />\n";
3842                }
3843
3844                $this->message .= wp_sprintf(
3845                    _n(
3846                        'Jetpack contains the most recent version of the old %l plugin.',
3847                        'Jetpack contains the most recent versions of the old %l plugins.',
3848                        count( $deactivated_titles ),
3849                        'jetpack'
3850                    ),
3851                    $deactivated_titles
3852                );
3853
3854                $this->message .= "<br />\n";
3855
3856                $this->message .= _n(
3857                    'The old version has been deactivated and can be removed from your site.',
3858                    'The old versions have been deactivated and can be removed from your site.',
3859                    count( $deactivated_titles ),
3860                    'jetpack'
3861                );
3862            }
3863        }
3864
3865        $this->privacy_checks = self::state( 'privacy_checks' );
3866
3867        if ( $this->message || $this->error || $this->privacy_checks ) {
3868            add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
3869        }
3870
3871        add_filter( 'jetpack_short_module_description', 'wptexturize' );
3872    }
3873
3874    /**
3875     * Display admin notice upon error.
3876     *
3877     * @return void
3878     */
3879    public function admin_notices() {
3880
3881        if ( $this->error ) {
3882            ?>
3883<div id="message" class="jetpack-message jetpack-err">
3884    <div class="squeezer">
3885        <h2>
3886            <?php
3887            echo wp_kses(
3888                $this->error,
3889                array(
3890                    'a'      => array( 'href' => array() ),
3891                    'small'  => true,
3892                    'code'   => true,
3893                    'strong' => true,
3894                    'br'     => true,
3895                    'b'      => true,
3896                )
3897            );
3898            ?>
3899            </h2>
3900            <?php
3901            $desc = self::state( 'error_description' );
3902            if ( $desc ) :
3903                ?>
3904        <p><?php echo esc_html( stripslashes( $desc ) ); ?></p>
3905<?php    endif; ?>
3906    </div>
3907</div>
3908            <?php
3909        }
3910
3911        if ( $this->message ) {
3912            ?>
3913<div id="message" class="jetpack-message">
3914    <div class="squeezer">
3915        <h2>
3916            <?php
3917            echo wp_kses(
3918                $this->message,
3919                array(
3920                    'strong' => array(),
3921                    'a'      => array( 'href' => true ),
3922                    'br'     => true,
3923                )
3924            );
3925            ?>
3926            </h2>
3927    </div>
3928</div>
3929            <?php
3930        }
3931
3932        if ( $this->privacy_checks ) :
3933            $module_names = array();
3934            $module_slugs = array();
3935
3936            $privacy_checks = explode( ',', $this->privacy_checks );
3937            $privacy_checks = array_filter( $privacy_checks, array( 'Jetpack', 'is_module' ) );
3938            foreach ( $privacy_checks as $module_slug ) {
3939                $module = self::get_module( $module_slug );
3940                if ( ! $module ) {
3941                    continue;
3942                }
3943
3944                $module_slugs[] = $module_slug;
3945                $module_names[] = "<strong>{$module['name']}</strong>";
3946            }
3947
3948            $module_slugs = implode( ',', $module_slugs );
3949            ?>
3950<div id="message" class="jetpack-message jetpack-err">
3951    <div class="squeezer">
3952        <h2><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h2><br />
3953        <p>
3954            <?php
3955            echo wp_kses(
3956                wptexturize(
3957                    wp_sprintf(
3958                        _nx(
3959                            "Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
3960                            "Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
3961                            count( $privacy_checks ),
3962                            '%l = list of Jetpack module/feature names',
3963                            'jetpack'
3964                        ),
3965                        $module_names
3966                    )
3967                ),
3968                array( 'strong' => true )
3969            );
3970
3971            echo "\n<br />\n";
3972
3973            echo wp_kses(
3974                sprintf(
3975                        /* translators: URL to deactivate Jetpack features. */
3976                    _nx(
3977                        'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
3978                        'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
3979                        count( $privacy_checks ),
3980                        '%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
3981                        'jetpack'
3982                    ),
3983                    esc_url(
3984                        wp_nonce_url(
3985                            self::admin_url(
3986                                array(
3987                                    'page'   => 'jetpack',
3988                                    'action' => 'deactivate',
3989                                    'module' => rawurlencode( $module_slugs ),
3990                                )
3991                            ),
3992                            "jetpack_deactivate-$module_slugs"
3993                        )
3994                    ),
3995                    esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
3996                ),
3997                array(
3998                    'a' => array(
3999                        'href'  => true,
4000                        'title' => true,
4001                    ),
4002                )
4003            );
4004            ?>
4005        </p>
4006    </div>
4007</div>
4008            <?php
4009endif;
4010    }
4011
4012    /**
4013     * We can't always respond to a signed XML-RPC request with a
4014     * helpful error message. In some circumstances, doing so could
4015     * leak information.
4016     *
4017     * Instead, track that the error occurred via a Jetpack_Option,
4018     * and send that data back in the heartbeat.
4019     * All this does is increment a number, but it's enough to find
4020     * trends.
4021     *
4022     * @param WP_Error $xmlrpc_error The error produced during
4023     *                               signature validation.
4024     */
4025    public function track_xmlrpc_error( $xmlrpc_error ) {
4026        $code = is_wp_error( $xmlrpc_error )
4027            ? $xmlrpc_error->get_error_code()
4028            : 'should-not-happen';
4029
4030        $xmlrpc_errors = Jetpack_Options::get_option( 'xmlrpc_errors', array() );
4031        if ( isset( $xmlrpc_errors[ $code ] ) && $xmlrpc_errors[ $code ] ) {
4032            // No need to update the option if we already have
4033            // this code stored.
4034            return;
4035        }
4036        $xmlrpc_errors[ $code ] = true;
4037
4038        Jetpack_Options::update_option( 'xmlrpc_errors', $xmlrpc_errors, false );
4039    }
4040
4041    /**
4042     * Initialize the jetpack stats instance only when needed
4043     *
4044     * @return void
4045     */
4046    private function initialize_stats() {
4047        if ( $this->a8c_mc_stats_instance === null ) {
4048            $this->a8c_mc_stats_instance = new Automattic\Jetpack\A8c_Mc_Stats();
4049        }
4050    }
4051
4052    /**
4053     * Record a stat for later output.  This will only currently output in the admin_footer.
4054     *
4055     * @param string $group Stats group.
4056     * @param string $detail Stats detail.
4057     */
4058    public function stat( $group, $detail ) {
4059        $this->initialize_stats();
4060        $this->a8c_mc_stats_instance->add( $group, $detail );
4061
4062        // Keep a local copy for backward compatibility (there are some direct checks on this).
4063        $this->stats = $this->a8c_mc_stats_instance->get_current_stats();
4064    }
4065
4066    /**
4067     * Load stats pixels. $group is auto-prefixed with "x_jetpack-"
4068     *
4069     * @param string $method Used to check if method is "server-side".
4070     */
4071    public function do_stats( $method = '' ) {
4072        $this->initialize_stats();
4073        if ( 'server_side' === $method ) {
4074            $this->a8c_mc_stats_instance->do_server_side_stats();
4075        } else {
4076            $this->a8c_mc_stats_instance->do_stats();
4077        }
4078
4079        // Keep a local copy for backward compatibility (there are some direct checks on this).
4080        $this->stats = array();
4081    }
4082
4083    /**
4084     * Runs stats code for a one-off, server-side.
4085     *
4086     * @param array|string $args The arguments to append to the URL. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
4087     *
4088     * @return bool If it worked.
4089     */
4090    public static function do_server_side_stat( $args ) {
4091        $url                   = self::build_stats_url( $args );
4092        $a8c_mc_stats_instance = new Automattic\Jetpack\A8c_Mc_Stats();
4093        return $a8c_mc_stats_instance->do_server_side_stat( $url );
4094    }
4095
4096    /**
4097     * Builds the stats url.
4098     *
4099     * @param array|string $args The arguments to append to the URL.
4100     *
4101     * @return string The URL to be pinged.
4102     */
4103    public static function build_stats_url( $args ) {
4104
4105        $a8c_mc_stats_instance = new Automattic\Jetpack\A8c_Mc_Stats();
4106        return $a8c_mc_stats_instance->build_stats_url( $args );
4107    }
4108
4109    /**
4110     * Builds a URL to the Jetpack connection auth page
4111     *
4112     * @since 3.9.5
4113     *
4114     * @param bool        $raw If true, URL will not be escaped.
4115     * @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
4116     *                              If string, will be a custom redirect.
4117     * @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
4118     * @param bool        $register If true, will generate a register URL regardless of the existing token, since 4.9.0.
4119     *
4120     * @return string Connect URL
4121     */
4122    public function build_connect_url( $raw = false, $redirect = false, $from = false, $register = false ) {
4123        $site_id    = Jetpack_Options::get_option( 'id' );
4124        $blog_token = ( new Tokens() )->get_access_token();
4125
4126        if ( $register || ! $blog_token || ! $site_id ) {
4127            $url = self::nonce_url_no_esc( self::admin_url( 'action=register' ), 'jetpack-register' );
4128
4129            if ( ! empty( $redirect ) ) {
4130                $url = add_query_arg(
4131                    'redirect',
4132                    rawurlencode( wp_validate_redirect( esc_url_raw( $redirect ) ) ),
4133                    $url
4134                );
4135            }
4136
4137            if ( is_network_admin() ) {
4138                $url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
4139            }
4140
4141            $calypso_env = ( new Host() )->get_calypso_env();
4142
4143            if ( ! empty( $calypso_env ) ) {
4144                $url = add_query_arg( 'calypso_env', $calypso_env, $url );
4145            }
4146        } else {
4147
4148            // Let's check the existing blog token to see if we need to re-register. We only check once per minute
4149            // because otherwise this logic can get us in to a loop.
4150            $last_connect_url_check = (int) Jetpack_Options::get_raw_option( 'jetpack_last_connect_url_check' );
4151            if ( ! $last_connect_url_check || ( time() - $last_connect_url_check ) > MINUTE_IN_SECONDS ) {
4152                Jetpack_Options::update_raw_option( 'jetpack_last_connect_url_check', time() );
4153
4154                $response = Client::wpcom_json_api_request_as_blog(
4155                    sprintf( '/sites/%d', $site_id ) . '?force=wpcom',
4156                    '1.1'
4157                );
4158
4159                if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
4160
4161                    // Generating a register URL instead to refresh the existing token.
4162                    return $this->build_connect_url( $raw, $redirect, $from, true );
4163                }
4164            }
4165
4166            $url = ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4167        }
4168
4169        if ( $from ) {
4170            $url = add_query_arg( 'from', $from, $url );
4171        }
4172
4173        $url = $raw ? esc_url_raw( $url ) : esc_url( $url );
4174        /**
4175         * Filter the URL used when connecting a user to a WordPress.com account.
4176         *
4177         * @since 8.1.0
4178         *
4179         * @param string $url Connection URL.
4180         * @param bool   $raw If true, URL will not be escaped.
4181         */
4182        return apply_filters( 'jetpack_build_connection_url', $url, $raw );
4183    }
4184
4185    /**
4186     * Create the Jetpack authorization URL.
4187     *
4188     * @param bool|string $redirect URL to redirect to.
4189     * @param null        $deprecated Deprecated since Jetpack 10.9.
4190     *
4191     * @todo Update default value for redirect since the called function expects a string.
4192     *
4193     * @deprecated 13.4
4194     *
4195     * @return mixed|void
4196     */
4197    public static function build_authorize_url( $redirect = false, $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
4198        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::build_authorize_url' );
4199
4200        return ( new Authorize_Redirect( static::connection() ) )->build_authorize_url( $redirect );
4201    }
4202
4203    /**
4204     * Filters the connection URL parameter array.
4205     *
4206     * @deprecated 13.4
4207     *
4208     * @param array $args default URL parameters used by the package.
4209     * @return array the modified URL arguments array.
4210     */
4211    public static function filter_connect_request_body( $args ) {
4212        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_request_body' );
4213
4214        return Authorize_Redirect::filter_connect_request_body( $args );
4215    }
4216
4217    /**
4218     * Filters the `jetpack/v4/connection/data` API response of the Connection package in order to
4219     * add Jetpack-the-plugin related permissions.
4220     *
4221     * @since 10.0
4222     *
4223     * @param array $current_user_connection_data An array containing the current user connection data.
4224     * @return array
4225     */
4226    public static function filter_jetpack_current_user_connection_data( $current_user_connection_data ) {
4227        $jetpack_permissions = array(
4228            'admin_page'         => current_user_can( 'jetpack_admin_page' ),
4229            'manage_modules'     => current_user_can( 'jetpack_manage_modules' ),
4230            'network_admin'      => current_user_can( 'jetpack_network_admin_page' ),
4231            'network_sites_page' => current_user_can( 'jetpack_network_sites_page' ),
4232            'edit_posts'         => current_user_can( 'edit_posts' ),
4233            'publish_posts'      => current_user_can( 'publish_posts' ),
4234            'manage_options'     => current_user_can( 'manage_options' ),
4235            'view_stats'         => current_user_can( 'view_stats' ),
4236            'manage_plugins'     => current_user_can( 'install_plugins' )
4237                                    && current_user_can( 'activate_plugins' )
4238                                    && current_user_can( 'update_plugins' )
4239                                    && current_user_can( 'delete_plugins' ),
4240        );
4241
4242        if ( isset( $current_user_connection_data['permissions'] ) &&
4243            is_array( $current_user_connection_data['permissions'] ) ) {
4244                $current_user_connection_data['permissions'] = array_merge( $current_user_connection_data['permissions'], $jetpack_permissions );
4245        } else {
4246            $current_user_connection_data['permissions'] = $jetpack_permissions;
4247        }
4248
4249        return $current_user_connection_data;
4250    }
4251
4252    /**
4253     * Filters the redirection URL that is used for connect requests. The redirect
4254     * URL should return the user back to the Jetpack console.
4255     *
4256     * @deprecated 13.4
4257     *
4258     * @param String $redirect the default redirect URL used by the package.
4259     * @return String the modified URL.
4260     */
4261    public static function filter_connect_redirect_url( $redirect ) {
4262        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Authorize_Redirect::filter_connect_redirect_url' );
4263        return Authorize_Redirect::filter_connect_redirect_url( $redirect );
4264    }
4265
4266    /**
4267     * This action fires at the beginning of the Manager::authorize method.
4268     */
4269    public static function authorize_starting() {
4270        $jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
4271        // Checking if site has been active/connected previously before recording unique connection.
4272        if ( ! $jetpack_unique_connection ) {
4273            // jetpack_unique_connection option has never been set.
4274            $jetpack_unique_connection = array(
4275                'connected'    => 0,
4276                'disconnected' => 0,
4277                'version'      => '3.6.1',
4278            );
4279
4280            update_option( 'jetpack_unique_connection', $jetpack_unique_connection );
4281
4282            // Track unique connection.
4283            $jetpack = self::init();
4284
4285            $jetpack->stat( 'connections', 'unique-connection' );
4286            $jetpack->do_stats( 'server_side' );
4287        }
4288
4289        // Increment number of times connected.
4290        $jetpack_unique_connection['connected'] += 1;
4291        Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
4292    }
4293
4294    /**
4295     * This action fires when the site is registered (connected at a site level).
4296     */
4297    public function handle_unique_registrations_stats() {
4298        $jetpack_unique_registrations = Jetpack_Options::get_option( 'unique_registrations' );
4299        // Checking if site has been registered previously before recording unique connection.
4300        if ( ! $jetpack_unique_registrations ) {
4301
4302            $jetpack_unique_registrations = 0;
4303
4304            $this->stat( 'connections', 'unique-registrations' );
4305            $this->do_stats( 'server_side' );
4306        }
4307
4308        // Increment number of times connected.
4309        ++$jetpack_unique_registrations;
4310        Jetpack_Options::update_option( 'unique_registrations', $jetpack_unique_registrations );
4311    }
4312
4313    /**
4314     * This action fires at the end of the Manager::authorize method when a secondary user is
4315     * linked.
4316     */
4317    public static function authorize_ending_linked() {
4318        // Don't activate anything since we are just connecting a user.
4319        self::state( 'message', 'linked' );
4320    }
4321
4322    /**
4323     * This action fires at the end of the Manager::authorize method when the master user is
4324     * authorized.
4325     *
4326     * @param array $data The request data.
4327     */
4328    public static function authorize_ending_authorized( $data ) {
4329        // If redirect_uri is SSO, ensure SSO module is enabled.
4330        parse_str( wp_parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
4331
4332        /** This filter is documented in class.jetpack-cli.php */
4333        $jetpack_start_enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
4334
4335        $activate_sso = (
4336            isset( $redirect_options['action'] ) &&
4337            'jetpack-sso' === $redirect_options['action'] &&
4338            $jetpack_start_enable_sso
4339        );
4340
4341        $do_redirect_on_error = ( 'client' === $data['auth_type'] );
4342
4343        self::handle_post_authorization_actions( $activate_sso, $do_redirect_on_error );
4344    }
4345
4346    /**
4347     * Fires on the jetpack_site_registered hook and acitvates default modules
4348     */
4349    public static function activate_default_modules_on_site_register() {
4350        self::handle_default_module_activation( false );
4351
4352        // Since this is a fresh connection, be sure to clear out IDC options.
4353        Identity_Crisis::clear_all_idc_options();
4354    }
4355
4356    /**
4357     * This action fires at the end of the REST_Connector connection_reconnect method when the
4358     * reconnect process is completed.
4359     * Note that this currently only happens when we don't need the user to re-authorize
4360     * their WP.com account, eg in cases where we are restoring a connection with
4361     * unhealthy blog token.
4362     */
4363    public static function reconnection_completed() {
4364        self::state( 'message', 'reconnection_completed' );
4365    }
4366
4367    /**
4368     * Apply activation source to a query string array.
4369     *
4370     * @param array $args Args used for a query string.
4371     *
4372     * @return void
4373     */
4374    public static function apply_activation_source_to_args( &$args ) {
4375        list( $activation_source_name, $activation_source_keyword ) = get_option( 'jetpack_activation_source' );
4376
4377        if ( $activation_source_name ) {
4378            $args['_as'] = rawurlencode( $activation_source_name );
4379        }
4380
4381        if ( $activation_source_keyword ) {
4382            $args['_ak'] = rawurlencode( $activation_source_keyword );
4383        }
4384    }
4385
4386    /**
4387     * Returns the reconnection URL.
4388     *
4389     * @param bool $raw True to return an unescaped URL. False returns value after `esc_url`.
4390     *
4391     * @return string|null
4392     */
4393    public function build_reconnect_url( $raw = false ) {
4394        $url = wp_nonce_url( self::admin_url( 'action=reconnect' ), 'jetpack-reconnect' );
4395        return $raw ? $url : esc_url( $url );
4396    }
4397
4398    /**
4399     * Jetpack Admin URL.
4400     *
4401     * @param array $args Query string args.
4402     *
4403     * @return string Jetpack admin URL.
4404     */
4405    public static function admin_url( $args = null ) {
4406        return ( new Paths() )->admin_url( $args );
4407    }
4408
4409    /**
4410     * Creates a nonce from an URL.
4411     *
4412     * @param string $actionurl URL for action.
4413     * @param string $action Nonce action.
4414     * @param string $name Query arg name.
4415     *
4416     * @return string
4417     */
4418    public static function nonce_url_no_esc( $actionurl, $action = -1, $name = '_wpnonce' ) {
4419        $actionurl = str_replace( '&amp;', '&', $actionurl );
4420        return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
4421    }
4422
4423    /**
4424     * Dismiss Jetpack notices.
4425     *
4426     * @return void
4427     */
4428    public function dismiss_jetpack_notice() {
4429        if ( ! isset( $_GET['jetpack-notice'] ) ) {
4430            return;
4431        }
4432
4433        switch ( $_GET['jetpack-notice'] ) {
4434            case 'dismiss':
4435                if ( check_admin_referer( 'jetpack-deactivate' ) && ! is_plugin_active_for_network( plugin_basename( JETPACK__PLUGIN_DIR . 'jetpack.php' ) ) ) {
4436
4437                    require_once ABSPATH . 'wp-admin/includes/plugin.php';
4438                    deactivate_plugins( JETPACK__PLUGIN_DIR . 'jetpack.php', false, false );
4439                    wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
4440                }
4441                break;
4442        }
4443    }
4444
4445    /**
4446     * Determines which module has a higher sort order.
4447     *
4448     * @param array $a Modules array.
4449     * @param array $b Modules array.
4450     *
4451     * @return int 0 if the same sort or (+/-) to indicate which is greater.
4452     */
4453    public static function sort_modules( $a, $b ) {
4454        return $a['sort'] <=> $b['sort'];
4455    }
4456
4457    /**
4458     * Recheck SSL status for use via an AJAX call.
4459     *
4460     * Sends data back via `wp_send_json`.
4461     *
4462     * @return void
4463     */
4464    public function ajax_recheck_ssl() {
4465        check_ajax_referer( 'recheck-ssl', 'ajax-nonce' );
4466        $result = self::permit_ssl( true );
4467        wp_send_json(
4468            array(
4469                'enabled' => $result,
4470                'message' => get_transient( 'jetpack_https_test_message' ),
4471            ),
4472            null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
4473            JSON_UNESCAPED_SLASHES
4474        );
4475    }
4476
4477    /* Client API */
4478
4479    /**
4480     * Verify the onboarding token.
4481     *
4482     * @deprecated since 13.9
4483     *
4484     * @param array  $token_data Token data.
4485     * @param string $token Token value.
4486     * @param string $request_data JSON-encoded request data.
4487     *
4488     * @return mixed
4489     */
4490    public static function verify_onboarding_token( $token_data, $token, $request_data ) {
4491        _deprecated_function( __METHOD__, '13.9' );
4492        // Default to a blog token.
4493        $token_type = 'blog';
4494
4495        // Let's see if this is onboarding. In such case, use user token type and the provided user id.
4496        if ( isset( $request_data ) || ! empty( $_GET['onboarding'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes, if caller is changing the site the nonce should be verified there.
4497            if ( ! empty( $_GET['onboarding'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
4498                $jpo = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
4499            } else {
4500                $jpo = json_decode( $request_data, true );
4501            }
4502
4503            $jpo_token = ! empty( $jpo['onboarding']['token'] ) ? $jpo['onboarding']['token'] : null;
4504            $jpo_user  = ! empty( $jpo['onboarding']['jpUser'] ) ? $jpo['onboarding']['jpUser'] : null;
4505
4506            if (
4507                isset( $jpo_user )
4508                && isset( $jpo_token )
4509                && is_email( $jpo_user )
4510                && ctype_alnum( $jpo_token )
4511                && isset( $_GET['rest_route'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
4512                && self::validate_onboarding_token_action(
4513                    $jpo_token,
4514                    wp_unslash( $_GET['rest_route'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- no site changes.
4515                )
4516            ) {
4517                $jp_user = get_user_by( 'email', $jpo_user );
4518                if ( is_a( $jp_user, 'WP_User' ) ) {
4519                    wp_set_current_user( $jp_user->ID );
4520                    $user_can = is_multisite()
4521                        ? current_user_can_for_site( get_current_blog_id(), 'manage_options' )
4522                        : current_user_can( 'manage_options' );
4523                    if ( $user_can ) {
4524                        $token_type              = 'user';
4525                        $token->external_user_id = $jp_user->ID;
4526                    }
4527                }
4528            }
4529
4530            $token_data['type']    = $token_type;
4531            $token_data['user_id'] = $token->external_user_id;
4532        }
4533
4534        return $token_data;
4535    }
4536
4537    /**
4538     * Create a random secret for validating onboarding payload
4539     *
4540     * @deprecated since 13.9
4541     * @return string Secret token
4542     */
4543    public static function create_onboarding_token() {
4544        _deprecated_function( __METHOD__, '13.9' );
4545        $token = Jetpack_Options::get_option( 'onboarding' );
4546        if ( false === ( $token ) ) {
4547            $token = wp_generate_password( 32, false );
4548            Jetpack_Options::update_option( 'onboarding', $token );
4549        }
4550
4551        return $token;
4552    }
4553
4554    /**
4555     * Remove the onboarding token
4556     *
4557     * @deprecated since 13.9
4558     * @return bool True on success, false on failure
4559     */
4560    public static function invalidate_onboarding_token() {
4561        _deprecated_function( __METHOD__, '13.9' );
4562        return Jetpack_Options::delete_option( 'onboarding' );
4563    }
4564
4565    /**
4566     * Validate an onboarding token for a specific action
4567     *
4568     * @deprecated since 13.9
4569     *
4570     * @param string $token Onboarding token.
4571     * @param string $action Action name.
4572     *
4573     * @return boolean True if token/action pair is accepted, false if not
4574     */
4575    public static function validate_onboarding_token_action( $token, $action ) {
4576        _deprecated_function( __METHOD__, '13.9' );
4577        // Compare tokens, bail if tokens do not match.
4578        if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) {
4579            return false;
4580        }
4581
4582        // List of valid actions we can take.
4583        $valid_actions = array(
4584            '/jetpack/v4/settings',
4585        );
4586
4587        // Only allow valid actions.
4588        if ( ! in_array( $action, $valid_actions, true ) ) {
4589            return false;
4590        }
4591
4592        return true;
4593    }
4594
4595    /**
4596     * Checks to see if the URL is using SSL to connect with Jetpack
4597     *
4598     * @param bool $force_recheck Force SSL recheck.
4599     *
4600     * @return boolean
4601     * @since 2.3.3
4602     */
4603    public static function permit_ssl( $force_recheck = false ) {
4604        // Do some fancy tests to see if ssl is being supported.
4605        $ssl = false;
4606        if ( ! $force_recheck ) {
4607            $ssl = get_transient( 'jetpack_https_test' );
4608        }
4609
4610        if ( $force_recheck || false === $ssl ) {
4611            $message = '';
4612            if ( ! str_starts_with( JETPACK__API_BASE, 'https' ) ) {
4613                $ssl = 0;
4614            } else {
4615                $ssl = 1;
4616
4617                if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
4618                    $ssl     = 0;
4619                    $message = __( 'WordPress reports no SSL support', 'jetpack' );
4620                } else {
4621                    $response = wp_remote_get( JETPACK__API_BASE . 'test/1/' );
4622                    if ( is_wp_error( $response ) ) {
4623                        $ssl     = 0;
4624                        $message = __( 'WordPress reports no SSL support', 'jetpack' );
4625                    } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
4626                        $ssl     = 0;
4627                        $message = __( 'Response was not OK: ', 'jetpack' ) . wp_remote_retrieve_body( $response );
4628                    }
4629                }
4630            }
4631            set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
4632            set_transient( 'jetpack_https_test_message', $message, DAY_IN_SECONDS );
4633        }
4634
4635        return (bool) $ssl;
4636    }
4637
4638    /**
4639     * Displays an admin_notice, alerting the user that outbound SSL isn't working.
4640     */
4641    public function alert_auto_ssl_fail() {
4642        if ( ! current_user_can( 'manage_options' ) ) {
4643            return;
4644        }
4645
4646        $ajax_nonce = wp_create_nonce( 'recheck-ssl' );
4647        ?>
4648
4649        <div id="jetpack-ssl-warning" class="error jp-identity-crisis">
4650            <div class="jp-banner__content">
4651                <h2><?php esc_html_e( 'Outbound HTTPS not working', 'jetpack' ); ?></h2>
4652                <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>
4653                <p>
4654                    <?php esc_html_e( 'Jetpack will re-test for HTTPS support once a day, but you can click here to try again immediately: ', 'jetpack' ); ?>
4655                    <a href="#" id="jetpack-recheck-ssl-button"><?php esc_html_e( 'Try again', 'jetpack' ); ?></a>
4656                    <span id="jetpack-recheck-ssl-output"><?php echo esc_html( get_transient( 'jetpack_https_test_message' ) ); ?></span>
4657                </p>
4658                <p>
4659                    <?php
4660                    printf(
4661                            /* translators: Both are URLs. First for the connection debug tool and the second for a support page. */
4662                        __( 'For more help, try our <a href="%1$s">connection debugger</a> or <a href="%2$s" target="_blank">troubleshooting tips</a>.', 'jetpack' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we're building known HTML.
4663                        esc_url( self::admin_url( array( 'page' => 'jetpack-debugger' ) ) ),
4664                        esc_url( Redirect::get_url( 'jetpack-support-getting-started-troubleshooting-tips' ) )
4665                    );
4666                    ?>
4667                </p>
4668            </div>
4669        </div>
4670        <style>
4671            #jetpack-recheck-ssl-output { margin-left: 5px; color: red; }
4672        </style>
4673        <script type="text/javascript">
4674            jQuery( document ).ready( function( $ ) {
4675                $( '#jetpack-recheck-ssl-button' ).click( function( e ) {
4676                    var $this = $( this );
4677                    $this.html( <?php echo wp_json_encode( esc_html__( 'Checking', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4678                    $( '#jetpack-recheck-ssl-output' ).html( '' );
4679                    e.preventDefault();
4680                    var data = { action: 'jetpack-recheck-ssl', 'ajax-nonce': <?php echo wp_json_encode( $ajax_nonce, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> };
4681                    $.post( ajaxurl, data )
4682                    .done( function( response ) {
4683                        if ( response.enabled ) {
4684                            $( '#jetpack-ssl-warning' ).hide();
4685                        } else {
4686                            this.html( <?php echo wp_json_encode( esc_html__( 'Try again', 'jetpack' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
4687                            $( '#jetpack-recheck-ssl-output' ).html( 'SSL Failed: ' + response.message );
4688                        }
4689                    }.bind( $this ) );
4690                } );
4691            } );
4692        </script>
4693
4694        <?php
4695    }
4696
4697    /**
4698     * Returns the connection manager object.
4699     *
4700     * @return Automattic\Jetpack\Connection\Manager
4701     */
4702    public static function connection() {
4703        $jetpack = static::init();
4704
4705        // If the connection manager hasn't been instantiated, do that now.
4706        if ( ! $jetpack->connection_manager ) {
4707            $jetpack->connection_manager = new Connection_Manager( 'jetpack' );
4708        }
4709
4710        return $jetpack->connection_manager;
4711    }
4712
4713    /**
4714     * Get verification secrets.
4715     *
4716     * @param string $action Action name.
4717     * @param int    $user_id User ID.
4718     *
4719     * @return array|string|WP_Error
4720     */
4721    public static function get_secrets( $action, $user_id ) {
4722        $secrets = ( new Secrets() )->get( $action, $user_id );
4723
4724        if ( Secrets::SECRETS_MISSING === $secrets ) {
4725            return new WP_Error( 'verify_secrets_missing', 'Verification secrets not found' );
4726        }
4727
4728        if ( Secrets::SECRETS_EXPIRED === $secrets ) {
4729            return new WP_Error( 'verify_secrets_expired', 'Verification took too long' );
4730        }
4731
4732        return $secrets;
4733    }
4734
4735    /**
4736     * Filters the token request body to include tracking properties.
4737     *
4738     * @param array $properties Token request properties.
4739     *
4740     * @return array amended properties.
4741     */
4742    public static function filter_token_request_body( $properties ) {
4743        $tracking        = new Tracking();
4744        $tracks_identity = $tracking->tracks_get_identity( get_current_user_id() );
4745
4746        return array_merge(
4747            $properties,
4748            array(
4749                '_ui' => $tracks_identity['_ui'],
4750                '_ut' => $tracks_identity['_ut'],
4751            )
4752        );
4753    }
4754
4755    /**
4756     * If the db version is showing something other that what we've got now, bump it to current.
4757     *
4758     * @return bool True if the option was incorrect and updated, false if nothing happened.
4759     */
4760    public static function maybe_set_version_option() {
4761        list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
4762        if ( JETPACK__VERSION !== $version ) {
4763            Jetpack_Options::update_option( 'version', JETPACK__VERSION . ':' . time() );
4764
4765            if ( version_compare( JETPACK__VERSION, $version, '>' ) ) {
4766                /** This action is documented in class.jetpack.php */
4767                do_action( 'updating_jetpack_version', JETPACK__VERSION, $version );
4768            }
4769
4770            return true;
4771        }
4772        return false;
4773    }
4774
4775    /* Client Server API */
4776
4777    /**
4778     * State is passed via cookies from one request to the next, but never to subsequent requests.
4779     * SET: state( $key, $value );
4780     * GET: $value = state( $key );
4781     *
4782     * @param string $key State key.
4783     * @param string $value Value.
4784     * @param bool   $restate Reset the cookie (private).
4785     */
4786    public static function state( $key = null, $value = null, $restate = false ) {
4787        return ( new CookieState() )->state( $key, $value, $restate );
4788    }
4789
4790    /**
4791     * Set an empty state.
4792     *
4793     * @return void
4794     */
4795    public static function restate() {
4796        self::state( null, null, true );
4797    }
4798
4799    /**
4800     * Determines whether the jetpackState[$key] value should be added to the
4801     * cookie.
4802     *
4803     * @param string $key The state key.
4804     *
4805     * @return boolean Whether the value should be added to the cookie.
4806     */
4807    public static function should_set_cookie( $key ) {
4808        return ( new CookieState() )->should_set_cookie( $key );
4809    }
4810
4811    /**
4812     * Check if site is publicly accessible.
4813     *
4814     * @param string $file Module file.
4815     *
4816     * @return void
4817     */
4818    public static function check_privacy( $file ) {
4819        static $is_site_publicly_accessible = null;
4820
4821        if ( $is_site_publicly_accessible === null ) {
4822            $is_site_publicly_accessible = false;
4823
4824            $rpc = new Jetpack_IXR_Client();
4825
4826            $success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
4827            if ( $success ) {
4828                $response = $rpc->getResponse();
4829                if ( $response ) {
4830                    $is_site_publicly_accessible = true;
4831                }
4832            }
4833
4834            Jetpack_Options::update_option( 'public', (int) $is_site_publicly_accessible );
4835        }
4836
4837        if ( $is_site_publicly_accessible ) {
4838            return;
4839        }
4840
4841        $module_slug = self::get_module_slug( $file );
4842
4843        $privacy_checks = self::state( 'privacy_checks' );
4844        if ( ! $privacy_checks ) {
4845            $privacy_checks = $module_slug;
4846        } else {
4847            $privacy_checks .= ",$module_slug";
4848        }
4849
4850        self::state( 'privacy_checks', $privacy_checks );
4851    }
4852
4853    /* JSON API Authorization */
4854
4855    /**
4856     * Handles the login action for Authorizing the JSON API
4857     */
4858    public function login_form_json_api_authorization() {
4859        $authorize_json_api = new Authorize_Json_Api();
4860        $authorize_json_api->verify_json_api_authorization_request();
4861
4862        add_action( 'wp_login', array( $authorize_json_api, 'store_json_api_authorization_token' ), 10, 2 );
4863
4864        add_action( 'login_message', array( $authorize_json_api, 'login_message_json_api_authorization' ) );
4865        add_action( 'login_form', array( $this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
4866        add_filter( 'site_url', array( $this, 'post_login_form_to_signed_url' ), 10, 3 );
4867    }
4868
4869    /**
4870     * Make sure the login form is POSTed to the signed URL so we can reverify the request.
4871     *
4872     * @param string $url Redirect URL.
4873     * @param string $path Path.
4874     * @param string $scheme URL Scheme.
4875     */
4876    public function post_login_form_to_signed_url( $url, $path, $scheme ) {
4877        if ( 'wp-login.php' !== $path || ( 'login_post' !== $scheme && 'login' !== $scheme ) ) {
4878            return $url;
4879        }
4880        $query_string = isset( $_SERVER['QUERY_STRING'] ) ? wp_unslash( $_SERVER['QUERY_STRING'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
4881        $parsed_url   = wp_parse_url( $url );
4882        $url          = strtok( $url, '?' );
4883        $url          = "$url?{$query_string}";
4884        if ( ! empty( $parsed_url['query'] ) ) {
4885            $url .= "&{$parsed_url['query']}";
4886        }
4887
4888        return $url;
4889    }
4890
4891    /**
4892     * Make sure the POSTed request is handled by the same action.
4893     */
4894    public function preserve_action_in_login_form_for_json_api_authorization() {
4895        $http_host   = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- escaped with esc_url below.
4896        $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- escaped with esc_url below.
4897        echo "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\n";
4898        echo "<input type='hidden' name='jetpack_json_api_original_query' value='" . esc_url( set_url_scheme( $http_host . $request_uri ) ) . "' />\n";
4899    }
4900
4901    /**
4902     * If someone logs in to approve API access, store the Access Code in usermeta.
4903     *
4904     * @deprecated 13.4
4905     *
4906     * @param string  $user_login Unused.
4907     * @param WP_User $user User logged in.
4908     */
4909    public function store_json_api_authorization_token( $user_login, $user ) {
4910        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::store_json_api_authorization_token' );
4911
4912        return ( new Authorize_Json_Api() )->store_json_api_authorization_token( $user_login, $user );
4913    }
4914
4915    /**
4916     * Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
4917     *
4918     * To be used with a filter of allowed domains for a redirect.
4919     *
4920     * @deprecated 13.4
4921     *
4922     * @param array $domains Allowed WP.com Environments.
4923     */
4924    public function allow_wpcom_public_api_domain( $domains ) {
4925        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Status\\Host::allow_wpcom_public_api_domain' );
4926
4927        return Host::allow_wpcom_public_api_domain( $domains );
4928    }
4929
4930    /**
4931     * Check if the redirect is encoded.
4932     *
4933     * @deprecated 13.4
4934     *
4935     * @param string $redirect_url Redirect URL.
4936     *
4937     * @return bool If redirect has been encoded.
4938     */
4939    public static function is_redirect_encoded( $redirect_url ) {
4940        _deprecated_function( __METHOD__, 'jetpack-13.4' );
4941        return preg_match( '/https?%3A%2F%2F/i', $redirect_url ) > 0;
4942    }
4943
4944    /**
4945     * Add all wordpress.com environments to the safe redirect allowed list.
4946     *
4947     * To be used with a filter of allowed domains for a redirect.
4948     *
4949     * @param array $domains Allowed WP.com Environments.
4950     *
4951     * @deprecated since 11.1
4952     */
4953    public function allow_wpcom_environments( $domains ) {
4954        return Host::allow_wpcom_environments( $domains );
4955    }
4956
4957    /**
4958     * Add the Access Code details to the public-api.wordpress.com redirect.
4959     *
4960     * @deprecated 13.4
4961     *
4962     * @param string  $redirect_to URL.
4963     * @param string  $original_redirect_to URL.
4964     * @param WP_User $user WP_User for the redirect.
4965     *
4966     * @return string
4967     */
4968    public function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
4969        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::add_token_to_login_redirect_json_api_authorization' );
4970
4971        return ( new Authorize_Json_Api() )->add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user );
4972    }
4973
4974    /**
4975     * Verifies the request by checking the signature
4976     *
4977     * @deprecated 13.4
4978     *
4979     * @since 4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
4980     * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
4981     *
4982     * @param null|array $environment Value to override $_REQUEST.
4983     */
4984    public function verify_json_api_authorization_request( $environment = null ) {
4985        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::verify_json_api_authorization_request' );
4986
4987        return ( new Authorize_Json_Api() )->verify_json_api_authorization_request( $environment );
4988    }
4989
4990    /**
4991     * HTML for the JSON API authorization notice.
4992     *
4993     * @deprecated 13.4
4994     *
4995     * @return string
4996     */
4997    public function login_message_json_api_authorization() {
4998        _deprecated_function( __METHOD__, 'jetpack-13.4', 'Automattic\\Jetpack\\Connection\\Authorize_Json_Api::login_message_json_api_authorization' );
4999
5000        return ( new Authorize_Json_Api() )->login_message_json_api_authorization();
5001    }
5002
5003    /**
5004     * Get $content_width, but with a <s>twist</s> filter.
5005     */
5006    public static function get_content_width() {
5007        $content_width = ( isset( $GLOBALS['content_width'] ) && is_numeric( $GLOBALS['content_width'] ) )
5008            ? $GLOBALS['content_width']
5009            : false;
5010        /**
5011         * Filter the Content Width value.
5012         *
5013         * @since 2.2.3
5014         *
5015         * @param string $content_width Content Width value.
5016         */
5017        return apply_filters( 'jetpack_content_width', $content_width );
5018    }
5019
5020    /**
5021     * Pings the WordPress.com Mirror Site for the specified options.
5022     *
5023     * @param string|array $option_names The option names to request from the WordPress.com Mirror Site.
5024     *
5025     * @return array An associative array of the option values as stored in the WordPress.com Mirror Site
5026     */
5027    public function get_cloud_site_options( $option_names ) {
5028        $option_names = array_filter( (array) $option_names, 'is_string' );
5029
5030        $xml = new Jetpack_IXR_Client();
5031        $xml->query( 'jetpack.fetchSiteOptions', $option_names );
5032        if ( $xml->isError() ) {
5033            return array(
5034                'error_code' => $xml->getErrorCode(),
5035                'error_msg'  => $xml->getErrorMessage(),
5036            );
5037        }
5038        $cloud_site_options = $xml->getResponse();
5039
5040        return $cloud_site_options;
5041    }
5042
5043    /**
5044     * Checks if the site is currently in an identity crisis.
5045     *
5046     * @return array|bool Array of options that are in a crisis, or false if everything is OK.
5047     */
5048    public static function check_identity_crisis() {
5049        if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() || ! Identity_Crisis::validate_sync_error_idc_option() ) {
5050            return false;
5051        }
5052        return Jetpack_Options::get_option( 'sync_error_idc' );
5053    }
5054
5055    /**
5056     * Normalizes a url by doing three things:
5057     *  - Strips protocol
5058     *  - Strips www
5059     *  - Adds a trailing slash
5060     *
5061     * @since 4.4.0
5062     * @param string $url URL.
5063     * @return WP_Error|string
5064     */
5065    public static function normalize_url_protocol_agnostic( $url ) {
5066        $parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
5067        if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
5068            /* translators: URL string */
5069            return new WP_Error( 'cannot_parse_url', sprintf( esc_html__( 'Cannot parse URL %s', 'jetpack' ), $url ) );
5070        }
5071
5072        // Strip www and protocols.
5073        $url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
5074        return $url;
5075    }
5076
5077    /**
5078     * Maybe Use a .min.css stylesheet, maybe not.
5079     *
5080     * Hooks onto `plugins_url` filter at priority 1, and accepts all 3 args.
5081     *
5082     * @param string $url URL.
5083     * @param string $path File path.
5084     * @param string $plugin Plugin.
5085     *
5086     * @return mixed
5087     */
5088    public static function maybe_min_asset( $url, $path, $plugin ) {
5089        // Short out on things trying to find actual paths.
5090        if ( ! $path || empty( $plugin ) ) {
5091            return $url;
5092        }
5093
5094        $path = ltrim( $path, '/' );
5095
5096        // Strip out the abspath.
5097        $base = dirname( plugin_basename( $plugin ) );
5098
5099        // Short out on non-Jetpack assets.
5100        if ( ! str_starts_with( $base, 'jetpack/' ) ) {
5101            return $url;
5102        }
5103
5104        // File name parsing.
5105        $file              = "{$base}/{$path}";
5106        $full_path         = JETPACK__PLUGIN_DIR . substr( $file, 8 );
5107        $file_name         = substr( $full_path, strrpos( $full_path, '/' ) + 1 );
5108        $file_name_parts_r = array_reverse( explode( '.', $file_name ) );
5109        $extension         = array_shift( $file_name_parts_r );
5110
5111        if ( in_array( strtolower( $extension ), array( 'css', 'js' ), true ) ) {
5112            // Already pointing at the minified version.
5113            if ( 'min' === $file_name_parts_r[0] ) {
5114                return $url;
5115            }
5116
5117            $min_full_path = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $full_path );
5118            if ( file_exists( $min_full_path ) ) {
5119                $url = preg_replace( "#\.{$extension}$#", ".min.{$extension}", $url );
5120                // If it's a CSS file, stash it so we can set the .min suffix for rtl-ing.
5121                if ( 'css' === $extension ) {
5122                    $key                      = str_replace( JETPACK__PLUGIN_DIR, 'jetpack/', $min_full_path );
5123                    self::$min_assets[ $key ] = $path;
5124                }
5125            }
5126        }
5127
5128        return $url;
5129    }
5130
5131    /**
5132     * If the asset is minified, let's flag .min as the suffix.
5133     *
5134     * Attached to `style_loader_src` filter.
5135     *
5136     * @param string $src source file.
5137     * @param string $handle The registered handle of the script in question.
5138     *
5139     * @return mixed
5140     */
5141    public static function set_suffix_on_min( $src, $handle ) {
5142        if ( ! is_string( $src ) || ! str_contains( $src, '.min.css' ) ) {
5143            return $src;
5144        }
5145
5146        if ( ! empty( self::$min_assets ) ) {
5147            foreach ( self::$min_assets as $file => $path ) {
5148                if ( str_contains( $src, $file ) ) {
5149                    wp_style_add_data( $handle, 'suffix', '.min' );
5150                    return $src;
5151                }
5152            }
5153        }
5154
5155        return $src;
5156    }
5157
5158    /**
5159     * Maybe inlines a stylesheet.
5160     *
5161     * @deprecated since 11.7.
5162     *
5163     * @param string $tag The tag that would link to the external asset.
5164     * @param string $handle The registered handle of the script in question.
5165     *
5166     * @return string
5167     */
5168    public static function maybe_inline_style( $tag, $handle ) { //phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
5169        _deprecated_function( __METHOD__, '11.7', 'wp_maybe_inline_styles' );
5170
5171        return $tag;
5172    }
5173
5174    /**
5175     * Loads a view file from the views
5176     *
5177     * Data passed in with the $data parameter will be available in the
5178     * template file as $data['value']
5179     *
5180     * @html-template-var array $data
5181     *
5182     * @param string $template - Template file to load.
5183     * @param array  $data - Any data to pass along to the template.
5184     * @return boolean - If template file was found.
5185     **/
5186    public function load_view( $template, $data = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is used via the required files.
5187        $views_dir = JETPACK__PLUGIN_DIR . 'views/';
5188
5189        if ( file_exists( $views_dir . $template ) ) {
5190            require_once $views_dir . $template;
5191            return true;
5192        }
5193
5194        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
5195            trigger_error( sprintf( 'Jetpack: Unable to find view file: %s', esc_html( $views_dir . $template ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
5196        }
5197        return false;
5198    }
5199
5200    /**
5201     * Throws warnings for deprecated hooks to be removed from Jetpack that cannot remain in the original place in the code.
5202     */
5203    public function deprecated_hooks() {
5204        $filter_deprecated_list = array(
5205            'jetpack_bail_on_shortcode'                    => array(
5206                'replacement' => 'jetpack_shortcodes_to_include',
5207                'version'     => 'jetpack-3.1.0',
5208            ),
5209            'wpl_sharing_2014_1'                           => array(
5210                'replacement' => null,
5211                'version'     => 'jetpack-3.6.0',
5212            ),
5213            'jetpack-tools-to-include'                     => array(
5214                'replacement' => 'jetpack_tools_to_include',
5215                'version'     => 'jetpack-3.9.0',
5216            ),
5217            'jetpack_identity_crisis_options_to_check'     => array(
5218                'replacement' => null,
5219                'version'     => 'jetpack-4.0.0',
5220            ),
5221            'update_option_jetpack_single_user_site'       => array(
5222                'replacement' => null,
5223                'version'     => 'jetpack-4.3.0',
5224            ),
5225            'audio_player_default_colors'                  => array(
5226                'replacement' => null,
5227                'version'     => 'jetpack-4.3.0',
5228            ),
5229            'add_option_jetpack_featured_images_enabled'   => array(
5230                'replacement' => null,
5231                'version'     => 'jetpack-4.3.0',
5232            ),
5233            'add_option_jetpack_update_details'            => array(
5234                'replacement' => null,
5235                'version'     => 'jetpack-4.3.0',
5236            ),
5237            'add_option_jetpack_updates'                   => array(
5238                'replacement' => null,
5239                'version'     => 'jetpack-4.3.0',
5240            ),
5241            'add_option_jetpack_network_name'              => array(
5242                'replacement' => null,
5243                'version'     => 'jetpack-4.3.0',
5244            ),
5245            'add_option_jetpack_network_allow_new_registrations' => array(
5246                'replacement' => null,
5247                'version'     => 'jetpack-4.3.0',
5248            ),
5249            'add_option_jetpack_network_add_new_users'     => array(
5250                'replacement' => null,
5251                'version'     => 'jetpack-4.3.0',
5252            ),
5253            'add_option_jetpack_network_site_upload_space' => array(
5254                'replacement' => null,
5255                'version'     => 'jetpack-4.3.0',
5256            ),
5257            'add_option_jetpack_network_upload_file_types' => array(
5258                'replacement' => null,
5259                'version'     => 'jetpack-4.3.0',
5260            ),
5261            'add_option_jetpack_network_enable_administration_menus' => array(
5262                'replacement' => null,
5263                'version'     => 'jetpack-4.3.0',
5264            ),
5265            'add_option_jetpack_is_multi_site'             => array(
5266                'replacement' => null,
5267                'version'     => 'jetpack-4.3.0',
5268            ),
5269            'add_option_jetpack_is_main_network'           => array(
5270                'replacement' => null,
5271                'version'     => 'jetpack-4.3.0',
5272            ),
5273            'add_option_jetpack_main_network_site'         => array(
5274                'replacement' => null,
5275                'version'     => 'jetpack-4.3.0',
5276            ),
5277            'jetpack_sync_all_registered_options'          => array(
5278                'replacement' => null,
5279                'version'     => 'jetpack-4.3.0',
5280            ),
5281            'jetpack_has_identity_crisis'                  => array(
5282                'replacement' => 'jetpack_sync_error_idc_validation',
5283                'version'     => 'jetpack-4.4.0',
5284            ),
5285            'jetpack_is_post_mailable'                     => array(
5286                'replacement' => null,
5287                'version'     => 'jetpack-4.4.0',
5288            ),
5289            'jetpack_seo_site_host'                        => array(
5290                'replacement' => null,
5291                'version'     => 'jetpack-5.1.0',
5292            ),
5293            'jetpack_installed_plugin'                     => array(
5294                'replacement' => 'jetpack_plugin_installed',
5295                'version'     => 'jetpack-6.0.0',
5296            ),
5297            'jetpack_holiday_snow_option_name'             => array(
5298                'replacement' => null,
5299                'version'     => 'jetpack-6.0.0',
5300            ),
5301            'jetpack_holiday_chance_of_snow'               => array(
5302                'replacement' => null,
5303                'version'     => 'jetpack-6.0.0',
5304            ),
5305            'jetpack_holiday_snow_js_url'                  => array(
5306                'replacement' => null,
5307                'version'     => 'jetpack-6.0.0',
5308            ),
5309            'jetpack_is_holiday_snow_season'               => array(
5310                'replacement' => null,
5311                'version'     => 'jetpack-6.0.0',
5312            ),
5313            'jetpack_holiday_snow_option_updated'          => array(
5314                'replacement' => null,
5315                'version'     => 'jetpack-6.0.0',
5316            ),
5317            'jetpack_holiday_snowing'                      => array(
5318                'replacement' => null,
5319                'version'     => 'jetpack-6.0.0',
5320            ),
5321            'jetpack_sso_auth_cookie_expirtation'          => array(
5322                'replacement' => 'jetpack_sso_auth_cookie_expiration',
5323                'version'     => 'jetpack-6.1.0',
5324            ),
5325            'jetpack_cache_plans'                          => array(
5326                'replacement' => null,
5327                'version'     => 'jetpack-6.1.0',
5328            ),
5329            'jetpack_enable_site_verification'             => array(
5330                'replacement' => null,
5331                'version'     => 'jetpack-6.5.0',
5332            ),
5333            'can_display_jetpack_manage_notice'            => array(
5334                'replacement' => null,
5335                'version'     => 'jetpack-7.3.0',
5336            ),
5337            'atd_http_post_timeout'                        => array(
5338                'replacement' => null,
5339                'version'     => 'jetpack-7.3.0',
5340            ),
5341            'atd_service_domain'                           => array(
5342                'replacement' => null,
5343                'version'     => 'jetpack-7.3.0',
5344            ),
5345            'atd_load_scripts'                             => array(
5346                'replacement' => null,
5347                'version'     => 'jetpack-7.3.0',
5348            ),
5349            'jetpack_widget_authors_exclude'               => array(
5350                'replacement' => 'jetpack_widget_authors_params',
5351                'version'     => 'jetpack-7.7.0',
5352            ),
5353            // Removed in Jetpack 7.9.0.
5354            'jetpack_pwa_manifest'                         => array(
5355                'replacement' => null,
5356                'version'     => 'jetpack-7.9.0',
5357            ),
5358            'jetpack_pwa_background_color'                 => array(
5359                'replacement' => null,
5360                'version'     => 'jetpack-7.9.0',
5361            ),
5362            'jetpack_check_mobile'                         => array(
5363                'replacement' => null,
5364                'version'     => 'jetpack-8.3.0',
5365            ),
5366            'jetpack_mobile_stylesheet'                    => array(
5367                'replacement' => null,
5368                'version'     => 'jetpack-8.3.0',
5369            ),
5370            'jetpack_mobile_template'                      => array(
5371                'replacement' => null,
5372                'version'     => 'jetpack-8.3.0',
5373            ),
5374            'jetpack_mobile_theme_menu'                    => array(
5375                'replacement' => null,
5376                'version'     => 'jetpack-8.3.0',
5377            ),
5378            'minileven_show_featured_images'               => array(
5379                'replacement' => null,
5380                'version'     => 'jetpack-8.3.0',
5381            ),
5382            'minileven_attachment_size'                    => array(
5383                'replacement' => null,
5384                'version'     => 'jetpack-8.3.0',
5385            ),
5386            'instagram_cache_oembed_api_response_body'     => array(
5387                'replacement' => null,
5388                'version'     => 'jetpack-9.1.0',
5389            ),
5390            'jetpack_can_make_outbound_https'              => array(
5391                'replacement' => null,
5392                'version'     => 'jetpack-9.1.0',
5393            ),
5394            'sharing_email_can_send'                       => array(
5395                'replacement' => null,
5396                'version'     => 'jetpack-11.0.0',
5397            ),
5398            'sharing_email_check'                          => array(
5399                'replacement' => null,
5400                'version'     => 'jetpack-11.0.0',
5401            ),
5402            'sharing_services_email'                       => array(
5403                'replacement' => null,
5404                'version'     => 'jetpack-11.0.0',
5405            ),
5406            'jetpack_dsp_promote_posts_enabled'            => array(
5407                'replacement' => null,
5408                'version'     => 'jetpack-11.8.0',
5409            ),
5410            'jetpack_are_blogging_prompts_enabled'         => array(
5411                'replacement' => null,
5412                'version'     => 'jetpack-11.8.0',
5413            ),
5414            'jetpack_subscriptions_modal_enabled'          => array(
5415                'replacement' => null,
5416                'version'     => 'jetpack-12.7.0',
5417            ),
5418            'jetpack_pre_connection_prompt_helpers'        => array(
5419                'replacement' => null,
5420                'version'     => 'jetpack-13.2.0',
5421            ),
5422            'jetpack_contact_form_use_package'             => array(
5423                'replacement' => null,
5424                'version'     => 'jetpack-13.4.0',
5425            ),
5426            // jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
5427            // We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
5428        );
5429
5430        foreach ( $filter_deprecated_list as $tag => $args ) {
5431            if ( has_filter( $tag ) ) {
5432                apply_filters_deprecated( $tag, array( null ), $args['version'], $args['replacement'] );
5433            }
5434        }
5435
5436        $action_deprecated_list = array(
5437            'jetpack_updated_theme'        => array(
5438                'replacement' => 'jetpack_updated_themes',
5439                'version'     => 'jetpack-6.2.0',
5440            ),
5441            'atd_http_post_error'          => array(
5442                'replacement' => null,
5443                'version'     => 'jetpack-7.3.0',
5444            ),
5445            'mobile_reject_mobile'         => array(
5446                'replacement' => null,
5447                'version'     => 'jetpack-8.3.0',
5448            ),
5449            'mobile_force_mobile'          => array(
5450                'replacement' => null,
5451                'version'     => 'jetpack-8.3.0',
5452            ),
5453            'mobile_app_promo_download'    => array(
5454                'replacement' => null,
5455                'version'     => 'jetpack-8.3.0',
5456            ),
5457            'mobile_setup'                 => array(
5458                'replacement' => null,
5459                'version'     => 'jetpack-8.3.0',
5460            ),
5461            'jetpack_mobile_footer_before' => array(
5462                'replacement' => null,
5463                'version'     => 'jetpack-8.3.0',
5464            ),
5465            'wp_mobile_theme_footer'       => array(
5466                'replacement' => null,
5467                'version'     => 'jetpack-8.3.0',
5468            ),
5469            'minileven_credits'            => array(
5470                'replacement' => null,
5471                'version'     => 'jetpack-8.3.0',
5472            ),
5473            'jetpack_mobile_header_before' => array(
5474                'replacement' => null,
5475                'version'     => 'jetpack-8.3.0',
5476            ),
5477            'jetpack_mobile_header_after'  => array(
5478                'replacement' => null,
5479                'version'     => 'jetpack-8.3.0',
5480            ),
5481            'sharing_email_dialog'         => array(
5482                'replacement' => null,
5483                'version'     => 'jetpack-11.0.0',
5484            ),
5485            'sharing_email_send_post'      => array(
5486                'replacement' => null,
5487                'version'     => 'jetpack-11.0.0',
5488            ),
5489        );
5490
5491        foreach ( $action_deprecated_list as $tag => $args ) {
5492            if ( has_action( $tag ) ) {
5493                do_action_deprecated( $tag, array(), $args['version'], $args['replacement'] );
5494            }
5495        }
5496    }
5497
5498    /**
5499     * Converts any url in a stylesheet, to the correct absolute url.
5500     *
5501     * Considerations:
5502     *  - Normal, relative URLs     `feh.png`
5503     *  - Data URLs                 `data:image/gif;base64,eh129ehiuehjdhsa==`
5504     *  - Schema-agnostic URLs      `//domain.com/feh.png`
5505     *  - Absolute URLs             `http://domain.com/feh.png`
5506     *  - Domain root relative URLs `/feh.png`
5507     *
5508     * @param string $css The raw CSS -- should be read in directly from the file.
5509     * @param string $css_file_url The URL that the file can be accessed at, for calculating paths from.
5510     *
5511     * @return mixed|string
5512     */
5513    public static function absolutize_css_urls( $css, $css_file_url ) {
5514        $pattern = '#url\((?P<path>[^)]*)\)#i';
5515        $css_dir = dirname( $css_file_url );
5516        $p       = wp_parse_url( $css_dir );
5517        $domain  = sprintf(
5518            '%1$s//%2$s%3$s%4$s',
5519            isset( $p['scheme'] ) ? "{$p['scheme']}:" : '',
5520            isset( $p['user'], $p['pass'] ) ? "{$p['user']}:{$p['pass']}@" : '',
5521            $p['host'],
5522            isset( $p['port'] ) ? ":{$p['port']}" : ''
5523        );
5524
5525        if ( preg_match_all( $pattern, $css, $matches, PREG_SET_ORDER ) ) {
5526            $replace = array();
5527            $find    = array();
5528            foreach ( $matches as $match ) {
5529                $url = trim( $match['path'], "'\" \t" );
5530
5531                // If this is a data url, we don't want to mess with it.
5532                if ( str_starts_with( $url, 'data:' ) ) {
5533                    continue;
5534                }
5535
5536                // If this is an absolute or protocol-agnostic url,
5537                // we don't want to mess with it.
5538                if ( preg_match( '#^(https?:)?//#i', $url ) ) {
5539                    continue;
5540                }
5541
5542                switch ( substr( $url, 0, 1 ) ) {
5543                    case '/':
5544                        $absolute = $domain . $url;
5545                        break;
5546                    default:
5547                        $absolute = $css_dir . '/' . $url;
5548                }
5549
5550                $find[]    = $match[0];
5551                $replace[] = sprintf( 'url("%s")', $absolute );
5552            }
5553            $css = str_replace( $find, $replace, $css );
5554        }
5555
5556        return $css;
5557    }
5558
5559    /**
5560     * Check the heartbeat data
5561     *
5562     * Organizes the heartbeat data by severity.  For example, if the site
5563     * is in an ID crisis, it will be in the $filtered_data['bad'] array.
5564     *
5565     * Data will be added to "caution" array, if it either:
5566     *  - Out of date Jetpack version
5567     *  - Out of date WP version
5568     *  - Out of date PHP version
5569     *
5570     * $return array $filtered_data
5571     */
5572    public static function jetpack_check_heartbeat_data() {
5573        $raw_data = Jetpack_Heartbeat::generate_stats_array();
5574
5575        $good    = array();
5576        $caution = array();
5577        $bad     = array();
5578
5579        foreach ( $raw_data as $stat => $value ) {
5580
5581            // Check jetpack version.
5582            if ( 'version' === $stat ) {
5583                if ( version_compare( $value, JETPACK__VERSION, '<' ) ) {
5584                    $caution[ $stat ] = $value . ' - min supported is ' . JETPACK__VERSION;
5585                    continue;
5586                }
5587            }
5588
5589            // Check WP version.
5590            if ( 'wp-version' === $stat ) {
5591                if ( version_compare( $value, JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
5592                    $caution[ $stat ] = $value . ' - min supported is ' . JETPACK__MINIMUM_WP_VERSION;
5593                    continue;
5594                }
5595            }
5596
5597            // Check PHP version.
5598            if ( 'php-version' === $stat ) {
5599                if ( version_compare( PHP_VERSION, JETPACK__MINIMUM_PHP_VERSION, '<' ) ) {
5600                    $caution[ $stat ] = $value . ' - min supported is ' . JETPACK__MINIMUM_PHP_VERSION;
5601                    continue;
5602                }
5603            }
5604
5605            // Check ID crisis.
5606            if ( 'identitycrisis' === $stat ) {
5607                if ( 'yes' === $value ) {
5608                    $bad[ $stat ] = $value;
5609                    continue;
5610                }
5611            }
5612
5613            // The rest are good :).
5614            $good[ $stat ] = $value;
5615        }
5616
5617        $filtered_data = array(
5618            'good'    => $good,
5619            'caution' => $caution,
5620            'bad'     => $bad,
5621        );
5622
5623        return $filtered_data;
5624    }
5625
5626    /**
5627     * This method is used to organize all options that can be reset
5628     * without disconnecting Jetpack.
5629     *
5630     * It is used in class.jetpack-cli.php to reset options
5631     *
5632     * @since 5.4.0 Logic moved to Jetpack_Options class. Method left in Jetpack class for backwards compat.
5633     *
5634     * @return array of options to delete.
5635     */
5636    public static function get_jetpack_options_for_reset() {
5637        return Jetpack_Options::get_options_for_reset();
5638    }
5639
5640    /**
5641     * Get the user's meta box order.
5642     *
5643     * @param array $sorted Value for the user's option.
5644     * @return mixed
5645     */
5646    public function get_user_option_meta_box_order_dashboard( $sorted ) {
5647        if ( ! is_array( $sorted ) ) {
5648            return $sorted;
5649        }
5650
5651        foreach ( $sorted as $box_context => $ids ) {
5652            if ( ! str_contains( $ids, 'dashboard_stats' ) ) {
5653                // If the old id isn't anywhere in the ids, don't bother exploding and fail out.
5654                continue;
5655            }
5656
5657            $ids_array = explode( ',', $ids );
5658            $key       = array_search( 'dashboard_stats', $ids_array, true );
5659
5660            if ( false !== $key ) {
5661                // If we've found that exact value in the option (and not `google_dashboard_stats` for example).
5662                $ids_array[ $key ]      = 'jetpack_summary_widget';
5663                $sorted[ $box_context ] = implode( ',', $ids_array );
5664                // We've found it, stop searching, and just return.
5665                break;
5666            }
5667        }
5668
5669        return $sorted;
5670    }
5671    /**
5672     * Checks if Akismet is active and working.
5673     *
5674     * We dropped support for Akismet 3.0 with Jetpack 6.1.1 while introducing a check for an Akismet valid key
5675     * that implied usage of methods present since more recent version.
5676     * See https://github.com/Automattic/jetpack/pull/9585
5677     *
5678     * @since  5.1.0
5679     *
5680     * @return bool True = Akismet available. False = Aksimet not available.
5681     */
5682    public static function is_akismet_active() {
5683        static $status = null;
5684
5685        if ( $status !== null ) {
5686            return $status;
5687        }
5688
5689        // Check if a modern version of Akismet is active.
5690        if ( ! method_exists( 'Akismet', 'http_post' ) ) {
5691            $status = false;
5692            return $status;
5693        }
5694
5695        // Make sure there is a key known to Akismet at all before verifying key.
5696        $akismet_key = Akismet::get_api_key();
5697        if ( ! $akismet_key ) {
5698            $status = false;
5699            return $status;
5700        }
5701
5702        // Possible values: valid, invalid, failure via Akismet. false if no status is cached.
5703        $akismet_key_state = get_transient( 'jetpack_akismet_key_is_valid' );
5704
5705        // Do not used the cache result in wp-admin or REST API requests if the key isn't valid, in case someone is actively renewing, etc.
5706        $recheck = ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && 'valid' !== $akismet_key_state;
5707        // We cache the result of the Akismet key verification for ten minutes.
5708        if ( ! $akismet_key_state || $recheck ) {
5709            $akismet_key_state = Akismet::verify_key( $akismet_key );
5710            set_transient( 'jetpack_akismet_key_is_valid', $akismet_key_state, 10 * MINUTE_IN_SECONDS );
5711        }
5712
5713        $status = 'valid' === $akismet_key_state;
5714
5715        return $status;
5716    }
5717
5718    /**
5719     * Given a minified path, and a non-minified path, will return
5720     * a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
5721     *
5722     * Both `$min_base` and `$non_min_base` are expected to be relative to the
5723     * root Jetpack directory.
5724     *
5725     * @since 5.6.0
5726     *
5727     * @param string $min_path Minimized path.
5728     * @param string $non_min_path Non-minimized path.
5729     * @return string The URL to the file
5730     */
5731    public static function get_file_url_for_environment( $min_path, $non_min_path ) {
5732        return Assets::get_file_url_for_environment( $min_path, $non_min_path );
5733    }
5734
5735    /**
5736     * Checks for whether Jetpack Backup is enabled.
5737     * Will return true if the state of Backup is anything except "unavailable".
5738     *
5739     * @return bool|int|mixed
5740     */
5741    public static function is_rewind_enabled() {
5742        // Rewind is a paid feature, therefore requires a user-level connection.
5743        if ( ! static::connection()->has_connected_owner() ) {
5744            return false;
5745        }
5746        $rewind_enabled = get_transient( 'jetpack_rewind_enabled' );
5747        $recheck        = ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && '0' === $rewind_enabled;
5748        if ( false === $rewind_enabled || $recheck ) {
5749            require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.core-rest-api-endpoints.php';
5750            $rewind_data    = (array) Jetpack_Core_Json_Api_Endpoints::rewind_data();
5751            $rewind_enabled = ( ! is_wp_error( $rewind_data )
5752                && ! empty( $rewind_data['state'] )
5753                && 'active' === $rewind_data['state'] )
5754                ? 1
5755                : 0;
5756            set_transient( 'jetpack_rewind_enabled', $rewind_enabled, 10 * MINUTE_IN_SECONDS );
5757        }
5758        return $rewind_enabled;
5759    }
5760
5761    /**
5762     * Return Calypso environment value; used for developing Jetpack and pairing
5763     * it with different Calypso enrionments, such as localhost.
5764     *
5765     * @deprecated 12.4 Moved to the Status package.
5766     *
5767     * @return string Calypso environment
5768     */
5769    public static function get_calypso_env() {
5770        _deprecated_function( __METHOD__, 'jetpack-12.4', '\Automattic\Jetpack\Status\Host->get_calypso_env' );
5771        return ( new Host() )->get_calypso_env();
5772    }
5773
5774    /**
5775     * Returns the hostname with protocol for Calypso.
5776     * Used for developing Jetpack with Calypso.
5777     *
5778     * @since 8.4.0
5779     *
5780     * @return string Calypso host.
5781     */
5782    public static function get_calypso_host() {
5783        $calypso_env = ( new Host() )->get_calypso_env();
5784        switch ( $calypso_env ) {
5785            case 'development':
5786                return 'http://calypso.localhost:3000/';
5787            case 'wpcalypso':
5788                return 'https://wpcalypso.wordpress.com/';
5789            case 'horizon':
5790                return 'https://horizon.wordpress.com/';
5791            default:
5792                return 'https://wordpress.com/';
5793        }
5794    }
5795
5796    /**
5797     * Handles activating default modules as well general cleanup for the new connection.
5798     *
5799     * @param boolean $activate_sso                 Whether to activate the SSO module when activating default modules.
5800     * @param boolean $redirect_on_activation_error Whether to redirect on activation error.
5801     * @param boolean $send_state_messages          Whether to send state messages.
5802     * @return void
5803     */
5804    public static function handle_post_authorization_actions(
5805        $activate_sso = false,
5806        $redirect_on_activation_error = false,
5807        $send_state_messages = true
5808    ) {
5809        $other_modules = $activate_sso
5810            ? array( 'sso' )
5811            : array();
5812
5813        if ( Jetpack_Options::get_option( 'active_modules_initialized' ) ) {
5814            $active_modules = self::get_active_modules();
5815            self::delete_active_modules();
5816
5817            self::activate_default_modules( 999, 1, array_merge( $active_modules, $other_modules ), $redirect_on_activation_error, $send_state_messages );
5818        } else {
5819            // Default modules that don't require a user were already activated on site_register.
5820            // This time let's activate only those that require a user, this assures we don't reactivate manually deactivated modules while the site was connected only at a site level.
5821            self::activate_default_modules( false, false, $other_modules, $redirect_on_activation_error, $send_state_messages, null, true );
5822            Jetpack_Options::update_option( 'active_modules_initialized', true );
5823        }
5824
5825        // Since this is a fresh connection, be sure to clear out IDC options.
5826        Identity_Crisis::clear_all_idc_options();
5827
5828        if ( $send_state_messages ) {
5829            self::state( 'message', 'authorized' );
5830        }
5831    }
5832
5833    /**
5834     * Whether UI for backups should be displayed.
5835     *
5836     * On WPCom platforms this is gated on the backups-self-serve site feature.
5837     * On self-hosted Jetpack sites it falls back to the jetpack_show_backups filter.
5838     *
5839     * @return bool Should backups UI be displayed?
5840     */
5841    public static function show_backups_ui() {
5842        if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
5843            return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'backups-self-serve' );
5844        }
5845
5846        /**
5847         * Whether UI for backups should be displayed.
5848         *
5849         * @since 6.5.0
5850         *
5851         * @param bool $show_backups Should UI for backups be displayed? True by default.
5852         */
5853        return self::is_plugin_active( 'vaultpress/vaultpress.php' ) || apply_filters( 'jetpack_show_backups', true );
5854    }
5855
5856    /**
5857     * Whether UI for security scanning should be displayed.
5858     *
5859     * On WPCom platforms this is gated on the scan-self-serve site feature.
5860     * On self-hosted Jetpack sites it always returns true.
5861     *
5862     * @return bool Should scan UI be displayed?
5863     */
5864    public static function show_scan_ui() {
5865        if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform() ) {
5866            return function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'scan-self-serve' );
5867        }
5868
5869        return true;
5870    }
5871
5872    /**
5873     * Clean leftoveruser meta.
5874     *
5875     * Delete Jetpack-related user meta when it is no longer needed.
5876     *
5877     * @since 7.3.0
5878     *
5879     * @param int $user_id User ID being updated.
5880     */
5881    public static function user_meta_cleanup( $user_id ) {
5882        $meta_keys = array(
5883            // AtD removed from Jetpack 7.3.
5884            'AtD_options',
5885            'AtD_check_when',
5886            'AtD_guess_lang',
5887            'AtD_ignored_phrases',
5888        );
5889
5890        foreach ( $meta_keys as $meta_key ) {
5891            if ( get_user_meta( $user_id, $meta_key ) ) {
5892                delete_user_meta( $user_id, $meta_key );
5893            }
5894        }
5895    }
5896
5897    /**
5898     * Checks if a Jetpack site is both active and not in offline mode.
5899     *
5900     * This is a DRY function to avoid repeating `Jetpack::is_connection_ready && ! Automattic\Jetpack\Status->is_offline_mode`.
5901     *
5902     * @since 8.8.0
5903     *
5904     * @return bool True if Jetpack is active and not in offline mode.
5905     */
5906    public static function is_active_and_not_offline_mode() {
5907        if ( ! self::is_connection_ready() || ( new Status() )->is_offline_mode() ) {
5908            return false;
5909        }
5910        return true;
5911    }
5912
5913    /**
5914     * Returns the list of products that we have available for purchase.
5915     *
5916     * This method will not take current purchases or upgrades into account
5917     * but is instead a static list of products Jetpack offers with some
5918     * corresponding sales text/materials.
5919     *
5920     * @param bool $show_legacy Determine if we should include legacy product/plan details.
5921     * @return array
5922     */
5923    public static function get_products_for_purchase( $show_legacy = false ) {
5924        $products = array();
5925
5926        $products['backup'] = array(
5927            'title'             => __( 'Jetpack Backup', 'jetpack' ),
5928            'slug'              => 'jetpack_backup_t1_yearly',
5929            'description'       => __( 'Never lose a word, image, page, or time worrying about your site with automated backups & one-click restores.', 'jetpack' ),
5930            'show_promotion'    => true,
5931            'discount_percent'  => 50,
5932            'included_in_plans' => array( 'security' ),
5933            'features'          => array(
5934                _x( 'Real-time cloud backups', 'Backup Product Feature', 'jetpack' ),
5935                _x( '10GB of backup storage', 'Backup Product Feature', 'jetpack' ),
5936                _x( '30-day archive & activity log*', 'Backup Product Feature', 'jetpack' ),
5937                _x( 'One-click restores', 'Backup Product Feature', 'jetpack' ),
5938            ),
5939            'disclaimer'        => array(
5940                'text'      => __( '* Subject to your usage and storage limit.', 'jetpack' ),
5941                'link_text' => __( 'Learn more', 'jetpack' ),
5942                'url'       => Redirect::get_url( 'jetpack-faq-backup-disclaimer' ),
5943            ),
5944        );
5945
5946        $products['creator'] = array(
5947            'title'             => __( 'Jetpack Creator', 'jetpack' ),
5948            'slug'              => 'jetpack_creator_yearly',
5949            'description'       => __( 'Craft stunning content, boost your subscriber base, and monetize your online presence.', 'jetpack' ),
5950            'show_promotion'    => true,
5951            'discount_percent'  => 50,
5952            'included_in_plans' => array( 'complete' ),
5953            'features'          => array(
5954                _x( 'Unlimited subscriber imports', 'Creator Product Feature', 'jetpack' ),
5955                _x( 'Earn more from your content', 'Creator Product Feature', 'jetpack' ),
5956                _x( 'Accept payments with PayPal', 'Creator Product Feature', 'jetpack' ),
5957                _x( 'Increase earnings with WordAds', 'Creator Product Feature', 'jetpack' ),
5958            ),
5959        );
5960
5961        $products['growth'] = array(
5962            'title'             => __( 'Jetpack Growth', 'jetpack' ),
5963            'slug'              => 'jetpack_growth_yearly',
5964            'description'       => __( 'Essential tools to help you grow your audience, track visitor engagement, and turn leads into loyal customers and advocates.', 'jetpack' ),
5965            'show_promotion'    => true,
5966            'discount_percent'  => 50,
5967            'included_in_plans' => array( 'complete' ),
5968            'features'          => array(
5969                _x( 'Jetpack Social', 'Growth Product Feature', 'jetpack' ),
5970                _x( 'Jetpack Stats (10K site views, upgradeable)', 'Growth Product Feature', 'jetpack' ),
5971                _x( 'Unlimited subscriber imports', 'Growth Product Feature', 'jetpack' ),
5972                _x( 'Earn more from your content', 'Growth Product Feature', 'jetpack' ),
5973                _x( 'Accept payments with PayPal', 'Growth Product Feature', 'jetpack' ),
5974                _x( 'Increase earnings with WordAds', 'Growth Product Feature', 'jetpack' ),
5975            ),
5976        );
5977
5978        $products['scan'] = array(
5979            'title'             => __( 'Jetpack Scan', 'jetpack' ),
5980            'slug'              => 'jetpack_scan',
5981            'description'       => __( 'Automatic scanning and one-click fixes keep your site one step ahead of security threats and malware.', 'jetpack' ),
5982            'show_promotion'    => true,
5983            'discount_percent'  => 50,
5984            'included_in_plans' => array( 'security' ),
5985            'features'          => array(
5986                _x( 'Automated daily scanning', 'Scan Product Feature', 'jetpack' ),
5987                _x( 'One-click fixes for most issues', 'Scan Product Feature', 'jetpack' ),
5988                _x( 'Instant email notifications', 'Scan Product Feature', 'jetpack' ),
5989                _x( 'Access to latest Firewall rules', 'Scan Product Feature', 'jetpack' ),
5990            ),
5991        );
5992
5993        $products['search'] = array(
5994            'title'             => __( 'Jetpack Site Search', 'jetpack' ),
5995            'slug'              => 'jetpack_search',
5996            'description'       => __( 'Help your site visitors find answers instantly so they keep reading and buying. Great for sites with a lot of content.', 'jetpack' ),
5997            'show_promotion'    => true,
5998            'discount_percent'  => 50,
5999            'included_in_plans' => array(),
6000            'features'          => array(
6001                _x( 'Instant search and indexing', 'Search Product Feature', 'jetpack' ),
6002                _x( 'Powerful filtering', 'Search Product Feature', 'jetpack' ),
6003                _x( 'Supports 38 languages', 'Search Product Feature', 'jetpack' ),
6004                _x( 'Spelling correction', 'Search Product Feature', 'jetpack' ),
6005            ),
6006        );
6007
6008        $products['akismet'] = array(
6009            'title'             => __( 'Akismet Anti-spam', 'jetpack' ),
6010            'slug'              => 'jetpack_anti_spam',
6011            'description'       => __( 'Save time and get better responses by automatically blocking spam from your comments and forms.', 'jetpack' ),
6012            'show_promotion'    => true,
6013            'discount_percent'  => 50,
6014            'included_in_plans' => array( 'security' ),
6015            'features'          => array(
6016                _x( 'Comment and form spam protection', 'Anti-Spam Product Feature', 'jetpack' ),
6017                _x( 'Block spam without CAPTCHAs', 'Anti-Spam Product Feature', 'jetpack' ),
6018                _x( 'Advanced stats', 'Anti-Spam Product Feature', 'jetpack' ),
6019            ),
6020        );
6021
6022        $products['security'] = array(
6023            'title'             => _x( 'Security', 'Jetpack product name', 'jetpack' ),
6024            'slug'              => 'jetpack_security_t1_yearly',
6025            'description'       => __( 'Comprehensive site security, including Backup, Scan, and Anti-spam.', 'jetpack' ),
6026            'show_promotion'    => true,
6027            'discount_percent'  => 50,
6028            'included_in_plans' => array(),
6029            'features'          => array(
6030                _x( 'Real-time cloud backups with 10GB storage', 'Security Tier 1 Feature', 'jetpack' ),
6031                _x( 'Automated real-time malware scan', 'Security Daily Plan Feature', 'jetpack' ),
6032                _x( 'One-click fixes for most threats', 'Security Daily Plan Feature', 'jetpack' ),
6033                _x( 'Comment & form spam protection', 'Security Daily Plan Feature', 'jetpack' ),
6034            ),
6035        );
6036
6037        $products['videopress'] = array(
6038            'title'             => __( 'Jetpack VideoPress', 'jetpack' ),
6039            'slug'              => 'jetpack_videopress',
6040            'description'       => __( 'High-quality, ad-free video built specifically for WordPress.', 'jetpack' ),
6041            'show_promotion'    => true,
6042            'discount_percent'  => 50,
6043            'included_in_plans' => array(),
6044            'features'          => array(
6045                _x( '1TB of storage', 'VideoPress Product Feature', 'jetpack' ),
6046                _x( 'Built into WordPress editor', 'VideoPress Product Feature', 'jetpack' ),
6047                _x( 'Ad-free and customizable player', 'VideoPress Product Feature', 'jetpack' ),
6048                _x( 'Unlimited users', 'VideoPress Product Feature', 'jetpack' ),
6049            ),
6050        );
6051
6052        if ( $show_legacy ) {
6053            $products['jetpack_backup_daily'] = array(
6054                'title'             => __( 'Jetpack Backup', 'jetpack' ),
6055                'slug'              => 'jetpack_backup_daily',
6056                'description'       => __( 'Never lose a word, image, page, or time worrying about your site with automated backups & one-click restores.', 'jetpack' ),
6057                'show_promotion'    => false,
6058                'discount_percent'  => 0,
6059                'included_in_plans' => array(),
6060                'features'          => array(
6061                    _x( 'Automated daily backups (off-site)', 'Backup Product Feature', 'jetpack' ),
6062                    _x( 'One-click restores', 'Backup Product Feature', 'jetpack' ),
6063                    _x( 'Unlimited backup storage', 'Backup Product Feature', 'jetpack' ),
6064                ),
6065            );
6066        }
6067
6068        return $products;
6069    }
6070
6071    /**
6072     * Register product descriptions for partner coupon usage.
6073     *
6074     * @since 10.4.0
6075     *
6076     * @param array $products An array of registered products.
6077     *
6078     * @return array
6079     */
6080    public function get_partner_coupon_product_descriptions( $products ) {
6081        return array_merge( $products, self::get_products_for_purchase( true ) );
6082    }
6083
6084    /**
6085     * Determine if the current user is allowed to make Jetpack purchases without
6086     * a WordPress.com account
6087     *
6088     * @return boolean True if the user can make purchases, false if not
6089     */
6090    public static function current_user_can_purchase() {
6091
6092        // The site must be site-connected to Jetpack (no users connected).
6093        if ( ! self::connection()->is_site_connection() ) {
6094            return false;
6095        }
6096
6097        // Make sure only administrators can make purchases.
6098        if ( ! current_user_can( 'manage_options' ) ) {
6099            return false;
6100        }
6101
6102        return true;
6103    }
6104
6105    /**
6106     * Add 5-star review link on the Jetpack Plugin meta, in the plugins list table (on the plugins page).
6107     *
6108     * @param array  $plugin_meta An array of the plugin's metadata.
6109     * @param string $plugin_file Path to the plugin file.
6110     *
6111     * @return array $plugin_meta An array of the plugin's metadata.
6112     */
6113    public function add_5_star_review_link( $plugin_meta, $plugin_file ) {
6114        if ( $plugin_file !== 'jetpack/jetpack.php' ) {
6115            return $plugin_meta;
6116        }
6117
6118        $u    = get_current_user_id();
6119        $site = get_site_url();
6120
6121        $plugin_meta[] = '<a href="https://jetpack.com/redirect?source=jetpack-plugin-review&site=' . esc_attr( $site ) . '&u=' . esc_attr( $u ) . '" target="_blank" rel="noopener noreferrer" title="' . esc_attr__( 'Rate Jetpack on WordPress.org', 'jetpack' ) . '" style="color: #ffb900">'
6122            . str_repeat( '<span class="dashicons dashicons-star-filled" style="font-size: 16px; width:16px; height: 16px"></span>', 5 )
6123            . '</a>';
6124
6125        return $plugin_meta;
6126    }
6127
6128    /**
6129     * Lazy instantiation of the Plugin_Tracking object.
6130     *
6131     * @since 13.9
6132     *
6133     * @return void
6134     */
6135    public function initialize_tracking() {
6136        if ( did_action( 'jetpack_initialize_tracking' ) > 1 ) {
6137            // Only need to run once.
6138            return;
6139        }
6140
6141        if ( ( new Tracking( 'jetpack', $this->connection_manager ) )->should_enable_tracking( new Terms_Of_Service(), new Status() ) || static::is_connection_ready() ) {
6142            ( new Plugin_Tracking() )->init();
6143        }
6144    }
6145
6146    /**
6147     * Run the "initialize tracking" hook.
6148     *
6149     * @since 13.9
6150     */
6151    public function run_initialize_tracking_action() {
6152        /**
6153         * Fires when the tracking needs to be initialized.
6154         * Doesn't necessarily mean that will actually happen, depends if the 'jetpack_tos_agreed' option is set.
6155         *
6156         * @since 13.9
6157         */
6158        do_action( 'jetpack_initialize_tracking' );
6159    }
6160
6161    /**
6162     * Initialize REST jsonAPI if needed.
6163     *
6164     * @return void
6165     */
6166    public function maybe_initialize_rest_jsonapi() {
6167        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
6168        if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
6169            require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST.
6170
6171            define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
6172            require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
6173        }
6174    }
6175
6176    /**
6177     * Run plugin post-activation actions if we need to.
6178     *
6179     * @return void
6180     */
6181    private function plugin_post_activation() {
6182        if ( ( new Status() )->is_offline_mode() ) {
6183            return;
6184        }
6185
6186        if ( get_transient( 'activated_jetpack' ) ) {
6187            delete_transient( 'activated_jetpack' );
6188
6189            if ( ( new Host() )->is_woa_site() ) {
6190                $redirect_url = static::admin_url( 'page=jetpack' );
6191            } elseif ( is_network_admin() ) {
6192                $redirect_url = admin_url( 'network/admin.php?page=jetpack' );
6193            } elseif ( get_transient( 'my_jetpack_product_activated' ) ) {
6194                // don't redirect if this is an activation that just came from My Jetpack
6195                // My Jetpack has its own set of post-activation redirects
6196                return;
6197            } elseif ( My_Jetpack_Initializer::should_initialize() ) {
6198                $redirect_url = static::admin_url( 'page=my-jetpack' );
6199            } else {
6200                $redirect_url = static::admin_url( 'page=jetpack' );
6201            }
6202
6203            wp_safe_redirect( $redirect_url );
6204        }
6205    }
6206}