Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions js/command-palette.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Command Palette integration for User Switching.
*/

( function () {
const commandsStore = wp.commands.store;
const coreStore = wp.coreData.store;
const dispatch = wp.data.dispatch;
const useSelect = wp.data.useSelect;
const useState = wp.element.useState;
const useEffect = wp.element.useEffect;
const useMemo = wp.element.useMemo;
const useDebounce = wp.compose.useDebounce;
const __ = wp.i18n.__;
const sprintf = wp.i18n.sprintf;
const el = wp.element.createElement;
const Dashicon = wp.components.Dashicon;

function useDebouncedValue( value ) {
const [ debouncedValue, setDebouncedValue ] = useState( '' );
const debounced = useDebounce( setDebouncedValue, 250 );
useEffect( function () {
debounced( value );
return function () {
debounced.cancel();
};
}, [ debounced, value ] );
return debouncedValue;
}

const settings = window.userSwitchingCommands;

if ( ! settings ) {
return;
}

const switchIcon = el( Dashicon, { icon: 'admin-users' } );

// Static command: Switch Back.
if ( settings.switchBackUrl ) {
dispatch( commandsStore ).registerCommand( {
name: 'user-switching/switch-back',
label: settings.switchBackLabel,
searchLabel: `Switch back ${ settings.switchBackLabel }`,
icon: settings.switchBackAvatar ? el( 'img', {
src: settings.switchBackAvatar,
alt: '',
} ) : switchIcon,
callback: function ( args ) {
document.location.href = settings.switchBackUrl;
args.close();
},
} );
}

// Static command: Switch Off.
if ( settings.switchOffUrl ) {
dispatch( commandsStore ).registerCommand( {
name: 'user-switching/switch-off',
label: settings.switchOffLabel,
// This facilitates searching either in English or the localized language
searchLabel: `Switch off ${ settings.switchOffLabel }`,
icon: switchIcon,
callback: function ( args ) {
document.location.href = settings.switchOffUrl;
args.close();
},
} );
}

// Dynamic command loader for searching users to switch to.
if ( settings.canSwitchUsers ) {
dispatch( commandsStore ).registerCommandLoader( {
name: 'user-switching/switch-to-user',
hook: function useSwitchToUserLoader( options ) {
// This facilitates searching either in English or the localized language
// and trims the search query to just the name.
const trimmedSearch = options.search
.replace( new RegExp( settings.switchToLabel, 'i' ), '' )
Comment thread
johnbillion marked this conversation as resolved.
.replace( /Switch to/i, '' )
.trim();
const search = useDebouncedValue( trimmedSearch );

const { users, isLoading } = useSelect( function ( select ) {
if ( ! search ) {
return { users: [], isLoading: false };
}

const query = {
search: search,
per_page: 10,
context: 'view',
};
const core = select( coreStore );

return {
users: core.getEntityRecords( 'root', 'user', query ) || [],
isLoading: ! core.hasFinishedResolution( 'getEntityRecords', [ 'root', 'user', query ] ),
};
}, [ search ] );

const commands = useMemo( function () {
return users
.filter( function ( user ) {
return user.user_switching_url;
} )
.map( function ( user ) {
const avatarUrl = user.avatar_urls && ( user.avatar_urls[ '48' ] || user.avatar_urls[ '24' ] );

return {
name: `user-switching/switch-to-${ user.id }`,
label: sprintf(
/* translators: %s: User's display name. */
__( 'Switch to %s', 'user-switching' ),
user.name,
),
// This facilitates searching either in English or the localized language
searchLabel: sprintf(
/* translators: %s: User's display name. */
__( 'Switch to %s', 'user-switching' ),
`${ user.name } ${ user.slug } Switch to ${ user.name } ${ user.slug }`,
),
icon: avatarUrl ? el( 'img', {
src: avatarUrl,
alt: '',
} ) : switchIcon,
callback: function ( args ) {
document.location.href = user.user_switching_url;
args.close();
},
};
} );
}, [ users ] );

return {
commands: commands,
isLoading: isLoading,
};
},
} );
}
} )();
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0"?>
<ruleset name="User Switching">
<arg name="cache" value="tests/cache/phpcs.json"/>
<arg name="extensions" value="php"/>

<config name="minimum_wp_version" value="6.2"/>
<config name="testVersion" value="7.4-"/>
Expand Down
2 changes: 1 addition & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ This feature is useful if you have multiple users on your site who may be switch

### Can I switch users directly from the admin toolbar?

Yes, there's a third party add-on plugin for this: [Admin Bar User Switching](https://wordpress.org/plugins/admin-bar-user-switching/).
Yes, in the [WordPress Command Palette](https://wordpress.org/documentation/article/site-editor-command-palette/) you can search for a user and immediately switch into their account. You can switch off and switch back from there too.

### Are any plugin actions called when a user switches account?

Expand Down
96 changes: 96 additions & 0 deletions user-switching.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
exit;
}

define( 'USER_SWITCHING_VERSION', '1.11.2' );

/**
* Main singleton class for the User Switching plugin.
*/
Expand Down Expand Up @@ -80,6 +82,10 @@ public function init_hooks(): void {
add_action( 'admin_bar_menu', [ $this, 'action_admin_bar_menu' ], 11 );
add_action( 'shutdown', [ $this, 'action_shutdown_for_wp_die' ], 1, 0 );

// Command Palette integration:
add_action( 'rest_api_init', [ $this, 'action_rest_api_init' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'action_enqueue_command_palette_script' ] );

// BuddyPress integration:
add_action( 'bp_member_header_actions', [ $this, 'action_bp_button' ], 11 );
add_action( 'bp_directory_members_actions', [ $this, 'action_bp_button' ], 11 );
Expand Down Expand Up @@ -1144,6 +1150,96 @@ public static function secure_auth_cookie(): bool {
return ( is_ssl() && ( 'https' === wp_parse_url( wp_login_url(), PHP_URL_SCHEME ) ) );
}

/**
* Registers a REST API field on the users endpoint containing the "Switch to" URL.
*/
public function action_rest_api_init(): void {
register_rest_field( 'user', 'user_switching_url', [
'get_callback' => function ( array $user ): ?string {
if ( ! current_user_can( 'switch_to_user', $user['id'] ) ) {
return null;
}

$target = get_userdata( $user['id'] );

if ( ! $target ) {
return null;
}

return wp_specialchars_decode( self::switch_to_url( $target ) );
},
'schema' => [
'type' => [
'string',
'null',
],
'description' => __( 'The URL to switch to this user.', 'user-switching' ),
'readonly' => true,
],
] );
}

/**
* Enqueues the command palette integration script.
*/
public function action_enqueue_command_palette_script(): void {
if ( ! wp_script_is( 'wp-commands', 'enqueued' ) ) {
return;
}

$old_user = self::get_old_user();
$can_switch_off = current_user_can( 'switch_off' );

if ( ! $can_switch_off && ! $old_user ) {
return;
}

$settings = [
'canSwitchUsers' => $can_switch_off,
'switchToLabel' => str_replace( '&nbsp;', ' ', __( 'Switch&nbsp;To', 'user-switching' ) ),
];

if ( $old_user instanceof WP_User ) {
$switch_back_url = add_query_arg( [
'redirect_to' => rawurlencode( self::current_url() ),
], self::switch_back_url( $old_user ) );

$settings['switchBackUrl'] = wp_specialchars_decode( $switch_back_url );
$settings['switchBackLabel'] = self::switch_back_message( $old_user );
$settings['switchBackAvatar'] = get_avatar_url( $old_user->ID, [ 'size' => 48 ] );
}

if ( $can_switch_off ) {
$switch_off_url = self::switch_off_url();
$redirect_to = is_admin() ? self::get_admin_redirect_to() : [
'redirect_to' => rawurlencode( self::current_url() ),
];

if ( is_array( $redirect_to ) ) {
$switch_off_url = add_query_arg( $redirect_to, $switch_off_url );
}

$settings['switchOffUrl'] = wp_specialchars_decode( $switch_off_url );
$settings['switchOffLabel'] = __( 'Switch Off', 'user-switching' );
}

wp_enqueue_script(
'user-switching',
plugins_url( 'js/command-palette.js', __FILE__ ),
[ 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n' ],
USER_SWITCHING_VERSION,
true
);

wp_set_script_translations( 'user-switching', 'user-switching' );

wp_localize_script(
'user-switching',
'userSwitchingCommands',
$settings
);
}

/**
* Adds a 'Switch back to {user}' link to the WooCommerce login screen.
*/
Expand Down