cms/drupal/includes/language.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Language Negotiation API.
       
     6  *
       
     7  * @see http://drupal.org/node/1497272
       
     8  */
       
     9 
       
    10 /**
       
    11  * No language negotiation. The default language is used.
       
    12  */
       
    13 define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default');
       
    14 
       
    15 /**
       
    16  * @defgroup language_negotiation Language Negotiation API functionality
       
    17  * @{
       
    18  * Functions to customize the language types and the negotiation process.
       
    19  *
       
    20  * The language negotiation API is based on two major concepts:
       
    21  * - Language types: types of translatable data (the types of data that a user
       
    22  *   can view or request).
       
    23  * - Language negotiation providers: functions for determining which language to
       
    24  *   use to present a particular piece of data to the user.
       
    25  * Both language types and language negotiation providers are customizable.
       
    26  *
       
    27  * Drupal defines three built-in language types:
       
    28  * - Interface language: The page's main language, used to present translated
       
    29  *   user interface elements such as titles, labels, help text, and messages.
       
    30  * - Content language: The language used to present content that is available
       
    31  *   in more than one language (see
       
    32  *   @link field_language Field Language API @endlink for details).
       
    33  * - URL language: The language associated with URLs. When generating a URL,
       
    34  *   this value will be used by url() as a default if no explicit preference is
       
    35  *   provided.
       
    36  * Modules can define additional language types through
       
    37  * hook_language_types_info(), and alter existing language type definitions
       
    38  * through hook_language_types_info_alter().
       
    39  *
       
    40  * Language types may be configurable or fixed. The language negotiation
       
    41  * providers associated with a configurable language type can be explicitly
       
    42  * set through the user interface. A fixed language type has predetermined
       
    43  * (module-defined) language negotiation settings and, thus, does not appear in
       
    44  * the configuration page. Here is a code snippet that makes the content
       
    45  * language (which by default inherits the interface language's values)
       
    46  * configurable:
       
    47  * @code
       
    48  * function mymodule_language_types_info_alter(&$language_types) {
       
    49  *   unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
       
    50  * }
       
    51  * @endcode
       
    52  *
       
    53  * Every language type can have a different set of language negotiation
       
    54  * providers assigned to it. Different language types often share the same
       
    55  * language negotiation settings, but they can have independent settings if
       
    56  * needed. If two language types are configured the same way, their language
       
    57  * switcher configuration will be functionally identical and the same settings
       
    58  * will act on both language types.
       
    59  *
       
    60  * Drupal defines the following built-in language negotiation providers:
       
    61  * - URL: Determine the language from the URL (path prefix or domain).
       
    62  * - Session: Determine the language from a request/session parameter.
       
    63  * - User: Follow the user's language preference.
       
    64  * - Browser: Determine the language from the browser's language settings.
       
    65  * - Default language: Use the default site language.
       
    66  * Language negotiation providers are simple callback functions that implement a
       
    67  * particular logic to return a language code. For instance, the URL provider
       
    68  * searches for a valid path prefix or domain name in the current request URL.
       
    69  * If a language negotiation provider does not return a valid language code, the
       
    70  * next provider associated to the language type (based on provider weight) is
       
    71  * invoked.
       
    72  *
       
    73  * Modules can define additional language negotiation providers through
       
    74  * hook_language_negotiation_info(), and alter existing providers through
       
    75  * hook_language_negotiation_info_alter(). Here is an example snippet that lets
       
    76  * path prefixes be ignored for administrative paths:
       
    77  * @code
       
    78  * function mymodule_language_negotiation_info_alter(&$negotiation_info) {
       
    79  *   // Replace the core function with our own function.
       
    80  *   module_load_include('language', 'inc', 'language.negotiation');
       
    81  *   $negotiation_info[LANGUAGE_NEGOTIATION_URL]['callbacks']['language'] = 'mymodule_from_url';
       
    82  *   $negotiation_info[LANGUAGE_NEGOTIATION_URL]['file'] = drupal_get_path('module', 'mymodule') . '/mymodule.module';
       
    83  * }
       
    84  *
       
    85  * function mymodule_from_url($languages) {
       
    86  *   // Use the core URL language negotiation provider to get a valid language
       
    87  *   // code.
       
    88  *   module_load_include('language', 'inc', 'language.negotiation');
       
    89  *   $langcode = language_from_url($languages);
       
    90  *
       
    91  *   // If we are on an administrative path, override with the default language.
       
    92  *   if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') {
       
    93  *     return language_default()->langcode;
       
    94  *   }
       
    95  *   return $langcode;
       
    96  * }
       
    97  * @endcode
       
    98  *
       
    99  * For more information, see
       
   100  * @link http://drupal.org/node/1497272 Language Negotiation API @endlink
       
   101  */
       
   102 
       
   103 /**
       
   104  * Returns all the defined language types.
       
   105  *
       
   106  * @return
       
   107  *   An array of language type names. The name will be used as the global
       
   108  *   variable name the language value will be stored in.
       
   109  */
       
   110 function language_types_info() {
       
   111   $language_types = &drupal_static(__FUNCTION__);
       
   112 
       
   113   if (!isset($language_types)) {
       
   114     $language_types = module_invoke_all('language_types_info');
       
   115     // Let other modules alter the list of language types.
       
   116     drupal_alter('language_types_info', $language_types);
       
   117   }
       
   118 
       
   119   return $language_types;
       
   120 }
       
   121 
       
   122 /**
       
   123  * Returns only the configurable language types.
       
   124  *
       
   125  * A language type maybe configurable or fixed. A fixed language type is a type
       
   126  * whose language negotiation providers are module-defined and not altered
       
   127  * through the user interface.
       
   128  *
       
   129  * @param $stored
       
   130  *   Optional. By default retrieves values from the 'language_types' variable to
       
   131  *   avoid unnecessary hook invocations.
       
   132  *   If set to FALSE retrieves values from the actual language type definitions.
       
   133  *   This allows to react to alterations performed on the definitions by modules
       
   134  *   installed after the 'language_types' variable is set.
       
   135  *
       
   136  * @return
       
   137  *   An array of language type names.
       
   138  */
       
   139 function language_types_configurable($stored = TRUE) {
       
   140   $configurable = &drupal_static(__FUNCTION__);
       
   141 
       
   142   if ($stored && !isset($configurable)) {
       
   143     $types = variable_get('language_types', drupal_language_types());
       
   144     $configurable = array_keys(array_filter($types));
       
   145   }
       
   146 
       
   147   if (!$stored) {
       
   148     $result = array();
       
   149     foreach (language_types_info() as $type => $info) {
       
   150       if (!isset($info['fixed'])) {
       
   151         $result[] = $type;
       
   152       }
       
   153     }
       
   154     return $result;
       
   155   }
       
   156 
       
   157   return $configurable;
       
   158 }
       
   159 
       
   160 /**
       
   161  * Disables the given language types.
       
   162  *
       
   163  * @param $types
       
   164  *   An array of language types.
       
   165  */
       
   166 function language_types_disable($types) {
       
   167   $enabled_types = variable_get('language_types', drupal_language_types());
       
   168 
       
   169   foreach ($types as $type) {
       
   170     unset($enabled_types[$type]);
       
   171   }
       
   172 
       
   173   variable_set('language_types', $enabled_types);
       
   174 }
       
   175 
       
   176 /**
       
   177  * Updates the language type configuration.
       
   178  */
       
   179 function language_types_set() {
       
   180   // Ensure that we are getting the defined language negotiation information. An
       
   181   // invocation of module_enable() or module_disable() could outdate the cached
       
   182   // information.
       
   183   drupal_static_reset('language_types_info');
       
   184   drupal_static_reset('language_negotiation_info');
       
   185 
       
   186   // Determine which language types are configurable and which not by checking
       
   187   // whether the 'fixed' key is defined. Non-configurable (fixed) language types
       
   188   // have their language negotiation settings stored there.
       
   189   $defined_providers = language_negotiation_info();
       
   190   foreach (language_types_info() as $type => $info) {
       
   191     if (isset($info['fixed'])) {
       
   192       $language_types[$type] = FALSE;
       
   193       $negotiation = array();
       
   194       foreach ($info['fixed'] as $weight => $id) {
       
   195         if (isset($defined_providers[$id])) {
       
   196           $negotiation[$id] = $weight;
       
   197         }
       
   198       }
       
   199       language_negotiation_set($type, $negotiation);
       
   200     }
       
   201     else {
       
   202       $language_types[$type] = TRUE;
       
   203     }
       
   204   }
       
   205 
       
   206   // Save language types.
       
   207   variable_set('language_types', $language_types);
       
   208 
       
   209   // Ensure that subsequent calls of language_types_configurable() return the
       
   210   // updated language type information.
       
   211   drupal_static_reset('language_types_configurable');
       
   212 }
       
   213 
       
   214 /**
       
   215  * Checks whether a language negotiation provider is enabled for a language type.
       
   216  *
       
   217  * This has two possible behaviors:
       
   218  *  - If $provider_id is given return its ID if enabled, FALSE otherwise.
       
   219  *  - If no ID is passed the first enabled language negotiation provider is
       
   220  *    returned.
       
   221  *
       
   222  * @param $type
       
   223  *   The language negotiation provider type.
       
   224  * @param $provider_id
       
   225  *   The language negotiation provider ID.
       
   226  *
       
   227  * @return
       
   228  *   The provider ID if it is enabled, FALSE otherwise.
       
   229  */
       
   230 function language_negotiation_get($type, $provider_id = NULL) {
       
   231   $negotiation = variable_get("language_negotiation_$type", array());
       
   232 
       
   233   if (empty($negotiation)) {
       
   234     return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE;
       
   235   }
       
   236 
       
   237   if (empty($provider_id)) {
       
   238     return key($negotiation);
       
   239   }
       
   240 
       
   241   if (isset($negotiation[$provider_id])) {
       
   242     return $provider_id;
       
   243   }
       
   244 
       
   245   return FALSE;
       
   246 }
       
   247 
       
   248 /**
       
   249  * Checks if the language negotiation provider is enabled for any language type.
       
   250  *
       
   251  * @param $provider_id
       
   252  *   The language negotiation provider ID.
       
   253  *
       
   254  * @return
       
   255  *   TRUE if there is at least one language type for which the given language
       
   256  *   provider is enabled, FALSE otherwise.
       
   257  */
       
   258 function language_negotiation_get_any($provider_id) {
       
   259   foreach (language_types_configurable() as $type) {
       
   260     if (language_negotiation_get($type, $provider_id)) {
       
   261       return TRUE;
       
   262     }
       
   263   }
       
   264 
       
   265   return FALSE;
       
   266 }
       
   267 
       
   268 /**
       
   269  * Returns the language switch links for the given language.
       
   270  *
       
   271  * @param $type
       
   272  *   The language negotiation type.
       
   273  * @param $path
       
   274  *   The internal path the switch links will be relative to.
       
   275  *
       
   276  * @return
       
   277  *   A keyed array of links ready to be themed.
       
   278  */
       
   279 function language_negotiation_get_switch_links($type, $path) {
       
   280   $links = FALSE;
       
   281   $negotiation = variable_get("language_negotiation_$type", array());
       
   282 
       
   283   // Only get the languages if we have more than one.
       
   284   if (count(language_list()) >= 2) {
       
   285     $language = language_initialize($type);
       
   286   }
       
   287 
       
   288   foreach ($negotiation as $id => $provider) {
       
   289     if (isset($provider['callbacks']['switcher'])) {
       
   290       if (isset($provider['file'])) {
       
   291         require_once DRUPAL_ROOT . '/' . $provider['file'];
       
   292       }
       
   293 
       
   294       $callback = $provider['callbacks']['switcher'];
       
   295       $result = $callback($type, $path);
       
   296 
       
   297       // Add support for WCAG 2.0's Language of Parts to add language identifiers.
       
   298       // http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html
       
   299       foreach ($result as $langcode => $link) {
       
   300         $result[$langcode]['attributes']['xml:lang'] = $langcode;
       
   301       }
       
   302 
       
   303       if (!empty($result)) {
       
   304         // Allow modules to provide translations for specific links.
       
   305         drupal_alter('language_switch_links', $result, $type, $path);
       
   306         $links = (object) array('links' => $result, 'provider' => $id);
       
   307         break;
       
   308       }
       
   309     }
       
   310   }
       
   311 
       
   312   return $links;
       
   313 }
       
   314 
       
   315 /**
       
   316  * Removes any unused language negotiation providers from the configuration.
       
   317  */
       
   318 function language_negotiation_purge() {
       
   319   // Ensure that we are getting the defined language negotiation information. An
       
   320   // invocation of module_enable() or module_disable() could outdate the cached
       
   321   // information.
       
   322   drupal_static_reset('language_negotiation_info');
       
   323   drupal_static_reset('language_types_info');
       
   324 
       
   325   $defined_providers = language_negotiation_info();
       
   326   foreach (language_types_info() as $type => $type_info) {
       
   327     $weight = 0;
       
   328     $negotiation = array();
       
   329     foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) {
       
   330       if (isset($defined_providers[$id])) {
       
   331         $negotiation[$id] = $weight++;
       
   332       }
       
   333     }
       
   334     language_negotiation_set($type, $negotiation);
       
   335   }
       
   336 }
       
   337 
       
   338 /**
       
   339  * Saves a list of language negotiation providers.
       
   340  *
       
   341  * @param $type
       
   342  *   The language negotiation type.
       
   343  * @param $language_providers
       
   344  *   An array of language negotiation provider weights keyed by provider ID.
       
   345  *   @see language_provider_weight()
       
   346  */
       
   347 function language_negotiation_set($type, $language_providers) {
       
   348   // Save only the necessary fields.
       
   349   $provider_fields = array('callbacks', 'file', 'cache');
       
   350 
       
   351   $negotiation = array();
       
   352   $providers_weight = array();
       
   353   $defined_providers = language_negotiation_info();
       
   354   $default_types = language_types_configurable(FALSE);
       
   355 
       
   356   // Initialize the providers weight list.
       
   357   foreach ($language_providers as $id => $provider) {
       
   358     $providers_weight[$id] = language_provider_weight($provider);
       
   359   }
       
   360 
       
   361   // Order providers list by weight.
       
   362   asort($providers_weight);
       
   363 
       
   364   foreach ($providers_weight as $id => $weight) {
       
   365     if (isset($defined_providers[$id])) {
       
   366       $provider = $defined_providers[$id];
       
   367       // If the provider does not express any preference about types, make it
       
   368       // available for any configurable type.
       
   369       $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types);
       
   370       // Check whether the provider is defined and has the right type.
       
   371       if (isset($types[$type])) {
       
   372         $provider_data = array();
       
   373         foreach ($provider_fields as $field) {
       
   374           if (isset($provider[$field])) {
       
   375             $provider_data[$field] = $provider[$field];
       
   376           }
       
   377         }
       
   378         $negotiation[$id] = $provider_data;
       
   379       }
       
   380     }
       
   381   }
       
   382 
       
   383   variable_set("language_negotiation_$type", $negotiation);
       
   384 }
       
   385 
       
   386 /**
       
   387  * Returns all the defined language negotiation providers.
       
   388  *
       
   389  * @return
       
   390  *   An array of language negotiation providers.
       
   391  */
       
   392 function language_negotiation_info() {
       
   393   $language_providers = &drupal_static(__FUNCTION__);
       
   394 
       
   395   if (!isset($language_providers)) {
       
   396     // Collect all the module-defined language negotiation providers.
       
   397     $language_providers = module_invoke_all('language_negotiation_info');
       
   398 
       
   399     // Add the default language negotiation provider.
       
   400     $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array(
       
   401       'callbacks' => array('language' => 'language_from_default'),
       
   402       'weight' => 10,
       
   403       'name' => t('Default'),
       
   404       'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)),
       
   405     );
       
   406 
       
   407     // Let other modules alter the list of language negotiation providers.
       
   408     drupal_alter('language_negotiation_info', $language_providers);
       
   409   }
       
   410 
       
   411   return $language_providers;
       
   412 }
       
   413 
       
   414 /**
       
   415  * Helper function used to cache the language negotiation providers results.
       
   416  *
       
   417  * @param $provider_id
       
   418  *   The language negotiation provider's identifier.
       
   419  * @param $provider
       
   420  *   (optional) An associative array of information about the provider to be
       
   421  *   invoked (see hook_language_negotiation_info() for details). If not passed
       
   422  *   in, it will be loaded through language_negotiation_info().
       
   423  *
       
   424  * @return
       
   425  *   A language object representing the language chosen by the provider.
       
   426  */
       
   427 function language_provider_invoke($provider_id, $provider = NULL) {
       
   428   $results = &drupal_static(__FUNCTION__);
       
   429 
       
   430   if (!isset($results[$provider_id])) {
       
   431     global $user;
       
   432 
       
   433     // Get languages grouped by status and select only the enabled ones.
       
   434     $languages = language_list('enabled');
       
   435     $languages = $languages[1];
       
   436 
       
   437     if (!isset($provider)) {
       
   438       $providers = language_negotiation_info();
       
   439       $provider = $providers[$provider_id];
       
   440     }
       
   441 
       
   442     if (isset($provider['file'])) {
       
   443       require_once DRUPAL_ROOT . '/' . $provider['file'];
       
   444     }
       
   445 
       
   446     // If the language negotiation provider has no cache preference or this is
       
   447     // satisfied we can execute the callback.
       
   448     $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
       
   449     $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE;
       
   450     $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE;
       
   451     $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
       
   452   }
       
   453 
       
   454   // Since objects are resources, we need to return a clone to prevent the
       
   455   // language negotiation provider cache from being unintentionally altered. The
       
   456   // same providers might be used with different language types based on
       
   457   // configuration.
       
   458   return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id];
       
   459 }
       
   460 
       
   461 /**
       
   462  * Returns the passed language negotiation provider weight or a default value.
       
   463  *
       
   464  * @param $provider
       
   465  *   A language negotiation provider data structure.
       
   466  *
       
   467  * @return
       
   468  *   A numeric weight.
       
   469  */
       
   470 function language_provider_weight($provider) {
       
   471   $default = is_numeric($provider) ? $provider : 0;
       
   472   return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default;
       
   473 }
       
   474 
       
   475 /**
       
   476  * Chooses a language based on language negotiation provider settings.
       
   477  *
       
   478  * @param $type
       
   479  *   The language type key to find the language for.
       
   480  *
       
   481  * @return
       
   482  *   The negotiated language object.
       
   483  */
       
   484 function language_initialize($type) {
       
   485   // Execute the language negotiation providers in the order they were set up and return the
       
   486   // first valid language found.
       
   487   $negotiation = variable_get("language_negotiation_$type", array());
       
   488 
       
   489   foreach ($negotiation as $provider_id => $provider) {
       
   490     $language = language_provider_invoke($provider_id, $provider);
       
   491     if ($language) {
       
   492       $language->provider = $provider_id;
       
   493       return $language;
       
   494     }
       
   495   }
       
   496 
       
   497   // If no other language was found use the default one.
       
   498   $language = language_default();
       
   499   $language->provider = LANGUAGE_NEGOTIATION_DEFAULT;
       
   500   return $language;
       
   501 }
       
   502 
       
   503 /**
       
   504  * Returns the default language negotiation provider.
       
   505  *
       
   506  * @return
       
   507  *   The default language code.
       
   508  */
       
   509 function language_from_default() {
       
   510   return language_default()->language;
       
   511 }
       
   512 
       
   513 /**
       
   514  * Splits the given path into prefix and actual path.
       
   515  *
       
   516  * Parse the given path and return the language object identified by the prefix
       
   517  * and the actual path.
       
   518  *
       
   519  * @param $path
       
   520  *   The path to split.
       
   521  * @param $languages
       
   522  *   An array of valid languages.
       
   523  *
       
   524  * @return
       
   525  *   An array composed of:
       
   526  *    - A language object corresponding to the identified prefix on success,
       
   527  *      FALSE otherwise.
       
   528  *    - The path without the prefix on success, the given path otherwise.
       
   529  */
       
   530 function language_url_split_prefix($path, $languages) {
       
   531   $args = empty($path) ? array() : explode('/', $path);
       
   532   $prefix = array_shift($args);
       
   533 
       
   534   // Search prefix within enabled languages.
       
   535   foreach ($languages as $language) {
       
   536     if (!empty($language->prefix) && $language->prefix == $prefix) {
       
   537       // Rebuild $path with the language removed.
       
   538       return array($language, implode('/', $args));
       
   539     }
       
   540   }
       
   541 
       
   542   return array(FALSE, $path);
       
   543 }
       
   544 
       
   545 /**
       
   546  * Returns the possible fallback languages ordered by language weight.
       
   547  *
       
   548  * @param
       
   549  *   (optional) The language type. Defaults to LANGUAGE_TYPE_CONTENT.
       
   550  *
       
   551  * @return
       
   552  *   An array of language codes.
       
   553  */
       
   554 function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) {
       
   555   $fallback_candidates = &drupal_static(__FUNCTION__);
       
   556 
       
   557   if (!isset($fallback_candidates)) {
       
   558     $fallback_candidates = array();
       
   559 
       
   560     // Get languages ordered by weight.
       
   561     // Use array keys to avoid duplicated entries.
       
   562     foreach (language_list('weight') as $languages) {
       
   563       foreach ($languages as $language) {
       
   564         $fallback_candidates[$language->language] = NULL;
       
   565       }
       
   566     }
       
   567 
       
   568     $fallback_candidates = array_keys($fallback_candidates);
       
   569     $fallback_candidates[] = LANGUAGE_NONE;
       
   570 
       
   571     // Let other modules hook in and add/change candidates.
       
   572     drupal_alter('language_fallback_candidates', $fallback_candidates);
       
   573   }
       
   574 
       
   575   return $fallback_candidates;
       
   576 }
       
   577 
       
   578 /**
       
   579  * @} End of "language_negotiation"
       
   580  */