WordPress Q&A

Welcome to the HitchHacker WordPress Q&A

We love to help you. As we’re all passionate WordPress users we are happy to help you get your open WordPress related questions answered and problems solved.

Fill out the form below and we will publish it after a quick review or get back to you.

Speed up WordPress when you have a lot of terms

The autosuggest procedure can slow down your WordPress wp-admin interface quite a lot when you have a huge amount of terms.

Sometimes it’s useful to just disable this feature to make wp-admin more responsive. You can do this by adding
the following to your functions.php file

add_action( 'admin_init', 'deregister_autosuggest' );
function deregister_autosuggest() {
	if ( is_admin() ) {
		wp_deregister_script( 'suggest' );
	}
}

WordPress Importer with remote lookup for assets.

Due to the lack of remote connectivity I did a small patch to the WordPress importer (v 0.61) that allows us to place the files locally and bypass the remote fetch the importer would usually attempt.

As I ran into this problem very frequently I think that might be useful for others as well.

In the plugins/wordpress-importer/wordpress-importer.php replace

$headers = wp_get_http( $url, $upload['file'] );

with

// fetch the remote url and write it to the placeholder
// Sometimes you already have all the files in place, so instead
// of downloading them again better see if you can reuse them
// put
//      define( 'WP_IMPORTER_CHECK_LOCAL_FILES', true );
// in you wp-config.php to do so.
$existing_upload = wp_upload_dir( $post['upload_date'] );
$existing_upload['file'] = $existing_upload['path'] . "/$file_name";
$existing_upload['url'] = $existing_upload['url'] . "/$file_name";
if ( defined( 'WP_IMPORTER_CHECK_LOCAL_FILES' ) && true === WP_IMPORTER_CHECK_LOCAL_FILES && file_exists( $existing_upload['file'] ) && filesize( $existing_upload['file'] ) > 0 ) {
    $headers = array(
        'response' => 200,
        'content-length' => filesize( $existing_upload['file'] ),
        'x-final-location' => $url
        );
    $upload = $existing_upload;
} else {
    $headers = wp_get_http( $url, $upload['file'] );
}

Then you can add define( 'WP_IMPORTER_CHECK_LOCAL_FILES', true ); in your config.php or functions.php file and the WordPress importer will try to locate the file in the uploads folder first and use it if it’s not with a zero filesize.

It’s been a while! Sorry for neglecting you!

I know, I know – I’ve been neglecting this site for way to long.

During my last year I did a quick detour into Cloud Computing, Openstack and Python. I learned a lot during this time but also noticed that I like WordPress and PHP development more.

So I’m back and working again full-time with WordPress and am planning to give this site the attention it deserves.

A couple of items are on my TODO list here:

– Build better and more frequently updated documentation

– Write more tips & tricks articles

 

Cache posts in category/tag/taxonomy for better performing content blocks

If you ever needed to grab a bunch of posts from different categories or taxonomies into your theme to fill certain layout blocks with their respective content you’d likely ended up doing a bunch of WP_Query calls with the desired parameters. That’s all nice and works ok, but it causes some additional database queries for the visitors which can be avoided by caching the results for these content blocks in a transient or object cache. Even better, as you know exactly that you need to update the caches only when there are changes to the terms you can even prime the cache objects from wp-admin whenever a term that matches your conditions is assigned to a post.

Here is how it could be done:

class Cached_Category_Posts { 

	private $args = array();
	private $cache_key = array();
	private $default_filter = 'cached_category_posts_defaults';
	private $args_filter = 'cached_category_posts_arguments';
	private $check_taxonomies = array( 'category' );
	
	public function __construct( $args=array() ) {
		$defaults = apply_filters( $this->default_filter, array( 'posts_per_page' => 10, 'orderby' => 'date', 'post_status' => 'publish' ) );
		$this->args = apply_filters( $this->args_filter, wp_parse_args( $args, $defaults ) );

		if ( !isset( $this->args['cat'] ) || empty( $this->args['cat'] ) )
			return new WP_Error( 'cached_category_posts_error', __( 'cat argument cannot be empty' ) );

		// make sure $this->args['cat'] is an integer
		$this->args['cat'] = (int) $this->args['cat'];

		// build a cache key based on the arguments
		$this->cache_key = md5( serialize( $this->args ) );

		// we know we only need to update the cache when terms change, so lets monitor term changes
		add_action( 'set_object_terms', array( &$this, 'maybe_refresh_cache' ), 9999, 6 );
	}

	/** 
	 * Cause a cache update whenever a term changes. Make sure to do this only when also other argument parameters
	 * fit the post data to avoid cache refreshs every time a draft is saved
	 */
	public function maybe_refresh_cache( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) {
		if ( !in_array( $taxonomy, $this->check_taxonomies ) )
			return;

		if ( !isset( $this->args['cat'] ) || empty( $this->args['cat'] ) )
			return;

		$post = get_post( $object_id );
		if ( is_wp_error( $post ) )
			return;

		// compare additional values in the post with the arguments set. eg post_status = publish
		foreach( array_keys( (array) $post ) as $key ) {
			if ( isset( $this->args[$key] ) && $post->{$key} <> $this->args[$key] ) {
				return;
			}
		}

		// grab an array of all terms included in this post and receive their term_ids
		$all_terms = array_unique( array_merge( $tt_ids, $old_tt_ids ) );
		$all_terms = array_map( array( &$this, 'get_term_id_from_tt_id' ), $all_terms );
		
		// if none of the set categories is in our arguments just skip it
		if ( !in_array( $this->args['cat'], $all_terms ) )
			return;
		
		// otherwise we need to refresh the cache
		$this->update();
	}

	/**
	 * Receive the term_id from a term_taxonomy_id
	 */
	private function get_term_id_from_tt_id( $tt_id ) {
		global $wpdb;
		return $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $tt_id ) );
	}

	/**
	 * Refresh the caches by calling get() with force_refresh = true
	 */
	public function update() {
		return $this->get( $force_refresh = true );
	}

	/**
	 * Run a WP_Query call with a check against a cached result first.
	 */
	public function get( $force_refresh = false ) {
		if ( !isset( $this->args['cat'] ) || empty( $this->args['cat'] ) )
			return new WP_Error( 'cached_category_posts_error', __( 'cat argument cannot be empty' ) );;
		
		$result = get_transient( $this->cache_key );
		if ( ! $result || true === $force_refresh ) {
			$result = new WP_Query( $this->args );
			set_transient( $this->cache_key, $result );
		}
		return $result;
	}

}

In your themes’ functions.php you would then initialize the class and the different cached query objects like this:

// initialize all the category caches we need.
add_action( 'init', 'my_setup_category_caches' );
function my_setup_category_caches() {
	global $my_cat_stories;
	$my_cat_stories['home-top-story'] = new Cached_Category_Posts( $args = array( 'cat' => 1, 'posts_per_page' => 1 ) );
	// possible usage in theme template $topstories = my_get_category_posts( 'home-top-story-2' );
}

// get category posts by their category identifier
function my_get_category_posts( $id ) {
	global $my_cat_stories;
	if ( isset( $my_cat_stories[$id] ) )
		return $my_cat_stories[$id]->get();
	else
		return false;
}

Although this should work nice you might want to keep in mind that this would store the whole WP_Query object in the transient. It makes most sense to utilize this only for small content blocks and not for some loops with a high amount of posts.

Lost in time(zones)? http://localtime.at can help

Working for a distributed company like Automattic is great, but with clients and co-workers spread across the globe I tend to get lost in timezone conversions. Sure there are some great tools like http://timeanddate.com and http://www.worldtimebuddy.com, but I needed something simple so this night I created http://localtime.at/, my own little timezone converter which takes a time and date and timezone in the url and lets you convert it to your local time.

It currently supports the following url formats:

It’s not much at the moment, but it does what I need and makes a good addition to Alfred.