' . 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.') .'

'; break; case 'admin/build/path-redirect/add': case 'admin/build/path-redirect/edit/%': $output .= '

' . t('If you need advanced redirection functionality (i.e. wildcards, etc.), you should be using a webserver rewriting engine rather than this module.') . '

'; 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 %redirect 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 @redirect.', 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 @redirect. You will be automatically redirected in 10 seconds.', array('@redirect' => $redirect['redirect_url'])), 'error'); drupal_set_html_head(''); } 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, //'language' => $language, ); 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); } else { drupal_write_record('path_redirect', $redirect, array('rid')); } 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()) { $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 = ''; } else { $path = $_GET['q']; } } else { if ($path == drupal_get_normal_path(variable_get('site_frontpage', 'node'))) { $path = ''; } } 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, ); }