You are here

globalredirect.module in Global Redirect 6

Same filename and directory in other branches
  1. 8 globalredirect.module
  2. 5 globalredirect.module
  3. 7 globalredirect.module

The Global Redirect module redirects for all paths which have aliases but are not using the aliases which reduces the risk of duplicate content.

File

globalredirect.module
View source
<?php

/**
 * @file
 * The Global Redirect module redirects for all paths which have aliases but
 * are not using the aliases which reduces the risk of duplicate content.
 */

/**
 * Implements hook_help().
 */
function globalredirect_help($path, $arg) {
  switch ($path) {
    case 'admin/help#globalredirect':
      return t('This module will do a 301 redirect for all nodes which have an alias but are not using that alias.');
  }
}

/**
 * Implements hook_init().
 */
function globalredirect_init() {
  global $language;

  /**
   * Get the settings.
   */
  $settings = _globalredirect_get_settings();

  // If GlobalRedirect is inactive (for any of the reasons listed in
  // _globalredirect_is_active()) then skip the rest of the function.
  if (!_globalredirect_is_active($settings)) {
    return FALSE;
  }

  // If menu checking is enabled, do the check. Note: Feature disabled by default.
  if ($settings['menu_check']) {

    // Check the access on the current path, return FALSE if access not
    // allowed. This stops redirection for paths without access permission.
    $item = menu_get_item();
    if (!$item['access']) {
      return FALSE;
    }
  }

  // Store the destination from the $_REQUEST as it breaks things if we leave
  // it in - restore it at the end...
  if (isset($_REQUEST['destination'])) {
    $destination = $_REQUEST['destination'];
    unset($_REQUEST['destination']);
  }

  // Get the Query String (minus the 'q'). If none set, set to NULL
  $query_string = drupal_query_string_encode($_GET, array(
    'q',
  ));
  if (empty($query_string)) {
    $query_string = NULL;
  }

  // Establish the language prefix that should be used, ie. the one that
  // drupal_goto() would use
  $options = array(
    'fragment' => '',
    'query' => '',
    'absolute' => FALSE,
    'alias' => FALSE,
    'prefix' => '',
    'external' => FALSE,
  );
  if (function_exists('language_url_rewrite')) {

    // Note 1 : the language_url_rewrite() takes path (by reference) as the
    //          first argument but does not use it at all
    // Note 2 : We use $_REQUEST['q'] here as we want the path in an untouched
    //          form ($_GET['q] gets modified by core)
    $path = isset($_REQUEST['q']) ? $_REQUEST['q'] : '';
    language_url_rewrite($path, $options);
  }
  $prefix = rtrim($options['prefix'], '/');

  // Do a check if this is a front page
  if (drupal_is_front_page()) {

    // If frontpage redirection is disabled, halt here
    if (!$settings['frontpage_redirect']) {
      return;
    }

    // Redirect if the current request does not refer to the front page in the
    // configured fashion (with or without a prefix)
    if (isset($_REQUEST['q']) && $_REQUEST['q'] != $prefix) {
      drupal_goto('', $query_string, NULL, 301);
    }
    elseif ($settings['nonclean_to_clean'] && (bool) variable_get('clean_url', 0) && (strpos(request_uri(), '?q=') || strpos(request_uri(), 'index.php'))) {
      drupal_goto('', $query_string, NULL, 301);
    }

    // If we've got to this point then we're on a front page with a VALID
    // request path (such as a language-prefix front page such as '/de')
    return;
  }

  // Trim any trailing slash off the end (eg, 'node/1/' to 'node/1')
  $request = $settings['deslash'] ? trim($_GET['q'], '/') : $_GET['q'];

  // Optional stripping of "/0". Disabled by default.
  switch ($settings['trailing_zero']) {
    case 2:

      // If 'taxonomy/term/*' only. If not, break out.
      if (drupal_substr($request, 0, 14) != 'taxonomy/term/') {
        break;
      }

    // If it is, fall through to general trailing zero method
    case 1:

      // If last 2 characters of URL are /0 then trim them off
      if (drupal_substr($request, -2) == '/0') {
        $request = drupal_substr($request, 0, -2);
      }
  }

  // If the feature is enabled, check and redirect taxonomy/term/* requests to their proper handler defined by hook_term_path().
  if ($settings['term_path_handler'] && module_exists('taxonomy') && preg_match('/taxonomy\\/term\\/([0-9]+)$/', $request, $matches)) {

    // So the feature is enabled, as is taxonomy module and the current request is a taxonomy term page.
    // NOTE: This will only match taxonomy term pages WITHOUT a depth modifier
    $term = taxonomy_get_term($matches[1]);

    // Get the term path for this term (handler is defined in the vocab table under module). If it differs from the request, then redirect.
    if (!empty($term) && ($term_path = taxonomy_term_path($term)) != $request) {
      $request = $term_path;
    }
  }

  // If Content Translation module is enabled then check the path is correct.
  if ($settings['language_redirect'] && module_exists('translation') && arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == '') {
    switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) {
      case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
      case LANGUAGE_NEGOTIATION_PATH:

        // Check if there's a translation for the current language of the requested node...
        $node_translations = translation_path_get_translations('node/' . arg(1));

        // If there is, go to the translation.
        if (!empty($node_translations[$language->language]) && $node_translations[$language->language] != 'node/' . arg(1)) {
          globalredirect_goto($node_translations[$language->language], $query_string);
        }
        else {
          $node = node_load(arg(1));
          if (!empty($node->language) && $node->language != $language->language) {
            $all_languages = language_list();

            // Change the global $language's prefix, to make drupal_goto() follow the proper prefix
            $language = $all_languages[$node->language];
            globalredirect_goto('node/' . $node->nid, $query_string);
          }
        }
        break;
      case LANGUAGE_NEGOTIATION_DOMAIN:

        // Let's check is there other languages on site.
        $all_languages = language_list();
        if (count($all_languages) > 1) {
          foreach ($all_languages as $l => $lang) {

            // Only test for languages other than the current one.
            if ($lang->language != $language->language) {
              $alias = drupal_get_path_alias($request, $lang->language);

              // There is a matching language for this alias
              if ($alias != $request) {
                if (isset($lang->domain)) {
                  drupal_goto($lang->domain . '/' . $alias, $query_string, NULL, 301);
                }
                break;
              }
            }
          }
        }
        break;
    }
  }

  // Find an alias (if any) for the request
  $langcode = isset($options['language']->language) ? $options['language']->language : '';
  $alias = drupal_get_path_alias($request, $langcode);
  if (function_exists('custom_url_rewrite_outbound')) {

    // Modules may alter outbound links by reference.
    custom_url_rewrite_outbound($alias, $options, $request);
  }
  if ($prefix && $alias) {
    $prefix .= '/';
  }

  // Alias case sensitivity check. If there is an alias from the previous
  // lookup, do a query to test for case.
  // NOTE: This test depends on the coalition of the table being case-insensitive (ending _ci).
  if ($alias && $settings['case_sensitive_urls']) {
    $alias_sensitive = db_result(db_query("SELECT dst FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $alias, $langcode));
    if ($alias_sensitive && $alias != $alias_sensitive) {

      // There is a match and there is a difference in case.
      $alias = $alias_sensitive;
    }
  }

  // Compare the request to the alias. This also works as a 'deslashing'
  // agent. If we have a language prefix then prefix the alias
  if ($_REQUEST['q'] != $prefix . $alias) {

    // If it's not just a slash or user has deslash on, redirect
    if (str_replace($prefix . $alias, '', $_REQUEST['q']) != '/' || $settings['deslash']) {
      globalredirect_goto($alias, $query_string);
    }
  }

  // If no alias was returned, the final check is to direct non-clean to
  // clean - if clean is enabled
  if ($settings['nonclean_to_clean'] && (bool) variable_get('clean_url', 0) && strpos(request_uri(), '?q=') && !menu_path_is_external($request)) {
    globalredirect_goto($request, $query_string);
  }

  // Restore the destination from earlier so its available in other places.
  if (isset($destination)) {
    $_REQUEST['destination'] = $destination;
  }

  // Add the canonical link to the head of the document if desired.
  if ($settings['canonical']) {
    drupal_add_link(array(
      'rel' => 'canonical',
      'href' => url(drupal_is_front_page() ? '<front>' : $_REQUEST['q'], array(
        'absolute' => TRUE,
        'query' => $query_string,
      )),
    ));
  }

  // Add the Content-Location header to the page
  if ($settings['content_location_header']) {
    drupal_set_header('Content-Location: ' . url(drupal_is_front_page() ? '<front>' : $_REQUEST['q'], array(
      'absolute' => TRUE,
      'query' => $query_string,
    )));
  }
}

/**
 * A wrapper for drupal_goto() that does not pass through if $path is an
 * external URL.
 */
function globalredirect_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 301) {
  if (!globalredirect_url_is_external($path)) {
    return drupal_goto($path, $query, $fragment, $http_response_code);
  }
}

/**
 * Check if a path is external (e.g. http://example.com).
 *
 * This is a simple copy of url_is_external() which if given an URL with an
 * invalid protocal will return FALSE, an undesired value. This function will
 * return TRUE given an URL with an invalid protocol.
 */
function globalredirect_url_is_external($path) {
  $colonpos = strpos($path, ':');
  return $colonpos !== FALSE && !preg_match('![/?#]!', drupal_substr($path, 0, $colonpos));
}

/**
 * Implements hook_menu().
 */
function globalredirect_menu() {
  $items['admin/settings/globalredirect'] = array(
    'title' => 'Global Redirect',
    'description' => 'Chose which features you would like enabled for Global Redirect',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'globalredirect_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'globalredirect.admin.inc',
  );
  return $items;
}

/**
 * Drupal 6 backport of drupal_is_cli().
 */
function globalredirect_is_cli() {
  return !isset($_SERVER['SERVER_SOFTWARE']) && (PHP_SAPI == 'cli' || is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0);
}

/**
 * Internal function to determine if GlobalRedirect is active.
 * Several rules have to be checked prior to execution, such as an empty post array,
 * the site must be online and we cannot be running in CLI mode (eg Drush).
 */
function _globalredirect_is_active($settings) {

  /**
   * We need to do a test to make sure we only clean up URL's for the main
   * request. This stops modules such as the Ad Module which had its own script
   * in its folder doing a bootstrap which invoked hook_init and caused some
   * banners to get "cleaned up"
   *
   * @see http://drupal.org/node/205810
   * @see http://drupal.org/node/278615
   */
  if (realpath($_SERVER['SCRIPT_FILENAME']) != realpath(getcwd() . '/index.php')) {
    return FALSE;
  }

  /**
   * If this is a command line request (Drush, etc), skip processing.
   */
  if (globalredirect_is_cli()) {
    return FALSE;
  }

  /**
   * If the site is in offline mode there is little point doing any of this as
   * you might end up redirecting to a 503.
   */
  if (variable_get('site_offline', 0)) {
    return FALSE;
  }

  /**
   * If there is something posted, GlobalRedirect is not active
   */
  if (!empty($_POST)) {
    return FALSE;
  }

  /**
   * If drupal_get_path_alias isn't preset, GlobalRedirect is not active
   */
  if (!function_exists('drupal_get_path_alias')) {
    return FALSE;
  }

  /**
   * If menu_check is enabled AND the menu_get_item function is missing, GlobalRedirect is disabled
   */
  if ($settings['menu_check'] && !function_exists('menu_get_item')) {
    return FALSE;
  }

  /**
   * If we're going to ignore the admin path, return false.
   * This is added to allow people to continue to get to the 'admin*' and 'batch*' paths,
   * even if the module is misbehaving.
   */
  if ($settings['ignore_admin_path'] && (arg(0) == 'admin' || arg(0) == 'batch')) {
    return FALSE;
  }

  /**
   * We seem to have passed all the tests - let say we're active
   */
  return TRUE;
}

/**
 * Return the settings with any defaults mapped over the top
 */
function _globalredirect_get_settings($default_only = FALSE) {
  $defaults = array(
    'deslash' => 1,
    'nonclean_to_clean' => 1,
    'trailing_zero' => 0,
    'menu_check' => 0,
    'case_sensitive_urls' => 1,
    'language_redirect' => 0,
    'canonical' => 0,
    'content_location_header' => 0,
    'term_path_handler' => 1,
    'frontpage_redirect' => 1,
    'ignore_admin_path' => 1,
  );
  if ($default_only) {
    return $defaults;
  }
  return variable_get('globalredirect_settings', array()) + $defaults;
}

Functions

Namesort descending Description
globalredirect_goto A wrapper for drupal_goto() that does not pass through if $path is an external URL.
globalredirect_help Implements hook_help().
globalredirect_init Implements hook_init().
globalredirect_is_cli Drupal 6 backport of drupal_is_cli().
globalredirect_menu Implements hook_menu().
globalredirect_url_is_external Check if a path is external (e.g. http://example.com).
_globalredirect_get_settings Return the settings with any defaults mapped over the top
_globalredirect_is_active Internal function to determine if GlobalRedirect is active. Several rules have to be checked prior to execution, such as an empty post array, the site must be online and we cannot be running in CLI mode (eg Drush).