We use cookies to improve your experience. No personal information is gathered and we don't serve ads. Cookies Policy.

ExpressionEngine Logo ExpressionEngine
Features Pricing Support Find A Developer
Partners Upgrades
Blog Add-Ons Learn
Docs Forums University
Log In or Sign Up
Log In Sign Up
ExpressionEngine Logo
Features Pro new Support Find A Developer
Partners Upgrades
Blog Add-Ons Learn
Docs Forums University Blog
  • Home
  • Forums

Force external urls in posts to open in new window

Development and Programming

bkuberek's avatar
bkuberek
124 posts
16 years ago
bkuberek's avatar bkuberek

Hi all,

I was hopping I could get some help on my extension. I created this extension, which worked fine on my local machine but does not work on our production server. It uses regular expressions to find links, check if the href points to an external url, then adds a target attribute to the link.

here is an outline:

  1. loop through all chosen custom fields, find <a> tags, and extract the attributes portion
foreach ($this->fields as $field_id => $field)
{
    if (!preg_match_all('/<a(.*)>.*<\/a>/iU', $field['content'], $matches, PREG_SET_ORDER))
    {
        continue;
    }
    ...
  1. For each match, loop through the attributes. If there are no attributes (such as basic anchors) skip and continue looping. If there are attributes, gather the attribute name and the attribute value.
foreach ($matches as $i => $match)
{
    if (!preg_match_all('/([^\s]*)=[\'\"]??([^\'^\"]*?)[\'\"]/iU', $match[1], $attr))
    {
        unset($matches[$i]);
        continue;
    }
        ...
  1. then I build an array where the key is the lowercase of the attribute name and the value is the attribute value. If there is no href attribute, I unset the current attribute index and skip this link.
// --------------------------------------
// make attribute names lowercase
// --------------------------------------

foreach ($attr[1] as $k => $v)
{
    $attr[1][$k] = strtolower($v);
}

// --------------------------------------
// create attributes associative array
// --------------------------------------
foreach ($attr[1] AS $index => $name) 
{
    $attributes[$i][$name] = $attr[2][$index];
}

// --------------------------------------
// check for the href attribute
// --------------------------------------

if (!isset($attributes[$i]['href']))
{
    unset($matches[$i]);
    unset($attributes[$i]);
    continue;
}
  1. here is the magic. Check for a host and if the host is different from the current host, recreate the link and its attributes, then add it to a queue.
// --------------------------------------
// get the value of href
// --------------------------------------

$url = $attributes[$i]['href'];

// --------------------------------------
// check the value of href
// --------------------------------------

if (substr($url, 0, 7) != 'http://' && substr($url, 0, 8) != 'https://')
{
    if (substr($url, 0, 1) == '/')
    {
        $url = $IN->GBL('HTTP_HOST', 'SERVER').$url;
    }
    $url = 'http://'.$url;
}

// --------------------------------------
// get the host part of the href
// --------------------------------------

$host = @parse_url($url, PHP_URL_HOST);

// --------------------------------------
// check the host
// --------------------------------------

if ($host == $IN->GBL('HTTP_HOST', 'SERVER'))
{
    continue;
}

// --------------------------------------
// check the attributes
// -------------------------------------

$attributes[$i]['target'] = '_blank';

if (!isset($attributes[$i]['class']))
{
    $attributes[$i]['class'] = $this->settings['class_name'];
}
else
{
    if (stripos($attributes[$i]['class'], $this->settings['class_name']) === false)
    {
        $attributes[$i]['class'] = $this->settings['class_name'].' '.$attributes[$i]['class'];
    }
}

// --------------------------------------
// rebuild the element attributes
// --------------------------------------

$new_attr = '';

foreach ($attributes[$i] as $k => $v)
{
    $new_attr .= ' '.$k.'="'.$v.'"';
}

// --------------------------------------
// create the new link element and add to the queue
// --------------------------------------

$new_link = str_replace($match[1], $new_attr, $match[0]);
$this->target[] = array($i, $new_link);
  1. Once all links have been looped through and the queue is not empty, we’ll find and replace them. Take the old link and replace with the new one.
// --------------------------------------
// if there is nothing in the queue, stop
// --------------------------------------
if (count($this->target) == 0)
{
    return false;
}

// --------------------------------------
// Replace links in the custom field
// --------------------------------------

for ($i = 0; $i < count($this->target); $i++)
{
    $this->fields[$field_id]['new_content'] = str_replace($matches[$this->target[$i][0]][0], $this->target[$i][1], $field['content']);
}

This worked fine on my local MAMP setup but didn’t work on the production server at RackSpace.

Can anyone point me any clues?

I have attached the original file for your convenience.

Thank you.

       
Derek Jones's avatar
Derek Jones
7,561 posts
16 years ago
Derek Jones's avatar Derek Jones

I don’t have time at this late hour to review what you’re doing, but just as a suggestion, would it not be easier to use CSS and jQuery to handle this? As a side note, it’s generally inconsiderate to force links to open in a new window.

       
bkuberek's avatar
bkuberek
124 posts
16 years ago
bkuberek's avatar bkuberek

I thought of using javascript for this. But it’s actually a way to enforce a rule. Editors are supposed to make external links target=”_blank”, but they don’t. This would only be active for editorial content and not for user submitted content (which are only comments). We tried to discuss this with the editors but they don’t want more to do. To tell you the truth, they want as much automation as possible. There is so much we already automated for them and now they want this too. I wonder what will be next.

Also, wouldn’t javascript slowdown the page? We already have Glam and Dart ads clogging the download pipes.

Thanks for taking your time to reply. If you do look at it, I have a feeling it’s a problem with the second regular expression. I’m not versed in regex and I actually threw that together.

Thanks again Derek

       
Derek Jones's avatar
Derek Jones
7,561 posts
16 years ago
Derek Jones's avatar Derek Jones

Well, my suggestion still stands. Let’s assume that it has been explained to the client what the pros are of not opening links in new windows are (not irritating visitors, retaining back button functionality, etc.), but that they knowingly insist. Using appropriate markup and javascript, you can prevent this from being applied site wide by only targeting links within “article content containers” and then drilling down as necessary to dynamically target only the links you wish to be affected. So if your articles are in an “article” class <div>, you can target with .article a {}, etc.

       
bkuberek's avatar
bkuberek
124 posts
16 years ago
bkuberek's avatar bkuberek

I actually just did this in javascript and worked great. I use prototype and came out like this:

$$('div.entry a[href]').each(function(node) {
    var host = window .location .hostname; // spaced out to avoid being stripped from post
    if (node.href.indexOf(host) == -1 && node.href != '') {
        node.addClassName('external');
        node.writeAttribute('target', '_blank');
    };
});

Thanks again.

       
Derek Jones's avatar
Derek Jones
7,561 posts
16 years ago
Derek Jones's avatar Derek Jones

Nicely done; and you’ll have saved many CPU cycles over the life of the site.

       
smartpill's avatar
smartpill
456 posts
16 years ago
smartpill's avatar smartpill

Blankwin is another standalone script that works nicely for this also.

       
bkuberek's avatar
bkuberek
124 posts
16 years ago
bkuberek's avatar bkuberek

Yeah I can’t believe I went through all the trouble of making that extension before trying this. Literally took me less than 5 minutes to write that.

Blankwin is another standalone script that works nicely for this also.

Very neat as well. Good thing about it is that it is not library dependent.

       

Reply

Sign In To Reply

ExpressionEngine Home Features Pro Contact Version Support
Learn Docs University Forums
Resources Support Add-Ons Partners Blog
Privacy Terms Trademark Use License

Packet Tide owns and develops ExpressionEngine. © Packet Tide, All Rights Reserved.