1 | <?php |
||||
2 | |||||
3 | namespace Pmochine\LaravelTongue\Localization; |
||||
4 | |||||
5 | use Illuminate\Http\Request; |
||||
6 | use Locale; |
||||
0 ignored issues
–
show
|
|||||
7 | |||||
8 | /** |
||||
9 | * Class was originally written by Mcamara. |
||||
10 | * Visit the package! |
||||
11 | * https://github.com/mcamara/laravel-localization. |
||||
12 | */ |
||||
13 | class TongueDetector |
||||
14 | { |
||||
15 | /** |
||||
16 | * @var string |
||||
17 | */ |
||||
18 | private $fallbackLocale; |
||||
19 | /** |
||||
20 | * @var array |
||||
21 | */ |
||||
22 | private $supportedLanguages; |
||||
23 | /** |
||||
24 | * @var Request |
||||
25 | */ |
||||
26 | private $request; |
||||
27 | /** |
||||
28 | * @var bool |
||||
29 | */ |
||||
30 | private $use_intl = false; |
||||
31 | |||||
32 | /** |
||||
33 | * @param string $fallbackLocale |
||||
34 | * @param array $supportedLanguages |
||||
35 | * @param Request $request |
||||
36 | */ |
||||
37 | public function __construct($fallbackLocale, $supportedLanguages, Request $request) |
||||
38 | { |
||||
39 | $this->fallbackLocale = $fallbackLocale; |
||||
40 | |||||
41 | $this->canonicalize($supportedLanguages); |
||||
42 | |||||
43 | $this->request = $request; |
||||
44 | } |
||||
45 | |||||
46 | /** |
||||
47 | * Negotiates language with the user's browser through the Accept-Language |
||||
48 | * HTTP header or the user's host address. Language codes are generally in |
||||
49 | * the form "ll" for a language spoken in only one country, or "ll-CC" for a |
||||
50 | * language spoken in a particular country. For example, U.S. English is |
||||
51 | * "en-US", while British English is "en-UK". Portuguese as spoken in |
||||
52 | * Portugal is "pt-PT", while Brazilian Portuguese is "pt-BR". |
||||
53 | * |
||||
54 | * This function is based on negotiateLanguage from Pear HTTP2 |
||||
55 | * http://pear.php.net/package/HTTP2/ |
||||
56 | * |
||||
57 | * Quality factors in the Accept-Language: header are supported, e.g.: |
||||
58 | * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8 |
||||
59 | * |
||||
60 | * @return string The negotiated language result or app.locale. |
||||
61 | */ |
||||
62 | public function negotiateLanguage() |
||||
63 | { |
||||
64 | $matches = $this->getMatchesFromAcceptedLanguages(); |
||||
65 | |||||
66 | foreach ($matches as $key => $q) { |
||||
67 | if (! empty($this->supportedLanguages[$key])) { |
||||
68 | return $key; |
||||
69 | } |
||||
70 | |||||
71 | if ($this->use_intl) { |
||||
72 | $key = Locale::canonicalize($key); |
||||
73 | } |
||||
74 | |||||
75 | // Search for acceptable locale by 'regional' => 'af_ZA' or 'lang' => 'af-ZA' match. |
||||
76 | foreach ($this->supportedLanguages as $key_supported => $locale) { |
||||
77 | if ((isset($locale['regional']) && $locale['regional'] == $key) |
||||
78 | || (isset($locale['lang']) && $locale['lang'] == $key) |
||||
79 | ) { |
||||
80 | return $key_supported; |
||||
81 | } |
||||
82 | } |
||||
83 | } |
||||
84 | |||||
85 | // If any (i.e. "*") is acceptable, return the first supported format |
||||
86 | if (isset($matches['*'])) { |
||||
87 | reset($this->supportedLanguages); |
||||
88 | |||||
89 | return key($this->supportedLanguages); |
||||
90 | } |
||||
91 | |||||
92 | if ($this->use_intl && ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { |
||||
93 | $http_accept_language = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']); |
||||
94 | |||||
95 | if (! empty($this->supportedLanguages[$http_accept_language])) { |
||||
96 | return $http_accept_language; |
||||
97 | } |
||||
98 | } |
||||
99 | |||||
100 | if ($this->request->server('REMOTE_HOST')) { |
||||
101 | $remote_host = explode('.', $this->request->server('REMOTE_HOST')); |
||||
0 ignored issues
–
show
It seems like
$this->request->server('REMOTE_HOST') can also be of type array and null ; however, parameter $string of explode() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
102 | $lang = strtolower(end($remote_host)); |
||||
103 | |||||
104 | if (! empty($this->supportedLanguages[$lang])) { |
||||
105 | return $lang; |
||||
106 | } |
||||
107 | } |
||||
108 | |||||
109 | return $this->fallbackLocale; |
||||
110 | } |
||||
111 | |||||
112 | /** |
||||
113 | * Return all the accepted languages from the browser. |
||||
114 | * |
||||
115 | * @return array Matches from the header field Accept-Languages |
||||
116 | */ |
||||
117 | private function getMatchesFromAcceptedLanguages() |
||||
118 | { |
||||
119 | $matches = []; |
||||
120 | |||||
121 | if ($acceptLanguages = $this->request->header('Accept-Language')) { |
||||
122 | $acceptLanguages = explode(',', $acceptLanguages); |
||||
0 ignored issues
–
show
It seems like
$acceptLanguages can also be of type array ; however, parameter $string of explode() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
123 | $generic_matches = []; |
||||
124 | |||||
125 | foreach ($acceptLanguages as $option) { |
||||
126 | $option = array_map('trim', explode(';', $option)); |
||||
127 | $l = $option[0]; |
||||
128 | if (isset($option[1])) { |
||||
129 | $q = (float) str_replace('q=', '', $option[1]); |
||||
130 | } else { |
||||
131 | $q = null; |
||||
132 | // Assign default low weight for generic values |
||||
133 | if ($l == '*/*') { |
||||
134 | $q = 0.01; |
||||
135 | } elseif (substr($l, -1) == '*') { |
||||
136 | $q = 0.02; |
||||
137 | } |
||||
138 | } |
||||
139 | // Unweighted values, get high weight by their position in the |
||||
140 | // list |
||||
141 | $q = isset($q) ? $q : 1000 - count($matches); |
||||
142 | $matches[$l] = $q; |
||||
143 | //If for some reason the Accept-Language header only sends language with country |
||||
144 | //we should make the language without country an accepted option, with a value |
||||
145 | //less than it's parent. |
||||
146 | $l_ops = explode('-', $l); |
||||
147 | array_pop($l_ops); |
||||
148 | while (! empty($l_ops)) { |
||||
149 | //The new generic option needs to be slightly less important than it's base |
||||
150 | $q -= 0.001; |
||||
151 | $op = implode('-', $l_ops); |
||||
152 | if (empty($generic_matches[$op]) || $generic_matches[$op] > $q) { |
||||
153 | $generic_matches[$op] = $q; |
||||
154 | } |
||||
155 | array_pop($l_ops); |
||||
156 | } |
||||
157 | } |
||||
158 | $matches = array_merge($generic_matches, $matches); |
||||
159 | arsort($matches, SORT_NUMERIC); |
||||
160 | } |
||||
161 | |||||
162 | return $matches; |
||||
163 | } |
||||
164 | |||||
165 | /** |
||||
166 | * Set the supportedLanguages using |
||||
167 | * http://php.net/manual/de/locale.canonicalize.php. |
||||
168 | */ |
||||
169 | protected function canonicalize($supportedLanguages) |
||||
170 | { |
||||
171 | if (class_exists('Locale')) { |
||||
172 | $this->use_intl = true; |
||||
173 | |||||
174 | foreach ($supportedLanguages as $key => $language) { |
||||
175 | if (isset($language['lang'])) { |
||||
176 | $language['lang'] = Locale::canonicalize($language['lang']); |
||||
177 | } else { |
||||
178 | $language['lang'] = Locale::canonicalize($key); |
||||
179 | } |
||||
180 | |||||
181 | if (isset($language['regional'])) { |
||||
182 | $language['regional'] = Locale::canonicalize($language['regional']); |
||||
183 | } |
||||
184 | |||||
185 | $this->supportedLanguages[$key] = $language; |
||||
186 | } |
||||
187 | } else { |
||||
188 | $this->supportedLanguages = $supportedLanguages; |
||||
189 | } |
||||
190 | } |
||||
191 | } |
||||
192 |
Let?s assume that you have a directory layout like this:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: