'', 'query' => $query_string, 'absolute' => FALSE, 'alias' => FALSE, 'prefix' => '', 'external' => FALSE, 'globalredirect' => TRUE, // inform other modules that hook_url_outbound_alter invoked by globalredirect ); // Get the request path. We cannot use request_path() as it uses a completely trimmed path. // We need just left trimmed. $request_path = globalredirect_request_path(); // Allow Drupal to rewrite the URL // Most commonly this will be used to invoke locale_language_url_rewrite_url(). // Via the locale_url_outbound_alter() implementation of hook_url_outbound_alter(). // NOTE: We use $request_path here as current_path() is $_GET['q'] which gets // altered by core, whereas $request_path is untouched drupal_alter('url_outbound', $request_path, $options, $request_path); // Extract the prefix from the 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 ($request_path != $prefix) { drupal_goto('', $options, 301); } elseif ($settings['nonclean_to_clean'] && ((bool)variable_get('clean_url', 0)) && (strpos(request_uri(), '?q=') || strpos(request_uri(), 'index.php'))) { drupal_goto('', $options, 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; } // Get the current page - it's already "deslashed" $current_path = current_path(); // Optional stripping of "/0". Disabled by default. switch ($settings['trailing_zero']) { case 2 : // If 'taxonomy/term/*' only. If not, break out. if (drupal_substr($current_path, 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($current_path, -2) == '/0') { $current_path = drupal_substr($current_path, 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/(\d+)$`', $current_path, $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_term_load($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 = entity_uri('taxonomy_term', $term)) && $term_path['path'] != $current_path) { $current_path = $term_path['path']; if (isset($options['query'])) { $options['query'] = array_merge($options['query'], $term_path['options']['query']); } elseif (isset($term_path['options']['query'])) { $options['query'] = $term_path['options']['query']; } } } // If Content Translation module is enabled then check the path is correct. if ($settings['language_redirect'] && module_exists('translation') && preg_match('/node\/([0-9]+)(\/view|)$/', $current_path, $matches)) { switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: // Check if there's a translation for the current language of the requested node... // $matches[0] is the entire matches path from above $node_translations = translation_path_get_translations($matches[0]); // If there is, go to the translation. if (!empty($node_translations[$language->language]) && $node_translations[$language->language] != $matches[0]) { globalredirect_goto($node_translations[$language->language], $options); } // If there is no translation, change the language to fit the content else { $node = node_load($matches[1]); // Check the node has a language set, that it isn't "NONE" (und) and that it dosn't match the site's current language if (isset($node->language) && $node->language != LANGUAGE_NONE && $node->language != $language->language) { $all_languages = language_list(); // TODO: Is it possible to get here with a node in a language not in $all_languages? // Change the global $language's prefix, to make drupal_goto() // follow the proper prefix $options['language'] = $language = $all_languages[$node->language]; globalredirect_goto('node/' . $node->nid . $matches[2], $options); } } break; case LOCALE_LANGUAGE_NEGOTIATION_URL_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($current_path, $lang->language); // There is a matching language for this alias if ($alias != $current_path) { if (isset($lang->domain)) { drupal_goto($lang->domain . '/' . $alias, $options, 301); } break; } } } } break; } } // Find an alias (if any) for the request $langcode = isset($options['language']->language) ? $options['language']->language : ''; $alias = drupal_get_path_alias($current_path, $langcode); // Modules may alter outbound links by reference drupal_alter('url_outbound', $alias, $options, $current_path); // If we have a prefix AND an alias, we need to make sure the prefix end with // a slash if ($prefix && $alias) { $prefix .= '/'; } // Alias case sensitivity check. // NOTE: This has changed. In D6 the $alias matched the request (in terms of // case). However in D7 $alias is already a true alias (accurate in case), // and therefore not the "true" request. So, if the alias and the request // path are case-insensitive the same then, if case sensitive URL's are // enabled, the alias SHOULD be the accurate $alias from above, otherwise it // should be the request_path(). // TODO: Test if this breaks the language checking above! if (strcasecmp($alias, request_path()) == 0) { // The alias and the request are identical (case insensitive)... Therefore... $alias = $settings['case_sensitive_urls'] ? $alias : request_path(); } // 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_path != $prefix . $alias) { // If it's not just a slash or user has deslash on, redirect, but avoid // redirecting to a directory if we are only deslashing, as apache mod_dir // can cause an infinite loop. if (str_replace($prefix . $alias, '', $request_path) != '/' || ($settings['deslash'] && !is_dir($alias))) { globalredirect_goto($alias, $options); } } // 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=')) { globalredirect_goto($current_path, $options); } // If on a comment path, try to redirect to the associate node if ($settings['comment_to_node'] && module_exists('comment') && preg_match('/comment\/(?P[0-9]+)$/', $current_path, $matches)) { $cid = $matches['cid']; // Code "borrowed" from comment_permalink($cid) if (($comment = comment_load($cid)) && ($node = node_load($comment->nid))) { // Find the current display page for this comment. $page = comment_get_display_page($comment->cid, $node->type); // If it's more than 0, set the query string parameter // TODO - maintain other params?! if ($page > 0) { $options['query']['page'] = $page; } // Add the fragment to the comment anchor $options['fragment'] = 'comment-' . $comment->cid; // Redirect to the node, with the comment querystring/fragment. globalredirect_goto('node/' . $node->nid, $options); } } // Restore the destination from earlier so its available in other places. if (isset($destination)) { $_GET['destination'] = $destination; } // Add the canonical link to the head of the document if desired. // TODO - The Canonical already gets set by Core for node page views... See http://api.drupal.org/api/function/node_page_view/7 if ($settings['canonical']) { drupal_add_html_head_link(array( 'rel' => 'canonical', 'href' => url(drupal_is_front_page() ? '' : $request_path, array('absolute' => TRUE, 'query' => $query_string)), )); } // Add the Content-Location header to the page if ($settings['content_location_header']) { drupal_add_http_header('Content-Location', url(drupal_is_front_page() ? '' : $request_path, 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 = '', array $options = array(), $http_response_code = 301) { if (!globalredirect_url_is_external($path)) { return drupal_goto($path, $options, $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/config/system/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; } /** * 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 ($_SERVER['SCRIPT_NAME'] != $GLOBALS['base_path'] . 'index.php') { return FALSE; } /** * If this is a command line request (Drush, etc), skip processing. */ if (drupal_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('maintenance_mode', 0) == 1) { 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*' 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, 'comment_to_node' => 0, ); if ($default_only) { return $defaults; } return variable_get('globalredirect_settings', array()) + $defaults; } /** * globalredirect_request_path() is borrowed from request_uri(), but it only ltrim's.. */ function globalredirect_request_path() { if (request_uri()) { if (isset($_REQUEST['q'])) { $path = $_REQUEST['q']; } else { // This is a request using a clean URL. Extract the path from REQUEST_URI. $request_path = strtok(request_uri(), '?'); $base_path_len = drupal_strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); // Unescape and strip $base_path prefix, leaving q without a leading slash. $path = drupal_substr(urldecode($request_path), $base_path_len + 1); } } else { // This is the front page. $path = ''; } // Under certain conditions Apache's RewriteRule directive prepends the value // assigned to $_GET['q'] with a slash. Moreover we can always have a trailing // slash in place, hence we need to normalize $_GET['q']. $path = ltrim($path, '/'); return $path; }