installed plugin Jetpack Protect
version 1.0.2
This commit is contained in:
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* IXR_Client
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 1.5
|
||||
* @since-jetpack 7.7 Moved to the jetpack-connection package.
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
|
||||
if ( ! class_exists( IXR_Client::class ) ) {
|
||||
require_once ABSPATH . WPINC . '/class-IXR.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A Jetpack implementation of the WordPress core IXR client.
|
||||
*/
|
||||
class Jetpack_IXR_Client extends IXR_Client {
|
||||
/**
|
||||
* Jetpack args, used for the remote requests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $jetpack_args = null;
|
||||
|
||||
/**
|
||||
* Remote Response Headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $response_headers = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Initialize a new Jetpack IXR client instance.
|
||||
*
|
||||
* @param array $args Jetpack args, used for the remote requests.
|
||||
* @param string|bool $path Path to perform the reuqest to.
|
||||
* @param int $port Port number.
|
||||
* @param int $timeout The connection timeout, in seconds.
|
||||
*/
|
||||
public function __construct( $args = array(), $path = false, $port = 80, $timeout = 15 ) {
|
||||
$connection = new Manager();
|
||||
|
||||
$defaults = array(
|
||||
'url' => $connection->xmlrpc_api_url(),
|
||||
'user_id' => 0,
|
||||
'headers' => array(),
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
$args['headers'] = array_merge( array( 'Content-Type' => 'text/xml' ), (array) $args['headers'] );
|
||||
|
||||
$this->jetpack_args = $args;
|
||||
|
||||
$this->IXR_Client( $args['url'], $path, $port, $timeout );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the IXR request.
|
||||
*
|
||||
* @param string[] ...$args IXR args.
|
||||
*
|
||||
* @return bool True if request succeeded, false otherwise.
|
||||
*/
|
||||
public function query( ...$args ) {
|
||||
$method = array_shift( $args );
|
||||
$request = new IXR_Request( $method, $args );
|
||||
$xml = trim( $request->getXml() );
|
||||
|
||||
$response = Client::remote_request( $this->jetpack_args, $xml );
|
||||
|
||||
// Store response headers.
|
||||
$this->response_headers = wp_remote_retrieve_headers( $response );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->error = new IXR_Error( -10520, sprintf( 'Jetpack: [%s] %s', $response->get_error_code(), $response->get_error_message() ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $response ) {
|
||||
$this->error = new IXR_Error( -10520, 'Jetpack: Unknown Error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
$this->error = new IXR_Error( -32300, 'transport error - HTTP status code was not 200' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = wp_remote_retrieve_body( $response );
|
||||
|
||||
// Now parse what we've got back.
|
||||
$this->message = new IXR_Message( $content );
|
||||
if ( ! $this->message->parse() ) {
|
||||
// XML error.
|
||||
$this->error = new IXR_Error( -32700, 'parse error. not well formed' );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the message a fault?
|
||||
if ( 'fault' === $this->message->messageType ) {
|
||||
$this->error = new IXR_Error( $this->message->faultCode, $this->message->faultString );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Message must be OK.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Jetpack error from the result of the last request.
|
||||
*
|
||||
* @param int $fault_code Fault code.
|
||||
* @param string $fault_string Fault string.
|
||||
* @return WP_Error Error object.
|
||||
*/
|
||||
public function get_jetpack_error( $fault_code = null, $fault_string = null ) {
|
||||
if ( $fault_code === null ) {
|
||||
$fault_code = $this->error->code;
|
||||
}
|
||||
|
||||
if ( $fault_string === null ) {
|
||||
$fault_string = $this->error->message;
|
||||
}
|
||||
|
||||
if ( preg_match( '#jetpack:\s+\[(\w+)\]\s*(.*)?$#i', $fault_string, $match ) ) {
|
||||
$code = $match[1];
|
||||
$message = $match[2];
|
||||
$status = $fault_code;
|
||||
return new \WP_Error( $code, $message, $status );
|
||||
}
|
||||
|
||||
return new \WP_Error( "IXR_{$fault_code}", $fault_string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a response header if set.
|
||||
*
|
||||
* @param string $name header name.
|
||||
* @return string|bool Header value if set, false if not set.
|
||||
*/
|
||||
public function get_response_header( $name ) {
|
||||
if ( isset( $this->response_headers[ $name ] ) ) {
|
||||
return $this->response_headers[ $name ];
|
||||
}
|
||||
// case-insensitive header names: http://www.ietf.org/rfc/rfc2616.txt.
|
||||
if ( isset( $this->response_headers[ strtolower( $name ) ] ) ) {
|
||||
return $this->response_headers[ strtolower( $name ) ];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* IXR_ClientMulticall
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 1.5
|
||||
* @since-jetpack 7.7 Moved to the jetpack-connection package.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Jetpack implementation of the WordPress core IXR client, capable of multiple calls in a single request.
|
||||
*/
|
||||
class Jetpack_IXR_ClientMulticall extends Jetpack_IXR_Client {
|
||||
/**
|
||||
* Storage for the IXR calls.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $calls = array();
|
||||
|
||||
/**
|
||||
* Add a IXR call to the client.
|
||||
* First argument is the method name.
|
||||
* The rest of the arguments are the params specified to the method.
|
||||
*
|
||||
* @param string[] ...$args IXR args.
|
||||
*/
|
||||
public function addCall( ...$args ) {
|
||||
$method_name = array_shift( $args );
|
||||
$struct = array(
|
||||
'methodName' => $method_name,
|
||||
'params' => $args,
|
||||
);
|
||||
$this->calls[] = $struct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the IXR multicall request.
|
||||
*
|
||||
* @param string[] ...$args IXR args.
|
||||
*
|
||||
* @return bool True if request succeeded, false otherwise.
|
||||
*/
|
||||
public function query( ...$args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$this->calls = $this->sort_calls( $this->calls );
|
||||
|
||||
// Prepare multicall, then call the parent::query() method.
|
||||
return parent::query( 'system.multicall', $this->calls );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the IXR calls.
|
||||
* Make sure syncs are always done first preserving relative order.
|
||||
*
|
||||
* @param array $calls Calls to sort.
|
||||
* @return array Sorted calls.
|
||||
*/
|
||||
public function sort_calls( $calls ) {
|
||||
$sync_calls = array();
|
||||
$other_calls = array();
|
||||
|
||||
foreach ( $calls as $call ) {
|
||||
if ( 'jetpack.syncContent' === $call['methodName'] ) {
|
||||
$sync_calls[] = $call;
|
||||
} else {
|
||||
$other_calls[] = $call;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge( $sync_calls, $other_calls );
|
||||
}
|
||||
}
|
@ -0,0 +1,692 @@
|
||||
<?php
|
||||
/**
|
||||
* Legacy Jetpack_Options class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* Class Jetpack_Options
|
||||
*/
|
||||
class Jetpack_Options {
|
||||
|
||||
/**
|
||||
* An array that maps a grouped option type to an option name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $grouped_options = array(
|
||||
'compact' => 'jetpack_options',
|
||||
'private' => 'jetpack_private_options',
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns an array of option names for a given type.
|
||||
*
|
||||
* @param string $type The type of option to return. Defaults to 'compact'.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_option_names( $type = 'compact' ) {
|
||||
switch ( $type ) {
|
||||
case 'non-compact':
|
||||
case 'non_compact':
|
||||
return array(
|
||||
'activated',
|
||||
'active_modules',
|
||||
'active_modules_initialized', // (bool) used to determine that all the default modules were activated, so we know how to act on a reconnection.
|
||||
'allowed_xsite_search_ids', // (array) Array of WP.com blog ids that are allowed to search the content of this site
|
||||
'available_modules',
|
||||
'do_activate',
|
||||
'edit_links_calypso_redirect', // (bool) Whether post/page edit links on front end should point to Calypso.
|
||||
'log',
|
||||
'slideshow_background_color',
|
||||
'widget_twitter',
|
||||
'wpcc_options',
|
||||
'relatedposts',
|
||||
'file_data',
|
||||
'autoupdate_plugins', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated
|
||||
'autoupdate_plugins_translations', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated translation files.
|
||||
'autoupdate_themes', // (array) An array of theme ids ( eg. twentyfourteen ) that should be autoupdated
|
||||
'autoupdate_themes_translations', // (array) An array of theme ids ( eg. twentyfourteen ) that should autoupdated translation files.
|
||||
'autoupdate_core', // (bool) Whether or not to autoupdate core
|
||||
'autoupdate_translations', // (bool) Whether or not to autoupdate all translations
|
||||
'json_api_full_management', // (bool) Allow full management (eg. Activate, Upgrade plugins) of the site via the JSON API.
|
||||
'sync_non_public_post_stati', // (bool) Allow synchronisation of posts and pages with non-public status.
|
||||
'site_icon_url', // (string) url to the full site icon
|
||||
'site_icon_id', // (int) Attachment id of the site icon file
|
||||
'dismissed_manage_banner', // (bool) Dismiss Jetpack manage banner allows the user to dismiss the banner permanently
|
||||
'unique_connection', // (array) A flag to determine a unique connection to wordpress.com two values "connected" and "disconnected" with values for how many times each has occured
|
||||
'unique_registrations', // (integer) A counter of how many times the site was registered
|
||||
'protect_whitelist', // (array) IP Address for the Protect module to ignore
|
||||
'sync_error_idc', // (bool|array) false or array containing the site's home and siteurl at time of IDC error
|
||||
'sync_health_status', // (bool|array) An array of data relating to Jetpack's sync health.
|
||||
'safe_mode_confirmed', // (bool) True if someone confirms that this site was correctly put into safe mode automatically after an identity crisis is discovered.
|
||||
'migrate_for_idc', // (bool) True if someone confirms that this site should migrate stats and subscribers from its previous URL
|
||||
'dismissed_connection_banner', // (bool) True if the connection banner has been dismissed
|
||||
'ab_connect_banner_green_bar', // (int) Version displayed of the A/B test for the green bar at the top of the connect banner.
|
||||
'onboarding', // (string) Auth token to be used in the onboarding connection flow
|
||||
'tos_agreed', // (bool) Whether or not the TOS for connection has been agreed upon.
|
||||
'static_asset_cdn_files', // (array) An nested array of files that we can swap out for cdn versions.
|
||||
'mapbox_api_key', // (string) Mapbox API Key, for use with Map block.
|
||||
'mailchimp', // (string) Mailchimp keyring data, for mailchimp block.
|
||||
'xmlrpc_errors', // (array) Keys are XML-RPC signature error codes. Values are truthy.
|
||||
'dismissed_wizard_banner', // (int) (DEPRECATED) True if the Wizard banner has been dismissed.
|
||||
);
|
||||
|
||||
case 'private':
|
||||
return array(
|
||||
'blog_token', // (string) The Client Secret/Blog Token of this site.
|
||||
'user_token', // (string) The User Token of this site. (deprecated)
|
||||
'user_tokens', // (array) User Tokens for each user of this site who has connected to jetpack.wordpress.com.
|
||||
'purchase_token', // (string) Token for logged out user purchases.
|
||||
'token_lock', // (string) Token lock in format `expiration_date|||site_url`.
|
||||
);
|
||||
|
||||
case 'network':
|
||||
return array(
|
||||
'onboarding', // (string) Auth token to be used in the onboarding connection flow
|
||||
'file_data', // (array) List of absolute paths to all Jetpack modules
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'id', // (int) The Client ID/WP.com Blog ID of this site.
|
||||
'publicize_connections', // (array) An array of Publicize connections from WordPress.com.
|
||||
'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
|
||||
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time.
|
||||
'old_version', // (string) Used to determine which modules are the most recently added. previous_version:time.
|
||||
'fallback_no_verify_ssl_certs', // (int) Flag for determining if this host must skip SSL Certificate verification due to misconfigured SSL.
|
||||
'time_diff', // (int) Offset between Jetpack server's clocks and this server's clocks. Jetpack Server Time = time() + (int) Jetpack_Options::get_option( 'time_diff' )
|
||||
'public', // (int|bool) If we think this site is public or not (1, 0), false if we haven't yet tried to figure it out.
|
||||
'videopress', // (array) VideoPress options array.
|
||||
'is_network_site', // (int|bool) If we think this site is a network or a single blog (1, 0), false if we haven't yet tried to figue it out.
|
||||
'social_links', // (array) The specified links for each social networking site.
|
||||
'identity_crisis_whitelist', // (array) An array of options, each having an array of the values whitelisted for it.
|
||||
'gplus_authors', // (array) The Google+ authorship information for connected users.
|
||||
'last_heartbeat', // (int) The timestamp of the last heartbeat that fired.
|
||||
'hide_jitm', // (array) A list of just in time messages that we should not show because they have been dismissed by the user.
|
||||
'custom_css_4.7_migration', // (bool) Whether Custom CSS has scanned for and migrated any legacy CSS CPT entries to the new Core format.
|
||||
'image_widget_migration', // (bool) Whether any legacy Image Widgets have been converted to the new Core widget.
|
||||
'gallery_widget_migration', // (bool) Whether any legacy Gallery Widgets have been converted to the new Core widget.
|
||||
'sso_first_login', // (bool) Is this the first time the user logins via SSO.
|
||||
'dismissed_hints', // (array) Part of Plugin Search Hints. List of cards that have been dismissed.
|
||||
'first_admin_view', // (bool) Set to true the first time the user views the admin. Usually after the initial connection.
|
||||
'setup_wizard_questionnaire', // (array) (DEPRECATED) List of user choices from the setup wizard.
|
||||
'setup_wizard_status', // (string) (DEPRECATED) Status of the setup wizard.
|
||||
'licensing_error', // (string) Last error message occurred while attaching licenses that is yet to be surfaced to the user.
|
||||
'recommendations_banner_dismissed', // (bool) Determines if the recommendations dashboard banner is dismissed or not.
|
||||
'recommendations_banner_enabled', // (bool) Whether the recommendations are enabled or not.
|
||||
'recommendations_data', // (array) The user choice and other data for the recommendations.
|
||||
'recommendations_step', // (string) The current step of the recommendations.
|
||||
'recommendations_conditional', // (array) An array of action-based recommendations.
|
||||
'licensing_activation_notice_dismiss', // (array) The `last_detached_count` and the `last_dismissed_time` for the user-license activation notice.
|
||||
'has_seen_wc_connection_modal', // (bool) Whether the site has displayed the WooCommerce Connection modal
|
||||
'partner_coupon', // (string) A Jetpack partner issued coupon to promote a sale together with Jetpack.
|
||||
'partner_coupon_added', // (string) A date for when `partner_coupon` was added, so we can auto-purge after a certain time interval.
|
||||
'dismissed_backup_review_request', // (bool) Determines if the plugin review request is dismissed.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the option name valid?
|
||||
*
|
||||
* @param string $name The name of the option.
|
||||
* @param string|null $group The name of the group that the option is in. Default to null, which will search non_compact.
|
||||
*
|
||||
* @return bool Is the option name valid?
|
||||
*/
|
||||
public static function is_valid( $name, $group = null ) {
|
||||
if ( is_array( $name ) ) {
|
||||
$compact_names = array();
|
||||
foreach ( array_keys( self::$grouped_options ) as $_group ) {
|
||||
$compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
|
||||
}
|
||||
|
||||
$result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
|
||||
|
||||
return empty( $result );
|
||||
}
|
||||
|
||||
if ( $group === null || 'non_compact' === $group ) {
|
||||
if ( in_array( $name, self::get_option_names( $group ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$grouped_options ) as $_group ) {
|
||||
if ( $group === null || $group === $_group ) {
|
||||
if ( in_array( $name, self::get_option_names( $_group ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an option must be saved for the whole network in WP Multisite
|
||||
*
|
||||
* @param string $option_name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_network_option( $option_name ) {
|
||||
if ( ! is_multisite() ) {
|
||||
return false;
|
||||
}
|
||||
return in_array( $option_name, self::get_option_names( 'network' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the requested option.
|
||||
* This is a wrapper around `get_option_from_database` so that we can filter the option.
|
||||
*
|
||||
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
|
||||
* @param mixed $default (optional).
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_option( $name, $default = false ) {
|
||||
/**
|
||||
* Filter Jetpack Options.
|
||||
* Can be useful in environments when Jetpack is running with a different setup
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @param string $value The value from the database.
|
||||
* @param string $name Option name, _without_ `jetpack_%` prefix.
|
||||
* @return string $value, unless the filters modify it.
|
||||
*/
|
||||
return apply_filters( 'jetpack_options', self::get_option_from_database( $name, $default ), $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
|
||||
*
|
||||
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
|
||||
* @param mixed $default (optional).
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function get_option_from_database( $name, $default = false ) {
|
||||
if ( self::is_valid( $name, 'non_compact' ) ) {
|
||||
if ( self::is_network_option( $name ) ) {
|
||||
return get_site_option( "jetpack_$name", $default );
|
||||
}
|
||||
|
||||
return get_option( "jetpack_$name", $default );
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$grouped_options ) as $group ) {
|
||||
if ( self::is_valid( $name, $group ) ) {
|
||||
return self::get_grouped_option( $group, $name, $default );
|
||||
}
|
||||
}
|
||||
|
||||
trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't wish to change legacy behavior.
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested option, and ensures it's autoloaded in the future.
|
||||
* This does _not_ adjust the prefix in any way (does not prefix jetpack_%)
|
||||
*
|
||||
* @param string $name Option name.
|
||||
* @param mixed $default (optional).
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_option_and_ensure_autoload( $name, $default ) {
|
||||
// In this function the name is not adjusted by prefixing jetpack_
|
||||
// so if it has already prefixed, we'll replace it and then
|
||||
// check if the option name is a network option or not.
|
||||
$jetpack_name = preg_replace( '/^jetpack_/', '', $name, 1 );
|
||||
$is_network_option = self::is_network_option( $jetpack_name );
|
||||
$value = $is_network_option ? get_site_option( $name ) : get_option( $name );
|
||||
|
||||
if ( false === $value && false !== $default ) {
|
||||
if ( $is_network_option ) {
|
||||
add_site_option( $name, $default );
|
||||
} else {
|
||||
add_option( $name, $default );
|
||||
}
|
||||
$value = $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update grouped option
|
||||
*
|
||||
* @param string $group Options group.
|
||||
* @param string $name Options name.
|
||||
* @param mixed $value Options value.
|
||||
*
|
||||
* @return bool Success or failure.
|
||||
*/
|
||||
private static function update_grouped_option( $group, $name, $value ) {
|
||||
$options = get_option( self::$grouped_options[ $group ] );
|
||||
if ( ! is_array( $options ) ) {
|
||||
$options = array();
|
||||
}
|
||||
$options[ $name ] = $value;
|
||||
|
||||
return update_option( self::$grouped_options[ $group ], $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the single given option. Updates jetpack_options or jetpack_$name as appropriate.
|
||||
*
|
||||
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
|
||||
* @param mixed $value Option value.
|
||||
* @param string $autoload If not compact option, allows specifying whether to autoload or not.
|
||||
*
|
||||
* @return bool Was the option successfully updated?
|
||||
*/
|
||||
public static function update_option( $name, $value, $autoload = null ) {
|
||||
/**
|
||||
* Fires before Jetpack updates a specific option.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 3.0.0
|
||||
*
|
||||
* @param str $name The name of the option being updated.
|
||||
* @param mixed $value The new value of the option.
|
||||
*/
|
||||
do_action( 'pre_update_jetpack_option_' . $name, $name, $value );
|
||||
if ( self::is_valid( $name, 'non_compact' ) ) {
|
||||
if ( self::is_network_option( $name ) ) {
|
||||
return update_site_option( "jetpack_$name", $value );
|
||||
}
|
||||
|
||||
return update_option( "jetpack_$name", $value, $autoload );
|
||||
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$grouped_options ) as $group ) {
|
||||
if ( self::is_valid( $name, $group ) ) {
|
||||
return self::update_grouped_option( $group, $name, $value );
|
||||
}
|
||||
}
|
||||
|
||||
trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't want to change legacy behavior.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the multiple given options. Updates jetpack_options and/or jetpack_$name as appropriate.
|
||||
*
|
||||
* @param array $array array( option name => option value, ... ).
|
||||
*/
|
||||
public static function update_options( $array ) {
|
||||
$names = array_keys( $array );
|
||||
|
||||
foreach ( array_diff( $names, self::get_option_names(), self::get_option_names( 'non_compact' ), self::get_option_names( 'private' ) ) as $unknown_name ) {
|
||||
trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $unknown_name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't change legacy behavior.
|
||||
unset( $array[ $unknown_name ] );
|
||||
}
|
||||
|
||||
foreach ( $names as $name ) {
|
||||
self::update_option( $name, $array[ $name ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given option. May be passed multiple option names as an array.
|
||||
* Updates jetpack_options and/or deletes jetpack_$name as appropriate.
|
||||
*
|
||||
* @param string|array $names Option names. They must come _without_ `jetpack_%` prefix. The method will prefix the option names.
|
||||
*
|
||||
* @return bool Was the option successfully deleted?
|
||||
*/
|
||||
public static function delete_option( $names ) {
|
||||
$result = true;
|
||||
$names = (array) $names;
|
||||
|
||||
if ( ! self::is_valid( $names ) ) {
|
||||
// phpcs:disable -- This line triggers a handful of errors; ignoring to avoid changing legacy behavior.
|
||||
trigger_error( sprintf( 'Invalid Jetpack option names: %s', print_r( $names, 1 ) ), E_USER_WARNING );
|
||||
// phpcs:enable
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
|
||||
if ( self::is_network_option( $name ) ) {
|
||||
$result = delete_site_option( "jetpack_$name" );
|
||||
} else {
|
||||
$result = delete_option( "jetpack_$name" );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$grouped_options ) as $group ) {
|
||||
if ( ! self::delete_grouped_option( $group, $names ) ) {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group option.
|
||||
*
|
||||
* @param string $group Option group name.
|
||||
* @param string $name Option name.
|
||||
* @param mixed $default Default option value.
|
||||
*
|
||||
* @return mixed Option.
|
||||
*/
|
||||
private static function get_grouped_option( $group, $name, $default ) {
|
||||
$options = get_option( self::$grouped_options[ $group ] );
|
||||
if ( is_array( $options ) && isset( $options[ $name ] ) ) {
|
||||
return $options[ $name ];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete grouped option.
|
||||
*
|
||||
* @param string $group Option group name.
|
||||
* @param array $names Option names.
|
||||
*
|
||||
* @return bool Success or failure.
|
||||
*/
|
||||
private static function delete_grouped_option( $group, $names ) {
|
||||
$options = get_option( self::$grouped_options[ $group ], array() );
|
||||
|
||||
$to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
|
||||
if ( $to_delete ) {
|
||||
foreach ( $to_delete as $name ) {
|
||||
unset( $options[ $name ] );
|
||||
}
|
||||
|
||||
return update_option( self::$grouped_options[ $group ], $options );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Raw option methods allow Jetpack to get / update / delete options via direct DB queries, including options
|
||||
* that are not created by the Jetpack plugin. This is helpful only in rare cases when we need to bypass
|
||||
* cache and filters.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Deletes an option via $wpdb query.
|
||||
*
|
||||
* @param string $name Option name.
|
||||
*
|
||||
* @return bool Is the option deleted?
|
||||
*/
|
||||
public static function delete_raw_option( $name ) {
|
||||
if ( self::bypass_raw_option( $name ) ) {
|
||||
return delete_option( $name );
|
||||
}
|
||||
global $wpdb;
|
||||
$result = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name = %s", $name ) );
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an option via $wpdb query.
|
||||
*
|
||||
* @param string $name Option name.
|
||||
* @param mixed $value Option value.
|
||||
* @param bool $autoload Specifying whether to autoload or not.
|
||||
*
|
||||
* @return bool Is the option updated?
|
||||
*/
|
||||
public static function update_raw_option( $name, $value, $autoload = false ) {
|
||||
if ( self::bypass_raw_option( $name ) ) {
|
||||
return update_option( $name, $value, $autoload );
|
||||
}
|
||||
global $wpdb;
|
||||
$autoload_value = $autoload ? 'yes' : 'no';
|
||||
|
||||
$old_value = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
|
||||
$name
|
||||
)
|
||||
);
|
||||
if ( $old_value === $value ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$serialized_value = maybe_serialize( $value );
|
||||
// below we used "insert ignore" to at least suppress the resulting error.
|
||||
$updated_num = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
|
||||
$serialized_value,
|
||||
$name
|
||||
)
|
||||
);
|
||||
|
||||
// Try inserting the option if the value doesn't exits.
|
||||
if ( ! $updated_num ) {
|
||||
$updated_num = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, %s )",
|
||||
$name,
|
||||
$serialized_value,
|
||||
$autoload_value
|
||||
)
|
||||
);
|
||||
}
|
||||
return (bool) $updated_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an option via $wpdb query.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @param string $name Option name.
|
||||
* @param mixed $default Default option value if option is not found.
|
||||
*
|
||||
* @return mixed Option value, or null if option is not found and default is not specified.
|
||||
*/
|
||||
public static function get_raw_option( $name, $default = null ) {
|
||||
if ( self::bypass_raw_option( $name ) ) {
|
||||
return get_option( $name, $default );
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$value = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
|
||||
$name
|
||||
)
|
||||
);
|
||||
$value = maybe_unserialize( $value );
|
||||
|
||||
if ( null === $value && null !== $default ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks for a constant that, if present, will disable direct DB queries Jetpack uses to manage certain options and force Jetpack to always use Options API instead.
|
||||
* Options can be selectively managed via a blocklist by filtering option names via the jetpack_disabled_raw_option filter.
|
||||
*
|
||||
* @param string $name Option name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function bypass_raw_option( $name ) {
|
||||
|
||||
if ( Constants::get_constant( 'JETPACK_DISABLE_RAW_OPTIONS' ) ) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Allows to disable particular raw options.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 5.5.0
|
||||
*
|
||||
* @param array $disabled_raw_options An array of option names that you can selectively blocklist from being managed via direct database queries.
|
||||
*/
|
||||
$disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
|
||||
return isset( $disabled_raw_options[ $name ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all known options that are used by Jetpack and managed by Jetpack_Options.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @param boolean $strip_unsafe_options If true, and by default, will strip out options necessary for the connection to WordPress.com.
|
||||
* @return array An array of all options managed via the Jetpack_Options class.
|
||||
*/
|
||||
public static function get_all_jetpack_options( $strip_unsafe_options = true ) {
|
||||
$jetpack_options = self::get_option_names();
|
||||
$jetpack_options_non_compat = self::get_option_names( 'non_compact' );
|
||||
$jetpack_options_private = self::get_option_names( 'private' );
|
||||
|
||||
$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
|
||||
|
||||
if ( $strip_unsafe_options ) {
|
||||
// Flag some Jetpack options as unsafe.
|
||||
$unsafe_options = array(
|
||||
'id', // (int) The Client ID/WP.com Blog ID of this site.
|
||||
'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
|
||||
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time
|
||||
|
||||
// non_compact.
|
||||
'activated',
|
||||
|
||||
// private.
|
||||
'register',
|
||||
'blog_token', // (string) The Client Secret/Blog Token of this site.
|
||||
'user_token', // (string) The User Token of this site. (deprecated)
|
||||
'user_tokens',
|
||||
);
|
||||
|
||||
// Remove the unsafe Jetpack options.
|
||||
foreach ( $unsafe_options as $unsafe_option ) {
|
||||
$key = array_search( $unsafe_option, $all_jp_options, true );
|
||||
if ( false !== $key ) {
|
||||
unset( $all_jp_options[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $all_jp_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all options that are not managed by the Jetpack_Options class that are used by Jetpack.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_wp_options() {
|
||||
// A manual build of the wp options.
|
||||
return array(
|
||||
'sharing-options',
|
||||
'disabled_likes',
|
||||
'disabled_reblogs',
|
||||
'jetpack_comments_likes_enabled',
|
||||
'stats_options',
|
||||
'stats_dashboard_widget',
|
||||
'safecss_preview_rev',
|
||||
'safecss_rev',
|
||||
'safecss_revision_migrated',
|
||||
'nova_menu_order',
|
||||
'jetpack_portfolio',
|
||||
'jetpack_portfolio_posts_per_page',
|
||||
'jetpack_testimonial',
|
||||
'jetpack_testimonial_posts_per_page',
|
||||
'sharedaddy_disable_resources',
|
||||
'sharing-options',
|
||||
'sharing-services',
|
||||
'site_icon_temp_data',
|
||||
'featured-content',
|
||||
'site_logo',
|
||||
'jetpack_dismissed_notices',
|
||||
'jetpack-twitter-cards-site-tag',
|
||||
'jetpack-sitemap-state',
|
||||
'jetpack_sitemap_post_types',
|
||||
'jetpack_sitemap_location',
|
||||
'jetpack_protect_key',
|
||||
'jetpack_protect_blocked_attempts',
|
||||
'jetpack_protect_activating',
|
||||
'jetpack_connection_banner_ab',
|
||||
'jetpack_active_plan',
|
||||
'jetpack_activation_source',
|
||||
'jetpack_site_products',
|
||||
'jetpack_sso_match_by_email',
|
||||
'jetpack_sso_require_two_step',
|
||||
'jetpack_sso_remove_login_form',
|
||||
'jetpack_last_connect_url_check',
|
||||
'jpo_business_address',
|
||||
'jpo_site_type',
|
||||
'jpo_homepage_format',
|
||||
'jpo_contact_page',
|
||||
'jetpack_excluded_extensions',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all options that can be safely reset by CLI.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @return array array Associative array containing jp_options which are managed by the Jetpack_Options class and wp_options which are not.
|
||||
*/
|
||||
public static function get_options_for_reset() {
|
||||
$all_jp_options = self::get_all_jetpack_options();
|
||||
|
||||
$wp_options = self::get_all_wp_options();
|
||||
|
||||
$options = array(
|
||||
'jp_options' => $all_jp_options,
|
||||
'wp_options' => $wp_options,
|
||||
);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all known options
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_all_known_options() {
|
||||
// Delete all compact options.
|
||||
foreach ( (array) self::$grouped_options as $option_name ) {
|
||||
delete_option( $option_name );
|
||||
}
|
||||
|
||||
// Delete all non-compact Jetpack options.
|
||||
foreach ( (array) self::get_option_names( 'non-compact' ) as $option_name ) {
|
||||
self::delete_option( $option_name );
|
||||
}
|
||||
|
||||
// Delete all options that can be reset via CLI, that aren't Jetpack options.
|
||||
foreach ( (array) self::get_all_wp_options() as $option_name ) {
|
||||
delete_option( $option_name );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,405 @@
|
||||
<?php
|
||||
/**
|
||||
* The Jetpack Connection signature class file.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
|
||||
/**
|
||||
* The Jetpack Connection signature class that is used to sign requests.
|
||||
*/
|
||||
class Jetpack_Signature {
|
||||
/**
|
||||
* Token part of the access token.
|
||||
*
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* Access token secret.
|
||||
*
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public $secret;
|
||||
|
||||
/**
|
||||
* The current request URL.
|
||||
*
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public $current_request_url;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $access_token Access token.
|
||||
* @param int $time_diff Timezone difference (in seconds).
|
||||
*/
|
||||
public function __construct( $access_token, $time_diff = 0 ) {
|
||||
$secret = explode( '.', $access_token );
|
||||
if ( 2 !== count( $secret ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->token = $secret[0];
|
||||
$this->secret = $secret[1];
|
||||
$this->time_diff = $time_diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the current request.
|
||||
*
|
||||
* @todo Implement a proper nonce verification.
|
||||
*
|
||||
* @param array $override Optional arguments to override the ones from the current request.
|
||||
* @return string|WP_Error Request signature, or a WP_Error on failure.
|
||||
*/
|
||||
public function sign_current_request( $override = array() ) {
|
||||
if ( isset( $override['scheme'] ) ) {
|
||||
$scheme = $override['scheme'];
|
||||
if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
|
||||
return new WP_Error( 'invalid_scheme', 'Invalid URL scheme' );
|
||||
}
|
||||
} else {
|
||||
if ( is_ssl() ) {
|
||||
$scheme = 'https';
|
||||
} else {
|
||||
$scheme = 'http';
|
||||
}
|
||||
}
|
||||
|
||||
$port = $this->get_current_request_port();
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidatedNotSanitized -- Sniff misses the esc_url_raw wrapper.
|
||||
$this->current_request_url = esc_url_raw( wp_unslash( "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ) ) );
|
||||
|
||||
if ( array_key_exists( 'body', $override ) && ! empty( $override['body'] ) ) {
|
||||
$body = $override['body'];
|
||||
} elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
$body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null;
|
||||
|
||||
// Convert the $_POST to the body, if the body was empty. This is how arrays are hashed
|
||||
// and encoded on the Jetpack side.
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( empty( $body ) && is_array( $_POST ) && count( $_POST ) > 0 ) {
|
||||
$body = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
}
|
||||
} elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'PUT' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
// This is a little strange-looking, but there doesn't seem to be another way to get the PUT body.
|
||||
$raw_put_data = file_get_contents( 'php://input' );
|
||||
parse_str( $raw_put_data, $body );
|
||||
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
$put_data = json_decode( $raw_put_data, true );
|
||||
if ( is_array( $put_data ) && count( $put_data ) > 0 ) {
|
||||
$body = $put_data;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$body = null;
|
||||
}
|
||||
|
||||
if ( empty( $body ) ) {
|
||||
$body = null;
|
||||
}
|
||||
|
||||
$a = array();
|
||||
foreach ( array( 'token', 'timestamp', 'nonce', 'body-hash' ) as $parameter ) {
|
||||
if ( isset( $override[ $parameter ] ) ) {
|
||||
$a[ $parameter ] = $override[ $parameter ];
|
||||
} else {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$a[ $parameter ] = isset( $_GET[ $parameter ] ) ? filter_var( wp_unslash( $_GET[ $parameter ] ) ) : '';
|
||||
}
|
||||
}
|
||||
|
||||
$method = isset( $override['method'] ) ? $override['method'] : ( isset( $_SERVER['REQUEST_METHOD'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : null );
|
||||
return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $method, $this->current_request_url, $body, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a specified request.
|
||||
*
|
||||
* @todo Having body_hash v. body-hash is annoying. Refactor to accept an array?
|
||||
* @todo Use wp_json_encode() instead of json_encode()?
|
||||
*
|
||||
* @param string $token Request token.
|
||||
* @param int $timestamp Timestamp of the request.
|
||||
* @param string $nonce Request nonce.
|
||||
* @param string $body_hash Request body hash.
|
||||
* @param string $method Request method.
|
||||
* @param string $url Request URL.
|
||||
* @param mixed $body Request body.
|
||||
* @param bool $verify_body_hash Whether to verify the body hash against the body.
|
||||
* @return string|WP_Error Request signature, or a WP_Error on failure.
|
||||
*/
|
||||
public function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '', $method = '', $url = '', $body = null, $verify_body_hash = true ) {
|
||||
if ( ! $this->secret ) {
|
||||
return new WP_Error( 'invalid_secret', 'Invalid secret' );
|
||||
}
|
||||
|
||||
if ( ! $this->token ) {
|
||||
return new WP_Error( 'invalid_token', 'Invalid token' );
|
||||
}
|
||||
|
||||
list( $token ) = explode( '.', $token );
|
||||
|
||||
$signature_details = compact( 'token', 'timestamp', 'nonce', 'body_hash', 'method', 'url' );
|
||||
|
||||
if ( 0 !== strpos( $token, "$this->token:" ) ) {
|
||||
return new WP_Error( 'token_mismatch', 'Incorrect token', compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
// If we got an array at this point, let's encode it, so we can see what it looks like as a string.
|
||||
if ( is_array( $body ) ) {
|
||||
if ( count( $body ) > 0 ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
|
||||
$body = json_encode( $body );
|
||||
|
||||
} else {
|
||||
$body = '';
|
||||
}
|
||||
}
|
||||
|
||||
$required_parameters = array( 'token', 'timestamp', 'nonce', 'method', 'url' );
|
||||
if ( $body !== null ) {
|
||||
$required_parameters[] = 'body_hash';
|
||||
if ( ! is_string( $body ) ) {
|
||||
return new WP_Error( 'invalid_body', 'Body is malformed.', compact( 'signature_details' ) );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $required_parameters as $required ) {
|
||||
if ( ! is_scalar( $$required ) ) {
|
||||
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', str_replace( '_', '-', $required ) ), compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
if ( ! strlen( $$required ) ) {
|
||||
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is missing.', str_replace( '_', '-', $required ) ), compact( 'signature_details' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $body ) ) {
|
||||
if ( $body_hash ) {
|
||||
return new WP_Error( 'invalid_body_hash', 'Invalid body hash for empty body.', compact( 'signature_details' ) );
|
||||
}
|
||||
} else {
|
||||
$connection = new Connection_Manager();
|
||||
if ( $verify_body_hash && $connection->sha1_base64( $body ) !== $body_hash ) {
|
||||
return new WP_Error( 'invalid_body_hash', 'The body hash does not match.', compact( 'signature_details' ) );
|
||||
}
|
||||
}
|
||||
|
||||
$parsed = wp_parse_url( $url );
|
||||
if ( ! isset( $parsed['host'] ) ) {
|
||||
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'url' ), compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $parsed['port'] ) ) {
|
||||
$port = $parsed['port'];
|
||||
} else {
|
||||
if ( 'http' === $parsed['scheme'] ) {
|
||||
$port = 80;
|
||||
} elseif ( 'https' === $parsed['scheme'] ) {
|
||||
$port = 443;
|
||||
} else {
|
||||
return new WP_Error( 'unknown_scheme_port', "The scheme's port is unknown", compact( 'signature_details' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! ctype_digit( "$timestamp" ) || 10 < strlen( $timestamp ) ) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug.
|
||||
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'timestamp' ), compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
$local_time = $timestamp - $this->time_diff;
|
||||
if ( $local_time < time() - 600 || $local_time > time() + 300 ) {
|
||||
return new WP_Error( 'invalid_signature', 'The timestamp is too old.', compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
if ( 12 < strlen( $nonce ) || preg_match( '/[^a-zA-Z0-9]/', $nonce ) ) {
|
||||
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'nonce' ), compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
$normalized_request_pieces = array(
|
||||
$token,
|
||||
$timestamp,
|
||||
$nonce,
|
||||
$body_hash,
|
||||
strtoupper( $method ),
|
||||
strtolower( $parsed['host'] ),
|
||||
$port,
|
||||
empty( $parsed['path'] ) ? '' : $parsed['path'],
|
||||
// Normalized Query String.
|
||||
);
|
||||
|
||||
$normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) );
|
||||
$flat_normalized_request_pieces = array();
|
||||
foreach ( $normalized_request_pieces as $piece ) {
|
||||
if ( is_array( $piece ) ) {
|
||||
foreach ( $piece as $subpiece ) {
|
||||
$flat_normalized_request_pieces[] = $subpiece;
|
||||
}
|
||||
} else {
|
||||
$flat_normalized_request_pieces[] = $piece;
|
||||
}
|
||||
}
|
||||
$normalized_request_pieces = $flat_normalized_request_pieces;
|
||||
|
||||
$normalized_request_string = join( "\n", $normalized_request_pieces ) . "\n";
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $this->secret, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and normalize the parameters from a query string.
|
||||
*
|
||||
* @param string $query_string Query string.
|
||||
* @return array Normalized query string parameters.
|
||||
*/
|
||||
public function normalized_query_parameters( $query_string ) {
|
||||
parse_str( $query_string, $array );
|
||||
|
||||
unset( $array['signature'] );
|
||||
|
||||
$names = array_keys( $array );
|
||||
$values = array_values( $array );
|
||||
|
||||
$names = array_map( array( $this, 'encode_3986' ), $names );
|
||||
$values = array_map( array( $this, 'encode_3986' ), $values );
|
||||
|
||||
$pairs = array_map( array( $this, 'join_with_equal_sign' ), $names, $values );
|
||||
|
||||
sort( $pairs );
|
||||
|
||||
return $pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string or array of strings according to RFC 3986.
|
||||
*
|
||||
* @param string|array $string_or_array String or array to encode.
|
||||
* @return string|array URL-encoded string or array.
|
||||
*/
|
||||
public function encode_3986( $string_or_array ) {
|
||||
if ( is_array( $string_or_array ) ) {
|
||||
return array_map( array( $this, 'encode_3986' ), $string_or_array );
|
||||
}
|
||||
|
||||
return rawurlencode( $string_or_array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a parameter name and a parameter value with an equals sign between them.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param string|array $value Parameter value.
|
||||
* @return string|array A string pair (e.g. `name=value`) or an array of string pairs.
|
||||
*/
|
||||
public function join_with_equal_sign( $name, $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
return $this->join_array_with_equal_sign( $name, $value );
|
||||
}
|
||||
return "{$name}={$value}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for join_with_equal_sign for handling arrayed values.
|
||||
* Explicitly supports nested arrays.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* @param array $value Parameter value.
|
||||
* @return array An array of string pairs (e.g. `[ name[example]=value ]`).
|
||||
*/
|
||||
private function join_array_with_equal_sign( $name, $value ) {
|
||||
$result = array();
|
||||
foreach ( $value as $value_key => $value_value ) {
|
||||
$joined_value = $this->join_with_equal_sign( $name . '[' . $value_key . ']', $value_value );
|
||||
if ( is_array( $joined_value ) ) {
|
||||
foreach ( array_values( $joined_value ) as $individual_joined_value ) {
|
||||
$result[] = $individual_joined_value;
|
||||
}
|
||||
} elseif ( is_string( $joined_value ) ) {
|
||||
$result[] = $joined_value;
|
||||
}
|
||||
}
|
||||
|
||||
sort( $result );
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port that should be considered to sign the current request.
|
||||
*
|
||||
* It will analyze the current request, as well as some Jetpack constants, to return the string
|
||||
* to be concatenated in the URL representing the port of the current request.
|
||||
*
|
||||
* @since 1.8.4
|
||||
*
|
||||
* @return string The port to be used in the signature
|
||||
*/
|
||||
public function get_current_request_port() {
|
||||
$host_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? $this->sanitize_host_post( $_SERVER['HTTP_X_FORWARDED_PORT'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
if ( '' === $host_port && isset( $_SERVER['SERVER_PORT'] ) ) {
|
||||
$host_port = $this->sanitize_host_post( $_SERVER['SERVER_PORT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This port logic is tested in the Jetpack_Cxn_Tests->test__server_port_value() test.
|
||||
* Please update the test if any changes are made in this logic.
|
||||
*/
|
||||
if ( is_ssl() ) {
|
||||
// 443: Standard Port
|
||||
// 80: Assume we're behind a proxy without X-Forwarded-Port. Hardcoding "80" here means most sites
|
||||
// with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with
|
||||
// the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a
|
||||
// site at https://example.com:80/ (which would be a strange configuration).
|
||||
// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
|
||||
// if the site is behind a proxy running on port 443 without
|
||||
// X-Forwarded-Port and the back end's port is *not* 80. It's better,
|
||||
// though, to configure the proxy to send X-Forwarded-Port.
|
||||
$https_port = defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ? $this->sanitize_host_post( JETPACK_SIGNATURE__HTTPS_PORT ) : '443';
|
||||
$port = in_array( $host_port, array( '443', '80', $https_port ), true ) ? '' : $host_port;
|
||||
} else {
|
||||
// 80: Standard Port
|
||||
// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
|
||||
// if the site is behind a proxy running on port 80 without
|
||||
// X-Forwarded-Port. It's better, though, to configure the proxy to
|
||||
// send X-Forwarded-Port.
|
||||
$http_port = defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ? $this->sanitize_host_post( JETPACK_SIGNATURE__HTTP_PORT ) : '80';
|
||||
$port = in_array( $host_port, array( '80', $http_port ), true ) ? '' : $host_port;
|
||||
}
|
||||
return (string) $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a variable checking if it's a valid port number, which can be an integer or a numeric string
|
||||
*
|
||||
* @since 1.8.4
|
||||
*
|
||||
* @param mixed $port_number Variable representing a port number.
|
||||
* @return string Always a string with a valid port number, or an empty string if input is invalid
|
||||
*/
|
||||
public function sanitize_host_post( $port_number ) {
|
||||
|
||||
if ( ! is_int( $port_number ) && ! is_string( $port_number ) ) {
|
||||
return '';
|
||||
}
|
||||
if ( is_string( $port_number ) && ! ctype_digit( $port_number ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( 0 >= (int) $port_number || 65535 < $port_number ) {
|
||||
return '';
|
||||
}
|
||||
return (string) $port_number;
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/**
|
||||
* Legacy Jetpack Tracks Client
|
||||
*
|
||||
* @package automattic/jetpack-tracking
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
|
||||
/**
|
||||
* Jetpack_Tracks_Client
|
||||
*
|
||||
* Send Tracks events on behalf of a user
|
||||
*
|
||||
* Example Usage:
|
||||
```php
|
||||
require( dirname(__FILE__).'path/to/tracks/class-jetpack-tracks-client.php' );
|
||||
|
||||
$result = Jetpack_Tracks_Client::record_event( array(
|
||||
'_en' => $event_name, // required
|
||||
'_ui' => $user_id, // required unless _ul is provided
|
||||
'_ul' => $user_login, // required unless _ui is provided
|
||||
|
||||
// Optional, but recommended
|
||||
'_ts' => $ts_in_ms, // Default: now
|
||||
'_via_ip' => $client_ip, // we use it for geo, etc.
|
||||
|
||||
// Possibly useful to set some context for the event
|
||||
'_via_ua' => $client_user_agent,
|
||||
'_via_url' => $client_url,
|
||||
'_via_ref' => $client_referrer,
|
||||
|
||||
// For user-targeted tests
|
||||
'abtest_name' => $abtest_name,
|
||||
'abtest_variation' => $abtest_variation,
|
||||
|
||||
// Your application-specific properties
|
||||
'custom_property' => $some_value,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
// Handle the error in your app
|
||||
}
|
||||
```
|
||||
*/
|
||||
class Jetpack_Tracks_Client {
|
||||
const PIXEL = 'https://pixel.wp.com/t.gif';
|
||||
const BROWSER_TYPE = 'php-agent';
|
||||
const USER_AGENT_SLUG = 'tracks-client';
|
||||
const VERSION = '0.3';
|
||||
|
||||
/**
|
||||
* Stores the Terms of Service Object Reference.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private static $terms_of_service = null;
|
||||
|
||||
/**
|
||||
* Record an event.
|
||||
*
|
||||
* @param mixed $event Event object to send to Tracks. An array will be cast to object. Required.
|
||||
* Properties are included directly in the pixel query string after light validation.
|
||||
* @return mixed True on success, WP_Error on failure
|
||||
*/
|
||||
public static function record_event( $event ) {
|
||||
if ( ! self::$terms_of_service ) {
|
||||
self::$terms_of_service = new \Automattic\Jetpack\Terms_Of_Service();
|
||||
}
|
||||
|
||||
// Don't track users who have opted out or not agreed to our TOS, or are not running an active Jetpack.
|
||||
if ( ! self::$terms_of_service->has_agreed() || ! empty( $_COOKIE['tk_opt-out'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $event instanceof Jetpack_Tracks_Event ) {
|
||||
$event = new Jetpack_Tracks_Event( $event );
|
||||
}
|
||||
if ( is_wp_error( $event ) ) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
$pixel = $event->build_pixel_url( $event );
|
||||
|
||||
if ( ! $pixel ) {
|
||||
return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 );
|
||||
}
|
||||
|
||||
return self::record_pixel( $pixel );
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously request the pixel.
|
||||
*
|
||||
* @param string $pixel The wp.com tracking pixel.
|
||||
* @return array|bool|WP_Error True if successful. wp_remote_get response or WP_Error if not.
|
||||
*/
|
||||
public static function record_pixel( $pixel ) {
|
||||
// Add the Request Timestamp and URL terminator just before the HTTP request.
|
||||
$pixel .= '&_rt=' . self::build_timestamp() . '&_=_';
|
||||
|
||||
$response = wp_remote_get(
|
||||
$pixel,
|
||||
array(
|
||||
'blocking' => true, // The default, but being explicit here :).
|
||||
'timeout' => 1,
|
||||
'redirection' => 2,
|
||||
'httpversion' => '1.1',
|
||||
'user-agent' => self::get_user_agent(),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$code = isset( $response['response']['code'] ) ? $response['response']['code'] : 0;
|
||||
|
||||
if ( 200 !== $code ) {
|
||||
return new WP_Error( 'request_failed', 'Tracks pixel request failed', $code );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user agent.
|
||||
*
|
||||
* @return string The user agent.
|
||||
*/
|
||||
public static function get_user_agent() {
|
||||
return self::USER_AGENT_SLUG . '-v' . self::VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an event and return its tracking URL
|
||||
*
|
||||
* @deprecated Call the `build_pixel_url` method on a Jetpack_Tracks_Event object instead.
|
||||
* @param array $event Event keys and values.
|
||||
* @return string URL of a tracking pixel.
|
||||
*/
|
||||
public static function build_pixel_url( $event ) {
|
||||
$_event = new Jetpack_Tracks_Event( $event );
|
||||
return $_event->build_pixel_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input for a tracks event.
|
||||
*
|
||||
* @deprecated Instantiate a Jetpack_Tracks_Event object instead
|
||||
* @param array $event Event keys and values.
|
||||
* @return mixed Validated keys and values or WP_Error on failure
|
||||
*/
|
||||
private static function validate_and_sanitize( $event ) {
|
||||
$_event = new Jetpack_Tracks_Event( $event );
|
||||
if ( is_wp_error( $_event ) ) {
|
||||
return $_event;
|
||||
}
|
||||
return get_object_vars( $_event );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a timestamp.
|
||||
*
|
||||
* Milliseconds since 1970-01-01.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function build_timestamp() {
|
||||
$ts = round( microtime( true ) * 1000 );
|
||||
return number_format( $ts, 0, '', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the user's anon id from cookies, or generates and sets a new one
|
||||
*
|
||||
* @return string An anon id for the user
|
||||
*/
|
||||
public static function get_anon_id() {
|
||||
static $anon_id = null;
|
||||
|
||||
if ( ! isset( $anon_id ) ) {
|
||||
|
||||
// Did the browser send us a cookie?
|
||||
if ( isset( $_COOKIE['tk_ai'] ) && preg_match( '#^[a-z]+:[A-Za-z0-9+/=]{24}$#', $_COOKIE['tk_ai'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
$anon_id = $_COOKIE['tk_ai']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
} else {
|
||||
|
||||
$binary = '';
|
||||
|
||||
// Generate a new anonId and try to save it in the browser's cookies.
|
||||
// Note that base64-encoding an 18 character string generates a 24-character anon id.
|
||||
for ( $i = 0; $i < 18; ++$i ) {
|
||||
$binary .= chr( wp_rand( 0, 255 ) );
|
||||
}
|
||||
|
||||
$anon_id = 'jetpack:' . base64_encode( $binary ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
|
||||
if ( ! headers_sent()
|
||||
&& ! ( defined( 'REST_REQUEST' ) && REST_REQUEST )
|
||||
&& ! ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
|
||||
) {
|
||||
setcookie( 'tk_ai', $anon_id, 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- This is a random value and should be fine.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $anon_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the WordPress.com user's Tracks identity, if connected.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function get_connected_user_tracks_identity() {
|
||||
$user_data = ( new Manager() )->get_connected_user_data();
|
||||
if ( ! $user_data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array(
|
||||
'blogid' => Jetpack_Options::get_option( 'id', 0 ),
|
||||
'userid' => $user_data['ID'],
|
||||
'username' => $user_data['login'],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Jetpack_Tracks_Event. Legacy.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
/*
|
||||
* Example Usage:
|
||||
```php
|
||||
require_once( dirname(__FILE__) . 'path/to/tracks/class-jetpack-tracks-event.php' );
|
||||
|
||||
$event = new Jetpack_Tracks_Event( array(
|
||||
'_en' => $event_name, // required
|
||||
'_ui' => $user_id, // required unless _ul is provided
|
||||
'_ul' => $user_login, // required unless _ui is provided
|
||||
|
||||
// Optional, but recommended
|
||||
'_via_ip' => $client_ip, // for geo, etc.
|
||||
|
||||
// Possibly useful to set some context for the event
|
||||
'_via_ua' => $client_user_agent,
|
||||
'_via_url' => $client_url,
|
||||
'_via_ref' => $client_referrer,
|
||||
|
||||
// For user-targeted tests
|
||||
'abtest_name' => $abtest_name,
|
||||
'abtest_variation' => $abtest_variation,
|
||||
|
||||
// Your application-specific properties
|
||||
'custom_property' => $some_value,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $event->error ) ) {
|
||||
// Handle the error in your app
|
||||
}
|
||||
|
||||
$bump_and_redirect_pixel = $event->build_signed_pixel_url();
|
||||
```
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Jetpack_Tracks_Event
|
||||
*/
|
||||
class Jetpack_Tracks_Event {
|
||||
const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/';
|
||||
const PROP_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/';
|
||||
|
||||
/**
|
||||
* Tracks Event Error.
|
||||
*
|
||||
* @var mixed Error.
|
||||
*/
|
||||
public $error;
|
||||
|
||||
/**
|
||||
* Jetpack_Tracks_Event constructor.
|
||||
*
|
||||
* @param object $event Tracks event.
|
||||
*/
|
||||
public function __construct( $event ) {
|
||||
$_event = self::validate_and_sanitize( $event );
|
||||
if ( is_wp_error( $_event ) ) {
|
||||
$this->error = $_event;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $_event as $key => $value ) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a track event.
|
||||
*/
|
||||
public function record() {
|
||||
return Jetpack_Tracks_Client::record_event( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate the event with all relevant info.
|
||||
*
|
||||
* @param mixed $event Object or (flat) array.
|
||||
* @return mixed The transformed event array or WP_Error on failure.
|
||||
*/
|
||||
public static function validate_and_sanitize( $event ) {
|
||||
$event = (object) $event;
|
||||
|
||||
// Required.
|
||||
if ( ! $event->_en ) {
|
||||
return new WP_Error( 'invalid_event', 'A valid event must be specified via `_en`', 400 );
|
||||
}
|
||||
|
||||
// delete non-routable addresses otherwise geoip will discard the record entirely.
|
||||
if ( property_exists( $event, '_via_ip' ) && preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) {
|
||||
unset( $event->_via_ip );
|
||||
}
|
||||
|
||||
$validated = array(
|
||||
'browser_type' => Jetpack_Tracks_Client::BROWSER_TYPE,
|
||||
'_aua' => Jetpack_Tracks_Client::get_user_agent(),
|
||||
);
|
||||
|
||||
$_event = (object) array_merge( (array) $event, $validated );
|
||||
|
||||
// If you want to block property names, do it here.
|
||||
|
||||
// Make sure we have an event timestamp.
|
||||
if ( ! isset( $_event->_ts ) ) {
|
||||
$_event->_ts = Jetpack_Tracks_Client::build_timestamp();
|
||||
}
|
||||
|
||||
return $_event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a pixel URL that will send a Tracks event when fired.
|
||||
* On error, returns an empty string ('').
|
||||
*
|
||||
* @return string A pixel URL or empty string ('') if there were invalid args.
|
||||
*/
|
||||
public function build_pixel_url() {
|
||||
if ( $this->error ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$args = get_object_vars( $this );
|
||||
|
||||
// Request Timestamp and URL Terminator must be added just before the HTTP request or not at all.
|
||||
unset( $args['_rt'] );
|
||||
unset( $args['_'] );
|
||||
|
||||
$validated = self::validate_and_sanitize( $args );
|
||||
|
||||
if ( is_wp_error( $validated ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Jetpack_Tracks_Client::PIXEL . '?' . http_build_query( $validated );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the event name.
|
||||
*
|
||||
* @param string $name Event name.
|
||||
* @return false|int
|
||||
*/
|
||||
public static function event_name_is_valid( $name ) {
|
||||
return preg_match( self::EVENT_NAME_REGEX, $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates prop name
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*
|
||||
* @return false|int Truthy value.
|
||||
*/
|
||||
public static function prop_name_is_valid( $name ) {
|
||||
return preg_match( self::PROP_NAME_REGEX, $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrutinize event name.
|
||||
*
|
||||
* @param object $event Tracks event.
|
||||
*/
|
||||
public static function scrutinize_event_names( $event ) {
|
||||
if ( ! self::event_name_is_valid( $event->_en ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$whitelisted_key_names = array(
|
||||
'anonId',
|
||||
'Browser_Type',
|
||||
);
|
||||
|
||||
foreach ( array_keys( (array) $event ) as $key ) {
|
||||
if ( in_array( $key, $whitelisted_key_names, true ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! self::prop_name_is_valid( $key ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,867 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack XMLRPC Server.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Secrets;
|
||||
use Automattic\Jetpack\Connection\Tokens;
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Roles;
|
||||
|
||||
/**
|
||||
* Just a sack of functions. Not actually an IXR_Server
|
||||
*/
|
||||
class Jetpack_XMLRPC_Server {
|
||||
/**
|
||||
* The current error object
|
||||
*
|
||||
* @var \WP_Error
|
||||
*/
|
||||
public $error = null;
|
||||
|
||||
/**
|
||||
* The current user
|
||||
*
|
||||
* @var \WP_User
|
||||
*/
|
||||
public $user = null;
|
||||
|
||||
/**
|
||||
* The connection manager object.
|
||||
*
|
||||
* @var Automattic\Jetpack\Connection\Manager
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Creates a new XMLRPC server object.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->connection = new Connection_Manager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist of the XML-RPC methods available to the Jetpack Server. If the
|
||||
* user is not authenticated (->login()) then the methods are never added,
|
||||
* so they will get a "does not exist" error.
|
||||
*
|
||||
* @param array $core_methods Core XMLRPC methods.
|
||||
*/
|
||||
public function xmlrpc_methods( $core_methods ) {
|
||||
$jetpack_methods = array(
|
||||
'jetpack.verifyAction' => array( $this, 'verify_action' ),
|
||||
'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
|
||||
'jetpack.unlinkUser' => array( $this, 'unlink_user' ),
|
||||
'jetpack.testConnection' => array( $this, 'test_connection' ),
|
||||
);
|
||||
|
||||
$jetpack_methods = array_merge( $jetpack_methods, $this->provision_xmlrpc_methods() );
|
||||
|
||||
$this->user = $this->login();
|
||||
|
||||
if ( $this->user ) {
|
||||
$jetpack_methods = array_merge(
|
||||
$jetpack_methods,
|
||||
array(
|
||||
'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
|
||||
$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
|
||||
$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the XML-RPC methods available to Jetpack for authenticated users.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 1.1.0
|
||||
*
|
||||
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
|
||||
* @param array $core_methods Available core XML-RPC methods.
|
||||
* @param \WP_User $user Information about the user authenticated in the request.
|
||||
*/
|
||||
$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the XML-RPC methods available to Jetpack for requests signed both with a blog token or a user token.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since 1.9.5 Introduced the $user parameter.
|
||||
* @since-jetpack 3.0.0
|
||||
*
|
||||
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
|
||||
* @param array $core_methods Available core XML-RPC methods.
|
||||
* @param \WP_User|bool $user Information about the user authenticated in the request. False if authenticated with blog token.
|
||||
*/
|
||||
return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods, $this->user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist of the bootstrap XML-RPC methods
|
||||
*/
|
||||
public function bootstrap_xmlrpc_methods() {
|
||||
return array(
|
||||
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
|
||||
'jetpack.remoteRegister' => array( $this, 'remote_register' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional method needed for authorization calls.
|
||||
*/
|
||||
public function authorize_xmlrpc_methods() {
|
||||
return array(
|
||||
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
|
||||
'jetpack.remoteRegister' => array( $this, 'remote_already_registered' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote provisioning methods.
|
||||
*/
|
||||
public function provision_xmlrpc_methods() {
|
||||
return array(
|
||||
'jetpack.remoteRegister' => array( $this, 'remote_register' ),
|
||||
'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
|
||||
'jetpack.remoteConnect' => array( $this, 'remote_connect' ),
|
||||
'jetpack.getUser' => array( $this, 'get_user' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify whether a local user exists and what role they have.
|
||||
*
|
||||
* @param int|string|array $request One of:
|
||||
* int|string The local User's ID, username, or email address.
|
||||
* array A request array containing:
|
||||
* 0: int|string The local User's ID, username, or email address.
|
||||
*
|
||||
* @return array|\IXR_Error Information about the user, or error if no such user found:
|
||||
* roles: string[] The user's rols.
|
||||
* login: string The user's username.
|
||||
* email_hash string[] The MD5 hash of the user's normalized email address.
|
||||
* caps string[] The user's capabilities.
|
||||
* allcaps string[] The user's granular capabilities, merged from role capabilities.
|
||||
* token_key string The Token Key of the user's Jetpack token. Empty string if none.
|
||||
*/
|
||||
public function get_user( $request ) {
|
||||
$user_id = is_array( $request ) ? $request[0] : $request;
|
||||
|
||||
if ( ! $user_id ) {
|
||||
return $this->error(
|
||||
new \WP_Error(
|
||||
'invalid_user',
|
||||
__( 'Invalid user identifier.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'get_user'
|
||||
);
|
||||
}
|
||||
|
||||
$user = $this->get_user_by_anything( $user_id );
|
||||
|
||||
if ( ! $user ) {
|
||||
return $this->error(
|
||||
new \WP_Error(
|
||||
'user_unknown',
|
||||
__( 'User not found.', 'jetpack-connection' ),
|
||||
404
|
||||
),
|
||||
'get_user'
|
||||
);
|
||||
}
|
||||
|
||||
$user_token = ( new Tokens() )->get_access_token( $user->ID );
|
||||
|
||||
if ( $user_token ) {
|
||||
list( $user_token_key ) = explode( '.', $user_token->secret );
|
||||
if ( $user_token_key === $user_token->secret ) {
|
||||
$user_token_key = '';
|
||||
}
|
||||
} else {
|
||||
$user_token_key = '';
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $user->ID,
|
||||
'login' => $user->user_login,
|
||||
'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
|
||||
'roles' => $user->roles,
|
||||
'caps' => $user->caps,
|
||||
'allcaps' => $user->allcaps,
|
||||
'token_key' => $user_token_key,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote authorization XMLRPC method handler.
|
||||
*
|
||||
* @param array $request the request.
|
||||
*/
|
||||
public function remote_authorize( $request ) {
|
||||
$user = get_user_by( 'id', $request['state'] );
|
||||
|
||||
/**
|
||||
* Happens on various request handling events in the Jetpack XMLRPC server.
|
||||
* The action combines several types of events:
|
||||
* - remote_authorize
|
||||
* - remote_provision
|
||||
* - get_user.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 8.0.0
|
||||
*
|
||||
* @param String $action the action name, i.e., 'remote_authorize'.
|
||||
* @param String $stage the execution stage, can be 'begin', 'success', 'error', etc.
|
||||
* @param array $parameters extra parameters from the event.
|
||||
* @param WP_User $user the acting user.
|
||||
*/
|
||||
do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'begin', array(), $user );
|
||||
|
||||
foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
|
||||
if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
|
||||
return $this->error(
|
||||
new \WP_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ),
|
||||
'remote_authorize'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $user ) {
|
||||
return $this->error( new \WP_Error( 'user_unknown', 'User not found.', 404 ), 'remote_authorize' );
|
||||
}
|
||||
|
||||
if ( $this->connection->has_connected_owner() && $this->connection->is_user_connected( $request['state'] ) ) {
|
||||
return $this->error( new \WP_Error( 'already_connected', 'User already connected.', 400 ), 'remote_authorize' );
|
||||
}
|
||||
|
||||
$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
|
||||
|
||||
if ( is_a( $verified, 'IXR_Error' ) ) {
|
||||
return $this->error( $verified, 'remote_authorize' );
|
||||
}
|
||||
|
||||
wp_set_current_user( $request['state'] );
|
||||
|
||||
$result = $this->connection->authorize( $request );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->error( $result, 'remote_authorize' );
|
||||
}
|
||||
|
||||
// This action is documented in class.jetpack-xmlrpc-server.php.
|
||||
do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'success' );
|
||||
|
||||
return array(
|
||||
'result' => $result,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
|
||||
* register this site so that a plan can be provisioned.
|
||||
*
|
||||
* @param array $request An array containing at minimum nonce and local_user keys.
|
||||
*
|
||||
* @return \WP_Error|array
|
||||
*/
|
||||
public function remote_register( $request ) {
|
||||
// This action is documented in class.jetpack-xmlrpc-server.php.
|
||||
do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'begin', array() );
|
||||
|
||||
$user = $this->fetch_and_verify_local_user( $request );
|
||||
|
||||
if ( ! $user ) {
|
||||
return $this->error(
|
||||
new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack-connection' ), 400 ),
|
||||
'remote_register'
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
|
||||
return $this->error( $user, 'remote_register' );
|
||||
}
|
||||
|
||||
if ( empty( $request['nonce'] ) ) {
|
||||
return $this->error(
|
||||
new \WP_Error(
|
||||
'nonce_missing',
|
||||
__( 'The required "nonce" parameter is missing.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_register'
|
||||
);
|
||||
}
|
||||
|
||||
$nonce = sanitize_text_field( $request['nonce'] );
|
||||
unset( $request['nonce'] );
|
||||
|
||||
$api_url = $this->connection->api_url( 'partner_provision_nonce_check' );
|
||||
$response = Client::_wp_remote_request(
|
||||
esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
|
||||
array( 'method' => 'GET' ),
|
||||
true
|
||||
);
|
||||
|
||||
if (
|
||||
200 !== wp_remote_retrieve_response_code( $response ) ||
|
||||
'OK' !== trim( wp_remote_retrieve_body( $response ) )
|
||||
) {
|
||||
return $this->error(
|
||||
new \WP_Error(
|
||||
'invalid_nonce',
|
||||
__( 'There was an issue validating this request.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_register'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! Jetpack_Options::get_option( 'id' ) || ! ( new Tokens() )->get_access_token() || ! empty( $request['force'] ) ) {
|
||||
wp_set_current_user( $user->ID );
|
||||
|
||||
// This code mostly copied from Jetpack::admin_page_load.
|
||||
if ( isset( $request['from'] ) ) {
|
||||
$this->connection->add_register_request_param( 'from', (string) $request['from'] );
|
||||
}
|
||||
$registered = $this->connection->try_registration();
|
||||
if ( is_wp_error( $registered ) ) {
|
||||
return $this->error( $registered, 'remote_register' );
|
||||
} elseif ( ! $registered ) {
|
||||
return $this->error(
|
||||
new \WP_Error(
|
||||
'registration_error',
|
||||
__( 'There was an unspecified error registering the site', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_register'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This action is documented in class.jetpack-xmlrpc-server.php.
|
||||
do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'success' );
|
||||
|
||||
return array(
|
||||
'client_id' => Jetpack_Options::get_option( 'id' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a substitute for remote_register() when the blog is already registered which returns an error code
|
||||
* signifying that state.
|
||||
* This is an unauthorized call and we should not be responding with any data other than the error code.
|
||||
*
|
||||
* @return \IXR_Error
|
||||
*/
|
||||
public function remote_already_registered() {
|
||||
return $this->error(
|
||||
new \WP_Error( 'already_registered', __( 'Blog is already registered', 'jetpack-connection' ), 400 ),
|
||||
'remote_register'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
|
||||
* register this site so that a plan can be provisioned.
|
||||
*
|
||||
* @param array $request An array containing at minimum a nonce key and a local_username key.
|
||||
*
|
||||
* @return \WP_Error|array
|
||||
*/
|
||||
public function remote_provision( $request ) {
|
||||
$user = $this->fetch_and_verify_local_user( $request );
|
||||
|
||||
if ( ! $user ) {
|
||||
return $this->error(
|
||||
new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack-connection' ), 400 ),
|
||||
'remote_provision'
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
|
||||
return $this->error( $user, 'remote_provision' );
|
||||
}
|
||||
|
||||
$site_icon = get_site_icon_url();
|
||||
|
||||
/**
|
||||
* Filters the Redirect URI returned by the remote_register XMLRPC method
|
||||
*
|
||||
* @param string $redirect_uri The Redirect URI
|
||||
*
|
||||
* @since 1.9.7
|
||||
*/
|
||||
$redirect_uri = apply_filters( 'jetpack_xmlrpc_remote_register_redirect_uri', admin_url() );
|
||||
|
||||
// Generate secrets.
|
||||
$roles = new Roles();
|
||||
$role = $roles->translate_user_to_role( $user );
|
||||
$secrets = ( new Secrets() )->generate( 'authorize', $user->ID );
|
||||
|
||||
$response = array(
|
||||
'jp_version' => Constants::get_constant( 'JETPACK__VERSION' ),
|
||||
'redirect_uri' => $redirect_uri,
|
||||
'user_id' => $user->ID,
|
||||
'user_email' => $user->user_email,
|
||||
'user_login' => $user->user_login,
|
||||
'scope' => $this->connection->sign_role( $role, $user->ID ),
|
||||
'secret' => $secrets['secret_1'],
|
||||
'is_active' => $this->connection->has_connected_owner(),
|
||||
);
|
||||
|
||||
if ( $site_icon ) {
|
||||
$response['site_icon'] = $site_icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the response of the remote_provision XMLRPC method
|
||||
*
|
||||
* @param array $response The response.
|
||||
* @param array $request An array containing at minimum a nonce key and a local_username key.
|
||||
* @param \WP_User $user The local authenticated user.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*/
|
||||
$response = apply_filters( 'jetpack_remote_xmlrpc_provision_response', $response, $request, $user );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array containing a local user identifier and a nonce, will attempt to fetch and set
|
||||
* an access token for the given user.
|
||||
*
|
||||
* @param array $request An array containing local_user and nonce keys at minimum.
|
||||
* @param \IXR_Client $ixr_client The client object, optional.
|
||||
* @return mixed
|
||||
*/
|
||||
public function remote_connect( $request, $ixr_client = false ) {
|
||||
if ( $this->connection->has_connected_owner() ) {
|
||||
return $this->error(
|
||||
new WP_Error(
|
||||
'already_connected',
|
||||
__( 'Jetpack is already connected.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_connect'
|
||||
);
|
||||
}
|
||||
|
||||
$user = $this->fetch_and_verify_local_user( $request );
|
||||
|
||||
if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
|
||||
return $this->error(
|
||||
new WP_Error(
|
||||
'input_error',
|
||||
__( 'Valid user is required.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_connect'
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $request['nonce'] ) ) {
|
||||
return $this->error(
|
||||
new WP_Error(
|
||||
'input_error',
|
||||
__( 'A non-empty nonce must be supplied.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_connect'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $ixr_client ) {
|
||||
$ixr_client = new Jetpack_IXR_Client();
|
||||
}
|
||||
// TODO: move this query into the Tokens class?
|
||||
$ixr_client->query(
|
||||
'jetpack.getUserAccessToken',
|
||||
array(
|
||||
'nonce' => sanitize_text_field( $request['nonce'] ),
|
||||
'external_user_id' => $user->ID,
|
||||
)
|
||||
);
|
||||
|
||||
$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
|
||||
if ( empty( $token ) ) {
|
||||
return $this->error(
|
||||
new WP_Error(
|
||||
'token_fetch_failed',
|
||||
__( 'Failed to fetch user token from WordPress.com.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_connect'
|
||||
);
|
||||
}
|
||||
$token = sanitize_text_field( $token );
|
||||
|
||||
( new Tokens() )->update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
|
||||
|
||||
/**
|
||||
* Hook fired at the end of the jetpack.remoteConnect XML-RPC callback
|
||||
*
|
||||
* @since 1.9.7
|
||||
*/
|
||||
do_action( 'jetpack_remote_connect_end' );
|
||||
|
||||
return $this->connection->has_connected_owner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the local user to act as.
|
||||
*
|
||||
* @param array $request the current request data.
|
||||
*/
|
||||
private function fetch_and_verify_local_user( $request ) {
|
||||
if ( empty( $request['local_user'] ) ) {
|
||||
return $this->error(
|
||||
new \WP_Error(
|
||||
'local_user_missing',
|
||||
__( 'The required "local_user" parameter is missing.', 'jetpack-connection' ),
|
||||
400
|
||||
),
|
||||
'remote_provision'
|
||||
);
|
||||
}
|
||||
|
||||
// Local user is used to look up by login, email or ID.
|
||||
$local_user_info = $request['local_user'];
|
||||
|
||||
return $this->get_user_by_anything( $local_user_info );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user object by its data.
|
||||
*
|
||||
* @param string $user_id can be any identifying user data.
|
||||
*/
|
||||
private function get_user_by_anything( $user_id ) {
|
||||
$user = get_user_by( 'login', $user_id );
|
||||
|
||||
if ( ! $user ) {
|
||||
$user = get_user_by( 'email', $user_id );
|
||||
}
|
||||
|
||||
if ( ! $user ) {
|
||||
$user = get_user_by( 'ID', $user_id );
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible error_codes:
|
||||
*
|
||||
* - verify_secret_1_missing
|
||||
* - verify_secret_1_malformed
|
||||
* - verify_secrets_missing: verification secrets are not found in database
|
||||
* - verify_secrets_incomplete: verification secrets are only partially found in database
|
||||
* - verify_secrets_expired: verification secrets have expired
|
||||
* - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
|
||||
* - state_missing: required parameter of state not found
|
||||
* - state_malformed: state is not a digit
|
||||
* - invalid_state: state in request does not match the stored state
|
||||
*
|
||||
* The 'authorize' and 'register' actions have additional error codes
|
||||
*
|
||||
* state_missing: a state ( user id ) was not supplied
|
||||
* state_malformed: state is not the correct data type
|
||||
* invalid_state: supplied state does not match the stored state
|
||||
*
|
||||
* @param array $params action An array of 3 parameters:
|
||||
* [0]: string action. Possible values are `authorize`, `publicize` and `register`.
|
||||
* [1]: string secret_1.
|
||||
* [2]: int state.
|
||||
* @return \IXR_Error|string IXR_Error on failure, secret_2 on success.
|
||||
*/
|
||||
public function verify_action( $params ) {
|
||||
$action = isset( $params[0] ) ? $params[0] : '';
|
||||
$verify_secret = isset( $params[1] ) ? $params[1] : '';
|
||||
$state = isset( $params[2] ) ? $params[2] : '';
|
||||
|
||||
$result = ( new Secrets() )->verify( $action, $verify_secret, $state );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->error( $result );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for wp_authenticate( $username, $password );
|
||||
*
|
||||
* @return \WP_User|bool
|
||||
*/
|
||||
public function login() {
|
||||
$this->connection->require_jetpack_authentication();
|
||||
$user = wp_authenticate( 'username', 'password' );
|
||||
if ( is_wp_error( $user ) ) {
|
||||
if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
|
||||
$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
|
||||
} else {
|
||||
$this->error = $user;
|
||||
}
|
||||
return false;
|
||||
} elseif ( ! $user ) { // Shouldn't happen.
|
||||
$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
|
||||
return false;
|
||||
}
|
||||
|
||||
wp_set_current_user( $user->ID );
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current error as an \IXR_Error
|
||||
*
|
||||
* @param \WP_Error|\IXR_Error $error The error object, optional.
|
||||
* @param string $event_name The event name.
|
||||
* @param \WP_User $user The user object.
|
||||
* @return bool|\IXR_Error
|
||||
*/
|
||||
public function error( $error = null, $event_name = null, $user = null ) {
|
||||
if ( null !== $event_name ) {
|
||||
// This action is documented in class.jetpack-xmlrpc-server.php.
|
||||
do_action( 'jetpack_xmlrpc_server_event', $event_name, 'fail', $error, $user );
|
||||
}
|
||||
|
||||
if ( $error !== null ) {
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
if ( is_wp_error( $this->error ) ) {
|
||||
$code = $this->error->get_error_data();
|
||||
if ( ! $code ) {
|
||||
$code = -10520;
|
||||
}
|
||||
$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
|
||||
if ( ! class_exists( \IXR_Error::class ) ) {
|
||||
require_once ABSPATH . WPINC . '/class-IXR.php';
|
||||
}
|
||||
return new \IXR_Error( $code, $message );
|
||||
} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* API Methods */
|
||||
|
||||
/**
|
||||
* Just authenticates with the given Jetpack credentials.
|
||||
*
|
||||
* @return string A success string. The Jetpack plugin filters it and make it return the Jetpack plugin version.
|
||||
*/
|
||||
public function test_connection() {
|
||||
/**
|
||||
* Filters the successful response of the XMLRPC test_connection method
|
||||
*
|
||||
* @param string $response The response string.
|
||||
*/
|
||||
return apply_filters( 'jetpack_xmlrpc_test_connection_response', 'success' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API user code.
|
||||
*
|
||||
* @param array $args arguments identifying the test site.
|
||||
*/
|
||||
public function test_api_user_code( $args ) {
|
||||
$client_id = (int) $args[0];
|
||||
$user_id = (int) $args[1];
|
||||
$nonce = (string) $args[2];
|
||||
$verify = (string) $args[3];
|
||||
|
||||
if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
if ( ! $user || is_wp_error( $user ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* phpcs:ignore
|
||||
debugging
|
||||
error_log( "CLIENT: $client_id" );
|
||||
error_log( "USER: $user_id" );
|
||||
error_log( "NONCE: $nonce" );
|
||||
error_log( "VERIFY: $verify" );
|
||||
*/
|
||||
|
||||
$jetpack_token = ( new Tokens() )->get_access_token( $user_id );
|
||||
|
||||
$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
|
||||
if ( ! $api_user_code ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hmac = hash_hmac(
|
||||
'md5',
|
||||
json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
|
||||
(object) array(
|
||||
'client_id' => (int) $client_id,
|
||||
'user_id' => (int) $user_id,
|
||||
'nonce' => (string) $nonce,
|
||||
'code' => (string) $api_user_code,
|
||||
)
|
||||
),
|
||||
$jetpack_token->secret
|
||||
);
|
||||
|
||||
if ( ! hash_equals( $hmac, $verify ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a user from WordPress.com
|
||||
*
|
||||
* When the request is done without any parameter, this XMLRPC callback gets an empty array as input.
|
||||
*
|
||||
* If $user_id is not provided, it will try to disconnect the current logged in user. This will fail if called by the Master User.
|
||||
*
|
||||
* If $user_id is is provided, it will try to disconnect the informed user, even if it's the Master User.
|
||||
*
|
||||
* @param mixed $user_id The user ID to disconnect from this site.
|
||||
*/
|
||||
public function unlink_user( $user_id = array() ) {
|
||||
$user_id = (int) $user_id;
|
||||
if ( $user_id < 1 ) {
|
||||
$user_id = null;
|
||||
}
|
||||
/**
|
||||
* Fired when we want to log an event to the Jetpack event log.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since-jetpack 7.7.0
|
||||
*
|
||||
* @param string $code Unique name for the event.
|
||||
* @param string $data Optional data about the event.
|
||||
*/
|
||||
do_action( 'jetpack_event_log', 'unlink' );
|
||||
return $this->connection->disconnect_user(
|
||||
$user_id,
|
||||
(bool) $user_id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the home URL and site URL for the current site which can be used on the WPCOM side for
|
||||
* IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
|
||||
* and the remote Jetpack site.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validate_urls_for_idc_mitigation() {
|
||||
return array(
|
||||
'home' => Urls::home_url(),
|
||||
'siteurl' => Urls::site_url(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the attachment parent object.
|
||||
*
|
||||
* @param array $args attachment and parent identifiers.
|
||||
*/
|
||||
public function update_attachment_parent( $args ) {
|
||||
$attachment_id = (int) $args[0];
|
||||
$parent_id = (int) $args[1];
|
||||
|
||||
return wp_update_post(
|
||||
array(
|
||||
'ID' => $attachment_id,
|
||||
'post_parent' => $parent_id,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
* Disconnect this blog from the connected wordpress.com account
|
||||
*
|
||||
* @deprecated since 1.25.0
|
||||
* @see Jetpack_XMLRPC_Methods::disconnect_blog() in the Jetpack plugin
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function disconnect_blog() {
|
||||
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::disconnect_blog()' );
|
||||
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
|
||||
return Jetpack_XMLRPC_Methods::disconnect_blog();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
* Returns what features are available. Uses the slug of the module files.
|
||||
*
|
||||
* @deprecated since 1.25.0
|
||||
* @see Jetpack_XMLRPC_Methods::features_available() in the Jetpack plugin
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function features_available() {
|
||||
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::features_available()' );
|
||||
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
|
||||
return Jetpack_XMLRPC_Methods::features_available();
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
* Returns what features are enabled. Uses the slug of the modules files.
|
||||
*
|
||||
* @deprecated since 1.25.0
|
||||
* @see Jetpack_XMLRPC_Methods::features_enabled() in the Jetpack plugin
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function features_enabled() {
|
||||
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::features_enabled()' );
|
||||
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
|
||||
return Jetpack_XMLRPC_Methods::features_enabled();
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
* Serve a JSON API request.
|
||||
*
|
||||
* @deprecated since 1.25.0
|
||||
* @see Jetpack_XMLRPC_Methods::json_api() in the Jetpack plugin
|
||||
*
|
||||
* @param array $args request arguments.
|
||||
*/
|
||||
public function json_api( $args = array() ) {
|
||||
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::json_api()' );
|
||||
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
|
||||
return Jetpack_XMLRPC_Methods::json_api( $args );
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user