I Open Sourced My Twenty Sixteen Child Theme

Today, I open sourced my Twenty Sixteen child theme. It includes an empty resume page template and a clean travel shortcode that uses Google Maps. You’ll need to check out the source code for usage, but it’s very simple 🙂

This child theme is meant to be only very small improvements to the WordPress Twenty Sixteen default theme, but they were improvements that I felt were very valuable.

View the code: https://github.com/rcoll/twentysixteen-rcollier

Pull requests are welcome.

Truncate a WordPress Database To X-Number of Posts

I regularly work with huge MySQL database files (10G-100G) so making copies and importing them locally for use in VVV is extremely cumbersome (at best). Here are some MySQL statements that allow you to truncate all but the most recent 1000 posts in your WordPress database. These are useful because they allow you to truncate the database to a manageable size before copying to your local environment or development server. Unless you have something unusual, these statements should retain all post-to-postmeta associations, comment-to-post associations, and term-to-post associations.

WARNING: DO NOT run these on your production database. You must make a copy of the whole database first and then run these statements on the copy. You have been warned!

Delete all but the most recent 1000 posts:

DELETE FROM wp_posts WHERE ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts WHERE post_type="post" AND post_status="publish" ORDER BY ID DESC LIMIT 1000 ) foo );

Delete all post meta that is not associated with the remaining posts:

DELETE FROM wp_postmeta WHERE post_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );

Delete all comments that are not associated with a remaining post:

DELETE FROM wp_comments WHERE comment_post_ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );

Delete all comment meta that is not associated with a remaining post:

DELETE FROM wp_commentmeta WHERE comment_ID NOT IN ( SELECT comment_ID FROM ( SELECT comment_ID FROM wp_comments ) foo );

Delete term relationships that don’t associate to a remaining post:

DELETE FROM wp_term_relationships WHERE object_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );

Delete terms that are no longer related to an existing post:

DELETE FROM wp_terms WHERE term_id NOT IN ( SELECT term_taxonomy_id FROM ( SELECT term_taxonomy_id FROM wp_term_relationships ) foo );

As an added bonus, delete transients from the options table:

DELETE FROM wp_options WHERE option_name like '%_transient_%';

And here’s the whole thing in one shot – script it in if you wish:

DELETE FROM wp_posts WHERE ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts WHERE post_type="post" AND post_status="publish" ORDER BY ID DESC LIMIT 1000 ) foo );
DELETE FROM wp_postmeta WHERE post_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );
DELETE FROM wp_comments WHERE comment_post_ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );
DELETE FROM wp_commentmeta WHERE comment_ID NOT IN ( SELECT comment_ID FROM ( SELECT comment_ID FROM wp_comments ) foo );
DELETE FROM wp_term_relationships WHERE object_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );
DELETE FROM wp_terms WHERE term_id NOT IN ( SELECT term_taxonomy_id FROM ( SELECT term_taxonomy_id FROM wp_term_relationships ) foo );
DELETE FROM wp_options WHERE option_name like '%_transient_%';

Automatic Featured Images for YouTube Posts

A few people have asked how I get the WordPress featured image for my YouTube posts. It’s actually quite simple – YouTube provides a thumbnail for each video you publish (in the form of http://img.youtube.com/vi/{video_id}/0.jpg) that is free for the taking.

Here is a little script that detects a YouTube embed and automatically downloads, imports, and assigns your YouTube thumbnail as the posts’ featured image. Remove lines 3 and 4 if you’re not using post formats.

function rdc_youtube_video_post( $post_id ) {
// No need to continue if it's not a video post
if ( 'video' != get_post_format( $post_id ) )
return false;

// No need to continue if we already have a post thumbnail
if ( has_post_thumbnail( $post_id ) )
return false;

// Get the post content
$the_post = get_post( $post_id );
$the_content = $the_post->post_content;

// No need to continue if we haven't inserted a youtube embed
if ( false === strpos( $the_content, 'www.youtube.com/embed/' ) )
return false;

// Extract the youtube ID
$matches = array();
preg_match_all( '/www.youtube.com/embed/(.*?)(?|")/i', $the_content, $matches );
$yt_id = $matches[1][0];

// Bail if we don't have a valid Youtube ID
if ( ! $yt_id || empty( $yt_id ) )
return false;

// Get the URL of the YouTube thumbnail
$yt_image_url = 'http://img.youtube.com/vi/' . $yt_id . '/0.jpg';

// Set up for importing and grab the remote image
$upload_dir = wp_upload_dir();
$image_contents = file_get_contents( $yt_image_url );
$filename = sanitize_file_name( basename( $yt_id . '.jpg' ) );

// Create the upload directory if we must
if ( wp_mkdir_p( $upload_dir['path'] ) )
$file = trailingslashit( $upload_dir['path'] ) . $filename;
else
$file = trailingslashit( $upload_dir['basedir'] ) . $filename;

// Store the image file on the server
file_put_contents( $file, $image_contents );

// Check the mime type of the file
$mimetype = wp_check_filetype( $filename, null );

// Consolidate some details about the attachment
$attachment = array(
'post_mime_type' => $mimetype['type'],
'post_title' => $filename,
'post_content' => '',
'post_status' => 'inherit',
);

// Insert the attachment into WordPress
$attach_id = wp_insert_attachment( $attachment, $file, $post_id );

// We need some additional functions
require_once( ABSPATH . 'wp-admin/includes/image.php' );

// Generate and save the attachment metadata
$attach_data = wp_generate_attachment_metadata( $attach_id, $file );
wp_update_attachment_metadata( $attach_id, $attach_data );

// Finally, set the post thumbnail to use our youtube image
set_post_thumbnail( $post_id, $attach_id );
}
add_action( 'save_post', 'rdc_youtube_video_post' );

Add a pageview column with Jetpack data source

I decided to get with the times and give Jetpack a try. If you’re unfamiliar with Jetpack, it’s essentially a WordPress plugin which ties your self-hosted WordPress blog to WordPress.com’s servers and allows nifty features like an image CDN, Gravatar hovercards, centralized comments, and a multitude of other features. Personally, I love the WordPress.com stats the most.

I missed my views column on my posts page in admin though (which I used to source from my Better Postviews plugin). So I wrote up this little function to get the data from Jetpack servers and happily place it where it belongs. Here is the code to do so:

<?php // Add a Views columns to insert jetpack postviews data into function rdc_add_views_column( $cols ) { $cols['pageviews'] = 'Views'; return $cols; } add_filter( 'manage_edit-post_columns', 'rdc_add_views_column' ); // Grab and display the postviews data from Jetpack for each post function rdc_add_views_col_data( $colname ) { global $post; // Make sure we're inserting into the correct column if ( 'pageviews' !== $colname ) return false; // Make sure jetpack and stats are available if ( ! ( class_exists( 'Jetpack' ) &amp;&amp; Jetpack::is_module_active( 'stats' ) ) ) { echo 'Error'; return false; } // Make sure stats_get_csv is available if ( ! function_exists( 'stats_get_csv' ) ) { echo 'Error'; return false; } // Try to get view count from post meta "cache" $view_count = get_post_meta( $post->ID, '_jetpack_post_views_count', true );
	$view_count_created = absint( get_post_meta( $post->ID, '_jetpack_post_views_count_created', true ) );

	// No "cache" value, hit the API for the value
	if ( ! $view_count || time() > $view_count_created + 3600 ) {
		// Get the post data from Jetpack
		$postviews = stats_get_csv( 'postviews', "post_id={$post->ID}" );

		// We have a problem if there was no data returned
		if ( ! $postviews ) {
			echo 'Error';
			return false;
		}

		// Get view count from returned results
		$view_count = absint( $postviews[0]['views'] );

		// Store the value and time as post meta
		update_post_meta( $post->ID, '_jetpack_post_views_count', absint( $view_count ) );
		update_post_meta( $post->ID, '_jetpack_post_views_count_created', absint( time() ) );
	}

	// Print Jetpack post views
	echo '<strong>' . number_format( absint( $view_count ) ) . '</strong>';
}
add_action( 'manage_posts_custom_column', 'rdc_add_views_col_data' );

Drop that code into your functions.php or another theme file. Also, if you’re REALLY scaling up, you’re probably using something like Varnish cache and the Better Postviews Plugin may not register an accurate view count. If that’s the case, Jetpack may be for you since WordPress.com stats are javascript based.

There are a ton of other metrics which you can pull from Jetpack also – check out George Stephanis’ slides from WordCamp Boston 2013 for some more info.

Enjoy!

Graphite Graphs for WordPress

It’s no secret that I love Graphite. My co-workers think it’s a bit ridiculous how much data I feed into our graphite server. I may be a bit excessive, but I have a monitor in my office which allows me to see at a glance every little detail about our network in real time – server health, pageviews, Google Pagespeed Insight scores, etc. I can’t even describe how wonderful a tool it is.

I wanted an easy way to display graphite graphs on the WordPress dashboard. So I rolled out a plugin over the weekend. I give you WordPress Graphite Graphs – I admit I [somewhat] got the idea from WPVIP’s “page generation time” graph in the VIP dashboard, but I’m sure they won’t mind. This plugin allows me (or you) to rapidly deploy monitoring graphs across many different sites without a lot of hassle and NO custom code for each site. Just plug in the URL of your graphite server, the metrics you want to display, and viola – Graphy delight! While I was at it, I went ahead and added support for graphs on the front-end in the sidebar that can be hidden from non-users if needed. Why not, right?

Shoot me a tweet if you’re interested in additional features or get in on the action and fork it on github.

A little PHP script to watch for 404 errors

This is a quick script which reads a standard RSS feed and then grabs the response header for each feed item and makes a log file of 404 errors. Useful in debugging sticky situations.

<?php 

$link_queue = array(); 
$exception_queue = array(); 

$timestamp = date( 'Y-m-d H:i:s' ); 
$rss = simplexml_load_file( $argv[1] ); 

foreach ( $rss-&gt;channel-&gt;item as $item )
  array_push( $link_queue, $item-&gt;link );

foreach ( $link_queue as $url ) {
  $handle = curl_init( $url );
  curl_setopt( $handle, CURLOPT_RETURNTRANSFER, TRUE );
  $response = curl_exec( $handle );
  $response_code = curl_getinfo( $handle, CURLINFO_HTTP_CODE );

  if ( $response_code !== 200 )
    array_push( $exception_queue, &quot;{$url} returned status code {$response_code}&quot; );

  curl_close( $handle );
}

if ( count( $exception_queue ) ) {
  foreach ( $exception_queue as $e )
    echo $timestamp . ' &gt; ' . $e . &quot;n&quot;;
} else {
  echo $timestamp . &quot; &gt; All URLs returned status code 200n&quot;;
}

die();

// omit

Throw it in cron like this:
*/30 * * * * php /path/to/script.php http://somesite.com/feed/ >> /path/to/log 2>&1

Or run it on the command line like this:
php /path/to/the/script.php http://somesite.com/feed/

Assign Random Featured Images To All Posts

Could be used for setting up dummy content for a staging environment, or something similar. Be my guest and let me know how you decide to use it in the comments! Also, check my github for this function built into a WP-CLI command.

	$posts = new WP_Query( array( 
		'posts_per_page' => -1, 
		'post_type' => 'post', 
	));
	
	$post_ids = array();
	
	while ( $posts->have_posts() ) {
		$posts->the_post();
		array_push( $post_ids, get_the_ID() );		
	}
	
	wp_reset_query();
	
	$attachments = new WP_Query( array( 
		'posts_per_page' => -1, 
		'post_type' => 'attachment', 
		'post_status' => 'inherit', 
	));
	
	$attachment_ids = array();
	
	while ( $attachments->have_posts() ) {
		$attachments->the_post();
		array_push( $attachment_ids, get_the_ID() );
	}
	
	wp_reset_query();
	
	foreach ( $post_ids as $post_id ) {
		$attachment_id = rand( 0, count( $attachment_ids ) - 1 );
		
		echo 'post:' . $post_id . ' attachment:' . $attachment_ids[$attachment_id];
		
		if ( update_post_meta( $post_id, '_thumbnail_id', $attachment_ids[$attachment_id] ) )
			echo ' SUCCESS';
		else
			echo ' FAILED';
	}
	
	die( 'script complete' );

WordPress Trackback Maintenance

Sometimes you just need to get rid of all that trackback data from your WordPress database. Here’s how to do it manually from your MySQL command line:

DELETE FROM wp_comments WHERE comment_type='trackback';
DELETE FROM wp_comments WHERE comment_type='pingback';

Great, but now your comments counts are all out of whack, since that integer is stored in the wp_posts table. Here’s how to check that:

SELECT wpp.id, wpp.post_title, wpp.comment_count, wpc.cnt
FROM wp_posts wpp
LEFT JOIN (
	SELECT comment_post_id AS c_post_id, count(*) AS cnt FROM wp_comments
	WHERE comment_approved = 1 GROUP BY comment_post_id) wpc
	ON wpp.id=wpc.c_post_id
	WHERE wpp.post_type IN ('post', 'page')
	AND (wpp.comment_count!=wpc.cnt OR (wpp.comment_count != 0 AND wpc.cnt IS NULL)
);

And here’s how to fix it (make a backup of your database first, just in case):

UPDATE wp_posts wpp
LEFT JOIN (
	SELECT comment_post_id AS c_post_id, count(*) AS cnt FROM wp_comments
	WHERE comment_approved = 1 GROUP BY comment_post_id) wpc
	ON wpp.id=wpc.c_post_id
	SET wpp.comment_count=wpc.cnt
	WHERE wpp.post_type IN ('post', 'page')
	AND (wpp.comment_count!=wpc.cnt OR (wpp.comment_count != 0 AND wpc.cnt IS NULL)
);

Cheers!

WordPress search__only_ Variables

Here’s another object-oriented mu-plugin that you can expand upon if needed. This adds the option to perform a search with “&search__only_titles” or “&search__only_content” in the url to search only titles or content of posts.

<?php

class rdc_search_object {
	
	function __construct() {
		add_filter( 'posts_search', 'filter__posts_search' );
	}
	
	function filter__posts_search( $query ) {
		$query_parts = explode( ' OR ', $query );
		
		if ( isset( $_GET['search__only_titles'] ) )
			return $query_parts[0] . '))';
		
		if ( isset( $_GET['search__only_content'] ) )
			return ' AND ((' . $query_parts[1];
		
		return $query;
	}
	
}

$rdc_search_object = new rdc_search_object();

// omit

Go ahead and use this juicy tidbit and let me know in the comments what creative use you found for it.

Never-Ending Pagination in WordPress

Take your site’s slideshows to the next level with the following:

// Slightly different way of getting a previous post than get_adjacent_post(), this 
// function will grab a post in a SINGLE specific category. We'll go ahead and 
// return it as a permalink since that's ultimately what we want anyways. 
function rdc_get_prev_url_in_category( $in_category ) {
	global $post, $wpdb;
	
	// Convert category to id if it's a slug
	if ( ! is_numeric( $in_category ) )
		$in_category = get_category_by_slug( $in_category )->term_id;

	// This query will grab the preceding post id in $in_category
	$query = $wpdb->prepare( "
		SELECT p.ID FROM $wpdb->posts AS p 
		INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id 
		INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 
		AND tt.taxonomy = 'category' 
		AND tt.term_id IN (%d) 
		WHERE p.ID < %d 
		AND p.post_type = 'post' 
		AND p.post_status = 'publish' 
		ORDER BY p.ID DESC LIMIT 1 
	", $in_category, $post->ID );
	
	// Formulate cache key and see if it exists
	$query_key = 'rdc_previous_post_' . md5( $query );
	$result = wp_cache_get( $query_key );

	// Cache key exists so lets use it
	if ( false !== $result ) {
		if ( $result )
			$prev_post = get_post( $result );
		return get_permalink( $prev_post->ID );
	}
	
	// Cache key didn't exist, lets run a new query
	$result = $wpdb->get_var( $query );

	// Query failed for some reason, probably this is
	// the first post in the category. Go home instead.
	if ( null === $result )
		return get_bloginfo( 'home' );
	
	// Save query result for use later
	wp_cache_set( $query_key, $result );
	
	// Return the permalink of the resulting post id
	if ( $result ) {
		$prev_post = get_post( $result );
		return get_permalink( $prev_post->ID );
	}
	
	// Something crazy happened to get here, but you 
	// never know ...
	return get_bloginfo( 'home' );	
}

The next part actually filters the wp_link_pages arguments and decides where to put a next button where one didn’t previously exist. I also threw in an additional previous button, which uses some javascript to take the user back to the previous page. There’s probably some creative logic you can write to do it without javascript, but that’s probably unnecessary in today’s world.

function rdc_filter_wp_link_pages_args( $r ) {
	global $page, $numpages;
	
	// These links should match the design of your existing links
	// and you should change 12345 to your slideshow category
			
	// If last page of a slideshow, show a "next" button where there wouldn't normally be one
	if ( $r['next_or_number'] == 'next' &amp;&amp; $page == $numpages &amp;&amp; '' == $r['previouspagelink'] ) {
		$repl_next_link = '<a href="' . rdc_get_prev_url_in_category( 12345 ) . '">NEXT &amp;raquo;</a>';
		echo $r['before'] . $repl_next_link . $r['after'];
	}
		
	// If first page of a slideshow, showing "back" button where there wouldn't normally be one 
	if ( $r['next_or_number'] == 'next' &amp;&amp; $page == 1 &amp;&amp; '' == $r['nextpagelink'] ) {
		$repl_prev_link = '<a onclick="window.history.back();">&amp;laquo; PREVIOUS</a>';
		echo $r['before'] . $repl_prev_link . $r['after'];
	}
		
	return $r;
}
add_filter( 'wp_link_pages_args', 'rdc_filter_wp_link_pages_args' );