WordPress Categories, RegEx and the Parent/Child Relationship

Categories: Blog, Development, Personal, PHP, WordPress
Tags: , , , , , , ,

Apr 9
2011

First let me start off saying that if any WordPress coders out there have a better solution for this problem please leave a comment below!

I recently needed to grab all the categories from WordPress and store them in an array ordered by the parent child relationship. Doesn’t sound too hard until I noticed the WordPress functions I was finding were not returning exactly what I needed.

Problems with WordPress Functions

get_categories() gave me the array I needed but wouldn’t keep the categories in the parent/child order I needed. The hierarchical argument sounds promising but is misleading. It simple includes the “sub-categories that are empty, as long as those sub-categories have sub-categories that are not empty”.

Using this I had my array but would need to build that parent/child relationship on my own.

wp dropdown categories() had promise but comes with HTML code to build the drop down menu. As does wp_list_categories(). Good, but not great.

How about Regular Expressions?

Q*bert RegEx

Q*bert RegEx

Because I didn’t want to have to rebuild the parent/child relationship I figured that either wp_dropdown_categories() or wp_list_categories() would be the way to go. Since wp_dropdown_categories() included   to separate child categories from their parents – and I needed this – it was the logical choice.

What this meant was that I was now left with finding a way to strip out the code and just leave myself with the category ID and the category name. This looked like a job for regular expressions.

Code From wp_dropdown_categories()

First things first – I needed to make sure to add the arguments to wp_dropdown_categories() to make sure that I get what I need:

  • Return the code, not display it: echo=0
  • Order by name: orderby=name
  • Get categories even if empty (no posts): hide_emtpy=0
  • Get all child categories without a depth limit:hierarchical=1

The function call as needed:

$cats = wp_dropdown_categories('hierarchical=1&hide_empty=0&echo=0&orderby=name');

Which would return the code looking like so (on a sample WordPress site):

<select name='cat' id='cat' class='postform' >
	<option class="level-0" value="5">Another Category</option>
	<option class="level-0" value="3">Category One</option>
	<option class="level-1" value="4">   Sub Category One</option>
	<option class="level-2" value="20">      Sub Sub</option>
	<option class="level-3" value="23">         third</option>
	<option class="level-0" value="1">General</option>
	<option class="level-0" value="12">Lakes</option>
	<option class="level-1" value="21">   Lakes Sub</option>
	<option class="level-1" value="22">   Sub</option>
</select>

So now I needed to do a few things with this code.

  1. Remove the opening and closing select tags.
  2. Remove tabs before option tags.
  3. Remove the opening and closing option tags but keep and reuse the ID (inside value attribute) and the category name (between the option tags).
  4. Want/need to keep the spaces before the category name to retain showing the parent/child relationship.
  5. Break the string apart by line in order to use the category ID and name to build my new array.

The code for all that fun stuff:

$cats = trim( strip_tags( wp_dropdown_categories('hierarchical=1&hide_empty=0&echo=0&orderby=name'), '<option>' ) );
$cats = preg_replace( '/<option class="level-[0-9]{0,}" value="([0-9]{1,})">(.*?)<\/option>/', '${1}:dmg:$2', $cats );
foreach ( explode( "\n", $cats ) as $i ){
  $foo = explode(':dmg:', $i);
  $new_opts_array[trim($foo[0])] = trim($foo[1]);
}
$val['opts'] = $new_opts_array;

The Breakdown

Let’s look at the code line by line to see what’s going on.

Line 1

First I use strip_tags() to remove any HTML tag other than the option tag. This removes the opening and closing select tags. I also use trim() to tidy up the string in case there is any unwanted whitespace. The result of this is stored in the $cats variable.

Line 2

I use preg_replace() to match and replace the option tag using backreferences as what to replace any match with.

/<option class="level-[0-9]{0,}" value="([0-9]{1,})">(.*?)<\/option>/'

You can see that the class attribute value of the option tag could have any level assigned to it depending on the parent/child relationship. The regex pattern [0-9]{0,} covers any level for us.

I needed to make sure to get the category ID and name so I could reuse those in my replacement. The way to go is using regex grouping and backreferences. Since the value attribute holds the category ID (an integer value) the pattern ([0-9]{1,}) works just fine. However, for the category name (between the option tags) could be a problem. I figured that for now (without a ton of testing) that matching everything inside using the pattern (.*?) would work fine – and so far, so good.

Thanks to the power of regular expressions and being able to backreference matches I could reuse the category ID and name with the following replacement value in preg_replace()${1}:dmg:$2

${1} is a backreference to the ID while $2 is a backreference to the category name.

:dmg: was included because I know I need to break the string apart later to build an array.  I couldn’t simply use a colon or hyphen because the category name may include one or more of those characters. So I tried to make it unique – something most people won’t have in their category title.

Line 3

First I explode() the string using the newline character as the delimiter which builds us an array of values – where each line (category ID and name) is an array item. Cycle through the array created using explode() by using foreach.

Line 4

Again I explode() the string from foreach using :dmg: (included back in preg_replace) as the delimiter.  This means the $foo array now contains the category ID at array index 0 and the category name at array index 1.

Line 5, 6 and 7

I just need to build my array using the category ID as the new array’s index and category ID as the value (line 5). Close the foreach loop (line 6) and assign my newly created array to the array I need to use elsewhere in my code  (line 7).

Conclusion

I looked around online before I started coding to see if I could find someone who already encountered my frustrations. I came up empty. To me this means one of two things.

  1. Either I’m not seeing/finding a WordPress function that does this for you.
  2. No one else has ever needed this or found a better work around using other functions.

If either is true and you know a better method – please share it with me. If not… well it was fun exercise in coding and hopefully it’ll come in useful to someone else. Enjoy the code.

 

Comments

Leave a comment

 

Leave a comment

Read comments

 



Please do not submit your comment more than once. It will appear once it has been approved.