Remove all plugins / install base theme
This commit is contained in:
@@ -1,497 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Connection Client class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* The Client class that is used to connect to WordPress.com Jetpack API.
|
||||
*/
|
||||
class Client {
|
||||
const WPCOM_JSON_API_VERSION = '1.1';
|
||||
|
||||
/**
|
||||
* Makes an authorized remote request using Jetpack_Signature
|
||||
*
|
||||
* @param array $args the arguments for the remote request.
|
||||
* @param array|String $body the request body.
|
||||
* @return array|WP_Error WP HTTP response on success
|
||||
*/
|
||||
public static function remote_request( $args, $body = null ) {
|
||||
if ( isset( $args['url'] ) ) {
|
||||
/**
|
||||
* Filters the remote request url.
|
||||
*
|
||||
* @since 1.30.12
|
||||
*
|
||||
* @param string The remote request url.
|
||||
*/
|
||||
$args['url'] = apply_filters( 'jetpack_remote_request_url', $args['url'] );
|
||||
}
|
||||
|
||||
$result = self::build_signed_request( $args, $body );
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = self::_wp_remote_request( $result['url'], $result['request'] );
|
||||
|
||||
Error_Handler::get_instance()->check_api_response_for_errors(
|
||||
$response,
|
||||
$result['auth'],
|
||||
empty( $args['url'] ) ? '' : $args['url'],
|
||||
empty( $args['method'] ) ? 'POST' : $args['method'],
|
||||
'rest'
|
||||
);
|
||||
|
||||
/**
|
||||
* Fired when the remote request response has been received.
|
||||
*
|
||||
* @since 1.30.8
|
||||
*
|
||||
* @param array|WP_Error The HTTP response.
|
||||
*/
|
||||
do_action( 'jetpack_received_remote_request_response', $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds authorization signature to a remote request using Jetpack_Signature
|
||||
*
|
||||
* @param array $args the arguments for the remote request.
|
||||
* @param array|String $body the request body.
|
||||
* @return WP_Error|array {
|
||||
* An array containing URL and request items.
|
||||
*
|
||||
* @type String $url The request URL.
|
||||
* @type array $request Request arguments.
|
||||
* @type array $auth Authorization data.
|
||||
* }
|
||||
*/
|
||||
public static function build_signed_request( $args, $body = null ) {
|
||||
add_filter(
|
||||
'jetpack_constant_default_value',
|
||||
__NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
$defaults = array(
|
||||
'url' => '',
|
||||
'user_id' => 0,
|
||||
'blog_id' => 0,
|
||||
'auth_location' => Constants::get_constant( 'JETPACK_CLIENT__AUTH_LOCATION' ),
|
||||
'method' => 'POST',
|
||||
'timeout' => 10,
|
||||
'redirection' => 0,
|
||||
'headers' => array(),
|
||||
'stream' => false,
|
||||
'filename' => null,
|
||||
'sslverify' => true,
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
$args['blog_id'] = (int) $args['blog_id'];
|
||||
|
||||
if ( 'header' !== $args['auth_location'] ) {
|
||||
$args['auth_location'] = 'query_string';
|
||||
}
|
||||
|
||||
$token = ( new Tokens() )->get_access_token( $args['user_id'] );
|
||||
if ( ! $token ) {
|
||||
return new \WP_Error( 'missing_token' );
|
||||
}
|
||||
|
||||
$method = strtoupper( $args['method'] );
|
||||
|
||||
$timeout = (int) $args['timeout'];
|
||||
|
||||
$redirection = $args['redirection'];
|
||||
$stream = $args['stream'];
|
||||
$filename = $args['filename'];
|
||||
$sslverify = $args['sslverify'];
|
||||
|
||||
$request = compact( 'method', 'body', 'timeout', 'redirection', 'stream', 'filename', 'sslverify' );
|
||||
|
||||
@list( $token_key, $secret ) = explode( '.', $token->secret ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
if ( empty( $token ) || empty( $secret ) ) {
|
||||
return new \WP_Error( 'malformed_token' );
|
||||
}
|
||||
|
||||
$token_key = sprintf(
|
||||
'%s:%d:%d',
|
||||
$token_key,
|
||||
Constants::get_constant( 'JETPACK__API_VERSION' ),
|
||||
$token->external_user_id
|
||||
);
|
||||
|
||||
$time_diff = (int) \Jetpack_Options::get_option( 'time_diff' );
|
||||
$jetpack_signature = new \Jetpack_Signature( $token->secret, $time_diff );
|
||||
|
||||
$timestamp = time() + $time_diff;
|
||||
|
||||
if ( function_exists( 'wp_generate_password' ) ) {
|
||||
$nonce = wp_generate_password( 10, false );
|
||||
} else {
|
||||
$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
|
||||
}
|
||||
|
||||
// Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing.
|
||||
if ( $body === null ) {
|
||||
$body_hash = '';
|
||||
|
||||
} else {
|
||||
// Allow arrays to be used in passing data.
|
||||
$body_to_hash = $body;
|
||||
|
||||
if ( is_array( $body ) ) {
|
||||
// We cast this to a new variable, because the array form of $body needs to be
|
||||
// maintained so it can be passed into the request later on in the code.
|
||||
if ( array() !== $body ) {
|
||||
$body_to_hash = wp_json_encode( self::_stringify_data( $body ) );
|
||||
} else {
|
||||
$body_to_hash = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_string( $body_to_hash ) ) {
|
||||
return new \WP_Error( 'invalid_body', 'Body is malformed.' );
|
||||
}
|
||||
|
||||
$body_hash = base64_encode( sha1( $body_to_hash, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
}
|
||||
|
||||
$auth = array(
|
||||
'token' => $token_key,
|
||||
'timestamp' => $timestamp,
|
||||
'nonce' => $nonce,
|
||||
'body-hash' => $body_hash,
|
||||
);
|
||||
|
||||
if ( false !== strpos( $args['url'], 'xmlrpc.php' ) ) {
|
||||
$url_args = array(
|
||||
'for' => 'jetpack',
|
||||
'wpcom_blog_id' => \Jetpack_Options::get_option( 'id' ),
|
||||
);
|
||||
} else {
|
||||
$url_args = array();
|
||||
}
|
||||
|
||||
if ( 'header' !== $args['auth_location'] ) {
|
||||
$url_args += $auth;
|
||||
}
|
||||
|
||||
$url = add_query_arg( urlencode_deep( $url_args ), $args['url'] );
|
||||
|
||||
$signature = $jetpack_signature->sign_request( $token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false );
|
||||
|
||||
if ( ! $signature || is_wp_error( $signature ) ) {
|
||||
return $signature;
|
||||
}
|
||||
|
||||
// Send an Authorization header so various caches/proxies do the right thing.
|
||||
$auth['signature'] = $signature;
|
||||
$auth['version'] = Constants::get_constant( 'JETPACK__VERSION' );
|
||||
$header_pieces = array();
|
||||
foreach ( $auth as $key => $value ) {
|
||||
$header_pieces[] = sprintf( '%s="%s"', $key, $value );
|
||||
}
|
||||
$request['headers'] = array_merge(
|
||||
$args['headers'],
|
||||
array(
|
||||
'Authorization' => 'X_JETPACK ' . implode( ' ', $header_pieces ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( 'header' !== $args['auth_location'] ) {
|
||||
$url = add_query_arg( 'signature', rawurlencode( $signature ), $url );
|
||||
}
|
||||
|
||||
return compact( 'url', 'request', 'auth' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for wp_remote_request(). Turns off SSL verification for certain SSL errors.
|
||||
* This is lame, but many, many, many hosts have misconfigured SSL.
|
||||
*
|
||||
* When Jetpack is registered, the jetpack_fallback_no_verify_ssl_certs option is set to the current time if:
|
||||
* 1. a certificate error is found AND
|
||||
* 2. not verifying the certificate works around the problem.
|
||||
*
|
||||
* The option is checked on each request.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param String $url the request URL.
|
||||
* @param array $args request arguments.
|
||||
* @param Boolean $set_fallback whether to allow flagging this request to use a fallback certficate override.
|
||||
* @return array|WP_Error WP HTTP response on success
|
||||
*/
|
||||
public static function _wp_remote_request( $url, $args, $set_fallback = false ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
|
||||
$fallback = \Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
|
||||
if ( false === $fallback ) {
|
||||
\Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL verification (`sslverify`) for the JetpackClient remote request
|
||||
* defaults to off, use this filter to force it on.
|
||||
*
|
||||
* Return `true` to ENABLE SSL verification, return `false`
|
||||
* to DISABLE SSL verification.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 3.6.0
|
||||
*
|
||||
* @param bool Whether to force `sslverify` or not.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_client_verify_ssl_certs', false ) ) {
|
||||
return wp_remote_request( $url, $args );
|
||||
}
|
||||
|
||||
if ( (int) $fallback ) {
|
||||
// We're flagged to fallback.
|
||||
$args['sslverify'] = false;
|
||||
}
|
||||
|
||||
$response = wp_remote_request( $url, $args );
|
||||
|
||||
if (
|
||||
! $set_fallback // We're not allowed to set the flag on this request, so whatever happens happens.
|
||||
||
|
||||
isset( $args['sslverify'] ) && ! $args['sslverify'] // No verification - no point in doing it again.
|
||||
||
|
||||
! is_wp_error( $response ) // Let it ride.
|
||||
) {
|
||||
self::set_time_diff( $response, $set_fallback );
|
||||
return $response;
|
||||
}
|
||||
|
||||
// At this point, we're not flagged to fallback and we are allowed to set the flag on this request.
|
||||
|
||||
$message = $response->get_error_message();
|
||||
|
||||
// Is it an SSL Certificate verification error?
|
||||
if (
|
||||
false === strpos( $message, '14090086' ) // OpenSSL SSL3 certificate error.
|
||||
&&
|
||||
false === strpos( $message, '1407E086' ) // OpenSSL SSL2 certificate error.
|
||||
&&
|
||||
false === strpos( $message, 'error setting certificate verify locations' ) // cURL CA bundle not found.
|
||||
&&
|
||||
false === strpos( $message, 'Peer certificate cannot be authenticated with' ) // cURL CURLE_SSL_CACERT: CA bundle found, but not helpful
|
||||
// Different versions of curl have different error messages
|
||||
// this string should catch them all.
|
||||
&&
|
||||
false === strpos( $message, 'Problem with the SSL CA cert' ) // cURL CURLE_SSL_CACERT_BADFILE: probably access rights.
|
||||
) {
|
||||
// No, it is not.
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Redo the request without SSL certificate verification.
|
||||
$args['sslverify'] = false;
|
||||
$response = wp_remote_request( $url, $args );
|
||||
|
||||
if ( ! is_wp_error( $response ) ) {
|
||||
// The request went through this time, flag for future fallbacks.
|
||||
\Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', time() );
|
||||
self::set_time_diff( $response, $set_fallback );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time difference for correct signature computation.
|
||||
*
|
||||
* @param HTTP_Response $response the response object.
|
||||
* @param Boolean $force_set whether to force setting the time difference.
|
||||
*/
|
||||
public static function set_time_diff( &$response, $force_set = false ) {
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
|
||||
// Only trust the Date header on some responses.
|
||||
if ( 200 != $code && 304 != $code && 400 != $code && 401 != $code ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
|
||||
return;
|
||||
}
|
||||
|
||||
$date = wp_remote_retrieve_header( $response, 'date' );
|
||||
if ( ! $date ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$time = (int) strtotime( $date );
|
||||
if ( 0 >= $time ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$time_diff = $time - time();
|
||||
|
||||
if ( $force_set ) { // During register.
|
||||
\Jetpack_Options::update_option( 'time_diff', $time_diff );
|
||||
} else { // Otherwise.
|
||||
$old_diff = \Jetpack_Options::get_option( 'time_diff' );
|
||||
if ( false === $old_diff || abs( $time_diff - (int) $old_diff ) > 10 ) {
|
||||
\Jetpack_Options::update_option( 'time_diff', $time_diff );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and build arguments for a WordPress.com REST API request.
|
||||
*
|
||||
* @param string $path REST API path.
|
||||
* @param string $version REST API version. Default is `2`.
|
||||
* @param array $args Arguments to {@see WP_Http}. Default is `array()`.
|
||||
* @param string $base_api_path REST API root. Default is `wpcom`.
|
||||
*
|
||||
* @return array|WP_Error $response Response data, else {@see WP_Error} on failure.
|
||||
*/
|
||||
public static function validate_args_for_wpcom_json_api_request(
|
||||
$path,
|
||||
$version = '2',
|
||||
$args = array(),
|
||||
$base_api_path = 'wpcom'
|
||||
) {
|
||||
$base_api_path = trim( $base_api_path, '/' );
|
||||
$version = ltrim( $version, 'v' );
|
||||
$path = ltrim( $path, '/' );
|
||||
|
||||
$filtered_args = array_intersect_key(
|
||||
$args,
|
||||
array(
|
||||
'headers' => 'array',
|
||||
'method' => 'string',
|
||||
'timeout' => 'int',
|
||||
'redirection' => 'int',
|
||||
'stream' => 'boolean',
|
||||
'filename' => 'string',
|
||||
'sslverify' => 'boolean',
|
||||
)
|
||||
);
|
||||
|
||||
// Use GET by default whereas `remote_request` uses POST.
|
||||
$request_method = isset( $filtered_args['method'] ) ? strtoupper( $filtered_args['method'] ) : 'GET';
|
||||
|
||||
$url = sprintf(
|
||||
'%s/%s/v%s/%s',
|
||||
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
|
||||
$base_api_path,
|
||||
$version,
|
||||
$path
|
||||
);
|
||||
|
||||
$validated_args = array_merge(
|
||||
$filtered_args,
|
||||
array(
|
||||
'url' => $url,
|
||||
'method' => $request_method,
|
||||
)
|
||||
);
|
||||
|
||||
return $validated_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the WordPress.com REST API with a user token.
|
||||
*
|
||||
* @param string $path REST API path.
|
||||
* @param string $version REST API version. Default is `2`.
|
||||
* @param array $args Arguments to {@see WP_Http}. Default is `array()`.
|
||||
* @param string $body Body passed to {@see WP_Http}. Default is `null`.
|
||||
* @param string $base_api_path REST API root. Default is `wpcom`.
|
||||
*
|
||||
* @return array|WP_Error $response Response data, else {@see WP_Error} on failure.
|
||||
*/
|
||||
public static function wpcom_json_api_request_as_user(
|
||||
$path,
|
||||
$version = '2',
|
||||
$args = array(),
|
||||
$body = null,
|
||||
$base_api_path = 'wpcom'
|
||||
) {
|
||||
$args = self::validate_args_for_wpcom_json_api_request( $path, $version, $args, $base_api_path );
|
||||
$args['user_id'] = get_current_user_id();
|
||||
|
||||
if ( isset( $body ) && ! isset( $args['headers'] ) && in_array( $args['method'], array( 'POST', 'PUT', 'PATCH' ), true ) ) {
|
||||
$args['headers'] = array( 'Content-Type' => 'application/json' );
|
||||
}
|
||||
|
||||
if ( isset( $body ) && ! is_string( $body ) ) {
|
||||
$body = wp_json_encode( $body );
|
||||
}
|
||||
|
||||
return self::remote_request( $args, $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the WordPress.com REST API using the blog token
|
||||
*
|
||||
* @param String $path The API endpoint relative path.
|
||||
* @param String $version The API version.
|
||||
* @param array $args Request arguments.
|
||||
* @param String $body Request body.
|
||||
* @param String $base_api_path (optional) the API base path override, defaults to 'rest'.
|
||||
* @return array|WP_Error $response Data.
|
||||
*/
|
||||
public static function wpcom_json_api_request_as_blog(
|
||||
$path,
|
||||
$version = self::WPCOM_JSON_API_VERSION,
|
||||
$args = array(),
|
||||
$body = null,
|
||||
$base_api_path = 'rest'
|
||||
) {
|
||||
$validated_args = self::validate_args_for_wpcom_json_api_request( $path, $version, $args, $base_api_path );
|
||||
$validated_args['blog_id'] = (int) \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
// For Simple sites get the response directly without any HTTP requests.
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
add_filter( 'is_jetpack_authorized_for_site', '__return_true' );
|
||||
require_lib( 'wpcom-api-direct' );
|
||||
return \WPCOM_API_Direct::do_request( $validated_args, $body );
|
||||
}
|
||||
|
||||
return self::remote_request( $validated_args, $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array or similar structure and recursively turns all values into strings. This is used to
|
||||
* make sure that body hashes are made ith the string version, which is what will be seen after a
|
||||
* server pulls up the data in the $_POST array.
|
||||
*
|
||||
* @param array|Mixed $data the data that needs to be stringified.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public static function _stringify_data( $data ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
|
||||
|
||||
// Booleans are special, lets just makes them and explicit 1/0 instead of the 0 being an empty string.
|
||||
if ( is_bool( $data ) ) {
|
||||
return $data ? '1' : '0';
|
||||
}
|
||||
|
||||
// Cast objects into arrays.
|
||||
if ( is_object( $data ) ) {
|
||||
$data = (array) $data;
|
||||
}
|
||||
|
||||
// Non arrays at this point should be just converted to strings.
|
||||
if ( ! is_array( $data ) ) {
|
||||
return (string) $data;
|
||||
}
|
||||
|
||||
foreach ( $data as &$value ) {
|
||||
$value = self::_stringify_data( $value );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin connection notices.
|
||||
*
|
||||
* @package automattic/jetpack-admin-ui
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
|
||||
/**
|
||||
* Admin connection notices.
|
||||
*/
|
||||
class Connection_Notice {
|
||||
|
||||
/**
|
||||
* Whether the class has been initialized.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $is_initialized = false;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! static::$is_initialized ) {
|
||||
add_action( 'current_screen', array( $this, 'initialize_notices' ) );
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the notices if needed.
|
||||
*
|
||||
* @param \WP_Screen $screen WP Core's screen object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize_notices( $screen ) {
|
||||
if ( ! in_array(
|
||||
$screen->id,
|
||||
array(
|
||||
'jetpack_page_akismet-key-config',
|
||||
'admin_page_jetpack_modules',
|
||||
),
|
||||
true
|
||||
) ) {
|
||||
add_action( 'admin_notices', array( $this, 'delete_user_update_connection_owner_notice' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an entire admin notice dedicated to messaging and handling of the case where a user is trying to delete
|
||||
* the connection owner.
|
||||
*/
|
||||
public function delete_user_update_connection_owner_notice() {
|
||||
global $current_screen;
|
||||
|
||||
/*
|
||||
* phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
*
|
||||
* This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
|
||||
* page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
|
||||
*/
|
||||
|
||||
if ( ! isset( $current_screen->base ) || 'users' !== $current_screen->base ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST['action'] ) || 'delete' !== $_REQUEST['action'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get connection owner or bail.
|
||||
$connection_manager = new Manager();
|
||||
$connection_owner_id = $connection_manager->get_connection_owner_id();
|
||||
if ( ! $connection_owner_id ) {
|
||||
return;
|
||||
}
|
||||
$connection_owner_userdata = get_userdata( $connection_owner_id );
|
||||
|
||||
// Bail if we're not trying to delete connection owner.
|
||||
$user_ids_to_delete = array();
|
||||
if ( isset( $_REQUEST['users'] ) ) {
|
||||
$user_ids_to_delete = array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['users'] ) );
|
||||
} elseif ( isset( $_REQUEST['user'] ) ) {
|
||||
$user_ids_to_delete[] = sanitize_text_field( wp_unslash( $_REQUEST['user'] ) );
|
||||
}
|
||||
|
||||
// phpcs:enable
|
||||
$user_ids_to_delete = array_map( 'absint', $user_ids_to_delete );
|
||||
$deleting_connection_owner = in_array( $connection_owner_id, (array) $user_ids_to_delete, true );
|
||||
if ( ! $deleting_connection_owner ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bail if they're trying to delete themselves to avoid confusion.
|
||||
if ( get_current_user_id() === $connection_owner_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tracking = new Tracking();
|
||||
|
||||
// Track it!
|
||||
if ( method_exists( $tracking, 'record_user_event' ) ) {
|
||||
$tracking->record_user_event( 'delete_connection_owner_notice_view' );
|
||||
}
|
||||
|
||||
$connected_admins = $connection_manager->get_connected_users( 'jetpack_disconnect' );
|
||||
$user = is_a( $connection_owner_userdata, 'WP_User' ) ? esc_html( $connection_owner_userdata->data->user_login ) : '';
|
||||
|
||||
echo "<div class='notice notice-warning' id='jetpack-notice-switch-connection-owner'>";
|
||||
echo '<h2>' . esc_html__( 'Important notice about your Jetpack connection:', 'jetpack-connection' ) . '</h2>';
|
||||
echo '<p>' . sprintf(
|
||||
/* translators: WordPress User, if available. */
|
||||
esc_html__( 'Warning! You are about to delete the Jetpack connection owner (%s) for this site, which may cause some of your Jetpack features to stop working.', 'jetpack-connection' ),
|
||||
esc_html( $user )
|
||||
) . '</p>';
|
||||
|
||||
if ( ! empty( $connected_admins ) && count( $connected_admins ) > 1 ) {
|
||||
echo '<form id="jp-switch-connection-owner" action="" method="post">';
|
||||
echo "<label for='owner'>" . esc_html__( 'You can choose to transfer connection ownership to one of these already-connected admins:', 'jetpack-connection' ) . ' </label>';
|
||||
|
||||
$connected_admin_ids = array_map(
|
||||
function ( $connected_admin ) {
|
||||
return $connected_admin->ID;
|
||||
},
|
||||
$connected_admins
|
||||
);
|
||||
|
||||
wp_dropdown_users(
|
||||
array(
|
||||
'name' => 'owner',
|
||||
'include' => array_diff( $connected_admin_ids, array( $connection_owner_id ) ),
|
||||
'show' => 'display_name_with_login',
|
||||
)
|
||||
);
|
||||
|
||||
echo '<p>';
|
||||
submit_button( esc_html__( 'Set new connection owner', 'jetpack-connection' ), 'primary', 'jp-switch-connection-owner-submit', false );
|
||||
echo '</p>';
|
||||
|
||||
echo "<div id='jp-switch-user-results'></div>";
|
||||
echo '</form>';
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
( function() {
|
||||
const switchOwnerButton = document.getElementById('jp-switch-connection-owner');
|
||||
if ( ! switchOwnerButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switchOwnerButton.addEventListener( 'submit', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = document.getElementById('jp-switch-connection-owner-submit');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
const results = document.getElementById('jp-switch-user-results');
|
||||
results.innerHTML = '';
|
||||
results.classList.remove( 'error-message' );
|
||||
|
||||
const handleAPIError = ( message ) => {
|
||||
submitBtn.disabled = false;
|
||||
|
||||
results.classList.add( 'error-message' );
|
||||
results.innerHTML = message || "<?php esc_html_e( 'Something went wrong. Please try again.', 'jetpack-connection' ); ?>";
|
||||
}
|
||||
|
||||
fetch(
|
||||
<?php echo wp_json_encode( esc_url_raw( get_rest_url() . 'jetpack/v4/connection/owner' ), JSON_HEX_TAG | JSON_HEX_AMP ); ?>,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-WP-Nonce': <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ), JSON_HEX_TAG | JSON_HEX_AMP ); ?>,
|
||||
},
|
||||
body: new URLSearchParams( new FormData( this ) ),
|
||||
}
|
||||
)
|
||||
.then( response => response.json() )
|
||||
.then( data => {
|
||||
if ( data.hasOwnProperty( 'code' ) && data.code === 'success' ) {
|
||||
// Owner successfully changed.
|
||||
results.innerHTML = <?php echo wp_json_encode( esc_html__( 'Success!', 'jetpack-connection' ), JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
|
||||
setTimeout(function () {
|
||||
document.getElementById( 'jetpack-notice-switch-connection-owner' ).style.display = 'none';
|
||||
}, 1000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleAPIError( data?.message );
|
||||
} )
|
||||
.catch( () => handleAPIError() );
|
||||
});
|
||||
} )();
|
||||
</script>
|
||||
<?php
|
||||
} else {
|
||||
echo '<p>' . esc_html__( 'Every Jetpack site needs at least one connected admin for the features to work properly. Please connect to your WordPress.com account via the button below. Once you connect, you may refresh this page to see an option to change the connection owner.', 'jetpack-connection' ) . '</p>';
|
||||
$connect_url = $connection_manager->get_authorization_url();
|
||||
$connect_url = add_query_arg( 'from', 'delete_connection_owner_notice', $connect_url );
|
||||
echo "<a href='" . esc_url( $connect_url ) . "' target='_blank' rel='noopener noreferrer' class='button-primary'>" . esc_html__( 'Connect to WordPress.com', 'jetpack-connection' ) . '</a>';
|
||||
}
|
||||
|
||||
echo '<p>';
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: URL to Jetpack support doc regarding the primary user. */
|
||||
__( "<a href='%s' target='_blank' rel='noopener noreferrer'>Learn more</a> about the connection owner and what will break if you do not have one.", 'jetpack-connection' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'target' => true,
|
||||
'rel' => true,
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url( Redirect::get_url( 'jetpack-support-primary-user' ) )
|
||||
);
|
||||
echo '</p>';
|
||||
echo '<p>';
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: URL to contact Jetpack support. */
|
||||
__( 'As always, feel free to <a href="%s" target="_blank" rel="noopener noreferrer">contact our support team</a> if you have any questions.', 'jetpack-connection' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'target' => true,
|
||||
'rel' => true,
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url( Redirect::get_url( 'jetpack-contact-support' ) )
|
||||
);
|
||||
echo '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
@@ -1,775 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection error class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* The Jetpack Connection Errors that handles errors
|
||||
*
|
||||
* This class handles the following workflow:
|
||||
*
|
||||
* 1. A XML-RCP request with an invalid signature triggers a error
|
||||
* 2. Applies a gate to only process each error code once an hour to avoid overflow
|
||||
* 3. It stores the error on the database, but we don't know yet if this is a valid error, because
|
||||
* we can't confirm it came from WP.com.
|
||||
* 4. It encrypts the error details and send it to thw wp.com server
|
||||
* 5. wp.com checks it and, if valid, sends a new request back to this site using the verify_xml_rpc_error REST endpoint
|
||||
* 6. This endpoint add this error to the Verified errors in the database
|
||||
* 7. Triggers a workflow depending on the error (display user an error message, do some self healing, etc.)
|
||||
*
|
||||
* Errors are stored in the database as options in the following format:
|
||||
*
|
||||
* [
|
||||
* $error_code => [
|
||||
* $user_id => [
|
||||
* $error_details
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* For each error code we store a maximum of 5 errors for 5 different user ids.
|
||||
*
|
||||
* An user ID can be
|
||||
* * 0 for blog tokens
|
||||
* * positive integer for user tokens
|
||||
* * 'invalid' for malformed tokens
|
||||
*
|
||||
* @since 1.14.2
|
||||
*/
|
||||
class Error_Handler {
|
||||
|
||||
/**
|
||||
* The name of the option that stores the errors
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STORED_ERRORS_OPTION = 'jetpack_connection_xmlrpc_errors';
|
||||
|
||||
/**
|
||||
* The name of the option that stores the errors
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STORED_VERIFIED_ERRORS_OPTION = 'jetpack_connection_xmlrpc_verified_errors';
|
||||
|
||||
/**
|
||||
* The prefix of the transient that controls the gate for each error code
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ERROR_REPORTING_GATE = 'jetpack_connection_error_reporting_gate_';
|
||||
|
||||
/**
|
||||
* Time in seconds a test should live in the database before being discarded
|
||||
*
|
||||
* @since 1.14.2
|
||||
*/
|
||||
const ERROR_LIFE_TIME = DAY_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* The error code for event tracking purposes.
|
||||
* If there are many, only the first error code will be tracked.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $error_code;
|
||||
|
||||
/**
|
||||
* List of known errors. Only error codes in this list will be handled
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $known_errors = array(
|
||||
'malformed_token',
|
||||
'malformed_user_id',
|
||||
'unknown_user',
|
||||
'no_user_tokens',
|
||||
'empty_master_user_option',
|
||||
'no_token_for_user',
|
||||
'token_malformed',
|
||||
'user_id_mismatch',
|
||||
'no_possible_tokens',
|
||||
'no_valid_user_token',
|
||||
'no_valid_blog_token',
|
||||
'unknown_token',
|
||||
'could_not_sign',
|
||||
'invalid_scheme',
|
||||
'invalid_secret',
|
||||
'invalid_token',
|
||||
'token_mismatch',
|
||||
'invalid_body',
|
||||
'invalid_signature',
|
||||
'invalid_body_hash',
|
||||
'invalid_nonce',
|
||||
'signature_mismatch',
|
||||
'invalid_connection_owner',
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds the instance of this singleton class
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @var Error_Handler $instance
|
||||
*/
|
||||
public static $instance = null;
|
||||
|
||||
/**
|
||||
* Initialize instance, hookds and load verified errors handlers
|
||||
*
|
||||
* @since 1.14.2
|
||||
*/
|
||||
private function __construct() {
|
||||
defined( 'JETPACK__ERRORS_PUBLIC_KEY' ) || define( 'JETPACK__ERRORS_PUBLIC_KEY', 'KdZY80axKX+nWzfrOcizf0jqiFHnrWCl9X8yuaClKgM=' );
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_verify_error_endpoint' ) );
|
||||
|
||||
$this->handle_verified_errors();
|
||||
|
||||
// If the site gets reconnected, clear errors.
|
||||
add_action( 'jetpack_site_registered', array( $this, 'delete_all_errors' ) );
|
||||
add_action( 'jetpack_get_site_data_success', array( $this, 'delete_all_api_errors' ) );
|
||||
add_filter( 'jetpack_connection_disconnect_site_wpcom', array( $this, 'delete_all_errors_and_return_unfiltered_value' ) );
|
||||
add_filter( 'jetpack_connection_delete_all_tokens', array( $this, 'delete_all_errors_and_return_unfiltered_value' ) );
|
||||
add_action( 'jetpack_unlinked_user', array( $this, 'delete_all_errors' ) );
|
||||
add_action( 'jetpack_updated_user_token', array( $this, 'delete_all_errors' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of verified errors and act upon them
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_verified_errors() {
|
||||
$verified_errors = $this->get_verified_errors();
|
||||
foreach ( array_keys( $verified_errors ) as $error_code ) {
|
||||
switch ( $error_code ) {
|
||||
case 'malformed_token':
|
||||
case 'token_malformed':
|
||||
case 'no_possible_tokens':
|
||||
case 'no_valid_user_token':
|
||||
case 'no_valid_blog_token':
|
||||
case 'unknown_token':
|
||||
case 'could_not_sign':
|
||||
case 'invalid_token':
|
||||
case 'token_mismatch':
|
||||
case 'invalid_signature':
|
||||
case 'signature_mismatch':
|
||||
case 'no_user_tokens':
|
||||
case 'no_token_for_user':
|
||||
case 'invalid_connection_owner':
|
||||
add_action( 'admin_notices', array( $this, 'generic_admin_notice_error' ) );
|
||||
add_action( 'react_connection_errors_initial_state', array( $this, 'jetpack_react_dashboard_error' ) );
|
||||
$this->error_code = $error_code;
|
||||
|
||||
// Since we are only generically handling errors, we don't need to trigger error messages for each one of them.
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instance of this singleton class
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return Error_Handler $instance
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of a connection error that was encountered
|
||||
*
|
||||
* @param \WP_Error $error The error object.
|
||||
* @param boolean $force Force the report, even if should_report_error is false.
|
||||
* @param boolean $skip_wpcom_verification Set to 'true' to verify the error locally and skip the WP.com verification.
|
||||
*
|
||||
* @return void
|
||||
* @since 1.14.2
|
||||
*/
|
||||
public function report_error( \WP_Error $error, $force = false, $skip_wpcom_verification = false ) {
|
||||
if ( in_array( $error->get_error_code(), $this->known_errors, true ) && $this->should_report_error( $error ) || $force ) {
|
||||
$stored_error = $this->store_error( $error );
|
||||
if ( $stored_error ) {
|
||||
$skip_wpcom_verification ? $this->verify_error( $stored_error ) : $this->send_error_to_wpcom( $stored_error );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the status of the gate
|
||||
*
|
||||
* This protects the site (and WPCOM) against over loads.
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param \WP_Error $error the error object.
|
||||
* @return boolean $should_report True if gate is open and the error should be reported.
|
||||
*/
|
||||
public function should_report_error( \WP_Error $error ) {
|
||||
if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to bypass the gate for the error handling
|
||||
*
|
||||
* By default, we only process errors once an hour for each error code.
|
||||
* This is done to avoid overflows. If you need to disable this gate, you can set this variable to true.
|
||||
*
|
||||
* This filter is useful for unit testing
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param boolean $bypass_gate whether to bypass the gate. Default is false, do not bypass.
|
||||
*/
|
||||
$bypass_gate = apply_filters( 'jetpack_connection_bypass_error_reporting_gate', false );
|
||||
if ( true === $bypass_gate ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$transient = self::ERROR_REPORTING_GATE . $error->get_error_code();
|
||||
|
||||
if ( get_transient( $transient ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_transient( $transient, true, HOUR_IN_SECONDS );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the error in the database so we know there is an issue and can inform the user
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param \WP_Error $error the error object.
|
||||
* @return boolean|array False if stored errors were not updated and the error array if it was successfully stored.
|
||||
*/
|
||||
public function store_error( \WP_Error $error ) {
|
||||
|
||||
$stored_errors = $this->get_stored_errors();
|
||||
$error_array = $this->wp_error_to_array( $error );
|
||||
$error_code = $error->get_error_code();
|
||||
$user_id = $error_array['user_id'];
|
||||
|
||||
if ( ! isset( $stored_errors[ $error_code ] ) || ! is_array( $stored_errors[ $error_code ] ) ) {
|
||||
$stored_errors[ $error_code ] = array();
|
||||
}
|
||||
|
||||
$stored_errors[ $error_code ][ $user_id ] = $error_array;
|
||||
|
||||
// Let's store a maximum of 5 different user ids for each error code.
|
||||
$error_code_count = is_countable( $stored_errors[ $error_code ] ) ? count( $stored_errors[ $error_code ] ) : 0;
|
||||
if ( $error_code_count > 5 ) {
|
||||
// array_shift will destroy keys here because they are numeric, so manually remove first item.
|
||||
$keys = array_keys( $stored_errors[ $error_code ] );
|
||||
unset( $stored_errors[ $error_code ][ $keys[0] ] );
|
||||
}
|
||||
|
||||
if ( update_option( self::STORED_ERRORS_OPTION, $stored_errors ) ) {
|
||||
return $error_array;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a WP_Error object in the array representation we store in the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param \WP_Error $error the error object.
|
||||
* @return boolean|array False if error is invalid or the error array
|
||||
*/
|
||||
public function wp_error_to_array( \WP_Error $error ) {
|
||||
|
||||
$data = $error->get_error_data();
|
||||
|
||||
if ( ! isset( $data['signature_details'] ) || ! is_array( $data['signature_details'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signature_details = $data['signature_details'];
|
||||
|
||||
if ( ! isset( $signature_details['token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_id = $this->get_user_id_from_token( $signature_details['token'] );
|
||||
|
||||
$error_array = array(
|
||||
'error_code' => $error->get_error_code(),
|
||||
'user_id' => $user_id,
|
||||
'error_message' => $error->get_error_message(),
|
||||
'error_data' => $signature_details,
|
||||
'timestamp' => time(),
|
||||
'nonce' => wp_generate_password( 10, false ),
|
||||
'error_type' => empty( $data['error_type'] ) ? '' : $data['error_type'],
|
||||
);
|
||||
|
||||
return $error_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the error to WP.com to be verified
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param array $error_array The array representation of the error as it is stored in the database.
|
||||
* @return bool
|
||||
*/
|
||||
public function send_error_to_wpcom( $error_array ) {
|
||||
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$encrypted_data = $this->encrypt_data_to_wpcom( $error_array );
|
||||
|
||||
if ( false === $encrypted_data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'body' => array(
|
||||
'error_data' => $encrypted_data,
|
||||
),
|
||||
);
|
||||
|
||||
// send encrypted data to WP.com Public-API v2.
|
||||
wp_remote_post( "https://public-api.wordpress.com/wpcom/v2/sites/{$blog_id}/jetpack-report-error/", $args );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data to be sent over to WP.com
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param array|string $data the data to be encoded.
|
||||
* @return boolean|string The encoded string on success, false on failure
|
||||
*/
|
||||
public function encrypt_data_to_wpcom( $data ) {
|
||||
|
||||
try {
|
||||
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
$encrypted_data = base64_encode( sodium_crypto_box_seal( wp_json_encode( $data ), base64_decode( JETPACK__ERRORS_PUBLIC_KEY ) ) );
|
||||
// phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
// phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
} catch ( \SodiumException $e ) {
|
||||
// error encrypting data.
|
||||
return false;
|
||||
}
|
||||
|
||||
return $encrypted_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the user ID from a token
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param string $token the token used to make the request.
|
||||
* @return string $the user id or `invalid` if user id not present.
|
||||
*/
|
||||
public function get_user_id_from_token( $token ) {
|
||||
$user_id = 'invalid';
|
||||
|
||||
if ( $token ) {
|
||||
$parsed_token = explode( ':', wp_unslash( $token ) );
|
||||
|
||||
if ( isset( $parsed_token[2] ) && ctype_digit( $parsed_token[2] ) ) {
|
||||
$user_id = $parsed_token[2];
|
||||
}
|
||||
}
|
||||
|
||||
return $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reported errors stored in the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return array $errors
|
||||
*/
|
||||
public function get_stored_errors() {
|
||||
|
||||
$stored_errors = get_option( self::STORED_ERRORS_OPTION );
|
||||
|
||||
if ( ! is_array( $stored_errors ) ) {
|
||||
$stored_errors = array();
|
||||
}
|
||||
|
||||
$stored_errors = $this->garbage_collector( $stored_errors );
|
||||
|
||||
return $stored_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the verified errors stored in the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return array $errors
|
||||
*/
|
||||
public function get_verified_errors() {
|
||||
|
||||
$verified_errors = get_option( self::STORED_VERIFIED_ERRORS_OPTION );
|
||||
|
||||
if ( ! is_array( $verified_errors ) ) {
|
||||
$verified_errors = array();
|
||||
}
|
||||
|
||||
$verified_errors = $this->garbage_collector( $verified_errors );
|
||||
|
||||
return $verified_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired errors from the array
|
||||
*
|
||||
* This method is called by get_stored_errors and get_verified errors and filters their result
|
||||
* Whenever a new error is stored to the database or verified, this will be triggered and the
|
||||
* expired error will be permantently removed from the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param array $errors array of errors as stored in the database.
|
||||
* @return array
|
||||
*/
|
||||
private function garbage_collector( $errors ) {
|
||||
foreach ( $errors as $error_code => $users ) {
|
||||
foreach ( $users as $user_id => $error ) {
|
||||
if ( self::ERROR_LIFE_TIME < time() - (int) $error['timestamp'] ) {
|
||||
unset( $errors[ $error_code ][ $user_id ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear empty error codes.
|
||||
$errors = array_filter(
|
||||
$errors,
|
||||
function ( $user_errors ) {
|
||||
return ! empty( $user_errors );
|
||||
}
|
||||
);
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored and verified errors from the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_all_errors() {
|
||||
$this->delete_stored_errors();
|
||||
$this->delete_verified_errors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored and verified API errors from the database, leave the non-API errors intact.
|
||||
*
|
||||
* @since 1.54.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_all_api_errors() {
|
||||
$type_filter = function ( $errors ) {
|
||||
if ( is_array( $errors ) ) {
|
||||
foreach ( $errors as $key => $error ) {
|
||||
if ( ! empty( $error['error_type'] ) && in_array( $error['error_type'], array( 'xmlrpc', 'rest' ), true ) ) {
|
||||
unset( $errors[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count( $errors ) ? $errors : null;
|
||||
};
|
||||
|
||||
$stored_errors = $this->get_stored_errors();
|
||||
if ( is_array( $stored_errors ) && count( $stored_errors ) ) {
|
||||
$stored_errors = array_filter( array_map( $type_filter, $stored_errors ) );
|
||||
if ( count( $stored_errors ) ) {
|
||||
update_option( static::STORED_ERRORS_OPTION, $stored_errors );
|
||||
} else {
|
||||
delete_option( static::STORED_ERRORS_OPTION );
|
||||
}
|
||||
}
|
||||
|
||||
$verified_errors = $this->get_verified_errors();
|
||||
if ( is_array( $verified_errors ) && count( $verified_errors ) ) {
|
||||
$verified_errors = array_filter( array_map( $type_filter, $verified_errors ) );
|
||||
if ( count( $verified_errors ) ) {
|
||||
update_option( static::STORED_VERIFIED_ERRORS_OPTION, $verified_errors );
|
||||
} else {
|
||||
delete_option( static::STORED_VERIFIED_ERRORS_OPTION );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored and verified errors from the database and returns unfiltered value
|
||||
*
|
||||
* This is used to hook into a couple of filters that expect true to not short circuit the disconnection flow
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param mixed $check The input sent by the filter.
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_all_errors_and_return_unfiltered_value( $check ) {
|
||||
$this->delete_all_errors();
|
||||
return $check;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the reported errors stored in the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return boolean True, if option is successfully deleted. False on failure.
|
||||
*/
|
||||
public function delete_stored_errors() {
|
||||
return delete_option( self::STORED_ERRORS_OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the verified errors stored in the database
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return boolean True, if option is successfully deleted. False on failure.
|
||||
*/
|
||||
public function delete_verified_errors() {
|
||||
return delete_option( self::STORED_VERIFIED_ERRORS_OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an error based on the nonce
|
||||
*
|
||||
* Receives a nonce and finds the related error.
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param string $nonce The nonce created for the error we want to get.
|
||||
* @return null|array Returns the error array representation or null if error not found.
|
||||
*/
|
||||
public function get_error_by_nonce( $nonce ) {
|
||||
$errors = $this->get_stored_errors();
|
||||
foreach ( $errors as $user_group ) {
|
||||
foreach ( $user_group as $error ) {
|
||||
if ( $error['nonce'] === $nonce ) {
|
||||
return $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the verified error list
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param array $error The error array, as it was saved in the unverified errors list.
|
||||
* @return void
|
||||
*/
|
||||
public function verify_error( $error ) {
|
||||
|
||||
$verified_errors = $this->get_verified_errors();
|
||||
$error_code = $error['error_code'];
|
||||
$user_id = $error['user_id'];
|
||||
|
||||
if ( ! isset( $verified_errors[ $error_code ] ) ) {
|
||||
$verified_errors[ $error_code ] = array();
|
||||
}
|
||||
|
||||
$verified_errors[ $error_code ][ $user_id ] = $error;
|
||||
|
||||
update_option( self::STORED_VERIFIED_ERRORS_OPTION, $verified_errors );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API end point for error hanlding.
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_verify_error_endpoint() {
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/verify_xmlrpc_error',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'verify_xml_rpc_error' ),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'nonce' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles verification that a xml rpc error is legit and came from WordPres.com
|
||||
*
|
||||
* @since 1.14.2
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function verify_xml_rpc_error( \WP_REST_Request $request ) {
|
||||
$error = $this->get_error_by_nonce( $request['nonce'] );
|
||||
|
||||
if ( $error ) {
|
||||
$this->verify_error( $error );
|
||||
return new \WP_REST_Response( true, 200 );
|
||||
}
|
||||
|
||||
return new \WP_REST_Response( false, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a generic error notice for all connection errors
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function generic_admin_notice_error() {
|
||||
// do not add admin notice to the jetpack dashboard.
|
||||
global $pagenow;
|
||||
if ( 'admin.php' === $pagenow || isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) { // phpcs:ignore
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'jetpack_connect' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the message to be displayed in the admin notices area when there's a connection error.
|
||||
*
|
||||
* By default we don't display any errors.
|
||||
*
|
||||
* Return an empty value to disable the message.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param string $message The error message.
|
||||
* @param array $errors The array of errors. See Automattic\Jetpack\Connection\Error_Handler for details on the array structure.
|
||||
*/
|
||||
$message = apply_filters( 'jetpack_connection_error_notice_message', '', $this->get_verified_errors() );
|
||||
|
||||
/**
|
||||
* Fires inside the admin_notices hook just before displaying the error message for a broken connection.
|
||||
*
|
||||
* If you want to disable the default message from being displayed, return an emtpy value in the jetpack_connection_error_notice_message filter.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param array $errors The array of errors. See Automattic\Jetpack\Connection\Error_Handler for details on the array structure.
|
||||
*/
|
||||
do_action( 'jetpack_connection_error_notice', $this->get_verified_errors() );
|
||||
|
||||
if ( empty( $message ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice notice-error is-dismissible jetpack-message jp-connect" style="display:block !important;">
|
||||
<p><?php echo esc_html( $message ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the error message to the Jetpack React Dashboard
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param array $errors The array of errors. See Automattic\Jetpack\Connection\Error_Handler for details on the array structure.
|
||||
* @return array
|
||||
*/
|
||||
public function jetpack_react_dashboard_error( $errors ) {
|
||||
$errors[] = array(
|
||||
'code' => 'connection_error',
|
||||
'message' => __( 'Your connection with WordPress.com seems to be broken. If you\'re experiencing issues, please try reconnecting.', 'jetpack-connection' ),
|
||||
'action' => 'reconnect',
|
||||
'data' => array( 'api_error_code' => $this->error_code ),
|
||||
);
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check REST API response for errors, and report them to WP.com if needed.
|
||||
*
|
||||
* @see wp_remote_request() For more information on the $http_response array format.
|
||||
* @param array|\WP_Error $http_response The response or WP_Error on failure.
|
||||
* @param array $auth_data Auth data, allowed keys: `token`, `timestamp`, `nonce`, `body-hash`.
|
||||
* @param string $url Request URL.
|
||||
* @param string $method Request method.
|
||||
* @param string $error_type The source of an error: 'xmlrpc' or 'rest'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_api_response_for_errors( $http_response, $auth_data, $url, $method, $error_type ) {
|
||||
if ( 200 === wp_remote_retrieve_response_code( $http_response ) || ! is_array( $auth_data ) || ! $url || ! $method ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body_raw = wp_remote_retrieve_body( $http_response );
|
||||
if ( ! $body_raw ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = json_decode( $body_raw, true );
|
||||
if ( empty( $body['error'] ) || ( ! is_string( $body['error'] ) && ! is_int( $body['error'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = new \WP_Error(
|
||||
$body['error'],
|
||||
empty( $body['message'] ) ? '' : $body['message'],
|
||||
array(
|
||||
'signature_details' => array(
|
||||
'token' => empty( $auth_data['token'] ) ? '' : $auth_data['token'],
|
||||
'timestamp' => empty( $auth_data['timestamp'] ) ? '' : $auth_data['timestamp'],
|
||||
'nonce' => empty( $auth_data['nonce'] ) ? '' : $auth_data['nonce'],
|
||||
'body_hash' => empty( $auth_data['body_hash'] ) ? '' : $auth_data['body_hash'],
|
||||
'method' => $method,
|
||||
'url' => $url,
|
||||
),
|
||||
'error_type' => in_array( $error_type, array( 'xmlrpc', 'rest' ), true ) ? $error_type : '',
|
||||
)
|
||||
);
|
||||
|
||||
$this->report_error( $error, false, true );
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Heartbeat package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
use Jetpack_Options;
|
||||
use WP_CLI;
|
||||
|
||||
/**
|
||||
* Heartbeat sends a batch of stats to wp.com once a day
|
||||
*/
|
||||
class Heartbeat {
|
||||
|
||||
/**
|
||||
* Holds the singleton instance of this class
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since-jetpack 2.3.3
|
||||
* @var Heartbeat
|
||||
*/
|
||||
private static $instance = false;
|
||||
|
||||
/**
|
||||
* Cronjob identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $cron_name = 'jetpack_v2_heartbeat';
|
||||
|
||||
/**
|
||||
* Singleton
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since-jetpack 2.3.3
|
||||
* @static
|
||||
* @return Heartbeat
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new Heartbeat();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for singleton
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since-jetpack 2.3.3
|
||||
*/
|
||||
private function __construct() {
|
||||
|
||||
// Schedule the task.
|
||||
add_action( $this->cron_name, array( $this, 'cron_exec' ) );
|
||||
|
||||
if ( ! wp_next_scheduled( $this->cron_name ) ) {
|
||||
// Deal with the old pre-3.0 weekly one.
|
||||
$timestamp = wp_next_scheduled( 'jetpack_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'jetpack_heartbeat' );
|
||||
}
|
||||
|
||||
wp_schedule_event( time(), 'daily', $this->cron_name );
|
||||
}
|
||||
|
||||
add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( __CLASS__, 'jetpack_xmlrpc_methods' ) );
|
||||
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
WP_CLI::add_command( 'jetpack-heartbeat', array( $this, 'cli_callback' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that gets executed on the wp-cron call
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since-jetpack 2.3.3
|
||||
* @global string $wp_version
|
||||
*/
|
||||
public function cron_exec() {
|
||||
|
||||
$a8c_mc_stats = new A8c_Mc_Stats();
|
||||
|
||||
/*
|
||||
* This should run daily. Figuring in for variances in
|
||||
* WP_CRON, don't let it run more than every 23 hours at most.
|
||||
*
|
||||
* i.e. if it ran less than 23 hours ago, fail out.
|
||||
*/
|
||||
$last = (int) Jetpack_Options::get_option( 'last_heartbeat' );
|
||||
if ( $last && ( $last + DAY_IN_SECONDS - HOUR_IN_SECONDS > time() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for an identity crisis
|
||||
*
|
||||
* If one exists:
|
||||
* - Bump stat for ID crisis
|
||||
* - Email site admin about potential ID crisis
|
||||
*/
|
||||
|
||||
// Coming Soon!
|
||||
|
||||
foreach ( self::generate_stats_array( 'v2-' ) as $key => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $v ) {
|
||||
$a8c_mc_stats->add( $key, (string) $v );
|
||||
}
|
||||
} else {
|
||||
$a8c_mc_stats->add( $key, (string) $value );
|
||||
}
|
||||
}
|
||||
|
||||
Jetpack_Options::update_option( 'last_heartbeat', time() );
|
||||
|
||||
$a8c_mc_stats->do_server_side_stats();
|
||||
|
||||
/**
|
||||
* Fires when we synchronize all registered options on heartbeat.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
do_action( 'jetpack_heartbeat' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates heartbeat stats data.
|
||||
*
|
||||
* @param string $prefix Prefix to add before stats identifier.
|
||||
*
|
||||
* @return array The stats array.
|
||||
*/
|
||||
public static function generate_stats_array( $prefix = '' ) {
|
||||
|
||||
/**
|
||||
* This filter is used to build the array of stats that are bumped once a day by Jetpack Heartbeat.
|
||||
*
|
||||
* Filter the array and add key => value pairs where
|
||||
* * key is the stat group name
|
||||
* * value is the stat name.
|
||||
*
|
||||
* Example:
|
||||
* add_filter( 'jetpack_heartbeat_stats_array', function( $stats ) {
|
||||
* $stats['is-https'] = is_ssl() ? 'https' : 'http';
|
||||
* });
|
||||
*
|
||||
* This will bump the stats for the 'is-https/https' or 'is-https/http' stat.
|
||||
*
|
||||
* @param array $stats The stats to be filtered.
|
||||
* @param string $prefix The prefix that will automatically be added at the begining at each stat group name.
|
||||
*/
|
||||
$stats = apply_filters( 'jetpack_heartbeat_stats_array', array(), $prefix );
|
||||
$return = array();
|
||||
|
||||
// Apply prefix to stats.
|
||||
foreach ( $stats as $stat => $value ) {
|
||||
$return[ "$prefix$stat" ] = $value;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers jetpack.getHeartbeatData xmlrpc method
|
||||
*
|
||||
* @param array $methods The list of methods to be filtered.
|
||||
* @return array $methods
|
||||
*/
|
||||
public static function jetpack_xmlrpc_methods( $methods ) {
|
||||
$methods['jetpack.getHeartbeatData'] = array( __CLASS__, 'xmlrpc_data_response' );
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the response for the jetpack.getHeartbeatData xmlrpc method
|
||||
*
|
||||
* @param array $params The parameters received in the request.
|
||||
* @return array $params all the stats that heartbeat handles.
|
||||
*/
|
||||
public static function xmlrpc_data_response( $params = array() ) {
|
||||
// The WordPress XML-RPC server sets a default param of array()
|
||||
// if no argument is passed on the request and the method handlers get this array in $params.
|
||||
// generate_stats_array() needs a string as first argument.
|
||||
$params = empty( $params ) ? '' : $params;
|
||||
return self::generate_stats_array( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear scheduled events
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deactivate() {
|
||||
// Deal with the old pre-3.0 weekly one.
|
||||
$timestamp = wp_next_scheduled( 'jetpack_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'jetpack_heartbeat' );
|
||||
}
|
||||
|
||||
$timestamp = wp_next_scheduled( $this->cron_name );
|
||||
wp_unschedule_event( $timestamp, $this->cron_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Interact with the Heartbeat
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* inspect (default): Gets the list of data that is going to be sent in the heartbeat and the date/time of the last heartbeat
|
||||
*
|
||||
* @param array $args Arguments passed via CLI.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cli_callback( $args ) {
|
||||
|
||||
$allowed_args = array(
|
||||
'inspect',
|
||||
);
|
||||
|
||||
if ( isset( $args[0] ) && ! in_array( $args[0], $allowed_args, true ) ) {
|
||||
/* translators: %s is a command like "prompt" */
|
||||
WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack-connection' ), $args[0] ) );
|
||||
}
|
||||
|
||||
$stats = self::generate_stats_array();
|
||||
$formatted_stats = array();
|
||||
|
||||
foreach ( $stats as $stat_name => $bin ) {
|
||||
$formatted_stats[] = array(
|
||||
'Stat name' => $stat_name,
|
||||
'Bin' => $bin,
|
||||
);
|
||||
}
|
||||
|
||||
WP_CLI\Utils\format_items( 'table', $formatted_stats, array( 'Stat name', 'Bin' ) );
|
||||
|
||||
$last_heartbeat = Jetpack_Options::get_option( 'last_heartbeat' );
|
||||
|
||||
if ( $last_heartbeat ) {
|
||||
$last_date = gmdate( 'Y-m-d H:i:s', $last_heartbeat );
|
||||
/* translators: %s is the full datetime of the last heart beat e.g. 2020-01-01 12:21:23 */
|
||||
WP_CLI::line( sprintf( __( 'Last heartbeat sent at: %s', 'jetpack-connection' ), $last_date ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The React initial state.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Status;
|
||||
|
||||
/**
|
||||
* The React initial state.
|
||||
*/
|
||||
class Initial_State {
|
||||
|
||||
/**
|
||||
* Get the initial state data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_data() {
|
||||
global $wp_version;
|
||||
|
||||
$status = new Status();
|
||||
|
||||
return array(
|
||||
'apiRoot' => esc_url_raw( rest_url() ),
|
||||
'apiNonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
|
||||
'connectionStatus' => REST_Connector::connection_status( false ),
|
||||
'userConnectionData' => REST_Connector::get_user_connection_data( false ),
|
||||
'connectedPlugins' => REST_Connector::get_connection_plugins( false ),
|
||||
'wpVersion' => $wp_version,
|
||||
'siteSuffix' => $status->get_site_suffix(),
|
||||
'connectionErrors' => Error_Handler::get_instance()->get_verified_errors(),
|
||||
'isOfflineMode' => $status->is_offline_mode(),
|
||||
'calypsoEnv' => ( new Status\Host() )->get_calypso_env(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the initial state into a JavaScript variable.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function render() {
|
||||
return 'var JP_CONNECTION_INITIAL_STATE; typeof JP_CONNECTION_INITIAL_STATE === "object" || (JP_CONNECTION_INITIAL_STATE = JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( self::get_data() ) ) . '")));';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the initial state using an inline script.
|
||||
*
|
||||
* @param string $handle The JS script handle.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render_script( $handle ) {
|
||||
wp_add_inline_script( $handle, static::render(), 'before' );
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The nonce handler.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* The nonce handler.
|
||||
*/
|
||||
class Nonce_Handler {
|
||||
|
||||
/**
|
||||
* How long the scheduled cleanup can run (in seconds).
|
||||
* Can be modified using the filter `jetpack_connection_nonce_scheduled_cleanup_limit`.
|
||||
*/
|
||||
const SCHEDULED_CLEANUP_TIME_LIMIT = 5;
|
||||
|
||||
/**
|
||||
* How many nonces should be removed per batch during the `clean_all()` run.
|
||||
*/
|
||||
const CLEAN_ALL_LIMIT_PER_BATCH = 1000;
|
||||
|
||||
/**
|
||||
* Nonce lifetime in seconds.
|
||||
*/
|
||||
const LIFETIME = HOUR_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* The nonces used during the request are stored here to keep them valid.
|
||||
* The property is static to keep the nonces accessible between the `Nonce_Handler` instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $nonces_used_this_request = array();
|
||||
|
||||
/**
|
||||
* The database object.
|
||||
*
|
||||
* @var \wpdb
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Initializing the object.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
|
||||
$this->db = $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduling the WP-cron cleanup event.
|
||||
*/
|
||||
public function init_schedule() {
|
||||
add_action( 'jetpack_clean_nonces', array( __CLASS__, 'clean_scheduled' ) );
|
||||
if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
|
||||
wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule the WP-cron cleanup event to make it start sooner.
|
||||
*/
|
||||
public function reschedule() {
|
||||
wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
|
||||
wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a used nonce to a list of known nonces.
|
||||
*
|
||||
* @param int $timestamp the current request timestamp.
|
||||
* @param string $nonce the nonce value.
|
||||
*
|
||||
* @return bool whether the nonce is unique or not.
|
||||
*/
|
||||
public function add( $timestamp, $nonce ) {
|
||||
if ( isset( static::$nonces_used_this_request[ "$timestamp:$nonce" ] ) ) {
|
||||
return static::$nonces_used_this_request[ "$timestamp:$nonce" ];
|
||||
}
|
||||
|
||||
// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp and $nonce.
|
||||
$timestamp = (int) $timestamp;
|
||||
$nonce = esc_sql( $nonce );
|
||||
|
||||
// Raw query so we can avoid races: add_option will also update.
|
||||
$show_errors = $this->db->hide_errors();
|
||||
|
||||
// Running `try...finally` to make sure that we re-enable errors in case of an exception.
|
||||
try {
|
||||
$old_nonce = $this->db->get_row(
|
||||
$this->db->prepare( "SELECT 1 FROM `{$this->db->options}` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
|
||||
);
|
||||
|
||||
if ( $old_nonce === null ) {
|
||||
$return = (bool) $this->db->query(
|
||||
$this->db->prepare(
|
||||
"INSERT INTO `{$this->db->options}` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
|
||||
"jetpack_nonce_{$timestamp}_{$nonce}",
|
||||
time(),
|
||||
'no'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$return = false;
|
||||
}
|
||||
} finally {
|
||||
$this->db->show_errors( $show_errors );
|
||||
}
|
||||
|
||||
static::$nonces_used_this_request[ "$timestamp:$nonce" ] = $return;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removing all existing nonces, or at least as many as possible.
|
||||
* Capped at 20 seconds to avoid breaking the site.
|
||||
*
|
||||
* @param int $cutoff_timestamp All nonces added before this timestamp will be removed.
|
||||
* @param int $time_limit How long the cleanup can run (in seconds).
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function clean_all( $cutoff_timestamp = PHP_INT_MAX, $time_limit = 20 ) {
|
||||
// phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
|
||||
for ( $end_time = time() + $time_limit; time() < $end_time; ) {
|
||||
$result = $this->delete( static::CLEAN_ALL_LIMIT_PER_BATCH, $cutoff_timestamp );
|
||||
|
||||
if ( ! $result ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled clean up of the expired nonces.
|
||||
*/
|
||||
public static function clean_scheduled() {
|
||||
/**
|
||||
* Adjust the time limit for the scheduled cleanup.
|
||||
*
|
||||
* @since 9.5.0
|
||||
*
|
||||
* @param int $time_limit How long the cleanup can run (in seconds).
|
||||
*/
|
||||
$time_limit = apply_filters( 'jetpack_connection_nonce_cleanup_runtime_limit', static::SCHEDULED_CLEANUP_TIME_LIMIT );
|
||||
|
||||
( new static() )->clean_all( time() - static::LIFETIME, $time_limit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the nonces.
|
||||
*
|
||||
* @param int $limit How many nonces to delete.
|
||||
* @param null|int $cutoff_timestamp All nonces added before this timestamp will be removed.
|
||||
*
|
||||
* @return int|false Number of removed nonces, or `false` if nothing to remove (or in case of a database error).
|
||||
*/
|
||||
public function delete( $limit = 10, $cutoff_timestamp = null ) {
|
||||
global $wpdb;
|
||||
|
||||
$ids = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_id FROM `{$wpdb->options}`"
|
||||
. " WHERE `option_name` >= 'jetpack_nonce_' AND `option_name` < %s"
|
||||
. ' LIMIT %d',
|
||||
'jetpack_nonce_' . $cutoff_timestamp,
|
||||
$limit
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! is_array( $ids ) ) {
|
||||
// There's an error and we can't proceed.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Removing zeroes in case AUTO_INCREMENT of the options table is broken, and all ID's are zeroes.
|
||||
$ids = array_filter( $ids );
|
||||
|
||||
if ( array() === $ids ) {
|
||||
// There's nothing to remove.
|
||||
return false;
|
||||
}
|
||||
|
||||
$ids_fill = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
|
||||
|
||||
$args = $ids;
|
||||
$args[] = 'jetpack_nonce_%';
|
||||
|
||||
// The Code Sniffer is unable to understand what's going on...
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
|
||||
return $wpdb->query( $wpdb->prepare( "DELETE FROM `{$wpdb->options}` WHERE `option_id` IN ( {$ids_fill} ) AND option_name LIKE %s", $args ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the cached nonces valid during the current request, therefore making them invalid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function invalidate_request_nonces() {
|
||||
static::$nonces_used_this_request = array();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Package_Version_Tracker class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* The Package_Version_Tracker class.
|
||||
*/
|
||||
class Package_Version_Tracker {
|
||||
|
||||
const PACKAGE_VERSION_OPTION = 'jetpack_package_versions';
|
||||
|
||||
/**
|
||||
* The cache key for storing a failed request to update remote package versions.
|
||||
* The caching logic is that when a failed request occurs, we cache it temporarily
|
||||
* with a set expiration time.
|
||||
* Only after the key has expired, we'll be able to repeat a remote request.
|
||||
* This also implies that the cached value is redundant, however we chose the datetime
|
||||
* of the failed request to avoid using booleans.
|
||||
*/
|
||||
const CACHED_FAILED_REQUEST_KEY = 'jetpack_failed_update_remote_package_versions';
|
||||
|
||||
/**
|
||||
* The min time difference in seconds for attempting to
|
||||
* update remote tracked package versions after a failed remote request.
|
||||
*/
|
||||
const CACHED_FAILED_REQUEST_EXPIRATION = 1 * HOUR_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Uses the jetpack_package_versions filter to obtain the package versions from packages that need
|
||||
* version tracking. If the package versions have changed, updates the option and notifies WPCOM.
|
||||
*/
|
||||
public function maybe_update_package_versions() {
|
||||
/**
|
||||
* Obtains the package versions.
|
||||
*
|
||||
* @since 1.30.2
|
||||
*
|
||||
* @param array An associative array of Jetpack package slugs and their corresponding versions as key/value pairs.
|
||||
*/
|
||||
$filter_versions = apply_filters( 'jetpack_package_versions', array() );
|
||||
|
||||
if ( ! is_array( $filter_versions ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$option_versions = get_option( self::PACKAGE_VERSION_OPTION, array() );
|
||||
|
||||
foreach ( $filter_versions as $package => $version ) {
|
||||
if ( ! is_string( $package ) || ! is_string( $version ) ) {
|
||||
unset( $filter_versions[ $package ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_array( $option_versions )
|
||||
|| count( array_diff_assoc( $filter_versions, $option_versions ) )
|
||||
|| count( array_diff_assoc( $option_versions, $filter_versions ) )
|
||||
) {
|
||||
$this->update_package_versions_option( $filter_versions );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the package versions:
|
||||
* - Sends the updated package versions to wpcom.
|
||||
* - Updates the 'jetpack_package_versions' option.
|
||||
*
|
||||
* @param array $package_versions The package versions.
|
||||
*/
|
||||
protected function update_package_versions_option( $package_versions ) {
|
||||
$connection = new Manager();
|
||||
if ( ! $connection->is_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$site_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$last_failed_attempt_within_hour = get_transient( self::CACHED_FAILED_REQUEST_KEY );
|
||||
|
||||
if ( $last_failed_attempt_within_hour ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = wp_json_encode(
|
||||
array(
|
||||
'package_versions' => $package_versions,
|
||||
)
|
||||
);
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/sites/%d/jetpack-package-versions', $site_id ),
|
||||
'2',
|
||||
array(
|
||||
'headers' => array( 'content-type' => 'application/json' ),
|
||||
'method' => 'POST',
|
||||
),
|
||||
$body,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||
update_option( self::PACKAGE_VERSION_OPTION, $package_versions );
|
||||
} else {
|
||||
set_transient( self::CACHED_FAILED_REQUEST_KEY, time(), self::CACHED_FAILED_REQUEST_EXPIRATION );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '1.60.1';
|
||||
|
||||
const PACKAGE_SLUG = 'connection';
|
||||
|
||||
/**
|
||||
* Adds the package slug and version to the package version tracker's data.
|
||||
*
|
||||
* @param array $package_versions The package version array.
|
||||
*
|
||||
* @return array The packge version array.
|
||||
*/
|
||||
public static function send_package_version_to_tracker( $package_versions ) {
|
||||
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
|
||||
return $package_versions;
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Storage for plugin connection information.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* The class serves a single purpose - to store the data which plugins use the connection, along with some auxiliary information.
|
||||
*/
|
||||
class Plugin_Storage {
|
||||
|
||||
const ACTIVE_PLUGINS_OPTION_NAME = 'jetpack_connection_active_plugins';
|
||||
|
||||
/**
|
||||
* Options where disabled plugins were stored
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
* @var string
|
||||
*/
|
||||
const PLUGINS_DISABLED_OPTION_NAME = 'jetpack_connection_disabled_plugins';
|
||||
|
||||
/**
|
||||
* Whether this class was configured for the first time or not.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $configured = false;
|
||||
|
||||
/**
|
||||
* Refresh list of connected plugins upon intialization.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $refresh_connected_plugins = false;
|
||||
|
||||
/**
|
||||
* Connected plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $plugins = array();
|
||||
|
||||
/**
|
||||
* The blog ID the storage is setup for.
|
||||
* The data will be refreshed if the blog ID changes.
|
||||
* Used for the multisite networks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $current_blog_id = null;
|
||||
|
||||
/**
|
||||
* Add or update the plugin information in the storage.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
* @param array $args Plugin arguments, optional.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function upsert( $slug, array $args = array() ) {
|
||||
self::$plugins[ $slug ] = $args;
|
||||
|
||||
// if plugin is not in the list of active plugins, refresh the list.
|
||||
if ( ! array_key_exists( $slug, (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) ) ) {
|
||||
self::$refresh_connected_plugins = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin information by slug.
|
||||
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
|
||||
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
|
||||
* so please make sure not to run the method too early in the code.
|
||||
*
|
||||
* @param string $slug The plugin slug.
|
||||
*
|
||||
* @return array|null|WP_Error
|
||||
*/
|
||||
public static function get_one( $slug ) {
|
||||
$plugins = self::get_all();
|
||||
|
||||
if ( $plugins instanceof WP_Error ) {
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
return empty( $plugins[ $slug ] ) ? null : $plugins[ $slug ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve info for all plugins that use the connection.
|
||||
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
|
||||
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
|
||||
* so please make sure not to run the method too early in the code.
|
||||
*
|
||||
* @since 1.39.0 deprecated the $connected_only argument.
|
||||
*
|
||||
* @param null $deprecated null plugins that were explicitly disconnected. Deprecated, there's no such a thing as disconnecting only specific plugins anymore.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function get_all( $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$maybe_error = self::ensure_configured();
|
||||
|
||||
if ( $maybe_error instanceof WP_Error ) {
|
||||
return $maybe_error;
|
||||
}
|
||||
|
||||
return self::$plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin connection info from Jetpack.
|
||||
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
|
||||
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
|
||||
* so please make sure not to run the method too early in the code.
|
||||
*
|
||||
* @param string $slug The plugin slug.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function delete( $slug ) {
|
||||
$maybe_error = self::ensure_configured();
|
||||
|
||||
if ( $maybe_error instanceof WP_Error ) {
|
||||
return $maybe_error;
|
||||
}
|
||||
|
||||
if ( array_key_exists( $slug, self::$plugins ) ) {
|
||||
unset( self::$plugins[ $slug ] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method makes sure that `Jetpack\Config` has finished, and it's now safe to retrieve the list of plugins.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
private static function ensure_configured() {
|
||||
if ( ! self::$configured ) {
|
||||
return new WP_Error( 'too_early', __( 'You cannot call this method until Jetpack Config is configured', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
if ( is_multisite() && get_current_blog_id() !== self::$current_blog_id ) {
|
||||
self::$plugins = (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() );
|
||||
self::$current_blog_id = get_current_blog_id();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once to configure this class after plugins_loaded.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function configure() {
|
||||
if ( self::$configured ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_multisite() ) {
|
||||
self::$current_blog_id = get_current_blog_id();
|
||||
}
|
||||
|
||||
// If a plugin was activated or deactivated.
|
||||
// self::$plugins is populated in Config::ensure_options_connection().
|
||||
$number_of_plugins_differ = count( self::$plugins ) !== count( (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
|
||||
|
||||
if ( $number_of_plugins_differ || true === self::$refresh_connected_plugins ) {
|
||||
self::update_active_plugins_option();
|
||||
}
|
||||
|
||||
self::$configured = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the active plugins option with current list of active plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_active_plugins_option() {
|
||||
// Note: Since this options is synced to wpcom, if you change its structure, you have to update the sanitizer at wpcom side.
|
||||
update_option( self::ACTIVE_PLUGINS_OPTION_NAME, self::$plugins );
|
||||
|
||||
if ( ! class_exists( 'Automattic\Jetpack\Sync\Settings' ) || ! \Automattic\Jetpack\Sync\Settings::is_sync_enabled() ) {
|
||||
self::update_active_plugins_wpcom_no_sync_fallback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin to the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function disable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin from the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function enable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all plugins that were disconnected by user.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_disabled_plugins() { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update active plugins option with current list of active plugins on WPCOM.
|
||||
* This is a fallback to ensure this option is always up to date on WPCOM in case
|
||||
* Sync is not present or disabled.
|
||||
*
|
||||
* @since 1.34.0
|
||||
*/
|
||||
private static function update_active_plugins_wpcom_no_sync_fallback() {
|
||||
$connection = new Manager();
|
||||
if ( ! $connection->is_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$site_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$body = wp_json_encode(
|
||||
array(
|
||||
'active_connected_plugins' => self::$plugins,
|
||||
)
|
||||
);
|
||||
|
||||
Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/sites/%d/jetpack-active-connected-plugins', $site_id ),
|
||||
'2',
|
||||
array(
|
||||
'headers' => array( 'content-type' => 'application/json' ),
|
||||
'method' => 'POST',
|
||||
),
|
||||
$body,
|
||||
'wpcom'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin connection management class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* Plugin connection management class.
|
||||
* The class represents a single plugin that uses Jetpack connection.
|
||||
* Its functionality has been pretty simplistic so far: add to the storage (`Plugin_Storage`), remove it from there,
|
||||
* and determine whether it's the last active connection. As the component grows, there'll be more functionality added.
|
||||
*/
|
||||
class Plugin {
|
||||
|
||||
/**
|
||||
* List of the keys allowed as arguments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $arguments_whitelist = array(
|
||||
'url_info',
|
||||
);
|
||||
|
||||
/**
|
||||
* Plugin slug.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* Initialize the plugin manager.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*/
|
||||
public function __construct( $slug ) {
|
||||
$this->slug = $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_slug() {
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin connection info into Jetpack.
|
||||
*
|
||||
* @param string $name Plugin name, required.
|
||||
* @param array $args Plugin arguments, optional.
|
||||
*
|
||||
* @return $this
|
||||
* @see $this->arguments_whitelist
|
||||
*/
|
||||
public function add( $name, array $args = array() ) {
|
||||
$args = compact( 'name' ) + array_intersect_key( $args, array_flip( $this->arguments_whitelist ) );
|
||||
|
||||
Plugin_Storage::upsert( $this->slug, $args );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin connection info from Jetpack.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function remove() {
|
||||
Plugin_Storage::delete( $this->slug );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this plugin connection is the only one active at the moment, if any.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_only() {
|
||||
$plugins = Plugin_Storage::get_all();
|
||||
|
||||
return ! $plugins || ( array_key_exists( $this->slug, $plugins ) && 1 === count( $plugins ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin to the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin from the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this plugin is allowed to use the connection.
|
||||
*
|
||||
* @deprecated since 11.0
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection Rest Authentication file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* The Jetpack Connection Rest Authentication class.
|
||||
*/
|
||||
class Rest_Authentication {
|
||||
|
||||
/**
|
||||
* The rest authentication status.
|
||||
*
|
||||
* @since 1.17.0
|
||||
* @var boolean
|
||||
*/
|
||||
private $rest_authentication_status = null;
|
||||
|
||||
/**
|
||||
* The rest authentication type.
|
||||
* Can be either 'user' or 'blog' depending on whether the request
|
||||
* is signed with a user or a blog token.
|
||||
*
|
||||
* @since 1.29.0
|
||||
* @var string
|
||||
*/
|
||||
private $rest_authentication_type = null;
|
||||
|
||||
/**
|
||||
* The Manager object.
|
||||
*
|
||||
* @since 1.17.0
|
||||
* @var Object
|
||||
*/
|
||||
private $connection_manager = null;
|
||||
|
||||
/**
|
||||
* Holds the singleton instance of this class
|
||||
*
|
||||
* @since 1.17.0
|
||||
* @var Object
|
||||
*/
|
||||
private static $instance = false;
|
||||
|
||||
/**
|
||||
* Flag used to avoid determine_current_user filter to enter an infinite loop
|
||||
*
|
||||
* @since 1.26.0
|
||||
* @var boolean
|
||||
*/
|
||||
private $doing_determine_current_user_filter = false;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*/
|
||||
private function __construct() {
|
||||
$this->connection_manager = new Manager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the single instance of this class.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
|
||||
add_filter( 'determine_current_user', array( self::$instance, 'wp_rest_authenticate' ) );
|
||||
add_filter( 'rest_authentication_errors', array( self::$instance, 'wp_rest_authentication_errors' ) );
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates requests from Jetpack server to WP REST API endpoints.
|
||||
* Uses the existing XMLRPC request signing implementation.
|
||||
*
|
||||
* @param int|bool $user User ID if one has been determined, false otherwise.
|
||||
*
|
||||
* @return int|null The user id or null if the request was authenticated via blog token, or not authenticated at all.
|
||||
*/
|
||||
public function wp_rest_authenticate( $user ) {
|
||||
if ( $this->doing_determine_current_user_filter ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$this->doing_determine_current_user_filter = true;
|
||||
|
||||
try {
|
||||
if ( ! empty( $user ) ) {
|
||||
// Another authentication method is in effect.
|
||||
return $user;
|
||||
}
|
||||
|
||||
add_filter(
|
||||
'jetpack_constant_default_value',
|
||||
__NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['_for'] ) || 'jetpack' !== $_GET['_for'] ) {
|
||||
// Nothing to do for this authentication method.
|
||||
return null;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
|
||||
// Nothing to do for this authentication method.
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
'rest_invalid_request',
|
||||
__( 'The request method is missing.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only support specific request parameters that have been tested and
|
||||
// are known to work with signature verification. A different method
|
||||
// can be passed to the WP REST API via the '?_method=' parameter if
|
||||
// needed.
|
||||
if ( 'GET' !== $_SERVER['REQUEST_METHOD'] && 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
'rest_invalid_request',
|
||||
__( 'This request method is not supported.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] && ! empty( file_get_contents( 'php://input' ) ) ) {
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
'rest_invalid_request',
|
||||
__( 'This request method does not support body parameters.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
$verified = $this->connection_manager->verify_xml_rpc_signature();
|
||||
|
||||
if (
|
||||
$verified &&
|
||||
isset( $verified['type'] ) &&
|
||||
'blog' === $verified['type']
|
||||
) {
|
||||
// Site-level authentication successful.
|
||||
$this->rest_authentication_status = true;
|
||||
$this->rest_authentication_type = 'blog';
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
$verified &&
|
||||
isset( $verified['type'] ) &&
|
||||
'user' === $verified['type'] &&
|
||||
! empty( $verified['user_id'] )
|
||||
) {
|
||||
// User-level authentication successful.
|
||||
$this->rest_authentication_status = true;
|
||||
$this->rest_authentication_type = 'user';
|
||||
return $verified['user_id'];
|
||||
}
|
||||
|
||||
// Something else went wrong. Probably a signature error.
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
'rest_invalid_signature',
|
||||
__( 'The request is not signed correctly.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
return null;
|
||||
} finally {
|
||||
$this->doing_determine_current_user_filter = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report authentication status to the WP REST API.
|
||||
*
|
||||
* @param WP_Error|mixed $value Error from another authentication handler, null if we should handle it, or another value if not.
|
||||
* @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
|
||||
*/
|
||||
public function wp_rest_authentication_errors( $value ) {
|
||||
if ( null !== $value ) {
|
||||
return $value;
|
||||
}
|
||||
return $this->rest_authentication_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the saved authentication state in between testing requests.
|
||||
*/
|
||||
public function reset_saved_auth_state() {
|
||||
$this->rest_authentication_status = null;
|
||||
$this->connection_manager->reset_saved_auth_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request was signed with a blog token.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @return bool True if the request was signed with a valid blog token, false otherwise.
|
||||
*/
|
||||
public static function is_signed_with_blog_token() {
|
||||
$instance = self::init();
|
||||
|
||||
return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type;
|
||||
}
|
||||
}
|
||||
@@ -1,850 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the Connection REST API endpoints.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Jetpack_XMLRPC_Server;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Registers the REST routes for Connections.
|
||||
*/
|
||||
class REST_Connector {
|
||||
/**
|
||||
* The Connection Manager.
|
||||
*
|
||||
* @var Manager
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* This property stores the localized "Insufficient Permissions" error message.
|
||||
*
|
||||
* @var string Generic error message when user is not allowed to perform an action.
|
||||
*/
|
||||
private static $user_permissions_error_msg;
|
||||
|
||||
const JETPACK__DEBUGGER_PUBLIC_KEY = "\r\n" . '-----BEGIN PUBLIC KEY-----' . "\r\n"
|
||||
. 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+uLLVoxGCY71LS6KFc6' . "\r\n"
|
||||
. '1UnF6QGBAsi5XF8ty9kR3/voqfOkpW+gRerM2Kyjy6DPCOmzhZj7BFGtxSV2ZoMX' . "\r\n"
|
||||
. '9ZwWxzXhl/Q/6k8jg8BoY1QL6L2K76icXJu80b+RDIqvOfJruaAeBg1Q9NyeYqLY' . "\r\n"
|
||||
. 'lEVzN2vIwcFYl+MrP/g6Bc2co7Jcbli+tpNIxg4Z+Hnhbs7OJ3STQLmEryLpAxQO' . "\r\n"
|
||||
. 'q8cbhQkMx+FyQhxzSwtXYI/ClCUmTnzcKk7SgGvEjoKGAmngILiVuEJ4bm7Q1yok' . "\r\n"
|
||||
. 'xl9+wcfW6JAituNhml9dlHCWnn9D3+j8pxStHihKy2gVMwiFRjLEeD8K/7JVGkb/' . "\r\n"
|
||||
. 'EwIDAQAB' . "\r\n"
|
||||
. '-----END PUBLIC KEY-----' . "\r\n";
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Manager $connection The Connection Manager.
|
||||
*/
|
||||
public function __construct( Manager $connection ) {
|
||||
$this->connection = $connection;
|
||||
|
||||
self::$user_permissions_error_msg = esc_html__(
|
||||
'You do not have the correct user permissions to perform this action.
|
||||
Please contact your site admin if you think this is a mistake.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
$jp_version = Constants::get_constant( 'JETPACK__VERSION' );
|
||||
|
||||
if ( ! $this->connection->has_connected_owner() ) {
|
||||
// Register a site.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/verify_registration',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'verify_registration' ),
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Authorize a remote user.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/remote_authorize',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::remote_authorize',
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
|
||||
// Get current connection status of Jetpack.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::connection_status',
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
|
||||
// Disconnect site.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::disconnect_site',
|
||||
'permission_callback' => __CLASS__ . '::disconnect_site_permission_check',
|
||||
'args' => array(
|
||||
'isActive' => array(
|
||||
'description' => __( 'Set to false will trigger the site to disconnect.', 'jetpack-connection' ),
|
||||
'validate_callback' => function ( $value ) {
|
||||
if ( false !== $value ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__( 'The isActive argument should be set to false.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// We are only registering this route if Jetpack-the-plugin is not active or it's version is ge 10.0-alpha.
|
||||
// The reason for doing so is to avoid conflicts between the Connection package and
|
||||
// older versions of Jetpack, registering the same route twice.
|
||||
if ( empty( $jp_version ) || version_compare( $jp_version, '10.0-alpha', '>=' ) ) {
|
||||
// Get current user connection data.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/data',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_user_connection_data',
|
||||
'permission_callback' => __CLASS__ . '::user_connection_data_permission_check',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Get list of plugins that use the Jetpack connection.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/plugins',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( __CLASS__, 'get_connection_plugins' ),
|
||||
'permission_callback' => __CLASS__ . '::connection_plugins_permission_check',
|
||||
)
|
||||
);
|
||||
|
||||
// Full or partial reconnect in case of connection issues.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/reconnect',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'connection_reconnect' ),
|
||||
'permission_callback' => __CLASS__ . '::jetpack_reconnect_permission_check',
|
||||
)
|
||||
);
|
||||
|
||||
// Register the site (get `blog_token`).
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/register',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'connection_register' ),
|
||||
'permission_callback' => __CLASS__ . '::jetpack_register_permission_check',
|
||||
'args' => array(
|
||||
'from' => array(
|
||||
'description' => __( 'Indicates where the registration action was triggered for tracking/segmentation purposes', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'registration_nonce' => array(
|
||||
'description' => __( 'The registration nonce', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'plugin_slug' => array(
|
||||
'description' => __( 'Indicates from what plugin the request is coming from', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Get authorization URL.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/authorize_url',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'connection_authorize_url' ),
|
||||
'permission_callback' => __CLASS__ . '::user_connection_data_permission_check',
|
||||
'args' => array(
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/user-token',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( static::class, 'update_user_token' ),
|
||||
'permission_callback' => array( static::class, 'update_user_token_permission_check' ),
|
||||
'args' => array(
|
||||
'user_token' => array(
|
||||
'description' => __( 'New user token', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'is_connection_owner' => array(
|
||||
'description' => __( 'Is connection owner', 'jetpack-connection' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Set the connection owner.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/owner',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( static::class, 'set_connection_owner' ),
|
||||
'permission_callback' => array( static::class, 'set_connection_owner_permission_check' ),
|
||||
'args' => array(
|
||||
'owner' => array(
|
||||
'description' => __( 'New owner', 'jetpack-connection' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles verification that a site is registered.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return string|WP_Error
|
||||
*/
|
||||
public function verify_registration( WP_REST_Request $request ) {
|
||||
$registration_data = array( $request['secret_1'], $request['state'] );
|
||||
|
||||
return $this->connection->handle_registration( $registration_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles verification that a site is registered
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array|wp-error
|
||||
*/
|
||||
public static function remote_authorize( $request ) {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->remote_authorize( $request );
|
||||
|
||||
if ( is_a( $result, 'IXR_Error' ) ) {
|
||||
$result = new WP_Error( $result->code, $result->message );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection status for this Jetpack site.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 4.3.0
|
||||
*
|
||||
* @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
|
||||
*
|
||||
* @return WP_REST_Response|array Connection information.
|
||||
*/
|
||||
public static function connection_status( $rest_response = true ) {
|
||||
$status = new Status();
|
||||
$connection = new Manager();
|
||||
|
||||
$connection_status = array(
|
||||
'isActive' => $connection->has_connected_owner(), // TODO deprecate this.
|
||||
'isStaging' => $status->is_staging_site(),
|
||||
'isRegistered' => $connection->is_connected(),
|
||||
'isUserConnected' => $connection->is_user_connected(),
|
||||
'hasConnectedOwner' => $connection->has_connected_owner(),
|
||||
'offlineMode' => array(
|
||||
'isActive' => $status->is_offline_mode(),
|
||||
'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
|
||||
'url' => $status->is_local_site(),
|
||||
/** This filter is documented in packages/status/src/class-status.php */
|
||||
'filter' => ( apply_filters( 'jetpack_development_mode', false ) || apply_filters( 'jetpack_offline_mode', false ) ), // jetpack_development_mode is deprecated.
|
||||
'wpLocalConstant' => defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV,
|
||||
),
|
||||
'isPublic' => '1' == get_option( 'blog_public' ), // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the connection status data.
|
||||
*
|
||||
* @since 1.25.0
|
||||
*
|
||||
* @param array An array containing the connection status data.
|
||||
*/
|
||||
$connection_status = apply_filters( 'jetpack_connection_status', $connection_status );
|
||||
|
||||
if ( $rest_response ) {
|
||||
return rest_ensure_response(
|
||||
$connection_status
|
||||
);
|
||||
} else {
|
||||
return $connection_status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugins connected to the Jetpack.
|
||||
*
|
||||
* @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
|
||||
*
|
||||
* @since 1.13.1
|
||||
* @since 1.38.0 Added $rest_response param.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
|
||||
*/
|
||||
public static function get_connection_plugins( $rest_response = true ) {
|
||||
$plugins = ( new Manager() )->get_connected_plugins();
|
||||
|
||||
if ( is_wp_error( $plugins ) ) {
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
array_walk(
|
||||
$plugins,
|
||||
function ( &$data, $slug ) {
|
||||
$data['slug'] = $slug;
|
||||
}
|
||||
);
|
||||
|
||||
if ( $rest_response ) {
|
||||
return rest_ensure_response( array_values( $plugins ) );
|
||||
}
|
||||
|
||||
return array_values( $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user can view Jetpack admin page and can activate plugins.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @return bool|WP_Error Whether user has the capability 'activate_plugins'.
|
||||
*/
|
||||
public static function activate_plugins_permission_check() {
|
||||
if ( current_user_can( 'activate_plugins' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check for the connection_plugins endpoint
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function connection_plugins_permission_check() {
|
||||
if ( true === static::activate_plugins_permission_check() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( true === static::is_request_signed_by_jetpack_debugger() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check for the disconnect site endpoint.
|
||||
*
|
||||
* @since 1.30.1
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to disconnect the site.
|
||||
*/
|
||||
public static function disconnect_site_permission_check() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'invalid_user_permission_jetpack_disconnect',
|
||||
self::get_user_permissions_error_msg(),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
|
||||
* Information about the master/primary user.
|
||||
* Information about the current user.
|
||||
*
|
||||
* @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
|
||||
*
|
||||
* @since 1.30.1
|
||||
*
|
||||
* @return \WP_REST_Response|array
|
||||
*/
|
||||
public static function get_user_connection_data( $rest_response = true ) {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$connection = new Manager();
|
||||
|
||||
$current_user = wp_get_current_user();
|
||||
$connection_owner = $connection->get_connection_owner();
|
||||
|
||||
$owner_display_name = false === $connection_owner ? null : $connection_owner->display_name;
|
||||
|
||||
$is_user_connected = $connection->is_user_connected();
|
||||
$is_master_user = false === $connection_owner ? false : ( $current_user->ID === $connection_owner->ID );
|
||||
$wpcom_user_data = $connection->get_connected_user_data();
|
||||
|
||||
// Add connected user gravatar to the returned wpcom_user_data.
|
||||
// Probably we shouldn't do this when $wpcom_user_data is false, but we have been since 2016 so
|
||||
// clients probably expect that by now.
|
||||
if ( false === $wpcom_user_data ) {
|
||||
$wpcom_user_data = array();
|
||||
}
|
||||
$wpcom_user_data['avatar'] = ( ! empty( $wpcom_user_data['email'] ) ?
|
||||
get_avatar_url(
|
||||
$wpcom_user_data['email'],
|
||||
array(
|
||||
'size' => 64,
|
||||
'default' => 'mysteryman',
|
||||
)
|
||||
)
|
||||
: false );
|
||||
|
||||
$current_user_connection_data = array(
|
||||
'isConnected' => $is_user_connected,
|
||||
'isMaster' => $is_master_user,
|
||||
'username' => $current_user->user_login,
|
||||
'id' => $current_user->ID,
|
||||
'blogId' => $blog_id,
|
||||
'wpcomUser' => $wpcom_user_data,
|
||||
'gravatar' => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ),
|
||||
'permissions' => array(
|
||||
'connect' => current_user_can( 'jetpack_connect' ),
|
||||
'connect_user' => current_user_can( 'jetpack_connect_user' ),
|
||||
'disconnect' => current_user_can( 'jetpack_disconnect' ),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the current user connection data.
|
||||
*
|
||||
* @since 1.30.1
|
||||
*
|
||||
* @param array An array containing the current user connection data.
|
||||
*/
|
||||
$current_user_connection_data = apply_filters( 'jetpack_current_user_connection_data', $current_user_connection_data );
|
||||
|
||||
$response = array(
|
||||
'currentUser' => $current_user_connection_data,
|
||||
'connectionOwner' => $owner_display_name,
|
||||
);
|
||||
|
||||
if ( $rest_response ) {
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check for the connection/data endpoint
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function user_connection_data_permission_check() {
|
||||
if ( current_user_can( 'jetpack_connect_user' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'invalid_user_permission_user_connection_data',
|
||||
self::get_user_permissions_error_msg(),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the request was signed with the Jetpack Debugger key
|
||||
*
|
||||
* @param string|null $pub_key The public key used to verify the signature. Default is the Jetpack Debugger key. This is used for testing purposes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_request_signed_by_jetpack_debugger( $pub_key = null ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['signature'] ) || ! isset( $_GET['timestamp'] ) || ! isset( $_GET['url'] ) || ! isset( $_GET['rest_route'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// signature timestamp must be within 5min of current time.
|
||||
if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signature = base64_decode( filter_var( wp_unslash( $_GET['signature'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
|
||||
$signature_data = wp_json_encode(
|
||||
array(
|
||||
'rest_route' => filter_var( wp_unslash( $_GET['rest_route'] ) ),
|
||||
'timestamp' => (int) $_GET['timestamp'],
|
||||
'url' => filter_var( wp_unslash( $_GET['url'] ) ),
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
! function_exists( 'openssl_verify' )
|
||||
|| 1 !== openssl_verify(
|
||||
$signature_data,
|
||||
$signature,
|
||||
$pub_key ? $pub_key : static::JETPACK__DEBUGGER_PUBLIC_KEY
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user is allowed to disconnect Jetpack.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @return bool|WP_Error Whether user has the capability 'jetpack_disconnect'.
|
||||
*/
|
||||
public static function jetpack_reconnect_permission_check() {
|
||||
if ( current_user_can( 'jetpack_reconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns generic error message when user is not allowed to perform an action.
|
||||
*
|
||||
* @return string The error message.
|
||||
*/
|
||||
public static function get_user_permissions_error_msg() {
|
||||
return self::$user_permissions_error_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint tried to partially or fully reconnect the website to WP.com.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_reconnect() {
|
||||
$response = array();
|
||||
|
||||
$next = null;
|
||||
|
||||
$result = $this->connection->restore();
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$response = $result;
|
||||
} elseif ( is_string( $result ) ) {
|
||||
$next = $result;
|
||||
} else {
|
||||
$next = true === $result ? 'completed' : 'failed';
|
||||
}
|
||||
|
||||
switch ( $next ) {
|
||||
case 'authorize':
|
||||
$response['status'] = 'in_progress';
|
||||
$response['authorizeUrl'] = $this->connection->get_authorization_url();
|
||||
break;
|
||||
case 'completed':
|
||||
$response['status'] = 'completed';
|
||||
/**
|
||||
* Action fired when reconnection has completed successfully.
|
||||
*
|
||||
* @since 1.18.1
|
||||
*/
|
||||
do_action( 'jetpack_reconnection_completed' );
|
||||
break;
|
||||
case 'failed':
|
||||
$response = new WP_Error( 'Reconnect failed' );
|
||||
break;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user is allowed to connect Jetpack.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*
|
||||
* @return bool|WP_Error Whether user has the capability 'jetpack_connect'.
|
||||
*/
|
||||
public static function jetpack_register_permission_check() {
|
||||
if ( current_user_can( 'jetpack_connect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_jetpack_connect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint tried to partially or fully reconnect the website to WP.com.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 7.7.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_register( $request ) {
|
||||
if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
|
||||
return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack-connection' ), array( 'status' => 403 ) );
|
||||
}
|
||||
|
||||
if ( isset( $request['from'] ) ) {
|
||||
$this->connection->add_register_request_param( 'from', (string) $request['from'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $request['plugin_slug'] ) ) {
|
||||
// If `plugin_slug` matches a plugin using the connection, let's inform the plugin that is establishing the connection.
|
||||
$connected_plugin = Plugin_Storage::get_one( (string) $request['plugin_slug'] );
|
||||
if ( ! is_wp_error( $connected_plugin ) && ! empty( $connected_plugin ) ) {
|
||||
$this->connection->set_plugin_instance( new Plugin( (string) $request['plugin_slug'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->connection->try_registration();
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
|
||||
|
||||
if ( class_exists( 'Jetpack' ) ) {
|
||||
$authorize_url = \Jetpack::build_authorize_url( $redirect_uri );
|
||||
} else {
|
||||
$authorize_url = $this->connection->get_authorization_url( null, $redirect_uri );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the response of jetpack/v4/connection/register endpoint
|
||||
*
|
||||
* @param array $response Array response
|
||||
* @since 1.27.0
|
||||
*/
|
||||
$response_body = apply_filters(
|
||||
'jetpack_register_site_rest_response',
|
||||
array()
|
||||
);
|
||||
|
||||
// We manipulate the alternate URLs after the filter is applied, so they can not be overwritten.
|
||||
$response_body['authorizeUrl'] = $authorize_url;
|
||||
if ( ! empty( $response_body['alternateAuthorizeUrl'] ) ) {
|
||||
$response_body['alternateAuthorizeUrl'] = Redirect::get_url( $response_body['alternateAuthorizeUrl'] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response_body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorization URL.
|
||||
*
|
||||
* @since 1.27.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_authorize_url( $request ) {
|
||||
$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
|
||||
$authorize_url = $this->connection->get_authorization_url( null, $redirect_uri );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'authorizeUrl' => $authorize_url,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint tried to partially or fully reconnect the website to WP.com.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public static function update_user_token( $request ) {
|
||||
$token_parts = explode( '.', $request['user_token'] );
|
||||
|
||||
if ( count( $token_parts ) !== 3 || ! (int) $token_parts[2] || ! ctype_digit( $token_parts[2] ) ) {
|
||||
return new WP_Error( 'invalid_argument_user_token', esc_html__( 'Invalid user token is provided', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$user_id = (int) $token_parts[2];
|
||||
|
||||
if ( false === get_userdata( $user_id ) ) {
|
||||
return new WP_Error( 'invalid_argument_user_id', esc_html__( 'Invalid user id is provided', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$connection = new Manager();
|
||||
|
||||
if ( ! $connection->is_connected() ) {
|
||||
return new WP_Error( 'site_not_connected', esc_html__( 'Site is not connected', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$is_connection_owner = isset( $request['is_connection_owner'] )
|
||||
? (bool) $request['is_connection_owner']
|
||||
: ( new Manager() )->get_connection_owner_id() === $user_id;
|
||||
|
||||
( new Tokens() )->update_user_token( $user_id, $request['user_token'], $is_connection_owner );
|
||||
|
||||
/**
|
||||
* Fires when the user token gets successfully replaced.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $token New user token.
|
||||
*/
|
||||
do_action( 'jetpack_updated_user_token', $user_id, $request['user_token'] );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects Jetpack from the WordPress.com Servers
|
||||
*
|
||||
* @since 1.30.1
|
||||
*
|
||||
* @return bool|WP_Error True if Jetpack successfully disconnected.
|
||||
*/
|
||||
public static function disconnect_site() {
|
||||
$connection = new Manager();
|
||||
|
||||
if ( $connection->is_connected() ) {
|
||||
$connection->disconnect_site();
|
||||
return rest_ensure_response( array( 'code' => 'success' ) );
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'disconnect_failed',
|
||||
esc_html__( 'Failed to disconnect the site as it appears already disconnected.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the API client is allowed to replace user token.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @return bool|WP_Error.
|
||||
*/
|
||||
public static function update_user_token_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_update_user_token', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the connection owner.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public static function set_connection_owner( $request ) {
|
||||
$new_owner_id = $request['owner'];
|
||||
|
||||
$owner_set = ( new Manager() )->update_connection_owner( $new_owner_id );
|
||||
|
||||
if ( is_wp_error( $owner_set ) ) {
|
||||
return $owner_set;
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user has permission to change the master user.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 6.2.0
|
||||
* @since-jetpack 7.7.0 Update so that any user with jetpack_disconnect privs can set owner.
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to change master user.
|
||||
*/
|
||||
public static function set_connection_owner_permission_check() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_set_connection_owner', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection Secrets class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* The Jetpack Connection Secrets class that is used to manage secrets.
|
||||
*/
|
||||
class Secrets {
|
||||
|
||||
const SECRETS_MISSING = 'secrets_missing';
|
||||
const SECRETS_EXPIRED = 'secrets_expired';
|
||||
const LEGACY_SECRETS_OPTION_NAME = 'jetpack_secrets';
|
||||
|
||||
/**
|
||||
* Deletes all connection secrets from the local Jetpack site.
|
||||
*/
|
||||
public function delete_all() {
|
||||
Jetpack_Options::delete_raw_option( 'jetpack_secrets' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the wp_generate_password function with the required parameters. This is the
|
||||
* default implementation of the secret callable, can be overridden using the
|
||||
* jetpack_connection_secret_generator filter.
|
||||
*
|
||||
* @return String $secret value.
|
||||
*/
|
||||
private function secret_callable_method() {
|
||||
$secret = wp_generate_password( 32, false );
|
||||
|
||||
// Some sites may hook into the random_password filter and make the password shorter, let's make sure our secret has the required length.
|
||||
$attempts = 1;
|
||||
$secret_length = strlen( $secret );
|
||||
while ( $secret_length < 32 && $attempts < 32 ) {
|
||||
++$attempts;
|
||||
$secret .= wp_generate_password( 32, false );
|
||||
$secret_length = strlen( $secret );
|
||||
}
|
||||
return (string) substr( $secret, 0, 32 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates two secret tokens and the end of life timestamp for them.
|
||||
*
|
||||
* @param String $action The action name.
|
||||
* @param Integer|bool $user_id The user identifier. Defaults to `false`.
|
||||
* @param Integer $exp Expiration time in seconds.
|
||||
*/
|
||||
public function generate( $action, $user_id = false, $exp = 600 ) {
|
||||
if ( false === $user_id ) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
|
||||
$callable = apply_filters( 'jetpack_connection_secret_generator', array( static::class, 'secret_callable_method' ) );
|
||||
|
||||
$secrets = Jetpack_Options::get_raw_option(
|
||||
self::LEGACY_SECRETS_OPTION_NAME,
|
||||
array()
|
||||
);
|
||||
|
||||
$secret_name = 'jetpack_' . $action . '_' . $user_id;
|
||||
|
||||
if (
|
||||
isset( $secrets[ $secret_name ] ) &&
|
||||
$secrets[ $secret_name ]['exp'] > time()
|
||||
) {
|
||||
return $secrets[ $secret_name ];
|
||||
}
|
||||
|
||||
$secret_value = array(
|
||||
'secret_1' => call_user_func( $callable ),
|
||||
'secret_2' => call_user_func( $callable ),
|
||||
'exp' => time() + $exp,
|
||||
);
|
||||
|
||||
$secrets[ $secret_name ] = $secret_value;
|
||||
|
||||
$res = Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
|
||||
return $res ? $secrets[ $secret_name ] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns two secret tokens and the end of life timestamp for them.
|
||||
*
|
||||
* @param String $action The action name.
|
||||
* @param Integer $user_id The user identifier.
|
||||
* @return string|array an array of secrets or an error string.
|
||||
*/
|
||||
public function get( $action, $user_id ) {
|
||||
$secret_name = 'jetpack_' . $action . '_' . $user_id;
|
||||
$secrets = Jetpack_Options::get_raw_option(
|
||||
self::LEGACY_SECRETS_OPTION_NAME,
|
||||
array()
|
||||
);
|
||||
|
||||
if ( ! isset( $secrets[ $secret_name ] ) ) {
|
||||
return self::SECRETS_MISSING;
|
||||
}
|
||||
|
||||
if ( $secrets[ $secret_name ]['exp'] < time() ) {
|
||||
$this->delete( $action, $user_id );
|
||||
return self::SECRETS_EXPIRED;
|
||||
}
|
||||
|
||||
return $secrets[ $secret_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes secret tokens in case they, for example, have expired.
|
||||
*
|
||||
* @param String $action The action name.
|
||||
* @param Integer $user_id The user identifier.
|
||||
*/
|
||||
public function delete( $action, $user_id ) {
|
||||
$secret_name = 'jetpack_' . $action . '_' . $user_id;
|
||||
$secrets = Jetpack_Options::get_raw_option(
|
||||
self::LEGACY_SECRETS_OPTION_NAME,
|
||||
array()
|
||||
);
|
||||
if ( isset( $secrets[ $secret_name ] ) ) {
|
||||
unset( $secrets[ $secret_name ] );
|
||||
Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a Previously Generated Secret.
|
||||
*
|
||||
* @param string $action The type of secret to verify.
|
||||
* @param string $secret_1 The secret string to compare to what is stored.
|
||||
* @param int $user_id The user ID of the owner of the secret.
|
||||
* @return WP_Error|string WP_Error on failure, secret_2 on success.
|
||||
*/
|
||||
public function verify( $action, $secret_1, $user_id ) {
|
||||
$allowed_actions = array( 'register', 'authorize', 'publicize' );
|
||||
if ( ! in_array( $action, $allowed_actions, true ) ) {
|
||||
return new WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
|
||||
}
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
|
||||
/**
|
||||
* We've begun verifying the previously generated secret.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 7.5.0
|
||||
*
|
||||
* @param string $action The type of secret to verify.
|
||||
* @param \WP_User $user The user object.
|
||||
*/
|
||||
do_action( 'jetpack_verify_secrets_begin', $action, $user );
|
||||
|
||||
$return_error = function ( WP_Error $error ) use ( $action, $user ) {
|
||||
/**
|
||||
* Verifying of the previously generated secret has failed.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 7.5.0
|
||||
*
|
||||
* @param string $action The type of secret to verify.
|
||||
* @param \WP_User $user The user object.
|
||||
* @param WP_Error $error The error object.
|
||||
*/
|
||||
do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
|
||||
|
||||
return $error;
|
||||
};
|
||||
|
||||
$stored_secrets = $this->get( $action, $user_id );
|
||||
$this->delete( $action, $user_id );
|
||||
|
||||
$error = null;
|
||||
if ( empty( $secret_1 ) ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secret_1_missing',
|
||||
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
|
||||
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack-connection' ), 'secret_1' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( ! is_string( $secret_1 ) ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secret_1_malformed',
|
||||
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
|
||||
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack-connection' ), 'secret_1' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( empty( $user_id ) ) {
|
||||
// $user_id is passed around during registration as "state".
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'state_missing',
|
||||
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
|
||||
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack-connection' ), 'state' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( ! ctype_digit( (string) $user_id ) ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'state_malformed',
|
||||
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
|
||||
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack-connection' ), 'state' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( self::SECRETS_MISSING === $stored_secrets ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secrets_missing',
|
||||
__( 'Verification secrets not found', 'jetpack-connection' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( self::SECRETS_EXPIRED === $stored_secrets ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secrets_expired',
|
||||
__( 'Verification took too long', 'jetpack-connection' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( ! $stored_secrets ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secrets_empty',
|
||||
__( 'Verification secrets are empty', 'jetpack-connection' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( is_wp_error( $stored_secrets ) ) {
|
||||
$stored_secrets->add_data( 400 );
|
||||
$error = $return_error( $stored_secrets );
|
||||
} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secrets_incomplete',
|
||||
__( 'Verification secrets are incomplete', 'jetpack-connection' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
|
||||
$error = $return_error(
|
||||
new WP_Error(
|
||||
'verify_secrets_mismatch',
|
||||
__( 'Secret mismatch', 'jetpack-connection' ),
|
||||
400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Something went wrong during the checks, returning the error.
|
||||
if ( ! empty( $error ) ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've succeeded at verifying the previously generated secret.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 7.5.0
|
||||
*
|
||||
* @param string $action The type of secret to verify.
|
||||
* @param \WP_User $user The user object.
|
||||
*/
|
||||
do_action( 'jetpack_verify_secrets_success', $action, $user );
|
||||
|
||||
return $stored_secrets['secret_2'];
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Server_Sandbox class.
|
||||
*
|
||||
* This feature is only useful for Automattic developers.
|
||||
* It configures Jetpack to talk to staging/sandbox servers
|
||||
* on WordPress.com instead of production servers.
|
||||
*
|
||||
* @package automattic/jetpack-sandbox
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* The Server_Sandbox class.
|
||||
*/
|
||||
class Server_Sandbox {
|
||||
|
||||
/**
|
||||
* Sets up the action hooks for the server sandbox.
|
||||
*/
|
||||
public function init() {
|
||||
if ( did_action( 'jetpack_server_sandbox_init' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'requests-requests.before_request', array( $this, 'server_sandbox' ), 10, 4 );
|
||||
add_action( 'admin_bar_menu', array( $this, 'admin_bar_add_sandbox_item' ), 999 );
|
||||
|
||||
/**
|
||||
* Fires when the server sandbox is initialized. This action is used to ensure that
|
||||
* the server sandbox action hooks are set up only once.
|
||||
*
|
||||
* @since 1.30.7
|
||||
*/
|
||||
do_action( 'jetpack_server_sandbox_init' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the new url and host values.
|
||||
*
|
||||
* @param string $sandbox Sandbox domain.
|
||||
* @param string $url URL of request about to be made.
|
||||
* @param array $headers Headers of request about to be made.
|
||||
* @param string $data The body of request about to be made.
|
||||
* @param string $method The method of request about to be made.
|
||||
*
|
||||
* @return array [ 'url' => new URL, 'host' => new Host, 'new_signature => New signature if url was changed ]
|
||||
*/
|
||||
public function server_sandbox_request_parameters( $sandbox, $url, $headers, $data = null, $method = 'GET' ) {
|
||||
$host = '';
|
||||
$new_signature = '';
|
||||
|
||||
if ( ! is_string( $sandbox ) || ! is_string( $url ) ) {
|
||||
return array(
|
||||
'url' => $url,
|
||||
'host' => $host,
|
||||
'new_signature' => $new_signature,
|
||||
);
|
||||
}
|
||||
|
||||
$url_host = wp_parse_url( $url, PHP_URL_HOST );
|
||||
|
||||
switch ( $url_host ) {
|
||||
case 'public-api.wordpress.com':
|
||||
case 'jetpack.wordpress.com':
|
||||
case 'jetpack.com':
|
||||
case 'dashboard.wordpress.com':
|
||||
$host = isset( $headers['Host'] ) ? $headers['Host'] : $url_host;
|
||||
$original_url = $url;
|
||||
$url = preg_replace(
|
||||
'@^(https?://)' . preg_quote( $url_host, '@' ) . '(?=[/?#].*|$)@',
|
||||
'${1}' . $sandbox,
|
||||
$url,
|
||||
1
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether to add the X Debug query parameter to the request made to the Sandbox
|
||||
*
|
||||
* @since 1.36.0
|
||||
*
|
||||
* @param bool $add_parameter Whether to add the parameter to the request or not. Default is to false.
|
||||
* @param string $url The URL of the request being made.
|
||||
* @param string $host The host of the request being made.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_sandbox_add_profile_parameter', false, $url, $host ) ) {
|
||||
$url = add_query_arg( 'XDEBUG_PROFILE', 1, $url );
|
||||
|
||||
// URL has been modified since the signature was created. We'll need a new one.
|
||||
$original_url = add_query_arg( 'XDEBUG_PROFILE', 1, $original_url );
|
||||
$new_signature = $this->get_new_signature( $original_url, $headers, $data, $method );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return compact( 'url', 'host', 'new_signature' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new signature for the request
|
||||
*
|
||||
* @param string $url The new URL to be signed.
|
||||
* @param array $headers The headers of the request about to be made.
|
||||
* @param string $data The body of request about to be made.
|
||||
* @param string $method The method of the request about to be made.
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_new_signature( $url, $headers, $data, $method ) {
|
||||
|
||||
if ( ! empty( $headers['Authorization'] ) ) {
|
||||
$a_headers = $this->extract_authorization_headers( $headers );
|
||||
if ( ! empty( $a_headers ) ) {
|
||||
$token_details = explode( ':', $a_headers['token'] );
|
||||
|
||||
if ( count( $token_details ) === 3 ) {
|
||||
$user_id = $token_details[2];
|
||||
$token = ( new Tokens() )->get_access_token( $user_id );
|
||||
$time_diff = (int) \Jetpack_Options::get_option( 'time_diff' );
|
||||
$jetpack_signature = new \Jetpack_Signature( $token->secret, $time_diff );
|
||||
|
||||
$signature = $jetpack_signature->sign_request(
|
||||
$a_headers['token'],
|
||||
$a_headers['timestamp'],
|
||||
$a_headers['nonce'],
|
||||
$a_headers['body-hash'],
|
||||
$method,
|
||||
$url,
|
||||
$data,
|
||||
false
|
||||
);
|
||||
|
||||
if ( $signature && ! is_wp_error( $signature ) ) {
|
||||
return $signature;
|
||||
} elseif ( is_wp_error( $signature ) ) {
|
||||
$this->log_new_signature_error( $signature->get_error_message() );
|
||||
}
|
||||
} else {
|
||||
$this->log_new_signature_error( 'Malformed token on Authorization Header' );
|
||||
}
|
||||
} else {
|
||||
$this->log_new_signature_error( 'Error extracting Authorization Header' );
|
||||
}
|
||||
} else {
|
||||
$this->log_new_signature_error( 'Empty Authorization Header' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error if the attempt to create a new signature fails
|
||||
*
|
||||
* @param string $message The error message.
|
||||
* @return void
|
||||
*/
|
||||
private function log_new_signature_error( $message ) {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
error_log( sprintf( "SANDBOXING: Error re-signing the request. '%s'", $message ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the values in the Authorization header into an array
|
||||
*
|
||||
* @param array $headers The headers of the request about to be made.
|
||||
* @return array|null
|
||||
*/
|
||||
public function extract_authorization_headers( $headers ) {
|
||||
if ( ! empty( $headers['Authorization'] ) && is_string( $headers['Authorization'] ) ) {
|
||||
$header = str_replace( 'X_JETPACK ', '', $headers['Authorization'] );
|
||||
$vars = explode( ' ', $header );
|
||||
$result = array();
|
||||
foreach ( $vars as $var ) {
|
||||
$elements = explode( '"', $var );
|
||||
if ( count( $elements ) === 3 ) {
|
||||
$result[ substr( $elements[0], 0, -1 ) ] = $elements[1];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies parameters of request in order to send the request to the
|
||||
* server specified by `JETPACK__SANDBOX_DOMAIN`.
|
||||
*
|
||||
* Attached to the `requests-requests.before_request` filter.
|
||||
*
|
||||
* @param string $url URL of request about to be made.
|
||||
* @param array $headers Headers of request about to be made.
|
||||
* @param array|string $data Data of request about to be made.
|
||||
* @param string $type Type of request about to be made.
|
||||
* @return void
|
||||
*/
|
||||
public function server_sandbox( &$url, &$headers, &$data = null, &$type = null ) {
|
||||
if ( ! Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$original_url = $url;
|
||||
|
||||
$request_parameters = $this->server_sandbox_request_parameters( Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ), $url, $headers, $data, $type );
|
||||
|
||||
$url = $request_parameters['url'];
|
||||
|
||||
if ( $request_parameters['host'] ) {
|
||||
$headers['Host'] = $request_parameters['host'];
|
||||
|
||||
if ( $request_parameters['new_signature'] ) {
|
||||
$headers['Authorization'] = preg_replace( '/signature=\"[^\"]+\"/', 'signature="' . $request_parameters['new_signature'] . '"', $headers['Authorization'] );
|
||||
}
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
error_log( sprintf( "SANDBOXING via '%s': '%s'", Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ), $original_url ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "Jetpack API Sandboxed" item to the admin bar if the JETPACK__SANDBOX_DOMAIN
|
||||
* constant is set.
|
||||
*
|
||||
* Attached to the `admin_bar_menu` action.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
|
||||
*/
|
||||
public function admin_bar_add_sandbox_item( $wp_admin_bar ) {
|
||||
if ( ! Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node = array(
|
||||
'id' => 'jetpack-connection-api-sandbox',
|
||||
'title' => 'Jetpack API Sandboxed',
|
||||
'meta' => array(
|
||||
'title' => 'Sandboxing via ' . Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ),
|
||||
),
|
||||
);
|
||||
|
||||
$wp_admin_bar->add_menu( $node );
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* A Terms of Service class for Jetpack.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
/**
|
||||
* Class Terms_Of_Service
|
||||
*
|
||||
* Helper class that is responsible for the state of agreement of the terms of service.
|
||||
*/
|
||||
class Terms_Of_Service {
|
||||
/**
|
||||
* Jetpack option name where the terms of service state is stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTION_NAME = 'tos_agreed';
|
||||
|
||||
/**
|
||||
* Allow the site to agree to the terms of service.
|
||||
*/
|
||||
public function agree() {
|
||||
$this->set_agree();
|
||||
/**
|
||||
* Acton fired when the master user has agreed to the terms of service.
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @since-jetpack 7.9.0
|
||||
*/
|
||||
do_action( 'jetpack_agreed_to_terms_of_service' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the site to reject to the terms of service.
|
||||
*/
|
||||
public function reject() {
|
||||
$this->set_reject();
|
||||
/**
|
||||
* Acton fired when the master user has revoked their agreement to the terms of service.
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @since-jetpack 7.9.1
|
||||
*/
|
||||
do_action( 'jetpack_reject_terms_of_service' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the master user has agreed to the terms of service.
|
||||
*
|
||||
* The following conditions have to be met in order to agree to the terms of service.
|
||||
* 1. The master user has gone though the connect flow.
|
||||
* 2. The site is not in dev mode.
|
||||
* 3. The master user of the site is still connected (deprecated @since 1.4.0).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_agreed() {
|
||||
if ( $this->is_offline_mode() ) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Before 1.4.0 we used to also check if the master user of the site is connected
|
||||
* by calling the Connection related `is_active` method.
|
||||
* As of 1.4.0 we have removed this check in order to resolve the
|
||||
* circular dependencies it was introducing to composer packages.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
return $this->get_raw_has_agreed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstracted for testing purposes.
|
||||
* Tells us if the site is in dev mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_offline_mode() {
|
||||
return ( new Status() )->is_offline_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets just the Jetpack Option that contains the terms of service state.
|
||||
* Abstracted for testing purposes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function get_raw_has_agreed() {
|
||||
return \Jetpack_Options::get_option( self::OPTION_NAME, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the correct Jetpack Option to mark the that the site has agreed to the terms of service.
|
||||
* Abstracted for testing purposes.
|
||||
*/
|
||||
protected function set_agree() {
|
||||
\Jetpack_Options::update_option( self::OPTION_NAME, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the correct Jetpack Option to mark that the site has rejected the terms of service.
|
||||
* Abstracted for testing purposes.
|
||||
*/
|
||||
protected function set_reject() {
|
||||
\Jetpack_Options::update_option( self::OPTION_NAME, false );
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection Tokens Locks class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
*
|
||||
* Jetpack Connection tokens cleanup during migration.
|
||||
* This class encapsulates plugin or tool specific code that activates token lock upon migration.
|
||||
*
|
||||
* The connection tokens are locked to the current domain.
|
||||
* If the database is imported on another site (domain name doesn't match), the tokens get removed.
|
||||
*
|
||||
* @see https://github.com/Automattic/jetpack/pull/23597
|
||||
* @see \Automattic\Jetpack\Connection\Tokens::is_locked()
|
||||
*/
|
||||
class Tokens_Locks {
|
||||
|
||||
/**
|
||||
* Whether the class has been initialized.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $is_initialized = false;
|
||||
|
||||
/**
|
||||
* Run the initializers if they haven't been run already.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( static::$is_initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->init_aiowpm();
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the token lock for AIOWPM plugin export.
|
||||
*
|
||||
* @param array $params The filter parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function aiowpm_set_lock( $params ) {
|
||||
( new Tokens() )->set_lock();
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the token lock for AIOWPM plugin export.
|
||||
*
|
||||
* @param array $params The filter parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function aiowpm_remove_lock( $params ) {
|
||||
( new Tokens() )->remove_lock();
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the All-in-One-WP-Migration plugin hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_aiowpm() {
|
||||
add_filter( 'ai1wm_export', array( $this, 'aiowpm_set_lock' ), 180 );
|
||||
add_filter( 'ai1wm_export', array( $this, 'aiowpm_remove_lock' ), 250 );
|
||||
}
|
||||
}
|
||||
@@ -1,687 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection Tokens class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Roles;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* The Jetpack Connection Tokens class that manages tokens.
|
||||
*/
|
||||
class Tokens {
|
||||
|
||||
const MAGIC_NORMAL_TOKEN_KEY = ';normal;';
|
||||
|
||||
/**
|
||||
* Datetime format.
|
||||
*/
|
||||
const DATE_FORMAT_ATOM = 'Y-m-d\TH:i:sP';
|
||||
|
||||
/**
|
||||
* Deletes all connection tokens and transients from the local Jetpack site.
|
||||
*/
|
||||
public function delete_all() {
|
||||
Jetpack_Options::delete_option(
|
||||
array(
|
||||
'blog_token',
|
||||
'user_token',
|
||||
'user_tokens',
|
||||
)
|
||||
);
|
||||
|
||||
$this->remove_lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the API request to validate the blog and user tokens.
|
||||
*
|
||||
* @param int|null $user_id ID of the user we need to validate token for. Current user's ID by default.
|
||||
*
|
||||
* @return array|false|WP_Error The API response: `array( 'blog_token_is_healthy' => true|false, 'user_token_is_healthy' => true|false )`.
|
||||
*/
|
||||
public function validate( $user_id = null ) {
|
||||
$blog_id = Jetpack_Options::get_option( 'id' );
|
||||
if ( ! $blog_id ) {
|
||||
return new WP_Error( 'site_not_registered', 'Site not registered.' );
|
||||
}
|
||||
$url = sprintf(
|
||||
'%s/%s/v%s/%s',
|
||||
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
|
||||
'wpcom',
|
||||
'2',
|
||||
'sites/' . $blog_id . '/jetpack-token-health'
|
||||
);
|
||||
|
||||
$user_token = $this->get_access_token( $user_id ? $user_id : get_current_user_id() );
|
||||
$blog_token = $this->get_access_token();
|
||||
|
||||
// Cannot validate non-existent tokens.
|
||||
if ( false === $user_token || false === $blog_token ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$method = 'POST';
|
||||
$body = array(
|
||||
'user_token' => $this->get_signed_token( $user_token ),
|
||||
'blog_token' => $this->get_signed_token( $blog_token ),
|
||||
);
|
||||
$response = Client::_wp_remote_request( $url, compact( 'body', 'method' ) );
|
||||
|
||||
if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return $body ? $body : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the API request to validate only the blog.
|
||||
*
|
||||
* @return bool|WP_Error Boolean with the test result. WP_Error if test cannot be performed.
|
||||
*/
|
||||
public function validate_blog_token() {
|
||||
$blog_id = Jetpack_Options::get_option( 'id' );
|
||||
if ( ! $blog_id ) {
|
||||
return new WP_Error( 'site_not_registered', 'Site not registered.' );
|
||||
}
|
||||
$url = sprintf(
|
||||
'%s/%s/v%s/%s',
|
||||
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
|
||||
'wpcom',
|
||||
'2',
|
||||
'sites/' . $blog_id . '/jetpack-token-health/blog'
|
||||
);
|
||||
|
||||
$method = 'GET';
|
||||
$response = Client::remote_request( compact( 'url', 'method' ) );
|
||||
|
||||
if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return is_array( $body ) && isset( $body['is_healthy'] ) && true === $body['is_healthy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the auth token.
|
||||
*
|
||||
* @param array $data The request data.
|
||||
* @param string $token_api_url The URL of the Jetpack "token" API.
|
||||
* @return object|WP_Error Returns the auth token on success.
|
||||
* Returns a WP_Error on failure.
|
||||
*/
|
||||
public function get( $data, $token_api_url ) {
|
||||
$roles = new Roles();
|
||||
$role = $roles->translate_current_user_to_role();
|
||||
|
||||
if ( ! $role ) {
|
||||
return new WP_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$client_secret = $this->get_access_token();
|
||||
if ( ! $client_secret ) {
|
||||
return new WP_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the URL of the first time the user gets redirected back to your site for connection
|
||||
* data processing.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 8.0.0
|
||||
*
|
||||
* @param string $redirect_url Defaults to the site admin URL.
|
||||
*/
|
||||
$processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) );
|
||||
|
||||
$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
|
||||
|
||||
/**
|
||||
* Filter the URL to redirect the user back to when the authentication process
|
||||
* is complete.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 8.0.0
|
||||
*
|
||||
* @param string $redirect_url Defaults to the site URL.
|
||||
*/
|
||||
$redirect = apply_filters( 'jetpack_token_redirect_url', $redirect );
|
||||
|
||||
$redirect_uri = ( 'calypso' === $data['auth_type'] )
|
||||
? $data['redirect_uri']
|
||||
: add_query_arg(
|
||||
array(
|
||||
'handler' => 'jetpack-connection-webhooks',
|
||||
'action' => 'authorize',
|
||||
'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
|
||||
'redirect' => $redirect ? rawurlencode( $redirect ) : false,
|
||||
),
|
||||
esc_url( $processing_url )
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the token request data.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 8.0.0
|
||||
*
|
||||
* @param array $request_data request data.
|
||||
*/
|
||||
$body = apply_filters(
|
||||
'jetpack_token_request_body',
|
||||
array(
|
||||
'client_id' => Jetpack_Options::get_option( 'id' ),
|
||||
'client_secret' => $client_secret->secret,
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $data['code'],
|
||||
'redirect_uri' => $redirect_uri,
|
||||
)
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
'body' => $body,
|
||||
'headers' => array(
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
);
|
||||
add_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
|
||||
$response = Client::_wp_remote_request( $token_api_url, $args );
|
||||
remove_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error( 'token_http_request_failed', $response->get_error_message() );
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
$entity = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( $entity ) {
|
||||
$json = json_decode( $entity );
|
||||
} else {
|
||||
$json = false;
|
||||
}
|
||||
|
||||
if ( 200 !== $code || ! empty( $json->error ) ) {
|
||||
if ( empty( $json->error ) ) {
|
||||
return new WP_Error( 'unknown', '', $code );
|
||||
}
|
||||
|
||||
/* translators: Error description string. */
|
||||
$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack-connection' ), (string) $json->error_description ) : '';
|
||||
|
||||
return new WP_Error( (string) $json->error, $error_description, $code );
|
||||
}
|
||||
|
||||
if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
|
||||
return new WP_Error( 'access_token', '', $code );
|
||||
}
|
||||
|
||||
if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) {
|
||||
return new WP_Error( 'token_type', '', $code );
|
||||
}
|
||||
|
||||
if ( empty( $json->scope ) ) {
|
||||
return new WP_Error( 'scope', 'No Scope', $code );
|
||||
}
|
||||
|
||||
// TODO: get rid of the error silencer.
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
@list( $role, $hmac ) = explode( ':', $json->scope );
|
||||
if ( empty( $role ) || empty( $hmac ) ) {
|
||||
return new WP_Error( 'scope', 'Malformed Scope', $code );
|
||||
}
|
||||
|
||||
if ( $this->sign_role( $role ) !== $json->scope ) {
|
||||
return new WP_Error( 'scope', 'Invalid Scope', $code );
|
||||
}
|
||||
|
||||
$cap = $roles->translate_role_to_cap( $role );
|
||||
if ( ! $cap ) {
|
||||
return new WP_Error( 'scope', 'No Cap', $code );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( $cap ) ) {
|
||||
return new WP_Error( 'scope', 'current_user_cannot', $code );
|
||||
}
|
||||
|
||||
return (string) $json->access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters a user token into the user_tokens option
|
||||
*
|
||||
* @param int $user_id The user id.
|
||||
* @param string $token The user token.
|
||||
* @param bool $is_master_user Whether the user is the master user.
|
||||
* @return bool
|
||||
*/
|
||||
public function update_user_token( $user_id, $token, $is_master_user ) {
|
||||
// Not designed for concurrent updates.
|
||||
$user_tokens = $this->get_user_tokens();
|
||||
if ( ! is_array( $user_tokens ) ) {
|
||||
$user_tokens = array();
|
||||
}
|
||||
$user_tokens[ $user_id ] = $token;
|
||||
if ( $is_master_user ) {
|
||||
$master_user = $user_id;
|
||||
$options = compact( 'user_tokens', 'master_user' );
|
||||
} else {
|
||||
$options = compact( 'user_tokens' );
|
||||
}
|
||||
return Jetpack_Options::update_options( $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a user role with the master access token.
|
||||
* If not specified, will default to the current user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $role User role.
|
||||
* @param int $user_id ID of the user.
|
||||
* @return string Signed user role.
|
||||
*/
|
||||
public function sign_role( $role, $user_id = null ) {
|
||||
if ( empty( $user_id ) ) {
|
||||
$user_id = (int) get_current_user_id();
|
||||
}
|
||||
|
||||
if ( ! $user_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = $this->get_access_token();
|
||||
if ( ! $token || is_wp_error( $token ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the request timeout value to 30 seconds.
|
||||
*
|
||||
* @return int Returns 30.
|
||||
*/
|
||||
public function return_30() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requested token.
|
||||
*
|
||||
* Tokens are one of two types:
|
||||
* 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token,
|
||||
* though some sites can have multiple "Special" Blog Tokens (see below). These tokens
|
||||
* are not associated with a user account. They represent the site's connection with
|
||||
* the Jetpack servers.
|
||||
* 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token.
|
||||
*
|
||||
* All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the
|
||||
* token, and $private is a secret that should never be displayed anywhere or sent
|
||||
* over the network; it's used only for signing things.
|
||||
*
|
||||
* Blog Tokens can be "Normal" or "Special".
|
||||
* * Normal: The result of a normal connection flow. They look like
|
||||
* "{$random_string_1}.{$random_string_2}"
|
||||
* That is, $token_key and $private are both random strings.
|
||||
* Sites only have one Normal Blog Token. Normal Tokens are found in either
|
||||
* Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN
|
||||
* constant (rare).
|
||||
* * Special: A connection token for sites that have gone through an alternative
|
||||
* connection flow. They look like:
|
||||
* ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}"
|
||||
* That is, $private is a random string and $token_key has a special structure with
|
||||
* lots of semicolons.
|
||||
* Most sites have zero Special Blog Tokens. Special tokens are only found in the
|
||||
* JETPACK_BLOG_TOKEN constant.
|
||||
*
|
||||
* In particular, note that Normal Blog Tokens never start with ";" and that
|
||||
* Special Blog Tokens always do.
|
||||
*
|
||||
* When searching for a matching Blog Tokens, Blog Tokens are examined in the following
|
||||
* order:
|
||||
* 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant)
|
||||
* 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' ))
|
||||
* 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant)
|
||||
*
|
||||
* @param int|false $user_id false: Return the Blog Token. int: Return that user's User Token.
|
||||
* @param string|false $token_key If provided, check that the token matches the provided input.
|
||||
* @param bool|true $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found.
|
||||
*
|
||||
* @return object|false|WP_Error
|
||||
*/
|
||||
public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
|
||||
if ( $this->is_locked() ) {
|
||||
$this->delete_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
$possible_special_tokens = array();
|
||||
$possible_normal_tokens = array();
|
||||
$user_tokens = $this->get_user_tokens();
|
||||
|
||||
if ( $user_id ) {
|
||||
if ( ! $user_tokens ) {
|
||||
return $suppress_errors ? false : new WP_Error( 'no_user_tokens', __( 'No user tokens found', 'jetpack-connection' ) );
|
||||
}
|
||||
if ( true === $user_id ) { // connection owner.
|
||||
$user_id = Jetpack_Options::get_option( 'master_user' );
|
||||
if ( ! $user_id ) {
|
||||
return $suppress_errors ? false : new WP_Error( 'empty_master_user_option', __( 'No primary user defined', 'jetpack-connection' ) );
|
||||
}
|
||||
}
|
||||
if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
|
||||
// translators: %s is the user ID.
|
||||
return $suppress_errors ? false : new WP_Error( 'no_token_for_user', sprintf( __( 'No token for user %d', 'jetpack-connection' ), $user_id ) );
|
||||
}
|
||||
$user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
|
||||
if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
|
||||
// translators: %s is the user ID.
|
||||
return $suppress_errors ? false : new WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack-connection' ), $user_id ) );
|
||||
}
|
||||
if ( $user_token_chunks[2] !== (string) $user_id ) {
|
||||
// translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token.
|
||||
return $suppress_errors ? false : new WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack-connection' ), $user_id, $user_token_chunks[2] ) );
|
||||
}
|
||||
$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
|
||||
} else {
|
||||
$stored_blog_token = Jetpack_Options::get_option( 'blog_token' );
|
||||
if ( $stored_blog_token ) {
|
||||
$possible_normal_tokens[] = $stored_blog_token;
|
||||
}
|
||||
|
||||
$defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' );
|
||||
|
||||
if ( $defined_tokens_string ) {
|
||||
$defined_tokens = explode( ',', $defined_tokens_string );
|
||||
foreach ( $defined_tokens as $defined_token ) {
|
||||
if ( ';' === $defined_token[0] ) {
|
||||
$possible_special_tokens[] = $defined_token;
|
||||
} else {
|
||||
$possible_normal_tokens[] = $defined_token;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
|
||||
$possible_tokens = $possible_normal_tokens;
|
||||
} else {
|
||||
$possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens );
|
||||
}
|
||||
|
||||
if ( ! $possible_tokens ) {
|
||||
// If no user tokens were found, it would have failed earlier, so this is about blog token.
|
||||
return $suppress_errors ? false : new WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$valid_token = false;
|
||||
|
||||
if ( false === $token_key ) {
|
||||
// Use first token.
|
||||
$valid_token = $possible_tokens[0];
|
||||
} elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
|
||||
// Use first normal token.
|
||||
$valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check.
|
||||
} else {
|
||||
// Use the token matching $token_key or false if none.
|
||||
// Ensure we check the full key.
|
||||
$token_check = rtrim( $token_key, '.' ) . '.';
|
||||
|
||||
foreach ( $possible_tokens as $possible_token ) {
|
||||
if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) {
|
||||
$valid_token = $possible_token;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $valid_token ) {
|
||||
if ( $user_id ) {
|
||||
// translators: %d is the user ID.
|
||||
return $suppress_errors ? false : new WP_Error( 'no_valid_user_token', sprintf( __( 'Invalid token for user %d', 'jetpack-connection' ), $user_id ) );
|
||||
} else {
|
||||
return $suppress_errors ? false : new WP_Error( 'no_valid_blog_token', __( 'Invalid blog token', 'jetpack-connection' ) );
|
||||
}
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'secret' => $valid_token,
|
||||
'external_user_id' => (int) $user_id,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the blog token to a new value.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $token the new blog token value.
|
||||
* @return Boolean Whether updating the blog token was successful.
|
||||
*/
|
||||
public function update_blog_token( $token ) {
|
||||
return Jetpack_Options::update_option( 'blog_token', $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks the current user from the linked WordPress.com user.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @todo Refactor to properly load the XMLRPC client independently.
|
||||
*
|
||||
* @param int $user_id The user identifier.
|
||||
*
|
||||
* @return bool Whether the disconnection of the user was successful.
|
||||
*/
|
||||
public function disconnect_user( $user_id ) {
|
||||
$tokens = $this->get_user_tokens();
|
||||
if ( ! $tokens ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $tokens[ $user_id ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset( $tokens[ $user_id ] );
|
||||
|
||||
$this->update_user_tokens( $tokens );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of user_id's that have user tokens for communicating with wpcom.
|
||||
* Able to select by specific capability.
|
||||
*
|
||||
* @deprecated 1.30.0
|
||||
* @see Manager::get_connected_users
|
||||
*
|
||||
* @param string $capability The capability of the user.
|
||||
* @param int|null $limit How many connected users to get before returning.
|
||||
* @return array Array of WP_User objects if found.
|
||||
*/
|
||||
public function get_connected_users( $capability = 'any', $limit = null ) {
|
||||
_deprecated_function( __METHOD__, '1.30.0' );
|
||||
return ( new Manager( 'jetpack' ) )->get_connected_users( $capability, $limit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a signed token.
|
||||
*
|
||||
* @param object $token the token.
|
||||
* @return WP_Error|string a signed token
|
||||
*/
|
||||
public function get_signed_token( $token ) {
|
||||
if ( ! isset( $token->secret ) || empty( $token->secret ) ) {
|
||||
return new WP_Error( 'invalid_token' );
|
||||
}
|
||||
|
||||
list( $token_key, $token_secret ) = explode( '.', $token->secret );
|
||||
|
||||
$token_key = sprintf(
|
||||
'%s:%d:%d',
|
||||
$token_key,
|
||||
Constants::get_constant( 'JETPACK__API_VERSION' ),
|
||||
$token->external_user_id
|
||||
);
|
||||
|
||||
$timestamp = time();
|
||||
|
||||
if ( function_exists( 'wp_generate_password' ) ) {
|
||||
$nonce = wp_generate_password( 10, false );
|
||||
} else {
|
||||
$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
|
||||
}
|
||||
|
||||
$normalized_request_string = implode(
|
||||
"\n",
|
||||
array(
|
||||
$token_key,
|
||||
$timestamp,
|
||||
$nonce,
|
||||
)
|
||||
) . "\n";
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
$signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) );
|
||||
|
||||
$auth = array(
|
||||
'token' => $token_key,
|
||||
'timestamp' => $timestamp,
|
||||
'nonce' => $nonce,
|
||||
'signature' => $signature,
|
||||
);
|
||||
|
||||
$header_pieces = array();
|
||||
foreach ( $auth as $key => $value ) {
|
||||
$header_pieces[] = sprintf( '%s="%s"', $key, $value );
|
||||
}
|
||||
|
||||
return implode( ' ', $header_pieces );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of user tokens
|
||||
*
|
||||
* @since 1.30.0
|
||||
*
|
||||
* @return bool|array An array of user tokens where keys are user IDs and values are the tokens. False if no user token is found.
|
||||
*/
|
||||
public function get_user_tokens() {
|
||||
return Jetpack_Options::get_option( 'user_tokens' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the option that stores the user tokens
|
||||
*
|
||||
* @since 1.30.0
|
||||
*
|
||||
* @param array $tokens An array of user tokens where keys are user IDs and values are the tokens.
|
||||
* @return bool Was the option successfully updated?
|
||||
*
|
||||
* @todo add validate the input.
|
||||
*/
|
||||
public function update_user_tokens( $tokens ) {
|
||||
return Jetpack_Options::update_option( 'user_tokens', $tokens );
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the tokens to the current site URL.
|
||||
*
|
||||
* @param int $timespan How long the tokens should be locked, in seconds.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set_lock( $timespan = HOUR_IN_SECONDS ) {
|
||||
try {
|
||||
$expires = ( new DateTime() )->add( DateInterval::createFromDateString( (int) $timespan . ' seconds' ) );
|
||||
} catch ( Exception $e ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false === $expires ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
return Jetpack_Options::update_option( 'token_lock', $expires->format( static::DATE_FORMAT_ATOM ) . '|||' . base64_encode( Urls::site_url() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the site lock from tokens.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function remove_lock() {
|
||||
Jetpack_Options::delete_option( 'token_lock' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the domain is locked, remove the lock if needed.
|
||||
* Possible scenarios:
|
||||
* - lock expired, site URL matches the lock URL: remove the lock, return false.
|
||||
* - lock not expired, site URL matches the lock URL: return false.
|
||||
* - site URL does not match the lock URL (expiration date is ignored): return true, do not remove the lock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
$the_lock = Jetpack_Options::get_option( 'token_lock' );
|
||||
if ( ! $the_lock ) {
|
||||
// Not locked.
|
||||
return false;
|
||||
}
|
||||
|
||||
$the_lock = explode( '|||', $the_lock, 2 );
|
||||
if ( count( $the_lock ) !== 2 ) {
|
||||
// Something's wrong with the lock.
|
||||
$this->remove_lock();
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
$locked_site_url = base64_decode( $the_lock[1] );
|
||||
$expires = $the_lock[0];
|
||||
|
||||
$expiration_date = DateTime::createFromFormat( static::DATE_FORMAT_ATOM, $expires );
|
||||
if ( false === $expiration_date || ! $locked_site_url ) {
|
||||
// Something's wrong with the lock.
|
||||
$this->remove_lock();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( Urls::site_url() === $locked_site_url ) {
|
||||
if ( new DateTime() > $expiration_date ) {
|
||||
// Site lock expired.
|
||||
// Site URL matches, removing the lock.
|
||||
$this->remove_lock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Site URL doesn't match.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Nosara Tracks for Jetpack
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
/**
|
||||
* The Tracking class, used to record events in wpcom
|
||||
*/
|
||||
class Tracking {
|
||||
/**
|
||||
* The assets version.
|
||||
*
|
||||
* @since 1.13.1
|
||||
* @deprecated since 1.40.1
|
||||
*
|
||||
* @var string Assets version.
|
||||
*/
|
||||
const ASSETS_VERSION = '1.0.0';
|
||||
|
||||
/**
|
||||
* Slug of the product that we are tracking.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $product_name;
|
||||
|
||||
/**
|
||||
* Connection manager object.
|
||||
*
|
||||
* @var Object
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Creates the Tracking object.
|
||||
*
|
||||
* @param String $product_name the slug of the product that we are tracking.
|
||||
* @param Automattic\Jetpack\Connection\Manager $connection the connection manager object.
|
||||
*/
|
||||
public function __construct( $product_name = 'jetpack', $connection = null ) {
|
||||
$this->product_name = $product_name;
|
||||
$this->connection = $connection;
|
||||
if ( $this->connection === null ) {
|
||||
// TODO We should always pass a Connection.
|
||||
$this->connection = new Connection\Manager();
|
||||
}
|
||||
|
||||
if ( ! did_action( 'jetpack_set_tracks_ajax_hook' ) ) {
|
||||
add_action( 'wp_ajax_jetpack_tracks', array( $this, 'ajax_tracks' ) );
|
||||
|
||||
/**
|
||||
* Fires when the Tracking::ajax_tracks() callback has been hooked to the
|
||||
* wp_ajax_jetpack_tracks action. This action is used to ensure that
|
||||
* the callback is hooked only once.
|
||||
*
|
||||
* @since 1.13.11
|
||||
*/
|
||||
do_action( 'jetpack_set_tracks_ajax_hook' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal method for for all tracking events triggered via the JavaScript client.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function ajax_tracks() {
|
||||
// Check for nonce.
|
||||
if (
|
||||
empty( $_REQUEST['tracksNonce'] )
|
||||
|| ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either.
|
||||
) {
|
||||
wp_send_json_error(
|
||||
__( 'You aren’t authorized to do that.', 'jetpack-connection' ),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] ) ) {
|
||||
wp_send_json_error(
|
||||
__( 'No valid event name or type.', 'jetpack-connection' ),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
$tracks_data = array();
|
||||
if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
|
||||
if ( is_array( $_REQUEST['tracksEventProp'] ) ) {
|
||||
$tracks_data = array_map( 'filter_var', wp_unslash( $_REQUEST['tracksEventProp'] ) );
|
||||
} else {
|
||||
$tracks_data = array( 'clicked' => filter_var( wp_unslash( $_REQUEST['tracksEventProp'] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->record_user_event( filter_var( wp_unslash( $_REQUEST['tracksEventName'] ) ), $tracks_data, null, false );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register script necessary for tracking.
|
||||
*
|
||||
* @param boolean $enqueue Also enqueue? defaults to false.
|
||||
*/
|
||||
public static function register_tracks_functions_scripts( $enqueue = false ) {
|
||||
|
||||
// Register jp-tracks as it is a dependency.
|
||||
wp_register_script(
|
||||
'jp-tracks',
|
||||
'//stats.wp.com/w.js',
|
||||
array(),
|
||||
gmdate( 'YW' ),
|
||||
true
|
||||
);
|
||||
|
||||
Assets::register_script(
|
||||
'jp-tracks-functions',
|
||||
'../dist/tracks-callables.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'dependencies' => array( 'jp-tracks' ),
|
||||
'enqueue' => $enqueue,
|
||||
'in_footer' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue script necessary for tracking.
|
||||
*/
|
||||
public function enqueue_tracks_scripts() {
|
||||
Assets::register_script(
|
||||
'jptracks',
|
||||
'../dist/tracks-ajax.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'dependencies' => array( 'jquery' ),
|
||||
'enqueue' => true,
|
||||
'in_footer' => true,
|
||||
)
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'jptracks',
|
||||
'jpTracksAJAX',
|
||||
array(
|
||||
'ajaxurl' => admin_url( 'admin-ajax.php' ),
|
||||
'jpTracksAJAX_nonce' => wp_create_nonce( 'jp-tracks-ajax-nonce' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event in Tracks.
|
||||
*
|
||||
* @param string $event_type Type of the event.
|
||||
* @param array $data Data to send with the event.
|
||||
* @param mixed $user Username, user_id, or WP_user object.
|
||||
* @param bool $use_product_prefix Whether to use the object's product name as a prefix to the event type. If
|
||||
* set to false, the prefix will be 'jetpack_'.
|
||||
*/
|
||||
public function record_user_event( $event_type, $data = array(), $user = null, $use_product_prefix = true ) {
|
||||
if ( ! $user ) {
|
||||
$user = wp_get_current_user();
|
||||
}
|
||||
$site_url = get_option( 'siteurl' );
|
||||
|
||||
$data['_via_ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? filter_var( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
|
||||
$data['_via_ip'] = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
|
||||
$data['_lg'] = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? filter_var( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : '';
|
||||
$data['blog_url'] = $site_url;
|
||||
$data['blog_id'] = \Jetpack_Options::get_option( 'id' );
|
||||
|
||||
// Top level events should not be namespaced.
|
||||
if ( '_aliasUser' !== $event_type ) {
|
||||
$prefix = $use_product_prefix ? $this->product_name : 'jetpack';
|
||||
$event_type = $prefix . '_' . $event_type;
|
||||
}
|
||||
|
||||
$data['jetpack_version'] = defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '0';
|
||||
|
||||
return $this->tracks_record_event( $user, $event_type, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an event in Tracks - this is the preferred way to record events from PHP.
|
||||
*
|
||||
* @param mixed $user username, user_id, or WP_user object.
|
||||
* @param string $event_name The name of the event.
|
||||
* @param array $properties Custom properties to send with the event.
|
||||
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
|
||||
*
|
||||
* @return bool true for success | \WP_Error if the event pixel could not be fired
|
||||
*/
|
||||
public function tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
|
||||
|
||||
// We don't want to track user events during unit tests/CI runs.
|
||||
if ( $user instanceof \WP_User && 'wptests_capabilities' === $user->cap_key ) {
|
||||
return false;
|
||||
}
|
||||
$terms_of_service = new Terms_Of_Service();
|
||||
$status = new Status();
|
||||
// Don't track users who have not agreed to our TOS.
|
||||
if ( ! $this->should_enable_tracking( $terms_of_service, $status ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event_obj = $this->tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis );
|
||||
|
||||
if ( is_wp_error( $event_obj->error ) ) {
|
||||
return $event_obj->error;
|
||||
}
|
||||
|
||||
return $event_obj->record();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether tracking should be enabled.
|
||||
*
|
||||
* @param Automattic\Jetpack\Terms_Of_Service $terms_of_service A Terms_Of_Service object.
|
||||
* @param Automattic\Jetpack\Status $status A Status object.
|
||||
*
|
||||
* @return boolean True if tracking should be enabled, else false.
|
||||
*/
|
||||
public function should_enable_tracking( $terms_of_service, $status ) {
|
||||
if ( $status->is_offline_mode() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $terms_of_service->has_agreed() || $this->connection->is_user_connected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Procedurally build a Tracks Event Object.
|
||||
* NOTE: Use this only when the simpler Automattic\Jetpack\Tracking->jetpack_tracks_record_event() function won't work for you.
|
||||
*
|
||||
* @param WP_user $user WP_user object.
|
||||
* @param string $event_name The name of the event.
|
||||
* @param array $properties Custom properties to send with the event.
|
||||
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
|
||||
*
|
||||
* @return \Jetpack_Tracks_Event|\WP_Error
|
||||
*/
|
||||
private function tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
|
||||
$identity = $this->tracks_get_identity( $user->ID );
|
||||
|
||||
$properties['user_lang'] = $user->get( 'WPLANG' );
|
||||
|
||||
$blog_details = array(
|
||||
'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ),
|
||||
);
|
||||
|
||||
$timestamp = ( false !== $event_timestamp_millis ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
|
||||
$timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' );
|
||||
|
||||
return new \Jetpack_Tracks_Event(
|
||||
array_merge(
|
||||
$blog_details,
|
||||
(array) $properties,
|
||||
$identity,
|
||||
array(
|
||||
'_en' => $event_name,
|
||||
'_ts' => $timestamp_string,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identity to send to tracks.
|
||||
*
|
||||
* @param int $user_id The user id of the local user.
|
||||
*
|
||||
* @return array $identity
|
||||
*/
|
||||
public function tracks_get_identity( $user_id ) {
|
||||
|
||||
// Meta is set, and user is still connected. Use WPCOM ID.
|
||||
$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
|
||||
if ( $wpcom_id && $this->connection->is_user_connected( $user_id ) ) {
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_id,
|
||||
);
|
||||
}
|
||||
|
||||
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
|
||||
if ( $this->connection->is_user_connected( $user_id ) ) {
|
||||
$wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
|
||||
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
|
||||
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_user_data['ID'],
|
||||
);
|
||||
}
|
||||
|
||||
// User isn't linked at all. Fall back to anonymous ID.
|
||||
$anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true );
|
||||
if ( ! $anon_id ) {
|
||||
$anon_id = \Jetpack_Tracks_Client::get_anon_id();
|
||||
add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false );
|
||||
}
|
||||
|
||||
if ( ! isset( $_COOKIE['tk_ai'] ) && ! headers_sent() ) {
|
||||
setcookie( 'tk_ai', $anon_id, 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- This is a random string and should be fine.
|
||||
}
|
||||
|
||||
return array(
|
||||
'_ut' => 'anon',
|
||||
'_ui' => $anon_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection package Urls class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* Provides Url methods for the Connection package.
|
||||
*/
|
||||
class Urls {
|
||||
|
||||
const HTTPS_CHECK_OPTION_PREFIX = 'jetpack_sync_https_history_';
|
||||
const HTTPS_CHECK_HISTORY = 5;
|
||||
|
||||
/**
|
||||
* Return URL from option or PHP constant.
|
||||
*
|
||||
* @param string $option_name (e.g. 'home').
|
||||
*
|
||||
* @return mixed|null URL.
|
||||
*/
|
||||
public static function get_raw_url( $option_name ) {
|
||||
$value = null;
|
||||
$constant = ( 'home' === $option_name )
|
||||
? 'WP_HOME'
|
||||
: 'WP_SITEURL';
|
||||
|
||||
// Since we disregard the constant for multisites in ms-default-filters.php,
|
||||
// let's also use the db value if this is a multisite.
|
||||
if ( ! is_multisite() && Constants::is_defined( $constant ) ) {
|
||||
$value = Constants::get_constant( $constant );
|
||||
} else {
|
||||
// Let's get the option from the database so that we can bypass filters. This will help
|
||||
// ensure that we get more uniform values.
|
||||
$value = \Jetpack_Options::get_raw_option( $option_name );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize domains by removing www unless declared in the site's option.
|
||||
*
|
||||
* @param string $option Option value from the site.
|
||||
* @param callable $url_function Function retrieving the URL to normalize.
|
||||
* @return mixed|string URL.
|
||||
*/
|
||||
public static function normalize_www_in_url( $option, $url_function ) {
|
||||
$url = wp_parse_url( call_user_func( $url_function ) );
|
||||
$option_url = wp_parse_url( get_option( $option ) );
|
||||
|
||||
if ( ! $option_url || ! $url ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ( "www.{$option_url[ 'host' ]}" === $url['host'] ) {
|
||||
// remove www if not present in option URL.
|
||||
$url['host'] = $option_url['host'];
|
||||
}
|
||||
if ( "www.{$url[ 'host' ]}" === $option_url['host'] ) {
|
||||
// add www if present in option URL.
|
||||
$url['host'] = $option_url['host'];
|
||||
}
|
||||
|
||||
$normalized_url = "{$url['scheme']}://{$url['host']}";
|
||||
if ( isset( $url['path'] ) ) {
|
||||
$normalized_url .= "{$url['path']}";
|
||||
}
|
||||
|
||||
if ( isset( $url['query'] ) ) {
|
||||
$normalized_url .= "?{$url['query']}";
|
||||
}
|
||||
|
||||
return $normalized_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL with a normalized protocol.
|
||||
*
|
||||
* @param callable $callable Function to retrieve URL option.
|
||||
* @param string $new_value URL Protocol to set URLs to.
|
||||
* @return string Normalized URL.
|
||||
*/
|
||||
public static function get_protocol_normalized_url( $callable, $new_value ) {
|
||||
$option_key = self::HTTPS_CHECK_OPTION_PREFIX . $callable;
|
||||
|
||||
$parsed_url = wp_parse_url( $new_value );
|
||||
|
||||
if ( ! $parsed_url ) {
|
||||
return $new_value;
|
||||
}
|
||||
if ( array_key_exists( 'scheme', $parsed_url ) ) {
|
||||
$scheme = $parsed_url['scheme'];
|
||||
} else {
|
||||
$scheme = '';
|
||||
}
|
||||
$scheme_history = get_option( $option_key, array() );
|
||||
|
||||
if ( ! is_array( $scheme_history ) ) {
|
||||
$scheme_history = array();
|
||||
}
|
||||
|
||||
$scheme_history[] = $scheme;
|
||||
|
||||
// Limit length to self::HTTPS_CHECK_HISTORY.
|
||||
$scheme_history = array_slice( $scheme_history, ( self::HTTPS_CHECK_HISTORY * -1 ) );
|
||||
|
||||
update_option( $option_key, $scheme_history );
|
||||
|
||||
$forced_scheme = in_array( 'https', $scheme_history, true ) ? 'https' : 'http';
|
||||
|
||||
return set_url_scheme( $new_value, $forced_scheme );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that is used when getting home or siteurl values. Decides
|
||||
* whether to get the raw or filtered value.
|
||||
*
|
||||
* @param string $url_type URL to get, home or siteurl.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_raw_or_filtered_url( $url_type ) {
|
||||
$url_function = ( 'home' === $url_type )
|
||||
? 'home_url'
|
||||
: 'site_url';
|
||||
|
||||
if (
|
||||
! Constants::is_defined( 'JETPACK_SYNC_USE_RAW_URL' ) ||
|
||||
Constants::get_constant( 'JETPACK_SYNC_USE_RAW_URL' )
|
||||
) {
|
||||
$scheme = is_ssl() ? 'https' : 'http';
|
||||
$url = (string) self::get_raw_url( $url_type );
|
||||
$url = set_url_scheme( $url, $scheme );
|
||||
} else {
|
||||
$url = self::normalize_www_in_url( $url_type, $url_function );
|
||||
}
|
||||
|
||||
return self::get_protocol_normalized_url( $url_function, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the escaped home_url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function home_url() {
|
||||
$url = self::get_raw_or_filtered_url( 'home' );
|
||||
|
||||
/**
|
||||
* Allows overriding of the home_url value that is synced back to WordPress.com.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 5.2.0
|
||||
*
|
||||
* @param string $home_url
|
||||
*/
|
||||
return esc_url_raw( apply_filters( 'jetpack_sync_home_url', $url ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the escaped siteurl.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function site_url() {
|
||||
$url = self::get_raw_or_filtered_url( 'siteurl' );
|
||||
|
||||
/**
|
||||
* Allows overriding of the site_url value that is synced back to WordPress.com.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 5.2.0
|
||||
*
|
||||
* @param string $site_url
|
||||
*/
|
||||
return esc_url_raw( apply_filters( 'jetpack_sync_site_url', $url ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return main site URL with a normalized protocol.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function main_network_site_url() {
|
||||
return self::get_protocol_normalized_url( 'main_network_site_url', network_site_url() );
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection package Utils class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Tracking;
|
||||
|
||||
/**
|
||||
* Provides utility methods for the Connection package.
|
||||
*/
|
||||
class Utils {
|
||||
|
||||
const DEFAULT_JETPACK__API_VERSION = 1;
|
||||
const DEFAULT_JETPACK__API_BASE = 'https://jetpack.wordpress.com/jetpack.';
|
||||
const DEFAULT_JETPACK__WPCOM_JSON_API_BASE = 'https://public-api.wordpress.com';
|
||||
|
||||
/**
|
||||
* Enters a user token into the user_tokens option
|
||||
*
|
||||
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Tokens->update_user_token() instead.
|
||||
*
|
||||
* @param int $user_id The user id.
|
||||
* @param string $token The user token.
|
||||
* @param bool $is_master_user Whether the user is the master user.
|
||||
* @return bool
|
||||
*/
|
||||
public static function update_user_token( $user_id, $token, $is_master_user ) {
|
||||
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Tokens->update_user_token' );
|
||||
return ( new Tokens() )->update_user_token( $user_id, $token, $is_master_user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the value of the api constant.
|
||||
*
|
||||
* @param String $constant_value The constant value.
|
||||
* @param String $constant_name The constant name.
|
||||
* @return mixed | null
|
||||
*/
|
||||
public static function jetpack_api_constant_filter( $constant_value, $constant_name ) {
|
||||
if ( $constant_value !== null ) {
|
||||
// If the constant value was already set elsewhere, use that value.
|
||||
return $constant_value;
|
||||
}
|
||||
|
||||
if ( defined( "self::DEFAULT_$constant_name" ) ) {
|
||||
return constant( "self::DEFAULT_$constant_name" );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to initialize default values of the constants.
|
||||
*/
|
||||
public static function init_default_constants() {
|
||||
add_filter(
|
||||
'jetpack_constant_default_value',
|
||||
array( __CLASS__, 'jetpack_api_constant_filter' ),
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the registration request body to include tracking properties.
|
||||
*
|
||||
* @param array $properties Already prepared tracking properties.
|
||||
* @return array amended properties.
|
||||
*/
|
||||
public static function filter_register_request_body( $properties ) {
|
||||
$tracking = new Tracking();
|
||||
$tracks_identity = $tracking->tracks_get_identity( get_current_user_id() );
|
||||
|
||||
return array_merge(
|
||||
$properties,
|
||||
array(
|
||||
'_ui' => $tracks_identity['_ui'],
|
||||
'_ut' => $tracks_identity['_ut'],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Connection Webhooks class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\CookieState;
|
||||
use Automattic\Jetpack\Roles;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* Connection Webhooks class.
|
||||
*/
|
||||
class Webhooks {
|
||||
|
||||
/**
|
||||
* The Connection Manager object.
|
||||
*
|
||||
* @var Manager
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Webhooks constructor.
|
||||
*
|
||||
* @param Manager $connection The Connection Manager object.
|
||||
*/
|
||||
public function __construct( $connection ) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the webhooks.
|
||||
*
|
||||
* @param Manager $connection The Connection Manager object.
|
||||
*/
|
||||
public static function init( $connection ) {
|
||||
$webhooks = new static( $connection );
|
||||
|
||||
add_action( 'init', array( $webhooks, 'controller' ) );
|
||||
add_action( 'load-toplevel_page_jetpack', array( $webhooks, 'fallback_jetpack_controller' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Jetpack plugin used to trigger this webhooks in Jetpack::admin_page_load()
|
||||
*
|
||||
* The Jetpack toplevel menu is still accessible for stand-alone plugins, and while there's no content for that page, there are still
|
||||
* actions from Calypso and WPCOM that reach that route regardless of the site having the Jetpack plugin or not. That's why we are still handling it here.
|
||||
*/
|
||||
public function fallback_jetpack_controller() {
|
||||
$this->controller( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* The "controller" decides which handler we need to run.
|
||||
*
|
||||
* @param bool $force Do not check if it's a webhook request and just run the controller.
|
||||
*/
|
||||
public function controller( $force = false ) {
|
||||
if ( ! $force ) {
|
||||
// The nonce is verified in specific handlers.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $_GET['handler'] ) || 'jetpack-connection-webhooks' !== $_GET['handler'] ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['connect_url_redirect'] ) ) {
|
||||
$this->handle_connect_url_redirect();
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $_GET['action'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The nonce is verified in specific handlers.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
switch ( $_GET['action'] ) {
|
||||
case 'authorize':
|
||||
$this->handle_authorize();
|
||||
$this->do_exit();
|
||||
break;
|
||||
case 'authorize_redirect':
|
||||
$this->handle_authorize_redirect();
|
||||
$this->do_exit();
|
||||
break;
|
||||
// Class Jetpack::admin_page_load() still handles other cases.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the authorization action.
|
||||
*/
|
||||
public function handle_authorize() {
|
||||
if ( $this->connection->is_connected() && $this->connection->is_user_connected() ) {
|
||||
$redirect_url = apply_filters( 'jetpack_client_authorize_already_authorized_url', admin_url() );
|
||||
wp_safe_redirect( $redirect_url );
|
||||
|
||||
return;
|
||||
}
|
||||
do_action( 'jetpack_client_authorize_processing' );
|
||||
|
||||
$data = stripslashes_deep( $_GET );
|
||||
$data['auth_type'] = 'client';
|
||||
$roles = new Roles();
|
||||
$role = $roles->translate_current_user_to_role();
|
||||
$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
|
||||
|
||||
check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" );
|
||||
|
||||
$tracking = new Tracking();
|
||||
|
||||
$result = $this->connection->authorize( $data );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
do_action( 'jetpack_client_authorize_error', $result );
|
||||
|
||||
$tracking->record_user_event(
|
||||
'jpc_client_authorize_fail',
|
||||
array(
|
||||
'error_code' => $result->get_error_code(),
|
||||
'error_message' => $result->get_error_message(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
/**
|
||||
* Fires after the Jetpack client is authorized to communicate with WordPress.com.
|
||||
*
|
||||
* @param int Jetpack Blog ID.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_client_authorized', Jetpack_Options::get_option( 'id' ) );
|
||||
|
||||
$tracking->record_user_event( 'jpc_client_authorize_success' );
|
||||
}
|
||||
|
||||
$fallback_redirect = apply_filters( 'jetpack_client_authorize_fallback_url', admin_url() );
|
||||
$redirect = wp_validate_redirect( $redirect ) ? $redirect : $fallback_redirect;
|
||||
|
||||
wp_safe_redirect( $redirect );
|
||||
}
|
||||
|
||||
/**
|
||||
* The authorhize_redirect webhook handler
|
||||
*/
|
||||
public function handle_authorize_redirect() {
|
||||
$authorize_redirect_handler = new Webhooks\Authorize_Redirect( $this->connection );
|
||||
$authorize_redirect_handler->handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* The `exit` is wrapped into a method so we could mock it.
|
||||
*/
|
||||
protected function do_exit() {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the `connect_url_redirect` action,
|
||||
* which is usually called to repeat an attempt for user to authorize the connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_connect_url_redirect() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
|
||||
$from = ! empty( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : 'iframe';
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- no site changes, sanitization happens in get_authorization_url()
|
||||
$redirect = ! empty( $_GET['redirect_after_auth'] ) ? wp_unslash( $_GET['redirect_after_auth'] ) : false;
|
||||
|
||||
add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
|
||||
|
||||
if ( ! $this->connection->is_user_connected() ) {
|
||||
if ( ! $this->connection->is_connected() ) {
|
||||
$this->connection->register();
|
||||
}
|
||||
|
||||
$connect_url = add_query_arg( 'from', $from, $this->connection->get_authorization_url( null, $redirect ) );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
|
||||
if ( isset( $_GET['notes_iframe'] ) ) {
|
||||
$connect_url .= '¬es_iframe';
|
||||
}
|
||||
wp_safe_redirect( $connect_url );
|
||||
$this->do_exit();
|
||||
} elseif ( ! isset( $_GET['calypso_env'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
|
||||
( new CookieState() )->state( 'message', 'already_authorized' );
|
||||
wp_safe_redirect( $redirect );
|
||||
$this->do_exit();
|
||||
} else {
|
||||
$connect_url = add_query_arg(
|
||||
array(
|
||||
'from' => $from,
|
||||
'already_authorized' => true,
|
||||
),
|
||||
$this->connection->get_authorization_url()
|
||||
);
|
||||
wp_safe_redirect( $connect_url );
|
||||
$this->do_exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* XMLRPC Async Call class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Jetpack_IXR_ClientMulticall;
|
||||
|
||||
/**
|
||||
* Make XMLRPC async calls to WordPress.com
|
||||
*
|
||||
* This class allows you to enqueue XMLRPC calls that will be grouped and sent
|
||||
* at once in a multi-call request at shutdown.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* XMLRPC_Async_Call::add_call( 'methodName', get_current_user_id(), $arg1, $arg2, etc... )
|
||||
*
|
||||
* See XMLRPC_Async_Call::add_call for details
|
||||
*/
|
||||
class XMLRPC_Async_Call {
|
||||
|
||||
/**
|
||||
* Hold the IXR Clients that will be dispatched at shutdown
|
||||
*
|
||||
* Clients are stored in the following schema:
|
||||
* [
|
||||
* $blog_id => [
|
||||
* $user_id => [
|
||||
* arrat of Jetpack_IXR_ClientMulticall
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $clients = array();
|
||||
|
||||
/**
|
||||
* Adds a new XMLRPC call to the queue to be processed on shutdown
|
||||
*
|
||||
* @param string $method The XML-RPC method.
|
||||
* @param integer $user_id The user ID used to make the request (will use this user's token); Use 0 for the blog token.
|
||||
* @param mixed ...$args This function accepts any number of additional arguments, that will be passed to the call.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_call( $method, $user_id = 0, ...$args ) {
|
||||
global $blog_id;
|
||||
|
||||
$client_blog_id = is_multisite() ? $blog_id : 0;
|
||||
|
||||
if ( ! isset( self::$clients[ $client_blog_id ] ) ) {
|
||||
self::$clients[ $client_blog_id ] = array();
|
||||
}
|
||||
|
||||
if ( ! isset( self::$clients[ $client_blog_id ][ $user_id ] ) ) {
|
||||
self::$clients[ $client_blog_id ][ $user_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => $user_id ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'ignore_user_abort' ) ) {
|
||||
ignore_user_abort( true );
|
||||
}
|
||||
|
||||
array_unshift( $args, $method );
|
||||
|
||||
call_user_func_array( array( self::$clients[ $client_blog_id ][ $user_id ], 'addCall' ), $args );
|
||||
|
||||
if ( false === has_action( 'shutdown', array( 'Automattic\Jetpack\Connection\XMLRPC_Async_Call', 'do_calls' ) ) ) {
|
||||
add_action( 'shutdown', array( 'Automattic\Jetpack\Connection\XMLRPC_Async_Call', 'do_calls' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the calls at shutdown
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function do_calls() {
|
||||
foreach ( self::$clients as $client_blog_id => $blog_clients ) {
|
||||
if ( $client_blog_id > 0 ) {
|
||||
$switch_success = switch_to_blog( $client_blog_id );
|
||||
|
||||
if ( ! $switch_success ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $blog_clients as $client ) {
|
||||
if ( empty( $client->calls ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
flush();
|
||||
$client->query();
|
||||
}
|
||||
|
||||
if ( $client_blog_id > 0 ) {
|
||||
restore_current_blog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the Connection XML-RPC methods.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* Registers the XML-RPC methods for Connections.
|
||||
*/
|
||||
class XMLRPC_Connector {
|
||||
/**
|
||||
* The Connection Manager.
|
||||
*
|
||||
* @var Manager
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Manager $connection The Connection Manager.
|
||||
*/
|
||||
public function __construct( Manager $connection ) {
|
||||
$this->connection = $connection;
|
||||
|
||||
// Adding the filter late to avoid being overwritten by Jetpack's XMLRPC server.
|
||||
add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attached to the `xmlrpc_methods` filter.
|
||||
*
|
||||
* @param array $methods The already registered XML-RPC methods.
|
||||
* @return array
|
||||
*/
|
||||
public function xmlrpc_methods( $methods ) {
|
||||
return array_merge(
|
||||
$methods,
|
||||
array(
|
||||
'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles verification that a site is registered.
|
||||
*
|
||||
* @param array $registration_data The data sent by the XML-RPC client:
|
||||
* [ $secret_1, $user_id ].
|
||||
*
|
||||
* @return string|IXR_Error
|
||||
*/
|
||||
public function verify_registration( $registration_data ) {
|
||||
return $this->output( $this->connection->handle_registration( $registration_data ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes output for XML-RPC.
|
||||
*
|
||||
* @param mixed $data The data to output.
|
||||
*/
|
||||
private function output( $data ) {
|
||||
if ( is_wp_error( $data ) ) {
|
||||
$code = $data->get_error_data();
|
||||
if ( ! $code ) {
|
||||
$code = -10520;
|
||||
}
|
||||
|
||||
if ( ! class_exists( \IXR_Error::class ) ) {
|
||||
require_once ABSPATH . WPINC . '/class-IXR.php';
|
||||
}
|
||||
return new \IXR_Error(
|
||||
$code,
|
||||
sprintf( 'Jetpack: [%s] %s', $data->get_error_code(), $data->get_error_message() )
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection Interface file.
|
||||
* No longer used.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
/**
|
||||
* This interface is no longer used and is now deprecated.
|
||||
*
|
||||
* @deprecated since jetpack 7.8
|
||||
*/
|
||||
interface Manager_Interface {
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Authorize_Redirect Webhook handler class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection\Webhooks;
|
||||
|
||||
use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Licensing;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use GP_Locales;
|
||||
use Jetpack_Network;
|
||||
|
||||
/**
|
||||
* Authorize_Redirect Webhook handler class.
|
||||
*/
|
||||
class Authorize_Redirect {
|
||||
|
||||
/**
|
||||
* Constructs the object
|
||||
*
|
||||
* @param Automattic\Jetpack\Connection\Manager $connection The Connection Manager object.
|
||||
*/
|
||||
public function __construct( $connection ) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the webhook
|
||||
*
|
||||
* This method implements what's in Jetpack::admin_page_load when the Jetpack plugin is not present
|
||||
*/
|
||||
public function handle() {
|
||||
|
||||
add_filter(
|
||||
'allowed_redirect_hosts',
|
||||
function ( $domains ) {
|
||||
$domains[] = 'jetpack.com';
|
||||
$domains[] = 'jetpack.wordpress.com';
|
||||
$domains[] = 'wordpress.com';
|
||||
// Calypso envs.
|
||||
$domains[] = 'calypso.localhost';
|
||||
$domains[] = 'wpcalypso.wordpress.com';
|
||||
$domains[] = 'horizon.wordpress.com';
|
||||
return array_unique( $domains );
|
||||
}
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$dest_url = empty( $_GET['dest_url'] ) ? null : esc_url_raw( wp_unslash( $_GET['dest_url'] ) );
|
||||
|
||||
if ( ! $dest_url || ( 0 === stripos( $dest_url, 'https://jetpack.com/' ) && 0 === stripos( $dest_url, 'https://wordpress.com/' ) ) ) {
|
||||
// The destination URL is missing or invalid, nothing to do here.
|
||||
exit;
|
||||
}
|
||||
|
||||
// The user is either already connected, or finished the connection process.
|
||||
if ( $this->connection->is_connected() && $this->connection->is_user_connected() ) {
|
||||
if ( class_exists( '\Automattic\Jetpack\Licensing' ) && method_exists( '\Automattic\Jetpack\Licensing', 'handle_user_connected_redirect' ) ) {
|
||||
Licensing::instance()->handle_user_connected_redirect( $dest_url );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $dest_url );
|
||||
exit;
|
||||
} elseif ( ! empty( $_GET['done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// The user decided not to proceed with setting up the connection.
|
||||
|
||||
wp_safe_redirect( Admin_Menu::get_top_level_menu_item_url() );
|
||||
exit;
|
||||
}
|
||||
|
||||
$redirect_args = array(
|
||||
'page' => 'jetpack',
|
||||
'action' => 'authorize_redirect',
|
||||
'dest_url' => rawurlencode( $dest_url ),
|
||||
'done' => '1',
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_GET['from'] ) && 'jetpack_site_only_checkout' === $_GET['from'] ) {
|
||||
$redirect_args['from'] = 'jetpack_site_only_checkout';
|
||||
}
|
||||
|
||||
wp_safe_redirect( $this->build_authorize_url( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Jetpack authorization URL. Copied from Jetpack class.
|
||||
*
|
||||
* @param bool|string $redirect URL to redirect to.
|
||||
*
|
||||
* @todo Update default value for redirect since the called function expects a string.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function build_authorize_url( $redirect = false ) {
|
||||
|
||||
add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
|
||||
add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
|
||||
|
||||
$url = $this->connection->get_authorization_url( wp_get_current_user(), $redirect );
|
||||
|
||||
remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
|
||||
remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
|
||||
|
||||
/**
|
||||
* This filter is documented in plugins/jetpack/class-jetpack.php
|
||||
*/
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the redirection URL that is used for connect requests. The redirect
|
||||
* URL should return the user back to the Jetpack console.
|
||||
* Copied from Jetpack class.
|
||||
*
|
||||
* @param String $redirect the default redirect URL used by the package.
|
||||
* @return String the modified URL.
|
||||
*/
|
||||
public static function filter_connect_redirect_url( $redirect ) {
|
||||
$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
|
||||
$redirect = $redirect
|
||||
? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
|
||||
: $jetpack_admin_page;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_REQUEST['is_multisite'] ) ) {
|
||||
$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the connection URL parameter array.
|
||||
* Copied from Jetpack class.
|
||||
*
|
||||
* @param array $args default URL parameters used by the package.
|
||||
* @return array the modified URL arguments array.
|
||||
*/
|
||||
public static function filter_connect_request_body( $args ) {
|
||||
if (
|
||||
Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
|
||||
&& include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
|
||||
) {
|
||||
$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
|
||||
$args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
|
||||
? $gp_locale->slug
|
||||
: '';
|
||||
}
|
||||
|
||||
$tracking = new Tracking();
|
||||
$tracks_identity = $tracking->tracks_get_identity( $args['state'] );
|
||||
|
||||
$args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'_ui' => $tracks_identity['_ui'],
|
||||
'_ut' => $tracks_identity['_ut'],
|
||||
)
|
||||
);
|
||||
|
||||
$calypso_env = self::get_calypso_env();
|
||||
|
||||
if ( ! empty( $calypso_env ) ) {
|
||||
$args['calypso_env'] = $calypso_env;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Calypso environment value; used for developing Jetpack and pairing
|
||||
* it with different Calypso enrionments, such as localhost.
|
||||
* Copied from Jetpack class.
|
||||
*
|
||||
* @since 1.37.1
|
||||
*
|
||||
* @return string Calypso environment
|
||||
*/
|
||||
public static function get_calypso_env() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['calypso_env'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return sanitize_key( $_GET['calypso_env'] );
|
||||
}
|
||||
|
||||
if ( getenv( 'CALYPSO_ENV' ) ) {
|
||||
return sanitize_key( getenv( 'CALYPSO_ENV' ) );
|
||||
}
|
||||
|
||||
if ( defined( 'CALYPSO_ENV' ) && CALYPSO_ENV ) {
|
||||
return sanitize_key( CALYPSO_ENV );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user