Hey there! I spend quite a bit of time creating unique plugins for WordPress, which often means tweaking and adding actions for different roles. It’s super important for me to see things from each role’s perspective. To do so, I could log out of my developer account and log back in as a different user or login to website in incognito mode, but let’s be honest, that can be a real hassle and a time sink.
So, to make my life easier, I came up with a nifty solution: a WordPress plugin that lets me flip between users, kind of like how you can swap between Google accounts. This handy tool doesn’t just save time, it also makes my user testing experience more accurate. I thought the idea of swapping between different user accounts on the fly was pretty cool, and that’s exactly what I’ve managed to do. Hope you find it as useful as I do!
The determine_current_user Hook
Lucky for us, WordPress, our user-friendly content management system, features a useful tool called the determine_current_user filter. As the name suggests, this handy feature allows you to set the user who is currently logged into the system.
<?php
apply_filters( ‘determine_current_user’, $user_id );
By default, the function registered to this hook operates like a detective, meticulously reviewing the information from the request’s cookies to determine the identity of the current user.
It’s as if it was tailor-made for our specific needs! The first step in this process is to simply let WordPress do what it does best – processing the request and determining who the current user is. WordPress performs this task with remarkable precision and efficiency, freeing us to focus on the next steps.
Once WordPress has determined the current user, we then step in by adding a filter to add our own unique touch to the process. We achieve this by filtering this user ID through our own set of criteria and conditions, and then returning the user id. However, our function, the user is returned according to our own set of rules and not just WordPress’s. This gives us a greater degree of control and flexibility, allowing us to tailor the process to suit our specific needs.
User-Faker Plugin Design
Let’s dive into the fun world of our user switcher’s functionality, which is based on some cool logic. So, you’re working with WordPress, right? You know the user who is currently logged in and active on the platform? Let’s call them the “real user”. If this real user has the magic power to operate as a different user, that’s where things get pretty interesting. We give will give him a list of all users in the admin bar, and the real user can pick anyone from this list to pretend to be – let’s call this user ‘fake user’. Fun, right?
Choosing a user to impersonate is as easy as pie. When the real user picks someone from the list, we use a little bit of Javascript magic to save the user ID of the selected user in a our cookie. After that, we reload the page to show the new changes.
Now, If our cookie is around, it’s going to hold onto the fake user ID for us. Now, when the determine_current_user hook comes into play, we’ll pass back this fake user ID through our filter. What does this mean? Well, it simply makes WordPress act like our fake user is the one who’s logged in and asking for the page. Pretty cool, huh?
Filter determine_current_user for User-Faker Plugin
So, let’s start coding. First we will implement determine_current_user filter.
<?php
class UserFaker {
// ...
public function setFakeUser(): void {
add_filter('determine_current_user', function ($userId) {
$this->realId = $userId ?: 0;
if ( $this->canFakeUser($this->realId) ) {
$fakeId = $this->fetchFakeId();
if ($fakeId > 0) {
$this->fakeId = $fakeId;
return $this->fakeId;
}
}
return $userId;
},50);
}
// ...
}
In the determine_current_user filter of UserFaker, here’s what happens: First, we tuck away the id of the real user just in case we need it later. Then we check to see if the user can impersonate others (you know, pretend to be someone else). If they can, we grab the chosen fake userId. Now, if the fake userId is valid (in other words, greater than 0), we hang on to it for later and give it back. But if it’s not, no worries—we just go back to the logged-in user, just like WordPress would normally do.
Now, let’s implement the missing functions:
<?php
class UserFaker {
// ...
private function canFakeUser(int $userId) : bool {
return $userId > 0 && user_can( $this->realId, $this->capFakeUser );
}
private function fetchFakeId(): int {
return intval($_COOKIE[$this->cookieFakeId]);
}
private string $cookieFakeId = 'wordpress_userFakeId';
private string $capFakeUser = 'fake_user';
// ...
}
The canFakeUser function is our little helper when we want to see if a user can step into the shoes of another user. It starts by checking if our user is actually logged in – we wouldn’t want any anonymous passers-by getting in on the action, would we? In tech speak, it’s making sure the $userId is more than 0. Then, it gives a quick check to see if our logged-in friend has the fake_user capability. Pretty simple, don’t you think?
The fetchFakeId function, with a spring in its step, will peek at the integer value of the cookie wordpress_userFakeId if it happens to be there. If it doesn’t find one, that’s totally okay! It’s content to simply return a 0. We’ll be setting this cookie via javascript later when we introduce the user selector in the admin bar.
User Selector for User-Faker Plugin
Alright, now let’s get our hands on presenting a list of users in the admin bar. To kick things off, we’ll whip up an endpoint that kindly hands over all users with their login-names and Ids.
The users endpoint
<?php
class UserFaker {
// ...
private function addRoutes() : void {
add_action('rest_api_init', function () {
register_rest_route('user-faker', "/users", [
'methods' => 'GET',
'callback' => function (\WP_REST_Request $request) {
return [
'code' => 0,
'users' => $this->findValidUsers()
];
},
'permission_callback' => function () {
return $this->canFakeUser($this->realId);
}
]);
});
}
// ...
}
When we’re setting up our endpoint, we’re going to get to know a nifty function called register_rest_route. This little helper gets called during the our rest_api_init hook. Think of register_rest_route and the rest_api_init hook as a dynamic duo, they work together like peanut butter and jelly. But remember, this function can only be used within this specific hook, or after it’s been triggered. It’s a bit picky about timing and order. So, always double-check and make sure register_rest_route is called at just the right time in the process. you wouldn’t want to WordPress to scream it you, would you?
We’ve added a new GET endpoint under the user-faker namespace, namely /users. As always, you can access this using the endpoint /wp-json/<namespace><route>. So, our neat little endpoint is reachable at /wp-json/user-faker/users. This will give you valid users along with their login-names and IDs, courtesy of our findValidUsers() function. You’ll find these in the users property. Also, we’ve added a code property with a value of 0, which is our way of telling you everything went smoothly. So, if you’re consuming this endpoint, make sure to check if the code is 0. (Just a heads up, we may add other codes in the future. For example, we might use 1 to indicate an error when fetching the valid users).
Our final step is setting up a permission_callback. This is a crucial step that ensures only those with the unique ability to impersonate another user can access our user list. We take security very seriously, especially when it comes to sensitive data. So, we try to limit endpoint usage to prevent any unwelcome access or modifications. Just a heads up, we’re using the canFakeUser function with the $realId here. But no worries, our trusty determine_current_user hook has already filled out the $realId. So, rest assured, we’ve got everything under control!
We’re also using this handy little function to track down the login names and IDs of our valid users.
<?php
class UserFaker {
// ...
private function findValidUsers(): array {
$query = new \WP_User_Query([
'order' => 'ASC',
'orderby' => 'user_login',
]);
return array_map(function (\WP_User $user) {
return ['id' => $user->ID, 'login' => $user->user_login];
}, $query->get_results());
}
// ...
}
Now, findValidUsers is pretty simple – it uses WP_User_Query, a nifty tool in WordPress that helps to fetch users. Once it’s done fetching the users based on our criteria (right now, we’re scanning for all users in the system, sorted by their login names), it transforms them into an array of login-names and their ids. Neat, isn’t it?
I am thrilled to announce that we have just completed the process of writing the endpoint. It’s now ready for testing, which is the next crucial step in our process. Now, bear in mind, only users with fake_user capability will be able to access the endpoint. Don’t worry, we’re going to add this capability to the admin role.
<?php
class UserFaker {
// ...
private function addCaps() : void {
$role = get_role('administrator');
$role->add_cap($this->capFakeUser);
}
// ..
}
So, if you’re among the privileged ones with the necessary permissions, here’s what you should expect to see. It will appear somewhat similar to this sample we’re providing.
{
"code": 0,
"users": [
{
"id": 1,
"login": "dan"
},
{
"id": 2,
"login": "jimmy"
}
]
}
Just a heads up though, if you’re missing the necessary permissions, you might run into a pesky 401 error.
{
"code": "rest_forbidden",
"message": "Sorry, you are not allowed to do that.",
"data": {
"status": 401
}
}
Adding the user selector to admin bar
Alright, let’s add the user selector to the admin bar. We’ll insert an empty menu item with an empty child item, but only if the user has the fake_user capability. Don’t worry, these empty items won’t remain empty for long. We’ll populate them with users from our /users endpoint using JavaScript later.
<?php
class UserFaker {
// ...
private function addAdminBar(): void {
add_action('admin_bar_menu', function (\WP_Admin_Bar $bar) {
if ( $this->canFakeUser($this->realId) ) {
$bar->add_menu([
'id' => 'fakeUserTitle',
'title' => '',
'meta' => [
'class' => 'fakeUserTitle'
],
]);
$bar->add_menu([
'id' => 'fakeUserBody',
'parent' => 'fakeUserTitle',
'title' => '',
'meta' => [
'html' => '',
'class' => 'fakeUserBody'
]
]);
}
},101);
// ...
}
// ...
}
In the title menu item, we’ll display the ‘fake user’ if there is one, otherwise the real user will take the spotlight. When clicked, a handy drop-down table will appear as the child menu item, listing all the users and a special option to ‘act as the real user’.
Here’s the fun part – when a user clicks on one of those table rows, we’ll store the selected user id in our cookie (and if they opt to ‘act as the real user’, we’ll simply remove the cookie). Then, we’ll give the page a quick refresh. Isn’t that neat?
Adding Assets
So, let add the css and javascript files that will do the magic. To add the CSS file, we’ll use wp_enqueue_style and for the Javascript file, we’ll use wp_enqueue_script. You might be thinking we should include those assets in the admin_enqueue_scripts hook, as we only need the scripts when the admin bar is displayed. But here’s a little twist – the admin bar also shows up in the front-end of WordPress. That’s right, pages where is_admin() returns false, like the home page and post pages, outside the wp-admin directory. So, we’ll wisely include it in both wp_enqueue_scripts and admin_enqueue_scripts hooks.
<?php
class UserFaker {
// ...
private function addAdminBar(): void {
// ...
$addAssets = function () {
if ($this->canFakeUser($this->realId)) {
wp_enqueue_script('user-faker-1', plugins_url('/assets/main.js', pluginFile));
wp_enqueue_style ('user-faker-2', plugins_url('/assets/main.css',pluginFile));
}
};
add_action( 'wp_enqueue_scripts' , $addAssets);
add_action( 'admin_enqueue_scripts' , $addAssets);
}
// ...
}
You know, I absolutely love creating the magic using JavaScript and CSS. But, I’ve found that TypeScript and SCSS are really my best friends when it comes to coding. They just make everything so much more manageable.
You’ve probably heard of TypeScript, right? It’s a statically typed superset of JavaScript that brings some awesome typing into the mix. It makes the code super easy to read and understand, which means debugging is a breeze and the overall code quality just skyrockets. The only downside is that you have to take an extra step to compile the TypeScript (.ts) files into JavaScript using the ‘tsc’ tool, but that’s a small price to pay for such great benefits.
SCSS, or Sassy CSS as I like to call it, is another gem. It has a really expressive syntax with cool features like variables, nesting, and mixins. This makes writing CSS feel like a party! But just like TypeScript, SCSS files need to be compiled into CSS using the ‘sass’ tool.
Sure, these processes need a few extra steps, but honestly, the gains in code readability, manageability, and scalability are totally worth the extra effort. And to make the compilation process as smooth as butter, I use file watchers in my IDE. File watchers are like little spies that keep an eye on the files for changes and automatically run the necessary compilation tasks. This lets me focus more on the fun part – the actual coding – and less on the behind-the-scenes stuff.
The TypeScript File – main.ts
Let’s start by adding some typing to our TypeScript file, shall we? We want to make sure the data we get back from the /users endpoint is just what we’re expecting. We’ll define two types: User and UserList, kind of like creating a blueprint for the data structure we’re hoping to see from our endpoint. It’s a neat little trick that not only makes our code more predictable but also easier to debug and keep up with in the long run. Isn’t that cool?
type User = {
id:number,
login:string
}
type UserData = {
code: number,
users: User[],
realId: number,
fakeId: number
}
Let’s kick things off by introducing a class called UserFaker. This nifty class comes with two HTML elements that make a special appearance only when the user has been bestowed with the fake_user capability. The first one, our good friend elemHead, is always hanging out, visible on the admin bar. Give it a click and voila! A popup menu featuring the elemBody element comes into view.
class UserFaker {
constructor() {
this.elemHead = (document.getElementsByClassName(this.idHead)[0])?.children[0] as HTMLElement;
this.elemBody = document.getElementsByClassName(this.idBody)[0] as HTMLElement;
// ...
}
private readonly elemHead: HTMLElement | null;
private readonly elemBody: HTMLElement | null;
private readonly idHead:string = 'fakeUserHead';
private readonly idBody:string = 'fakeUserBody';
}
Considering the fact that we are accessing the elements of the Document Object Model (DOM), it is crucial that the class needs to be initialized in the DOMContentLoaded. DOMContentLoaded is an event that fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. By initializing the class in this event, we ensure that all the DOM elements we need to interact with are fully loaded and accessible, which is critical for the proper functioning of our plugin.
window.addEventListener( 'DOMContentLoaded', () => {
const uf: UserFaker = new UserFaker();
})
When above elements are spotted, a request zips off to the /users endpoint. And guess what? We’re focusing on two specific users: ‘realUser’ and ‘fakeUser’. As soon as they’re identified, these users get a VIP pass for some special treatment. ‘RealUser’ is displayed first, and if a ‘fakeUser’ happens to be around, it is displayed up next. Once we’ve taken care of these priority users, our plugin happily display all the rest of the users in the list.
class UserFaker {
constructor() {
if ( this.elemBody && this.elemHead) {
fetch(this.endpoint).then((res: any) => {
res.json().then((data: UserData) => {
if ( data.code !== 0 ) {
return
}
const popUser = (arr: User[], id: number): User|null => {
for (let kk = 0; kk < arr.length; kk++) {
if (arr[kk].id === id) {
const user = arr[kk];
arr.splice(kk,1);
return user;
}
}
return null;
};
const realUser: User|null = popUser(data.users, data.realId);
const fakeUser: User|null = popUser(data.users, data.fakeId);
let body: string = '';
let head: string = '';
body += this.addUser( {id: 0 , login: "Real User"} , this.isRealUser(data) );
if ( realUser !== null ) {
body += this.addUser( realUser, this.isRealUser(data));
}
if ( fakeUser !== null ) {
body += this.addUser( fakeUser, true);
}
for( const user of data.users ) {
body += this.addUser(user, user.id === data.fakeId || (data.fakeId === 0 && user.id === data.realId) );
}
(this.elemBody as HTMLElement).innerHTML = body;
}
}
Now, let’s go ahead and display our friendly fakeUser in the elemHead if it exists! If not, no worries, we’ve got our trusty realUser with the “Real:” prefix ready to stand in.
// ...
if ( fakeUser !== null ) {
(this.elemHead as HTMLElement).innerHTML = `${fakeUser.login}`;
} else if ( realUser !== null ) {
(this.elemHead as HTMLElement).innerHTML = `Real: ${realUser.login}`;
}
// ...
Alright, let’s now add a click handler for each user in elemBody.
let elems = document.getElementsByClassName('uf');
for( let kk=0; kk < elems.length; kk++ ) {
elems[kk].addEventListener('click', () => {
this.clickUser(elems[kk] as HTMLElement)
}, false);
}
In our friendly little clickUser function, what we do is check if our user element isn’t already the chosen one (aka, if it doesn’t have the ‘active’ class). If it’s not, we kindly remove the ‘active’ class from any other user elements and pass it on to our newly selected element. Then, it fetches the ID of this selected element and updates our cookie with this selected ID (and just between you and me, if the ID happens to be that of the real user, we’ll just go ahead and remove the cookie) and then reload the page.
private clickUser(elem: HTMLElement) {
if ( !elem.classList.contains('active') ) {
elem.classList.add('active');
const id = elem.dataset.id;
let elems = document.getElementsByClassName('uf');
for (let kk: number = 0; kk < elems.length; kk++) {
const elem1: HTMLElement = elems[kk] as HTMLElement;
if (elem1.dataset.id !== id) {
elem1.classList.remove('active');
}
}
this.updateCookie(parseInt(elem.dataset.id ?? '') );
window.location.reload();
}
}
private updateCookie(id: number|null) {
const keyFake = '$this->keyFake';
if ( id !== -1 && id !== null ) {
document.cookie = `${this.keyFake}=${id}; path=/ ; Secure`;
} else {
document.cookie = `${this.keyFake}=; path=/ ; Secure ; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
}
}
The last but certainly not least step in our journey is to sprinkle some magic on the visual aspects of our plugin. We’ll be diving into our main.css file and adding a few special styles. This way, we’ll give our plugin a bit more pizzazz, making sure it’s not only super useful, but also a treat for the eyes!
Your Feedback
I am absolutely thrilled to announce that after extensive work, we has successfully completed the development of our new plugin. I find this plugin useful, and I am confident that it will significantly improve your experience.
I would love to hear from you! Are you enjoying the plugin? What areas do you think could use improvement? I am all ears!
You can find the GitHub Repository for the Plugin at https://github.com/geekfomo/user-faker.