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.

Intstalling MySQL 5.5 on OS X (Mountain) Lion

I have a dark and painful history of installing MySQL the wrong way, which has forced many late nights and ungodly amounts of frustration. It should be super easy, and sometimes it is, but sometimes it is not. And it can suck.

This post is as much a documentation of what I did so I can re-read it the next time I screw up something as it is a tutorial for you. Let’s begin.

Don’t use MacPorts, use the .dmg from the MySQL website

MacPorts will give you MySQL 5.1.48 or something like it when you install the mysql5 port. The mysql55 port doesn’t upgrade your mysql5 port, it runs side by side and can be super confusing if you want to run terminal commands or change permissions on directories your forgot about when you installed mysql5. mysql5-devel doesn’t build on Mountain Lion yet. Awesome.

The .dmg file will give you everything you need: installs beautifully in /usr/local/mysql, provides a startup item installer that will fire up the mysqld_safe launch daemon when you turn your computer on, and a Preferences pane item that will allow you to start and restart MySQL until you are blue in the face with the simple press of a button.

Your install should start like this:

  • Download the .dmg
  • port uninstall mysql5 and mysql5-devel or whatever else you have installed
  • Add this to your ~/.bash_profileexport PATH=$PATH:/usr/local/mysql/bin
  • Restart your computer to make sure only your new MySQL 5.5’s daemon is running (when you uninstall the MacPorts, their daemons are still running until you kill them or restart) by typing:
    ps aux | grep mysql

If you see anything running from /opt, your MacPort(s) aren’t uninstalled.

After you have restarted your computer:

  • Fire up Terminal and type: mysql -u root
  • Run this query:  GRANT ALL ON *.* TO 'root'@'localhost' IDENTIFIED BY 'mypassword' WITH GRANT OPTION; (replacing ‘mypassword’ with whatever you want your root password to be)

At this point, you should have a database that works. If not, read about symlink-ing .sock files, etc here: How to install MySQL 5.5 on Mac OS X 10.7 Lion

Ep #1: WordPress things you should never do

Here is a somewhat random list of rules containing things I hope you never do in PHP or WordPress.

Do not ignore your error_ and access_ logs

If you don’t have your Apache error_log running 100% of the time you are coding, your site is probably full of bugs. Your error_log should only contain intentional messages you have included to benchmark or log specific activity. If should NEVER contain PHP Errors, Warnings or Notices. Those should be corrected immediately. Once your site rolls to production and you start getting a lot of traffic, monitor your logs there as well. Production will expose every edge case you could possibly dream up.

You should regularly check your Apache access_log as well for requests to your domain that don’t return something less than HTTP error code 400. If any of your files or scripts return 400, 404, or 500 in WordPress, you may get into a loop of infinite recursion that your Web Server will choke on after about 10 times through this circle of hell.

To tail your error_log for PHP-only errors, etc (using MacPorts):

tail -f /opt/local/apache2/logs/error_log | grep "PHP " &

Also make sure your wp-config.php contains the following:

// turn on all errors
error_reporting( -1 );

Don’t try to access Array / Object properties that don’t exist

90% of the errors that inexperienced PHP developers generate are based on doing something like this:

// $data might be empty
$data = give_me_what_I_want();

echo $data[0]['items'][7]->fail();

The second you try to access the 0th index of $data, your code will fire off a PHP Notice. DO NOT IGNORE IT. You have 2 choices with arrays to avoid this:

// check that the whole path is cool

if ( isset( $data[0]['items'][7] ) )
     $data[0]['items'][7]->fail();

// be safe all the way down

if ( !empty( $data ) )
    if ( !empty( $data['items'] ) )
        if ( is_a( $data[0]['items'][7], 'SomeObj' ) )
            $data[0]['items'][7]->fail();

You don’t have to get ridiculous, but you also don’t have to be careless.

isset() won’t throw any log notices when you’re checking for depth in an array that may not have it.

empty() is a great utility. It will return a boolean for 0, empty string, empty array, false, or null. The best way to check a variable that might be an array, but might be nothing. You should always check an array for empty before iterating over it, this will prevent  an error being thrown when you start your foreach loop:

// do this

if ( !empty( $might_be_empty_array ) ) foreach ( $might_be_empty_array as $item ) {
    blame_nacin( $item );
}

// not this

foreach ( $im_empty_and_logging_it as $item ) {
     hope_its_not_empty( $item );
}

Don’t use mysql_real_escape_string

If you think you can just call this function out of nowhere to escape some text to be used in a SQL statement, you server is probably barfing and you don’t know it:

$whoops = mysql_real_escape_string( 'escape me!' );

// your logs are blowing up while server
// tries to connect to MySQL with default connection params
// because you didn't pass in an open connection to MySQL
// as the 2nd parameter

// use me instead

$cool = $wpdb->escape( 'escape me!' );

Most functions that start with mysql_* are the procedural counterpart to the object-oriented functions made available by the MySQL extension to PHP. Most of the procedural functions (similar to Memcache functions in this way) require the connection, or current resource instance, passed in as an argument as well. Even if you did pass in the resource, you should be using the mysqli functions instead (MySQL Improved Interface). WordPress uses mysql_* everywhere, so I guess forget what I just said….

ereg_*

This should be blatantly obvious by now, but if you aren’t a whiz at RegEx and Google that wrong site, you may end up pasting in some deprecated POSIX regular expressions when you should be using PCRE = Perl-compatible Regular Expressions.

POSIX = ereg_* functions
PCRE = preg_* functions

serialize

I am going to dare you to name me a good reason to create serialized arrays in your code. Since you can’t find one, I am going to ask you how great of an idea it is to store serialized arrays in the database. Since you don’t know the answer, I am going to ask you how knowledgable you are about ASCII, UTF-*, and ISO-*. Since you have no clue why I am asking you that, I want you to heed my warning: DO NOT USE SERIALIZED ARRAYS. If you do use serialized arrays, store NUMBERS and CHARs only. DO NOT STORE STRINGS of any length that constitute any amount of whitespace.

Here’s why:

Serialized arrays that contain strings bind those strings to their string length, making them as non-portable as possible. If you import / export data and have ANY weird characters that were pasted in from Word or worse, your strings may become invisibly altered and won’t match your bound string lengths. This is super important because of the way we get our data into a usable format when it is stored this way is through unserialize. unserialize fails easy and often when dealing with weird strings.

In WordPress, maybe_unserialize is a function invoked to do this to strings stored in the wp_*meta tables. maybe_unserialize will fail as well, easily and often, but does so….SILENTLY.

You might ask yourself, when would I ever do this anyways?

Does this look familiar?

$data = array( 'format' => 'long', 'color' => 'red' );
update_post_meta( $post_id, 'stuff', $data );

My example is harmless – but let’s say you are using Post Meta to store some SEO text, you are susceptible to maybe_unserialize failing. maybe_unserialize won’t return mangled text when this happens, it will return quite literally nothing.

Another huge problem is the version of MySQL you might be running. MySQL 5.5 is WAY more forgiving with invisible characters in UTF-8 strings with illegal bytes. If you are running any flavor of MySQL 5.1.* and you import data from a MySQL 5.5 database, you mean get bombarded with foreign characters you didn’t have in your 5.5 environment. If you were storing that data in serialized arrays, the data will cause unserialize to fail.

Be careful using $_SESSION

Using $_SESSIONs with Memcached can be palatable, but for most people that don’t know what they are doing, using PHP sessions can be extremely problematic. In almost every case, PHP will store your session_id( ) for a user as a Cookie, it will then throw no-cache headers with every request. Which sucks.

When no-cache is triggered by the session_start() function, this is what the HTTP headers look like:

Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache

Those headers instruct your browser to re-request the page every time you visit it in your browser. Luckily for us, this little nugget tell sessions to not use a cache limiter. It will allow Batcache to work as well. Without it, Batcache will not work:

To turn off the no-cache headers:

session_cache_limiter( false );
session_start();

“switch_to_blog( )” is an Unfunny Nightmare

It can be extremely challenging to write WordPress code that works across many environments and allows you to use a custom location for WordPress media, WordPress multisite media, and WordPress core itself. I wrote about this extensively here. The code I included in that post works, although a lot of it was excerpted to be shown as a short example here or there. If your site switches from one blog to the next and doesn’t intermingle content – you really don’t have much to worry about after your initial setup. But in almost all cases, if you want to start using switch_to_blog() to co-mingle content from multiple sites inline, get ready to do some debugging!

switch_to_blog() works like so:

// I am on my blog

switch_to_blog( 2 );

// I am on your mom's blog

restore_current_blog();

// I am on my blog

Simple enough. This will switch context all over the place in WP core. wp_posts will become wp_2_posts. get_current_blog_id() will return 2 because the global variable $blog_id will be set to 2, etc.

In my post about environments, I had a lot of filters that I suggested adding in wp-content/sunrise.php that work just great in switching initial context to a specific blog and in setting up overrides for default media upload paths etc. They work fine… unless you ever plan on using switch_to_blog().

Here’s an example:

// Before
add_filter( 'pre_option_home', function ( $str ) use ( $domain ) {
    return 'http://' . $domain;
} );

// After using switch_to_blog and realizing I needed to account
// for any sort of weird context I may find myself in

add_filter( 'pre_option_home', function () {
    global $current_blog;
    $extra = rtrim( $current_blog->path, '/' );
    return 'http://' . MY_ENVIRONMENT_HOST . $extra;
} );

Ok cool, so you pull a path from a global variable and append it if it still has a value after being rtrim‘d? Easy. That would be true if $current_blog and its properties were updated every time switch_to_blog() is called. It is not!

$current_blog is one of the values we set in wp-content/sunrise.php to override the database and set our $current_blog->domain value to our current environment. $current_blog and $current_site exist mainly for use on the initialization of Multisite. Outside of startup, they aren’t really accessed or modified.

Because I want a static way to access dynamic variables, I have added some code to change the context of $current_blog when switch_to_blog() is called in wp-content/sunrise.php:

function emusic_switch_to_blog( $blog_id, $prev_blog_id = 0 ) {
    if ( $blog_id === $prev_blog_id )
        return;

    global $current_blog, $wpdb;
    $current_blog = $wpdb->get_row( "SELECT * FROM {$wpdb->blogs} WHERE blog_id = {$blog_id} LIMIT 1" );
    $current_blog->domain = MY_ENVIRONMENT_HOST;
}

emusic_switch_to_blog( $the_id );

add_action( 'switch_blog', 'emusic_switch_to_blog', 10, 2 );

Now that I have added that action, my filter pre_option_home will work. I use the same method for pre_option_siteurl. What I was previously doing to retrieve the path of the current blog didn’t work:

add_filter( 'pre_option_siteurl', function () {
    $extra = rtrim( get_blog_details( get_current_blog_id() )->path, '/' );
    return 'http://' . EMUSIC_CURRENT_HOST . $extra;
} );

Why didn’t it work? get_blog_details() eventually does $details->siteurl = get_blog_option( $blog_id, 'siteurl' ); giving us a nice and hardy dose of infinite recursion. So to combat it – I implemented the setting of $current_blog on the switch_blog action so its properties are always the current blog’s properties. Boom.

The next 2 annoyances are media upload urls / paths and admin paths. We use custom media locations for the main blog and network blogs:

add_filter( 'pre_option_upload_path', function () {
    $id = get_current_blog_id();
    if ( 1 < $id )
        return $_SERVER['DOCUMENT_ROOT'] . "/blogs/{$id}/files";

     return $_SERVER['DOCUMENT_ROOT'] . '/' . EMUSIC_UPLOADS;
} );

add_filter( 'pre_option_upload_url_path', function () {
    $id = get_current_blog_id();
    if ( 1 < $id )
        return 'http://' . EMUSIC_CURRENT_HOST . "/blogs/{$id}/files";

     return 'http://' . EMUSIC_CURRENT_HOST  . '/' . EMUSIC_UPLOADS;
} );

We switched blog context using switch_to_blog(), which triggers our action, which then sets the global variable $blog_id to our current blog’s id, which can then be retrieved from within functions by get_current_blog_id(). Yeah, that’s a mouthful. We are also using a constant for our host name, so we don’t have to query for it / output-buffer it / or str_replace() it.

The admin is a little bit trickier because we usually don’t call switch_to_blog() in the code, but the admin bar will list your blogs and has URLs to their Dashboards etc. We can filter those as well:

function get_admin_host( $url, $path, $blog_id = '' ) {
    $path = ltrim( $path, '/' );

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

    $blog_path = rtrim( get_blog_details( $blog_id )->path, '/' );
    return sprintf( 'http://%s%s/wp-admin/%s', EMUSIC_CURRENT_HOST, $blog_path, $path );
}

add_filter( 'admin_url', 'get_admin_host', 10, 3 );

function get_network_admin_host( $url, $path, $blog_id = '' ) {
    $path = ltrim( $path, '/' );

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

    $blog_path = rtrim( get_blog_details( $blog_id )->path, '/' );
    return sprintf( 'http://%s%s/wp-admin/network/%s', EMUSIC_CURRENT_HOST, $blog_path, $path );
}

add_filter( 'network_admin_url', 'get_network_admin_host', 10, 3 );

If you want to see this in action, I recently integrated the eMusic editors’ 17 Dots blog into eMusic proper, check it out: 17 Dots. You can see multiple blogs intertwining on the homepage.

Major bug with WordPress + New Relic

If you haven’t seen New Relic yet, definitely take a look, it’s awesome.

That being said… if you are running New Relic monitoring on a huge production site, your logs might be spinning out of control without you knowing it, and I’ll explain why.

We use New Relic at eMusic to monitor our WordPress servers and many other application servers – Web Services, Databases etc – and we started noticing this in the logs like whoa: failed to delete New Relic auto-RUM

In New Relic’s FAQ section:

Beginning with release 2.6 of the PHP agent, the automatic Real User Monitoring (auto-RUM) feature is implemented using an output buffer. This has several advantages, but the most important one is that it is now completely accurate for all frameworks, not just Drupal and WordPress. The old mechanism was fraught with problems and highly sensitive to things like extra Drupal modules being installed, or customization of the header format. Using this new scheme, all of the problems go away. However, there is a down-side, but only for specific PHP code. This manifests itself as a PHP notice that PHP failed to delete buffer New Relic auto-RUM in…. If you do not have notices enabled, you may not see this and depending on how your code is written, you may enter an infite loop in your script, which will eventually time out, and simply render either an empty or a partial page.

To understand the reason for this error and how it can create an infinite loop in code that previously appeared to work, it is worth reading the PHP documentation on the ob_start() PHP function. Of special interest is the last optional parameter, which is a boolean value called erase that defaults to true. If you call ob_start() yourself and pass in a value of false for that argument, you will encounter the exact same warning and for the same reason. If that variable is set to false, it means that the buffer, once created, can not be destroyed with functions like ob_end_clean(), ob_get_clean(), ob_end_flush() etc. The reason is that PHP assumes that if a buffer is created with that flag that it modifies the buffer contents in such a way that the buffer cannot be arbitrarily stopped and deleted, and this is indeed the case with the auto-RUM buffer. Essentially, inside the agent code, we start an output buffer with that flag set to false, in order to prevent anyone from deleting that buffer. It should also be noted that New Relic is not the only extension that does this. The standard zlib extension that ships with PHP does the same thing, for the exact same reasons.

We have had several customers that were affected by this, and in all cases it was due to problematic code. Universally, they all had code similar to the following:

while (ob_get_level()) {
  ob_end_flush ();
}

The intent behind this code is to get rid of all output buffers that may exist prior to this code, ostensibly to create a buffer that the code has full control over. The problem with this is that it will create an infinite loop if you use New Relic, the zlib extension, or any buffer created with the erase parameter set to false. The reason is pretty simple. The call to ob_get_level() will eventually reach a point where it encounters a non-erasable buffer. That means the loop will never ever exit, because ob_get_level() will always return a value. To make matters worse, PHP tries to be helpful and spit out a notice informing you it couldn’t close whatever the top-most non-erasable buffer is. Since you are doing this in a loop, that message will be repeated for as long as the loop repeats itself, which could be infinitely.

So basically, you’re cool if you don’t try to flush all of the output buffers in a loop, because you will end up breaking New Relic’s buffer. Problematic as well if you are managing several of your own nested output buffers. But the problem might not be you, the problem is / could be WordPress.

Line 250 of wp-includes/default-filters.php:

add_action( 'shutdown', 'wp_ob_end_flush_all', 1 );

What does that code do?

/**
 * Flush all output buffers for PHP 5.2.
 *
 * Make sure all output buffers are flushed before our singletons our destroyed.
 *
 * @since 2.2.0
 */
function wp_ob_end_flush_all() {
	$levels = ob_get_level();
	for ($i=0; $i

So that’s not good. We found our culprit (if we were having the problem New Relic describes above). How to fix it?

I put this in wp-content/sunrise.php

<?php
remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 );

function flush_no_new_relic() {
	$levels = ob_get_level();
	for ( $i = 0; $i < $levels - 1; $i++ )
		ob_end_flush();
}

add_action( 'shutdown', 'flush_no_new_relic', 1, 0 );

This preserves New Relic’s final output buffer. An esoteric error, but something to be aware of if you are monitoring WordPress with New Relic.

OS X 10.8 Mountain Lion Preview + MacPorts

I love having access to software early, so I bought a Mac Developer Program membership last year for 100 bones and subsequently never really used it. I got an invite last week to download the Developer Preview of Mountain Lion, the forthcoming OS X upgrade, so I did.

Note: as soon as you upgrade to Mountain Lion, you need to install the preview of Xcode 4.4. After doing that, you have to install Xcode command line tools, which doesn’t happen automatically. If you don’t follow this extra step, you won’t have anything at your disposal in Terminal – make, svn, etc.

Installing pre-release software is dangerous and will break things all over the place. I’m going to tell you what breaks (that I know of so far) and how to fix it (if i know).

Chrome

I’m not going to pretend like I know why, but Chrome runs like shit so far in Mountain Lion. Might have something to do with Darwin 11 vs 12, might not. However, Safari is NOT broken and runs faster and smoother than I have ever seen it. I have a strong affinity for Chrome so I may not switch back yet, but man, it is fast.

Little Snitch

If you have pirated software and you want to block all attempts at activation pings in a managed way, you probably have a program like Little Snitch helping you since it allows you to block all internet access to selected programs. I use it because I have Adobe Master Collection installed and I, not surprisingly, didn’t pay the thousands of dollars that it costs. Little Snitch doesn’t work because it sniffs the OS version (10.8) and deems itself incompatible.

MacPorts – specific ports

This one’s a doozy. If you run port upgrade outdated, every one of your MacPorts will be up for upgrade because they were previously compiled on Darwin 11, not Darwin 12 which ships with OS X 10.8. Annoying, and time-consuming, but not a deal-breaker… until you get to any ports which require libxml2, which is “most.”

Because some of your ports will bail on error, you need to upgrade ports individually (get the list using port outdated) using port upgrade fontforge or whatever.

Once you have upgraded every port that doesn’t die on error, you will realize you have a ton of ports that are in limbo, namely, php5 and all php5-* extensions, X11 and all libraries, and postgresql90. They all have libxml2 as a dependency which no worky. The error is due to locale in reinplace which is not fixed in the distributed version of MacPorts but is fixed in SVN trunk of MacPorts project: http://trac.macports.org/browser/trunk/base/src/port1.0/portutil.tcl?rev=89839.

So, if we want to fix our ports, we need to run trunk of MacPorts while running the dev version of Mountain Lion.

mkdir -p /opt/mports
cd /opt/mports
svn checkout https://svn.macports.org/repository/macports/trunk
cd /opt/mports/trunk/base
./configure --enable-readline
make
sudo make install
make distclean

port edit libxml2
--- and change "reinplace" to "reinplace -locale C"

At this point, you need to go back to individually upgrading ports, but you’ll find that most of them get upgraded as dependencies of other ports, so it won’t take as long as before.

Ports that are still broken after this for me: ffmpeg – due to failure of libvpx port (VP8 codec), postgresql90, and the PHP PostgreSQL extension (php5-postgresql). I’m sure there are many others.

Mountain Lion is pretty cool and worth upgrading to if you have the option. Once you upgrade, you can’t go back to Lion without having previously cloned your hard drive, etc, so be careful and be willing to get your hands dirty when things don’t work.

I installed Mountain Lion on my laptop, which I have been using less and less for programming lately, but I often need to run local PHP, so I had no choice but to figure all of this out.