Getting the REST API ready for Multisite
A WordPress Core case study
Presented by @felixarntz
Multisite contributor
Not about decision, but how we're getting there
Basic understanding of REST API and multisite required
But we can sneak our way in there. 😏
Multisite: first core component to announce REST API efforts and collaboration.
James Nylen: driving force with REST API development.
K. Adam White: design lead for the REST API focus.
What do we need to do?
Enhance the wp/v2/users
endpoint to support Multisite functionality
Introduce a wp/v2/sites
endpoint for access to sites in a network
( Introduce a wp/v2/networks
endpoint for access to networks) → Later
users
: some functionality implemented wrong, others not supported at all
sites
: obvious, since these are the main objects we deal with in Multisite
networks
: edge-case functionality; as plugin first, in core later
1. Enhancing the wp/v2/users
endpoint
Fix what's wrong
Implement missing functionality
Unfortunately the merged API included some rushed code to account for Multisite compatibility.
1.a. Fix what's wrong with wp/v2/users
4.7 allowed to read and edit users even when they weren't a member of the current site.
In 4.7 editing a user from another site would automatically result in that user being added to the current site.
This is wrong, but implementing a solid solution will take time.
→ Quick fix: Remove the functionality now!
The current site is always the one you access the REST API through.
1.a. Fix what's wrong with wp/v2/users
In 4.7 the capabilities checked in the REST API when updating a user are wrong.
In multisite, only network administrators can edit other users than themselves. Regular site administrators can only change roles of other users.
In the REST API, network administrators can do these things, however site administrators cannot change user roles.
→ That has not been discussed yet.
Current code
if ( ! current_user_can( 'edit_user' , $user->ID ) ) {
return new WP_Error( 'rest_cannot_edit' , __( 'Sorry, you are not allowed to edit this user.' ), array ( 'status' => rest_authorization_required_code() ) );
}
if ( ! empty ( $request['roles' ] ) && ! current_user_can( 'edit_users' ) ) {
return new WP_Error( 'rest_cannot_edit_roles' , __( 'Sorry, you are not allowed to edit roles of this user.' ), array ( 'status' => rest_authorization_required_code() ) );
}
return true ;
Possible fix
if ( ! empty ( $request['roles' ] ) ) {
if ( ! current_user_can( 'promote_user' , $user->ID ) ) {
return new WP_Error( 'rest_cannot_edit_roles' , __( 'Sorry, you are not allowed to edit roles of this user.' ), array ( 'status' => rest_authorization_required_code() ) );
}
$request_params = $request->get_params();
if ( count( $request_params ) === 2 ) {
return true ;
}
}
if ( ! current_user_can( 'edit_user' , $user->ID ) ) {
return new WP_Error( 'rest_cannot_edit' , __( 'Sorry, you are not allowed to edit this user.' ), array ( 'status' => rest_authorization_required_code() ) );
}
return true ;
promote_users
is the correct capability for role updates (site administrators have it).
promote_user
is a meta capability that maps to promote_users
.
1.b. Implement missing functionality for wp/v2/users
Current state:
You cannot view or edit users from another site.
You cannot add existing users to a site.
You cannot remove users from a site.
You cannot delete users.
The latter two features were discussed and disabled in Ticket #38962 prior to 4.7.
This is what we need to implement.
We have a clean starting base to work from.
That the latter two features were disabled, confirms our decision for the first two.
Challenge: Users are global objects in a Multisite.
Possible solution: Introduce a global
parameter.
It will determine whether a user from another site is being requested.
It will only be available to network administrators.
It will be available redundantly on all sites (easier interaction, no CORS issues).
Read access to users
GET wp/v2/users
will list users from the current site.
GET wp/v2/users?global=true
will list all users.
Read access to a user
GET wp/v2/users/<id>
displays a user from the current site.
GET wp/v2/users/<id>?global=true
displays any user.
Edit access to a user
POST/PUT/PATCH wp/v2/users/<id>
allows editing a user of the current site.
POST/PUT/PATCH wp/v2/users/<id>?global=true
allows editing a user from any site.
Only network administrators can edit users.
Regular admins can only change user roles for the current site.
Creating and adding a user
POST wp/v2/users
creates a new user and adds it to the current site.
POST wp/v2/users?email=<existing-email-address>
adds an existing user to the current site.
→ This is not very clear and we might need to find a better solution.
Related problem: Site administrators can create users, but not edit them. ¯\_(ツ)_/¯
Do we consider creating a user and adding them to the current site a global operation or not?
Deleting and removing a user
DELETE wp/v2/users/<id>
removes a user from the current site.
DELETE wp/v2/users/<id>?global=true
deletes a user completely.
Deleting a user will probably require passing parameters about how to remove that user from each site.
The reassign
parameter is required for removing a user.
2. Introducing a wp/v2/sites
endpoint
Implement a set of functions for a real sites API
Figure out how to support queries by certain site data
Actually building the API endpoint will be straightforward once we have this.
Getting the REST API ready for Multisite
A WordPress Core case study
The complex part about the REST API is not the REST API itself, but to bring the necessary structure to all the different parts of Core.
Getting Multisite ready for the REST API
A WordPress Core case study
We haven't even really talked about the actual REST API endpoint yet. It's rather straightforward though, all we know is that it will be restricted to network administrators.
2.a. Implementing a real sites API
WP_Site
class
→ ✅ in 4.5
WP_Site_Query
class
→ ✅ in 4.6
2.a. Implementing a real sites API
wpmu_create_blog( $domain, $path, $title, $user_id, $meta, $site_id )
function
→ wp_insert_site( $args )
→ wp_install_site( $site_id, $args )
update_blog_details( $blog_id, $details )
function
→ wp_update_site( $site_id, $args )
wpmu_delete_blog( $blog_id, $drop )
function
→ wp_delete_site( $site_id )
→ wp_uninstall_site( $site_id )
There's install_blog( $blog_id, $blog_title )
and wp_install_defaults( $user_id )
as well.
wpmu_delete_blog()
is only loaded when in the admin which does not make much sense.
2.b. Figuring out better query support
Current state:
You cannot query sites by their title, description, language or other important piece of data.
Retrieving those pieces of data requires use of the rather expensive switch_to_blog()
function.
switch_to_blog()
is not crazy expensive (does not make a database request), but still overhead we should avoid.
Example: To provide a list of 20 sites, switch_to_blog()
needs to be called 20 times.
Challenge: These values are stored per site in individual wp_options
tables.
Possible solution:
Approach: Site metadata
wp_posts
→ wp_postmeta
wp_comments
→ wp_commentmeta
wp_users
→ wp_usermeta
wp_terms
→ wp_termmeta
wp_site
→ wp_sitemeta
(Networks!)
wp_blogs
→ wp_blogmeta
? (Sites!)
Old naming conventions: site used to be blog , network used to be site .
Approach: Site metadata
Initial plan: Determine a whitelist of options that should be migrated to the new site metadata table.
There's no way we can migrate all options over to site metadata, as this will cause a massive table.
wp_options
is bad enough as is performance-wise.
Approach: Site metadata?
Andy Skelton: Has worked at Automattic on wordpress.com since the beginning.
How do we want the data to be stored?
Core obviously cannot require something like Elasticsearch, but is there a viable alternative?
Approach: Site metadata
Currently discussed: Introduce a site meta table independently from options, as this will open up the new `wp/v2/sites` endpoint to developers.
What is metadata?
Are all options actually site metadata?
Are options a subset of site metadata?
Are options and site metadata something completely different?
Conceptual issue, still ongoing discussion.
Which data do we migrate?
Do we even need wp_blogmeta
?
What is metadata?
Looking at the current state of network data in WordPress:
For networks, there is no meta API, instead it has its own options API.
However, all network options are stored in a table called wp_sitemeta
.
wpmu_create_blog()
accepts a $meta
array, and the values in it are then stored as options.
¯\_(ツ)_/¯
→ We're working on that as well.
Apparently Core has not decided itself what metadata is.
We can use your help!
Please join us if you're interested or have ideas to help!
If you got confused by all that, I'm sure you can help us make it less confusing.
Thank you!
Felix Arntz
Plugin Developer / Core Committer / Freelancer
Getting the REST APIready for Multisite
A WordPress Core case study
Presented by @felixarntz
Multisite contributor
Not about decision, but how we're getting there
Basic understanding of REST API and multisite required