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.

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.

localtime.at

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.

Integrate Cool Applications with WordPress.com

Reblogged from WordPress.com News:

Click to visit the original post

We are happy to announce our new REST Application Programming Interface (API) that lets developers explore, interact, and create great new content with the vast community of sites on the WordPress.com network and, in the near future, Jetpack-enabled sites.

The API gives developers access to posts and comments, as well as the ability to Follow, Like, or…

Read more… 261 more words

Filter arrays of data by any field in the array/objects

I frequently need to grab a subset of data from an array. I just stumbled over the FilterIterator which provides a nice interface to do just this. Based on this class here is a implementation which lets you perform basic filtering.

Here is an example:

$dummy_array = array(
	array( 'id' => 1, 'name' => 'Jon Doe', 'date' => '2012-01-01' ),
	array( 'id' => 2, 'name' => 'Jack Black', 'date' => '2012-02-01' ),
	array( 'id' => 3, 'name' => 'Jane Doe', 'date' => '2012-02-04' ),
	array( 'id' => 4, 'name' => 'Simon Smith', 'date' => '2012-03-01' ), 
);

echo "\nall Doe\n";
$does = simple_array_filter( $dummy_array, 'name', 'regex', '#doe$#i' );
foreach( $does as $row )
	var_export( $row );

echo "\nall in February\n";
$febs = simple_array_filter( $dummy_array )
	->add_filter( 'date', 'newer_than', '2012-02-01 00:00:00' )
	->add_filter( 'date', 'older_than', '2012-02-29 23:59:59' );
foreach( $febs as $row )
	var_export( $row );

And here is the class that makes it happen.

/**
 * Provide easy filtering iterator for arrays
 * @see: http://php.net/manual/en/class.filteriterator.php
 */
class Simple_Array_Filter extends FilterIterator {
    private $filters = array();
	private $data = array();
	
    public function __construct( $data, $filter_field = NULL, $filter_method = NULL, $filter_value = NULL ) {
		$this->data = $data;
		$iterator = $this->get_iterator();
		parent::__construct( $iterator );

		if ( ! is_null( $filter_field ) && ! is_null( $filter_method ) && ! is_null( $filter_value ) )
			$this->add_filter( $filter_field, $filter_method, $filter_value );
    }

	public function get_iterator() {
		if ( is_object( $this->data ) && method_exists( $this->data, 'getIterator' ) )
			return $this->data->getIterator();
		if ( is_array( $this->data ) ) {
			$object = new ArrayObject( $this->data );
			return $object->getIterator();
		}
		return $this->data;
    }
	
    public function accept() {
        $data = $this->getInnerIterator()->current();
		
		return $this->dispatch_filter( $data );
	}

	public function add_filter( $filter_field, $filter_method, $filter_value ) {
		$this->filters[] = array( 'filter_field' => $filter_field, 'filter_method' => $filter_method, 'filter_value' => $filter_value );
		return $this;
	}
	
	private function dispatch_filter( $data ) {
		$result = true; // by default don't filter
		foreach( $this->filters as $filter ) {
			extract( $filter );
			if ( is_object( $data ) ) {
				if ( isset( $data->{$filter_field} ) ) {
					$cmp_value = $data->{$filter_field};
				}
			} else if ( is_array( $data ) ) {
				if ( isset( $data[$filter_field] ) ) {
					$cmp_value = $data[$filter_field];
				}
			}
			
			if ( empty( $cmp_value ) )
				continue;
			
			if ( is_callable( array( &$this, $filter_method ) ) )
				$result = call_user_func( array( &$this, $filter_method ), $cmp_value, $filter_value );
			else if ( function_exists( $filter_method ) ) {
				$result = call_user_func( $filter_method, $cmp_value, $filter_value );
			}

			if ( false == $result )
				return false;
		}
		return true;
	}

	private function gt( $cmp_value, $filter_value ) {
		if ( is_numeric( $cmp_value ) )
			return (int) $cmp_value > (int) $filter_value;
		else
			return strcmp( $cmp_value, $filter_value ) > 0;
	}
	
	private function lt( $cmp_value, $filter_value ) {
		if ( is_numeric( $cmp_value ) )
			return (int) $cmp_value < (int) $filter_value;
		else
			return strcmp( $cmp_value, $filter_value ) < 0;

	}
	
	private function eq( $cmp_value, $filter_value ) {
		if ( is_numeric( $cmp_value ) )
			return (int) $cmp_value == (int) $filter_value;
		else
			return strcmp( $cmp_value, $filter_value ) == 0;

	}
	
	private function regex( $cmp_value, $filter_value ) {
		return preg_match( $filter_value, $cmp_value );
	}

	private function older_than( $cmp_value, $filter_value ) {
		if ( !is_numeric( $cmp_value ) )
			$cmp_value = strtotime( $cmp_value );
		if ( !is_numeric( $filter_value ) )
			$filter_value = strtotime( $filter_value );
		else
			$filter_value = $filter_value;

		return $cmp_value < $filter_value;
	}

	private function newer_than( $cmp_value, $filter_value ) {
		if ( !is_numeric( $cmp_value ) )
			$cmp_value = strtotime( $cmp_value );
		if ( !is_numeric( $filter_value ) )
			$filter_value = strtotime( $filter_value );
		else
			$filter_value = $filter_value;

		return $cmp_value > $filter_value;

	}
}
if ( !function_exists( 'simple_array_filter' ) ) {
	function simple_array_filter( $data, $filter_field = NULL, $filter_method = NULL, $filter_value = NULL ) {
		$simple_array_filter = new Simple_Array_Filter( $data, $filter_field, $filter_method, $filter_value );
		return $simple_array_filter;
	}
}

Minimalistic progress bar for CLI based PHP scripts

I’m doing some CLI scripting here and there and as I’m still on the way to implement it the right way using a framework like wpcli, wpshell or the like for my scripts I sometimes like to put a little progress bar in my scripts to see how things are going and how much longer the script might run. So this is what I do.

<?php

$time1 = time(); 	// set the start time

dosomethingnasty();
function dosomethingnasty() {
	for( $i=0; $i < 300; $i++ ) {
		progress( $i, 300 );
		sleep( 1 );
	}
}


/**
 * Print simple progress bar.
 * 
 * @access public
 * @param int $processed amount of items processed
 * @param int $max maximum amount of items to process
 * @return void
 */
function progress( $processed, $max ) {
	global $time1;
	$progress = round( $processed / ( $max / 100 ), 2);
	$progress_points = floor($progress/2);
	$time_x=time();
	$timediff = $time_x - $time1;
	$estimation = round( ( ( ( 100 / $progress *  $timediff ) - $timediff ) / 60 ), 2 );
	echo str_pad( str_repeat( "#", $progress_points ), 52, " ", STR_PAD_RIGHT) . sprintf( "%.2f", $progress ) . str_pad( "% ( ". sprintf( "%.2f", $estimation ) . " min left )", 27, " ", STR_PAD_RIGHT). "\r" ;
}

Follow

Get every new post delivered to your Inbox.

Join 84 other followers