Add new comment

Drupal 7 bootstrap explained: DRUPAL_BOOTSTRAP_LANGUAGE

I've covered DRUPAL_BOOTSTRAP_SESSION and DRUPAL_BOOTSTRAP_PAGE_HEADER earlier. In this part I look at what Drupal 7 does in the next bootstrap stage after those, DRUPAL_BOOTSTRAP_LANGUAGE.

This stage starts when drupal_language_initialize() gets called.

<?php
/**
 * Initializes all the defined language types.
 */
function drupal_language_initialize() {
 
$types = language_types();

  // Ensure the language is correctly returned, even without multilanguage
  // support. Also make sure we have a $language fallback, in case a language
  // negotiation callback needs to do a full bootstrap.
  // Useful for eg. XML/HTML 'lang' attributes.
 
$default = language_default();
  foreach (
$types as $type) {
   
$GLOBALS[$type] = $default;
  }
?>

First the defined language types are retrieved with language_types(). That function retrieves them from the persistent Drupal variable "language_types" or falls back to the default values returned by drupal_language_types(). Next a call to language_default() is used to retrieve a default language object. The language types are then set up as global variables, each containing the default language object. For a default Drupal 7 install these globals will be $language, $language_content and $language_url.

<?php if (drupal_multilingual()) {
    include_once
DRUPAL_ROOT . '/includes/language.inc';
    foreach (
$types as $type) {
     
$GLOBALS[$type] = language_initialize($type);
    }
?>

The next part is only executed if drupal_multilingual() indicates that more than one language is enabled on the site (it checks the Drupal persistent variable "language_count" is more than one). If that is the case, language.inc is included and the default language object is replaced in the language globals with the result of language_initialize()

<?php
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());

?>

language_initialize() gets the configured language providers for the language type that was requested to be initialized with the parameter. These are callbacks to define the current language based on things such as the URL and are the methods of language detection that you can configure from the UI in Drupal for the interface language type. The provider callbacks are then called with language_provider_invoke().

<?php
/**
 * 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_provider_invoke($provider_id, $provider = NULL) {
 
$results = &drupal_static(__FUNCTION__);

  if (!isset($results[$provider_id])) {
    global
$user;

    // Get languages grouped by status and select only the enabled ones.
   
$languages = language_list('enabled');
?>

List of enabled languages is first fetched by calling language_list().

<?php
/**
 * Returns a list of installed languages, indexed by the specified key.
 *
 * @param $field
 *   (optional) The field to index the list with.
 *
 * @return
 *   An associative array, keyed on the values of $field.
 *   - If $field is 'weight' or 'enabled', the array is nested, with the outer
 *     array's values each being associative arrays with language codes as
 *     keys and language objects as values.
 *   - For all other values of $field, the array is only one level deep, and
 *     the array's values are language objects.
 */
function language_list($field = 'language') {
 
$languages = &drupal_static(__FUNCTION__);
 
// Init language list
 
if (!isset($languages)) {
    if (
drupal_multilingual() || module_exists('locale')) {
     
$languages['language'] = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC')->fetchAllAssoc('language');
?>

If the current Drupal installation has more than one enabled language or the locale module is enabled, $languages['language'] is filled data about the site's configured languages from the database, keyed by the language code.

<?php
     
// Users cannot uninstall the native English language. However, we allow
      // it to be hidden from the installed languages. Therefore, at least one
      // other language must be enabled then.
     
if (!$languages['language']['en']->enabled && !variable_get('language_native_enabled', TRUE)) {
        unset(
$languages['language']['en']);
      }
    }
    else {
     
// No locale module, so use the default language only.
     
$default = language_default();
     
$languages['language'][$default->language] = $default;
    }
  }
?>

The Drupal persistent variable "language_native_enabled" can be used to hide english from the list of installed languages if it is also disabled. This is because it cannot be uninstalled unlike all other languages so this provides a way to remove it from the translation interface for sites that do not use english at all. This did not receive an UI option to set because Drupal was in UI freeze when the change was committed. If there is only one enabled language or locale module isn't enabled, only the default language information is set to $language['language'].

<?php
 
// Return the array indexed by the right field
 
if (!isset($languages[$field])) {
   
$languages[$field] = array();
    foreach (
$languages['language'] as $lang) {
     
// Some values should be collected into an array
     
if (in_array($field, array('enabled', 'weight'))) {
       
$languages[$field][$lang->$field][$lang->language] = $lang;
      }
      else {
       
$languages[$field][$lang->$field] = $lang;
      }
    }
  }
  return
$languages[$field];
}
?>

Next the fetched language values are keyed by the requested field. "enabled" and "weight" can have same keys for several languages so for those an array is used in the value. In the bootstrap process keying by "enabled" was requested so enabled languages will be in an array keyed by "1" and disabled in an array keyed by "0".

<?php
    $languages
= $languages[1];
    if (!isset(
$provider)) {
     
$providers = language_negotiation_info();
     
$provider = $providers[$provider_id];
    }

    if (isset($provider['file'])) {
      require_once
DRUPAL_ROOT . '/' . $provider['file'];
    }
?>

Back in language_provider_invoke(), the $provider parameter is checked to exist and then language_negotiation_info() is called if the check passes. That function only constructs a list of all available language provider by invoking the module-defined hook hook_language_negotiation_info() and adding the default provider which simply returns the site default language. Before returning it also invokes hook_language_negotiation_info_alter(). The $provider parameter is then replaced with the retrieved info for that provider.

If the provider info lists a file it is defined in, that file is next included.

<?php

    // If the language provider has no cache preference or this is satisfied
    // we can execute the callback.
   
$cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
?>

The providers can set a page cache level (off, normal or aggressive) that needs to be set or the provider will not be invoked. In Drupal core this is only used for the browser language detection doesn't work properly with page caching. You can read more about that and more on why the language negotiation works like it currently does in this issue. Here the providers are only called if there is no cache requirement or there is an authenticated user (anonymous user has uid 0) or the set page cache level is appropriate and this test will ensure that the browser language detection provider will only get called when appropriate.

<?php
    $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;
  }

  // Since objects are resources we need to return a clone to prevent the
  // provider cache to be unintentionally altered. The same providers might be
  // used with different language types based on configuration.
 
return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id];
}
?>

The provider callback is also checked to exist and called if it does and the cache check passed. The result is inserted to the $results array keyed by the provider ID if it is found to be an enabled language.

<?php

  foreach ($negotiation as $provider_id => $provider) {
   
$language = language_provider_invoke($provider_id, $provider);
    if (
$language) {
     
$language->provider = $provider_id;
      return
$language;
    }
  }
?>

Returning to language_initialize() we see the significance of using clone() at the last line of the language_provider_invoke() function. Without it, the function would return a variable containing the same object identifier pointing to the same language object from the static array variable returned by language_list() for any one language that may get returned multiple times if language types share the same detected language as they often do. Then without clone() the same language object would be set in all the globals. Setting the provider property would then cause it to be set for all of them and overwrite previously set values.

<?php

  // If no other language was found use the default one.
 
$language = language_default();
 
$language->provider = LANGUAGE_NEGOTIATION_DEFAULT;
  return
$language;
}
?>

Finally language_initialize() will return the default language object if a provider was not able to return a detected value.

<?php
// Allow modules to react on language system initialization in multilingual
    // environments.
   
bootstrap_invoke_all('language_init');
  }
}
?>

Finally the hook_language_init() hook is invoked and the language bootstrap stage is done. This stage took care of the language negotiation and set up the language globals which are then used by various parts of Drupal to determine what language should be used when displaying the interface, content and URLs.

Tagit: 

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Fill in the blank.