Subscribe to
Posts
Comments
NSLog(); Header Image

Fixing “Read More” Links in WordPress

WordPress uses a template tag function called the_content() ((Line 54 of /wp-includes/template-functions-post.php in version 2.0.5.)) to publish content. This function does little more than call another function, get_the_content() ((Eight lines down in the same file.)).

The get_the_content() function is responsible for, among other things, splitting up WordPress posts at the <!--more--> delimiter. It creates HTML that looks like this:

<p>Last paragraph of main entry<br /><a href="http://permalink">ReadMoreText</a></p>

The optional parameters (there are three, but only two work in WP 2.0) allow you to change the "ReadMoreText" in the example output above. The problem? Not only is the link missing a title, but the text is all you can control.

WordPress' built-in "wpautop" function controls how posts are formatted with <p> and <br /> tags. A single line break gets a break and double breaks - two line ending characters - are wrapped in paragraph tags. When you submit a post from nearly any third-party blogging tool (like) Ecto, it submits both an "entry body" and "extended entry." WordPress converts these to a format matching this:

entry_body\n<!--more-->\nextended_entry

When it later comes time for get_the_content(), the function dutifully explode()s on the <!--more-->, leaving a trailing "\n" in the body and one at the beginning of the extended entry ((This is significant because WordPress later double-<br />s these "\n"s with an <a href="more"></a> link in between. In other words, the paragraph break across your entry body and extended entry aren't true paragraph breaks, but two line breaks)). Next, it puts the title-less <a href=""> in ((Line 95 of /wp-includes/template-functions-post.php in WP 2.0.5.)):

$output .= ' <a href="'. get_permalink() . "#more-$id\">$more_link_text</a>";

Not only are links created without a title, but this approach is less than ideal for a couple of reasons:

  1. People who want to put "read more…" links on the same line as their last sentence cannot because the "wpautop" inserts a <br /> tag.
  2. People who want their "Read More…" links in their own paragraph can do so, but the output is ugly HTML.

As I said above, only "ReadMoreText" can be replaced. The the_content() function will happily accept text like this:

the_content('</p><p class="readmorelink">Read More');

Unfortunately, that does just what you'd expect:

<p>Last paragraph of main entry<br /><a href="http://permalink"></p><p class="readmorelink">Read More</a></p>

In this case, the anchor tag spans across paragraphs. Ick. If you're not afraid of modifying core WordPress files (in this case, /wp-includes/template-functions-post.php), you can do something like this:

$output .= '</p><p class="readmorelink"><a href="'. get_permalink() . "#more-$id\" title=\"".the_title('','',false)."\">$more_link_text</a>";

That produces HTML that resembles this:

<p>Last paragraph of main entry.</p>
<p class="readmorelink"><a href="http://permalink#more-xxxx" title="Title Here">Read More</a></p>

That's acceptable. If you want your "read more" links in the same paragraph as the final paragraph of your entry body, you'll want to employ this change instead:

$output .= ' <span class="readmorelink"><a href="'. get_permalink() . "#more-$id\" title=\"".the_title('','',false)."\">$more_link_text</a></span>";

or

$output .= ' <a href="'. get_permalink() . "#more-$id\" title=\"".the_title('','',false)."\" class=\"readmorelink\">$more_link_text</a></span>";

Unfortunately, without testing for and removing the final two "\n" characters at the end and beginning of the entry body and extended entry respectively, you'll get extra <br /> tags. In this example, your link would be on a different line (but within the same set of <p> tags.). You can remove (actually, move) the links like this:

Change this (line 90):
$output .= $teaser;
to this:
$output .= ("\n" == substr($teaser, -1, 1)) ? substr($teaser, 0, -1) : $teaser;

And change this (line 93):
$output .= '<a id="more-'.$id.'"></a>'.$content[1];
to this:
$output .= '<a id="more-'.$id.'"></a>' . "\n".$content[1];

That performs a ternary test on the raw, un-wpautoped text to remove the trailing "\n" characters from the entry body and to double them up in the extended entry. Because this eliminates the problem noted in the third footnote, I've left this code in my copy even though I use the paragraph version of the "read more" link ((The only side effect? That your <a href="#more-xxxx"> link appears in the final paragraph of the entry body on the permalink page. That is no worse, if you think about it, than not actually having a separate paragraph across that boundary.)).

What To Do About It?
I'm too new to WordPress to know whether this function can be modified via a plugin. It's not a "pluggable" function, so that rules that out. I'm not keen on modifying core files, either.

In the end, and to maintain backwards compatibility, I think the best approach to solving this problem would be for WordPress to alter the the_content() function. Currently, as I said, it takes three variables. It should take four:

<?php the_content('more_link_text', strip_teaser, 'more_file', 'link_type');?>

The additional "link_type" variable could be set to false by default and WordPress would continue to create links as it does now. If this is set to "endp" it could create the link at the end of the "teaser" or "entry body" paragraph. If it's set to "newp" then WordPress would wrap the link and text in its own paragraph. Perhaps "endp:class" and "newp:class" could be used to specify the class the span or p tags would use ((I'm not familiar enough with WordPress's internal code formatting to know if this is a valid method of formatting, but I'm trying to point out how the functionality could be improved, not necessarily the exact way in which the code should be written.)).

I think the function should also be modified to add the title attribute to links and to remove the trailing "\n" in every instance, as it offers nothing of benefit.

I'm not sure where to file such a bug report within WordPress, so I'll post this and ask around. The WordPress folks are free, naturally, to take my suggestions, take any of the code above, and use any of it without restriction.

6 Responses to "Fixing “Read More” Links in WordPress"

  1. The way to fix this (so that a WP-upgrade doesn't undo your work) is to use the WP filter system.

    add_filter('the_content', 'nslog_read_more', 7);

    You can see where filters registered to the_content are applied by looking for the apply_filters(); call for the_content Note that the name the_content is meant to correspond to the_content() but that doesn't mean that every function automatically has a filter.

    I have a quasi-documented list of filters here.

    The nslog_read_more() function should take a string input (it'll get the content of the post) and should return that content (after modification, of course).

    The third param (7) is a priority for the filter. Default priority is 10. I've set this to 7 because at 10, wpautop() kicks in. So for the preg_replace() calls in nslog_read_more(), you'll be dealing with linebreaks, not paragraph/break tags. If you wanted to deal with the HTML, you could hook in using a priority of 11.

  2. [quote comment="23042"]The way to fix this (so that a WP-upgrade doesn't undo your work) is to use the WP filter system.[/quote]

    Only some of this can be done via a plugin. Variables like $more_link_text are never in scope, so I have no way to easily determine the link's text (<a href="http://permalink"></p><p class="readmorelink">Read More</a>), so I have no way to search for that string and replace it. I could probably do so with preg_replace, but that approach seems quite easily broken since $more_link_text can contain HTML.

    I've made a plugin that removes (most of) the line breaks, though, so that's a start.

  3. [quote comment="23091"]I've made a plugin that removes (most of) the line breaks, though, so that's a start.[/quote]

    Turns out I was able to hack around enough and get a plugin that works on my system, at least. I've released it as a version 0.1 plugin named BetterWPReadMore.

  4. [...] A day after complaining about the way WordPress handles "read more" links, I've cobbled together a plugin I've named "BetterWPReadMore." It's available from a modified "Plugins" tab at the top of every page. [...]

  5. I was reading some questions like this and the best answer I got was that wordpress has to put the more in its own element so there is no way to have it on the same line. However I discovered that if you don't tag your paragraphs with a in the html of the admin post section wordpress adds them for you. It may be auto matic or because I have the setting>General>WordPress should correct invalidly nested XHTML automatically box checked any way I do this and just put the more tag at the end of the sentence that I want it and word press nests them together.