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

'; case 'admin/build/path-redirect/'. arg(2): case 'admin/build/path-redirect/edit/'. arg(3): return '

'. t("The from path must be an internal Drupal path in the form of 'node/123', 'admin/logs', or 'taxonomy/term/123'. The to path can be either an internal Drupal path as above or a complete external URL such as http://www.example.com/. Furthermore, the to path may contain query arguments (such as 'page=2') and fragment anchors, to make it possible to redirect to 'admin/user?page=1#help'. Most redirects will not contain queries or anchors.") .'

'; } } /** * If a redirect is found for the current path, perform the redirect. */ function path_redirect_check() { // Extract the Drupal path and query string from the request URI. $path = drupal_substr(urldecode(request_uri()), drupal_strlen($GLOBALS['base_path'])); if (preg_match('/^\?q=/', $path)) { $path = preg_replace(array('/^\?q=/', '/&/'), array('', '?'), $path, 1); } // Remove trailing slash from path. $path = preg_replace('/\/\?|\/$/', '', $path); $r = db_fetch_object(db_query("SELECT rid, redirect, query, fragment, type FROM {path_redirect} WHERE path = '%s' OR path = '%s'", $path, urlencode(utf8_encode($path)))); if (!$r) { // If there is no match against path and query string, check just the path $path = preg_replace('/\?.*/', '', $path); $r = db_fetch_object(db_query("SELECT rid, redirect, query, fragment, type FROM {path_redirect} WHERE path = '%s' OR path = '%s'", $path, urlencode(utf8_encode($path)))); } if ($r) { drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); $r->query = strlen($r->query) ? $r->query : NULL; $r->fragment = strlen($r->fragment) ? $r->fragment : NULL; $redirect = url($r->redirect, $r->query, $r->fragment, TRUE); if (url($r->redirect) == url($path)) { // Prevent infinite loop redirection. watchdog('path_redirect', t('Redirect to %redirect is causing an infinite loop; redirect cancelled.', array('%redirect' => $r->redirect)), WATCHDOG_WARNING, l(t('edit'), 'admin/build/path-redirect/edit/'. $r->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 redirected to @redirect.', array('@redirect' => $redirect))); } elseif (variable_get('path_redirect_redirect_warning', 0)) { // Show a message and automatically redirect after 5 seconds. drupal_set_message(t('This page has been moved to @redirect and will redirect in 5 seconds. You may want to update your bookmarks.', array('@redirect' => $redirect)), 'error'); drupal_set_html_head(""); } else { // Perform the redirect directly. unset($_REQUEST['destination']); drupal_goto($r->redirect, $r->query, $r->fragment, $r->type); } } } /** * Implementation of hook_menu */ function path_redirect_menu($may_cache) { $access = user_access('administer redirects'); $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/build/path-redirect', 'title' => t('URL redirects'), 'description' => t('Redirect users from one URL to another'), 'callback' => 'path_redirect_admin', 'access' => $access, ); $items[] = array( 'path' => 'admin/build/path-redirect/list', 'title' => t('List'), 'description' => t('List all URL redirects'), 'access' => $access, 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -3, ); $items[] = array( 'path' => 'admin/build/path-redirect/add', 'title' => t('Add redirect'), 'description' => t('Add a new URL redirect'), 'callback' => 'drupal_get_form', 'callback arguments' => array('path_redirect_edit'), 'access' => $access, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/build/path-redirect/edit', 'title' => t('Edit'), 'description' => t('Edit an existing URL redirect'), 'callback' => 'drupal_get_form', 'callback arguments' => array('path_redirect_edit'), 'access' => $access, 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/build/path-redirect/delete', 'title' => t('Delete'), 'description' => t('Delete an existing URL redirect'), 'callback' => 'drupal_get_form', 'callback arguments' => array('path_redirect_delete_confirm'), 'access' => $access, 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/build/path-redirect/settings', 'title' => t('Settings'), 'description' => t('Configure behavior for URL redirects'), 'callback' => 'drupal_get_form', 'callback arguments' => 'path_redirect_settings', 'access' => $access, 'type' => MENU_LOCAL_TASK, ); } else { path_redirect_check(); } return $items; } /** * Implementation of hook_perm */ function path_redirect_perm() { return array('administer redirects'); } /** * Render a list of redirects for the main admin page. */ function path_redirect_admin() { $header = array( array('data' => t('From'), 'field' => 'path', 'sort' => 'asc'), array('data' => t('To'), 'field' => 'redirect'), array('data' => t('Type'), 'field' => 'type'), array('data' => t('Operations'), 'colspan' => '2'), ); $result = pager_query('SELECT rid, path, redirect, query, fragment, type FROM {path_redirect}'. tablesort_sql($header), 50); $rows = array(); while ($r = db_fetch_object($result)) { $redirect = url($r->redirect, ($r->query ? $r->query : NULL), ($r->fragment? $r->fragment : NULL), TRUE); $rows[] = array( // @todo: Revise the following messy, confusing line. l($r->path, preg_replace('/\?.*/', '', $r->path), array(), strstr($r->path, '?') ? preg_replace('/.*\?/', '', $r->path) : NULL), l($redirect, $redirect), $r->type, l(t('edit'), 'admin/build/path-redirect/edit/'. $r->rid), l(t('delete'), 'admin/build/path-redirect/delete/'. $r->rid), ); } if (empty($rows)) { $rows[] = array(array('data' => t('No redirects have been added.'), 'colspan' => '5')); } $output = theme('table', $header, $rows, array('class' => 'path-redirects')); $output .= '

'. l(t('Add redirect'), 'admin/build/path-redirect/add') .'

'; $output .= theme('pager'); return $output; } /** * Callback for add and edit pages. * * @return * A form for drupal_get_form. */ function path_redirect_edit($rid = FALSE) { if ($rid) { $redirect = path_redirect_load($rid); drupal_set_title(check_plain($redirect['path'])); $output = path_redirect_edit_form($redirect); } else { $breadcrumbs = drupal_get_breadcrumb(); array_push($breadcrumbs, l(t('URL redirects'), 'admin/build/path-redirect')); drupal_set_breadcrumb($breadcrumbs); drupal_set_title(t('Add redirect')); $output = path_redirect_edit_form(); } return $output; } function path_redirect_edit_form($edit = array('path' => '', 'redirect' => '', 'query' => '', 'fragment' => '', 'type' => PATH_REDIRECT_DEFAULT_TYPE, 'rid' => NULL)) { $form['path'] = array( '#type' => 'textfield', '#title' => t('From'), '#description' => t('Enter a Drupal path or path alias to redirect. Fragment anchors #foo are not allowed.'), '#size' => 42, '#maxlength' => 255, '#default_value' => $edit['path'], '#field_prefix' => url(NULL, NULL, NULL, TRUE) . (variable_get('clean_url', 0) ? '' : '?q='), ); $form['redirect'] = array( '#type' => 'item', '#prefix' => '
', '#suffix' => '
', '#title' => t('To'), '#description' => '
'. t('Enter a Drupal path, path alias, or external URL to redirect to. Use %front to redirect to the front page. Enter (optional) queries after "?" and (optional) anchor after "#". Most redirects will not contain queries or fragment anchors.', array('%front' => '')) .'
', ); $form['redirect']['redirect'] = array( '#type' => 'textfield', '#size' => 30, '#maxlength' => 255, '#default_value' => $edit['redirect'], ); $form['redirect'][] = array( '#value' => '?', ); $form['redirect']['query'] = array( '#type' => 'textfield', '#size' => 12, '#maxlength' => 255, '#default_value' => $edit['query'], ); $form['redirect'][] = array( '#value' => '#', ); $form['redirect']['fragment'] = array( '#type' => 'textfield', '#size' => 12, '#maxlength' => 50, '#default_value' => $edit['fragment'], ); $form[] = array( '#value' => "

", // little bit of extra space ); $form['type'] = array( '#type' => 'fieldset', '#title' => t('Redirect Type'), '#collapsible' => TRUE, '#collapsed' => ($edit['type'] == PATH_REDIRECT_DEFAULT_TYPE), ); foreach (path_redirect_status_codes() as $key => $info) { $form['type'][]['type'] = array( '#type' => 'radio', '#title' => $info['title'], '#description' => $info['description'], '#return_value' => $key, '#default_value' => $edit['type'], ); } $form['type']['link'] = array( '#type' => 'markup', '#value' => t('

Find more information about http redirect codes here.

'), ); $form['rid'] = array( '#type' => 'hidden', '#value' => $edit['rid'], ); $form['submit'] = array( '#type' => 'submit', '#value' => $edit['rid'] ? t('Update redirect') : t('Create new redirect'), ); return $form; } function path_redirect_edit_validate($form_id, &$form_values, $form) { // Allow spaces in "from" path; encode everything but the query string $form_values['path'] = urlencode(preg_replace('/\?.*/', '', $form_values['path'])) . (strstr($form_values['path'], '?') ? preg_replace('/.*(\?)/', '$1', $form_values['path']) : ''); form_set_value($form['path'], $form_values['path']); if (trim($form_values['path']) == '') { form_set_error('path', t('You must enter a from path.')); } else { $path_error = ''; // The "from" path should not conflict with another redirect $result = path_redirect_load(NULL, $form_values['path']); if ($result && (!$form_values['rid'] || ($form_values['rid'] !== $result['rid']))) { $path_error .= ' '. t('The from path you entered is already redirected. You can edit this redirect instead.', array('@edit-page' => url('admin/build/path-redirect/edit/'. $result['rid']))); } // Check that the "from" path is valid and contains no # fragment if (strpos($form_values['path'], '#') !== FALSE) { $path_error .= ' '. t('You cannot redirect from a fragment anchor.'); } // Make sure "from" has the form of a local Drupal path if (!valid_url($form_values['path'])) { $path_error .= ' '. t('The redirect from path does not appear valid. This must be a local Drupal path.'); } if (!empty($path_error)) { form_set_error('path', $path_error); } } if (!valid_url($form_values['redirect']) && !valid_url($form_values['redirect'], TRUE) && $form_values['redirect'] != '') { form_set_error('redirect', t('The redirect to path does not appear valid.')); } if ($form_values['redirect'] == '') { $form_values['redirect'] = variable_get('site_frontpage', 'node'); } // check that there there are no redirect loops if ($form_values['path'] === $form_values['redirect']) { form_set_error('redirect', t('You are attempting to redirect the page to itself. This will result in an infinite loop.')); } } function path_redirect_edit_submit($form_id, &$form_values) { path_redirect_save($form_values); drupal_set_message(t('Redirect has been saved.')); drupal_goto('admin/build/path-redirect'); } function path_redirect_save($edit) { if (empty($edit['rid'])) { $edit['rid'] = db_next_id('{path_redirect}_rid'); } else { path_redirect_delete(NULL, NULL, $edit['rid']); } if (empty($edit['type'])) { $edit['type'] = PATH_REDIRECT_DEFAULT_TYPE; } if (empty($edit['query'])) { $edit['query'] = ''; } if (empty($edit['fragment'])) { $edit['fragment'] = ''; } // Remove any other redirects from the same path db_query("DELETE FROM {path_redirect} WHERE path = '%s'", $edit['path']); return db_query("INSERT INTO {path_redirect} (rid, path, redirect, query, fragment, type) VALUES (%d, '%s', '%s', '%s', '%s', '%s')", $edit['rid'], $edit['path'], $edit['redirect'], $edit['query'], $edit['fragment'], $edit['type']); } /** * Retrieve the specified URL redirect */ function path_redirect_load($rid = NULL, $from = NULL) { if (!empty($rid)) { $result = db_fetch_array(db_query("SELECT rid, path, redirect, query, fragment, type FROM {path_redirect} WHERE rid = %d", $rid)); } else if (!empty($from)) { $result = db_fetch_array(db_query("SELECT rid, path, redirect, query, fragment, type FROM {path_redirect} WHERE path = '%s'", $from)); } if ($result) { $result['path'] = urldecode($result['path']); } return $result; } /** * Delete the specified path redirect. This will delete as specifically as * possible, in order: by $rid, by ($from, $to), by $from, or by $to. * Multiple redirects may be deleted if the $to parameter matches more than * one entry. * * This function is part of an API available for external code to use. * * @param $from * Source path of redirect to delete. * @param $to * Destination path or URL of redirect to delete. * @param $rid * Unique ID of redirect to delete. * @return * The result of the deletion query. */ function path_redirect_delete($from = NULL, $to = NULL, $rid = NULL) { if (!empty($rid)) { $result = db_query("DELETE FROM {path_redirect} WHERE rid = %d", $rid); } else if (!empty($from)) { if (!empty($to)) { $result = db_query("DELETE FROM {path_redirect} WHERE path = '%s' AND redirect = '%s'", $from, $to); } else { $result = db_query("DELETE FROM {path_redirect} WHERE path = '%s'", $from); } } else if (!empty($to)) { $result = db_query("DELETE FROM {path_redirect} WHERE redirect = '%s'", $to); } return $result; } function path_redirect_delete_confirm($rid) { $form['rid'] = array( '#type' => 'value', '#value' => $rid, ); $redirect = path_redirect_load($rid); return confirm_form($form, t('Are you sure you want to delete the redirect from %path to %redirect?', array('%path' => $redirect['path'], '%redirect' => $redirect['redirect'])), isset($_GET['destination']) ? $_GET['destination'] : 'admin/build/path-redirect'); } function path_redirect_delete_confirm_submit($form_id, $form_values) { if ($form_values['confirm']) { path_redirect_delete(NULL, NULL, $form_values['rid']); drupal_set_message(t('The redirect has been deleted.')); return 'admin/build/path-redirect'; } } function path_redirect_settings() { $form['path_redirect_nodeapi_enabled'] = array( '#type' => 'radios', '#title' => t('Enable on edit pages'), '#default_value' => variable_get('path_redirect_nodeapi_enabled', 0), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Enable management of URL redirects directly on content editing pages.'), ); $form['path_redirect_redirect_warning'] = array( '#type' => 'radios', '#title' => t('Warn on redirect'), '#default_value' => variable_get('path_redirect_redirect_warning', 0), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Display a warning message to users when a redirect takes place.'), ); $form['path_redirect_allow_bypass'] = array( '#type' => 'radios', '#title' => t('Allow bypassing'), '#default_value' => variable_get('path_redirect_allow_bypass', 0), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Allow users to bypass redirects by appending ?redirect=no to the URL.'), ); return system_settings_form($form); } function path_redirect_form_alter($form_id, &$form) { if (variable_get('path_redirect_nodeapi_enabled', 0) && isset($form['#id']) && $form['#id'] == 'node-form' && user_access('administer redirects')) { $form['redirects'] = array( '#type' => 'fieldset', '#access' => user_access('administer redirects'), '#title' => t('URL redirects'), '#collapsible' => TRUE, '#collapsed' => !db_num_rows(path_redirect_node_redirects($form['#node']->nid)), '#prefix' => '
', '#suffix' => '
', '#weight' => 31, ); $form['redirects']['list'] = _path_redirect_node_form_list($form['#node']); $form['redirects']['path_redirect_add'] = array( '#type' => 'textfield', '#title' => t('Add a redirect from'), '#description' => t('Path from which to redirect to this node. Do not include the leading slash.'), '#maxlength' => 255, '#size' => 42, ); } } function path_redirect_node_redirects($nid) { return db_query(" SELECT rid, path, redirect, type FROM {path_redirect} pr LEFT JOIN {url_alias} ua ON pr.redirect = ua.dst WHERE pr.redirect = 'node/%d' OR ua.src = 'node/%d' ORDER BY pr.path", $nid, $nid); } function _path_redirect_node_form_list($node) { $form['#theme'] = 'path_redirect_node_form_list'; if (!empty($node->nid)) { $result = path_redirect_node_redirects($node->nid); if ($result) { $destination = drupal_get_destination(); $types = path_redirect_status_codes(); while ($redirect = db_fetch_object($result)) { $form['redirects'][$redirect->rid]['path'] = array( '#value' => htmlspecialchars(urldecode($redirect->path)), ); $form['redirects'][$redirect->rid]['redirect'] = array( '#value' => $redirect->redirect, ); $form['redirects'][$redirect->rid]['type'] = array( '#value' => ''. $redirect->type .'', ); $form['redirects'][$redirect->rid]['test'] = array( '#value' => l(t('test'), preg_replace('/\?.*/', '', urldecode($redirect->path)), NULL, strstr($redirect->path, '?') ? preg_replace('/.*\?/', '', $redirect->path) : NULL), ); $form['redirects'][$redirect->rid]['edit'] = array( '#value' => l(t('edit'), 'admin/build/path-redirect/edit/'. $redirect->rid, array(), $destination), ); $form['redirects'][$redirect->rid]['delete'] = array( '#value' => l(t('delete'), 'admin/build/path-redirect/delete/'. $redirect->rid, array(), $destination), ); } } } return $form; } function theme_path_redirect_node_form_list(&$form) { if (count(element_children($form['redirects']))) { $header = array( t('From'), t('To'), t('Type'), array('data' => t('Operations'), 'colspan' => 3), ); foreach (element_children($form['redirects']) as $key) { $rows[] = array( drupal_render($form['redirects'][$key]['path']), drupal_render($form['redirects'][$key]['redirect']), drupal_render($form['redirects'][$key]['type']), drupal_render($form['redirects'][$key]['test']), drupal_render($form['redirects'][$key]['edit']), drupal_render($form['redirects'][$key]['delete']), ); } return theme('table', $header, $rows); } } function path_redirect_nodeapi(&$node, $op, $arg) { if (user_access('administer redirects')) { switch ($op) { case 'validate': if (db_result(db_query("SELECT COUNT(rid) FROM {path_redirect} WHERE path = '%s' AND redirect != '%s' AND redirect != '%s'", $node->path_redirect_add, "node/$node->nid", $node->path))) { form_set_error('path_redirect_add', t('The path is already redirected elsewhere.')); } break; case 'load': break; case 'update': case 'insert': if ($node->path_redirect_add) { path_redirect_save(array( 'path' => $node->path_redirect_add, 'redirect' => 'node/'. $node->nid, 'type' => 301, )); } break; case 'delete': break; } } } /** * This is a copy of drupal_goto() redesigned for use during the bootstrap */ function path_redirect_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { $url = $path; // Make the given path or URL absolute if (!preg_match('/^[a-z]+:\/\//', $url)) { global $base_url; $url = $base_url .'/'. $url; } $url .= (empty($query) ? '' : '?'. $query); $url .= (empty($fragment) ? '' : '#'. $fragment); // Remove newlines from the URL to avoid header injection attacks. $url = str_replace(array("\n", "\r"), '', $url); // Before the redirect, allow modules to react to the end of the page request. bootstrap_invoke_all('exit'); // Even though session_write_close() is registered as a shutdown function, we // need all session data written to the database before redirecting. session_write_close(); header('Location: '. $url, TRUE, $http_response_code); // The "Location" header sends a REDIRECT status code to the http // daemon. In some cases this can go wrong, so we make sure none // of the code below the drupal_goto() call gets executed when we redirect. exit(); } /** * Return an array of 300-range status codes * placed here for clarity */ function path_redirect_status_codes() { $codes = array( 300 => array('title' => t('300 Multiple Choices'), 'description' => t('The request is ambiguous and needs clarification as to which resource was requested.')), 301 => array('title' => t('301 Moved Permanently'), 'description' => t('Moved Permanently. The resource has permanently moved elsewhere, the response indicates where it has gone to. Recommended.')), 302 => array('title' => t('302 Found'), 'description' => t('The resource has temporarily moved elsewhere, the response indicates where it is at present. This is Drupal\'s default redirect type.')), 303 => array('title' => t('303 See Other'), 'description' => t('See Other/Redirect. A preferred alternative source should be used at present.')), 304 => array('title' => t('304 Not Modified'), 'description' => t('The server has identified from the request information that the client\'s copy of the information is up-to-date and the requested information does not need to be sent again.')), 305 => array('title' => t('305 Use Proxy'), 'description' => t('The request must be sent through the indicated proxy server.')), 307 => array('title' => t('307 Temporary Redirect'), 'description' => t('The resource has temporarily moved elsewhere, the response indicates where it is at present. Client should still use this URL.')), ); return $codes; }