WordPress Howto: Adding a class name to post links with a specific URL

WordPress is a pretty cool beast; there is a lot you can do to extend it, and pretty easily. Having said that, digging around in the documentation for the more advanced stuff can be a bear.

Recently I needed to extend WordPress to alter every link in a post, to add a new class, but only if the link matched a specific domain name and also had a certain path in that domain.  The class name will signify that it is a "members only" link so people will know before they click that they will need to log in to a web site with a membership.

I chose PHP over jQuery because we will use an icon to signify this link type and I was afraid that if jQuery waited until the document was loaded before it parsed and added the class, the whole post would flicker and re-adjust paragraphs for each new icon that was added.  The icons will be a background of a class and therefore the text will be shifted over 20-30 pixels to accomodate it.  PHP offered the advantage of loading the class name into the post before it ever hit the browser, eliminating this problem.

 
function add_class_to_member_links ( $data )
{
        // regex based on http://www.the-art-of-web.com/php/parse-links/
        $ahref_regexp = "<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
	$class_regexp = 'class\s*=\s*"??([^">]+)"?';
        $matches       = array ();
 
    preg_match_all("/$ahref_regexp/siU", $data, $matches, PREG_SET_ORDER);
 
    foreach ( $matches as $the_match )
    {
        $url = parse_url ( $the_match[2] );
 
        if ( !preg_match ( '#domain\.org$#', $url['host'] ) )
        {
            continue; // the domain didn't match, not a members-only link
        }
 
        if ( !preg_match ( '#^/Members#', $url['path'] ) )
        {
            continue;  // the path didn't match, not a members-only link
        }
 
        // if we are here, this *is* a members-only link,
        // time to alter the class attribute, if it exists
        $match      = $the_match[0];
 
        // if is_feed is true this page load is from some
        // sort of RSS or Atom feed, not the web site
        if ( is_feed () )
	{
            $replacement_match = $match . ' (member link)';
	} else {  // a web site page load
            $lighter = array ();
            preg_match_all("/$class_regexp/si", $match, $lighter);
 
            $full_class_to_replace  = $lighter[0][0];
            // above looks like 'class="big small"' if exists at all
            $class_list   		    = $lighter[1][0];
            // above looks like 'big small'
            $replacement_class_list = trim ( $class_list . ' members-only' );
            // above looks like 'big small members-only'
 
            // we didn't find a class list, so we're going to add it in
            if ( empty ( $full_class_to_replace ) )
            {
		$replacement_match = str_replace ( '>',
                     ' class="'.$replacement_class_list.'">', $match );
            } else {
                $replacement_match = str_replace ( $full_class_to_replace,
                     ' class="'.$replacement_class_list.'" ', $match );
            }
        }
 
	    $data = str_replace ( $match, $replacement_match, $data );
	}
 
	return $data;
}
 
add_filter('the_content', 'add_class_to_member_links',11);
 

This could be done in jQuery just as well, and has an advantage that a mistake in jQuery code or a really weird non-standard tag just means the link goes unlabelled. In PHP something weird could really muck up the way the post content looks.

If you have any questions, I'm "ha17" on stackoverflow.com.