You are here

pathfilter.module in Path Filter 6.2

This filter takes internal Drupal paths in double quotes, written as e.g. "internal:node/99", and replaces them with the appropriate absolute http URL using Drupal's url() function [1]. E.g. for a site located at http://example.com/mysite

"internal:node/99" becomes "http://example.com/mysite/node/99"

Note: Because it uses url(), if a path alias exists, it will be substituted.

[1] http://api.drupal.org/api/4.7/function/url

Credits: @author Ray Zimmerman (drupal.org user "RayZ") @author Tom Kirkpatrick (drupal.org user "mrfelton"), www.kirkdesigns.co.uk @author George Montana Harkin (drupal.org user "harking") Auto internalization @author Shayne Huddleston (shayne.huddleston@oregonstate.edu) Fix to auto internailze all node fields

File

pathfilter.module
View source
<?php

/**
 * @file
 * This filter takes internal Drupal paths in double quotes, written as
 * e.g. "internal:node/99", and replaces them with the appropriate absolute
 * http URL using Drupal's url() function [1]. E.g. for a site located at
 * http://example.com/mysite
 *
 *   "internal:node/99" becomes "http://example.com/mysite/node/99"
 *
 * Note: Because it uses url(), if a path alias exists, it will be substituted.
 *
 * [1] http://api.drupal.org/api/4.7/function/url
 *
 * Credits:
 * @author Ray Zimmerman (drupal.org user "RayZ")
 * @author Tom Kirkpatrick (drupal.org user "mrfelton"), www.kirkdesigns.co.uk
 * @author George Montana Harkin (drupal.org user "harking") Auto internalization
 * @author Shayne Huddleston (shayne.huddleston@oregonstate.edu) Fix to auto internailze all node fields
 */
global $_pathfilter_replacement_patterns;
$_pathfilter_replacement_patterns = array();
$_pathfilter_replacement_patterns[] = '/(["\'])(internal):([^"#\\?\']*)\\??([^"#\']+)?#?([^"\']+)?\\1/';
$_pathfilter_replacement_patterns[] = '/(["\'])(files):([^"\']*)\\1/';

/**
 * Implementation of hook_filter_tips().
 */
function pathfilter_filter_tips($delta, $format, $long = FALSE) {
  switch ($delta) {
    case 0:
      if ($long) {
        $output = '<p>' . t('Internal paths in single or double quotes, written as "internal:node/99", for example, are replaced with the appropriate absolute URL or path. Given a site located at http://example.com/mysite, assuming clean URLs are enabled and "internal:admin/user" becomes "http://example.com/mysite/admin/user" and "internal:node/99" becomes "http://example.com/mysite/node/99". If \'node/99\' has a URL alias assigned, such as \'news/latest\' the alias will be substituted giving "http://example.com/mysite/news/latest".') . '</p>';
        $output .= '<p>' . t('Paths to files in single or double quotes, written as "files:somefile.ext", for example, are replaced with the appropriate URL that can be used to download the file.') . '</p>';
        return $output;
      }
      else {
        return t('Internal paths in single or double quotes, written as "internal:node/99", for example, are replaced with the appropriate absolute URL or path. Paths to files in single or double quotes, written as "files:somefile.ext", for example, are replaced with the appropriate URL that can be used to download the file.');
      }
      break;
  }
}

/**
 * Implementation of hook_filter().
 */
function pathfilter_filter($op, $delta = 0, $format = -1, $text = '') {

  // The "list" operation provides the module an opportunity to declare
  // both how many filters it defines and a human-readable name for each filter.
  // Note that the returned name should be passed through t() for translation.
  if ($op == 'list') {
    return array(
      0 => t('Internal path filter'),
    );
  }

  // All operations besides "list" provide a $delta argument so we know which
  // filter they refer to.
  switch ($delta) {
    case 0:
      switch ($op) {

        // This description is shown in the administrative interface, unlike
        // the filter tips which are shown in the content editing interface.
        case 'description':
          $output = t('Internal paths in single or double quotes, written as "internal:node/99", for example, are replaced with the appropriate absolute URL or path.');
          $output .= t(' Paths to files in single or double quotes, written as "files:somefile.ext", for example, are replaced with the appropriate URL that can be used to download the file.');
          return $output;

        // The actual filtering is performed here. The supplied text should be
        // returned, once any necessary substitutions have taken place.
        case 'process':
          global $_pathfilter_replacement_patterns;

          // use the 'currying' technique to allow us to pass the format into
          // the callback function.
          $callback = _pathfilter_curry('_pathfilter_process', 3);
          return preg_replace_callback($_pathfilter_replacement_patterns, $callback($format, TRUE), $text);

        // Filter settings for pathfilter.
        case 'settings':
          return _pathfilter_settings($format);
        default:
          return $text;
      }
      break;
  }
}

/*
 * A 'currying' function that allows paramaters to be passed into the
 * preg_replace_callback callback. Taken from
 * http://ie2.php.net/manual/en/function.preg-replace-callback.php#88013
 */
function _pathfilter_curry($func, $arity) {
  return create_function('', "\n    \$args = func_get_args();\n    if(count(\$args) >= {$arity})\n      return call_user_func_array('{$func}', \$args);\n    \$args = var_export(\$args, 1);\n    return create_function('','\n      \$a = func_get_args();\n      \$z = ' . \$args . ';\n      \$a = array_merge(\$z,\$a);\n      return call_user_func_array(\\'{$func}\\', \$a);\n    ');\n  ");
}
function _pathfilter_process($format, $convert_to_alias = TRUE, $matches) {
  switch ($matches[2]) {
    case 'internal':
      return _pathfilter_process_internal($format, $convert_to_alias, $matches);

      //Converts to alias form.
      break;
    case 'files':
      return _pathfilter_process_files($format, $matches);
  }
}
function _pathfilter_process_internal($format, $convert_to_alias, $matches) {
  $absolute = variable_get('pathfilter_link_absolute_' . $format, 1) ? TRUE : FALSE;
  $link = '';

  // If we are going to convert our output to the aliased version ($convert_to_alias is set to TRUE),
  // We need to pass 'alias' => true into url to tell it to not pass our URL through
  // drupal_get_path_alias() thus keeping our 'node/#' format.
  if (module_exists('i18n')) {
    if ($path = drupal_get_normal_path($matches[3])) {
      if (substr($path, 0, 5) == 'node/') {
        $nid = substr($path, 5);
      }
    }
    elseif (preg_match('/(node\\/([0-9]+))$/', $matches[3], $match)) {
      $nid = $match[2];
    }
    if (!empty($nid)) {
      $languages = language_list('enabled');
      $languages = $languages[1];
      $language = $languages[i18n_node_get_lang($nid)];
      $link = url($matches[3], array(
        'query' => $matches[4],
        'fragment' => $matches[5],
        'absolute' => $absolute,
        'alias' => !$convert_to_alias,
        'language' => $language,
      ));
    }
  }
  $link = $link ? $link : url($matches[3], array(
    'query' => !empty($matches[4]) ? $matches[4] : '',
    'fragment' => !empty($matches[5]) ? $matches[5] : '',
    'absolute' => $absolute,
    'alias' => !$convert_to_alias,
  ));
  return $matches[1] . $link . $matches[1];
}
function _pathfilter_process_files($format, $matches) {
  $absolute = variable_get('pathfilter_link_absolute_' . $format, 1) ? TRUE : FALSE;
  $link = file_create_url($matches[3]);
  $link = $absolute ? $link : substr($link, strlen($GLOBALS['base_url']));
  return $matches[1] . $link . $matches[1];
}

/**
 * Helper settings function for hook_filter('settings').
 */
function _pathfilter_settings($format) {
  $form = array();
  $form['pathfilter'] = array(
    '#type' => 'fieldset',
    '#title' => t('Internal path filter'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['pathfilter']['pathfilter_link_absolute_' . $format] = array(
    '#type' => 'radios',
    '#title' => t('Convert internal paths to'),
    '#options' => array(
      1 => t('Absolute URL (including http://www.example.com)'),
      0 => t('Absolute path (relative to document root)'),
    ),
    '#default_value' => variable_get('pathfilter_link_absolute_' . $format, 1),
    '#description' => t('Should internal paths be transformed to absolute URLs, such as %absolute_url or absolute paths, like %absolute_path. Note that your changes may not appear until the cache has been cleared.', array(
      '%absolute_url' => 'http://www.example.com/my-page',
      '%absolute_path' => '/my-page',
    )),
  );
  $form['pathfilter']['pathfilter_process_all'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable automatic url processing for attributes.'),
    '#default_value' => variable_get('pathfilter_process_all', 1),
    '#description' => t('When this option is enabled, all %element_list elements will automatically have the servername and base path of their src, href, and action urls replaced with %internal. On editing of these elements, all instances of %internal will be replaced with the site\'s base path (%current_base_path).', array(
      '%element_list' => '<img>, <a>, <script>, <object>, and <form>',
      '%internal' => '\'internal:\'',
      '%current_base_path' => base_path(),
    )),
  );
  return $form;
}

/**
 * Modifies submitted node body values. Replaces base_path() in images, hrefs,
 * etc with 'internal:' on save. When editing a node, the body's 'internal:'
 * strings are replaced with 'base_path()'.
 */
function pathfilter_nodeapi(&$node, $op) {
  switch ($op) {
    case 'prepare':

      // Convert "internal:" and "files:" back into full URL of site for editing.
      // We convert back into 'node/#' if possible to keep from breaking URLs when an
      // alias changes
      _pathfilter_replace_links($node, '_pathfilter_replace_internal');
      break;
    case 'presave':

      /** We do more when we look for what to replace with 'internal:'
       * - Only want to replace items that are links, not text or data
       * - Check to see if they specified the hostname of the server */
      _pathfilter_replace_links($node, '_pathfilter_internalize');
      break;
  }
}

/**
 * Replaces the links in select node properties (body, teaser, and any
 * textfields that have formatting defined for them)
 *
 * $node      : StdClass Object
 * $func_name : String           Can be either _pathfilter_internalize or _pathfilter_replace_internal
 *                               both functions replace links, one is before saving and one is
 *                               before viewing
 */
function _pathfilter_replace_links(&$node, $func_name) {

  // If we have our pathfilter variable defined let's replace otherwise bail out.
  if (variable_get('pathfilter_process_all', 1)) {

    // Let's get all the pathfilters we have defined and their corresponding formats.
    $defined_formats = array();
    $result = db_query("SELECT format FROM {filters} WHERE module = 'pathfilter'");
    while ($row = db_fetch_object($result)) {
      $defined_formats[] = $row->format;
    }

    // Iterate through our objects properties looking for body, teaser, and any fields that have
    // formatting defined for them.
    foreach ($node as $key => &$value) {

      //if this is the body or teaser lets run our replaces
      if ($key == "body" || $key == "teaser") {
        if (in_array($node->format, $defined_formats)) {
          $func_name($node->format, $value);
        }
      }
      elseif (is_array($value)) {

        // Look at the rest of the fields...if they have a format from our defined formats list
        // and we have a value that is a string run our replace...otherwise ignore them
        if (isset($value[0]) && is_array($value[0]) && !empty($value[0]['format']) && in_array($value[0]['format'], $defined_formats) && !empty($value[0]['value']) && is_string($value[0]['value'])) {
          $func_name($value[0]['format'], $value[0]['value']);
        }
      }
    }
  }
}

/**
 * Replaces the internal: and files: in elements with the url for it
 */
function _pathfilter_replace_internal($format, &$text) {
  global $_pathfilter_replacement_patterns;

  // use the 'currying' technique to allow us to pass the format into
  // the callback function.
  $callback = _pathfilter_curry('_pathfilter_process', 3);
  $text = preg_replace_callback($_pathfilter_replacement_patterns, $callback($format, FALSE), $text);

  //Converts to non-aliased form.
}

/**
 * Internalizes all urls in a string automatically, doing the user's job for them.
 */
function _pathfilter_internalize($format, &$item) {

  // First we find all of the items that look like they need to be replaced.
  $pattern = '/(<img|<a|<script|<object|<form|<param)[^\\>]*>/i';
  preg_match_all($pattern, $item, $matches);

  // Then we normalize the links of the items that matched and do
  // 'files:' replacement and then 'internal:' replacement.
  foreach ($matches[0] as $match) {

    // Obtain the URL out of the html tag.
    preg_match('/(src=|href=|action=|data=|value=)(\'|")([^\'"]*)(\'|"|>)/', $match, $url);

    // Do replacement with 'files:' if appropriate, 'internal:' otherwise.
    $base_files_url_re = _pathfilter_base_files_url_re();
    $replaced_path = preg_replace($base_files_url_re, 'files:', $url[3], 1);
    if ($replaced_path == $url[3]) {

      // 'files:' was not found, try 'internal:'.
      // remove the base path
      $base_url_re = _pathfilter_base_url_re();
      $tmp_replaced_path = preg_replace($base_url_re, '', $url[3], 1);

      // remove any language prefix or q= string
      $tmp_replaced_path = preg_replace('#^[^/](.*/?)??(node/[0-9]*)$#', "\$2", $tmp_replaced_path);

      // ensure we have an internal path
      $tmp_replaced_path = drupal_get_normal_path($tmp_replaced_path);

      // If this link is to a node
      preg_match('#^node/[0-9]*$#', $tmp_replaced_path, $matches);
      if ($matches) {

        // add the internal: prefix
        $replaced_path = 'internal:' . $tmp_replaced_path;
      }
    }

    // Update original string with changes, if needed.
    if ($replaced_path != $url[3]) {
      $item = preg_replace('/(src=|href=|action=|data=|value=)(\'|")(' . preg_quote($url[3], '/') . ')(\'|"|>)/', '$1$2' . $replaced_path . '$4', $item, 1);
    }
  }
}

/**
 * Generates the regular expression that will be used to find a url that should
 * have 'internal:'.
 */
function _pathfilter_base_url_re() {
  static $base_url_re;
  if ($base_url_re != '') {
    return $base_url_re;
  }
  global $base_url;
  $base_path = base_path();
  if ($base_path != '/') {
    $tmp_base_url = str_replace($base_path, '', $base_url . '/');
  }
  else {
    $tmp_base_url = $base_url;
  }
  $base_url_re = array(
    0 => '/^(' . preg_quote($tmp_base_url, '/') . ')?' . preg_quote($base_path, '/') . '/',
    1 => '/^(' . preg_quote($tmp_base_url, '/') . ')?' . preg_quote(drupal_urlencode($base_path), '/') . '/',
  );
  return $base_url_re;
}

/**
 * Generates the regular expression that will be used to find a url that should
 * have 'files:'.
 */
function _pathfilter_base_files_url_re() {
  static $base_files_url_re;
  if ($base_files_url_re != '') {
    return $base_files_url_re;
  }

  //Taken from file_create_url() http://api.drupal.org/api/function/file_create_url
  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
    case FILE_DOWNLOADS_PUBLIC:
      $files_url = file_directory_path() . '/';
      break;
    case FILE_DOWNLOADS_PRIVATE:
      $files_url = 'system/files/';
      break;
  }
  global $base_url;
  $base_path = base_path();
  if ($base_path != '/') {
    $tmp_base_url = str_replace($base_path, '', $base_url . '/');
  }
  else {
    $tmp_base_url = $base_url;
  }
  $base_files_url_re = array(
    0 => '/^(' . preg_quote($tmp_base_url, '/') . ')?' . preg_quote($base_path . $files_url, '/') . '/',
    1 => '/^(' . preg_quote($tmp_base_url, '/') . ')?' . preg_quote(drupal_urlencode($base_path . $files_url), '/') . '/',
  );
  return $base_files_url_re;
}

Functions

Namesort descending Description
pathfilter_filter Implementation of hook_filter().
pathfilter_filter_tips Implementation of hook_filter_tips().
pathfilter_nodeapi Modifies submitted node body values. Replaces base_path() in images, hrefs, etc with 'internal:' on save. When editing a node, the body's 'internal:' strings are replaced with 'base_path()'.
_pathfilter_base_files_url_re Generates the regular expression that will be used to find a url that should have 'files:'.
_pathfilter_base_url_re Generates the regular expression that will be used to find a url that should have 'internal:'.
_pathfilter_curry
_pathfilter_internalize Internalizes all urls in a string automatically, doing the user's job for them.
_pathfilter_process
_pathfilter_process_files
_pathfilter_process_internal
_pathfilter_replace_internal Replaces the internal: and files: in elements with the url for it
_pathfilter_replace_links Replaces the links in select node properties (body, teaser, and any textfields that have formatting defined for them)
_pathfilter_settings Helper settings function for hook_filter('settings').

Globals

Namesort descending Description
$_pathfilter_replacement_patterns @file This filter takes internal Drupal paths in double quotes, written as e.g. "internal:node/99", and replaces them with the appropriate absolute http URL using Drupal's url() function [1]. E.g. for a site located…