WordPress + Post Formats

A few months ago, I was tasked with integrating our editors’ blog, 17 Dots, into eMusic proper. Back story: eMusic has completed its transition to WordPress for 100% of our CMS needs. We are supporting all kinds of weird legacy content and admin config screens, and we have started the foray into Multisite. 17 Dots was the 2nd site in our network (yet it has blog_id 3, because maybe I did something weird locally…). When I started working on it, I decided I was going to implement Post Formats into the 17 Dots child theme (its parent theme is called Dark and is the base theme for eMusic).

17 Dots is a good fit for Post Formats because it is 1) a blog and 2) its posts are usually a text post, a piece of media, or text with a featured photo or two. In my mind, the Standard, Image, Audio, Video, and Gallery post formats would be a great way to give archive.php and single.php “theme hints.” Rather than making a bunch of templates, I wanted to keep this theme super simple and use a switch statement to display the small portion of content of a post that was different based on its post format tag.

Additionally, I had to go back through the archives of 17 Dots (basically a history of widgets on the internet) and make sure all of the posts published in 2008 still worked (or if they were broken, fix them). There were 3000-4000 posts, and I’ll give you a spoiler now: I went through all them.

If you don’t know what Post Formats are, here’s the link to them in the Codex.

What Post Formats Are

Post formats are a way of describing your posts. That’s really it. post_format is the taxonomy the post format terms belong to. The terms are pre-baked: Standard, Aside, Link, Gallery, Status, Quote, Image. To enable post formats in your theme, you need to slap the following in functions.php:

// add any of the built-in post formats to the list
add_theme_support( 'post-formats', array( 'gallery', 'image', 'video', 'audio' ) );

So what does this do for you? It adds this in the edit post admin:

Feel the heat.

So now when you save your post, you can start using the following theme functions:

<?php
// conditional
if ( has_post_format( 'audio' ) )

// retrieval
get_post_format();

So that’s what post formats ARE, a way to tell your theme – “hey, I’m different! Do you care?”

What Post Formats Are Not

Post formats are NOT extensible. What do you mean, EVERYTHING in WordPress is extensible? Technically, you could unregister the post format admin widget and roll your own, and make the Post Format taxonomy screen visible, but by default, you can’t roll your own post formats. Here is an example of how NOT extensible the default set of post formats are:

function get_post_format_strings() {
	$strings = array(
		'standard' => _x( 'Standard', 'Post format' ),
		'aside'    => _x( 'Aside',    'Post format' ),
		'chat'     => _x( 'Chat',     'Post format' ),
		'gallery'  => _x( 'Gallery',  'Post format' ),
		'link'     => _x( 'Link',     'Post format' ),
		'image'    => _x( 'Image',    'Post format' ),
		'quote'    => _x( 'Quote',    'Post format' ),
		'status'   => _x( 'Status',   'Post format' ),
		'video'    => _x( 'Video',    'Post format' ),
		'audio'    => _x( 'Audio',    'Post format' ),
	);
	return $strings;
}

Zero filters.

If you talk to WordPress overlords about this, this is not controversial to them. Their thinking is that this makes for maximum compatibility across themes. My opinion is that post formats are such an abstract concept, there is almost no way to insure posts are tagged with the proper format and there is no mechanism for standardizing their display. I’ll explain.

The Basics: How We Use Post Formats on 17 Dots

Everything by default is a “Standard” post. Meaning, it gets no taxonomy relationship, it is left alone. Because I support a Featured Image on all posts, the “Image” post format basically does nothing. Associating it with Image *might* be useful later, but I can’t think immediately think of a reason why.

Here is an “Image” post – no different than a post with featured image:

Audio is for when – you guessed it! – the post is about something you can listen to, and Video is for when you can watch something. (Notice how vague I was about what Audio and Video do – more to come).

Gallery supports flipping through a bunch of images as expected.

As always, we want our theme templates to be lightweight, meaning most of the PHP coding is encapsulated somewhere else. The theme needs to just be like – “right here is where audio goes, or something” – here’s my switch statement:

<?php
$content = trim( get_the_content( 'more »' ) );
if ( is_single() ) {
    ?>
    <time><?php echo mysql2date( 'm.d.y', $post->post_date ); ?></time>
    <?php
    the_post_header();

    the_featured_image( true, $content );

    switch ( get_post_format() ) {
    case 'audio':
        the_featured_audio( $content );
        break;

    case 'gallery':
        the_post_gallery();
        break;

    case 'video':
        the_featured_video( $content );
        break;

    case 'image':
        break;

    case 'standard':
    default:
        if ( has_featured_album() )
            the_featured_album();
        break;
    }

    echo apply_filters( 'the_content', $content );

    wp_link_pages();
} else {

Some things to note:

  • My theme is vague, but it basically says: show the important thing first, then the rest of the content
  • If the post format is nothing or standard, try to grab a Featured Album widget. If post formats were extensible, I could have created a Featured Album post format, but to do that, I have to hack the admin and extend the built-in post formats. Too gross.
  • I am passing the content to some of the functions so they can alter the content by-reference if necessary. I’ll show you each function in a minute.

Why Post Types > Post Formats

The implementation of custom Post Types makes a lot of sense. Post, Page, Attachment, Revision, and Menu Item are all just registered custom post types with the _builtin flag set to true. That means: WordPress is responsible for those, the rest, you are on your own. Post formats aren’t extensible, so the initial Post Formats are all you have. If they would just use a similar registration process (setting the _builtin flag), THOSE formats could be compatible across themes and then: roll your own, you’re on your own.

The Featured Album

Does it kill me to have to check for the featured album on EVERY standard post? No. But sometimes a feature requires pinging an external service and it can be annoying when 75% of your posts will never have that item.

Here’s the code to check for the Featured Album:

/**
 * Widget for Featured Album, used in The Loop
 *
 */
function the_featured_album() {
    $item = has_featured_album();
    if ( !empty( $item ) ) {
        switch_to_blog( 1 );
        $data = 'A' === $item['domain'] ? get_album( $item['work_id'] ) : get_book( $item['work_id'] );
        if ( !empty( $data ) ) {
            $work = 'A' === $item['domain'] && isset( $data['album']  ) ? $data['album'] :
                ( isset( $data['book'] ) ? $data['book'] : '' );
            if ( !empty( $work ) ) {
            ?>
            <div class="featured-item bundle editorial-bundle">
            <?php 'A' === $item['domain'] ? price_bundle( $work ) : book_price_bundle( $work ); ?>
            </div>
            <?php
            }
        }
        restore_current_blog();
    }
}

function has_featured_album() {
    return get_post_meta( get_the_ID(), 'featured_catalog_item', true );
}

Because I have abstracted so much code, you might not know that get_album() and get_book() check Memcached and if not found, hit our catalog service, which then hits our pricing service, etc. I also share this code across themes and sites, so I fire off a switch_to_blog() to always keep it in the proper context when rendering – way down in price_bundle(), home_url() is called and we need it to resolve to the main site, not 17 Dots.

So yeah, I would like this to be its own post format – it is not.

I would also like to be able to differentiate between Featured Album and Featured Audiobook (we sell audiobooks, too):

Media

Since we’ve established that Standard doesn’t really matter, neither does Image, and we can’t roll our own – the 2 types that we need to come up with a strategy for are Audio and Video (let’s assume Gallery is way more straightforward and WordPress-y out of the box).

17 Dots in the past has had all kinds of media content on it: Videos, TV widgets, Audio, YouTube, Kickstarter widgets, Flickr widgets, etc – and remember that I said that going through the content that I had to import was like viewing a history of widgets on the internet.

That’s because the delivery mechanism for this content was never unified. It was copy-and-paste embed or object tags from whatever site contained the content. So, I may have a post that features a video, but the source of that video may be dubious code-wise.

The first step in making sure that content stays put and is supported in our transition is to make sure that our Editors (the few users who can actually post to the blog) have unfiltered HTML access. I know I want to go back and remove any weird JavaScript or embeds, but for now I just want to do no harm, so I add this code to my 17 Dots theme init:

kses_remove_filters();

// Makes sure $wp_roles is initialized
get_role( 'administrator' );

global $wp_roles;
// Dont use get_role() wrapper, it doesn't work as a one off.
// (get_role does not properly return as reference)
$wp_roles->role_objects['administrator']->add_cap( 'unfiltered_html' );
$wp_roles->role_objects['editor']->add_cap( 'unfiltered_html' );

Now that the editors can put whatever they want into the editor, I went back through the archives using SQL and RegEx and figured out which posts contained this rich content. What I found wasn’t startling, 10-20 different ways of embedding YouTube, services that no longer existed, JavaScript that caused half of the document structure to get swallowed by some bad document.write, 4 <object> tags in a post that caused the page to load too slow and the archive pages that contained it to load slower.

Ugh. I could’ve left the legacy content alone and just focused on the future, but I got OCD and decided to fix it.

oEmbed

If you don’t have any clue what oEmbed is: you’re probably not alone, but should find out as soon as possible. Here are some docs.

oEmbed is basically a unified way to send a URL to someone and get back a response that explains how to display that content. Perfect example: YouTube.

Visit this link in your browser: http://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=HA-YJeeJ248&format=json

I ask YouTube: whadup with this URL? YouTube’s all like: here man, use this. If the URL was bogus or the content was no longer supported, I wouldn’t have any HTML to show. As with most things, WordPress has abstracted this discovery for us. Once you turn on embeds, WordPress will automatically scan the_content() on display and make the oEmbed calls for you. If the call returns no HTML for the URL, WP will just print out the url. To turn the feature on:

oEmbed is backwards-compatible and future-proof. Why? If YouTube continues to support oEmbed, you can always ping the service for the most relevant embed code. Remember how I said we had 10 different versions of the YouTube embed/object tags in posts from the past? If I went and replaced the embed code with the one-line URL, I could turn on Embeds and have those URLs automatically parsed. I also wouldn’t need to check each video by hand to make sure it was still watchable. If it’s not, we display the URL / you figure the rest out! Leaving the URL unadultered is way better than loading some deprecated or broken Flash widget.

WordPress natively supports YouTube and Vimeo through oEmbed. MOST of the videos posted on 17 Dots were from one of these 2 sources. However, there were many as well that were 1) rendered using <iframe>, <script>, <object> and 2) the service providing them didn’t have an oEmbed endpoint. So this provides a little bit of challenge: how do I generically take post_content and know whether it’s going to be oEmbed-ed or whether I need to print out the source or something. And do I tag all of these with the Video post format?

oEmbed returning an iframe or object/embed that renders Video or Audio is a gray area as far as post formats go. Why? YouTube is video, certainly, but it is not Video from your Media Library. When you associate a post with the Video format, WordPress, which was super unspecific about how to implement post formats, is going to explicitly try to grab a video you have attached to your post, wrap it up in some ping service enclosure and try to say This Post Contains Video! to the services it is pinging without actually checking that you actually associated a video attachment with that post – and when it tries to do all of those pings, your Apache server will probably start seg-faulting like crazy. CONUNDRUM.

For 17 Dots, and to fix this, I had to do this in my theme:

remove_action( 'publish_post', '_publish_post_hook', 5, 1 );

When I did, I was in business and displaying video for posts:

Ok cool, but the video for this post displays in 2 more places: the Archives and on the right rail of the homepage.

In the Archives:

A video on the homepage:

Notice how the videos resized themselves by location. This was not an accident. One of the reasons is the magic of oEmbed, the other is by filtering an option for media width – let’s revisit our theme function called the_featured_video():

function the_featured_video( &$content ) {
    $url = trim( array_shift( explode( "n", $content ) ) );
    $w = get_option( 'embed_size_w' );
    if ( !is_single() )
        $url = str_replace( '448', $w, $url );

    if ( 0 === strpos( $url, 'http://' ) ) {
        echo apply_filters( 'the_content', $url );
        $content = trim( str_replace( $url, '', $content ) );
    } else if ( preg_match ( '#^<(script|iframe|embed|object)#i', $url ) ) {
        $h = get_option( 'embed_size_h' );
        if ( !empty( $h ) ) {
            if ( $w === $h ) $h = ceil( $w * 0.75 );

            $url = preg_replace(
                array( '#height="[0-9]+?"#i', '#height=[0-9]+?#i' ),
                array( sprintf( 'height="%d"', $h ), sprintf( 'height=%d', $h ) ),
                $url
            );
        }

        echo $url;
        $content = trim( str_replace( $url, '', $content ) );
    }
}

I’ll translate: 448 is the width of our single post content area. If I were to use an object or embed code from another site, I should be a good samaritan and set the width to 448. If not, in single.php, I have added this:

add_filter( 'pre_option_embed_size_w', function () {
    return 448;
} );

For archives, I use the actual default embed_size_w specified in the admin. Weirdly, our archives have bigger videos than out single posts, but whatever – what is this, a deposition?!

The other area of note is the right rail on the home page and site-wide on 17 Dots. Because WordPress sends our embed_size_w to YouTube, et al, when doing oEmbed discovery, YouTube will send us back a video corresponding to those dimensions, if it can.

For the side rail:

function _feature_bundle_media_size_w_filter() {
    return 252;
}

function _feature_bundle_media_size_h_filter() {
    return 252;
}

function _feature_youtube_add_wmode( $data ) {
    if ( strstr( $data, 'www.youtube.com' ) && false === strstr( $data, 'wmode=transparent' ) ) {
        $data = preg_replace( '/src="([^"]+)"/', 'src="$1&wmode=transparent"', $data );
    }
    return $data;
}

function dots_feature_bundle( $feature ) {
    if ( !empty( $feature ) ) {
        add_filter( 'pre_option_embed_size_w', '_feature_bundle_media_size_w_filter' );
        add_filter( 'pre_option_embed_size_h', '_feature_bundle_media_size_h_filter' );
        add_filter( 'oembed_dataparse',        '_feature_youtube_add_wmode' );

        .....

        if ( has_post_format( 'video', $feature ) ) {
            the_featured_video( $feature->post_content );
        }

        .....

        remove_filter( 'pre_option_embed_size_w', '_feature_bundle_media_size_w_filter' );
        remove_filter( 'pre_option_embed_size_h', '_feature_bundle_media_size_h_filter' );
        remove_filter( 'oembed_dataparse',        '_feature_youtube_add_wmode' );
    }
}

So as we can see, the extensibility with theming is limitless, but nothing guarantees that any of the stuff I just did would be supported by another theme.

Audio is *basically* the same as Video:

  • I support oEmbed where appropriate and parse iframe / object / embed / script where necessary
  • I wrote a player that will handle actual Media Library audio attachments
  • If you don’t attach real audio WP will cause massive seg-faults, etc
  • Tag your post with Audio post format anyways

Add Your Own oEmbed Endpoints

If WP doesn’t parse your favorite oEmbed provider URLs, but that service has an oEmbed endpoint, you can add them to WordPress whitelist (I did this with SoundCloud and Kickstarter):

wp_oembed_add_provider( '#http://(www.)?soundcloud.com/.*#i', 'http://soundcloud.com/oembed', true );
wp_oembed_add_provider( '#http://(www.)?kickstarter.com/projects/.*#i', 'http://www.kickstarter.com/services/oembed', true );

Here’s SoundCloud in action:

On our home page, where 17 Dots is located in the right rail as a preview, I need to not show SoundCloud, because I know SoundCloud will return no HTML to be parsed at that size:

if ( has_post_format( 'audio', $feature ) ) {
    $url = trim( array_shift( explode( "n", $feature->post_content ) ) );

    if ( 0 === strpos( $url, 'http://' ) ) {
        $feature->post_content = trim( str_replace( $url, '', $feature->post_content ) );
    }
}

Conclusion

So those are some post formats and how I chose to use them. I can guarantee no 2 implementations will be alike. Compatible? Maybe.

3 thoughts on “WordPress + Post Formats

  1. Great post Scott, thanks.

    Why just don’t register new custom taxonomy for 17 Dots specific post formats?

    • Of course I could, but it isn’t real life or death to have to check post_meta to know if there is a featured item. My main complaints are about the non-extensibility, which is how tags and categories got started before taxonomies. Why not make it extensible from the beginning?

      • I remember there was big conversations about making custom post formats, if I remember correctly main reason for not doing this was standardizing them.

        Not sure if there would be any difference in performance, most of post formats specific functions are just wrappers.

Comments are closed.