updated plugin Jetpack Protect
version 4.0.0
This commit is contained in:
@ -25,6 +25,9 @@ class Connection_Assets {
|
||||
|
||||
/**
|
||||
* Register assets.
|
||||
*
|
||||
* NOTICE: Please think twice before including Connection scripts in the frontend.
|
||||
* Those scripts are intended to be used in WP admin area.
|
||||
*/
|
||||
public static function register_assets() {
|
||||
|
||||
|
@ -40,7 +40,7 @@ class Connection_Notice {
|
||||
* @return void
|
||||
*/
|
||||
public function initialize_notices( $screen ) {
|
||||
if ( ! in_array(
|
||||
if ( in_array(
|
||||
$screen->id,
|
||||
array(
|
||||
'jetpack_page_akismet-key-config',
|
||||
@ -48,6 +48,19 @@ class Connection_Notice {
|
||||
),
|
||||
true
|
||||
) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
*
|
||||
* This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
|
||||
* page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
|
||||
*/
|
||||
|
||||
if ( isset( $screen->base ) && 'users' === $screen->base
|
||||
&& isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action']
|
||||
) {
|
||||
add_action( 'admin_notices', array( $this, 'delete_user_update_connection_owner_notice' ) );
|
||||
}
|
||||
}
|
||||
@ -57,23 +70,6 @@ class Connection_Notice {
|
||||
* the connection owner.
|
||||
*/
|
||||
public function delete_user_update_connection_owner_notice() {
|
||||
global $current_screen;
|
||||
|
||||
/*
|
||||
* phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
*
|
||||
* This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
|
||||
* page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
|
||||
*/
|
||||
|
||||
if ( ! isset( $current_screen->base ) || 'users' !== $current_screen->base ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST['action'] ) || 'delete' !== $_REQUEST['action'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get connection owner or bail.
|
||||
$connection_manager = new Manager();
|
||||
$connection_owner_id = $connection_manager->get_connection_owner_id();
|
||||
|
@ -691,7 +691,7 @@ class Error_Handler {
|
||||
/**
|
||||
* Fires inside the admin_notices hook just before displaying the error message for a broken connection.
|
||||
*
|
||||
* If you want to disable the default message from being displayed, return an emtpy value in the jetpack_connection_error_notice_message filter.
|
||||
* If you want to disable the default message from being displayed, return an empty value in the jetpack_connection_error_notice_message filter.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
|
@ -80,6 +80,20 @@ class Manager {
|
||||
*/
|
||||
private static $disconnected_users = array();
|
||||
|
||||
/**
|
||||
* Cached connection status.
|
||||
*
|
||||
* @var bool|null True if the site is connected, false if not, null if not determined yet.
|
||||
*/
|
||||
private static $is_connected = null;
|
||||
|
||||
/**
|
||||
* Tracks whether connection status invalidation hooks have been added.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $connection_invalidators_added = false;
|
||||
|
||||
/**
|
||||
* Initialize the object.
|
||||
* Make sure to call the "Configure" first.
|
||||
@ -123,7 +137,9 @@ class Manager {
|
||||
add_filter( 'shutdown', array( new Package_Version_Tracker(), 'maybe_update_package_versions' ) );
|
||||
}
|
||||
|
||||
add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ) );
|
||||
// This runs on priority 11 - at least one api method in the connection package is set to override a previously
|
||||
// existing method from the Jetpack plugin. Running later than Jetpack's api init ensures the override is successful.
|
||||
add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ), 11 );
|
||||
|
||||
( new Nonce_Handler() )->init_schedule();
|
||||
|
||||
@ -140,6 +156,8 @@ class Manager {
|
||||
add_action( 'deleted_user', array( $manager, 'disconnect_user_force' ), 9, 1 );
|
||||
add_action( 'remove_user_from_blog', array( $manager, 'disconnect_user_force' ), 9, 1 );
|
||||
|
||||
$manager->add_connection_status_invalidation_hooks();
|
||||
|
||||
// Set up package version hook.
|
||||
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
|
||||
|
||||
@ -157,6 +175,28 @@ class Manager {
|
||||
Partner::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds hooks to invalidate the memoized connection status.
|
||||
*/
|
||||
private function add_connection_status_invalidation_hooks() {
|
||||
if ( self::$connection_invalidators_added ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force is_connected() to recompute after important actions.
|
||||
add_action( 'jetpack_site_registered', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'jetpack_site_disconnected', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'jetpack_sync_register_user', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_id', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_blog_token', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_user_token', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_user_tokens', array( $this, 'reset_connection_status' ) );
|
||||
// phpcs:ignore WPCUT.SwitchBlog.SwitchBlog -- wpcom flags **every** use of switch_blog, apparently expecting valid instances to ignore or suppress the sniff.
|
||||
add_action( 'switch_blog', array( $this, 'reset_connection_status' ) );
|
||||
|
||||
self::$connection_invalidators_added = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the XMLRPC request handlers.
|
||||
*
|
||||
@ -172,7 +212,7 @@ class Manager {
|
||||
$deprecated,
|
||||
$has_connected_owner,
|
||||
$is_signed,
|
||||
Jetpack_XMLRPC_Server $xmlrpc_server = null
|
||||
?Jetpack_XMLRPC_Server $xmlrpc_server = null
|
||||
) {
|
||||
add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ), 1000, 2 );
|
||||
if ( $deprecated !== null ) {
|
||||
@ -280,7 +320,7 @@ class Manager {
|
||||
nocache_headers();
|
||||
$wp_xmlrpc_server->serve_request();
|
||||
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -415,8 +455,9 @@ class Manager {
|
||||
|
||||
if (
|
||||
empty( $token_key )
|
||||
||
|
||||
empty( $version ) || (string) $jetpack_api_version !== $version ) {
|
||||
|| empty( $version )
|
||||
|| (string) $jetpack_api_version !== $version
|
||||
) {
|
||||
return new \WP_Error( 'malformed_token', 'Malformed token in request', compact( 'signature_details', 'error_type' ) );
|
||||
}
|
||||
|
||||
@ -596,9 +637,31 @@ class Manager {
|
||||
* @return bool
|
||||
*/
|
||||
public function is_connected() {
|
||||
$has_blog_id = (bool) \Jetpack_Options::get_option( 'id' );
|
||||
$has_blog_token = (bool) $this->get_tokens()->get_access_token();
|
||||
return $has_blog_id && $has_blog_token;
|
||||
if ( self::$is_connected === null ) {
|
||||
if ( ! self::$connection_invalidators_added ) {
|
||||
$this->add_connection_status_invalidation_hooks();
|
||||
}
|
||||
|
||||
$has_blog_id = (bool) \Jetpack_Options::get_option( 'id' );
|
||||
if ( $has_blog_id ) {
|
||||
$has_blog_token = (bool) $this->get_tokens()->get_access_token();
|
||||
self::$is_connected = ( $has_blog_id && $has_blog_token );
|
||||
} else {
|
||||
// Short-circuit, no need to check for tokens if there's no blog ID.
|
||||
self::$is_connected = false;
|
||||
}
|
||||
}
|
||||
return self::$is_connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the memoized connection status.
|
||||
* This will force the connection status to be recomputed on the next check.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function reset_connection_status() {
|
||||
self::$is_connected = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -875,25 +938,54 @@ class Manager {
|
||||
|
||||
// Using wp_redirect intentionally because we're redirecting outside.
|
||||
wp_redirect( $this->get_authorization_url( $user, $redirect_url ) ); // phpcs:ignore WordPress.Security.SafeRedirect
|
||||
exit();
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force user disconnect.
|
||||
*
|
||||
* @param int $user_id Local (external) user ID.
|
||||
* @param int $user_id Local (external) user ID.
|
||||
* @param bool $disconnect_all_users Whether to disconnect all users before disconnecting the primary user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect_user_force( $user_id ) {
|
||||
public function disconnect_user_force( $user_id, $disconnect_all_users = false ) {
|
||||
if ( ! (int) $user_id ) {
|
||||
// Missing user ID.
|
||||
return false;
|
||||
}
|
||||
// If we are disconnecting the primary user we may need to disconnect all other users first
|
||||
if ( $user_id === $this->get_connection_owner_id() && $disconnect_all_users && ! $this->disconnect_all_users_except_primary() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->disconnect_user( $user_id, true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects all users except the primary user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect_all_users_except_primary() {
|
||||
|
||||
$all_connected_users = $this->get_connected_users();
|
||||
|
||||
foreach ( $all_connected_users as $user ) {
|
||||
// Skip the primary.
|
||||
if ( $user->ID === $this->get_connection_owner_id() ) {
|
||||
continue;
|
||||
}
|
||||
$disconnected = $this->disconnect_user( $user->ID, false, true );
|
||||
// If we fail to disconnect any user, we should not proceed with disconnecting the primary user.
|
||||
if ( ! $disconnected ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks the current user from the linked WordPress.com user.
|
||||
*
|
||||
@ -1505,6 +1597,16 @@ class Manager {
|
||||
// With site connections in mind, non-admin users can connect their account only if a connection owner exists.
|
||||
$caps = $this->has_connected_owner() ? array( 'read' ) : array( 'manage_options' );
|
||||
break;
|
||||
case 'jetpack_unlink_user':
|
||||
$is_offline_mode = ( new Status() )->is_offline_mode();
|
||||
if ( $is_offline_mode ) {
|
||||
$caps = array( 'do_not_allow' );
|
||||
break;
|
||||
}
|
||||
|
||||
// Non-admins can always disconnect
|
||||
$caps = array( 'read' );
|
||||
break;
|
||||
}
|
||||
return $caps;
|
||||
}
|
||||
@ -1561,12 +1663,17 @@ class Manager {
|
||||
return $cached_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't use the 'ID' field, but need it to overcome a WP caching bug: https://core.trac.wordpress.org/ticket/62003
|
||||
*
|
||||
* @todo Remote the 'ID' field from users fetching when the issue is fixed and Jetpack-supported WP versions move beyond it.
|
||||
*/
|
||||
$earliest_registered_users = get_users(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
'orderby' => 'user_registered',
|
||||
'order' => 'ASC',
|
||||
'fields' => array( 'user_registered' ),
|
||||
'fields' => array( 'ID', 'user_registered' ),
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
@ -2125,6 +2232,8 @@ class Manager {
|
||||
|
||||
( new Nonce_Handler() )->clean_all();
|
||||
|
||||
Heartbeat::init()->deactivate();
|
||||
|
||||
/**
|
||||
* Fires before a site is disconnected.
|
||||
*
|
||||
|
@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Connection;
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '4.0.1';
|
||||
const PACKAGE_VERSION = '6.8.1';
|
||||
|
||||
const PACKAGE_SLUG = 'connection';
|
||||
|
||||
|
@ -15,7 +15,7 @@ use Jetpack_Options;
|
||||
* Disable direct access.
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,14 +17,6 @@ class Plugin_Storage {
|
||||
|
||||
const ACTIVE_PLUGINS_OPTION_NAME = 'jetpack_connection_active_plugins';
|
||||
|
||||
/**
|
||||
* Options where disabled plugins were stored
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
* @var string
|
||||
*/
|
||||
const PLUGINS_DISABLED_OPTION_NAME = 'jetpack_connection_disabled_plugins';
|
||||
|
||||
/**
|
||||
* Transient name used as flag to indicate that the active connected plugins list needs refreshing.
|
||||
*/
|
||||
@ -93,13 +85,9 @@ class Plugin_Storage {
|
||||
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
|
||||
* so please make sure not to run the method too early in the code.
|
||||
*
|
||||
* @since 1.39.0 deprecated the $connected_only argument.
|
||||
*
|
||||
* @param null $deprecated null plugins that were explicitly disconnected. Deprecated, there's no such a thing as disconnecting only specific plugins anymore.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function get_all( $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
public static function get_all() {
|
||||
$maybe_error = self::ensure_configured();
|
||||
|
||||
if ( $maybe_error instanceof WP_Error ) {
|
||||
@ -144,7 +132,10 @@ class Plugin_Storage {
|
||||
}
|
||||
|
||||
if ( is_multisite() && get_current_blog_id() !== self::$current_blog_id ) {
|
||||
self::$plugins = (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() );
|
||||
if ( self::$current_blog_id ) {
|
||||
// If blog ID got changed, pull the list of active plugins for that blog from the database.
|
||||
self::$plugins = (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() );
|
||||
}
|
||||
self::$current_blog_id = get_current_blog_id();
|
||||
}
|
||||
|
||||
@ -234,43 +225,6 @@ class Plugin_Storage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin to the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function disable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin from the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function enable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all plugins that were disconnected by user.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_disabled_plugins() { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update active plugins option with current list of active plugins on WPCOM.
|
||||
* This is a fallback to ensure this option is always up to date on WPCOM in case
|
||||
|
@ -31,6 +31,13 @@ class Plugin {
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* Users Connection Admin instance.
|
||||
*
|
||||
* @var Users_Connection_Admin
|
||||
*/
|
||||
private $users_connection_admin;
|
||||
|
||||
/**
|
||||
* Initialize the plugin manager.
|
||||
*
|
||||
@ -38,6 +45,9 @@ class Plugin {
|
||||
*/
|
||||
public function __construct( $slug ) {
|
||||
$this->slug = $slug;
|
||||
|
||||
// Initialize Users_Connection_Admin
|
||||
$this->users_connection_admin = new Users_Connection_Admin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,36 +97,4 @@ class Plugin {
|
||||
|
||||
return ! $plugins || ( array_key_exists( $this->slug, $plugins ) && 1 === count( $plugins ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin to the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin from the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this plugin is allowed to use the connection.
|
||||
*
|
||||
* @deprecated since 11.0
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -219,4 +219,17 @@ class Rest_Authentication {
|
||||
|
||||
return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request was signed with a user token.
|
||||
*
|
||||
* @since 6.7.0
|
||||
*
|
||||
* @return bool True if the request was signed with a valid user token, false otherwise.
|
||||
*/
|
||||
public static function is_signed_with_user_token() {
|
||||
$instance = self::init();
|
||||
|
||||
return true === $instance->rest_authentication_status && 'user' === $instance->rest_authentication_type;
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +167,20 @@ class REST_Connector {
|
||||
)
|
||||
);
|
||||
|
||||
// Disconnect/unlink user from WordPress.com servers.
|
||||
// this endpoint is set to override the older endpoint that was previously in the Jetpack plugin
|
||||
// Override is here in case an older version of the Jetpack plugin is installed alongside an updated standalone
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/user',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::unlink_user',
|
||||
'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
|
||||
),
|
||||
true // override other implementations
|
||||
);
|
||||
|
||||
// We are only registering this route if Jetpack-the-plugin is not active or it's version is ge 10.0-alpha.
|
||||
// The reason for doing so is to avoid conflicts between the Connection package and
|
||||
// older versions of Jetpack, registering the same route twice.
|
||||
@ -214,20 +228,15 @@ class REST_Connector {
|
||||
'callback' => array( $this, 'connection_register' ),
|
||||
'permission_callback' => __CLASS__ . '::jetpack_register_permission_check',
|
||||
'args' => array(
|
||||
'from' => array(
|
||||
'from' => array(
|
||||
'description' => __( 'Indicates where the registration action was triggered for tracking/segmentation purposes', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'registration_nonce' => array(
|
||||
'description' => __( 'The registration nonce', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'redirect_uri' => array(
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'plugin_slug' => array(
|
||||
'plugin_slug' => array(
|
||||
'description' => __( 'Indicates from what plugin the request is coming from', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
@ -252,6 +261,34 @@ class REST_Connector {
|
||||
)
|
||||
);
|
||||
|
||||
// Provider-specific authorization URL endpoint
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/authorize_url/(?P<provider>[a-zA-Z]+)',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'connection_authorize_url_provider' ),
|
||||
'permission_callback' => __CLASS__ . '::user_connection_data_permission_check',
|
||||
'args' => array(
|
||||
'provider' => array(
|
||||
'description' => __( 'Authentication provider (google, github, apple, link)', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'enum' => array( 'google', 'github', 'apple', 'link' ),
|
||||
),
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'email_address' => array(
|
||||
'description' => __( 'Email address for magic link authentication', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/user-token',
|
||||
@ -340,9 +377,15 @@ class REST_Connector {
|
||||
*
|
||||
* @return WP_Error|array
|
||||
*/
|
||||
public static function remote_provision( WP_REST_Request $request ) {
|
||||
public function remote_provision( WP_REST_Request $request ) {
|
||||
$request_data = $request->get_params();
|
||||
|
||||
if ( current_user_can( 'jetpack_connect_user' ) ) {
|
||||
$request_data['local_user'] = get_current_user_id();
|
||||
}
|
||||
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->remote_provision( $request );
|
||||
$result = $xmlrpc_server->remote_provision( $request_data );
|
||||
|
||||
if ( is_a( $result, 'IXR_Error' ) ) {
|
||||
$result = new WP_Error( $result->code, $result->message );
|
||||
@ -394,9 +437,15 @@ class REST_Connector {
|
||||
/**
|
||||
* Remote provision endpoint permission check.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function remote_provision_permission_check() {
|
||||
public function remote_provision_permission_check( WP_REST_Request $request ) {
|
||||
if ( empty( $request['local_user'] ) && current_user_can( 'jetpack_connect_user' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_remote_provision', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
@ -545,15 +594,36 @@ class REST_Connector {
|
||||
*
|
||||
* @since 1.30.1
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to disconnect the site.
|
||||
* @since 5.1.0 Modified the permission check to accept requests signed with blog tokens.
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to disconnect the site or the request is signed with a blog token (aka a direct request from WPCOM).
|
||||
*/
|
||||
public static function disconnect_site_permission_check() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
|
||||
*
|
||||
* @since 6.3.3
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to unlink.
|
||||
*/
|
||||
public static function unlink_user_permission_callback() {
|
||||
// This is a mapped capability
|
||||
// phpcs:ignore WordPress.WP.Capabilities.Unknown
|
||||
if ( current_user_can( 'jetpack_unlink_user' ) && ( new Manager() )->is_user_connected( get_current_user_id() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'invalid_user_permission_jetpack_disconnect',
|
||||
'invalid_user_permission_unlink_user',
|
||||
self::get_user_permissions_error_msg(),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
@ -607,11 +677,15 @@ class REST_Connector {
|
||||
'id' => $current_user->ID,
|
||||
'blogId' => $blog_id,
|
||||
'wpcomUser' => $wpcom_user_data,
|
||||
'gravatar' => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ),
|
||||
'gravatar' => get_avatar_url( $current_user->ID ),
|
||||
'permissions' => array(
|
||||
'connect' => current_user_can( 'jetpack_connect' ),
|
||||
'connect_user' => current_user_can( 'jetpack_connect_user' ),
|
||||
'disconnect' => current_user_can( 'jetpack_disconnect' ),
|
||||
'connect' => current_user_can( 'jetpack_connect' ),
|
||||
'connect_user' => current_user_can( 'jetpack_connect_user' ),
|
||||
// This is a mapped capability
|
||||
// phpcs:ignore WordPress.WP.Capabilities.Unknown
|
||||
'unlink_user' => current_user_can( 'jetpack_unlink_user' ),
|
||||
'disconnect' => current_user_can( 'jetpack_disconnect' ),
|
||||
'manage_options' => current_user_can( 'manage_options' ),
|
||||
),
|
||||
);
|
||||
|
||||
@ -627,6 +701,7 @@ class REST_Connector {
|
||||
$response = array(
|
||||
'currentUser' => $current_user_connection_data,
|
||||
'connectionOwner' => $owner_display_name,
|
||||
'isRegistered' => $connection->is_connected(),
|
||||
);
|
||||
|
||||
if ( $rest_response ) {
|
||||
@ -781,9 +856,10 @@ class REST_Connector {
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint tried to partially or fully reconnect the website to WP.com.
|
||||
* The endpoint tried to connect Jetpack site to WPCOM.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since 6.7.0 No longer needs `registration_nonce`.
|
||||
* @since-jetpack 7.7.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
@ -791,10 +867,6 @@ class REST_Connector {
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_register( $request ) {
|
||||
if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
|
||||
return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack-connection' ), array( 'status' => 403 ) );
|
||||
}
|
||||
|
||||
if ( isset( $request['from'] ) ) {
|
||||
$this->connection->add_register_request_param( 'from', (string) $request['from'] );
|
||||
}
|
||||
@ -930,6 +1002,51 @@ class REST_Connector {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks current user from the WordPress.com Servers.
|
||||
*
|
||||
* @since 6.3.3
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return bool|WP_Error True if user successfully unlinked.
|
||||
*/
|
||||
public static function unlink_user( $request ) {
|
||||
|
||||
if ( ! isset( $request['linked'] ) || false !== $request['linked'] ) {
|
||||
return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack-connection' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
// If the user is also connection owner, we need to disconnect all users. Since disconnecting all users is a destructive action, we need to pass a parameter to confirm the action.
|
||||
$disconnect_all_users = false;
|
||||
|
||||
if ( ( new Manager() )->get_connection_owner_id() === get_current_user_id() ) {
|
||||
if ( isset( $request['disconnect-all-users'] ) && false !== $request['disconnect-all-users'] ) {
|
||||
$disconnect_all_users = true;
|
||||
} else {
|
||||
return new WP_Error( 'unlink_user_failed', esc_html__( 'Unable to unlink the connection owner.', 'jetpack-connection' ), array( 'status' => 400 ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Allow admins to force a disconnect by passing the "force" parameter
|
||||
// This allows an admin to disconnect themselves
|
||||
if ( isset( $request['force'] ) && false !== $request['force'] && current_user_can( 'manage_options' ) && ( new Manager( 'jetpack' ) )->disconnect_user_force( get_current_user_id(), $disconnect_all_users ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
} elseif ( ( new Manager( 'jetpack' ) )->disconnect_user() ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user. Please try again.', 'jetpack-connection' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the API client is allowed to replace user token.
|
||||
*
|
||||
@ -1021,4 +1138,53 @@ class REST_Connector {
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_connection_check', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider-specific authorization URL endpoint
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_authorize_url_provider( $request ) {
|
||||
$provider = $request['provider'];
|
||||
$redirect_uri = $request['redirect_uri'] ?? '';
|
||||
|
||||
// Validate magic link parameters if provider is 'link'
|
||||
if ( 'link' === $provider ) {
|
||||
if ( empty( $request['email_address'] ) ) {
|
||||
return new WP_Error(
|
||||
'missing_email',
|
||||
__( 'Email address is required for magic link authentication.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
// Sanitize email address
|
||||
$email = sanitize_email( $request['email_address'] );
|
||||
if ( ! is_email( $email ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_email',
|
||||
__( 'Invalid email address format.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$authorize_url = ( new Authorize_Redirect( $this->connection ) )->build_authorize_url(
|
||||
$redirect_uri,
|
||||
false,
|
||||
false,
|
||||
$provider,
|
||||
array(
|
||||
'email_address' => $email ?? '',
|
||||
)
|
||||
);
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'authorizeUrl' => $authorize_url,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -280,24 +280,28 @@ class Tracking {
|
||||
*/
|
||||
public function tracks_get_identity( $user_id ) {
|
||||
|
||||
// Meta is set, and user is still connected. Use WPCOM ID.
|
||||
// Meta is set, and user is still connected. Use WPCOM ID.
|
||||
$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
|
||||
if ( $wpcom_id && $this->connection->is_user_connected( $user_id ) ) {
|
||||
if ( $wpcom_id && is_string( $wpcom_id ) && $this->connection->is_user_connected( $user_id ) ) {
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_id,
|
||||
);
|
||||
}
|
||||
|
||||
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
|
||||
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
|
||||
if ( $this->connection->is_user_connected( $user_id ) ) {
|
||||
$wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
|
||||
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
|
||||
$wpcom_id = $wpcom_user_data['ID'] ?? null;
|
||||
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_user_data['ID'],
|
||||
);
|
||||
if ( is_string( $wpcom_id ) ) {
|
||||
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_id );
|
||||
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// User isn't linked at all. Fall back to anonymous ID.
|
||||
|
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles the WordPress.com account column in the users list table.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* Class Users_Connection_Admin
|
||||
*/
|
||||
class Users_Connection_Admin {
|
||||
/**
|
||||
* The column ID used for the WordPress.com account column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COLUMN_ID = 'user_jetpack';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Only set up hooks if we're in the admin area and user has proper permissions
|
||||
add_action( 'init', array( $this, 'init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the admin functionality if conditions are met.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! is_admin() || ! current_user_can( 'manage_options' ) || ( new Host() )->is_wpcom_simple() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'manage_users_columns', array( $this, 'add_connection_column' ) );
|
||||
add_filter( 'manage_users_custom_column', array( $this, 'render_connection_column' ), 9, 3 ); // Priority 9 to run before SSO
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
add_action( 'admin_print_styles-users.php', array( $this, 'add_connection_column_styles' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the connection column to the users list table.
|
||||
*
|
||||
* @param array $columns The current columns.
|
||||
* @return array Modified columns.
|
||||
*/
|
||||
public function add_connection_column( $columns ) {
|
||||
$columns[ self::COLUMN_ID ] = sprintf(
|
||||
'<span class="jetpack-connection-tooltip-icon" role="tooltip" tabindex="0" aria-label="%2$s: %1$s">
|
||||
%1$s
|
||||
<span class="jetpack-connection-tooltip"></span>
|
||||
</span>',
|
||||
esc_html__( 'WordPress.com account', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the connection column content.
|
||||
*
|
||||
* @param string $output Custom column output.
|
||||
* @param string $column_name Column name.
|
||||
* @param int $user_id ID of the currently-listed user.
|
||||
* @return string
|
||||
*/
|
||||
public function render_connection_column( $output, $column_name, $user_id ) {
|
||||
if ( self::COLUMN_ID !== $column_name ) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
if ( ( new Manager() )->is_user_connected( $user_id ) ) {
|
||||
return sprintf(
|
||||
'<span title="%1$s" class="jetpack-connection-status">%2$s</span>',
|
||||
esc_attr__( 'This user has connected their WordPress.com account.', 'jetpack-connection' ),
|
||||
esc_html__( 'Connected', 'jetpack-connection' )
|
||||
);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles.
|
||||
*
|
||||
* @param string $hook The current admin page.
|
||||
*/
|
||||
public function enqueue_scripts( $hook ) {
|
||||
if ( 'users.php' !== $hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assets::register_script(
|
||||
'jetpack-users-connection',
|
||||
'../dist/jetpack-users-connection.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'strategy' => 'defer',
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'version' => Package_Version::PACKAGE_VERSION,
|
||||
'deps' => array( 'wp-i18n' ),
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'jetpack-users-connection',
|
||||
'jetpackConnectionTooltips',
|
||||
array(
|
||||
'columnTooltip' => esc_html__( 'Connecting a WordPress.com account unlocks Jetpack’s full suite of features including secure logins.', 'jetpack-connection' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add styles for the connection column.
|
||||
*/
|
||||
public function add_connection_column_styles() {
|
||||
?>
|
||||
<style>
|
||||
.jetpack-connection-tooltip-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* Add [?] icon using pseudo-element, only in column header */
|
||||
th.manage-column .jetpack-connection-tooltip-icon::after {
|
||||
content: '[?]';
|
||||
color: #3c434a;
|
||||
font-size: 1em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.jetpack-connection-tooltip {
|
||||
position: absolute;
|
||||
background: #f6f7f7;
|
||||
top: -85px;
|
||||
width: 250px;
|
||||
padding: 7px;
|
||||
color: #3c434a;
|
||||
font-size: .75rem;
|
||||
line-height: 17px;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
display: none;
|
||||
border-radius: 4px;
|
||||
font-family: sans-serif;
|
||||
box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.1);
|
||||
left: -170px;
|
||||
}
|
||||
.column-user_jetpack {
|
||||
width: 140px;
|
||||
}
|
||||
/* Show tooltip on hover and focus */
|
||||
.jetpack-connection-tooltip-icon:hover .jetpack-connection-tooltip,
|
||||
.jetpack-connection-tooltip-icon:focus-within .jetpack-connection-tooltip {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column ID. Allows other classes to reference the same column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_column_id() {
|
||||
return self::COLUMN_ID;
|
||||
}
|
||||
}
|
@ -163,7 +163,7 @@ class Webhooks {
|
||||
* @return never
|
||||
*/
|
||||
protected function do_exit() {
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,6 +199,10 @@ class Webhooks {
|
||||
wp_safe_redirect( $redirect );
|
||||
$this->do_exit();
|
||||
} else {
|
||||
if ( 'connect-after-checkout' === $from && $redirect ) {
|
||||
wp_safe_redirect( $redirect );
|
||||
$this->do_exit();
|
||||
}
|
||||
$connect_url = add_query_arg(
|
||||
array(
|
||||
'from' => $from,
|
||||
|
@ -162,17 +162,21 @@ class UI {
|
||||
|
||||
$consumer_chosen = null;
|
||||
$consumer_url_length = 0;
|
||||
|
||||
foreach ( $consumers as $consumer ) {
|
||||
foreach ( $consumers as &$consumer ) {
|
||||
if ( empty( $consumer['admin_page'] ) || ! is_string( $consumer['admin_page'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $consumer['customContent'] ) && is_callable( $consumer['customContent'] ) ) {
|
||||
$consumer['customContent'] = call_user_func( $consumer['customContent'] );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
|
||||
$consumer_chosen = $consumer;
|
||||
$consumer_url_length = strlen( $consumer['admin_page'] );
|
||||
}
|
||||
}
|
||||
unset( $consumer );
|
||||
|
||||
static::$consumers = $consumer_chosen ? $consumer_chosen : array_shift( $consumers );
|
||||
|
||||
|
@ -191,7 +191,7 @@ class SSO {
|
||||
Helpers::delete_connection_for_user( $current_user->ID );
|
||||
wp_logout();
|
||||
wp_safe_redirect( wp_login_url() );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,7 +491,7 @@ class SSO {
|
||||
|
||||
$tracking->record_user_event( 'sso_login_redirect_success' );
|
||||
wp_safe_redirect( $sso_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
} elseif ( Helpers::display_sso_form_for_action( $action ) ) {
|
||||
|
||||
@ -509,7 +509,7 @@ class SSO {
|
||||
$sso_url = $this->get_sso_url_or_die( $reauth );
|
||||
$tracking->record_user_event( 'sso_login_redirect_bypass_success' );
|
||||
wp_safe_redirect( $sso_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
$this->display_sso_login_form();
|
||||
@ -622,7 +622,7 @@ class SSO {
|
||||
|
||||
<?php if ( $display_name && $gravatar ) : ?>
|
||||
<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>">
|
||||
<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack-connection' ); ?>
|
||||
<?php esc_html_e( 'Log in with another WordPress.com account', 'jetpack-connection' ); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<p>
|
||||
@ -969,7 +969,7 @@ class SSO {
|
||||
admin_url()
|
||||
)
|
||||
);
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
add_filter( 'allowed_redirect_hosts', array( Helpers::class, 'allowed_redirect_hosts' ) );
|
||||
@ -977,7 +977,7 @@ class SSO {
|
||||
/** This filter is documented in core/src/wp-login.php */
|
||||
apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
|
||||
);
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
|
||||
@ -1207,7 +1207,7 @@ class SSO {
|
||||
|
||||
add_filter( 'allowed_redirect_hosts', array( Helpers::class, 'allowed_redirect_hosts' ) );
|
||||
wp_safe_redirect( $connect_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Connection\Package_Version;
|
||||
use Automattic\Jetpack\Connection\Users_Connection_Admin as Base_Admin;
|
||||
use Automattic\Jetpack\Roles;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
@ -21,7 +22,7 @@ use WP_User_Query;
|
||||
/**
|
||||
* Jetpack sso user admin class.
|
||||
*/
|
||||
class User_Admin {
|
||||
class User_Admin extends Base_Admin {
|
||||
/**
|
||||
* Instance of WP_User_Query.
|
||||
*
|
||||
@ -56,16 +57,20 @@ class User_Admin {
|
||||
add_action( 'user_new_form', array( $this, 'render_custom_email_message_form_field' ), 1 );
|
||||
add_action( 'delete_user_form', array( $this, 'render_invitations_notices_for_deleted_users' ) );
|
||||
add_action( 'delete_user', array( $this, 'revoke_user_invite' ) );
|
||||
add_filter( 'manage_users_columns', array( $this, 'jetpack_user_connected_th' ) );
|
||||
add_filter( 'manage_users_custom_column', array( $this, 'jetpack_show_connection_status' ), 10, 3 );
|
||||
add_action( 'user_row_actions', array( $this, 'jetpack_user_table_row_actions' ), 10, 2 );
|
||||
add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) );
|
||||
|
||||
if ( isset( $_GET['jetpack-sso-invite-user'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) );
|
||||
}
|
||||
|
||||
add_action( 'admin_post_jetpack_invite_user_to_wpcom', array( $this, 'invite_user_to_wpcom' ) );
|
||||
add_action( 'admin_post_jetpack_revoke_invite_user_to_wpcom', array( $this, 'handle_request_revoke_invite' ) );
|
||||
add_action( 'admin_post_jetpack_resend_invite_user_to_wpcom', array( $this, 'handle_request_resend_invite' ) );
|
||||
add_action( 'admin_print_styles-users.php', array( $this, 'jetpack_user_table_styles' ) );
|
||||
add_filter( 'users_list_table_query_args', array( $this, 'set_user_query' ), 100, 1 );
|
||||
add_action( 'admin_print_styles-user-new.php', array( $this, 'jetpack_new_users_styles' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
|
||||
self::$tracking = new Tracking();
|
||||
}
|
||||
@ -102,6 +107,7 @@ class User_Admin {
|
||||
* Revokes WordPress.com invitation.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @return mixed Response from the API call or false on failure.
|
||||
*/
|
||||
public function revoke_user_invite( $user_id ) {
|
||||
try {
|
||||
@ -970,39 +976,14 @@ class User_Admin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a column in the user admin table to display user connection status and actions.
|
||||
* Deprecated method. Adds a column in the user admin table to display user connection status and actions.
|
||||
*
|
||||
* @param array $columns User list table columns.
|
||||
*
|
||||
* @return array
|
||||
* @deprecated 6.5.0
|
||||
*/
|
||||
public function jetpack_user_connected_th( $columns ) {
|
||||
Assets::register_script(
|
||||
'jetpack-sso-users',
|
||||
'../../dist/jetpack-sso-users.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'strategy' => 'defer',
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'version' => Package_Version::PACKAGE_VERSION,
|
||||
)
|
||||
);
|
||||
|
||||
$tooltip_string = esc_attr__( 'Jetpack SSO allows a seamless and secure experience on WordPress.com. Join millions of WordPress users who trust us to keep their accounts safe.', 'jetpack-connection' );
|
||||
|
||||
wp_add_inline_script(
|
||||
'jetpack-sso-users',
|
||||
"var Jetpack_SSOTooltip = { 'tooltipString': '{$tooltip_string}' }",
|
||||
'before'
|
||||
);
|
||||
|
||||
$columns['user_jetpack'] = sprintf(
|
||||
'<span class="jetpack-sso-invitation-tooltip-icon jetpack-sso-status-column" role="tooltip" aria-label="%3$s: %1$s" tabindex="0">%2$s</span>',
|
||||
$tooltip_string,
|
||||
esc_html__( 'SSO Status', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
_deprecated_function( __METHOD__, 'package-6.5.0' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
@ -1175,59 +1156,58 @@ class User_Admin {
|
||||
* @param string $val HTML for the column.
|
||||
* @param string $col User list table column.
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return string
|
||||
* @return string Modified column content.
|
||||
*/
|
||||
public function jetpack_show_connection_status( $val, $col, $user_id ) {
|
||||
if ( 'user_jetpack' === $col ) {
|
||||
if ( ( new Manager() )->is_user_connected( $user_id ) ) {
|
||||
$connection_html = sprintf(
|
||||
'<span title="%1$s" class="jetpack-sso-invitation">%2$s</span>',
|
||||
esc_attr__( 'This user is connected and can log-in to this site.', 'jetpack-connection' ),
|
||||
esc_html__( 'Connected', 'jetpack-connection' )
|
||||
);
|
||||
return $connection_html;
|
||||
} else {
|
||||
$has_pending_invite = self::has_pending_wpcom_invite( $user_id );
|
||||
if ( $has_pending_invite ) {
|
||||
$connection_html = sprintf(
|
||||
'<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>',
|
||||
esc_attr__( 'This user didn’t accept the invitation to join this site yet.', 'jetpack-connection' ),
|
||||
esc_html__( 'Pending invite', 'jetpack-connection' )
|
||||
);
|
||||
return $connection_html;
|
||||
}
|
||||
$nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
|
||||
$connection_html = sprintf(
|
||||
// Using formmethod and formaction because we can't nest forms and have to submit using the main form.
|
||||
'<span tabindex="0" role="tooltip" aria-label="%4$s: %3$s" class="jetpack-sso-invitation-tooltip-icon sso-disconnected-user">
|
||||
<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a>
|
||||
<span class="sso-disconnected-user-icon dashicons dashicons-warning">
|
||||
<span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip">%3$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
add_query_arg(
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'invite_nonce' => $nonce,
|
||||
'action' => 'jetpack_invite_user_to_wpcom',
|
||||
),
|
||||
admin_url( 'admin-post.php' )
|
||||
),
|
||||
esc_html__( 'Send invite', 'jetpack-connection' ),
|
||||
esc_attr__( 'This user doesn’t have an SSO connection to WordPress.com. Invite them to the site to increase security and improve their experience.', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
return $connection_html;
|
||||
}
|
||||
if ( 'user_jetpack' !== $col ) {
|
||||
return $val;
|
||||
}
|
||||
return $val;
|
||||
|
||||
// Get base connection status from parent
|
||||
$connection_status = parent::render_connection_column( '', $col, $user_id );
|
||||
|
||||
// If user is not connected, check for pending invite
|
||||
if ( ! $connection_status ) {
|
||||
$has_pending_invite = self::has_pending_wpcom_invite( $user_id );
|
||||
if ( $has_pending_invite ) {
|
||||
return sprintf(
|
||||
'<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>',
|
||||
esc_attr__( 'This user didn’t accept the invitation to join this site yet.', 'jetpack-connection' ),
|
||||
esc_html__( 'Pending invite', 'jetpack-connection' )
|
||||
);
|
||||
}
|
||||
|
||||
// Show invite button for non-connected users
|
||||
$nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
|
||||
return sprintf(
|
||||
'<span tabindex="0" role="tooltip" aria-label="%4$s: %3$s" class="jetpack-sso-invitation-tooltip-icon sso-disconnected-user">
|
||||
<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a>
|
||||
<span class="sso-disconnected-user-icon dashicons dashicons-warning">
|
||||
<span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip">%3$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
add_query_arg(
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'invite_nonce' => $nonce,
|
||||
'action' => 'jetpack_invite_user_to_wpcom',
|
||||
),
|
||||
admin_url( 'admin-post.php' )
|
||||
),
|
||||
esc_html__( 'Send invite', 'jetpack-connection' ),
|
||||
esc_attr__( 'This user doesn’t have a Jetpack SSO connection to WordPress.com. Invite them to the site to increase security and improve their experience.', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
}
|
||||
|
||||
return $connection_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates error notices and redirects the user to the previous page.
|
||||
*
|
||||
* @param array $query_params - query parameters added to redirection URL.
|
||||
* @phan-suppress PhanPluginNeverReturnMethod
|
||||
*/
|
||||
public function create_error_notice_and_redirect( $query_params ) {
|
||||
$ref = wp_get_referer();
|
||||
@ -1239,7 +1219,8 @@ class User_Admin {
|
||||
$query_params,
|
||||
$ref
|
||||
);
|
||||
return wp_safe_redirect( $url );
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1254,9 +1235,6 @@ class User_Admin {
|
||||
#the-list tr:has(.sso-pending-invite) {
|
||||
background: #E9F0F5;
|
||||
}
|
||||
.fixed .column-user_jetpack {
|
||||
width: 100px;
|
||||
}
|
||||
.jetpack-sso-invitation {
|
||||
background: none;
|
||||
border: none;
|
||||
@ -1293,9 +1271,6 @@ class User_Admin {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.jetpack-sso-th-tooltip {
|
||||
left: -170px;
|
||||
}
|
||||
.jetpack-sso-td-tooltip {
|
||||
left: -256px;
|
||||
}
|
||||
@ -1319,4 +1294,29 @@ class User_Admin {
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue SSO-specific scripts.
|
||||
*
|
||||
* @param string $hook The current admin page.
|
||||
*/
|
||||
public function enqueue_scripts( $hook ) {
|
||||
if ( 'users.php' !== $hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::enqueue_scripts( $hook );
|
||||
// Enqueue the SSO users script.
|
||||
Assets::register_script(
|
||||
'jetpack-sso-users',
|
||||
'../../dist/jetpack-sso-users.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'strategy' => 'defer',
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'version' => Package_Version::PACKAGE_VERSION,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ document.addEventListener( 'DOMContentLoaded', function () {
|
||||
tooltip.innerHTML += ' [?]';
|
||||
|
||||
const tooltipTextbox = document.createElement( 'span' );
|
||||
tooltipTextbox.classList.add( 'jetpack-sso-invitation-tooltip', 'jetpack-sso-th-tooltip' );
|
||||
tooltipTextbox.classList.add( 'jetpack-sso-invitation-tooltip' );
|
||||
|
||||
const tooltipString = window.Jetpack_SSOTooltip.tooltipString;
|
||||
tooltipTextbox.innerHTML += tooltipString;
|
||||
@ -28,7 +28,7 @@ document.addEventListener( 'DOMContentLoaded', function () {
|
||||
*/
|
||||
function removeTooltip() {
|
||||
// Only remove tooltip if the element isn't currently active.
|
||||
if ( document.activeElement === tooltip ) {
|
||||
if ( tooltip.ownerDocument.activeElement === tooltip ) {
|
||||
return;
|
||||
}
|
||||
tooltip.removeChild( tooltipTextbox );
|
||||
@ -56,7 +56,7 @@ document.addEventListener( 'DOMContentLoaded', function () {
|
||||
* @param {Event} event - Triggering event.
|
||||
*/
|
||||
function removeSSOInvitationTooltip( event ) {
|
||||
if ( document.activeElement === event.target ) {
|
||||
if ( event.target.ownerDocument.activeElement === event.target ) {
|
||||
return;
|
||||
}
|
||||
this.querySelector( '.jetpack-sso-invitation-tooltip' ).style.display = 'none';
|
||||
|
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Trait WPCOM_REST_API_Proxy_Request
|
||||
*
|
||||
* Used to proxy requests to wpcom servers.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection\Traits;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Status\Visitor;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
|
||||
trait WPCOM_REST_API_Proxy_Request {
|
||||
|
||||
/**
|
||||
* Base path for the API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_api_path;
|
||||
|
||||
/**
|
||||
* Version of the API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* The base of the controller's route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base;
|
||||
|
||||
/**
|
||||
* Proxy request to wpcom servers on behalf of a user or using the Site-level Connection (blog token).
|
||||
*
|
||||
* @param WP_REST_Request $request Request to proxy.
|
||||
* @param string $path Path to append to the rest base.
|
||||
* @param string $context Whether the request should be proxied on behalf of the current user or using the Site-level Connection, aka 'blog' token. Can be Either 'user' or 'blog'. Defaults to 'user'.
|
||||
* @param bool $allow_fallback_to_blog If the $context is 'user', whether we should fallback to using the Site-level Connection in case the current user is not connected.
|
||||
* @param array $request_options Request options to pass to wp_remote_request.
|
||||
*
|
||||
* @return mixed|WP_Error Response from wpcom servers or an error.
|
||||
*/
|
||||
public function proxy_request_to_wpcom( $request, $path = '', $context = 'user', $allow_fallback_to_blog = false, $request_options = array() ) {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
$path = '/sites/' . rawurldecode( $blog_id ) . '/' . rawurldecode( ltrim( $this->rest_base, '/' ) ) . ( $path ? '/' . rawurldecode( ltrim( $path, '/' ) ) : '' );
|
||||
$query_params = $request->get_query_params();
|
||||
$manager = new Manager();
|
||||
|
||||
/*
|
||||
* A rest_route parameter can be added when using plain permalinks.
|
||||
* It is not necessary to pass them to WordPress.com,
|
||||
* and may even cause issues with some endpoints.
|
||||
* Let's remove it.
|
||||
*/
|
||||
if ( isset( $query_params['rest_route'] ) ) {
|
||||
unset( $query_params['rest_route'] );
|
||||
}
|
||||
$api_url = add_query_arg( $query_params, $path );
|
||||
|
||||
$request_options = array_replace_recursive(
|
||||
array(
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
|
||||
),
|
||||
'method' => $request->get_method(),
|
||||
),
|
||||
$request_options
|
||||
);
|
||||
|
||||
// If no body is present, passing it as $request->get_body() will cause an error.
|
||||
$body = $request->get_body() ? $request->get_body() : null;
|
||||
|
||||
$response = new WP_Error(
|
||||
'rest_unauthorized',
|
||||
__( 'Please connect your user account to WordPress.com', 'jetpack-connection' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
|
||||
if ( 'user' === $context ) {
|
||||
if ( ! $manager->is_user_connected() ) {
|
||||
if ( false === $allow_fallback_to_blog ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$context = 'blog';
|
||||
} else {
|
||||
$response = Client::wpcom_json_api_request_as_user( $api_url, $this->version, $request_options, $body, $this->base_api_path );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'blog' === $context ) {
|
||||
if ( ! $manager->is_connected() ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog( $api_url, $this->version, $request_options, $body, $this->base_api_path );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response_status = wp_remote_retrieve_response_code( $response );
|
||||
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( $response_status >= 400 ) {
|
||||
$code = $response_body['code'] ?? 'unknown_error';
|
||||
$message = $response_body['message'] ?? __( 'An unknown error occurred.', 'jetpack-connection' );
|
||||
|
||||
return new WP_Error( $code, $message, array( 'status' => $response_status ) );
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy request to wpcom servers on behalf of a user.
|
||||
*
|
||||
* @param WP_REST_Request $request Request to proxy.
|
||||
* @param string $path Path to append to the rest base.
|
||||
* @param array $request_options Request options to pass to wp_remote_request.
|
||||
*
|
||||
* @return mixed|WP_Error Response from wpcom servers or an error.
|
||||
*/
|
||||
public function proxy_request_to_wpcom_as_user( $request, $path = '', $request_options = array() ) {
|
||||
return $this->proxy_request_to_wpcom( $request, $path, 'user', false, $request_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy request to wpcom servers using the Site-level Connection (blog token).
|
||||
*
|
||||
* @param WP_REST_Request $request Request to proxy.
|
||||
* @param string $path Path to append to the rest base.
|
||||
* @param array $request_options Request options to pass to wp_remote_request.
|
||||
*
|
||||
* @return mixed|WP_Error Response from wpcom servers or an error.
|
||||
*/
|
||||
public function proxy_request_to_wpcom_as_blog( $request, $path = '', $request_options = array() ) {
|
||||
return $this->proxy_request_to_wpcom( $request, $path, 'blog', false, $request_options );
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class Authorize_Redirect {
|
||||
|
||||
if ( ! $dest_url || ( 0 === stripos( $dest_url, 'https://jetpack.com/' ) && 0 === stripos( $dest_url, 'https://wordpress.com/' ) ) ) {
|
||||
// The destination URL is missing or invalid, nothing to do here.
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
// The user is either already connected, or finished the connection process.
|
||||
@ -73,12 +73,12 @@ class Authorize_Redirect {
|
||||
}
|
||||
|
||||
wp_safe_redirect( $dest_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
} elseif ( ! empty( $_GET['done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// The user decided not to proceed with setting up the connection.
|
||||
|
||||
wp_safe_redirect( Admin_Menu::get_top_level_menu_item_url() );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
$redirect_args = array(
|
||||
@ -94,29 +94,66 @@ class Authorize_Redirect {
|
||||
}
|
||||
|
||||
wp_safe_redirect( $this->build_authorize_url( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) ) );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Jetpack authorization URL.
|
||||
*
|
||||
* @since 2.7.6 Added optional $from and $raw parameters.
|
||||
* @since 6.8.0 Added optional $provider and $provider_args parameters.
|
||||
*
|
||||
* @param bool|string $redirect URL to redirect to.
|
||||
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param string|null $provider The authentication provider (google, github, apple, link).
|
||||
* @param array|null $provider_args Additional provider-specific arguments.
|
||||
*
|
||||
* @todo Update default value for redirect since the called function expects a string.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function build_authorize_url( $redirect = false, $from = false, $raw = false ) {
|
||||
public function build_authorize_url( $redirect = false, $from = false, $raw = false, $provider = null, $provider_args = null ) {
|
||||
|
||||
add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
|
||||
add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
|
||||
|
||||
$url = $this->connection->get_authorization_url( wp_get_current_user(), $redirect, $from, $raw );
|
||||
|
||||
// If a provider is specified, modify the URL to use the provider-specific endpoint
|
||||
if ( $provider && in_array( $provider, array( 'google', 'github', 'apple', 'link' ), true ) ) {
|
||||
// Parse the URL to modify it safely
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
if ( ! empty( $url_parts['host'] ) && ! empty( $url_parts['path'] ) ) {
|
||||
// Build the new URL using wordpress.com as the host
|
||||
$url_parts['host'] = 'wordpress.com';
|
||||
$url_parts['path'] = '/log-in/jetpack/' . $provider;
|
||||
|
||||
// Preserve the query parameters
|
||||
$query_params = array();
|
||||
if ( ! empty( $url_parts['query'] ) ) {
|
||||
parse_str( $url_parts['query'], $query_params );
|
||||
}
|
||||
|
||||
// Add magic link specific parameters if provider is 'link'
|
||||
if ( 'link' === $provider && is_array( $provider_args ) && ! empty( $provider_args['email_address'] ) ) {
|
||||
$query_params['email_address'] = $provider_args['email_address'];
|
||||
// Add flag to trigger magic link flow
|
||||
$query_params['auto_trigger'] = '1';
|
||||
}
|
||||
|
||||
// URL encode all parameter values
|
||||
$query_params = array_map( 'rawurlencode', $query_params );
|
||||
|
||||
// Rebuild the URL
|
||||
$url = 'https://' . $url_parts['host'] . $url_parts['path'];
|
||||
if ( ! empty( $query_params ) ) {
|
||||
$url = add_query_arg( $query_params, $url );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
|
||||
remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
|
||||
|
||||
@ -125,11 +162,14 @@ class Authorize_Redirect {
|
||||
*
|
||||
* @since jetpack-8.9.0
|
||||
* @since 2.7.6 Added $raw parameter.
|
||||
* @since 6.8.0 Added $provider and $provider_args parameters.
|
||||
*
|
||||
* @param string $url Connection URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param string $url Connection URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param string|null $provider The authentication provider if specified.
|
||||
* @param array|null $provider_args Additional provider-specific arguments.
|
||||
*/
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url, $raw );
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url, $raw, $provider, $provider_args );
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user