A few notes on bbPress

I have been spending a lot of time off and on the past week creating a bbPress (message boards, or “forum software,” or “bulletin board”) theme for eMusic. Theming is fun because you get to touch almost every feature of the product, bbPress and/or WordPress. In the midst of this, I’ve been discovering how bbPress does things and making some modifications along the way. Those mods may only exist in eMusic and never make it into bbPress, but I have shared ideas with JJJ so I figured I would share them here as well for anyone to check out and supply feedback.

Queries within Queries with Queries

bbPress abstracts almost everything. For most WordPress-y things, there is a bbPress-y thing. One thing that will immediately confuse and potentially wreak havoc is the way “topics” are queried. Because the default WordPress query is the page or custom post type you are on, the topics for a forum are queried using a second mechanism: bbp_has_topics( ), which upon success, returns a WP_Query object.

That’s all well and good, but at any given time, there is only 1 bbPress query and no hardened reference to the original query. If you are using the default theme or just modifying its presentation, you probably don’t care. If you are implementing a design that has multiple “topics” queries, you are kinda up shit’s creek unless you roll your own code. The template tags / functions provided by bbPress will work to an extent, but they will also shift bbPress’s entire context every time you call bbp_has_topics( ).

The solution espoused to me was to roll my own WP_Query objects to replicate what bbPress is doing in the background, but I don’t want to do that. I want to use bbPress functions and fix whatever context is set along the way when necessary. WordPress maintains state and stores the main query by using $wp_the_query and $wp_query, for the main query and then the current query, respectively. bbPress clobbers $bbp->topic_query every time bbp_has_topics( ) is run. bbPress is treating bbp_has_topics( ) like query_posts( ) in WordPress, not like new WP_Query( ). Ask Nacin how he feels about query_posts( ).

bbPress makes an attempt to provide query context by providing the function bbp_set_query_name( $name )

Guess what it does internally… basically nothing. So I wanted to fix this, here’s some code I am using:

global $emusic_the_bbp_query;
$emusic_the_bbp_query = array();
function emusic_set_bbp_query( $name = 'main', $params = array() ) {
    global $emusic_the_bbp_query;
    $bbp = bbpress();

    bbp_set_query_name( $name );

    if ( !empty( $bbp->topic_query ) && empty( $emusic_the_bbp_query ) )
        $emusic_the_bbp_query['main'] = $bbp->topic_query;

    if ( !empty( $name ) && isset( $emusic_the_bbp_query[$name] ) ) {
        $bbp->topic_query = $emusic_the_bbp_query[$name];
        return $bbp->topic_query;
    }

    if ( !empty( $params ) ) {
        bbp_has_topics( $params );
        $emusic_the_bbp_query[$name] = $bbp->topic_query;
        return $bbp->topic_query;
    }
}

function emusic_reset_bbp_query() {
    global $emusic_the_bbp_query;
    $bbp = bbpress();
    $bbp->topic_query = $emusic_the_bbp_query['main'];
    bbp_set_query_name();
}

My code does a few things:

  • Sets $emusic_the_bbp_query whenever you start to change context the first time
  • Always allows you to retrieve the default context when you are done with a sub-query for topics
  • Non-persistently caches topic queries by name

So where’s my use case? I want to show a “snapshot” of your subscribed-to topics, favorite topics, popular topics, and super-sticky topics in the sidebar on every page, and I want to reuse theme code (template parts) to do so. Imagine calling query_posts( ) 5 times in a template on the WordPress side…

Could I have accomplished this with WP_Query? Yeah, but… I want to use the bbPress stuff. So here’s what I do:

<?php if ( is_user_logged_in() ):   ?>
<div class="meta-block">
    <h3>Your Topics</h3>
    <span class="double-line-narrow"></span>
    <?php
    if ( bbp_is_subscriptions_active() ) : ?>
        <h4 class="sub-head">
            Subscribed
            <a class="aux" href="<?php bbp_user_profile_url( get_current_user_id() ) ?>">view all</a>
        </h4>
        <span class="double-line-narrow"></span>
        <?php
        $subscriptions = bbp_get_user_subscribed_topic_ids( get_current_user_id() );
        if ( !empty( $subscriptions ) ):
            emusic_set_bbp_query( 'bbp_user_profile_subscriptions', array( 'post__in' => $subscriptions, 'posts_per_page' => 3 ) );

            while ( bbp_topics() ) : bbp_the_topic();

                bbp_get_template_part( 'loop' , 'single-sidebar-topic' );

            endwhile;
        else:
            printf( '<p>%s</p>', __( 'You haven't subscribed to any posts.' ) );
        endif;

        emusic_reset_bbp_query();

    endif; ?>

    <h4 class="sub-head">
        Favorites
        <a class="aux" href="<?php bbp_user_profile_url( get_current_user_id() ) ?>">view all</a>
    </h4>
    <span class="double-line-narrow"></span>
    <?php
    $favorites = bbp_get_user_favorites_topic_ids( get_current_user_id() );
    if ( !empty( $favorites ) ):
        emusic_set_bbp_query( 'bbp_user_profile_favorites', array( 'post__in' => $favorites, 'posts_per_page' => 3 ) );

        while ( bbp_topics() ) : bbp_the_topic();

            bbp_get_template_part( 'loop' , 'single-sidebar-topic' );

        endwhile;
    else:
        printf( '<p>%s</p>', __( 'You haven't favorited any posts.' ) );
    endif;

    emusic_reset_bbp_query();
?>
</div>
<?php endif ?>

<?php
$super_stickies = get_option( '_bbp_super_sticky_topics', array() );
if ( !empty( $super_stickies ) ): ?>
    <div class="meta-block">
        <h3>Featured Discussions</h3>
        <span class="double-line-narrow"></span>
        <?php
        emusic_set_bbp_query( 'bbp_super_stickies', array( 'post__in' => $super_stickies, 'posts_per_page' => 4 ) );

        while ( bbp_topics() ) : bbp_the_topic();

            bbp_get_template_part( 'loop' , 'single-sidebar-topic' );

        endwhile;
        ?>
    </div>
<?php endif ?>

Meta Queries like whoa

First of all, JJJ has done an awesome job making bbPress a plugin, and it is super sweet how seamlessly it integrates with everything else in WordPress. The two areas I would like to help make improvements in are 1) cache and non-persistent cache 2) Meta Query performance in WordPress as a whole. bbPress makes Meta Queries like (holy shit) whoa. For 99% of installs, who cares. For us, my current dataset is already 100s of 1000s of posts and 2-3 million rows of postmeta. A meta query basically says:

  • I need stuff from the posts table
  • I need stuff joined from the postmeta table
  • I have indexed columns in both
  • Fuck that noise, let’s join on an unindexable LONGTEXT column and pray for mercy

I wish meta_query never existed in WordPress. But it does, and bbPress sings its song throughout. I have a few ideas for reducing number of queries made and also splitting the query into 2, so that PHP can compare integers instead of trying to sort them as text in MySQL. I have done a lot of performance testing on meta_queries, and I actually ditched them in a few places for WP_Query because they do not scale out of the box.

But I’m not just gonna whine about it, I’m gonna try to contribute and I’ll report back when I do.