Update to REST API Post Embeds: category and tag filters work again!

in WordPress

The past few weeks have been interesting. WordPress 4.7 is coming up, and everyone is talking about the WP REST API.

I took that opportunity to look at one of my plugins, REST API Post Embeds, and make sure it was compatible with the upcoming WordPress 4.7. It turns out it wasn’t!

After running a few tests, I realized that my plugin couldn’t filter a list of posts by tag or by category anymore. In fact, most of the other filters were also broken when you were using the WP REST API to pull posts from a site.

I checked the WP REST API GitHub repository and discovered this issue: Remove the filter param and NEVER EVER EVER bring it back. This effectively broke all filtering in my plugin.

The WP REST API used to hide all query parameters under a filter key. In practice, that means that to query for a subset of posts using the WP REST API, you needed to build your URL like so:

wp-json/wp/v2/posts?filter[category_name]=wordpress

While that worked, it wasn’t super friendly. I quickly realized that when building my plugin and comparing WordPress.com REST API queries with WP REST API queries. I consequently implemented a little work-around to add that filter key.

Fast-forward until a few weeks, when the WP REST API decided to remove that filter key. Queries now look like this:

wp-json/wp/v2/posts?categories=11

It’s a lot more readable, isn’t it?

The problem is, unless I’m missing something you can’t query for posts matching a specific category or tag name anymore. You now have to filter by category or tag ID. To get that ID, you need to do an additional REST API call, to the categories or tags endpoint.

Here is how I implemented that call in my plugin:

/**
 * Get a category or a tag ID from the WP REST API.
 *
 * @since 1.4.0
 *
 * @param string $term_name Name of the Category or Tag.
 * @param string $tax_name  'categories' or 'tags'.
 * @param array  $atts      Shortcode attributes.
 *
 * @return string $term_id Category or Tag ID.
 */
public function get_cat_tag_id( $term_name, $tax_name, $atts ) {
	if ( ! $atts || ! $term_name || ! $tax_name ) {
		return;
	}

	/** This filter is documented in rest-api-post-embeds.php */
	$blog_id = apply_filters( 'jeherve_post_embed_blog_id', '' );

	// Overwrite the blog ID if it was defined in the shortcode.
	if ( $atts['url'] ) {
		$blog_id = urlencode( $atts['url'] );
	}

	// If no post ID, let's stop right there.
	if ( ! $blog_id ) {
		return;
	}

	$tax_query_url = sprintf(
		esc_url( '%1$s/wp-json/wp/v2/%2$s?slug=%3$s' ),
		$blog_id,
		$tax_name,
		$term_name
	);

	// Build a hash of the query URL. We'll use it later when building the transient.
	if ( $tax_query_url ) {
		$tax_query_hash = substr( md5( $tax_query_url ), 0, 21 );
	} else {
		return;
	}

	// Look for data in our transient.
	$cached_tax = get_transient( 'jeherve_post_embed_term_' . $term_name . '_' . $tax_query_hash );
	if ( false === $cached_tax ) {
		$term_response = wp_remote_retrieve_body(
			wp_remote_get( esc_url_raw( $tax_query_url ) )
		);

		/**
		 * Filter the amount of time each Term info is cached.
		 *
		 * @since 1.4.0
		 *
		 * @param string $term_caching Amount of time each term is cached. Default is a day.
		 */
		$term_caching = apply_filters( 'jeherve_post_embed_term_cache', 1 * DAY_IN_SECONDS );

		set_transient( 'jeherve_post_embed_term_' . $term_name . '_' . $tax_query_hash, $term_response, $term_caching );
	} else {
		$term_response = $cached_tax;
	}

	if ( ! is_wp_error( $term_response )  ) {
		$term_info = json_decode( $term_response, true );
		$term_id = $term_info[0]['id'];
	} else {
		return;
	}

	return $term_id;
}

As you can see, I cache the results of those calls for a day. I added a filter so folks can change that default caching period, but I think a day is good enough; you don’t make changes to your categories or tag info that often after all.

If you use my plugin on one of your sites, you can update right now, and things should start working again!