WordPress + Regionalization

Regionalized merch, regionalized promos, regionalized blog posts, and the list goes on and on

Regionalized merch, regionalized promos, regionalized blog posts, and the list goes on and on

One of the coolest things about WordPress is that it is built from the ground up with translation tools. Many blogs want to vibe in a language other than English, and many blogs want to get their international game on and present content to people in several languages.

But what if you want to detect a user’s geographic location and display content based on their country code (“US” for USA, “FR” for France, et al) or your own regional site code (“EU” for European Union countries, for example)? This is not built-in but can be built by you if you know what you are doing. Let’s learn!

Btdubs, eMusic makes extensive use of regionalization. We have 4 regional sites: US, UK, EU, CA. We regionalize everything, so almost every database query on our site has to be subject to some filtering. We also have to intersect WP with our regionalized catalog data and in various places link them together magically.

Custom Taxonomy: Region

Creating new taxonomies in WP requires code, but adding terms to that taxonomy can be done by anyone in the admin. Let’s get the code out of the way:

/**
 * Shortcut function for assigning Labels to a custom taxonomy
 *
 * @param string $term
 * @param string $plural If not specified, "s" is added to the end of $term
 * @return array Labels for use by the custom taxonomy
 *
 */
function emusic_tax_inflection( $str = '', $plural = '' ) {
    $p = strlen( $plural ) ? $plural : $str . 's';

    return array(
        'name'              => _x( $p, 'taxonomy general name' ),
        'singular_name'     => _x( $str, 'taxonomy singular name' ),
        'search_items'      => __( 'Search ' . $p ),
        'all_items'         => __( 'All ' . $p ),
        'parent_item'       => __( 'Parent ' . $str ),
        'parent_item_colon' => __( 'Parent ' . $str . ':' ),
        'edit_item'         => __( 'Edit ' . $str ),
        'update_item'       => __( 'Update ' . $str ),
        'add_new_item'      => __( 'Add New ' . $str ),
        'new_item_name'     => __( 'New ' . $str . ' Name' ),
        'menu_name'         => __( $p ),
    );
}

$post_types = get_post_types( array( 'exclude_from_search' => false, '_builtin' => false ) );

$defaults = array(
    'hierarchical'      => true,
    'public'            => true,
    'show_ui'           => true,
    '_builtin'          => true,
    'show_in_nav_menus' => false,
    'query_var'         => true,
    'rewrite'           => false
);

register_taxonomy( 'region', $post_types, wp_parse_args( array(
    'labels' => emusic_tax_inflection( 'Region' )
), $defaults ) );

Because we load a bunch of custom taxonomies, this code helps us stay modular. But yes, this is the amount of code required if you only have one taxonomy!

All you *really* need to know is: we made a hierarchical taxonomy. It’s called region. We are now going to use it EVERYWHERE.

Boom

So now that we have a taxonomy for region, we want to be able to assign region(s) to posts.

Sorting

We also want to view the region in the posts list table. We can add custom columns to the posts table and make the region column sortable. We need some more code for that:

class CustomClassNamedWhatever {

.......

/**
 * Filters for Admin
 *
 */
function admin() {
    add_filter( 'manage_posts_columns', array( $this, 'manage_columns' ) );
    add_action( 'manage_posts_custom_column', array( $this, 'manage_custom_column' ), 10, 2 );
    add_filter( 'posts_clauses', array( $this, 'clauses' ), 10, 2 );

	// this is an internal method that gives me an array of relevant post types
    foreach ( $this->get_post_types( false ) as $t ) {
        add_filter( "manage_edit-{$t}_sortable_columns", array( $this, 'sortables' ) );
    }
}

/**
 * Register sortable columns
 *
 * @param array $columns
 * @return array
 */
function sortables( $columns ) {
    $post_type_obj = get_post_type();

    if ( is_object_in_taxonomy( $post_type_obj, 'region' ) )
	$columns['region'] = 'region';

    return $columns;
}

/**
 * Add custom column headers
 *
 * @param array $defaults
 * @return array
 */
function manage_columns( $columns ) {
    $columns['region'] = __( 'Region' );
    return $columns;
}

/**
 * Output terms for post in tax
 *
 * @param int $id
 * @param string $tax
 */
function _list_terms( $id, $tax ) {
    $terms = wp_get_object_terms( $id, $tax, array( 'fields' => 'names' ) );
    if ( ! empty( $terms ) )
        echo join( ', ', $terms );
}
/**
 * Output custom HTML for custom column row
 *
 * @param string $column
 * @param int $id
 */
function manage_custom_column( $column, $post_id ) {
    if ( 'region' === $column )
	$this->_list_terms( $id, $column );
}

/**
 * Filter SQL with this monstrosity for sorting
 *
 * @global hyperdb $wpdb
 * @param array $clauses
 * @param WP_Query $wp_query
 * @return array
 *
 * TODO: fix this
 */
function clauses( $clauses, $wp_query ) {
    global $wpdb;

    if ( isset( $wp_query->query['orderby'] ) &&
        'region' === $wp_query->query['orderby'] ) {
        $tax = $wp_query->query['orderby'];

        $clauses['join'] .= <<term_relationships} ON {$wpdb->posts}.ID={$wpdb->term_relationships}.object_id
LEFT OUTER JOIN {$wpdb->term_taxonomy} USING (term_taxonomy_id)
LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
SQL;

        $clauses['where'] .= " AND (taxonomy = '{$tax}' OR taxonomy IS NULL)";
        $clauses['groupby'] = "object_id";
        $clauses['orderby']  = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
        $clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
    }

    return $clauses;
}

........

}

After all of that, we can sort our posts by region:

Sorting

If you’re new to WordPress, you may have just barfed. Just know that we added relevant code to make the Region column sortable. Meanwhile, we still haven’t done anything to make our site regionalized. If we let WordPress just do its thing, you would still get content from every region, not to the one you are specifically targeting.

Geolocation

We have very specific values we check to regionalize users. If you’re anonymous, we get an X-Akamai-Edgescape HTTP header that can parsed, and it contains a value for “country_code.” Based on that country code, we can assign you to a region. Anybody with a debug console can probably view this header in their eMusic requests. If you’re logged-in or “cookied” – we pin you to your original country code.

At the end of the day, we want to be able to set some constants:

/**
 * Register default constants
 *
 */
if ( ! defined( 'THE_COUNTRY' ) )
    define( 'THE_COUNTRY', get_country_code() );

if ( ! defined( 'THE_REGION' ) )
    define( 'THE_REGION', get_region() );

if ( ! defined( 'CURRENCY_SYMBOL' ) ) {
    global $currency_symbol_map;
    if ( isset( $currency_symbol_map[THE_REGION] ) ) {
	define( 'CURRENCY_SYMBOL', $currency_symbol_map[THE_REGION] );
    } else {
	define( 'CURRENCY_SYMBOL', $currency_symbol_map['US'] );
    }
}

get_country_code() and get_region() are very specific to eMusic, so I won’t bore you with them, but they both return a 2-character-uppercase value for country or region, “US” and “US” for example. Now, anywhere in our code where we need to refer to region or country, THE_REGION and THE_COUNTRY will do it.

Linking Region to Taxonomy

Ok cool, we have a taxonomy, and we have some vague representation of region / country defined as constants. We still don’t have regionalized content. One way we could link region to taxonomy terms would be to make a map and refer to it when necessary:

$regions_map = array(
    'US' => 3,
    'UK' => 6,
    'CA' => 9,
    'EU' => 11,
);

This method sucks. Why? Are those IDs term_ids or term_taxonomy_ids? What if they change? Also, grabbing a PHP global every time you need to translate region code to term_id is weird. How else can we get the terms belonging to the region taxonomy? Let’s try this database call which is cached in memory after the first time it is retrieved:

get_terms( 'region' );

That’s great, but what do I do with it? Because your Region codes are also the names of your region terms, you could dynamically create your map like this:

$map = array();
foreach ( $terms as $term )
    $map[$term->name] = $term->term_taxonomy_id;

Next question: where do I set this, and how do I account for crazy stuff like switch_to_blog()? Try this:

/**
 * Automatically resets regions_tax_map on switch_to_blog()
 * Because we switch_to_blog() in sunrise.php, this function is
 * called when plugins_loaded to create initial values
 *
 * @param int $blog_id
 *
 */
function set_regions_tax_map( $blog_id = 0 ) {
     if ( ! isset( get_emusic()->regions_tax_maps ) )
	get_emusic()->regions_tax_maps = array();

     if ( ! taxonomy_exists( 'region' ) )
	return;

     if ( empty( $blog_id ) )
	$blog_id = get_current_blog_id();

     if ( isset( get_emusic()->regions_tax_maps[$blog_id] ) ) {
	get_emusic()->regions_tax_map = get_emusic()->regions_tax_maps[$blog_id];
	return;
    }

    $terms = get_terms( 'region' );

    $map = array();
    foreach ( $terms as $term )
	$map[$term->name] = $term->term_taxonomy_id;

    get_emusic()->regions_tax_maps[$blog_id] = $map;
    get_emusic()->regions_tax_map = get_emusic()->regions_tax_maps[$blog_id];
}

/**
 * The initial setting of $emusic->regions_tax_map on load
 *
 */
add_action( 'init', 'set_regions_tax_map', 20 );
/**
 * Resets context on switch to blog
 *
 */
add_action( 'switch_blog', 'set_regions_tax_map' );

STILL, we don’t have regionalized content, we just have a standard way of mapping region code to term_taxonomy_ids. So why do I care about the IDs? Because we have to use them to alter our default WP queries so that posts only intersect the region of the current user.

Tax Query

Tax Query is an advanced way of altering WP_Query. If you load a page of WP posts, you ran WP_Query. A developer can make their own instances of WP_Query or alter the global one. We need to alter the global one and we don’t want to ever use query_posts to do that. The first thing we need to do is retrieve our map of Term Name => Term Taxonomy ID:

/**
 * Gets a map of region name => term_taxonomy_id
 *
 * @return array
 */
function get_current_regions_tax_map() {
    return get_emusic()->regions_tax_map;
}

Now that we have that, we want to use it in the tax query that we are going to inject into WP_Query. One step at a time, here’s the tax_query portion, encapsulated in a function that always returns the proper context:

/**
 * Encapsulates common code to create a regionalized tax_query
 *
 * e.g. $tax_query = array( get_region_tax_query() );
 *
 * @param int $region
 * @param int $all
 * @return array
 */
function get_region_tax_query( $region = '', $all = true ) {
	$regions_tax_map = get_current_regions_tax_map();
	if ( empty( $regions_tax_map ) )
		return;

	$terms = array();
	$terms[] = $regions_tax_map[ empty( $region ) ? THE_REGION : strtoupper( $region ) ];
	if ( true === $all )
		$terms[] = $regions_tax_map['ALL'];

	$tax_query = array(
		'operator'		=> 'IN',
		'taxonomy'		=> 'region',
		'field'			=> 'term_taxonomy_id',
		'terms'			=> $terms,
		'include_children'	=> false
	);

	return $tax_query;
}

Being able to query by term_taxonomy_id is important, because it is the primary key for the term / taxonomy relationship. If I had passed term_ids, they would need to be translated into term_taxonomy_ids by WP_Tax_Query before WP_Query could complete its logic. I helped nurse this new functionality along in WordPress core (3.5): https://core.trac.wordpress.org/ticket/21228

Ok cool, we have the tax_query portion, but we haven’t applied it to the global WP_Query yet, let’s do that next.

“pre_get_posts”

Here is what you may use in your theme or plugin to apply regionalization to WP_Query. The best place to hook in is “pre_get_posts.” In this hook, we can directly alter the query:

class MyAwesomeExampleThemeClass {

.....

protected function regionalize() {
    add_filter( 'pre_get_posts', array( $this, 'pre_posts' ) );
}

/**
 *
 * @param WP_Query $query
 * @return WP_Query
 */
function pre_posts( $query ) {
    if ( $query->is_main_query() && ! is_admin() && ( is_archive() || is_front_page() || is_search() ) ) {
        if ( ! $query->is_post_type_archive() )
            $query->set( 'post_type', $this->post_types );

        $query->set( 'tax_query', array( get_region_tax_query() ) );

        $query = apply_filters( 'emusic_pre_get_posts', $query );
    }
    return $query;
}

....

}

The above will regionalize posts in search results and on archive pages. If you want to alter a query inline, now all you have to do is something like this:

$results = new WP_Query( array(
    'tax_query'	=> array( get_region_tax_query() ),
    'orderby' => 'comment_count',
    'order' => 'DESC',
    'posts_per_page'=> $limit
) );

In Action

View eMusic in multiple regions:

From the US
From France in the EU
From Canada (CA)
From Great Britain, mate (UK)

WordCamp NYC 2012: “Cloud, Cache, and Configs”

Here are the slides from my talk today:

I spoke for 40 minutes to a room full of people that had no idea what I was saying. Seriously.

Have any of you made a plugin before? Silence / crickets. Cool, well let me dive into scaling HTTP parallelization for 15 minutes…

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.

Listen To This: “Neck of the Woods” – Silversun Pickups

Image

Update: Read More at Rolling Stone

I saw Silversun Pickups at the All Points West Festival in 2009. They were awesome. Since then, I have pretty much forgotten about them. Since I have worn out most of the new records I have discovered this year, I decided to pop over to the eMusic homepage and see what was new and exciting. Downloaded Neck of the Woods for fun. It is really really good. I love the electronic drums and the insanely persistent motion the create. Here are some of my favorite tunes from the record:

“Make Believe”

“Busy Bees”

“Here We Are (Chancer)”

Listen To This: “Some Nights” by fun.

Unequivocally, the best album of 2012 so far. I keep running into people who haven’t heard it yet, or have, but not the whole record. Trust me. This is as good as it gets.

Lead singer / creative force Nate Ruess (formerly of The Format) sounds like a man possessed on this cabaret of an operatic blast of an indie-pop treasure. “We Are Young” is ubiquitous by now, and luckily unseated Nickelback as the last song by a band to debut at #1 on the Billboard Hot 100. The song is great, but almost takes a backseat to the fire-blast of a Freddie Mercury opener that is the title track. With lines like “WHAT DO I STAND FOR?” and “SOME NIGHTS I STAY UP, CASHING IN MY BAD LUCK” sung in Queen-like harmony and declaration, it’s impossible to not lose your shit during it.

fun. lucked out by getting Jeff Bhasker to produce the record, which I think sounds more focused and cleaned-up than Aim and Ignitecheck his credits.

With most records I like, I scan YouTube for live performances to see if the artist is the real deal. Almost every live capture of “Some Nights” (the song) is hard to hear because the crowd is going insane the whole time.

Here’s a great acoustic performance of “We Are Young” with Janelle Monáe:

Here’s a video of people going fucking apeshit at their show in Atlanta during “Some Nights”:

This record will be hard to unseat as my favorite for the year. eMusic even did a Six Degrees on it.

Listen To This: “Strange Weekend” – Porcelain Raft

2012 got off to a slow start for me as far as new music goes. By this time last year, I was already knee deep in the Akron / Family record, the new PJ Harvey, and had re-fallen in love with the last 2 Andrew Bird records. This year started off slow. Heartless Bastards released a great record, Sharon Van Etten released her first great record, but nothing was blowing my mind. I can safely say Strange Weekend is already one of the best records of 2012.

I first heard “Picture” by Porcelain Raft in this eMusic ad:

You should immediately notice how awesome the tune is. The rest of the album is equally as cool – from the eMusic review:

With their underwater acoustics, synths sparkling in oceans of reverb, and Remiddi’s high, ethereal chirp, these lovingly constructed, basement-recorded jams float by in a gorgeous haze – like a brilliant dream you barely remember, a soft drug trip you didn’t realize you were having. But for all its spacey minimalist texture, this isn’t remotely close to chillwave: There’s too much concrete pain and sweat in Remiddi’s voice, too much clarity in the arrangements, which (even at their most ambient) put melody over mosaics.

Mauro Remiddi (from Rome, Italy) went solo at 40 and made what has to be the best music of his career as Porcelain Raft.