You are here

elf.module in External Links Filter 6.3

Adds an icon to external and mailto links.

File

elf.module
View source
<?php

/**
 * @file
 *   Adds an icon to external and mailto links.
 */

/**
 * Implementation of hook_init().
 */
function elf_init() {
  require_once 'includes/common.inc';
  $path = drupal_get_path('module', 'elf');
  drupal_add_css("{$path}/css/elf.css");
  if (variable_get('elf_window', FALSE)) {
    drupal_add_js("{$path}/js/elf.js");
  }
}

/**
 * Implementation of hook_menu().
 */
function elf_menu() {
  $items['admin/settings/filters/elf'] = array(
    'title' => 'External Links Filter',
    'description' => 'Configure the external links filter.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'elf_form_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['elf/go'] = array(
    'page callback' => 'elf_redirect',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_filter().
 */
function elf_filter($op, $delta = 0, $format = -1, $text = '', $cache_id = 0) {
  switch ($op) {
    case 'list':
      return array(
        0 => t('Add an icon to external and mailto links'),
      );
    case 'process':
      return elf_replace($text, $format);
    case 'settings':
      $form['elf'] = array(
        '#type' => 'fieldset',
        '#title' => t('External links filter'),
        '#collapsible' => TRUE,
      );
      $form['elf']["elf_nofollow_{$format}"] = array(
        '#type' => 'checkbox',
        '#title' => t('Add !nofollow to external links', array(
          '!nofollow' => '<code>rel="nofollow"</code>',
        )),
        '#default_value' => variable_get("elf_nofollow_{$format}", FALSE),
      );
      return $form;
    default:
      return $text;
  }
}

/**
 * Admin settings form.
 *
 * @see elf_form_settings_submit()
 */
function elf_form_settings(array &$form_state) {
  $domains = variable_get('elf_domains', array());
  $form['elf_domains'] = array(
    '#type' => 'textarea',
    '#default_value' => implode("\n", $domains),
    '#title' => t('Internal domains'),
    '#description' => t('If your site spans multiple domains, specify each of them on a new line to prevent them from being seen as external sites. Make sure to include the right protocol; !example_right, and not !example_wrong, for instance. Asterisks are wildcards.', array(
      '!example_right' => '<code>http://example.com</code>',
      '!example_wrong' => '<code>example.com</code>',
    )),
  );
  $form['elf_window'] = array(
    '#type' => 'checkbox',
    '#default_value' => variable_get('elf_window', FALSE),
    '#title' => t('Use JavaScript to open external links in a new window'),
  );
  $form['elf_redirect'] = array(
    '#type' => 'checkbox',
    '#default_value' => variable_get('elf_redirect', FALSE),
    '#title' => t('Redirect users to external websites via !url_path.', array(
      '!url_path' => '<code>/elf/go</code>',
    )),
  );
  return system_settings_form($form);
}

/**
 * Form validation handler for elf_form_settings().
 *
 * @see elf_form_settings()
 */
function elf_form_settings_validate(array $form, array &$form_state) {
  $values = preg_split('#\\s#', $form_state['values']['elf_domains']);
  $domains = array();
  $errors = array();
  foreach ($values as $value) {

    // Remove trailing slashes, because not all users will use those for their links.
    $value = trim($value, '/');
    if (strlen($value)) {
      if (!menu_path_is_external($value)) {
        $errors[] = $value;
      }
      $domains[] = $value;
    }
  }
  if ($errors) {
    form_set_error('elf_domains', format_plural(count($errors), '%domain is not a valid external domain.', '%domain are no valid external domains', array(
      '%domain' => implode(', ', $errors),
    )));
  }
  else {
    form_set_value($form['elf_domains'], array_unique($domains), $form_state);
  }
}

/**
 * Add classes to external or mailto links in a text.
 *
 * @param $text string
 *   The text to filter.
 *
 * @return string
 *   The (filtered) text.
 */
function elf_replace($text, $format) {
  $document = elf_filter_dom_load($text);
  $links = $document
    ->getElementsByTagName('a');
  foreach ($links as $a) {
    if ($href = $a
      ->getAttribute('href')) {

      // This is a mailto link.
      if (strpos($href, 'mailto:') === 0) {
        $a
          ->setAttribute('class', $a
          ->getAttribute('class') ? $a
          ->getAttribute('class') . ' elf-mailto elf-icon' : 'elf-mailto elf-icon');
      }
      elseif (elf_url_external($href)) {

        // Add external class.
        $a
          ->setAttribute('class', $a
          ->getAttribute('class') ? $a
          ->getAttribute('class') . ' elf-external elf-icon' : 'elf-external elf-icon');
        if ($a
          ->getElementsByTagName('img')->length > 0) {
          $a
            ->setAttribute('class', $a
            ->getAttribute('class') ? $a
            ->getAttribute('class') . ' elf-img' : 'elf-img');
        }

        // Add nofollow.
        $nofollow = variable_get("filter_html_nofollow_{$format}", FALSE);
        if ($nofollow) {
          $rel = array_filter(explode(' ', $a
            ->getAttribute('rel')));
          if (!in_array('nofollow', $rel)) {
            $rel[] = 'nofollow';
            $a
              ->setAttribute('rel', implode(' ', $rel));
          }
        }

        // Add redirect.
        if (variable_get('elf_redirect', FALSE)) {
          $a
            ->setAttribute('href', url('elf/go', array(
            'query' => array(
              'url' => $a
                ->getAttribute('href'),
            ),
          )));
        }
      }
    }
  }
  return elf_filter_dom_serialize($document);
}

/**
 * Test if a URL is external
 *
 * @param $url string
 */
function elf_url_external($url) {
  global $base_url;
  static $pattern = NULL;

  // Statically store internal domains as a PCRE pattern.
  if (!$pattern) {
    $domains = array();
    foreach (array_merge(variable_get('elf_domains', array()), array(
      $base_url,
    )) as $domain) {
      $domains[] = preg_quote($domain, '#');
    }
    $pattern = '#^(' . str_replace('\\*', '.*', implode('|', $domains)) . ')#';
  }
  return preg_match($pattern, $url) ? FALSE : menu_path_is_external($url);
}

/**
 * Redirect the browser to the external URL from $_GET['url'].
 */
function elf_redirect() {
  drupal_goto($_GET['url']);
}

/**
 * Using Drupal 7's filter_dom_load function which is not available in d6.
 */
function elf_filter_dom_load($text) {
  $dom_document = new DOMDocument();

  // Ignore warnings during HTML soup loading.
  @$dom_document
    ->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $text . '</body></html>');
  return $dom_document;
}

/**
 * Using Drupal 7's filter_dom_serialize function which is not available in d6.
 */
function elf_filter_dom_serialize($dom_document) {
  $body_node = $dom_document
    ->getElementsByTagName('body')
    ->item(0);
  $body_content = '';
  foreach ($body_node
    ->getElementsByTagName('script') as $node) {
    elf_filter_dom_serialize_escape_cdata_element($dom_document, $node);
  }
  foreach ($body_node
    ->getElementsByTagName('style') as $node) {
    elf_filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
  }
  foreach ($body_node->childNodes as $child_node) {
    $body_content .= $dom_document
      ->saveXML($child_node);
  }
  return preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
}

/**
 * Using Drupal 7's filter_dom_serialize_escape_cdata_element function which is not available in d6.
 */
function elf_filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, $comment_start = '//', $comment_end = '') {
  foreach ($dom_element->childNodes as $node) {
    if (get_class($node) == 'DOMCdataSection') {
      $embed_prefix = "\n<!--{$comment_start}--><![CDATA[{$comment_start} ><!--{$comment_end}\n";
      $embed_suffix = "\n{$comment_start}--><!]]>{$comment_end}\n";
      $data = str_replace(']]>', ']]]]><![CDATA[>', $node->data);
      $fragment = $dom_document
        ->createDocumentFragment();
      $fragment
        ->appendXML($embed_prefix . $data . $embed_suffix);
      $dom_element
        ->appendChild($fragment);
      $dom_element
        ->removeChild($node);
    }
  }
}

Functions

Namesort descending Description
elf_filter Implementation of hook_filter().
elf_filter_dom_load Using Drupal 7's filter_dom_load function which is not available in d6.
elf_filter_dom_serialize Using Drupal 7's filter_dom_serialize function which is not available in d6.
elf_filter_dom_serialize_escape_cdata_element Using Drupal 7's filter_dom_serialize_escape_cdata_element function which is not available in d6.
elf_form_settings Admin settings form.
elf_form_settings_validate Form validation handler for elf_form_settings().
elf_init Implementation of hook_init().
elf_menu Implementation of hook_menu().
elf_redirect Redirect the browser to the external URL from $_GET['url'].
elf_replace Add classes to external or mailto links in a text.
elf_url_external Test if a URL is external