Syndicate

Feed

The read more link in teaser hack

Unlike Wordpress, Drupal offers no way in core to put a read more link inline with the teaser. Out-of-the-box Drupal’s teaser customizing is very basic. You may either 1- insert in your posting an HTML comment à la Wordpress, <!--break-->, to tell Drupal where you want your teaser to end (in Wordpress, you’ll type the Quicktag <!--more--> instead), or 2- you leave it up to Drupal to make a teaser out of the n first words of the post. Drupal will take that teaser and the full content and store them separately in the database. In Wordpress, by default, you’ll find a “more...” link at the end of the teaser, inviting you to read the full posting. Drupal does offer a “read more” link, but you’ll find it with the other posting links, away from the teaser. Chances are : readers will miss that link and believe that the teaser is all there is to read in the posting.

This article describes a simple hack to remedy the situation. It works in Drupal 4.7 and 5.

UPDATE April 8th, 2008 : it works in Drupal 6 as well. Make sure your code accounts for the change in the l() function signature. For this, refer yourself to the Drupal API — follow the link.

Decide on which HTML text you want to display as your read more link. Will that be “more...” like in Wordpress, or “more »»” ? That text will become YOUR_TEXT_FOR_THE_LINK in the hack below. If you need to use a special character, use an entity :

Character you want to display Corresponding entity
» &raquo;
&rarr;
&rArr;

In a text editor, open the file modules/node/node.module if you’re using Drupal 5.x, or, alternatively, modules/node.module if you’re using Drupal 4.7.x.

Look for the definition of the function node_prepare(). Once you find it, add to it the following line of code :

<?php
function node_prepare($node, $teaser = FALSE) {
 
$node->readmore = (strlen($node->teaser) < strlen($node->body));

  if (
$teaser == FALSE) {
   
$node->body = check_markup($node->body, $node->format, FALSE);
  }
  else {
   
/* Beginning of hack -- modified by YOUR_NAME */
   
if($node->readmore) {
     
$node->teaser .= '&nbsp;'. l(t('YOUR_TEXT_FOR_THE_LINK'),
       
'node/' . $node->nid, array('class' => 'read-more',
       
'title' => 'Read the rest of this posting.'),
       
NULL, NULL, TRUE, TRUE);
    }
   
/* End of hack */
   
$node->teaser = check_markup($node->teaser, $node->format, FALSE);
  }

 
$node->content['body'] = array(
   
'#value' => $teaser ? $node->teaser : $node->body,
   
'#weight' => 0,
  );

  return
$node;
}
?>

Do you want to remove the existing read more link that’s added by Drupal to the posting links ? Add two lines of code instead :

<?php
function node_prepare($node, $teaser = FALSE) {
 
$node->readmore = (strlen($node->teaser) < strlen($node->body));

  if (
$teaser == FALSE) {
   
$node->body = check_markup($node->body, $node->format, FALSE);
  }
  else {
   
/* Beginning of hack -- modified by YOUR_NAME */
   
if($node->readmore) {
     
$node->teaser .= '&nbsp;'. l(t('YOUR_TEXT_FOR_THE_LINK'),
       
'node/' . $node->nid, array('class' => 'read-more',
       
'title' => 'Read the rest of this posting.'),
       
NULL, NULL, TRUE, TRUE);
     
$node->readmore = FALSE; // To remove the default read more link
   
}
   
/* End of hack */
   
$node->teaser = check_markup($node->teaser, $node->format, FALSE);
  }

 
$node->content['body'] = array(
   
'#value' => $teaser ? $node->teaser : $node->body,
   
'#weight' => 0,
  );

  return
$node;
}
?>

Upload your edited file to your server.

Notes

There is one contributed module that does this, but it modifies the teaser after the teaser has been filtered, instead of before. That module is bloated, i.e. about 200 lines of code... while we’re only adding two lines here, with surgical precision and no performance hit.

The read more link we’re generating is a URL that points to the alias of the full posting as in http://11heavens.com/read-more-link. It’s very important to generate a URL here, rather than a web-root relative link like /read-more-link, because teasers are displayed in RSS feeds, on other web sites. We don’t want that read more link to point to a non-existant node on the other web site.

You may want to change the class and title attributes of the link. The title attribute in the code sample above is Read the rest of this posting. It is what’s used in English by default for the read more link that’s with the other node links, i.e. in $links.

In the l() call, we’re using TRUE as sixth (6th) argument because we want an absolute path, that is, a URL, for the link. And we’re adding TRUE as seventh (7th) argument if we’re using HTML entities in our read more link.

l($text, $path, $attributes = array(), $query = NULL, $fragment = NULL, $absolute = FALSE, $html = FALSE)

$absolute
Whether to force the output to be an absolute link (beginning with http:). Useful for links that will be displayed outside the site, such as in an RSS feed.
$html
Whether the title is HTML, or just plain-text. For example for making an image a link, this must be set to TRUE, or else you will see the encoded HTML. Same thing if you are using HTML entities, you need to set it to TRUE, or else you will see something like read&nbsp;more&nbsp;&rArr;.

Hacking the core

Personally, I hack the core when there is no other solution that I know of to fix something I dislike. If there is another solution, such as one that involves overriding a themeable function or modifying a template file, I’ll do that instead. Hacking the core makes it difficult to upgrade Drupal. After each upgrade, I have to go through my list of hacks and re-implement them as needed. I keep a list, a short list (I currently have 3 very simple hacks in core, one that is a fix to image.inc to improve the jpeg quality of generated images). In my list, I write down 1.- the file name, 2.- the number (#) of the edited line, and 3.- some description of what I have done and why. Additionally, I put my full name in the code in a comment next to the hack I implement (to make it easy to search for in my text editor). As my understanding of Drupal increases, some hacks are dropped. And I make sure to only implement simple hacks. UPDATE April 14th, 2007 : Hacking the core is neither for children, nor monkeys. I will not condescend to my readers by introducing this article with a huge blinking warning sign *CAUTION-WHAT-YOU-ARE-ABOUT-TO-DO-IS-DANGEROUS* because it simply is not dangerous. Whenever we believe that someone is not as smart as us, chances are the truth is opposite, and life will show us that. Have fun with your CMS, it’s all that matters.

Last edited by Caroline Schnapp about 9 years ago.

Comments

Not a partifcularly good suggestion (imho)...

Hacking the core is not a good idea because it breaks the upgrade path. Yeah you can keep notes, but wouldn't it just be a LOT easier to not break the path in the first place? Wouldn't it be better to write a small module which uses hook_nodeapi and picks up on the prepare op (check the $op arg). The odds of that module breaking between upgrades is very low indeed and you dont need to bother editing core.

If you like, i can explain this a little futher - but it should be pretty simple to figure out.

Maybe you can improve ed_readmore

Hacking the core is not a good idea because it breaks the upgrade path.

Your solution is not better because it involves the creation of a contributed module that needs to be upgraded too, when Drupal is upgraded. I have exactly 5 very simple hacks. If ones does this properly, it takes about 5 minutes to add the changes when ones upgrades Drupal, while ones has to wait for a contributed module to be upgraded by its maintainer.

The contributed module ed_readmore uses the hook_nodeapi on 'view' and 'rss_feed' (for drupal 5) and just 'view' (for drupal 4.7) and needs 179-209 lines of code to do the job. Maybe you can improve it. Maybe you can submit a patch for it.

I hear you on the 'upgrade path', and I agree with it, whole-heartedly, but it has not become a religion to me. Every situation has its best fix. For me this hack is the best fix... until a better solution comes along.

As the two previous comments

As the two previous comments pointed out hacking core modules is not a good idea, especially when it can be done another way. I actually had to do this yesterday. I needed to use "full story" for story nodes and "read more" for all other nodes. In that situation using .tpl files is much better I think, in my case node-story.tpl. I use the tip pointed out in this comment.

As with most cases in Drupal there is more than one way to achieve what you want, choose the one that suits you best I guess.

Cheers.

Nesta

In that situation using .tpl files is much better I think, in my case node-story.tpl. I use the tip pointed out in this comment.

That's a very good trick. Note however that you will not get a link inline with the teaser that way. Your link will come after a closing block tag. The content markup has been cleened up once it gets to your template file, that's why. It has received a closing (block) tag. It has been filtered with a check_markup().

Unlike Wordpress, Drupal

Unlike Wordpress, Drupal offers no way in core to put a read more link inline with the teaser. Out-of-the-box Drupal’s teaser customizing is very basic. You may either 1- insert in your posting an HTML comment à la Wordpress, , to tell e-papierosy Drupal where you want your teaser to end (in Wordpress, you’ll type the Quicktag instead), or 2- you leave it up to Drupal to make a teaser out of the n first words of the post. Drupal e-papierosy will take that teaser and the full content and store them separately in the database. In Wordpress, by default, you’ll find a “more...” link at the end of the teaser, inviting you to read the full posting. Drupal does offer a “read more” link, but you’ll find it with the other posting links, away from the teaser. Chances are : readers will miss that link and believe that the teaser is all there is to read in the posting.

I don't recommend this *at all*.

Claims of no way to do it? Actually publically suggesting people hack core for something that can be done with medium difficulty?

One of the most popular articles in my blog tells you how to do this in only a few lines of code. ed_readmore was based upon this hack; it probably got 'bloated' (100 lines of code in a module doesn't sound that bloated) because it lets people pick options.

I didn't see it mentioned...

...maybe I'm missing it - but, at least for future versions of Drupal, this whole conversation is about to be rendered moot with the jquery teaser patch having now been committed to core http://drupal.org/node/107061.

The main problem with

The main problem with hacking core is that you create your own fork. You have now taken a step outside of the Drupal community because now you're running code nobody else has. In other words, you now are maintaining your own cms. As a practice, this has much more of a potential down-the-line cost than having a custom module that may or may not need attention, depending upon api changes.

It may not seem like much with a little hack like this, but do a few of those, forget about them as you move on to other websites or other projects and so on, and your next update suddenly becomes much more complicated, whereas a custom module may not need any changes at all.

I agree with Laura

Laura I agree,
By hacking code you are now the maintainer of a branch. And you are taking on the responsibility of the security of that branch.

The Drupal API is designed to lessen the impact of any possible security mistakes that one might make with a contributed module.

IMHO if there is an alternative to hacking core, that is the way to go.

If ed_readmore is too much bloat for you (though I would be surprised if it shows any measurable impact on performance), use Merlin's suggested method.

maybe its because its friday the 13th...

maybe its because its friday the 13th...
but my thought here, is-

lets all try to chill a bit...
(inside joke on site maintainer alias included, no extra charge)-

meaning just that no one in their right mind would advocate
hacking core as a regular or extended practice-

or thinking anything other than exactly what
merlinofchaos demonstrates here-


http://www.angrydonuts.com/the_nuisance_of_the_read_more_fl

is the right way to go...

i think what caroline is just saying-
and disclaiming it very well-

is just that from time to time...
based on your own individual drupal knowledge and ability to practice it-

having a few little tricks that get drupal to work how you want it to-
can make your own particular drupal experience a bit more magical.

of course being a good magician takes practice-
and again as caroline explains-

as any one of us is able to better grasp how and why drupal works-
our methods should always strive for the "right" way-

and we are all lucky enough to have such a fine platform that always
seems to provide that way-

especially now with the incredible flexibility of the api system, modules and now jquery.

and of course the great work by people like steven-
as caleb g mentions, here-

http://drupal.org/node/107061

magic takes practice-
and drupal is a world unto itself...

lets not misunderstand someone who is just doing a good job passing
on a trick or two that makes it easier for someone to use drupal-

knowing that the more they use and better understand it-
they will want to better their technique for modifying that usage.

How about using regular expressions in node.tpl.php?

That’s another route that does not involve hacking the core.
You still end up with the extra read more link, but you can hide it with CSS.
ps: Merlin, it’s an honor for me to have you walk in here.
ps2: Hi Vincent!

There is a third way

There is a way to achieve this without patching core or adding a bulky module. You can simply modify the node rendering function of your theme. For example, I am using a modified chameleon theme for my blog site: http://chri.st and I patched mychameleon_node to add three lines of code:

if ($teaser && $node->teaser) {
$output .= $node->teaser;
/* READ MORE MODS */
if ($node->readmore) {
$output .= " nid\">[read more...]\n";
}

/* end mods */
}
else {
$output .= $node->body;
}

Have a look at my site to see the effect.

Hi Andrew

The problem with this method is that you’ll get an ending block element tag before that span, so the link won’t be inline with the teaser. I know that because I tried your method. But you can use regular expressions to modify the teaser right in node.tpl.php.

if ($teaser && $node->teaser) {

Beware of something here, that probably will never be a problem : sometimes (often never) a teaser will BE the content, that is, the content will be short enough that the teaser will be the full view. However, you may still want users to click on 'read more' to get them on the node page, so that they read comments and see file attachments.

href=\"node/$node->nid\">

Here it is perfectly fine to use a link like /node/x (if someone looks in and wander). Because node.tpl.php is about how the node is rendered on one’s own web site, not in RSS feeds. You should add the forward slash here, though (avoid a relative link). And for code readability, it would be nice to use XHTML outside the print statement, instead of using escaping... but that is up to one’s preferences.

Thanks Caroline

I will all the extra forward slash as you recommend. You are right about not being inline. I would prefer the "read more" to be inline, so maybe I will go with your solution.
I often have short posts that are only the teaser, so your second comment is also relevant.
Oh, and thanks for your responses over on drupal.org to my request for a CSS book recommendation.
b.t.w. Laura's email address was showing up in the contact form after she posted.

Contact info module bug here?

Aside: Coming back, my comment form is autofilled with Andrewfn's info. Is the next commenter seeing my info?

basicmagic, I don't think anyone needs to chill. This is open source, and people are free to do what they will. Nobody is saying "can't" -- just that certain decisions lead to a set of potential consequences.

Anyway, I'll let it be here. I've had my say on this a few weeks ago here: Laura's blog.

Laura

Yes there definitely is a problem with contact info module

Because now I have revisted the site using a different browser, and lo and behold, all my info is filled in!

Hi Laura

Because now I have revisited the site using a different browser, and lo and behold, all my info is filled in!

I am using the following module to remember contact info : contact_remember. Now I know it’s buggy. Thank you.

You're welcome Andrew

And I will disable the contact_remember module.

Andrew

I have looked at your web site and I wanted to tell you that, although the [read more] link is not inline with the teaser, with the current placement of that link it both looks really good (as it is) and it is totally obvious. Well done.

Thanks

Originally I found that people were not reading more unless the teaser ended mid-sentence, so I wanted to make it really obvious. I'm glad you think it works. Thanks for the comment!

b.t.w. really great site!

WOW Thank you!

b.t.w. really great site!
Thank you (( A. )).
Originally I found that people were not reading more unless the teaser ended mid-sentence...
The Art of the tease(r)!
And I think that the HEAD FIRST book would be great for your wife.

problems when logging in/out

Hi,
I just inserted the code in de node_prepare() function.
Now I'm having problems when a user logs in or out, first a white screen appears, when you do a reload I have the following errormessage:

warning: Cannot modify header information - headers already sent by (output started at /mounted-storage/ho...www/modules/node/node.module:1) in /mounted-storage/ho...www/includes/session.inc on line 100.
warning: session_regenerate_id() [function.session-regenerate-id]: Cannot regenerate session id - headers already sent in /mounted-storage/ho...www/includes/session.inc on line 103.
warning: Cannot modify header information - headers already sent by (output started at /mounted-storage/ho...www/modules/node/node.module:1) in /mounted-storage/ho...www/includes/common.inc on line 311.

any idea what is going wrong?

Ben

Look at this page : http://drupal.org/node/1424

Thx!

Yes, great, it works now!!
Thx for this!

read more hack

Hallo Caroline,
I like your hack, however on my page it doesn't show-up in-line. It shows on a new line after the body text.
If I look to the code generated i see a just before the new "read more". I can't see where that hard-return is comming from. Could this be generated bij the teaser-view?
Thanks for your time.
Clivesj

off-topic:
I applied for membership for your site and am awaiting approval ;>

had to "read more" first

Ok I see it now. Up to now I let Drupal determine the point to split the body for the teaser. In that case the will be used, and the hacked-link will not appear in-line.
I use now, and the link shows beautifully in-line.
Thank for the hack.
Clivesj

Fixing core

Hi Caroline, good to see you are still keeping busy with Drupal ;-)

Just wondering if you know if there is an issue on d.o. to fix core to make it easier to position the read more link. I was sure I'd seen something but can't find it now ... maybe it was an ordinary forum topic,..

Cheers for now,
gpk

I don't think so.

... and *wave*. Sorry for the hiatus.

These weren't it, but are

These weren't it, but are related:
move 'read more' links to end of node content (this one has some recent activity but seems an odd approach to me though the intent looks good)
Link 'read more' has no theme feature and cannot be removed.
Introduce theme_read_more()

This probably touches on it:
Refactor page (and node) rendering
but as moshe points out (Move node links into $node->content) it's probably headed nowhere.

This was the one:
Better workflow for core teasers
As you said there,

ALSO, I think that it's about time that a readmore link INLINE with the teaser becomes an optional admininstrator setting as well

[update] And *wave back* ... can't actually remember if ;-) implied irony !!! [/update]

chrisada says...

chrisada says:

Move 'read more' links to end of node content... That is the behaviour of most systems, and I think it is more natural.

I agree. If there is no option, than it should be moved there.

Thank you for the links.