Justin Tadlock

Post Formats: Chat

This is the first post in a series of handling post formats for theme developers. Throughout the series, I plan to offer solutions for more standardization between themes for handling post formats. This series will not be a complete guide on using this feature, but I hope it at least gives theme developers some neat ideas on implementing post formats in a portable fashion.

In this first post of a series on handling post formats for theme developers, I’d like to present to you an option for formatting the “chat” post format.

The chat post format

Since all WordPress post formats are a part of a standard, we should look at WordPress.org’s definition for the “chat” post format:

chat - A chat transcript, like so:

John: foo
Mary: bar
John: foo 2

That’s pretty simple. As theme developers, we should expect the chat text to be entered into the post content editor in that manner.

The forming of a solution

David Chandra started an interesting discussion on the Theme Hybrid support forums that led to an awesome collaboration of code.

Before we really got started on the code, there was one major feature I wanted to see handled before calling it a “solution”: We must allow more than two speakers. Most of the solutions I’d seen for chats only handled two speakers (for example, alternating paragraph colors with CSS). I wanted to be able to have 100s of speakers, each with their own unique classes for styling in a theme. It makes sense, right? Not all chats are between two speakers.

David went to work hashing out a solution. Honestly, he did all the dirty work. I just cleaned it up and put it in a pretty package.

The final output

The following is a screenshot of a demo I put together for this feature with a little styling thrown in. As you can see, it has multiple speakers, each with a custom color to differentiate them.

Screenshot of a chat post in WordPress

The code

The following is the final PHP code we came up with for copying to a theme’s functions.php file.

/* Filter the content of chat posts. */
add_filter( 'the_content', 'my_format_chat_content' );

/* Auto-add paragraphs to the chat text. */
add_filter( 'my_post_format_chat_text', 'wpautop' );

/**
 * This function filters the post content when viewing a post with the "chat" post format.  It formats the
 * content with structured HTML markup to make it easy for theme developers to style chat posts.  The
 * advantage of this solution is that it allows for more than two speakers (like most solutions).  You can
 * have 100s of speakers in your chat post, each with their own, unique classes for styling.
 *
 * @author David Chandra
 * @link http://www.turtlepod.org
 * @author Justin Tadlock
 * @link http://justintadlock.com
 * @copyright Copyright (c) 2012
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * @link http://justintadlock.com/archives/2012/08/21/post-formats-chat
 *
 * @global array $_post_format_chat_ids An array of IDs for the chat rows based on the author.
 * @param string $content The content of the post.
 * @return string $chat_output The formatted content of the post.
 */
function my_format_chat_content( $content ) {
    global $_post_format_chat_ids;

    /* If this is not a 'chat' post, return the content. */
    if ( !has_post_format( 'chat' ) )
        return $content;

    /* Set the global variable of speaker IDs to a new, empty array for this chat. */
    $_post_format_chat_ids = array();

    /* Allow the separator (separator for speaker/text) to be filtered. */
    $separator = apply_filters( 'my_post_format_chat_separator', ':' );

    /* Open the chat transcript div and give it a unique ID based on the post ID. */
    $chat_output = "\n\t\t\t" . '<div id="chat-transcript-' . esc_attr( get_the_ID() ) . '" class="chat-transcript">';

    /* Split the content to get individual chat rows. */
    $chat_rows = preg_split( "/(\r?\n)+|(<br\s*\/?>\s*)+/", $content );

    /* Loop through each row and format the output. */
    foreach ( $chat_rows as $chat_row ) {

        /* If a speaker is found, create a new chat row with speaker and text. */
        if ( strpos( $chat_row, $separator ) ) {

            /* Split the chat row into author/text. */
            $chat_row_split = explode( $separator, trim( $chat_row ), 2 );

            /* Get the chat author and strip tags. */
            $chat_author = strip_tags( trim( $chat_row_split[0] ) );

            /* Get the chat text. */
            $chat_text = trim( $chat_row_split[1] );

            /* Get the chat row ID (based on chat author) to give a specific class to each row for styling. */
            $speaker_id = my_format_chat_row_id( $chat_author );

            /* Open the chat row. */
            $chat_output .= "\n\t\t\t\t" . '<div class="chat-row ' . sanitize_html_class( "chat-speaker-{$speaker_id}" ) . '">';

            /* Add the chat row author. */
            $chat_output .= "\n\t\t\t\t\t" . '<div class="chat-author ' . sanitize_html_class( strtolower( "chat-author-{$chat_author}" ) ) . ' vcard"><cite class="fn">' . apply_filters( 'my_post_format_chat_author', $chat_author, $speaker_id ) . '</cite>' . $separator . '</div>';

            /* Add the chat row text. */
            $chat_output .= "\n\t\t\t\t\t" . '<div class="chat-text">' . str_replace( array( "\r", "\n", "\t" ), '', apply_filters( 'my_post_format_chat_text', $chat_text, $chat_author, $speaker_id ) ) . '</div>';

            /* Close the chat row. */
            $chat_output .= "\n\t\t\t\t" . '</div><!-- .chat-row -->';
        }

        /**
         * If no author is found, assume this is a separate paragraph of text that belongs to the
         * previous speaker and label it as such, but let's still create a new row.
         */
        else {

            /* Make sure we have text. */
            if ( !empty( $chat_row ) ) {

                /* Open the chat row. */
                $chat_output .= "\n\t\t\t\t" . '<div class="chat-row ' . sanitize_html_class( "chat-speaker-{$speaker_id}" ) . '">';

                /* Don't add a chat row author.  The label for the previous row should suffice. */

                /* Add the chat row text. */
                $chat_output .= "\n\t\t\t\t\t" . '<div class="chat-text">' . str_replace( array( "\r", "\n", "\t" ), '', apply_filters( 'my_post_format_chat_text', $chat_row, $chat_author, $speaker_id ) ) . '</div>';

                /* Close the chat row. */
                $chat_output .= "\n\t\t\t</div><!-- .chat-row -->";
            }
        }
    }

    /* Close the chat transcript div. */
    $chat_output .= "\n\t\t\t</div><!-- .chat-transcript -->\n";

    /* Return the chat content and apply filters for developers. */
    return apply_filters( 'my_post_format_chat_content', $chat_output );
}

/**
 * This function returns an ID based on the provided chat author name.  It keeps these IDs in a global
 * array and makes sure we have a unique set of IDs.  The purpose of this function is to provide an "ID"
 * that will be used in an HTML class for individual chat rows so they can be styled.  So, speaker "John"
 * will always have the same class each time he speaks.  And, speaker "Mary" will have a different class
 * from "John" but will have the same class each time she speaks.
 *
 * @author David Chandra
 * @link http://www.turtlepod.org
 * @author Justin Tadlock
 * @link http://justintadlock.com
 * @copyright Copyright (c) 2012
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * @link http://justintadlock.com/archives/2012/08/21/post-formats-chat
 *
 * @global array $_post_format_chat_ids An array of IDs for the chat rows based on the author.
 * @param string $chat_author Author of the current chat row.
 * @return int The ID for the chat row based on the author.
 */
function my_format_chat_row_id( $chat_author ) {
    global $_post_format_chat_ids;

    /* Let's sanitize the chat author to avoid craziness and differences like "John" and "john". */
    $chat_author = strtolower( strip_tags( $chat_author ) );

    /* Add the chat author to the array. */
    $_post_format_chat_ids[] = $chat_author;

    /* Make sure the array only holds unique values. */
    $_post_format_chat_ids = array_unique( $_post_format_chat_ids );

    /* Return the array key for the chat author and add "1" to avoid an ID of "0". */
    return absint( array_search( $chat_author, $_post_format_chat_ids ) ) + 1;
}

Feel free to break this and build from it. If it’s not a complete solution for you, it should at least be a good starting point.

How to add text to chat posts

Following the example from WordPress.org, you’d enter your chat transcript directly in the post content editor like so:

John: foo

Mary: bar This is an example of a WordPress page, you could edit this to put information about yourself or your site so readers know where you are coming from.

John: foo 2 What: is: this:

Smith: Aye, matey!

Joe: Huh?

Mary: bar 2

You can also post it without the spaces between each speaker. I just prefer it the above way for readability.

How chat posts are formatted

Given the above example, you’d get the following output in your HTML source code.

<div id="chat-transcript-562" class="chat-transcript">
    <div class="chat-row chat-speaker-1">
        <div class="chat-author chat-author-john vcard"><cite class="fn">John</cite>:</div>
        <div class="chat-text"><p>foo</p></div>
    </div><!-- .chat-row -->
    <div class="chat-row chat-speaker-2">
        <div class="chat-author chat-author-mary vcard"><cite class="fn">Mary</cite>:</div>
        <div class="chat-text"><p>bar This is an example of a WordPress page, you could edit this to put information about yourself or your site so readers know where you are coming from.</p></div>
    </div><!-- .chat-row -->
    <div class="chat-row chat-speaker-1">
        <div class="chat-author chat-author-john vcard"><cite class="fn">John</cite>:</div>
        <div class="chat-text"><p>foo 2 What: is: this:</p></div>
    </div><!-- .chat-row -->
    <div class="chat-row chat-speaker-3">
        <div class="chat-author chat-author-smith vcard"><cite class="fn">Smith</cite>:</div>
        <div class="chat-text"><p>Aye, matey!</p></div>
    </div><!-- .chat-row -->
    <div class="chat-row chat-speaker-4">
        <div class="chat-author chat-author-joe vcard"><cite class="fn">Joe</cite>:</div>
        <div class="chat-text"><p>Huh?</p></div>
    </div><!-- .chat-row -->
    <div class="chat-row chat-speaker-2">
        <div class="chat-author chat-author-mary vcard"><cite class="fn">Mary</cite>:</div>
        <div class="chat-text"><p>bar 2</p></div>
    </div><!-- .chat-row -->
</div><!-- .chat-transcript -->

Styling chats

As you can see from the previous section, the source code gives you quite a few ways to style things:

/* Wrapper for entire chat transcript. */
.chat-transcript { }

/* Individual section of the chat. */
.chat-row { }

/* Individual speaker in the chat. */
.chat-speaker-xxx { }

/* Chat author. */
.chat-author { }
.chat-author cite { }

/* Chat text. */
.chat-text { }

There are some other useful classes and IDs to use from the code, but these would be the most common for styling.

Actually use chat transcripts

One of the post formats that theme developers tend to shy away from is the chat format. There’ve been no great solutions in the past, so I hope this helps in implementing something that’s useable for you in theme development. Please feel free to tinker with this code, break it, and send me fixes/enhancements you’d like to see thrown in.

Also, be sure to thank David Chandra for getting me started on this. And, check out his theme site, Genbu Theme (he’s redeveloping the theme to use my Hybrid Core framework).