Index: globalredirect.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/globalredirect/globalredirect.admin.inc,v
retrieving revision 1.1.2.8
diff -u -r1.1.2.8 globalredirect.admin.inc
--- globalredirect.admin.inc	3 Mar 2010 16:56:21 -0000	1.1.2.8
+++ globalredirect.admin.inc	20 May 2010 14:01:37 -0000
@@ -10,6 +10,8 @@
  *  Function to generate the form setting array
  */
 function globalredirect_settings() {
+  $settings = _globalredirect_get_settings();
+  
   $form['globalredirect_deslash'] = array(
     '#type' => 'radios',
     '#title' => t('Deslash'),
@@ -18,7 +20,7 @@
       GLOBALREDIRECT_FEATURE_DISABLED => t('Off'),
       GLOBALREDIRECT_DESLASH_ENABLED  => t('On'),
     ),
-    '#default_value' => variable_get('globalredirect_deslash', GLOBALREDIRECT_DESLASH_ENABLED),
+    '#default_value' => $settings->deslash,
   );
 
   $form['globalredirect_nonclean2clean'] = array(
@@ -29,7 +31,7 @@
       GLOBALREDIRECT_FEATURE_DISABLED => t('Off'),
       GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED  => t('On'),
     ),
-    '#default_value' => variable_get('globalredirect_nonclean2clean', GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED),
+    '#default_value' => $settings->nonclean2clean,
   );
 
   $form['globalredirect_trailingzero'] = array(
@@ -41,7 +43,7 @@
       GLOBALREDIRECT_TRAILINGZERO_TAXTERM => t('Enabled for taxonomy term pages only'),
       GLOBALREDIRECT_TRAILINGZERO_ALL => t('Enabled for all pages'),
     ),
-    '#default_value' => variable_get('globalredirect_trailingzero', GLOBALREDIRECT_FEATURE_DISABLED),
+    '#default_value' => $settings->trailingzero,
   );
 
   $form['globalredirect_menu_check'] = array(
@@ -52,7 +54,7 @@
       GLOBALREDIRECT_FEATURE_DISABLED => t('Disabled'),
       GLOBALREDIRECT_MENU_CHECK_ENABLED => t('Enabled'),
     ),
-    '#default_value' => variable_get('globalredirect_menu_check', GLOBALREDIRECT_FEATURE_DISABLED),
+    '#default_value' => $settings->menu_check,
   );
 
   $form['globalredirect_case_sensitive_urls'] = array(
@@ -63,7 +65,19 @@
       GLOBALREDIRECT_FEATURE_DISABLED => t('Disabled'),
       GLOBALREDIRECT_CASE_SENSITIVE_URLS_ENABLED => t('Enabled'),
     ),
-    '#default_value' => variable_get('globalredirect_case_sensitive_urls', GLOBALREDIRECT_CASE_SENSITIVE_URLS_ENABLED),
+    '#default_value' => $settings->case_sensitive_urls,
+  );
+
+  $form['globalredirect_mixed_language'] = array(
+    '#type' => 'radios',
+    '#title' => t('Avoid mixed language pages'),
+    '#description' => t('If enabled, any node page with a node language different from the interface language will be redirected.'),
+    '#options' => array(
+      GLOBALREDIRECT_FEATURE_DISABLED => t("Disabled, don't redirect."),
+      GLOBALREDIRECT_MIXED_LANGUAGE_REDIRECT_TRANSLATION => t("Redirect to the node translation in the current interface language, if available."),
+      GLOBALREDIRECT_MIXED_LANGUAGE_REDIRECT_NATIVE => t("Redirect, change the interface language to the node's language"),
+    ),
+    '#default_value' => $settings->mixed_language,
   );
 
   return system_settings_form($form);
Index: globalredirect.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/globalredirect/globalredirect.module,v
retrieving revision 1.1.2.4.2.5.2.21
diff -u -r1.1.2.4.2.5.2.21 globalredirect.module
--- globalredirect.module	18 Apr 2010 13:34:47 -0000	1.1.2.4.2.5.2.21
+++ globalredirect.module	26 May 2010 00:51:03 -0000
@@ -20,6 +20,9 @@
 
 define('GLOBALREDIRECT_MENU_CHECK_ENABLED', 1);
 
+define('GLOBALREDIRECT_MIXED_LANGUAGE_REDIRECT_TRANSLATION', 1);
+define('GLOBALREDIRECT_MIXED_LANGUAGE_REDIRECT_NATIVE', 2);
+
 /**
  * Implements hook_help().
  */
@@ -31,10 +34,14 @@
 }
 
 /**
- * Implements hook_init().
+ * Check if globalredirect should run at all.
+ *
+ * @return bool
+ *   if FALSE, globalredirect will not run this time.
+ *   It might still run, if its hook_init is called
+ *   a second time during the same request.
  */
-function globalredirect_init() {
-  global $language;
+function _globalredirect_is_active($settings) {
 
   /**
    * We need to do a test to make sure we only clean up URL's for the main
@@ -66,13 +73,6 @@
   if (variable_get('site_offline', 0) == 1) return FALSE;
 
   /**
-   * Use of menu_get_item should be optional as it appears in some situations
-   * it causes WSOD's...
-   */
-  $menu_check = variable_get('globalredirect_menu_check', GLOBALREDIRECT_FEATURE_DISABLED);
-
-
-  /**
    * We need to make sure this hook only fires in certain conditions:
    *   1) If the 'drupal_get_path' function exists. Sometimes hook_init gets
    *      called twice, the first call hasn't included path.inc.
@@ -80,19 +80,37 @@
    *      posts to an source path rather than the alias. GlobalRedirect
    *      sometimes interrupts the post and redirects to the alias instead.
    */
-
-  if (function_exists('drupal_get_path_alias') &&
-      ($menu_check == GLOBALREDIRECT_FEATURE_DISABLED || ($menu_check == GLOBALREDIRECT_MENU_CHECK_ENABLED && function_exists('menu_get_item'))) &&
-      empty($_POST)) {
-
-    // If menu checking is enabled, do the check. Feature disabled by default.
-    if ($menu_check == GLOBALREDIRECT_MENU_CHECK_ENABLED) {
-      // Check the access on the current path, return FALSE if access not
-      // allowed. This stops redirection for paths without access permission.
+  if (!function_exists('drupal_get_path_alias')) {
+    return FALSE;
+  }
+  
+  if (!empty($_POST)) {
+    return FALSE;
+  }
+  
+  if ($settings->menu_check) {
+    if (!function_exists('menu_get_item')) {
+      return FALSE;
+    } else {
       $item = menu_get_item();
       _menu_check_access($item, $item['map']);
-      if (!$item['access']) return FALSE;
+      if (!$item['access']) {
+        return FALSE;
+      }
     }
+  }
+  
+  return TRUE;
+}
+
+
+/**
+ * Implements hook_init().
+ */
+function globalredirect_init() {
+  $settings = _globalredirect_get_settings(TRUE);
+  
+  if (_globalredirect_is_active($settings)) {
 
     // Store the destination from the $_REQUEST as it breaks things if we leave
     // it in - restore it at the end...
@@ -100,110 +118,336 @@
       $destination = $_REQUEST['destination'];
       unset($_REQUEST['destination']);
     }
+    
+    _globalredirect_do_init($settings);
+
+    // Restore the destination from earlier so its available in other places.
+    if (isset($destination)) {
+      $_REQUEST['destination'] = $destination;
+    }
+  }
+}
+
+
+function _globalredirect_do_init($settings) {
+  
+  if (TRUE) {
 
     // 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 that language_url_rewrite() takes path (by reference) as the
-      // first argument but does not use it at all
-      $path = $_REQUEST['q'];
-      language_url_rewrite($path, $options);
-    }
-    $prefix = rtrim($options['prefix'], '/');
-
+    
+    $options = _globalredirect_get_options();
 
     // Do a check if this is a front page
     if (drupal_is_front_page()) {
-      // Redirect if the current request does not refer to the front page in the
-      // configured fashion (with or without a prefix)
-      if ($_REQUEST['q'] != $prefix) {
-        drupal_goto('', $query_string, NULL, 301);
-      }
-      elseif((variable_get('globalredirect_nonclean2clean', GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED) == GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED) &&
-              ((bool)variable_get('clean_url', 0)) &&
-              (strpos(request_uri(), '?q=') || strpos(request_uri(), 'index.php'))) {
-        drupal_goto('', $query_string, NULL, 301);
-      }
+      _globalredirect_frontpage($settings, rtrim($options['prefix'], '/'), $query_string);
       // 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')
-    $redirect_slash = variable_get('globalredirect_deslash', GLOBALREDIRECT_DESLASH_ENABLED) == GLOBALREDIRECT_DESLASH_ENABLED;
-    $request = $redirect_slash ? trim($_GET['q'], '/') : $_GET['q'];
-
-    // Optional stripping of "/0". Disabled by default.
-    switch (variable_get('globalredirect_trailingzero', GLOBALREDIRECT_FEATURE_DISABLED)) {
-      case GLOBALREDIRECT_TRAILINGZERO_TAXTERM :
-        // 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 GLOBALREDIRECT_TRAILINGZERO_ALL :
-        // If last 2 characters of URL are /0 then trim them off
-        if (drupal_substr($request, -2) == '/0') {
-          $request = rtrim($request, '/0');
-        }
-    }
+    
+    $request = _globalredirect_get_request($settings);
+    
+    $original_request = $request;
+    $original_options = $options;
+    
+    _globalredirect_alter_destination($settings, $request, $options);
+    
+    
+    // $options['prefix'] might have changed.
+    $prefix = rtrim($options['prefix'], '/');
 
 
     // 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.
-    if ($alias && variable_get('globalredirect_case_sensitive_urls', GLOBALREDIRECT_CASE_SENSITIVE_URLS_ENABLED) == GLOBALREDIRECT_CASE_SENSITIVE_URLS_ENABLED) {
-      $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;
-      }
-    }
+    // This function will call custom_url_rewrite_outbound(),
+    // which can have any implementation.
+    $alias = _globalredirect_find_alias($settings, $request, $options);
+    
+    // We don't know what custom_url_rewrite_outbound() does,
+    // so we wait until now to avoid side effects.
+    $options['query'] = $query_string;
+    
+    $prefix_with_alias = ($prefix && $alias) ? $prefix . '/' . $alias : $prefix . $alias;
 
     // 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 ($_REQUEST['q'] != $prefix_with_alias) {
       // If it's not just a slash or user has deslash on, redirect
-      if (str_replace($prefix . $alias, '', $_REQUEST['q']) != '/' || $redirect_slash) {
-        drupal_goto($alias, $query_string, NULL, 301);
+      if (str_replace($prefix_with_alias, '', $_REQUEST['q']) != '/' || $settings->deslash) {
+        _globalredirect_goto($alias, $options, 301);
       }
     }
+    
+    // now compare the language domain
+    if ($original_options['base_url'] != $options['base_url']) {
+      _globalredirect_goto($alias, $options, 301);
+    }
 
     // If no alias was returned, the final check is to direct non-clean to
     // clean - if clean is enabled
-    if ((variable_get('globalredirect_nonclean2clean', GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED) == GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED) && ((bool)variable_get('clean_url', 0)) && strpos(request_uri(), '?q=')) {
-      drupal_goto($request, $query_string, NULL, 301);
+    if (
+      $settings->nonclean2clean &&
+      (bool)variable_get('clean_url', 0) &&
+      strpos(request_uri(), '?q=')
+    ) {
+      _globalredirect_goto($request, $options, 301);
     }
+  }
+}
 
-    // Restore the destination from earlier so its available in other places.
-    if (isset($destination)) $_REQUEST['destination'] = $destination;
+
+function _globalredirect_get_request($settings) {
+
+  // Trim any trailing slash off the end (eg, 'node/1/' to 'node/1')
+  $request = $settings->deslash ? trim($_GET['q'], '/') : $_GET['q'];
+  
+  // TODO: maybe this logic should be part of _globalredirect_alter_destination().
+  // Optional stripping of "/0". Disabled by default.
+  switch ($settings->trailingzero) {
+    case GLOBALREDIRECT_TRAILINGZERO_TAXTERM :
+      // 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 GLOBALREDIRECT_TRAILINGZERO_ALL :
+      // If last 2 characters of URL are /0 then trim them off
+      if (drupal_substr($request, -2) == '/0') {
+        $request = rtrim($request, '/0');
+      }
   }
+  
+  return $request;
 }
 
+
+function _globalredirect_get_options($options = array()) {
+  // 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 that language_url_rewrite() takes path (by reference) as the
+    // first argument but does not use it at all
+    $path = 'ignore';
+    // This function will set
+    // - $options['language'] to global $language, or unset.
+    // - $options['absolute'], to change language domain.
+    // - $options['base_url'], to change language domain.
+    // - $options['prefix'], to change language prefix.
+    language_url_rewrite($path, $options);
+  }
+  
+  return $options;
+}
+
+
+function _globalredirect_alter_destination($settings, &$system_path, &$options) {
+  $langcode = isset($options['language']) ? $options['language']->language : NULL;
+  $rewritten_system_path = $system_path;
+  
+  $modules = module_implements('globalredirect_destination_alter');
+  // avoid unlimited iteration.
+  $iteration_limit = count($modules) * 3 + 20;
+  $visited_ref_values = array();
+  $ref_value = $langcode . ':' . $system_path;
+  $start_ref_value = $ref_value;
+  do {
+    if (isset($visited_ref_values[$ref_value])) {
+      // TODO: show error message
+      return;
+    }
+    if (count($visited_ref_values) > $iteration_limit) {
+      // TODO: show error message
+      return;
+    }
+    $visited_ref_values[$ref_value] = true;
+    $modified = false;
+    foreach ($modules as $module) {
+      $function = $module . '_globalredirect_destination_alter';
+      $function($langcode, $rewritten_system_path);
+      $new_ref_value = $langcode . ':' . $rewritten_system_path;
+      if ($new_ref_value != $ref_value) {
+        $modified = true;
+        $ref_value = $new_ref_value;
+        // start again.
+        break;
+      }
+    }
+  } while ($modified);
+  
+  $options['language'] = _globalredirect_load_language($langcode);
+  $system_path = $rewritten_system_path;
+  
+  // $options might have changed, we need to sanitize them.
+  $options = _globalredirect_get_options($options);
+}
+
+
+/**
+ * Implementation of hook_globalredirect_destination_alter()
+ */
+function globalredirect_globalredirect_destination_alter(&$langcode, &$system_path) {
+  $settings = _globalredirect_get_settings(TRUE);
+  if (!$settings->mixed_language) {
+    return;
+  }
+  $m = array();
+  if (preg_match('/^node\/(\d+)$/', $system_path, $m)) {
+    list(,$nid) = $m;
+    $node = node_load($nid);
+    if (is_object($node) && isset($node->language) && $node->language != $langcode) {
+      switch ($settings->mixed_language) {
+        case GLOBALREDIRECT_MIXED_LANGUAGE_REDIRECT_TRANSLATION:
+          if (function_exists('translation_path_get_translations') && isset($node->tnid)) {
+            $translations = translation_node_get_translations($node->tnid);
+            if (isset($translations[$langcode])) {
+              $system_path = 'node/' . $translations[$langcode]->nid;
+            }
+          }
+          break;
+        case GLOBALREDIRECT_MIXED_LANGUAGE_REDIRECT_NATIVE:
+          $langcode = $node->language;
+      }
+    }
+  }
+}
+
+function _globalredirect_find_alias($settings, $request, &$options) {
+  $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);
+  }
+
+  // Alias case sensitivity check. If there is an alias from the previous
+  // lookup, do a query to test for case.
+  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) {
+      // There is a match and there is a difference in case.
+      $alias = $alias_sensitive;
+    }
+  }
+  
+  return $alias;
+}
+
+
+function _globalredirect_load_language($langcode, $else = NULL) {
+  return $langcode ? db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $langcode)) : $else;
+}
+
+
+function _globalredirect_frontpage($settings, $prefix, $query_string) {
+  // Redirect if the current request does not refer to the front page in the
+  // configured fashion (with or without a prefix)
+  if ($_REQUEST['q'] != $prefix) {
+    drupal_goto('', $query_string, NULL, 301);
+  }
+  elseif(
+    $settings->nonclean2clean &&
+    (bool)variable_get('clean_url', 0) &&
+    (strpos(request_uri(), '?q=') || strpos(request_uri(), 'index.php'))
+  ) {
+    drupal_goto('', $query_string, NULL, 301);
+  }
+}
+
+
+/**
+ * Same as drupal_goto(), but allows to
+ * define a language in $options['language']
+ *
+ * @param $path
+ *   The alias or system path
+ * @param $options
+ *   
+ *   
+ */
+function _globalredirect_goto($path, $options) {
+  $destination = FALSE;
+  if (isset($_REQUEST['destination'])) {
+    $destination = $_REQUEST['destination'];
+  }
+  else if (isset($_REQUEST['edit']['destination'])) {
+    $destination = $_REQUEST['edit']['destination'];
+  }
+
+  if ($destination) {
+    // Do not redirect to an absolute URL originating from user input.
+    $colonpos = strpos($destination, ':');
+    $absolute = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($destination, 0, $colonpos)));
+    if (!$absolute) {
+      extract(parse_url(urldecode($destination)));
+    }
+  }
+
+  $url = url($path, array('absolute' => true) + $options);
+  // Remove newlines from the URL to avoid header injection attacks.
+  $url = str_replace(array("\n", "\r"), '', $url);
+
+  // Allow modules to react to the end of the page request before redirecting.
+  // We do not want this while running update.php.
+  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
+    module_invoke_all('exit', $url);
+  }
+
+  // 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 be wrong, so we make sure none of the code below the
+  // drupal_goto() call gets executed upon redirection.
+  exit();
+}
+
+/**
+ * Load all settings into an object.
+ *
+ * @param $disabled_is_false
+ *   If TRUE, all -1 values will be changed to FALSE.
+ *   This allows for more compact if() conditions.
+ */
+function _globalredirect_get_settings($disabled_is_false = FALSE) {
+  $settings = array(
+    'deslash' => GLOBALREDIRECT_DESLASH_ENABLED,
+    'nonclean2clean' => GLOBALREDIRECT_NONCLEAN2CLEAN_ENABLED,
+    'trailingzero' => GLOBALREDIRECT_FEATURE_DISABLED,
+    // Use of menu_get_item should be optional as
+    // it appears in some situations it causes WSOD's...
+    'menu_check' => GLOBALREDIRECT_FEATURE_DISABLED,
+    'case_sensitive_urls' => GLOBALREDIRECT_CASE_SENSITIVE_URLS_ENABLED,
+    'mixed_language' => GLOBALREDIRECT_FEATURE_DISABLED,
+  );
+  
+  foreach ($settings as $suffix => $value) {
+    $settings[$suffix] = variable_get('globalredirect_'.$suffix, $value);
+    if ($disabled_is_false && $settings[$suffix] == GLOBALREDIRECT_FEATURE_DISABLED) {
+      $settings[$suffix] = FALSE;
+    }
+  }
+  
+  return (object)$settings;
+}
+
+
 /**
  * Implements hook_menu().
  */

