At my day job, we have about 1,000 sites spread across 30 WordPress multisite installs. The installs all run many of the same plugins and settings, especially at the network level. This causes a lot of wasted time for our staff: They have to manually repeat the same settings across 30 installs. Because of this, we're moving to something I like to call "Remote Control WordPress".
We designate one install as the "control", where we configure network settings, and then the other 30 "client" installs get their network settings from the control.
It's going to take me several articles to lay this all out. I hope you pore over every pain-staking and obscure detail. This method has the potential to save us dozens of man-hours per year, and also greatly reduces the chance for human error. Fewer clicks means fewer mistakes! It's a tricky approach and new approach, but I'm stoked about it.
Here's an overview, before we dig in deeper:
- The control install exposes data via the WP REST API V2 plugin.
- The control install runs a custom plugin to add network settings to the WP API (they're not in there by default). This custom plugin also registers a global variable that gives us a programmatic way to distinguish between the control install and the other 30 client installs.
- The control install runs the Oauth1 plugin to secure the settings from public browsing, yet expose them to our scripted requests from the client installs.
- Both the control install and our 30 client installs run many "feature" plugins: custom network plugins that provide common WordPress hackery, such as a custom logo on the wp-login screen.
- Both the control install and our 30 client installs run a custom plugin for managing network settings. It provides an abstraction layer for our feature plugins to get their settings from the control install.
What You'll Need to Follow Along
- Two WordPress multisite installs: One will be the control install, and the other will act as a client install. In my examples, the control install will be my personal website, and the client install will be my local MAMP. At time of writing, both installs are on WordPress 4.5.3. In this first article, we'll only need the control install. Subsequent articles will involve the client install.
- A total of five plugins, as I eluded to above. Some of them are on GitHub and some of them are in the .org repo. Some of them are written by me, and some of them are written by people way smarter than me. Some of them will likely be part of core someday, and some of them are just for demonstration. I'll address them each as they're needed. This first article will require two of the five plugins.
- The theme doesn't really matter, but my examples will depict twentysixteen. If you have any problems following along, mimicking that would be a good practice.
Are you with me so far? I realize this has been a fast overview, but just like at the end of middle school dances, we're about to slow things down. Our goal for the rest of this article is to expose network settings to the WP API on our control install. We'll get to the rest of the components in subsequent articles. Fire up your test installs and follow along!
Alright, on your control install, try browsing
http://example.com/wp-json/wp/v2/posts. This should get you a 404 error.
Now install and network activate the WP REST API V2 plugin and browse
/wp-json/wp/v2/posts again. You should get something like the following:
Sweet! You have JSON data now. Surf around for a bit, using the endpoints in the docs as a guide -- or better yet, the link urls in the JSON response itself. For more details, also see this excellent article by Andy Adams on fetching posts via the API.
You might notice in the docs that network options are not available for fetching, but we'll fix that soon. For now, just load
/wp-json/css_tricks_wp_api_control/v1/ url on your control server and note the 404. Then, install and network activate my CSS Tricks WP API Control plugin. That
/wp-json/css_tricks_wp_api_control/v1/ url should work for you now, returning some data about the
network_settings route my plugin registers.
If you squint hard enough, you'll pick up on the most interesting part of the response:
That's telling you that my endpoint accepts one argument, called
meta_key, and that it's required.
/wp-json/css_tricks_wp_api_control/v1/network_settings -- what's that url all about? The
/wp-json/ portion is ever-present for the WP API. That just means you're getting JSON. The
/css_tricks_wp_api_control/ portion is the namespace for my plugin. Note that core uses the namespace
/wp/, such as in the posts example above. The
/v1/ indicates that this is version 1 of the endpoint that my plugin is using. I could update this version number if I want to break backwards compatibility, but otherwise I would not, say, update this each time I update the version number of my plugin itself. Finally, I've chosen to register the
/network_settings/ endpoint for getting network settings.
Digging into the Control Plugin
The control plugin has two purposes. First, you'll see that it registers a global variable,
CSS_TRICKS_WP_API CONTROL, which other plugins can sniff for in order to determine if they are running on the control install or a client install.
Second, you'll see that it registers network options for querying in the WP API. That file is thoroughly commented -- please give it a quick read. For now, I'll highlight two functions:
callback() is what returns the network settings from the database. It requires a
GET variable for meta_key, giving us a way to get a specific network option. You can see that it's required if you try to load
/wp-json/css_tricks_wp_api_control/v1/network_settings without tacking on a
?meta_key=whatever: You get a 400 admonishing you for failing to provide a meta key.
permission_callback() designates that, when requesting data from our custom route, the we must provide some authentication in order to see the network settings in the WP API, otherwise we'd probably have a security problem. We'll do this via the OAuth1 plugin in the next article. For now, you can see that auth is required if you try to load
/wp-json/css_tricks_wp_api_control/v1/network_settings?meta_key=whatever: In this case, you get a 403 spanking you for failing to authenticate.
If you're interested in exploring the
network_settings endpoint without dealing with authentication, you can omit the call to
permissions_callback, or make it always return
TRUE, but beware that it's a security hole.
This file could have been written much differently. For a more complex endpoint, you'd be better off extending the WP Rest Controller. I think that would be overkill for our case, and certainly would be a tutorial unto itself.
There are a few different things you should be wondering about at this point.
One is authentication. How are the client installs supposed to make it past
permission_callback()? We'll accomplish this in the next article via a long and brutal battle with the OAuth1 plugin.
Another is DRY/WET. If our 30 client installs run many feature plugins, wouldn't it be a drag if all of those feature plugins hard-coded a query to the control install? Therefore, we're going to write an abstraction layer that all of our feature plugins can use to query the control blog. The control blog will even use it to get settings from itself! Heady stuff; I'll save that for the third and final article in this series.
Then there's performance. We're going to make an http request each time we need a lousy setting? Nope. We're gonna cache the results, which I'll also demonstrate in the third article.
More generally, you might just have the objection that this seems like a lot of work. To this I'd say, compared to what? I'm writing this series because I think it's better than having a staff manage the same settings across 30 installs. I think by the end, you may agree!
If you're having trouble following along thus far, here are the best places to go next:
- Buying the WROX book on plugin development is like buying yourself a career.
- Torque's WP API book. These guys run WP Engine - need I say more?
- The WP API Docs, where the section on extending the API is particularly relevant to the terrain we've covered thus far.
Ready For More?
If you're following so far, brace yourself for a savage dance with OAuth in the next article!