Disable post revisions for all post types

WordPress has a handy post revision feature which saves earlier states of a post as you make edits. Not everyone wants or needs this feature, however, and it’s handy to be able to turn it off. Plugins exist for this, but I prefer code solutions that I can put in my themes or site-specific plugins. Here’s a simple method to disable post revisions without installing a plugin:

 * Disable revisions for all post types.
function my_disable_post_revisions() {
	foreach ( get_post_types() as $post_type ) {
		remove_post_type_support( $post_type, 'revisions' );
add_action( 'init', 'my_disable_post_revisions', 999 );

If you’d like to disable post revisions for only specific set of built-in or custom post types instead of targeting all types, you can do that with an array:

 * Disable revisions for all post types.
function my_disable_post_revisions() {
	$types = array( 'post', 'my-custom-type' );
	foreach ( $types as $post_type ) {
		remove_post_type_support( $post_type, 'revisions' );
add_action( 'init', 'my_disable_post_revisions', 999 );

The high priority, 999, means it’s almost certain to execute after any other code which adds revision support.

The ability to disable revisions is particularly helpful when moving a site from another system to WordPress. Frequent post editing and other operations may result in a large number of revisions saved in the database.

If you want to start saving revisions again, simply remove this code from your theme or plugin.

Site search tracking for WordPress with Google Analytics

With just a little setup, Google analytics can track keywords and phrases entered into your WordPress site’s search box. By default, your search results will show up as regular page views which look something like /?s=Search Term&submit=Search. Using GA’s Site Search Tracking feature, however, you can filter these terms into the Behavior > Site Search section of the reporting interface in order to gain deeper insight into what your visitors search for on your site.

Set it up

Screen Shot 2016-09-06 at 5.43.29 PMTo configure site search tracking for a WordPress site:

  1. Log in to Google Analytics and click on Admin.
  2. Select the appropriate Account, Property, and View for your site at the head of their respective columns in the Admin interface.
  3. In the View column, select View Settings.
  4. Enable the Site Search Tracking option with the toggle switch.
  5. Set the Site Search Parameter field to “s” which is the parameter WordPress uses for search terms.
  6. Optionally, check “Strip query parameters out of URL.” This will make all searches show up in analytics with the same URL (“/?s=Search Term&submit=Search” by default,) making it easier to track total search volume in your other reporting views.
  7. Click Save to finish!

Now search data will be parsed out into the Site Search interface for all future traffic.

Advanced use

If you have a custom search form which filters using default or custom taxonomies, you can get further insight into visitors’ search habits on your site using Site Search Categories. In the same interface, enable Site Search Categories and enter a comma separated list of any taxonomy identifiers which may appear in your search queries. These identifiers will be the query_var for the taxonomy. For a custom taxonomy this is likely the taxonomy name, unless you’ve specifically replaced it. For tags and Categories it will be “tag” and “category_name” respectively. Again, you can optionally check “Strip category parameters out of URL” to keep all of your searches tracking as a single URL.

Now that you’re tracking your search terms, check out Google’s advice for interpreting it. To paraphrase my buddy Gahlord, analytics data is only useful if you’re using it make decisions.

Remove the tag cloud from the taxonomy edit screen

Screen Shot 2016-03-25 at 12.24.45 PM

In the WordPress admin taxonomy edit screen, Tags and any hierarchical custom taxonomies include a tag cloud of “Popular Items” in the left column above the Add Term form. I find this feature useless in most cases, particularly on a large site that may have hundreds of terms in a taxonomy.

Strangely, the official way to disable this tag cloud is to set the popular_items label to null when registering your taxonomy. Indeed, this is the only place the label appears to be used at all. If you’ve registered your own taxonomies and you can override that label in your code, go ahead and do that to clear up the problem. If you have taxonomies registered by a plugin or another method that is outside of your direct control, you can remove it by filtering the taxonomy arguments to unset the label. Here’s how:

 * Remove tag cloud from taxonomy edit screen.
function my_remove_popular_term_cloud( $args ) {
	$args['labels']['popular_items'] = null;
	return $args;
add_filter( 'register_taxonomy_args', 'my_remove_popular_term_cloud' );

If you want to target specific taxonomies, you can check which taxonomy you’re working with by passing additional parameters to the filter:

 * Remove tag cloud from taxonomy edit screen.
function my_remove_popular_term_cloud( $args, $taxonomy ) {
	if ( 'post_tag' === $taxonomy ) {
		$args['labels']['popular_items'] = null;
	return $args;
add_filter( 'register_taxonomy_args', 'my_remove_popular_term_cloud', 10, 2 );

Now your taxonomy edit screens will be nice and tidy!

Find the top level parent term

Here’s a quick helper function to find the top level ancestor of a given term. If you like to organize your categories in nested fashion, this function will find the very top level parent no matter how deep down the term your working with is nested.

 * Get the top level parent of a given term.
 * @param WP_Term|int The term who's ancestors we'll be tracing.
 * @param string Name of taxonomy to search. Required only if $term is an ID instead of a WP_Term object.
 * @return WP_Term|bool The top level parent of $term. If $term has no parent, return false.
function get_term_progenitor( $term, $tax = 'category' ) {
	if ( is_int( $term ) ) {
		$term = get_term_by( 'id', $term, $tax );

	if ( 0 == $term->parent || ! $term instanceof WP_Term ) {
		return false;

	while ( $term instanceof WP_Term && 0 != $term->parent ) {
		$term = get_term_by( 'id', $term->parent, $term->taxonomy );
	return $term;

To use it feed in a term object or a term id and taxonomy combo to get the original ancestor of your term. If your term has no parent, it will return false. This is helpful if you need to apply a class to a whole tree of terms, for example. If your

It’s worth noting that this function will call get_term_by() multiple times, and it is known to create slower database queries. If you have nested your terms more than a few levels it may be worth storing the results of this function in a persistent object cache to speed up performance.

Enable the YouTube iframe API for embedded videos

By default, videos embedded in WordPress posts are missing the enablejsapi querystring parameter that allows YouTube’s iframe API to interact with them. Fortunately you can filter the results of embedded media using the oembed_result filter. Here’s an example:

function my_youtube_player_iframe_api( $html ) {
	if ( false !== strpos( $html, 'youtube' ) ) {
		$html = str_replace( '?feature=oembed', '?feature=oembed&enablejsapi=1', $html );
	return $html;
add_filter( 'oembed_result', 'my_youtube_player_iframe_api', 10, 1 );

OEmbed results are cached as post meta, so once you’ve modified the output you’ll have to delete previously-cached URLs before the new filter will take effect for existing posts.

Once you’ve done all of that you can configure and use the iframe API to track interactions, manipulate video controls, and do anything else that the API exposes.

Add async, defer, or other attributes to enqueued WordPress scripts

Sometimes it’s useful to add the async or defer attributes to your script calls in order to prevent that script from blocking the rest of your page from rendering. This is particularly useful with third party scripts which you do not host in the same place as the rest of your site.

If you need to load an enqueued script asynchronously in WordPress, or modify the <script> element in any other way, you can use code like the following:

 * Add async attributes to enqueued scripts where needed.
 * The ability to filter script tags was added in WordPress 4.1 for this purpose.
function my_async_scripts( $tag, $handle, $src ) {
    // the handles of the enqueued scripts we want to async
    $async_scripts = array( 'some-script', 'another-script' );

    if ( in_array( $handle, $async_scripts ) ) {
        return '<script type="text/javascript" src="' . $src . '" async="async"></script>' . "\n";

    return $tag;
add_filter( 'script_loader_tag', 'my_async_scripts', 10, 3 );

The script_loader_tag filter was added in WordPress 4.1 for specifically this purpose. It is run whenever a script tag is generated by WordPress using the wp_enqueue_script() function. In this example we compare the script $handle against a list of known scripts that we want to load asynchronously.

New Plugin: Custom Related Products for WooCommerce

I don’t think I’ve ever built a WooCommerce site where the client didn’t ask: “How do I pick which related products to display.” The answer has always been “You don’t; the system randomly picks products from the same category.” No client has ever been happy with that answer.

Now I’m happy to say I’ll never have to give that answer again, and neither will you! My latest plugin, Custom Related Products for WooCommerce, replaces the default related products functionality in WooCommerce. With the plugin activated, edit any product and click the “Linked Products” tab. In addition to Cross-sells and Upsells, you’ll now have a Related Products box. As long as you’re using a default related products implementation, the plugin will work automatically to show the products you selected in the Related Products list at the bottom of the detail view, no theme updates required.

Get it!

You can download Custom Related Products for WooCommerce from WordPress.org or install it from the Add New Plugins page in your admin starting today. If you have suggestions for changes, you can fork the plugin and submit pull requests with GitHub.


WordPress Plugin: Override Comment Deadline

Today I’m pleased to release a discussion-focused plugin to the directory, called Override Comment Deadline. Now you can have “Automatically Close Comments” enabled in your discussion settings to limit spam activity on old posts while keeping commenting open on specific posts. To use it, make sure you’ve set a deadline on the discussion settings page and then edit any post and check the box to keep comments open indefinitely.

Get it!

You can download Override Comment Deadline from WordPress.org or install it from the Add New Plugins page in your admin starting today. If you have suggestions for changes, you can fork the plugin and submit pull requests with GitHub.

Clean up bloated WordPress comment tables

Spam comments are the worst. Even with Akismet active to prevent them from appearing on your site, they can still cause problems. That’s because the comment still gets stored and Akismet creates comment meta entries every time it does something. Over the course of time those unnecessary database entries can amount to hundreds of megabytes even on a reasonably small site. Multiply that by a couple hundred sites on a multisite network, and it can quickly start costing you real money in terms of hosting and causing serious problems when you back up your database.

Fortunately, MySQL can help you eliminate all this comment meta bloat with a few simple queries:

DELETE FROM wp_comments WHERE comment_approved = "spam";
DELETE FROM wp_commentmeta WHERE meta_key LIKE "akismet_%";

As always, make sure to back up your database before running any destructive queries on it.

Once you’ve deleted all of those Spam comments and Akismet meta entries, make sure to optimize the database tables.

OPTIMIZE TABLE wp_comments;
OPTIMIZE TABLE wp_commentmeta;

If you’re running a multisite network, you’ll have to run the delete queries and the optimizations for each site, or at least each site which is causing problems. To determine which tables are the most bloated, you can run a query like this:

SELECT table_name AS "Tables", 
    round(((data_length + index_length) / 1024 / 1024), 2) "Size in MB" 
    FROM information_schema.TABLES 
    WHERE table_schema = "YOUR_DATABASE_NAME" AND table_name LIKE "%comment%"
    ORDER BY (data_length + index_length) DESC;

This will list all comment and commentmeta tables sorted by size, from largest to smallest. Once you have a list of all tables you want to clean up, you can run the DELETE and OPTIMIZE queries for each table. Or, you could write a script to clear this generally useless data from your database at regular intervals.

Debug WP-Cron with Trigger Scheduled Events

I’ve added a new plugin to the directory called Trigger Scheduled Events, which does pretty much what it says on the tin. With it, you can view a list of all events scheduled by WP-Cron and run any of them instantly instead of waiting until the next time the event is scheduled to be fired.

WP-Cron is useful because it allows you to schedule events to happen later using the wp_schedule_event() function, but during development this can lead to a lot of waiting around to see if your code works. Trigger Scheduled Events gets around this problem by allowing you to run your events on demand.

Get it!

You can download the plugin through the Add New menu from your WordPress admin or you can download it from the plugin directory. If you’d like to make suggestions for improvements, you can do that over at the plugin’s GitHub page.