1

I'm using Drupal 7's l() function in my template as follows:

<h3>
  <?php print l(
    $fields['title']->content, 
    'node/'.$fields['nid']->content, 
    array('attributes' => array())
  ); ?>
</h3>

The problem is, this is causing apostrophes in my titles to display at &#039 instead of an apostrophe. How can I use l() properly to keep this from happening?

0

2 Answers 2

2

You could add that option in l() function : array('html'=>true).

<h3>
  <?php print l(
    $fields['title']->content, 
    'node/'.$fields['nid']->content, 
    array('html'=>true))
  ); ?>
</h3>

Be aware that the escaping serves a purpose: it prevents security problems such as XSS. When you use the 'html'=>true option, make sure you are passing in safe HTML code.

Better use decode_entities plus strip_tags, because the former "could revert previous sanitization efforts":

print l( decode_entities( strip_tags( $fields['title']->content )), 'node/'.$fields['nid']->content );

Edit

As per marcvangend, below examples shouldn't be necessary, since Drupal l() function takes care of of path aliases and language prefixes.

Try also

<h3>
  <?php 
    global $language;
    $langcode = $language-> language,
    print l(t($fields['title']->content), '/'. $langcode .'/'. drupal_get_path_alias('node/'.$fields['nid']->content, $langcode ));
  ?>
</h3>

Or

<h3>
  <?php 
    global $language;
    $langcode = $language-> language;
    print '<a href="/' . drupal_get_path_alias('node/'.$fields['nid']->content, $langcode ) . '">'. t($fields['title']->content) .'</a>';
  ?>
</h3>
5
  • 1
    I recommend the first option, normally the other two should not be needed. Let Drupal take care of path aliases and language prefixes. The l() function does that automatically. Commented Dec 2, 2016 at 17:02
  • html => true will do it. Not sure if I would pass field values like that..
    – Kevin
    Commented Dec 2, 2016 at 17:07
  • @Kojo the first option works - but as you mentioned this isn't safe. Now anyone creating an article on my site can implement an XSS attack. I have vetted all the writers but it significantly increases the attack surface of my site. Now an attacker can hack any writer's account and get in that way, rather than having to hack an admin account. If that's the only solution it seems that the l() function is absolutely useless - am I missing something?
    – YPCrumble
    Commented Dec 2, 2016 at 17:19
  • @Kojo it works but doesn't this simply urlencode and then reverse that url encoding? It would seem that l() is missing an option to not urlencode in the first place, without needing to allow full HTML.
    – YPCrumble
    Commented Dec 2, 2016 at 22:52
  • @YPCrumble please see my answer for recommended built-in sanitization options. Commented Dec 3, 2016 at 10:59
3

@Kojo's answer is getting a bit crowded and confusing IMHO so let me post my recommended approach here.


'html' => true

The key to the problem is passing the html option to the l() function like this:

<h3>
  <?php print l(
    $fields['title']->content, 
    'node/' . $fields['nid']->content, 
    array('html' => TRUE)
  ); ?>
</h3>

This tells l() to bypass the check_plain() function which normally escapes the link text.

Security

Using 'html' => TRUE is a security risk if you are not sure if the author can be trusted (ie. it is user generated content).

Depending on where the variables in your template come from, $fields['title']->content may already have been sanitized. You can test this by setting the title to <script>alert('hi');</script>. Load the page. If you see a javascript popup, you have an XSS vulnerability to fix. Fortunately, Drupal has a couple of functions built in for that.

Solutions for an XSS vulnerability

  1. Use filter_xss(). A filter that only allows a small selection of HTML tags, fixes invalid markup, and filters out XSS risks. Example:

    l(filter_xss($unsafe_text), $path, array('html' => TRUE));
    
  2. Use filter_xss_admin(). This is a very permissive option, like filter_xss(), but only recommended to filter content entered by trusted site admins. Example:

    l(filter_xss_admin($unsafe_text), $path, array('html' => TRUE));
    
  3. Use check_markup(). This is the most configurable option. It uses one of your configured text formats, such as "filtered html", to filter out all but the whitelisted html tags (it will call filter_xss() for you). It will also apply other filters enabled on the text format. Make sure you choose a text format which has the "HTML filter" enabled in its configuration. Example:

    l(check_markup($unsafe_text), $path, array('html' => TRUE));
    
2
  • 1
    Thanks for your detailed explanation. I see check_markup is the best option imo. But Is print l( decode_entities( strip_tags( $fields['title']->content )), 'node/'.$fields['nid']->content ); wrong? It uses l () sanitize functions. Thanks for your lights
    – Kojo
    Commented Dec 3, 2016 at 12:02
  • 1
    I wouldn't call it "wrong", but there are some disadvantages to strip_tags(), which you can find under the notes in the php documentation. That's why I prefer the Drupal filter functions. They have been tested by thousands, and people smarter than me have thought about it longer than I ever will. Commented Dec 3, 2016 at 21:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.