Dealing with big WordPress plugins

This post has been sitting as a draft for over 2 years now and I don’t remember why I didn’t just hit publish at the time. I’m no longer at WSU, but I do still agree with what I wrote, so here you go!

May 15, 2017:

I spend quite a bit of time on plugins at WSU.

One of the challenges is finding the right way to provide a variety of features to 4500 users while not sacrificing security, performance, and stability.

Every time we need a feature, we’re faced with two options:

  1. Use an existing plugin.
  2. Create a new plugin.

The best, best, best answer is to use an existing plugin. When this happens, it means that something out there not only meets the requirements, but happens to align with our other guidelines:

  • Has an open source license that is compatible with the GPL v2 (or later) license.
  • Has an open development process and a well defined path to contribution.
  • Solves a very specific need and does not contain a large amount of code.
  • Passes a full code review by WSU’s web team or other trusted members of the web community.

When I started tackling all of this a few years ago, I made a few exceptions for some big WordPress plugins. A couple require licenses, a couple have a massive code base, and a couple have fairly closed development processes without a defined path to contribution.

At the time I figured the trade off was a great set of features with a relatively low level of effort. A few years later I now know that it’s these plugins that add the most overhead. Not financial overhead—the world of WordPress plugins is still priced so very low, but time and effort overhead.

There are a handful of smaller plugins we’ve had installed for years now without any kind of issue. They usually solve a limited number of problems and, because of the backward compatibility provided by WordPress, just work.

When a plugin gets big, it becomes more of a product than a solution. It often has a product team focused on improving the feature set, attracting more customers, and increasing revenue. Changes are made that adjust markup on the front end and UI/UX on the back end, all in an effort to improve the experience for the product’s customers.

As the maintainer of a diverse platform powering 1500 sites at a university, I’m more often looking for solutions rather than products.

To me, these are plugins that have made a handful of clear decisions and modified WordPress in a small way. Ideally, there’s a way to unhook each of the decisions or a way to build on the solution in a custom way. I can create a simple plugin that extends with decisions of my own.

So, building on an earlier writeup, if you find yourself in a position where you need to support thousands of sites and users and you have the opportunity to start from scratch, here’s how I’d approach plugins:

  • Keep things simple.
  • If a big plugin is useful, find the right way for it to be activated on only one site.
  • Build extensions that provide access to the big plugin from other sites.
  • Make your own plugins small and focused.
  • Don’t get behind on updates.

I’ll stress a couple of these in more words.

If you give a site owner the ability to activate a plugin, there is a very good chance they will activate the plugin. 1500 sites later, you’ll be digging for hours to see who is really using the plugin when you want to reduce its impact. So make sure big plugins are only activated exactly where you want.

Don’t get behind on updates. The WordPress plugin model doesn’t really account for LTS releases, so security updates and bug fixes are shipped alongside UI/UX changes. If you’ve fallen for a big plugin and the product changes are causing an unexpected burden, you’re still better off updating and dealing with it now.

And get good at saying no in a productive way.

Restricted XML-RPC methods for WordPress mobile app support

For a long time I’ve had the path to xmlrpc.php blocked completely on a handful of sites so that I didn’t have to worry about Things. One thing that this messes with, if you don’t have Jetpack installed, is the WordPress mobile app. Without a WordPress.com connection, the mobile application relies on the XML-RPC API provided by your WordPress.

So I made a couple plugins.

First, the one you shouldn’t use in production, Log XML-RPC Requests, does exactly what it implies: logs incoming XML-RPC requests to a WordPress site as a custom post type.

I activated this on a site and then went screen by screen through the WordPress Android application to determine what XML-RPC methods were absolutely required in order for things to work.

That data was then used in the Restricted XML-RPC Methods plugin. This rejects any XML-RPC request that is not one of those required by the WordPress Android application. And pingbacks, because they’re underused and will always have a special place in my heart. 🙂

Any extra methods required by the WordPress IOS app are not enabled, but only because I don’t use an iPhone as my primary device. I’m happy to add those in if somebody passes me the list. It’s also possible that it just works!

Encoding serialized attributes for Gutenberg blocks in PHP

I stumbled backwards into this and didn’t see it discussed anywhere else yet, so here’s a path for anyone else finding themselves temporarily confused and internet searching.

Say you’ve created a custom block for the WordPress block editor, Gutenberg. In addition to regular content, there are one or more attributes associated with the block:

<!-- wp:jf/custom { "data":"Some data assigned as an attribute." } /-->

Now that you have the block in place, you want to migrate the meta from thousands of posts into this structure and inject it into the post content. This data can contain all sorts of characters, including quotes and HTML markup.

If you enter quotes and HTML markup directly into Gutenberg, the content is saved like so:

<!-- wp:jf/custom { "data":"Hey, a \u003Ca href=\u0022http:s\/\/jeremyfelt.com\/\u0022\u003Elink to my website\u003C\/a\u003E." } /-->

This works because the Block API replaces a handful of special characters via regex after JSON.stringify()has done the initial translation.

WordPress does not have a PHP mirror of the Block API’s serializeAttributes(), but the existing functions for encoding JSON are really flexible. Per the documentation for json_encode(), which itself is used by wp_json_encode(), a handful of constants are available to apply as a bitmask to ensure specific characters are encoded in a specific way.

So we end up with this:

<?php
// String from non-Gutenberg data source.
$string = 'Hey, a <a href="https://jeremyfelt.com/">link to my website</a>.';

$attribute = wp_json_encode( $string, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE );

// And $attribute is now:
// "Hey, a \u003Ca href=\u0022https:\/\/jeremyfelt.com\/\u0022\u003Elink to my website\u003C\/a\u003E."

// If storing with wp_insert_post(), etc...
$attribute = addslashes( $attribute );

$block = '<!-- wp:jf/custom { "data":' . $attribute . ' } /-->';

And the block string can now be stored as part of the post content.

Note: addslashes() is needed if storing the data with wp_insert_post(), etc… because those strip slashes and \u003Ca looks like a slash that needs to be stripped unless it itself is slashed.