diff options
author | Angie Byron <webchick@24967.no-reply.drupal.org> | 2009-10-09 16:33:14 +0000 |
---|---|---|
committer | Angie Byron <webchick@24967.no-reply.drupal.org> | 2009-10-09 16:33:14 +0000 |
commit | 1b9cde9d85b2c04df35b04cfbd12f68243a118bc (patch) | |
tree | a04c4251366fe90e4453a26dbbd12b503219eed3 /includes | |
parent | d72c565607380c5e92df6bcc6067ca0536c4a5e4 (diff) | |
download | brdo-1b9cde9d85b2c04df35b04cfbd12f68243a118bc.tar.gz brdo-1b9cde9d85b2c04df35b04cfbd12f68243a118bc.tar.bz2 |
#282191 by plach, nedjo, catch, et al: TF #1: Allow different interface language for the same path.
Diffstat (limited to 'includes')
-rw-r--r-- | includes/bootstrap.inc | 55 | ||||
-rw-r--r-- | includes/common.inc | 31 | ||||
-rw-r--r-- | includes/language.inc | 454 | ||||
-rw-r--r-- | includes/locale.inc | 518 | ||||
-rw-r--r-- | includes/theme.inc | 4 |
5 files changed, 921 insertions, 141 deletions
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index cda1bb8b8..fe9a95471 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -154,27 +154,19 @@ define('DRUPAL_AUTHENTICATED_RID', 2); define('DRUPAL_KILOBYTE', 1024); /** - * No language negotiation. The default language is used. + * The type of language used to define the content language. */ -define('LANGUAGE_NEGOTIATION_NONE', 0); +define('LANGUAGE_TYPE_CONTENT', 'language'); /** - * Path based negotiation with fallback to default language if no defined path - * prefix identified. + * The type of language used to select the user interface. */ -define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1); +define('LANGUAGE_TYPE_INTERFACE', 'language_interface'); /** - * Path based negotiation with fallback to user preferences and browser - * language detection if no defined path prefix identified. + * The type of language used for URLs. */ -define('LANGUAGE_NEGOTIATION_PATH', 2); - -/** - * Domain based negotiation with fallback to default language if no language - * identified by domain. - */ -define('LANGUAGE_NEGOTIATION_DOMAIN', 3); +define('LANGUAGE_TYPE_URL', 'language_url'); /** * Language written left to right. Possible value of $language->direction. @@ -1628,23 +1620,50 @@ function get_t() { } /** - * Choose a language for the current page, based on site and user preferences. + * Initialize all the defined language types. */ function drupal_language_initialize() { - global $language, $user; + $types = language_types(); // Ensure the language is correctly returned, even without multilanguage support. // Useful for eg. XML/HTML 'lang' attributes. if (variable_get('language_count', 1) == 1) { - $language = language_default(); + $default = language_default(); + foreach ($types as $type) { + $GLOBALS[$type] = $default; + } } else { include_once DRUPAL_ROOT . '/includes/language.inc'; - $language = language_initialize(); + foreach ($types as $type) { + $GLOBALS[$type] = language_initialize($type); + } } } /** + * The built-in language types. + * + * @return + * An array of key-values pairs where the key is the language type and the + * value is its configurability. + */ +function drupal_language_types() { + return array( + LANGUAGE_TYPE_CONTENT => TRUE, + LANGUAGE_TYPE_INTERFACE => TRUE, + LANGUAGE_TYPE_URL => FALSE, + ); +} + +/** + * Return an array of the available language types. + */ +function language_types() { + return array_keys(variable_get('language_types', drupal_language_types())); +} + +/** * Get a list of languages set up indexed by the specified key * * @param $field The field to index the list with. diff --git a/includes/common.inc b/includes/common.inc index 3f26c4949..d8661100c 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -393,6 +393,26 @@ function drupal_get_query_parameters(array $query = NULL, array $exclude = array } /** + * 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 drupal_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; +} + +/** * Parse an array into a valid, rawurlencoded query string. * * This differs from http_build_query() as we need to rawurlencode() (instead of @@ -1455,12 +1475,12 @@ function fix_gpc_magic() { * The translated string. */ function t($string, array $args = array(), array $options = array()) { - global $language; + global $language_interface; static $custom_strings; // Merge in default. if (empty($options['langcode'])) { - $options['langcode'] = isset($language->language) ? $language->language : 'en'; + $options['langcode'] = isset($language_interface->language) ? $language_interface->language : 'en'; } if (empty($options['context'])) { $options['context'] = ''; @@ -2438,7 +2458,8 @@ function url($path = NULL, array $options = array()) { $path = ''; } elseif (!empty($path) && !$options['alias']) { - $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : ''); + $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; + $path = drupal_get_path_alias($path, $language); } if (function_exists('custom_url_rewrite_outbound')) { @@ -2546,7 +2567,7 @@ function drupal_attributes(array $attributes = array()) { * an HTML string containing a link to the given path. */ function l($text, $path, array $options = array()) { - global $language; + global $language_url; // Merge in defaults. $options += array( @@ -2556,7 +2577,7 @@ function l($text, $path, array $options = array()) { // Append active class. if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) && - (empty($options['language']) || $options['language']->language == $language->language)) { + (empty($options['language']) || $options['language']->language == $language_url->language)) { $options['attributes']['class'][] = 'active'; } diff --git a/includes/language.inc b/includes/language.inc index 9a990a722..f170cf9a0 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -7,138 +7,392 @@ */ /** - * Choose a language for the page, based on language negotiation settings. - */ -function language_initialize() { - global $user; - - // Configured presentation language mode. - $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE); - // Get a list of enabled languages. - $languages = language_list('enabled'); - $languages = $languages[1]; - - switch ($mode) { - case LANGUAGE_NEGOTIATION_NONE: - return language_default(); - - case LANGUAGE_NEGOTIATION_DOMAIN: - foreach ($languages as $language) { - $host = parse_url($language->domain, PHP_URL_HOST); - if ($host && ($_SERVER['HTTP_HOST'] == $host)) { - return $language; - } - } - return language_default(); - - case LANGUAGE_NEGOTIATION_PATH_DEFAULT: - case LANGUAGE_NEGOTIATION_PATH: - // $_GET['q'] might not be available at this time, because - // path initialization runs after the language bootstrap phase. - $args = isset($_GET['q']) ? explode('/', $_GET['q']) : array(); - $prefix = array_shift($args); - // Search prefix within enabled languages. - foreach ($languages as $language) { - if (!empty($language->prefix) && $language->prefix == $prefix) { - // Rebuild $GET['q'] with the language removed. - $_GET['q'] = implode('/', $args); - return $language; - } + * No language negotiation. The default language is used. + */ +define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default'); + +/** + * Return all the defined language types. + * + * @return + * An array of language type names. The name will be used as the global + * variable name the language value will be stored in. + */ +function language_types_info() { + $language_types = &drupal_static(__FUNCTION__); + + if (!isset($language_types)) { + $language_types = module_invoke_all('language_types_info'); + // Let other modules alter the list of language types. + drupal_alter('language_types_info', $language_types); + } + + return $language_types; +} + +/** + * Return only the configurable language types. + * + * A language type maybe configurable or fixed. A fixed language type is a type + * whose negotiation values are unchangable and defined while defining the + * language type itself. + * + * @return + * An array of language type names. + */ +function language_types_configurable() { + $configurable = &drupal_static(__FUNCTION__); + + if (!isset($configurable)) { + $types = variable_get('language_types', drupal_language_types()); + $configurable = array_keys(array_filter($types)); + } + + return $configurable; +} + +/** + * Disable the given language types. + * + * @param $types + * An array of language types. + */ +function language_types_disable($types) { + $enabled_types = variable_get('language_types', drupal_language_types()); + + foreach ($types as $type) { + unset($enabled_types[$type]); + } + + variable_set('language_types', $enabled_types); +} + +/** + * Check if a language provider is enabled. + * + * This has two possible behaviors: + * - If $provider_id is given return its ID if enabled, FALSE otherwise. + * - If no ID is passed the first enabled language provider is returned. + * + * @param $type + * The language negotiation type. + * @param $provider_id + * The language provider ID. + * + * @return + * The provider ID if it is enabled, FALSE otherwise. + */ +function language_negotiation_get($type, $provider_id = NULL) { + $negotiation = variable_get("language_negotiation_$type", array()); + + if (empty($negotiation)) { + return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE; + } + + if (empty($provider_id)) { + return key($negotiation); + } + + if (isset($negotiation[$provider_id])) { + return $provider_id; + } + + return FALSE; +} + +/** + * Check if the given language provider is enabled for any configurable language + * type. + * + * @param $provider_id + * The language provider ID. + * + * @return + * TRUE if there is at least one language type for which the give language + * provider is enabled, FALSE otherwise. + */ +function language_negotiation_get_any($provider_id) { + foreach (language_types_configurable() as $type) { + if (language_negotiation_get($type, $provider_id)) { + return TRUE; + } + } + + return FALSE; +} + +/** + * Return the language switch links for the given language. + * + * @param $type + * The language negotiation type. + * @param $path + * The internal path the switch links will be relative to. + * + * @return + * A keyed array of links ready to be themed. + */ +function language_negotiation_get_switch_links($type, $path) { + $links = FALSE; + $negotiation = variable_get("language_negotiation_$type", array()); + + foreach ($negotiation as $id => $provider) { + if (isset($provider['callbacks']['switcher'])) { + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; } - if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) { - // If we did not found the language by prefix, choose the default. - return language_default(); + + $callback = $provider['callbacks']['switcher']; + $result = $callback($type, $path); + + if (!empty($result)) { + // Allow modules to provide translations for specific links. + drupal_alter('language_switch_links', $result, $type, $path); + $links = (object) array('links' => $result, 'provider' => $id); + break; } - break; + } + } + + return $links; +} + + +/** + * Save a list of language providers. + * + * @param $type + * The language negotiation type. + * @param $language_providers + * An array of language provider ids. + */ +function language_negotiation_set($type, $language_providers) { + // Save only the necessary fields. + $provider_fields = array('callbacks', 'file', 'cache'); + + $negotiation = array(); + $providers_weight = array(); + $defined_providers = language_negotiation_info(); + $default_types = language_types_configurable(); + + // Initialize the providers weight list. + foreach ($language_providers as $id => $provider) { + $providers_weight[$id] = language_provider_weight($provider); } - // User language. - if ($user->uid && isset($languages[$user->language])) { - return $languages[$user->language]; + // Order providers list by weight. + asort($providers_weight); + + foreach ($providers_weight as $id => $weight) { + if (isset($defined_providers[$id])) { + $provider = $defined_providers[$id]; + // If the provider does not express any preference about types, make it + // available for any configurable type. + $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); + // Check if the provider is defined and has the right type. + if (isset($types[$type])) { + $provider_data = array(); + foreach ($provider_fields as $field) { + if (isset($provider[$field])) { + $provider_data[$field] = $provider[$field]; + } + } + $negotiation[$id] = $provider_data; + } + } } - // Browser accept-language parsing. - if ($language = language_from_browser()) { - return $language; + variable_set("language_negotiation_$type", $negotiation); +} + +/** + * Return all the defined language providers. + * + * @return + * An array of language providers. + */ +function language_negotiation_info() { + $language_providers = &drupal_static(__FUNCTION__); + + if (!isset($language_providers)) { + // Collect all the module-defined language negotiation providers. + $language_providers = module_invoke_all('language_negotiation_info'); + + // Add the default language provider. + $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( + 'callbacks' => array('language' => 'language_from_default'), + 'weight' => 10, + 'name' => t('Default'), + 'description' => t('The default site language (@language_name) is used.', array('@language_name' => language_default()->native)), + ); + + // Let other modules alter the list of language providers. + drupal_alter('language_negotiation_info', $language_providers); } - // Fall back on the default if everything else fails. - return language_default(); + return $language_providers; } /** - * Identify language from the Accept-language HTTP header we got. + * Helper function used to cache the language providers results. + * + * @param $provider_id + * The language provider ID. + * @param $provider + * The language provider to be invoked. If not passed it will be explicitly + * loaded through language_negotiation_info(). + * + * @return + * The language provider's return value. */ -function language_from_browser() { - // Specified by the user via the browser's Accept Language setting - // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" - $browser_langs = array(); +function language_provider_invoke($provider_id, $provider = NULL) { + $results = &drupal_static(__FUNCTION__); - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']); - for ($i = 0; $i < count($browser_accept); $i++) { - // The language part is either a code or a code with a quality. - // We cannot do anything with a * code, so it is skipped. - // If the quality is missing, it is assumed to be 1 according to the RFC. - if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) { - $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0); - } + if (!isset($results[$provider_id])) { + global $user; + + // Get languages grouped by status and select only the enabled ones. + $languages = language_list('enabled'); + $languages = $languages[1]; + + if (!isset($provider)) { + $providers = language_negotiation_info(); + $provider = $providers[$provider_id]; + } + + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; } + + // If the language provider has no cache preference or this is satisified + // we can execute the callback. + $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', CACHE_DISABLED); + $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; + $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; + $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } - // Order the codes by quality - arsort($browser_langs); + return $results[$provider_id]; +} + +/** + * Return the passed language provider weight or a default value. + * + * @param $provider + * A language provider data structure. + * + * @return + * A numeric weight. + */ +function language_provider_weight($provider) { + $default = is_numeric($provider) ? $provider : 0; + return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default; +} - // Try to find the first preferred language we have - $languages = language_list('enabled'); - foreach ($browser_langs as $langcode => $q) { - if (isset($languages['1'][$langcode])) { - return $languages['1'][$langcode]; +/** + * Choose a language for the given type based on language negotiation settings. + * + * @param $type + * The language type. + * + * @return + * The negotiated language object. + */ +function language_initialize($type) { + // Execute the language providers in the order they were set up and return the + // first valid language found. + $negotiation = variable_get("language_negotiation_$type", array()); + + foreach ($negotiation as $id => $provider) { + $language = language_provider_invoke($id, $provider); + if ($language) { + return $language; } } + + // If no other language was found use the default one. + return language_default(); } /** - * Rewrite URLs with language based prefix. Parameters are the same - * as those of the url() function. + * Default language provider. + * + * @return + * The default language code. */ -function language_url_rewrite(&$path, &$options) { - global $language; +function language_from_default() { + return language_default()->language; +} +/** + * Rewrite URLs allowing modules to hook in. + * + * @param $path + * The path to rewrite. + * @param $options + * An associative array of additional options as in url(). + */ +function language_url_rewrite(&$path, &$options) { // Only modify relative (insite) URLs. if (!$options['external']) { + static $callbacks; - // Language can be passed as an option, or we go for current language. - if (!isset($options['language'])) { - $options['language'] = $language; - } + if (!isset($callbacks)) { + $callbacks = array(); - switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) { - case LANGUAGE_NEGOTIATION_NONE: - // No language dependent path allowed in this mode. - unset($options['language']); - break; + foreach (language_types_configurable() as $type) { + // Get url rewriter callbacks only from enabled language providers. + $negotiation = variable_get("language_negotiation_$type", array()); - case LANGUAGE_NEGOTIATION_DOMAIN: - if ($options['language']->domain) { - // Ask for an absolute URL with our modified base_url. - $options['absolute'] = TRUE; - $options['base_url'] = $options['language']->domain; - } - break; + foreach ($negotiation as $id => $provider) { + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; + } - case LANGUAGE_NEGOTIATION_PATH_DEFAULT: - $default = language_default(); - if ($options['language']->language == $default->language) { - break; + // Avoid duplicate callback entries. + if (isset($provider['callbacks']['url_rewrite'])) { + $callbacks[$provider['callbacks']['url_rewrite']] = NULL; + } } - // Intentionally no break here. + } - case LANGUAGE_NEGOTIATION_PATH: - if (!empty($options['language']->prefix)) { - $options['prefix'] = $options['language']->prefix . '/'; - } - break; + $callbacks = array_keys($callbacks); + } + + foreach ($callbacks as $callback) { + $callback($path, $options); } } } + +/** + * Split the given path into prefix and actual path. + * + * Parse the given path and return the language object identified by the + * prefix and the actual path. + * + * @param $path + * The path to split. + * @param $languages + * An array of valid languages. + * + * @return + * An array composed of: + * - A language object corresponding to the identified prefix on success, + * FALSE otherwise. + * - The path without the prefix on success, the given path otherwise. + */ +function language_url_split_prefix($path, $languages) { + $args = empty($path) ? array() : explode('/', $path); + $prefix = array_shift($args); + + // Search prefix within enabled languages. + foreach ($languages as $language) { + if (!empty($language->prefix) && $language->prefix == $prefix) { + // Rebuild $path with the language removed. + return array($language, implode('/', $args)); + } + } + + return array(FALSE, $path); +} diff --git a/includes/locale.inc b/includes/locale.inc index 85e056ce1..cc95ef7f4 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -24,6 +24,18 @@ define('LOCALE_IMPORT_OVERWRITE', 0); define('LOCALE_IMPORT_KEEP', 1); /** + * URL language negotiation: use the path prefix as URL language + * indicator. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX', 0); + +/** + * URL language negotiation: use the domain as URL language + * indicator. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN', 1); + +/** * @defgroup locale-language-overview Language overview functionality * @{ */ @@ -473,7 +485,7 @@ function locale_languages_delete_form_submit($form, &$form_state) { */ /** - * @defgroup locale-languages-negotiation Language negotiation options screen + * @defgroup locale-languages-negotiation Language negotiation options * @{ */ @@ -481,33 +493,507 @@ function locale_languages_delete_form_submit($form, &$form_state) { * Setting for language negotiation options */ function locale_languages_configure_form() { - $form['language_negotiation'] = array( - '#title' => t('Language negotiation'), - '#type' => 'radios', - '#options' => array( - LANGUAGE_NEGOTIATION_NONE => t('None.'), - LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'), - LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language fallback.'), - LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')), - '#default_value' => variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE), - '#description' => t("Select the mechanism used to determine your site's presentation language. <strong>Modifying this setting may break all incoming URLs and should be used with caution in a production environment.</strong>") + include_once DRUPAL_ROOT . '/includes/language.inc'; + + $form = array( + '#submit' => array('locale_languages_configure_form_submit'), + '#theme' => 'locale_languages_configure_form', + '#language_types' => language_types_configurable(), + '#language_types_info' => language_types_info(), + '#language_providers' => language_negotiation_info(), ); + + foreach ($form['#language_types'] as $type) { + _locale_languages_configure_form_language_table($form, $type); + } + $form['submit'] = array( '#type' => 'submit', - '#value' => t('Save settings') + '#value' => t('Save settings'), ); + return $form; } /** - * Submit function for language negotiation settings. + * Helper function to build a language provider table. + */ +function _locale_languages_configure_form_language_table(&$form, $type) { + $info = $form['#language_types_info'][$type]; + + $table_form = array( + '#title' => t('@type language', array('@type' => $info['name'])), + '#tree' => TRUE, + '#description' => $info['description'], + '#language_providers' => array(), + '#show_operations' => FALSE, + 'weight' => array('#tree' => TRUE), + 'enabled' => array('#tree' => TRUE), + ); + + $language_providers = $form['#language_providers']; + $enabled_providers = variable_get("locale_language_providers_enabled_$type", array()); + $providers_weight = variable_get("locale_language_providers_weight_$type", array()); + + // Add missing data to the providers lists. + foreach ($language_providers as $id => $provider) { + if (!isset($providers_weight[$id])) { + $providers_weight[$id] = language_provider_weight($provider); + } + if (!isset($enabled_providers[$id])) { + $enabled_providers[$id] = FALSE; + } + } + + // Order providers list by weight. + asort($providers_weight); + + foreach ($providers_weight as $id => $weight) { + $enabled = $enabled_providers[$id]; + $provider = $language_providers[$id]; + + // List the provider only if the current type is defined in its 'types' key. + // If it is not defined default to all the configurabe language types. + $types = array_flip(isset($provider['types']) ? $provider['types'] : $form['#language_types']); + + if (isset($types[$type])) { + $table_form['#language_providers'][$id] = $provider; + + $table_form['weight'][$id] = array( + '#type' => 'weight', + '#default_value' => $weight, + '#attributes' => array('class' => array("language-provider-weight-$type")), + ); + + $table_form['title'][$id] = array('#markup' => check_plain($provider['name'])); + + $table_form['enabled'][$id] = array('#type' => 'checkbox', '#default_value' => $enabled); + if ($id === LANGUAGE_NEGOTIATION_DEFAULT) { + $table_form['enabled'][$id]['#default_value'] = TRUE; + $table_form['enabled'][$id]['#attributes'] = array('disabled' => 'disabled'); + } + + $table_form['description'][$id] = array('#markup' => filter_xss_admin($provider['description'])); + + $config_op = ''; + if (isset($provider['config'])) { + $config_op = l(t('Configure'), $provider['config']); + // If there is at least one operation enabled show the operation column. + $table_form['#show_operations'] = TRUE; + } + $table_form['operation'][$id] = array('#markup' => $config_op); + } + } + + $form[$type] = $table_form; +} + +/** + * Theme the language configure form. + * + * @ingroup themeable + */ +function theme_locale_languages_configure_form($variables) { + $form = $variables['form']; + $output = ''; + + foreach ($form['#language_types'] as $type) { + $rows = array(); + $info = $form['#language_types_info'][$type]; + $title = '<label>' . $form[$type]['#title'] . '</label>'; + $description = '<div class="description">' . $form[$type]['#description'] . '</div>'; + + foreach ($form[$type]['title'] as $id => $element) { + // Do not take form control structures. + if (is_array($element) && element_child($id)) { + $row = array( + 'data' => array( + '<strong>' . drupal_render($form[$type]['title'][$id]) . '</strong>', + drupal_render($form[$type]['description'][$id]), + drupal_render($form[$type]['enabled'][$id]), + drupal_render($form[$type]['weight'][$id]), + ), + 'class' => array('draggable'), + ); + if ($form[$type]['#show_operations']) { + $row['data'][] = drupal_render($form[$type]['operation'][$id]); + } + $rows[] = $row; + } + } + + $header = array( + array('data' => t('Detection method')), + array('data' => t('Description')), + array('data' => t('Enabled')), + array('data' => t('Weight')), + ); + + // If there is at least one operation enabled show the operation column. + if ($form[$type]['#show_operations']) { + $header[] = array('data' => t('Operations')); + } + + $variables = array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array('id' => "language-negotiation-providers-$type"), + ); + $table = theme('table', $variables); + $table .= drupal_render_children($form[$type]); + + drupal_add_tabledrag("language-negotiation-providers-$type", 'order', 'sibling', "language-provider-weight-$type"); + + $output .= '<div class="form-item">' . $title . $description . $table . '</div>'; + } + + $output .= drupal_render_children($form); + return $output; +} + +/** + * Submit handler for language negotiation settings. */ function locale_languages_configure_form_submit($form, &$form_state) { - variable_set('language_negotiation', $form_state['values']['language_negotiation']); - drupal_set_message(t('Language negotiation configuration saved.')); + $language_types = array(); + $configurable_types = $form['#language_types']; + + foreach ($configurable_types as $type) { + $negotiation = array(); + $enabled_providers = $form_state['values'][$type]['enabled']; + $enabled_providers[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE; + $providers_weight = $form_state['values'][$type]['weight']; + $language_types[$type] = TRUE; + + foreach ($providers_weight as $id => $weight) { + if ($enabled_providers[$id]) { + $provider = $form[$type]['#language_providers'][$id]; + $provider['weight'] = $weight; + $negotiation[$id] = $provider; + } + } + + language_negotiation_set($type, $negotiation); + variable_set("locale_language_providers_enabled_$type", $enabled_providers); + variable_set("locale_language_providers_weight_$type", $providers_weight); + } + + // Save non-configurable language types negotiation. + $language_types_info = language_types_info(); + $defined_providers = $form['#language_providers']; + foreach ($language_types_info as $type => $info) { + if (isset($info['fixed'])) { + $language_types[$type] = FALSE; + $negotiation = array(); + foreach ($info['fixed'] as $id) { + if (isset($defined_providers[$id])) { + $negotiation[$id] = $defined_providers[$id]; + } + } + language_negotiation_set($type, $negotiation); + } + } + + // Save language types. + variable_set('language_types', $language_types); + $form_state['redirect'] = 'admin/config/regional/language'; - return; + drupal_set_message(t('Language negotiation configuration saved.')); +} + +/** + * The URL language provider configuration form. + */ +function locale_language_providers_url_form() { + $form = array(); + + $form['locale_language_negotiation_url_part'] = array( + '#title' => t('URL language indicator'), + '#type' => 'radios', + '#options' => array( + LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX => t('Path prefix'), + LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN => t('Domain'), + ), + '#default_value' => variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX), + '#description' => t('Select which part of the URL will determine the language.'), + ); + + $form['#redirect'] = 'admin/config/regional/language/configure'; + + return system_settings_form($form); +} + +/** + * The URL language provider configuration form. + */ +function locale_language_providers_session_form() { + $form = array(); + + $form['locale_language_negotiation_session_param'] = array( + '#title' => t('Request/session parameter'), + '#type' => 'textfield', + '#default_value' => variable_get('locale_language_negotiation_session_param', 'language'), + '#description' => t('This value will be the name of the request/session parameter which will be used to determine the desired language.'), + ); + + $form['#redirect'] = 'admin/config/regional/language/configure'; + + return system_settings_form($form); +} + +/** + * Identify the language from the current content language. + * + * @return + * The current content language code. + */ +function locale_language_from_content() { + global $language; + return isset($language->language) ? $language->language : FALSE; +} + +/** + * Identify language from the Accept-language HTTP header we got. + * + * We perform browser accept-language parsing only if page cache is disabled, + * otherwise we would cache a user-specific preference. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function locale_language_from_browser($languages) { + // Specified by the user via the browser's Accept Language setting + // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" + $browser_langs = array(); + + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']); + foreach ($browser_accept as $langpart) { + // The language part is either a code or a code with a quality. + // We cannot do anything with a * code, so it is skipped. + // If the quality is missing, it is assumed to be 1 according to the RFC. + if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($langpart), $found)) { + $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0); + } + } + } + + // Order the codes by quality + arsort($browser_langs); + + // Try to find the first preferred language we have + foreach ($browser_langs as $langcode => $q) { + if (isset($languages[$langcode])) { + return $langcode; + } + } + + return FALSE; +} + +/** + * Identify language from the user preferences. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on succes, FALSE otherwise. + */ +function locale_language_from_user($languages) { + // User preference (only for logged users). + global $user; + + if ($user->uid) { + return $user->language; + } + + // No language preference from the user. + return FALSE; +} + +/** + * Identify language from a request/session parameter. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on succes, FALSE otherwise. + */ +function locale_language_from_session($languages) { + $param = variable_get('locale_language_negotiation_session_param', 'language'); + + // Request parameter. + if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { + return $_SESSION[$param] = $langcode; + } + + // Session parameter. + if (isset($_SESSION[$param])) { + return $_SESSION[$param]; + } + + return FALSE; } + +/** + * Identify language via URL prefix or domain. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on succes, FALSE otherwise. + */ +function locale_language_from_url($languages) { + $language_url = FALSE; + + switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { + case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: + // $_GET['q'] might not be available at this time, because + // path initialization runs after the language bootstrap phase. + list($language, $_GET['q']) = language_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); + if ($language !== FALSE) { + $language_url = $language->language; + } + break; + + case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: + foreach ($languages as $language) { + $host = parse_url($language->domain, PHP_URL_HOST); + if ($host && ($_SERVER['HTTP_HOST'] == $host)) { + $language_url = $language->language; + break; + } + } + break; + } + + return $language_url; +} + +/** + * Return the URL language switcher block. Translation links may be provided by + * other modules. + */ +function locale_language_switcher_url($type, $path) { + $languages = language_list('enabled'); + $links = array(); + + foreach ($languages[1] as $language) { + $links[$language->language] = array( + 'href' => $path, + 'title' => $language->native, + 'language' => $language, + 'attributes' => array('class' => array('language-link')), + ); + } + + return $links; +} + +/** + * Return the session language switcher block. + */ +function locale_language_switcher_session($type, $path) { + drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css'); + + $param = variable_get('locale_language_negotiation_session_param', 'language'); + $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->language; + + $languages = language_list('enabled'); + $links = array(); + + $query = $_GET; + unset($query['q']); + + foreach ($languages[1] as $language) { + $langcode = $language->language; + $links[$langcode] = array( + 'href' => $path, + 'title' => $language->native, + 'attributes' => array('class' => array('language-link')), + 'query' => $query, + ); + if ($language_query != $langcode) { + $links[$langcode]['query'][$param] = $langcode; + } + else { + $links[$langcode]['attributes']['class'][] = ' session-active'; + } + } + + return $links; +} + +/** + * Rewrite URLs for the URL language provider. + */ +function locale_language_url_rewrite_url(&$path, &$options) { + // Language can be passed as an option, or we go for current URL language. + if (!isset($options['language'])) { + global $language_url; + $options['language'] = $language_url; + } + + if (isset($options['language'])) { + switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { + case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: + if ($options['language']->domain) { + // Ask for an absolute URL with our modified base_url. + $options['absolute'] = TRUE; + $options['base_url'] = $options['language']->domain; + } + break; + + case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: + if (!empty($options['language']->prefix)) { + $options['prefix'] = $options['language']->prefix . '/'; + } + break; + } + } +} + +/** + * Rewrite URLs for the Session language provider. + */ +function locale_language_url_rewrite_session(&$path, &$options) { + static $query_rewrite, $query_param, $query_value; + + // The following values are not supposed to change during a single page + // request processing. + if (!isset($query_rewrite)) { + global $user; + if (!$user->uid) { + $languages = language_list('enabled'); + $languages = $languages[1]; + $query_param = check_plain(variable_get('locale_language_negotiation_session_param', 'language')); + $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; + $query_rewrite = isset($languages[$query_value]) && language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_SESSION); + } + else { + $query_rewrite = FALSE; + } + } + + // If the user is anonymous, the user language provider is enabled, and the + // corresponding option has been set, we must preserve any explicit user + // language preference even with cookies disabled. + if ($query_rewrite) { + if (is_string($options['query'])) { + $options['query'] = drupal_get_query_array($options['query']); + } + if (!isset($options['query'][$query_param])) { + $options['query'][$query_param] = $query_value; + } + } +} + /** * @} End of "locale-languages-negotiation" */ diff --git a/includes/theme.inc b/includes/theme.inc index b06f62c32..f17f10155 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1397,7 +1397,7 @@ function theme_links($variables) { $links = $variables['links']; $attributes = $variables['attributes']; $heading = $variables['heading']; - global $language; + global $language_url; $output = ''; if (count($links) > 0) { @@ -1438,7 +1438,7 @@ function theme_links($variables) { $class[] = 'last'; } if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page())) - && (empty($link['language']) || $link['language']->language == $language->language)) { + && (empty($link['language']) || $link['language']->language == $language_url->language)) { $class[] = 'active'; } $output .= '<li' . drupal_attributes(array('class' => $class)) . '>'; |