mark.watero.us

Wordpress stuff, a statistics plugin, and jello

Articles found in the ‘Theme Development’ pigeonhole

Control your navigation with a theme options page

2 comments

There is an amazing collection of free and premium themes available for the Wordpress platform, and you can find almost everything from the most minimalistic, to some more fancier than others.

What really makes some of these themes shine above and beyond the rest though, is the attention to detail that the theme creator has put into their work. That means beyond just making it look great, they’ve gone above and beyond making it work great behind the scenes.

For my first tutorial on the topic of Theme Development, I’m going to look at a simple way to provide users of your theme with a way to control what is, and more importantly, what isn’t displayed on your navigation menu. We won’t be going into any advanced techniques here, no object oriented programming for example, just the very basics of what we need to do with PHP and already present Wordpress functions to add this simple but often overlooked feature.

Adding an administration option to your theme

If you’re not aware of what functions.php is, it’s basically like having a plugin that’s attached directly to your theme, and automatically loaded when your theme is activated. It’s commonly used for doing such tasks as registering sidebars, though it’s usefulness surely doesn’t end there.

Almost anything you could use a plugin for, you can use functions.php for, with a few minor exceptions (see here).

Note: This tutorial is designed for use by people intending to develop their own theme, however it can also be easily applied to any existing theme.

The first thing we need to do is add an administration page for the theme options, so let’s open up functions.php and add the following code:

1
2
3
4
5
6
7
8
function yourtheme_add_pages () {
	add_theme_page('Theme Options', 'Theme Options', 'edit_themes', basename(__FILE__), 'yourtheme_theme_options_page');
}
 
function yourtheme_theme_options_page () {
}
 
add_action('admin_menu', 'yourtheme_add_pages');

All we’re really doing here is using two built in Wordpress functions, add_theme_page(), and add_action() to add a new menu option under the “Appearance” menu of your blogs dashboard.

The first of our two new functions wraps add_theme_page() for use by add_action(), and builds a framework for adding more functionality later. The second of our new functions is just a placeholder for right now, and will be used by add_theme_page() to tell Wordpress what to do when the new menu option is selected.

Finally, we use add_action() to hook our functions into Wordpress. If you aren’t fully aware of how Wordpress actions and hooks work, please see Adding your own hooks to Wordpress, a previous post of mine, or the Wordpress Plugin API.

Hit save, and reload your dashboard. There should be a new option under “Appearance”, called “Theme Options”. If you click on this option, it won’t do anything yet, so let’s make it do something.

Collecting the information and using it

So now that we know how to add a new theme options page, lets move on. Open functions.php again, and add the following code (noting that this should replace the earlier yourtheme_add_pages() function):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
define('YOURTHEME_EXCLUDE_OPTION', 'yourtheme_excluded_pages');
 
function yourtheme_theme_options_page () {
	global $wpdb;
 
	$pages = $wpdb->get_results("SELECT `ID`, `post_title` from `{$wpdb->posts}` WHERE `post_type` = 'page' AND `post_parent` = 0 ORDER BY `post_title`");
 
?>
<div class="wrap">
<h2>Theme Menu Options</h2>
 
<p>Select the pages from the following list that you would like excluded from your main navigation;</p>
 
<form method="POST" action="">
 
<div style="border: 1px dashed #ccc;padding: 20px;background-color: #fff">
<?php
	$excluded = explode(',', get_option(YOURTHEME_EXCLUDE_OPTION));
	if (is_array($pages)) {
 
		foreach ($pages as $page) {
			echo '<input type="checkbox" name="exclude_pages[]" value="'.$page->ID;
			echo (in_array($page->ID, $excluded)) ? '" checked="checked"' : '"';
			echo ' /> '.$page->post_title.'<br />';
		}
 
	}
 
?>
</div>
 
<p class="submit"><input name="save" type="submit" value="Save Settings" /></p>
</form>
 
</div>
<?php
}

As you probably noticed, the first thing we did was to define a new constant, ‘YOURTHEME_EXCLUDE_OPTION’. This will contain the name of the Wordpress option you will be saving the data under, so we can use it again later and have one central place to change the name should we want to down the road.

We’ve also added a little meat to the yourtheme_theme_options_page(), starting with a call to the Wordpress database object in order to populate our list of available pages. Once we have this information we move on to displaying the form for selecting which pages we want to exclude.

Most of the form is completely arbitrary in its layout, I’ve gone ahead and added some minimal styling to make it look a little nicer should you be using this code directly. The most important part of the above function that we’ve added is this part here:

1
2
3
4
5
6
7
8
9
10
	$excluded = explode(',', get_option(YOURTHEME_EXCLUDE_OPTION));
	if (is_array($pages)) {
 
		foreach ($pages as $page) {
			echo '<input type="checkbox" name="exclude_pages[]" value="'.$page->ID;
			echo (in_array($page->ID, $excluded)) ? '" checked="checked"' : '"';
			echo ' /> '.$page->post_title.'<br />';
		}
 
	}

We first load the option where we’re saving all this data into a variable, and check to see if any pages have been previously excluded. As of right now, there won’t be any of course, but we’re including it now to be prepared for the future.

After that, we check to make sure that we have some results from the earlier database query. Since get_results() will always return an array of results (either objects, or an array of arrays depending on what you specify), even when only one row is returned, we’re going to check it with is_array().

Note: Normally I would suggest having any queries from the Wordpress Database Class return arrays instead of objects, as this can create unnecessary overhead, however for the purposes of this tutorial I have chosen to go with objects so that the code will be more readable.

Once we’ve confirmed that we’ve got some data to work with, the next five lines of code runs a loop, creating our form checkboxes. For every checkbox that’s created, we check to see if the page has been previously excluded and if so, automatically marks it. Now that we’ve got our page completed, we need to add some additional code to save the input to our database.

Remembering what’s been excluded

Once more we dive into functions.php, and update our first function, yourtheme_add_pages(), to look like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function yourtheme_add_pages () {
	if ($_GET['page'] == basename(__FILE__) &amp;&amp; !empty($_POST['save'])) {
 
		if (isset($_POST[ 'exclude_pages' ])) {
			update_option(YOURTHEME_EXCLUDE_OPTION, implode(',', $_POST['exclude_pages']) );
			$GLOBALS['ex_saved'] = TRUE;
 
		} else{
			delete_option(YOURTHEME_EXCLUDE_OPTION);
		}
 
	}
 
	add_theme_page('Theme Options', 'Theme Options', 'edit_themes', basename(__FILE__), 'yourtheme_theme_options_page');
}

We’re also going to add the following to the yourtheme_theme_options_page() function, right below the database call:

1
2
3
4
5
6
	$pages = $wpdb->get_results("SELECT `ID`, `post_title` from `{$wpdb->posts}` WHERE `post_type` = 'page' AND `post_parent` = 0 ORDER BY `post_title`");
 
	if ($GLOBALS['ex_saved'] == TRUE) {
		echo '<div id="message" class="updated"><p><strong>Your new settings have been saved.</strong></p></div>';
		unset($GLOBALS['ex_saved']);
	}

We’re nearly done at this point. The code added to the yourtheme_add_pages() is pretty straight forward, we first check to see if we’re on the right page, and simultaneously whether or not we have any form data in our POST array. If this turns out to be true, we then proceed to check and see if we have any data in the ‘excluded_pages’ array sent by the form.

If we do, that data is turned into a comma delimited list, added to the database, and we set a global variable which tells us information has been saved for use later on. If no data is found in the ‘excluded_pages’ array, then we delete the option from the database, as that probably means the user doesn’t want to exclude any of their pages.

Additionally, we added four lines of code to yourtheme_theme_options_page() which will check for that global variable we set earlier, and if it’s present, displays an “Information has been saved” notice to the user, then clears the variable so we don’t have the notice show up any more than it should.

The administrative side of our project is now complete, so let’s move on to making it actually work in our theme.

Wrapping up; excluding the theme pages

Only two more lines of code, and we have a fully operational page exclusion system built into our theme. Open your header.php, and somewhere near the top add the following code:

1
$pages_to_exclude = 'exclude='. get_option(YOURTHEME_EXCLUDE_OPTION);

The above code creates a string that we’ll be using later on, by pulling our previously saved data from the database, appending it to the string ‘exclude=’ and finally placing the completed string inside of the variable $pages_to_exclude.

If you’re familiar with Wordpress theme development, you should also be familiar with wp_list_pages(), which creates an HTML list of all the pages from your blog. Most people make use of this to build their themes primary form of navigation. What a lot of people don’t know is how versatile this function can be.

While I won’t get into the details of everything it can do, we will make use of one option in particular here, that being the ‘exclude’ option (seems to be named appropriately for this exercise, no?). Find in your theme where the wp_list_pages() function is called — it should look something like:

1
wp_list_pages('title_li=&amp;depth=1&amp;sort_column=post_title');

We’re going to add the string we saved earlier to the one inside the above function call, to exclude the pages selected by the user, like so:

1
wp_list_pages('title_li=&amp;depth=1&amp;sort_column=post_title&amp;'.$pages_to_exclude);

…and that’s all there is to it! Once you’ve done that, go check off a few pages to be excluded, and test your handywork!

Final Notes: I have not added any security to these functions whatsoever, use them ‘as is’ at your own risk. Whenever you are working with forms and administrative procedures in Wordpress, be sure to use built in security such as wp_nonce_field() and check_admin_referer(), as well as utilizing your own custom built security methods where necessary. For example, with the above code, you could run checks against the posted data to make sure that they are all numeric, with PHP’s is_numeric() or force them to be numeric with intval(). Security is everybody’s responsibility.

PPS: I’m having a few issues with the syntax highlighter I’ve chosen to use for these posts. While the ‘copy to clipboard’ and ‘view plain’ features seem to work just fine, the actual display of the code on my page sometimes omits new lines or adds them where I least expect them. If you have a Wordpress powered blog and have found a solution you are happy with, please feel free to tell me about it.

Written by mark

August 10th, 2009 at 10:07 pm