Wordpress

WordPress Custom functions.php Template, Part 2

In a recent post, we show you how to clean up and enhance the functionality of WordPress with a custom functions.php template. In that post, we explain how using a custom functions.php template can speed up development while optimizing many key aspects of WordPress. In this post, we deliver another prime collection of 15 custom functions to enhance your WordPress site. These functions provide all sorts of useful functionality, including stuff like:

  • Callback function for a custom comments loop
  • Automatic content insertion in posts and feeds
  • Spam and delete links for comments when logged in
  • Buffer period before new posts are added to your feeds
  • Including an Admin link to the “All-Settings” page
  • Removing the version and generator information from your site and feed

These new functions extend the functionality of our original functions.php template with 15 more useful functions. Not everything presented here is going to be necessary for all of your themes, so just grab what you need and add it your own custom functions.php file.

In this DiW article, we extend our original functions.php template with 15 more useful functions.

As before, we’ll first walk through each of the functions and then unify them into a working functions.php template. To use, just copy and paste the template code at the end of this article or grab a copy of the zipped functions.php file and enjoy a custom collection of functions that will help you optimize your development process while enhancing WordPress with some awesome new functionality.

Insert custom content after each post

If you look at the different things contained within a typical post, you will find many common and repetitive items, such as feed links, copyright information, and social-media bookmarks. While there’s nothing wrong with inserting this content into your single.php template, it is sometimes easier to manage things from the functions.php file. For example, at Perishable Press, I include a brief copyright statement at the end of each post. By including the following code in my functions.php template, it’s something that happens automatically:

// add custom post content
function add_post_content($content) {
	if(!is_feed() && !is_home()) {
		$content .= '<p>This article is copyright &copy; '.date('Y').'&nbsp;'.bloginfo('name').'</p>';
	}
	return $content;
}
add_filter('the_content', 'add_post_content');

The trick here is an old one, but it’s extremely useful. By changing the line beginning with “$content.”, you can add just about any content you like.

Insert custom content in your feeds

Just as with the previous method, this function makes it possible to automatically add any content to your feeds. Yes, I know there are plugins that will make your feed footers do backflips, but for a simple copyright message or other info, I think it is easier and more efficient to simply toss a few lines into your custom functions.php template:

// add custom feed content
function add_feed_content($content) {
	if(is_feed()) {
		$content .= '<p>This article is copyright &copy; '.date('Y').'&nbsp;'.bloginfo('name').'</p>';
	}
	return $content;
}
add_filter('the_excerpt_rss', 'add_feed_content');
add_filter('the_content', 'add_feed_content');

As before, you can change the added content to whatever you want by editing the “$content” variable. Once in place, this will append a dynamic copyright message to each post in your feed. Note: if you happen to be adding the same content to your web pages (using the previous method) and your feeds (using this method), you may combine the two functions like so:

// add custom content to feeds and posts
function add_custom_content($content) {
	if(!is_home()) {
		$content .= '<p>This article is copyright &copy; '.date('Y').'&nbsp;'.bloginfo('name').'</p>';
	}
	return $content;
}
add_filter('the_excerpt_rss', 'add_custom_content');
add_filter('the_content', 'add_custom_content');

Remember that if you use this combined function that you should remove or comment out both of the individual ones to avoid duplicate output. It will be included but commented out in the complete functions.php template file.

Completely remove the version number from pages and feeds

A commonly cited security measure for WordPress-powered sites involves removing the automatically generated version number from appearing in the <head> section of your pages’ source code. This type of security technique is referred to as “security through obscurity,” and aims at hiding potentially sensitive information from a would-be attacker. Here’s a function that does the job:

// remove version info from head and feeds
function complete_version_removal() {
	return '';
}
add_filter('the_generator', 'complete_version_removal');

This will remove your site’s version and generator information from all of your pages and feeds, making it a little bit harder for the bad guys and a little bit safer for you and your visitors.

Customize the admin footer message

Customization is what makes your online experience something special. For your own sites and for your clients’ sites, it is nice to customize the generic admin footer message with something a little more useful, informative, and inspiring. This little gem makes it easy:

// customize admin footer text
function custom_admin_footer() {
	echo '<a href="http://monzilla.biz/">Website Design by Monzilla Media</a>';
} 
add_filter('admin_footer_text', 'custom_admin_footer');

Here, for the sake of example, we are including a link to my web-design company, but you can customize the content with just about anything you wish. If this were something I had to do manually for each site, I probably wouldn’t do it. But by adding these few lines to my functions.php template, it all happens automatically, without me having to think about it. Nice ;)

Enable HTML markup in user profiles

When customizing their profiles, users may want to include a hyperlink, bold text, or some other HTML markup. By default, WordPress prevents this from happening, but you can easily enable it with this friendly little snippet:

// enable html markup in user profiles
remove_filter('pre_user_description', 'wp_filter_kses');

Note: as miqrogroove points out, enabling HTML may be best left to sites that have open user-registration disabled. Use with discretion.

Delay feed update after posting

As a chronic perfectionist, I hate it when I post something only to discover an error a few minutes later. By the time I notice, fix and update the post, it’s already been beamed out all over the freakin’ place. To prevent this, I like to take the advice of WPEngineer and give myself a little buffer period or “safety net” after publishing my posts.

// delay feed update
function publish_later_on_feed($where) {
	global $wpdb;

	if (is_feed()) {
		// timestamp in WP-format
		$now = gmdate('Y-m-d H:i:s');

		// value for wait; + device
		$wait = '5'; // integer

		// http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_timestampdiff
		$device = 'MINUTE'; // MINUTE, HOUR, DAY, WEEK, MONTH, YEAR

		// add SQL-sytax to default $where
		$where .= " AND TIMESTAMPDIFF($device, $wpdb->posts.post_date_gmt, '$now') > $wait ";
	}
	return $where;
}
add_filter('posts_where', 'publish_later_on_feed');

This code quietly and automatically gives me an extra five minutes before my post is added to my feeds. If you need more or less than five minutes, just edit the “$wait” variable with the integer of your choice.

Add an Admin link to the WordPress “All-Settings” page

As we’ve explained before, WordPress makes it easy to view and edit your blog settings from the Admin area. Thanks to a file named “options.php”, WordPress will display all of your database settings and enable you to edit a majority of them. To view this page, you need to log in as Admin and enter this URL in your browser’s address bar:

http://domain.tld/wp-admin/options.php

Having access to your “All-Settings” page can be extremely helpful, so it’s nice to display a direct link for easy access. The following slice of code in your functions.php file is all that’s needed:

// admin link for all settings
function all_settings_link() {
	add_options_page(__('All Settings'), __('All Settings'), 'administrator', 'options.php');
}
add_action('admin_menu', 'all_settings_link');

Once in place, you will see a link to your “All Settings” page in the WordPress Admin. You can change the name of the link to whatever you prefer by editing the two instances of “All Settings”.

Remove all nofollow attributes from comments

There are a million ways to remove nofollow from your comments, but nothing is as easy as this method provided by code guru Thomas Scholz:

// remove nofollow from comments
function xwp_dofollow($str) {
	$str = preg_replace(
		'~<a ([^>]*)\s*(["|\']{1}\w*)\s*nofollow([^>]*)>~U',
		'<a ${1}${2}${3}>', $str);
	return str_replace(array(' rel=""', " rel=''"), '', $str);
}
remove_filter('pre_comment_content',     'wp_rel_nofollow');
add_filter   ('get_comment_author_link', 'xwp_dofollow');
add_filter   ('post_comments_link',      'xwp_dofollow');
add_filter   ('comment_reply_link',      'xwp_dofollow');
add_filter   ('comment_text',            'xwp_dofollow');

That code will remove all instances of the nefarious “nofollow” attribute from all comment items, including the author link and comment text. This is a great way to automatically remove nofollow with no plugins or hacking required.

Enable easy display of your post’s word count

This function enables you to display your post’s word count without cluttering up the WordPress loop. To display the word count of your posts, first place this in your functions.php file:

// count words in posts
function word_count() {
	global $post;
	echo str_word_count($post->post_content);
}

Then, simply place this anywhere in your loop where you would like the number to appear:

<?php word_count(); ?>

So for example, you could display your post’s word count to your visitors with something like:

<p>This post contains a whopping <?php word_count(); ?> words.</p>

We can also easily display the word count of other items, such as the post title:

<?php echo str_word_count($post->post_title); ?>

Likewise, we can exclude the function from functions.php and get the word count with a single line of code in the loop:

<?php echo str_word_count($post->post_content); ?>

Just slap ‘em anywhere in your post loop to display your word count. Not always needed, but nice to have available ;)

Enable delete and spam links for comments

Managing your comments is easily done via the Admin, but geeks like me also like to see things from the outside, as they appear to the public. Browsing through comments as they appear on your site helps you catch things that may have eluded routine approvals in the Admin’s comment listings. To help facilitate this process, I like to include a backwards-compatible set of spam and delete links next to each comment. This makes it super-easy to cultivate comments live on the site.

// spam & delete links for all versions of wordpress
function delete_comment_link($id) {
	if (current_user_can('edit_post')) {
		echo '| <a href="'.get_bloginfo('wpurl').'/wp-admin/comment.php?action=cdc&c='.$id.'">del</a> ';
		echo '| <a href="'.get_bloginfo('wpurl').'/wp-admin/comment.php?action=cdc&dt=spam&c='.$id.'">spam</a>';
	}
}

Once this is included in your functions.php file, displaying the links is as easy as adding this to your comments loop:

<?php delete_comment_link(get_comment_ID()); ?>

Disable all WordPress feeds

Thanks to Frank from WPEngineer, it is easy to disable all feed functionality for your site. Here is the functions.php code:

Note: Only use this function if you want to disable your feeds! This function will be commented out of the complete functions.php file.

// disable all feeds
function fb_disable_feed() {
	wp_die(__('<h1>Feed not available, please visit our <a href="'.get_bloginfo('url').'">Home Page</a>!</h1>'));
}
add_action('do_feed',      'fb_disable_feed', 1);
add_action('do_feed_rdf',  'fb_disable_feed', 1);
add_action('do_feed_rss',  'fb_disable_feed', 1);
add_action('do_feed_rss2', 'fb_disable_feed', 1);
add_action('do_feed_atom', 'fb_disable_feed', 1);

This function will quietly and completely disable all of your site’s feeds, including all different formats. With this code in place, any request for your feed will return the following message in a nice, big set of <h1> tags:

Feed not available, please visit our Home Page!

This message is easily customized by editing the “wp_die” argument in the function.

Customize the default gravatars and add your own

Instead of suffering with the rather dull default gravatars, use this code to customize your own:

// customize default gravatars
function custom_gravatars($avatar_defaults) {

	// change the default gravatar
	$customGravatar1 = get_bloginfo('template_directory').'/images/gravatar-01.png';
	$avatar_defaults[$customGravatar1] = 'Default';

	// add a custom user gravatar
	$customGravatar2 = get_bloginfo('template_directory').'/images/gravatar-02.png';
	$avatar_defaults[$customGravatar2] = 'Custom Gravatar';

	// add another custom gravatar
	$customGravatar3 = get_bloginfo('template_directory').'/images/gravatar-03.png';
	$avatar_defaults[$customGravatar3] = 'Custom gravatar';

	return $avatar_defaults;
}
add_filter('avatar_defaults', 'custom_gravatars');

Of course, you can manually choose a default gravatar by specifying its location via the “get_avatar” template tag, but this method makes it so much easier. Not only does it replace the default with multiple custom gravatars, but it also goes the extra mile by displaying them in the WordPress Admin area under Settings > Discussion. From there, you can select any of your custom gravatars by simply selecting it and clicking the “Update” button. Nothing could be easier.

A few notes about this function:

  • As is, it adds three new gravatars to choose as defaults.
  • Each gravatar should be located in your theme’s images directory.
  • You can add as many default gravatars as you would like by emulating the code pattern.
  • If you keep your custom gravatars someplace else, be sure and edit the image paths accordingly.

Disable automatic formatting in post content via shortcode

WPRecipes shows us how to prevent WordPress from automatically formatting chunks of post content by using a shortcode. This is very useful for posting code, poetry, or anything else that you would like to display in raw format. To do this, we add the following snippet to our functions.php file:

// disable auto formatting in posts
function my_formatter($content) {
	$new_content = '';
	$pattern_full = '{(\[raw\].*?\[/raw\])}is';
	$pattern_contents = '{\[raw\](.*?)\[/raw\]}is';
	$pieces = preg_split($pattern_full, $content, -1, PREG_SPLIT_DELIM_CAPTURE);

	foreach ($pieces as $piece) {
		if (preg_match($pattern_contents, $piece, $matches)) {
			$new_content .= $matches[1];
		} else {
			$new_content .= wptexturize(wpautop($piece));
		}
	}

	return $new_content;
}
remove_filter('the_content', 'wpautop');
remove_filter('the_content', 'wptexturize');
add_filter('the_content', 'my_formatter', 99);

With that code in place, you can insert unformatted code into your posts via the “[raw]” shortcode, for example:

[raw]Unformatted content[/raw]

Anything contained within those shortcodes will be left unformatted when displayed in your posts.

Escape HTML entities in comments

Kaspars Dambis shows us how to automatically escape encoded HTML entities in the comments that readers leave on your posts. This is especially useful for blogs that deal in heavy code exchanges on post threads. This enables your commentators to simply wrap their code in <code> tags and not have to worry about manually converting characters such as angled brackets (“<” & “>”) into their encoded equivalents (“&lt;” & “&gt;”).

// escape html entities in comments
function encode_code_in_comment($source) {
	$encoded = preg_replace_callback('/<code>(.*?)<\/code>/ims',
	create_function('$matches', '$matches[1] = preg_replace(array("/^[\r|\n]+/i", "/[\r|\n]+$/i"), "", $matches[1]); 
	return "<code>" . htmlentities($matches[1]) . "</"."code>";'), $source);
	if ($encoded)
		return $encoded;
	else
		return $source;
}
add_filter('pre_comment_content', 'encode_code_in_comment');

Once in place in your functions.php file, simply tell your commentators to wrap their code snippets in <code> tags. In other words, any content wrapped in <code> tags will be encoded automatically. Plus, in order to prevent unwanted <br /> tags, this function also automatically removes line breaks after the opening <code> tag and before the closing <code> tag.

Custom comments display callback function

When designing your theme’s comments.php file, you can use either WordPress’ default code or create your own for full control over your comments display. If you’re rolling your own, it is often easiest to grab a solid comments-display template and customize it to suit your needs. After many iterations and much refining, here is the comments callback function that I usually begin with:

// custom comments callback function
function custom_comments_callback($comment, $args, $depth) {
	$GLOBALS['comment'] = $comment; ?>

	<li <?php comment_class(); ?> id="comment-<?php comment_ID(); ?>">
		<div>
			<?php echo get_avatar(get_comment_author_email(), $size = '50', $default = bloginfo('stylesheet_directory').'/images/gravatar.png'); ?>

			<div>
            			<?php printf(__('%s'), get_comment_author_link()); ?> &ndash; <a href="<?php echo htmlspecialchars(get_comment_link($comment->comment_ID)); ?>"><?php comment_date('F j, Y'); ?> @ <?php comment_time(); ?></a><?php edit_comment_link('Edit', ' &ndash; ', ''); ?>
			</div>
			<?php if ($comment->comment_approved == '0') : ?>

			<p><?php _e('Your comment is awaiting moderation.'); ?></p>
			<?php endif; ?>

			<div><?php comment_text(); ?></div>

			<div id="comment-reply-<?php comment_ID(); ?>">
				<?php comment_reply_link(array_merge($args, array('reply_text'=>'Reply', 'login_text'=>'Log in to Reply', 'add_below'=>'comment-reply', 'depth'=>$depth, 'max_depth'=>$args['max_depth']))); ?> 

			</div>
		</div>

<?php } // WP adds the closing </li>

There’s a lot going on here, so let’s highlight the important stuff:

  • Do not add the closing </li> element – WordPress does this for you.
  • This function calls for a default gravatar in your theme’s “images” directory. Either ensure you’ve got the required image in place or edit the code to reflect your site’s reality.
  • Once this function is included in your functions.php file, you can display it by using the following comments loop in your comments.php file:
<?php if (have_comments()) : ?>
<ol>
	<?php wp_list_comments('type=comment&style=ol&callback=custom_comments_callback'); ?>
</ol>
<?php else : ?>

Notice the third parameter for the wp_list_comments template tag? that’s what makes it go. Also important is the “style=ol” parameter, which tells WordPress that we are using an ordered list for comments and that it should automatically close the markup with a </li> element.

Putting it all together..

As promised, here is the full-meal deal – the entire collection neatly organized into a single chunk of code:

<?php // custom functions.php template @ digwp.com

// add custom post content
function add_post_content($content) {
	if(!is_feed() && !is_home()) {
		$content .= '<p>This article is copyright &copy; '.date('Y').'&nbsp;'.bloginfo('name').'</p>';
	}
	return $content;
}
add_filter('the_content', 'add_post_content');

// add custom feed content
function add_feed_content($content) {
	if(is_feed()) {
		$content .= '<p>This article is copyright &copy; '.date('Y').'&nbsp;'.bloginfo('name').'</p>';
	}
	return $content;
}
add_filter('the_excerpt_rss', 'add_feed_content');
add_filter('the_content', 'add_feed_content');

/* add custom content to feeds and posts
function add_custom_content($content) {
	if(!is_home()) {
		$content .= '<p>This article is copyright &copy; '.date('Y').'&nbsp;'.bloginfo('name').'</p>';
	}
	return $content;
}
add_filter('the_excerpt_rss', 'add_custom_content');
add_filter('the_content', 'add_custom_content'); */

// remove version info from head and feeds
function complete_version_removal() {
	return '';
}
add_filter('the_generator', 'complete_version_removal');

// customize admin footer text
function custom_admin_footer() {
	echo '<a href="http://monzilla.biz/">Website Design by Monzilla Media</a>';
} 
add_filter('admin_footer_text', 'custom_admin_footer');

// enable html markup in user profiles
remove_filter('pre_user_description', 'wp_filter_kses');

// delay feed update
function publish_later_on_feed($where) {
	global $wpdb;

	if (is_feed()) {
		// timestamp in WP-format
		$now = gmdate('Y-m-d H:i:s');

		// value for wait; + device
		$wait = '5'; // integer

		// http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_timestampdiff
		$device = 'MINUTE'; // MINUTE, HOUR, DAY, WEEK, MONTH, YEAR

		// add SQL-sytax to default $where
		$where .= " AND TIMESTAMPDIFF($device, $wpdb->posts.post_date_gmt, '$now') > $wait ";
	}
	return $where;
}
add_filter('posts_where', 'publish_later_on_feed');

// admin link for all settings
function all_settings_link() {
	add_options_page(__('All Settings'), __('All Settings'), 'administrator', 'options.php');
}
add_action('admin_menu', 'all_settings_link');

// remove nofollow from comments
function xwp_dofollow($str) {
	$str = preg_replace(
		'~<a ([^>]*)\s*(["|\']{1}\w*)\s*nofollow([^>]*)>~U',
		'<a ${1}${2}${3}>', $str);
	return str_replace(array(' rel=""', " rel=''"), '', $str);
}
remove_filter('pre_comment_content',     'wp_rel_nofollow');
add_filter   ('get_comment_author_link', 'xwp_dofollow');
add_filter   ('post_comments_link',      'xwp_dofollow');
add_filter   ('comment_reply_link',      'xwp_dofollow');
add_filter   ('comment_text',            'xwp_dofollow');

// count words in posts
function word_count() {
	global $post;
	echo str_word_count($post->post_content);
}

// spam & delete links for all versions of wordpress
function delete_comment_link($id) {
	if (current_user_can('edit_post')) {
		echo '| <a href="'.get_bloginfo('wpurl').'/wp-admin/comment.php?action=cdc&c='.$id.'">del</a> ';
		echo '| <a href="'.get_bloginfo('wpurl').'/wp-admin/comment.php?action=cdc&dt=spam&c='.$id.'">spam</a>';
	}
}

/* disable all feeds
function fb_disable_feed() {
	wp_die(__('<h1>Feed not available, please visit our <a href="'.get_bloginfo('url').'">Home Page</a>!</h1>'));
}
add_action('do_feed',      'fb_disable_feed', 1);
add_action('do_feed_rdf',  'fb_disable_feed', 1);
add_action('do_feed_rss',  'fb_disable_feed', 1);
add_action('do_feed_rss2', 'fb_disable_feed', 1);
add_action('do_feed_atom', 'fb_disable_feed', 1); */

// customize default gravatars
function custom_gravatars($avatar_defaults) {

	// change the default gravatar
	$customGravatar1 = get_bloginfo('template_directory').'/images/gravatar-01.png';
	$avatar_defaults[$customGravatar1] = 'Default';

	// add a custom user gravatar
	$customGravatar2 = get_bloginfo('template_directory').'/images/gravatar-02.png';
	$avatar_defaults[$customGravatar2] = 'Custom Gravatar';

	// add another custom gravatar
	$customGravatar3 = get_bloginfo('template_directory').'/images/gravatar-03.png';
	$avatar_defaults[$customGravatar3] = 'Custom gravatar';

	return $avatar_defaults;
}
add_filter('avatar_defaults', 'custom_gravatars');

// disable auto formatting in posts
function my_formatter($content) {
	$new_content = '';
	$pattern_full = '{(\[raw\].*?\[/raw\])}is';
	$pattern_contents = '{\[raw\](.*?)\[/raw\]}is';
	$pieces = preg_split($pattern_full, $content, -1, PREG_SPLIT_DELIM_CAPTURE);

	foreach ($pieces as $piece) {
		if (preg_match($pattern_contents, $piece, $matches)) {
			$new_content .= $matches[1];
		} else {
			$new_content .= wptexturize(wpautop($piece));
		}
	}

	return $new_content;
}
remove_filter('the_content', 'wpautop');
remove_filter('the_content', 'wptexturize');
add_filter('the_content', 'my_formatter', 99);

// escape html entities in comments
function encode_code_in_comment($source) {
	$encoded = preg_replace_callback('/<code>(.*?)<\/code>/ims',
	create_function('$matches', '$matches[1] = preg_replace(array("/^[\r|\n]+/i", "/[\r|\n]+$/i"), "", $matches[1]); 
	return "<code>" . htmlentities($matches[1]) . "</"."code>";'), $source);
	if ($encoded)
		return $encoded;
	else
		return $source;
}
add_filter('pre_comment_content', 'encode_code_in_comment');

// custom comments callback function
function custom_comments_callback($comment, $args, $depth) {
	$GLOBALS['comment'] = $comment; ?>

	<li <?php comment_class(); ?> id="comment-<?php comment_ID(); ?>">
		<div>
			<?php echo get_avatar(get_comment_author_email(), $size = '50', $default = bloginfo('stylesheet_directory').'/images/gravatar.png'); ?>

			<div>
            			<?php printf(__('%s'), get_comment_author_link()); ?> &ndash; <a href="<?php echo htmlspecialchars(get_comment_link($comment->comment_ID)); ?>"><?php comment_date('F j, Y'); ?> @ <?php comment_time(); ?></a><?php edit_comment_link('Edit', ' &ndash; ', ''); ?>
			</div>
			<?php if ($comment->comment_approved == '0') : ?>

			<p><?php _e('Your comment is awaiting moderation.'); ?></p>
			<?php endif; ?>

			<div><?php comment_text(); ?></div>

			<div id="comment-reply-<?php comment_ID(); ?>">
				<?php comment_reply_link(array_merge($args, array('reply_text'=>'Reply', 'login_text'=>'Log in to Reply', 'add_below'=>'comment-reply', 'depth'=>$depth, 'max_depth'=>$args['max_depth']))); ?> 

			</div>
		</div>

<?php } // WP adds the closing </li>

?>

Download the custom functions.php template file

If you prefer, you can download this code in a ready-to-go functions.php file in zipped format:

Download zipped functions.php Template File

Also..

If this post is useful to DiW readers, I’ll be writing up yet another article (part 3!) with even more useful custom functions. If that happens, the third set of functions will include some slightly more advanced techniques. Stay tuned!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s