Use the WordPress REST API to create a custom taxonomy term

It’s unreasonably difficult to find information about working with custom taxonomies and their terms in the WordPress REST API. It would be easy to conclude that it’s not possible or that one has to create a custom endpoint to achieve that functionality.

It ends up being fairly straightforward, so I’m sharing what I learned here in case it saves someone else time.

The developer REST API reference endpoint list doesn’t make any obvious reference to custom taxonomies or terms. If you end up on the tags endpoint reference page, you’ll see that there’s a way to work with these terms in the standard post_tag taxonomy, but no way to specify a custom taxonomy during write operations. There’s actually a taxonomies endpoint reference page that doesn’t seem to be linked to from the main navigation anywhere, but can be found in a search; still, nothing on it about working with terms in those taxonomies.

But eventually you’ll find your way to the page Adding REST API Support For Custom Content Types, which has a section Registering A Custom Taxonomy With REST API Support that finally makes it clear that in order to work with custom taxonomies in the REST API, you have to enable/create an endpoint for that purpose.

In your custom taxonomy definition, it ends up being pretty simple with these additional arguments to the register_taxonomy call:

    'show_in_rest'          => true,
    'rest_base'             => 'my-taxonomy-api-slug',
    'rest_controller_class' => 'WP_REST_Terms_Controller',

The second two are optional, where you can define the REST API url slug and controller you want to use; if you leave them out it will use the default terms controller and a slug derived from your taxonomy definition.

With this code in place, you’ll now have two new REST API endpoints available:

 /wp/v2/my-taxonomy-api-slug
 /wp/v2/my-taxonomy-api-slug/(?P<id>[\d]+)

You can then essentially treat those the same way as the tags endpoints, getting/posting to create, update and retrieve terms in that custom taxonomy. For example, to create a new term:

POST https://example.test/wp-json/wp/v2/my-taxonomy-api-slug

slug:my-first-term
name:My First Term

I’ve submitted a pull request to the WP API docs to add at least one pointer in the documentation to help others on a similar quest get there faster.

Updated May 3 @ 11:49 AM Eastern to note that the second two taxonomy registration options rest_base and rest_controller_class are optional to define, thanks to Marcus Kober for noting this.

WooCommerce Subscriptions API start_date bug workaround

The WooCommerce Subscriptions plugin has a bug where if you use its REST API endpoints to make updates to a subscription, it will almost always reset the start date of the subscription you are updating to the current date and time.

I reported the bug to the WooCommerce folks in November 2022 and I believe it has a GitHub issue filed. When I checked in about it recently I was told it’s a low priority to fix. I consider it somewhat serious for my purposes — unexpectedly overwriting/losing key data about a record that’s used in calculating user-facing financial transactions —  so I’m documenting it here for others who might encounter it, along with a possible workaround.

The bug is in the plugin file includes/api/class-wc-rest-subscriptions-controller.php that processes the incoming API request. In particular, in the function prepare_object_for_database() there’s this code:

// If the start date is not set in the request, set its default to now.
if ( ! isset( $request['start_date'] ) ) {
	$request['start_date'] = gmdate( 'Y-m-d H:i:s' );
}

All of the valid dates contained in the $request array are subsequently copied into an array called $dates. Later in the same function, there’s this code:

if ( ! empty( $dates ) ) {
	...
	try {
		$subscription->update_dates( $dates );
	} catch ( Exception $e ) {
		...
	}
}

The implication is that for completely unrelated API requests to change something like, say, a meta field value or the subscription’s status, the date validation and update logic will be run. And because the start date value is overridden to be the current date, it means that any API request to update a completely unrelated field is going to unintentionally reset the start date of the subscription.

Nothing about the documentation at https://woocommerce.github.io/subscriptions-rest-api-docs/?php#update-a-subscription indicates that a start_date value is required in the API requests. In fact, the example given in those docs where the status of a subscription is updated would trigger this bug.

I’ve even noticed this bug surfacing even in regular wp-admin operations involving WooCommerce Subscriptions, as I think some of the logic used to do something like put a subscription on hold or cancel it from within the admin interface is calling the same internal functions.

My workaround for this bug, at least on the API client side, introduces an extra API request, and so is less than ideal for any kind of production or long-term use.

For every API request I make to the Subscriptions API update endpoint, if I’m not explicitly setting/changing the start_date field in my request, I first fetch the existing subscription record and then set the start_date field in my request to the current value.

if ('subscription' === $recordType && empty($updateValues['start_date'])) {
      $current_values = $wooApiFacade::find($recordId);
      $updateValues['start_date'] = Carbon::parse($current_values['start_date_gmt'])->format('Y-m-d\ H:i:s');
}

Hopefully they’ll fix this issue sooner rather than later so that users of the plugin don’t unexpectedly see subscription start dates overwritten.

Customizing Newsletter Glue for WordPress post notifications

I love the Newsletter Glue plugin for WordPress. Before I found it, I had tinkered for a long time with different imperfect solutions to what seemed like a simple need: quickly and easily send people an engaging, visually appealing email when I publish a new article on my website.

I’ve tried manually sending out Mailchimp campaigns after publishing (too slow and cumbersome to customize per post), using messages sent directly from my WordPress host server (too prone to being flagged as spam), automatically generating Mailchimp campaigns from an RSS feed (limited scheduling options and prone to errors), using WordPress.com new post notifications via Jetpack (not very customizable), developing my own plugin to send on demand via MailChimp’s API (very functional but not easily re-usable across sites), and others.

Alas, nothing felt quite like what I wanted.

I’d revisit the challenge now and then, especially when I’d see everybody over there loving on Substack like “oh isn’t amazing how you can send out a readable email newsletter from a website, what an innovation” and found myself rolling my eyes especially hard. “SURELY WE SHOULD BE ABLE TO DO THAT WITH WORDPRESS!” I’d exclaim. And then my family would look at me with tilted heads before going back to whatever they were doing.

I was about to start putting some serious time into turning my own proof of concept Mailchimp API plugin into something reusable and shareable, when I did one last round of research into existing options. And that’s when I found Newsletter Glue. The sky opened up. Light shone down. I did a dance. At least that’s what it felt like. (I see that Justin Tadlock at WP Tavern has had a similar experience.)

So, yes, ahem, where was I? Oh, right: Newsletter Glue is an elegant solution to a real need in the world of WordPress publishing. Go check it out if you haven’t already. I bought a 5-site license and sent Lesley Sim, one of the plugin’s co-creators, a note of appreciation.

The rest of this blog post is about a few additional Newsletter Glue customizations I set up. While some of this is possible via the Newsletter Glue UI, I did it via a small custom plugin that I could re-use across all of my WordPress sites without additional configuration.

Continue reading Customizing Newsletter Glue for WordPress post notifications

Unlocking email content into RSS feeds redux with WordPress and Postie

As a part of some local journalism projects I’m exploring, I wanted to have a way to get information that is being emailed around (press releases, newsletters) into a publicly accessible RSS feed.

I’ve already explored this general “unlock email into an RSS feed” workflow using Zapier but Zapier’s limitations around translating HTML email messages into useful RSS entries led me to explore other options. For a while now I’ve been using Feedly’s paid feature that lets you receive email at a custom address and puts the content into your feed reading experience, and that’s actually been a good solution for me as an individual. (I made sure to set up an address at a domain I control and aliased that to Feedly’s provided address, in case I want to move to another solution later.)

But if we want to help a given audience have better access to information that’s only available in email but is intended to be public, I don’t think it scales well to ask them all to subscribe to the same email newsletter, or to all sign up for a Feedly paid plan. And yet so many organizations continue to use email as a way to distribute information, often instead of a website, and it doesn’t scale well to beg each of them to start (or go back to?) publishing their updates on a website with an RSS feed.

I started looking at using Mailparser for a more generalized solution. Receive the emails, have Mailparser extract the information into a structured, API-queryable format, and then download that information (including any attachments and images) and put it up on a publicly accessible URL somewhere. I knew I needed a way to organize the information coming in according to the email address of the person who sent it, so I’d have to build a small application that managed that categorization during the publishing process.

And then I realized I was basically getting into CMS territory. Publishing text and media on a website. Organizing content by categories and authors. Searchable and sortable. Yeah, I know a tool that already does all that stuff really well: WordPress.

But was I still going to need to build a glue application to process the emails and create WordPress posts?

I was aware of Jetpack’s post by email feature and I think it could work well for some scenarios, but I wanted something a little more purpose built. I did a little bit of Googling and found Postie, a WordPress plugin that has great features for bringing emails into WordPress posts. I exclaimed many words of delight upon finding it, and continued to be impressed as I looked through the thoughtful documentation, the developer-oriented options for extending and customizing it, and the active support and maintenance that goes into it. The WordPress community is amazing that way. I sent the developer a donation.

So, here’s the new workflow I would use:

  • Email is sent to an alias at my custom domain, which goes to a free email provider with IMAP access.
  • Every half hour, Postie goes out and checks for new email messages and creates pending WordPress posts.
  • I get a notification in a Slack channel about the WordPress post, and can publish it if appropriate.
  • The WordPress site provides a built in RSS feed of “emails” as posts on the site.

Amazing! But I still wanted a way to organize the incoming emails and resulting posts based on sender, without using Postie’s default method of creating WordPress users for each sender.

So, I created a custom taxonomy, Sources, with some term meta fields that allow me to associate email addresses and to select whether posts from that source should be pending or published by default. (Of course, email can be forged so it’s never safe to depend on the value of the “From” address in an email to authenticate anything important.)

Here are some code snippets used to accomplish this in a custom WordPress plugin I set up to complement Postie.

Continue reading Unlocking email content into RSS feeds redux with WordPress and Postie

Ending support for some little-used WordPress plugins

Today I am making these three little-used WordPress plugins that I created inactive, in the sense that I will no longer update them, test them against newer versions of WordPress, or provide support for them. If after a time no one else wants to take over maintaining them, they will be closed.

Each plugin has around 10 or fewer active installations and minimal or no user reviews. Given that it takes time to test each plugin I maintain for upcoming WordPress releases and PHP changes, and to support any inquiries or feature requests they may receive, I have to balance the requirements of continuing that work against the value each plugin offers. In these cases, I’d like to free up that maintenance time for other things.

I will leave the GitHub repos public indefinitely in case anyone wants to fork them or take them over.

This change does not affect the other WordPress plugins I maintain.

If you’re interested in taking over responsibility for one or more of these plugins, please contact me.

Thanks to everyone who tried them out and gave feedback along the way.

Create and send custom Mailchimp email campaigns from WordPress

There are lots of Mailchimp + WordPress integrations out there already. But when I wanted to create a customized, automated daily email campaign that would be generated from WordPress content (beyond just a listing of recent posts) and sent to a Mailchimp list, I couldn’t find anything to do just that.

For a little while I used MailChimp’s ability to read RSS feeds and generate campaigns from them, and I built a custom RSS feed on my WordPress site where the latest entry in the feed was the customized content I wanted to be populated into the *|RSSITEM:CONTENT_FULL|* variable to then go out to my subscribers.

But this was unreliable — sometimes the message wouldn’t go out because of caching issues on my end, mysterious days where MailChimp didn’t seem to check the feed, and there were other quirks — and even working with MailChimp support I couldn’t get things to a stable state. I also didn’t like that MailChimp’s system forced me to pick a top-of-the-hour time during the day when the feed would be checked, and if for some reason it was missed, I had to reconfigure the whole campaign to get the message out.

I needed a better way.

The resulting method that’s been working for almost a year now is to initiate and send the Mailchimp campaign directly from within WordPress. I get full control of scheduling, message generation and formatting. I can re-run the campaign send if I need to. And it has worked reliably every day. Here’s how I set it up (with inspiration from this blog post by Isabel Castillo).

Continue reading Create and send custom Mailchimp email campaigns from WordPress

Testing my WordPress plugins in preparation for WordPress core releases

A couple times per year, WordPress plugin authors and owners get an email like this one:

WordPress 5.6 is imminent! Are your plugins ready?

You’re receiving this email because you have commit access or ownership of existing, open plugins hosted on WordPress.org. The next release of WordPress, 5.6, is scheduled for 08 December 2020.

We would like you to take this time to review your existing plugins and ensure their ongoing compatibility with WordPress. Once you’ve done so, you can update the readme “Tested up to:” value to 5.6. This information provides peace of mind to users and helps encourage them to update WordPress.

Here are the current “Tested up to:” values for each of your plugins:

The message goes on from there to list the plugins I’m responsible for and some notes and details about what’s new in the upcoming WordPress release.

In case it’s not clear, this is an important moment because the authors of tens of thousands of WordPress plugins are being asked to help ensure that when the many millions of WordPress sites out there upgrade to the upcoming release, that those sites continue to look and function as expected by their users. It’s an impressive example of how the WordPress developer community works together in the background to help sustain and grow the larger WordPress ecosystem.

For authors of widely used plugins, by the time this email goes out their plugin may already be fully ready, especially if they’ve been following or maybe even contributing to the development of the new WordPress core release. Some plugin authors rightly have an extensive automated test suite in place to confirm that every part of their plugin’s functionality works against the latest beta or release candidate version of the new version before it comes out.

Authors and maintainers of smaller plugins (like me) may not have the same infrastructure set up, and instead need to perform some manual testing of our plugins to ensure they’re ready.

So, here are the steps I follow every major release cycle to make sure my plugins have been tested and are ready for the new version.

Continue reading Testing my WordPress plugins in preparation for WordPress core releases

How long does it take between when a plugin update is released and when auto-updates install it on your WordPress site?

Auto-updates for WordPress themes and plugins were released this year in WordPress version 5.5. They allow WordPress site owners to opt-in to automatically have new versions of plugins and themes installed when they are released, without any intervention from the site owner.

If you use auto-updates, one question might be on your mind:

How long will it take between when the author of a plugin releases a new version and when that new version is installed on your WordPress site?

This question is vital for site owners and managers. Especially in scenarios when new plugin or theme versions contain critical security fixes, time is of the essence to avoid possible unauthorized access to your WordPress site.

To get to the answer, let’s first review how plugin and theme releases happen.

The Plugin and Theme Release Process

When a plugin or theme author is ready to make an update to their software, they upload those changes to the directory on WordPress.org. This is where the code for their theme or plugin is hosted publicly.

Most theme and plugin authors also indicate the release of non-trivial changes by increasing the version number associated with their plugin. Maybe it’s a small “point release” like going from version 1.1 to version 1.2, or maybe it’s a major release like going from version 3.0 to version 4.0. The change in version number lets everyone know that there’s new functionality and fixes available. It’s a convenient way to refer to how software has changed over time.

Once the updated software and version number change is live on WordPress.org, it’s immediately in effect for new installations of that plugin or theme. Anyone downloading and installing a plugin or theme from that directory will now be using the latest code made available by the author.

But what about existing sites that already have that theme or plugin installed? How do they learn about the new changes and new version?

How WordPress Sites Discover Updates

You might think it happens through a “push notification” sent to your site from WordPress.org. But the WordPress.org systems would have to contact thousands or maybe millions of sites to tell them about an update to a single plugin. That’s just not practical.

Continue reading How long does it take between when a plugin update is released and when auto-updates install it on your WordPress site?

Cloning a WP Engine site to a local VVV development environment

When doing custom WordPress theme and plugin development for a site that’s already launched, I find it is essential to get my local development environment set up as close as possible to the live environment where the site is hosted. This minimizes headaches and unexpected problems that come when making any updated code live.

Here are the steps I use to clone the database, theme, plugin and media/uploads from a site hosted on WP Engine into a locally-hosted development environment powered by VVV. This method allows me to develop and test new functionality against recent site content before I deploy it to a staging environment for final testing.

The usual warning applies: you should make sure you know what each of these steps is actually doing and adjust them to fit your specific setup. Some of these actions could result in data loss, so please use them at your own risk.

Okay, here we go:

  1. Make sure any custom theme and plugin code you’ll be working with is under source control, probably using these instructions from WP Engine, and up to date locally. Make sure that those directories are not accidentally included in the .gitignore file for your repo. (I handle this by default ignoring everything in, say, the plugins directory with /wp-content/plugins/* and then adding exclusion rules for each directory I’m working on, e.g. !/wp-content/plugins/my-custom-plugin*.)
  2. Initiate a backup checkpoint on the environment you want to clone. If you’re okay using the slightly out of date one that WP Engine automatically makes every morning, that’s fine.
  3. Initiate a download of the backup checkpoint, choosing the “partial” option and checking “Entire database,” “Plugins” and “Themes.” Leave “Media uploads” and “Everything else” unchecked.
  4. Use the WP Engine SSH gateway to rsync the media folder from the WP Engine environment to your local environment. Here’s an example command for a “mystaging” environment…you will want to try your version with the dry run -n flag first to make sure it is going to do what you want!
    rsync -n -rlD -vz --size-only --delete mystaging:sites/mystaging/wp-content/uploads/ /Users/chris/vvv/www/mysite/public_html/wp-content/uploads/
    

    This should delete any local media files not in the WP Engine environment, and copy everything else down that isn’t already there. It could take a while if it’s the first time you’re running it, but subsequent runs should only grab new uploads to the site.

  5. Unzip the downloaded backup checkpoint on your local system.
  6. Move the mysql.sql file into a directory that will be accessible from your VVV Vagrant shell: mv ~/Downloads/wp-content/mysql.sql /Users/chris/vvv/www/mysite/
  7. Update your local plugins directory from the downloaded backup. Again, please use the -n flag with your version of this command first to preview the results:
    rsync -rlD -vz --size-only --delete --exclude=query-monitor ~/Downloads/wp-content/plugins/ /Users/chris/vvv/www/mysite/public_html/wp-content/plugins/
    

    Note that this command also excludes removing the query-monitor plugin, which I have installed locally but not in the WP Engine environment. That way it can stay ready to activate without reinstalling.

  8. Repeat the above step for the themes directory if need be.
  9. Resolve any discrepancies, things git notes as changes that need to be committed, missing files, etc. There shouldn’t be any but it’s worth checking.
  10. SSH into your VVV box: vagrant ssh
  11. Make sure your WP dev site and the WP Engine site are running the same version of WordPress core.
  12. Go to the directory where the site lives, e.g. cd /srv/www/mysite/public_html/.
  13. Clear out the existing database (again, assuming you don’t have any locally staged changed that you would care about): wp db reset.
  14. Import the database from the WP Engine environment: wp db import ../mysql.sql
  15. Update any content or configuration references to the site’s hostname, noting that you may want to run this with the --dry-run flag first: wp search-replace '//mystaging.com' '//mysite.test' --precise --recurse-objects --all-tables --skip-columns=guid.
  16. If you need to put any of your plugins in local development mode, now’s the time, e.g. wp config set JETPACK_DEV_DEBUG true --raw. And, if you  were using a local development plugin like Query Monitor, reactivate it: wp plugin install query-monitor --activate

That’s it! In only 16 steps you should have a fully up to date copy of your site’s data available for local development and testing. Obviously much of the above could be scripted (with appropriate safeguards) to save time and reduce human error in the process.

I hope you find this helpful. If you have improvements to suggest or your own fun methods of cloning environments, please share in the comments.

Running WordPress cron on a multisite instance

For a long time I used the WP Cron Control plugin and an associated cron job to make sure that scheduled actions on my WordPress multisite instance were executed properly. (You should never rely on event execution that is triggered by visits to your website, the WordPress default, IMHO.) But after the upgrade to WordPress 5.4 I noticed that some of my scheduled events in WordPress were not firing on time, sometimes delayed by 10-20 minutes. I did some troubleshooting and got as far as suspecting a weird interaction between that plugin and WordPress 5.4, but never got to the bottom of it.

When I reluctantly went in search of a new solution, I decided to try using WP CLI cron commands, executed via my server’s own cron service. Ryan Hellyer provided most of what I needed in this helpful post, and I extended it a bit for my own purposes.

Here’s the resulting script that I use:

#!/bin/bash

# This script runs all due events on every WordPress site in a Multisite install, using WP CLI.
# To be run by the "www-data" user every minute or so.
#
# Thanks https://geek.hellyer.kiwi/2017/01/29/using-wp-cli-run-cron-jobs-multisite-networks/

PATH_TO_WORDPRESS="/path/to/wordpress"
DEBUG=false
DEBUG_LOG=/var/log/wp-cron

if [ "$DEBUG" = true ]; then
        echo $(date -u) "Running WP Cron for all sites." >> $DEBUG_LOG
fi

for URL in = $(wp site list --field=url --path="/path/to/wordpress" --deleted=0 --archived=0)
do
        if [[ $URL == "http"* ]]; then
                if [ "$DEBUG" = true ]; then
                        echo $(date -u) "Running WP Cron for $URL:" >> $DEBUG_LOG
                        wp cron event run --due-now --url="$URL" --path="$PATH_TO_WORDPRESS" >> $DEBUG_LOG
                else
                        wp cron event run --quiet --due-now --url="$URL" --path="$PATH_TO_WORDPRESS"
                fi
        fi
done

Then, in my system crontab:

# Run WordPress Cron For All Sites
*/2 * * * * www-data /bin/bash /path/to/bin/run-wp-cli-cron-for-sites.bash

Yes, I run cron every 2 minutes; there are some sites I operate that require very precise execution times in order to be useful. One implication is that this solution does not scale up very well; if the total execution time of all cron jobs across all sites exceeds 2 minutes, I could quickly run into situations where duplicate jobs are running trying to do the same thing, and that could be bad for performance or worse.