xref: /web-php/include/langchooser.inc (revision 00342c40)
1<?php
2
3/*
4
5 This script tries to guess what language to use for
6 language dependent operations (lookup, search, books
7 page display, etc.), considering all possible factors
8 affecting language selection.
9
10 After this script run, $LANG is set to the preferred
11 language, or is the empty string, if no manual is
12 available on the current mirror site.
13
14 $EXPL_LANG will also be set to the explicitly provided
15 language, or will not exist if there are only implications
16 on the preferred language.
17
18 $UA_LANGS will contain the user agent language settings
19 parsed as an array. The language names are corrected for
20 php.net usage in this array. This is just to present to
21 the user in case he would like to get information on the
22 parsed language information (see /my.php).
23
24 The $_SERVER['STRIPPED_URI'] var is also set to the
25 stripped request URI (in case of a shortcut, the
26 language is stipped, so the shortcut handling code
27 is not bothered with it).
28
29*/
30
31// Default STRIPPED_URI
32$_SERVER['STRIPPED_URI'] = htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8');
33
34// The code is encapsulated in a function,
35// so the variable namespace is not polluted
36list($LANG, $EXPL_LANG, $UA_LANGS) = language_choose_code();
37
38// Compatibility
39if ($EXPL_LANG == '') { unset($EXPL_LANG); }
40
41function language_choose_code()
42{
43    // Contains all the languages picked up by the
44    // process in priority order (without repeating codes)
45    $languages = [];
46
47    // Default values for languages
48    $explicitly_specified = ''; $selected = '';
49
50    // Specified for the request (GET/POST parameter)
51    if (!empty($_REQUEST['lang']) && is_string($_REQUEST['lang'])) {
52        $explicitly_specified = language_add(htmlspecialchars($_REQUEST['lang'], ENT_QUOTES, 'UTF-8'), $languages);
53
54        // Set the language in a cookie for a year
55        mirror_setcookie("LAST_LANG", $explicitly_specified, 60 * 60 * 24 * 365);
56    }
57
58    // Specified in a shortcut URL (eg. /en/echo or /pt_br/echo)
59    if (preg_match("!^/(\\w{2}(_\\w{2})?)/!", htmlspecialchars($_SERVER['REQUEST_URI'],ENT_QUOTES, 'UTF-8'), $flang)) {
60
61        // Put language into preference list
62        $rlang = language_add($flang[1], $languages);
63
64        // Set explicity specified language
65        if (empty($explicitly_specified)) {
66            $explicitly_specified = $rlang;
67        }
68
69        // Drop out langauge specification from URL, as this is already handled
70        $_SERVER['STRIPPED_URI'] = preg_replace(
71            "!^/$flang[1]/!", "/", htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8'),
72        );
73
74    }
75
76    // Specified in a manual URL (eg. manual/en/ or manual/pt_br/)
77    if (preg_match("!^/manual/(\\w{2}(_\\w{2})?)(/|$)!", htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8'), $flang)) {
78
79        $flang = language_add($flang[1], $languages);
80
81        // Set explicity specified language
82        if (empty($explicitly_specified)) {
83            $explicitly_specified = $flang;
84        }
85
86        // Set the language in a cookie for a year
87        mirror_setcookie("LAST_LANG", $flang, 60 * 60 * 24 * 365);
88    }
89
90    // Honor the users own language setting (if available)
91    if (myphpnet_language()) {
92        language_add(myphpnet_language(), $languages);
93    }
94
95    // Specified by the user via the browser's Accept Language setting
96    // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
97    $browser_langs = []; $parsed_langs = [];
98
99    // Check if we have $_SERVER['HTTP_ACCEPT_LANGUAGE'] set and
100    // it no longer breaks if you only have one language set :)
101    if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
102        $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
103
104        // Go through all language preference specs
105        foreach ($browser_accept as $value) {
106            // The language part is either a code or a code with a quality
107            // We cannot do anything with a * code, so it is skipped
108            // If the quality is missing, it is assumed to be 1 according to the RFC
109            if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($value), $found)) {
110                $quality = (isset($found[3]) ? (float) $found[3] : 1.0);
111                $browser_langs[] = [$found[1], $quality];
112            }
113            unset($found);
114        }
115    }
116
117    // Order the codes by quality
118    usort($browser_langs, "language_accept_order");
119
120    // For all languages found in the accept-language
121    foreach ($browser_langs as $langdata) {
122
123        // Translation table for accept-language codes and phpdoc codes
124        switch ($langdata[0]) {
125            case "pt-br":
126                $langdata[0] = 'pt_br';
127                break;
128            case "zh-cn":
129                $langdata[0] = 'zh';
130                break;
131            case "zh-hk":
132                $langdata[0] = 'hk';
133                break;
134            case "zh-tw":
135                $langdata[0] = 'tw';
136                break;
137        }
138
139        // We do not support flavors of languages (except the ones above)
140        // This is not in conformance to the RFC, but it here for user
141        // convinience reasons
142        if (preg_match("!^(.+)-!", $langdata[0], $match)) {
143            $langdata[0] = $match[1];
144        }
145
146        // Add language to priority order
147        $parsed_langs[] = language_add($langdata[0], $languages);
148    }
149
150    // Language preferred by this mirror site
151    language_add(default_language(), $languages);
152
153    // Last default language is English
154    language_add("en", $languages);
155
156    // Try to find out what language is available on this mirror.
157    // As most of the language dependant operations involve manual
158    // page display (lookup, search, shortcuts), we will check for
159    // the index file of manuals.
160/*
161    foreach ($languages as $language) {
162        if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/manual/$language/index.php")) {
163            $selected = $language;
164            break;
165        }
166    }
167*/
168    $selected = $languages[0];
169
170    // Return with all found data
171    return [$selected, $explicitly_specified, $parsed_langs];
172}
173
174// Add a language to the possible languages' list
175function language_add($langcode, &$langs)
176{
177    global $LANGUAGES, $INACTIVE_ONLINE_LANGUAGES;
178
179    // Make language code lowercase, html encode special chars and remove slashes
180    $langcode = strtolower(htmlspecialchars($langcode));
181
182    // The Brazilian Portuguese code needs special attention
183    if ($langcode == 'pt_br') { $langcode = 'pt_BR'; }
184
185    // Append language code in priority order if it is not
186    // there already and supported by the PHP site. Try to
187    // lower number of file_exists() calls to the minumum...
188    if (!in_array($langcode, $langs, false) && isset($LANGUAGES[$langcode])
189        && !isset($INACTIVE_ONLINE_LANGUAGES[$langcode])) {
190        $langs[] = $langcode;
191    }
192
193    // Return with language code
194    return $langcode;
195}
196
197// Order the array of compiled
198// accept-language codes by quality value
199function language_accept_order($a, $b)
200{
201    if ($a[1] == $b[1]) { return 0; }
202    return ($a[1] > $b[1]) ? -1 : 1;
203}
204