Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Component / Utility / UserAgent.php
1 <?php
2
3 namespace Drupal\Component\Utility;
4
5 /**
6  * Provides user agent related utility functions.
7  *
8  * @ingroup utility
9  */
10 class UserAgent {
11
12   /**
13    * Identifies user agent language from the Accept-language HTTP header.
14    *
15    * The algorithm works as follows:
16    * - map user agent language codes to available language codes.
17    * - order all user agent language codes by qvalue from high to low.
18    * - add generic user agent language codes if they aren't already specified
19    *   but with a slightly lower qvalue.
20    * - find the most specific available language code with the highest qvalue.
21    * - if 2 or more languages are having the same qvalue, respect the order of
22    *   them inside the $languages array.
23    *
24    * We perform user agent accept-language parsing only if page cache is
25    * disabled, otherwise we would cache a user-specific preference.
26    *
27    * @param string $http_accept_language
28    *   The value of the "Accept-Language" HTTP header.
29    * @param array $langcodes
30    *   An array of available language codes to pick from.
31    * @param array $mappings
32    *   (optional) Custom mappings to support user agents that are sending non
33    *   standard language codes. No mapping is assumed by default.
34    *
35    * @return string
36    *   The selected language code or FALSE if no valid language can be
37    *   identified.
38    */
39   public static function getBestMatchingLangcode($http_accept_language, $langcodes, $mappings = []) {
40     // The Accept-Language header contains information about the language
41     // preferences configured in the user's user agent / operating system.
42     // RFC 2616 (section 14.4) defines the Accept-Language header as follows:
43     //   Accept-Language = "Accept-Language" ":"
44     //                  1#( language-range [ ";" "q" "=" qvalue ] )
45     //   language-range  = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
46     // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
47     $ua_langcodes = [];
48     if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($http_accept_language), $matches, PREG_SET_ORDER)) {
49       foreach ($matches as $match) {
50         if ($mappings) {
51           $langcode = strtolower($match[1]);
52           foreach ($mappings as $ua_langcode => $standard_langcode) {
53             if ($langcode == $ua_langcode) {
54               $match[1] = $standard_langcode;
55             }
56           }
57         }
58         // We can safely use strtolower() here, tags are ASCII.
59         // RFC2616 mandates that the decimal part is no more than three digits,
60         // so we multiply the qvalue by 1000 to avoid floating point
61         // comparisons.
62         $langcode = strtolower($match[1]);
63         $qvalue = isset($match[2]) ? (float) $match[2] : 1;
64         // Take the highest qvalue for this langcode. Although the request
65         // supposedly contains unique langcodes, our mapping possibly resolves
66         // to the same langcode for different qvalues. Keep the highest.
67         $ua_langcodes[$langcode] = max(
68           (int) ($qvalue * 1000),
69           (isset($ua_langcodes[$langcode]) ? $ua_langcodes[$langcode] : 0)
70         );
71       }
72     }
73
74     // We should take pristine values from the HTTP headers, but Internet
75     // Explorer from version 7 sends only specific language tags (eg. fr-CA)
76     // without the corresponding generic tag (fr) unless explicitly configured.
77     // In that case, we assume that the lowest value of the specific tags is the
78     // value of the generic language to be as close to the HTTP 1.1 spec as
79     // possible.
80     // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and
81     // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx
82     asort($ua_langcodes);
83     foreach ($ua_langcodes as $langcode => $qvalue) {
84       // For Chinese languages the generic tag is either zh-hans or zh-hant, so
85       // we need to handle this separately, we can not split $langcode on the
86       // first occurrence of '-' otherwise we get a non-existing language zh.
87       // All other languages use a langcode without a '-', so we can safely
88       // split on the first occurrence of it.
89       if (strlen($langcode) > 7 && (substr($langcode, 0, 7) == 'zh-hant' || substr($langcode, 0, 7) == 'zh-hans')) {
90         $generic_tag = substr($langcode, 0, 7);
91       }
92       else {
93         $generic_tag = strtok($langcode, '-');
94       }
95       if (!empty($generic_tag) && !isset($ua_langcodes[$generic_tag])) {
96         // Add the generic langcode, but make sure it has a lower qvalue as the
97         // more specific one, so the more specific one gets selected if it's
98         // defined by both the user agent and us.
99         $ua_langcodes[$generic_tag] = $qvalue - 0.1;
100       }
101     }
102
103     // Find the added language with the greatest qvalue, following the rules
104     // of RFC 2616 (section 14.4). If several languages have the same qvalue,
105     // prefer the one with the greatest weight.
106     $best_match_langcode = FALSE;
107     $max_qvalue = 0;
108     foreach ($langcodes as $langcode_case_sensitive) {
109       // Language tags are case insensitive (RFC2616, sec 3.10).
110       $langcode = strtolower($langcode_case_sensitive);
111
112       // If nothing matches below, the default qvalue is the one of the wildcard
113       // language, if set, or is 0 (which will never match).
114       $qvalue = isset($ua_langcodes['*']) ? $ua_langcodes['*'] : 0;
115
116       // Find the longest possible prefix of the user agent supplied language
117       // ('the language-range') that matches this site language ('the language
118       // tag').
119       $prefix = $langcode;
120       do {
121         if (isset($ua_langcodes[$prefix])) {
122           $qvalue = $ua_langcodes[$prefix];
123           break;
124         }
125       } while ($prefix = substr($prefix, 0, strrpos($prefix, '-')));
126
127       // Find the best match.
128       if ($qvalue > $max_qvalue) {
129         $best_match_langcode = $langcode_case_sensitive;
130         $max_qvalue = $qvalue;
131       }
132     }
133
134     return $best_match_langcode;
135   }
136
137 }