ron rothman.ron rothman
selectively conformist

WordPress: Fix Category Exclusions in next_post_link() / previous_post_link()

(UPDATE: Word on the street (see comment 27, below) is that this bug has been fixed in WordPress 2.1.)

I recently came across a known bug in WordPress 1.5.2 (well, at least it’s known to users; I can only assume that the dev community is also aware of it). Just wanted to post my fix here, in case anyone else finds it helpful.

The Bug

Category exclusion is broken. The problem is that the category exclusion parameter (the 4th param) of next_post_link() and previous_post_link() is effectively ignored.

(next_post_link() and previous_post_link() are the new versions of the now-deprecated next_post() and previous_post().)

A quick scan through the WordPress code indicates that the “category exclude” feature isn’t even implemented (at least not in a serious way) for next/previous_post_link. I haven’t spent much time perusing the WordPress code base, but from what I can tell, it seems that the relevant sections of these functions were never upgraded from an earlier WP version–one with a different schema; in particular, one with a different method of storing post-category relationships.

The FixHack

Here is how I fixed it in my installation (see warnings below before trying this at home):

1. Edit wp-includes/template-functions-links.php. At approximately line 253 (in function get_previous_post) [line 221 in WP1.5], you’ll see this:

    $sql_exclude_cats = '';
    if (!empty($excluded_categories)) {
        $blah = explode('and', $excluded_categories);
        foreach($blah as $category) {
            $category = intval($category);
            $sql_exclude_cats .= " AND post_category != $category";
        }
    }

2. Replace that block with the following (beware of copy/paste errors, especially with respect to line breaks):

    $sql_exclude_cats = '';
    if (!empty($excluded_categories)) {
### ronr hack BEGIN ###
        # find ids of all posts in excluded cats
        $query =
            "SELECT post_id
             FROM $wpdb->post2cat
             WHERE category_id IN ("
             . str_replace(',', ',', $excluded_categories)
             . ')';
        $excluded_post_rows = $wpdb->get_results($query);

        # build IN clause from those ids
        if (count($excluded_post_rows) > 0) {
            $sql_exclude_cats =
                ' AND ID NOT IN ('
                . intval($excluded_post_rows[0]->post_id);
            for ($i = 1; $i < (count($excluded_post_rows)); $i++) {
                $sql_exclude_cats .=
                    ','
                    . intval($excluded_post_rows[$i]->post_id);
            }
            $sql_exclude_cats .= " )";
        }
### ronr hack END ###
    }

(Note: instead of copy/pasting the above code, you can download it here.)

3. Now, make the same replacement in the corresponding block in get_next_post, which is a little further down in the file.

Disclaimers

Presumably, this bug will be fixed properly in an upcoming release, so now that I’ve got it working (albeit jury-rigged), I’m not going to spend any more time on it. If it doesn’t get fixed soon, perhaps I’ll clean it up and fold it into a plug-in (perhaps even as Asides plug-in).

My code does no error checking, and it assumes that the value of the exclude_categories parameter is not screwy. It’s provided as-is. I would never release code like this if it wasn’t intended to be used solely by me. If I wrote code like this at work, I’d deserve to be fired. Constructive suggestions for improvements are always welcome, but please don’t bitch to me about how it didn’t work for you, broke your blog, crashed your server, ruined your marriage, etc. Capisce?

Note too that I’ve changed the original interface; instead of separating excluded category ids with the word “and” (odd), it now expects a comma (not odd).

Anyway, good luck. And if you happen to find this useful, please let me know.

33 Responses to “WordPress: Fix Category Exclusions in next_post_link() / previous_post_link()” [Leave yours »]

  1. emanuel ferritis [subscribed to comments] said:

    dr. ron, thank you so much for posting this fix. i tell you, if i had i nickel for every time the category exclusion parameter (4th param) of next_post_link() and previous_post_link() is effectively ignored… i would have five cents. fast programming related question - do i put your fix in before or after the “if/then” statement?

    1
  2. Ron [subscribed to comments] [author of post] said:

    X-( why you little…

    2
  3. Sara [subscribed to comments] said:

    Why does the sql exclude cats? I think that’s just plain mean.

    3
  4. Arthur [subscribed to comments] said:

    THANK YOU!!!

    Ron, thank you so much for this solution! I just changed my template-function-links.php file, and it works perfectly! I’ve been looking for a solution for this for several weeks now, and I had just about given up when I saw your posting at wordpress.org/support. This is exactly what I need, and I very much appreciate your implementing it. :)

    One point about the code in html that you have posted above: the last line that reads $sql_exclude_cats .= ‘)’; actually uses curly single quotes, ie: the html code shows the entities ‘ and ’ which may confuse those who are cutting and pasting from this page.

    Ron, thank you again. I’m an HTML/JavaScript sort of guy who is completely lost in the world of PHP and SQL. Please let me know if there is anything I can ever do for you! :)

    – Arthur

    4
  5. Ron [subscribed to comments] [author of post] said:

    arthur,

    thanks for the comment–i’m so happy that this was actually helpful to someone! (and now maybe my wisecracking, non-techie friends will keep their traps shut :-B ).

    and extra thanks for pointing out the cut/paste problem! i’ve fixed the code snippet. (though i had to settle for inferior double quotes since i couldn’t quickly figure out how to escape the php code in WordPress. :) )

    enjoy!

    5
  6. Ron [subscribed to comments] [author of post] said:

    Sara said:

    Why does the sql exclude cats? I think that’s just plain mean.

    even though i laughed out loud at your comment, you’re still in trouble. (along with that “Ferritis” character.)

    6
  7. Sara [subscribed to comments] said:

    meow. =^..^=

    7
  8. a4g [subscribed to comments] said:

    Ron,

    Can’t get this to work. I’ve tried the version above and the one on the wordpress forum — I get this error:

    Parse error: parse error, unexpected ‘;’, expecting T_PAAMAYIM_NEKUDOTAYIM or ‘(’ in /home/jump1932/public_html/wp/wp-includes/template-functions-links.php on line 260

    After replacing all the & quot; and replacing

    $sql_exclude_cats =
    ' AND ID NOT IN ('

    with

    $sql_exclude_cats .=
    ' AND ID NOT IN ('

    I got the site to load without error, but there still seems to be no functionality. I’m trying to hide my category “9″. Note: I did put the appropriate declarations in the next_post_link().

    Any ideas?

    8
  9. Ron [subscribed to comments] [author of post] said:

    a4g,

    i’ll try to help… hopefully we can get it working for you.

    a few things:

    1. oops, sorry about the &quot;’s. i’ve edited the entry and fixed them above. (by way of explanation, they were there because of a code-formatting plug-in that i installed after i wrote this entry.)

    2. i think your change of “=” to “.=” was an error (albeit a harmless one). it should in fact be a straight assignment there, not a concatenation.

    would you mind trying one more time to copy & paste the (fixed) code into your installation from scratch?

    if you still see the problem, write back and include how you’re calling next_post_link(). also, let me know if it doesn’t work for both next_post_link and prev_post_link, since there’s the outside possibility (because of all the manual editing required) that it ends up working for one of them but not the other.

    (and if the problem is solved, then let me know that too. :) )

    p.s., i find it odd that your php installation is giving you error messages in hebrew (PAAMAYIM NEKUDOTAYIM). :-/

    9
  10. a4g [subscribed to comments] said:

    Working!!!!

    Okay, now I’ll heap all the richly deserved praise upon you, Ron. AWESOME!

    True, my “=” -> “.=” was pure desperation — but I did manage to hunt down the code problem:

    when cutting & pasting from the above box, the

    $sql_exclude_cats .= ',' . intval($excluded_post_rows[$i]->post_
    id);

    (which is forced onto two lines) inserts a CRLF between “_” and “id”, which understandably [m]ucks up the works. By backspacing the “id” back onto the “post_”, everything started working hunky-dory.

    As to the hebrew error message, I’ve always been disgusted by accusations of some shadowy international zionist conspiracy, but now I’m not so sure… ;-).

    Thanks again, Ron.

    10
  11. Ron [subscribed to comments] [author of post] said:

    excellent–glad to hear it!! (the part about your code working–not the part about the zionist conspiracy. :D )

    p.s., i’m going to add a note beneath the code snippet to help people avoid the copy&paste trouble you just had.

    11
  12. thanks! I did this fix so that my single page “next” and “previous” link wouldn’t grab stuff from my events calendar… I’m using a modified kubrick theme which calls the “deprecated” previous_post and next_post tags… I was all set to go replace them with the new tags, but a quick test to make sure my application of your fix didn’t break anything revealed that the exclusion built into those tags now works, regardless of the codex statement that it’s an “archaic parameter … does not function in 1.5.”

    12
  13. Ron [subscribed to comments] [author of post] said:

    thanks john; good to know.

    to restate what i think you said: you found that the deprecated functions next_post() and previous_post() both do correctly exclude categories, so if someone is using those functions then they need not apply the hack i outlined above. (and just so people know, it looks from your blog like you’re talking about WP version 1.5.2 here.)

    this info will surely help some people out, but i would also advise some caution. if the codex states that the parameter is “archaic,” then its behaviour–across future releases–is tenuous at best (and undefined at worst).

    in any case, i hope this is fixed in 1.6. i’ll be happy to retire this blog entry. :)

    13
  14. the exclusion wasn’t working until I applied your hack. I was just mentioning that your hack seems to fix the old deprecated tags as well as the new ones.

    14
  15. Arthur [subscribed to comments] said:

    Sigh…Ron, I don’t suppose you would want to take a crack at upgrading this to work in WP 2.0, where the cat params still don’t seem to work?

    – Your ever greatful WordPress fan, Arthur.

    15
  16. Arthur [subscribed to comments] said:

    Whoops! Nevermind!!! :)

    Your fix still works with version 2.0, thanks again. :)

    16
  17. Ron [subscribed to comments] [author of post] said:

    thanks for the info–i’m glad to know it works in 2.0! (i haven’t yet upgraded from 1.5.1.)

    btw, it looks like someone else noticed that it’s still broken in 2.0 (and fixed it):

    http://wordpress.org/support/topic/44031?replies=11

    so, pretty soon, my hack will become obsolete.

    17
  18. This works in 2.0.1, also. Keep in mind what a4g pointed out up there; make sure that post_id is on one line, with no line break in between them, and this should work like so:

    next_post_link('%link', '%title', false, 2)

    Change that number to the category ID of whatever category (or categories?) you want to exclude.

    Thanks for this. It’s much easier to follow than the current trac solution, which only suggests changes to get_next_post and next_post_link. Did I misunderstand that trac file? I like that it tells me which lines to delete and include, but this solution just seems to make more sense because we alter both the next and previous functions.

    18
  19. Ron [subscribed to comments] [author of post] said:

    hi todd,

    i’m glad you found this useful. i suspect that the next WP release will (finally!) fix this problem.

    i’m not really familiar with the trac solution, but it looks to me like it’s meant to be used with the “patch” command to automatically apply the changes to your files.

    19
  20. David Weingart said:

    Just another note of thanks - this fixed a big problem I was having with a custom WordPress setup. I’m running 2.0.1

    It looks like this bug has been fixed for the next release, so it’s safe to apply your hack without upgrade worries.

    20
  21. Thanks a lot! I’m using Wordpress 2.0.4 an this issui still seems not to be fixed. Applied your hack just by copy & past -ing it, and everything works great! Now I’ll have a look at the code and try to find out what you did actuall :-) kind regards, sofastar

    21
  22. [...] You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your ownsite. [...]

    22
  23. Alif [subscribed to comments] said:

    This is not directly related to the post, but I wonder if you have any idea:

    Whenever I post PHP code (all properly escaped into HTML entities) within a post in Wordpress 2.0.4 I get Error 403 on post.php!

    Although it is my first time, it seems to me that people have been posting PHP code snippets forever in Wordpress. Do you have any idea why this might be happening?

    23
  24. Ron,

    Thanks for this fix man. It was just what I was looking for.

    Brance

    24
  25. Hi Ron,

    Thanks for the “hack” - it really works like a charm and has been ever since I implemented the Sideblog plugin on top of WP2.03. Would you know whether the faulty core code has been fixed in WP2.1?

    Thanks,
    Oliver

    25
  26. Ron [subscribed to comments] [author of post] said:

    good question. i’m not sure yet; i’m waiting for the bugs to be shaken out before i switch to the 2.1 branch. (but maybe someone else on this thread has installed 2.1 and can tell us?)

    26
  27. I just gave it a shot and it does work fine!! So while your hack helped me out for a long time (again thanks for providing it!) with 2.1 it became obsolete. ;)

    27
  28. Ron [subscribed to comments] [author of post] said:

    Excellent! I’m happy to [finally!] be obsolete. :)

    28
  29. Stewart Johnson [subscribed to comments] said:

    I’m trying to get this feature (the fourth parameter to get_next_post) to work in WP 2.1.3 and it still seems to be broken. I looked at the WP code (now in wp-includes/link-template.php) and they’ve changed it, but it still doesn’t work for me.

    If I use

    get_next_post(’%link’, ‘%title’, FALSE, ‘1 and 2′);

    then posts in category 2 are ignored, but not those in 1. It’s quite annoying!

    I looked at the code, but at cursory inspection I can’t see what’s wrong with it. Have you managed to get this feature to work in v2.1.3?

    29
  30. Stewart Johnson [subscribed to comments] said:

    Wait, I just figured out what the error is. For WP 2.1.3 the code in get_next_post and get_previous_post hasn’t been tested, but most of the mistakes are harmless (for example $sql_exclude_cats is declared and never used).

    There’s a one line fix for WP2.1.3 to get this code to work. In both functions, this line (lines 294 and 331 in link-template.php):

    $sql_cat_ids = " OR pc.category_ID = '$category'";

    should be changed to

    $sql_cat_ids .= " OR pc.category_ID = '$category'";

    (changed = to .=)

    Where do I submit WP bug reports & fixes. Can I submit a patch myself?

    30
  31. Stewart Johnson [subscribed to comments] said:

    Sorry to spam you. :">

    I figured out how to submit bug reports:

    http://trac.wordpress.org/ticket/4304

    31
  32. Andy [subscribed to comments] said:

    Thanks for this, Stewart. The problem subsists in 2.2.1. Your fix works for me but it’s now line numbers 339 and 376 that need to be changed.

    32
  33. Steve M [subscribed to comments] said:

    Personally, I had a whole array of problems with this and I only had 4 categories. The solution is still dodgy in Wordpress 2.3, but I got it working perfectly by:


    < ?php next_post_link($format='%link »', $link='%title', $in_same_cat
    = false, $excluded_categories = 'X and X and X' ); ?>

    It’s explained in better detail on the wordpress codex here

    As you will no doubt notice, the third parameter ‘$in_same_cat’ is bogus and doesn’t work if set to ‘true.’

    33

Leave a Reply

Comment formatting tips are available.


Your comment will appear on the site once it's approved. (Please read the COMMENT POLICY before posting.)