Pillage and Plunder: Letting Vimeo Build Your WordPress Posts For You

I recently took on a small site for an amazingly talented motion graphics designer. Mainly because her work is so good that I could put it on a plain HTML page with no CSS and it would still look amazing, but also so I could dip my toes back into frontend development again and attempt to hone my very rusty skills. By “hone my very rusty skills”, I mean “ask my friend who’s a brilliant frontend developer for an update on everything that happened in frontend in the last five years, and watch some tutorials on SASS and Gulp, while shaking my head and moaning about how easy everyone has it these days and thanking the browser gods that IE6 doesn’t need supporting anymore.

The main feature of the site is video of the aforementioned amazingly talented motion graphics designer’s (ATMGD) work. All of her video is on Vimeo already. WordPress automatically embeds Vimeo into posts. The brief also called for a large featured image for each post. WordPress supports featured images. Too easy. On presenting the ATMGD with a list of instructions on adding content that involved uploading and adding a thumbnail and pasting a video URL into the content area, she quietly reminded me of another site I had built her that automatically pulls a thumbnail from the Vimeo site. I quietly reminded myself to stop making life easy for clients as it just makes them lazy before my curiosity (ok, my ego) kicked in and I set to work adding some functions to do just that.

My first step was to add a custom meta box with an input field to capture the Vimeo video URL for a post. We will need to use the URL to fetch images from the Vimeo API, and we can write a simple function to render it out wherever its required.

/**
 * Displays a Vimeo Video or placeholder.
 */
function vimeo_video() {

	$vimeo_url = get_post_meta( get_the_id(), 'vimeo_url', true );

	if ( '' === $vimeo_url ) {
		// return placeholder image;
	}

	echo apply_filters( 'the_content', $vimeo_url );
}

Behind the scenes, we need to leverage the save_post_{$post_type} hook. In my case, I’m using the humble post and not a custom post type, so I’m hooking save_post_post.

/**
 * Save post metadata when a post is saved.
 *
 * @param int  $post_id The post ID.
 * @return void.
 */
function vimeo_meta( $post_id ) {

	// E.g. https://vimeo.com/29440238
	$vimeo_url = filter_input( INPUT_POST, 'vimeo_url', FILTER_SANITIZE_URL );

	// None saved. Exit.
	if ( '' === $vimeo_url ) {
		return;
	}

	// Otherwise, store the Vimeo URL.
	update_post_meta( $post_id, 'vimeo_url', $vimeo_url );

	// Grab the ID.
	$url_bits  = parse_url( $vimeo_url ); // E.g. /29440238.
	$vimeo_id  = substr( $url_bits['path'], 1 ); // E.g. 29440238.

	// Hit the API.
	$vimeo_api = "http://vimeo.com/api/v2/video/{$vimeo_id}.json";
	$response  = wp_remote_get( $vimeo_api );

	if ( is_wp_error( $response ) ) {
		// Perform error handling.
		return;
	}

	// Parse response.
	$body  = wp_remote_retrieve_body( $response );
	$json  = json_decode( $body, true );
	$large = $json[0]['thumbnail_large'];

	// Sideload image into WP.
	$image_id = media_sideload_image( $large, $post_id, $json[0]['title'], 'id' );

	set_post_thumbnail( $post_id, $image_id );
}
add_action( 'save_post_post', 'vimeo_meta' );

Bingo! Hole in one! Knocked it out of the park!

Nope.

This is all well and good in that it achieves the job, but there are several flaws. The first thing that jumps out is that this function will run every single time you save a post. Every. Single. Time. At some point the good people at Vimeo are going to send some large men with sticks round to politely ask you to stop continually hitting their API to pull the large thumbnail. So lets make sure we only hit the API when we’ve actually changed an image. While we’re at it, lets make sure its possible to delete a Vimeo URL if required as well.

/**
 * Save post metadata when a post is saved.
 *
 * @param int  $post_id The post ID.
 * @return void.
 */
function vimeo_meta( $post_id ) {

	$vimeo_url     = filter_input( INPUT_POST, 'vimeo_url', FILTER_SANITIZE_URL );	
	$old_vimeo_url = get_post_meta( $post_id, 'vimeo_url', true );

	// None saved. None added.
	if ( '' === $vimeo_url && '' === $old_vimeo_url ) {
		return;
	}

	// No change.
	if ( $vimeo_url === $old_vimeo_url ) {
		return;
	}

	// Saved URL to be deleted.
	if ( '' === $vimeo_url && '' !== $old_vimeo_url ) {
		delete_vimeo( $post_id );
		return;
	}

	// Otherwise, add or update.
	update_vimeo( $post_id, $vimeo_url );
}
add_action( 'save_post_post', 'vimeo_meta' );

/**
 * Called when removing a Vimeo URL.
 *
 * @param  integer $post_id   Post ID.
 * @return void.
 */
function delete_vimeo( $post_id ) {

	// Delete URL.
	delete_post_meta( $post_id, 'vimeo_url' );
}

/**
 * Called when updating a Vimeo URL.
 * When updating, we fetch thumbnails for the video and store.
 *
 * @param  integer $post_id   Post ID.
 * @param  string  $vimeo_url Vimeo URL.
 * @return void.
 */
function update_vimeo( $post_id, $vimeo_url ) {

	// First store the Vimeo URL.
	update_post_meta( $post_id, 'vimeo_url', $vimeo_url );

	// Grab the ID.
	$url_bits  = parse_url( $vimeo_url ); // E.g. /29440238.
	$vimeo_id  = substr( $url_bits['path'], 1 ); // E.g. 29440238.

	// Hit the API.
	$vimeo_api = "http://vimeo.com/api/v2/video/{$vimeo_id}.json";
	$response  = wp_remote_get( $vimeo_api );

	if ( is_wp_error( $response ) ) {
		// Perform error handling.
		return;
	}

	// Parse response.
	$body  = wp_remote_retrieve_body( $response );
	$json  = json_decode( $body, true );
	$large = $json[0]['thumbnail_large'];

	// Sideload image into WP.
	$image_id = media_sideload_image( $large, $post_id, $json[0]['title'], 'id' );

	set_post_thumbnail( $post_id, $image_id );
}

Now we are only hitting the Vimeo API when updating the Vimeo video URL. This may be enough in most circumstances, but unfortunately, part of the site brief was that we have a full width thumbnail as a “hero image” on every archive page. Any post can potentially be the featured “hero” post. Anyone with my cynical, negative world view can already anticipate the large dump the universe is about to land on my head (ok so its the Vimeo API, not the universe, and its good of Vimeo to even have an API, but let me wallow in self-pity, I’m good at it). Yup, the largest thumbnail isn’t big enough for our needs.

While wallowing in self-pity, my, usually lacking, powers of observation kicked in (see, I can do self-pity anywhere) and I noticed that the thumbnail URLs were identical except for having a suffix of “_100” for small, “_200” for medium and “_640” for large. I pasted the URL minus the suffix into a browser, and lo and behold, my self-pity, and mistrust of the universe was blown out of the water because there in front of my doubting eyes, was a full-size image. All we need to do is build a full-size URL out of one of the thumbnail URLs, and pass that into the media_sideload_image function. I opted for the small thumbnail as I assumed this would be the safest bet to be returned every time (self-pity, a lack of trust in the universe and expecting the worst are all key skills if you want to write good code).

// Parse response.
$body       = wp_remote_retrieve_body( $response );
$json       = json_decode( $body, true );
$small      = $json[0]['thumbnail_small'];
$small_bits = parse_url( $small );
$path       = $small_bits['path'];
$path_bits  = explode( '_', $path );
$fullsize   = $small_bits['scheme'] . '://' . $small_bits['host'] . $path_bits[0] . '.jpg';

// Sideload image into WP.
$image_id = media_sideload_image( $fullsize, $post_id, $json[0]['title'], 'id' );

So now we have functionality that saves a Vimeo URL, hits the Vimeo API, retrieves a full-size thumbnail image and stores it as the featured post image. The client is happy, the world is a better place, and I’ve managed to complete the job in the ridiculously small amount of hours I quoted to do this all in, but wait…

What happens if the ATMGD saves a Vimeo video, the thumbnail is retrieved and stored to the site and assigned. The ATMGD then decides to use a different video. The thumbnail is retrieved and stored to the site and assigned. The ATMGD changes her mind and reverts back to the first Vimeo video. The thumbnail is retrieved and stored to the site and assigned. I have the same image saved to the site twice. The world stops turning. Coders of the universe unite to pour scorn on this clearly inefficient process. The small levels of self-confidence I have left are shattered, and I never write a line of code again. For the rest of my living days people in the street point me out and whisper to each other that I am wasteful and inefficient. Somewhere in the future a T-1000 is being primed to be sent back to the past to end me.

You may laugh, but these are the thoughts that keep me awake at night. I’m also kept awake by the fact I can’t afford a bed because I spend hours more than I quoted on in order to cover for unlikely edge cases, but there’s no turning back now. As the Vimeo video ID is what kicks this whole process off, and the attachment ID is what we need to link to the post, it makes sense that we store the attachment ID to an array keyed with the Vimeo ID. We can store this to the site’s options and do a quick look up whenever a thumbnail is required.

Full code below.

I’m off to recode a form in such a way that it allows for a user called “Aaron Anderson” who has a broken “A” key on his keyboard to still complete it.

2018-03-15: Update – note the wp_verify_nonce on $_POST[‘_inline_edit’] line below. I just added this after cleverly wiping a bunch of Vimeo URLs on the ATMGD’s site while using the quick edit screen. Face meet palm.

/**
 * Save post metadata when a post is saved.
 *
 * @param int  $post_id The post ID.
 * @return void.
 */
function vimeo_meta( $post_id ) {

	if ( isset( $_POST['_inline_edit'] ) && wp_verify_nonce( $_POST['_inline_edit'], 'inlineeditnonce' ) ) {
		return;
	}

	$vimeo_url     = filter_input( INPUT_POST, 'vimeo_url', FILTER_SANITIZE_URL );	
	$old_vimeo_url = get_post_meta( $post_id, 'vimeo_url', true );

	// None saved. None added.
	if ( '' === $vimeo_url && '' === $old_vimeo_url ) {
		return;
	}

	// No change.
	if ( $vimeo_url === $old_vimeo_url ) {
		return;
	}

	// Saved URL to be deleted.
	if ( '' === $vimeo_url && '' !== $old_vimeo_url ) {
		delete_vimeo( $post_id );
		return;
	}

	// Otherwise, add or update.
	update_vimeo( $post_id, $vimeo_url );
}
add_action( 'save_post_post', 'vimeo_meta' );

/**
 * Called when removing a Vimeo URL.
 *
 * @param  integer $post_id   Post ID.
 * @return void.
 */
function delete_vimeo( $post_id ) {

	// Delete URL.
	delete_post_meta( $post_id, 'vimeo_url' );
}

/**
 * Called when updating a Vimeo URL.
 * When updating, we fetch thumbnails for the video and store.
 *
 * @param  integer $post_id   Post ID.
 * @param  string  $vimeo_url Vimeo URL.
 * @return void.
 */
function update_vimeo( $post_id, $vimeo_url ) {

	// First store the Vimeo URL.
	update_post_meta( $post_id, 'vimeo_url', $vimeo_url );

	// Grab the ID.
	$url_bits  = parse_url( $vimeo_url ); // E.g. /29440238.
	$vimeo_id  = substr( $url_bits['path'], 1 ); // E.g. 29440238.

	// Look for an image ID in options.
	$attachment_ids = get_option( 'vimeo-thumbs' );

	// If we have a thumb stored for this ID already, use it.
	if ( false !== $attachment_ids && isset( $attachment_ids[ $vimeo_id ] ) ) {
		set_post_thumbnail( $post_id, $attachment_ids[ $vimeo_id ] );
		return;
	}

	// Otherwise, hit the API.
	$vimeo_api = "http://vimeo.com/api/v2/video/{$vimeo_id}.json";
	$response  = wp_remote_get( $vimeo_api );

	if ( is_wp_error( $response ) ) {
		// Perform error handling.
		return;
	}

	// Parse response.
	$body       = wp_remote_retrieve_body( $response );
	$json       = json_decode( $body, true );
	$small      = $json[0]['thumbnail_small'];
	$small_bits = parse_url( $small );
	$path       = $small_bits['path'];
	$path_bits  = explode( '_', $path );
	$fullsize   = $small_bits['scheme'] . '://' . $small_bits['host'] . $path_bits[0] . '.jpg';

	// Sideload image into WP.
	$image_id = media_sideload_image( $large, $post_id, $json[0]['title'], 'id' );

	if ( is_wp_error( $image_id ) ) {
		// Perform error handling.
		return;
	}

	// Store the attachment ID to options.
	$attachment_ids[ $vimeo_id ] = $image_id;
	$options = update_option( 'vimeo-thumbs', $attachment_ids );

	set_post_thumbnail( $post_id, $image_id );
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.