Need a WordPress website this weekend? Start here...

Estimated Reading Time Plugin : Sharpening the stick (technical tuesday)

(Reading time: 6 – 10 minutes)

Tim Ferriss has a lot to say about blogging. In fact, he can talk for an hour straight about it, as I found out last year at WordCamp 2009 in San Francisco.

I learned about Tim’s strategy, and he gave us a few of his tactics and techniques. One of Tim’s really interesting tactics for treating readers with respect was providing an estimate of how much time it might take them to read an article. For those unacquainted with Tim’s writing, sometimes it’s long, 3000 words or more long.

These days, I’m starting to see a lot more blogs with an estimated reading time displayed. Back last June, a whole year ago, there weren’t very many. There are surely several ways to implement an estimated reading time; being WordPress, there is a plugin.

And so, once again, a Technical Tuesday, where we break down one of Website In A Weekend’s most popular plugins: Estimated Reading Time.

But…

Before we get started, (IMPORTANT →) You Do Not Need To Read The Code To Get The Point.

You do, however, need to scan over the code. Take it in as a “block.” Notice it’s not very long.

The Estimated Reading Time plugin

The Estimated Reading Time plugin is pretty short, just a couple of dozen lines. If this hurts your eyeballs, by all means jump right over the code.

Otherwise, check it out in all it’s PHP glory:

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
if (!function_exists('est_read_time')):
function est_read_time( $return = false) {
	$wordcount = round(str_word_count(get_the_content()), -2);
	$minutes_fast = ceil($wordcount / 250);
	$minutes_slow = ceil($wordcount / 150);
 
	if ($wordcount <= 150) {
		$output = __("Reading time: < 1 minute");
	} else {
		$output = sprintf(__("Reading time: %s - %s minutes"), $minutes_fast, $minutes_slow);
	}
	if ($return) {
		return '<p class="estread">' . $output . '</p>';
	} else {
		echo '<p class="estread">' . $output . '</p>';
	}
}
endif;
 
if (!function_exists('est_the_content')):
function est_the_content( $orig ) {
	// Prepend the reading time to the post content
	return est_read_time(true) . "\n\n" . $orig;
}
endif;
 
if (function_exists('add_filter')):
   // Set this to priority 9 so it's called before wptextuarize/wpautop/etc
   add_filter('the_content', 'est_the_content', 9); 
endif;

That’s not bad, 30 lines of pure D elegance.

Just for fun, let’s break it down… (no? Go for the goodies?)

The break down

1
2
if (!function_exists('est_read_time')):
function est_read_time( $return = false) {

Let’s check to see if this function for estimated reading time already exists in WordPress, perhaps in another plugin. If so, skip over it.

If not, do some computation.

3
4
5
	$wordcount = round(str_word_count(get_the_content()), -2);
	$minutes_fast = ceil($wordcount / 250);
	$minutes_slow = ceil($wordcount / 150);

These simple computations are the heart of the plugin, and if you’ve noticed the “2-2″ bug, this computation is where that occurs. Here’s how it works:

  1. After counting the words with str_word_count, the PHP round function with argument -2 multiplies the result by 100.
  2. Then, dividing by 250 and 150, respectively, gives a range, and the ceil function returns the next highest integer from the division.

Not hard, right?

Let’s figure out what to show the reader:

7
8
9
10
11
	if ($wordcount <= 150) {
		$output = __("Reading time: < 1 minute");
	} else {
		$output = sprintf(__("Reading time: %s - %s minutes"), $minutes_fast, $minutes_slow);
	}

Here’s the other half of the “2-2 minutes” bug. Fixing it won’t be hard, will just take a little time examining how round and ceil work, or kludging in a fix by checking to see if $minutes_fast equals $minutes_slow.

Once we know what we want to display…

12
13
14
15
16
17
18
	if ($return) {
		return '<p class="estread">' . $output . '</p>';
	} else {
		echo '<p class="estread">' . $output . '</p>';
	}
}
endif;

This means that when the function is called from some other part of WordPress, it will return a string to wherever it’s wanted. Else, it prints the estimated reading time string straight to your screen.

Note that “estread” class is added automatically. This CSS currently has to be added manually to your style sheet. (custom.css if you use Thesis) In the near future, the plugin will have it’s own style sheet.

Here’s where we set up for the hook…

12
13
14
15
16
17
if (!function_exists('est_the_content')):
function est_the_content( $orig ) {
	// Prepend the reading time to the post content
	return est_read_time(true) . "\n\n" . $orig;
}
endif;

Calling est_reading_time with true. We want the string back because…

12
13
14
15
if (function_exists('add_filter')):
   // Set this to priority 9 so it's called before wptextuarize/wpautop/etc
   add_filter('the_content', 'est_the_content', 9); 
endif;

…we’re adding the string with the reading time directly to the content.

Easy, right?

“But why,” you might ask, “should I bother myself with these tedious details?”

That’s an excellent question, and you, Website In A Weekend reader, deserve an answer.

Ripping back the (plugin) veil

Long time readers (bofem) recall that I’ve published similar code and plugin breakdowns in the past. Just last week, we tore into the SEO Slugs plugin. A month or two past, we had the world’s shortest plugin.

Stay with me here…

If you think I might demonstrating something (more than just showing off even though I am sort of showing off, at least a little bit), you might be right.

I’m setting the stage for a nice big twist of Blogistan’s collective nose. Remember this old saw “Too many plugins is bad and makes your blog slow.”?

Who says so? Why do they say it? How do they know, who granted their authority and why should I believe what J. Random Blogger says about plugins, loading speed, or any other damn thing about blogging?

More important, why should you believe it?

Rule of thumb: If it didn’t come directly from a WordPress core developer, or plugin author with years of experience, or a professional webmaster, take it with a grain of salt. Maybe a big lump of salt.

Worse yet, even if you get your information straight from the horse’s mouth (so to speak), it may be suspect. I’ll go deeper into “why” in the future.

Let’s wrap this up.

Do you need estimated reading time?

This plugin is pretty small, and it’s not going to put much of a load on your WordPress installation (almost none, really), so using it won’t cost much more than installing it. But before you “just install it,” note that my primary reason for using it is because the length of my articles varies wildly between roughly 1 minute reading time to over 20 minutes reading time.

Further, the topics that I write on vary between highly accessible to highly technical.

Because of this wide range of length and topical difficulty in my blog posts, letting the reader know what’s in store just seems polite. And it works. Readers have commented that having some notion of their commitment ahead of time is very helpful.

Seeing a short post (1 – 2 minutes) may bring a reader in who might otherwise pass. Seeing a long post, a reader might choose to go get a cup of coffee before starting to read (I say this because this is what I’ve been told by a reader in the past.)

Displaying an estimated reading may or may not be useful for you. If you write articles of uniform length, your readers always know what to expect, reading time is superfluous. Likewise, if your articles are fairly well-focused and written in a uniform voice, your articles can be any length, from 100 to 3000 words, and your readers will be there for you regardless of article length.

Also. Remember why I do this stuff: Talent is Overrated, I write to learn, and deliberate practice is the only sure path to mastery.

Really Fast SEO Slugs with WordPress plugin (Technical Tuesday)

(Reading time: 12 – 19 minutes)

I was talking to Julie Roads the other day about post slugs, and it occurred to me I haven’t written on post slugs for quite a while. In fact, while it’s on my mind, go visit Julie and get yourself some refresh and reload.

Now that you’ve wiped your slate clean, let’s reload it with Stuff You Need To Know. Like post slugs.

Writing slate from the 19th century - Interactive Ancient History from Historic Connections

Life without paper - Interactive Ancient History from Historic Connections

Although I discuss post slugs in Blog Post Engineering in some detail, much of what follows is new material. Some of this may go into the next release of Blog Post Engineering as well.

It turns out there exists a spiffy little plugin called “SEO Slugs,” which will:

  • Help you create shorter, keyword-rich slugs using your article title.
  • Will not automatically change your title, so you have ultimate control (as it should be).

The SEO Slugs plugin is very old and hasn’t been updated in years. I usually recommend NOT using older plugins (for good reason), but as you will see, there isn’t anything out of date in SEO Slugs. (That’s either a testament to the strength of WordPress design, or an indictment to it’s lack of innovation. I could argue both sides. I’ll spare us all.)

This plugin is cool, because you don’t really have to do anything, it just works quietly in the background:

SEO Slugs plugin works automatically to remove stopwords

SEO Slugs plugin works automatically to remove stopwords

Since it’s (Technical) Tuesday, it’s a perfect opportunity to get all hard core about it too. So I’m going to break this stepwise for you. You will see how I attack plugin problems, and how this particular plugin works to your benefit.

SEO Slugs plugin

First, if code makes your eyes glaze over, just skip right over to where I break this plugin down one piece at a time.

Since SEO Slugs is really short, I’m listing out all 43 lines right here:

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
38
39
40
41
42
43
44
<?php
/*
Plugin Name: SEO Slugs
Plugin URI: http://www.vretoolbar.com/news/seo-slugs-wordpress-plugin
Description: Removes common words like 'a', 'the', 'in' from post slugs to improve SEO.
Version: 1.0
Author: Andrei Mikrukov
Author URI: http://www.vretoolbar.com
*/
 
/*
Copyright Andrei Mikrukov 2007
 
Licensed under the terms of the GPL version 2, see:
http://www.gnu.org/licenses/gpl.txt
 
Provided without warranty, inluding any implied warrant of merchantability or fitness for purpose.
*/
 
add_filter('name_save_pre', 'seo_slugs', 0);
 
function seo_slugs($slug) {
 
    // We don't want to change an existing slug
    if ($slug) return $slug;
 
    global $wpdb;
    $seo_slug = strtolower(stripslashes($_POST['post_title']));
 
    $seo_slug = preg_replace('/&.+?;/', '', $seo_slug); // kill HTML entities
    // kill anything that is not a letter, digit, space or apostrophe
    $seo_slug = preg_replace ("/[^a-zA-Z0-9 \']/", "", $seo_slug);
    // Turn it to an array and strip common words by comparing against c.w. array
    $seo_slug_array = array_diff (split(" ", $seo_slug), seo_slugs_stop_words());
    // Turn the sanitized array into a string
    $seo_slug = join("-", $seo_slug_array);
 
    return $seo_slug;
}
 
function seo_slugs_stop_words () {
    return array ("a", "able", "about", "above", "abroad", "according", "accordingly", "across", "actually", "adj", "after", "afterwards", "again", "against", "ago", "ahead", "ain't", "all", "allow", "allows", "almost", "alone", "along", "alongside", "already", "also", "although", "always", "am", "amid", "amidst", "among", "amongst", "an", "and", "another", "any", "anybody", "anyhow", "anyone", "anything", "anyway", "anyways", "anywhere", "apart", "appear", "appreciate", "appropriate", "are", "aren't", "around", "as", "a's", "aside", "ask", "asking", "associated", "at", "available", "away", "awfully", "b", "back", "backward", "backwards", "be", "became", "because", "become", "becomes", "becoming", "been", "before", "beforehand", "begin", "behind", "being", "believe", "below", "beside", "besides", "best", "better", "between", "beyond", "both", "brief", "but", "by", "c", "came", "can", "cannot", "cant", "can't", "caption", "cause", "causes", "certain", "certainly", "changes", "clearly", "c'mon", "co", "co.", "com", "come", "comes", "concerning", "consequently", "consider", "considering", "contain", "containing", "contains", "corresponding", "could", "couldn't", "course", "c's", "currently", "d", "dare", "daren't", "definitely", "described", "despite", "did", "didn't", "different", "directly", "do", "does", "doesn't", "doing", "done", "don't", "down", "downwards", "during", "e", "each", "edu", "eg", "eight", "eighty", "either", "else", "elsewhere", "end", "ending", "enough", "entirely", "especially", "et", "etc", "even", "ever", "evermore", "every", "everybody", "everyone", "everything", "everywhere", "ex", "exactly", "example", "except", "f", "fairly", "far", "farther", "few", "fewer", "fifth", "first", "five", "followed", "following", "follows", "for", "forever", "former", "formerly", "forth", "forward", "found", "four", "from", "further", "furthermore", "g", "get", "gets", "getting", "given", "gives", "go", "goes", "going", "gone", "got", "gotten", "greetings", "h", "had", "hadn't", "half", "happens", "hardly", "has", "hasn't", "have", "haven't", "having", "he", "he'd", "he'll", "hello", "help", "hence", "her", "here", "hereafter", "hereby", "herein", "here's", "hereupon", "hers", "herself", "he's", "hi", "him", "himself", "his", "hither", "hopefully", "how", "howbeit", "however", "hundred", "i", "i'd", "ie", "if", "ignored", "i'll", "i'm", "immediate", "in", "inasmuch", "inc", "inc.", "indeed", "indicate", "indicated", "indicates", "inner", "inside", "insofar", "instead", "into", "inward", "is", "isn't", "it", "it'd", "it'll", "its", "it's", "itself", "i've", "j", "just", "k", "keep", "keeps", "kept", "know", "known", "knows", "l", "last", "lately", "later", "latter", "latterly", "least", "less", "lest", "let", "let's", "like", "liked", "likely", "likewise", "little", "look", "looking", "looks", "low", "lower", "ltd", "m", "made", "mainly", "make", "makes", "many", "may", "maybe", "mayn't", "me", "mean", "meantime", "meanwhile", "merely", "might", "mightn't", "mine", "minus", "miss", "more", "moreover", "most", "mostly", "mr", "mrs", "much", "must", "mustn't", "my", "myself", "n", "name", "namely", "nd", "near", "nearly", "necessary", "need", "needn't", "needs", "neither", "never", "neverf", "neverless", "nevertheless", "new", "next", "nine", "ninety", "no", "nobody", "non", "none", "nonetheless", "noone", "no-one", "nor", "normally", "not", "nothing", "notwithstanding", "novel", "now", "nowhere", "o", "obviously", "of", "off", "often", "oh", "ok", "okay", "old", "on", "once", "one", "ones", "one's", "only", "onto", "opposite", "or", "other", "others", "otherwise", "ought", "oughtn't", "our", "ours", "ourselves", "out", "outside", "over", "overall", "own", "p", "particular", "particularly", "past", "per", "perhaps", "placed", "please", "plus", "possible", "presumably", "probably", "provided", "provides", "q", "que", "quite", "qv", "r", "rather", "rd", "re", "really", "reasonably", "recent", "recently", "regarding", "regardless", "regards", "relatively", "respectively", "right", "round", "s", "said", "same", "saw", "say", "saying", "says", "second", "secondly", "see", "seeing", "seem", "seemed", "seeming", "seems", "seen", "self", "selves", "sensible", "sent", "serious", "seriously", "seven", "several", "shall", "shan't", "she", "she'd", "she'll", "she's", "should", "shouldn't", "since", "six", "so", "some", "somebody", "someday", "somehow", "someone", "something", "sometime", "sometimes", "somewhat", "somewhere", "soon", "sorry", "specified", "specify", "specifying", "still", "sub", "such", "sup", "sure", "t", "take", "taken", "taking", "tell", "tends", "th", "than", "thank", "thanks", "thanx", "that", "that'll", "thats", "that's", "that've", "the", "their", "theirs", "them", "themselves", "then", "thence", "there", "thereafter", "thereby", "there'd", "therefore", "therein", "there'll", "there're", "theres", "there's", "thereupon", "there've", "these", "they", "they'd", "they'll", "they're", "they've", "thing", "things", "think", "third", "thirty", "this", "thorough", "thoroughly", "those", "though", "three", "through", "throughout", "thru", "thus", "till", "to", "together", "too", "took", "toward", "towards", "tried", "tries", "truly", "try", "trying", "t's", "twice", "two", "u", "un", "under", "underneath", "undoing", "unfortunately", "unless", "unlike", "unlikely", "until", "unto", "up", "upon", "upwards", "us", "use", "used", "useful", "uses", "using", "usually", "v", "value", "various", "versus", "very", "via", "viz", "vs", "w", "want", "wants", "was", "wasn't", "way", "we", "we'd", "welcome", "well", "we'll", "went", "were", "we're", "weren't", "we've", "what", "whatever", "what'll", "what's", "what've", "when", "whence", "whenever", "where", "whereafter", "whereas", "whereby", "wherein", "where's", "whereupon", "wherever", "whether", "which", "whichever", "while", "whilst", "whither", "who", "who'd", "whoever", "whole", "who'll", "whom", "whomever", "who's", "whose", "why", "will", "willing", "wish", "with", "within", "without", "wonder", "won't", "would", "wouldn't", "x", "y", "yes", "yet", "you", "you'd", "you'll", "your", "you're", "yours", "yourself", "yourselves", "you've", "z", "zero");
}
?>

Spiffy, right? I love little pieces of code like this. Pure poetry.

Let’s prise it apart and ponder it piecewise.

Let’s break it down

Once again, if you’re getting queasy, just skip right over this part to get to the goodies at the end.

And once again, why all this trouble? Couple of reasons: SEO Slugs is very short, so “Because I can” is really all the justification I need. But another, more important reason is that it illustrates a very important and powerful capability of WordPress, in a context that most intermediate WordPress bloggers can understand. That is, you. (And that includes you too Mr. El Dorado)

Start at the beginning…

…which is usually the best place to start.

add_filter is a critical WordPress function

In this case, on line 20 we’re kicking off with add_filter, one of the top five most important functions in WordPress. Dig into the WordPress code, add_filter is everywhere you look. Even with relatively benign functions such as register_activation_hook, scratch it deep enough, there’s a daggone add_filter lurking down in the basement.

20
add_filter('name_save_pre', 'seo_slugs', 0);

Here’s what add_filter does: it takes a function, here given by seo_slugs declared on line 22, and adds that function to the list of functions which get called when the name_save_pre template is invoked. That is, when you press “Save Draft,” the seo_slugs function processes your article title and emits a tasty new post slug full of keyword-rich scrumptiousness.

But suppose you like your post slug. It’s quite tasty enough, thank you. That’s cool, SEO Slugs will keeps it mitts to itself. Here’s proof:

24
25
    // We don't want to change an existing slug
    if ($slug) return $slug;

All this means is that when you put your own slug in the little box, you get to keep it:

Use your own post slug.

Use your own post slug.

What’s next…

27
28
    global $wpdb;
    $seo_slug = strtolower(stripslashes($_POST['post_title']));

wpdb is your WordPress database. It’s another one of those things that’s everywhere, you need to make it’s acquaintance sooner or later.

The next function, strtolower takes your title and turns all your capital letters into lower case letters. Actually, it takes your title after it does a little magic on it with stripslashes, but let’s leave that rabbit hole for another day.

Next, if you have some of those cool HTML symbols in your title, like hearts ♥s and stuff , we have to remove those for the post slug:

30
    $seo_slug = preg_replace('/&.+?;/', '', $seo_slug); // kill HTML entities

preg_replace is easy: Perl-compatible regular expression, replace all matching instances.

You know how I always say, “Simple, but not easy”? Well, this is easy, but not simple.

Let’s get rid of more cruft people like me put in our blog post titles, like punctation marks, etc.

31
32
    // kill anything that is not a letter, digit, space or apostrophe
    $seo_slug = preg_replace ("/[^a-zA-Z0-9 \']/", "", $seo_slug);

Self-explanatory, if you grok regular expressions. ^a-zA-Z0-9 means get rid of everything not a through z, A through Z or 0 through 9. Easy peasy. (But not simple.)

Here’s where we get to the good stuff. Our post slug is all prepped, now it’s time to strip out the stop words. Words like “a” and “the” and “and” which are boring and unrankable. Seriously, trying to rank for the word “the” would be pretty hard.

31
32
33
34
35
36
    // Turn it to an array and strip common words by comparing against c.w. array
    $seo_slug_array = array_diff (split(" ", $seo_slug), seo_slugs_stop_words());
    // Turn the sanitized array into a string
    $seo_slug = join("-", $seo_slug_array);
 
    return $seo_slug;

Picking up the pace, line 32 matches your title against an array of stop words, and deletes everything in your title that is also in the array. Line 34 puts it all back together, this is what you see in your permalink after you save or publish your blog post. Line 36 is the seo_slugs function giving your brand new slug back to WordPress.

And there you have it, the entire SEO Slugs plugin.

Give it try, see what you think.

UPDATE: Don’t know why I didn’t think of this before.

“Why all this toil and trouble?”

That’s a pretty good question.

In part, I’m showing you exactly what I do when I teach myself. I just go through the code one line at a time, learn what each line does, then (the hard part) figure out why that line is necessary.

Understanding source code requires an investment of time and mental energy. While I eat this stuff two or three meals per day, it’s not quite to some people’s taste, and others are downright allergic to the very notion.

But there are rewards.

Blog Post Engineering!

One of which is overcoming all fear of WordPress coding. Well, at least most fear. There is some scary sh*t in the WordPress core, but you never need look at that if you just stick to WordPress plugins.

Knowledge builds on itself incrementally. Learning a half dozen of the most common functions you find in plugins goes a long way to stomping that learning curve flat. This is usually irrelevant, I admit, except when it’s Zero Dark Thirty and your blog is down. And you don’t know why. Then knowing a little WordPress seems pretty smart.

In any case, between this article and Blog Post Engineering, you have a grip on post slugs. Provided you have a grip on Blog Post Engineering… Add to Cart

Lagniappe

July 17, 2010, and I’ve put a full week into the next release of Blog Post Engineering. Julie Angelos just pinged Top 10 Reasons WordPress is the future of the internet, with a CommentLuv link looking like this:ㄥ❀∨モ criticism ♥.

How could anyone resist that?

Now, I asserted that SEO Slugs removes fancy pants HTML entities, and it does. But you can still have such beasties in your post slug, just paste them in manually.

Here’s proof:

I have no idea what the effect on SEO is with such a spiffy little post slug. Neither do I care! Once in a while, just have some fun tweaking the rules.