31. January 2007 · Comments Off on Hierarchical Navigation in WordPress 2.1 · Categories: WordPress · Tags: , , , , , , ,

This is a long post about how I hacked a hierarchical navigation system into WordPress 2.1. If you’re just here for the code, head to the end.

The Vision
I wanted to convert a website that I help maintain from flat files to WordPress. This is a simple website, no dynamic content, just lots and lots of pages arranged in a hierarchical format. This maps very well to WordPress’ categories, so it shouldn’t be too hard, right? Ha.

The sidebar should show a list of all top-level pages, which we’re calling categories in WordPress terminology. Each of these categories will need a post or WordPress Page (which I’m going to capitalize here to differentiate it from a “page” which could be a post or Page) that serves as “what the user sees when they click on this.” So if I have three main sections of the website, each of those has some sort of title page that describes the section and might list the children. Typically WordPress category pages just show the most recent posts in that category, but we can easily change that if we decided to use them (I didn’t).

If we’ve stumbled into a child category, the navigation should show a breadcrumb trail from the correct parent category to the current child (there might be a few parent/child categories in between). Also, if the category we’re in has any child categories, those should show up. Finally, the navigation should show all the other posts in the current category.

I’m envisioning a simple, hierarchical navigation where you can see all the parents, how you got to your current category, and all the other pages and children in that category. The guts should be dynamic enough that if a person creates a new category (including header page) and puts a post in that category, it should all just magically appear.

You can see the flat-file implementation of this navigation system at the Center for Effective Discipline. This is the site I want to move to WordPress.

Some Details

My initial thought was to just use categories and so when you click on a navigation page, it takes you to the appropriate category page. The only problem with that is that, for this site, the header pages generally echo the navigation in that they list their child pages. We would have to use full permalinks here because WordPress forces you to put category pages in a different hierarchy then the pages. For example, if you click on the category A’s page, it takes you to /wp/category/a but all of the pages in category A are stored in /wp/a. I want the post authors to just be able to write “new-page-slug/” as the reference, and not the whole permalink. So categories as header pages are out.

Instead we have to use Pages. Unfortunately, Pages aren’t associated with Categories inside WordPress. This can be solved by Yellow Swordfish’s Page Category Organizer plugin, which is what I used. What we have to do is basically map Pages to categories, and have them use the exact same names and (most importantly) slugs as the category they’re emulating. This allows us to look at the category we’re in, find it’s slug and then know that the page we need will be at the same slug, just without the “category” subdirectory.

Logically, we’re not too bad. We just need to generate a list of parent categories, replace these with the appropriate page instead, then list the needed child pages (if any) and finally print out all the pages (and child categories, if there are any) in this category. Easy!

Smokey, my friend, you are entering a world of pain.
The title of this post explicitly says WordPress 2.1 for a reason. The main reason is that with this release the WP team deprecated the old ways of listing categories, and now want you to us wp_list_categories(). No problem, except that this new version (wait for it) doesn’t support the echo parameter. This translates to: we can’t manipulate the output. So we’re hosed, except that wp_dropdown_categories() does support echo. This translates to a lot more junk for us to have to parse out, but it gives us the starting point we need. It only goes downhill from here.

Overview of solution
The code I ended up writing is a over 100 lines long, but here’s what it does in a nutshell (and in order):

  1. Find out what category we’re in. This is more complicated than normal, because we might be on a Page which is category-less in WordPress. This is where the plugin listed above comes in.
  2. If we’re in a child category, create an array that holds the chain of parents up to the root parent category. We’ll need to walk this backwards when we print out those top level parents, so that under the correct one we see it’s child, and then that child’s child and so forth down to our current category. This is the “breadcrumb navigation” part.
  3. Get a list of all the Pages and throw it into an array. We’ll need to cross reference this with our parent categories so that anything that doesn’t have both a Page and a parent category doesn’t show up. This allows us to have Pages that aren’t these special categories, as well as categories for organizational purposes that won’t be displayed in this navigation.
  4. Get the list of parent categories and store this in an array. I exclude categories like “Uncategorized” and some that I know I want hidden. I think the previous step would have taken care of this, but this code was written somewhat organically (re: my planned code looked nothing like this). During this step, also throw child categories into an array so we have the information for the breadcrumb later.
  5. Action time. We want to generate a simple linked list (just the list items right now, the outside ordered or unordered list tags can be done on the page itself) with our navigation links. So spit out a link with each parent category, and a link to the appropriate Page. When we get to the category that’s either our current category, or that category’s root parent, then we go nuts:
    1. If we’re a child category, then walk the ancestry tree down from parent to child to child’s child and put them each as a new unordered list and list item (we’re nesting here).
    2. Then we check for any child categories. We list each of our children, but ignore our children’s children and any progeny below them. If we have any, open one (and just one for this step, no matter how many kids) unordered list and then spit out the appropriate list items.
    3. Then we open up an unordered list if we didn’t do it in the last step, and spit out all the posts for our category. Then close the unordered list.
    4. Finally, close all the unordered lists we opened up for the breadcrumb navigation.

The code
Ok, enough yapping, here it is. I really am a total amateur at WordPress hacking. Same goes for PHP; I probably looked up every single function I used. So I’d love to see a cleaned up version of this. If I wasn’t back in my MBA program (classes resumed in full on Monday, hence my push to finish it before homework starts piling on) I think cleaning this up and making it an actual WP Plugin would rock. If you have suggestions for improvements, or just want to grab the below code and use it for yourself, other people, to actually rewrite as a plugin, etc, please do.

Finally, keep in mind that my permalinks are set up as /%category%/%postname%/. You will need to do the same for this code to have any hope of it working. Also, as stated above, you will need a Page for each category, and the slug for both the category and that Page must match exactly. If you have any questions or comments, feel free to leave me a note.

UPDATE: For your convenience, here’s the actual sidebar.php file. Note I added “.txt” to the end of it, just take that off. The bulk of this file is the hierarchical navigation (I don’t have much else in there save the login on the home page).

<?php
if ($cat) {
$current_categoryID = $cat;
} else {
$current_categoryID = get_page_category();
}
$pathToHome = get_option(‘home’); //the url to get to the root

$thisCat = get_category($current_categoryID);

//If we’re a child, create an array showing our tree ([0] is us, [1] our parent up to [x] a true parent)
if ($thisCat->category_parent != 0) {
$walkCat = $thisCat;
$ancestryCount=0;
while ($walkCat->category_parent != 0) {
$ancestry[$ancestryCount] = $walkCat->category_nicename;
$walkCat = get_category($walkCat->category_parent);
$ancestryCount++;
}
$rootParent = $walkCat->category_nicename;
$ancestry[$ancestryCount] = $rootParent;
}

$listOfPages = wp_list_pages(‘title_li=&echo=0&depth=1’);
preg_match_all(‘|a href=”([^”]+)|’,$listOfPages, $usedPageLinks);
preg_match_all(‘|>([^<]+)|’,$listOfPages, $usedPageNames);
$countPages=0;
while ($usedPageLinks[1][$countPages]) {
$tempPageLink = $usedPageLinks[1][$countPages];
$tempPageName = $usedPageNames[1][$countPages];
$tempPageLink = preg_replace(“|$pathToHome|i”,”,$tempPageLink);
$tempPageLink = preg_replace(‘|^/|’,”,$tempPageLink);
$pageNamesBySlug[$tempPageLink] = $tempPageName; // now we have a list of the parent pages
$countPages++;
}

$pathToCategories = get_category_link(9897); //this will fail if you actually have category #9897 …
$listOfCats = wp_dropdown_categories(‘echo=0&exclude=1, 2, 7&orderby=name’);
preg_match_all(‘/value=”(\d+)”>/’,$listOfCats, $usedCatIDs);
preg_match_all(‘|”>([^<]+)|’,$listOfCats, $usedCatNames);
$countcat=0;
while ($usedCatIDs[1][$countcat]) {
$tempCatID = $usedCatIDs[1][$countcat];
$tempCatLink = get_category_link($tempCatID);
$tempCatName = $usedCatNames[1][$countcat];
$tempCatLink = preg_replace(“|$pathToCategories|i”,”,$tempCatLink);
if ($pageNamesBySlug[$tempCatLink]) {
$parentPages[$tempCatLink] = $tempCatName;
$parentPageIDs[$tempCatLink] = $tempCatID;
$parentPagesOrderBy[$countcat] = $tempCatLink;
} else {
$childPages[$tempCatLink] = $tempCatName; //put the children into their own storage tank
preg_match_all(‘|^[^/]+|’,$tempCatLink, $thisChildParent);
$cppc=0;
while ($childPagesParents[$thisChildParent[0][0] . ‘/’][$cppc]) {
$cppc++;
}
$childPagesParents[$thisChildParent[0][0] . ‘/’][$cppc] = $tempCatLink;
}
$countcat++;
}
foreach ($parentPagesOrderBy as $ppob) {
echo ‘<li><a href=”‘ . $pathToHome . ‘/’ . $ppob . ‘”>’ . $parentPages[$ppob] . ‘</a>’;
if (($rootParent . ‘/’ == $ppob) || ((!$rootParent) && ($parentPageIDs[$ppob] == $current_categoryID))) {
//we’re at in our current category or its root parent
if ($rootParent . ‘/’ == $ppob) {
//oh, we must be a child. first we show our ancestry, working backwords:
$pathToChild = $pathToHome . ‘/’ . $ppob;
$pathToChild = preg_replace(‘|/$|’,”,$pathToChild);
$fullChildName = $ppob;
$fullChildName = preg_replace(‘|/$|’,”,$fullChildName);
$ancestryCount–; //we’ve just taken care of our root parent, so no need to repeat it.
$childCount = $ancestryCount;
while ($ancestryCount >= 0) {
$pathToChild .= ‘/’ . $ancestry[$ancestryCount];
$fullChildName .= ‘/’ . $ancestry[$ancestryCount];
if (!preg_match(‘|/$|’,$pathToChild)) {
$pathToChild .= ‘/’;
}
echo “\n” . ‘<ul><li><a href=”‘ . $pathToChild . ‘”>’ . $childPages[$fullChildName . ‘/’] . ‘</a>’;
$ancestryCount–;
}
}
// check for kids.
if ($childPagesParents[$ppob]) {
$haveAChild=0;
$myNiceName = $ppob;
foreach ($childPagesParents[$ppob] as $oneChild) {
if ($rootParent . ‘/’ == $ppob) { //we’re a child, so we need to make sure these aren’t siblings, us, uncles, etc.
if ($oneChild == $fullChildName . ‘/’) { //if the child is us, then skip it (in case we’re a child-parent)
continue;
} elseif (!preg_match(“|^$fullChildName|”,$oneChild)) { //it’s an aunt, uncle or sibling
continue;
}
$myNiceName = $fullChildName . ‘/’;
}
if (preg_match(“|^$myNiceName([^\/]+)\/(\w+)|”,$oneChild)) { //skip our grandchildren and younger
continue;
}
if (!$haveAChild) { //if this is to be our first child, then create an unordered list
echo “<ul>\n”;
}
echo ‘<li><a href=”‘ . $pathToHome . ‘/’ . $oneChild . ‘”>’ . $childPages[$oneChild] . ‘</a></li>’ . “\n”;
$haveAChild++;
}
}
if (!$haveAChild) { //we’ve had no kids, so start an unordered list
echo “<ul>\n”;
}

//then we show category posts: ?>
<?php
$postlist = get_posts(“numberposts=99&orderby=post_title&order=ASC&category=$current_categoryID”);
foreach ($postlist as $post) : setup_postdata($post); ?>
<li><a href=”<?php the_permalink(); ?>”><?php the_title(); ?></a></li>
<?php endforeach; ?>
</ul>

<?php
if ($rootParent . ‘/’ == $ppob) {
// need to close our unordered ancestry list
while ($childCount >= 0) {
echo ‘</li></ul>’ . “\n”;
$childCount–;
}
}

} //end of showing ancestry & posts
echo ‘</li>’ . “\n”;
}
?>

29. January 2007 · Comments Off on WordPress 2.1 upgrade (or: sorry about the missing links) · Categories: WordPress · Tags: , , ,

Sorry, this blog is going hardcore nerd for a couple of days. I’m in the midst of WordPress hackery so y’all are getting subjected to it. A new cocktail very soon…

In the meantime, the Linky sideroll is back. It took me until today to realize it was gone. It turns out wp_get_links() may or may not have been deprecated in 2.1, but it now works differently and the codex doesn’t seem quite up to date. I had to make sure they all had a category, and then change the call from the old way (no parameters) to:

wp_get_links('category=4&before=<li>&after=</li>');

It’s possible I’m supposed to be using wp_list_bookmarks() instead, but frankly that doesn’t seem to have the options I need.

28. January 2007 · Comments Off on Using a single post as a home page in WordPress · Categories: WordPress · Tags: , , , , ,

UPDATE:
WordPress 2.1 allows you to set an page (not post) as your front page easily. Everything below is still valid if you need a post instead, but given the new built in functionality, using a page is now a preferred option. See here for details.

UPDATE #2:
Ok, so don’t do this. It only works until you have a enough posts that it would have moved your one post off the front page. Since your page may be limited to, say, 10 posts, if you’ve written 10 posts after this the hack below makes WP think it’s posted those ten, so it stops before getting to the post you want. Obviously updating the time-stamp is a workaround, but a silly one.

So I’ve been looking at perhaps moving The Center for Effective Discipline’s website to use WordPress for the back-end. The site is huge, and needs to be moved to either a CMS system or WordPress. I’ve heard about speed and complexity issues with CMS systems, so right now we’re going down the WordPress avenue.

Anyhow, using WordPress 2.1 I wanted to use one post as the homepage. I could have easily just downloaded Semiologic’s Static Front Page Plugin, but then I would have had to use a page. I’m looking at using pages for navigation, so while that’s still an option, I went the post route instead. This requires a few changes.

I ended up basically just creating a post in its own category, and then limiting the homepage to just that category. These changes are probably hackier than you need, but for future reference and in case someone else is trying the same, here’s what I did:

  1. Create a category or use one that only this post will be in. You’ll need the ID for this category (you can see it in Site Admin -> Manage -> Categories). I used “Uncategorized”, which is ID 1, since all articles on this site going forward need to be in an existing category.
  2. Then you need to change the index.php file for whatever theme you’re using. Here is my file, with my additions in bold:

    < ?php get_header(); ?>
    <div id="content" class="narrowcolumn">
    < ?php if ($posts) { ?>
    < ?php foreach ($posts as $post) : start_wp(); ?>
    < ?php if ( (in_category('1')) || !is_home() ) { ?>
    < ?php require(TEMPLATEPATH. "/post.php");?>
    < ?php } ?>
    < ?php endforeach; ?>
    < ?php if(!is_home()) { ?>

    < ?php posts_nav_link(' — ', __('« Previous Page'), __('Next Page »')); ?>

    < ?php } ?>
    < ?php } else { ?>
    (... rest of file)

    This boils down to adding two “IF” statements. The first says “only display posts from category 1 if this is the home page.” The second says, “if we’re on the home page, don’t put navigation aides (like “next post”) even if WordPress thinks it needs them.” That second IF statement is needed because WordPress thinks it’s about to display all your most recent posts, only when it’s about to display them we told it to filter based on the first IF statement. By the time the code gets down to the navigation links, it doesn’t know that we filtered the posts and thinks it has displayed lots and lots. As such, if you’re using this hack to just display one category on the home page, don’t use the second IF statement!

  3. That’s it. Save and upload the new index.php file and you should be good to go.

Another post on how to create a hierarchical navigation system (that shows categories and all of the posts in the category you’re currently in) in the sidebar is coming soon. Try not to dance in the streets with excitement.

First off: my apologies for anyone who caught me in the middle of this today. You probably saw a very broken website for a while.

This post is mostly for my own reference, so unless you’re interested in the intricacies of WordPress, you might want to move on. I decided (after already moving WordPress manually the other day) that I wanted my WordPress installation to be installed using Dreamhost’s one-click install. This would let me easily upgrade it in the future, instead of having to do it all by hand. Unfortunately, that feature only works on installing it to a totally emptydirectory, something one only learns after deleting all the old WordPress installs and telling the installer robot to go ahead. This would have meant nuking my entire website, and then restoring it after the install. That would have taken hours over FTP, and the very thought makes me nervous.

So I reverted back to installing it in the WP subdirectory. However, I wanted to make ardenstone.com pretend to be the new site, so I followed the directions to do so. This works fairly well, but this situation requires the following notes for future upgrades:

  1. If I upgrade in the future, I’ll probably need to copy index.php from WP to the root.
  2. After copying this, modify fourth line to say:
    require('./wp/wp-blog-header.php');

Not too bad, but unfortunately at last the theme I modified and am currently using also needs some modification. I have the sneaking suspicion that this means I can’t easily change themes anymore, which kind of sucks. Anyhow, the other thing to change is the third line in header.php:
require('./wp/wp-blog-header.php');

Good times.

27. January 2007 · Comments Off on Leave me home for a day… · Categories: Linky

So it turns out there’s a website where you can paint a stick figure (or other object, you can really muck about with the canvas) and then it animates it dancing. Brilliant!

Me in scooter gear, dancing better than perhaps I do in real life:

On second thought, it definitely dances better than I do. There are lots of other creations on the website including lots of doodles, a ninja, skeleton and lots of male genitalia. Just so you’re warned.

26. January 2007 · Comments Off on Christy’s Chocolate Martini · Categories: Cocktails · Tags: , , , , , , , ,

Since I’m apparently posting about drinks I’m not a big fan of, I might as well put this one up. I just don’t enjoy very sweet drinks, although I’m not opposed to the occasional White Russian. That being said, lots of fabulous folks like sweet alcohol, and if you’re one of them, this is for you. Although it’s not a real martini, it’s named after the restaurant and bar drinks that inspired it, and in honor of its greatest fan.

Christy’s Chocolate Martini

1 1/2 oz. vanilla vodka
1 oz. Godiva chocolate liqueur
1/2 oz. Bailey’s
1/2 oz. Kahlúa

Shake with ice until quite cold. Strain into a cocktail glass.

It’s that easy, and ridiculously popular. Feel free to add or substitute some Chambord for raspberry flavoring, although I tend to drop the Bailey’s and Kahlúa when I do that. One final note is that I think the Godiva has a poor aftertaste, so it might be worth finding a substitute. Crème de Cacao tends to be a bit thin, but it’s been years since I stocked it so I’ll leave experimentation as an exercise to the reader. Especially since my alcohol is currently relegated to a small space in a relatively small apartment, and new purchases are on an even smaller budget during grad school.

Ok kiddlies, gather ’round the campfire for an exciting yarn about internet links and procrastination.

{sound of crickets}

Shocking. Anyhow, the other day Tavern Wench added me to her list of links. Of course, as soon as she did this, I changed the location of this blog (my sense of timing is a well oiled machine). Now, if you haven’t already visited her, I’d highly recommend you go there now and read a talented story teller recount tales from the bar industry. In addition, you can find a myriad of drink recipes, and easily kill the rest of your day (week?) in a most enjoyable manner.

But we’re here to talk about me. I noticed she’d added the link because WordPress has this feature where it notes incoming links. This is total nerd gold, so of course I poked around to find that it’s using Technorati. Now, Technorati’s been around a while and I’ve heard a few folks who really love it. Mainly it seems more status symbol than useful site, but perhaps that’s just because I haven’t really explored it. Anyhow, I go and search for “Ardenstone” and get nothing. After some mucking about, you need at least “ardenstone.com” to get that one link. Either way I’m not included in the “Blog Directory.” Now this upsets my inner nerd a little.

So I go to add a link. Except you can’t unless you register. I just went through process of trying to figure out which of my 900 registrations link to the old address for this blog, so that just isn’t going to happen. The other thing I can do is add a little link back to Technorati, which its search engines will find and I’ll be flagged a blog (or something). Except this is the first time I’ve been to Technorati for months, so why am I going to direct my 3 readers there? So instead I look at their popular search terms, and see a lot of rubbish except for Violent Acres which I’ve never heard of and ends up being just the sort of blog that gets all the other bloggers in a tizzy. And loves it so adds fuel to the fire, and queue endless circles. Isn’t the internet awesome? Of course a I read too many articles (it is a fun blog) and end up a couple of links later at Inkscape which is an open source SVG editor. This keys off an interest of mine and I’m tempted to download it because vector graphics rocks, and so does free & open source software. This is when I come to my senses and decide that none of this is helping me get done what I want to do today. So instead I spend fifteen minutes writing a blog post about it.

I love the internet.

25. January 2007 · Comments Off on I’ve moved! · Categories: General

It’s long overdue, and just in time to break a bunch of folk’s links, but I’ve changed this blog to be the main page on my site. As such, it’s now http://www.ardenstone.com without the “/wp/” at the end. This had been in the works for a while (hence the recent additions of resume, older non-flickr photos, etc. to the right hand menubar). I need to go add a redirect to the old address now. My apologies for breaking everyone’s (and by everyone I mean all three of you) links, but it’s nice to get rid of the old homepage.