diff options
Diffstat (limited to 'plugins/jetpack/class.jetpack.php')
-rw-r--r-- | plugins/jetpack/class.jetpack.php | 432 |
1 files changed, 300 insertions, 132 deletions
diff --git a/plugins/jetpack/class.jetpack.php b/plugins/jetpack/class.jetpack.php index 9c80a053..cb4cd846 100644 --- a/plugins/jetpack/class.jetpack.php +++ b/plugins/jetpack/class.jetpack.php @@ -318,6 +318,11 @@ class Jetpack { public $json_api_authorization_request = array(); /** + * @var string Transient key used to prevent multiple simultaneous plugin upgrades + */ + public static $plugin_upgrade_lock_key = 'jetpack_upgrade_lock'; + + /** * Holds the singleton instance of this class * @since 2.3.3 * @var Jetpack @@ -345,6 +350,14 @@ class Jetpack { if ( Jetpack::is_active() ) { list( $version ) = explode( ':', Jetpack_Options::get_option( 'version' ) ); if ( JETPACK__VERSION != $version ) { + // Prevent multiple upgrades at once - only a single process should trigger + // an upgrade to avoid stampedes + if ( false !== get_transient( self::$plugin_upgrade_lock_key ) ) { + return; + } + + // Set a short lock to prevent multiple instances of the upgrade + set_transient( self::$plugin_upgrade_lock_key, 1, 10 ); // check which active modules actually exist and remove others from active_modules list $unfiltered_modules = Jetpack::get_active_modules(); @@ -387,6 +400,8 @@ class Jetpack { // being initialized late during the page load. In this case we wait // until the next proper admin page load with Jetpack active. if ( ! did_action( 'jetpack_modules_loaded' ) ) { + delete_transient( self::$plugin_upgrade_lock_key ); + return; } @@ -402,6 +417,8 @@ class Jetpack { ) { do_action( 'jetpack_sitemaps_purge_data' ); } + + delete_transient( self::$plugin_upgrade_lock_key ); } static function activate_manage( ) { @@ -612,6 +629,7 @@ class Jetpack { // A filter to control all just in time messages add_filter( 'jetpack_just_in_time_msgs', '__return_true', 9 ); + add_filter( 'jetpack_just_in_time_msg_cache', '__return_true', 9); // If enabled, point edit post and page links to Calypso instead of WP-Admin. // We should make sure to only do this for front end links. @@ -637,6 +655,11 @@ class Jetpack { * These are sync actions that we need to keep track of for jitms */ add_filter( 'jetpack_sync_before_send_updated_option', array( $this, 'jetpack_track_last_sync_callback' ), 99 ); + + // Actually push the stats on shutdown. + if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) { + add_action( 'shutdown', array( $this, 'push_stats' ) ); + } } function point_edit_links_to_calypso( $default_url, $post_id ) { @@ -669,6 +692,17 @@ class Jetpack { } function jetpack_track_last_sync_callback( $params ) { + /** + * Filter to turn off jitm caching + * + * @since 5.4.0 + * + * @param bool false Whether to cache just in time messages + */ + if ( ! apply_filters( 'jetpack_just_in_time_msg_cache', false ) ) { + return $params; + } + if ( is_array( $params ) && isset( $params[0] ) ) { $option = $params[0]; if ( 'active_plugins' === $option ) { @@ -687,10 +721,10 @@ class Jetpack { Jetpack_Options::update_option( 'dismissed_connection_banner', 1 ); wp_send_json_success(); } - + wp_die(); } - + function jetpack_admin_ajax_tracks_callback() { // Check for nonce if ( ! isset( $_REQUEST['tracksNonce'] ) || ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) ) { @@ -703,7 +737,11 @@ class Jetpack { $tracks_data = array(); if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) { - $tracks_data = array( 'clicked' => $_REQUEST['tracksEventProp'] ); + if ( is_array( $_REQUEST['tracksEventProp'] ) ) { + $tracks_data = $_REQUEST['tracksEventProp']; + } else { + $tracks_data = array( 'clicked' => $_REQUEST['tracksEventProp'] ); + } } JetpackTracking::record_user_event( $_REQUEST['tracksEventName'], $tracks_data ); @@ -775,7 +813,7 @@ class Jetpack { /** * If there are any stats that need to be pushed, but haven't been, push them now. */ - function __destruct() { + function push_stats() { if ( ! empty( $this->stats ) ) { $this->do_stats( 'server_side' ); } @@ -1421,6 +1459,7 @@ class Jetpack { 'seo-tools', 'google-analytics', 'wordads', + 'search', ); $plan['class'] = 'business'; } @@ -1642,10 +1681,15 @@ class Jetpack { * Loads the currently active modules. */ public static function load_modules() { - if ( ! self::is_active() && !self::is_development_mode() ) { - if ( ! is_multisite() || ! get_site_option( 'jetpack_protect_active' ) ) { - return; - } + if ( + ! self::is_active() + && ! self::is_development_mode() + && ( + ! is_multisite() + || ! get_site_option( 'jetpack_protect_active' ) + ) + ) { + return; } $version = Jetpack_Options::get_option( 'version' ); @@ -1722,8 +1766,7 @@ class Jetpack { do_action( 'jetpack_modules_loaded' ); // 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. - if ( Jetpack::is_active() || Jetpack::is_development_mode() ) - require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' ); + require_once( JETPACK__PLUGIN_DIR . 'modules/module-extras.php' ); } /** @@ -2375,37 +2418,28 @@ class Jetpack { public static function get_file_data( $file, $headers ) { //Get just the filename from $file (i.e. exclude full path) so that a consistent hash is generated $file_name = basename( $file ); - $file_data_option = Jetpack_Options::get_option( 'file_data', array() ); - $key = md5( $file_name . serialize( $headers ) ); - $refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 ); + + $cache_key = 'jetpack_file_data_' . JETPACK__VERSION; + + $file_data_option = get_transient( $cache_key ); + + if ( false === $file_data_option ) { + $file_data_option = array(); + } + + $key = md5( $file_name . serialize( $headers ) ); + $refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 ); // If we don't need to refresh the cache, and already have the value, short-circuit! - if ( ! $refresh_cache && isset( $file_data_option[ JETPACK__VERSION ][ $key ] ) ) { - return $file_data_option[ JETPACK__VERSION ][ $key ]; + if ( ! $refresh_cache && isset( $file_data_option[ $key ] ) ) { + return $file_data_option[ $key ]; } $data = get_file_data( $file, $headers ); - // Strip out any old Jetpack versions that are cluttering the option. - // - // We maintain the data for the current version of Jetpack plus the previous version - // to prevent repeated DB hits on large sites hosted with multiple web servers - // on a single database (since all web servers might not be updated simultaneously) - - $file_data_option[ JETPACK__VERSION ][ $key ] = $data; - - if ( count( $file_data_option ) > 2 ) { - $count = 0; - krsort( $file_data_option ); - foreach ( $file_data_option as $version => $values ) { - $count++; - if ( $count > 2 && JETPACK__VERSION != $version ) { - unset( $file_data_option[ $version ] ); - } - } - } + $file_data_option[ $key ] = $data; - Jetpack_Options::update_option( 'file_data', $file_data_option ); + set_transient( $cache_key, $file_data_option, 29 * DAY_IN_SECONDS ); return $data; } @@ -2884,8 +2918,61 @@ p { // For firing one-off events (notices) immediately after activation set_transient( 'activated_jetpack', true, .1 * MINUTE_IN_SECONDS ); + update_option( 'jetpack_activation_source', self::get_activation_source( wp_get_referer() ) ); + Jetpack::plugin_initialize(); } + + public static function get_activation_source( $referer_url ) { + + if ( defined( 'WP_CLI' ) && WP_CLI ) { + return array( 'wp-cli', null ); + } + + $referer = parse_url( $referer_url ); + + $source_type = 'unknown'; + $source_query = null; + + if ( ! is_array( $referer ) ) { + return array( $source_type, $source_query ); + } + + $plugins_path = parse_url( admin_url( 'plugins.php' ), PHP_URL_PATH ); + $plugins_install_path = parse_url( admin_url( 'plugin-install.php' ), PHP_URL_PATH );// /wp-admin/plugin-install.php + + if ( isset( $referer['query'] ) ) { + parse_str( $referer['query'], $query_parts ); + } else { + $query_parts = array(); + } + + if ( $plugins_path === $referer['path'] ) { + $source_type = 'list'; + } elseif ( $plugins_install_path === $referer['path'] ) { + $tab = isset( $query_parts['tab'] ) ? $query_parts['tab'] : 'featured'; + switch( $tab ) { + case 'popular': + $source_type = 'popular'; + break; + case 'recommended': + $source_type = 'recommended'; + break; + case 'favorites': + $source_type = 'favorites'; + break; + case 'search': + $source_type = 'search-' . ( isset( $query_parts['type'] ) ? $query_parts['type'] : 'term' ); + $source_query = isset( $query_parts['s'] ) ? $query_parts['s'] : null; + break; + default: + $source_type = 'featured'; + } + } + + return array( $source_type, $source_query ); + } + /** * Runs before bumping version numbers up to a new version * @param string $version Version:timestamp @@ -3836,7 +3923,7 @@ p { JetpackTracking::record_user_event( 'jpc_register_success', array( 'from' => $from ) ); - + wp_redirect( $this->build_connect_url( true, $redirect, $from ) ); exit; case 'activate' : @@ -4253,15 +4340,21 @@ p { } } else { - // Checking existing token - $response = Jetpack_Client::wpcom_json_api_request_as_blog( - sprintf( '/sites/%d', $site_id ) .'?force=wpcom', - '1.1' - ); + // Let's check the existing blog token to see if we need to re-register. We only check once per minute + // because otherwise this logic can get us in to a loop. + $last_connect_url_check = intval( Jetpack_Options::get_raw_option( 'jetpack_last_connect_url_check' ) ); + if ( ! $last_connect_url_check || ( time() - $last_connect_url_check ) > MINUTE_IN_SECONDS ) { + Jetpack_Options::update_raw_option( 'jetpack_last_connect_url_check', time() ); + + $response = Jetpack_Client::wpcom_json_api_request_as_blog( + sprintf( '/sites/%d', $site_id ) .'?force=wpcom', + '1.1' + ); - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { - // Generating a register URL instead to refresh the existing token - return $this->build_connect_url( $raw, $redirect, $from, true ); + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + // Generating a register URL instead to refresh the existing token + return $this->build_connect_url( $raw, $redirect, $from, true ); + } } if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && include_once JETPACK__GLOTPRESS_LOCALES_PATH ) { @@ -4300,7 +4393,7 @@ p { $auth_type = apply_filters( 'jetpack_auth_type', 'calypso' ); $tracks_identity = jetpack_tracks_get_identity( get_current_user_id() ); - + $args = urlencode_deep( array( 'response_type' => 'code', @@ -4332,6 +4425,8 @@ p { ) ); + self::apply_activation_source_to_args( $args ); + $url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) ); } @@ -4344,9 +4439,29 @@ p { $url = add_query_arg( 'calypso_env', sanitize_key( $_GET['calypso_env'] ), $url ); } + if ( false !== ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) { + $url = add_query_arg( 'onboarding', $token, $url ); + + // Remove this once https://github.com/Automattic/wp-calypso/pull/17094 is merged. + // Uncomment for development until it's merged. + //$url = add_query_arg( 'calypso_env', 'development', $url ); + } + return $raw ? $url : esc_url( $url ); } + public static function apply_activation_source_to_args( &$args ) { + list( $activation_source_name, $activation_source_keyword ) = get_option( 'jetpack_activation_source' ); + + if ( $activation_source_name ) { + $args['_as'] = urlencode( $activation_source_name ); + } + + if ( $activation_source_keyword ) { + $args['_ak'] = urlencode( $activation_source_keyword ); + } + } + function build_reconnect_url( $raw = false ) { $url = wp_nonce_url( Jetpack::admin_url( 'action=reconnect' ), 'jetpack-reconnect' ); return $raw ? $url : esc_url( $url ); @@ -4597,6 +4712,53 @@ p { } /** + * Create a random secret for validating onboarding payload + * + * @return string Secret token + */ + public static function create_onboarding_token() { + if ( false === ( $token = Jetpack_Options::get_option( 'onboarding' ) ) ) { + $token = wp_generate_password( 32, false ); + Jetpack_Options::update_option( 'onboarding', $token ); + } + + return $token; + } + + /** + * Remove the onboarding token + * + * @return bool True on success, false on failure + */ + public static function invalidate_onboarding_token() { + return Jetpack_Options::delete_option( 'onboarding' ); + } + + /** + * Validate an onboarding token for a specific action + * + * @return boolean True if token/action pair is accepted, false if not + */ + public static function validate_onboarding_token_action( $token, $action ) { + // Compare tokens, bail if tokens do not match + if ( ! hash_equals( $token, Jetpack_Options::get_option( 'onboarding' ) ) ) { + return false; + } + + // List of valid actions we can take + $valid_actions = array( + '/jetpack/v4/settings', + ); + + // Whitelist the action + if ( ! in_array( $action, $valid_actions ) ) { + return false; + } + + return true; + } + + /** * Checks to see if the URL is using SSL to connect with Jetpack * * @since 2.3.3 @@ -4775,12 +4937,43 @@ p { * * @since 2.6 * @return int + * @deprecated **/ public function get_remote_query_timeout_limit() { - $timeout = (int) ini_get( 'max_execution_time' ); - if ( ! $timeout ) // Ensure exec time set in php.ini - $timeout = 30; - return intval( $timeout / 2 ); + _deprecated_function( __METHOD__, 'jetpack-5.4' ); + return Jetpack::get_max_execution_time(); + } + + /** + * Builds the timeout limit for queries talking with the wpcom servers. + * + * Based on local php max_execution_time in php.ini + * + * @since 5.4 + * @return int + **/ + public static function get_max_execution_time() { + $timeout = (int) ini_get( 'max_execution_time' ); + + // Ensure exec time set in php.ini + if ( ! $timeout ) { + $timeout = 30; + } + return $timeout; + } + + /** + * Sets a minimum request timeout, and returns the current timeout + * + * @since 5.4 + **/ + public static function set_min_time_limit( $min_timeout ) { + $timeout = self::get_max_execution_time(); + if ( $timeout < $min_timeout ) { + $timeout = $min_timeout; + set_time_limit( $timeout ); + } + return $timeout; } @@ -4847,7 +5040,9 @@ p { return new Jetpack_Error( 'missing_secrets' ); } - $timeout = Jetpack::init()->get_remote_query_timeout_limit(); + // better to try (and fail) to set a higher timeout than this system + // supports than to have register fail for more users than it should + $timeout = Jetpack::set_min_time_limit( 60 ) / 2; $gmt_offset = get_option( 'gmt_offset' ); if ( ! $gmt_offset ) { @@ -4882,6 +5077,9 @@ p { ), 'timeout' => $timeout, ); + + self::apply_activation_source_to_args( $args['body'] ); + $response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'register' ) ), $args, true ); // Make sure the response is valid and does not contain any Jetpack errors @@ -5064,6 +5262,30 @@ p { return false; } + // Let's see if this is onboarding. In such case, use user token type and the provided user id. + if ( isset( $this->HTTP_RAW_POST_DATA ) ) { + $jpo = json_decode( $this->HTTP_RAW_POST_DATA ); + if ( + isset( $jpo->onboarding ) && + isset( $jpo->onboarding->jpUser ) && isset( $jpo->onboarding->token ) && + is_email( $jpo->onboarding->jpUser ) && ctype_alnum( $jpo->onboarding->token ) && + isset( $_GET['rest_route'] ) && + self::validate_onboarding_token_action( $jpo->onboarding->token, $_GET['rest_route'] ) + ) { + $jpUser = get_user_by( 'email', $jpo->onboarding->jpUser ); + if ( is_a( $jpUser, 'WP_User' ) ) { + wp_set_current_user( $jpUser->ID ); + $user_can = is_multisite() + ? current_user_can_for_blog( get_current_blog_id(), 'manage_options' ) + : current_user_can( 'manage_options' ); + if ( $user_can ) { + $token_type = 'user'; + $token->external_user_id = $jpUser->ID; + } + } + } + } + $this->xmlrpc_verification = array( 'type' => $token_type, 'user_id' => $token->external_user_id, @@ -5825,6 +6047,9 @@ p { /** * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup. * + * @since 4.4.0 + * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC + * * @return bool */ public static function validate_sync_error_idc_option() { @@ -5848,8 +6073,8 @@ p { // Is the site opted in and does the stored sync_error_idc option match what we now generate? $sync_error = Jetpack_Options::get_option( 'sync_error_idc' ); - $local_options = self::get_sync_error_idc_option(); if ( $idc_allowed && $sync_error && self::sync_idc_optin() ) { + $local_options = self::get_sync_error_idc_option(); if ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) { $is_valid = true; } @@ -5897,31 +6122,37 @@ p { * Gets the value that is to be saved in the jetpack_sync_error_idc option. * * @since 4.4.0 + * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB * * @param array $response * @return array Array of the local urls, wpcom urls, and error code */ public static function get_sync_error_idc_option( $response = array() ) { - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php'; - $local_options = array( - 'home' => Jetpack_Sync_Functions::home_url(), - 'siteurl' => Jetpack_Sync_Functions::site_url(), - ); + $returned_values = get_transient( 'jetpack_idc_option' ); + if ( false === $returned_values ) { + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php'; + $local_options = array( + 'home' => Jetpack_Sync_Functions::home_url(), + 'siteurl' => Jetpack_Sync_Functions::site_url(), + ); - $options = array_merge( $local_options, $response ); + $options = array_merge( $local_options, $response ); - $returned_values = array(); - foreach( $options as $key => $option ) { - if ( 'error_code' === $key ) { - $returned_values[ $key ] = $option; - continue; - } + $returned_values = array(); + foreach( $options as $key => $option ) { + if ( 'error_code' === $key ) { + $returned_values[ $key ] = $option; + continue; + } - if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) { - continue; + if ( is_wp_error( $normalized_url = self::normalize_url_protocol_agnostic( $option ) ) ) { + continue; + } + + $returned_values[ $key ] = $normalized_url; } - $returned_values[ $key ] = $normalized_url; + set_transient( 'jetpack_idc_option', $returned_values, MINUTE_IN_SECONDS ); } return $returned_values; @@ -6391,74 +6622,12 @@ p { * * It is used in class.jetpack-cli.php to reset options * + * @since 5.4.0 Logic moved to Jetpack_Options class. Method left in Jetpack class for backwards compat. + * * @return array of options to delete. */ public static function get_jetpack_options_for_reset() { - $jetpack_options = Jetpack_Options::get_option_names(); - $jetpack_options_non_compat = Jetpack_Options::get_option_names( 'non_compact' ); - $jetpack_options_private = Jetpack_Options::get_option_names( 'private' ); - - $all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private ); - - // A manual build of the wp options - $wp_options = array( - 'sharing-options', - 'disabled_likes', - 'disabled_reblogs', - 'jetpack_comments_likes_enabled', - 'wp_mobile_excerpt', - 'wp_mobile_featured_images', - 'wp_mobile_app_promos', - 'stats_options', - 'stats_dashboard_widget', - 'safecss_preview_rev', - 'safecss_rev', - 'safecss_revision_migrated', - 'nova_menu_order', - 'jetpack_portfolio', - 'jetpack_portfolio_posts_per_page', - 'jetpack_testimonial', - 'jetpack_testimonial_posts_per_page', - 'wp_mobile_custom_css', - 'sharedaddy_disable_resources', - 'sharing-options', - 'sharing-services', - 'site_icon_temp_data', - 'featured-content', - 'site_logo', - 'jetpack_dismissed_notices', - ); - - // Flag some Jetpack options as unsafe - $unsafe_options = array( - 'id', // (int) The Client ID/WP.com Blog ID of this site. - 'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com. - 'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time - 'jumpstart', // (string) A flag for whether or not to show the Jump Start. Accepts: new_connection, jumpstart_activated, jetpack_action_taken, jumpstart_dismissed. - - // non_compact - 'activated', - - // private - 'register', - 'blog_token', // (string) The Client Secret/Blog Token of this site. - 'user_token', // (string) The User Token of this site. (deprecated) - 'user_tokens' - ); - - // Remove the unsafe Jetpack options - foreach ( $unsafe_options as $unsafe_option ) { - if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) { - unset( $all_jp_options[ $key ] ); - } - } - - $options = array( - 'jp_options' => $all_jp_options, - 'wp_options' => $wp_options - ); - - return $options; + return Jetpack_Options::get_options_for_reset(); } /** @@ -6533,13 +6702,12 @@ p { public function wp_dashboard_setup() { if ( self::is_active() ) { add_action( 'jetpack_dashboard_widget', array( __CLASS__, 'dashboard_widget_footer' ), 999 ); - $widget_title = __( 'Site Stats', 'jetpack' ); } if ( has_action( 'jetpack_dashboard_widget' ) ) { wp_add_dashboard_widget( 'jetpack_summary_widget', - $widget_title, + esc_html__( 'Site Stats', 'jetpack' ), array( __CLASS__, 'dashboard_widget' ) ); wp_enqueue_style( 'jetpack-dashboard-widget', plugins_url( 'css/dashboard-widget.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION ); |