WordPress 3.6 + Audio/Video

Screen Shot 2013-08-01 at 1.39.43 PM

I have already written once about the new support for Audio / Video in WordPress 3.6:
http://make.wordpress.org/core/2013/04/08/audio-video-support-in-core/

I also spoke about the new features in my Code Poet interview:
http://build.codepoet.com/2013/07/18/scott-taylor-interview/

I wanted to use this space to give the users some information about the new code and how to take advantage of it.

Upload Limits

A lot of Apache / PHP installs have an arbitrarily low file upload size limit set (usually 2MB). An average .mp3 file is at least 3-5MB. Video can be way bigger. If you are on a shared host that doesn’t allow uploads over a certain size, you may have to get more creative about where your audio and video files are stored. The only difference will be not having them in your media library. You can use all of the new a/v features with remote files.

If you have access to your own server and can tweak your configs, you can change your settings to allow larger uploads. There are a bunch of ways to override – either by altering your php.ini file or setting PHP values in .htaccess and then restarting Apache. The best way is to change your .ini settings:

// find the location of your php.ini file:
php -i |grep php.ini

// in php.ini
upload_max_filesize = 2M

// change this to something bigger
upload_max_filesize = 2000M

// also change this value
post_max_size = 2000M

You may need to change permissions of php.ini to make it writable.

Mime-Types

Paul Irish’s HTML5 Boilerplate has all of the settings you need to support the types used by HTML5 audio and video <audio> and <video> tags: the file

The most useful lines:

<IfModule mod_mime.c>
  # Audio
    AddType audio/mp4              m4a f4a f4b
    AddType audio/ogg              oga ogg

  # Video
    AddType video/mp4              mp4 m4v f4v f4p
    AddType video/ogg              ogv
    AddType video/webm             webm
    AddType video/x-flv            flv
</IfModule>

If you don’t do this, your server may send the files with a mime-type of application/octet-stream. They might play, might not, but it’ll probably get weird.

Shortcodes

The simplest way to use the new media shortcodes is to not write them by hand. When you click “Insert Into Post” from Media Library for an audio or video file now, the shortcode will be written into the editor for you. Probably don’t mess with it. If you are a video savant and have multiple copies of your video or audio in all of the HTML5 formats, you can use them all, but you will have to write in the extra attributes by hand.

Examples of shortcodes inserted by media library:

// by default uses the "src" attribute
[audio src="http://tunez.net/cool-song-bro.mp3"]

// multiple files for maximum HTML5 playback
[audio mp3="http://tunez.net/cool-song-bro.mp3"
    ogg="http://tunez.net/cool-song-bro.mp3"]

The [audio] shortcode also allows the following attributes: loop, autoplay, and preload (defaults to none).

// by default uses the "src" attribute
[video width="400" height="300" src="http://tunez.net/cool-movie-bro.mp4"]

// multiple files for maximum HTML5 playback
[video width="400" height="300"
    mp4="http://tunez.net/cool-movie-bro.mp4"
    ogv="http://tunez.net/cool-movie-bro.ogv"
    webm="http://tunez.net/cool-movie-bro.webm"]

The [video] shortcode also allows the following attributes: poster, loop, autoplay, and preload (defaults to metadata).

Embed Handlers

If you put an audio or video URL on a line by itself – boom, you’re done. A player will show up if the file extension is in the list of supported types.

http://mp3-url

This is where my text starts - notice the breathing room
between the URL and the content.

Metadata

Your audio and video uploads now have metadata that is extracted when they are uploaded. Prior to 3.6, no metadata was generated for audio and video files. A/V files are typically created with ID3 tags. ID3 tags contain data like artist, album, song, genre, etc for audio files and length, dimensions, codecs, etc for video.

To access this data:

// assuming you have an attachment ID
$meta = wp_get_attachment_metadata( $attachment->ID );

// debug to see what is available
print_r( $meta );

// always check for the property's existence before doing anything
if ( ! empty( $meta['length_formatted'] ) )
     echo $meta['length_formatted'];

None of the data is guaranteed to be there. If you have uploaded audio or video previously, this data won’t be generated on the fly. We are using the getID3 library to parse the files on upload. The code is elaborate. It is also possible that your media files don’t contain ID3 tags. If your files DO contain data, some of it is displayed in the sidebar on the Edit Media page. Some of the fields on the Edit Media page are pre-populated now with data from your ID3 tags, if they were present on upload.

Images embedded in MP3s

If you add the following lines to your functions.php:

add_post_type_support( 'attachment:audio', 'thumbnail' );
add_post_type_support( 'attachment:video', 'thumbnail' );
add_theme_support( 'post-thumbnails', array( 'attachment:audio', 'attachment:video' ) );

If your mp3 contains an image, the image will be extracted, uploaded to your media library, and will be the post thumbnail for your audio file.

Screen Shot 2013-08-01 at 4.58.17 PM

Best Albums of 2013 So Far

Disclosure, Settle

I have FOMO when it comes to music, so I have been focusing all week on listening to as much 2013 music as possible. I scoured other people’s mid-year lists, downloaded some stuff from eMusic / bought some stuff on iTunes, watched all kinds of clips on YouTube.

At this point in the year, electronic music is king. There are also big releases that have yet to drop and will definitely shake up the year-end list (Drake and The Weeknd, to name a few). The records at the top are unsurprising.

The Best

Disclosure – Settle
Kanye West – Yeezus
Phosphorescent – Muchacho
The National – Trouble Will Find Me
Yeah Yeah Yeahs – Mosquito
Kacey Musgraves – Same Trailer Different Park
Vampire Weekend – Modern Vampires of the City

Other things worth listening to:

Akron/Family – Sub Verses
Woodkid – The Golden Age
Daughn Gibson – Me Moan
Lightning Dust – Fantasy
Deptford Goth – Life After Defo
Flume – Flume
Little Boots – Nocturnes
Autre Ne Veut – Anxiety
Daughter – If You Leave

House of Balloons, chock full of samples

Image

The story of The Weeknd is folklore by now, but you may not realize how much of the first mixtape, House of Balloons, is cribbed from other artists. I don’t think it diminishes the record, but you probably have no idea how much Beach House you are listening to in the background. House of Balloons is the first of 3 mixtapes that dropped in 2010 under the moniker of The Weeknd, all 3 later released as the remastered / repackaged Trilogy.

“House of Balloons/Glass Table Girls”

Samples “Happy House” by Siouxsie and the Banshees. “This is… a Happy House…” etc, listen for it:

Stuffed in the background is this track from 1980:

“The Party & The After Party”

The first half of the track happens over a sped-up version of Beach House’s “Master of None” from their 2006 self-titled debut. Here’s The Weeknd sampling it, I was floored how much of this track is based on it:

And here’s the original Beach House version:

“Loft Music”

“Loft Music” features another sped-up, flanged-out Beach House track, from 2008’s Devotion, “Gila.”

Here’s the original, which inspires the vibe of the whole tune at double speed:

“The Knowing”

This track features sped-up, warped bits of the Cocteau Twins track “Cherry Coloured Funk” from their record Heaven or Las Vegas, also the name of a track on The Weeknd’s mixtape, Thursday. It’s the first thing you hear:

Cocteau Twins:

“What You Need”

The version which ultimately ended up on Trilogy is devoid of “Rock the Boat” by Aaliyah, but the original mixtape version has excerpts:

Aaliyah:

There was some beef from Jeremy Rose (the producer of “What You Need,” “The Party & The After Party,” and “Loft Music”) about not getting proper credit for his role on the record. Ironically, 2 of those tunes are the ones which sample Beach House the most.

Concerts I attended in 2012

Sharon Van Etten and Shearwater

I do this post every year (2011, 2010) as a wrap-up to remind me of all of the awesome stuff I saw over the year. Here’s the full list, followed after by a breakdown:

01.21.2012 Akron / Family – 285 Kent

02.17.2012 Sleigh Bells – Terminal 5
02.24.2012 Heartless Bastards – Webster Hall
02.25.2012 Sharon Van Etten – Bowery Ballroom
02.29.2012 Craig Finn – Mercury Lounge

03.08.2012 Craig Finn – Music Hall of Williamsburg
03.17.2012 Megafaun – Highline Ballroom
03.30.2012 Andrew Bird – Greene Space
03.30.2012 Of Montreal – Webster Hall

04.04.2012 Bruce Springsteen – Izod Center
04.06.2012 The War on Drugs – Music Hall of Williamsburg
04.10.2012 The Horrors – Music Hall of Williamsburg
04.12.2012 White Rabbits – Webster Hall
04.24.2012 Allo Darlin’ – Mercury Lounge
04.28.2012 Death Cab for Cutie / Youth Lagoon – Beacon Theater

05.04.2012 Andrew Bird – Beacon Theater
05.09.2012 The Avett Brothers – Terminal 5
05.11.2012 M. Ward – Webster Hall
05.11.2012 Rob Delaney – Bowery Ballroom
05.31.2012 Radiohead – Prudential Center

06.01.2012 Porcelain Raft – Mercury Lounge
06.22.2012 fun. – Music Hall of Williamsburg
06.29.2012 Akron / Family – Brooklyn Bowl

07.10.2012 Dirty Projectors – Prospect Park Bandshell
07.25.2012 Wilco – Terminal 5

08.02.2012 Donna The Buffalo – Rockin’ the River Cruise
08.06.2012 Mighty Mighty Bosstones – Webster Hall
08.08.2012 Bloc Party – Terminal 5
08.20.2012 Matthew E. White – Mercury Lounge
08.27.2012 Louis C.K. – The Bell House

09.21.2012 The Book of Mormon – Eugene O’Neill Theatre
09.22.2012 Bon Iver – Radio City Music Hall
09.23.2012 Metric – Radio City Music Hall
09.29.2012 Global Citizen Global Festival (Black Keys, Band of Horses, Foo Fighters, Neil Young)

11.14.12 Yellow Ostrich / Strand of Oaks – Bowery Ballroom
11.17.12 Occupy Sandy Benefit (Dirty Projectors, Vampire Weekend, et al) – St. Ann & The Holy Trinity
11.17.12 Matthew Dear – Webster Hall

12.11.12 Band of Horses (Acoustic) – Grand Ballroom
12.11.12 Band of Horses (Electric) – Hammerstein Ballroom
12.21.12 Dave Matthews Band / The Lumineers – Barclay’s Center
12.31.12 Blonde Redhead / Beach Fossils – Irving Plaza

Best Shows I Saw in 2012

#1 Bruce Springsteen – Izod Center 04.04.2012

a26693cc7eb711e180d51231380fcd7e_7

It is hard to explain the sheer electricity Bruce brings to a stadium in New Jersey. In Jersey, The Boss and Jovi are Gods. The Boss, deservedly so. Almost every time I think back to any key moment from this show, I get goosebumps like it’s happening right now. Aside from the spotless production and pro band, it’s hard to compete with moments like:

  • Bruce and Little Steven sharing the mic on “She’s The One”
  • Being only a few miles from Ground Zero and listening to a gospel-tinged “My City of Ruins”
  • Watching a 62-year old frontman who’s in better shape than Justin Bieber lead 18,000 people in sing the joyful yet heartbreaking “Waiting on a Sunny Day”
  • In the first tour after Clarence Clemons’ death, playing “10th Avenue Freeze Out” and after the line “the big man joined the band…” – coming to a dead stop, while all of the video screens in the arena flash images of Bruce and Clarence through the years as the band remains silent
  • “Thunder Road” and “Born to Run”

#2 Band of Horses (Acoustic) – Grand Ballroom 12.11.12
Band of Horses (Electric) – Hammerstein Ballroom

Band of Horses

No big deal, just saw one of my favorite bands play live every song they’ve recorded at 2 shows in the same night. First show started at 7:30pm. 2nd show ended at 1am. I was floating on air for days after this show.

### Special Acoustic Performance ###

On My Way Back Home
Marry Song
Dilly
Long Vows
Shut-In Tourist
St. Augustine
Evening Kitchen
No One’s Gonna Love You
Neighbor
Detlef Schrempf
How to Live
Lamb on the Lam (In the City)
Slow Cruel Hands of Time
Simple Man (Lynyrd Skynyrd cover) (By request)
Wicked Gil
The Funeral
The General Specific

### Electric Music ###

Set #1

For Annabelle
The First Song
Electric Music
Part One
Older
A Little Biblical
Weed Party
The Great Salt Lake
Blue Beard
Compliments
Dumpster World
Cigarettes, Wedding Bands
Factory
Knock Knock
Window Blues
Laredo
Ode to LRC
The Funeral

Set #2

Our Swords
Everything’s Gonna Be Undone
No One’s Gonna Love You
NW Apt.
Feud
Is There a Ghost
Heartbreak on the 101
Infinite Arms
I Go to the Barn Because I Like The
Monsters
Neighbor
(reprise)

#3 Megafaun – Highline Ballroom 03.17.2012

From my Facebook “Last night was a blurry mess / I have half-eaten Mamoun’s in the fridge / I bought Megafaun 2 rounds of shots.” Great night though. The bass player came out and hugged me after. This show was on St. Patrick’s Day and was sadly under-attended. Didn’t matter, Chris and I loved it.

#4 Craig Finn – Mercury Lounge 02.29.2012

Craig Finn

Craig Finn is the lead singer of arguably my favorite (NYC) band, The Hold Steady. Seth had an extra, so I got into this sold-out show where we stood front row the whole time. Loved hearing the tunes from his underrated solo record live.

#5 Bon Iver – Radio City Music Hall 09.22.2012

Bon Iver at Radio City

They made their way through almost every song from both records. “Beth / Rest” and “re: stacks” moved my universe. AMAZE.

#6 fun. – Music Hall of Williamsburg 06.22.2012

fun.

Randomly replied to an email from their website and got 2 VIP tix to this invite-only tiny show for some vague T-Mobile / Walmart Soundcheck event. It was awesome. They had my favorite album on 2012.

Best Albums of 2012

fun.

I agonize over this list every year, for 100% personal reasons. I feel like I have failed as a human if I miss anything hot that happened musically in a year. Because I live in the city that never sleeps, when I like a record, I can typically see the artist play it live soon after it comes out. Here’s the year in review.

Top 10

Some Nights, fun.

fun., Some Nights

channel Orange

Frank Ocean, channel ORANGE

Wrecking Ball

Bruce Springsteen, Wrecking Ball

Strange Weekend

Porcelain Raft, Strange Weekend

Bloom

Beach House, Bloom

Swing Lo Magellan

Dirty Projectors, Swing Lo Magellan

Tramp

Sharon Van Etten, Tramp

Beams

Matthew Dear, Beams

Escort

Escort, Escort

Fear Fun

Father John Misty, Fear Fun

Special Award: The Weeknd, Trilogy
Technically, this encompasses 3 records that dropped last year. Chris had a visceral reaction of disgust when I even hinted at throwing this at #1

Also Really Good

The Walkmen, Heaven
Craig Finn, Clear Heart Full Eyes
Strand of Oaks, Dark Shores
Purity Ring, Shrines
Lord Huron, Lonesome Dreams

Notable

Japandroids, Celebration Rock
Beak>, >>
Django Django, Django Django
Cat Power, Sun
Daughn Gibson, All Hell
White Rabbits, Milk Famous
Silversun Pickups, Neck of the Woods
Kendrick Lamar, good kid, m.A.A.d city

WordPress 3.5 + Me

WordPress 3.5 dropped today. This is a special release for me because my picture made it to the Credits and I had 30-40 of my patches committed. Here’s the full list: https://core.trac.wordpress.org/search?q=wonderboymusic&noquickjump=1&changeset=on

The hightlights:

I have 55 patches on deck for 3.6 already, excited to see what makes it! If anyone out there is thinking about contributing to core and is hesitant, don’t be. 90% of success is showing up. Be There. Subscribe to Trac. Comment on tickets. Test patches. Occasionally check in on IRC. The people who are making WordPress are there. You could be one of them.

I was just a little lad with a dream 2 years ago at my first WordCamp in NYC when I grilled Nacin and Koop about using IDs instead of classes in the CSS selectors for Twenty Ten. Koop talked to me afterward and suggested I contribute to core. My first patch was at the after-party for WordCamp San Francisco 2011 at 2am at the old Automattic space at the Pier on the Embarcadero. I got 1 patch into 3.2. 1 patch into 3.3. Zero into 3.4. And here we are.

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)