Using data stores to get custom user capabilities in WordPress

Once again, I find myself writing a blog post for my own reference, but in the hope that others might arrive here via Google to find a solution to their requirement. In this case, the aim was to find out if a WordPress editor has the requisite capabilities to display a custom PluginDocumentSettingPanel in the editor.

Integrating withSelect to our ReactJS code in the Gutenberg Block Editor allows us to get data relating to the current state of the editor or of the user. For example, we can get the current user data with wp.data.select("core").getCurrentUser() or the current block editor settings using wp.data.select('core/editor').getEditorSettings(). But not everything is available through these functions. Although we can find out whether the current user is allowed to create users by using wp.data.select('core').canUser('create', 'users'), the canUser method requires two parameters: the action create and the object users.

In the plugin under development, the permissions on the specific function – to create a duplicate post with a different format from within the block editor – are assigned with the custom capability shp_printcopy_create. We can’t use canUser here, because we’re creating a regular post. Using wp.data.select('core').canUser('create', 'posts') will return true for all editors: even those who shouldn’t be allowed to use the function at hand.

After a little research, I found that it’s recommended that one avoid hard-coding permissions into the codebase. That means pinging the server to find out whether the user has the relevant capability. I chose to do so by adding a custom REST API endpoint.

register_rest_route('my/prefix', '/can-create-printcopy/', [
	'methods'  => WP_REST_Server::READABLE,
	'permission_callback' => '__return_true',
	'callback' => function(){
		return current_user_can('shp_printcopy_create');
	}
]);

In order to get this data in the block editor, we need to add our own data handler, which fetches the value from the server and saves it to the current state, so that we can use the state value in our component or block code. In WordPress parlance, this is a data store.

We can register a custom data store with a reducer (which handles updates to the state), actions (which allow us to carry out actions within the store), selectors (which we can call in our component/block code), controls (which, in this case, handle the API request), and resolvers (which define the workflows: which endpoint to ping, and then what to do with the data).

This requirement for getting the results of a custom REST API endpoint is pretty common, and so the following example code should be a good starting point.

import apiFetch from '@wordpress/api-fetch';
import { createReduxStore, register } from '@wordpress/data';

// Some default values
const DEFAULT_STATE = {
		can_create: false,
	},
	DEFAULT_ACTION = {};

// Actions which can be carried out on the data store
const actions = {
	setState(item, can_create) {
		return {
			type: 'SET_CAN_CREATE',
			item,
			can_create,
		};
	},
	fetchFromAPI(path) {
		return {
			type: 'FETCH_FROM_API',
			path,
		};
	},
};

// Create a store which we can use via wp.data.select("shp/printcopy")
const store = createReduxStore('shp/printcopy', {
	reducer(state = DEFAULT_STATE, action = DEFAULT_ACTION) {
		// Update the state with the fetched value
		switch (action.type) {
			case 'SET_CAN_CREATE':
				const updated_state = {
					...state,
					can_create: action.can_create,
				};
				return updated_state;
		}

		return state;
	},

	actions,

	selectors: {
		currentUserCanCreate(state, item) {
			// Get the value from the state object
			const { can_create } = state;
			return can_create;
		},
	},

	controls: {
		FETCH_FROM_API(action) {
			// Get the data from the API route
			return apiFetch({ path: action.path });
		},
	},

	resolvers: {
		*currentUserCanCreate(item) {
			// Get the results from the API and update the state object.
			const path = '/my/prefix/can-create-printcopy/';
			const can_create = yield actions.fetchFromAPI(path);
			return actions.setState(item, can_create);
		},
	},
});

register(store);

This data store allows us to use wp.data.select("shp/printcopy").currentUserCanCreate() and get a result from the server. If the value has already been fetched once, then it will be returned from the state object: it won’t ping the server repeatedly. The value of can_create is an asynchronous value and will be false (from the default state) initially, and will then be updated with the value returned from the server.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.