Tag Archives: wordpress

Hierarchical Menu

Problems

After using the menu editor in the admin interface to modify the menu structure, an ugly problem showed up. I had created a subpage, but the drop down menu wasn’t showing. Instead, a scroll bar appeared in the header when I moved my mouse over the menu item, and unless I put focus on the header first so I could scroll down with the arrow keys later, I couldn’t see the menu item at all. Also, the colour was all wrong. The sub-item showed up with a dark gray background colour and black text, not exactly the most readable combination.

Fix the Scrollbars

Scrollbars appear when the overflow property in CSS is set to auto. I found the offending line in #header and removed it. However, the header’s colour no longer filled the header section. It didn’t have enough height. The wrapper <div> container was not expanding to include the heights of the elements.

To solve this, I simply added height: 144px to #branding. The value of 144px came from adding the height values of the contained <div> blocks. The header colour now expands to fill the header section, just like it had before.

Fix the Colours

Examining the elements in Google Chrome revealed that the colours for nested menus are controlled by a few CSS selectors. Note that these are for the twenty ten theme, different themes might use different CSS selectors.

For the current item the mouse is over, the selector is #menu-main .menu-item a:hover

For the unselected parent, the selector is #access li:hover > a, #access ul ul :hover > a

For the child item, the selector is #access ul ul a

Modify the background-color property in these selectors and that’s it!

Favicon

I took the logo and resized the longest dimension down to 16 pixels in GIMP, then created a new 16 by 16 pixel image and pasted it in.

It was necessary to delete the background and extend the layer size to the image size to get the desired effect of a transparent background. After that, just save the image as an .ico file and follow the instructions at the WordPress Codex.

It was necessary to include the line the codex page said to add into header.php.

Remove Posts Menu from Admin

While the content manager has the edit_posts capability so he can edit media items, this also exposes the posts menu. I don’t want the content manager to handle normal posts, so I decided to hide the menu item from the admin interface. It will still be possible to go to the URL manually and edit posts, but removing the item from the menu reduces the chance of user error.

The point is to reduce the number of options in the menu so the user isn’t intimidated. Really preventing the user from editing posts isn’t such an important issue for my use.

The article at sixrevisions was used to figure out what code to include. I determine which role the user holds by checking his capabilities. In my setup, only the content manager and admin has edit_sermons, but only the admin has update_core. Thus being having edit_sermons but not update_core makes the user a content manager.

function mbpc_remove_menu_items() {
	if ( current_user_can( 'edit_sermons' ) && !current_user_can( 'update_core') ) {

		global $menu;
		$restricted = array(__('Posts'));
		end ($menu);
		while (prev($menu)){
			$value = explode(' ',$menu[key($menu)][0]);
			if(in_array($value[0] != NULL?$value[0]:"" , $restricted)) {
				unset($menu[key($menu)]);
			}
		}
	}
}

add_action('admin_menu', 'mbpc_remove_menu_items');

Now when logged in as a content manager, the Posts menu can no longer be seen. Less clutter, less confusion.

Roles and Capabilities for Custom Post Types

WordPress access rights are controlled by Roles and Capabilities. To perform an action, your user role must have that capability. For example, to edit posts, your role must have edit_posts assigned to it.

Custom Taxonomies

Strangely, while managing terms for custom taxonomies requires manage_categories, this does not give the user the right to assign terms to the post. To allow that, add

'capabilities' => array('assign_terms' => 'edit_sermons')

to the arguments array of register_taxonomy(). Replace the word “sermons” with whatever you want to name this capability. Naming it edit_sermons means that the user who can edit sermon custom post types will also be able to assign terms belonging to this taxonomy.

Custom Post Types

Posts and pages have built in capabilities for editing, adding, publishing etc etc. To restrict custom post types, it’s necessary to assign them a new bunch of capabilities. Do this by adding the following code to the array of arguments.

'capability_type' => 'sermon',
'map_meta_cap' => true

The map_meta_cap key is necessary so that edit_sermons, delete_sermons and add_sermons will work as expected.

This is explained in Justin Tadlock’s article, but he had to do it manually then.

Assign Capabilities

Use the Members plugin to create roles and assign capabilities. When assigning capabilities, give edit_{capability_type}, publish_{capability_type}, but add an ‘s’ after that (plural form).

Built in Categories with Custom Post Types

How to do that is covered in detail in this post on adding a new custom post type.

However, view by category doesn’t work by default with custom post types. This is because the query doesn’t cater for it, thus I had to override category.php and modify the query.

Copy category.php from the twenty ten theme and paste it in my own theme folder. Add the following code before the loop is called.

global $wp_query;
$args = array_merge($wp_query>query,array('post_type'=>array('post','newsletter'),'paged'=>get_query_var('paged')));
query_posts($args);

This file has also been modified the call the custom loop for newsletters because that post type will have some extra meta data for display.

Custom Post Type Archives Part 5

I certainly didn’t expect a part 5, but my monthly archive links suddenly stopped working! I hadn’t made any theme changes related to them so this was a most surprising problem.

First of all, I tried to find out which template file was getting called, but my debug statements in various files weren’t being called at all. Finally, a close look at the template hierarchy revealed that a 404 error goes straight to the 404 page, and there it was. This suggests that the query couldn’t find anything. Printing out the array of query variables made me realize that the URL segments were being mapped to the wrong variables.

This was most unexpected, and for some unknown reason, even reactivating the Custom Post Type Archives plugin didn’t work.

A lot of Googling revealed this discussion which suggests <domain>/2010/?post_type=cpt works. It was time to find out more about how URL rewriting works.

The Codex didn’t have much information about them, just some shallow user level instructions on what they look like and how to activate them. I did remember reading before that the rewriting was done by parsing the URL and giving everything to index.php to handle. Since I could get my custom post types to display with the right template file when using ugly URLs, getting the rewrite rules to work would be the key to the solution.

The code for rewrites is in wp-includes/rewrite.php, which is where the WP_Rewrite class is defined. Looking through the whole file gave me a better understanding of what’s happening. It seems that lots of rules are actually stored internally. These rules convert the pretty links into query variables which the rest of the WordPress framework can understand.

The function reference for WP_Rewrite explained the variables and the filters, which could be used to modify the rewrite rules. With this clue, I went through the source code of the plugin to find where it adds the rewrite rules. I then modified it to suit my own purposes.

The code is as follows.

function register_post_type_rewrite_rules($wp_rewrite) {

	$args = array('public' => true, '_builtin' => false);	//get all public custom post types
	$output = 'names';
	$operator = 'and';
	$post_types = get_post_types($args,$output,$operator);

	$url_base = ($url_base == '') ? $url_base : $url_base . '/';
	$custom_rules = array();

	$post_types = implode('|', $post_types);
		$custom_rules = array( "$url_base($post_types)/([0-9]+)/([0-9]{1,2})/([0-9]{1,2})/?$" =>
				'index.php?post_type_index=1&post_type=' . $wp_rewrite->preg_index(1) . '&year=' . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3) . '&day=' . $wp_rewrite->preg_index(4),		//year month day

								"$url_base($post_types)/([0-9]+)/([0-9]{1,2})/?$"  =>
				'index.php?post_type_index=1&post_type=' . $wp_rewrite->preg_index(1) . '&year=' . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3),			//year month
								"$url_base($post_types)/([0-9]+)/?$" =>
				'index.php?post_type_index=1&post_type=' . $wp_rewrite->preg_index(1) . '&year=' . $wp_rewrite->preg_index(2)	//year
							);

	$wp_rewrite->rules = array_merge($custom_rules, $wp_rewrite->rules); // merge existing rules with custom ones
	
	return $wp_rewrite;
}

add_filter('generate_rewrite_rules', 'register_post_type_rewrite_rules', 100);

First, get all public custom post types. This is explained in the function reference for get_post_types()

Next, add rewrite rules for year/month/day, year/month, and year URL permalinks to a custom array.

Finally, merge this array to the existing one and return the entire set of rules.

Once again, custom post type archives work like they’re supposed to. Phew!

Read about the next improvement to the custom post type archives widget at the Custom Post Type Archives jQuery Accordion post.

Members Plugin

I needed to figure out how to control who could add the different types of content to the site. The information in the WordPress codex on Roles and Capabilities isn’t too complete at the moment. It took a few readings to understand the system, but there was still not much clue on how to write code to customize it.

After trying a couple of different plugins, I finally found that the Members plugin by Justin Tadlock does what I want. This plugin can be used to manage roles and then assign them capabilities. It can also do other things like limit content from certain users, but I only want to control the types of content which the different roles can post.

My plan is to have a blogger role who will only post normal WordPress posts. These will appear on the blog page. Another role will take on the role of content manager. This role will be given control over the sermons and newsletters, but not the blog posts. The primary intention of all this is to make the admin interface less complex for the end users and only show them the things they can edit.

The blogger role is currently fulfilled by the default author role. By adding the ‘capability_type’ key to the custom post types, the default author can no longer see them.

Using the plugin, it was a simple task to add the role of content manager. I then gave it capabilities to edit and publish sermons and newsletters. The necessary capabilities are edit_sermons, edit_others_sermons, edit_published_sermons, publish_sermons, delete_sermons, delete_others_sermons for sermons. Replace ‘sermons’ with ‘newsletters’ and all all the capabilities for control over newsletters.

It is also necessary to grant manage_categories so category and taxonomy terms can be added and managed. To allow the role to assign terms in custom taxonomies, the key-value pair 'capabilities' => array('assign_terms' => 'edit_sermons') is needed when registering the custom taxonomy. Once again, replace ‘sermon’ with whatever capability_type you want.

To allow media to be managed, the edit_posts and *_posts capabilities also had to be added. * is a wildcard for all the other functions. This actually allows the content manager control over the blogger’s posts, but the problem is mitigated somewhat by hiding the posts menu from the interface.

While the author role currently fulfills the blogger role, it would be better to separate them. The default author role will then be given the combined capabilities of church_cm and blogger.

Add from Server plugin

Uploads through the admin interface go into the uploads folder with the structure year/month. This is usually fine, but what if I want to upload lots of past content? If I upload a lot of past content now, it’ll all end up in the 2011/03 folder, regardless of when the content was produced. This will be messy. The better solution is to upload each to its own year/month folder, a much neater solution.

However, manually placing the files into the desired folder structure does not cause them to show up in the admin interface. It turns out that WordPress doesn’t check the folders to see if anything is inside. A database entry is needed. This can be accomplished with the Add from Server plugin, which scans the directories and adds the required database entry.

This allows the files on the server to be added to the database and hence show up in the media library. The file will attach itself to the post which is currently being written, unless the checkbox “Do not add to current post gallery” is selected.

Custom Post Type Archives Part 4

Phew, custom post type archives are causing lots of problems. Three separate posts about them already, and the plugin was the one which broke WordPress 3.1. Well the updated version doesn’t. However, this led to a thought. Since it’s supposed to be built in, could I do it without using the plugin? After all, I was just after some very basic functionality. The answer turns out to be yes, well, almost.

Enabling the Feature

There is an attribute has_archive in the register_post_type() function which activates the archives feature, but it defaults to false. I added the code in to set this to true. This information was from Mark McWilliams, the same post also gave some hints on how the permalinks would look like.

Disabling the Plugin

The next step was to disable the plugin and see what stops working. Looks like the sidebar has some problems, which also prevents the admin bar from loading properly. The month listing from the custom widget wasn’t appearing at all.

Fixing the Widget

The widget depends on the custom get_post_type_archives() function, explained in Part 2, to do its magic.

Using a debug echo statement and commenting out suspect lines, I found the problem in one of the custom functions provided by the plugin. It returns the permalink of the post type. This can actually be generalized as

get_bloginfo('url') . '/' . $post_type . '/';

Now I get output with a link to the archives by date, but it wasn’t showing the correct number of months. Why??

Filter getarchives_where

Even the plugin just let the built in WordPress function wp_get_archives() do the hard work, so why did it work with the plugin activated? A closer look at the function itself, and the way it was used by the plugin, revealed that wp_get_archives() was insufficient to get archives by post type. It applies a filter to get the correct SQL Where clause. This was used by the plugin.

Since I only want that function, I copied the entire thing over to my own functions.php file.

function pta_wp_get_archives_filter($where, $options) {
	if(!isset($options['post_type'])) return $where; // OK - this is regular wp_get_archives call - don't do anything
	
	global $wpdb; // get the DB engine
	
	$post_type = $wpdb->escape($options['post_type']); // escape the passed value to be SQL safe
	if($post_type == 'all') $post_type = ''; // if we want to have archives for all post types
	else $post_type = "post_type = '$post_type' AND"; // otherwise just for specific one
	
	$where = str_replace('post_type = \'post\' AND', $post_type, $where);
	
	return $where;
}
add_filter('getarchives_where', 'pta_wp_get_archives_filter', 10, 2);

Now the archive links work and the correct number of links are displayed, depending on what the post type is!

Archive Templates

This approach led to my custom post types being displayed incorrectly as they now use the archive.php template file. It only displays the excerpts for archives, so the shortcodes and meta data no longer show up. As each post will be very short in length, I want the full content and meta data to show up, just like it does on the main page listings.

The solution to this was simply to copy archive.php to the child theme, rename it archive-{post-type}.php, and change the function call to the loop so that it calls my previously written custom loops.

Page Templates

{post-type}-template.php were previously used for both the display of the full listing as well as the archive pages. With WordPress 3.1, archives are handled by the archive template pages. Thus, the if-else conditions which handled query_posts() can be removed. The page templates will be used just for display of all the custom posts.

Conclusion

With all these changes, I can now use one less plugin and use more of the functionality available in WordPress Core.

Custom Post Type Archives Part 5