You are here

path_redirect.module in Path redirect 6

Same filename and directory in other branches
  1. 5 path_redirect.module

File

path_redirect.module
View source
<?php

/**
 * Implements hook_help().
 */
function path_redirect_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/build/path-redirect':
    case 'admin/build/path-redirect/list':

      //$output .= '<p>' . t('Here you can set up URL redirecting for this site. Any existing or non-existing path within this site can redirect to any internal or external URL.') .'</p>';
      break;
    case 'admin/build/path-redirect/add':
    case 'admin/build/path-redirect/edit/%':
      $output .= '<p>' . t('If you need advanced redirection functionality (i.e. wildcards, etc.), you should be using a <a href="http://en.wikipedia.org/wiki/Mod_rewrite">webserver rewriting engine</a> rather than this module.') . '</p>';
      break;
  }
  return $output;
}

/**
 * Implements hook_perm().
 */
function path_redirect_perm() {
  return array(
    'administer redirects',
  );
}

/**
 * Implements hook_menu().
 */
function path_redirect_menu() {
  $items['admin/build/path-redirect'] = array(
    'title' => 'URL redirects',
    'description' => 'Redirect users from one URL to another.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'path_redirect_admin_redirects',
    ),
    'access arguments' => array(
      'administer redirects',
    ),
    'file' => 'path_redirect.admin.inc',
  );
  $items['admin/build/path-redirect/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/build/path-redirect/add'] = array(
    'title' => 'Add redirect',
    'description' => 'Add a new URL redirect.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'path_redirect_edit_form',
    ),
    'access arguments' => array(
      'administer redirects',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'path_redirect.admin.inc',
  );
  $items['admin/build/path-redirect/edit/%path_redirect'] = array(
    'title' => 'Edit redirect',
    'description' => 'Edit an existing URL redirect.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'path_redirect_edit_form',
      4,
    ),
    'access arguments' => array(
      'administer redirects',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'path_redirect.admin.inc',
  );
  $items['admin/build/path-redirect/delete/%path_redirect'] = array(
    'title' => 'Delete redirect',
    'description' => 'Delete an existing URL redirect.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'path_redirect_delete_form',
      4,
    ),
    'access arguments' => array(
      'administer redirects',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'path_redirect.admin.inc',
  );
  $items['admin/build/path-redirect/settings'] = array(
    'title' => 'Settings',
    'description' => 'Configure behavior for URL redirects.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'path_redirect_settings_form',
    ),
    'access arguments' => array(
      'administer redirects',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
    'file' => 'path_redirect.admin.inc',
  );
  $items['js/path_redirect/autocomplete_404'] = array(
    'page callback' => 'path_redirect_js_autocomplete_404',
    'access arguments' => array(
      'administer redirects',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'path_redirect.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_init().
 */
function path_redirect_init() {
  if (defined('MAINTENANCE_MODE')) {
    return;
  }
  path_redirect_goto();
}
function path_redirect_goto($redirect = NULL) {
  if (!isset($redirect)) {
    $path = path_redirect_get_path();
    $language = $GLOBALS['language']->language;
    $query = path_redirect_get_query();
    $redirect = path_redirect_load_by_source($path, $language, $query);
  }
  elseif (is_numeric($redirect)) {
    $redirect = path_redirect_load($redirect);
  }
  if ($redirect) {

    // Create the absolute redirection URL.
    $redirect['redirect_url'] = url($redirect['redirect'], array(
      'query' => $redirect['query'],
      'fragment' => $redirect['fragment'],
      'absolute' => TRUE,
    ));

    // Update the last used timestamp so that unused redirects can be purged.
    db_query("UPDATE {path_redirect} SET last_used = %d WHERE rid = %d", time(), $redirect['rid']);
    if (url($redirect['redirect']) == url($_GET['q'])) {

      // Prevent infinite loop redirection.
      watchdog('path_redirect', 'Redirect to <code>%redirect</code> is causing an infinite loop; redirect cancelled.', array(
        '%redirect' => $redirect['redirect_url'],
      ), WATCHDOG_WARNING, l(t('Edit'), 'admin/build/path-redirect/edit/' . $redirect['rid']));
    }
    elseif (variable_get('path_redirect_allow_bypass', 0) && isset($_GET['redirect']) && $_GET['redirect'] === 'no') {

      // If the user has requested not to be redirected, show a message.
      drupal_set_message(t('This page has been moved to <a href="@redirect">@redirect</a>.', array(
        '@redirect' => $redirect['redirect_url'],
      )));
    }
    elseif (variable_get('path_redirect_redirect_warning', 0)) {

      // Show a message and automatically redirect after 10 seconds.
      drupal_set_message(t('This page has been moved to <a href="@redirect">@redirect</a>. You will be automatically redirected in 10 seconds.', array(
        '@redirect' => $redirect['redirect_url'],
      )), 'error');
      drupal_set_html_head('<meta http-equiv="refresh" content="10;url=' . $redirect['redirect_url'] . '" />');
    }
    else {

      // Perform the redirect.
      unset($_REQUEST['destination']);
      drupal_goto($redirect['redirect_url'], NULL, NULL, $redirect['type']);
    }
  }

  // If this is being executed as a menu item, return a not found flag.
  return MENU_NOT_FOUND;
}

/**
 * Implements hook_cron().
 */
function path_redirect_cron() {

  // Purge inactive redirects from the database.
  if ($purge = variable_get('path_redirect_purge_inactive', 0)) {
    $query = db_query("SELECT rid FROM {path_redirect} WHERE last_used < %d", time() - $purge);
    $rids = array();
    while ($rid = db_result($query)) {
      $rids[] = $rid;
    }
    if ($rids) {
      path_redirect_delete_multiple($rids);
      watchdog('path_redirect', format_plural(count($rids), 'Removed 1 inactive redirect from the database.', 'Removed @count inactive redirects from the database.'));
    }
  }
}

/**
 * Implements hook_path_redirect_operations().
 */
function path_redirect_path_redirect_operations() {
  $operations = array(
    'delete' => array(
      'action' => t('Delete'),
      'action_past' => t('Deleted'),
      'callback' => 'path_redirect_delete_multiple',
      'confirm' => TRUE,
    ),
  );
  return $operations;
}

/**
 * Implements hook_content_extra_fields().
 *
 * This is so that the URL redirect fieldset is sortable on the node edit form.
 */
function path_redirect_content_extra_fields() {
  $extras['path_redirect'] = array(
    'label' => t('URL redirects'),
    'description' => t('Path redirect module listing'),
    'weight' => 30,
  );
  return $extras;
}

/**
 * Implements hook_form_alter().
 *
 * Add a summary of redirects pointing to a node on its edit form.
 */
function path_redirect_form_alter(&$form, $form_state, $form_id) {
  if (substr($form_id, -10) == '_node_form' && !empty($form['#node']->nid)) {
    $redirect = array(
      'redirect' => 'node/' . $form['#node']->nid,
      'language' => $form['#node']->language,
    );
    $form['path_redirect'] = array(
      '#type' => 'fieldset',
      '#title' => t('URL redirects'),
      '#description' => t('The following are a list of URL redirects that point to this location.'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#access' => user_access('administer redirects'),
      '#weight' => 30,
      '#group' => 'additional_settings',
      '#attached' => array(
        'js' => array(
          'vertical-tabs' => drupal_get_path('module', 'path_redirect') . '/path_redirect.js',
        ),
      ),
    );
    module_load_include('inc', 'path_redirect', 'path_redirect.admin');
    $form['path_redirect']['table'] = path_redirect_list_redirects(array(), array(
      'redirect' => 'node/' . $form['#node']->nid,
    ));
    $form['path_redirect']['add'] = array(
      '#value' => path_redirect_local_actions($redirect),
    );
  }
}
function path_redirect_local_actions($redirect = array()) {
  $links = array(
    'add' => array(
      'title' => $redirect ? t('Add redirect to this location') : t('Add redirect'),
      'href' => 'admin/build/path-redirect/add',
      'query' => drupal_get_destination() . ($redirect ? '&' . drupal_query_string_encode($redirect) : ''),
    ),
  );
  return theme('links', $links, array(
    'class' => 'item-list action-links',
  ));
}

/**
 * Implements hook_nodeapi().
 */
function path_redirect_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'presave':
      if (!empty($node->nid) && !empty($node->path)) {
        path_redirect_check_alias_changed('node/' . $node->nid, $node->path, $node->language);
      }
      break;
    case 'delete':

      // When a node is deleted, also delete the redirects to it (they will result in a 404).
      path_redirect_delete_multiple(NULL, array(
        'source' => 'node/' . $node->nid,
      ));
      path_redirect_delete_multiple(NULL, array(
        'redirect' => 'node/' . $node->nid,
      ));
      break;
  }
}

/**
 * Creates a redirect if an URL alias is being changed.
 *
 * @param $path
 *   The base (normal) path.
 * @param $new_alias
 *   The new alias for the path.
 * @param $language
 *   The language of the alias being created.
 * @return
 *   TRUE if a redirect was created, or FALSE otherwise.
 */
function path_redirect_check_alias_changed($path, $new_alias, $language = '') {
  if (!variable_get('path_redirect_auto_redirect', 1) || empty($new_alias)) {
    return FALSE;
  }
  $old_alias = drupal_get_path_alias($path, $language);
  if ($old_alias != $path && $old_alias != $new_alias) {

    // If the user is manually changing the path alias, add a redirect from the old alias to the node.
    $redirect = array(
      'source' => $old_alias,
      'redirect' => $path,
    );
    path_redirect_save($redirect);
    return $redirect;
  }
}

/**
 * Implements hook_taxonomy().
 */
function path_redirect_taxonomy($op, $type, $array = NULL) {
  if ($op == 'delete' && $type == 'term') {

    // Delete any redirects to valid taxonomy paths.
    $term = (object) $array;
    $term_uri = taxonomy_term_path($term);
    path_redirect_delete_multiple(NULL, array(
      'source' => $term_uri,
    ));
    path_redirect_delete_multiple(NULL, array(
      'redirect' => $term_uri,
    ));
    if ($term_uri != "taxonomy/term/{$term->tid}") {
      path_redirect_delete_multiple(NULL, array(
        'source' => "taxonomy/term/{$term->tid}",
      ));
      path_redirect_delete_multiple(NULL, array(
        'redirect' => "taxonomy/term/{$term->tid}",
      ));
    }
  }
}

/**
 * Implements hook_user().
 */
function path_redirect_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'delete') {

    // Delete any redirects to valid user paths.
    path_redirect_delete_multiple(NULL, array(
      'source' => 'user/' . $account->uid,
    ));
    path_redirect_delete_multiple(NULL, array(
      'redirect' => 'user/' . $account->uid,
    ));
  }
}

/**
 * Save an URL redirect to the database.
 */
function path_redirect_save(&$redirect) {

  // Merge default values.
  $redirect += array(
    'rid' => NULL,
    'query' => '',
    'fragment' => '',
    'language' => '',
    'type' => variable_get('path_redirect_default_status', 301),
    'last_used' => time(),
  );

  // Convert query arrays into a saveable query string.
  if (isset($redirect['source_query']) && is_array($redirect['source_query'])) {
    $redirect['source'] .= '?' . drupal_query_string_encode($redirect['source_query']);
  }
  if (is_array($redirect['query'])) {
    $redirect['query'] = drupal_query_string_encode($redirect['query']);
  }

  // Allow spaces in "from" path
  // @todo Move to validation?
  $redirect['source'] = str_replace('+', ' ', $redirect['source']);

  // Remove beginning and trailing slashes from path.
  // @todo Move to validation?
  $redirect['source'] = trim($redirect['source'], '\\/?');
  path_redirect_clear_cache();
  if (empty($redirect['rid'])) {
    drupal_write_record('path_redirect', $redirect);
    module_invoke_all('path_redirect_insert', $redirect);
  }
  else {
    drupal_write_record('path_redirect', $redirect, array(
      'rid',
    ));
    module_invoke_all('path_redirect_update', $redirect);
  }
  return $redirect;
}

/**
 * Load a redirect by ID.
 *
 * @param $rid
 *   An integer with the redirect ID.
 */
function path_redirect_load($rid) {
  $redirect = path_redirect_load_multiple(array(
    $rid,
  ));
  return $redirect ? reset($redirect) : FALSE;
}

/**
 * Load a redirect by incoming path and language.
 *
 * @param $source
 *   The incoming path.
 * @param $language
 *   An optional language code. If not provided this will examine all language-
 *   neutral redirects.
 * @param $query
 *   An optional query string to match.
 */
function path_redirect_load_by_source($source, $language = '', $query = array()) {
  $where = $query ? "(source = '%s' OR source LIKE '%s%%')" : "source = '%s'";
  $args = $query ? array(
    $source,
    $source . '?',
  ) : array(
    $source,
  );
  $args[] = $language;
  $rid_query = db_query("SELECT rid FROM {path_redirect} WHERE {$where} AND language IN ('%s', '') ORDER BY language DESC, source DESC, rid DESC", $args);
  $rids = array();
  while ($rid = db_result($rid_query)) {
    $rids[] = $rid;
  }
  if ($rids && ($redirects = path_redirect_load_multiple($rids))) {

    // Narrow down the list of candidates.
    foreach ($redirects as $rid => $redirect) {
      if (!empty($redirect['source_query'])) {
        if (empty($query) || !path_redirect_compare_array($redirect['source_query'], $query)) {
          unset($redirects[$rid]);
          continue;
        }
      }

      // Add a case sensitive matches condition to be used in sorting.
      if ($source !== $redirect['source']) {
        $redirects[$rid]['weight'] = 1;
      }
    }
    if (!empty($redirects)) {

      // Sort the redirects in the proper order.
      uasort($redirects, '_path_redirect_uasort');

      // Allow other modules to alter the redirect candidates before selecting the top one.
      $context = array(
        'language' => $language,
        'query' => $query,
      );
      drupal_alter('path_redirect_load_by_source', $redirects, $source, $context);
      return !empty($redirects) ? reset($redirects) : FALSE;
    }
  }
  return FALSE;
}
function path_redirect_load_multiple($rids = NULL, $conditions = array()) {
  if (isset($rids) && empty($rids)) {
    return array();
  }
  $query = array();
  _path_redirect_build_conditions($query, $rids, $conditions);
  $sql = "SELECT * FROM {path_redirect} WHERE " . implode(' AND ', $query['conditions']);
  $query = db_query($sql, $query['args']);
  $redirects = array();
  while ($redirect = db_fetch_array($query)) {

    // Unpack query strings into arrays.
    if (!isset($redirect['source_query']) || !is_array($redirect['source_query'])) {
      $parsed = parse_url($redirect['source']) + array(
        'query' => '',
      );
      $redirect['source_query'] = path_redirect_get_query_array($parsed['query']);
      $redirect['source'] = $parsed['path'];
    }
    $redirect['query'] = path_redirect_get_query_array($redirect['query']);
    $redirects[$redirect['rid']] = $redirect;
  }
  return $redirects;
}

/**
 * uasort callback; Compare redirects based on language neutrality and rids.
 */
function _path_redirect_uasort($a, $b) {
  $a_weight = isset($a['weight']) ? $a['weight'] : 0;
  $b_weight = isset($b['weight']) ? $b['weight'] : 0;
  if ($a_weight != $b_weight) {

    // First sort by weight (case sensitivity).
    return $a_weight > $b_weight;
  }
  elseif ($a['language'] != $b['language']) {

    // Then sort by language specific over language neutral.
    return $a['language'] == '';
  }
  elseif (empty($a['source_query']) != empty($b['source_query'])) {

    // Then sort by redirects that do not have query strings over ones that do.
    return empty($a['source_query']);
  }
  else {

    // Lastly sort by the highest redirect ID.
    return $a['rid'] < $b['rid'];
  }
}
function _path_redirect_build_conditions(&$query, $rids, $conditions) {
  static $schema;
  if (!isset($schema)) {
    $schema = !defined('MAINTENANCE_MODE') ? drupal_get_schema('path_redirect') : drupal_get_schema_unprocessed('path_redirect', 'path_redirect');
  }
  $query += array(
    'conditions' => array(),
    'args' => array(),
  );
  if ($rids) {
    $conditions += array(
      'rid' => array(),
    );
    $conditions['rid'] = array_merge($rids, (array) $conditions['rid']);
  }
  if ($conditions) {
    foreach ($conditions as $field => $value) {
      if (!is_string($field) || !isset($schema['fields'][$field])) {
        continue;
      }

      //if ($field == 'langauge' && !is_array($value)) {

      //  $value = array($value, '');

      //}
      $type = $schema['fields'][$field]['type'];
      if (is_array($value)) {
        $conditions[$field] = "{$field} IN (" . db_placeholders($value, $type) . ')';
        $query['args'] = array_merge($query['args'], $value);
      }
      else {
        $conditions[$field] = "{$field} = " . db_type_placeholder($type);
        $query['args'][] = $value;
      }
    }
  }
  $query['conditions'] = array_merge($query['conditions'], $conditions);
  return $query;
}
function path_redirect_clear_cache() {
  cache_clear_all(NULL, 'cache_page');
}

/**
 * Delete a redirect.
 *
 * @param $rid
 *   The ID of the redirect to delete.
 */
function path_redirect_delete($rid, $deprecated = FALSE) {

  // @todo Remove legacy path_redirect_delete support for pathauto.
  if (is_string($rid) && is_string($deprecated)) {
    return path_redirect_delete_multiple(NULL, array(
      'source' => $rid,
      'redirect' => $deprecated,
    ));
  }
  elseif (is_array($rid) && !isset($rid['rid'])) {
    $rid = $rid['rid'];
  }
  return path_redirect_delete_multiple(array(
    $rid,
  ));
}

/**
 * Delete multiple redirects.
 *
 * @param $rids
 *   An optional array or redirect IDs.
 * @param $conditions
 *   An optional array of conditions keyed by field to match.
 * @return
 *   The number of deleted redirects.
 */
function path_redirect_delete_multiple($rids = NULL, $conditions = array()) {
  if ($redirects = path_redirect_load_multiple($rids, $conditions)) {
    foreach ($redirects as $redirect) {
      module_invoke_all('path_redirect_delete', $redirect);
    }
  }
  $query = array();
  _path_redirect_build_conditions($query, $rids, $conditions);
  $sql = 'DELETE FROM {path_redirect} WHERE ' . implode(' AND ', $query['conditions']);
  db_query($sql, $query['args']);
  $deleted = db_affected_rows();
  path_redirect_clear_cache();
  return $deleted;
}
function path_redirect_get_path($path = NULL) {
  if (!isset($path)) {
    if (drupal_is_front_page()) {
      $path = '<front>';
    }
    else {
      $path = $_GET['q'];
    }
  }
  else {
    if ($path == drupal_get_normal_path(variable_get('site_frontpage', 'node'))) {
      $path = '<front>';
    }
  }
  return $path;
}
function path_redirect_get_query($query = NULL) {
  if (!isset($query)) {
    $query = $_GET;
  }
  unset($query['q']);
  return $query;
}
function path_redirect_build_url($path, $query = '', $fragment = '', $clean_url = NULL) {
  if (!isset($clean_url)) {
    $clean_url = variable_get('clean_url', 0);
  }
  $url = $path;
  if ($query) {
    $url .= $clean_url ? '?' : '&';
    if (is_array($query)) {
      $url .= drupal_query_string_encode($query);
    }
    else {
      $url .= $query;
    }
  }
  if ($fragment) {
    $url .= '#' . $fragment;
  }
  return $url;
}

/**
 * Compare tha all values and associations in one array match another array.
 *
 * We cannot use array_diff_assoc() here because we need to be recursive.
 *
 * @param $match
 *   The array that has the values.
 * @param $haystack
 *   The array that will be searched for values.
 */
function path_redirect_compare_array($match, $haystack) {
  foreach ($match as $key => $value) {
    if (!array_key_exists($key, $haystack)) {
      return FALSE;
    }
    elseif (is_array($value)) {
      if (!is_array($haystack[$key])) {
        return FALSE;
      }
      elseif (!path_redirect_compare_array($value, $haystack[$key])) {
        return FALSE;
      }
    }
    elseif ($value != $haystack[$key]) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Split an URL-encoded query string into an array.
 *
 * @param $query
 *   The query string to split.
 *
 * @return
 *   An array of url decoded couples $param_name => $value.
 */
function path_redirect_get_query_array($query) {
  $result = array();
  if (!empty($query)) {
    foreach (explode('&', $query) as $param) {
      $param = explode('=', $param);
      $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
    }
  }
  return $result;
}
function path_redirect_variables() {
  return array(
    'path_redirect_redirect_warning' => 0,
    'path_redirect_allow_bypass' => 0,
    'path_redirect_auto_redirect' => 1,
    'path_redirect_purge_inactive' => 0,
    'path_redirect_default_status' => 301,
    'path_redirect_nodeapi_enabled' => NULL,
  );
}

Functions

Namesort descending Description
path_redirect_build_url
path_redirect_check_alias_changed Creates a redirect if an URL alias is being changed.
path_redirect_clear_cache
path_redirect_compare_array Compare tha all values and associations in one array match another array.
path_redirect_content_extra_fields Implements hook_content_extra_fields().
path_redirect_cron Implements hook_cron().
path_redirect_delete Delete a redirect.
path_redirect_delete_multiple Delete multiple redirects.
path_redirect_form_alter Implements hook_form_alter().
path_redirect_get_path
path_redirect_get_query
path_redirect_get_query_array Split an URL-encoded query string into an array.
path_redirect_goto
path_redirect_help Implements hook_help().
path_redirect_init Implements hook_init().
path_redirect_load Load a redirect by ID.
path_redirect_load_by_source Load a redirect by incoming path and language.
path_redirect_load_multiple
path_redirect_local_actions
path_redirect_menu Implements hook_menu().
path_redirect_nodeapi Implements hook_nodeapi().
path_redirect_path_redirect_operations Implements hook_path_redirect_operations().
path_redirect_perm Implements hook_perm().
path_redirect_save Save an URL redirect to the database.
path_redirect_taxonomy Implements hook_taxonomy().
path_redirect_user Implements hook_user().
path_redirect_variables
_path_redirect_build_conditions
_path_redirect_uasort uasort callback; Compare redirects based on language neutrality and rids.