TPACK_SHOULD_HANDLE_IDC constant if set. * * @param bool $default Whether the site is opted in to IDC mitigation. * * @since 0.2.6 */ return (bool) apply_filters( 'jetpack_should_handle_idc', $default ); } /** * Whether the site is undergoing identity crisis. * * @return bool */ public static function has_identity_crisis() { return false !== static::check_identity_crisis() && ! static::$is_safe_mode_confirmed; } /** * Whether an admin has confirmed safe mode. * Unlike `static::$is_safe_mode_confirmed` this function always returns the actual flag value. * * @return bool */ public static function safe_mode_is_confirmed() { return Jetpack_Options::get_option( 'safe_mode_confirmed' ); } /** * Returns the mismatched URLs. * * @return array|bool The mismatched urls, or false if the site is not connected, offline, in safe mode, or the IDC error is not valid. */ public static function get_mismatched_urls() { if ( ! static::has_identity_crisis() ) { return false; } $data = static::check_identity_crisis(); if ( ! $data || ! isset( $data['error_code'] ) || ! isset( $data['wpcom_home'] ) || ! isset( $data['home'] ) || ! isset( $data['wpcom_siteurl'] ) || ! isset( $data['siteurl'] ) ) { // The jetpack_sync_error_idc option is missing a key. return false; } if ( 'jetpack_site_url_mismatch' === $data['error_code'] ) { return array( 'wpcom_url' => $data['wpcom_siteurl'], 'current_url' => $data['siteurl'], ); } return array( 'wpcom_url' => $data['wpcom_home'], 'current_url' => $data['home'], ); } /** * Try to detect $_SERVER['HTTP_HOST'] being used within WP_SITEURL or WP_HOME definitions inside of wp-config. * * If `HTTP_HOST` usage is found, it's possbile (though not certain) that site URLs are dynamic. * * When a site URL is dynamic, it can lead to a Jetpack IDC. If potentially dynamic usage is detected, * helpful support info will be shown on the IDC UI about setting a static site/home URL. * * @return bool True if potentially dynamic site urls were detected in wp-config, false otherwise. */ public static function detect_possible_dynamic_site_url() { $transient_key = 'jetpack_idc_possible_dynamic_site_url_detected'; $transient_val = get_transient( $transient_key ); if ( false !== $transient_val ) { return (bool) $transient_val; } $path = self::locate_wp_config(); $wp_config = $path ? file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents if ( $wp_config ) { $matched = preg_match( '/define ?\( ?[\'"](?:WP_SITEURL|WP_HOME).+(?:HTTP_HOST).+\);/', $wp_config ); if ( $matched ) { set_transient( $transient_key, 1, HOUR_IN_SECONDS ); return true; } } set_transient( $transient_key, 0, HOUR_IN_SECONDS ); return false; } /** * Gets path to WordPress configuration. * Source: https://github.com/wp-cli/wp-cli/blob/master/php/utils.php * * @return string */ public static function locate_wp_config() { static $path; if ( null === $path ) { $path = false; if ( getenv( 'WP_CONFIG_PATH' ) && file_exists( getenv( 'WP_CONFIG_PATH' ) ) ) { $path = getenv( 'WP_CONFIG_PATH' ); } elseif ( file_exists( ABSPATH . 'wp-config.php' ) ) { $path = ABSPATH . 'wp-config.php'; } elseif ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) { $path = dirname( ABSPATH ) . '/wp-config.php'; } if ( $path ) { $path = realpath( $path ); } } return $path; } /** * Adds `url_secret` to the `jetpack.idcUrlValidation` URL validation endpoint. * Adds `url_secret_error` in case of an error. * * @param array $response The endpoint response that we're modifying. * * @return array * * phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag -- The exception is being caught, false positive. */ public static function add_secret_to_url_validation_response( array $response ) { try { $secret = new URL_Secret(); $secret->create(); if ( $secret->exists() ) { $response['url_secret'] = $secret->get_secret(); } } catch ( Exception $e ) { $response['url_secret_error'] = new WP_Error( 'unable_to_create_url_secret', $e->getMessage() ); } return $response; } /** * Check if URL is an IP. * * @param string $hostname The hostname to check. * @return bool */ public static function url_is_ip( $hostname = null ) { if ( ! $hostname ) { $hostname = wp_parse_url( Urls::site_url(), PHP_URL_HOST ); } $is_ip = filter_var( $hostname, FILTER_VALIDATE_IP ) !== false ? $hostname : false; return $is_ip; } /** * Add IDC-related data to the registration query. * * @param array $params The existing query params. * * @return array */ public static function register_request_body( array $params ) { $persistent_blog_id = get_option( static::PERSISTENT_BLOG_ID_OPTION_NAME ); if ( $persistent_blog_id ) { $params['persistent_blog_id'] = $persistent_blog_id; $params['url_secret'] = URL_Secret::create_secret( 'registration_request_url_secret_failed' ); } return $params; } /** * Set the necessary options when site gets registered. * * @param int $blog_id The blog ID. * * @return void */ public static function site_registered( $blog_id ) { update_option( static::PERSISTENT_BLOG_ID_OPTION_NAME, (int) $blog_id, false ); } /** * Check if we need to update the ip_requester option. * * @param string $hostname The hostname to check. * * @return void */ public static function maybe_update_ip_requester( $hostname ) { // Check if transient exists $transient_key = ip2long( $hostname ); if ( $transient_key && ! get_transient( 'jetpack_idc_ip_requester_' . $transient_key ) ) { self::set_ip_requester_for_idc( $hostname, $transient_key ); } } /** * If URL is an IP, add the IP value to the ip_requester option with its expiry value. * * @param string $hostname The hostname to check. * @param int $transient_key The transient key. */ public static function set_ip_requester_for_idc( $hostname, $transient_key ) { // Check if option exists $data = Jetpack_Options::get_option( 'identity_crisis_ip_requester' ); $ip_requester = array( 'ip' => $hostname, 'expires_at' => time() + 360, ); // If not set, initialize it if ( empty( $data ) ) { $data = array( $ip_requester ); } else { $updated_data = array(); $updated_value = false; // Remove expired values and update existing IP foreach ( $data as $item ) { if ( time() > $item['expires_at'] ) { continue; // Skip expired IP } if ( $item['ip'] === $hostname ) { $item['expires_at'] = time() + 360; $updated_value = true; } $updated_data[] = $item; } if ( ! $updated_value || empty( $updated_data ) ) { $updated_data[] = $ip_requester; } $data = $updated_data; } self::update_ip_requester( $data, $transient_key ); } /** * Update the ip_requester option and set a transient to expire in 5 minutes. * * @param array $data The data to be updated. * @param int $transient_key The transient key. * * @return void */ public static function update_ip_requester( $data, $transient_key ) { // Update the option $updated = Jetpack_Options::update_option( 'identity_crisis_ip_requester', $data ); // Set a transient to expire in 5 minutes if ( $updated ) { $transient_name = 'jetpack_idc_ip_requester_' . $transient_key; set_transient( $transient_name, $data, 300 ); } } /** * Adds `ip_requester` to the `jetpack.idcUrlValidation` URL validation endpoint. * * @param array $response The enpoint response that we're modifying. * * @return array */ public static function add_ip_requester_to_url_validation_response( array $response ) { $requesters = Jetpack_Options::get_option( 'identity_crisis_ip_requester' ); if ( $requesters ) { // Loop through the requesters and add the IP to the response if it's not expired $i = 0; foreach ( $requesters as $ip ) { if ( $ip['expires_at'] > time() ) { $response['ip_requester'][] = $ip['ip']; } // Limit the response to five IPs $i = ++$i; if ( $i === 5 ) { break; } } } return $response; } }